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
This commit is contained in:
parent
b431d09e2f
commit
d4f1648681
@ -1,16 +1,8 @@
|
|||||||
'use strict';
|
'use strict';
|
||||||
|
|
||||||
import React, {Component} from 'react';
|
import React, {FC, useRef, useState} from 'react';
|
||||||
|
|
||||||
import {
|
import {TouchableOpacity, View} from 'react-native';
|
||||||
Text,
|
|
||||||
TouchableOpacity,
|
|
||||||
View,
|
|
||||||
ActivityIndicator,
|
|
||||||
ToastAndroid,
|
|
||||||
Platform,
|
|
||||||
Alert,
|
|
||||||
} from 'react-native';
|
|
||||||
|
|
||||||
import Video, {
|
import Video, {
|
||||||
AudioTrack,
|
AudioTrack,
|
||||||
@ -20,434 +12,169 @@ import Video, {
|
|||||||
OnTextTracksData,
|
OnTextTracksData,
|
||||||
OnVideoAspectRatioData,
|
OnVideoAspectRatioData,
|
||||||
TextTrack,
|
TextTrack,
|
||||||
VideoDecoderProperties,
|
|
||||||
OnBufferData,
|
OnBufferData,
|
||||||
OnAudioFocusChangedData,
|
OnAudioFocusChangedData,
|
||||||
OnVideoErrorData,
|
OnVideoErrorData,
|
||||||
VideoRef,
|
VideoRef,
|
||||||
ResizeMode,
|
|
||||||
SelectedTrack,
|
|
||||||
DRMType,
|
|
||||||
OnTextTrackDataChangedData,
|
OnTextTrackDataChangedData,
|
||||||
TextTrackType,
|
|
||||||
ISO639_1,
|
|
||||||
OnSeekData,
|
OnSeekData,
|
||||||
OnPlaybackStateChangedData,
|
OnPlaybackStateChangedData,
|
||||||
OnPlaybackRateChangeData,
|
OnPlaybackRateChangeData,
|
||||||
OnVideoTracksData,
|
OnVideoTracksData,
|
||||||
VideoTrack,
|
|
||||||
SelectedVideoTrackType,
|
SelectedVideoTrackType,
|
||||||
SelectedVideoTrack,
|
|
||||||
BufferingStrategyType,
|
BufferingStrategyType,
|
||||||
ReactVideoSource,
|
ReactVideoSource,
|
||||||
Drm,
|
SelectedTrackType,
|
||||||
TextTracks,
|
|
||||||
} from 'react-native-video';
|
} from 'react-native-video';
|
||||||
import ToggleControl from './ToggleControl';
|
|
||||||
import MultiValueControl, {
|
|
||||||
MultiValueControlPropType,
|
|
||||||
} from './MultiValueControl';
|
|
||||||
import styles from './styles';
|
import styles from './styles';
|
||||||
import AudioTrackSelector from './components/AudioTracksSelector';
|
import {AdditionalSourceInfo} from './types';
|
||||||
import TextTrackSelector from './components/TextTracksSelector';
|
import {
|
||||||
import VideoTrackSelector from './components/VideoTracksSelector';
|
bufferConfig,
|
||||||
import Seeker from './components/Seeker';
|
defaultValue,
|
||||||
|
srcList,
|
||||||
|
textTracksSelectionBy,
|
||||||
|
} from './constants';
|
||||||
|
import {Overlay, toast} from './components';
|
||||||
|
|
||||||
type AdditionnalSourceInfo = {
|
type Props = NonNullable<unknown>;
|
||||||
textTracks: TextTracks;
|
|
||||||
adTagUrl: string;
|
|
||||||
description: string;
|
|
||||||
drm: Drm;
|
|
||||||
noView: boolean;
|
|
||||||
};
|
|
||||||
|
|
||||||
type SampleVideoSource = ReactVideoSource | AdditionnalSourceInfo;
|
const VideoPlayer: FC<Props> = ({}) => {
|
||||||
|
const [state, setState] = useState(defaultValue);
|
||||||
|
const videoRef = useRef<VideoRef>(null);
|
||||||
|
const viewStyle = state.fullscreen ? styles.fullScreen : styles.halfScreen;
|
||||||
|
const currentSrc = srcList[state.srcListId];
|
||||||
|
const additional = currentSrc as AdditionalSourceInfo;
|
||||||
|
|
||||||
interface StateType {
|
const onAudioTracks = (data: OnAudioTracksData) => {
|
||||||
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<AudioTrack>;
|
|
||||||
textTracks: Array<TextTrack>;
|
|
||||||
videoTracks: Array<VideoTrack>;
|
|
||||||
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 selectedTrack = data.audioTracks?.find((x: AudioTrack) => {
|
const selectedTrack = data.audioTracks?.find((x: AudioTrack) => {
|
||||||
return x.selected;
|
return x.selected;
|
||||||
});
|
});
|
||||||
if (selectedTrack?.index) {
|
if (selectedTrack?.index) {
|
||||||
this.setState({
|
setState({
|
||||||
|
...state,
|
||||||
audioTracks: data.audioTracks,
|
audioTracks: data.audioTracks,
|
||||||
selectedAudioTrack: {
|
selectedAudioTrack: {
|
||||||
type: SelectedVideoTrackType.INDEX,
|
type: SelectedTrackType.INDEX,
|
||||||
value: selectedTrack?.index,
|
value: selectedTrack?.index,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.setState({
|
setState({
|
||||||
|
...state,
|
||||||
audioTracks: data.audioTracks,
|
audioTracks: data.audioTracks,
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onVideoTracks = (data: OnVideoTracksData) => {
|
const onVideoTracks = (data: OnVideoTracksData) => {
|
||||||
console.log('onVideoTracks', data.videoTracks);
|
console.log('onVideoTracks', data.videoTracks);
|
||||||
this.setState({
|
setState({
|
||||||
|
...state,
|
||||||
videoTracks: data.videoTracks,
|
videoTracks: data.videoTracks,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
onTextTracks = (data: OnTextTracksData) => {
|
const onTextTracks = (data: OnTextTracksData) => {
|
||||||
const selectedTrack = data.textTracks?.find((x: TextTrack) => {
|
const selectedTrack = data.textTracks?.find((x: TextTrack) => {
|
||||||
return x?.selected;
|
return x?.selected;
|
||||||
});
|
});
|
||||||
|
|
||||||
if (selectedTrack?.language) {
|
if (selectedTrack?.language) {
|
||||||
this.setState({
|
setState({
|
||||||
|
...state,
|
||||||
textTracks: data.textTracks,
|
textTracks: data.textTracks,
|
||||||
selectedTextTrack:
|
selectedTextTrack:
|
||||||
this.textTracksSelectionBy === 'index'
|
textTracksSelectionBy === 'index'
|
||||||
? {
|
? {
|
||||||
type: 'index',
|
type: SelectedTrackType.INDEX,
|
||||||
value: selectedTrack?.index,
|
value: selectedTrack?.index,
|
||||||
}
|
}
|
||||||
: {
|
: {
|
||||||
type: 'language',
|
type: SelectedTrackType.LANGUAGE,
|
||||||
value: selectedTrack?.language,
|
value: selectedTrack?.language,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
} else {
|
} else {
|
||||||
this.setState({
|
setState({
|
||||||
|
...state,
|
||||||
textTracks: data.textTracks,
|
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)}`);
|
console.log(`Subtitles: ${JSON.stringify(data, null, 2)}`);
|
||||||
};
|
};
|
||||||
|
|
||||||
onAspectRatio = (data: OnVideoAspectRatioData) => {
|
const onAspectRatio = (data: OnVideoAspectRatioData) => {
|
||||||
console.log('onAspectRadio called ' + JSON.stringify(data));
|
console.log('onAspectRadio called ' + JSON.stringify(data));
|
||||||
this.setState({
|
setState({
|
||||||
|
...state,
|
||||||
videoWidth: data.width,
|
videoWidth: data.width,
|
||||||
videoHeight: data.height,
|
videoHeight: data.height,
|
||||||
});
|
});
|
||||||
};
|
};
|
||||||
|
|
||||||
onVideoBuffer = (param: OnBufferData) => {
|
const onVideoBuffer = (param: OnBufferData) => {
|
||||||
console.log('onVideoBuffer');
|
console.log('onVideoBuffer');
|
||||||
this.setState({isLoading: param.isBuffering});
|
setState({...state, isLoading: param.isBuffering});
|
||||||
};
|
};
|
||||||
|
|
||||||
onReadyForDisplay = () => {
|
const onReadyForDisplay = () => {
|
||||||
console.log('onReadyForDisplay');
|
console.log('onReadyForDisplay');
|
||||||
this.setState({isLoading: false});
|
setState({...state, isLoading: false});
|
||||||
};
|
};
|
||||||
|
|
||||||
onAudioBecomingNoisy = () => {
|
const onAudioBecomingNoisy = () => {
|
||||||
this.setState({paused: true});
|
setState({...state, paused: true});
|
||||||
};
|
};
|
||||||
|
|
||||||
onAudioFocusChanged = (event: OnAudioFocusChangedData) => {
|
const onAudioFocusChanged = (event: OnAudioFocusChangedData) => {
|
||||||
this.setState({paused: !event.hasAudioFocus});
|
setState({...state, paused: !event.hasAudioFocus});
|
||||||
};
|
};
|
||||||
|
|
||||||
toast = (visible: boolean, message: string) => {
|
const onError = (err: OnVideoErrorData) => {
|
||||||
if (visible) {
|
|
||||||
if (Platform.OS === 'android') {
|
|
||||||
ToastAndroid.showWithGravityAndOffset(
|
|
||||||
message,
|
|
||||||
ToastAndroid.LONG,
|
|
||||||
ToastAndroid.BOTTOM,
|
|
||||||
25,
|
|
||||||
50,
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
Alert.alert(message, message);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
};
|
|
||||||
|
|
||||||
onError = (err: OnVideoErrorData) => {
|
|
||||||
console.log(JSON.stringify(err));
|
console.log(JSON.stringify(err));
|
||||||
this.toast(true, 'error: ' + JSON.stringify(err));
|
toast(true, 'error: ' + JSON.stringify(err));
|
||||||
};
|
};
|
||||||
|
|
||||||
onEnd = () => {
|
const onEnd = () => {
|
||||||
if (!this.state.loop) {
|
if (!state.loop) {
|
||||||
this.channelUp();
|
channelUp();
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
onPlaybackRateChange = (data: OnPlaybackRateChangeData) => {
|
const onPlaybackRateChange = (data: OnPlaybackRateChangeData) => {
|
||||||
console.log('onPlaybackRateChange', data);
|
console.log('onPlaybackRateChange', data);
|
||||||
};
|
};
|
||||||
|
|
||||||
onPlaybackStateChanged = (data: OnPlaybackStateChangedData) => {
|
const onPlaybackStateChanged = (data: OnPlaybackStateChangedData) => {
|
||||||
console.log('onPlaybackStateChanged', data);
|
console.log('onPlaybackStateChanged', data);
|
||||||
};
|
};
|
||||||
|
|
||||||
toggleFullscreen() {
|
const goToChannel = (channel: number) => {
|
||||||
this.setState({fullscreen: !this.state.fullscreen});
|
setState({
|
||||||
}
|
...state,
|
||||||
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({
|
|
||||||
srcListId: channel,
|
srcListId: channel,
|
||||||
duration: 0.0,
|
duration: 0.0,
|
||||||
currentTime: 0.0,
|
currentTime: 0.0,
|
||||||
@ -462,370 +189,79 @@ class VideoPlayer extends Component {
|
|||||||
type: SelectedVideoTrackType.AUTO,
|
type: SelectedVideoTrackType.AUTO,
|
||||||
},
|
},
|
||||||
});
|
});
|
||||||
}
|
};
|
||||||
|
|
||||||
channelUp() {
|
const channelUp = () => {
|
||||||
console.log('channel up');
|
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');
|
console.log('channel down');
|
||||||
this.goToChannel(
|
goToChannel((state.srcListId + srcList.length - 1) % srcList.length);
|
||||||
(this.state.srcListId + this.srcList.length - 1) % this.srcList.length,
|
};
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
videoSeek(position: number) {
|
return (
|
||||||
this.setState({isSeeking: true});
|
<View style={styles.container}>
|
||||||
this.video?.seek(position);
|
{(srcList[state.srcListId] as AdditionalSourceInfo)?.noView ? null : (
|
||||||
}
|
<TouchableOpacity style={viewStyle}>
|
||||||
|
<Video
|
||||||
renderSeekBar() {
|
showNotificationControls={state.showNotificationControls}
|
||||||
return (
|
ref={videoRef}
|
||||||
<Seeker
|
source={currentSrc as ReactVideoSource}
|
||||||
currentTime={this.state.currentTime}
|
textTracks={additional?.textTracks}
|
||||||
duration={this.state.duration}
|
adTagUrl={additional?.adTagUrl}
|
||||||
isLoading={this.state.isLoading}
|
drm={additional?.drm}
|
||||||
videoSeek={prop => this.videoSeek(prop)}
|
style={viewStyle}
|
||||||
isUISeeking={this.state.isSeeking}
|
rate={state.rate}
|
||||||
|
paused={state.paused}
|
||||||
|
volume={state.volume}
|
||||||
|
muted={state.muted}
|
||||||
|
fullscreen={state.fullscreen}
|
||||||
|
controls={state.showRNVControls}
|
||||||
|
resizeMode={state.resizeMode}
|
||||||
|
onLoad={onLoad}
|
||||||
|
onAudioTracks={onAudioTracks}
|
||||||
|
onTextTracks={onTextTracks}
|
||||||
|
onVideoTracks={onVideoTracks}
|
||||||
|
onTextTrackDataChanged={onTextTrackDataChanged}
|
||||||
|
onProgress={onProgress}
|
||||||
|
onEnd={onEnd}
|
||||||
|
progressUpdateInterval={1000}
|
||||||
|
onError={onError}
|
||||||
|
onAudioBecomingNoisy={onAudioBecomingNoisy}
|
||||||
|
onAudioFocusChanged={onAudioFocusChanged}
|
||||||
|
onLoadStart={onVideoLoadStart}
|
||||||
|
onAspectRatio={onAspectRatio}
|
||||||
|
onReadyForDisplay={onReadyForDisplay}
|
||||||
|
onBuffer={onVideoBuffer}
|
||||||
|
onSeek={onSeek}
|
||||||
|
repeat={state.loop}
|
||||||
|
selectedTextTrack={state.selectedTextTrack}
|
||||||
|
selectedAudioTrack={state.selectedAudioTrack}
|
||||||
|
selectedVideoTrack={state.selectedVideoTrack}
|
||||||
|
playInBackground={false}
|
||||||
|
bufferConfig={{
|
||||||
|
...bufferConfig,
|
||||||
|
cacheSizeMB: state.useCache ? 200 : 0,
|
||||||
|
}}
|
||||||
|
preventsDisplaySleepDuringVideoPlayback={true}
|
||||||
|
poster={state.poster}
|
||||||
|
onPlaybackRateChange={onPlaybackRateChange}
|
||||||
|
onPlaybackStateChanged={onPlaybackStateChanged}
|
||||||
|
bufferingStrategy={BufferingStrategyType.DEFAULT}
|
||||||
|
debug={{enable: true, thread: true}}
|
||||||
|
/>
|
||||||
|
</TouchableOpacity>
|
||||||
|
)}
|
||||||
|
<Overlay
|
||||||
|
channelDown={channelDown}
|
||||||
|
channelUp={channelUp}
|
||||||
|
setState={setState}
|
||||||
|
state={state}
|
||||||
|
ref={videoRef}
|
||||||
/>
|
/>
|
||||||
);
|
</View>
|
||||||
}
|
);
|
||||||
|
};
|
||||||
IndicatorLoadingView() {
|
|
||||||
if (this.state.isLoading) {
|
|
||||||
return (
|
|
||||||
<ActivityIndicator
|
|
||||||
color="#3235fd"
|
|
||||||
size="large"
|
|
||||||
style={styles.IndicatorStyle}
|
|
||||||
/>
|
|
||||||
);
|
|
||||||
} else {
|
|
||||||
return <View />;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
renderTopControl() {
|
|
||||||
return (
|
|
||||||
<View style={styles.topControlsContainer}>
|
|
||||||
<Text style={styles.controlOption}>
|
|
||||||
{(this.srcList[this.state.srcListId] as AdditionnalSourceInfo)
|
|
||||||
?.description || 'local file'}
|
|
||||||
</Text>
|
|
||||||
<View>
|
|
||||||
<TouchableOpacity
|
|
||||||
onPress={() => {
|
|
||||||
this.toggleControls();
|
|
||||||
}}>
|
|
||||||
<Text style={styles.leftRightControlOption}>
|
|
||||||
{this.state.showRNVControls ? 'Hide controls' : 'Show controls'}
|
|
||||||
</Text>
|
|
||||||
</TouchableOpacity>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
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()}
|
|
||||||
<View style={styles.topControls}>
|
|
||||||
<View style={styles.resizeModeControl}>
|
|
||||||
{this.renderTopControl()}
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
{!this.state.showRNVControls ? (
|
|
||||||
<>
|
|
||||||
<View style={styles.leftControls}>
|
|
||||||
<ToggleControl
|
|
||||||
onPress={() => {
|
|
||||||
this.channelDown();
|
|
||||||
}}
|
|
||||||
text="ChDown"
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<View style={styles.rightControls}>
|
|
||||||
<ToggleControl
|
|
||||||
onPress={() => {
|
|
||||||
this.channelUp();
|
|
||||||
}}
|
|
||||||
text="ChUp"
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<View style={styles.bottomControls}>
|
|
||||||
<View style={styles.generalControls}>
|
|
||||||
{Platform.OS === 'android' ? (
|
|
||||||
<View style={styles.generalControls}>
|
|
||||||
<ToggleControl
|
|
||||||
onPress={() => {
|
|
||||||
this.popupInfo();
|
|
||||||
}}
|
|
||||||
text="decoderInfo"
|
|
||||||
/>
|
|
||||||
<ToggleControl
|
|
||||||
isSelected={this.state.useCache}
|
|
||||||
onPress={() => {
|
|
||||||
this.setState({useCache: !this.state.useCache});
|
|
||||||
}}
|
|
||||||
selectedText="enable cache"
|
|
||||||
unselectedText="disable cache"
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
) : null}
|
|
||||||
<ToggleControl
|
|
||||||
isSelected={this.state.paused}
|
|
||||||
onPress={() => {
|
|
||||||
this.setState({paused: !this.state.paused});
|
|
||||||
}}
|
|
||||||
selectedText="pause"
|
|
||||||
unselectedText="playing"
|
|
||||||
/>
|
|
||||||
<ToggleControl
|
|
||||||
isSelected={this.state.loop}
|
|
||||||
onPress={() => {
|
|
||||||
this.setState({loop: !this.state.loop});
|
|
||||||
}}
|
|
||||||
selectedText="loop enable"
|
|
||||||
unselectedText="loop disable"
|
|
||||||
/>
|
|
||||||
<ToggleControl
|
|
||||||
onPress={() => {
|
|
||||||
this.toggleFullscreen();
|
|
||||||
}}
|
|
||||||
text="fullscreen"
|
|
||||||
/>
|
|
||||||
<ToggleControl
|
|
||||||
onPress={() => {
|
|
||||||
this.toggleDecoration();
|
|
||||||
}}
|
|
||||||
text="decoration"
|
|
||||||
/>
|
|
||||||
<ToggleControl
|
|
||||||
isSelected={!!this.state.poster}
|
|
||||||
onPress={() => {
|
|
||||||
this.setState({
|
|
||||||
poster: this.state.poster ? undefined : this.samplePoster,
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
selectedText="poster"
|
|
||||||
unselectedText="no poster"
|
|
||||||
/>
|
|
||||||
<ToggleControl
|
|
||||||
isSelected={this.state.showNotificationControls}
|
|
||||||
onPress={() => {
|
|
||||||
this.toggleShowNotificationControls();
|
|
||||||
}}
|
|
||||||
selectedText="hide notification controls"
|
|
||||||
unselectedText="show notification controls"
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
<View style={styles.generalControls}>
|
|
||||||
{/* shall be replaced by slider */}
|
|
||||||
<MultiValueControl
|
|
||||||
values={[0, 0.25, 0.5, 1.0, 1.5, 2.0]}
|
|
||||||
onPress={this.onRateSelected}
|
|
||||||
selected={this.state.rate}
|
|
||||||
/>
|
|
||||||
{/* shall be replaced by slider */}
|
|
||||||
<MultiValueControl
|
|
||||||
values={[0.5, 1, 1.5]}
|
|
||||||
onPress={this.onVolumeSelected}
|
|
||||||
selected={this.state.volume}
|
|
||||||
/>
|
|
||||||
<MultiValueControl
|
|
||||||
values={[
|
|
||||||
ResizeMode.COVER,
|
|
||||||
ResizeMode.CONTAIN,
|
|
||||||
ResizeMode.STRETCH,
|
|
||||||
]}
|
|
||||||
onPress={this.onResizeModeSelected}
|
|
||||||
selected={this.state.resizeMode}
|
|
||||||
/>
|
|
||||||
<ToggleControl
|
|
||||||
isSelected={this.state.muted}
|
|
||||||
onPress={() => {
|
|
||||||
this.setState({muted: !this.state.muted});
|
|
||||||
}}
|
|
||||||
text="muted"
|
|
||||||
/>
|
|
||||||
{Platform.OS === 'ios' ? (
|
|
||||||
<ToggleControl
|
|
||||||
isSelected={this.state.paused}
|
|
||||||
onPress={() => {
|
|
||||||
this.video
|
|
||||||
?.save({})
|
|
||||||
?.then(response => {
|
|
||||||
console.log('Downloaded URI', response);
|
|
||||||
})
|
|
||||||
.catch(error => {
|
|
||||||
console.log('error during save ', error);
|
|
||||||
});
|
|
||||||
}}
|
|
||||||
text="save"
|
|
||||||
/>
|
|
||||||
) : null}
|
|
||||||
</View>
|
|
||||||
{this.renderSeekBar()}
|
|
||||||
<View style={styles.generalControls}>
|
|
||||||
<AudioTrackSelector
|
|
||||||
audioTracks={this.state.audioTracks}
|
|
||||||
selectedAudioTrack={this.state.selectedAudioTrack}
|
|
||||||
onValueChange={this.onSelectedAudioTrackChange}
|
|
||||||
/>
|
|
||||||
<TextTrackSelector
|
|
||||||
textTracks={this.state.textTracks}
|
|
||||||
selectedTextTrack={this.state.selectedTextTrack}
|
|
||||||
onValueChange={this.onSelectedTextTrackChange}
|
|
||||||
textTracksSelectionBy={this.textTracksSelectionBy}
|
|
||||||
/>
|
|
||||||
<VideoTrackSelector
|
|
||||||
videoTracks={this.state.videoTracks}
|
|
||||||
selectedVideoTrack={this.state.selectedVideoTrack}
|
|
||||||
onValueChange={this.onSelectedVideoTrackChange}
|
|
||||||
/>
|
|
||||||
</View>
|
|
||||||
</View>
|
|
||||||
</>
|
|
||||||
) : null}
|
|
||||||
</>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
renderVideoView() {
|
|
||||||
const viewStyle = this.state.fullscreen
|
|
||||||
? styles.fullScreen
|
|
||||||
: styles.halfScreen;
|
|
||||||
|
|
||||||
const currentSrc = this.srcList[this.state.srcListId];
|
|
||||||
const additionnal = currentSrc as AdditionnalSourceInfo;
|
|
||||||
|
|
||||||
return (
|
|
||||||
<TouchableOpacity style={viewStyle}>
|
|
||||||
<Video
|
|
||||||
showNotificationControls={this.state.showNotificationControls}
|
|
||||||
ref={(ref: VideoRef) => {
|
|
||||||
this.video = ref;
|
|
||||||
}}
|
|
||||||
source={currentSrc as ReactVideoSource}
|
|
||||||
textTracks={additionnal?.textTracks}
|
|
||||||
adTagUrl={additionnal?.adTagUrl}
|
|
||||||
drm={additionnal?.drm}
|
|
||||||
style={viewStyle}
|
|
||||||
rate={this.state.rate}
|
|
||||||
paused={this.state.paused}
|
|
||||||
volume={this.state.volume}
|
|
||||||
muted={this.state.muted}
|
|
||||||
fullscreen={this.state.fullscreen}
|
|
||||||
controls={this.state.showRNVControls}
|
|
||||||
resizeMode={this.state.resizeMode}
|
|
||||||
onLoad={this.onLoad}
|
|
||||||
onAudioTracks={this.onAudioTracks}
|
|
||||||
onTextTracks={this.onTextTracks}
|
|
||||||
onVideoTracks={this.onVideoTracks}
|
|
||||||
onTextTrackDataChanged={this.onTextTrackDataChanged}
|
|
||||||
onProgress={this.onProgress}
|
|
||||||
onEnd={this.onEnd}
|
|
||||||
progressUpdateInterval={1000}
|
|
||||||
onError={this.onError}
|
|
||||||
onAudioBecomingNoisy={this.onAudioBecomingNoisy}
|
|
||||||
onAudioFocusChanged={this.onAudioFocusChanged}
|
|
||||||
onLoadStart={this.onVideoLoadStart}
|
|
||||||
onAspectRatio={this.onAspectRatio}
|
|
||||||
onReadyForDisplay={this.onReadyForDisplay}
|
|
||||||
onBuffer={this.onVideoBuffer}
|
|
||||||
onSeek={this.onSeek}
|
|
||||||
repeat={this.state.loop}
|
|
||||||
selectedTextTrack={this.state.selectedTextTrack}
|
|
||||||
selectedAudioTrack={this.state.selectedAudioTrack}
|
|
||||||
selectedVideoTrack={this.state.selectedVideoTrack}
|
|
||||||
playInBackground={false}
|
|
||||||
bufferConfig={{
|
|
||||||
minBufferMs: 15000,
|
|
||||||
maxBufferMs: 50000,
|
|
||||||
bufferForPlaybackMs: 2500,
|
|
||||||
bufferForPlaybackAfterRebufferMs: 5000,
|
|
||||||
cacheSizeMB: this.state.useCache ? 200 : 0,
|
|
||||||
live: {
|
|
||||||
targetOffsetMs: 500,
|
|
||||||
},
|
|
||||||
}}
|
|
||||||
preventsDisplaySleepDuringVideoPlayback={true}
|
|
||||||
poster={this.state.poster}
|
|
||||||
onPlaybackRateChange={this.onPlaybackRateChange}
|
|
||||||
onPlaybackStateChanged={this.onPlaybackStateChanged}
|
|
||||||
bufferingStrategy={BufferingStrategyType.DEFAULT}
|
|
||||||
debug={{enable: true, thread: true}}
|
|
||||||
/>
|
|
||||||
</TouchableOpacity>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
|
|
||||||
render() {
|
|
||||||
return (
|
|
||||||
<View style={styles.container}>
|
|
||||||
{(this.srcList[this.state.srcListId] as AdditionnalSourceInfo)?.noView
|
|
||||||
? null
|
|
||||||
: this.renderVideoView()}
|
|
||||||
{this.renderOverlay()}
|
|
||||||
</View>
|
|
||||||
);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
export default VideoPlayer;
|
export default VideoPlayer;
|
||||||
|
@ -108,7 +108,7 @@ class VideoPlayer extends Component {
|
|||||||
this.setState({paused: !this.state.paused});
|
this.setState({paused: !this.state.paused});
|
||||||
}}>
|
}}>
|
||||||
<Video
|
<Video
|
||||||
source={require('./broadchurch.mp4')}
|
source={require('./assets/videos/broadchurch.mp4')}
|
||||||
style={styles.fullScreen}
|
style={styles.fullScreen}
|
||||||
rate={this.state.rate}
|
rate={this.state.rate}
|
||||||
paused={this.state.paused}
|
paused={this.state.paused}
|
||||||
|
1
examples/basic/src/assets/index.ts
Normal file
1
examples/basic/src/assets/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './videos';
|
4
examples/basic/src/assets/videos/index.ts
Normal file
4
examples/basic/src/assets/videos/index.ts
Normal file
@ -0,0 +1,4 @@
|
|||||||
|
export const localeVideo = {
|
||||||
|
broadchurch: require('./broadchurch.mp4'),
|
||||||
|
portrait: require('./portrait.mp4'),
|
||||||
|
};
|
@ -10,7 +10,7 @@ export interface AudioTrackSelectorType {
|
|||||||
onValueChange: (arg0: string) => void;
|
onValueChange: (arg0: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const AudioTrackSelector = ({
|
export const AudioTrackSelector = ({
|
||||||
audioTracks,
|
audioTracks,
|
||||||
selectedAudioTrack,
|
selectedAudioTrack,
|
||||||
onValueChange,
|
onValueChange,
|
||||||
@ -49,5 +49,3 @@ const AudioTrackSelector = ({
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default AudioTrackSelector;
|
|
||||||
|
22
examples/basic/src/components/Indicator.tsx
Normal file
22
examples/basic/src/components/Indicator.tsx
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
import React, {FC, memo} from 'react';
|
||||||
|
import {ActivityIndicator, View} from 'react-native';
|
||||||
|
import styles from '../styles.tsx';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
isLoading: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
const _Indicator: FC<Props> = ({isLoading}) => {
|
||||||
|
if (!isLoading) {
|
||||||
|
return <View />;
|
||||||
|
}
|
||||||
|
return (
|
||||||
|
<ActivityIndicator
|
||||||
|
color="#3235fd"
|
||||||
|
size="large"
|
||||||
|
style={styles.IndicatorStyle}
|
||||||
|
/>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
|
||||||
|
export const Indicator = memo(_Indicator);
|
310
examples/basic/src/components/Overlay.tsx
Normal file
310
examples/basic/src/components/Overlay.tsx
Normal file
@ -0,0 +1,310 @@
|
|||||||
|
import React, {forwardRef, memo, useCallback} from 'react';
|
||||||
|
import {Indicator} from './Indicator.tsx';
|
||||||
|
import {View} from 'react-native';
|
||||||
|
import styles from '../styles.tsx';
|
||||||
|
import ToggleControl from '../ToggleControl.tsx';
|
||||||
|
import {
|
||||||
|
isAndroid,
|
||||||
|
isIos,
|
||||||
|
samplePoster,
|
||||||
|
textTracksSelectionBy,
|
||||||
|
} from '../constants';
|
||||||
|
import MultiValueControl, {
|
||||||
|
MultiValueControlPropType,
|
||||||
|
} from '../MultiValueControl.tsx';
|
||||||
|
import {
|
||||||
|
ResizeMode,
|
||||||
|
SelectedTrackType,
|
||||||
|
SelectedVideoTrackType,
|
||||||
|
VideoDecoderProperties,
|
||||||
|
VideoRef,
|
||||||
|
} from 'react-native-video';
|
||||||
|
import {StateType} from '../types';
|
||||||
|
import {
|
||||||
|
toast,
|
||||||
|
Seeker,
|
||||||
|
AudioTrackSelector,
|
||||||
|
TextTrackSelector,
|
||||||
|
VideoTrackSelector,
|
||||||
|
TopControl,
|
||||||
|
} from '../components';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
channelDown: () => void;
|
||||||
|
channelUp: () => void;
|
||||||
|
setState: (value: StateType) => void;
|
||||||
|
state: StateType;
|
||||||
|
};
|
||||||
|
|
||||||
|
const _Overlay = forwardRef<VideoRef, Props>((props, ref) => {
|
||||||
|
const {state, setState, channelUp, channelDown} = props;
|
||||||
|
const popupInfo = useCallback(() => {
|
||||||
|
VideoDecoderProperties.getWidevineLevel().then((widevineLevel: number) => {
|
||||||
|
VideoDecoderProperties.isHEVCSupported().then((hevc: string) => {
|
||||||
|
VideoDecoderProperties.isCodecSupported('video/avc', 1920, 1080).then(
|
||||||
|
(avc: string) => {
|
||||||
|
toast(
|
||||||
|
true,
|
||||||
|
'Widevine level: ' +
|
||||||
|
widevineLevel +
|
||||||
|
'\n hevc: ' +
|
||||||
|
hevc +
|
||||||
|
'\n avc: ' +
|
||||||
|
avc,
|
||||||
|
);
|
||||||
|
},
|
||||||
|
);
|
||||||
|
});
|
||||||
|
});
|
||||||
|
}, []);
|
||||||
|
|
||||||
|
const toggleFullscreen = () => {
|
||||||
|
setState({...state, fullscreen: !state.fullscreen});
|
||||||
|
};
|
||||||
|
const toggleControls = () => {
|
||||||
|
setState({...state, showRNVControls: !state.showRNVControls});
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleDecoration = () => {
|
||||||
|
setState({...state, decoration: !state.decoration});
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-expect-error
|
||||||
|
ref.current?.setFullScreen(!state.decoration);
|
||||||
|
};
|
||||||
|
|
||||||
|
const toggleShowNotificationControls = () => {
|
||||||
|
setState({
|
||||||
|
...state,
|
||||||
|
showNotificationControls: !state.showNotificationControls,
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSelectedAudioTrackChange = (itemValue: string) => {
|
||||||
|
console.log('on audio value change ' + itemValue);
|
||||||
|
if (itemValue === 'none') {
|
||||||
|
setState({
|
||||||
|
...state,
|
||||||
|
selectedAudioTrack: {
|
||||||
|
type: SelectedTrackType.DISABLED,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setState({
|
||||||
|
...state,
|
||||||
|
selectedAudioTrack: {
|
||||||
|
type: SelectedTrackType.INDEX,
|
||||||
|
value: itemValue,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSelectedTextTrackChange = (itemValue: string) => {
|
||||||
|
console.log('on value change ' + itemValue);
|
||||||
|
setState({
|
||||||
|
...state,
|
||||||
|
selectedTextTrack: {
|
||||||
|
type:
|
||||||
|
textTracksSelectionBy === 'index'
|
||||||
|
? SelectedTrackType.INDEX
|
||||||
|
: SelectedTrackType.LANGUAGE,
|
||||||
|
value: itemValue,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
};
|
||||||
|
|
||||||
|
const onSelectedVideoTrackChange = (itemValue: string) => {
|
||||||
|
console.log('on value change ' + itemValue);
|
||||||
|
if (itemValue === undefined || itemValue === 'auto') {
|
||||||
|
setState({
|
||||||
|
...state,
|
||||||
|
selectedVideoTrack: {
|
||||||
|
type: SelectedVideoTrackType.AUTO,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
} else {
|
||||||
|
setState({
|
||||||
|
...state,
|
||||||
|
selectedVideoTrack: {
|
||||||
|
type: SelectedVideoTrackType.INDEX,
|
||||||
|
value: itemValue,
|
||||||
|
},
|
||||||
|
});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
const videoSeek = (position: number) => {
|
||||||
|
setState({...state, isSeeking: true});
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-expect-error
|
||||||
|
ref.current?.seek(position);
|
||||||
|
};
|
||||||
|
|
||||||
|
const onRateSelected = (value: MultiValueControlPropType) => {
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
setState({...state, rate: value});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const onVolumeSelected = (value: MultiValueControlPropType) => {
|
||||||
|
if (typeof value === 'number') {
|
||||||
|
setState({...state, volume: value});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
const onResizeModeSelected = (value: MultiValueControlPropType) => {
|
||||||
|
if (typeof value === 'object') {
|
||||||
|
setState({...state, resizeMode: value});
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
return (
|
||||||
|
<>
|
||||||
|
<Indicator isLoading={state.isLoading} />
|
||||||
|
<View style={styles.topControls}>
|
||||||
|
<View style={styles.resizeModeControl}>
|
||||||
|
<TopControl
|
||||||
|
srcListId={state.srcListId}
|
||||||
|
showRNVControls={state.showRNVControls}
|
||||||
|
toggleControls={toggleControls}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
{!state.showRNVControls ? (
|
||||||
|
<>
|
||||||
|
<View style={styles.leftControls}>
|
||||||
|
<ToggleControl onPress={channelDown} text="ChDown" />
|
||||||
|
</View>
|
||||||
|
<View style={styles.rightControls}>
|
||||||
|
<ToggleControl onPress={channelUp} text="ChUp" />
|
||||||
|
</View>
|
||||||
|
<View style={styles.bottomControls}>
|
||||||
|
<View style={styles.generalControls}>
|
||||||
|
{isAndroid ? (
|
||||||
|
<View style={styles.generalControls}>
|
||||||
|
<ToggleControl onPress={popupInfo} text="decoderInfo" />
|
||||||
|
<ToggleControl
|
||||||
|
isSelected={state.useCache}
|
||||||
|
onPress={() => {
|
||||||
|
setState({...state, useCache: !state.useCache});
|
||||||
|
}}
|
||||||
|
selectedText="enable cache"
|
||||||
|
unselectedText="disable cache"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
) : null}
|
||||||
|
<ToggleControl
|
||||||
|
isSelected={state.paused}
|
||||||
|
onPress={() => {
|
||||||
|
setState({...state, paused: !state.paused});
|
||||||
|
}}
|
||||||
|
selectedText="pause"
|
||||||
|
unselectedText="playing"
|
||||||
|
/>
|
||||||
|
<ToggleControl
|
||||||
|
isSelected={state.loop}
|
||||||
|
onPress={() => {
|
||||||
|
setState({...state, loop: !state.loop});
|
||||||
|
}}
|
||||||
|
selectedText="loop enable"
|
||||||
|
unselectedText="loop disable"
|
||||||
|
/>
|
||||||
|
<ToggleControl onPress={toggleFullscreen} text="fullscreen" />
|
||||||
|
<ToggleControl onPress={toggleDecoration} text="decoration" />
|
||||||
|
<ToggleControl
|
||||||
|
isSelected={!!state.poster}
|
||||||
|
onPress={() => {
|
||||||
|
setState({
|
||||||
|
...state,
|
||||||
|
poster: state.poster ? undefined : samplePoster,
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
selectedText="poster"
|
||||||
|
unselectedText="no poster"
|
||||||
|
/>
|
||||||
|
<ToggleControl
|
||||||
|
isSelected={state.showNotificationControls}
|
||||||
|
onPress={toggleShowNotificationControls}
|
||||||
|
selectedText="hide notification controls"
|
||||||
|
unselectedText="show notification controls"
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
<View style={styles.generalControls}>
|
||||||
|
{/* shall be replaced by slider */}
|
||||||
|
<MultiValueControl
|
||||||
|
values={[0, 0.25, 0.5, 1.0, 1.5, 2.0]}
|
||||||
|
onPress={onRateSelected}
|
||||||
|
selected={state.rate}
|
||||||
|
/>
|
||||||
|
{/* shall be replaced by slider */}
|
||||||
|
<MultiValueControl
|
||||||
|
values={[0.5, 1, 1.5]}
|
||||||
|
onPress={onVolumeSelected}
|
||||||
|
selected={state.volume}
|
||||||
|
/>
|
||||||
|
<MultiValueControl
|
||||||
|
values={[
|
||||||
|
ResizeMode.COVER,
|
||||||
|
ResizeMode.CONTAIN,
|
||||||
|
ResizeMode.STRETCH,
|
||||||
|
]}
|
||||||
|
onPress={onResizeModeSelected}
|
||||||
|
selected={state.resizeMode}
|
||||||
|
/>
|
||||||
|
<ToggleControl
|
||||||
|
isSelected={state.muted}
|
||||||
|
onPress={() => {
|
||||||
|
setState({...state, muted: !state.muted});
|
||||||
|
}}
|
||||||
|
text="muted"
|
||||||
|
/>
|
||||||
|
{isIos ? (
|
||||||
|
<ToggleControl
|
||||||
|
isSelected={state.paused}
|
||||||
|
onPress={() => {
|
||||||
|
// eslint-disable-next-line @typescript-eslint/ban-ts-comment
|
||||||
|
// @ts-expect-error
|
||||||
|
ref.current
|
||||||
|
?.save({})
|
||||||
|
?.then((response: unknown) => {
|
||||||
|
console.log('Downloaded URI', response);
|
||||||
|
})
|
||||||
|
.catch((error: unknown) => {
|
||||||
|
console.log('error during save ', error);
|
||||||
|
});
|
||||||
|
}}
|
||||||
|
text="save"
|
||||||
|
/>
|
||||||
|
) : null}
|
||||||
|
</View>
|
||||||
|
<Seeker
|
||||||
|
currentTime={state.currentTime}
|
||||||
|
duration={state.duration}
|
||||||
|
isLoading={state.isLoading}
|
||||||
|
videoSeek={prop => videoSeek(prop)}
|
||||||
|
isUISeeking={state.isSeeking}
|
||||||
|
/>
|
||||||
|
<View style={styles.generalControls}>
|
||||||
|
<AudioTrackSelector
|
||||||
|
audioTracks={state.audioTracks}
|
||||||
|
selectedAudioTrack={state.selectedAudioTrack}
|
||||||
|
onValueChange={onSelectedAudioTrackChange}
|
||||||
|
/>
|
||||||
|
<TextTrackSelector
|
||||||
|
textTracks={state.textTracks}
|
||||||
|
selectedTextTrack={state.selectedTextTrack}
|
||||||
|
onValueChange={onSelectedTextTrackChange}
|
||||||
|
textTracksSelectionBy={textTracksSelectionBy}
|
||||||
|
/>
|
||||||
|
<VideoTrackSelector
|
||||||
|
videoTracks={state.videoTracks}
|
||||||
|
selectedVideoTrack={state.selectedVideoTrack}
|
||||||
|
onValueChange={onSelectedVideoTrackChange}
|
||||||
|
/>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
</>
|
||||||
|
) : null}
|
||||||
|
</>
|
||||||
|
);
|
||||||
|
});
|
||||||
|
|
||||||
|
export const Overlay = memo(_Overlay);
|
@ -10,7 +10,7 @@ interface SeekerProps {
|
|||||||
videoSeek: (arg0: number) => void;
|
videoSeek: (arg0: number) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const Seeker = ({
|
export const Seeker = ({
|
||||||
currentTime,
|
currentTime,
|
||||||
duration,
|
duration,
|
||||||
isLoading,
|
isLoading,
|
||||||
@ -150,5 +150,3 @@ const Seeker = ({
|
|||||||
</View>
|
</View>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default Seeker;
|
|
||||||
|
@ -11,7 +11,7 @@ export interface TextTrackSelectorType {
|
|||||||
textTracksSelectionBy: string;
|
textTracksSelectionBy: string;
|
||||||
}
|
}
|
||||||
|
|
||||||
const TextTrackSelector = ({
|
export const TextTrackSelector = ({
|
||||||
textTracks,
|
textTracks,
|
||||||
selectedTextTrack,
|
selectedTextTrack,
|
||||||
onValueChange,
|
onValueChange,
|
||||||
@ -60,5 +60,3 @@ const TextTrackSelector = ({
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default TextTrackSelector;
|
|
||||||
|
18
examples/basic/src/components/Toast.ts
Normal file
18
examples/basic/src/components/Toast.ts
Normal file
@ -0,0 +1,18 @@
|
|||||||
|
import {Alert, ToastAndroid} from 'react-native';
|
||||||
|
import {isAndroid} from '../constants';
|
||||||
|
|
||||||
|
export const toast = (visible: boolean, message: string) => {
|
||||||
|
if (visible) {
|
||||||
|
if (isAndroid) {
|
||||||
|
ToastAndroid.showWithGravityAndOffset(
|
||||||
|
message,
|
||||||
|
ToastAndroid.LONG,
|
||||||
|
ToastAndroid.BOTTOM,
|
||||||
|
25,
|
||||||
|
50,
|
||||||
|
);
|
||||||
|
} else {
|
||||||
|
Alert.alert(message, message);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
};
|
37
examples/basic/src/components/TopControl.tsx
Normal file
37
examples/basic/src/components/TopControl.tsx
Normal file
@ -0,0 +1,37 @@
|
|||||||
|
import React, {FC, memo} from 'react';
|
||||||
|
import {Text, TouchableOpacity, View} from 'react-native';
|
||||||
|
import styles from '../styles.tsx';
|
||||||
|
import {srcList} from '../constants';
|
||||||
|
import {AdditionalSourceInfo} from '../types';
|
||||||
|
|
||||||
|
type Props = {
|
||||||
|
srcListId: number;
|
||||||
|
showRNVControls: boolean;
|
||||||
|
toggleControls: () => void;
|
||||||
|
};
|
||||||
|
|
||||||
|
const _TopControl: FC<Props> = ({
|
||||||
|
toggleControls,
|
||||||
|
showRNVControls,
|
||||||
|
srcListId,
|
||||||
|
}) => {
|
||||||
|
return (
|
||||||
|
<View style={styles.topControlsContainer}>
|
||||||
|
<Text style={styles.controlOption}>
|
||||||
|
{(srcList[srcListId] as AdditionalSourceInfo)?.description ||
|
||||||
|
'local file'}
|
||||||
|
</Text>
|
||||||
|
<View>
|
||||||
|
<TouchableOpacity
|
||||||
|
onPress={() => {
|
||||||
|
toggleControls();
|
||||||
|
}}>
|
||||||
|
<Text style={styles.leftRightControlOption}>
|
||||||
|
{showRNVControls ? 'Hide controls' : 'Show controls'}
|
||||||
|
</Text>
|
||||||
|
</TouchableOpacity>
|
||||||
|
</View>
|
||||||
|
</View>
|
||||||
|
);
|
||||||
|
};
|
||||||
|
export const TopControl = memo(_TopControl);
|
@ -14,7 +14,7 @@ export interface VideoTrackSelectorType {
|
|||||||
onValueChange: (arg0: string) => void;
|
onValueChange: (arg0: string) => void;
|
||||||
}
|
}
|
||||||
|
|
||||||
const VideoTrackSelector = ({
|
export const VideoTrackSelector = ({
|
||||||
videoTracks,
|
videoTracks,
|
||||||
selectedVideoTrack,
|
selectedVideoTrack,
|
||||||
onValueChange,
|
onValueChange,
|
||||||
@ -60,5 +60,3 @@ const VideoTrackSelector = ({
|
|||||||
</>
|
</>
|
||||||
);
|
);
|
||||||
};
|
};
|
||||||
|
|
||||||
export default VideoTrackSelector;
|
|
||||||
|
8
examples/basic/src/components/index.ts
Normal file
8
examples/basic/src/components/index.ts
Normal file
@ -0,0 +1,8 @@
|
|||||||
|
export * from './Indicator';
|
||||||
|
export * from './Seeker';
|
||||||
|
export * from './AudioTracksSelector';
|
||||||
|
export * from './VideoTracksSelector';
|
||||||
|
export * from './TextTracksSelector';
|
||||||
|
export * from './Overlay';
|
||||||
|
export * from './TopControl';
|
||||||
|
export * from './Toast';
|
200
examples/basic/src/constants/general.ts
Normal file
200
examples/basic/src/constants/general.ts
Normal file
@ -0,0 +1,200 @@
|
|||||||
|
import {
|
||||||
|
BufferConfig,
|
||||||
|
DRMType,
|
||||||
|
ISO639_1,
|
||||||
|
ResizeMode,
|
||||||
|
SelectedVideoTrackType,
|
||||||
|
TextTrackType,
|
||||||
|
} from 'react-native-video';
|
||||||
|
import {SampleVideoSource, StateType} from '../types';
|
||||||
|
import {localeVideo} from '../assets';
|
||||||
|
import {Platform} from 'react-native';
|
||||||
|
|
||||||
|
export const textTracksSelectionBy = 'index';
|
||||||
|
|
||||||
|
export const isIos = Platform.OS === 'ios';
|
||||||
|
|
||||||
|
export const isAndroid = Platform.OS === 'android';
|
||||||
|
|
||||||
|
export const srcAllPlatformList = [
|
||||||
|
{
|
||||||
|
description: 'local file landscape',
|
||||||
|
uri: localeVideo.broadchurch,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'local file landscape cropped',
|
||||||
|
uri: localeVideo.broadchurch,
|
||||||
|
cropStart: 3000,
|
||||||
|
cropEnd: 10000,
|
||||||
|
},
|
||||||
|
{
|
||||||
|
description: 'local file portrait',
|
||||||
|
uri: localeVideo.portrait,
|
||||||
|
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',
|
||||||
|
},
|
||||||
|
],
|
||||||
|
},
|
||||||
|
];
|
||||||
|
|
||||||
|
export const srcIosList = [];
|
||||||
|
|
||||||
|
export const 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
|
||||||
|
export const samplePoster =
|
||||||
|
'https://upload.wikimedia.org/wikipedia/commons/1/18/React_Native_Logo.png';
|
||||||
|
|
||||||
|
export const srcList: SampleVideoSource[] = srcAllPlatformList.concat(
|
||||||
|
isAndroid ? srcAndroidList : srcIosList,
|
||||||
|
);
|
||||||
|
|
||||||
|
export const defaultValue: 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,
|
||||||
|
};
|
||||||
|
|
||||||
|
export const bufferConfig: BufferConfig = {
|
||||||
|
minBufferMs: 15000,
|
||||||
|
maxBufferMs: 50000,
|
||||||
|
bufferForPlaybackMs: 2500,
|
||||||
|
bufferForPlaybackAfterRebufferMs: 5000,
|
||||||
|
live: {
|
||||||
|
targetOffsetMs: 500,
|
||||||
|
},
|
||||||
|
}
|
1
examples/basic/src/constants/index.ts
Normal file
1
examples/basic/src/constants/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './general';
|
1
examples/basic/src/types/index.ts
Normal file
1
examples/basic/src/types/index.ts
Normal file
@ -0,0 +1 @@
|
|||||||
|
export * from './types';
|
49
examples/basic/src/types/types.ts
Normal file
49
examples/basic/src/types/types.ts
Normal file
@ -0,0 +1,49 @@
|
|||||||
|
import {
|
||||||
|
AudioTrack,
|
||||||
|
Drm,
|
||||||
|
ReactVideoSource,
|
||||||
|
ResizeMode,
|
||||||
|
SelectedTrack,
|
||||||
|
SelectedVideoTrack,
|
||||||
|
TextTrack,
|
||||||
|
TextTracks,
|
||||||
|
VideoTrack,
|
||||||
|
} from 'react-native-video';
|
||||||
|
|
||||||
|
export type AdditionalSourceInfo = {
|
||||||
|
textTracks: TextTracks;
|
||||||
|
adTagUrl: string;
|
||||||
|
description: string;
|
||||||
|
drm: Drm;
|
||||||
|
noView: boolean;
|
||||||
|
};
|
||||||
|
|
||||||
|
export type SampleVideoSource = ReactVideoSource | AdditionalSourceInfo;
|
||||||
|
|
||||||
|
export interface StateType {
|
||||||
|
rate: number;
|
||||||
|
volume: number;
|
||||||
|
muted: boolean;
|
||||||
|
resizeMode: ResizeMode;
|
||||||
|
duration: number;
|
||||||
|
currentTime: number;
|
||||||
|
videoWidth: number;
|
||||||
|
videoHeight: number;
|
||||||
|
paused: boolean;
|
||||||
|
fullscreen: boolean;
|
||||||
|
decoration: boolean;
|
||||||
|
isLoading: boolean;
|
||||||
|
audioTracks: Array<AudioTrack>;
|
||||||
|
textTracks: Array<TextTrack>;
|
||||||
|
videoTracks: Array<VideoTrack>;
|
||||||
|
selectedAudioTrack: SelectedTrack | undefined;
|
||||||
|
selectedTextTrack: SelectedTrack | undefined;
|
||||||
|
selectedVideoTrack: SelectedVideoTrack;
|
||||||
|
srcListId: number;
|
||||||
|
loop: boolean;
|
||||||
|
showRNVControls: boolean;
|
||||||
|
useCache: boolean;
|
||||||
|
poster?: string;
|
||||||
|
showNotificationControls: boolean;
|
||||||
|
isSeeking: boolean;
|
||||||
|
}
|
Loading…
Reference in New Issue
Block a user