react-native-video
Advanced tools
| <manifest xmlns:android="http://schemas.android.com/apk/res/android" | ||
| package="com.brentvatne.RCTVideo"> | ||
| package="com.brentvatne.react"> | ||
| </manifest> |
| package com.brentvatne.react; | ||
| import android.media.MediaPlayer; | ||
| import android.net.Uri; | ||
| import android.os.Handler; | ||
| import android.util.Log; | ||
| import android.net.Uri; | ||
| import android.webkit.CookieManager; | ||
| import java.util.Map; | ||
| import java.util.HashMap; | ||
| import com.facebook.react.bridge.Arguments; | ||
| import com.facebook.react.bridge.LifecycleEventListener; | ||
| import com.facebook.react.bridge.WritableMap; | ||
@@ -17,4 +17,7 @@ import com.facebook.react.uimanager.ThemedReactContext; | ||
| import java.util.HashMap; | ||
| import java.util.Map; | ||
| public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnPreparedListener, MediaPlayer | ||
| .OnErrorListener, MediaPlayer.OnBufferingUpdateListener, MediaPlayer.OnCompletionListener { | ||
| .OnErrorListener, MediaPlayer.OnBufferingUpdateListener, MediaPlayer.OnCompletionListener, MediaPlayer.OnInfoListener, LifecycleEventListener { | ||
@@ -27,3 +30,6 @@ public enum Events { | ||
| EVENT_SEEK("onVideoSeek"), | ||
| EVENT_END("onVideoEnd"); | ||
| EVENT_END("onVideoEnd"), | ||
| EVENT_STALLED("onPlaybackStalled"), | ||
| EVENT_RESUME("onPlaybackResume"), | ||
| EVENT_READY_FOR_DISPLAY("onReadyForDisplay"); | ||
@@ -53,2 +59,6 @@ private final String mName; | ||
| public static final String EVENT_PROP_SEEK_TIME = "seekTime"; | ||
| public static final String EVENT_PROP_NATURALSIZE = "naturalSize"; | ||
| public static final String EVENT_PROP_WIDTH = "width"; | ||
| public static final String EVENT_PROP_HEIGHT = "height"; | ||
| public static final String EVENT_PROP_ORIENTATION = "orientation"; | ||
@@ -75,6 +85,8 @@ public static final String EVENT_PROP_ERROR = "error"; | ||
| private float mRate = 1.0f; | ||
| private boolean mPlayInBackground = false; | ||
| private boolean mMediaPlayerValid = false; // True if mMediaPlayer is in prepared, started, or paused state. | ||
| private boolean mMediaPlayerValid = false; // True if mMediaPlayer is in prepared, started, paused or completed state. | ||
| private int mVideoDuration = 0; | ||
| private int mVideoBufferedDuration = 0; | ||
| private boolean isCompleted = false; | ||
@@ -86,2 +98,3 @@ public ReactVideoView(ThemedReactContext themedReactContext) { | ||
| mEventEmitter = themedReactContext.getJSModule(RCTEventEmitter.class); | ||
| themedReactContext.addLifecycleEventListener(this); | ||
@@ -95,3 +108,3 @@ initializeMediaPlayerIfNeeded(); | ||
| if (mMediaPlayerValid) { | ||
| if (mMediaPlayerValid && !isCompleted) { | ||
| WritableMap event = Arguments.createMap(); | ||
@@ -118,2 +131,4 @@ event.putDouble(EVENT_PROP_CURRENT_TIME, mMediaPlayer.getCurrentPosition() / 1000.0); | ||
| mMediaPlayer.setOnCompletionListener(this); | ||
| mMediaPlayer.setOnInfoListener(this); | ||
| } | ||
@@ -123,2 +138,3 @@ } | ||
| public void setSrc(final String uriString, final String type, final boolean isNetwork, final boolean isAsset) { | ||
| mSrcUriString = uriString; | ||
@@ -194,2 +210,3 @@ mSrcType = type; | ||
| public void setRepeatModifier(final boolean repeat) { | ||
| mRepeat = repeat; | ||
@@ -203,2 +220,3 @@ | ||
| public void setPausedModifier(final boolean paused) { | ||
| mPaused = paused; | ||
@@ -257,10 +275,25 @@ | ||
| public void setPlayInBackground(final boolean playInBackground) { | ||
| mPlayInBackground = playInBackground; | ||
| } | ||
| @Override | ||
| public void onPrepared(MediaPlayer mp) { | ||
| mMediaPlayerValid = true; | ||
| mVideoDuration = mp.getDuration(); | ||
| WritableMap naturalSize = Arguments.createMap(); | ||
| naturalSize.putInt(EVENT_PROP_WIDTH, mp.getVideoWidth()); | ||
| naturalSize.putInt(EVENT_PROP_HEIGHT, mp.getVideoHeight()); | ||
| if (mp.getVideoWidth() > mp.getVideoHeight()) | ||
| naturalSize.putString(EVENT_PROP_ORIENTATION, "landscape"); | ||
| else | ||
| naturalSize.putString(EVENT_PROP_ORIENTATION, "portrait"); | ||
| WritableMap event = Arguments.createMap(); | ||
| event.putDouble(EVENT_PROP_DURATION, mVideoDuration / 1000.0); | ||
| event.putDouble(EVENT_PROP_CURRENT_TIME, mp.getCurrentPosition() / 1000.0); | ||
| event.putMap(EVENT_PROP_NATURALSIZE, naturalSize); | ||
| // TODO: Actually check if you can. | ||
@@ -281,2 +314,3 @@ event.putBoolean(EVENT_PROP_FAST_FORWARD, true); | ||
| public boolean onError(MediaPlayer mp, int what, int extra) { | ||
| WritableMap error = Arguments.createMap(); | ||
@@ -292,2 +326,20 @@ error.putInt(EVENT_PROP_WHAT, what); | ||
| @Override | ||
| public boolean onInfo(MediaPlayer mp, int what, int extra) { | ||
| switch (what) { | ||
| case MediaPlayer.MEDIA_INFO_BUFFERING_START: | ||
| mEventEmitter.receiveEvent(getId(), Events.EVENT_STALLED.toString(), Arguments.createMap()); | ||
| break; | ||
| case MediaPlayer.MEDIA_INFO_BUFFERING_END: | ||
| mEventEmitter.receiveEvent(getId(), Events.EVENT_RESUME.toString(), Arguments.createMap()); | ||
| break; | ||
| case MediaPlayer.MEDIA_INFO_VIDEO_RENDERING_START: | ||
| mEventEmitter.receiveEvent(getId(), Events.EVENT_READY_FOR_DISPLAY.toString(), Arguments.createMap()); | ||
| break; | ||
| default: | ||
| } | ||
| return false; | ||
| } | ||
| @Override | ||
| public void onBufferingUpdate(MediaPlayer mp, int percent) { | ||
@@ -307,2 +359,5 @@ mVideoBufferedDuration = (int) Math.round((double) (mVideoDuration * percent) / 100.0); | ||
| super.seekTo(msec); | ||
| if (isCompleted && mVideoDuration != 0 && msec < mVideoDuration) { | ||
| isCompleted = false; | ||
| } | ||
| } | ||
@@ -313,3 +368,4 @@ } | ||
| public void onCompletion(MediaPlayer mp) { | ||
| mMediaPlayerValid = false; | ||
| isCompleted = true; | ||
| mEventEmitter.receiveEvent(getId(), Events.EVENT_END.toString(), null); | ||
@@ -320,2 +376,3 @@ } | ||
| protected void onDetachedFromWindow() { | ||
| mMediaPlayerValid = false; | ||
@@ -327,5 +384,22 @@ super.onDetachedFromWindow(); | ||
| protected void onAttachedToWindow() { | ||
| super.onAttachedToWindow(); | ||
| setSrc(mSrcUriString, mSrcType, mSrcIsNetwork, mSrcIsAsset); | ||
| } | ||
| @Override | ||
| public void onHostPause() { | ||
| if (mMediaPlayer != null && !mPlayInBackground) { | ||
| mMediaPlayer.pause(); | ||
| } | ||
| } | ||
| @Override | ||
| public void onHostResume() { | ||
| } | ||
| @Override | ||
| public void onHostDestroy() { | ||
| } | ||
| } |
@@ -33,2 +33,3 @@ package com.brentvatne.react; | ||
| public static final String PROP_RATE = "rate"; | ||
| public static final String PROP_PLAY_IN_BACKGROUND = "playInBackground"; | ||
@@ -110,2 +111,7 @@ @Override | ||
| } | ||
| @ReactProp(name = PROP_PLAY_IN_BACKGROUND, defaultBoolean = false) | ||
| public void setPlayInBackground(final ReactVideoView videoView, final boolean playInBackground) { | ||
| videoView.setPlayInBackground(playInBackground); | ||
| } | ||
| } |
+1
-1
| { | ||
| "name": "react-native-video", | ||
| "version": "0.8.0", | ||
| "version": "0.9.0", | ||
| "description": "A <Video /> element for react-native", | ||
@@ -5,0 +5,0 @@ "main": "Video.js", |
+33
-3
@@ -43,2 +43,4 @@ #import "RCTConvert.h" | ||
| BOOL _playbackStalled; | ||
| BOOL _playInBackground; | ||
| BOOL _playWhenInactive; | ||
| NSString * _resizeMode; | ||
@@ -65,2 +67,4 @@ BOOL _fullscreenPlayerPresented; | ||
| _playerBufferEmpty = YES; | ||
| _playInBackground = false; | ||
| _playWhenInactive = false; | ||
@@ -73,2 +77,7 @@ [[NSNotificationCenter defaultCenter] addObserver:self | ||
| [[NSNotificationCenter defaultCenter] addObserver:self | ||
| selector:@selector(applicationDidEnterBackground:) | ||
| name:UIApplicationDidEnterBackgroundNotification | ||
| object:nil]; | ||
| [[NSNotificationCenter defaultCenter] addObserver:self | ||
| selector:@selector(applicationWillEnterForeground:) | ||
@@ -129,5 +138,13 @@ name:UIApplicationWillEnterForegroundNotification | ||
| { | ||
| if (!_paused) { | ||
| [_player pause]; | ||
| [_player setRate:0.0]; | ||
| if (_playInBackground || _playWhenInactive || _paused) return; | ||
| [_player pause]; | ||
| [_player setRate:0.0]; | ||
| } | ||
| - (void)applicationDidEnterBackground:(NSNotification *)notification | ||
| { | ||
| if (_playInBackground) { | ||
| // Needed to play sound in background. See https://developer.apple.com/library/ios/qa/qa1668/_index.html | ||
| [_playerLayer setPlayer:nil]; | ||
| } | ||
@@ -139,2 +156,5 @@ } | ||
| [self applyModifiers]; | ||
| if (_playInBackground) { | ||
| [_playerLayer setPlayer:_player]; | ||
| } | ||
| } | ||
@@ -411,2 +431,12 @@ | ||
| - (void)setPlayInBackground:(BOOL)playInBackground | ||
| { | ||
| _playInBackground = playInBackground; | ||
| } | ||
| - (void)setPlayWhenInactive:(BOOL)playWhenInactive | ||
| { | ||
| _playWhenInactive = playWhenInactive; | ||
| } | ||
| - (void)setPaused:(BOOL)paused | ||
@@ -413,0 +443,0 @@ { |
@@ -51,2 +51,4 @@ #import "RCTVideoManager.h" | ||
| RCT_EXPORT_VIEW_PROPERTY(volume, float); | ||
| RCT_EXPORT_VIEW_PROPERTY(playInBackground, BOOL); | ||
| RCT_EXPORT_VIEW_PROPERTY(playWhenInactive, BOOL); | ||
| RCT_EXPORT_VIEW_PROPERTY(rate, float); | ||
@@ -53,0 +55,0 @@ RCT_EXPORT_VIEW_PROPERTY(seek, float); |
@@ -1,9 +0,1 @@ | ||
| // | ||
| // RCTVideoPlayerViewController.m | ||
| // RCTVideo | ||
| // | ||
| // Created by Stanisław Chmiela on 31.03.2016. | ||
| // Copyright © 2016 Facebook. All rights reserved. | ||
| // | ||
| #import "RCTVideoPlayerViewController.h" | ||
@@ -10,0 +2,0 @@ |
@@ -1,9 +0,1 @@ | ||
| // | ||
| // RCTVideoPlayerViewControllerDelegate.h | ||
| // RCTVideo | ||
| // | ||
| // Created by Stanisław Chmiela on 01.04.2016. | ||
| // Copyright © 2016 Facebook. All rights reserved. | ||
| // | ||
| #import <Foundation/Foundation.h> | ||
@@ -10,0 +2,0 @@ #import "AVKit/AVKit.h" |
+30
-5
@@ -32,9 +32,10 @@ ## react-native-video | ||
| First, copy your video file to `android/app/src/main/res/raw/`, then | ||
| make the following additions to the given files: | ||
| Install [rnpm](https://github.com/rnpm/rnpm) and run `rnpm link react-native-video` | ||
| Or if you have trouble using [rnpm](https://github.com/rnpm/rnpm), make the following additions to the given files manually: | ||
| **android/settings.gradle** | ||
| ``` | ||
| include ':RCTVideo', ':app' | ||
| project(':RCTVideo').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-video/android') | ||
| include ':react-native-video' | ||
| project(':react-native-video').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-video/android') | ||
| ``` | ||
@@ -46,3 +47,3 @@ | ||
| ... | ||
| compile project(':RCTVideo') | ||
| compile project(':react-native-video') | ||
| } | ||
@@ -63,2 +64,19 @@ ``` | ||
| ### Note:In react-native >= 0.29.0 you have to edit MainApplication.java | ||
| **MainApplication.java** (react-native >= 0.29.0) | ||
| On top, where imports are: | ||
| ```java | ||
| import com.brentvatne.react.ReactVideoPackage; | ||
| ``` | ||
| Under `.addPackage(new MainReactPackage())`: | ||
| ```java | ||
| .addPackage(new ReactVideoPackage()) | ||
| ``` | ||
| ## Usage | ||
@@ -77,2 +95,4 @@ | ||
| repeat={true} // Repeat forever. | ||
| playInBackground={false} // Audio continues to play when app entering background. | ||
| playWhenInactive={false} // [iOS] Video continues to play when control or notification center are shown. | ||
| onLoadStart={this.loadStart} // Callback when video starts to load | ||
@@ -97,2 +117,6 @@ onLoad={this.setDuration} // Callback when video loads | ||
| ### Play in background on iOS | ||
| To enable audio to play in background on iOS the audio session needs to be set to `AVAudioSessionCategoryPlayback`. See [Apple documentation][3]. | ||
| ## Static Methods | ||
@@ -128,2 +152,3 @@ | ||
| [2]: https://github.com/brentvatne/react-native-video/tree/master/Examples/VideoPlayer | ||
| [3]: https://developer.apple.com/library/ios/qa/qa1668/_index.html | ||
@@ -130,0 +155,0 @@ --- |
@@ -1,8 +0,1 @@ | ||
| // | ||
| // UIView+FindUIViewController.m | ||
| // RCTVideo | ||
| // | ||
| // Created by Stanisław Chmiela on 31.03.2016. | ||
| // Copyright © 2016 Facebook. All rights reserved. | ||
| // | ||
| // Source: http://stackoverflow.com/a/3732812/1123156 | ||
@@ -9,0 +2,0 @@ |
+55
-82
@@ -1,17 +0,6 @@ | ||
| import React from 'react'; | ||
| import ReactNative from 'react-native'; | ||
| import React, {Component, PropTypes} from 'react'; | ||
| import {StyleSheet, requireNativeComponent, NativeModules, View} from 'react-native'; | ||
| import resolveAssetSource from 'react-native/Libraries/Image/resolveAssetSource'; | ||
| import VideoResizeMode from './VideoResizeMode.js'; | ||
| const { | ||
| Component, | ||
| PropTypes, | ||
| } = React; | ||
| const { | ||
| StyleSheet, | ||
| requireNativeComponent, | ||
| NativeModules, | ||
| View, | ||
| } = ReactNative; | ||
| const styles = StyleSheet.create({ | ||
@@ -25,24 +14,2 @@ base: { | ||
| constructor(props, context) { | ||
| super(props, context); | ||
| this.seek = this.seek.bind(this); | ||
| this.presentFullscreenPlayer = this.presentFullscreenPlayer.bind(this); | ||
| this.dismissFullscreenPlayer = this.dismissFullscreenPlayer.bind(this); | ||
| this._assignRoot = this._assignRoot.bind(this); | ||
| this._onLoadStart = this._onLoadStart.bind(this); | ||
| this._onLoad = this._onLoad.bind(this); | ||
| this._onError = this._onError.bind(this); | ||
| this._onProgress = this._onProgress.bind(this); | ||
| this._onSeek = this._onSeek.bind(this); | ||
| this._onEnd = this._onEnd.bind(this); | ||
| this._onFullscreenPlayerWillPresent = this._onFullscreenPlayerWillPresent.bind(this); | ||
| this._onFullscreenPlayerDidPresent = this._onFullscreenPlayerDidPresent.bind(this); | ||
| this._onFullscreenPlayerWillDismiss = this._onFullscreenPlayerWillDismiss.bind(this); | ||
| this._onFullscreenPlayerDidDismiss = this._onFullscreenPlayerDidDismiss.bind(this); | ||
| this._onReadyForDisplay = this._onReadyForDisplay.bind(this); | ||
| this._onPlaybackStalled = this._onPlaybackStalled.bind(this); | ||
| this._onPlaybackResume = this._onPlaybackResume.bind(this); | ||
| this._onPlaybackRateChange = this._onPlaybackRateChange.bind(this); | ||
| } | ||
| setNativeProps(nativeProps) { | ||
@@ -52,107 +19,105 @@ this._root.setNativeProps(nativeProps); | ||
| seek(time) { | ||
| seek = (time) => { | ||
| this.setNativeProps({ seek: time }); | ||
| } | ||
| }; | ||
| presentFullscreenPlayer() { | ||
| presentFullscreenPlayer = () => { | ||
| this.setNativeProps({ fullscreen: true }); | ||
| } | ||
| }; | ||
| dismissFullscreenPlayer() { | ||
| dismissFullscreenPlayer = () => { | ||
| this.setNativeProps({ fullscreen: false }); | ||
| } | ||
| }; | ||
| _assignRoot(component) { | ||
| _assignRoot = (component) => { | ||
| this._root = component; | ||
| } | ||
| }; | ||
| _onLoadStart(event) { | ||
| _onLoadStart = (event) => { | ||
| if (this.props.onLoadStart) { | ||
| this.props.onLoadStart(event.nativeEvent); | ||
| } | ||
| } | ||
| }; | ||
| _onLoad(event) { | ||
| _onLoad = (event) => { | ||
| if (this.props.onLoad) { | ||
| this.props.onLoad(event.nativeEvent); | ||
| } | ||
| } | ||
| }; | ||
| _onError(event) { | ||
| _onError = (event) => { | ||
| if (this.props.onError) { | ||
| this.props.onError(event.nativeEvent); | ||
| } | ||
| } | ||
| }; | ||
| _onProgress(event) { | ||
| _onProgress = (event) => { | ||
| if (this.props.onProgress) { | ||
| this.props.onProgress(event.nativeEvent); | ||
| } | ||
| } | ||
| }; | ||
| _onSeek(event) { | ||
| _onSeek = (event) => { | ||
| if (this.props.onSeek) { | ||
| this.props.onSeek(event.nativeEvent); | ||
| } | ||
| } | ||
| }; | ||
| _onEnd(event) { | ||
| _onEnd = (event) => { | ||
| if (this.props.onEnd) { | ||
| this.props.onEnd(event.nativeEvent); | ||
| } | ||
| } | ||
| }; | ||
| _onFullscreenPlayerWillPresent(event) { | ||
| _onFullscreenPlayerWillPresent = (event) => { | ||
| if (this.props.onFullscreenPlayerWillPresent) { | ||
| this.props.onFullscreenPlayerWillPresent(event.nativeEvent); | ||
| } | ||
| } | ||
| }; | ||
| _onFullscreenPlayerDidPresent(event) { | ||
| _onFullscreenPlayerDidPresent = (event) => { | ||
| if (this.props.onFullscreenPlayerDidPresent) { | ||
| this.props.onFullscreenPlayerDidPresent(event.nativeEvent); | ||
| } | ||
| } | ||
| }; | ||
| _onFullscreenPlayerWillDismiss(event) { | ||
| _onFullscreenPlayerWillDismiss = (event) => { | ||
| if (this.props.onFullscreenPlayerWillDismiss) { | ||
| this.props.onFullscreenPlayerWillDismiss(event.nativeEvent); | ||
| } | ||
| } | ||
| }; | ||
| _onFullscreenPlayerDidDismiss(event) { | ||
| _onFullscreenPlayerDidDismiss = (event) => { | ||
| if (this.props.onFullscreenPlayerDidDismiss) { | ||
| this.props.onFullscreenPlayerDidDismiss(event.nativeEvent); | ||
| } | ||
| } | ||
| }; | ||
| _onReadyForDisplay(event) { | ||
| _onReadyForDisplay = (event) => { | ||
| if (this.props.onReadyForDisplay) { | ||
| this.props.onReadyForDisplay(event.nativeEvent); | ||
| } | ||
| } | ||
| }; | ||
| _onPlaybackStalled(event) { | ||
| _onPlaybackStalled = (event) => { | ||
| if (this.props.onPlaybackStalled) { | ||
| this.props.onPlaybackStalled(event.nativeEvent); | ||
| } | ||
| } | ||
| }; | ||
| _onPlaybackResume(event) { | ||
| _onPlaybackResume = (event) => { | ||
| if (this.props.onPlaybackResume) { | ||
| this.props.onPlaybackResume(event.nativeEvent); | ||
| } | ||
| } | ||
| }; | ||
| _onPlaybackRateChange(event) { | ||
| _onPlaybackRateChange = (event) => { | ||
| if (this.props.onPlaybackRateChange) { | ||
| this.props.onPlaybackRateChange(event.nativeEvent); | ||
| } | ||
| } | ||
| }; | ||
| render() { | ||
| const { | ||
| source, | ||
| resizeMode, | ||
| } = this.props; | ||
| const resizeMode = this.props.resizeMode; | ||
| const source = resolveAssetSource(this.props.source) || {}; | ||
@@ -220,3 +185,9 @@ let uri = source.uri; | ||
| /* Wrapper component */ | ||
| source: PropTypes.object, | ||
| source: PropTypes.oneOfType([ | ||
| PropTypes.shape({ | ||
| uri: PropTypes.string | ||
| }), | ||
| // Opaque type returned by require('./video.mp4') | ||
| PropTypes.number | ||
| ]), | ||
| resizeMode: PropTypes.string, | ||
@@ -228,2 +199,4 @@ repeat: PropTypes.bool, | ||
| rate: PropTypes.number, | ||
| playInBackground: PropTypes.bool, | ||
| playWhenInactive: PropTypes.bool, | ||
| controls: PropTypes.bool, | ||
@@ -247,7 +220,7 @@ currentTime: PropTypes.number, | ||
| /* Required by react-native */ | ||
| scaleX: React.PropTypes.number, | ||
| scaleY: React.PropTypes.number, | ||
| translateX: React.PropTypes.number, | ||
| translateY: React.PropTypes.number, | ||
| rotation: React.PropTypes.number, | ||
| scaleX: PropTypes.number, | ||
| scaleY: PropTypes.number, | ||
| translateX: PropTypes.number, | ||
| translateY: PropTypes.number, | ||
| rotation: PropTypes.number, | ||
| ...View.propTypes, | ||
@@ -254,0 +227,0 @@ }; |
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
Sorry, the diff of this file is not supported yet
| <?xml version="1.0" encoding="UTF-8"?> | ||
| <!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd"> | ||
| <plist version="1.0"> | ||
| <dict> | ||
| <key>SchemeUserState</key> | ||
| <dict> | ||
| <key>RCTVideo.xcscheme</key> | ||
| <dict> | ||
| <key>orderHint</key> | ||
| <integer>0</integer> | ||
| </dict> | ||
| </dict> | ||
| <key>SuppressBuildableAutocreation</key> | ||
| <dict> | ||
| <key>58B511DA1A9E6C8500147676</key> | ||
| <dict> | ||
| <key>primary</key> | ||
| <true/> | ||
| </dict> | ||
| </dict> | ||
| </dict> | ||
| </plist> |
639
5.79%152
19.69%82690
-10.07%23
-14.81%