1 | import * as React from 'react';
|
2 | import { View, createElement } from 'react-native';
|
3 |
|
4 | import { AVPlaybackNativeSource, AVPlaybackStatus, AVPlaybackStatusToSet } from './AV';
|
5 | import ExponentAV from './ExponentAV';
|
6 |
|
7 | type ExponentVideoProps = {
|
8 | source: AVPlaybackNativeSource | null;
|
9 | resizeMode?: object;
|
10 | status?: AVPlaybackStatusToSet;
|
11 | useNativeControls?: boolean;
|
12 | onStatusUpdate?: (event: { nativeEvent: AVPlaybackStatus }) => void;
|
13 | onReadyForDisplay?: (event: { nativeEvent: object }) => void;
|
14 | onFullscreenUpdate?: (event: { nativeEvent: object }) => void;
|
15 | onLoadStart: () => void;
|
16 | onLoad: (event: { nativeEvent: AVPlaybackStatus }) => void;
|
17 | onError: (event: { nativeEvent: { error: string } }) => void;
|
18 |
|
19 | scaleX?: number;
|
20 | scaleY?: number;
|
21 | translateX?: number;
|
22 | translateY?: number;
|
23 | rotation?: number;
|
24 | } & React.ComponentProps<typeof View>;
|
25 |
|
26 | export type NaturalSize = {
|
27 | width: number;
|
28 | height: number;
|
29 | orientation: 'portrait' | 'landscape';
|
30 | };
|
31 |
|
32 | export const FULLSCREEN_UPDATE_PLAYER_WILL_PRESENT = 0;
|
33 | export const FULLSCREEN_UPDATE_PLAYER_DID_PRESENT = 1;
|
34 | export const FULLSCREEN_UPDATE_PLAYER_WILL_DISMISS = 2;
|
35 | export const FULLSCREEN_UPDATE_PLAYER_DID_DISMISS = 3;
|
36 |
|
37 | export const IOS_FULLSCREEN_UPDATE_PLAYER_WILL_PRESENT = FULLSCREEN_UPDATE_PLAYER_WILL_PRESENT;
|
38 | export const IOS_FULLSCREEN_UPDATE_PLAYER_DID_PRESENT = FULLSCREEN_UPDATE_PLAYER_DID_PRESENT;
|
39 | export const IOS_FULLSCREEN_UPDATE_PLAYER_WILL_DISMISS = FULLSCREEN_UPDATE_PLAYER_WILL_DISMISS;
|
40 | export const IOS_FULLSCREEN_UPDATE_PLAYER_DID_DISMISS = FULLSCREEN_UPDATE_PLAYER_DID_DISMISS;
|
41 |
|
42 | const Video: any = React.forwardRef((props, ref) => createElement('video', { ...props, ref }));
|
43 |
|
44 | export default class ExponentVideo extends React.Component<ExponentVideoProps> {
|
45 | _video?: HTMLVideoElement;
|
46 |
|
47 | componentDidMount() {
|
48 | const isIE11 = !!window['MSStream'];
|
49 | document.addEventListener(
|
50 | isIE11 ? 'MSFullscreenChange' : 'fullscreenchange',
|
51 | this.onFullscreenChange
|
52 | );
|
53 | }
|
54 |
|
55 | componentWillUnmount() {
|
56 | const isIE11 = !!window['MSStream'];
|
57 | document.addEventListener(
|
58 | isIE11 ? 'MSFullscreenChange' : 'fullscreenchange',
|
59 | this.onFullscreenChange
|
60 | );
|
61 | }
|
62 |
|
63 | onFullscreenChange = event => {
|
64 | if (!this.props.onFullscreenUpdate) return;
|
65 |
|
66 | if (event.target === this._video) {
|
67 | if (document.fullscreenElement) {
|
68 | this.props.onFullscreenUpdate({
|
69 | nativeEvent: { fullscreenUpdate: FULLSCREEN_UPDATE_PLAYER_DID_PRESENT },
|
70 | });
|
71 | } else {
|
72 | this.props.onFullscreenUpdate({
|
73 | nativeEvent: { fullscreenUpdate: FULLSCREEN_UPDATE_PLAYER_DID_DISMISS },
|
74 | });
|
75 | }
|
76 | }
|
77 | };
|
78 |
|
79 | onStatusUpdate = async () => {
|
80 | if (!this.props.onStatusUpdate) {
|
81 | return;
|
82 | }
|
83 | const nativeEvent = await ExponentAV.getStatusForVideo(this._video);
|
84 | this.props.onStatusUpdate({ nativeEvent });
|
85 | };
|
86 |
|
87 | onLoadStart = () => {
|
88 | if (!this.props.onLoadStart) {
|
89 | return;
|
90 | }
|
91 | this.props.onLoadStart();
|
92 | this.onStatusUpdate();
|
93 | };
|
94 |
|
95 | onLoadedData = event => {
|
96 | if (!this.props.onLoad) {
|
97 | return;
|
98 | }
|
99 | this.props.onLoad(event);
|
100 | this.onStatusUpdate();
|
101 | };
|
102 |
|
103 | onError = event => {
|
104 | if (!this.props.onError) {
|
105 | return;
|
106 | }
|
107 | this.props.onError(event);
|
108 | this.onStatusUpdate();
|
109 | };
|
110 |
|
111 | onProgress = () => {
|
112 | this.onStatusUpdate();
|
113 | };
|
114 |
|
115 | onSeeking = () => {
|
116 | this.onStatusUpdate();
|
117 | };
|
118 |
|
119 | onEnded = () => {
|
120 | this.onStatusUpdate();
|
121 | };
|
122 |
|
123 | onLoadedMetadata = () => {
|
124 | this.onStatusUpdate();
|
125 | };
|
126 |
|
127 | onCanPlay = event => {
|
128 | if (!this.props.onReadyForDisplay) {
|
129 | return;
|
130 | }
|
131 | this.props.onReadyForDisplay(event);
|
132 | this.onStatusUpdate();
|
133 | };
|
134 |
|
135 | onStalled = () => {
|
136 | this.onStatusUpdate();
|
137 | };
|
138 |
|
139 | onRef = (ref: HTMLVideoElement) => {
|
140 | this._video = ref;
|
141 | this.onStatusUpdate();
|
142 | };
|
143 |
|
144 | render() {
|
145 | const { source, status = {}, resizeMode: objectFit, useNativeControls, style } = this.props;
|
146 |
|
147 | const customStyle = {
|
148 | position: undefined,
|
149 | objectFit,
|
150 | overflow: 'hidden',
|
151 | };
|
152 | return (
|
153 | <Video
|
154 | ref={this.onRef}
|
155 | onLoadStart={this.onLoadStart}
|
156 | onLoadedData={this.onLoadedData}
|
157 | onError={this.onError}
|
158 | onTimeUpdate={this.onProgress}
|
159 | onSeeking={this.onSeeking}
|
160 | onEnded={this.onEnded}
|
161 | onLoadedMetadata={this.onLoadedMetadata}
|
162 | onCanPlay={this.onCanPlay}
|
163 | onStalled={this.onStalled}
|
164 | src={(source || { uri: undefined }).uri}
|
165 | muted={status.isMuted}
|
166 | loop={status.isLooping}
|
167 | autoPlay={status.shouldPlay}
|
168 | controls={useNativeControls}
|
169 | style={[style, customStyle]}
|
170 | />
|
171 | );
|
172 | }
|
173 | }
|