UNPKG

11.5 kBJavaScriptView Raw
1import omit from 'lodash/omit';
2import nullthrows from 'nullthrows';
3import * as React from 'react';
4import { findNodeHandle, Image, StyleSheet, View } from 'react-native';
5import { assertStatusValuesInBounds, getNativeSourceAndFullInitialStatusForLoadAsync, getNativeSourceFromSource, getUnloadedStatus, PlaybackMixin, } from './AV';
6import ExpoVideoManager from './ExpoVideoManager';
7import ExponentAV from './ExponentAV';
8import ExponentVideo from './ExponentVideo';
9import { ResizeMode, } from './Video.types';
10export { ResizeMode, };
11export const FULLSCREEN_UPDATE_PLAYER_WILL_PRESENT = 0;
12export const FULLSCREEN_UPDATE_PLAYER_DID_PRESENT = 1;
13export const FULLSCREEN_UPDATE_PLAYER_WILL_DISMISS = 2;
14export const FULLSCREEN_UPDATE_PLAYER_DID_DISMISS = 3;
15export const IOS_FULLSCREEN_UPDATE_PLAYER_WILL_PRESENT = FULLSCREEN_UPDATE_PLAYER_WILL_PRESENT;
16export const IOS_FULLSCREEN_UPDATE_PLAYER_DID_PRESENT = FULLSCREEN_UPDATE_PLAYER_DID_PRESENT;
17export const IOS_FULLSCREEN_UPDATE_PLAYER_WILL_DISMISS = FULLSCREEN_UPDATE_PLAYER_WILL_DISMISS;
18export const IOS_FULLSCREEN_UPDATE_PLAYER_DID_DISMISS = FULLSCREEN_UPDATE_PLAYER_DID_DISMISS;
19const _STYLES = StyleSheet.create({
20 base: {
21 overflow: 'hidden',
22 },
23 poster: {
24 position: 'absolute',
25 left: 0,
26 top: 0,
27 right: 0,
28 bottom: 0,
29 resizeMode: 'contain',
30 },
31 video: {
32 position: 'absolute',
33 left: 0,
34 top: 0,
35 right: 0,
36 bottom: 0,
37 },
38});
39// On a real device UIManager should be present, however when running offline tests with jest-expo
40// we have to use the provided native module mock to access constants
41const ExpoVideoManagerConstants = ExpoVideoManager;
42const ExpoVideoViewManager = ExpoVideoManager;
43export default class Video extends React.Component {
44 // componentOrHandle: null | number | React.Component<any, any> | React.ComponentClass<any>
45 constructor(props) {
46 super(props);
47 this._nativeRef = React.createRef();
48 this._onPlaybackStatusUpdate = null;
49 // Internal methods
50 this._handleNewStatus = (status) => {
51 if (this.state.showPoster &&
52 status.isLoaded &&
53 (status.isPlaying || status.positionMillis !== 0)) {
54 this.setState({ showPoster: false });
55 }
56 if (this.props.onPlaybackStatusUpdate) {
57 this.props.onPlaybackStatusUpdate(status);
58 }
59 if (this._onPlaybackStatusUpdate) {
60 this._onPlaybackStatusUpdate(status);
61 }
62 };
63 this._performOperationAndHandleStatusAsync = async (operation) => {
64 const video = this._nativeRef.current;
65 if (!video) {
66 throw new Error(`Cannot complete operation because the Video component has not yet loaded`);
67 }
68 const handle = findNodeHandle(this._nativeRef.current);
69 const status = await operation(handle);
70 this._handleNewStatus(status);
71 return status;
72 };
73 // ### iOS Fullscreening API ###
74 this._setFullscreen = async (value) => {
75 return this._performOperationAndHandleStatusAsync((tag) => ExpoVideoViewManager.setFullscreen(tag, value));
76 };
77 this.presentFullscreenPlayer = async () => {
78 return this._setFullscreen(true);
79 };
80 this.presentIOSFullscreenPlayer = () => {
81 console.warn("You're using `presentIOSFullscreenPlayer`. Please migrate your code to use `presentFullscreenPlayer` instead.");
82 return this.presentFullscreenPlayer();
83 };
84 this.presentFullscreenPlayerAsync = async () => {
85 return await this.presentFullscreenPlayer();
86 };
87 this.dismissFullscreenPlayer = async () => {
88 return this._setFullscreen(false);
89 };
90 this.dismissIOSFullscreenPlayer = () => {
91 console.warn("You're using `dismissIOSFullscreenPlayer`. Please migrate your code to use `dismissFullscreenPlayer` instead.");
92 this.dismissFullscreenPlayer();
93 };
94 // ### Unified playback API ### (consistent with Audio.js)
95 // All calls automatically call onPlaybackStatusUpdate as a side effect.
96 // Get status API
97 this.getStatusAsync = async () => {
98 return this._performOperationAndHandleStatusAsync((tag) => ExponentAV.getStatusForVideo(tag));
99 };
100 // Loading / unloading API
101 this.loadAsync = async (source, initialStatus = {}, downloadFirst = true) => {
102 const { nativeSource, fullInitialStatus, } = await getNativeSourceAndFullInitialStatusForLoadAsync(source, initialStatus, downloadFirst);
103 return this._performOperationAndHandleStatusAsync((tag) => ExponentAV.loadForVideo(tag, nativeSource, fullInitialStatus));
104 };
105 // Equivalent to setting URI to null.
106 this.unloadAsync = async () => {
107 return this._performOperationAndHandleStatusAsync((tag) => ExponentAV.unloadForVideo(tag));
108 };
109 // Set status API (only available while isLoaded = true)
110 this.setStatusAsync = async (status) => {
111 assertStatusValuesInBounds(status);
112 return this._performOperationAndHandleStatusAsync((tag) => ExponentAV.setStatusForVideo(tag, status));
113 };
114 this.replayAsync = async (status = {}) => {
115 if (status.positionMillis && status.positionMillis !== 0) {
116 throw new Error('Requested position after replay has to be 0.');
117 }
118 return this._performOperationAndHandleStatusAsync((tag) => ExponentAV.replayVideo(tag, {
119 ...status,
120 positionMillis: 0,
121 shouldPlay: true,
122 }));
123 };
124 // ### Callback wrappers ###
125 this._nativeOnPlaybackStatusUpdate = (event) => {
126 this._handleNewStatus(event.nativeEvent);
127 };
128 // TODO make sure we are passing the right stuff
129 this._nativeOnLoadStart = () => {
130 if (this.props.onLoadStart) {
131 this.props.onLoadStart();
132 }
133 };
134 this._nativeOnLoad = (event) => {
135 if (this.props.onLoad) {
136 this.props.onLoad(event.nativeEvent);
137 }
138 this._handleNewStatus(event.nativeEvent);
139 };
140 this._nativeOnError = (event) => {
141 const error = event.nativeEvent.error;
142 if (this.props.onError) {
143 this.props.onError(error);
144 }
145 this._handleNewStatus(getUnloadedStatus(error));
146 };
147 this._nativeOnReadyForDisplay = (event) => {
148 if (this.props.onReadyForDisplay) {
149 this.props.onReadyForDisplay(event.nativeEvent);
150 }
151 };
152 this._nativeOnFullscreenUpdate = (event) => {
153 if (this.props.onIOSFullscreenUpdate && this.props.onFullscreenUpdate) {
154 console.warn("You've supplied both `onIOSFullscreenUpdate` and `onFullscreenUpdate`. You're going to receive updates on both the callbacks.");
155 }
156 else if (this.props.onIOSFullscreenUpdate) {
157 console.warn("You're using `onIOSFullscreenUpdate`. Please migrate your code to use `onFullscreenUpdate` instead.");
158 }
159 if (this.props.onIOSFullscreenUpdate) {
160 this.props.onIOSFullscreenUpdate(event.nativeEvent);
161 }
162 if (this.props.onFullscreenUpdate) {
163 this.props.onFullscreenUpdate(event.nativeEvent);
164 }
165 };
166 this._renderPoster = () => this.props.usePoster && this.state.showPoster ? (
167 // @ts-ignore: the react-native type declarations are overly restrictive
168 React.createElement(Image, { style: [_STYLES.poster, this.props.posterStyle], source: this.props.posterSource })) : null;
169 this.state = {
170 showPoster: !!props.usePoster,
171 };
172 }
173 setNativeProps(nativeProps) {
174 const nativeVideo = nullthrows(this._nativeRef.current);
175 nativeVideo.setNativeProps(nativeProps);
176 }
177 setOnPlaybackStatusUpdate(onPlaybackStatusUpdate) {
178 this._onPlaybackStatusUpdate = onPlaybackStatusUpdate;
179 this.getStatusAsync();
180 }
181 render() {
182 const source = getNativeSourceFromSource(this.props.source) || undefined;
183 let nativeResizeMode = ExpoVideoManagerConstants.ScaleNone;
184 if (this.props.resizeMode) {
185 const resizeMode = this.props.resizeMode;
186 if (resizeMode === ResizeMode.STRETCH) {
187 nativeResizeMode = ExpoVideoManagerConstants.ScaleToFill;
188 }
189 else if (resizeMode === ResizeMode.CONTAIN) {
190 nativeResizeMode = ExpoVideoManagerConstants.ScaleAspectFit;
191 }
192 else if (resizeMode === ResizeMode.COVER) {
193 nativeResizeMode = ExpoVideoManagerConstants.ScaleAspectFill;
194 }
195 }
196 // Set status via individual props
197 const status = { ...this.props.status };
198 [
199 'progressUpdateIntervalMillis',
200 'positionMillis',
201 'shouldPlay',
202 'rate',
203 'shouldCorrectPitch',
204 'volume',
205 'isMuted',
206 'isLooping',
207 ].forEach(prop => {
208 if (prop in this.props) {
209 status[prop] = this.props[prop];
210 }
211 });
212 // Replace selected native props
213 // @ts-ignore: TypeScript thinks "children" is not in the list of props
214 const nativeProps = {
215 ...omit(this.props, 'source', 'onPlaybackStatusUpdate', 'usePoster', 'posterSource', 'posterStyle', ...Object.keys(status)),
216 style: StyleSheet.flatten([_STYLES.base, this.props.style]),
217 source,
218 resizeMode: nativeResizeMode,
219 status,
220 onStatusUpdate: this._nativeOnPlaybackStatusUpdate,
221 onLoadStart: this._nativeOnLoadStart,
222 onLoad: this._nativeOnLoad,
223 onError: this._nativeOnError,
224 onReadyForDisplay: this._nativeOnReadyForDisplay,
225 onFullscreenUpdate: this._nativeOnFullscreenUpdate,
226 };
227 return (React.createElement(View, { style: nativeProps.style, pointerEvents: "box-none" },
228 React.createElement(ExponentVideo, Object.assign({ ref: this._nativeRef }, nativeProps, { style: _STYLES.video })),
229 this._renderPoster()));
230 }
231}
232Video.RESIZE_MODE_CONTAIN = ResizeMode.CONTAIN;
233Video.RESIZE_MODE_COVER = ResizeMode.COVER;
234Video.RESIZE_MODE_STRETCH = ResizeMode.STRETCH;
235Video.IOS_FULLSCREEN_UPDATE_PLAYER_WILL_PRESENT = IOS_FULLSCREEN_UPDATE_PLAYER_WILL_PRESENT;
236Video.IOS_FULLSCREEN_UPDATE_PLAYER_DID_PRESENT = IOS_FULLSCREEN_UPDATE_PLAYER_DID_PRESENT;
237Video.IOS_FULLSCREEN_UPDATE_PLAYER_WILL_DISMISS = IOS_FULLSCREEN_UPDATE_PLAYER_WILL_DISMISS;
238Video.IOS_FULLSCREEN_UPDATE_PLAYER_DID_DISMISS = IOS_FULLSCREEN_UPDATE_PLAYER_DID_DISMISS;
239Video.FULLSCREEN_UPDATE_PLAYER_WILL_PRESENT = FULLSCREEN_UPDATE_PLAYER_WILL_PRESENT;
240Video.FULLSCREEN_UPDATE_PLAYER_DID_PRESENT = FULLSCREEN_UPDATE_PLAYER_DID_PRESENT;
241Video.FULLSCREEN_UPDATE_PLAYER_WILL_DISMISS = FULLSCREEN_UPDATE_PLAYER_WILL_DISMISS;
242Video.FULLSCREEN_UPDATE_PLAYER_DID_DISMISS = FULLSCREEN_UPDATE_PLAYER_DID_DISMISS;
243Object.assign(Video.prototype, PlaybackMixin);
244//# sourceMappingURL=Video.js.map
\No newline at end of file