diff --git a/CHANGELOG.md b/CHANGELOG.md index 88b60563..2d1200cf 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -1,9 +1,17 @@ ## Changelog ### Next Version + +* Support video cachging for iOS ([#955](https://github.com/react-native-community/react-native-video/pull/955)) + +### Version 3.2.0 * Basic fullscreen support for Android MediaPlayer [#1138](https://github.com/react-native-community/react-native-video/pull/1138) * Simplify default Android SDK code [#1145](https://github.com/react-native-community/react-native-video/pull/1145) [#1146](https://github.com/react-native-community/react-native-video/pull/1146) -* Support video cachging for iOS ([#955](https://github.com/react-native-community/react-native-video/pull/955)) +* Various iOS sideloaded text track fixes [#1157](https://github.com/react-native-community/react-native-video/pull/1157) +* Fix #1150 where assets with bundled assets don't work on iOS in release mode [#1162](https://github.com/react-native-community/react-native-video/pull/1162) +* Support configuring the buffer on Android ExoPlayer [#1160](https://github.com/react-native-community/react-native-video/pull/1160) +* Prevent sleep from sleeping while videos are playing on Android MediaPlayer [#1117](https://github.com/react-native-community/react-native-video/pull/1117) +* Update NewtonSoft JSON to match react-native-windows version [#1169](https://github.com/react-native-community/react-native-video/pull/1169) ### Version 3.1.0 * Support sidecar text tracks on iOS [#1109](https://github.com/react-native-community/react-native-video/pull/1109) diff --git a/README.md b/README.md index 0da8c54d..b77100ee 100644 --- a/README.md +++ b/README.md @@ -236,6 +236,7 @@ var styles = StyleSheet.create({ ### Configurable props * [allowsExternalPlayback](#allowsexternalplayback) * [audioOnly](#audioonly) +* [bufferConfig](#bufferconfig) * [ignoreSilentSwitch](#ignoresilentswitch) * [muted](#muted) * [paused](#paused) @@ -288,6 +289,30 @@ For this to work, the poster prop must be set. Platforms: all +#### bufferConfig +Adjust the buffer settings. This prop takes an object with one or more of the properties listed below. + +Property | Type | Description +--- | --- | --- +minBufferMs | number | The default minimum duration of media that the player will attempt to ensure is buffered at all times, in milliseconds. +maxBufferMs | number | The default maximum duration of media that the player will attempt to buffer, in milliseconds. +bufferForPlaybackMs | number | The default duration of media that must be buffered for playback to start or resume following a user action such as a seek, in milliseconds. +playbackAfterRebufferMs | number | The default duration of media that must be buffered for playback to resume after a rebuffer, in milliseconds. A rebuffer is defined to be caused by buffer depletion rather than a user action. + +This prop should only be set when you are setting the source, changing it after the media is loaded will cause it to be reloaded. + +Example with default values: +``` +bufferConfig={{ + minBufferMs: 15000, + maxBufferMs: 50000, + bufferForPlaybackMs: 2500, + bufferForPlaybackAfterRebufferMs: 5000 +}} +``` + +Platforms: Android ExoPlayer + #### ignoreSilentSwitch Controls the iOS silent switch behavior * **"inherit" (default)** - Use the default AVPlayer behavior diff --git a/Video.js b/Video.js index f32725f3..5c6cd1d3 100644 --- a/Video.js +++ b/Video.js @@ -345,6 +345,12 @@ Video.propTypes = { paused: PropTypes.bool, muted: PropTypes.bool, volume: PropTypes.number, + bufferConfig: PropTypes.shape({ + minBufferMs: PropTypes.number, + maxBufferMs: PropTypes.number, + bufferForPlaybackMs: PropTypes.number, + bufferForPlaybackAfterRebufferMs: PropTypes.number, + }), stereoPan: PropTypes.number, rate: PropTypes.number, playInBackground: PropTypes.bool, 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 487da343..9ef099cf 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java @@ -58,6 +58,7 @@ import com.google.android.exoplayer2.trackselection.MappingTrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.upstream.DataSource; +import com.google.android.exoplayer2.upstream.DefaultAllocator; import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.Util; @@ -109,6 +110,11 @@ class ReactExoplayerView extends FrameLayout implements private boolean isBuffering; private float rate = 1f; + private int minBufferMs = DefaultLoadControl.DEFAULT_MIN_BUFFER_MS; + private int maxBufferMs = DefaultLoadControl.DEFAULT_MAX_BUFFER_MS; + private int bufferForPlaybackMs = DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS; + private int bufferForPlaybackAfterRebufferMs = DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS; + // Props from React private Uri srcUri; private String extension; @@ -234,7 +240,9 @@ class ReactExoplayerView extends FrameLayout implements if (player == null) { TrackSelection.Factory videoTrackSelectionFactory = new AdaptiveTrackSelection.Factory(BANDWIDTH_METER); trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory); - player = ExoPlayerFactory.newSimpleInstance(getContext(), trackSelector, new DefaultLoadControl()); + DefaultAllocator allocator = new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE); + DefaultLoadControl defaultLoadControl = new DefaultLoadControl(allocator, minBufferMs, maxBufferMs, bufferForPlaybackMs, bufferForPlaybackAfterRebufferMs, -1, true); + player = ExoPlayerFactory.newSimpleInstance(getContext(), trackSelector, defaultLoadControl); player.addListener(this); player.setMetadataOutput(this); exoPlayerView.setPlayer(player); @@ -320,7 +328,6 @@ class ReactExoplayerView extends FrameLayout implements private void releasePlayer() { if (player != null) { - isPaused = player.getPlayWhenReady(); updateResumePosition(); player.release(); player.setMetadataOutput(null); @@ -931,4 +938,13 @@ class ReactExoplayerView extends FrameLayout implements public void setUseTextureView(boolean useTextureView) { exoPlayerView.setUseTextureView(useTextureView); } + + public void setBufferConfig(int newMinBufferMs, int newMaxBufferMs, int newBufferForPlaybackMs, int newBufferForPlaybackAfterRebufferMs) { + minBufferMs = newMinBufferMs; + maxBufferMs = newMaxBufferMs; + bufferForPlaybackMs = newBufferForPlaybackMs; + bufferForPlaybackAfterRebufferMs = newBufferForPlaybackAfterRebufferMs; + releasePlayer(); + initializePlayer(); + } } 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 fbc8a9ad..9ead70ce 100644 --- a/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java +++ b/android-exoplayer/src/main/java/com/brentvatne/exoplayer/ReactExoplayerViewManager.java @@ -11,6 +11,7 @@ import com.facebook.react.common.MapBuilder; import com.facebook.react.uimanager.ThemedReactContext; import com.facebook.react.uimanager.ViewGroupManager; import com.facebook.react.uimanager.annotations.ReactProp; +import com.google.android.exoplayer2.DefaultLoadControl; import com.google.android.exoplayer2.upstream.RawResourceDataSource; import java.util.HashMap; @@ -38,6 +39,11 @@ public class ReactExoplayerViewManager extends ViewGroupManager0) { @@ -648,7 +648,7 @@ public class ReactVideoView extends ScalableVideoView implements MediaPlayer.OnP else { setSrc(mSrcUriString, mSrcType, mSrcIsNetwork, mSrcIsAsset, mRequestHeaders); } - + setKeepScreenOn(true); } @Override diff --git a/ios/Video/RCTVideo.m b/ios/Video/RCTVideo.m index 6842703c..7c98707b 100644 --- a/ios/Video/RCTVideo.m +++ b/ios/Video/RCTVideo.m @@ -313,7 +313,7 @@ static int const RCTVideoUnset = -1; [self removePlayerItemObservers]; dispatch_after(dispatch_time(DISPATCH_TIME_NOW, (int64_t)(0 * NSEC_PER_SEC)), dispatch_get_main_queue(), ^{ - + // perform on next run loop, otherwise other passed react-props may not be set [self playerItemForSource:source withCallback:^(AVPlayerItem * playerItem) { _playerItem = playerItem; @@ -353,8 +353,12 @@ static int const RCTVideoUnset = -1; } - (NSURL*) urlFilePath:(NSString*) filepath { - NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); + if ([filepath containsString:@"file://"]) { + return [NSURL URLWithString:filepath]; + } + // code to support local caching + NSArray *paths = NSSearchPathForDirectoriesInDomains(NSDocumentDirectory, NSUserDomainMask, YES); NSString* relativeFilePath = [filepath lastPathComponent]; // the file may be multiple levels below the documents directory NSArray* fileComponents = [filepath componentsSeparatedByString:@"Documents/"]; @@ -392,6 +396,7 @@ static int const RCTVideoUnset = -1; atTime:kCMTimeZero error:nil]; + NSMutableArray* validTextTracks = [NSMutableArray array]; for (int i = 0; i < _textTracks.count; ++i) { AVURLAsset *textURLAsset; NSString *textUri = [_textTracks objectAtIndex:i][@"uri"]; @@ -401,6 +406,8 @@ static int const RCTVideoUnset = -1; textURLAsset = [AVURLAsset URLAssetWithURL:[self urlFilePath:textUri] options:nil]; } AVAssetTrack *textTrackAsset = [textURLAsset tracksWithMediaType:AVMediaTypeText].firstObject; + if (!textTrackAsset) continue; // fix when there's no textTrackAsset + [validTextTracks addObject:[_textTracks objectAtIndex:i]]; AVMutableCompositionTrack *textCompTrack = [mixComposition addMutableTrackWithMediaType:AVMediaTypeText preferredTrackID:kCMPersistentTrackID_Invalid]; @@ -409,6 +416,9 @@ static int const RCTVideoUnset = -1; atTime:kCMTimeZero error:nil]; } + if (validTextTracks.count != _textTracks.count) { + [self setTextTracks:validTextTracks]; + } handler([AVPlayerItem playerItemWithAsset:mixComposition]); } @@ -892,7 +902,10 @@ static int const RCTVideoUnset = -1; selectedTrackIndex = index; } } - } else { // type "system" + } + + // in the situation that a selected text track is not available (eg. specifies a textTrack not available) + if (![type isEqualToString:@"disabled"] && selectedTrackIndex == RCTVideoUnset) { CFArrayRef captioningMediaCharacteristics = MACaptionAppearanceCopyPreferredCaptioningMediaCharacteristics(kMACaptionAppearanceDomainUser); NSArray *captionSettings = (__bridge NSArray*)captioningMediaCharacteristics; if ([captionSettings containsObject:AVMediaCharacteristicTranscribesSpokenDialogForAccessibility]) { diff --git a/package.json b/package.json index 998f1626..3051b567 100644 --- a/package.json +++ b/package.json @@ -1,6 +1,6 @@ { "name": "react-native-video", - "version": "3.1.0", + "version": "3.2.0", "description": "A