src/IVSPlayer.tsx (398 lines of code) (raw):
import React, {
useCallback,
useEffect,
useImperativeHandle,
useRef,
} from 'react';
import {
requireNativeComponent,
ViewStyle,
StyleSheet,
UIManager,
findNodeHandle,
View,
NativeSyntheticEvent,
Platform,
} from 'react-native';
import type { LogLevel, PlayerState } from './enums';
import type {
Quality,
PlayerData,
TextCue,
TextMetadataCue,
VideoData,
IVSPlayerRef,
ResizeMode,
Source,
} from './types';
import { createSourceWrapper } from './source';
type IVSPlayerProps = {
style?: ViewStyle;
testID?: string;
ref: any;
muted?: boolean;
loop?: boolean;
liveLowLatency?: boolean;
rebufferToLive?: boolean;
playbackRate?: number;
streamUrl?: string;
resizeMode?: ResizeMode;
logLevel?: LogLevel;
progressInterval?: number;
pipEnabled?: boolean;
volume?: number;
quality?: Quality | null;
autoMaxQuality?: Quality | null;
autoQualityMode?: boolean;
breakpoints?: number[];
maxBitrate?: number;
initialBufferDuration?: number;
onSeek?(event: NativeSyntheticEvent<{ position: number }>): void;
onData?(event: NativeSyntheticEvent<{ playerData: PlayerData }>): void;
onVideoStatistics?(
event: NativeSyntheticEvent<{ videoData: VideoData }>
): void;
onPlayerStateChange?(
event: NativeSyntheticEvent<{ state: PlayerState }>
): void;
onDurationChange?(
event: NativeSyntheticEvent<{ duration: number | null }>
): void;
onQualityChange?(event: NativeSyntheticEvent<{ quality: Quality }>): void;
onPipChange?(event: NativeSyntheticEvent<{ active: boolean | string }>): void;
onRebuffering?(): void;
onLoadStart?(): void;
onLoad?(event: NativeSyntheticEvent<{ duration: number | null }>): void;
onLiveLatencyChange?(
event: NativeSyntheticEvent<{ liveLatency: number }>
): void;
onTextCue?(event: NativeSyntheticEvent<{ textCue: TextCue }>): void;
onTextMetadataCue?(
event: NativeSyntheticEvent<{ textMetadataCue: TextMetadataCue }>
): void;
onProgress?(event: NativeSyntheticEvent<{ position: number }>): void;
onError?(event: NativeSyntheticEvent<{ error: string }>): void;
onTimePoint?(event: NativeSyntheticEvent<{ position: number }>): void;
};
const VIEW_NAME = 'AmazonIvs';
const IVSPlayer = requireNativeComponent<IVSPlayerProps>(VIEW_NAME);
export type Props = {
style?: ViewStyle;
testID?: string;
paused?: boolean;
muted?: boolean;
loop?: boolean;
autoplay?: boolean;
streamUrl?: string;
liveLowLatency?: boolean;
rebufferToLive?: boolean;
playbackRate?: number;
logLevel?: LogLevel;
resizeMode?: ResizeMode;
progressInterval?: number;
volume?: number;
quality?: Quality | null;
autoMaxQuality?: Quality | null;
autoQualityMode?: boolean;
breakpoints?: number[];
maxBitrate?: number;
initialBufferDuration?: number;
pipEnabled?: boolean;
onSeek?(position: number): void;
onData?(data: PlayerData): void;
onVideoStatistics?(data: VideoData): void;
onPlayerStateChange?(state: PlayerState): void;
onDurationChange?(duration: number | null): void;
onQualityChange?(quality: Quality | null): void;
onPipChange?(isActive: boolean): void;
onRebuffering?(): void;
onLoadStart?(): void;
onLoad?(duration: number | null): void;
onLiveLatencyChange?(liveLatency: number): void;
onTextCue?(textCue: TextCue): void;
onTextMetadataCue?(textMetadataCue: TextMetadataCue): void;
onProgress?(progress: number): void;
onError?(error: string): void;
onTimePoint?(position: number): void;
children?: React.ReactNode;
};
function parseDuration(duration: number | null) {
if (duration == null) {
return duration;
}
return duration > 0 ? duration : Infinity;
}
const IVSPlayerContainer = React.forwardRef<IVSPlayerRef, Props>(
(
{
style,
streamUrl,
paused,
muted,
loop = false,
resizeMode,
autoplay = true,
liveLowLatency,
rebufferToLive,
playbackRate,
pipEnabled,
logLevel,
progressInterval,
volume,
quality,
autoMaxQuality,
autoQualityMode,
breakpoints = [],
maxBitrate,
initialBufferDuration,
onSeek,
onData,
onVideoStatistics,
onPlayerStateChange,
onDurationChange,
onQualityChange,
onPipChange,
onRebuffering,
onLoadStart,
onLoad,
onLiveLatencyChange,
onTextCue,
onTextMetadataCue,
onProgress,
onError,
onTimePoint,
children,
},
ref
) => {
const mediaPlayerRef = useRef(null);
const initialized = useRef(false);
const preload = useCallback((url: string) => {
const sourceWrapper = createSourceWrapper(url);
UIManager.dispatchViewManagerCommand(
findNodeHandle(mediaPlayerRef.current),
UIManager.getViewManagerConfig(VIEW_NAME).Commands.preload,
[sourceWrapper.getId(), sourceWrapper.getUri()]
);
return sourceWrapper;
}, []);
const loadSource = useCallback((source: Source) => {
UIManager.dispatchViewManagerCommand(
findNodeHandle(mediaPlayerRef.current),
UIManager.getViewManagerConfig(VIEW_NAME).Commands.loadSource,
[source.getId()]
);
}, []);
const releaseSource = useCallback((source: Source) => {
UIManager.dispatchViewManagerCommand(
findNodeHandle(mediaPlayerRef.current),
UIManager.getViewManagerConfig(VIEW_NAME).Commands.releaseSource,
[source.getId()]
);
}, []);
const play = useCallback(() => {
UIManager.dispatchViewManagerCommand(
findNodeHandle(mediaPlayerRef.current),
UIManager.getViewManagerConfig(VIEW_NAME).Commands.play,
[]
);
}, []);
const pause = useCallback(() => {
UIManager.dispatchViewManagerCommand(
findNodeHandle(mediaPlayerRef.current),
UIManager.getViewManagerConfig(VIEW_NAME).Commands.pause,
[]
);
}, []);
const seekTo = useCallback((value) => {
UIManager.dispatchViewManagerCommand(
findNodeHandle(mediaPlayerRef.current),
UIManager.getViewManagerConfig(VIEW_NAME).Commands.seekTo,
[value]
);
}, []);
const setOrigin = useCallback((value) => {
UIManager.dispatchViewManagerCommand(
findNodeHandle(mediaPlayerRef.current),
UIManager.getViewManagerConfig(VIEW_NAME).Commands.setOrigin,
[value]
);
}, []);
const togglePip = useCallback(() => {
UIManager.dispatchViewManagerCommand(
findNodeHandle(mediaPlayerRef.current),
UIManager.getViewManagerConfig(VIEW_NAME).Commands.togglePip,
[]
);
}, []);
useEffect(() => {
if (initialized.current || autoplay) {
if (paused) {
pause();
} else {
play();
}
}
initialized.current = true;
}, [pause, paused, play, autoplay]);
useImperativeHandle(
ref,
() => ({
preload,
loadSource,
releaseSource,
play,
pause,
seekTo,
setOrigin,
togglePip,
}),
[
preload,
loadSource,
releaseSource,
play,
pause,
seekTo,
setOrigin,
togglePip,
]
);
const onSeekHandler = (
event: NativeSyntheticEvent<{ position: number }>
) => {
const { position } = event.nativeEvent;
onSeek?.(position);
};
const onPlayerStateChangeHandler = (
event: NativeSyntheticEvent<{ state: PlayerState }>
) => {
const { state } = event.nativeEvent;
onPlayerStateChange?.(state);
};
const onDurationChangeHandler = (
event: NativeSyntheticEvent<{ duration: number | null }>
) => {
const { duration } = event.nativeEvent;
onDurationChange?.(parseDuration(duration));
};
const onQualityChangeHandler = (
event: NativeSyntheticEvent<{ quality: Quality }>
) => {
const { quality: newQuality } = event.nativeEvent;
onQualityChange?.(newQuality);
};
const onPipChangeHandler = (
event: NativeSyntheticEvent<{ active: string | boolean }>
) => {
const { active } = event.nativeEvent;
onPipChange?.(active === true || active === 'true');
};
const onLoadHandler = (
event: NativeSyntheticEvent<{
duration: number | null;
}>
) => {
if (Platform.OS === 'android') {
const shouldAutoPlay = autoplay && !paused;
shouldAutoPlay ? play() : pause();
}
const { duration } = event.nativeEvent;
onLoad?.(parseDuration(duration));
};
const onLiveLatencyChangeHandler = (
event: NativeSyntheticEvent<{ liveLatency: number }>
) => {
const { liveLatency } = event.nativeEvent;
onLiveLatencyChange?.(liveLatency);
};
const onDataHandler = (
event: NativeSyntheticEvent<{ playerData: PlayerData }>
) => {
const { playerData } = event.nativeEvent;
onData?.(playerData);
};
const onTextCueHandler = (
event: NativeSyntheticEvent<{ textCue: TextCue }>
) => {
const { textCue } = event.nativeEvent;
onTextCue?.(textCue);
};
const onVideoStatisticsHandler = (
event: NativeSyntheticEvent<{ videoData: VideoData }>
) => {
const { videoData } = event.nativeEvent;
const statistics: VideoData = {
...videoData,
duration: parseDuration(videoData.duration),
};
onVideoStatistics?.(statistics);
};
const onTextMetadataCueHandler = (
event: NativeSyntheticEvent<{ textMetadataCue: TextMetadataCue }>
) => {
const { textMetadataCue } = event.nativeEvent;
onTextMetadataCue?.(textMetadataCue);
};
const onProgressHandler = (
event: NativeSyntheticEvent<{ position: number }>
) => {
const { position } = event.nativeEvent;
onProgress?.(position);
};
const onErrorHandler = (event: NativeSyntheticEvent<{ error: string }>) => {
const { error } = event.nativeEvent;
onError?.(error);
};
const onTimePointHandler = (
event: NativeSyntheticEvent<{ position: number }>
) => {
const { position } = event.nativeEvent;
onTimePoint?.(position);
};
return (
<View style={[styles.container, style]} ref={ref as any}>
<IVSPlayer
testID="IVSPlayer"
muted={muted}
loop={loop}
liveLowLatency={liveLowLatency}
rebufferToLive={rebufferToLive}
style={styles.mediaPlayer}
ref={mediaPlayerRef}
playbackRate={playbackRate}
streamUrl={streamUrl}
logLevel={logLevel}
resizeMode={resizeMode}
progressInterval={progressInterval}
volume={volume}
quality={quality}
initialBufferDuration={initialBufferDuration}
autoMaxQuality={autoMaxQuality}
autoQualityMode={autoQualityMode}
breakpoints={breakpoints}
maxBitrate={maxBitrate}
pipEnabled={pipEnabled}
onVideoStatistics={
onVideoStatistics ? onVideoStatisticsHandler : undefined
}
onData={onDataHandler}
onSeek={onSeekHandler}
onQualityChange={onQualityChangeHandler}
onPipChange={onPipChangeHandler}
onPlayerStateChange={onPlayerStateChangeHandler}
onDurationChange={onDurationChangeHandler}
onRebuffering={onRebuffering}
onLoadStart={onLoadStart}
onLoad={onLoadHandler}
onTextCue={onTextCueHandler}
onTextMetadataCue={onTextMetadataCueHandler}
onProgress={onProgressHandler}
onLiveLatencyChange={
onLiveLatencyChange ? onLiveLatencyChangeHandler : undefined
}
onError={onErrorHandler}
onTimePoint={onTimePointHandler}
/>
<View style={styles.children}>{children}</View>
</View>
);
}
);
const styles = StyleSheet.create({
container: {
flex: 1,
},
mediaPlayer: {
flex: 1,
},
children: {
position: 'absolute',
width: '100%',
height: '100%',
},
});
export default IVSPlayerContainer;