feat: bump up fabric example react-native iOS (#3951)
* feat: 🔥 version bump react native for ios FabricExample * feat: ✨ copied newly created basic example TS files
This commit is contained in:
75
examples/FabricExample/src/MultiValueControl.tsx
Normal file
75
examples/FabricExample/src/MultiValueControl.tsx
Normal file
@@ -0,0 +1,75 @@
|
||||
import React, {FunctionComponent} from 'react';
|
||||
|
||||
import {
|
||||
StyleSheet,
|
||||
Text,
|
||||
TextStyle,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native';
|
||||
import {ResizeMode} from 'react-native-video';
|
||||
|
||||
export type MultiValueControlPropType = number | string | ResizeMode;
|
||||
|
||||
/*
|
||||
* MultiValueControl displays a list clickable text view
|
||||
*/
|
||||
|
||||
interface MultiValueControlType<T> {
|
||||
// a list a string or number to be displayed
|
||||
values: Array<T>;
|
||||
// The selected value in values
|
||||
selected?: T;
|
||||
// callback to press onPress
|
||||
onPress: (arg: MultiValueControlPropType) => void;
|
||||
}
|
||||
|
||||
const MultiValueControl: FunctionComponent<
|
||||
MultiValueControlType<MultiValueControlPropType>
|
||||
> = ({values, selected, onPress}) => {
|
||||
const selectedStyle: TextStyle = StyleSheet.flatten([
|
||||
styles.option,
|
||||
{fontWeight: 'bold'},
|
||||
]);
|
||||
|
||||
const unselectedStyle: TextStyle = StyleSheet.flatten([
|
||||
styles.option,
|
||||
{fontWeight: 'normal'},
|
||||
]);
|
||||
|
||||
return (
|
||||
<View style={styles.container}>
|
||||
{values.map((value: MultiValueControlPropType) => {
|
||||
const _style = value === selected ? selectedStyle : unselectedStyle;
|
||||
return (
|
||||
<TouchableOpacity
|
||||
key={value}
|
||||
onPress={() => {
|
||||
onPress?.(value);
|
||||
}}>
|
||||
<Text style={_style}>{value}</Text>
|
||||
</TouchableOpacity>
|
||||
);
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
option: {
|
||||
alignSelf: 'center',
|
||||
fontSize: 11,
|
||||
color: 'white',
|
||||
paddingLeft: 2,
|
||||
paddingRight: 2,
|
||||
lineHeight: 12,
|
||||
},
|
||||
container: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
export default MultiValueControl;
|
73
examples/FabricExample/src/ToggleControl.tsx
Normal file
73
examples/FabricExample/src/ToggleControl.tsx
Normal file
@@ -0,0 +1,73 @@
|
||||
import React from 'react';
|
||||
|
||||
import {
|
||||
StyleSheet,
|
||||
Text,
|
||||
TextStyle,
|
||||
TouchableOpacity,
|
||||
View,
|
||||
} from 'react-native';
|
||||
|
||||
/*
|
||||
* ToggleControl displays a 2 states clickable text
|
||||
*/
|
||||
|
||||
interface ToggleControlType {
|
||||
// boolean indicating if text is selected state
|
||||
isSelected?: boolean;
|
||||
// value of text when selected
|
||||
selectedText?: string;
|
||||
// value of text when NOT selected
|
||||
unselectedText?: string;
|
||||
// default text if no only one text field is needed
|
||||
text?: string;
|
||||
// callback called when pressing the component
|
||||
onPress: () => void;
|
||||
}
|
||||
|
||||
const ToggleControl = ({
|
||||
isSelected,
|
||||
selectedText,
|
||||
unselectedText,
|
||||
text,
|
||||
onPress,
|
||||
}: ToggleControlType) => {
|
||||
const selectedStyle: TextStyle = StyleSheet.flatten([
|
||||
styles.controlOption,
|
||||
{fontWeight: 'bold'},
|
||||
]);
|
||||
|
||||
const unselectedStyle: TextStyle = StyleSheet.flatten([
|
||||
styles.controlOption,
|
||||
{fontWeight: 'normal'},
|
||||
]);
|
||||
|
||||
const style = isSelected ? selectedStyle : unselectedStyle;
|
||||
const _text = text ? text : isSelected ? selectedText : unselectedText;
|
||||
return (
|
||||
<View style={styles.resizeModeControl}>
|
||||
<TouchableOpacity onPress={onPress}>
|
||||
<Text style={style}>{_text}</Text>
|
||||
</TouchableOpacity>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
controlOption: {
|
||||
alignSelf: 'center',
|
||||
fontSize: 11,
|
||||
color: 'white',
|
||||
paddingLeft: 2,
|
||||
paddingRight: 2,
|
||||
lineHeight: 12,
|
||||
},
|
||||
resizeModeControl: {
|
||||
flex: 1,
|
||||
flexDirection: 'row',
|
||||
alignItems: 'center',
|
||||
justifyContent: 'center',
|
||||
},
|
||||
});
|
||||
|
||||
export default ToggleControl;
|
File diff suppressed because it is too large
Load Diff
@@ -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;
|
154
examples/FabricExample/src/components/Seeker.tsx
Normal file
154
examples/FabricExample/src/components/Seeker.tsx
Normal file
@@ -0,0 +1,154 @@
|
||||
import React, {useCallback, useEffect, useState} from 'react';
|
||||
import {PanResponder, View} from 'react-native';
|
||||
import styles from '../styles';
|
||||
|
||||
interface SeekerProps {
|
||||
currentTime: number;
|
||||
duration: number;
|
||||
isLoading: boolean;
|
||||
isUISeeking: boolean;
|
||||
videoSeek: (arg0: number) => void;
|
||||
}
|
||||
|
||||
const Seeker = ({
|
||||
currentTime,
|
||||
duration,
|
||||
isLoading,
|
||||
isUISeeking,
|
||||
videoSeek,
|
||||
}: SeekerProps) => {
|
||||
const [seeking, setSeeking] = useState(false);
|
||||
const [seekerPosition, setSeekerPosition] = useState(0);
|
||||
const [seekerWidth, setSeekerWidth] = useState(0);
|
||||
|
||||
/**
|
||||
* Set the position of the seekbar's components
|
||||
* (both fill and handle) according to the
|
||||
* position supplied.
|
||||
*
|
||||
* @param {float} position position in px of seeker handle}
|
||||
*/
|
||||
const updateSeekerPosition = useCallback(
|
||||
(position = 0) => {
|
||||
if (position <= 0) {
|
||||
position = 0;
|
||||
} else if (position >= seekerWidth) {
|
||||
position = seekerWidth;
|
||||
}
|
||||
setSeekerPosition(position);
|
||||
},
|
||||
[seekerWidth],
|
||||
);
|
||||
|
||||
/**
|
||||
* Return the time that the video should be at
|
||||
* based on where the seeker handle is.
|
||||
*
|
||||
* @return {float} time in ms based on seekerPosition.
|
||||
*/
|
||||
const calculateTimeFromSeekerPosition = () => {
|
||||
const percent = seekerPosition / seekerWidth;
|
||||
return duration * percent;
|
||||
};
|
||||
|
||||
/**
|
||||
* Get our seekbar responder going
|
||||
*/
|
||||
|
||||
const seekPanResponder = PanResponder.create({
|
||||
// Ask to be the responder.
|
||||
onStartShouldSetPanResponder: (_evt, _gestureState) => true,
|
||||
onMoveShouldSetPanResponder: (_evt, _gestureState) => true,
|
||||
|
||||
/**
|
||||
* When we start the pan tell the machine that we're
|
||||
* seeking. This stops it from updating the seekbar
|
||||
* position in the onProgress listener.
|
||||
*/
|
||||
onPanResponderGrant: (evt, _gestureState) => {
|
||||
const position = evt.nativeEvent.locationX;
|
||||
updateSeekerPosition(position);
|
||||
setSeeking(true);
|
||||
},
|
||||
|
||||
/**
|
||||
* When panning, update the seekbar position, duh.
|
||||
*/
|
||||
onPanResponderMove: (evt, _gestureState) => {
|
||||
const position = evt.nativeEvent.locationX;
|
||||
updateSeekerPosition(position);
|
||||
},
|
||||
|
||||
/**
|
||||
* On release we update the time and seek to it in the video.
|
||||
* If you seek to the end of the video we fire the
|
||||
* onEnd callback
|
||||
*/
|
||||
onPanResponderRelease: (_evt, _gestureState) => {
|
||||
const time = calculateTimeFromSeekerPosition();
|
||||
if (time >= duration && !isLoading) {
|
||||
// FIXME ...
|
||||
// state.paused = true;
|
||||
// this.onEnd();
|
||||
} else {
|
||||
videoSeek(time);
|
||||
setSeeking(false);
|
||||
}
|
||||
},
|
||||
});
|
||||
|
||||
useEffect(() => {
|
||||
if (!isLoading && !seeking && !isUISeeking) {
|
||||
const percent = currentTime / duration;
|
||||
const position = seekerWidth * percent;
|
||||
updateSeekerPosition(position);
|
||||
}
|
||||
}, [
|
||||
currentTime,
|
||||
duration,
|
||||
isLoading,
|
||||
seekerWidth,
|
||||
seeking,
|
||||
isUISeeking,
|
||||
updateSeekerPosition,
|
||||
]);
|
||||
|
||||
if (!seekPanResponder) {
|
||||
return null;
|
||||
}
|
||||
const seekerStyle = [
|
||||
styles.seekbarFill,
|
||||
{
|
||||
width: seekerPosition > 0 ? seekerPosition : 0,
|
||||
backgroundColor: '#FFF',
|
||||
},
|
||||
];
|
||||
|
||||
const seekerPositionStyle = [
|
||||
styles.seekbarHandle,
|
||||
{
|
||||
left: seekerPosition > 0 ? seekerPosition : 0,
|
||||
},
|
||||
];
|
||||
|
||||
const seekerPointerStyle = [styles.seekbarCircle, {backgroundColor: '#FFF'}];
|
||||
|
||||
return (
|
||||
<View
|
||||
style={styles.seekbarContainer}
|
||||
{...seekPanResponder.panHandlers}
|
||||
{...styles.generalControls}>
|
||||
<View
|
||||
style={styles.seekbarTrack}
|
||||
onLayout={event => setSeekerWidth(event.nativeEvent.layout.width)}
|
||||
pointerEvents={'none'}>
|
||||
<View style={seekerStyle} pointerEvents={'none'} />
|
||||
</View>
|
||||
<View style={seekerPositionStyle} pointerEvents={'none'}>
|
||||
<View style={seekerPointerStyle} pointerEvents={'none'} />
|
||||
</View>
|
||||
</View>
|
||||
);
|
||||
};
|
||||
|
||||
export default Seeker;
|
64
examples/FabricExample/src/components/TextTracksSelector.tsx
Normal file
64
examples/FabricExample/src/components/TextTracksSelector.tsx
Normal 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;
|
@@ -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;
|
BIN
examples/FabricExample/src/portrait.mp4
Normal file
BIN
examples/FabricExample/src/portrait.mp4
Normal file
Binary file not shown.
167
examples/FabricExample/src/styles.tsx
Normal file
167
examples/FabricExample/src/styles.tsx
Normal 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;
|
Reference in New Issue
Block a user