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
|
||||||
|
}
|
||||||
}
|
}
|
7
.gitignore
vendored
7
.gitignore
vendored
@ -54,4 +54,9 @@ android/keystores/debug.keystore
|
|||||||
# windows
|
# windows
|
||||||
Deploy.binlog
|
Deploy.binlog
|
||||||
msbuild.binlog
|
msbuild.binlog
|
||||||
android/buildOutput_*
|
android/buildOutput_*
|
||||||
|
|
||||||
|
# lib build
|
||||||
|
lib/
|
||||||
|
!src/lib
|
||||||
|
*.tsbuildinfo
|
@ -1,8 +1,6 @@
|
|||||||
{
|
{
|
||||||
"requirePragma": true,
|
|
||||||
"singleQuote": true,
|
"singleQuote": true,
|
||||||
"trailingComma": "all",
|
"trailingComma": "all",
|
||||||
"bracketSpacing": false,
|
"bracketSpacing": false,
|
||||||
"jsxBracketSameLine": true,
|
"jsxBracketSameLine": true
|
||||||
"parser": "flow"
|
|
||||||
}
|
}
|
@ -1,6 +1,7 @@
|
|||||||
## Changelog
|
## Changelog
|
||||||
|
|
||||||
## Next
|
## 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)
|
- 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 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)
|
- 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",
|
"name": "react-native-video",
|
||||||
"version": "6.0.0-alpha.8",
|
"version": "6.0.0-alpha.8",
|
||||||
"description": "A <Video /> element for react-native",
|
"description": "A <Video /> element for react-native",
|
||||||
"main": "Video.js",
|
"main": "lib/index",
|
||||||
"source": "Video.js",
|
"source": "src/index",
|
||||||
|
"react-native": "src/index",
|
||||||
"license": "MIT",
|
"license": "MIT",
|
||||||
"author": "Community Contributors",
|
"author": "Community Contributors",
|
||||||
"homepage": "https://github.com/react-native-video/react-native-video#readme",
|
"homepage": "https://github.com/react-native-video/react-native-video#readme",
|
||||||
@ -11,24 +12,32 @@
|
|||||||
"type": "git",
|
"type": "git",
|
||||||
"url": "git@github.com:react-native-video/react-native-video.git"
|
"url": "git@github.com:react-native-video/react-native-video.git"
|
||||||
},
|
},
|
||||||
|
"resolutions": {
|
||||||
|
"@types/react": "~18.0.0"
|
||||||
|
},
|
||||||
"devDependencies": {
|
"devDependencies": {
|
||||||
"@react-native-community/eslint-config": "^0.0.5",
|
"@react-native/eslint-config": "^0.72.2",
|
||||||
"eslint": "^6.5.1",
|
"@types/jest": "^28.1.2",
|
||||||
"react": "16.9.0",
|
"@types/react": "~18.0.0",
|
||||||
"react-native": "0.61.5",
|
"@types/react-native": "0.72.3",
|
||||||
"react-native-windows": "^0.61.0-0"
|
"@typescript-eslint/eslint-plugin": "^6.7.4",
|
||||||
},
|
"eslint": "^8.19.0",
|
||||||
"dependencies": {
|
"eslint-plugin-jest": "^27.4.2",
|
||||||
"deprecated-react-native-prop-types": "^2.2.0",
|
"jest": "^29.7.0",
|
||||||
"keymirror": "^0.1.1",
|
"prettier": "^2.4.1",
|
||||||
"prop-types": "^15.7.2"
|
"react": "18.2.0",
|
||||||
|
"react-native": "0.72.5",
|
||||||
|
"react-native-windows": "^0.61.0-0",
|
||||||
|
"typescript": "5.1.6"
|
||||||
},
|
},
|
||||||
|
"dependencies": {},
|
||||||
"peerDependencies": {
|
"peerDependencies": {
|
||||||
"react": "*",
|
"react": "*",
|
||||||
"react-native": "*"
|
"react-native": "*"
|
||||||
},
|
},
|
||||||
"scripts": {
|
"scripts": {
|
||||||
"lint": "yarn eslint .",
|
"lint": "yarn eslint .",
|
||||||
|
"build": "yarn tsc",
|
||||||
"xbasic": "yarn --cwd examples/basic",
|
"xbasic": "yarn --cwd examples/basic",
|
||||||
"test": "echo no test available"
|
"test": "echo no test available"
|
||||||
},
|
},
|
||||||
@ -36,11 +45,13 @@
|
|||||||
"android",
|
"android",
|
||||||
"ios",
|
"ios",
|
||||||
"windows",
|
"windows",
|
||||||
"FilterType.js",
|
"src",
|
||||||
"DRMType.js",
|
|
||||||
"TextTrackType.js",
|
|
||||||
"VideoResizeMode.js",
|
|
||||||
"react-native-video.podspec",
|
"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',
|
PLAYREADY: 'playready',
|
||||||
CLEARKEY: 'clearkey',
|
CLEARKEY: 'clearkey',
|
||||||
FAIRPLAY: 'fairplay',
|
FAIRPLAY: 'fairplay',
|
||||||
};
|
} as const;
|
@ -15,4 +15,4 @@ export default {
|
|||||||
TONAL: 'CIPhotoEffectTonal',
|
TONAL: 'CIPhotoEffectTonal',
|
||||||
TRANSFER: 'CIPhotoEffectTransfer',
|
TRANSFER: 'CIPhotoEffectTransfer',
|
||||||
SEPIA: 'CISepiaTone',
|
SEPIA: 'CISepiaTone',
|
||||||
};
|
} as const;
|
@ -2,4 +2,4 @@ export default {
|
|||||||
SRT: 'application/x-subrip',
|
SRT: 'application/x-subrip',
|
||||||
TTML: 'application/ttml+xml',
|
TTML: 'application/ttml+xml',
|
||||||
VTT: 'text/vtt',
|
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