Merge remote-tracking branch 'origin/master' into feat/web

This commit is contained in:
2024-10-12 22:33:11 -06:00
99 changed files with 5602 additions and 4049 deletions

View File

@@ -83,8 +83,6 @@ android {
namespace "com.videoplayer"
compileOptions {
// These options are necessary to be able to build fro source
coreLibraryDesugaringEnabled true
sourceCompatibility JavaVersion.VERSION_11
targetCompatibility JavaVersion.VERSION_11
}
@@ -155,8 +153,6 @@ dependencies {
because("kotlin-stdlib-jdk8 is now a part of kotlin-stdlib")
}
}
// coreLibraryDesugaring is mandatory to be able to build exoplayer from source
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:2.0.4'
}
apply from: file("../../node_modules/@react-native-community/cli-platform-android/native_modules.gradle"); applyNativeModulesAppBuildGradle(project)

File diff suppressed because it is too large Load Diff

View File

@@ -16,14 +16,15 @@
"dependencies": {
"@expo/metro-runtime": "~3.2.1",
"@react-native-picker/picker": "2.7.5",
"expo": "^51.0.17",
"expo-asset": "^10.0.9",
"expo-image": "^1.12.12",
"expo": "^51.0.32",
"expo-asset": "~10.0.10",
"expo-image": "^1.12.15",
"expo-navigation-bar": "~3.0.7",
"react": "18.2.0",
"react-native": "0.74.3",
"react-native": "0.74.5",
"react-dom": "18.2.0",
"react-native-web": "~0.19.10",
"react-native-windows": "0.74.1"
"react-native-windows": "0.74.19"
},
"devDependencies": {
"@babel/core": "^7.24.0",

View File

@@ -1,8 +1,8 @@
'use strict';
import React, {type FC, useCallback, useRef, useState} from 'react';
import React, {type FC, useCallback, useRef, useState, useEffect} from 'react';
import {Platform, TouchableOpacity, View} from 'react-native';
import {Platform, TouchableOpacity, View, StatusBar} from 'react-native';
import Video, {
VideoRef,
@@ -26,23 +26,24 @@ import Video, {
type OnPlaybackRateChangeData,
type OnVideoTracksData,
type ReactVideoSource,
type TextTracks,
type VideoTrack,
type SelectedTrack,
type SelectedVideoTrack,
type EnumValues,
OnBandwidthUpdateData,
ControlsStyles,
} from 'react-native-video';
import styles from './styles';
import {type AdditionalSourceInfo} from './types';
import {bufferConfig, srcList, textTracksSelectionBy} from './constants';
import {Overlay, toast} from './components';
type AdditionnalSourceInfo = {
textTracks: TextTracks;
adTagUrl: string;
description: string;
noView: boolean;
};
import {
bufferConfig,
isAndroid,
srcList,
textTracksSelectionBy,
audioTracksSelectionBy,
} from './constants';
import {Overlay, toast, VideoLoader} from './components';
import * as NavigationBar from 'expo-navigation-bar';
type Props = NonNullable<unknown>;
@@ -76,7 +77,7 @@ const VideoPlayer: FC<Props> = ({}) => {
const [repeat, setRepeat] = useState(false);
const [controls, setControls] = useState(false);
const [useCache, setUseCache] = useState(false);
const [poster, setPoster] = useState<string | undefined>(undefined);
const [showPoster, setShowPoster] = useState<boolean>(false);
const [showNotificationControls, setShowNotificationControls] =
useState(false);
const [isSeeking, setIsSeeking] = useState(false);
@@ -111,19 +112,30 @@ const VideoPlayer: FC<Props> = ({}) => {
goToChannel((srcListId + srcList.length - 1) % srcList.length);
}, [goToChannel, srcListId]);
useEffect(() => {
if (isAndroid) {
NavigationBar.setVisibilityAsync('visible');
}
}, []);
const onAudioTracks = (data: OnAudioTracksData) => {
console.log('onAudioTracks', data);
const selectedTrack = data.audioTracks?.find((x: AudioTrack) => {
return x.selected;
});
if (selectedTrack?.index) {
setAudioTracks(data.audioTracks);
setSelectedAudioTrack({
type: SelectedTrackType.INDEX,
value: selectedTrack.index,
});
} else {
setAudioTracks(data.audioTracks);
let value;
if (audioTracksSelectionBy === SelectedTrackType.INDEX) {
value = selectedTrack?.index;
} else if (audioTracksSelectionBy === SelectedTrackType.LANGUAGE) {
value = selectedTrack?.language;
} else if (audioTracksSelectionBy === SelectedTrackType.TITLE) {
value = selectedTrack?.title;
}
setAudioTracks(data.audioTracks);
setSelectedAudioTrack({
type: audioTracksSelectionBy,
value: value,
});
};
const onVideoTracks = (data: OnVideoTracksData) => {
@@ -136,22 +148,19 @@ const VideoPlayer: FC<Props> = ({}) => {
return x?.selected;
});
if (selectedTrack?.language) {
setTextTracks(data.textTracks);
if (textTracksSelectionBy === 'index') {
setSelectedTextTrack({
type: SelectedTrackType.INDEX,
value: selectedTrack?.index,
});
} else {
setSelectedTextTrack({
type: SelectedTrackType.LANGUAGE,
value: selectedTrack?.language,
});
}
} else {
setTextTracks(data.textTracks);
setTextTracks(data.textTracks);
let value;
if (textTracksSelectionBy === SelectedTrackType.INDEX) {
value = selectedTrack?.index;
} else if (textTracksSelectionBy === SelectedTrackType.LANGUAGE) {
value = selectedTrack?.language;
} else if (textTracksSelectionBy === SelectedTrackType.TITLE) {
value = selectedTrack?.title;
}
setSelectedTextTrack({
type: textTracksSelectionBy,
value: value,
});
};
const onLoad = (data: OnLoadData) => {
@@ -221,28 +230,48 @@ const VideoPlayer: FC<Props> = ({}) => {
console.log('onPlaybackStateChanged', data);
};
const onVideoBandwidthUpdate = (data: OnBandwidthUpdateData) => {
console.log('onVideoBandwidthUpdate', data);
};
const onFullScreenExit = () => {
// iOS pauses video on exit from full screen
Platform.OS === 'ios' && setPaused(true);
};
const _renderLoader = showPoster ? () => <VideoLoader /> : undefined;
const _subtitleStyle = {subtitlesFollowVideo: true};
const _controlsStyles : ControlsStyles = {
hideNavigationBarOnFullScreenMode: true,
hideNotificationBarOnFullScreenMode: true,
liveLabel: "LIVE"
};
const _bufferConfig = {
...bufferConfig,
cacheSizeMB: useCache ? 200 : 0,
};
useEffect(() => {
videoRef.current?.setSource(currentSrc)
}, [currentSrc])
return (
<View style={styles.container}>
<StatusBar animated={true} backgroundColor="black" hidden={false} />
{(srcList[srcListId] as AdditionalSourceInfo)?.noView ? null : (
<TouchableOpacity style={viewStyle}>
<Video
showNotificationControls={showNotificationControls}
ref={videoRef}
source={currentSrc as ReactVideoSource}
textTracks={additional?.textTracks}
adTagUrl={additional?.adTagUrl}
// source={currentSrc as ReactVideoSource}
drm={additional?.drm}
style={viewStyle}
rate={rate}
paused={paused}
volume={volume}
muted={muted}
fullscreen={fullscreen}
controls={controls}
resizeMode={resizeMode}
onFullscreenPlayerWillDismiss={onFullScreenExit}
@@ -261,22 +290,22 @@ const VideoPlayer: FC<Props> = ({}) => {
onAspectRatio={onAspectRatio}
onReadyForDisplay={onReadyForDisplay}
onBuffer={onVideoBuffer}
onBandwidthUpdate={onVideoBandwidthUpdate}
onSeek={onSeek}
repeat={repeat}
selectedTextTrack={selectedTextTrack}
selectedAudioTrack={selectedAudioTrack}
selectedVideoTrack={selectedVideoTrack}
playInBackground={false}
bufferConfig={{
...bufferConfig,
cacheSizeMB: useCache ? 200 : 0,
}}
bufferConfig={_bufferConfig}
preventsDisplaySleepDuringVideoPlayback={true}
poster={poster}
renderLoader={_renderLoader}
onPlaybackRateChange={onPlaybackRateChange}
onPlaybackStateChanged={onPlaybackStateChanged}
bufferingStrategy={BufferingStrategyType.DEFAULT}
debug={{enable: true, thread: true}}
subtitleStyle={_subtitleStyle}
controlsStyles={_controlsStyles}
/>
</TouchableOpacity>
)}
@@ -302,7 +331,7 @@ const VideoPlayer: FC<Props> = ({}) => {
paused={paused}
volume={volume}
setControls={setControls}
poster={poster}
showPoster={showPoster}
rate={rate}
setFullscreen={setFullscreen}
setPaused={setPaused}
@@ -311,7 +340,7 @@ const VideoPlayer: FC<Props> = ({}) => {
setIsSeeking={setIsSeeking}
repeat={repeat}
setRepeat={setRepeat}
setPoster={setPoster}
setShowPoster={setShowPoster}
setRate={setRate}
setResizeMode={setResizeMode}
setShowNotificationControls={setShowNotificationControls}

View File

@@ -1,19 +1,25 @@
import {Picker} from '@react-native-picker/picker';
import {Text} from 'react-native';
import type {AudioTrack, SelectedTrack} from 'react-native-video';
import {
SelectedTrackType,
type AudioTrack,
type SelectedTrack,
} from 'react-native-video';
import styles from '../styles';
import React from 'react';
export interface AudioTrackSelectorType {
audioTracks: Array<AudioTrack>;
selectedAudioTrack: SelectedTrack | undefined;
onValueChange: (arg0: string) => void;
onValueChange: (arg0: string | number) => void;
audioTracksSelectionBy: SelectedTrackType;
}
export const AudioTrackSelector = ({
audioTracks,
selectedAudioTrack,
onValueChange,
audioTracksSelectionBy,
}: AudioTrackSelectorType) => {
return (
<>
@@ -25,7 +31,7 @@ export const AudioTrackSelector = ({
onValueChange={itemValue => {
if (itemValue !== 'empty') {
console.log('on audio value change ' + itemValue);
onValueChange(`${itemValue}`);
onValueChange(itemValue);
}
}}>
{audioTracks?.length <= 0 ? (
@@ -37,11 +43,19 @@ export const AudioTrackSelector = ({
if (!track) {
return;
}
let value;
if (audioTracksSelectionBy === SelectedTrackType.INDEX) {
value = track.index;
} else if (audioTracksSelectionBy === SelectedTrackType.LANGUAGE) {
value = track.language;
} else if (audioTracksSelectionBy === SelectedTrackType.TITLE) {
value = track.title;
}
return (
<Picker.Item
label={`${track.language} - ${track.title} - ${track.selected}`}
value={`${track.index}`}
key={`${track.index}`}
label={`${value} - ${track.selected}`}
value={`${value}`}
key={`${value}`}
/>
);
})}

View File

@@ -1,22 +1,8 @@
import React, {FC, memo} from 'react';
import {ActivityIndicator, View} from 'react-native';
import styles from '../styles.tsx';
import React, {memo} from 'react';
import {ActivityIndicator} from 'react-native';
type Props = {
isLoading: boolean;
};
const _Indicator: FC<Props> = ({isLoading}) => {
if (!isLoading) {
return <View />;
}
return (
<ActivityIndicator
color="#3235fd"
size="large"
style={styles.IndicatorStyle}
/>
);
const _Indicator = () => {
return <ActivityIndicator color="#3235fd" size="large" />;
};
export const Indicator = memo(_Indicator);

View File

@@ -21,7 +21,7 @@ interface MultiValueControlType<T> {
onPress: (arg: T) => void;
}
const MultiValueControl = <T extends number | string | ResizeMode>({
export const MultiValueControl = <T extends number | string | ResizeMode>({
values,
selected,
onPress,

View File

@@ -5,19 +5,14 @@ import React, {
type Dispatch,
type SetStateAction,
} 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,
audioTracksSelectionBy,
} from '../constants';
import MultiValueControl, {
type MultiValueControlPropType,
} from '../MultiValueControl.tsx';
import {
ResizeMode,
VideoRef,
@@ -31,14 +26,15 @@ import {
type VideoTrack,
type AudioTrack,
} from 'react-native-video';
import {
toast,
Seeker,
AudioTrackSelector,
TextTrackSelector,
VideoTrackSelector,
TopControl,
} from '../components';
import {toast} from './Toast';
import {Seeker} from './Seeker';
import {AudioTrackSelector} from './AudioTracksSelector';
import {VideoTrackSelector} from './VideoTracksSelector';
import {TextTrackSelector} from './TextTracksSelector';
import {TopControl} from './TopControl';
import {ToggleControl} from './ToggleControl';
import {MultiValueControl} from './MultiValueControl';
type Props = {
channelDown: () => void;
@@ -69,8 +65,8 @@ type Props = {
setPaused: Dispatch<SetStateAction<boolean>>;
repeat: boolean;
setRepeat: Dispatch<SetStateAction<boolean>>;
poster: string | undefined;
setPoster: Dispatch<SetStateAction<string | undefined>>;
showPoster: boolean;
setShowPoster: Dispatch<SetStateAction<boolean>>;
muted: boolean;
setMuted: Dispatch<SetStateAction<boolean>>;
currentTime: number;
@@ -108,8 +104,8 @@ const _Overlay = forwardRef<VideoRef, Props>((props, ref) => {
setPaused,
setRepeat,
repeat,
setPoster,
poster,
setShowPoster,
showPoster,
setMuted,
muted,
duration,
@@ -157,27 +153,20 @@ const _Overlay = forwardRef<VideoRef, Props>((props, ref) => {
setShowNotificationControls(prev => !prev);
};
const onSelectedAudioTrackChange = (itemValue: string) => {
const onSelectedAudioTrackChange = (itemValue: string | number) => {
console.log('on audio value change ' + itemValue);
if (itemValue === 'none') {
setSelectedAudioTrack({
type: SelectedTrackType.DISABLED,
});
} else {
setSelectedAudioTrack({
type: SelectedTrackType.INDEX,
value: itemValue,
});
setSelectedAudioTrack({type: audioTracksSelectionBy, value: itemValue});
}
};
const onSelectedTextTrackChange = (itemValue: string) => {
console.log('on value change ' + itemValue);
const type =
textTracksSelectionBy === 'index'
? SelectedTrackType.INDEX
: SelectedTrackType.LANGUAGE;
setSelectedTextTrack({type, value: itemValue});
setSelectedTextTrack({type: textTracksSelectionBy, value: itemValue});
};
const onSelectedVideoTrackChange = (itemValue: string) => {
@@ -217,14 +206,12 @@ const _Overlay = forwardRef<VideoRef, Props>((props, ref) => {
const toggleRepeat = () => setRepeat(prev => !prev);
const togglePoster = () =>
setPoster(prev => (prev ? undefined : samplePoster));
const togglePoster = () => setShowPoster(prev => !prev);
const toggleMuted = () => setMuted(prev => !prev);
return (
<>
<Indicator isLoading={isLoading} />
<View style={styles.topControls}>
<View style={styles.resizeModeControl}>
<TopControl
@@ -270,7 +257,7 @@ const _Overlay = forwardRef<VideoRef, Props>((props, ref) => {
<ToggleControl onPress={toggleFullscreen} text="fullscreen" />
<ToggleControl onPress={openDecoration} text="decoration" />
<ToggleControl
isSelected={!!poster}
isSelected={showPoster}
onPress={togglePoster}
selectedText="poster"
unselectedText="no poster"
@@ -339,6 +326,7 @@ const _Overlay = forwardRef<VideoRef, Props>((props, ref) => {
audioTracks={audioTracks}
selectedAudioTrack={selectedAudioTrack}
onValueChange={onSelectedAudioTrackChange}
audioTracksSelectionBy={audioTracksSelectionBy}
/>
<TextTrackSelector
textTracks={textTracks}

View File

@@ -1,6 +1,10 @@
import {Picker} from '@react-native-picker/picker';
import {Text} from 'react-native';
import type {TextTrack, SelectedTrack} from 'react-native-video';
import {
type TextTrack,
type SelectedTrack,
SelectedTrackType,
} from 'react-native-video';
import styles from '../styles';
import React from 'react';
@@ -38,23 +42,15 @@ export const TextTrackSelector = ({
if (!track) {
return;
}
if (textTracksSelectionBy === 'index') {
return (
<Picker.Item
label={`${track.index}`}
value={track.index}
key={track.index}
/>
);
} else {
return (
<Picker.Item
label={track.language}
value={track.language}
key={track.language}
/>
);
let value;
if (textTracksSelectionBy === SelectedTrackType.INDEX) {
value = track.index;
} else if (textTracksSelectionBy === SelectedTrackType.LANGUAGE) {
value = track.language;
} else if (textTracksSelectionBy === SelectedTrackType.TITLE) {
value = track.title;
}
return <Picker.Item label={`${value}`} value={value} key={value} />;
})}
</Picker>
</>

View File

@@ -25,7 +25,7 @@ interface ToggleControlType {
onPress: () => void;
}
const ToggleControl = ({
export const ToggleControl = ({
isSelected,
selectedText,
unselectedText,

View File

@@ -0,0 +1,15 @@
import {Text, View} from 'react-native';
import {Indicator} from './Indicator.tsx';
import React, {memo} from 'react';
import styles from '../styles.tsx';
const _VideoLoader = () => {
return (
<View style={styles.indicatorContainer}>
<Text style={styles.indicatorText}>Loading...</Text>
<Indicator />
</View>
);
};
export const VideoLoader = memo(_VideoLoader);

View File

@@ -1,3 +1,4 @@
export * from './VideoLoader';
export * from './Indicator';
export * from './Seeker';
export * from './AudioTracksSelector';
@@ -6,3 +7,5 @@ export * from './TextTracksSelector';
export * from './Overlay';
export * from './TopControl';
export * from './Toast';
export * from './ToggleControl';
export * from './MultiValueControl';

View File

@@ -2,13 +2,19 @@ import {
BufferConfig,
DRMType,
ISO639_1,
SelectedTrackType,
TextTrackType,
} from 'react-native-video';
import {SampleVideoSource} from '../types';
import {localeVideo} from '../assets';
import {Platform} from 'react-native';
export const textTracksSelectionBy = 'index';
// This constant allows to change how the sample behaves regarding to audio and texts selection.
// You can change it to change how selector will use tracks information.
// by default, index will be displayed and index will be applied to selected tracks.
// You can also use LANGUAGE or TITLE
export const textTracksSelectionBy = SelectedTrackType.INDEX;
export const audioTracksSelectionBy = SelectedTrackType.INDEX;
export const isIos = Platform.OS === 'ios';
@@ -25,6 +31,10 @@ export const srcAllPlatformList = [
cropStart: 3000,
cropEnd: 10000,
},
{
description: 'video with 90° rotation',
uri: 'https://bn-dev.fra1.digitaloceanspaces.com/km-tournament/uploads/rn_image_picker_lib_temp_2ee86a27_9312_4548_84af_7fd75d9ad4dd_ad8b20587a.mp4',
},
{
description: 'local file portrait',
uri: localeVideo.portrait,
@@ -86,6 +96,11 @@ export const srcAllPlatformList = [
uri: 'https://bitmovin-a.akamaihd.net/content/sintel/hls/playlist.m3u8',
startPosition: 50000,
},
{
description: 'mp3 with texttrack',
uri: 'https://traffic.libsyn.com/democracynow/wx2024-0702_SOT_DeadCalm-LucileSmith-FULL-V2.mxf-audio.mp3', // an mp3 file
textTracks: [], // empty text track list
},
{
description: 'BigBugBunny sideLoaded subtitles',
// sideloaded subtitles wont work for streaming like HLS on ios
@@ -100,11 +115,19 @@ export const srcAllPlatformList = [
},
],
},
{
description: '(mp4) big buck bunny With Ads',
ad: {
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: 'https://d23dyxeqlo5psv.cloudfront.net/big_buck_bunny.mp4',
},
];
export const srcIosList = [];
export const srcIosList: SampleVideoSource[] = [];
export const srcAndroidList = [
export const srcAndroidList: SampleVideoSource[] = [
{
description: 'Another live sample',
uri: 'https://live.forstreet.cl/live/livestream.m3u8',
@@ -126,12 +149,6 @@ export const srcAndroidList = [
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',
@@ -157,13 +174,12 @@ export const srcAndroidList = [
},
];
// poster which can be displayed
export const samplePoster =
'https://upload.wikimedia.org/wikipedia/commons/1/18/React_Native_Logo.png';
const platformSrc: SampleVideoSource[] = isAndroid
? srcAndroidList
: srcIosList;
export const srcList: SampleVideoSource[] = srcAllPlatformList.concat(
isAndroid ? srcAndroidList : srcIosList,
);
export const srcList: SampleVideoSource[] =
platformSrc.concat(srcAllPlatformList);
export const bufferConfig: BufferConfig = {
minBufferMs: 15000,

View File

@@ -102,9 +102,15 @@ const styles = StyleSheet.create({
borderWidth: 1,
borderColor: 'red',
},
IndicatorStyle: {
flex: 1,
indicatorContainer: {
justifyContent: 'center',
alignItems: 'center',
gap: 10,
width: '100%',
height: '100%',
},
indicatorText: {
color: 'white',
},
seekbarContainer: {
flex: 1,

View File

@@ -1,11 +1,11 @@
import {Drm, ReactVideoSource, TextTracks} from 'react-native-video';
export type AdditionalSourceInfo = {
textTracks: TextTracks;
adTagUrl: string;
description: string;
drm: Drm;
noView: boolean;
textTracks?: TextTracks;
adTagUrl?: string;
description?: string;
drm?: Drm;
noView?: boolean;
};
export type SampleVideoSource = ReactVideoSource | AdditionalSourceInfo;

File diff suppressed because it is too large Load Diff