From e4da5c97734862e6a3585d06088b7aed336166e8 Mon Sep 17 00:00:00 2001 From: benlime Date: Sun, 30 Apr 2017 13:43:01 +0200 Subject: [PATCH 01/98] adds resizeMode to poster image --- Video.js | 3 ++- 1 file changed, 2 insertions(+), 1 deletion(-) diff --git a/Video.js b/Video.js index 4d69b333..ed55b7fc 100644 --- a/Video.js +++ b/Video.js @@ -217,7 +217,6 @@ export default class Video extends Component { top: 0, right: 0, bottom: 0, - resizeMode: 'contain', }; return ( @@ -229,6 +228,7 @@ export default class Video extends Component { ); @@ -271,6 +271,7 @@ Video.propTypes = { ]), resizeMode: PropTypes.string, poster: PropTypes.string, + posterResizeMode: Image.propTypes.resizeMode, repeat: PropTypes.bool, paused: PropTypes.bool, muted: PropTypes.bool, From 451f8d0919e94e44d7b294b187ac527533d06b82 Mon Sep 17 00:00:00 2001 From: Alex Fox Date: Thu, 7 Sep 2017 13:16:44 +0100 Subject: [PATCH 02/98] Fixed rate not being respected after seeking Referenced in issue => https://github.com/react-native-community/react-native-video/issues/763 --- ios/RCTVideo.m | 4 +++- 1 file changed, 3 insertions(+), 1 deletion(-) diff --git a/ios/RCTVideo.m b/ios/RCTVideo.m index 9962c2bb..097e16c8 100644 --- a/ios/RCTVideo.m +++ b/ios/RCTVideo.m @@ -561,7 +561,9 @@ static NSString *const timedMetadata = @"timedMetadata"; if (CMTimeCompare(current, cmSeekTime) != 0) { if (!wasPaused) [_player pause]; [_player seekToTime:cmSeekTime toleranceBefore:tolerance toleranceAfter:tolerance completionHandler:^(BOOL finished) { - if (!wasPaused) [_player play]; + if (!wasPaused) { + [self setPaused:false]; + } if(self.onVideoSeek) { self.onVideoSeek(@{@"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(item.currentTime)], @"seekTime": [NSNumber numberWithFloat:seekTime], From 1bdd8720fbc08b2a04edf2b59e8e03063ed41c48 Mon Sep 17 00:00:00 2001 From: Anne Glines Date: Mon, 11 Sep 2017 22:30:17 -0700 Subject: [PATCH 03/98] Reseting isCompleted flag on source change --- android/src/main/java/com/brentvatne/react/ReactVideoView.java | 1 + 1 file changed, 1 insertion(+) diff --git a/android/src/main/java/com/brentvatne/react/ReactVideoView.java b/android/src/main/java/com/brentvatne/react/ReactVideoView.java index 94a25a0e..f426c30e 100644 --- a/android/src/main/java/com/brentvatne/react/ReactVideoView.java +++ b/android/src/main/java/com/brentvatne/react/ReactVideoView.java @@ -297,6 +297,7 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP WritableMap event = Arguments.createMap(); event.putMap(ReactVideoViewManager.PROP_SRC, src); mEventEmitter.receiveEvent(getId(), Events.EVENT_LOAD_START.toString(), event); + isCompleted = false; try { prepareAsync(this); From 3e0f084c621e7af0a60a13c701cb94cd3b211041 Mon Sep 17 00:00:00 2001 From: Louis Capitanchik Date: Wed, 27 Sep 2017 16:13:29 +0100 Subject: [PATCH 04/98] Implement 'rate' prop for android devices - Version locked to 6.0+ because that is the version that introduced setPlaybackParams - Ignores rate prop as before on android versions lower than 6.0 --- .../main/java/com/brentvatne/react/ReactVideoView.java | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/android/src/main/java/com/brentvatne/react/ReactVideoView.java b/android/src/main/java/com/brentvatne/react/ReactVideoView.java index 94a25a0e..099bec33 100644 --- a/android/src/main/java/com/brentvatne/react/ReactVideoView.java +++ b/android/src/main/java/com/brentvatne/react/ReactVideoView.java @@ -5,6 +5,7 @@ import android.content.res.AssetFileDescriptor; import android.graphics.Matrix; import android.media.MediaPlayer; import android.net.Uri; +import android.os.Build; import android.os.Handler; import android.util.Log; import android.view.MotionEvent; @@ -377,8 +378,11 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP mRate = rate; if (mMediaPlayerValid) { - // TODO: Implement this. - Log.e(ReactVideoViewManager.REACT_CLASS, "Setting playback rate is not yet supported on Android"); + if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { + mMediaPlayer.setPlaybackParams(mMediaPlayer.getPlaybackParams().setSpeed(rate)); + } else { + Log.e(ReactVideoViewManager.REACT_CLASS, "Setting playback rate is not yet supported on Android versions below 6.0"); + } } } @@ -388,7 +392,7 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP setPausedModifier(mPaused); setMutedModifier(mMuted); setProgressUpdateInterval(mProgressUpdateInterval); -// setRateModifier(mRate); + setRateModifier(mRate); } public void setPlayInBackground(final boolean playInBackground) { From 435669a944430fa51b8bbd640c6bf66c7756a1ce Mon Sep 17 00:00:00 2001 From: Dan Hodos Date: Thu, 28 Sep 2017 21:37:26 -0400 Subject: [PATCH 05/98] Extract method to add observer for progress update --- ios/RCTVideo.m | 18 +++++++++++------- 1 file changed, 11 insertions(+), 7 deletions(-) diff --git a/ios/RCTVideo.m b/ios/RCTVideo.m index 9962c2bb..88f3a640 100644 --- a/ios/RCTVideo.m +++ b/ios/RCTVideo.m @@ -124,6 +124,16 @@ static NSString *const timedMetadata = @"timedMetadata"; return (kCMTimeRangeZero); } +-(void)addPlayerTimeObserver +{ + const Float64 progressUpdateIntervalMS = _progressUpdateInterval / 1000; + // @see endScrubbing in AVPlayerDemoPlaybackViewController.m of https://developer.apple.com/library/ios/samplecode/AVPlayerDemo/Introduction/Intro.html + __weak RCTVideo *weakSelf = self; + _timeObserver = [_player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(progressUpdateIntervalMS, NSEC_PER_SEC) + queue:NULL + usingBlock:^(CMTime time) { [weakSelf sendProgressUpdate]; } + ]; +} /* Cancels the previously registered time observer. */ -(void)removePlayerTimeObserver @@ -283,13 +293,7 @@ static NSString *const timedMetadata = @"timedMetadata"; [_player addObserver:self forKeyPath:playbackRate options:0 context:nil]; _playbackRateObserverRegistered = YES; - const Float64 progressUpdateIntervalMS = _progressUpdateInterval / 1000; - // @see endScrubbing in AVPlayerDemoPlaybackViewController.m of https://developer.apple.com/library/ios/samplecode/AVPlayerDemo/Introduction/Intro.html - __weak RCTVideo *weakSelf = self; - _timeObserver = [_player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(progressUpdateIntervalMS, NSEC_PER_SEC) - queue:NULL - usingBlock:^(CMTime time) { [weakSelf sendProgressUpdate]; } - ]; + [self addPlayerTimeObserver]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ //Perform on next run loop, otherwise onVideoLoadStart is nil From 10cba5ad5c545a1ea6a6e7d53020f834466aa72e Mon Sep 17 00:00:00 2001 From: Dan Hodos Date: Thu, 28 Sep 2017 21:37:52 -0400 Subject: [PATCH 06/98] Reset progress observer on update interval changes --- ios/RCTVideo.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ios/RCTVideo.m b/ios/RCTVideo.m index 88f3a640..68e52e31 100644 --- a/ios/RCTVideo.m +++ b/ios/RCTVideo.m @@ -725,6 +725,11 @@ static NSString *const timedMetadata = @"timedMetadata"; - (void)setProgressUpdateInterval:(float)progressUpdateInterval { _progressUpdateInterval = progressUpdateInterval; + + if (_timeObserver) { + [self removePlayerTimeObserver]; + [self addPlayerTimeObserver]; + } } - (void)removePlayerLayer From 2ae99bd484486f468932db7830b7db8b69ccc507 Mon Sep 17 00:00:00 2001 From: Jan Lievens Date: Tue, 24 Oct 2017 09:47:43 +0200 Subject: [PATCH 07/98] remove observers before adding thus preventing multiple observers for the same notification --- ios/RCTVideo.m | 7 +++++++ 1 file changed, 7 insertions(+) diff --git a/ios/RCTVideo.m b/ios/RCTVideo.m index 9962c2bb..ab0927c6 100644 --- a/ios/RCTVideo.m +++ b/ios/RCTVideo.m @@ -454,10 +454,17 @@ static NSString *const timedMetadata = @"timedMetadata"; - (void)attachListeners { // listen for end of file + [[NSNotificationCenter defaultCenter] removeObserver:self + name:AVPlayerItemDidPlayToEndTimeNotification + object:[_player currentItem]]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playerItemDidReachEnd:) name:AVPlayerItemDidPlayToEndTimeNotification object:[_player currentItem]]; + + [[NSNotificationCenter defaultCenter] removeObserver:self + name:AVPlayerItemPlaybackStalledNotification + object:nil]; [[NSNotificationCenter defaultCenter] addObserver:self selector:@selector(playbackStalled:) name:AVPlayerItemPlaybackStalledNotification From f7b7f2666a206bc92faa94028b26d65b45a7300c Mon Sep 17 00:00:00 2001 From: Marc-Olivier Duval Date: Tue, 24 Oct 2017 21:49:46 -0400 Subject: [PATCH 08/98] Apply fix in playInBackground props --- .../java/com/brentvatne/exoplayer/ReactExoplayerView.java | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index 52bf24e6..c12b2a48 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -167,6 +167,9 @@ class ReactExoplayerView extends FrameLayout implements @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); + if (playInBackground) { + return; + } stopPlayback(); } @@ -174,9 +177,6 @@ class ReactExoplayerView extends FrameLayout implements @Override public void onHostResume() { - if (playInBackground) { - return; - } setPlayWhenReady(!isPaused); } From ee5818b6ff4e39b1d5bdc5d59616f0f5f255ad7c Mon Sep 17 00:00:00 2001 From: Marc-Olivier Duval Date: Tue, 24 Oct 2017 22:42:41 -0400 Subject: [PATCH 09/98] Fix bug with playInBackground --- .../java/com/brentvatne/exoplayer/ReactExoplayerView.java | 7 +++---- 1 file changed, 3 insertions(+), 4 deletions(-) diff --git a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index c12b2a48..34df0031 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -167,9 +167,6 @@ class ReactExoplayerView extends FrameLayout implements @Override protected void onDetachedFromWindow() { super.onDetachedFromWindow(); - if (playInBackground) { - return; - } stopPlayback(); } @@ -209,12 +206,14 @@ class ReactExoplayerView extends FrameLayout implements player.setMetadataOutput(this); exoPlayerView.setPlayer(player); audioBecomingNoisyReceiver.setListener(this); - setPlayWhenReady(!isPaused); playerNeedsSource = true; PlaybackParameters params = new PlaybackParameters(rate, 1f); player.setPlaybackParameters(params); } + + setPlayWhenReady(!isPaused); + if (playerNeedsSource && srcUri != null) { MediaSource mediaSource = buildMediaSource(srcUri, extension); mediaSource = repeat ? new LoopingMediaSource(mediaSource) : mediaSource; From 0fe621a26db4f9e091649b0304a5715d4d8b2947 Mon Sep 17 00:00:00 2001 From: Marc-Olivier Duval Date: Tue, 24 Oct 2017 22:42:51 -0400 Subject: [PATCH 10/98] Updated exoplayer readme --- android-exoplayer/README.md | 3 +-- 1 file changed, 1 insertion(+), 2 deletions(-) diff --git a/android-exoplayer/README.md b/android-exoplayer/README.md index e2d4f9fa..9396763b 100644 --- a/android-exoplayer/README.md +++ b/android-exoplayer/README.md @@ -42,6 +42,5 @@ https://github.com/google/ExoPlayer ## Unimplemented props -- `playInBackground={true}` -- `rate={1.0}` - Expansion file - `source={{ mainVer: 1, patchVer: 0 }}` + From 3e293407e8b15cb97b9ed475a0d65e40b079c466 Mon Sep 17 00:00:00 2001 From: Marc-Olivier Duval Date: Tue, 24 Oct 2017 22:50:20 -0400 Subject: [PATCH 11/98] updated readme to with a guid to setup exoplayer --- README.md | 10 ++++++++++ 1 file changed, 10 insertions(+) diff --git a/README.md b/README.md index 597b71ec..89e46a2f 100644 --- a/README.md +++ b/README.md @@ -269,6 +269,16 @@ Toggles a fullscreen player. Access using a ref to the component. - [Lumpen Radio](https://github.com/jhabdas/lumpen-radio) contains another example integration using local files and full screen background video. +## Use ExoPlayer on Android + +To use ExoPlayer instead of the default player, you have to change android to android-exoplayer in settings.gradle + +**android/settings.gradle** + +```gradle +project(':react-native-video').projectDir = new File(rootProject.projectDir, '../node_modules/react-native-video/android-exoplayer') +``` + ## TODOS - [ ] Add support for captions From c0a8f7c0c9ebf1a82a6f37381de9d96754c931c9 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Kay=20Pl=C3=B6=C3=9Fer?= Date: Fri, 24 Nov 2017 16:22:41 +0100 Subject: [PATCH 12/98] Document fullscreen callbacks fix #861 --- README.md | 10 +++++++--- 1 file changed, 7 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index 597b71ec..01470edf 100644 --- a/README.md +++ b/README.md @@ -165,12 +165,16 @@ using System.Collections.Generic; playWhenInactive={false} // [iOS] Video continues to play when control or notification center are shown. ignoreSilentSwitch={"ignore"} // [iOS] ignore | obey - When 'ignore', audio will still play with the iOS hard silent switch set to silent. When 'obey', audio will toggle with the switch. When not specified, will inherit audio settings as usual. progressUpdateInterval={250.0} // [iOS] Interval to fire onProgress (default to ~250ms) + onBuffer={this.onBuffer} // Callback when remote video is buffering + onEnd={this.onEnd} // Callback when playback finishes + onError={this.videoError} // Callback when video cannot be loaded + onFullscreenPlayerWillPresent={this.fullScreenPlayerWillPresent} // Callback before fullscreen starts + onFullscreenPlayerDidPresent={this.fullScreenPlayerDidPresent} // Callback after fullscreen started + onFullscreenPlayerWillDismiss={this.fullScreenPlayerWillDismiss} // Callback before fullscreen stops + onFullscreenPlayerDidDismiss={this.fullScreenPlayerDidDissmiss} // Callback after fullscreen stopped onLoadStart={this.loadStart} // Callback when video starts to load onLoad={this.setDuration} // Callback when video loads onProgress={this.setTime} // Callback every ~250ms with currentTime - onEnd={this.onEnd} // Callback when playback finishes - onError={this.videoError} // Callback when video cannot be loaded - onBuffer={this.onBuffer} // Callback when remote video is buffering onTimedMetadata={this.onTimedMetadata} // Callback when the stream receive some metadata style={styles.backgroundVideo} /> From ce7c732453476cf77b8583981601f521cbb83410 Mon Sep 17 00:00:00 2001 From: Jordan Becker Date: Thu, 7 Dec 2017 19:35:32 +0100 Subject: [PATCH 13/98] Add requiresMainQueueSetup method Since RN 0.49, `requiresMainQueueSetup` needs to be defined if the module overrides `constantsToExport`. --- ios/RCTVideoManager.m | 5 +++++ 1 file changed, 5 insertions(+) diff --git a/ios/RCTVideoManager.m b/ios/RCTVideoManager.m index a5cf85e0..c1edb9b7 100644 --- a/ios/RCTVideoManager.m +++ b/ios/RCTVideoManager.m @@ -62,4 +62,9 @@ RCT_EXPORT_VIEW_PROPERTY(onPlaybackRateChange, RCTBubblingEventBlock); }; } ++ (BOOL)requiresMainQueueSetup +{ + return YES; +} + @end From 64191298e514ab52b9cb4fcabcb7e38b40a698fe Mon Sep 17 00:00:00 2001 From: sm2017 Date: Sun, 24 Dec 2017 14:44:00 +0330 Subject: [PATCH 14/98] Update ReactVideoView.cs Fix 'Event' does not contain a constructor that takes 2 arguments ReactNativeVideo --- windows/ReactNativeVideo.Net46/ReactVideoView.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/ReactNativeVideo.Net46/ReactVideoView.cs b/windows/ReactNativeVideo.Net46/ReactVideoView.cs index 8a8b1b29..643965ee 100644 --- a/windows/ReactNativeVideo.Net46/ReactVideoView.cs +++ b/windows/ReactNativeVideo.Net46/ReactVideoView.cs @@ -328,7 +328,7 @@ namespace ReactNativeVideo private readonly JObject _eventData; public ReactVideoEvent(string eventName, int viewTag, JObject eventData) - : base(viewTag, TimeSpan.FromTicks(Environment.TickCount)) + : base(viewTag) { _eventName = eventName; _eventData = eventData; From dd242476a3615055343c0c06a119713f9df6fb0e Mon Sep 17 00:00:00 2001 From: sm2017 Date: Sun, 24 Dec 2017 14:46:02 +0330 Subject: [PATCH 15/98] Update ReactVideoView.cs Fix 'Event' does not contain a constructor that takes 2 arguments ReactNativeVideo --- windows/ReactNativeVideo/ReactVideoView.cs | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/windows/ReactNativeVideo/ReactVideoView.cs b/windows/ReactNativeVideo/ReactVideoView.cs index 22063aff..5a6c103f 100644 --- a/windows/ReactNativeVideo/ReactVideoView.cs +++ b/windows/ReactNativeVideo/ReactVideoView.cs @@ -334,7 +334,7 @@ namespace ReactNativeVideo private readonly JObject _eventData; public ReactVideoEvent(string eventName, int viewTag, JObject eventData) - : base(viewTag, TimeSpan.FromTicks(Environment.TickCount)) + : base(viewTag) { _eventName = eventName; _eventData = eventData; From 7d48f22d9871bf98f4a1208e53d218954135f6fa Mon Sep 17 00:00:00 2001 From: Alejandro Rangel Date: Thu, 11 Jan 2018 09:16:29 -0800 Subject: [PATCH 16/98] add seekableDuration to android ReactVideoView --- android/src/main/java/com/brentvatne/react/ReactVideoView.java | 2 ++ 1 file changed, 2 insertions(+) diff --git a/android/src/main/java/com/brentvatne/react/ReactVideoView.java b/android/src/main/java/com/brentvatne/react/ReactVideoView.java index 94a25a0e..f609bcc4 100644 --- a/android/src/main/java/com/brentvatne/react/ReactVideoView.java +++ b/android/src/main/java/com/brentvatne/react/ReactVideoView.java @@ -64,6 +64,7 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP public static final String EVENT_PROP_DURATION = "duration"; public static final String EVENT_PROP_PLAYABLE_DURATION = "playableDuration"; + public static final String EVENT_PROP_SEEKABLE_DURATION = "seekableDuration"; public static final String EVENT_PROP_CURRENT_TIME = "currentTime"; public static final String EVENT_PROP_SEEK_TIME = "seekTime"; public static final String EVENT_PROP_NATURALSIZE = "naturalSize"; @@ -127,6 +128,7 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP WritableMap event = Arguments.createMap(); event.putDouble(EVENT_PROP_CURRENT_TIME, mMediaPlayer.getCurrentPosition() / 1000.0); event.putDouble(EVENT_PROP_PLAYABLE_DURATION, mVideoBufferedDuration / 1000.0); //TODO:mBufferUpdateRunnable + event.putDouble(EVENT_PROP_SEEKABLE_DURATION, mVideoDuration / 1000.0); mEventEmitter.receiveEvent(getId(), Events.EVENT_PROGRESS.toString(), event); // Check for update after an interval From 5d274631c8d742a5faa49532bf0a967da760be2c Mon Sep 17 00:00:00 2001 From: Brandon Moon Date: Mon, 29 Jan 2018 13:25:58 -0700 Subject: [PATCH 17/98] Link up cookies so exoplayer can use them --- .../brentvatne/exoplayer/DataSourceUtil.java | 31 +++++++++++++------ .../exoplayer/ReactExoplayerView.java | 16 +++------- 2 files changed, 26 insertions(+), 21 deletions(-) diff --git a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/DataSourceUtil.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/DataSourceUtil.java index 0077c3b0..c486bf0b 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/DataSourceUtil.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/DataSourceUtil.java @@ -1,7 +1,11 @@ package com.brentvatne.exoplayer; import android.content.Context; +import android.content.ContextWrapper; +import com.facebook.react.bridge.ReactContext; +import com.facebook.react.modules.network.CookieJarContainer; +import com.facebook.react.modules.network.ForwardingCookieHandler; import com.facebook.react.modules.network.OkHttpClientProvider; import com.google.android.exoplayer2.ext.okhttp.OkHttpDataSourceFactory; import com.google.android.exoplayer2.upstream.DataSource; @@ -10,6 +14,10 @@ import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory; import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.util.Util; +import okhttp3.Cookie; +import okhttp3.JavaNetCookieJar; +import okhttp3.OkHttpClient; + public class DataSourceUtil { private DataSourceUtil() { @@ -23,14 +31,14 @@ public class DataSourceUtil { DataSourceUtil.userAgent = userAgent; } - public static String getUserAgent(Context context) { + public static String getUserAgent(ReactContext context) { if (userAgent == null) { - userAgent = Util.getUserAgent(context.getApplicationContext(), "ReactNativeVideo"); + userAgent = Util.getUserAgent(context, "ReactNativeVideo"); } return userAgent; } - public static DataSource.Factory getRawDataSourceFactory(Context context) { + public static DataSource.Factory getRawDataSourceFactory(ReactContext context) { if (rawDataSourceFactory == null) { rawDataSourceFactory = buildRawDataSourceFactory(context); } @@ -41,7 +49,7 @@ public class DataSourceUtil { DataSourceUtil.rawDataSourceFactory = factory; } - public static DataSource.Factory getDefaultDataSourceFactory(Context context, DefaultBandwidthMeter bandwidthMeter) { + public static DataSource.Factory getDefaultDataSourceFactory(ReactContext context, DefaultBandwidthMeter bandwidthMeter) { if (defaultDataSourceFactory == null) { defaultDataSourceFactory = buildDataSourceFactory(context, bandwidthMeter); } @@ -52,17 +60,20 @@ public class DataSourceUtil { DataSourceUtil.defaultDataSourceFactory = factory; } - private static DataSource.Factory buildRawDataSourceFactory(Context context) { + private static DataSource.Factory buildRawDataSourceFactory(ReactContext context) { return new RawResourceDataSourceFactory(context.getApplicationContext()); } - private static DataSource.Factory buildDataSourceFactory(Context context, DefaultBandwidthMeter bandwidthMeter) { - Context appContext = context.getApplicationContext(); - return new DefaultDataSourceFactory(appContext, bandwidthMeter, - buildHttpDataSourceFactory(appContext, bandwidthMeter)); + private static DataSource.Factory buildDataSourceFactory(ReactContext context, DefaultBandwidthMeter bandwidthMeter) { + return new DefaultDataSourceFactory(context, bandwidthMeter, + buildHttpDataSourceFactory(context, bandwidthMeter)); } - private static HttpDataSource.Factory buildHttpDataSourceFactory(Context context, DefaultBandwidthMeter bandwidthMeter) { + private static HttpDataSource.Factory buildHttpDataSourceFactory(ReactContext context, DefaultBandwidthMeter bandwidthMeter) { + OkHttpClient client = OkHttpClientProvider.getOkHttpClient(); + CookieJarContainer container = (CookieJarContainer) client.cookieJar(); + ForwardingCookieHandler handler = new ForwardingCookieHandler(context); + container.setCookieJar(new JavaNetCookieJar(handler)); return new OkHttpDataSourceFactory(OkHttpClientProvider.getOkHttpClient(), getUserAgent(context), bandwidthMeter); } diff --git a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index f24d94c2..074531af 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -124,14 +124,13 @@ class ReactExoplayerView extends FrameLayout implements public ReactExoplayerView(ThemedReactContext context) { super(context); + this.themedReactContext = context; createViews(); this.eventEmitter = new VideoEventEmitter(context); - this.themedReactContext = context; audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE); themedReactContext.addLifecycleEventListener(this); audioBecomingNoisyReceiver = new AudioBecomingNoisyReceiver(themedReactContext); - initializePlayer(); } @@ -353,7 +352,7 @@ class ReactExoplayerView extends FrameLayout implements * @return A new DataSource factory. */ private DataSource.Factory buildDataSourceFactory(boolean useBandwidthMeter) { - return DataSourceUtil.getDefaultDataSourceFactory(getContext(), useBandwidthMeter ? BANDWIDTH_METER : null); + return DataSourceUtil.getDefaultDataSourceFactory(this.themedReactContext, useBandwidthMeter ? BANDWIDTH_METER : null); } // AudioManager.OnAudioFocusChangeListener implementation @@ -482,7 +481,6 @@ class ReactExoplayerView extends FrameLayout implements @Override public void onPlayerError(ExoPlaybackException e) { String errorString = null; - Exception ex = e; if (e.type == ExoPlaybackException.TYPE_RENDERER) { Exception cause = e.getRendererException(); if (cause instanceof MediaCodecRenderer.DecoderInitializationException) { @@ -505,12 +503,8 @@ class ReactExoplayerView extends FrameLayout implements } } } - else if (e.type == ExoPlaybackException.TYPE_SOURCE) { - ex = e.getSourceException(); - errorString = getResources().getString(R.string.unrecognized_media_format); - } if (errorString != null) { - eventEmitter.error(errorString, ex); + eventEmitter.error(errorString, e); } playerNeedsSource = true; if (isBehindLiveWindow(e)) { @@ -549,7 +543,7 @@ class ReactExoplayerView extends FrameLayout implements this.srcUri = uri; this.extension = extension; - this.mediaDataSourceFactory = DataSourceUtil.getDefaultDataSourceFactory(getContext(), BANDWIDTH_METER); + this.mediaDataSourceFactory = DataSourceUtil.getDefaultDataSourceFactory(this.themedReactContext, BANDWIDTH_METER); if (!isOriginalSourceNull && !isSourceEqual) { reloadSource(); @@ -568,7 +562,7 @@ class ReactExoplayerView extends FrameLayout implements this.srcUri = uri; this.extension = extension; - this.mediaDataSourceFactory = DataSourceUtil.getRawDataSourceFactory(getContext()); + this.mediaDataSourceFactory = DataSourceUtil.getRawDataSourceFactory(this.themedReactContext); if (!isOriginalSourceNull && !isSourceEqual) { reloadSource(); From f2e182addc70cb380b56db190b62cca56d3dcac4 Mon Sep 17 00:00:00 2001 From: Brandon Moon Date: Mon, 29 Jan 2018 13:32:31 -0700 Subject: [PATCH 18/98] Bring things up to date with master from copied code --- .../java/com/brentvatne/exoplayer/ReactExoplayerView.java | 8 +++++++- 1 file changed, 7 insertions(+), 1 deletion(-) diff --git a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index 074531af..cf8ebc28 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -131,6 +131,7 @@ class ReactExoplayerView extends FrameLayout implements themedReactContext.addLifecycleEventListener(this); audioBecomingNoisyReceiver = new AudioBecomingNoisyReceiver(themedReactContext); + initializePlayer(); } @@ -481,6 +482,7 @@ class ReactExoplayerView extends FrameLayout implements @Override public void onPlayerError(ExoPlaybackException e) { String errorString = null; + Exception ex = e; if (e.type == ExoPlaybackException.TYPE_RENDERER) { Exception cause = e.getRendererException(); if (cause instanceof MediaCodecRenderer.DecoderInitializationException) { @@ -503,8 +505,12 @@ class ReactExoplayerView extends FrameLayout implements } } } + else if (e.type == ExoPlaybackException.TYPE_SOURCE) { + ex = e.getSourceException(); + errorString = getResources().getString(R.string.unrecognized_media_format); + } if (errorString != null) { - eventEmitter.error(errorString, e); + eventEmitter.error(errorString, ex); } playerNeedsSource = true; if (isBehindLiveWindow(e)) { From 2c966fe7ad1965688e65aa72517f27051685588b Mon Sep 17 00:00:00 2001 From: Param Aggarwal Date: Sat, 17 Feb 2018 19:37:31 +0530 Subject: [PATCH 19/98] Fix playableDuration attribute of onProgress event --- .../main/java/com/brentvatne/exoplayer/ReactExoplayerView.java | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index f24d94c2..fed6ddbb 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -113,7 +113,7 @@ class ReactExoplayerView extends FrameLayout implements && player.getPlayWhenReady() ) { long pos = player.getCurrentPosition(); - eventEmitter.progressChanged(pos, player.getBufferedPercentage()); + eventEmitter.progressChanged(pos, player.getBufferedPosition()); msg = obtainMessage(SHOW_PROGRESS); sendMessageDelayed(msg, Math.round(mProgressUpdateInterval)); } From cfab35d4845847d0d2638f02f69b55143d1e3930 Mon Sep 17 00:00:00 2001 From: masarusanjp Date: Wed, 28 Feb 2018 11:15:42 +0900 Subject: [PATCH 20/98] fxied an issue that does not use passed argument --- ios/RCTVideo.m | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/ios/RCTVideo.m b/ios/RCTVideo.m index 0f1227b1..53d2047d 100644 --- a/ios/RCTVideo.m +++ b/ios/RCTVideo.m @@ -93,7 +93,7 @@ static NSString *const timedMetadata = @"timedMetadata"; playerLayer.showsPlaybackControls = NO; playerLayer.rctDelegate = self; playerLayer.view.frame = self.bounds; - playerLayer.player = _player; + playerLayer.player = player; playerLayer.view.frame = self.bounds; return playerLayer; } From 28bae40c6c0d5b58bb30c2e56249b91fe7ae115d Mon Sep 17 00:00:00 2001 From: seansy Date: Mon, 19 Mar 2018 12:56:55 -0700 Subject: [PATCH 21/98] Fixes #963 Bug happens when uri is changed for a video. The fix: Remove player layer before addPlayerItemObservers so _playerItemObserversSet is still set to NO if observers have already been removed. --- ios/RCTVideo.m | 7 ++----- 1 file changed, 2 insertions(+), 5 deletions(-) diff --git a/ios/RCTVideo.m b/ios/RCTVideo.m index 0f1227b1..ab0d5879 100644 --- a/ios/RCTVideo.m +++ b/ios/RCTVideo.m @@ -140,8 +140,8 @@ static NSString *const timedMetadata = @"timedMetadata"; - (void)dealloc { [[NSNotificationCenter defaultCenter] removeObserver:self]; - [self removePlayerItemObservers]; [self removePlayerLayer]; + [self removePlayerItemObservers]; [_player removeObserver:self forKeyPath:playbackRate context:nil]; } @@ -252,9 +252,6 @@ static NSString *const timedMetadata = @"timedMetadata"; * observer set */ - (void)removePlayerItemObservers { - if (_playerLayer) { - [_playerLayer removeObserver:self forKeyPath:readyForDisplayKeyPath]; - } if (_playerItemObserversSet) { [_playerItem removeObserver:self forKeyPath:statusKeyPath]; [_playerItem removeObserver:self forKeyPath:playbackBufferEmptyKeyPath]; @@ -268,13 +265,13 @@ static NSString *const timedMetadata = @"timedMetadata"; - (void)setSrc:(NSDictionary *)source { + [self removePlayerLayer]; [self removePlayerTimeObserver]; [self removePlayerItemObservers]; _playerItem = [self playerItemForSource:source]; [self addPlayerItemObservers]; [_player pause]; - [self removePlayerLayer]; [_playerViewController.view removeFromSuperview]; _playerViewController = nil; From 2477288ea2d6d3281792a73ffff0dd41837d741e Mon Sep 17 00:00:00 2001 From: Peace Date: Tue, 3 Apr 2018 12:19:04 -0500 Subject: [PATCH 22/98] Upgrade ExoPlayer to 2.7.2 --- android-exoplayer/build.gradle | 4 ++-- .../brentvatne/exoplayer/ExoPlayerView.java | 20 +++++++++++++++++-- .../exoplayer/ReactExoplayerView.java | 20 +++++++++++++++++-- 3 files changed, 38 insertions(+), 6 deletions(-) diff --git a/android-exoplayer/build.gradle b/android-exoplayer/build.gradle index b25b8164..6d995692 100644 --- a/android-exoplayer/build.gradle +++ b/android-exoplayer/build.gradle @@ -12,8 +12,8 @@ android { dependencies { provided 'com.facebook.react:react-native:+' - compile 'com.google.android.exoplayer:exoplayer:r2.4.0' - compile('com.google.android.exoplayer:extension-okhttp:r2.4.0') { + compile 'com.google.android.exoplayer:exoplayer:2.7.2' + compile('com.google.android.exoplayer:extension-okhttp:2.7.2') { exclude group: 'com.squareup.okhttp3', module: 'okhttp' } compile 'com.squareup.okhttp3:okhttp:3.4.2' diff --git a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java index 224a2d81..e4a7a57b 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ExoPlayerView.java @@ -27,6 +27,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.ui.SubtitleView; import java.util.List; +import java.lang.Object; @TargetApi(16) public final class ExoPlayerView extends FrameLayout { @@ -212,12 +213,12 @@ public final class ExoPlayerView extends FrameLayout { } @Override - public void onPositionDiscontinuity() { + public void onPositionDiscontinuity(int reason) { // Do nothing. } @Override - public void onTimelineChanged(Timeline timeline, Object manifest) { + public void onTimelineChanged(Timeline timeline, Object manifest, int reason) { // Do nothing. } @@ -235,6 +236,21 @@ public final class ExoPlayerView extends FrameLayout { public void onMetadata(Metadata metadata) { Log.d("onMetadata", "onMetadata"); } + + @Override + public void onSeekProcessed() { + // Do nothing. + } + + @Override + public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) { + // Do nothing. + } + + @Override + public void onRepeatModeChanged(int repeatMode) { + // Do nothing. + } } } diff --git a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index f24d94c2..bc871bb3 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -52,6 +52,7 @@ import java.net.CookieHandler; import java.net.CookieManager; import java.net.CookiePolicy; import java.lang.Math; +import java.lang.Object; @SuppressLint("ViewConstructor") class ReactExoplayerView extends FrameLayout implements @@ -455,7 +456,7 @@ class ReactExoplayerView extends FrameLayout implements } @Override - public void onPositionDiscontinuity() { + public void onPositionDiscontinuity(int reason) { if (playerNeedsSource) { // This will only occur if the user has performed a seek whilst in the error state. Update the // resume position so that if the user then retries, playback will resume from the position to @@ -465,7 +466,22 @@ class ReactExoplayerView extends FrameLayout implements } @Override - public void onTimelineChanged(Timeline timeline, Object manifest) { + public void onTimelineChanged(Timeline timeline, Object manifest, int reason) { + // Do nothing. + } + + @Override + public void onSeekProcessed() { + // Do nothing. + } + + @Override + public void onShuffleModeEnabledChanged(boolean shuffleModeEnabled) { + // Do nothing. + } + + @Override + public void onRepeatModeChanged(int repeatMode) { // Do nothing. } From 2d1c0023d01104d66289f6e4c0008c3f8d096325 Mon Sep 17 00:00:00 2001 From: Hampton Maxwell Date: Tue, 15 May 2018 22:19:12 -0700 Subject: [PATCH 23/98] Clear the progress timer at video end, restore on seek --- ios/RCTVideo.m | 23 ++++++++++++++++------- 1 file changed, 16 insertions(+), 7 deletions(-) diff --git a/ios/RCTVideo.m b/ios/RCTVideo.m index 53d2047d..25590978 100644 --- a/ios/RCTVideo.m +++ b/ios/RCTVideo.m @@ -124,6 +124,16 @@ static NSString *const timedMetadata = @"timedMetadata"; return (kCMTimeRangeZero); } +- (void)addPlayerTimeObserver +{ + const Float64 progressUpdateIntervalMS = _progressUpdateInterval / 1000; + // @see endScrubbing in AVPlayerDemoPlaybackViewController.m of https://developer.apple.com/library/ios/samplecode/AVPlayerDemo/Introduction/Intro.html + __weak RCTVideo *weakSelf = self; + _timeObserver = [_player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(progressUpdateIntervalMS, NSEC_PER_SEC) + queue:NULL + usingBlock:^(CMTime time) { [weakSelf sendProgressUpdate]; } + ]; +} /* Cancels the previously registered time observer. */ -(void)removePlayerTimeObserver @@ -289,13 +299,7 @@ static NSString *const timedMetadata = @"timedMetadata"; [_player addObserver:self forKeyPath:playbackRate options:0 context:nil]; _playbackRateObserverRegistered = YES; - const Float64 progressUpdateIntervalMS = _progressUpdateInterval / 1000; - // @see endScrubbing in AVPlayerDemoPlaybackViewController.m of https://developer.apple.com/library/ios/samplecode/AVPlayerDemo/Introduction/Intro.html - __weak RCTVideo *weakSelf = self; - _timeObserver = [_player addPeriodicTimeObserverForInterval:CMTimeMakeWithSeconds(progressUpdateIntervalMS, NSEC_PER_SEC) - queue:NULL - usingBlock:^(CMTime time) { [weakSelf sendProgressUpdate]; } - ]; + [self addPlayerTimeObserver]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ //Perform on next run loop, otherwise onVideoLoadStart is nil @@ -488,6 +492,8 @@ static NSString *const timedMetadata = @"timedMetadata"; AVPlayerItem *item = [notification object]; [item seekToTime:kCMTimeZero]; [self applyModifiers]; + } else { + [self removePlayerTimeObserver]; } } @@ -567,6 +573,9 @@ static NSString *const timedMetadata = @"timedMetadata"; if (CMTimeCompare(current, cmSeekTime) != 0) { if (!wasPaused) [_player pause]; [_player seekToTime:cmSeekTime toleranceBefore:tolerance toleranceAfter:tolerance completionHandler:^(BOOL finished) { + if (!_timeObserver) { + [self addPlayerTimeObserver]; + } if (!wasPaused) [_player play]; if(self.onVideoSeek) { self.onVideoSeek(@{@"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(item.currentTime)], From 489b16f11e5474c3adeafdf948d5e785d0b031d1 Mon Sep 17 00:00:00 2001 From: Hampton Maxwell Date: Thu, 17 May 2018 14:12:04 -0700 Subject: [PATCH 24/98] Base bufferedDuration on percentage & total duration. Add seekableDuration --- .../java/com/brentvatne/exoplayer/ReactExoplayerView.java | 3 ++- .../main/java/com/brentvatne/exoplayer/VideoEventEmitter.java | 4 +++- 2 files changed, 5 insertions(+), 2 deletions(-) diff --git a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index fed6ddbb..44e7a9ee 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -113,7 +113,8 @@ class ReactExoplayerView extends FrameLayout implements && player.getPlayWhenReady() ) { long pos = player.getCurrentPosition(); - eventEmitter.progressChanged(pos, player.getBufferedPosition()); + long bufferedDuration = player.getBufferedPercentage() * player.getDuration(); + eventEmitter.progressChanged(pos, bufferedDuration, player.getDuration()); msg = obtainMessage(SHOW_PROGRESS); sendMessageDelayed(msg, Math.round(mProgressUpdateInterval)); } diff --git a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/VideoEventEmitter.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/VideoEventEmitter.java index b2ee4a15..ff3b4555 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/VideoEventEmitter.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/VideoEventEmitter.java @@ -89,6 +89,7 @@ class VideoEventEmitter { private static final String EVENT_PROP_DURATION = "duration"; private static final String EVENT_PROP_PLAYABLE_DURATION = "playableDuration"; + private static final String EVENT_PROP_SEEKABLE_DURATION = "seekableDuration"; private static final String EVENT_PROP_CURRENT_TIME = "currentTime"; private static final String EVENT_PROP_SEEK_TIME = "seekTime"; private static final String EVENT_PROP_NATURAL_SIZE = "naturalSize"; @@ -141,10 +142,11 @@ class VideoEventEmitter { receiveEvent(EVENT_LOAD, event); } - void progressChanged(double currentPosition, double bufferedDuration) { + void progressChanged(double currentPosition, double bufferedDuration, double seekableDuration) { WritableMap event = Arguments.createMap(); event.putDouble(EVENT_PROP_CURRENT_TIME, currentPosition / 1000D); event.putDouble(EVENT_PROP_PLAYABLE_DURATION, bufferedDuration / 1000D); + event.putDouble(EVENT_PROP_SEEKABLE_DURATION, seekableDuration / 1000D); receiveEvent(EVENT_PROGRESS, event); } From ad8f6b49f53516e6b90ba632db6158bf8390fda4 Mon Sep 17 00:00:00 2001 From: Hampton Maxwell Date: Thu, 17 May 2018 15:42:44 -0700 Subject: [PATCH 25/98] Support setting fullscreen UI and generating events for it --- .../exoplayer/ReactExoplayerView.java | 40 +++++++++++++++++++ .../exoplayer/VideoEventEmitter.java | 29 ++++++++++++++ 2 files changed, 69 insertions(+) diff --git a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java index 44e7a9ee..2342413b 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -1,6 +1,7 @@ package com.brentvatne.exoplayer; import android.annotation.SuppressLint; +import android.app.Activity; import android.content.Context; import android.media.AudioManager; import android.net.Uri; @@ -8,6 +9,8 @@ import android.os.Handler; import android.os.Message; import android.text.TextUtils; import android.util.Log; +import android.view.View; +import android.view.Window; import android.widget.FrameLayout; import com.brentvatne.react.R; @@ -85,6 +88,7 @@ class ReactExoplayerView extends FrameLayout implements private int resumeWindow; private long resumePosition; private boolean loadVideoStarted; + private boolean isFullscreen; private boolean isPaused = true; private boolean isBuffering; private float rate = 1f; @@ -331,6 +335,9 @@ class ReactExoplayerView extends FrameLayout implements } private void onStopPlayback() { + if (isFullscreen) { + setFullscreen(false); + } setKeepScreenOn(false); audioManager.abandonAudioFocus(this); } @@ -638,4 +645,37 @@ class ReactExoplayerView extends FrameLayout implements public void setDisableFocus(boolean disableFocus) { this.disableFocus = disableFocus; } + + public void setFullscreen(boolean fullscreen) { + if (fullscreen == isFullscreen) { + return; // Avoid generating events when nothing is changing + } + isFullscreen = fullscreen; + + Activity activity = themedReactContext.getCurrentActivity(); + if (activity == null) { + return; + } + Window window = activity.getWindow(); + View decorView = window.getDecorView(); + int uiOptions; + if (isFullscreen) { + if (Util.SDK_INT >= 19) { // 4.4+ + uiOptions = SYSTEM_UI_FLAG_HIDE_NAVIGATION + | SYSTEM_UI_FLAG_IMMERSIVE_STICKY + | SYSTEM_UI_FLAG_FULLSCREEN; + } else { + uiOptions = SYSTEM_UI_FLAG_HIDE_NAVIGATION + | SYSTEM_UI_FLAG_FULLSCREEN; + } + eventEmitter.fullscreenWillPresent(); + decorView.setSystemUiVisibility(uiOptions); + eventEmitter.fullscreenDidPresent(); + } else { + uiOptions = View.SYSTEM_UI_FLAG_VISIBLE; + eventEmitter.fullscreenWillDismiss(); + decorView.setSystemUiVisibility(uiOptions); + eventEmitter.fullscreenDidDismiss(); + } + } } diff --git a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/VideoEventEmitter.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/VideoEventEmitter.java index ff3b4555..ab2eda86 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/VideoEventEmitter.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/VideoEventEmitter.java @@ -31,6 +31,11 @@ class VideoEventEmitter { private static final String EVENT_PROGRESS = "onVideoProgress"; private static final String EVENT_SEEK = "onVideoSeek"; private static final String EVENT_END = "onVideoEnd"; + private static final String EVENT_FULLSCREEN_WILL_PRESENT = "onVideoFullscreenPlayerWillPresent"; + private static final String EVENT_FULLSCREEN_DID_PRESENT = "onVideoFullscreenPlayerDidPresent"; + private static final String EVENT_FULLSCREEN_WILL_DISMISS = "onVideoFullscreenPlayerWillDismiss"; + private static final String EVENT_FULLSCREEN_DID_DISMISS = "onVideoFullscreenPlayerDidDismiss"; + private static final String EVENT_STALLED = "onPlaybackStalled"; private static final String EVENT_RESUME = "onPlaybackResume"; private static final String EVENT_READY = "onReadyForDisplay"; @@ -48,6 +53,10 @@ class VideoEventEmitter { EVENT_PROGRESS, EVENT_SEEK, EVENT_END, + EVENT_FULLSCREEN_WILL_PRESENT, + EVENT_FULLSCREEN_DID_PRESENT, + EVENT_FULLSCREEN_WILL_DISMISS, + EVENT_FULLSCREEN_DID_DISMISS, EVENT_STALLED, EVENT_RESUME, EVENT_READY, @@ -67,6 +76,10 @@ class VideoEventEmitter { EVENT_PROGRESS, EVENT_SEEK, EVENT_END, + EVENT_FULLSCREEN_WILL_PRESENT, + EVENT_FULLSCREEN_DID_PRESENT, + EVENT_FULLSCREEN_WILL_DISMISS, + EVENT_FULLSCREEN_DID_DISMISS, EVENT_STALLED, EVENT_RESUME, EVENT_READY, @@ -175,6 +188,22 @@ class VideoEventEmitter { receiveEvent(EVENT_END, null); } + void fullscreenWillPresent() { + receiveEvent(EVENT_FULLSCREEN_WILL_PRESENT, null); + } + + void fullscreenDidPresent() { + receiveEvent(EVENT_FULLSCREEN_DID_PRESENT, null); + } + + void fullscreenWillDismiss() { + receiveEvent(EVENT_FULLSCREEN_WILL_DISMISS, null); + } + + void fullscreenDidDismiss() { + receiveEvent(EVENT_FULLSCREEN_DID_DISMISS, null); + } + void error(String errorString, Exception exception) { WritableMap error = Arguments.createMap(); error.putString(EVENT_PROP_ERROR_STRING, errorString); From 052e532b587270d005a50449faeb312d1ee62906 Mon Sep 17 00:00:00 2001 From: Hampton Maxwell Date: Thu, 17 May 2018 16:13:12 -0700 Subject: [PATCH 26/98] Add support for fullscreen on ExoPlayer --- README.md | 12 +++++++++++- 1 file changed, 11 insertions(+), 1 deletion(-) diff --git a/README.md b/README.md index e64916ca..06fcf07a 100644 --- a/README.md +++ b/README.md @@ -177,6 +177,8 @@ using System.Collections.Generic; // Later to trigger fullscreen this.player.presentFullscreenPlayer() +// Disable fullscreen +this.player.dismissFullscreenPlayer() // To set video position in seconds (seek) this.player.seek(0) @@ -217,6 +219,10 @@ To see full list of available props, you can check [the propTypes](https://githu onError={this.videoError} // Callback when video cannot be loaded style={styles.backgroundVideo} /> +// Later to enable fullscreen UI mode (ExoPlayer only). Combine with setting the style to be height & width from Dimensions.get('screen') +this.player.presentFullscreenPlayer() +// Disable fullscreen UI mode + // Later on in your styles.. var styles = Stylesheet.create({ backgroundVideo: { @@ -254,7 +260,11 @@ Seeks the video to the specified time (in seconds). Access using a ref to the co `presentFullscreenPlayer()` -Toggles a fullscreen player. Access using a ref to the component. +Enable the fullscreen player. Access using a ref to the component. + +`dimissFullscreenPlayer()` + +Disable the fullscreen player. Access using a ref to the component. ## Examples From d0d67eb715c636067d7f5852ca1af877974a8458 Mon Sep 17 00:00:00 2001 From: Hampton Maxwell Date: Thu, 17 May 2018 16:15:15 -0700 Subject: [PATCH 27/98] Forgot to include comment about disabling ExoPlayer fullscreen --- README.md | 1 + 1 file changed, 1 insertion(+) diff --git a/README.md b/README.md index 06fcf07a..f6624956 100644 --- a/README.md +++ b/README.md @@ -222,6 +222,7 @@ To see full list of available props, you can check [the propTypes](https://githu // Later to enable fullscreen UI mode (ExoPlayer only). Combine with setting the style to be height & width from Dimensions.get('screen') this.player.presentFullscreenPlayer() // Disable fullscreen UI mode +this.player.dismissFullscreenPlayer() // Later on in your styles.. var styles = Stylesheet.create({ From 4f6baaa2ac42918fd9ad5435e117076d3270dae5 Mon Sep 17 00:00:00 2001 From: Hampton Maxwell Date: Fri, 18 May 2018 13:30:01 -0700 Subject: [PATCH 28/98] Add missing prop for enabling fullscreen --- .../com/brentvatne/exoplayer/ReactExoplayerViewManager.java | 6 ++++++ 1 file changed, 6 insertions(+) diff --git a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java index dda8d000..b500f400 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java @@ -32,6 +32,7 @@ public class ReactExoplayerViewManager extends ViewGroupManager Date: Sun, 20 May 2018 23:02:56 -0700 Subject: [PATCH 29/98] Delay all rate changes until the player unpauses --- .../main/java/com/brentvatne/react/ReactVideoView.java | 10 +++++++++- 1 file changed, 9 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 099bec33..4efe88fd 100644 --- a/android/src/main/java/com/brentvatne/react/ReactVideoView.java +++ b/android/src/main/java/com/brentvatne/react/ReactVideoView.java @@ -96,6 +96,7 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP private float mVolume = 1.0f; private float mProgressUpdateInterval = 250.0f; private float mRate = 1.0f; + private float mActiveRate = 1.0f; private boolean mPlayInBackground = false; private boolean mActiveStatePauseStatus = false; private boolean mActiveStatePauseStatusInitialized = false; @@ -344,6 +345,10 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP } else { if (!mMediaPlayer.isPlaying()) { start(); + // Setting the rate unpauses, so we have to wait for an unpause + if (mRate != mActiveRate) { + setRateModifier(mRate); + } // Also Start the Progress Update Handler mProgressUpdateHandler.post(mProgressUpdateRunnable); @@ -379,7 +384,10 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP if (mMediaPlayerValid) { if (Build.VERSION.SDK_INT >= Build.VERSION_CODES.M) { - mMediaPlayer.setPlaybackParams(mMediaPlayer.getPlaybackParams().setSpeed(rate)); + if (!mPaused) { // Applying the rate while paused will cause the video to start + mMediaPlayer.setPlaybackParams(mMediaPlayer.getPlaybackParams().setSpeed(rate)); + mActiveRate = rate; + } } else { Log.e(ReactVideoViewManager.REACT_CLASS, "Setting playback rate is not yet supported on Android versions below 6.0"); } From 785cac6f46b90caf7f6b0650528b163ddd8c9326 Mon Sep 17 00:00:00 2001 From: Peace Date: Mon, 21 May 2018 09:28:45 -0500 Subject: [PATCH 30/98] Upgrade Exoplayer to 2.7.3; OkHttp to 3.9.1. --- android-exoplayer/build.gradle | 6 +++--- 1 file changed, 3 insertions(+), 3 deletions(-) diff --git a/android-exoplayer/build.gradle b/android-exoplayer/build.gradle index 6d995692..9aeaa593 100644 --- a/android-exoplayer/build.gradle +++ b/android-exoplayer/build.gradle @@ -12,9 +12,9 @@ android { dependencies { provided 'com.facebook.react:react-native:+' - compile 'com.google.android.exoplayer:exoplayer:2.7.2' - compile('com.google.android.exoplayer:extension-okhttp:2.7.2') { + compile 'com.google.android.exoplayer:exoplayer:2.7.3' + compile('com.google.android.exoplayer:extension-okhttp:2.7.3') { exclude group: 'com.squareup.okhttp3', module: 'okhttp' } - compile 'com.squareup.okhttp3:okhttp:3.4.2' + compile 'com.squareup.okhttp3:okhttp:3.9.1' } From 76cc5ec4f0b923c155faf3b7cb49e63310d2a8a4 Mon Sep 17 00:00:00 2001 From: Akshay Gore Date: Tue, 22 May 2018 00:43:10 -0700 Subject: [PATCH 31/98] Add App Transport Security Exceptions Configuration --- README.md | 4 +++- docs/AppTransportSecuritySetting.png | Bin 0 -> 26292 bytes 2 files changed, 3 insertions(+), 1 deletion(-) create mode 100644 docs/AppTransportSecuritySetting.png diff --git a/README.md b/README.md index 787af986..20449944 100644 --- a/README.md +++ b/README.md @@ -199,7 +199,9 @@ var styles = StyleSheet.create({ }); ``` -- * *For iOS you also need to specify muted for this to work* +- * For iOS you also need to specify muted for this to work* +- * To load the HTTP source url on iOS you need to add the exception into the Info.plist + To see full list of available props, you can check [the propTypes](https://github.com/react-native-community/react-native-video/blob/master/Video.js#L246) of the Video.js component. diff --git a/docs/AppTransportSecuritySetting.png b/docs/AppTransportSecuritySetting.png new file mode 100644 index 0000000000000000000000000000000000000000..b57be9a46c69708be16f40e0f0da47a3170ade99 GIT binary patch literal 26292 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

