diff --git a/assets/icons/arrowsDiagonalIn_stroke2_corner0_rounded.svg b/assets/icons/arrowsDiagonalIn_stroke2_corner0_rounded.svg new file mode 100644 index 00000000..a9532cd9 --- /dev/null +++ b/assets/icons/arrowsDiagonalIn_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/arrowsDiagonalIn_stroke2_corner2_rounded.svg b/assets/icons/arrowsDiagonalIn_stroke2_corner2_rounded.svg new file mode 100644 index 00000000..9b92e533 --- /dev/null +++ b/assets/icons/arrowsDiagonalIn_stroke2_corner2_rounded.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/arrowsDiagonalOut_stroke2_corner0_rounded.svg b/assets/icons/arrowsDiagonalOut_stroke2_corner0_rounded.svg new file mode 100644 index 00000000..9987b344 --- /dev/null +++ b/assets/icons/arrowsDiagonalOut_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/arrowsDiagonalOut_stroke2_corner2_rounded.svg b/assets/icons/arrowsDiagonalOut_stroke2_corner2_rounded.svg new file mode 100644 index 00000000..36d8e1d6 --- /dev/null +++ b/assets/icons/arrowsDiagonalOut_stroke2_corner2_rounded.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/cc_filled_stroke2_corner0_rounded.svg b/assets/icons/cc_filled_stroke2_corner0_rounded.svg new file mode 100644 index 00000000..58823ca8 --- /dev/null +++ b/assets/icons/cc_filled_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/cc_stroke2_corner0_rounded.svg b/assets/icons/cc_stroke2_corner0_rounded.svg new file mode 100644 index 00000000..fcda1570 --- /dev/null +++ b/assets/icons/cc_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/pause_filled_corner0_rounded.svg b/assets/icons/pause_filled_corner0_rounded.svg new file mode 100644 index 00000000..0037701f --- /dev/null +++ b/assets/icons/pause_filled_corner0_rounded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/pause_filled_corner2_rounded.svg b/assets/icons/pause_filled_corner2_rounded.svg new file mode 100644 index 00000000..98726d87 --- /dev/null +++ b/assets/icons/pause_filled_corner2_rounded.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/pause_stroke2_corner0_rounded.svg b/assets/icons/pause_stroke2_corner0_rounded.svg new file mode 100644 index 00000000..d2735ed2 --- /dev/null +++ b/assets/icons/pause_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/pause_stroke2_corner2_rounded.svg b/assets/icons/pause_stroke2_corner2_rounded.svg new file mode 100644 index 00000000..3a8c0b43 --- /dev/null +++ b/assets/icons/pause_stroke2_corner2_rounded.svg @@ -0,0 +1 @@ + diff --git a/assets/icons/play_filled_corner0_rounded.svg b/assets/icons/play_filled_corner0_rounded.svg new file mode 100644 index 00000000..7bee1ae9 --- /dev/null +++ b/assets/icons/play_filled_corner0_rounded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/assets/icons/play_stroke2_corner0_rounded.svg b/assets/icons/play_stroke2_corner0_rounded.svg new file mode 100644 index 00000000..d7321b9b --- /dev/null +++ b/assets/icons/play_stroke2_corner0_rounded.svg @@ -0,0 +1 @@ + \ No newline at end of file diff --git a/src/components/icons/ArrowsDiagonal.tsx b/src/components/icons/ArrowsDiagonal.tsx new file mode 100644 index 00000000..3f9ae40e --- /dev/null +++ b/src/components/icons/ArrowsDiagonal.tsx @@ -0,0 +1,17 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const ArrowsDiagonalOut_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M14 5a1 1 0 1 1 0-2h6a1 1 0 0 1 1 1v6a1 1 0 1 1-2 0V6.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L17.586 5H14ZM4 13a1 1 0 0 1 1 1v3.586l4.293-4.293a1 1 0 0 1 1.414 1.414L6.414 19H10a1 1 0 1 1 0 2H4a1 1 0 0 1-1-1v-6a1 1 0 0 1 1-1Z', +}) + +export const ArrowsDiagonalIn_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M20.957 3.043a1 1 0 0 1 0 1.414L16.414 9H20a1 1 0 1 1 0 2h-6a1 1 0 0 1-1-1V4a1 1 0 1 1 2 0v3.586l4.543-4.543a1 1 0 0 1 1.414 0ZM3 14a1 1 0 0 1 1-1h6a1 1 0 0 1 1 1v6a1 1 0 1 1-2 0v-3.586l-4.543 4.543a1 1 0 0 1-1.414-1.414L7.586 15H4a1 1 0 0 1-1-1Z', +}) + +export const ArrowsDiagonalOut_Stroke2_Corner2_Rounded = createSinglePathSVG({ + path: 'M13 4a1 1 0 0 1 1-1h5a2 2 0 0 1 2 2v5a1 1 0 1 1-2 0V6.414l-4.293 4.293a1 1 0 0 1-1.414-1.414L17.586 5H14a1 1 0 0 1-1-1Zm-9 9a1 1 0 0 1 1 1v3.586l4.293-4.293a1 1 0 0 1 1.414 1.414L6.414 19H10a1 1 0 1 1 0 2H5a2 2 0 0 1-2-2v-5a1 1 0 0 1 1-1Z', +}) + +export const ArrowsDiagonalIn_Stroke2_Corner2_Rounded = createSinglePathSVG({ + path: 'M20.957 3.043a1 1 0 0 1 0 1.414L16.414 9H20a1 1 0 1 1 0 2h-5a2 2 0 0 1-2-2V4a1 1 0 1 1 2 0v3.586l4.543-4.543a1 1 0 0 1 1.414 0ZM3 14a1 1 0 0 1 1-1h5a2 2 0 0 1 2 2v5a1 1 0 1 1-2 0v-3.586l-4.543 4.543a1 1 0 0 1-1.414-1.414L7.586 15H4a1 1 0 0 1-1-1Z', +}) diff --git a/src/components/icons/CC.tsx b/src/components/icons/CC.tsx new file mode 100644 index 00000000..da2e7c5d --- /dev/null +++ b/src/components/icons/CC.tsx @@ -0,0 +1,9 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const CC_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M3 4a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4Zm2 1v14h14V5H5Zm10.957 6.293a1 1 0 1 0 0 1.414 1 1 0 0 1 1.414 1.414 3 3 0 1 1 0-4.242 1 1 0 0 1-1.414 1.414Zm-6.331-.22a1 1 0 1 0 .331 1.634 1 1 0 0 1 1.414 1.414 3 3 0 1 1 0-4.242 1 1 0 0 1-1.414 1.414.994.994 0 0 0-.331-.22Z', +}) + +export const CC_Filled_Corner0_Rounded = createSinglePathSVG({ + path: 'M3 4a1 1 0 0 1 1-1h16a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H4a1 1 0 0 1-1-1V4Zm11.543 7.293a1 1 0 0 1 1.414 0 1 1 0 0 0 1.414-1.414 3 3 0 1 0 0 4.242 1 1 0 0 0-1.414-1.414 1 1 0 0 1-1.414-1.414Zm-6 0a1 1 0 0 1 1.414 0 1 1 0 0 0 1.414-1.414 3 3 0 1 0 0 4.243 1 1 0 0 0-1.414-1.415 1 1 0 0 1-1.414-1.414Z', +}) diff --git a/src/components/icons/Pause.tsx b/src/components/icons/Pause.tsx new file mode 100644 index 00000000..927f285a --- /dev/null +++ b/src/components/icons/Pause.tsx @@ -0,0 +1,17 @@ +import {createSinglePathSVG} from './TEMPLATE' + +export const Pause_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M4 4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V4Zm2 1v14h2V5H6Zm8-1a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1h-4a1 1 0 0 1-1-1V4Zm2 1v14h2V5h-2Z', +}) + +export const Pause_Filled_Corner0_Rounded = createSinglePathSVG({ + path: 'M4 4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1H5a1 1 0 0 1-1-1V4ZM14 4a1 1 0 0 1 1-1h4a1 1 0 0 1 1 1v16a1 1 0 0 1-1 1h-4a1 1 0 0 1-1-1V4Z', +}) + +export const Pause_Stroke2_Corner2_Rounded = createSinglePathSVG({ + path: 'M4 6a3 3 0 0 1 6 0v12a3 3 0 1 1-6 0V6Zm3-1a1 1 0 0 0-1 1v12a1 1 0 1 0 2 0V6a1 1 0 0 0-1-1Zm7 1a3 3 0 1 1 6 0v12a3 3 0 1 1-6 0V6Zm3-1a1 1 0 0 0-1 1v12a1 1 0 1 0 2 0V6a1 1 0 0 0-1-1Z', +}) + +export const Pause_Filled_Corner2_Rounded = createSinglePathSVG({ + path: 'M4 6a3 3 0 0 1 6 0v12a3 3 0 1 1-6 0V6ZM14 6a3 3 0 1 1 6 0v12a3 3 0 1 1-6 0V6Z', +}) diff --git a/src/components/icons/Play.tsx b/src/components/icons/Play.tsx index acf421d5..176b24f2 100644 --- a/src/components/icons/Play.tsx +++ b/src/components/icons/Play.tsx @@ -1,5 +1,13 @@ import {createSinglePathSVG} from './TEMPLATE' +export const Play_Stroke2_Corner0_Rounded = createSinglePathSVG({ + path: 'M5.507 2.13a1 1 0 0 1 1.008.013l15 9a1 1 0 0 1 0 1.714l-15 9A1 1 0 0 1 5 21V3a1 1 0 0 1 .507-.87ZM7 4.766v14.468L19.056 12 7 4.766Z', +}) + +export const Play_Filled_Corner0_Rounded = createSinglePathSVG({ + path: 'M6.514 2.143A1 1 0 0 0 5 3v18a1 1 0 0 0 1.514.858l15-9a1 1 0 0 0 0-1.716l-15-9Z', +}) + export const Play_Stroke2_Corner2_Rounded = createSinglePathSVG({ path: 'M5 5.086C5 2.736 7.578 1.3 9.576 2.534L20.77 9.448c1.899 1.172 1.899 3.932 0 5.104L9.576 21.466C7.578 22.701 5 21.263 5 18.914V5.086Zm3.525-.85A1 1 0 0 0 7 5.085v13.828a1 1 0 0 0 1.525.85l11.194-6.913a1 1 0 0 0 0-1.702L8.525 4.235Z', }) diff --git a/src/platform/detection.ts b/src/platform/detection.ts index f00df0ee..c62ae71a 100644 --- a/src/platform/detection.ts +++ b/src/platform/detection.ts @@ -14,6 +14,7 @@ export const isMobileWeb = isWeb && // @ts-ignore we know window exists -prf global.window.matchMedia(isMobileWebMediaQuery)?.matches +export const isIPhoneWeb = isWeb && /iPhone/.test(navigator.userAgent) export const deviceLocales = dedupArray( getLocales?.() diff --git a/src/screens/Messages/Conversation/MessagesList.tsx b/src/screens/Messages/Conversation/MessagesList.tsx index 11b951e9..c0e78e97 100644 --- a/src/screens/Messages/Conversation/MessagesList.tsx +++ b/src/screens/Messages/Conversation/MessagesList.tsx @@ -387,9 +387,6 @@ export function MessagesList({ renderItem={renderItem} keyExtractor={keyExtractor} disableFullWindowScroll={true} - // Prevents wrong position in Firefox when sending a message - // as well as scroll getting stuck on Chome when scrolling upwards. - disableContainStyle={true} disableVirtualization={true} style={animatedListStyle} // The extra two items account for the header and the footer components diff --git a/src/state/persisted/schema.ts b/src/state/persisted/schema.ts index 0b652a1f..331a111a 100644 --- a/src/state/persisted/schema.ts +++ b/src/state/persisted/schema.ts @@ -91,6 +91,7 @@ const schema = z.object({ disableAutoplay: z.boolean().optional(), kawaii: z.boolean().optional(), hasCheckedForStarterPack: z.boolean().optional(), + subtitlesEnabled: z.boolean().optional(), /** @deprecated */ mutedThreads: z.array(z.string()), }) @@ -133,6 +134,7 @@ export const defaults: Schema = { disableAutoplay: PlatformInfo.getIsReducedMotionEnabled(), kawaii: false, hasCheckedForStarterPack: false, + subtitlesEnabled: true, } export function tryParse(rawData: string): Schema | undefined { diff --git a/src/state/preferences/index.tsx b/src/state/preferences/index.tsx index e6b53d5b..c7eaf272 100644 --- a/src/state/preferences/index.tsx +++ b/src/state/preferences/index.tsx @@ -9,6 +9,7 @@ import {Provider as InAppBrowserProvider} from './in-app-browser' import {Provider as KawaiiProvider} from './kawaii' import {Provider as LanguagesProvider} from './languages' import {Provider as LargeAltBadgeProvider} from './large-alt-badge' +import {Provider as SubtitlesProvider} from './subtitles' import {Provider as UsedStarterPacksProvider} from './used-starter-packs' export { @@ -24,6 +25,7 @@ export { export * from './hidden-posts' export {useLabelDefinitions} from './label-defs' export {useLanguagePrefs, useLanguagePrefsApi} from './languages' +export {useSetSubtitlesEnabled, useSubtitlesEnabled} from './subtitles' export function Provider({children}: React.PropsWithChildren<{}>) { return ( @@ -36,7 +38,9 @@ export function Provider({children}: React.PropsWithChildren<{}>) { - {children} + + {children} + diff --git a/src/state/preferences/subtitles.tsx b/src/state/preferences/subtitles.tsx new file mode 100644 index 00000000..e0e89feb --- /dev/null +++ b/src/state/preferences/subtitles.tsx @@ -0,0 +1,42 @@ +import React from 'react' + +import * as persisted from '#/state/persisted' + +type StateContext = boolean +type SetContext = (v: boolean) => void + +const stateContext = React.createContext( + Boolean(persisted.defaults.subtitlesEnabled), +) +const setContext = React.createContext((_: boolean) => {}) + +export function Provider({children}: {children: React.ReactNode}) { + const [state, setState] = React.useState( + Boolean(persisted.get('subtitlesEnabled')), + ) + + const setStateWrapped = React.useCallback( + (subtitlesEnabled: persisted.Schema['subtitlesEnabled']) => { + setState(Boolean(subtitlesEnabled)) + persisted.write('subtitlesEnabled', subtitlesEnabled) + }, + [setState], + ) + + React.useEffect(() => { + return persisted.onUpdate('subtitlesEnabled', nextSubtitlesEnabled => { + setState(Boolean(nextSubtitlesEnabled)) + }) + }, [setStateWrapped]) + + return ( + + + {children} + + + ) +} + +export const useSubtitlesEnabled = () => React.useContext(stateContext) +export const useSetSubtitlesEnabled = () => React.useContext(setContext) diff --git a/src/view/com/posts/FeedItem.tsx b/src/view/com/posts/FeedItem.tsx index 2c2e2163..a6e721d4 100644 --- a/src/view/com/posts/FeedItem.tsx +++ b/src/view/com/posts/FeedItem.tsx @@ -507,7 +507,6 @@ const styles = StyleSheet.create({ paddingRight: 15, // @ts-ignore web only -prf cursor: 'pointer', - overflow: 'hidden', }, replyLine: { width: 2, diff --git a/src/view/com/util/List.tsx b/src/view/com/util/List.tsx index e1a10e47..9d9b1d80 100644 --- a/src/view/com/util/List.tsx +++ b/src/view/com/util/List.tsx @@ -28,8 +28,6 @@ export type ListProps = Omit< // Web only prop to contain the scroll to the container rather than the window disableFullWindowScroll?: boolean sideBorders?: boolean - // Web only prop to disable a perf optimization (which would otherwise be on). - disableContainStyle?: boolean } export type ListRef = React.MutableRefObject diff --git a/src/view/com/util/List.web.tsx b/src/view/com/util/List.web.tsx index 5aa69935..5f89cfbb 100644 --- a/src/view/com/util/List.web.tsx +++ b/src/view/com/util/List.web.tsx @@ -4,11 +4,10 @@ import {ReanimatedScrollEvent} from 'react-native-reanimated/lib/typescript/rean import {batchedUpdates} from '#/lib/batchedUpdates' import {useNonReactiveCallback} from '#/lib/hooks/useNonReactiveCallback' +import {usePalette} from '#/lib/hooks/usePalette' +import {useWebMediaQueries} from '#/lib/hooks/useWebMediaQueries' import {useScrollHandlers} from '#/lib/ScrollContext' -import {isSafari} from 'lib/browser' -import {usePalette} from 'lib/hooks/usePalette' -import {useWebMediaQueries} from 'lib/hooks/useWebMediaQueries' -import {addStyle} from 'lib/styles' +import {addStyle} from '#/lib/styles' export type ListMethods = any // TODO: Better types. export type ListProps = Omit< @@ -26,8 +25,6 @@ export type ListProps = Omit< // Web only prop to contain the scroll to the container rather than the window disableFullWindowScroll?: boolean sideBorders?: boolean - // Web only prop to disable a perf optimization (which would otherwise be on). - disableContainStyle?: boolean } export type ListRef = React.MutableRefObject // TODO: Better types. @@ -60,7 +57,6 @@ function ListImpl( extraData, style, sideBorders = true, - disableContainStyle, ...props }: ListProps, ref: React.Ref, @@ -364,7 +360,6 @@ function ListImpl( renderItem={renderItem} extraData={extraData} onItemSeen={onItemSeen} - disableContainStyle={disableContainStyle} /> ) })} @@ -442,7 +437,6 @@ let Row = function RowImpl({ renderItem, extraData: _unused, onItemSeen, - disableContainStyle, }: { item: ItemT index: number @@ -452,7 +446,6 @@ let Row = function RowImpl({ | ((data: {index: number; item: any; separators: any}) => React.ReactNode) extraData: any onItemSeen: ((item: any) => void) | undefined - disableContainStyle?: boolean }): React.ReactNode { const rowRef = React.useRef(null) const intersectionTimeout = React.useRef(undefined) @@ -501,11 +494,8 @@ let Row = function RowImpl({ return null } - const shouldDisableContainStyle = disableContainStyle || isSafari return ( - + {renderItem({item, index, separators: null as any})} ) @@ -576,10 +566,6 @@ const styles = StyleSheet.create({ marginLeft: 'auto', marginRight: 'auto', }, - contain: { - // @ts-ignore web only - contain: 'layout paint', - }, minHeightViewport: { // @ts-ignore web only minHeight: '100vh', diff --git a/src/view/com/util/post-embeds/ActiveVideoContext.tsx b/src/view/com/util/post-embeds/ActiveVideoContext.tsx index 6804436a..d18dfc09 100644 --- a/src/view/com/util/post-embeds/ActiveVideoContext.tsx +++ b/src/view/com/util/post-embeds/ActiveVideoContext.tsx @@ -1,37 +1,103 @@ -import React, {useCallback, useId, useMemo, useState} from 'react' +import React, { + useCallback, + useEffect, + useId, + useMemo, + useRef, + useState, +} from 'react' +import {useWindowDimensions} from 'react-native' +import {isNative} from '#/platform/detection' import {VideoPlayerProvider} from './VideoPlayerContext' const ActiveVideoContext = React.createContext<{ activeViewId: string | null setActiveView: (viewId: string, src: string) => void + sendViewPosition: (viewId: string, y: number) => void } | null>(null) export function ActiveVideoProvider({children}: {children: React.ReactNode}) { const [activeViewId, setActiveViewId] = useState(null) + const activeViewLocationRef = useRef(Infinity) const [source, setSource] = useState(null) + const {height: windowHeight} = useWindowDimensions() + + // minimising re-renders by using refs + const manuallySetRef = useRef(false) + const activeViewIdRef = useRef(activeViewId) + useEffect(() => { + activeViewIdRef.current = activeViewId + }, [activeViewId]) + + const setActiveView = useCallback( + (viewId: string, src: string) => { + setActiveViewId(viewId) + setSource(src) + manuallySetRef.current = true + // we don't know the exact position, but it's definitely on screen + // so just guess that it's in the middle. Any value is fine + // so long as it's not offscreen + activeViewLocationRef.current = windowHeight / 2 + }, + [windowHeight], + ) + + const sendViewPosition = useCallback( + (viewId: string, y: number) => { + if (isNative) return + + if (viewId === activeViewIdRef.current) { + activeViewLocationRef.current = y + } else { + if ( + distanceToIdealPosition(y) < + distanceToIdealPosition(activeViewLocationRef.current) + ) { + // if the old view was manually set, only usurp if the old view is offscreen + if ( + manuallySetRef.current && + withinViewport(activeViewLocationRef.current) + ) { + return + } + + setActiveViewId(viewId) + activeViewLocationRef.current = y + manuallySetRef.current = false + } + } + + function distanceToIdealPosition(yPos: number) { + return Math.abs(yPos - windowHeight / 2.5) + } + + function withinViewport(yPos: number) { + return yPos > 0 && yPos < windowHeight + } + }, + [windowHeight], + ) const value = useMemo( () => ({ activeViewId, - setActiveView: (viewId: string, src: string) => { - setActiveViewId(viewId) - setSource(src) - }, + setActiveView, + sendViewPosition, }), - [activeViewId], + [activeViewId, setActiveView, sendViewPosition], ) return ( - + {children} ) } -export function useActiveVideoView() { +export function useActiveVideoView({source}: {source: string}) { const context = React.useContext(ActiveVideoContext) if (!context) { throw new Error('useActiveVideo must be used within a ActiveVideoProvider') @@ -41,7 +107,12 @@ export function useActiveVideoView() { return { active: context.activeViewId === id, setActive: useCallback( - (source: string) => context.setActiveView(id, source), + () => context.setActiveView(id, source), + [context, id, source], + ), + currentActiveView: context.activeViewId, + sendPosition: useCallback( + (y: number) => context.sendViewPosition(id, y), [context, id], ), } diff --git a/src/view/com/util/post-embeds/VideoEmbed.tsx b/src/view/com/util/post-embeds/VideoEmbed.tsx index 5e5293a5..429312d9 100644 --- a/src/view/com/util/post-embeds/VideoEmbed.tsx +++ b/src/view/com/util/post-embeds/VideoEmbed.tsx @@ -11,10 +11,10 @@ import {VideoEmbedInner} from './VideoEmbedInner' export function VideoEmbed({source}: {source: string}) { const t = useTheme() - const {active, setActive} = useActiveVideoView() + const {active, setActive} = useActiveVideoView({source}) const {_} = useLingui() - const onPress = useCallback(() => setActive(source), [setActive, source]) + const onPress = useCallback(() => setActive(), [setActive]) return ( {active ? ( - + ) : ( + )} + + ) +} diff --git a/src/view/com/util/post-embeds/VideoEmbedInner.tsx b/src/view/com/util/post-embeds/VideoEmbedInner.tsx index ef067870..9b1fd54f 100644 --- a/src/view/com/util/post-embeds/VideoEmbedInner.tsx +++ b/src/view/com/util/post-embeds/VideoEmbedInner.tsx @@ -13,7 +13,12 @@ import {atoms as a} from '#/alf' import {Text} from '#/components/Typography' import {useVideoPlayer} from './VideoPlayerContext' -export const VideoEmbedInner = ({}: {source: string}) => { +export function VideoEmbedInner({}: { + source: string + active: boolean + setActive: () => void + onScreen: boolean +}) { const player = useVideoPlayer() const aref = useAnimatedRef() const {height: windowHeight} = useWindowDimensions() diff --git a/src/view/com/util/post-embeds/VideoEmbedInner.web.tsx b/src/view/com/util/post-embeds/VideoEmbedInner.web.tsx index cb02743c..f5f47db5 100644 --- a/src/view/com/util/post-embeds/VideoEmbedInner.web.tsx +++ b/src/view/com/util/post-embeds/VideoEmbedInner.web.tsx @@ -1,52 +1,93 @@ -import React, {useEffect, useRef} from 'react' +import React, {useEffect, useRef, useState} from 'react' +import {View} from 'react-native' import Hls from 'hls.js' import {atoms as a} from '#/alf' +import {Controls} from './VideoWebControls' -export const VideoEmbedInner = ({source}: {source: string}) => { +export function VideoEmbedInner({ + source, + active, + setActive, + onScreen, +}: { + source: string + active: boolean + setActive: () => void + onScreen: boolean +}) { + const containerRef = useRef(null) const ref = useRef(null) + const [focused, setFocused] = useState(false) + const [hasSubtitleTrack, setHasSubtitleTrack] = useState(false) + + const hlsRef = useRef(undefined) - // Use HLS.js to play HLS video useEffect(() => { - if (ref.current) { - if (ref.current.canPlayType('application/vnd.apple.mpegurl')) { - ref.current.src = source - } else if (Hls.isSupported()) { - var hls = new Hls() - hls.loadSource(source) - hls.attachMedia(ref.current) - } else { - // TODO: fallback + if (!ref.current) return + if (!Hls.isSupported()) throw new HLSUnsupportedError() + + const hls = new Hls({capLevelToPlayerSize: true}) + hlsRef.current = hls + + hls.attachMedia(ref.current) + hls.loadSource(source) + + // initial value, later on it's managed by Controls + hls.autoLevelCapping = 0 + + hls.on(Hls.Events.SUBTITLE_TRACKS_UPDATED, (event, data) => { + if (data.subtitleTracks.length > 0) { + setHasSubtitleTrack(true) } + }) + + return () => { + hlsRef.current = undefined + hls.detachMedia() + hls.destroy() } }, [source]) - useEffect(() => { - if (ref.current) { - const observer = new IntersectionObserver( - ([entry]) => { - if (ref.current) { - if (entry.isIntersecting) { - if (ref.current.paused) { - ref.current.play() - } - } else { - if (!ref.current.paused) { - ref.current.pause() - } - } - } - }, - {threshold: 0}, - ) - - observer.observe(ref.current) - - return () => { - observer.disconnect() - } - } - }, []) - - return