Merge remote-tracking branch 'origin/master' into feat/web
This commit is contained in:
400
src/Video.tsx
400
src/Video.tsx
@@ -8,9 +8,17 @@ import React, {
|
||||
} from 'react';
|
||||
import type {ElementRef} from 'react';
|
||||
import {View, StyleSheet, Image, Platform, processColor} from 'react-native';
|
||||
import type {StyleProp, ImageStyle, NativeSyntheticEvent} from 'react-native';
|
||||
import type {
|
||||
StyleProp,
|
||||
ImageStyle,
|
||||
NativeSyntheticEvent,
|
||||
ViewStyle,
|
||||
ImageResizeMode,
|
||||
} from 'react-native';
|
||||
|
||||
import NativeVideoComponent from './specs/VideoNativeComponent';
|
||||
import NativeVideoComponent, {
|
||||
NativeCmcdConfiguration,
|
||||
} from './specs/VideoNativeComponent';
|
||||
import type {
|
||||
OnAudioFocusChangedData,
|
||||
OnAudioTracksData,
|
||||
@@ -37,12 +45,14 @@ import {
|
||||
resolveAssetSourceForVideo,
|
||||
} from './utils';
|
||||
import NativeVideoManager from './specs/NativeVideoManager';
|
||||
import {ViewType, type VideoSaveData} from './types';
|
||||
import {type VideoSaveData, CmcdMode, ViewType} from './types';
|
||||
import type {
|
||||
OnLoadData,
|
||||
OnTextTracksData,
|
||||
OnReceiveAdEventData,
|
||||
ReactVideoProps,
|
||||
CmcdData,
|
||||
ReactVideoSource,
|
||||
} from './types';
|
||||
|
||||
export interface VideoRef {
|
||||
@@ -56,6 +66,7 @@ export interface VideoRef {
|
||||
) => void;
|
||||
setVolume: (volume: number) => void;
|
||||
setFullScreen: (fullScreen: boolean) => void;
|
||||
setSource: (source?: ReactVideoSource) => void;
|
||||
save: (options: object) => Promise<VideoSaveData> | void;
|
||||
getCurrentPosition: () => Promise<number>;
|
||||
}
|
||||
@@ -66,8 +77,10 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
||||
source,
|
||||
style,
|
||||
resizeMode,
|
||||
posterResizeMode,
|
||||
poster,
|
||||
posterResizeMode,
|
||||
renderLoader,
|
||||
contentStartTime,
|
||||
drm,
|
||||
textTracks,
|
||||
selectedVideoTrack,
|
||||
@@ -77,6 +90,8 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
||||
useSecureView,
|
||||
viewType,
|
||||
shutterColor,
|
||||
adTagUrl,
|
||||
adLanguage,
|
||||
onLoadStart,
|
||||
onLoad,
|
||||
onError,
|
||||
@@ -107,82 +122,152 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
||||
onTextTrackDataChanged,
|
||||
onVideoTracks,
|
||||
onAspectRatio,
|
||||
localSourceEncryptionKeyScheme,
|
||||
...rest
|
||||
},
|
||||
ref,
|
||||
) => {
|
||||
const nativeRef = useRef<ElementRef<typeof NativeVideoComponent>>(null);
|
||||
const [showPoster, setShowPoster] = useState(!!poster);
|
||||
|
||||
const isPosterDeprecated = typeof poster === 'string';
|
||||
|
||||
const _renderLoader = useMemo(
|
||||
() =>
|
||||
!renderLoader
|
||||
? undefined
|
||||
: renderLoader instanceof Function
|
||||
? renderLoader
|
||||
: () => renderLoader,
|
||||
[renderLoader],
|
||||
);
|
||||
|
||||
const hasPoster = useMemo(() => {
|
||||
if (_renderLoader) {
|
||||
return true;
|
||||
}
|
||||
|
||||
if (isPosterDeprecated) {
|
||||
return !!poster;
|
||||
}
|
||||
|
||||
return !!poster?.source;
|
||||
}, [isPosterDeprecated, poster, _renderLoader]);
|
||||
|
||||
const [showPoster, setShowPoster] = useState(hasPoster);
|
||||
|
||||
const [
|
||||
_restoreUserInterfaceForPIPStopCompletionHandler,
|
||||
setRestoreUserInterfaceForPIPStopCompletionHandler,
|
||||
] = useState<boolean | undefined>();
|
||||
|
||||
const hasPoster = !!poster;
|
||||
const sourceToUnternalSource = useCallback(
|
||||
(_source?: ReactVideoSource) => {
|
||||
if (!_source) {
|
||||
return undefined;
|
||||
}
|
||||
const resolvedSource = resolveAssetSourceForVideo(_source);
|
||||
let uri = resolvedSource.uri || '';
|
||||
if (uri && uri.match(/^\//)) {
|
||||
uri = `file://${uri}`;
|
||||
}
|
||||
if (!uri) {
|
||||
console.log('Trying to load empty source');
|
||||
}
|
||||
const isNetwork = !!(uri && uri.match(/^(rtp|rtsp|http|https):/));
|
||||
const isAsset = !!(
|
||||
uri &&
|
||||
uri.match(
|
||||
/^(assets-library|ipod-library|file|content|ms-appx|ms-appdata):/,
|
||||
)
|
||||
);
|
||||
|
||||
const posterStyle = useMemo<StyleProp<ImageStyle>>(
|
||||
() => ({
|
||||
...StyleSheet.absoluteFillObject,
|
||||
resizeMode:
|
||||
posterResizeMode && posterResizeMode !== 'none'
|
||||
? posterResizeMode
|
||||
: 'contain',
|
||||
}),
|
||||
[posterResizeMode],
|
||||
const selectedDrm = _source.drm || drm;
|
||||
const _textTracks = _source.textTracks || textTracks;
|
||||
const _drm = !selectedDrm
|
||||
? undefined
|
||||
: {
|
||||
type: selectedDrm.type,
|
||||
licenseServer: selectedDrm.licenseServer,
|
||||
headers: generateHeaderForNative(selectedDrm.headers),
|
||||
contentId: selectedDrm.contentId,
|
||||
certificateUrl: selectedDrm.certificateUrl,
|
||||
base64Certificate: selectedDrm.base64Certificate,
|
||||
useExternalGetLicense: !!selectedDrm.getLicense,
|
||||
multiDrm: selectedDrm.multiDrm,
|
||||
localSourceEncryptionKeyScheme:
|
||||
selectedDrm.localSourceEncryptionKeyScheme ||
|
||||
localSourceEncryptionKeyScheme,
|
||||
};
|
||||
|
||||
let _cmcd: NativeCmcdConfiguration | undefined;
|
||||
if (Platform.OS === 'android' && source?.cmcd) {
|
||||
const cmcd = source.cmcd;
|
||||
|
||||
if (typeof cmcd === 'boolean') {
|
||||
_cmcd = cmcd ? {mode: CmcdMode.MODE_QUERY_PARAMETER} : undefined;
|
||||
} else if (typeof cmcd === 'object' && !Array.isArray(cmcd)) {
|
||||
const createCmcdHeader = (property?: CmcdData) =>
|
||||
property ? generateHeaderForNative(property) : undefined;
|
||||
|
||||
_cmcd = {
|
||||
mode: cmcd.mode ?? CmcdMode.MODE_QUERY_PARAMETER,
|
||||
request: createCmcdHeader(cmcd.request),
|
||||
session: createCmcdHeader(cmcd.session),
|
||||
object: createCmcdHeader(cmcd.object),
|
||||
status: createCmcdHeader(cmcd.status),
|
||||
};
|
||||
} else {
|
||||
throw new Error(
|
||||
'Invalid CMCD configuration: Expected a boolean or an object.',
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
const selectedContentStartTime =
|
||||
_source.contentStartTime || contentStartTime;
|
||||
|
||||
const _ad =
|
||||
_source.ad ||
|
||||
(adTagUrl || adLanguage
|
||||
? {adTagUrl: adTagUrl, adLanguage: adLanguage}
|
||||
: undefined);
|
||||
|
||||
return {
|
||||
uri,
|
||||
isNetwork,
|
||||
isAsset,
|
||||
shouldCache: resolvedSource.shouldCache || false,
|
||||
type: resolvedSource.type || '',
|
||||
mainVer: resolvedSource.mainVer || 0,
|
||||
patchVer: resolvedSource.patchVer || 0,
|
||||
requestHeaders: generateHeaderForNative(resolvedSource.headers),
|
||||
startPosition: resolvedSource.startPosition ?? -1,
|
||||
cropStart: resolvedSource.cropStart || 0,
|
||||
cropEnd: resolvedSource.cropEnd,
|
||||
contentStartTime: selectedContentStartTime,
|
||||
metadata: resolvedSource.metadata,
|
||||
drm: _drm,
|
||||
ad: _ad,
|
||||
cmcd: _cmcd,
|
||||
textTracks: _textTracks,
|
||||
textTracksAllowChunklessPreparation:
|
||||
resolvedSource.textTracksAllowChunklessPreparation,
|
||||
};
|
||||
},
|
||||
[
|
||||
adLanguage,
|
||||
adTagUrl,
|
||||
contentStartTime,
|
||||
drm,
|
||||
localSourceEncryptionKeyScheme,
|
||||
source?.cmcd,
|
||||
textTracks,
|
||||
],
|
||||
);
|
||||
|
||||
const src = useMemo<VideoSrc | undefined>(() => {
|
||||
if (!source) {
|
||||
return undefined;
|
||||
}
|
||||
const resolvedSource = resolveAssetSourceForVideo(source);
|
||||
let uri = resolvedSource.uri || '';
|
||||
if (uri && uri.match(/^\//)) {
|
||||
uri = `file://${uri}`;
|
||||
}
|
||||
if (!uri) {
|
||||
console.log('Trying to load empty source');
|
||||
}
|
||||
const isNetwork = !!(uri && uri.match(/^(rtp|rtsp|http|https):/));
|
||||
const isAsset = !!(
|
||||
uri &&
|
||||
uri.match(
|
||||
/^(assets-library|ipod-library|file|content|ms-appx|ms-appdata):/,
|
||||
)
|
||||
);
|
||||
|
||||
const selectedDrm = source.drm || drm;
|
||||
const _drm = !selectedDrm
|
||||
? undefined
|
||||
: {
|
||||
type: selectedDrm.type,
|
||||
licenseServer: selectedDrm.licenseServer,
|
||||
headers: generateHeaderForNative(selectedDrm.headers),
|
||||
contentId: selectedDrm.contentId,
|
||||
certificateUrl: selectedDrm.certificateUrl,
|
||||
base64Certificate: selectedDrm.base64Certificate,
|
||||
useExternalGetLicense: !!selectedDrm.getLicense,
|
||||
multiDrm: selectedDrm.multiDrm,
|
||||
};
|
||||
|
||||
return {
|
||||
uri,
|
||||
isNetwork,
|
||||
isAsset,
|
||||
shouldCache: resolvedSource.shouldCache || false,
|
||||
type: resolvedSource.type || '',
|
||||
mainVer: resolvedSource.mainVer || 0,
|
||||
patchVer: resolvedSource.patchVer || 0,
|
||||
requestHeaders: generateHeaderForNative(resolvedSource.headers),
|
||||
startPosition: resolvedSource.startPosition ?? -1,
|
||||
cropStart: resolvedSource.cropStart || 0,
|
||||
cropEnd: resolvedSource.cropEnd,
|
||||
metadata: resolvedSource.metadata,
|
||||
drm: _drm,
|
||||
textTracksAllowChunklessPreparation:
|
||||
resolvedSource.textTracksAllowChunklessPreparation,
|
||||
};
|
||||
}, [drm, source]);
|
||||
return sourceToUnternalSource(source);
|
||||
}, [sourceToUnternalSource, source]);
|
||||
|
||||
const _selectedTextTrack = useMemo(() => {
|
||||
if (!selectedTextTrack) {
|
||||
@@ -304,6 +389,16 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
||||
);
|
||||
}, []);
|
||||
|
||||
const setSource = useCallback(
|
||||
(_source?: ReactVideoSource) => {
|
||||
return NativeVideoManager.setSourceCmd(
|
||||
getReactTag(nativeRef),
|
||||
sourceToUnternalSource(_source),
|
||||
);
|
||||
},
|
||||
[sourceToUnternalSource],
|
||||
);
|
||||
|
||||
const presentFullscreenPlayer = useCallback(
|
||||
() => setFullScreen(true),
|
||||
[setFullScreen],
|
||||
@@ -501,7 +596,8 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
||||
[onControlsVisibilityChange],
|
||||
);
|
||||
|
||||
const usingExternalGetLicense = drm?.getLicense instanceof Function;
|
||||
const selectedDrm = source?.drm || drm;
|
||||
const usingExternalGetLicense = selectedDrm?.getLicense instanceof Function;
|
||||
|
||||
const onGetLicense = useCallback(
|
||||
async (event: NativeSyntheticEvent<OnGetLicenseData>) => {
|
||||
@@ -509,33 +605,43 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
||||
return;
|
||||
}
|
||||
const data = event.nativeEvent;
|
||||
let result;
|
||||
if (data?.spcBase64) {
|
||||
try {
|
||||
// Handles both scenarios, getLicenseOverride being a promise and not.
|
||||
const license = await drm.getLicense(
|
||||
try {
|
||||
if (!data?.spcBase64) {
|
||||
throw new Error('No spc received');
|
||||
}
|
||||
// Handles both scenarios, getLicenseOverride being a promise and not.
|
||||
const license = await Promise.resolve(
|
||||
selectedDrm.getLicense(
|
||||
data.spcBase64,
|
||||
data.contentId,
|
||||
data.licenseUrl,
|
||||
data.loadedLicenseUrl,
|
||||
);
|
||||
result =
|
||||
typeof license === 'string' ? license : 'Empty license result';
|
||||
} catch {
|
||||
result = 'fetch error';
|
||||
),
|
||||
).catch(() => {
|
||||
throw new Error('fetch error');
|
||||
});
|
||||
if (typeof license !== 'string') {
|
||||
throw Error('Empty license result');
|
||||
}
|
||||
if (nativeRef.current) {
|
||||
NativeVideoManager.setLicenseResultCmd(
|
||||
getReactTag(nativeRef),
|
||||
license,
|
||||
data.loadedLicenseUrl,
|
||||
);
|
||||
}
|
||||
} catch (e) {
|
||||
const msg = e instanceof Error ? e.message : 'fetch error';
|
||||
if (nativeRef.current) {
|
||||
NativeVideoManager.setLicenseResultErrorCmd(
|
||||
getReactTag(nativeRef),
|
||||
msg,
|
||||
data.loadedLicenseUrl,
|
||||
);
|
||||
}
|
||||
} else {
|
||||
result = 'No spc received';
|
||||
}
|
||||
if (nativeRef.current) {
|
||||
NativeVideoManager.setLicenseResultErrorCmd(
|
||||
getReactTag(nativeRef),
|
||||
result,
|
||||
data.loadedLicenseUrl,
|
||||
);
|
||||
}
|
||||
},
|
||||
[drm, usingExternalGetLicense],
|
||||
[selectedDrm, usingExternalGetLicense],
|
||||
);
|
||||
|
||||
useImperativeHandle(
|
||||
@@ -551,6 +657,7 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
||||
setVolume,
|
||||
getCurrentPosition,
|
||||
setFullScreen,
|
||||
setSource,
|
||||
}),
|
||||
[
|
||||
seek,
|
||||
@@ -563,6 +670,7 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
||||
setVolume,
|
||||
getCurrentPosition,
|
||||
setFullScreen,
|
||||
setSource,
|
||||
],
|
||||
);
|
||||
|
||||
@@ -573,42 +681,120 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
||||
const shallForceViewType =
|
||||
hasValidDrmProp && (viewType === ViewType.TEXTURE || useTextureView);
|
||||
|
||||
if (shallForceViewType) {
|
||||
console.warn(
|
||||
'cannot use DRM on texture view. please set useTextureView={false}',
|
||||
);
|
||||
}
|
||||
if (useSecureView && useTextureView) {
|
||||
console.warn(
|
||||
'cannot use SecureView on texture view. please set useTextureView={false}',
|
||||
);
|
||||
}
|
||||
|
||||
return shallForceViewType
|
||||
? useSecureView
|
||||
? ViewType.SURFACE_SECURE
|
||||
: ViewType.SURFACE // check if we should force the type to Surface due to DRM
|
||||
: viewType
|
||||
? viewType // else use ViewType from source
|
||||
: useSecureView // else infer view type from useSecureView and useTextureView
|
||||
? ViewType.SURFACE_SECURE
|
||||
: useTextureView
|
||||
? ViewType.TEXTURE
|
||||
: ViewType.SURFACE;
|
||||
if (shallForceViewType) {
|
||||
console.warn(
|
||||
'cannot use DRM on texture view. please set useTextureView={false}',
|
||||
);
|
||||
return useSecureView ? ViewType.SURFACE_SECURE : ViewType.SURFACE;
|
||||
}
|
||||
|
||||
if (viewType !== undefined && viewType !== null) {
|
||||
return viewType;
|
||||
}
|
||||
|
||||
if (useSecureView) {
|
||||
return ViewType.SURFACE_SECURE;
|
||||
}
|
||||
|
||||
if (useTextureView) {
|
||||
return ViewType.TEXTURE;
|
||||
}
|
||||
|
||||
return ViewType.SURFACE;
|
||||
}, [drm, useSecureView, useTextureView, viewType]);
|
||||
|
||||
const _renderPoster = useCallback(() => {
|
||||
if (!hasPoster || !showPoster) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// poster resize mode
|
||||
let _posterResizeMode: ImageResizeMode = 'contain';
|
||||
|
||||
if (!isPosterDeprecated && poster?.resizeMode) {
|
||||
_posterResizeMode = poster.resizeMode;
|
||||
} else if (posterResizeMode && posterResizeMode !== 'none') {
|
||||
_posterResizeMode = posterResizeMode;
|
||||
}
|
||||
|
||||
// poster style
|
||||
const baseStyle: StyleProp<ImageStyle> = {
|
||||
...StyleSheet.absoluteFillObject,
|
||||
resizeMode: _posterResizeMode,
|
||||
};
|
||||
|
||||
let posterStyle: StyleProp<ImageStyle> = baseStyle;
|
||||
|
||||
if (!isPosterDeprecated && poster?.style) {
|
||||
const styles = Array.isArray(poster.style)
|
||||
? poster.style
|
||||
: [poster.style];
|
||||
posterStyle = [baseStyle, ...styles];
|
||||
}
|
||||
|
||||
// render poster
|
||||
if (_renderLoader && (poster || posterResizeMode)) {
|
||||
console.warn(
|
||||
'You provided both `renderLoader` and `poster` or `posterResizeMode` props. `renderLoader` will be used.',
|
||||
);
|
||||
}
|
||||
|
||||
// render loader
|
||||
if (_renderLoader) {
|
||||
return (
|
||||
<View style={StyleSheet.absoluteFill}>
|
||||
{_renderLoader({
|
||||
source: source,
|
||||
style: posterStyle,
|
||||
resizeMode: resizeMode,
|
||||
})}
|
||||
</View>
|
||||
);
|
||||
}
|
||||
|
||||
return (
|
||||
<Image
|
||||
{...(isPosterDeprecated ? {} : poster)}
|
||||
source={isPosterDeprecated ? {uri: poster} : poster?.source}
|
||||
style={posterStyle}
|
||||
/>
|
||||
);
|
||||
}, [
|
||||
hasPoster,
|
||||
isPosterDeprecated,
|
||||
poster,
|
||||
posterResizeMode,
|
||||
_renderLoader,
|
||||
showPoster,
|
||||
source,
|
||||
resizeMode,
|
||||
]);
|
||||
|
||||
const _style: StyleProp<ViewStyle> = useMemo(
|
||||
() => ({
|
||||
...StyleSheet.absoluteFillObject,
|
||||
...(showPoster ? {display: 'none'} : {}),
|
||||
}),
|
||||
[showPoster],
|
||||
);
|
||||
|
||||
return (
|
||||
<View style={style}>
|
||||
<NativeVideoComponent
|
||||
ref={nativeRef}
|
||||
{...rest}
|
||||
src={src}
|
||||
style={StyleSheet.absoluteFill}
|
||||
style={_style}
|
||||
resizeMode={resizeMode}
|
||||
restoreUserInterfaceForPIPStopCompletionHandler={
|
||||
_restoreUserInterfaceForPIPStopCompletionHandler
|
||||
}
|
||||
textTracks={textTracks}
|
||||
selectedTextTrack={_selectedTextTrack}
|
||||
selectedAudioTrack={_selectedAudioTrack}
|
||||
selectedVideoTrack={_selectedVideoTrack}
|
||||
@@ -678,9 +864,7 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
||||
}
|
||||
viewType={_viewType}
|
||||
/>
|
||||
{hasPoster && showPoster ? (
|
||||
<Image style={posterStyle} source={{uri: poster}} />
|
||||
) : null}
|
||||
{_renderPoster()}
|
||||
</View>
|
||||
);
|
||||
},
|
||||
|
||||
@@ -236,6 +236,7 @@ const Video = forwardRef<VideoRef, ReactVideoProps>(
|
||||
controls={controls}
|
||||
loop={repeat}
|
||||
playsInline
|
||||
//@ts-ignore
|
||||
poster={poster}
|
||||
onCanPlay={() => onBuffer?.({isBuffering: false})}
|
||||
onWaiting={() => onBuffer?.({isBuffering: true})}
|
||||
|
||||
@@ -13,7 +13,7 @@ export const withBackgroundAudio: ConfigPlugin<boolean> = (
|
||||
|
||||
if (enableBackgroundAudio) {
|
||||
if (!modes.includes('audio')) {
|
||||
modes.push('audio');
|
||||
config.modResults.UIBackgroundModes = [...modes, 'audio'];
|
||||
}
|
||||
} else {
|
||||
config.modResults.UIBackgroundModes = modes.filter(
|
||||
|
||||
@@ -24,6 +24,19 @@ export const withNotificationControls: ConfigPlugin<boolean> = (
|
||||
application.service = [];
|
||||
}
|
||||
|
||||
// We check if the VideoPlaybackService is already defined in the AndroidManifest.xml
|
||||
// to prevent adding duplicate service entries. If the service exists, we will remove
|
||||
// it before adding the updated configuration to ensure there are no conflicts or redundant
|
||||
// service declarations in the manifest.
|
||||
const existingServiceIndex = application.service.findIndex(
|
||||
(service) =>
|
||||
service?.$?.['android:name'] ===
|
||||
'com.brentvatne.exoplayer.VideoPlaybackService',
|
||||
);
|
||||
if (existingServiceIndex !== -1) {
|
||||
application.service.splice(existingServiceIndex, 1);
|
||||
}
|
||||
|
||||
application.service.push({
|
||||
$: {
|
||||
'android:name': 'com.brentvatne.exoplayer.VideoPlaybackService',
|
||||
|
||||
@@ -1,4 +1,5 @@
|
||||
import Video from './Video';
|
||||
export {VideoDecoderProperties} from './VideoDecoderProperties';
|
||||
export * from './types';
|
||||
export {Video};
|
||||
export default Video;
|
||||
|
||||
@@ -21,6 +21,7 @@ export interface VideoManagerType {
|
||||
licenseUrl: string,
|
||||
) => Promise<void>;
|
||||
setFullScreenCmd: (reactTag: Int32, fullScreen: boolean) => Promise<void>;
|
||||
setSourceCmd: (reactTag: Int32, source?: UnsafeObject) => Promise<void>;
|
||||
setVolumeCmd: (reactTag: Int32, volume: number) => Promise<void>;
|
||||
save: (reactTag: Int32, option: UnsafeObject) => Promise<VideoSaveData>;
|
||||
getCurrentPosition: (reactTag: Int32) => Promise<Int32>;
|
||||
|
||||
@@ -26,6 +26,11 @@ type VideoMetadata = Readonly<{
|
||||
imageUri?: string;
|
||||
}>;
|
||||
|
||||
export type AdsConfig = Readonly<{
|
||||
adTagUrl?: string;
|
||||
adLanguage?: string;
|
||||
}>;
|
||||
|
||||
export type VideoSrc = Readonly<{
|
||||
uri?: string;
|
||||
isNetwork?: boolean;
|
||||
@@ -38,9 +43,13 @@ export type VideoSrc = Readonly<{
|
||||
startPosition?: Float;
|
||||
cropStart?: Float;
|
||||
cropEnd?: Float;
|
||||
contentStartTime?: Int32; // Android
|
||||
metadata?: VideoMetadata;
|
||||
drm?: Drm;
|
||||
cmcd?: NativeCmcdConfiguration; // android
|
||||
textTracksAllowChunklessPreparation?: boolean; // android
|
||||
textTracks?: TextTracks;
|
||||
ad?: AdsConfig;
|
||||
}>;
|
||||
|
||||
type DRMType = WithDefault<string, 'widevine'>;
|
||||
@@ -59,6 +68,16 @@ type Drm = Readonly<{
|
||||
base64Certificate?: boolean; // ios default: false
|
||||
useExternalGetLicense?: boolean; // ios
|
||||
multiDrm?: WithDefault<boolean, false>; // android
|
||||
localSourceEncryptionKeyScheme?: string; // ios
|
||||
}>;
|
||||
|
||||
type CmcdMode = WithDefault<Int32, 1>;
|
||||
export type NativeCmcdConfiguration = Readonly<{
|
||||
mode?: CmcdMode; // default: MODE_QUERY_PARAMETER
|
||||
request?: Headers;
|
||||
session?: Headers;
|
||||
object?: Headers;
|
||||
status?: Headers;
|
||||
}>;
|
||||
|
||||
type TextTracks = ReadonlyArray<
|
||||
@@ -121,6 +140,7 @@ type SubtitleStyle = Readonly<{
|
||||
paddingLeft?: WithDefault<Float, 0>;
|
||||
paddingRight?: WithDefault<Float, 0>;
|
||||
opacity?: WithDefault<Float, 1>;
|
||||
subtitlesFollowVideo?: WithDefault<boolean, true>;
|
||||
}>;
|
||||
|
||||
type OnLoadData = Readonly<{
|
||||
@@ -283,8 +303,20 @@ export type OnAudioFocusChangedData = Readonly<{
|
||||
}>;
|
||||
|
||||
type ControlsStyles = Readonly<{
|
||||
hideSeekBar?: boolean;
|
||||
hidePosition?: WithDefault<boolean, false>;
|
||||
hidePlayPause?: WithDefault<boolean, false>;
|
||||
hideForward?: WithDefault<boolean, false>;
|
||||
hideRewind?: WithDefault<boolean, false>;
|
||||
hideNext?: WithDefault<boolean, false>;
|
||||
hidePrevious?: WithDefault<boolean, false>;
|
||||
hideFullscreen?: WithDefault<boolean, false>;
|
||||
hideSeekBar?: WithDefault<boolean, false>;
|
||||
hideDuration?: WithDefault<boolean, false>;
|
||||
hideNavigationBarOnFullScreenMode?: WithDefault<boolean, true>;
|
||||
hideNotificationBarOnFullScreenMode?: WithDefault<boolean, true>;
|
||||
hideSettingButton?: WithDefault<boolean, true>;
|
||||
seekIncrementMS?: Int32;
|
||||
liveLabel?: string;
|
||||
}>;
|
||||
|
||||
export type OnControlsVisibilityChange = Readonly<{
|
||||
@@ -293,7 +325,6 @@ export type OnControlsVisibilityChange = Readonly<{
|
||||
|
||||
export interface VideoNativeProps extends ViewProps {
|
||||
src?: VideoSrc;
|
||||
adTagUrl?: string;
|
||||
allowsExternalPlayback?: boolean; // ios, true
|
||||
disableFocus?: boolean; // android
|
||||
maxBitRate?: Float;
|
||||
@@ -302,7 +333,6 @@ export interface VideoNativeProps extends ViewProps {
|
||||
automaticallyWaitsToMinimizeStalling?: boolean;
|
||||
shutterColor?: Int32;
|
||||
audioOutput?: WithDefault<string, 'speaker'>;
|
||||
textTracks?: TextTracks;
|
||||
selectedTextTrack?: SelectedTextTrack;
|
||||
selectedAudioTrack?: SelectedAudioTrack;
|
||||
selectedVideoTrack?: SelectedVideoTrack; // android
|
||||
@@ -325,11 +355,9 @@ export interface VideoNativeProps extends ViewProps {
|
||||
fullscreenOrientation?: WithDefault<string, 'all'>;
|
||||
progressUpdateInterval?: Float;
|
||||
restoreUserInterfaceForPIPStopCompletionHandler?: boolean;
|
||||
localSourceEncryptionKeyScheme?: string;
|
||||
debug?: DebugConfig;
|
||||
showNotificationControls?: WithDefault<boolean, false>; // Android, iOS
|
||||
bufferConfig?: BufferConfig; // Android
|
||||
contentStartTime?: Int32; // Android
|
||||
currentPlaybackTime?: Double; // Android
|
||||
disableDisconnectError?: boolean; // Android
|
||||
focusable?: boolean; // Android
|
||||
|
||||
@@ -1,6 +1,15 @@
|
||||
import type {ISO639_1} from './language';
|
||||
import type {ReactVideoEvents} from './events';
|
||||
import type {StyleProp, ViewProps, ViewStyle} from 'react-native';
|
||||
import type {
|
||||
ImageProps,
|
||||
StyleProp,
|
||||
ViewProps,
|
||||
ViewStyle,
|
||||
ImageRequireSource,
|
||||
ImageURISource,
|
||||
ImageStyle,
|
||||
} from 'react-native';
|
||||
import type {ReactNode} from 'react';
|
||||
import type VideoResizeMode from './ResizeMode';
|
||||
import type FilterType from './FilterType';
|
||||
import type ViewType from './ViewType';
|
||||
@@ -23,9 +32,13 @@ export type ReactVideoSourceProperties = {
|
||||
startPosition?: number;
|
||||
cropStart?: number;
|
||||
cropEnd?: number;
|
||||
contentStartTime?: number; // Android
|
||||
metadata?: VideoMetadata;
|
||||
drm?: Drm;
|
||||
cmcd?: Cmcd; // android
|
||||
textTracksAllowChunklessPreparation?: boolean;
|
||||
textTracks?: TextTracks;
|
||||
ad?: AdConfig;
|
||||
};
|
||||
|
||||
export type ReactVideoSource = Readonly<
|
||||
@@ -34,6 +47,13 @@ export type ReactVideoSource = Readonly<
|
||||
}
|
||||
>;
|
||||
|
||||
export type ReactVideoPosterSource = ImageURISource | ImageRequireSource;
|
||||
|
||||
export type ReactVideoPoster = Omit<ImageProps, 'source'> & {
|
||||
// prevents giving source in the array
|
||||
source?: ReactVideoPosterSource;
|
||||
};
|
||||
|
||||
export type VideoMetadata = Readonly<{
|
||||
title?: string;
|
||||
subtitle?: string;
|
||||
@@ -54,6 +74,11 @@ export enum DRMType {
|
||||
FAIRPLAY = 'fairplay',
|
||||
}
|
||||
|
||||
export type AdConfig = Readonly<{
|
||||
adTagUrl?: string;
|
||||
adLanguage?: ISO639_1;
|
||||
}>;
|
||||
|
||||
export type Drm = Readonly<{
|
||||
type?: DRMType;
|
||||
licenseServer?: string;
|
||||
@@ -62,6 +87,7 @@ export type Drm = Readonly<{
|
||||
certificateUrl?: string; // ios
|
||||
base64Certificate?: boolean; // ios default: false
|
||||
multiDrm?: boolean; // android
|
||||
localSourceEncryptionKeyScheme?: string; // ios
|
||||
/* eslint-disable @typescript-eslint/no-unused-vars */
|
||||
getLicense?: (
|
||||
spcBase64: string,
|
||||
@@ -72,6 +98,27 @@ export type Drm = Readonly<{
|
||||
/* eslint-enable @typescript-eslint/no-unused-vars */
|
||||
}>;
|
||||
|
||||
export enum CmcdMode {
|
||||
MODE_REQUEST_HEADER = 0,
|
||||
MODE_QUERY_PARAMETER = 1,
|
||||
}
|
||||
/**
|
||||
* Custom key names MUST carry a hyphenated prefix to ensure that there will not be a
|
||||
* namespace collision with future revisions to this specification. Clients SHOULD
|
||||
* use a reverse-DNS syntax when defining their own prefix.
|
||||
*
|
||||
* @see https://cdn.cta.tech/cta/media/media/resources/standards/pdfs/cta-5004-final.pdf CTA-5004 Specification (Page 6, Section 3.1)
|
||||
*/
|
||||
export type CmcdData = Record<`${string}-${string}`, string | number>;
|
||||
export type CmcdConfiguration = Readonly<{
|
||||
mode?: CmcdMode; // default: MODE_QUERY_PARAMETER
|
||||
request?: CmcdData;
|
||||
session?: CmcdData;
|
||||
object?: CmcdData;
|
||||
status?: CmcdData;
|
||||
}>;
|
||||
export type Cmcd = boolean | CmcdConfiguration;
|
||||
|
||||
export enum BufferingStrategyType {
|
||||
DEFAULT = 'Default',
|
||||
DISABLE_BUFFERING = 'DisableBuffering',
|
||||
@@ -131,6 +178,7 @@ export type SubtitleStyle = {
|
||||
paddingLeft?: number;
|
||||
paddingRight?: number;
|
||||
opacity?: number;
|
||||
subtitlesFollowVideo?: boolean;
|
||||
};
|
||||
|
||||
export enum TextTrackType {
|
||||
@@ -208,20 +256,42 @@ export type AudioOutput = 'speaker' | 'earpiece';
|
||||
|
||||
export type ControlsStyles = {
|
||||
hideSeekBar?: boolean;
|
||||
hideDuration?: boolean;
|
||||
hidePosition?: boolean;
|
||||
hidePlayPause?: boolean;
|
||||
hideForward?: boolean;
|
||||
hideRewind?: boolean;
|
||||
hideNext?: boolean;
|
||||
hidePrevious?: boolean;
|
||||
hideFullscreen?: boolean;
|
||||
hideNavigationBarOnFullScreenMode?: boolean;
|
||||
hideNotificationBarOnFullScreenMode?: boolean;
|
||||
hideSettingButton?: boolean;
|
||||
seekIncrementMS?: number;
|
||||
liveLabel?: string;
|
||||
};
|
||||
|
||||
export interface ReactVideoRenderLoaderProps {
|
||||
source?: ReactVideoSource;
|
||||
style?: StyleProp<ImageStyle>;
|
||||
resizeMode?: EnumValues<VideoResizeMode>;
|
||||
}
|
||||
|
||||
export interface ReactVideoProps extends ReactVideoEvents, ViewProps {
|
||||
source?: ReactVideoSource;
|
||||
/** @deprecated */
|
||||
/** @deprecated Use source.drm */
|
||||
drm?: Drm;
|
||||
style?: StyleProp<ViewStyle>;
|
||||
/** @deprecated Use source.ad.adTagUrl */
|
||||
adTagUrl?: string;
|
||||
/** @deprecated Use source.ad.adLanguage */
|
||||
adLanguage?: ISO639_1;
|
||||
audioOutput?: AudioOutput; // Mobile
|
||||
automaticallyWaitsToMinimizeStalling?: boolean; // iOS
|
||||
bufferConfig?: BufferConfig; // Android
|
||||
bufferingStrategy?: BufferingStrategyType;
|
||||
chapters?: Chapters[]; // iOS
|
||||
/** @deprecated Use source.contentStartTime */
|
||||
contentStartTime?: number; // Android
|
||||
controls?: boolean;
|
||||
currentPlaybackTime?: number; // Android
|
||||
@@ -243,12 +313,14 @@ export interface ReactVideoProps extends ReactVideoEvents, ViewProps {
|
||||
pictureInPicture?: boolean; // iOS
|
||||
playInBackground?: boolean;
|
||||
playWhenInactive?: boolean; // iOS
|
||||
poster?: string;
|
||||
poster?: string | ReactVideoPoster; // string is deprecated
|
||||
/** @deprecated use **resizeMode** key in **poster** props instead */
|
||||
posterResizeMode?: EnumValues<PosterResizeModeType>;
|
||||
preferredForwardBufferDuration?: number; // iOS
|
||||
preventsDisplaySleepDuringVideoPlayback?: boolean;
|
||||
progressUpdateInterval?: number;
|
||||
rate?: number;
|
||||
renderLoader?: ReactNode | ((arg0: ReactVideoRenderLoaderProps) => ReactNode);
|
||||
repeat?: boolean;
|
||||
reportBandwidth?: boolean; //Android
|
||||
resizeMode?: EnumValues<VideoResizeMode>;
|
||||
@@ -258,14 +330,16 @@ export interface ReactVideoProps extends ReactVideoEvents, ViewProps {
|
||||
selectedVideoTrack?: SelectedVideoTrack; // android
|
||||
subtitleStyle?: SubtitleStyle; // android
|
||||
shutterColor?: string; // Android
|
||||
/** @deprecated Use source.textTracks */
|
||||
textTracks?: TextTracks;
|
||||
testID?: string;
|
||||
viewType?: ViewType;
|
||||
/** @deprecated */
|
||||
/** @deprecated Use viewType */
|
||||
useTextureView?: boolean; // Android
|
||||
/** @deprecated */
|
||||
/** @deprecated Use viewType*/
|
||||
useSecureView?: boolean; // Android
|
||||
volume?: number;
|
||||
/** @deprecated use **localSourceEncryptionKeyScheme** key in **drm** props instead */
|
||||
localSourceEncryptionKeyScheme?: string;
|
||||
debug?: DebugConfig;
|
||||
allowsExternalPlayback?: boolean; // iOS
|
||||
|
||||
Reference in New Issue
Block a user