[Video] Remember mute state while scrolling (#5331)
parent
791bc7afbe
commit
843f9925f5
|
@ -68,7 +68,7 @@
|
||||||
"@fortawesome/free-regular-svg-icons": "^6.1.1",
|
"@fortawesome/free-regular-svg-icons": "^6.1.1",
|
||||||
"@fortawesome/free-solid-svg-icons": "^6.1.1",
|
"@fortawesome/free-solid-svg-icons": "^6.1.1",
|
||||||
"@fortawesome/react-native-fontawesome": "^0.3.2",
|
"@fortawesome/react-native-fontawesome": "^0.3.2",
|
||||||
"@haileyok/bluesky-video": "0.1.2",
|
"@haileyok/bluesky-video": "0.1.4",
|
||||||
"@lingui/react": "^4.5.0",
|
"@lingui/react": "^4.5.0",
|
||||||
"@mattermost/react-native-paste-input": "^0.7.1",
|
"@mattermost/react-native-paste-input": "^0.7.1",
|
||||||
"@miblanchard/react-native-slider": "^2.3.1",
|
"@miblanchard/react-native-slider": "^2.3.1",
|
||||||
|
|
|
@ -52,6 +52,7 @@ import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed'
|
||||||
import {Provider as StarterPackProvider} from '#/state/shell/starter-pack'
|
import {Provider as StarterPackProvider} from '#/state/shell/starter-pack'
|
||||||
import {Provider as HiddenRepliesProvider} from '#/state/threadgate-hidden-replies'
|
import {Provider as HiddenRepliesProvider} from '#/state/threadgate-hidden-replies'
|
||||||
import {TestCtrls} from '#/view/com/testing/TestCtrls'
|
import {TestCtrls} from '#/view/com/testing/TestCtrls'
|
||||||
|
import {Provider as VideoVolumeProvider} from '#/view/com/util/post-embeds/VideoVolumeContext'
|
||||||
import * as Toast from '#/view/com/util/Toast'
|
import * as Toast from '#/view/com/util/Toast'
|
||||||
import {Shell} from '#/view/shell'
|
import {Shell} from '#/view/shell'
|
||||||
import {ThemeProvider as Alf} from '#/alf'
|
import {ThemeProvider as Alf} from '#/alf'
|
||||||
|
@ -109,40 +110,43 @@ function InnerApp() {
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<Splash isReady={isReady && hasCheckedReferrer}>
|
<Splash isReady={isReady && hasCheckedReferrer}>
|
||||||
<RootSiblingParent>
|
<RootSiblingParent>
|
||||||
<React.Fragment
|
<VideoVolumeProvider>
|
||||||
// Resets the entire tree below when it changes:
|
<React.Fragment
|
||||||
key={currentAccount?.did}>
|
// Resets the entire tree below when it changes:
|
||||||
<QueryProvider currentDid={currentAccount?.did}>
|
key={currentAccount?.did}>
|
||||||
<StatsigProvider>
|
<QueryProvider currentDid={currentAccount?.did}>
|
||||||
<MessagesProvider>
|
<StatsigProvider>
|
||||||
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
|
<MessagesProvider>
|
||||||
<LabelDefsProvider>
|
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
|
||||||
<ModerationOptsProvider>
|
<LabelDefsProvider>
|
||||||
<LoggedOutViewProvider>
|
<ModerationOptsProvider>
|
||||||
<SelectedFeedProvider>
|
<LoggedOutViewProvider>
|
||||||
<HiddenRepliesProvider>
|
<SelectedFeedProvider>
|
||||||
<UnreadNotifsProvider>
|
<HiddenRepliesProvider>
|
||||||
<BackgroundNotificationPreferencesProvider>
|
<UnreadNotifsProvider>
|
||||||
<MutedThreadsProvider>
|
<BackgroundNotificationPreferencesProvider>
|
||||||
<ProgressGuideProvider>
|
<MutedThreadsProvider>
|
||||||
<GestureHandlerRootView style={s.h100pct}>
|
<ProgressGuideProvider>
|
||||||
<TestCtrls />
|
<GestureHandlerRootView
|
||||||
<Shell />
|
style={s.h100pct}>
|
||||||
<NuxDialogs />
|
<TestCtrls />
|
||||||
</GestureHandlerRootView>
|
<Shell />
|
||||||
</ProgressGuideProvider>
|
<NuxDialogs />
|
||||||
</MutedThreadsProvider>
|
</GestureHandlerRootView>
|
||||||
</BackgroundNotificationPreferencesProvider>
|
</ProgressGuideProvider>
|
||||||
</UnreadNotifsProvider>
|
</MutedThreadsProvider>
|
||||||
</HiddenRepliesProvider>
|
</BackgroundNotificationPreferencesProvider>
|
||||||
</SelectedFeedProvider>
|
</UnreadNotifsProvider>
|
||||||
</LoggedOutViewProvider>
|
</HiddenRepliesProvider>
|
||||||
</ModerationOptsProvider>
|
</SelectedFeedProvider>
|
||||||
</LabelDefsProvider>
|
</LoggedOutViewProvider>
|
||||||
</MessagesProvider>
|
</ModerationOptsProvider>
|
||||||
</StatsigProvider>
|
</LabelDefsProvider>
|
||||||
</QueryProvider>
|
</MessagesProvider>
|
||||||
</React.Fragment>
|
</StatsigProvider>
|
||||||
|
</QueryProvider>
|
||||||
|
</React.Fragment>
|
||||||
|
</VideoVolumeProvider>
|
||||||
</RootSiblingParent>
|
</RootSiblingParent>
|
||||||
</Splash>
|
</Splash>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
|
|
|
@ -41,6 +41,7 @@ import {Provider as SelectedFeedProvider} from '#/state/shell/selected-feed'
|
||||||
import {Provider as StarterPackProvider} from '#/state/shell/starter-pack'
|
import {Provider as StarterPackProvider} from '#/state/shell/starter-pack'
|
||||||
import {Provider as HiddenRepliesProvider} from '#/state/threadgate-hidden-replies'
|
import {Provider as HiddenRepliesProvider} from '#/state/threadgate-hidden-replies'
|
||||||
import {Provider as ActiveVideoProvider} from '#/view/com/util/post-embeds/ActiveVideoWebContext'
|
import {Provider as ActiveVideoProvider} from '#/view/com/util/post-embeds/ActiveVideoWebContext'
|
||||||
|
import {Provider as VideoVolumeProvider} from '#/view/com/util/post-embeds/VideoVolumeContext'
|
||||||
import * as Toast from '#/view/com/util/Toast'
|
import * as Toast from '#/view/com/util/Toast'
|
||||||
import {ToastContainer} from '#/view/com/util/Toast.web'
|
import {ToastContainer} from '#/view/com/util/Toast.web'
|
||||||
import {Shell} from '#/view/shell/index'
|
import {Shell} from '#/view/shell/index'
|
||||||
|
@ -95,42 +96,44 @@ function InnerApp() {
|
||||||
<Alf theme={theme}>
|
<Alf theme={theme}>
|
||||||
<ThemeProvider theme={theme}>
|
<ThemeProvider theme={theme}>
|
||||||
<RootSiblingParent>
|
<RootSiblingParent>
|
||||||
<ActiveVideoProvider>
|
<VideoVolumeProvider>
|
||||||
<React.Fragment
|
<ActiveVideoProvider>
|
||||||
// Resets the entire tree below when it changes:
|
<React.Fragment
|
||||||
key={currentAccount?.did}>
|
// Resets the entire tree below when it changes:
|
||||||
<QueryProvider currentDid={currentAccount?.did}>
|
key={currentAccount?.did}>
|
||||||
<StatsigProvider>
|
<QueryProvider currentDid={currentAccount?.did}>
|
||||||
<MessagesProvider>
|
<StatsigProvider>
|
||||||
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
|
<MessagesProvider>
|
||||||
<LabelDefsProvider>
|
{/* LabelDefsProvider MUST come before ModerationOptsProvider */}
|
||||||
<ModerationOptsProvider>
|
<LabelDefsProvider>
|
||||||
<LoggedOutViewProvider>
|
<ModerationOptsProvider>
|
||||||
<SelectedFeedProvider>
|
<LoggedOutViewProvider>
|
||||||
<HiddenRepliesProvider>
|
<SelectedFeedProvider>
|
||||||
<UnreadNotifsProvider>
|
<HiddenRepliesProvider>
|
||||||
<BackgroundNotificationPreferencesProvider>
|
<UnreadNotifsProvider>
|
||||||
<MutedThreadsProvider>
|
<BackgroundNotificationPreferencesProvider>
|
||||||
<SafeAreaProvider>
|
<MutedThreadsProvider>
|
||||||
<ProgressGuideProvider>
|
<SafeAreaProvider>
|
||||||
<Shell />
|
<ProgressGuideProvider>
|
||||||
<NuxDialogs />
|
<Shell />
|
||||||
</ProgressGuideProvider>
|
<NuxDialogs />
|
||||||
</SafeAreaProvider>
|
</ProgressGuideProvider>
|
||||||
</MutedThreadsProvider>
|
</SafeAreaProvider>
|
||||||
</BackgroundNotificationPreferencesProvider>
|
</MutedThreadsProvider>
|
||||||
</UnreadNotifsProvider>
|
</BackgroundNotificationPreferencesProvider>
|
||||||
</HiddenRepliesProvider>
|
</UnreadNotifsProvider>
|
||||||
</SelectedFeedProvider>
|
</HiddenRepliesProvider>
|
||||||
</LoggedOutViewProvider>
|
</SelectedFeedProvider>
|
||||||
</ModerationOptsProvider>
|
</LoggedOutViewProvider>
|
||||||
</LabelDefsProvider>
|
</ModerationOptsProvider>
|
||||||
</MessagesProvider>
|
</LabelDefsProvider>
|
||||||
</StatsigProvider>
|
</MessagesProvider>
|
||||||
</QueryProvider>
|
</StatsigProvider>
|
||||||
</React.Fragment>
|
</QueryProvider>
|
||||||
<ToastContainer />
|
</React.Fragment>
|
||||||
</ActiveVideoProvider>
|
<ToastContainer />
|
||||||
|
</ActiveVideoProvider>
|
||||||
|
</VideoVolumeProvider>
|
||||||
</RootSiblingParent>
|
</RootSiblingParent>
|
||||||
</ThemeProvider>
|
</ThemeProvider>
|
||||||
</Alf>
|
</Alf>
|
||||||
|
|
|
@ -9,6 +9,7 @@ import {useLingui} from '@lingui/react'
|
||||||
import {HITSLOP_30} from '#/lib/constants'
|
import {HITSLOP_30} from '#/lib/constants'
|
||||||
import {clamp} from '#/lib/numbers'
|
import {clamp} from '#/lib/numbers'
|
||||||
import {useAutoplayDisabled} from '#/state/preferences'
|
import {useAutoplayDisabled} from '#/state/preferences'
|
||||||
|
import {useVideoVolumeState} from 'view/com/util/post-embeds/VideoVolumeContext'
|
||||||
import {atoms as a, useTheme} from '#/alf'
|
import {atoms as a, useTheme} from '#/alf'
|
||||||
import {useIsWithinMessage} from '#/components/dms/MessageContext'
|
import {useIsWithinMessage} from '#/components/dms/MessageContext'
|
||||||
import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute'
|
import {Mute_Stroke2_Corner0_Rounded as MuteIcon} from '#/components/icons/Mute'
|
||||||
|
@ -37,8 +38,8 @@ export const VideoEmbedInnerNative = React.forwardRef(
|
||||||
const videoRef = useRef<BlueskyVideoView>(null)
|
const videoRef = useRef<BlueskyVideoView>(null)
|
||||||
const autoplayDisabled = useAutoplayDisabled()
|
const autoplayDisabled = useAutoplayDisabled()
|
||||||
const isWithinMessage = useIsWithinMessage()
|
const isWithinMessage = useIsWithinMessage()
|
||||||
|
const {muted, setMuted} = useVideoVolumeState()
|
||||||
|
|
||||||
const [isMuted, setIsMuted] = React.useState(true)
|
|
||||||
const [isPlaying, setIsPlaying] = React.useState(false)
|
const [isPlaying, setIsPlaying] = React.useState(false)
|
||||||
const [timeRemaining, setTimeRemaining] = React.useState(0)
|
const [timeRemaining, setTimeRemaining] = React.useState(0)
|
||||||
const [error, setError] = React.useState<string>()
|
const [error, setError] = React.useState<string>()
|
||||||
|
@ -66,7 +67,7 @@ export const VideoEmbedInnerNative = React.forwardRef(
|
||||||
<BlueskyVideoView
|
<BlueskyVideoView
|
||||||
url={embed.playlist}
|
url={embed.playlist}
|
||||||
autoplay={!autoplayDisabled && !isWithinMessage}
|
autoplay={!autoplayDisabled && !isWithinMessage}
|
||||||
beginMuted={true}
|
beginMuted={autoplayDisabled ? false : muted}
|
||||||
style={[a.rounded_sm]}
|
style={[a.rounded_sm]}
|
||||||
onActiveChange={e => {
|
onActiveChange={e => {
|
||||||
setIsActive(e.nativeEvent.isActive)
|
setIsActive(e.nativeEvent.isActive)
|
||||||
|
@ -75,7 +76,7 @@ export const VideoEmbedInnerNative = React.forwardRef(
|
||||||
setIsLoading(e.nativeEvent.isLoading)
|
setIsLoading(e.nativeEvent.isLoading)
|
||||||
}}
|
}}
|
||||||
onMutedChange={e => {
|
onMutedChange={e => {
|
||||||
setIsMuted(e.nativeEvent.isMuted)
|
setMuted(e.nativeEvent.isMuted)
|
||||||
}}
|
}}
|
||||||
onStatusChange={e => {
|
onStatusChange={e => {
|
||||||
setStatus(e.nativeEvent.status)
|
setStatus(e.nativeEvent.status)
|
||||||
|
@ -103,7 +104,6 @@ export const VideoEmbedInnerNative = React.forwardRef(
|
||||||
togglePlayback={() => {
|
togglePlayback={() => {
|
||||||
videoRef.current?.togglePlayback()
|
videoRef.current?.togglePlayback()
|
||||||
}}
|
}}
|
||||||
isMuted={isMuted}
|
|
||||||
isPlaying={isPlaying}
|
isPlaying={isPlaying}
|
||||||
timeRemaining={timeRemaining}
|
timeRemaining={timeRemaining}
|
||||||
/>
|
/>
|
||||||
|
@ -119,17 +119,16 @@ function VideoControls({
|
||||||
togglePlayback,
|
togglePlayback,
|
||||||
timeRemaining,
|
timeRemaining,
|
||||||
isPlaying,
|
isPlaying,
|
||||||
isMuted,
|
|
||||||
}: {
|
}: {
|
||||||
enterFullscreen: () => void
|
enterFullscreen: () => void
|
||||||
toggleMuted: () => void
|
toggleMuted: () => void
|
||||||
togglePlayback: () => void
|
togglePlayback: () => void
|
||||||
timeRemaining: number
|
timeRemaining: number
|
||||||
isPlaying: boolean
|
isPlaying: boolean
|
||||||
isMuted: boolean
|
|
||||||
}) {
|
}) {
|
||||||
const {_} = useLingui()
|
const {_} = useLingui()
|
||||||
const t = useTheme()
|
const t = useTheme()
|
||||||
|
const {muted} = useVideoVolumeState()
|
||||||
|
|
||||||
// show countdown when:
|
// show countdown when:
|
||||||
// 1. timeRemaining is a number - was seeing NaNs
|
// 1. timeRemaining is a number - was seeing NaNs
|
||||||
|
@ -161,10 +160,10 @@ function VideoControls({
|
||||||
|
|
||||||
<ControlButton
|
<ControlButton
|
||||||
onPress={toggleMuted}
|
onPress={toggleMuted}
|
||||||
label={isMuted ? _(msg`Unmute`) : _(msg`Mute`)}
|
label={muted ? _(msg`Unmute`) : _(msg`Mute`)}
|
||||||
accessibilityHint={_(msg`Tap to toggle sound`)}
|
accessibilityHint={_(msg`Tap to toggle sound`)}
|
||||||
style={{right: 6}}>
|
style={{right: 6}}>
|
||||||
{isMuted ? (
|
{muted ? (
|
||||||
<MuteIcon width={13} fill={t.palette.white} />
|
<MuteIcon width={13} fill={t.palette.white} />
|
||||||
) : (
|
) : (
|
||||||
<UnmuteIcon width={13} fill={t.palette.white} />
|
<UnmuteIcon width={13} fill={t.palette.white} />
|
||||||
|
|
|
@ -0,0 +1,32 @@
|
||||||
|
import React from 'react'
|
||||||
|
|
||||||
|
const Context = React.createContext(
|
||||||
|
{} as {
|
||||||
|
muted: boolean
|
||||||
|
setMuted: (muted: boolean) => void
|
||||||
|
},
|
||||||
|
)
|
||||||
|
|
||||||
|
export function Provider({children}: {children: React.ReactNode}) {
|
||||||
|
const [muted, setMuted] = React.useState(true)
|
||||||
|
|
||||||
|
const value = React.useMemo(
|
||||||
|
() => ({
|
||||||
|
muted,
|
||||||
|
setMuted,
|
||||||
|
}),
|
||||||
|
[muted, setMuted],
|
||||||
|
)
|
||||||
|
|
||||||
|
return <Context.Provider value={value}>{children}</Context.Provider>
|
||||||
|
}
|
||||||
|
|
||||||
|
export function useVideoVolumeState() {
|
||||||
|
const context = React.useContext(Context)
|
||||||
|
if (!context) {
|
||||||
|
throw new Error(
|
||||||
|
'useVideoVolumeState must be used within a VideoVolumeProvider',
|
||||||
|
)
|
||||||
|
}
|
||||||
|
return context
|
||||||
|
}
|
|
@ -4104,10 +4104,10 @@
|
||||||
resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.2.0.tgz#5f3d96ec6b2354ad6d8a28bf216a1d97b5426861"
|
resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.2.0.tgz#5f3d96ec6b2354ad6d8a28bf216a1d97b5426861"
|
||||||
integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==
|
integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==
|
||||||
|
|
||||||
"@haileyok/bluesky-video@0.1.2":
|
"@haileyok/bluesky-video@0.1.4":
|
||||||
version "0.1.2"
|
version "0.1.4"
|
||||||
resolved "https://registry.yarnpkg.com/@haileyok/bluesky-video/-/bluesky-video-0.1.2.tgz#53abb04c22885fcf8a1d8a7510d2cfbe7d45ff91"
|
resolved "https://registry.yarnpkg.com/@haileyok/bluesky-video/-/bluesky-video-0.1.4.tgz#76acad0dffb9c80745bb752577be23cb566e4562"
|
||||||
integrity sha512-OPltVPNhjrm/+d4YYbaSsKLK7VQWC62ci8J05GO4I/PhWsYLWsAu79CGfZ1YTpfpIjYXyo0HjMmiig5X/hhOsQ==
|
integrity sha512-ggpk6E6U3giT+tmTc4GPraViA3ssnP32/Bty61UbZ3LiCQuc694LX+AOt01SfQ0B0fyd63J9DtT5rfaEJyjuzg==
|
||||||
|
|
||||||
"@hapi/accept@^6.0.3":
|
"@hapi/accept@^6.0.3":
|
||||||
version "6.0.3"
|
version "6.0.3"
|
||||||
|
|
Loading…
Reference in New Issue