From d4f164868154eb3c21e0e3fcc5ea5e4d7a3c3768 Mon Sep 17 00:00:00 2001 From: Seyed Mostafa Hasani Date: Fri, 28 Jun 2024 13:03:10 +0330 Subject: [PATCH] refactor: basic example from class component to functional component (#3934) * refactor: basic example from class component to functional component * refactor: toast component path * refactor: bufferConfig prop * refacotr: import component path * fix: seekbar issue on iOS --- examples/basic/src/VideoPlayer.tsx | 856 +++--------------- examples/basic/src/VideoPlayer.windows.tsx | 2 +- examples/basic/src/assets/index.ts | 1 + .../src/{ => assets/videos}/broadchurch.mp4 | Bin examples/basic/src/assets/videos/index.ts | 4 + .../src/{ => assets/videos}/portrait.mp4 | Bin .../src/components/AudioTracksSelector.tsx | 4 +- examples/basic/src/components/Indicator.tsx | 22 + examples/basic/src/components/Overlay.tsx | 310 +++++++ examples/basic/src/components/Seeker.tsx | 4 +- .../src/components/TextTracksSelector.tsx | 4 +- examples/basic/src/components/Toast.ts | 18 + examples/basic/src/components/TopControl.tsx | 37 + .../src/components/VideoTracksSelector.tsx | 4 +- examples/basic/src/components/index.ts | 8 + examples/basic/src/constants/general.ts | 200 ++++ examples/basic/src/constants/index.ts | 1 + examples/basic/src/types/index.ts | 1 + examples/basic/src/types/types.ts | 49 + 19 files changed, 802 insertions(+), 723 deletions(-) create mode 100644 examples/basic/src/assets/index.ts rename examples/basic/src/{ => assets/videos}/broadchurch.mp4 (100%) create mode 100644 examples/basic/src/assets/videos/index.ts rename examples/basic/src/{ => assets/videos}/portrait.mp4 (100%) create mode 100644 examples/basic/src/components/Indicator.tsx create mode 100644 examples/basic/src/components/Overlay.tsx create mode 100644 examples/basic/src/components/Toast.ts create mode 100644 examples/basic/src/components/TopControl.tsx create mode 100644 examples/basic/src/components/index.ts create mode 100644 examples/basic/src/constants/general.ts create mode 100644 examples/basic/src/constants/index.ts create mode 100644 examples/basic/src/types/index.ts create mode 100644 examples/basic/src/types/types.ts diff --git a/examples/basic/src/VideoPlayer.tsx b/examples/basic/src/VideoPlayer.tsx index ba12f403..b6593302 100644 --- a/examples/basic/src/VideoPlayer.tsx +++ b/examples/basic/src/VideoPlayer.tsx @@ -1,16 +1,8 @@ 'use strict'; -import React, {Component} from 'react'; +import React, {FC, useRef, useState} from 'react'; -import { - Text, - TouchableOpacity, - View, - ActivityIndicator, - ToastAndroid, - Platform, - Alert, -} from 'react-native'; +import {TouchableOpacity, View} from 'react-native'; import Video, { AudioTrack, @@ -20,434 +12,169 @@ import Video, { OnTextTracksData, OnVideoAspectRatioData, TextTrack, - VideoDecoderProperties, OnBufferData, OnAudioFocusChangedData, OnVideoErrorData, VideoRef, - ResizeMode, - SelectedTrack, - DRMType, OnTextTrackDataChangedData, - TextTrackType, - ISO639_1, OnSeekData, OnPlaybackStateChangedData, OnPlaybackRateChangeData, OnVideoTracksData, - VideoTrack, SelectedVideoTrackType, - SelectedVideoTrack, BufferingStrategyType, ReactVideoSource, - Drm, - TextTracks, + SelectedTrackType, } from 'react-native-video'; -import ToggleControl from './ToggleControl'; -import MultiValueControl, { - MultiValueControlPropType, -} from './MultiValueControl'; import styles from './styles'; -import AudioTrackSelector from './components/AudioTracksSelector'; -import TextTrackSelector from './components/TextTracksSelector'; -import VideoTrackSelector from './components/VideoTracksSelector'; -import Seeker from './components/Seeker'; +import {AdditionalSourceInfo} from './types'; +import { + bufferConfig, + defaultValue, + srcList, + textTracksSelectionBy, +} from './constants'; +import {Overlay, toast} from './components'; -type AdditionnalSourceInfo = { - textTracks: TextTracks; - adTagUrl: string; - description: string; - drm: Drm; - noView: boolean; -}; +type Props = NonNullable; -type SampleVideoSource = ReactVideoSource | AdditionnalSourceInfo; +const VideoPlayer: FC = ({}) => { + const [state, setState] = useState(defaultValue); + const videoRef = useRef(null); + const viewStyle = state.fullscreen ? styles.fullScreen : styles.halfScreen; + const currentSrc = srcList[state.srcListId]; + const additional = currentSrc as AdditionalSourceInfo; -interface StateType { - rate: number; - volume: number; - muted: boolean; - resizeMode: ResizeMode; - duration: number; - currentTime: number; - videoWidth: number; - videoHeight: number; - paused: boolean; - fullscreen: true; - decoration: true; - isLoading: boolean; - audioTracks: Array; - textTracks: Array; - videoTracks: Array; - selectedAudioTrack: SelectedTrack | undefined; - selectedTextTrack: SelectedTrack | undefined; - selectedVideoTrack: SelectedVideoTrack; - srcListId: number; - loop: boolean; - showRNVControls: boolean; - useCache: boolean; - poster?: string; - showNotificationControls: boolean; - isSeeking: boolean; -} - -class VideoPlayer extends Component { - state: StateType = { - rate: 1, - volume: 1, - muted: false, - resizeMode: ResizeMode.CONTAIN, - duration: 0.0, - currentTime: 0.0, - videoWidth: 0, - videoHeight: 0, - paused: false, - fullscreen: true, - decoration: true, - isLoading: false, - audioTracks: [], - textTracks: [], - videoTracks: [], - selectedAudioTrack: undefined, - selectedTextTrack: undefined, - selectedVideoTrack: { - type: SelectedVideoTrackType.AUTO, - }, - srcListId: 0, - loop: false, - showRNVControls: false, - useCache: false, - poster: undefined, - showNotificationControls: false, - isSeeking: false, - }; - - // internal usage change to index if you want to select tracks by index instead of lang - textTracksSelectionBy = 'index'; - - srcAllPlatformList = [ - { - description: 'local file landscape', - uri: require('./broadchurch.mp4'), - }, - { - description: 'local file landscape cropped', - uri: require('./broadchurch.mp4'), - cropStart: 3000, - cropEnd: 10000, - }, - { - description: 'local file portrait', - uri: require('./portrait.mp4'), - metadata: { - title: 'Test Title', - subtitle: 'Test Subtitle', - artist: 'Test Artist', - description: 'Test Description', - imageUri: - 'https://pbs.twimg.com/profile_images/1498641868397191170/6qW2XkuI_400x400.png', - }, - }, - { - description: '(hls|live) red bull tv', - textTracksAllowChunklessPreparation: false, - uri: 'https://rbmn-live.akamaized.net/hls/live/590964/BoRB-AT/master_928.m3u8', - metadata: { - title: 'Custom Title', - subtitle: 'Custom Subtitle', - artist: 'Custom Artist', - description: 'Custom Description', - imageUri: - 'https://pbs.twimg.com/profile_images/1498641868397191170/6qW2XkuI_400x400.png', - }, - }, - { - description: 'invalid URL', - uri: 'mmt://www.youtube.com', - type: 'mpd', - }, - {description: '(no url) Stopped playback', uri: undefined}, - { - description: '(no view) no View', - noView: true, - }, - { - description: 'Another live sample', - uri: 'https://live.forstreet.cl/live/livestream.m3u8', - }, - { - description: 'another bunny (can be saved)', - uri: 'https://rawgit.com/mediaelement/mediaelement-files/master/big_buck_bunny.mp4', - headers: {referer: 'www.github.com', 'User-Agent': 'react.native.video'}, - }, - { - description: 'sintel with subtitles', - uri: 'https://bitmovin-a.akamaihd.net/content/sintel/hls/playlist.m3u8', - }, - { - description: 'sintel starts at 20sec', - uri: 'https://bitmovin-a.akamaihd.net/content/sintel/hls/playlist.m3u8', - startPosition: 50000, - }, - { - description: 'BigBugBunny sideLoaded subtitles', - // sideloaded subtitles wont work for streaming like HLS on ios - // mp4 - uri: 'https://d23dyxeqlo5psv.cloudfront.net/big_buck_bunny.mp4', - textTracks: [ - { - title: 'test', - language: 'en' as ISO639_1, - type: TextTrackType.VTT, - uri: 'https://bitdash-a.akamaihd.net/content/sintel/subtitles/subtitles_en.vtt', - }, - ], - }, - ]; - - srcIosList = []; - - srcAndroidList = [ - { - description: 'Another live sample', - uri: 'https://live.forstreet.cl/live/livestream.m3u8', - }, - { - description: 'asset file', - uri: 'asset:///broadchurch.mp4', - }, - { - description: '(dash) sintel subtitles', - uri: 'https://bitmovin-a.akamaihd.net/content/sintel/sintel.mpd', - }, - { - description: '(mp4) big buck bunny', - uri: 'http://d23dyxeqlo5psv.cloudfront.net/big_buck_bunny.mp4', - }, - { - description: '(mp4|subtitles) demo with sintel Subtitles', - uri: 'http://www.youtube.com/api/manifest/dash/id/bf5bb2419360daf1/source/youtube?as=fmp4_audio_clear,fmp4_sd_hd_clear&sparams=ip,ipbits,expire,source,id,as&ip=0.0.0.0&ipbits=0&expire=19000000000&signature=51AF5F39AB0CEC3E5497CD9C900EBFEAECCCB5C7.8506521BFC350652163895D4C26DEE124209AA9E&key=ik0', - type: 'mpd', - }, - { - description: '(mp4) big buck bunny With Ads', - adTagUrl: - 'https://pubads.g.doubleclick.net/gampad/ads?iu=/21775744923/external/vmap_ad_samples&sz=640x480&cust_params=sample_ar%3Dpremidpostoptimizedpodbumper&ciu_szs=300x250&gdfp_req=1&ad_rule=1&output=vmap&unviewed_position_start=1&env=vp&impl=s&cmsid=496&vid=short_onecue&correlator=', - uri: 'http://d23dyxeqlo5psv.cloudfront.net/big_buck_bunny.mp4', - }, - { - description: 'WV: Secure SD & HD (cbcs,MP4,H264)', - uri: 'https://storage.googleapis.com/wvmedia/cbcs/h264/tears/tears_aes_cbcs.mpd', - drm: { - type: DRMType.WIDEVINE, - licenseServer: - 'https://proxy.uat.widevine.com/proxy?provider=widevine_test', - }, - }, - { - description: 'Secure UHD (cenc)', - uri: 'https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears_uhd.mpd', - drm: { - type: DRMType.WIDEVINE, - licenseServer: - 'https://proxy.uat.widevine.com/proxy?provider=widevine_test', - }, - }, - { - description: 'rtsp big bug bunny', - uri: 'rtsp://rtspstream:3cfa3c36a9c00f4aa38f3cd35816b287@zephyr.rtsp.stream/movie', - type: 'rtsp', - }, - ]; - - // poster which can be displayed - samplePoster = - 'https://upload.wikimedia.org/wikipedia/commons/1/18/React_Native_Logo.png'; - - srcList: SampleVideoSource[] = this.srcAllPlatformList.concat( - Platform.OS === 'android' ? this.srcAndroidList : this.srcIosList, - ); - - video?: VideoRef; - - popupInfo = () => { - VideoDecoderProperties.getWidevineLevel().then((widevineLevel: number) => { - VideoDecoderProperties.isHEVCSupported().then((hevc: string) => { - VideoDecoderProperties.isCodecSupported('video/avc', 1920, 1080).then( - (avc: string) => { - this.toast( - true, - 'Widevine level: ' + - widevineLevel + - '\n hevc: ' + - hevc + - '\n avc: ' + - avc, - ); - }, - ); - }); - }); - }; - - onLoad = (data: OnLoadData) => { - this.setState({duration: data.duration, loading: false}); - this.onAudioTracks(data); - this.onTextTracks(data); - this.onVideoTracks(data); - }; - - onProgress = (data: OnProgressData) => { - this.setState({currentTime: data.currentTime}); - }; - - onSeek = (data: OnSeekData) => { - this.setState({isSeeking: false}); - this.setState({currentTime: data.currentTime}); - }; - - onVideoLoadStart = () => { - console.log('onVideoLoadStart'); - this.setState({isLoading: true}); - }; - - onAudioTracks = (data: OnAudioTracksData) => { + const onAudioTracks = (data: OnAudioTracksData) => { const selectedTrack = data.audioTracks?.find((x: AudioTrack) => { return x.selected; }); if (selectedTrack?.index) { - this.setState({ + setState({ + ...state, audioTracks: data.audioTracks, selectedAudioTrack: { - type: SelectedVideoTrackType.INDEX, + type: SelectedTrackType.INDEX, value: selectedTrack?.index, }, }); } else { - this.setState({ + setState({ + ...state, audioTracks: data.audioTracks, }); } }; - onVideoTracks = (data: OnVideoTracksData) => { + const onVideoTracks = (data: OnVideoTracksData) => { console.log('onVideoTracks', data.videoTracks); - this.setState({ + setState({ + ...state, videoTracks: data.videoTracks, }); }; - onTextTracks = (data: OnTextTracksData) => { + const onTextTracks = (data: OnTextTracksData) => { const selectedTrack = data.textTracks?.find((x: TextTrack) => { return x?.selected; }); if (selectedTrack?.language) { - this.setState({ + setState({ + ...state, textTracks: data.textTracks, selectedTextTrack: - this.textTracksSelectionBy === 'index' + textTracksSelectionBy === 'index' ? { - type: 'index', + type: SelectedTrackType.INDEX, value: selectedTrack?.index, } : { - type: 'language', + type: SelectedTrackType.LANGUAGE, value: selectedTrack?.language, }, }); } else { - this.setState({ + setState({ + ...state, textTracks: data.textTracks, }); } }; - onTextTrackDataChanged = (data: OnTextTrackDataChangedData) => { + const onLoad = (data: OnLoadData) => { + onAudioTracks(data); + onTextTracks(data); + onVideoTracks(data); + setState({...state, duration: data.duration}); + }; + + const onProgress = (data: OnProgressData) => { + setState({...state, currentTime: data.currentTime}); + }; + + const onSeek = (data: OnSeekData) => { + setState({...state, currentTime: data.currentTime, isSeeking: false}); + }; + + const onVideoLoadStart = () => { + console.log('onVideoLoadStart'); + setState({...state, isLoading: true}); + }; + + const onTextTrackDataChanged = (data: OnTextTrackDataChangedData) => { console.log(`Subtitles: ${JSON.stringify(data, null, 2)}`); }; - onAspectRatio = (data: OnVideoAspectRatioData) => { + const onAspectRatio = (data: OnVideoAspectRatioData) => { console.log('onAspectRadio called ' + JSON.stringify(data)); - this.setState({ + setState({ + ...state, videoWidth: data.width, videoHeight: data.height, }); }; - onVideoBuffer = (param: OnBufferData) => { + const onVideoBuffer = (param: OnBufferData) => { console.log('onVideoBuffer'); - this.setState({isLoading: param.isBuffering}); + setState({...state, isLoading: param.isBuffering}); }; - onReadyForDisplay = () => { + const onReadyForDisplay = () => { console.log('onReadyForDisplay'); - this.setState({isLoading: false}); + setState({...state, isLoading: false}); }; - onAudioBecomingNoisy = () => { - this.setState({paused: true}); + const onAudioBecomingNoisy = () => { + setState({...state, paused: true}); }; - onAudioFocusChanged = (event: OnAudioFocusChangedData) => { - this.setState({paused: !event.hasAudioFocus}); + const onAudioFocusChanged = (event: OnAudioFocusChangedData) => { + setState({...state, paused: !event.hasAudioFocus}); }; - toast = (visible: boolean, message: string) => { - if (visible) { - if (Platform.OS === 'android') { - ToastAndroid.showWithGravityAndOffset( - message, - ToastAndroid.LONG, - ToastAndroid.BOTTOM, - 25, - 50, - ); - } else { - Alert.alert(message, message); - } - } - }; - - onError = (err: OnVideoErrorData) => { + const onError = (err: OnVideoErrorData) => { console.log(JSON.stringify(err)); - this.toast(true, 'error: ' + JSON.stringify(err)); + toast(true, 'error: ' + JSON.stringify(err)); }; - onEnd = () => { - if (!this.state.loop) { - this.channelUp(); + const onEnd = () => { + if (!state.loop) { + channelUp(); } }; - onPlaybackRateChange = (data: OnPlaybackRateChangeData) => { + const onPlaybackRateChange = (data: OnPlaybackRateChangeData) => { console.log('onPlaybackRateChange', data); }; - onPlaybackStateChanged = (data: OnPlaybackStateChangedData) => { + const onPlaybackStateChanged = (data: OnPlaybackStateChangedData) => { console.log('onPlaybackStateChanged', data); }; - toggleFullscreen() { - this.setState({fullscreen: !this.state.fullscreen}); - } - toggleControls() { - this.setState({showRNVControls: !this.state.showRNVControls}); - } - - toggleDecoration() { - this.setState({decoration: !this.state.decoration}); - this.video?.setFullScreen(!this.state.decoration); - } - - toggleShowNotificationControls() { - this.setState({ - showNotificationControls: !this.state.showNotificationControls, - }); - } - - goToChannel(channel: number) { - this.setState({ + const goToChannel = (channel: number) => { + setState({ + ...state, srcListId: channel, duration: 0.0, currentTime: 0.0, @@ -462,370 +189,79 @@ class VideoPlayer extends Component { type: SelectedVideoTrackType.AUTO, }, }); - } + }; - channelUp() { + const channelUp = () => { console.log('channel up'); - this.goToChannel((this.state.srcListId + 1) % this.srcList.length); - } + goToChannel((state.srcListId + 1) % srcList.length); + }; - channelDown() { + const channelDown = () => { console.log('channel down'); - this.goToChannel( - (this.state.srcListId + this.srcList.length - 1) % this.srcList.length, - ); - } + goToChannel((state.srcListId + srcList.length - 1) % srcList.length); + }; - videoSeek(position: number) { - this.setState({isSeeking: true}); - this.video?.seek(position); - } - - renderSeekBar() { - return ( - this.videoSeek(prop)} - isUISeeking={this.state.isSeeking} + return ( + + {(srcList[state.srcListId] as AdditionalSourceInfo)?.noView ? null : ( + + + )} + - ); - } - - IndicatorLoadingView() { - if (this.state.isLoading) { - return ( - - ); - } else { - return ; - } - } - - renderTopControl() { - return ( - - - {(this.srcList[this.state.srcListId] as AdditionnalSourceInfo) - ?.description || 'local file'} - - - { - this.toggleControls(); - }}> - - {this.state.showRNVControls ? 'Hide controls' : 'Show controls'} - - - - - ); - } - - onRateSelected = (value: MultiValueControlPropType) => { - this.setState({rate: value}); - }; - onVolumeSelected = (value: MultiValueControlPropType) => { - this.setState({volume: value}); - }; - onResizeModeSelected = (value: MultiValueControlPropType) => { - this.setState({resizeMode: value}); - }; - - onSelectedAudioTrackChange = (itemValue: string) => { - console.log('on audio value change ' + itemValue); - if (itemValue === 'none') { - this.setState({ - selectedAudioTrack: SelectedVideoTrackType.DISABLED, - }); - } else { - this.setState({ - selectedAudioTrack: { - type: SelectedVideoTrackType.INDEX, - value: itemValue, - }, - }); - } - }; - - onSelectedTextTrackChange = (itemValue: string) => { - console.log('on value change ' + itemValue); - this.setState({ - selectedTextTrack: { - type: this.textTracksSelectionBy === 'index' ? 'index' : 'language', - value: itemValue, - }, - }); - }; - - onSelectedVideoTrackChange = (itemValue: string) => { - console.log('on value change ' + itemValue); - if (itemValue === undefined || itemValue === 'auto') { - this.setState({ - selectedVideoTrack: { - type: SelectedVideoTrackType.AUTO, - }, - }); - } else { - this.setState({ - selectedVideoTrack: { - type: SelectedVideoTrackType.INDEX, - value: itemValue, - }, - }); - } - }; - - renderOverlay() { - return ( - <> - {this.IndicatorLoadingView()} - - - {this.renderTopControl()} - - - {!this.state.showRNVControls ? ( - <> - - { - this.channelDown(); - }} - text="ChDown" - /> - - - { - this.channelUp(); - }} - text="ChUp" - /> - - - - {Platform.OS === 'android' ? ( - - { - this.popupInfo(); - }} - text="decoderInfo" - /> - { - this.setState({useCache: !this.state.useCache}); - }} - selectedText="enable cache" - unselectedText="disable cache" - /> - - ) : null} - { - this.setState({paused: !this.state.paused}); - }} - selectedText="pause" - unselectedText="playing" - /> - { - this.setState({loop: !this.state.loop}); - }} - selectedText="loop enable" - unselectedText="loop disable" - /> - { - this.toggleFullscreen(); - }} - text="fullscreen" - /> - { - this.toggleDecoration(); - }} - text="decoration" - /> - { - this.setState({ - poster: this.state.poster ? undefined : this.samplePoster, - }); - }} - selectedText="poster" - unselectedText="no poster" - /> - { - this.toggleShowNotificationControls(); - }} - selectedText="hide notification controls" - unselectedText="show notification controls" - /> - - - {/* shall be replaced by slider */} - - {/* shall be replaced by slider */} - - - { - this.setState({muted: !this.state.muted}); - }} - text="muted" - /> - {Platform.OS === 'ios' ? ( - { - this.video - ?.save({}) - ?.then(response => { - console.log('Downloaded URI', response); - }) - .catch(error => { - console.log('error during save ', error); - }); - }} - text="save" - /> - ) : null} - - {this.renderSeekBar()} - - - - - - - - ) : null} - - ); - } - - renderVideoView() { - const viewStyle = this.state.fullscreen - ? styles.fullScreen - : styles.halfScreen; - - const currentSrc = this.srcList[this.state.srcListId]; - const additionnal = currentSrc as AdditionnalSourceInfo; - - return ( - - - ); - } - - render() { - return ( - - {(this.srcList[this.state.srcListId] as AdditionnalSourceInfo)?.noView - ? null - : this.renderVideoView()} - {this.renderOverlay()} - - ); - } -} + + ); +}; export default VideoPlayer; diff --git a/examples/basic/src/VideoPlayer.windows.tsx b/examples/basic/src/VideoPlayer.windows.tsx index f39f177b..5c66eeba 100644 --- a/examples/basic/src/VideoPlayer.windows.tsx +++ b/examples/basic/src/VideoPlayer.windows.tsx @@ -108,7 +108,7 @@ class VideoPlayer extends Component { this.setState({paused: !this.state.paused}); }}>