fix(android)!: rework video tracks management (#3778)

* fix: fix crash when invalid index type is provided and minor clean up
* fix: review video track management. Fix index support and rework string vs int in tracks management
* fix: ABR track selection check
* fix: split track selector in sample and lint code
* fix: ensure we don't report null fields
* chore: improve tracks displayed
* chore: start moving to selection by index only
This commit is contained in:
Olivier Bouillet 2024-05-22 14:01:55 +02:00 committed by GitHub
parent dbd7d7aa2c
commit cad5c4624c
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
15 changed files with 530 additions and 288 deletions

View File

@ -9,7 +9,7 @@ class VideoTrack {
var height = 0
var bitrate = 0
var codecs = ""
var id = -1
var index = -1
var trackId = ""
var isSelected = false
}

View File

@ -194,9 +194,15 @@ public class VideoEventEmitter {
WritableMap audioTrack = Arguments.createMap();
audioTrack.putInt("index", i);
audioTrack.putString("title", format.getTitle());
audioTrack.putString("type", format.getMimeType());
audioTrack.putString("language", format.getLanguage());
audioTrack.putInt("bitrate", format.getBitrate());
if (format.getMimeType() != null) {
audioTrack.putString("type", format.getMimeType());
}
if (format.getLanguage() != null) {
audioTrack.putString("language", format.getLanguage());
}
if (format.getBitrate() > 0) {
audioTrack.putInt("bitrate", format.getBitrate());
}
audioTrack.putBoolean("selected", format.isSelected());
waAudioTracks.pushMap(audioTrack);
}
@ -214,7 +220,8 @@ public class VideoEventEmitter {
videoTrack.putInt("height",vTrack.getHeight());
videoTrack.putInt("bitrate", vTrack.getBitrate());
videoTrack.putString("codecs", vTrack.getCodecs());
videoTrack.putInt("trackId",vTrack.getId());
videoTrack.putString("trackId", vTrack.getTrackId());
videoTrack.putInt("index", vTrack.getIndex());
videoTrack.putBoolean("selected", vTrack.isSelected());
waVideoTracks.pushMap(videoTrack);
}

View File

@ -36,8 +36,8 @@ public final class ExoPlayerView extends FrameLayout implements AdViewProvider {
private final AspectRatioFrameLayout layout;
private final ComponentListener componentListener;
private ExoPlayer player;
private Context context;
private ViewGroup.LayoutParams layoutParams;
private final Context context;
private final ViewGroup.LayoutParams layoutParams;
private final FrameLayout adOverlayFrameLayout;
private boolean useTextureView = true;

View File

@ -1428,13 +1428,14 @@ public class ReactExoplayerView extends FrameLayout implements
videoTrack.setBitrate(format.bitrate == Format.NO_VALUE ? 0 : format.bitrate);
if (format.codecs != null) videoTrack.setCodecs(format.codecs);
videoTrack.setTrackId(format.id == null ? String.valueOf(trackIndex) : format.id);
videoTrack.setIndex(trackIndex);
return videoTrack;
}
private ArrayList<VideoTrack> getVideoTrackInfo() {
ArrayList<VideoTrack> videoTracks = new ArrayList<>();
if (trackSelector == null) {
// Likely player is unmounting so no audio tracks are available anymore
// Likely player is unmounting so no video tracks are available anymore
return videoTracks;
}
MappingTrackSelector.MappedTrackInfo info = trackSelector.getCurrentMappedTrackInfo();
@ -1869,14 +1870,15 @@ public class ReactExoplayerView extends FrameLayout implements
}
}
} else if ("index".equals(type)) {
try {
int iValue = Integer.parseInt(value);
if (iValue < groups.length) {
groupIndex = iValue;
}
} catch (Exception e) {
DebugLog.e(TAG, "cannot parse index:" + value);
int iValue = Integer.parseInt(value);
if (trackType == C.TRACK_TYPE_VIDEO && groups.length == 1) {
groupIndex = 0;
if (iValue < groups.get(groupIndex).length) {
tracks.set(0, iValue);
}
} else if (iValue < groups.length) {
groupIndex = iValue;
}
} else if ("resolution".equals(type)) {
int height = Integer.parseInt(value);

View File

@ -237,9 +237,9 @@ Example:
{ title: '#3 English Director Commentary', language: 'en', index: 2, type: 'text/vtt' }
],
videoTracks: [
{ bitrate: 3987904, codecs: "avc1.640028", height: 720, trackId: "f1-v1-x3", width: 1280 },
{ bitrate: 7981888, codecs: "avc1.640028", height: 1080, trackId: "f2-v1-x3", width: 1920 },
{ bitrate: 1994979, codecs: "avc1.4d401f", height: 480, trackId: "f3-v1-x3", width: 848 }
{ index: 0, bitrate: 3987904, codecs: "avc1.640028", height: 720, trackId: "f1-v1-x3", width: 1280 },
{ index: 1, bitrate: 7981888, codecs: "avc1.640028", height: 1080, trackId: "f2-v1-x3", width: 1920 },
{ index: 2, bitrate: 1994979, codecs: "avc1.4d401f", height: 480, trackId: "f3-v1-x3", width: 848 }
]
}
```
@ -550,7 +550,8 @@ Payload:
| Property | Type | Description |
| -------- | ------- | ------------------------------------- |
| trackId | number | Internal track ID |
| index | number | index of the track |
| trackId | string | Internal track ID |
| codecs | string | MimeType of codec used for this track |
| width | number | Track width |
| height | number | Track height |
@ -563,7 +564,8 @@ Example:
{
videoTracks: [
{
trackId: 0,
index: O,
trackId: "0",
codecs: 'video/mp4',
width: 1920,
height: 1080,

View File

@ -3,7 +3,6 @@
import React, {Component} from 'react';
import {
StyleSheet,
Text,
TouchableOpacity,
View,
@ -15,8 +14,6 @@ import {
Alert,
} from 'react-native';
import {Picker} from '@react-native-picker/picker';
import Video, {
AudioTrack,
OnAudioTracksData,
@ -39,12 +36,33 @@ import Video, {
OnSeekData,
OnPlaybackStateChangedData,
OnPlaybackRateChangeData,
OnVideoTracksData,
VideoTrack,
SelectedVideoTrackType,
SelectedVideoTrack,
BufferingStrategyType,
ReactVideoSource,
Drm,
TextTracks,
} 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';
type AdditionnalSourceInfo = {
textTracks: TextTracks;
adTagUrl: string;
description: string;
drm: Drm;
noView: boolean;
};
type SampleVideoSource = ReactVideoSource | AdditionnalSourceInfo;
interface StateType {
rate: number;
@ -65,8 +83,10 @@ interface StateType {
seeking: boolean;
audioTracks: Array<AudioTrack>;
textTracks: Array<TextTrack>;
videoTracks: Array<VideoTrack>;
selectedAudioTrack: SelectedTrack | undefined;
selectedTextTrack: SelectedTrack | undefined;
selectedVideoTrack: SelectedVideoTrack;
srcListId: number;
loop: boolean;
showRNVControls: boolean;
@ -95,8 +115,12 @@ class VideoPlayer extends Component {
seeking: false,
audioTracks: [],
textTracks: [],
videoTracks: [],
selectedAudioTrack: undefined,
selectedTextTrack: undefined,
selectedVideoTrack: {
type: SelectedVideoTrackType.AUTO,
},
srcListId: 0,
loop: false,
showRNVControls: false,
@ -108,7 +132,7 @@ class VideoPlayer extends Component {
seekerWidth = 0;
// internal usage change to index if you want to select tracks by index instead of lang
textTracksSelectionBy = 'lang';
textTracksSelectionBy = 'index';
srcAllPlatformList = [
{
@ -123,8 +147,9 @@ class VideoPlayer extends Component {
subtitle: 'Test Subtitle',
artist: 'Test Artist',
description: 'Test Description',
imageUri: 'https://pbs.twimg.com/profile_images/1498641868397191170/6qW2XkuI_400x400.png'
}
imageUri:
'https://pbs.twimg.com/profile_images/1498641868397191170/6qW2XkuI_400x400.png',
},
},
{
description: '(hls|live) red bull tv',
@ -134,8 +159,9 @@ class VideoPlayer extends Component {
subtitle: 'Custom Subtitle',
artist: 'Custom Artist',
description: 'Custom Description',
imageUri: 'https://pbs.twimg.com/profile_images/1498641868397191170/6qW2XkuI_400x400.png'
}
imageUri:
'https://pbs.twimg.com/profile_images/1498641868397191170/6qW2XkuI_400x400.png',
},
},
{
description: 'invalid URL',
@ -239,7 +265,7 @@ class VideoPlayer extends Component {
samplePoster =
'https://upload.wikimedia.org/wikipedia/commons/1/18/React_Native_Logo.png';
srcList = this.srcAllPlatformList.concat(
srcList: SampleVideoSource[] = this.srcAllPlatformList.concat(
Platform.OS === 'android' ? this.srcAndroidList : this.srcIosList,
);
@ -270,6 +296,7 @@ class VideoPlayer extends Component {
this.setState({duration: data.duration, loading: false});
this.onAudioTracks(data);
this.onTextTracks(data);
this.onVideoTracks(data);
};
updateSeeker = () => {
@ -301,12 +328,12 @@ class VideoPlayer extends Component {
const selectedTrack = data.audioTracks?.find((x: AudioTrack) => {
return x.selected;
});
if (selectedTrack?.language) {
if (selectedTrack?.index) {
this.setState({
audioTracks: data.audioTracks,
selectedAudioTrack: {
type: 'language',
value: selectedTrack?.language,
type: SelectedVideoTrackType.INDEX,
value: selectedTrack?.index,
},
});
} else {
@ -316,6 +343,13 @@ class VideoPlayer extends Component {
}
};
onVideoTracks = (data: OnVideoTracksData) => {
console.log('onVideoTracks', data.videoTracks);
this.setState({
videoTracks: data.videoTracks,
});
};
onTextTracks = (data: OnTextTracksData) => {
const selectedTrack = data.textTracks?.find((x: TextTrack) => {
return x?.selected;
@ -324,13 +358,16 @@ class VideoPlayer extends Component {
if (selectedTrack?.language) {
this.setState({
textTracks: data.textTracks,
selectedTextTrack: this.textTracksSelectionBy === 'index' ? {
type: 'index',
value: selectedTrack?.index,
}: {
type: 'language',
value: selectedTrack?.language,
},
selectedTextTrack:
this.textTracksSelectionBy === 'index'
? {
type: 'index',
value: selectedTrack?.index,
}
: {
type: 'language',
value: selectedTrack?.language,
},
});
} else {
this.setState({
@ -445,6 +482,9 @@ class VideoPlayer extends Component {
textTracks: [],
selectedAudioTrack: undefined,
selectedTextTrack: undefined,
selectedVideoTrack: {
type: SelectedVideoTrackType.AUTO,
},
});
}
@ -641,7 +681,8 @@ class VideoPlayer extends Component {
return (
<View style={styles.topControlsContainer}>
<Text style={styles.controlOption}>
{this.srcList[this.state.srcListId]?.description || 'local file'}
{(this.srcList[this.state.srcListId] as AdditionnalSourceInfo)
?.description || 'local file'}
</Text>
<View>
<TouchableOpacity
@ -667,6 +708,50 @@ class VideoPlayer extends Component {
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 (
<>
@ -809,77 +894,22 @@ class VideoPlayer extends Component {
</View>
{this.renderSeekBar()}
<View style={styles.generalControls}>
<Text style={styles.controlOption}>AudioTrack</Text>
{this.state.audioTracks?.length <= 0 ? (
<Text style={styles.emptyPickerItem}>empty</Text>
) : (
<Picker
style={styles.picker}
itemStyle={styles.pickerItem}
selectedValue={this.state.selectedAudioTrack?.value}
onValueChange={itemValue => {
console.log('on audio value change ' + itemValue);
this.setState({
selectedAudioTrack: {
type: 'language',
value: itemValue,
},
});
}}>
{this.state.audioTracks.map(track => {
if (!track) {
return;
}
return (
<Picker.Item
label={track.language}
value={track.language}
key={track.language}
/>
);
})}
</Picker>
)}
<Text style={styles.controlOption}>TextTrack</Text>
{this.state.textTracks?.length <= 0 ? (
<Text style={styles.emptyPickerItem}>empty</Text>
) : (
<Picker
style={styles.picker}
itemStyle={styles.pickerItem}
selectedValue={this.state.selectedTextTrack?.value}
onValueChange={itemValue => {
console.log('on value change ' + itemValue);
this.setState({
selectedTextTrack: {
type: this.textTracksSelectionBy === 'index' ? 'index': 'language',
value: itemValue,
},
});
}}>
<Picker.Item label={'none'} value={'none'} key={'none'} />
{this.state.textTracks.map(track => {
if (!track) {
return;
}
if (this.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}
/>);
}
})}
</Picker>
)}
<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>
</>
@ -893,6 +923,9 @@ class VideoPlayer extends Component {
? styles.fullScreen
: styles.halfScreen;
const currentSrc = this.srcList[this.state.srcListId];
const additionnal = currentSrc as AdditionnalSourceInfo;
return (
<TouchableOpacity style={viewStyle}>
<Video
@ -900,10 +933,10 @@ class VideoPlayer extends Component {
ref={(ref: VideoRef) => {
this.video = ref;
}}
source={this.srcList[this.state.srcListId]}
textTracks={this.srcList[this.state.srcListId]?.textTracks}
adTagUrl={this.srcList[this.state.srcListId]?.adTagUrl}
drm={this.srcList[this.state.srcListId]?.drm}
source={currentSrc as ReactVideoSource}
textTracks={additionnal?.textTracks}
adTagUrl={additionnal?.adTagUrl}
drm={additionnal?.drm}
style={viewStyle}
rate={this.state.rate}
paused={this.state.paused}
@ -915,6 +948,7 @@ class VideoPlayer extends Component {
onLoad={this.onLoad}
onAudioTracks={this.onAudioTracks}
onTextTracks={this.onTextTracks}
onVideoTracks={this.onVideoTracks}
onTextTrackDataChanged={this.onTextTrackDataChanged}
onProgress={this.onProgress}
onEnd={this.onEnd}
@ -930,6 +964,7 @@ class VideoPlayer extends Component {
repeat={this.state.loop}
selectedTextTrack={this.state.selectedTextTrack}
selectedAudioTrack={this.state.selectedAudioTrack}
selectedVideoTrack={this.state.selectedVideoTrack}
playInBackground={false}
bufferConfig={{
minBufferMs: 15000,
@ -955,7 +990,7 @@ class VideoPlayer extends Component {
render() {
return (
<View style={styles.container}>
{this.srcList[this.state.srcListId]?.noView
{(this.srcList[this.state.srcListId] as AdditionnalSourceInfo)?.noView
? null
: this.renderVideoView()}
{this.renderOverlay()}
@ -963,168 +998,4 @@ class VideoPlayer extends Component {
);
}
}
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'black',
},
halfScreen: {
position: 'absolute',
top: 50,
left: 50,
bottom: 100,
right: 100,
},
fullScreen: {
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
right: 0,
},
bottomControls: {
backgroundColor: 'transparent',
borderRadius: 5,
position: 'absolute',
bottom: 20,
left: 20,
right: 20,
},
leftControls: {
backgroundColor: 'transparent',
borderRadius: 5,
position: 'absolute',
top: 20,
bottom: 20,
left: 20,
},
rightControls: {
backgroundColor: 'transparent',
borderRadius: 5,
position: 'absolute',
top: 20,
bottom: 20,
right: 20,
},
topControls: {
backgroundColor: 'transparent',
borderRadius: 4,
position: 'absolute',
top: 20,
left: 20,
right: 20,
flex: 1,
flexDirection: 'row',
overflow: 'hidden',
paddingBottom: 10,
},
generalControls: {
flex: 1,
flexDirection: 'row',
borderRadius: 4,
overflow: 'hidden',
paddingBottom: 10,
},
rateControl: {
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
},
volumeControl: {
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
},
resizeModeControl: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
leftRightControlOption: {
alignSelf: 'center',
fontSize: 11,
color: 'white',
padding: 10,
lineHeight: 12,
},
controlOption: {
alignSelf: 'center',
fontSize: 11,
color: 'white',
paddingLeft: 2,
paddingRight: 2,
lineHeight: 12,
},
pickerContainer: {
width: 100,
alignSelf: 'center',
color: 'white',
borderWidth: 1,
borderColor: 'red',
},
IndicatorStyle: {
flex: 1,
justifyContent: 'center',
},
seekbarContainer: {
flex: 1,
flexDirection: 'row',
borderRadius: 4,
height: 30,
},
seekbarTrack: {
backgroundColor: '#333',
height: 1,
position: 'relative',
top: 14,
width: '100%',
},
seekbarFill: {
backgroundColor: '#FFF',
height: 1,
width: '100%',
},
seekbarHandle: {
position: 'absolute',
marginLeft: -7,
height: 28,
width: 28,
},
seekbarCircle: {
borderRadius: 12,
position: 'relative',
top: 8,
left: 8,
height: 12,
width: 12,
},
picker: {
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
width: 100,
height: 40,
},
pickerItem: {
color: 'white',
width: 100,
height: 40,
},
emptyPickerItem: {
color: 'white',
marginTop: 20,
marginLeft: 20,
flex: 1,
width: 100,
height: 40,
},
topControlsContainer: {
paddingTop: 30,
}
});
export default VideoPlayer;

View File

@ -0,0 +1,53 @@
import {Picker} from '@react-native-picker/picker';
import {Text} from 'react-native';
import {AudioTrack, 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;
}
const AudioTrackSelector = ({
audioTracks,
selectedAudioTrack,
onValueChange,
}: AudioTrackSelectorType) => {
return (
<>
<Text style={styles.controlOption}>AudioTrack</Text>
<Picker
style={styles.picker}
itemStyle={styles.pickerItem}
selectedValue={selectedAudioTrack?.value}
onValueChange={itemValue => {
if (itemValue !== 'empty') {
console.log('on audio value change ' + itemValue);
onValueChange(`${itemValue}`);
}
}}>
{audioTracks?.length <= 0 ? (
<Picker.Item label={'empty'} value={'empty'} key={'empty'} />
) : (
<Picker.Item label={'none'} value={'none'} key={'none'} />
)}
{audioTracks.map(track => {
if (!track) {
return;
}
return (
<Picker.Item
label={`${track.language} - ${track.title} - ${track.selected}`}
value={`${track.index}`}
key={`${track.index}`}
/>
);
})}
</Picker>
</>
);
};
export default AudioTrackSelector;

View File

@ -0,0 +1,64 @@
import {Picker} from '@react-native-picker/picker';
import {Text} from 'react-native';
import {TextTrack, SelectedTrack} from 'react-native-video';
import styles from '../styles';
import React from 'react';
export interface TextTrackSelectorType {
textTracks: Array<TextTrack>;
selectedTextTrack: SelectedTrack | undefined;
onValueChange: (arg0: string) => void;
textTracksSelectionBy: string;
}
const TextTrackSelector = ({
textTracks,
selectedTextTrack,
onValueChange,
textTracksSelectionBy,
}: TextTrackSelectorType) => {
return (
<>
<Text style={styles.controlOption}>TextTrack</Text>
<Picker
style={styles.picker}
itemStyle={styles.pickerItem}
selectedValue={`${selectedTextTrack?.value}`}
onValueChange={itemValue => {
if (itemValue !== 'empty') {
onValueChange(itemValue);
}
}}>
{textTracks?.length <= 0 ? (
<Picker.Item label={'empty'} value={'empty'} key={'empty'} />
) : (
<Picker.Item label={'none'} value={'none'} key={'none'} />
)}
{textTracks.map(track => {
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}
/>
);
}
})}
</Picker>
</>
);
};
export default TextTrackSelector;

View File

@ -0,0 +1,64 @@
import {Picker} from '@react-native-picker/picker';
import {Text} from 'react-native';
import {
SelectedVideoTrack,
SelectedVideoTrackType,
VideoTrack,
} from 'react-native-video';
import styles from '../styles';
import React from 'react';
export interface VideoTrackSelectorType {
videoTracks: Array<VideoTrack>;
selectedVideoTrack: SelectedVideoTrack | undefined;
onValueChange: (arg0: string) => void;
}
const VideoTrackSelector = ({
videoTracks,
selectedVideoTrack,
onValueChange,
}: VideoTrackSelectorType) => {
return (
<>
<Text style={styles.controlOption}>VideoTrack</Text>
<Picker
style={styles.picker}
itemStyle={styles.pickerItem}
selectedValue={
selectedVideoTrack === undefined ||
selectedVideoTrack?.type === SelectedVideoTrackType.AUTO
? 'auto'
: `${selectedVideoTrack?.value}`
}
onValueChange={itemValue => {
if (itemValue !== 'empty') {
onValueChange(itemValue);
}
}}>
<Picker.Item label={'auto'} value={'auto'} key={'auto'} />
{videoTracks?.length <= 0 || videoTracks?.length <= 0 ? (
<Picker.Item label={'empty'} value={'empty'} key={'empty'} />
) : (
<Picker.Item label={'none'} value={'none'} key={'none'} />
)}
{videoTracks?.map(track => {
if (!track) {
return;
}
return (
<Picker.Item
label={`${track.width}x${track.height} ${Math.floor(
(track.bitrate || 0) / 8 / 1024,
)} Kbps`}
value={`${track.index}`}
key={track.index}
/>
);
})}
</Picker>
</>
);
};
export default VideoTrackSelector;

View File

@ -0,0 +1,167 @@
import {StyleSheet} from 'react-native';
const styles = StyleSheet.create({
container: {
flex: 1,
justifyContent: 'center',
alignItems: 'center',
backgroundColor: 'black',
},
halfScreen: {
position: 'absolute',
top: 50,
left: 50,
bottom: 100,
right: 100,
},
fullScreen: {
position: 'absolute',
top: 0,
left: 0,
bottom: 0,
right: 0,
},
bottomControls: {
backgroundColor: 'transparent',
borderRadius: 5,
position: 'absolute',
bottom: 20,
left: 20,
right: 20,
},
leftControls: {
backgroundColor: 'transparent',
borderRadius: 5,
position: 'absolute',
top: 20,
bottom: 20,
left: 20,
},
rightControls: {
backgroundColor: 'transparent',
borderRadius: 5,
position: 'absolute',
top: 20,
bottom: 20,
right: 20,
},
topControls: {
backgroundColor: 'transparent',
borderRadius: 4,
position: 'absolute',
top: 20,
left: 20,
right: 20,
flex: 1,
flexDirection: 'row',
overflow: 'hidden',
paddingBottom: 10,
},
generalControls: {
flex: 1,
flexDirection: 'row',
borderRadius: 4,
overflow: 'hidden',
paddingBottom: 10,
},
rateControl: {
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
},
volumeControl: {
flex: 1,
flexDirection: 'row',
justifyContent: 'center',
},
resizeModeControl: {
flex: 1,
flexDirection: 'row',
alignItems: 'center',
justifyContent: 'center',
},
leftRightControlOption: {
alignSelf: 'center',
fontSize: 11,
color: 'white',
padding: 10,
lineHeight: 12,
},
controlOption: {
alignSelf: 'center',
fontSize: 11,
color: 'white',
paddingLeft: 2,
paddingRight: 2,
lineHeight: 12,
},
pickerContainer: {
width: 100,
alignSelf: 'center',
color: 'white',
borderWidth: 1,
borderColor: 'red',
},
IndicatorStyle: {
flex: 1,
justifyContent: 'center',
},
seekbarContainer: {
flex: 1,
flexDirection: 'row',
borderRadius: 4,
height: 30,
},
seekbarTrack: {
backgroundColor: '#333',
height: 1,
position: 'relative',
top: 14,
width: '100%',
},
seekbarFill: {
backgroundColor: '#FFF',
height: 1,
width: '100%',
},
seekbarHandle: {
position: 'absolute',
marginLeft: -7,
height: 28,
width: 28,
},
seekbarCircle: {
borderRadius: 12,
position: 'relative',
top: 8,
left: 8,
height: 12,
width: 12,
},
picker: {
flex: 1,
color: 'white',
flexDirection: 'row',
justifyContent: 'center',
width: 100,
height: 40,
},
pickerItem: {
color: 'white',
width: 100,
height: 40,
},
emptyPickerItem: {
color: 'white',
marginTop: 20,
marginLeft: 20,
flex: 1,
width: 100,
height: 40,
},
topControlsContainer: {
paddingTop: 30,
},
});
export default styles;

View File

@ -6,6 +6,6 @@
"react": ["./node_modules/@types/react"]
}
},
"include": ["src", "**/*.js"],
"jsx": "react",
"exclude": ["node_modules"]
}

View File

@ -222,13 +222,14 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
if (!selectedVideoTrack) {
return;
}
const value = selectedVideoTrack?.value
? `${selectedVideoTrack.value}`
: undefined;
const type = typeof selectedVideoTrack.value;
if (type !== 'number' && type !== 'string') {
console.log('invalid type provided to selectedVideoTrack');
return;
}
return {
type: selectedVideoTrack?.type,
value,
value: `${selectedVideoTrack.value}`,
};
}, [selectedVideoTrack]);

View File

@ -225,7 +225,8 @@ export type OnTextTrackDataChangedData = Readonly<{
export type OnVideoTracksData = Readonly<{
videoTracks: {
trackId: Int32;
index: Int32;
tracksId?: string;
codecs?: string;
width?: Float;
height?: Float;

View File

@ -21,6 +21,7 @@ import type {
export type AudioTrack = OnAudioTracksData['audioTracks'][number];
export type TextTrack = OnTextTracksData['textTracks'][number];
export type VideoTrack = OnVideoTracksData['videoTracks'][number];
export type OnLoadData = Readonly<{
currentTime: number;
@ -48,6 +49,15 @@ export type OnLoadData = Readonly<{
type?: WithDefault<'srt' | 'ttml' | 'vtt', 'srt'>;
selected?: boolean;
}[];
videoTracks: {
index: number;
tracksID?: string;
codecs?: string;
width?: number;
height?: number;
bitrate?: number;
selected?: boolean;
}[];
}>;
export type OnTextTracksData = Readonly<{

View File

@ -117,7 +117,7 @@ export enum SelectedVideoTrackType {
export type SelectedVideoTrack = {
type: SelectedVideoTrackType;
value?: number;
value?: string | number;
};
export type SubtitleStyle = {