feat: add typescript (#3266)
* chore: update dependencies * chore: add typescript config * feat: add types * chore: add build command * chore: fix types * fix: update linters * chore: add display name to component * chore: fix types * chore: remove re-declare name variables * docs: update changelog
This commit is contained in:
parent
f4acaccd80
commit
92831afd5f
@ -1,3 +1,10 @@
|
||||
{
|
||||
"extends": "@react-native-community",
|
||||
"extends": [
|
||||
"@react-native",
|
||||
"eslint:recommended",
|
||||
"plugin:react/recommended"
|
||||
],
|
||||
"parserOptions": {
|
||||
"requireConfigFile": false
|
||||
}
|
||||
}
|
5
.gitignore
vendored
5
.gitignore
vendored
@ -55,3 +55,8 @@ android/keystores/debug.keystore
|
||||
Deploy.binlog
|
||||
msbuild.binlog
|
||||
android/buildOutput_*
|
||||
|
||||
# lib build
|
||||
lib/
|
||||
!src/lib
|
||||
*.tsbuildinfo
|
@ -1,8 +1,6 @@
|
||||
{
|
||||
"requirePragma": true,
|
||||
"singleQuote": true,
|
||||
"trailingComma": "all",
|
||||
"bracketSpacing": false,
|
||||
"jsxBracketSameLine": true,
|
||||
"parser": "flow"
|
||||
"jsxBracketSameLine": true
|
||||
}
|
@ -1,6 +1,7 @@
|
||||
## Changelog
|
||||
|
||||
## Next
|
||||
- All: add built-in typescript support [#3266](https://github.com/react-native-video/react-native-video/pull/3266)
|
||||
- iOS, Android: expose playback functions to ref [#3245](https://github.com/react-native-video/react-native-video/pull/3245)
|
||||
- Windows: fix build error from over-specified SDK version [#3246](https://github.com/react-native-video/react-native-video/pull/3246)
|
||||
- Windows: fix `onError` not being raised [#3247](https://github.com/react-native-video/react-native-video/pull/3247)
|
||||
|
587
Video.js
587
Video.js
@ -1,587 +0,0 @@
|
||||
import React, { Component } from 'react';
|
||||
import PropTypes from 'prop-types';
|
||||
import { StyleSheet, requireNativeComponent, NativeModules, UIManager, View, Image, Platform, findNodeHandle } from 'react-native';
|
||||
import { ViewPropTypes, ImagePropTypes } from 'deprecated-react-native-prop-types';
|
||||
import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';
|
||||
import TextTrackType from './TextTrackType';
|
||||
import FilterType from './FilterType';
|
||||
import DRMType from './DRMType';
|
||||
import VideoResizeMode from './VideoResizeMode.js';
|
||||
|
||||
const styles = StyleSheet.create({
|
||||
base: {
|
||||
overflow: 'hidden',
|
||||
},
|
||||
});
|
||||
|
||||
const { VideoDecoderProperties } = NativeModules;
|
||||
export { TextTrackType, FilterType, DRMType, VideoDecoderProperties };
|
||||
|
||||
export default class Video extends Component {
|
||||
|
||||
constructor(props) {
|
||||
super(props);
|
||||
|
||||
this.state = {
|
||||
showPoster: !!props.poster,
|
||||
};
|
||||
}
|
||||
|
||||
setNativeProps(nativeProps) {
|
||||
this._root.setNativeProps(nativeProps);
|
||||
}
|
||||
|
||||
toTypeString(x) {
|
||||
switch (typeof x) {
|
||||
case 'object':
|
||||
return x instanceof Date
|
||||
? x.toISOString()
|
||||
: JSON.stringify(x); // object, null
|
||||
case 'undefined':
|
||||
return '';
|
||||
default: // boolean, number, string
|
||||
return x.toString();
|
||||
}
|
||||
}
|
||||
|
||||
stringsOnlyObject(obj) {
|
||||
const strObj = {};
|
||||
|
||||
Object.keys(obj).forEach(x => {
|
||||
strObj[x] = this.toTypeString(obj[x]);
|
||||
});
|
||||
|
||||
return strObj;
|
||||
}
|
||||
|
||||
seek = (time, tolerance = 100) => {
|
||||
if (isNaN(time)) {throw new Error('Specified time is not a number');}
|
||||
|
||||
if (Platform.OS === 'ios') {
|
||||
this.setNativeProps({
|
||||
seek: {
|
||||
time,
|
||||
tolerance,
|
||||
},
|
||||
});
|
||||
} else {
|
||||
this.setNativeProps({ seek: time });
|
||||
}
|
||||
};
|
||||
|
||||
presentFullscreenPlayer = () => {
|
||||
this.setNativeProps({ fullscreen: true });
|
||||
};
|
||||
|
||||
dismissFullscreenPlayer = () => {
|
||||
this.setNativeProps({ fullscreen: false });
|
||||
};
|
||||
|
||||
save = async (options) => {
|
||||
return await NativeModules.VideoManager.save(options, findNodeHandle(this._root));
|
||||
}
|
||||
|
||||
pause = async () => {
|
||||
await this.setPlayerPauseState(true);
|
||||
};
|
||||
|
||||
play = async () => {
|
||||
await this.setPlayerPauseState(false);
|
||||
};
|
||||
|
||||
setPlayerPauseState = async (paused) => {
|
||||
return await NativeModules.VideoManager.setPlayerPauseState(paused, findNodeHandle(this._root));
|
||||
};
|
||||
|
||||
restoreUserInterfaceForPictureInPictureStopCompleted = (restored) => {
|
||||
this.setNativeProps({ restoreUserInterfaceForPIPStopCompletionHandler: restored });
|
||||
};
|
||||
|
||||
_assignRoot = (component) => {
|
||||
this._root = component;
|
||||
};
|
||||
|
||||
_hidePoster = () => {
|
||||
if (this.state.showPoster) {
|
||||
this.setState({ showPoster: false });
|
||||
}
|
||||
}
|
||||
|
||||
_onLoadStart = (event) => {
|
||||
if (this.props.onLoadStart) {
|
||||
this.props.onLoadStart(event.nativeEvent);
|
||||
}
|
||||
};
|
||||
|
||||
_onPlaybackStateChanged = (event) => {
|
||||
if (this.props.onPlaybackStateChanged) {
|
||||
this.props.onPlaybackStateChanged(event.nativeEvent);
|
||||
}
|
||||
};
|
||||
|
||||
_onLoad = (event) => {
|
||||
// Need to hide poster here for windows as onReadyForDisplay is not implemented
|
||||
if (Platform.OS === 'windows') {
|
||||
this._hidePoster();
|
||||
}
|
||||
if (this.props.onLoad) {
|
||||
this.props.onLoad(event.nativeEvent);
|
||||
}
|
||||
};
|
||||
|
||||
_onAudioTracks = (event) => {
|
||||
if (this.props.onAudioTracks) {
|
||||
this.props.onAudioTracks(event.nativeEvent);
|
||||
}
|
||||
};
|
||||
|
||||
_onTextTracks = (event) => {
|
||||
if (this.props.onTextTracks) {
|
||||
this.props.onTextTracks(event.nativeEvent);
|
||||
}
|
||||
};
|
||||
|
||||
_onVideoTracks = (event) => {
|
||||
if (this.props.onVideoTracks) {
|
||||
this.props.onVideoTracks(event.nativeEvent);
|
||||
}
|
||||
};
|
||||
|
||||
_onError = (event) => {
|
||||
if (this.props.onError) {
|
||||
this.props.onError(event.nativeEvent);
|
||||
}
|
||||
};
|
||||
|
||||
_onProgress = (event) => {
|
||||
if (this.props.onProgress) {
|
||||
this.props.onProgress(event.nativeEvent);
|
||||
}
|
||||
};
|
||||
|
||||
_onBandwidthUpdate = (event) => {
|
||||
if (this.props.onBandwidthUpdate) {
|
||||
this.props.onBandwidthUpdate(event.nativeEvent);
|
||||
}
|
||||
};
|
||||
|
||||
_onSeek = (event) => {
|
||||
if (this.props.onSeek) {
|
||||
this.props.onSeek(event.nativeEvent);
|
||||
}
|
||||
};
|
||||
|
||||
_onEnd = (event) => {
|
||||
if (this.props.onEnd) {
|
||||
this.props.onEnd(event.nativeEvent);
|
||||
}
|
||||
};
|
||||
|
||||
_onTimedMetadata = (event) => {
|
||||
if (this.props.onTimedMetadata) {
|
||||
this.props.onTimedMetadata(event.nativeEvent);
|
||||
}
|
||||
};
|
||||
|
||||
_onFullscreenPlayerWillPresent = (event) => {
|
||||
if (this.props.onFullscreenPlayerWillPresent) {
|
||||
this.props.onFullscreenPlayerWillPresent(event.nativeEvent);
|
||||
}
|
||||
};
|
||||
|
||||
_onFullscreenPlayerDidPresent = (event) => {
|
||||
if (this.props.onFullscreenPlayerDidPresent) {
|
||||
this.props.onFullscreenPlayerDidPresent(event.nativeEvent);
|
||||
}
|
||||
};
|
||||
|
||||
_onFullscreenPlayerWillDismiss = (event) => {
|
||||
if (this.props.onFullscreenPlayerWillDismiss) {
|
||||
this.props.onFullscreenPlayerWillDismiss(event.nativeEvent);
|
||||
}
|
||||
};
|
||||
|
||||
_onFullscreenPlayerDidDismiss = (event) => {
|
||||
if (this.props.onFullscreenPlayerDidDismiss) {
|
||||
this.props.onFullscreenPlayerDidDismiss(event.nativeEvent);
|
||||
}
|
||||
};
|
||||
|
||||
_onReadyForDisplay = (event) => {
|
||||
if (!this.props.audioOnly) {
|
||||
this._hidePoster();
|
||||
}
|
||||
|
||||
if (this.props.onReadyForDisplay) {
|
||||
this.props.onReadyForDisplay(event.nativeEvent);
|
||||
}
|
||||
};
|
||||
|
||||
_onPlaybackStalled = (event) => {
|
||||
if (this.props.onPlaybackStalled) {
|
||||
this.props.onPlaybackStalled(event.nativeEvent);
|
||||
}
|
||||
};
|
||||
|
||||
_onPlaybackResume = (event) => {
|
||||
if (this.props.onPlaybackResume) {
|
||||
this.props.onPlaybackResume(event.nativeEvent);
|
||||
}
|
||||
};
|
||||
|
||||
_onPlaybackRateChange = (event) => {
|
||||
if (this.props.onPlaybackRateChange) {
|
||||
this.props.onPlaybackRateChange(event.nativeEvent);
|
||||
}
|
||||
};
|
||||
|
||||
_onExternalPlaybackChange = (event) => {
|
||||
if (this.props.onExternalPlaybackChange) {
|
||||
this.props.onExternalPlaybackChange(event.nativeEvent);
|
||||
}
|
||||
}
|
||||
|
||||
_onAudioBecomingNoisy = () => {
|
||||
if (this.props.onAudioBecomingNoisy) {
|
||||
this.props.onAudioBecomingNoisy();
|
||||
}
|
||||
};
|
||||
|
||||
_onPictureInPictureStatusChanged = (event) => {
|
||||
if (this.props.onPictureInPictureStatusChanged) {
|
||||
this.props.onPictureInPictureStatusChanged(event.nativeEvent);
|
||||
}
|
||||
};
|
||||
|
||||
_onRestoreUserInterfaceForPictureInPictureStop = (event) => {
|
||||
if (this.props.onRestoreUserInterfaceForPictureInPictureStop) {
|
||||
this.props.onRestoreUserInterfaceForPictureInPictureStop();
|
||||
}
|
||||
};
|
||||
|
||||
_onAudioFocusChanged = (event) => {
|
||||
if (this.props.onAudioFocusChanged) {
|
||||
this.props.onAudioFocusChanged(event.nativeEvent);
|
||||
}
|
||||
};
|
||||
|
||||
_onBuffer = (event) => {
|
||||
if (this.props.onBuffer) {
|
||||
this.props.onBuffer(event.nativeEvent);
|
||||
}
|
||||
};
|
||||
|
||||
_onGetLicense = (event) => {
|
||||
if (this.props.drm && this.props.drm.getLicense instanceof Function) {
|
||||
const data = event.nativeEvent;
|
||||
if (data && data.spcBase64) {
|
||||
const getLicenseOverride = this.props.drm.getLicense(data.spcBase64, data.contentId, data.licenseUrl);
|
||||
const getLicensePromise = Promise.resolve(getLicenseOverride); // Handles both scenarios, getLicenseOverride being a promise and not.
|
||||
getLicensePromise.then((result => {
|
||||
if (result !== undefined) {
|
||||
NativeModules.VideoManager.setLicenseResult(result, data.licenseUrl, findNodeHandle(this._root));
|
||||
} else {
|
||||
NativeModules.VideoManager.setLicenseError && NativeModules.VideoManager.setLicenseError('Empty license result', data.licenseUrl, findNodeHandle(this._root));
|
||||
}
|
||||
})).catch((error) => {
|
||||
NativeModules.VideoManager.setLicenseError && NativeModules.VideoManager.setLicenseError(error, data.licenseUrl, findNodeHandle(this._root));
|
||||
});
|
||||
} else {
|
||||
NativeModules.VideoManager.setLicenseError && NativeModules.VideoManager.setLicenseError('No spc received', data.licenseUrl, findNodeHandle(this._root));
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
_onReceiveAdEvent = (event) => {
|
||||
if (this.props.onReceiveAdEvent) {
|
||||
this.props.onReceiveAdEvent(event.nativeEvent);
|
||||
}
|
||||
};
|
||||
|
||||
getViewManagerConfig = viewManagerName => {
|
||||
if (!UIManager.getViewManagerConfig) {
|
||||
return UIManager[viewManagerName];
|
||||
}
|
||||
return UIManager.getViewManagerConfig(viewManagerName);
|
||||
};
|
||||
|
||||
render() {
|
||||
const resizeMode = this.props.resizeMode;
|
||||
const source = resolveAssetSource(this.props.source) || {};
|
||||
const shouldCache = !source.__packager_asset;
|
||||
|
||||
let uri = source.uri || '';
|
||||
if (uri && uri.match(/^\//)) {
|
||||
uri = `file://${uri}`;
|
||||
}
|
||||
|
||||
if (!uri) {
|
||||
console.log('Trying to load empty source.');
|
||||
}
|
||||
|
||||
const isNetwork = !!(uri && uri.match(/^https?:/i));
|
||||
const isAsset = !!(uri && uri.match(/^(assets-library|ph|ipod-library|file|content|ms-appx|ms-appdata):/i));
|
||||
|
||||
if ((uri || uri === '') && !isNetwork && !isAsset) {
|
||||
if (this.props.onError) {
|
||||
this.props.onError({error: {errorString: 'invalid url, player will stop', errorCode: 'INVALID_URL'}});
|
||||
}
|
||||
}
|
||||
|
||||
let nativeResizeMode;
|
||||
const RCTVideoInstance = this.getViewManagerConfig('RCTVideo');
|
||||
|
||||
if (resizeMode === VideoResizeMode.stretch) {
|
||||
nativeResizeMode = RCTVideoInstance.Constants.ScaleToFill;
|
||||
} else if (resizeMode === VideoResizeMode.contain) {
|
||||
nativeResizeMode = RCTVideoInstance.Constants.ScaleAspectFit;
|
||||
} else if (resizeMode === VideoResizeMode.cover) {
|
||||
nativeResizeMode = RCTVideoInstance.Constants.ScaleAspectFill;
|
||||
} else {
|
||||
nativeResizeMode = RCTVideoInstance.Constants.ScaleNone;
|
||||
}
|
||||
|
||||
const nativeProps = Object.assign({}, this.props);
|
||||
Object.assign(nativeProps, {
|
||||
style: [styles.base, nativeProps.style],
|
||||
resizeMode: nativeResizeMode,
|
||||
src: {
|
||||
uri,
|
||||
isNetwork,
|
||||
isAsset,
|
||||
shouldCache,
|
||||
type: source.type || '',
|
||||
mainVer: source.mainVer || 0,
|
||||
patchVer: source.patchVer || 0,
|
||||
requestHeaders: source.headers ? this.stringsOnlyObject(source.headers) : {},
|
||||
startTime: source.startTime || 0,
|
||||
endTime: source.endTime,
|
||||
// Custom Metadata
|
||||
title: source.title,
|
||||
subtitle: source.subtitle,
|
||||
description: source.description,
|
||||
},
|
||||
onVideoLoadStart: this._onLoadStart,
|
||||
onVideoPlaybackStateChanged: this._onPlaybackStateChanged,
|
||||
onVideoLoad: this._onLoad,
|
||||
onAudioTracks: this._onAudioTracks,
|
||||
onTextTracks: this._onTextTracks,
|
||||
onVideoTracks: this._onVideoTracks,
|
||||
onVideoError: this._onError,
|
||||
onVideoProgress: this._onProgress,
|
||||
onVideoSeek: this._onSeek,
|
||||
onVideoEnd: this._onEnd,
|
||||
onVideoBuffer: this._onBuffer,
|
||||
onVideoBandwidthUpdate: this._onBandwidthUpdate,
|
||||
onTimedMetadata: this._onTimedMetadata,
|
||||
onVideoAudioBecomingNoisy: this._onAudioBecomingNoisy,
|
||||
onVideoExternalPlaybackChange: this._onExternalPlaybackChange,
|
||||
onVideoFullscreenPlayerWillPresent: this._onFullscreenPlayerWillPresent,
|
||||
onVideoFullscreenPlayerDidPresent: this._onFullscreenPlayerDidPresent,
|
||||
onVideoFullscreenPlayerWillDismiss: this._onFullscreenPlayerWillDismiss,
|
||||
onVideoFullscreenPlayerDidDismiss: this._onFullscreenPlayerDidDismiss,
|
||||
onReadyForDisplay: this._onReadyForDisplay,
|
||||
onPlaybackStalled: this._onPlaybackStalled,
|
||||
onPlaybackResume: this._onPlaybackResume,
|
||||
onPlaybackRateChange: this._onPlaybackRateChange,
|
||||
onAudioFocusChanged: this._onAudioFocusChanged,
|
||||
onAudioBecomingNoisy: this._onAudioBecomingNoisy,
|
||||
onGetLicense: nativeProps.drm && nativeProps.drm.getLicense && this._onGetLicense,
|
||||
onPictureInPictureStatusChanged: this._onPictureInPictureStatusChanged,
|
||||
onRestoreUserInterfaceForPictureInPictureStop: this._onRestoreUserInterfaceForPictureInPictureStop,
|
||||
onReceiveAdEvent: this._onReceiveAdEvent,
|
||||
});
|
||||
|
||||
const posterStyle = {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
resizeMode: this.props.posterResizeMode || 'contain',
|
||||
};
|
||||
|
||||
return (
|
||||
<View style={nativeProps.style}>
|
||||
<RCTVideo
|
||||
ref={this._assignRoot}
|
||||
{...nativeProps}
|
||||
style={StyleSheet.absoluteFill}
|
||||
/>
|
||||
{this.state.showPoster && (
|
||||
<Image style={posterStyle} source={{ uri: this.props.poster }} />
|
||||
)}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
Video.propTypes = {
|
||||
filter: PropTypes.oneOf([
|
||||
FilterType.NONE,
|
||||
FilterType.INVERT,
|
||||
FilterType.MONOCHROME,
|
||||
FilterType.POSTERIZE,
|
||||
FilterType.FALSE,
|
||||
FilterType.MAXIMUMCOMPONENT,
|
||||
FilterType.MINIMUMCOMPONENT,
|
||||
FilterType.CHROME,
|
||||
FilterType.FADE,
|
||||
FilterType.INSTANT,
|
||||
FilterType.MONO,
|
||||
FilterType.NOIR,
|
||||
FilterType.PROCESS,
|
||||
FilterType.TONAL,
|
||||
FilterType.TRANSFER,
|
||||
FilterType.SEPIA,
|
||||
]),
|
||||
filterEnabled: PropTypes.bool,
|
||||
onVideoLoadStart: PropTypes.func,
|
||||
onVideoLoad: PropTypes.func,
|
||||
onVideoBuffer: PropTypes.func,
|
||||
onVideoError: PropTypes.func,
|
||||
onVideoProgress: PropTypes.func,
|
||||
onVideoBandwidthUpdate: PropTypes.func,
|
||||
onVideoSeek: PropTypes.func,
|
||||
onVideoEnd: PropTypes.func,
|
||||
onTimedMetadata: PropTypes.func,
|
||||
onVideoAudioBecomingNoisy: PropTypes.func,
|
||||
onVideoExternalPlaybackChange: PropTypes.func,
|
||||
onVideoFullscreenPlayerWillPresent: PropTypes.func,
|
||||
onVideoFullscreenPlayerDidPresent: PropTypes.func,
|
||||
onVideoFullscreenPlayerWillDismiss: PropTypes.func,
|
||||
onVideoFullscreenPlayerDidDismiss: PropTypes.func,
|
||||
|
||||
/* Wrapper component */
|
||||
source: PropTypes.oneOfType([
|
||||
PropTypes.shape({
|
||||
uri: PropTypes.string,
|
||||
}),
|
||||
// Opaque type returned by require('./video.mp4')
|
||||
PropTypes.number,
|
||||
]),
|
||||
drm: PropTypes.shape({
|
||||
type: PropTypes.oneOf([
|
||||
DRMType.CLEARKEY, DRMType.FAIRPLAY, DRMType.WIDEVINE, DRMType.PLAYREADY,
|
||||
]),
|
||||
licenseServer: PropTypes.string,
|
||||
headers: PropTypes.shape({}),
|
||||
base64Certificate: PropTypes.bool,
|
||||
certificateUrl: PropTypes.string,
|
||||
getLicense: PropTypes.func,
|
||||
}),
|
||||
localSourceEncryptionKeyScheme: PropTypes.string,
|
||||
minLoadRetryCount: PropTypes.number,
|
||||
maxBitRate: PropTypes.number,
|
||||
resizeMode: PropTypes.string,
|
||||
poster: PropTypes.string,
|
||||
posterResizeMode: ImagePropTypes.resizeMode,
|
||||
repeat: PropTypes.bool,
|
||||
automaticallyWaitsToMinimizeStalling: PropTypes.bool,
|
||||
allowsExternalPlayback: PropTypes.bool,
|
||||
selectedAudioTrack: PropTypes.shape({
|
||||
type: PropTypes.string.isRequired,
|
||||
value: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
]),
|
||||
}),
|
||||
selectedVideoTrack: PropTypes.shape({
|
||||
type: PropTypes.string.isRequired,
|
||||
value: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
]),
|
||||
}),
|
||||
selectedTextTrack: PropTypes.shape({
|
||||
type: PropTypes.string.isRequired,
|
||||
value: PropTypes.oneOfType([
|
||||
PropTypes.string,
|
||||
PropTypes.number,
|
||||
]),
|
||||
}),
|
||||
textTracks: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
title: PropTypes.string,
|
||||
uri: PropTypes.string.isRequired,
|
||||
type: PropTypes.oneOf([
|
||||
TextTrackType.SRT,
|
||||
TextTrackType.TTML,
|
||||
TextTrackType.VTT,
|
||||
]),
|
||||
language: PropTypes.string.isRequired,
|
||||
})
|
||||
),
|
||||
chapters: PropTypes.arrayOf(
|
||||
PropTypes.shape({
|
||||
title: PropTypes.string,
|
||||
startTime: PropTypes.number.isRequired,
|
||||
endTime: PropTypes.number.isRequired,
|
||||
})
|
||||
),
|
||||
paused: PropTypes.bool,
|
||||
muted: PropTypes.bool,
|
||||
volume: PropTypes.number,
|
||||
bufferConfig: PropTypes.shape({
|
||||
minBufferMs: PropTypes.number,
|
||||
maxBufferMs: PropTypes.number,
|
||||
bufferForPlaybackMs: PropTypes.number,
|
||||
bufferForPlaybackAfterRebufferMs: PropTypes.number,
|
||||
maxHeapAllocationPercent: PropTypes.number,
|
||||
}),
|
||||
rate: PropTypes.number,
|
||||
pictureInPicture: PropTypes.bool,
|
||||
playInBackground: PropTypes.bool,
|
||||
preferredForwardBufferDuration: PropTypes.number,
|
||||
playWhenInactive: PropTypes.bool,
|
||||
ignoreSilentSwitch: PropTypes.oneOf(['ignore', 'obey']),
|
||||
reportBandwidth: PropTypes.bool,
|
||||
contentStartTime: PropTypes.number,
|
||||
disableFocus: PropTypes.bool,
|
||||
focusable: PropTypes.bool,
|
||||
disableBuffering: PropTypes.bool,
|
||||
controls: PropTypes.bool,
|
||||
audioOnly: PropTypes.bool,
|
||||
audioOutput: PropTypes.oneOf(['earpiece', 'speaker']),
|
||||
fullscreenAutorotate: PropTypes.bool,
|
||||
fullscreenOrientation: PropTypes.oneOf(['all', 'landscape', 'portrait']),
|
||||
progressUpdateInterval: PropTypes.number,
|
||||
subtitleStyle: PropTypes.shape({
|
||||
paddingTop: PropTypes.number,
|
||||
paddingBottom: PropTypes.number,
|
||||
paddingLeft: PropTypes.number,
|
||||
paddingRight: PropTypes.number,
|
||||
fontSize: PropTypes.number,
|
||||
}),
|
||||
useTextureView: PropTypes.bool,
|
||||
useSecureView: PropTypes.bool,
|
||||
hideShutterView: PropTypes.bool,
|
||||
shutterColor: PropTypes.string,
|
||||
onLoadStart: PropTypes.func,
|
||||
onPlaybackStateChanged: PropTypes.func,
|
||||
onLoad: PropTypes.func,
|
||||
onAudioTracks: PropTypes.func,
|
||||
onTextTracks: PropTypes.func,
|
||||
onVideoTracks: PropTypes.func,
|
||||
onBuffer: PropTypes.func,
|
||||
onError: PropTypes.func,
|
||||
onProgress: PropTypes.func,
|
||||
onBandwidthUpdate: PropTypes.func,
|
||||
onSeek: PropTypes.func,
|
||||
onEnd: PropTypes.func,
|
||||
onFullscreenPlayerWillPresent: PropTypes.func,
|
||||
onFullscreenPlayerDidPresent: PropTypes.func,
|
||||
onFullscreenPlayerWillDismiss: PropTypes.func,
|
||||
onFullscreenPlayerDidDismiss: PropTypes.func,
|
||||
onReadyForDisplay: PropTypes.func,
|
||||
onPlaybackStalled: PropTypes.func,
|
||||
onPlaybackResume: PropTypes.func,
|
||||
onPlaybackRateChange: PropTypes.func,
|
||||
onAudioFocusChanged: PropTypes.func,
|
||||
onAudioBecomingNoisy: PropTypes.func,
|
||||
onPictureInPictureStatusChanged: PropTypes.func,
|
||||
onExternalPlaybackChange: PropTypes.func,
|
||||
adTagUrl: PropTypes.string,
|
||||
onReceiveAdEvent: PropTypes.func,
|
||||
|
||||
/* Required by react-native */
|
||||
...ViewPropTypes,
|
||||
};
|
||||
|
||||
const RCTVideo = requireNativeComponent('RCTVideo');
|
@ -1,7 +0,0 @@
|
||||
import keyMirror from 'keymirror';
|
||||
|
||||
export default keyMirror({
|
||||
contain: null,
|
||||
cover: null,
|
||||
stretch: null,
|
||||
});
|
45
package.json
45
package.json
@ -2,8 +2,9 @@
|
||||
"name": "react-native-video",
|
||||
"version": "6.0.0-alpha.8",
|
||||
"description": "A <Video /> element for react-native",
|
||||
"main": "Video.js",
|
||||
"source": "Video.js",
|
||||
"main": "lib/index",
|
||||
"source": "src/index",
|
||||
"react-native": "src/index",
|
||||
"license": "MIT",
|
||||
"author": "Community Contributors",
|
||||
"homepage": "https://github.com/react-native-video/react-native-video#readme",
|
||||
@ -11,24 +12,32 @@
|
||||
"type": "git",
|
||||
"url": "git@github.com:react-native-video/react-native-video.git"
|
||||
},
|
||||
"resolutions": {
|
||||
"@types/react": "~18.0.0"
|
||||
},
|
||||
"devDependencies": {
|
||||
"@react-native-community/eslint-config": "^0.0.5",
|
||||
"eslint": "^6.5.1",
|
||||
"react": "16.9.0",
|
||||
"react-native": "0.61.5",
|
||||
"react-native-windows": "^0.61.0-0"
|
||||
},
|
||||
"dependencies": {
|
||||
"deprecated-react-native-prop-types": "^2.2.0",
|
||||
"keymirror": "^0.1.1",
|
||||
"prop-types": "^15.7.2"
|
||||
"@react-native/eslint-config": "^0.72.2",
|
||||
"@types/jest": "^28.1.2",
|
||||
"@types/react": "~18.0.0",
|
||||
"@types/react-native": "0.72.3",
|
||||
"@typescript-eslint/eslint-plugin": "^6.7.4",
|
||||
"eslint": "^8.19.0",
|
||||
"eslint-plugin-jest": "^27.4.2",
|
||||
"jest": "^29.7.0",
|
||||
"prettier": "^2.4.1",
|
||||
"react": "18.2.0",
|
||||
"react-native": "0.72.5",
|
||||
"react-native-windows": "^0.61.0-0",
|
||||
"typescript": "5.1.6"
|
||||
},
|
||||
"dependencies": {},
|
||||
"peerDependencies": {
|
||||
"react": "*",
|
||||
"react-native": "*"
|
||||
},
|
||||
"scripts": {
|
||||
"lint": "yarn eslint .",
|
||||
"build": "yarn tsc",
|
||||
"xbasic": "yarn --cwd examples/basic",
|
||||
"test": "echo no test available"
|
||||
},
|
||||
@ -36,11 +45,13 @@
|
||||
"android",
|
||||
"ios",
|
||||
"windows",
|
||||
"FilterType.js",
|
||||
"DRMType.js",
|
||||
"TextTrackType.js",
|
||||
"VideoResizeMode.js",
|
||||
"src",
|
||||
"react-native-video.podspec",
|
||||
"docs"
|
||||
"docs",
|
||||
"!android/build",
|
||||
"!android/buildOutput_*",
|
||||
"!android/local.properties",
|
||||
"!ios/build",
|
||||
"!**/*.tsbuildinfo"
|
||||
]
|
||||
}
|
||||
|
449
src/Video.tsx
Normal file
449
src/Video.tsx
Normal file
@ -0,0 +1,449 @@
|
||||
import React, {
|
||||
useState,
|
||||
useCallback,
|
||||
useMemo,
|
||||
useRef,
|
||||
forwardRef,
|
||||
useImperativeHandle,
|
||||
type ComponentRef,
|
||||
} from "react";
|
||||
import {
|
||||
View,
|
||||
StyleSheet,
|
||||
Image,
|
||||
Platform,
|
||||
} from "react-native";
|
||||
import NativeVideoComponent, { RCTVideoConstants } from "./VideoNativeComponent";
|
||||
import type { NativeVideoResizeMode, OnAudioFocusChangedData, OnAudioTracksData, OnPlaybackStateChangedData, OnTextTracksData, OnTimedMetadataData, OnVideoErrorData, OnVideoTracksData } from './VideoNativeComponent'
|
||||
|
||||
import type { StyleProp, ImageStyle, NativeSyntheticEvent } from "react-native";
|
||||
import {
|
||||
type VideoComponentType,
|
||||
type OnLoadData,
|
||||
type OnGetLicenseData,
|
||||
type OnLoadStartData,
|
||||
type OnProgressData,
|
||||
type OnSeekData,
|
||||
type OnPictureInPictureStatusChangedData,
|
||||
type OnBandwidthUpdateData,
|
||||
type OnBufferData,
|
||||
type OnExternalPlaybackChangeData,
|
||||
type OnReceiveAdEventData,
|
||||
VideoManager,
|
||||
} from "./VideoNativeComponent";
|
||||
import type { ReactVideoProps } from "./types/video";
|
||||
import { getReactTag, resolveAssetSourceForVideo } from "./utils";
|
||||
|
||||
export interface VideoRef {
|
||||
seek: (time: number, tolerance?: number) => void;
|
||||
resume: () => void;
|
||||
pause: () => void;
|
||||
presentFullscreenPlayer: () => void;
|
||||
dismissFullscreenPlayer: () => void;
|
||||
restoreUserInterfaceForPictureInPictureStopCompleted: (restore: boolean) => void;
|
||||
}
|
||||
|
||||
const Video = forwardRef<VideoRef, ReactVideoProps>(
|
||||
(
|
||||
{
|
||||
source,
|
||||
style,
|
||||
resizeMode,
|
||||
posterResizeMode,
|
||||
poster,
|
||||
fullscreen,
|
||||
drm,
|
||||
textTracks,
|
||||
selectedAudioTrack,
|
||||
selectedTextTrack,
|
||||
onLoadStart,
|
||||
onLoad,
|
||||
onError,
|
||||
onProgress,
|
||||
onSeek,
|
||||
onEnd,
|
||||
onBuffer,
|
||||
onBandwidthUpdate,
|
||||
onExternalPlaybackChange,
|
||||
onFullscreenPlayerWillPresent,
|
||||
onFullscreenPlayerDidPresent,
|
||||
onFullscreenPlayerWillDismiss,
|
||||
onFullscreenPlayerDidDismiss,
|
||||
onReadyForDisplay,
|
||||
onPlaybackRateChange,
|
||||
onAudioBecomingNoisy,
|
||||
onPictureInPictureStatusChanged,
|
||||
onRestoreUserInterfaceForPictureInPictureStop,
|
||||
onReceiveAdEvent,
|
||||
onPlaybackStateChanged,
|
||||
onAudioFocusChanged,
|
||||
onIdle,
|
||||
onTimedMetadata,
|
||||
onAudioTracks,
|
||||
onTextTracks,
|
||||
onVideoTracks,
|
||||
...rest
|
||||
},
|
||||
ref
|
||||
) => {
|
||||
const nativeRef = useRef<ComponentRef<VideoComponentType>>(null);
|
||||
const [showPoster, setShowPoster] = useState(!!poster);
|
||||
const [isFullscreen, setIsFullscreen] = useState(fullscreen);
|
||||
const [
|
||||
_restoreUserInterfaceForPIPStopCompletionHandler,
|
||||
setRestoreUserInterfaceForPIPStopCompletionHandler,
|
||||
] = useState<boolean | undefined>();
|
||||
|
||||
const posterStyle = useMemo<StyleProp<ImageStyle>>(
|
||||
() => ({
|
||||
...StyleSheet.absoluteFillObject,
|
||||
resizeMode:
|
||||
posterResizeMode && posterResizeMode !== "none"
|
||||
? posterResizeMode
|
||||
: "contain",
|
||||
}),
|
||||
[posterResizeMode]
|
||||
);
|
||||
|
||||
const src = useMemo(() => {
|
||||
if (!source) return undefined;
|
||||
|
||||
const resolvedSource = resolveAssetSourceForVideo(source);
|
||||
let uri = resolvedSource.uri || "";
|
||||
if (uri && uri.match(/^\//)) uri = `file://${uri}`;
|
||||
if (!uri) console.warn("Trying to load empty source");
|
||||
const isNetwork = !!(uri && uri.match(/^https?:/));
|
||||
const isAsset = !!(
|
||||
uri &&
|
||||
uri.match(
|
||||
/^(assets-library|ipod-library|file|content|ms-appx|ms-appdata):/
|
||||
)
|
||||
);
|
||||
|
||||
return {
|
||||
uri,
|
||||
isNetwork,
|
||||
isAsset,
|
||||
shouldCache: resolvedSource.shouldCache || false,
|
||||
type: resolvedSource.type || "",
|
||||
mainVer: resolvedSource.mainVer || 0,
|
||||
patchVer: resolvedSource.patchVer || 0,
|
||||
requestHeaders: resolvedSource?.headers || {},
|
||||
startTime: resolvedSource.startTime || 0,
|
||||
endTime: resolvedSource.endTime
|
||||
};
|
||||
}, [source]);
|
||||
|
||||
const _resizeMode: NativeVideoResizeMode = useMemo(() => {
|
||||
switch (resizeMode) {
|
||||
case "contain":
|
||||
return RCTVideoConstants.ScaleAspectFit;
|
||||
case "cover":
|
||||
return RCTVideoConstants.ScaleAspectFill;
|
||||
case "stretch":
|
||||
return RCTVideoConstants.ScaleToFill;
|
||||
default:
|
||||
return RCTVideoConstants.ScaleNone;
|
||||
}
|
||||
}, [resizeMode]);
|
||||
|
||||
const _drm = useMemo(() => {
|
||||
if (!drm) return;
|
||||
return {
|
||||
drmType: drm.type,
|
||||
licenseServer: drm.licenseServer,
|
||||
headers: drm.headers,
|
||||
contentId: drm.contentId,
|
||||
certificateUrl: drm.certificateUrl,
|
||||
base64Certificate: drm.base64Certificate,
|
||||
useExternalGetLicense: !!drm.getLicense,
|
||||
};
|
||||
}, [drm]);
|
||||
|
||||
|
||||
const _selectedTextTrack = useMemo(() => {
|
||||
if (!selectedTextTrack) return;
|
||||
if (typeof selectedTextTrack?.value === 'number') return {
|
||||
seletedTextType: selectedTextTrack?.type,
|
||||
index: selectedTextTrack?.value,
|
||||
}
|
||||
return {
|
||||
selectedTextType: selectedTextTrack?.type,
|
||||
value: selectedTextTrack?.value,
|
||||
}
|
||||
}, [selectedTextTrack]);
|
||||
|
||||
const _selectedAudioTrack = useMemo(() => {
|
||||
if (!selectedAudioTrack) return;
|
||||
if (typeof selectedAudioTrack?.value === 'number') return {
|
||||
selectedAudioType: selectedAudioTrack?.type,
|
||||
index: selectedAudioTrack?.value,
|
||||
}
|
||||
return {
|
||||
selectedAudioType: selectedAudioTrack?.type,
|
||||
value: selectedAudioTrack?.value,
|
||||
}
|
||||
}, [selectedAudioTrack]);
|
||||
|
||||
|
||||
const seek = useCallback(
|
||||
async (time: number, tolerance?: number) => {
|
||||
if (isNaN(time)) throw new Error("Specified time is not a number");
|
||||
|
||||
if (!nativeRef.current) {
|
||||
console.warn("Video Component is not mounted");
|
||||
return;
|
||||
}
|
||||
|
||||
Platform.select({
|
||||
ios: () => {
|
||||
nativeRef.current?.setNativeProps({
|
||||
seek: {
|
||||
time,
|
||||
tolerance,
|
||||
},
|
||||
});
|
||||
},
|
||||
default: () => {
|
||||
nativeRef.current?.setNativeProps({
|
||||
seek: time,
|
||||
});
|
||||
},
|
||||
})();
|
||||
},
|
||||
[]
|
||||
);
|
||||
|
||||
const presentFullscreenPlayer = useCallback(() => {
|
||||
setIsFullscreen(true);
|
||||
}, [setIsFullscreen]);
|
||||
|
||||
const dismissFullscreenPlayer = useCallback(() => {
|
||||
setIsFullscreen(false);
|
||||
}, [setIsFullscreen]);
|
||||
|
||||
const save = useCallback(async () => {
|
||||
await VideoManager.save(getReactTag(nativeRef));
|
||||
}, []);
|
||||
|
||||
const pause = useCallback(async () => {
|
||||
await VideoManager.setPlayerPauseState(true, getReactTag(nativeRef));
|
||||
}, []);
|
||||
|
||||
const resume = useCallback(async () => {
|
||||
await VideoManager.setPlayerPauseState(false, getReactTag(nativeRef));
|
||||
}, []);
|
||||
|
||||
const restoreUserInterfaceForPictureInPictureStopCompleted = useCallback(
|
||||
(restored: boolean) => {
|
||||
setRestoreUserInterfaceForPIPStopCompletionHandler(restored);
|
||||
},
|
||||
[setRestoreUserInterfaceForPIPStopCompletionHandler]
|
||||
);
|
||||
|
||||
const onVideoLoadStart = useCallback(
|
||||
(e: NativeSyntheticEvent<OnLoadStartData>) => {
|
||||
onLoadStart?.(e.nativeEvent);
|
||||
},
|
||||
[onLoadStart]
|
||||
);
|
||||
|
||||
const onVideoLoad = useCallback(
|
||||
(e: NativeSyntheticEvent<OnLoadData>) => {
|
||||
if (Platform.OS === "windows") setShowPoster(false);
|
||||
onLoad?.(e.nativeEvent);
|
||||
},
|
||||
[onLoad, setShowPoster]
|
||||
);
|
||||
|
||||
const onVideoError = useCallback(
|
||||
(e: NativeSyntheticEvent<OnVideoErrorData>) => {
|
||||
onError?.(e.nativeEvent);
|
||||
},
|
||||
[onError]
|
||||
);
|
||||
|
||||
const onVideoProgress = useCallback(
|
||||
(e: NativeSyntheticEvent<OnProgressData>) => {
|
||||
onProgress?.(e.nativeEvent);
|
||||
},
|
||||
[onProgress]
|
||||
);
|
||||
|
||||
const onVideoSeek = useCallback(
|
||||
(e: NativeSyntheticEvent<OnSeekData>) => {
|
||||
onSeek?.(e.nativeEvent);
|
||||
},
|
||||
[onSeek]
|
||||
);
|
||||
|
||||
// android only
|
||||
const onVideoPlaybackStateChanged = useCallback((e: NativeSyntheticEvent<OnPlaybackStateChangedData>) => {
|
||||
onPlaybackStateChanged?.(e.nativeEvent);
|
||||
}, [onPlaybackStateChanged])
|
||||
|
||||
// android only
|
||||
const onVideoIdle = useCallback(() => {
|
||||
onIdle?.()
|
||||
}, [onIdle])
|
||||
|
||||
const _onTimedMetadata = useCallback(
|
||||
(e: NativeSyntheticEvent<OnTimedMetadataData>) => {
|
||||
onTimedMetadata?.(e.nativeEvent);
|
||||
},
|
||||
[onTimedMetadata]
|
||||
);
|
||||
|
||||
const _onAudioTracks = useCallback((e: NativeSyntheticEvent<OnAudioTracksData>) => {
|
||||
onAudioTracks?.(e.nativeEvent)
|
||||
}, [onAudioTracks])
|
||||
|
||||
const _onTextTracks = useCallback((e: NativeSyntheticEvent<OnTextTracksData>) => {
|
||||
onTextTracks?.(e.nativeEvent)
|
||||
}, [onTextTracks])
|
||||
|
||||
const _onVideoTracks = useCallback((e: NativeSyntheticEvent<OnVideoTracksData>) => {
|
||||
onVideoTracks?.(e.nativeEvent)
|
||||
}, [onVideoTracks])
|
||||
|
||||
const _onPlaybackRateChange = useCallback(
|
||||
(e: NativeSyntheticEvent<Readonly<{ playbackRate: number }>>) => {
|
||||
onPlaybackRateChange?.(e.nativeEvent);
|
||||
},
|
||||
[onPlaybackRateChange]
|
||||
);
|
||||
|
||||
const _onReadyForDisplay = useCallback(() => {
|
||||
setShowPoster(false);
|
||||
onReadyForDisplay?.();
|
||||
}, [setShowPoster, onReadyForDisplay]);
|
||||
|
||||
const _onPictureInPictureStatusChanged = useCallback(
|
||||
(e: NativeSyntheticEvent<OnPictureInPictureStatusChangedData>) => {
|
||||
onPictureInPictureStatusChanged?.(e.nativeEvent);
|
||||
},
|
||||
[onPictureInPictureStatusChanged]
|
||||
);
|
||||
|
||||
const _onAudioFocusChanged = useCallback((e: NativeSyntheticEvent<OnAudioFocusChangedData>) => {
|
||||
onAudioFocusChanged?.(e.nativeEvent)
|
||||
}, [onAudioFocusChanged])
|
||||
|
||||
const onVideoBuffer = useCallback((e: NativeSyntheticEvent<OnBufferData>) => {
|
||||
onBuffer?.(e.nativeEvent);
|
||||
}, [onBuffer]);
|
||||
|
||||
const onVideoExternalPlaybackChange = useCallback((e: NativeSyntheticEvent<OnExternalPlaybackChangeData>) => {
|
||||
onExternalPlaybackChange?.(e.nativeEvent);
|
||||
}, [onExternalPlaybackChange])
|
||||
|
||||
const _onBandwidthUpdate = useCallback((e: NativeSyntheticEvent<OnBandwidthUpdateData>) => {
|
||||
onBandwidthUpdate?.(e.nativeEvent);
|
||||
}, [onBandwidthUpdate]);
|
||||
|
||||
const _onReceiveAdEvent = useCallback((e: NativeSyntheticEvent<OnReceiveAdEventData>) => {
|
||||
onReceiveAdEvent?.(e.nativeEvent);
|
||||
}, [onReceiveAdEvent]);
|
||||
|
||||
const onGetLicense = useCallback(
|
||||
(event: NativeSyntheticEvent<OnGetLicenseData>) => {
|
||||
if (drm && drm.getLicense instanceof Function) {
|
||||
const data = event.nativeEvent;
|
||||
if (data && data.spcBase64) {
|
||||
const getLicenseOverride = drm.getLicense(data.spcBase64, data.contentId, data.licenseUrl);
|
||||
const getLicensePromise = Promise.resolve(getLicenseOverride); // Handles both scenarios, getLicenseOverride being a promise and not.
|
||||
getLicensePromise.then((result => {
|
||||
if (result !== undefined) {
|
||||
nativeRef.current && VideoManager.setLicenseResult(result, data.licenseUrl, getReactTag(nativeRef));
|
||||
} else {
|
||||
nativeRef.current && VideoManager.setLicenseResultError('Empty license result', data.licenseUrl, getReactTag(nativeRef));
|
||||
}
|
||||
})).catch(() => {
|
||||
nativeRef.current && VideoManager.setLicenseResultError('fetch error', data.licenseUrl, getReactTag(nativeRef));
|
||||
});
|
||||
} else {
|
||||
VideoManager.setLicenseResultError('No spc received', data.licenseUrl, getReactTag(nativeRef));
|
||||
}
|
||||
}
|
||||
},
|
||||
[drm]
|
||||
);
|
||||
|
||||
useImperativeHandle(
|
||||
ref,
|
||||
() => ({
|
||||
seek,
|
||||
presentFullscreenPlayer,
|
||||
dismissFullscreenPlayer,
|
||||
save,
|
||||
pause,
|
||||
resume,
|
||||
restoreUserInterfaceForPictureInPictureStopCompleted,
|
||||
}),
|
||||
[
|
||||
seek,
|
||||
presentFullscreenPlayer,
|
||||
dismissFullscreenPlayer,
|
||||
save,
|
||||
pause,
|
||||
resume,
|
||||
restoreUserInterfaceForPictureInPictureStopCompleted,
|
||||
]
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={style}>
|
||||
<NativeVideoComponent
|
||||
ref={nativeRef}
|
||||
{...rest}
|
||||
src={src}
|
||||
drm={_drm}
|
||||
style={StyleSheet.absoluteFill}
|
||||
resizeMode={_resizeMode}
|
||||
fullscreen={isFullscreen}
|
||||
restoreUserInterfaceForPIPStopCompletionHandler={
|
||||
_restoreUserInterfaceForPIPStopCompletionHandler
|
||||
}
|
||||
textTracks={textTracks}
|
||||
selectedTextTrack={_selectedTextTrack}
|
||||
selectedAudioTrack={_selectedAudioTrack}
|
||||
onGetLicense={onGetLicense}
|
||||
onVideoLoad={onVideoLoad}
|
||||
onVideoLoadStart={onVideoLoadStart}
|
||||
onVideoError={onVideoError}
|
||||
onVideoProgress={onVideoProgress}
|
||||
onVideoSeek={onVideoSeek}
|
||||
onVideoEnd={onEnd}
|
||||
onVideoBuffer={onVideoBuffer}
|
||||
onVideoPlaybackStateChanged={onVideoPlaybackStateChanged}
|
||||
onBandwidthUpdate={_onBandwidthUpdate}
|
||||
onTimedMetadata={_onTimedMetadata}
|
||||
onAudioTracks={_onAudioTracks}
|
||||
onTextTracks={_onTextTracks}
|
||||
onVideoTracks={_onVideoTracks}
|
||||
onVideoFullscreenPlayerDidDismiss={onFullscreenPlayerDidDismiss}
|
||||
onVideoFullscreenPlayerDidPresent={onFullscreenPlayerDidPresent}
|
||||
onVideoFullscreenPlayerWillDismiss={onFullscreenPlayerWillDismiss}
|
||||
onVideoFullscreenPlayerWillPresent={onFullscreenPlayerWillPresent}
|
||||
onVideoExternalPlaybackChange={onVideoExternalPlaybackChange}
|
||||
onVideoIdle={onVideoIdle}
|
||||
onAudioFocusChanged={_onAudioFocusChanged}
|
||||
onReadyForDisplay={_onReadyForDisplay}
|
||||
onPlaybackRateChange={_onPlaybackRateChange}
|
||||
onVideoAudioBecomingNoisy={onAudioBecomingNoisy}
|
||||
onPictureInPictureStatusChanged={_onPictureInPictureStatusChanged}
|
||||
onRestoreUserInterfaceForPictureInPictureStop={
|
||||
onRestoreUserInterfaceForPictureInPictureStop
|
||||
}
|
||||
onReceiveAdEvent={_onReceiveAdEvent}
|
||||
/>
|
||||
{showPoster ? (
|
||||
<Image style={posterStyle} source={{ uri: poster }} />
|
||||
) : null}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
);
|
||||
|
||||
Video.displayName = "Video";
|
||||
export default Video;
|
323
src/VideoNativeComponent.ts
Normal file
323
src/VideoNativeComponent.ts
Normal file
@ -0,0 +1,323 @@
|
||||
import type { HostComponent, NativeSyntheticEvent, ViewProps } from 'react-native';
|
||||
import { NativeModules, requireNativeComponent } from 'react-native';
|
||||
import { getViewManagerConfig } from './utils';
|
||||
|
||||
// -------- There are types for native component (future codegen) --------
|
||||
// if you are looking for types for react component, see src/types/video.ts
|
||||
|
||||
type Headers = Record<string, any>
|
||||
|
||||
type VideoSrc = Readonly<{
|
||||
uri?: string;
|
||||
isNetwork?: boolean;
|
||||
isAsset?: boolean;
|
||||
shouldCache?: boolean;
|
||||
type?: string;
|
||||
mainVer?: number;
|
||||
patchVer?: number;
|
||||
requestHeaders?: Headers;
|
||||
startTime?: number;
|
||||
endTime?: number;
|
||||
}>
|
||||
|
||||
|
||||
export type Filter = 'None' |
|
||||
'CIColorInvert' |
|
||||
'CIColorMonochrome' |
|
||||
'CIColorPosterize' |
|
||||
'CIFalseColor' |
|
||||
'CIMaximumComponent' |
|
||||
'CIMinimumComponent' |
|
||||
'CIPhotoEffectChrome' |
|
||||
'CIPhotoEffectFade' |
|
||||
'CIPhotoEffectInstant' |
|
||||
'CIPhotoEffectMono' |
|
||||
'CIPhotoEffectNoir' |
|
||||
'CIPhotoEffectProcess' |
|
||||
'CIPhotoEffectTonal' |
|
||||
'CIPhotoEffectTransfer' |
|
||||
'CISepiaTone';
|
||||
|
||||
export type DrmType = 'widevine' | 'playready' | 'clearkey' | 'fairplay';
|
||||
|
||||
type Drm = Readonly<{
|
||||
drmType?: DrmType;
|
||||
licenseServer?: string;
|
||||
headers?: Headers;
|
||||
contentId?: string; // ios
|
||||
certificateUrl?: string; // ios
|
||||
base64Certificate?: boolean; // ios default: false
|
||||
useExternalGetLicense?: boolean; // ios
|
||||
}>
|
||||
|
||||
type TextTracks = ReadonlyArray<Readonly<{
|
||||
title: string;
|
||||
language: string;
|
||||
type: string;
|
||||
uri: string;
|
||||
}>>
|
||||
|
||||
type TextTrackType = 'system' | 'disabled' | 'title' | 'language' | 'index';
|
||||
|
||||
type SelectedTextTrack = Readonly<{
|
||||
selectedTextType: TextTrackType;
|
||||
value?: string;
|
||||
index?: number;
|
||||
}>
|
||||
|
||||
type AudioTrackType = 'system' | 'disabled' | 'title' | 'language' | 'index';
|
||||
|
||||
type SelectedAudioTrack = Readonly<{
|
||||
selectedAudioType: AudioTrackType;
|
||||
value?: string;
|
||||
index?: number;
|
||||
}>
|
||||
|
||||
export type Seek = Readonly<{
|
||||
time: number;
|
||||
tolerance?: number;
|
||||
}>
|
||||
|
||||
type BufferConfig = Readonly<{
|
||||
minBufferMs?: number;
|
||||
maxBufferMs?: number;
|
||||
bufferForPlaybackMs?: number;
|
||||
bufferForPlaybackAfterRebufferMs?: number;
|
||||
maxHeapAllocationPercent?: number;
|
||||
minBackBufferMemoryReservePercent?: number;
|
||||
minBufferMemoryReservePercent?: number;
|
||||
}>
|
||||
|
||||
type SelectedVideoTrack = Readonly<{
|
||||
type: 'auto' | 'disabled' | 'resolution' | 'index';
|
||||
value?: number;
|
||||
}>
|
||||
|
||||
type SubtitleStyle = Readonly<{
|
||||
fontSize?: number;
|
||||
paddingTop?: number;
|
||||
paddingBottom?: number;
|
||||
paddingLeft?: number;
|
||||
paddingRight?: number;
|
||||
}>
|
||||
|
||||
export type OnLoadData = Readonly<{
|
||||
currentTime: number;
|
||||
duration: number;
|
||||
naturalSize: Readonly<{
|
||||
width: number;
|
||||
height: number;
|
||||
orientation: 'portrait' | 'landscape';
|
||||
}>;
|
||||
}>
|
||||
|
||||
export type OnLoadStartData = Readonly<{
|
||||
isNetwork: boolean;
|
||||
type: string;
|
||||
uri: string;
|
||||
}>
|
||||
|
||||
export type OnBufferData = Readonly<{ isBuffering: boolean }>;
|
||||
|
||||
|
||||
export type OnProgressData = Readonly<{
|
||||
currentTime: number;
|
||||
playableDuration: number;
|
||||
seekableDuration: number;
|
||||
}>
|
||||
|
||||
export type OnBandwidthUpdateData = Readonly<{
|
||||
bitrate: number;
|
||||
}>;
|
||||
|
||||
export type OnSeekData = Readonly<{
|
||||
currentTime: number;
|
||||
seekTime: number;
|
||||
finished: boolean;
|
||||
}>
|
||||
|
||||
export type OnPlaybackStateChangedData = Readonly<{
|
||||
isPlaying: boolean;
|
||||
}>
|
||||
|
||||
export type OnTimedMetadataData = Readonly<{
|
||||
metadata: ReadonlyArray<Readonly<{
|
||||
value?: string
|
||||
identifier: string
|
||||
}>>
|
||||
}>
|
||||
|
||||
|
||||
export type OnAudioTracksData = Readonly<{
|
||||
audioTracks: ReadonlyArray<Readonly<{
|
||||
index: number
|
||||
title?: string
|
||||
language?: string
|
||||
bitrate?: number
|
||||
type?: string
|
||||
selected?: boolean
|
||||
}>>
|
||||
}>
|
||||
|
||||
export type OnTextTracksData = Readonly<{
|
||||
textTracks: ReadonlyArray<Readonly<{
|
||||
index: number
|
||||
title?: string
|
||||
language?: string
|
||||
/**
|
||||
* iOS only supports VTT, Android supports all 3
|
||||
*/
|
||||
type?: 'srt' | 'ttml' | 'vtt'
|
||||
selected?: boolean
|
||||
}>>
|
||||
}>
|
||||
|
||||
export type OnVideoTracksData = Readonly<{
|
||||
videoTracks: ReadonlyArray<Readonly<{
|
||||
trackId: number
|
||||
codecs?: string
|
||||
width?: number
|
||||
height?: number
|
||||
bitrate?: number
|
||||
selected?: boolean
|
||||
}>>
|
||||
}>
|
||||
|
||||
export type OnPlaybackData = Readonly<{
|
||||
playbackRate: number;
|
||||
}>;
|
||||
|
||||
export type OnExternalPlaybackChangeData = Readonly<{
|
||||
isExternalPlaybackActive: boolean;
|
||||
}>
|
||||
|
||||
export type OnGetLicenseData = Readonly<{
|
||||
licenseUrl: string;
|
||||
contentId: string;
|
||||
spcBase64: string;
|
||||
}>
|
||||
|
||||
export type OnPictureInPictureStatusChangedData = Readonly<{
|
||||
isActive: boolean;
|
||||
}>
|
||||
|
||||
export type OnReceiveAdEventData = Readonly<{
|
||||
event: string;
|
||||
}>
|
||||
|
||||
export type OnVideoErrorData = Readonly<{
|
||||
error: string;
|
||||
}>
|
||||
|
||||
export type OnAudioFocusChangedData = Readonly<{
|
||||
hasFocus: boolean;
|
||||
}>
|
||||
|
||||
export type NativeVideoResizeMode = 'ScaleNone' | 'ScaleToFill' | 'ScaleAspectFit' | 'ScaleAspectFill';
|
||||
export interface VideoNativeProps extends ViewProps {
|
||||
src?: VideoSrc;
|
||||
drm?: Drm;
|
||||
adTagUrl?: string;
|
||||
allowsExternalPlayback?: boolean; // ios, true
|
||||
maxBitRate?: number;
|
||||
resizeMode?: NativeVideoResizeMode;
|
||||
repeat?: boolean;
|
||||
automaticallyWaitsToMinimizeStalling?: boolean
|
||||
textTracks?: TextTracks;
|
||||
selectedTextTrack?: SelectedTextTrack;
|
||||
selectedAudioTrack?: SelectedAudioTrack;
|
||||
paused?: boolean;
|
||||
muted?: boolean;
|
||||
controls?: boolean;
|
||||
filter?: Filter;
|
||||
filterEnabled?: boolean;
|
||||
volume?: number; // default 1.0
|
||||
playInBackground?: boolean;
|
||||
preventsDisplaySleepDuringVideoPlayback?: boolean;
|
||||
preferredForwardBufferDuration?: number; //ios, 0
|
||||
playWhenInactive?: boolean; // ios, false
|
||||
pictureInPicture?: boolean; // ios, false
|
||||
ignoreSilentSwitch?: 'inherit' | 'ignore' | 'obey'; // ios, 'inherit'
|
||||
mixWithOthers?: 'inherit' | 'mix' | 'duck'; // ios, 'inherit'
|
||||
rate?: number;
|
||||
fullscreen?: boolean; // ios, false
|
||||
fullscreenAutorotate?: boolean;
|
||||
fullscreenOrientation?: 'all' | 'landscape' | 'portrait';
|
||||
progressUpdateInterval?: number;
|
||||
restoreUserInterfaceForPIPStopCompletionHandler?: boolean;
|
||||
localSourceEncryptionKeyScheme?: string;
|
||||
|
||||
backBufferDurationMs?: number; // Android
|
||||
bufferConfig?: BufferConfig; // Android
|
||||
contentStartTime?: number; // Android
|
||||
currentPlaybackTime?: number; // Android
|
||||
disableDisconnectError?: boolean; // Android
|
||||
focusable?: boolean; // Android
|
||||
hideShutterView?: boolean; // Android
|
||||
minLoadRetryCount?: number; // Android
|
||||
reportBandwidth?: boolean; //Android
|
||||
selectedVideoTrack?: SelectedVideoTrack; // android
|
||||
subtitleStyle?: SubtitleStyle // android
|
||||
trackId?: string; // Android
|
||||
useTextureView?: boolean; // Android
|
||||
useSecureView?: boolean; // Android
|
||||
|
||||
onVideoLoad?: (event: NativeSyntheticEvent<OnLoadData>) => void;
|
||||
onVideoLoadStart?: (event: NativeSyntheticEvent<OnLoadStartData>) => void;
|
||||
onVideoBuffer?: (event: NativeSyntheticEvent<OnBufferData>) => void;
|
||||
onVideoError?: (event: NativeSyntheticEvent<OnVideoErrorData>) => void;
|
||||
onVideoProgress?: (event: NativeSyntheticEvent<OnProgressData>) => void;
|
||||
onBandwidthUpdate?: (event: NativeSyntheticEvent<OnBandwidthUpdateData>) => void;
|
||||
onVideoSeek?: (event: NativeSyntheticEvent<OnSeekData>) => void;
|
||||
onVideoEnd?: (event: NativeSyntheticEvent<Readonly<{}>>) => void; // all
|
||||
onVideoAudioBecomingNoisy?: (event: NativeSyntheticEvent<Readonly<{}>>) => void;
|
||||
onVideoFullscreenPlayerWillPresent?: (event: NativeSyntheticEvent<Readonly<{}>>) => void; // ios, android
|
||||
onVideoFullscreenPlayerDidPresent?: (event: NativeSyntheticEvent<Readonly<{}>>) => void; // ios, android
|
||||
onVideoFullscreenPlayerWillDismiss?: (event: NativeSyntheticEvent<Readonly<{}>>) => void; // ios, android
|
||||
onVideoFullscreenPlayerDidDismiss?: (event: NativeSyntheticEvent<Readonly<{}>>) => void; // ios, android
|
||||
onReadyForDisplay?: (event: NativeSyntheticEvent<Readonly<{}>>) => void;
|
||||
onPlaybackRateChange?: (event: NativeSyntheticEvent<OnPlaybackData>) => void; // all
|
||||
onVideoExternalPlaybackChange?: (event: NativeSyntheticEvent<OnExternalPlaybackChangeData>) => void;
|
||||
onGetLicense?: (event: NativeSyntheticEvent<OnGetLicenseData>) => void;
|
||||
onPictureInPictureStatusChanged?: (event: NativeSyntheticEvent<OnPictureInPictureStatusChangedData>) => void;
|
||||
onRestoreUserInterfaceForPictureInPictureStop?: (event: NativeSyntheticEvent<Readonly<{}>>) => void;
|
||||
onReceiveAdEvent?: (event: NativeSyntheticEvent<OnReceiveAdEventData>) => void;
|
||||
onVideoPlaybackStateChanged?: (event: NativeSyntheticEvent<OnPlaybackStateChangedData>) => void; // android only
|
||||
onVideoIdle?: (event: NativeSyntheticEvent<{}>) => void; // android only (nowhere in document, so do not use as props. just type declaration)
|
||||
onAudioFocusChanged?: (event: NativeSyntheticEvent<OnAudioFocusChangedData>) => void; // android only (nowhere in document, so do not use as props. just type declaration)
|
||||
onTimedMetadata?: (event: NativeSyntheticEvent<OnTimedMetadataData>) => void; // ios, android
|
||||
onAudioTracks?: (event: NativeSyntheticEvent<OnAudioTracksData>) => void; // android
|
||||
onTextTracks?: (event: NativeSyntheticEvent<OnTextTracksData>) => void; // android
|
||||
onVideoTracks?: (event: NativeSyntheticEvent<OnVideoTracksData>) => void; // android
|
||||
}
|
||||
|
||||
export type VideoComponentType = HostComponent<VideoNativeProps>;
|
||||
|
||||
export interface VideoManagerType {
|
||||
save: (reactTag: number) => Promise<void>;
|
||||
setPlayerPauseState: (paused: boolean, reactTag: number) => Promise<void>;
|
||||
setLicenseResult: (result: string, licenseUrl: string, reactTag: number) => Promise<void>;
|
||||
setLicenseResultError: (error: string, licenseUrl: string, reactTag: number) => Promise<void>;
|
||||
}
|
||||
|
||||
export interface VideoDecoderPropertiesType {
|
||||
getWidevineLevel: () => Promise<number>;
|
||||
isCodecSupported: (mimeType: string, width: number, height: number) => Promise<'unsupported' | 'hardware' | 'software'>;
|
||||
isHEVCSupported: () => Promise<'unsupported' | 'hardware' | 'software'>;
|
||||
}
|
||||
|
||||
export type VideoViewManagerConfig = {
|
||||
Constants: {
|
||||
ScaleNone: any;
|
||||
ScaleToFill: any;
|
||||
ScaleAspectFit: any;
|
||||
ScaleAspectFill: any;
|
||||
};
|
||||
Commands: { [key: string]: number; };
|
||||
};
|
||||
|
||||
export const VideoManager = NativeModules.VideoManager as VideoManagerType;
|
||||
export const VideoDecoderProperties = NativeModules.VideoDecoderProperties as VideoDecoderPropertiesType;
|
||||
export const RCTVideoConstants = (getViewManagerConfig('RCTVideo') as VideoViewManagerConfig).Constants;
|
||||
|
||||
export default requireNativeComponent<VideoNativeProps>('RCTVideo') as VideoComponentType;
|
11
src/index.ts
Normal file
11
src/index.ts
Normal file
@ -0,0 +1,11 @@
|
||||
import Video from "./Video";
|
||||
|
||||
export { default as FilterType } from './lib/FilterType';
|
||||
export { default as VideoResizeMode } from './lib/VideoResizeMode';
|
||||
export { default as TextTrackType } from './lib/TextTrackType';
|
||||
export { default as DRMType } from './lib/DRMType';
|
||||
export { VideoDecoderProperties } from './VideoNativeComponent';
|
||||
|
||||
export type { VideoRef } from './Video';
|
||||
|
||||
export default Video;
|
@ -3,4 +3,4 @@ export default {
|
||||
PLAYREADY: 'playready',
|
||||
CLEARKEY: 'clearkey',
|
||||
FAIRPLAY: 'fairplay',
|
||||
};
|
||||
} as const;
|
@ -15,4 +15,4 @@ export default {
|
||||
TONAL: 'CIPhotoEffectTonal',
|
||||
TRANSFER: 'CIPhotoEffectTransfer',
|
||||
SEPIA: 'CISepiaTone',
|
||||
};
|
||||
} as const;
|
@ -2,4 +2,4 @@ export default {
|
||||
SRT: 'application/x-subrip',
|
||||
TTML: 'application/ttml+xml',
|
||||
VTT: 'text/vtt',
|
||||
};
|
||||
} as const;
|
5
src/lib/VideoResizeMode.ts
Normal file
5
src/lib/VideoResizeMode.ts
Normal file
@ -0,0 +1,5 @@
|
||||
export default {
|
||||
contain: 'contain',
|
||||
cover: 'cover',
|
||||
stretch: 'stretch',
|
||||
} as const;
|
30
src/types/events.ts
Normal file
30
src/types/events.ts
Normal file
@ -0,0 +1,30 @@
|
||||
import type { OnBandwidthUpdateData, OnBufferData, OnLoadData, OnLoadStartData, OnProgressData, OnSeekData, OnPlaybackData, OnExternalPlaybackChangeData, OnPictureInPictureStatusChangedData, OnReceiveAdEventData, OnVideoErrorData, OnPlaybackStateChangedData, OnAudioFocusChangedData, OnTimedMetadataData, OnAudioTracksData, OnTextTracksData, OnVideoTracksData } from "../VideoNativeComponent";
|
||||
|
||||
export interface ReactVideoEvents {
|
||||
onAudioBecomingNoisy?: () => void //Android, iOS
|
||||
onAudioFocusChanged?: (e: OnAudioFocusChangedData) => void // Android
|
||||
onIdle?: () => void // Android
|
||||
onBandwidthUpdate?: (e: OnBandwidthUpdateData) => void //Android
|
||||
onBuffer?: (e: OnBufferData) => void //Android, iOS
|
||||
onEnd?: () => void //All
|
||||
onError?: (e: OnVideoErrorData) => void //Android, iOS
|
||||
onExternalPlaybackChange?: (e: OnExternalPlaybackChangeData) => void //iOS
|
||||
onFullscreenPlayerWillPresent?: () => void //Android, iOS
|
||||
onFullscreenPlayerDidPresent?: () => void //Android, iOS
|
||||
onFullscreenPlayerWillDismiss?: () => void //Android, iOS
|
||||
onFullscreenPlayerDidDismiss?: () => void //Android, iOS
|
||||
onLoad?: (e: OnLoadData) => void //All
|
||||
onLoadStart?: (e: OnLoadStartData) => void //All
|
||||
onPictureInPictureStatusChanged?: (e: OnPictureInPictureStatusChangedData) => void //iOS
|
||||
onPlaybackRateChange?: (e: OnPlaybackData) => void //All
|
||||
onProgress?: (e: OnProgressData) => void //All
|
||||
onReadyForDisplay?: () => void //Android, iOS, Web
|
||||
onReceiveAdEvent?: (e: OnReceiveAdEventData) => void //Android, iOS
|
||||
onRestoreUserInterfaceForPictureInPictureStop?: () => void //iOS
|
||||
onSeek?: (e: OnSeekData) => void //Android, iOS, Windows UWP
|
||||
onPlaybackStateChanged?: (e: OnPlaybackStateChangedData) => void // Android
|
||||
onTimedMetadata?: (e: OnTimedMetadataData) => void //Android, iOS
|
||||
onAudioTracks?: (e: OnAudioTracksData) => void // Android
|
||||
onTextTracks?: (e: OnTextTracksData) => void //Android
|
||||
onVideoTracks?: (e: OnVideoTracksData) => void //Android
|
||||
}
|
184
src/types/language.ts
Normal file
184
src/types/language.ts
Normal file
@ -0,0 +1,184 @@
|
||||
export type ISO639_1 =
|
||||
| 'aa'
|
||||
| 'ab'
|
||||
| 'ae'
|
||||
| 'af'
|
||||
| 'ak'
|
||||
| 'am'
|
||||
| 'an'
|
||||
| 'ar'
|
||||
| 'as'
|
||||
| 'av'
|
||||
| 'ay'
|
||||
| 'az'
|
||||
| 'ba'
|
||||
| 'be'
|
||||
| 'bg'
|
||||
| 'bi'
|
||||
| 'bm'
|
||||
| 'bn'
|
||||
| 'bo'
|
||||
| 'br'
|
||||
| 'bs'
|
||||
| 'ca'
|
||||
| 'ce'
|
||||
| 'ch'
|
||||
| 'co'
|
||||
| 'cr'
|
||||
| 'cs'
|
||||
| 'cu'
|
||||
| 'cv'
|
||||
| 'cy'
|
||||
| 'da'
|
||||
| 'de'
|
||||
| 'dv'
|
||||
| 'dz'
|
||||
| 'ee'
|
||||
| 'el'
|
||||
| 'en'
|
||||
| 'eo'
|
||||
| 'es'
|
||||
| 'et'
|
||||
| 'eu'
|
||||
| 'fa'
|
||||
| 'ff'
|
||||
| 'fi'
|
||||
| 'fj'
|
||||
| 'fo'
|
||||
| 'fr'
|
||||
| 'fy'
|
||||
| 'ga'
|
||||
| 'gd'
|
||||
| 'gl'
|
||||
| 'gn'
|
||||
| 'gu'
|
||||
| 'gv'
|
||||
| 'ha'
|
||||
| 'he'
|
||||
| 'hi'
|
||||
| 'ho'
|
||||
| 'hr'
|
||||
| 'ht'
|
||||
| 'hu'
|
||||
| 'hy'
|
||||
| 'hz'
|
||||
| 'ia'
|
||||
| 'id'
|
||||
| 'ie'
|
||||
| 'ig'
|
||||
| 'ii'
|
||||
| 'ik'
|
||||
| 'io'
|
||||
| 'is'
|
||||
| 'it'
|
||||
| 'iu'
|
||||
| 'ja'
|
||||
| 'jv'
|
||||
| 'ka'
|
||||
| 'kg'
|
||||
| 'ki'
|
||||
| 'kj'
|
||||
| 'kk'
|
||||
| 'kl'
|
||||
| 'km'
|
||||
| 'kn'
|
||||
| 'ko'
|
||||
| 'kr'
|
||||
| 'ks'
|
||||
| 'ku'
|
||||
| 'kv'
|
||||
| 'kw'
|
||||
| 'ky'
|
||||
| 'la'
|
||||
| 'lb'
|
||||
| 'lg'
|
||||
| 'li'
|
||||
| 'ln'
|
||||
| 'lo'
|
||||
| 'lt'
|
||||
| 'lu'
|
||||
| 'lv'
|
||||
| 'mg'
|
||||
| 'mh'
|
||||
| 'mi'
|
||||
| 'mk'
|
||||
| 'ml'
|
||||
| 'mn'
|
||||
| 'mr'
|
||||
| 'ms'
|
||||
| 'mt'
|
||||
| 'my'
|
||||
| 'na'
|
||||
| 'nb'
|
||||
| 'nd'
|
||||
| 'ne'
|
||||
| 'ng'
|
||||
| 'nl'
|
||||
| 'nn'
|
||||
| 'no'
|
||||
| 'nr'
|
||||
| 'nv'
|
||||
| 'ny'
|
||||
| 'oc'
|
||||
| 'oj'
|
||||
| 'om'
|
||||
| 'or'
|
||||
| 'os'
|
||||
| 'pa'
|
||||
| 'pi'
|
||||
| 'pl'
|
||||
| 'ps'
|
||||
| 'pt'
|
||||
| 'qu'
|
||||
| 'rm'
|
||||
| 'rn'
|
||||
| 'ro'
|
||||
| 'ru'
|
||||
| 'rw'
|
||||
| 'sa'
|
||||
| 'sc'
|
||||
| 'sd'
|
||||
| 'se'
|
||||
| 'sg'
|
||||
| 'si'
|
||||
| 'sk'
|
||||
| 'sl'
|
||||
| 'sm'
|
||||
| 'sn'
|
||||
| 'so'
|
||||
| 'sq'
|
||||
| 'sr'
|
||||
| 'ss'
|
||||
| 'st'
|
||||
| 'su'
|
||||
| 'sv'
|
||||
| 'sw'
|
||||
| 'ta'
|
||||
| 'te'
|
||||
| 'tg'
|
||||
| 'th'
|
||||
| 'ti'
|
||||
| 'tk'
|
||||
| 'tl'
|
||||
| 'tn'
|
||||
| 'to'
|
||||
| 'tr'
|
||||
| 'ts'
|
||||
| 'tt'
|
||||
| 'tw'
|
||||
| 'ty'
|
||||
| 'ug'
|
||||
| 'uk'
|
||||
| 'ur'
|
||||
| 'uz'
|
||||
| 've'
|
||||
| 'vi'
|
||||
| 'vo'
|
||||
| 'wa'
|
||||
| 'wo'
|
||||
| 'xh'
|
||||
| 'yi'
|
||||
| 'yo'
|
||||
| 'za'
|
||||
| 'zh'
|
||||
| 'zu';
|
135
src/types/video.ts
Normal file
135
src/types/video.ts
Normal file
@ -0,0 +1,135 @@
|
||||
import type { ISO639_1 } from './language';
|
||||
import type { ReactVideoEvents } from './events';
|
||||
import type { StyleProp, ViewStyle } from 'react-native'
|
||||
|
||||
type Filter = | 'None'
|
||||
| 'CIColorInvert'
|
||||
| 'CIColorMonochrome'
|
||||
| 'CIColorPosterize'
|
||||
| 'CIFalseColor'
|
||||
| 'CIMaximumComponent'
|
||||
| 'CIMinimumComponent'
|
||||
| 'CIPhotoEffectChrome'
|
||||
| 'CIPhotoEffectFade'
|
||||
| 'CIPhotoEffectInstant'
|
||||
| 'CIPhotoEffectMono'
|
||||
| 'CIPhotoEffectNoir'
|
||||
| 'CIPhotoEffectProcess'
|
||||
| 'CIPhotoEffectTonal'
|
||||
| 'CIPhotoEffectTransfer'
|
||||
| 'CISepiaTone'
|
||||
|
||||
|
||||
|
||||
type Headers = Record<string, string>;
|
||||
|
||||
export type ReactVideoSource = Readonly<{
|
||||
uri?: string;
|
||||
isNetwork?: boolean;
|
||||
isAsset?: boolean;
|
||||
shouldCache?: boolean;
|
||||
type?: string;
|
||||
mainVer?: number;
|
||||
patchVer?: number;
|
||||
headers?: Headers;
|
||||
startTime?: number;
|
||||
endTime?: number;
|
||||
}>;
|
||||
|
||||
export type ReactVideoDrm = Readonly<{
|
||||
type?: 'widevine' | 'playready' | 'clearkey' | 'fairplay';
|
||||
licenseServer?: string;
|
||||
headers?: Headers;
|
||||
contentId?: string; // ios
|
||||
certificateUrl?: string; // ios
|
||||
base64Certificate?: boolean; // ios default: false
|
||||
getLicense?: (licenseUrl: string, contentId: string, spcBase64: string) => void; // ios
|
||||
}>
|
||||
|
||||
type BufferConfig = {
|
||||
minBufferMs?: number;
|
||||
maxBufferMs?: number;
|
||||
bufferForPlaybackMs?: number;
|
||||
bufferForPlaybackAfterRebufferMs?: number;
|
||||
maxHeapAllocationPercent?: number;
|
||||
minBackBufferMemoryReservePercent?: number;
|
||||
minBufferMemoryReservePercent?: number;
|
||||
}
|
||||
|
||||
type SelectedTrack = {
|
||||
type: 'system' | 'disabled' | 'title' | 'language' | 'index';
|
||||
value?: string | number;
|
||||
}
|
||||
|
||||
type SelectedVideoTrack = {
|
||||
type: 'auto' | 'disabled' | 'resolution' | 'index'
|
||||
value?: number;
|
||||
}
|
||||
|
||||
type SubtitleStyle = {
|
||||
fontSize?: number;
|
||||
paddingTop?: number;
|
||||
paddingBottom?: number;
|
||||
paddingLeft?: number;
|
||||
paddingRight?: number;
|
||||
}
|
||||
|
||||
type TextTracks = {
|
||||
title: string;
|
||||
language: ISO639_1;
|
||||
type: | 'application/x-subrip'
|
||||
| 'application/ttml+xml'
|
||||
| 'text/vtt';
|
||||
uri: string;
|
||||
}[]
|
||||
|
||||
export interface ReactVideoProps extends ReactVideoEvents {
|
||||
source?: ReactVideoSource;
|
||||
drm?: ReactVideoDrm;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
adTagUrl?: string; // iOS
|
||||
audioOnly?: boolean;
|
||||
automaticallyWaitsToMinimizeStalling?: boolean; // iOS
|
||||
backBufferDurationMs?: number; // Android
|
||||
bufferConfig?: BufferConfig; // Android
|
||||
contentStartTime?: number; // Android
|
||||
controls?: boolean;
|
||||
currentPlaybackTime?: number; // Android
|
||||
disableFocus?: boolean;
|
||||
disableDisconnectError?: boolean; // Android
|
||||
filter?: Filter; // iOS
|
||||
filterEnabled?: boolean; // iOS
|
||||
focusable?: boolean; // Android
|
||||
fullscreen?: boolean; // iOS
|
||||
fullscreenAutorotate?: boolean; // iOS
|
||||
fullscreenOrientation?: 'all' | 'landscape' | 'portrait'; // iOS
|
||||
hideShutterView?: boolean; // Android
|
||||
ignoreSilentSwitch?: 'inherit' | 'ignore' | 'obey' // iOS
|
||||
minLoadRetryCount?: number; // Android
|
||||
maxBitRate?: number;
|
||||
mixWithOthers?: 'inherit' | 'mix' | 'duck'; // iOS
|
||||
muted?: boolean;
|
||||
paused?: boolean;
|
||||
pictureInPicture?: boolean // iOS
|
||||
playInBackground?: boolean;
|
||||
playWhenInactive?: boolean // iOS
|
||||
poster?: string;
|
||||
posterResizeMode?: 'contain' | 'center' | 'cover' | 'none' | 'repeat' | 'stretch';
|
||||
preferredForwardBufferDuration?: number// iOS
|
||||
preventsDisplaySleepDuringVideoPlayback?: boolean;
|
||||
progressUpdateInterval?: number;
|
||||
rate?: number;
|
||||
repeat?: boolean;
|
||||
reportBandwidth?: boolean; //Android
|
||||
resizeMode?: 'none' | 'contain' | 'cover' | 'stretch';
|
||||
selectedAudioTrack?: SelectedTrack;
|
||||
selectedTextTrack?: SelectedTrack;
|
||||
selectedVideoTrack?: SelectedVideoTrack; // android
|
||||
subtitleStyle?: SubtitleStyle // android
|
||||
textTracks?: TextTracks;
|
||||
trackId?: string; // Android
|
||||
useTextureView?: boolean; // Android
|
||||
useSecureView?: boolean; // Android
|
||||
volume?: number;
|
||||
localSourceEncryptionKeyScheme?: string;
|
||||
}
|
37
src/utils.ts
Normal file
37
src/utils.ts
Normal file
@ -0,0 +1,37 @@
|
||||
import type { Component, RefObject, ComponentClass } from 'react';
|
||||
import { Image, UIManager, findNodeHandle } from "react-native";
|
||||
import type { ImageSourcePropType } from 'react-native';
|
||||
import type { ReactVideoSource } from './types/video';
|
||||
|
||||
type Source = ImageSourcePropType | ReactVideoSource;
|
||||
|
||||
export function resolveAssetSourceForVideo(source: Source): ReactVideoSource {
|
||||
if (typeof source === 'number') {
|
||||
return {
|
||||
uri: Image.resolveAssetSource(source).uri,
|
||||
};
|
||||
}
|
||||
return source as ReactVideoSource;
|
||||
}
|
||||
|
||||
export function getReactTag(ref: RefObject<Component<any, any, any> | ComponentClass<any, any> | null>): number {
|
||||
if (!ref.current) {
|
||||
throw new Error("Video Component is not mounted");
|
||||
}
|
||||
|
||||
const reactTag = findNodeHandle(ref.current);
|
||||
|
||||
if (!reactTag) {
|
||||
throw new Error("Cannot find reactTag for Video Component in components tree");
|
||||
}
|
||||
|
||||
return reactTag;
|
||||
}
|
||||
|
||||
export function getViewManagerConfig(name: string) {
|
||||
if('getViewManagerConfig' in UIManager) {
|
||||
return UIManager.getViewManagerConfig(name);
|
||||
}
|
||||
|
||||
return UIManager[name];
|
||||
}
|
5
tsconfig.build.json
Normal file
5
tsconfig.build.json
Normal file
@ -0,0 +1,5 @@
|
||||
|
||||
{
|
||||
"extends": "./tsconfig",
|
||||
"exclude": ["examples", "lib"]
|
||||
}
|
33
tsconfig.json
Normal file
33
tsconfig.json
Normal file
@ -0,0 +1,33 @@
|
||||
{
|
||||
"compilerOptions": {
|
||||
"rootDir": "./src",
|
||||
"outDir": "lib",
|
||||
"paths": {
|
||||
"react-native-video": ["./src/index"]
|
||||
},
|
||||
"composite": true,
|
||||
"declaration": true,
|
||||
"sourceMap": true,
|
||||
"allowUnreachableCode": false,
|
||||
"allowUnusedLabels": false,
|
||||
"esModuleInterop": true,
|
||||
"forceConsistentCasingInFileNames": true,
|
||||
"jsx": "react",
|
||||
"lib": ["esnext"],
|
||||
"module": "esnext",
|
||||
"moduleResolution": "node",
|
||||
"noFallthroughCasesInSwitch": true,
|
||||
"noImplicitReturns": true,
|
||||
"noImplicitUseStrict": false,
|
||||
"noStrictGenericChecks": false,
|
||||
"noUncheckedIndexedAccess": true,
|
||||
"noUnusedLocals": true,
|
||||
"noUnusedParameters": true,
|
||||
"resolveJsonModule": true,
|
||||
"skipLibCheck": true,
|
||||
"strict": true,
|
||||
"target": "esnext",
|
||||
"verbatimModuleSyntax": true
|
||||
},
|
||||
"exclude": ["examples", "lib"]
|
||||
}
|
Loading…
Reference in New Issue
Block a user