* attempt some sort of "usurping" system * polling-based active video approach * split into inner component again * click to steal active video * disable findAndActivateVideo on native * new intersectionobserver approach - wip * fix types * disable perf optimisation to allow overflow * make active player indicator subtler, clean up video utils * partially fix double-playing * start working on controls * fullscreen API * get buttons working somewhat * rm source from where it shouldn't be * use video elem as source of truth * fix keyboard nav + mute state * new icons, add fullscreen + time + fix play * unmount when far offscreen + round 2dp * listen globally to clicks rather than blur event * move controls to new file * reduce quality when not active * add hover state to buttons * stop propagation of videoplayer click * move around autoplay effects * increase background contrast * add subtitles button * add stopPropagation to root of video player * clean up VideoWebControls * fix chrome * change quality based on focused state * use autoLevelCapping instead of nextLevel * get subtitle track from stream * always use hlsjs * rework hls into a ref * render player earlier, allowing preload * add error boundary * clean up component structure and organisation * rework fullscreen API * disable fullscreen on iPhone * don't play when ready on pause * debounce buffering * simplify giant list of event listeners * update pref * reduce prop drilling * minimise rerenders in `ActiveViewContext` * restore prop drilling --------- Co-authored-by: Samuel Newman <10959775+mozzius@users.noreply.github.com> Co-authored-by: Hailey <me@haileyok.com>
This commit is contained in:
parent
b701e8c68c
commit
fff2c079c2
32 changed files with 1087 additions and 87 deletions
|
|
@ -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<HTMLDivElement>(null)
|
||||
const ref = useRef<HTMLVideoElement>(null)
|
||||
const [focused, setFocused] = useState(false)
|
||||
const [hasSubtitleTrack, setHasSubtitleTrack] = useState(false)
|
||||
|
||||
const hlsRef = useRef<Hls | undefined>(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 <video ref={ref} style={a.flex_1} controls playsInline autoPlay loop />
|
||||
return (
|
||||
<View
|
||||
style={[
|
||||
a.w_full,
|
||||
a.rounded_sm,
|
||||
// TODO: get from embed metadata
|
||||
// max should be 1 / 1
|
||||
{aspectRatio: 16 / 9},
|
||||
a.overflow_hidden,
|
||||
]}>
|
||||
<div
|
||||
ref={containerRef}
|
||||
style={{width: '100%', height: '100%', display: 'flex'}}>
|
||||
<video
|
||||
ref={ref}
|
||||
style={{width: '100%', height: '100%', objectFit: 'contain'}}
|
||||
playsInline
|
||||
preload="none"
|
||||
loop
|
||||
muted={!focused}
|
||||
/>
|
||||
<Controls
|
||||
videoRef={ref}
|
||||
hlsRef={hlsRef}
|
||||
active={active}
|
||||
setActive={setActive}
|
||||
focused={focused}
|
||||
setFocused={setFocused}
|
||||
onScreen={onScreen}
|
||||
fullscreenRef={containerRef}
|
||||
hasSubtitleTrack={hasSubtitleTrack}
|
||||
/>
|
||||
</div>
|
||||
</View>
|
||||
)
|
||||
}
|
||||
|
||||
export class HLSUnsupportedError extends Error {
|
||||
constructor() {
|
||||
super('HLS is not supported')
|
||||
}
|
||||
}
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue