1 | import React, {Component} from 'react';
|
2 | import PropTypes from 'prop-types';
|
3 | import {StyleSheet, requireNativeComponent, NativeModules, View, ViewPropTypes, Image, Platform, findNodeHandle} from 'react-native';
|
4 | import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource';
|
5 | import TextTrackType from './TextTrackType';
|
6 | import FilterType from './FilterType';
|
7 | import VideoResizeMode from './VideoResizeMode.js';
|
8 |
|
9 | const styles = StyleSheet.create({
|
10 | base: {
|
11 | overflow: 'hidden',
|
12 | },
|
13 | });
|
14 |
|
15 | export { TextTrackType, FilterType };
|
16 |
|
17 | export default class Video extends Component {
|
18 |
|
19 | constructor(props) {
|
20 | super(props);
|
21 |
|
22 | this.state = {
|
23 | showPoster: true,
|
24 | };
|
25 | }
|
26 |
|
27 | setNativeProps(nativeProps) {
|
28 | this._root.setNativeProps(nativeProps);
|
29 | }
|
30 |
|
31 | toTypeString(x) {
|
32 | switch (typeof x) {
|
33 | case "object":
|
34 | return x instanceof Date
|
35 | ? x.toISOString()
|
36 | : JSON.stringify(x);
|
37 | case "undefined":
|
38 | return "";
|
39 | default:
|
40 | return x.toString();
|
41 | }
|
42 | }
|
43 |
|
44 | stringsOnlyObject(obj) {
|
45 | const strObj = {};
|
46 |
|
47 | Object.keys(obj).forEach(x => {
|
48 | strObj[x] = this.toTypeString(obj[x]);
|
49 | });
|
50 |
|
51 | return strObj;
|
52 | }
|
53 |
|
54 | seek = (time, tolerance = 100) => {
|
55 | if (isNaN(time)) throw new Error('Specified time is not a number');
|
56 |
|
57 | if (Platform.OS === 'ios') {
|
58 | this.setNativeProps({
|
59 | seek: {
|
60 | time,
|
61 | tolerance
|
62 | }
|
63 | });
|
64 | } else {
|
65 | this.setNativeProps({ seek: time });
|
66 | }
|
67 | };
|
68 |
|
69 | presentFullscreenPlayer = () => {
|
70 | this.setNativeProps({ fullscreen: true });
|
71 | };
|
72 |
|
73 | dismissFullscreenPlayer = () => {
|
74 | this.setNativeProps({ fullscreen: false });
|
75 | };
|
76 |
|
77 | save = async (options?) => {
|
78 | return await NativeModules.VideoManager.save(options, findNodeHandle(this._root));
|
79 | }
|
80 |
|
81 | _assignRoot = (component) => {
|
82 | this._root = component;
|
83 | };
|
84 |
|
85 | _onLoadStart = (event) => {
|
86 | if (this.props.onLoadStart) {
|
87 | this.props.onLoadStart(event.nativeEvent);
|
88 | }
|
89 | };
|
90 |
|
91 | _onLoad = (event) => {
|
92 | if (this.props.onLoad) {
|
93 | this.props.onLoad(event.nativeEvent);
|
94 | }
|
95 | };
|
96 |
|
97 | _onError = (event) => {
|
98 | if (this.props.onError) {
|
99 | this.props.onError(event.nativeEvent);
|
100 | }
|
101 | };
|
102 |
|
103 | _onProgress = (event) => {
|
104 | if (this.props.onProgress) {
|
105 | this.props.onProgress(event.nativeEvent);
|
106 | }
|
107 | };
|
108 |
|
109 | _onBandwidthUpdate = (event) => {
|
110 | if (this.props.onBandwidthUpdate) {
|
111 | this.props.onBandwidthUpdate(event.nativeEvent);
|
112 | }
|
113 | };
|
114 |
|
115 | _onSeek = (event) => {
|
116 | if (this.state.showPoster && !this.props.audioOnly) {
|
117 | this.setState({showPoster: false});
|
118 | }
|
119 |
|
120 | if (this.props.onSeek) {
|
121 | this.props.onSeek(event.nativeEvent);
|
122 | }
|
123 | };
|
124 |
|
125 | _onEnd = (event) => {
|
126 | if (this.props.onEnd) {
|
127 | this.props.onEnd(event.nativeEvent);
|
128 | }
|
129 | };
|
130 |
|
131 | _onTimedMetadata = (event) => {
|
132 | if (this.props.onTimedMetadata) {
|
133 | this.props.onTimedMetadata(event.nativeEvent);
|
134 | }
|
135 | };
|
136 |
|
137 | _onFullscreenPlayerWillPresent = (event) => {
|
138 | if (this.props.onFullscreenPlayerWillPresent) {
|
139 | this.props.onFullscreenPlayerWillPresent(event.nativeEvent);
|
140 | }
|
141 | };
|
142 |
|
143 | _onFullscreenPlayerDidPresent = (event) => {
|
144 | if (this.props.onFullscreenPlayerDidPresent) {
|
145 | this.props.onFullscreenPlayerDidPresent(event.nativeEvent);
|
146 | }
|
147 | };
|
148 |
|
149 | _onFullscreenPlayerWillDismiss = (event) => {
|
150 | if (this.props.onFullscreenPlayerWillDismiss) {
|
151 | this.props.onFullscreenPlayerWillDismiss(event.nativeEvent);
|
152 | }
|
153 | };
|
154 |
|
155 | _onFullscreenPlayerDidDismiss = (event) => {
|
156 | if (this.props.onFullscreenPlayerDidDismiss) {
|
157 | this.props.onFullscreenPlayerDidDismiss(event.nativeEvent);
|
158 | }
|
159 | };
|
160 |
|
161 | _onReadyForDisplay = (event) => {
|
162 | if (this.props.onReadyForDisplay) {
|
163 | this.props.onReadyForDisplay(event.nativeEvent);
|
164 | }
|
165 | };
|
166 |
|
167 | _onPlaybackStalled = (event) => {
|
168 | if (this.props.onPlaybackStalled) {
|
169 | this.props.onPlaybackStalled(event.nativeEvent);
|
170 | }
|
171 | };
|
172 |
|
173 | _onPlaybackResume = (event) => {
|
174 | if (this.props.onPlaybackResume) {
|
175 | this.props.onPlaybackResume(event.nativeEvent);
|
176 | }
|
177 | };
|
178 |
|
179 | _onPlaybackRateChange = (event) => {
|
180 | if (this.state.showPoster && event.nativeEvent.playbackRate !== 0 && !this.props.audioOnly) {
|
181 | this.setState({showPoster: false});
|
182 | }
|
183 |
|
184 | if (this.props.onPlaybackRateChange) {
|
185 | this.props.onPlaybackRateChange(event.nativeEvent);
|
186 | }
|
187 | };
|
188 |
|
189 | _onExternalPlaybackChange = (event) => {
|
190 | if (this.props.onExternalPlaybackChange) {
|
191 | this.props.onExternalPlaybackChange(event.nativeEvent);
|
192 | }
|
193 | }
|
194 |
|
195 | _onAudioBecomingNoisy = () => {
|
196 | if (this.props.onAudioBecomingNoisy) {
|
197 | this.props.onAudioBecomingNoisy();
|
198 | }
|
199 | };
|
200 |
|
201 | _onAudioFocusChanged = (event) => {
|
202 | if (this.props.onAudioFocusChanged) {
|
203 | this.props.onAudioFocusChanged(event.nativeEvent);
|
204 | }
|
205 | };
|
206 |
|
207 | _onBuffer = (event) => {
|
208 | if (this.props.onBuffer) {
|
209 | this.props.onBuffer(event.nativeEvent);
|
210 | }
|
211 | };
|
212 |
|
213 | render() {
|
214 | const resizeMode = this.props.resizeMode;
|
215 | const source = resolveAssetSource(this.props.source) || {};
|
216 |
|
217 | let uri = source.uri || '';
|
218 | if (uri && uri.match(/^\//)) {
|
219 | uri = `file://${uri}`;
|
220 | }
|
221 |
|
222 | const isNetwork = !!(uri && uri.match(/^https?:/));
|
223 | const isAsset = !!(uri && uri.match(/^(assets-library|ipod-library|file|content|ms-appx|ms-appdata):/));
|
224 |
|
225 | let nativeResizeMode;
|
226 | if (resizeMode === VideoResizeMode.stretch) {
|
227 | nativeResizeMode = NativeModules.UIManager.RCTVideo.Constants.ScaleToFill;
|
228 | } else if (resizeMode === VideoResizeMode.contain) {
|
229 | nativeResizeMode = NativeModules.UIManager.RCTVideo.Constants.ScaleAspectFit;
|
230 | } else if (resizeMode === VideoResizeMode.cover) {
|
231 | nativeResizeMode = NativeModules.UIManager.RCTVideo.Constants.ScaleAspectFill;
|
232 | } else {
|
233 | nativeResizeMode = NativeModules.UIManager.RCTVideo.Constants.ScaleNone;
|
234 | }
|
235 |
|
236 | const nativeProps = Object.assign({}, this.props);
|
237 | Object.assign(nativeProps, {
|
238 | style: [styles.base, nativeProps.style],
|
239 | resizeMode: nativeResizeMode,
|
240 | src: {
|
241 | uri,
|
242 | isNetwork,
|
243 | isAsset,
|
244 | type: source.type || '',
|
245 | mainVer: source.mainVer || 0,
|
246 | patchVer: source.patchVer || 0,
|
247 | requestHeaders: source.headers ? this.stringsOnlyObject(source.headers) : {}
|
248 | },
|
249 | onVideoLoadStart: this._onLoadStart,
|
250 | onVideoLoad: this._onLoad,
|
251 | onVideoError: this._onError,
|
252 | onVideoProgress: this._onProgress,
|
253 | onVideoSeek: this._onSeek,
|
254 | onVideoEnd: this._onEnd,
|
255 | onVideoBuffer: this._onBuffer,
|
256 | onVideoBandwidthUpdate: this._onBandwidthUpdate,
|
257 | onTimedMetadata: this._onTimedMetadata,
|
258 | onVideoAudioBecomingNoisy: this._onAudioBecomingNoisy,
|
259 | onVideoExternalPlaybackChange: this._onExternalPlaybackChange,
|
260 | onVideoFullscreenPlayerWillPresent: this._onFullscreenPlayerWillPresent,
|
261 | onVideoFullscreenPlayerDidPresent: this._onFullscreenPlayerDidPresent,
|
262 | onVideoFullscreenPlayerWillDismiss: this._onFullscreenPlayerWillDismiss,
|
263 | onVideoFullscreenPlayerDidDismiss: this._onFullscreenPlayerDidDismiss,
|
264 | onReadyForDisplay: this._onReadyForDisplay,
|
265 | onPlaybackStalled: this._onPlaybackStalled,
|
266 | onPlaybackResume: this._onPlaybackResume,
|
267 | onPlaybackRateChange: this._onPlaybackRateChange,
|
268 | onAudioFocusChanged: this._onAudioFocusChanged,
|
269 | onAudioBecomingNoisy: this._onAudioBecomingNoisy,
|
270 | });
|
271 |
|
272 | const posterStyle = {
|
273 | ...StyleSheet.absoluteFillObject,
|
274 | resizeMode: this.props.posterResizeMode || 'contain',
|
275 | };
|
276 |
|
277 | return (
|
278 | <React.Fragment>
|
279 | <RCTVideo ref={this._assignRoot} {...nativeProps} />
|
280 | {this.props.poster &&
|
281 | this.state.showPoster && (
|
282 | <View style={nativeProps.style}>
|
283 | <Image style={posterStyle} source={{ uri: this.props.poster }} />
|
284 | </View>
|
285 | )}
|
286 | </React.Fragment>
|
287 | );
|
288 | }
|
289 | }
|
290 |
|
291 | Video.propTypes = {
|
292 | filter: PropTypes.oneOf([
|
293 | FilterType.NONE,
|
294 | FilterType.INVERT,
|
295 | FilterType.MONOCHROME,
|
296 | FilterType.POSTERIZE,
|
297 | FilterType.FALSE,
|
298 | FilterType.MAXIMUMCOMPONENT,
|
299 | FilterType.MINIMUMCOMPONENT,
|
300 | FilterType.CHROME,
|
301 | FilterType.FADE,
|
302 | FilterType.INSTANT,
|
303 | FilterType.MONO,
|
304 | FilterType.NOIR,
|
305 | FilterType.PROCESS,
|
306 | FilterType.TONAL,
|
307 | FilterType.TRANSFER,
|
308 | FilterType.SEPIA
|
309 | ]),
|
310 | filterEnabled: PropTypes.bool,
|
311 |
|
312 | src: PropTypes.object,
|
313 | seek: PropTypes.oneOfType([
|
314 | PropTypes.number,
|
315 | PropTypes.object
|
316 | ]),
|
317 | fullscreen: PropTypes.bool,
|
318 | onVideoLoadStart: PropTypes.func,
|
319 | onVideoLoad: PropTypes.func,
|
320 | onVideoBuffer: PropTypes.func,
|
321 | onVideoError: PropTypes.func,
|
322 | onVideoProgress: PropTypes.func,
|
323 | onVideoBandwidthUpdate: PropTypes.func,
|
324 | onVideoSeek: PropTypes.func,
|
325 | onVideoEnd: PropTypes.func,
|
326 | onTimedMetadata: PropTypes.func,
|
327 | onVideoAudioBecomingNoisy: PropTypes.func,
|
328 | onVideoExternalPlaybackChange: PropTypes.func,
|
329 | onVideoFullscreenPlayerWillPresent: PropTypes.func,
|
330 | onVideoFullscreenPlayerDidPresent: PropTypes.func,
|
331 | onVideoFullscreenPlayerWillDismiss: PropTypes.func,
|
332 | onVideoFullscreenPlayerDidDismiss: PropTypes.func,
|
333 |
|
334 |
|
335 | source: PropTypes.oneOfType([
|
336 | PropTypes.shape({
|
337 | uri: PropTypes.string
|
338 | }),
|
339 |
|
340 | PropTypes.number
|
341 | ]),
|
342 | maxBitRate: PropTypes.number,
|
343 | resizeMode: PropTypes.string,
|
344 | poster: PropTypes.string,
|
345 | posterResizeMode: Image.propTypes.resizeMode,
|
346 | repeat: PropTypes.bool,
|
347 | allowsExternalPlayback: PropTypes.bool,
|
348 | selectedAudioTrack: PropTypes.shape({
|
349 | type: PropTypes.string.isRequired,
|
350 | value: PropTypes.oneOfType([
|
351 | PropTypes.string,
|
352 | PropTypes.number
|
353 | ])
|
354 | }),
|
355 | selectedVideoTrack: PropTypes.shape({
|
356 | type: PropTypes.string.isRequired,
|
357 | value: PropTypes.oneOfType([
|
358 | PropTypes.string,
|
359 | PropTypes.number
|
360 | ])
|
361 | }),
|
362 | selectedTextTrack: PropTypes.shape({
|
363 | type: PropTypes.string.isRequired,
|
364 | value: PropTypes.oneOfType([
|
365 | PropTypes.string,
|
366 | PropTypes.number
|
367 | ])
|
368 | }),
|
369 | textTracks: PropTypes.arrayOf(
|
370 | PropTypes.shape({
|
371 | title: PropTypes.string,
|
372 | uri: PropTypes.string.isRequired,
|
373 | type: PropTypes.oneOf([
|
374 | TextTrackType.SRT,
|
375 | TextTrackType.TTML,
|
376 | TextTrackType.VTT,
|
377 | ]),
|
378 | language: PropTypes.string.isRequired
|
379 | })
|
380 | ),
|
381 | paused: PropTypes.bool,
|
382 | muted: PropTypes.bool,
|
383 | volume: PropTypes.number,
|
384 | bufferConfig: PropTypes.shape({
|
385 | minBufferMs: PropTypes.number,
|
386 | maxBufferMs: PropTypes.number,
|
387 | bufferForPlaybackMs: PropTypes.number,
|
388 | bufferForPlaybackAfterRebufferMs: PropTypes.number,
|
389 | }),
|
390 | stereoPan: PropTypes.number,
|
391 | rate: PropTypes.number,
|
392 | playInBackground: PropTypes.bool,
|
393 | playWhenInactive: PropTypes.bool,
|
394 | ignoreSilentSwitch: PropTypes.oneOf(['ignore', 'obey']),
|
395 | reportBandwidth: PropTypes.bool,
|
396 | disableFocus: PropTypes.bool,
|
397 | controls: PropTypes.bool,
|
398 | audioOnly: PropTypes.bool,
|
399 | currentTime: PropTypes.number,
|
400 | fullscreenAutorotate: PropTypes.bool,
|
401 | fullscreenOrientation: PropTypes.oneOf(['all','landscape','portrait']),
|
402 | progressUpdateInterval: PropTypes.number,
|
403 | useTextureView: PropTypes.bool,
|
404 | hideShutterView: PropTypes.bool,
|
405 | onLoadStart: PropTypes.func,
|
406 | onLoad: PropTypes.func,
|
407 | onBuffer: PropTypes.func,
|
408 | onError: PropTypes.func,
|
409 | onProgress: PropTypes.func,
|
410 | onBandwidthUpdate: PropTypes.func,
|
411 | onSeek: PropTypes.func,
|
412 | onEnd: PropTypes.func,
|
413 | onFullscreenPlayerWillPresent: PropTypes.func,
|
414 | onFullscreenPlayerDidPresent: PropTypes.func,
|
415 | onFullscreenPlayerWillDismiss: PropTypes.func,
|
416 | onFullscreenPlayerDidDismiss: PropTypes.func,
|
417 | onReadyForDisplay: PropTypes.func,
|
418 | onPlaybackStalled: PropTypes.func,
|
419 | onPlaybackResume: PropTypes.func,
|
420 | onPlaybackRateChange: PropTypes.func,
|
421 | onAudioFocusChanged: PropTypes.func,
|
422 | onAudioBecomingNoisy: PropTypes.func,
|
423 | onExternalPlaybackChange: PropTypes.func,
|
424 |
|
425 |
|
426 | scaleX: PropTypes.number,
|
427 | scaleY: PropTypes.number,
|
428 | translateX: PropTypes.number,
|
429 | translateY: PropTypes.number,
|
430 | rotation: PropTypes.number,
|
431 | ...ViewPropTypes,
|
432 | };
|
433 |
|
434 | const RCTVideo = requireNativeComponent('RCTVideo', Video, {
|
435 | nativeOnly: {
|
436 | src: true,
|
437 | seek: true,
|
438 | fullscreen: true,
|
439 | },
|
440 | });
|