From 4d14f3946ba1fb0058621d33821aef159f7304a5 Mon Sep 17 00:00:00 2001 From: jtrzupek Date: Sun, 7 Jun 2026 14:03:52 +0200 Subject: [PATCH] feat(player): start muted, unmute via button (autoplay-friendly) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Scenes/movies now start with sound OFF; user enables audio via a control (UX request). NativeVideoPlayer: useVideoPlayer starts muted=true + speaker toggle in top controls + always-visible "Tap for sound" pill while muted. WebView path: injected autoplay sets muted=true (also makes muted autoplay reliable per browser policy → faster CDN extraction); host player controls handle unmute when the WebView is the actual surface. Verified on emulator against the live runtime-1.1 OTA bundle: video starts muted (pill shown), tap unmutes (pill clears). Co-Authored-By: Claude Opus 4.8 --- mobile/src/screens/PlayerScreen.tsx | 43 ++++++++++++++++++++++++++++- 1 file changed, 42 insertions(+), 1 deletion(-) diff --git a/mobile/src/screens/PlayerScreen.tsx b/mobile/src/screens/PlayerScreen.tsx index e8cdb2e..0cd53cc 100644 --- a/mobile/src/screens/PlayerScreen.tsx +++ b/mobile/src/screens/PlayerScreen.tsx @@ -161,8 +161,19 @@ function NativeVideoPlayer({ params }: { params: RouteParams }) { const player = useVideoPlayer(source, (p) => { p.loop = false; + // Start wyciszony — dźwięk dopiero po tapnięciu przycisku (UX request 2026-06-07). + // Dodatkowo: muted autoplay nie wymaga audio-focus, więc nie ucisza muzyki usera + // przy podglądzie i nie zaskakuje głośnym startem. + p.muted = true; p.play(); }); + const [muted, setMuted] = React.useState(true); + const toggleMute = React.useCallback(() => { + const next = !player.muted; + player.muted = next; + setMuted(next); + setControlsVisible(true); + }, [player]); const statusEvent = useEvent(player, 'statusChange', { status: player.status }); const status = statusEvent?.status ?? player.status; @@ -637,6 +648,14 @@ function NativeVideoPlayer({ params }: { params: RouteParams }) { )} + {/* Unmute pill — zawsze widoczny gdy wyciszone (poza fade controls), bo start jest + muted i user musi wiedzieć jak włączyć dźwięk. Tap → unmute. */} + {muted && ( + + 🔇 Tap for sound + + )} + {/* Controls overlay — pointerEvents box-none żeby gesture overlay pod spodem dalej dostawał taps poza interactive elements (back, play, scrubber). */} )} + + {muted ? '🔇' : '🔊'} + {isLandscape ? '⤬' : '⛶'} @@ -1053,7 +1075,12 @@ const INJECTED_JS = ` // i ma valid src, próbujemy odpalić. if (el.tagName === 'VIDEO' && el.paused && (el.src || el.currentSrc)) { try { - el.muted = false; + // Start muted (UX request 2026-06-07: dźwięk dopiero po geście usera). + // Bonus: muted autoplay jest dozwolony przez politykę przeglądarki, więc + // video faktycznie rusza bez gestu → szybsza ekstrakcja CDN URL (potem + // nav.replace na NativeVideoPlayer). W odsłoniętym WebView dźwięk włącza + // user przez własne kontrolki playera hostera. + el.muted = true; const p = el.play(); if (p && p.catch) p.catch(function(){}); } catch (e) {} @@ -1614,4 +1641,18 @@ const styles = StyleSheet.create({ borderRadius: 14, overflow: 'hidden', }, + unmutePill: { + position: 'absolute', + bottom: 96, + alignSelf: 'center', + backgroundColor: 'rgba(0,0,0,0.78)', + paddingVertical: 8, + paddingHorizontal: 16, + borderRadius: 18, + }, + unmutePillText: { + color: theme.fg, + fontSize: 14, + fontWeight: '700', + }, });