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) => {
|
const player = useVideoPlayer(source, (p) => {
|
||||||
p.loop = false;
|
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();
|
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 statusEvent = useEvent(player, 'statusChange', { status: player.status });
|
||||||
const status = statusEvent?.status ?? player.status;
|
const status = statusEvent?.status ?? player.status;
|
||||||
|
|
@ -637,6 +648,14 @@ function NativeVideoPlayer({ params }: { params: RouteParams }) {
|
||||||
</View>
|
</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
|
{/* Controls overlay — pointerEvents box-none żeby gesture overlay pod spodem
|
||||||
dalej dostawał taps poza interactive elements (back, play, scrubber). */}
|
dalej dostawał taps poza interactive elements (back, play, scrubber). */}
|
||||||
<Animated.View
|
<Animated.View
|
||||||
|
|
@ -654,6 +673,9 @@ function NativeVideoPlayer({ params }: { params: RouteParams }) {
|
||||||
) : (
|
) : (
|
||||||
<View style={{ flex: 1 }} />
|
<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}>
|
<Pressable onPress={toggleFullscreen} hitSlop={16} style={styles.iconBtn}>
|
||||||
<Text style={styles.iconText}>{isLandscape ? '⤬' : '⛶'}</Text>
|
<Text style={styles.iconText}>{isLandscape ? '⤬' : '⛶'}</Text>
|
||||||
</Pressable>
|
</Pressable>
|
||||||
|
|
@ -1053,7 +1075,12 @@ const INJECTED_JS = `
|
||||||
// i ma valid src, próbujemy odpalić.
|
// i ma valid src, próbujemy odpalić.
|
||||||
if (el.tagName === 'VIDEO' && el.paused && (el.src || el.currentSrc)) {
|
if (el.tagName === 'VIDEO' && el.paused && (el.src || el.currentSrc)) {
|
||||||
try {
|
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();
|
const p = el.play();
|
||||||
if (p && p.catch) p.catch(function(){});
|
if (p && p.catch) p.catch(function(){});
|
||||||
} catch (e) {}
|
} catch (e) {}
|
||||||
|
|
@ -1614,4 +1641,18 @@ const styles = StyleSheet.create({
|
||||||
borderRadius: 14,
|
borderRadius: 14,
|
||||||
overflow: 'hidden',
|
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