Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix(useVideoTexture): lazy initialize hls.js when supported #1919

Merged
merged 1 commit into from
Apr 13, 2024
Merged
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
39 changes: 27 additions & 12 deletions src/core/useVideoTexture.tsx
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@ import * as THREE from 'three'
import { useEffect, useRef } from 'react'
import { useThree } from '@react-three/fiber'
import { suspend } from 'suspend-react'
import Hls, { HlsConfig } from 'hls.js'
import type { HlsConfig, default as Hls } from 'hls.js'

interface VideoTextureProps extends HTMLVideoElement {
unsuspend?: 'canplay' | 'canplaythrough' | 'loadstart' | 'loadedmetadata'
Expand All @@ -14,6 +14,23 @@ interface HLSConfiguration {
hls: HlsConfig
}

const IS_BROWSER =
typeof window !== 'undefined' &&
typeof window.document?.createElement === 'function' &&
typeof window.navigator?.userAgent === 'string'

let _HLSModule: typeof import('hls.js') | null = null
async function getHLS(url: URL, config: Partial<HlsConfig>): Promise<Hls | null> {
if (IS_BROWSER && url.pathname.endsWith('.m3u8')) {
_HLSModule ??= await import('hls.js')
if (_HLSModule.default.isSupported()) {
return new _HLSModule.default({ ...config })
}
}

return null
}

export function useVideoTexture(src: string | MediaStream, props?: Partial<VideoTextureProps>) {
const { unsuspend, start, crossOrigin, muted, loop, hls, ...rest } = {
unsuspend: 'loadedmetadata',
Expand All @@ -27,14 +44,13 @@ export function useVideoTexture(src: string | MediaStream, props?: Partial<Video
}

const url = new URL(typeof src === 'string' ? src : '', window.location.href)
const shouldUseHLS = url.pathname.endsWith('.m3u8') && Hls.isSupported()
const hlsRef = useRef(shouldUseHLS ? new Hls({ ...hls }) : null)
const hlsRef = useRef<Hls | null>(null)
const videoRef = useRef<HTMLVideoElement | null>(null)
const gl = useThree((state) => state.gl)

const texture = suspend(
() =>
new Promise((res, rej) => {
new Promise(async (res, rej) => {
const video = Object.assign(document.createElement('video'), {
src: (typeof src === 'string' && src) || undefined,
srcObject: (src instanceof MediaStream && src) || undefined,
Expand All @@ -47,14 +63,13 @@ export function useVideoTexture(src: string | MediaStream, props?: Partial<Video

// hlsjs extension
if (typeof src === 'string') {
if (shouldUseHLS) {
const _hls: Hls | null = hlsRef.current
if (_hls) {
_hls.attachMedia(video)
_hls.on(Hls.Events.MEDIA_ATTACHED, () => {
_hls.loadSource(src)
})
}
const _hls = (hlsRef.current = await getHLS(url, hls))

if (_hls) {
_hls.attachMedia(video)
_hls.on('hlsMediaAttached' as typeof Hls.Events.MEDIA_ATTACHED, () => {
_hls.loadSource(src)
})
} else {
video.src = src
}
Expand Down
Loading