diff --git a/RCTVideo.m b/RCTVideo.m index 4221fe76..9f52a3d0 100644 --- a/RCTVideo.m +++ b/RCTVideo.m @@ -40,6 +40,8 @@ static NSString *const playbackRate = @"rate"; BOOL _paused; BOOL _repeat; BOOL _playbackStalled; + BOOL _playInBackground; + BOOL _playWhenInactive; NSString * _resizeMode; BOOL _fullscreenPlayerPresented; UIViewController * _presentingViewController; @@ -61,12 +63,19 @@ static NSString *const playbackRate = @"rate"; _progressUpdateInterval = 250; _controls = NO; _playerBufferEmpty = YES; + _playInBackground = false; + _playWhenInactive = false; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillResignActive:) name:UIApplicationWillResignActiveNotification object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self + selector:@selector(applicationDidEnterBackground:) + name:UIApplicationDidEnterBackgroundNotification + object:nil]; + [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(applicationWillEnterForeground:) name:UIApplicationWillEnterForegroundNotification @@ -123,15 +132,26 @@ static NSString *const playbackRate = @"rate"; - (void)applicationWillResignActive:(NSNotification *)notification { - 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]; } } - (void)applicationWillEnterForeground:(NSNotification *)notification { [self applyModifiers]; + if (_playInBackground) { + [_playerLayer setPlayer:_player]; + } } #pragma mark - Progress @@ -403,6 +423,16 @@ static NSString *const playbackRate = @"rate"; _resizeMode = mode; } +- (void)setPlayInBackground:(BOOL)playInBackground +{ + _playInBackground = playInBackground; +} + +- (void)setPlayWhenInactive:(BOOL)playWhenInactive +{ + _playWhenInactive = playWhenInactive; +} + - (void)setPaused:(BOOL)paused { if (paused) { diff --git a/RCTVideoManager.m b/RCTVideoManager.m index 1eb56369..3e11d412 100644 --- a/RCTVideoManager.m +++ b/RCTVideoManager.m @@ -48,6 +48,8 @@ RCT_EXPORT_VIEW_PROPERTY(paused, BOOL); 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); 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 --- diff --git a/Video.js b/Video.js index 32e2bb07..185c4d0d 100644 --- a/Video.js +++ b/Video.js @@ -222,6 +222,8 @@ Video.propTypes = { muted: PropTypes.bool, volume: PropTypes.number, rate: PropTypes.number, + playInBackground: PropTypes.bool, + playWhenInactive: PropTypes.bool, controls: PropTypes.bool, currentTime: PropTypes.number, onLoadStart: PropTypes.func, diff --git a/android/src/main/java/com/brentvatne/react/ReactVideoView.java b/android/src/main/java/com/brentvatne/react/ReactVideoView.java index cafd8c5b..7a01cb2a 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"), @@ -73,6 +74,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; @@ -83,6 +85,7 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP mThemedReactContext = themedReactContext; mEventEmitter = themedReactContext.getJSModule(RCTEventEmitter.class); + themedReactContext.addLifecycleEventListener(this); initializeMediaPlayerIfNeeded(); setSurfaceTextureListener(this); @@ -248,6 +251,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; @@ -324,4 +331,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); + } }