about summary refs log tree commit diff
diff options
context:
space:
mode:
authorHailey <me@haileyok.com>2024-09-13 14:07:13 -0700
committerGitHub <noreply@github.com>2024-09-13 14:07:13 -0700
commit843f9925f5d0773db321e617c1bd0be6a308ef7f (patch)
tree1233962c59d92247f64e7dd299910653a76bf78a
parent791bc7afbe0efd9519740b999799e6002b0fc835 (diff)
downloadvoidsky-843f9925f5d0773db321e617c1bd0be6a308ef7f.tar.zst
[Video] Remember mute state while scrolling (#5331)
-rw-r--r--package.json2
-rw-r--r--src/App.native.tsx72
-rw-r--r--src/App.web.tsx75
-rw-r--r--src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx15
-rw-r--r--src/view/com/util/post-embeds/VideoVolumeContext.tsx32
-rw-r--r--yarn.lock8
6 files changed, 121 insertions, 83 deletions
diff --git a/package.json b/package.json
index 5401d5f7d..1cff0d453 100644
--- a/package.json
+++ b/package.json
@@ -68,7 +68,7 @@
     "@fortawesome/free-regular-svg-icons": "^6.1.1",
     "@fortawesome/free-solid-svg-icons": "^6.1.1",
     "@fortawesome/react-native-fontawesome": "^0.3.2",
-    "@haileyok/bluesky-video": "0.1.2",
+    "@haileyok/bluesky-video": "0.1.4",
     "@lingui/react": "^4.5.0",
     "@mattermost/react-native-paste-input": "^0.7.1",
     "@miblanchard/react-native-slider": "^2.3.1",
diff --git a/src/App.native.tsx b/src/App.native.tsx
index 04fea126c..2ec666e2c 100644
--- a/src/App.native.tsx
+++ b/src/App.native.tsx
@@ -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 HiddenRepliesProvider} from '#/state/threadgate-hidden-replies'
 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 {Shell} from '#/view/shell'
 import {ThemeProvider as Alf} from '#/alf'
@@ -109,40 +110,43 @@ function InnerApp() {
       <ThemeProvider theme={theme}>
         <Splash isReady={isReady && hasCheckedReferrer}>
           <RootSiblingParent>
-            <React.Fragment
-              // Resets the entire tree below when it changes:
-              key={currentAccount?.did}>
-              <QueryProvider currentDid={currentAccount?.did}>
-                <StatsigProvider>
-                  <MessagesProvider>
-                    {/* LabelDefsProvider MUST come before ModerationOptsProvider */}
-                    <LabelDefsProvider>
-                      <ModerationOptsProvider>
-                        <LoggedOutViewProvider>
-                          <SelectedFeedProvider>
-                            <HiddenRepliesProvider>
-                              <UnreadNotifsProvider>
-                                <BackgroundNotificationPreferencesProvider>
-                                  <MutedThreadsProvider>
-                                    <ProgressGuideProvider>
-                                      <GestureHandlerRootView style={s.h100pct}>
-                                        <TestCtrls />
-                                        <Shell />
-                                        <NuxDialogs />
-                                      </GestureHandlerRootView>
-                                    </ProgressGuideProvider>
-                                  </MutedThreadsProvider>
-                                </BackgroundNotificationPreferencesProvider>
-                              </UnreadNotifsProvider>
-                            </HiddenRepliesProvider>
-                          </SelectedFeedProvider>
-                        </LoggedOutViewProvider>
-                      </ModerationOptsProvider>
-                    </LabelDefsProvider>
-                  </MessagesProvider>
-                </StatsigProvider>
-              </QueryProvider>
-            </React.Fragment>
+            <VideoVolumeProvider>
+              <React.Fragment
+                // Resets the entire tree below when it changes:
+                key={currentAccount?.did}>
+                <QueryProvider currentDid={currentAccount?.did}>
+                  <StatsigProvider>
+                    <MessagesProvider>
+                      {/* LabelDefsProvider MUST come before ModerationOptsProvider */}
+                      <LabelDefsProvider>
+                        <ModerationOptsProvider>
+                          <LoggedOutViewProvider>
+                            <SelectedFeedProvider>
+                              <HiddenRepliesProvider>
+                                <UnreadNotifsProvider>
+                                  <BackgroundNotificationPreferencesProvider>
+                                    <MutedThreadsProvider>
+                                      <ProgressGuideProvider>
+                                        <GestureHandlerRootView
+                                          style={s.h100pct}>
+                                          <TestCtrls />
+                                          <Shell />
+                                          <NuxDialogs />
+                                        </GestureHandlerRootView>
+                                      </ProgressGuideProvider>
+                                    </MutedThreadsProvider>
+                                  </BackgroundNotificationPreferencesProvider>
+                                </UnreadNotifsProvider>
+                              </HiddenRepliesProvider>
+                            </SelectedFeedProvider>
+                          </LoggedOutViewProvider>
+                        </ModerationOptsProvider>
+                      </LabelDefsProvider>
+                    </MessagesProvider>
+                  </StatsigProvider>
+                </QueryProvider>
+              </React.Fragment>
+            </VideoVolumeProvider>
           </RootSiblingParent>
         </Splash>
       </ThemeProvider>
diff --git a/src/App.web.tsx b/src/App.web.tsx
index ff9944fa4..bef320826 100644
--- a/src/App.web.tsx
+++ b/src/App.web.tsx
@@ -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 HiddenRepliesProvider} from '#/state/threadgate-hidden-replies'
 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 {ToastContainer} from '#/view/com/util/Toast.web'
 import {Shell} from '#/view/shell/index'
