From c45a79a346d055a03691a3f9de640c2cfd30bde7 Mon Sep 17 00:00:00 2001 From: Tobias Hasselrot Date: Fri, 30 Oct 2015 10:34:54 +0100 Subject: [PATCH 01/11] Added playInBackground property to allow continous live streaming when opening notification center or control center --- RCTVideo.m | 9 ++++++++- RCTVideoManager.m | 1 + Video.ios.js | 1 + 3 files changed, 10 insertions(+), 1 deletion(-) diff --git a/RCTVideo.m b/RCTVideo.m index 37e09e8b..11e74428 100644 --- a/RCTVideo.m +++ b/RCTVideo.m @@ -34,6 +34,7 @@ static NSString *const playbackLikelyToKeepUpKeyPath = @"playbackLikelyToKeepUp" BOOL _muted; BOOL _paused; BOOL _repeat; + BOOL _playInBackground; NSString * _resizeMode; } @@ -48,6 +49,7 @@ static NSString *const playbackLikelyToKeepUpKeyPath = @"playbackLikelyToKeepUp" _pendingSeek = false; _pendingSeekTime = 0.0f; _lastSeekTime = 0.0f; + _playInBackground = false; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive:) @@ -72,7 +74,7 @@ static NSString *const playbackLikelyToKeepUpKeyPath = @"playbackLikelyToKeepUp" - (void)applicationWillResignActive:(NSNotification *)notification { - if (!_paused) { + if (!_paused && !_playInBackground) { [self stopProgressTimer]; [_player setRate:0.0]; } @@ -285,6 +287,11 @@ static NSString *const playbackLikelyToKeepUpKeyPath = @"playbackLikelyToKeepUp" _playerLayer.videoGravity = mode; } +- (void)setPlayInBackground:(BOOL)playInBackground +{ + _playInBackground = playInBackground; +} + - (void)setPaused:(BOOL)paused { if (paused) { diff --git a/RCTVideoManager.m b/RCTVideoManager.m index 3f931999..02d3ea6d 100644 --- a/RCTVideoManager.m +++ b/RCTVideoManager.m @@ -39,6 +39,7 @@ RCT_EXPORT_VIEW_PROPERTY(repeat, BOOL); RCT_EXPORT_VIEW_PROPERTY(paused, BOOL); RCT_EXPORT_VIEW_PROPERTY(muted, BOOL); RCT_EXPORT_VIEW_PROPERTY(volume, float); +RCT_EXPORT_VIEW_PROPERTY(playInBackground, BOOL); RCT_EXPORT_VIEW_PROPERTY(rate, float); RCT_EXPORT_VIEW_PROPERTY(seek, float); diff --git a/Video.ios.js b/Video.ios.js index 4e716931..d9f16633 100644 --- a/Video.ios.js +++ b/Video.ios.js @@ -20,6 +20,7 @@ var Video = React.createClass({ muted: PropTypes.bool, volume: PropTypes.number, rate: PropTypes.number, + playInBackground: PropTypes.bool, onLoadStart: PropTypes.func, onLoad: PropTypes.func, onError: PropTypes.func, From 07ceef9152f06a400f72453c3bea4eb6f41a6773 Mon Sep 17 00:00:00 2001 From: Tobias Hasselrot Date: Wed, 16 Dec 2015 13:53:04 +0100 Subject: [PATCH 02/11] Added back playInBackground property in js --- Video.js | 1 + 1 file changed, 1 insertion(+) diff --git a/Video.js b/Video.js index 77323549..cd8e3ee4 100644 --- a/Video.js +++ b/Video.js @@ -127,6 +127,7 @@ Video.propTypes = { muted: PropTypes.bool, volume: PropTypes.number, rate: PropTypes.number, + playInBackground: PropTypes.bool, onLoadStart: PropTypes.func, onLoad: PropTypes.func, onError: PropTypes.func, From 848e6632a31304d0464e40877e3527122b9a1e16 Mon Sep 17 00:00:00 2001 From: Tobias Hasselrot Date: Thu, 28 Apr 2016 15:06:22 +0200 Subject: [PATCH 03/11] Sound now plays when app is in background. --- RCTVideo.m | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/RCTVideo.m b/RCTVideo.m index 11ae0798..d6bf41b4 100644 --- a/RCTVideo.m +++ b/RCTVideo.m @@ -119,7 +119,10 @@ static NSString *const playbackBufferEmptyKeyPath = @"playbackBufferEmpty"; - (void)applicationWillResignActive:(NSNotification *)notification { - if (!_paused && !_playInBackground) { + if (_playInBackground) { + // Needed to play sound in background. See https://developer.apple.com/library/ios/qa/qa1668/_index.html + [_playerLayer setPlayer:nil]; + } else if (!_paused) { [_player pause]; [_player setRate:0.0]; } @@ -128,6 +131,9 @@ static NSString *const playbackBufferEmptyKeyPath = @"playbackBufferEmpty"; - (void)applicationWillEnterForeground:(NSNotification *)notification { [self applyModifiers]; + if (_playInBackground) { + [_playerLayer setPlayer:_player]; + } } #pragma mark - Progress From 39443ae37187afe4e5cc52f3523458d0e487fd0a Mon Sep 17 00:00:00 2001 From: Tobias Hasselrot Date: Fri, 29 Apr 2016 09:46:28 +0200 Subject: [PATCH 04/11] Fixed background playback behind notification center. --- RCTVideo.m | 18 ++++++++++++++---- 1 file changed, 14 insertions(+), 4 deletions(-) diff --git a/RCTVideo.m b/RCTVideo.m index d6bf41b4..28b25bb9 100644 --- a/RCTVideo.m +++ b/RCTVideo.m @@ -63,6 +63,11 @@ static NSString *const playbackBufferEmptyKeyPath = @"playbackBufferEmpty"; name:UIApplicationWillResignActiveNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(applicationDidEnterBackground:) + name:UIApplicationDidEnterBackgroundNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification @@ -119,15 +124,20 @@ static NSString *const playbackBufferEmptyKeyPath = @"playbackBufferEmpty"; - (void)applicationWillResignActive:(NSNotification *)notification { - if (_playInBackground) { - // Needed to play sound in background. See https://developer.apple.com/library/ios/qa/qa1668/_index.html - [_playerLayer setPlayer:nil]; - } else if (!_paused) { + if (!_paused && !_playInBackground) { [_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]; + } +} + - (void)applicationWillEnterForeground:(NSNotification *)notification { [self applyModifiers]; From eb313378374db00e2940881ee808f343a734145c Mon Sep 17 00:00:00 2001 From: tobias Date: Fri, 29 Apr 2016 13:55:34 +0200 Subject: [PATCH 05/11] Added play when inactive to support playing while showing notification center. --- RCTVideo.m | 15 +++++++++++---- Video.ios.js | 1 + Video.js | 1 + 3 files changed, 13 insertions(+), 4 deletions(-) diff --git a/RCTVideo.m b/RCTVideo.m index 28b25bb9..6664532b 100644 --- a/RCTVideo.m +++ b/RCTVideo.m @@ -37,6 +37,7 @@ static NSString *const playbackBufferEmptyKeyPath = @"playbackBufferEmpty"; BOOL _paused; BOOL _repeat; BOOL _playInBackground; + BOOL _playWhenInactive; NSString * _resizeMode; BOOL _fullscreenPlayerPresented; UIViewController * _presentingViewController; @@ -57,6 +58,7 @@ static NSString *const playbackBufferEmptyKeyPath = @"playbackBufferEmpty"; _controls = NO; _playerBufferEmpty = YES; _playInBackground = false; + _playWhenInactive = false; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive:) @@ -124,10 +126,10 @@ static NSString *const playbackBufferEmptyKeyPath = @"playbackBufferEmpty"; - (void)applicationWillResignActive:(NSNotification *)notification { - if (!_paused && !_playInBackground) { - [_player pause]; - [_player setRate:0.0]; - } + if (_playInBackground || _playWhenInactive || _paused) return; + + [_player pause]; + [_player setRate:0.0]; } - (void)applicationDidEnterBackground:(NSNotification *)notification @@ -371,6 +373,11 @@ static NSString *const playbackBufferEmptyKeyPath = @"playbackBufferEmpty"; _playInBackground = playInBackground; } +- (void)setPlayWhenInactive:(BOOL)playWhenInactive +{ + _playWhenInactive = playWhenInactive; +} + - (void)setPaused:(BOOL)paused { if (paused) { diff --git a/Video.ios.js b/Video.ios.js index d9f16633..f75fbc3d 100644 --- a/Video.ios.js +++ b/Video.ios.js @@ -21,6 +21,7 @@ var Video = React.createClass({ volume: PropTypes.number, rate: PropTypes.number, playInBackground: PropTypes.bool, + playWhenInactive: PropTypes.bool, onLoadStart: PropTypes.func, onLoad: PropTypes.func, onError: PropTypes.func, diff --git a/Video.js b/Video.js index 466c3223..1447eee4 100644 --- a/Video.js +++ b/Video.js @@ -187,6 +187,7 @@ Video.propTypes = { volume: PropTypes.number, rate: PropTypes.number, playInBackground: PropTypes.bool, + playWhenInactive: PropTypes.bool, controls: PropTypes.bool, currentTime: PropTypes.number, onLoadStart: PropTypes.func, From 8844cc031692c8f8b5f008adb59deac69de97b5a Mon Sep 17 00:00:00 2001 From: Tobias Hasselrot Date: Fri, 29 Apr 2016 14:16:21 +0200 Subject: [PATCH 06/11] Added missing property for playWhenInactive. --- RCTVideoManager.m | 1 + 1 file changed, 1 insertion(+) diff --git a/RCTVideoManager.m b/RCTVideoManager.m index fcc1451d..3f55b599 100644 --- a/RCTVideoManager.m +++ b/RCTVideoManager.m @@ -45,6 +45,7 @@ RCT_EXPORT_VIEW_PROPERTY(muted, BOOL); RCT_EXPORT_VIEW_PROPERTY(controls, BOOL); RCT_EXPORT_VIEW_PROPERTY(volume, float); RCT_EXPORT_VIEW_PROPERTY(playInBackground, BOOL); +RCT_EXPORT_VIEW_PROPERTY(playWhenInactive, BOOL); RCT_EXPORT_VIEW_PROPERTY(rate, float); RCT_EXPORT_VIEW_PROPERTY(seek, float); RCT_EXPORT_VIEW_PROPERTY(currentTime, float); From 2cba8c8eeec1c2f04990210a33f9e26da452cdaf Mon Sep 17 00:00:00 2001 From: tobias Date: Wed, 1 Jun 2016 17:05:42 +0200 Subject: [PATCH 07/11] Added play in background for Android --- .../com/brentvatne/react/ReactVideoView.java | 24 ++++++++++++++++++- .../react/ReactVideoViewManager.java | 6 +++++ 2 files changed, 29 insertions(+), 1 deletion(-) diff --git a/android/src/main/java/com/brentvatne/react/ReactVideoView.java b/android/src/main/java/com/brentvatne/react/ReactVideoView.java index 3e44bfbe..d47806bb 100644 --- a/android/src/main/java/com/brentvatne/react/ReactVideoView.java +++ b/android/src/main/java/com/brentvatne/react/ReactVideoView.java @@ -9,13 +9,14 @@ import java.util.Map; import java.util.HashMap; import com.facebook.react.bridge.Arguments; import com.facebook.react.bridge.WritableMap; +import com.facebook.react.bridge.LifecycleEventListener; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.events.RCTEventEmitter; import com.yqritc.scalablevideoview.ScalableType; import com.yqritc.scalablevideoview.ScalableVideoView; public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnPreparedListener, MediaPlayer - .OnErrorListener, MediaPlayer.OnBufferingUpdateListener, MediaPlayer.OnCompletionListener { + .OnErrorListener, MediaPlayer.OnBufferingUpdateListener, MediaPlayer.OnCompletionListener, LifecycleEventListener { public enum Events { EVENT_LOAD_START("onVideoLoadStart"), @@ -69,6 +70,7 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP private boolean mMuted = false; private float mVolume = 1.0f; private float mRate = 1.0f; + private boolean mPlayInBackground = false; private boolean mMediaPlayerValid = false; // True if mMediaPlayer is in prepared, started, or paused state. private int mVideoDuration = 0; @@ -79,6 +81,7 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP mThemedReactContext = themedReactContext; mEventEmitter = themedReactContext.getJSModule(RCTEventEmitter.class); + themedReactContext.addLifecycleEventListener(this); initializeMediaPlayerIfNeeded(); setSurfaceTextureListener(this); @@ -244,6 +247,10 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP // setRateModifier(mRate); } + public void setPlayInBackground(final boolean playInBackground) { + mPlayInBackground = playInBackground; + } + @Override public void onPrepared(MediaPlayer mp) { mMediaPlayerValid = true; @@ -311,4 +318,19 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP 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() { + } } diff --git a/android/src/main/java/com/brentvatne/react/ReactVideoViewManager.java b/android/src/main/java/com/brentvatne/react/ReactVideoViewManager.java index 6439f757..5585adf0 100644 --- a/android/src/main/java/com/brentvatne/react/ReactVideoViewManager.java +++ b/android/src/main/java/com/brentvatne/react/ReactVideoViewManager.java @@ -30,6 +30,7 @@ public class ReactVideoViewManager extends SimpleViewManager { public static final String PROP_VOLUME = "volume"; public static final String PROP_SEEK = "seek"; public static final String PROP_RATE = "rate"; + public static final String PROP_PLAY_IN_BACKGROUND = "playInBackground"; @Override public String getName() { @@ -106,4 +107,9 @@ public class ReactVideoViewManager extends SimpleViewManager { public void setRate(final ReactVideoView videoView, final float rate) { videoView.setRateModifier(rate); } + + @ReactProp(name = PROP_PLAY_IN_BACKGROUND, defaultBoolean = false) + public void setPlayInBackground(final ReactVideoView videoView, final boolean playInBackground) { + videoView.setPlayInBackground(playInBackground); + } } From ace2207eaf7d6160160272611e2476bbc2244ff7 Mon Sep 17 00:00:00 2001 From: tobias Date: Thu, 2 Jun 2016 07:45:18 +0200 Subject: [PATCH 08/11] Deleted ios specific file that was removed in master --- Video.ios.js | 111 --------------------------------------------------- 1 file changed, 111 deletions(-) delete mode 100644 Video.ios.js diff --git a/Video.ios.js b/Video.ios.js deleted file mode 100644 index f75fbc3d..00000000 --- a/Video.ios.js +++ /dev/null @@ -1,111 +0,0 @@ -var React = require('react-native'); -var { StyleSheet, requireNativeComponent, PropTypes, NativeModules, } = React; - -var VideoResizeMode = require('./VideoResizeMode'); -var { extend } = require('lodash'); - -var VIDEO_REF = 'video'; - -var Video = React.createClass({ - propTypes: { - /* Native only */ - src: PropTypes.object, - seek: PropTypes.number, - - /* Wrapper component */ - source: PropTypes.object, - resizeMode: PropTypes.string, - repeat: PropTypes.bool, - paused: PropTypes.bool, - muted: PropTypes.bool, - volume: PropTypes.number, - rate: PropTypes.number, - playInBackground: PropTypes.bool, - playWhenInactive: PropTypes.bool, - onLoadStart: PropTypes.func, - onLoad: PropTypes.func, - onError: PropTypes.func, - onProgress: PropTypes.func, - onEnd: PropTypes.func, - }, - - setNativeProps(props) { - this.refs[VIDEO_REF].setNativeProps(props); - }, - - _onLoadStart(event) { - this.props.onLoadStart && this.props.onLoadStart(event.nativeEvent); - }, - - _onLoad(event) { - this.props.onLoad && this.props.onLoad(event.nativeEvent); - }, - - _onError(event) { - this.props.onError && this.props.onError(event.nativeEvent); - }, - - _onProgress(event) { - this.props.onProgress && this.props.onProgress(event.nativeEvent); - }, - - _onSeek(event) { - this.props.onSeek && this.props.onSeek(event.nativeEvent); - }, - - seek(time) { - this.setNativeProps({seek: parseFloat(time)}); - }, - - _onEnd(event) { - this.props.onEnd && this.props.onEnd(event.nativeEvent); - }, - - render() { - var style = [styles.base, this.props.style]; - var source = this.props.source; - var uri = source.uri; - if (uri && uri.match(/^\//)) { - uri = 'file://' + uri; - } - var isNetwork = !!(uri && uri.match(/^https?:/)); - var isAsset = !!(uri && uri.match(/^(assets-library|file):/)); - - var resizeMode; - if (this.props.resizeMode === VideoResizeMode.stretch) { - resizeMode = NativeModules.VideoManager.ScaleToFill; - } else if (this.props.resizeMode === VideoResizeMode.contain) { - resizeMode = NativeModules.VideoManager.ScaleAspectFit; - } else if (this.props.resizeMode === VideoResizeMode.cover) { - resizeMode = NativeModules.VideoManager.ScaleAspectFill; - } else { - resizeMode = NativeModules.VideoManager.ScaleNone; - } - - var nativeProps = extend({}, this.props, { - style, - resizeMode: resizeMode, - src: { - uri: uri, - isNetwork, - isAsset, - type: source.type || 'mp4' - }, - onVideoLoad: this._onLoad, - onVideoProgress: this._onProgress, - onVideoEnd: this._onEnd, - }); - - return ; - }, -}); - -var RCTVideo = requireNativeComponent('RCTVideo', Video); - -var styles = StyleSheet.create({ - base: { - overflow: 'hidden', - }, -}); - -module.exports = Video; From f98ce00a304feb6dcfffe07db58c390ebfbdc580 Mon Sep 17 00:00:00 2001 From: tobias Date: Thu, 2 Jun 2016 07:58:38 +0200 Subject: [PATCH 09/11] Code style changes --- RCTVideo.m | 10 +++++----- 1 file changed, 5 insertions(+), 5 deletions(-) diff --git a/RCTVideo.m b/RCTVideo.m index eba681ca..44879f3f 100644 --- a/RCTVideo.m +++ b/RCTVideo.m @@ -63,7 +63,7 @@ static NSString *const playbackRate = @"rate"; _progressUpdateInterval = 250; _controls = NO; _playerBufferEmpty = YES; - _playInBackground = false; + _playInBackground = false; _playWhenInactive = false; [[NSNotificationCenter defaultCenter] addObserver:self @@ -140,10 +140,10 @@ static NSString *const playbackRate = @"rate"; - (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]; - } + if (_playInBackground) { + // Needed to play sound in background. See https://developer.apple.com/library/ios/qa/qa1668/_index.html + [_playerLayer setPlayer:nil]; + } } - (void)applicationWillEnterForeground:(NSNotification *)notification From b58c7e0f978e6f8804542115d0ce591ea9301ce2 Mon Sep 17 00:00:00 2001 From: Tobias Hasselrot Date: Thu, 2 Jun 2016 08:29:16 +0200 Subject: [PATCH 10/11] Added documentation of playInBackground and playWhenInactive props. --- README.md | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/README.md b/README.md index 48cfbb3e..9fe041e5 100644 --- a/README.md +++ b/README.md @@ -71,6 +71,8 @@ Under `.addPackage(new MainReactPackage())`: paused={false} // Pauses playback entirely. resizeMode="cover" // Fill the whole screen at aspect ratio. 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 onLoad={this.setDuration} // Callback when video loads onProgress={this.setTime} // Callback every ~250ms with currentTime @@ -90,6 +92,10 @@ var styles = StyleSheet.create({ }); ``` +### 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 `seek(seconds)` @@ -120,6 +126,7 @@ Seeks the video to the specified time (in seconds). Access using a ref to the co [1]: https://github.com/brentvatne/react-native-login/blob/56c47a5d1e23781e86e19b27e10427fd6391f666/App/Screens/UserInfoScreen.js#L32-L35 [2]: https://github.com/brentvatne/react-native-video/tree/master/Examples/VideoPlayer +[3]: https://developer.apple.com/library/ios/qa/qa1668/_index.html --- From 1aded320196494635bd82825134630c2b2f10d57 Mon Sep 17 00:00:00 2001 From: tobias Date: Thu, 2 Jun 2016 08:33:18 +0200 Subject: [PATCH 11/11] Remove spaces --- RCTVideo.m | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/RCTVideo.m b/RCTVideo.m index 44879f3f..9f52a3d0 100644 --- a/RCTVideo.m +++ b/RCTVideo.m @@ -425,12 +425,12 @@ static NSString *const playbackRate = @"rate"; - (void)setPlayInBackground:(BOOL)playInBackground { - _playInBackground = playInBackground; + _playInBackground = playInBackground; } - (void)setPlayWhenInactive:(BOOL)playWhenInactive { - _playWhenInactive = playWhenInactive; + _playWhenInactive = playWhenInactive; } - (void)setPaused:(BOOL)paused