From b06cfc8640a165ffafdfc31fc90875c25d48b9b9 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 30 Jun 2024 09:35:05 +0000 Subject: [PATCH 01/30] Add shell.nix for nix users --- shell.nix | 12 ++++++++++++ 1 file changed, 12 insertions(+) create mode 100644 shell.nix diff --git a/shell.nix b/shell.nix new file mode 100644 index 0000000000..0e5b883036 --- /dev/null +++ b/shell.nix @@ -0,0 +1,12 @@ +{pkgs ? import {}}: + pkgs.mkShell { + packages = with pkgs; [ + nodejs-18_x + nodePackages.yarn + eslint_d + prettierd + jdk11 + (jdt-language-server.override { jdk = jdk11; }) + ]; + } + From b5260cae8f41a00c81d8da08c4747acf7eea222f Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 30 Jun 2024 09:35:34 +0000 Subject: [PATCH 02/30] Add VideoDecoderProperties for the web --- src/VideoDecoderProperties.web.ts | 45 +++++++++++++++++++++++++++++++ src/index.ts | 2 +- 2 files changed, 46 insertions(+), 1 deletion(-) create mode 100644 src/VideoDecoderProperties.web.ts diff --git a/src/VideoDecoderProperties.web.ts b/src/VideoDecoderProperties.web.ts new file mode 100644 index 0000000000..13001c0267 --- /dev/null +++ b/src/VideoDecoderProperties.web.ts @@ -0,0 +1,45 @@ +/// +import type {VideoDecoderPropertiesType} from './specs/VideoNativeComponent'; + +class VideoDecoderProperties implements VideoDecoderPropertiesType { + async getWidevineLevel() { + return 0; + } + + static canPlay(codec: string): boolean { + // most chrome based browser (and safari I think) supports matroska but reports they do not. + // for those browsers, only check the codecs and not the container. + if (navigator.userAgent.search('Firefox') === -1) { + codec = codec.replace('video/x-matroska', 'video/mp4'); + } + + // Find any video element that we could use to check, or create one that will + // instantly be garbage collected + const videos = document.getElementsByTagName('video'); + const video = videos.item(0) ?? document.createElement('video'); + + return !!video.canPlayType(codec); + } + + async isCodecSupported( + mimeType: string, + _width: number, + _height: number, + ): Promise<'unsupported' | 'hardware' | 'software'> { + // TODO: Figure out if we can get hardware support information + return VideoDecoderProperties.canPlay(mimeType) + ? 'software' + : 'unsupported'; + } + + async isHEVCSupported(): Promise<'unsupported' | 'hardware' | 'software'> { + // Just a dummy vidoe mime type codec with HEVC to check. + return VideoDecoderProperties.canPlay( + 'video/x-matroska; codecs="hvc1.1.4.L96.BO"', + ) + ? 'software' + : 'unsupported'; + } +} + +export default VideoDecoderProperties; diff --git a/src/index.ts b/src/index.ts index fc88496dbd..1057753e9c 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,6 @@ import Video from './Video'; export {VideoDecoderProperties} from './VideoDecoderProperties'; -export * from './types'; +export type * from './types'; export type {VideoRef} from './Video'; export {Video}; export default Video; From 8c723068bdfacce2dd948f4d343f9bd1d5eb03a7 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 30 Jun 2024 09:38:10 +0000 Subject: [PATCH 03/30] Move video ref type to its own file --- src/Video.tsx | 3 +-- src/index.ts | 2 -- src/specs/NativeVideoManager.ts | 5 +---- src/types/index.ts | 1 + src/types/video-ref.ts | 18 ++++++++++++++++++ 5 files changed, 21 insertions(+), 8 deletions(-) create mode 100644 src/types/video-ref.ts diff --git a/src/Video.tsx b/src/Video.tsx index 9571493f97..7d9b68269e 100644 --- a/src/Video.tsx +++ b/src/Video.tsx @@ -45,8 +45,7 @@ import { resolveAssetSourceForVideo, } from './utils'; import NativeVideoManager from './specs/NativeVideoManager'; -import type {VideoSaveData} from './specs/NativeVideoManager'; -import {CmcdMode, ViewType} from './types'; +import {ViewType, type VideoSaveData, CmcdMode} from './types'; import type { OnLoadData, OnTextTracksData, diff --git a/src/index.ts b/src/index.ts index 1057753e9c..73a4756cfb 100644 --- a/src/index.ts +++ b/src/index.ts @@ -1,6 +1,4 @@ import Video from './Video'; export {VideoDecoderProperties} from './VideoDecoderProperties'; export type * from './types'; -export type {VideoRef} from './Video'; export {Video}; -export default Video; diff --git a/src/specs/NativeVideoManager.ts b/src/specs/NativeVideoManager.ts index 3b1b261ef6..a7dde175ec 100644 --- a/src/specs/NativeVideoManager.ts +++ b/src/specs/NativeVideoManager.ts @@ -4,10 +4,7 @@ import type { Float, UnsafeObject, } from 'react-native/Libraries/Types/CodegenTypes'; - -export type VideoSaveData = { - uri: string; -}; +import type {VideoSaveData} from '../types/video-ref'; // @TODO rename to "Spec" when applying new arch export interface VideoManagerType { diff --git a/src/types/index.ts b/src/types/index.ts index ab7cf42db9..24725b6ce3 100644 --- a/src/types/index.ts +++ b/src/types/index.ts @@ -7,4 +7,5 @@ export {default as ResizeMode} from './ResizeMode'; export {default as TextTrackType} from './TextTrackType'; export {default as ViewType} from './ViewType'; export * from './video'; +export * from './video-ref'; export * from '../specs/VideoNativeComponent'; diff --git a/src/types/video-ref.ts b/src/types/video-ref.ts new file mode 100644 index 0000000000..aeadc7d69b --- /dev/null +++ b/src/types/video-ref.ts @@ -0,0 +1,18 @@ +export type VideoSaveData = { + uri: string; +}; + +export interface VideoRef { + seek: (time: number, tolerance?: number) => void; + resume: () => void; + pause: () => void; + presentFullscreenPlayer: () => void; + dismissFullscreenPlayer: () => void; + restoreUserInterfaceForPictureInPictureStopCompleted: ( + restore: boolean, + ) => void; + save: (options: object) => Promise; + setVolume: (volume: number) => void; + getCurrentPosition: () => Promise; + setFullScreen: (fullScreen: boolean) => void; +} From afa8c03f6372403901db7f25707bb11212084880 Mon Sep 17 00:00:00 2001 From: Zoe Roux Date: Sun, 30 Jun 2024 10:21:34 +0000 Subject: [PATCH 04/30] Create ref handling and basics stollen from Kyoo --- src/Video.web.tsx | 159 ++++++++++++++++++++++++++++++++++++++++++++++ 1 file changed, 159 insertions(+) create mode 100644 src/Video.web.tsx diff --git a/src/Video.web.tsx b/src/Video.web.tsx new file mode 100644 index 0000000000..ca93256999 --- /dev/null +++ b/src/Video.web.tsx @@ -0,0 +1,159 @@ +import React, { + forwardRef, + useCallback, + useEffect, + useImperativeHandle, + useRef, +} from 'react'; +import type {VideoRef, ReactVideoProps} from './types'; + +const Video = forwardRef( + ( + { + source, + paused, + muted, + volume, + onBuffer, + onLoad, + onProgress, + onError, + onEnd, + onPlaybackStateChanged, + }, + ref, + ) => { + const nativeRef = useRef(null); + const errorHandler = useRef(onError); + errorHandler.current = onError; + + const seek = useCallback(async (time: number, _tolerance?: number) => { + if (isNaN(time)) { + throw new Error('Specified time is not a number'); + } + if (!nativeRef.current) { + console.warn('Video Component is not mounted'); + return; + } + nativeRef.current.currentTime = time; + }, []); + + const pause = useCallback(() => { + if (!nativeRef.current) { + return; + } + nativeRef.current.pause(); + }, []); + + const resume = useCallback(() => { + if (!nativeRef.current) { + return; + } + nativeRef.current.play(); + }, []); + + const unsupported = useCallback(() => { + throw new Error('This is unsupported on the web'); + }, []); + + useImperativeHandle( + ref, + () => ({ + seek, + pause, + resume, + // making the video fullscreen does not work with some subtitles polyfils + // so I decided to not include it. + presentFullscreenPlayer: unsupported, + dismissFullscreenPlayer: unsupported, + save: unsupported, + restoreUserInterfaceForPictureInPictureStopCompleted: unsupported, + }), + [seek, pause, resume, unsupported], + ); + + useEffect(() => { + if (paused) { + pause(); + } else { + resume(); + } + }, [paused, pause, resume]); + useEffect(() => { + if (!nativeRef.current || !volume) { + return; + } + nativeRef.current.volume = Math.max(0, Math.min(volume, 100)) / 100; + }, [volume]); + + const setPlay = useSetAtom(playAtom); + useEffect(() => { + if (!nativeRef.current) return; + // Set play state to the player's value (if autoplay is denied) + setPlay(!nativeRef.current.paused); + }, [setPlay]); + + const setProgress = useSetAtom(progressAtom); + + return ( + <> + +