@@ -95,42 +96,44 @@ function InnerApp() {
       <Alf theme={theme}>
         <ThemeProvider theme={theme}>
           <RootSiblingParent>
-            <ActiveVideoProvider>
-              <React.Fragment
-                // Resets the entire tree below when it changes:
-                key={currentAccount?.did}>
-                <QueryProvider currentDid={currentAccount?.did}>
-                  <StatsigProvider>
-                    <MessagesProvider>
-                      {/* LabelDefsProvider MUST come before ModerationOptsProvider */}
-                      <LabelDefsProvider>
-                        <ModerationOptsProvider>
-                          <LoggedOutViewProvider>
-                            <SelectedFeedProvider>
-                              <HiddenRepliesProvider>
-                                <UnreadNotifsProvider>
-                                  <BackgroundNotificationPreferencesProvider>
-                                    <MutedThreadsProvider>
-                                      <SafeAreaProvider>
-                                        <ProgressGuideProvider>
-                                          <Shell />
-                                          <NuxDialogs />
-                                        </ProgressGuideProvider>
-                                      </SafeAreaProvider>
-                                    </MutedThreadsProvider>
-                                  </BackgroundNotificationPreferencesProvider>
-                                </UnreadNotifsProvider>
-                              </HiddenRepliesProvider>
-                            </SelectedFeedProvider>
-                          </LoggedOutViewProvider>
-                        </ModerationOptsProvider>
-                      </LabelDefsProvider>
-                    </MessagesProvider>
-                  </StatsigProvider>
-                </QueryProvider>
-              </React.Fragment>
-              <ToastContainer />
-            </ActiveVideoProvider>
+            <VideoVolumeProvider>
+              <ActiveVideoProvider>
+                <React.Fragment
+                  // Resets the entire tree below when it changes:
+                  key={currentAccount?.did}>
+                  <QueryProvider currentDid={currentAccount?.did}>
+                    <StatsigProvider>
+                      <MessagesProvider>
+                        {/* LabelDefsProvider MUST come before ModerationOptsProvider */}
+                        <LabelDefsProvider>
+                          <ModerationOptsProvider>
+                            <LoggedOutViewProvider>
+                              <SelectedFeedProvider>
+                                <HiddenRepliesProvider>
+                                  <UnreadNotifsProvider>
+                                    <BackgroundNotificationPreferencesProvider>
+                                      <MutedThreadsProvider>
+                                        <SafeAreaProvider>
+                                          <ProgressGuideProvider>
+                                            <Shell />
+                                            <NuxDialogs />
+                                          </ProgressGuideProvider>
+                                        </SafeAreaProvider>
+                                      </MutedThreadsProvider>
+                                    </BackgroundNotificationPreferencesProvider>
+                                  </UnreadNotifsProvider>
+                                </HiddenRepliesProvider>
+                              </SelectedFeedProvider>
+                            </LoggedOutViewProvider>
+                          </ModerationOptsProvider>
+                        </LabelDefsProvider>
+                      </MessagesProvider>
+                    </StatsigProvider>
+                  </QueryProvider>
+                </React.Fragment>
+                <ToastContainer />
+              </ActiveVideoProvider>
+            </VideoVolumeProvider>
           </RootSiblingParent>
         </ThemeProvider>
       </Alf>
diff --git a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx
index 39ed990ab..ee71daa83 100644
--- a/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx
+++ b/src/view/com/util/post-embeds/VideoEmbedInner/VideoEmbedInnerNative.tsx
@@ -9,6 +9,7 @@ import {useLingui} from '@lingui/react'
 import {HITSLOP_30} from '#/lib/constants'
 import {clamp} from '#/lib/numbers'
 import {useAutoplayDisabled} from '#/state/preferences'
+import {useVideoVolumeState} from 'view/com/util/post-embeds/VideoVolumeContext'
 import {atoms as a, useTheme} from '#/alf'
 import {useIsWithinMessage} from '#/components/dms/MessageContext'
 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 autoplayDisabled = useAutoplayDisabled()
     const isWithinMessage = useIsWithinMessage()
+    const {muted, setMuted} = useVideoVolumeState()
 
-    const [isMuted, setIsMuted] = React.useState(true)
     const [isPlaying, setIsPlaying] = React.useState(false)
     const [timeRemaining, setTimeRemaining] = React.useState(0)
     const [error, setError] = React.useState<string>()
@@ -66,7 +67,7 @@ export const VideoEmbedInnerNative = React.forwardRef(
         <BlueskyVideoView
           url={embed.playlist}
           autoplay={!autoplayDisabled && !isWithinMessage}
-          beginMuted={true}
+          beginMuted={autoplayDisabled ? false : muted}
           style={[a.rounded_sm]}
           onActiveChange={e => {
             setIsActive(e.nativeEvent.isActive)
@@ -75,7 +76,7 @@ export const VideoEmbedInnerNative = React.forwardRef(
             setIsLoading(e.nativeEvent.isLoading)
           }}
           onMutedChange={e => {
-            setIsMuted(e.nativeEvent.isMuted)
+            setMuted(e.nativeEvent.isMuted)
           }}
           onStatusChange={e => {
             setStatus(e.nativeEvent.status)
@@ -103,7 +104,6 @@ export const VideoEmbedInnerNative = React.forwardRef(
           togglePlayback={() => {
             videoRef.current?.togglePlayback()
           }}
-          isMuted={isMuted}
           isPlaying={isPlaying}
           timeRemaining={timeRemaining}
         />
@@ -119,17 +119,16 @@ function VideoControls({
   togglePlayback,
   timeRemaining,
   isPlaying,
-  isMuted,
 }: {
   enterFullscreen: () => void
   toggleMuted: () => void
   togglePlayback: () => void
   timeRemaining: number
   isPlaying: boolean
-  isMuted: boolean
 }) {
   const {_} = useLingui()
   const t = useTheme()
+  const {muted} = useVideoVolumeState()
 
   // show countdown when:
   // 1. timeRemaining is a number - was seeing NaNs
@@ -161,10 +160,10 @@ function VideoControls({
 
       <ControlButton
         onPress={toggleMuted}
-        label={isMuted ? _(msg`Unmute`) : _(msg`Mute`)}
+        label={muted ? _(msg`Unmute`) : _(msg`Mute`)}
         accessibilityHint={_(msg`Tap to toggle sound`)}
         style={{right: 6}}>
-        {isMuted ? (
+        {muted ? (
           <MuteIcon width={13} fill={t.palette.white} />
         ) : (
           <UnmuteIcon width={13} fill={t.palette.white} />
diff --git a/src/view/com/util/post-embeds/VideoVolumeContext.tsx b/src/view/com/util/post-embeds/VideoVolumeContext.tsx
new file mode 100644
index 000000000..cccb93ba8
--- /dev/null
+++ b/src/view/com/util/post-embeds/VideoVolumeContext.tsx
@@ -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
+}
diff --git a/yarn.lock b/yarn.lock
index 5fd07230a..16cfb3406 100644
--- a/yarn.lock
+++ b/yarn.lock
@@ -4104,10 +4104,10 @@
   resolved "https://registry.yarnpkg.com/@graphql-typed-document-node/core/-/core-3.2.0.tgz#5f3d96ec6b2354ad6d8a28bf216a1d97b5426861"
   integrity sha512-mB9oAsNCm9aM3/SOv4YtBMqZbYj10R7dkq8byBqxGY/ncFwhf2oQzMV+LCRlWoDSEBJ3COiR1yeDvMtsoOsuFQ==
 
-"@haileyok/bluesky-video@0.1.2":
-  version "0.1.2"
-  resolved "https://registry.yarnpkg.com/@haileyok/bluesky-video/-/bluesky-video-0.1.2.tgz#53abb04c22885fcf8a1d8a7510d2cfbe7d45ff91"
-  integrity sha512-OPltVPNhjrm/+d4YYbaSsKLK7VQWC62ci8J05GO4I/PhWsYLWsAu79CGfZ1YTpfpIjYXyo0HjMmiig5X/hhOsQ==
+"@haileyok/bluesky-video@0.1.4":
+  version "0.1.4"
+  resolved "https://registry.yarnpkg.com/@haileyok/bluesky-video/-/bluesky-video-0.1.4.tgz#76acad0dffb9c80745bb752577be23cb566e4562"
+  integrity sha512-ggpk6E6U3giT+tmTc4GPraViA3ssnP32/Bty61UbZ3LiCQuc694LX+AOt01SfQ0B0fyd63J9DtT5rfaEJyjuzg==
 
 "@hapi/accept@^6.0.3":
   version "6.0.3"