feat(player): start muted, unmute via button (autoplay-friendly)
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 <noreply@anthropic.com>
This commit is contained in:
parent
9d0cb7f26e
commit
4d14f3946b
1 changed files with 42 additions and 1 deletions
|
|
@ -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 }) {
|
|||
</View>
|
||||
)}
|
||||
|
||||
{/* 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 && (
|
||||
<Pressable onPress={toggleMute} style={styles.unmutePill} hitSlop={12}>
|
||||
<Text style={styles.unmutePillText}>🔇 Tap for sound</Text>
|
||||
</Pressable>
|
||||
)}
|
||||
|
||||
{/* Controls overlay — pointerEvents box-none żeby gesture overlay pod spodem
|
||||
dalej dostawał taps poza interactive elements (back, play, scrubber). */}
|
||||
<Animated.View
|
||||
|
|
@ -654,6 +673,9 @@ function NativeVideoPlayer({ params }: { params: RouteParams }) {
|
|||
) : (
|
||||
<View style={{ flex: 1 }} />
|
||||
)}
|
||||
<Pressable onPress={toggleMute} hitSlop={16} style={styles.iconBtn}>
|
||||
<Text style={styles.iconText}>{muted ? '🔇' : '🔊'}</Text>
|
||||
</Pressable>
|
||||
<Pressable onPress={toggleFullscreen} hitSlop={16} style={styles.iconBtn}>
|
||||
<Text style={styles.iconText}>{isLandscape ? '⤬' : '⛶'}</Text>
|
||||
</Pressable>
|
||||
|
|
@ -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',
|
||||
},
|
||||
});
|
||||
|
|
|
|||
Loading…
Add table
Reference in a new issue