uqKV1Gs|uk72DfHqpp>|y6w z`N_WmD|Ou%=6?=5L*tW(7U9$KF(_{frkH)Cr>7UOEyichY-u5dAzC{-JBsHp#k1!F z^le*vzEZaB;yUmjaGJvDB`hg?vw<{52u3(3^~<;_HPKH!hPwGtHAdH+c(4nkUK&tU z*7n3-SX87&@XwB>t`=dd$7z-qz+#l7X34T=AOZ6~gF^pg`ZO^F>EA$ffE2}A?zOXp zg~j^C?qne^hF5N4C7RFGf!5`Hwbf)f=+P^JRDc`1+l&zA@84++!1@yy!aJ6~QzAmS z_EV3rNWG-<_I%sdUoM>=kC?|EC?{7w+joPz&HC?Jz(@ch|3U-BSP;WI{+-P(a661c zqxR$XP$Fv)f<_IK}~U<4+bf2wGRZ3vzNcF_Gjju`dd7-#v=W+xaEK%gzo z((|AC;Qp!4o>Mv7-vtx(e_wz98I>)7k_N^x`2TKs@(dqtpDJco$}b9g{V<%gxIC~_ I%rNl(0B+btdjJ3c literal 0 HcmV?d00001 From 9a9e36c71e04df47eb7509869780d3e7cb99374a Mon Sep 17 00:00:00 2001 From: Akshay Gore Date: Tue, 22 May 2018 00:49:51 -0700 Subject: [PATCH 32/98] update the wording --- README.md | 5 +++-- 1 file changed, 3 insertions(+), 2 deletions(-) diff --git a/README.md b/README.md index 20449944..65636a2c 100644 --- a/README.md +++ b/README.md @@ -199,8 +199,9 @@ var styles = StyleSheet.create({ }); ``` -- * For iOS you also need to specify muted for this to work* -- * To load the HTTP source url on iOS you need to add the exception into the Info.plist +- For iOS you also need to specify muted for this to work +- To load the HTTP source url on iOS you need to add/modify the App Transport Security Exception into the Info.plist + To see full list of available props, you can check [the propTypes](https://github.com/react-native-community/react-native-video/blob/master/Video.js#L246) of the Video.js component. From 8970a1ea977855e4bbaecef6df730353910f1398 Mon Sep 17 00:00:00 2001 From: Akshay Gore Date: Tue, 22 May 2018 12:18:35 -0700 Subject: [PATCH 33/98] add explanation for add modifying the ATS exception --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index 65636a2c..c3f6ba27 100644 --- a/README.md +++ b/README.md @@ -200,7 +200,7 @@ var styles = StyleSheet.create({ ``` - For iOS you also need to specify muted for this to work -- To load the HTTP source url on iOS you need to add/modify the App Transport Security Exception into the Info.plist +- iOS 9+ requires all webservices and files loaded inside the app to use HTTPS. To use the non-secured (HTTP) webservices or files, you need to add/modify the App Transport Security Exception into the Info.plist From 1868bd3402c4f6229d9314da306afd4a7c7ee2e4 Mon Sep 17 00:00:00 2001 From: Hampton Maxwell Date: Tue, 22 May 2018 12:51:59 -0700 Subject: [PATCH 34/98] Clean up work about transport security --- README.md | 7 ++++--- 1 file changed, 4 insertions(+), 3 deletions(-) diff --git a/README.md b/README.md index c3f6ba27..71f78deb 100644 --- a/README.md +++ b/README.md @@ -198,13 +198,14 @@ var styles = StyleSheet.create({ }, }); ``` +To see the full list of available props, you can check the [propTypes](https://github.com/react-native-community/react-native-video/blob/master/Video.js#L246) of the Video.js component. + +- By default, iOS 9+ will only load encrypted HTTPS urls. If you need to load content from a webserver that only supports HTTP, you will need to modify your Info.plist file and add the following entry: -- For iOS you also need to specify muted for this to work -- iOS 9+ requires all webservices and files loaded inside the app to use HTTPS. To use the non-secured (HTTP) webservices or files, you need to add/modify the App Transport Security Exception into the Info.plist -To see full list of available props, you can check [the propTypes](https://github.com/react-native-community/react-native-video/blob/master/Video.js#L246) of the Video.js component. +For more detailed info check this [article](https://cocoacasts.com/how-to-add-app-transport-security-exception-domains) ## Android Expansion File Usage From c3ea90bc590e16d378bc1e44672d3ebf98d14300 Mon Sep 17 00:00:00 2001 From: Hampton Maxwell Date: Mon, 28 May 2018 18:51:53 -0700 Subject: [PATCH 35/98] Condense instructions for each platform --- README.md | 109 ++++++++++++++++++++---------------------------------- 1 file changed, 41 insertions(+), 68 deletions(-) diff --git a/README.md b/README.md index 71f78deb..3fe07c5f 100644 --- a/README.md +++ b/README.md @@ -5,12 +5,28 @@ A `

+ iOS Run `react-native link` to link the react-native-video library. If you would like to allow other apps to play music over your video component, add: @@ -28,9 +44,10 @@ If you would like to allow other apps to play music over your video component, a } ``` Note: you can also use the `ignoreSilentSwitch` prop, shown below. +
-#### tvOS - +
+ tvOS Run `react-native link` to link the react-native-video library. `react-native link` don’t works properly with the tvOS target so we need to add the library manually. @@ -50,11 +67,10 @@ Scroll to « Linked Frameworks and Libraries » and tap on the + button Select RCTVideo-tvOS +
-That’s all, you can use react-native-video for your tvOS application - -#### Android - +
+ Android Run `react-native link` to link the react-native-video library. Or if you have trouble, make the following additions to the given files manually: @@ -94,9 +110,10 @@ protected List getPackages() { ); } ``` +
-#### Windows - +
+ Windows Make the following additions to the given files manually: **windows/myapp.sln** @@ -105,18 +122,17 @@ Add the `ReactNativeVideo` project to your solution. 1. Open the solution in Visual Studio 2015 2. Right-click Solution icon in Solution Explorer > Add > Existing Project... -3. - UWP: Select `node_modules\react-native-video\windows\ReactNativeVideo\ReactNativeVideo.csproj` - WPF: Select `node_modules\react-native-video\windows\ReactNativeVideo.Net46\ReactNativeVideo.Net46.csproj` +3a. UWP: Select `node_modules\react-native-video\windows\ReactNativeVideo\ReactNativeVideo.csproj` +3b. WPF: Select `node_modules\react-native-video\windows\ReactNativeVideo.Net46\ReactNativeVideo.Net46.csproj` **windows/myapp/myapp.csproj** Add a reference to `ReactNativeVideo` to your main application project. From Visual Studio 2015: 1. Right-click main application project > Add > Reference... -2. - UWP: Check `ReactNativeVideo` from Solution Projects. - WPF: Check `ReactNativeVideo.Net46` from Solution Projects. +2a. UWP: Check `ReactNativeVideo` from Solution Projects. + +WPF: Check `ReactNativeVideo.Net46` from Solution Projects. **MainPage.cs** @@ -143,6 +159,7 @@ using System.Collections.Generic; ... ``` +
## Usage @@ -181,6 +198,7 @@ using System.Collections.Generic; // Later to trigger fullscreen this.player.presentFullscreenPlayer() + // Disable fullscreen this.player.dismissFullscreenPlayer() @@ -202,47 +220,16 @@ To see the full list of available props, you can check the [propTypes](https://g - By default, iOS 9+ will only load encrypted HTTPS urls. If you need to load content from a webserver that only supports HTTP, you will need to modify your Info.plist file and add the following entry: - For more detailed info check this [article](https://cocoacasts.com/how-to-add-app-transport-security-exception-domains) + -## Android Expansion File Usage - -```javascript -// Within your render function, assuming you have a file called -// "background.mp4" in your expansion file. Just add your main and (if applicable) patch version -