Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Implement ios bufferConfig #1353

Open
wants to merge 8 commits into
base: master
Choose a base branch
from
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 5 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -379,17 +379,19 @@ bufferForPlaybackAfterRebufferMs | number | The default duration of media that m

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.

On iOS, only `bufferForPlaybackMs` and `bufferForPlaybackAfterRebufferMs` is supported. If these values are not specified, or no `bufferConfig` is supplied, then the default AVPlayer buffering is used. This behaviour tries to determine whether the video item is likely to play through given the current buffer rate, and if so, the video starts to play.

Example with default values:
```
bufferConfig={{
minBufferMs: 15000,
maxBufferMs: 50000,
minBufferMs: 15000, // not supported on iOS
maxBufferMs: 50000, // not supported on iOS
bufferForPlaybackMs: 2500,
bufferForPlaybackAfterRebufferMs: 5000
}}
```

Platforms: Android ExoPlayer
Platforms: Android ExoPlayer, iOS

#### currentPlaybackTime
When playing an HLS live stream with a `EXT-X-PROGRAM-DATE-TIME` tag configured, then this property will contain the epoch value in msec.
Expand Down
67 changes: 55 additions & 12 deletions ios/Video/RCTVideo.m
Original file line number Diff line number Diff line change
Expand Up @@ -10,6 +10,7 @@
static NSString *const playbackLikelyToKeepUpKeyPath = @"playbackLikelyToKeepUp";
static NSString *const playbackBufferEmptyKeyPath = @"playbackBufferEmpty";
static NSString *const readyForDisplayKeyPath = @"readyForDisplay";
static NSString *const loadedTimeRangesKeyPath = @"loadedTimeRanges";
static NSString *const playbackRate = @"rate";
static NSString *const timedMetadata = @"timedMetadata";
static NSString *const externalPlaybackActive = @"externalPlaybackActive";
Expand Down Expand Up @@ -54,6 +55,11 @@ @implementation RCTVideo
Float64 _progressUpdateInterval;
BOOL _controls;
id _timeObserver;

/* For keeping track of buffer states */
BOOL _playbackStarted;
BOOL _seeked;
Float64 _previousTime;

/* Keep track of any modifiers, need to be applied after each play */
float _volume;
Expand All @@ -68,6 +74,7 @@ @implementation RCTVideo
NSArray * _textTracks;
NSDictionary * _selectedTextTrack;
NSDictionary * _selectedAudioTrack;
NSDictionary * _bufferConfig;
BOOL _playbackStalled;
BOOL _playInBackground;
BOOL _preventsDisplaySleepDuringVideoPlayback;
Expand Down Expand Up @@ -117,6 +124,10 @@ - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
_preferredForwardBufferDuration = 0.0f;
_allowsExternalPlayback = YES;
_playWhenInactive = false;

_playbackStarted = NO;
_seeked = NO;
_previousTime = 0.0;
_pictureInPicture = false;
_ignoreSilentSwitch = @"inherit"; // inherit, ignore, obey
_mixWithOthers = @"inherit"; // inherit, mix, duck
Expand Down Expand Up @@ -281,16 +292,24 @@ - (void)sendProgressUpdate

[[NSNotificationCenter defaultCenter] postNotificationName:@"RCTVideo_progress" object:nil userInfo:@{@"progress": [NSNumber numberWithDouble: currentTimeSecs / duration]}];

if( currentTimeSecs >= 0 && self.onVideoProgress) {
self.onVideoProgress(@{
@"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(currentTime)],
@"playableDuration": [self calculatePlayableDuration],
@"atValue": [NSNumber numberWithLongLong:currentTime.value],
@"atTimescale": [NSNumber numberWithInt:currentTime.timescale],
@"currentPlaybackTime": [NSNumber numberWithLongLong:[@(floor([currentPlaybackTime timeIntervalSince1970] * 1000)) longLongValue]],
@"target": self.reactTag,
@"seekableDuration": [self calculateSeekableDuration],
});
if(currentTimeSecs >= 0) {
_playbackStarted = YES;
if (self.onVideoProgress) {
self.onVideoProgress(@{
@"currentTime": [NSNumber numberWithFloat:CMTimeGetSeconds(currentTime)],
@"playableDuration": [self calculatePlayableDuration],
@"atValue": [NSNumber numberWithLongLong:currentTime.value],
@"atTimescale": [NSNumber numberWithInt:currentTime.timescale],
@"currentPlaybackTime": [NSNumber numberWithLongLong:[@(floor([currentPlaybackTime timeIntervalSince1970] * 1000)) longLongValue]],
@"target": self.reactTag,
@"seekableDuration": [self calculateSeekableDuration],
});
}
if (_previousTime != currentTimeSecs) {
// video has progressed
_seeked = NO; // seeked has completed and video has enough data in buffer to play again
}
_previousTime = currentTimeSecs;
}
}

Expand Down Expand Up @@ -334,6 +353,7 @@ - (void)addPlayerItemObservers
[_playerItem addObserver:self forKeyPath:statusKeyPath options:0 context:nil];
[_playerItem addObserver:self forKeyPath:playbackBufferEmptyKeyPath options:0 context:nil];
[_playerItem addObserver:self forKeyPath:playbackLikelyToKeepUpKeyPath options:0 context:nil];
[_playerItem addObserver:self forKeyPath:loadedTimeRangesKeyPath options:0 context:nil];
[_playerItem addObserver:self forKeyPath:timedMetadata options:NSKeyValueObservingOptionNew context:nil];
_playerItemObserversSet = YES;
}
Expand All @@ -347,6 +367,7 @@ - (void)removePlayerItemObservers
[_playerItem removeObserver:self forKeyPath:statusKeyPath];
[_playerItem removeObserver:self forKeyPath:playbackBufferEmptyKeyPath];
[_playerItem removeObserver:self forKeyPath:playbackLikelyToKeepUpKeyPath];
[_playerItem removeObserver:self forKeyPath:loadedTimeRangesKeyPath];
[_playerItem removeObserver:self forKeyPath:timedMetadata];
_playerItemObserversSet = NO;
}
Expand Down Expand Up @@ -715,6 +736,23 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N
}
_playerBufferEmpty = NO;
self.onVideoBuffer(@{@"isBuffering": @(NO), @"target": self.reactTag});
} else if ([keyPath isEqualToString:loadedTimeRangesKeyPath]) {
if (_bufferConfig) {
double buffered = [[self calculatePlayableDuration] doubleValue] - [[NSNumber numberWithFloat:CMTimeGetSeconds(_player.currentTime)] doubleValue];
double threshold = 0.0;
if (_bufferConfig[@"bufferForPlaybackAfterRebufferMs"]) {
double playbackAfterRebufferMs = [_bufferConfig[@"bufferForPlaybackAfterRebufferMs"] doubleValue];
threshold = playbackAfterRebufferMs / 1000; // default to playbackAfterRebufferMs
}
if ((!_playbackStarted || _seeked) && _bufferConfig[@"bufferForPlaybackMs"]) {
// video is yet to start playback, or user has interrupted video with a seek event
double bufferForPlaybackMs = [_bufferConfig[@"bufferForPlaybackMs"] doubleValue];
threshold = bufferForPlaybackMs / 1000; // bufferForPlaybackMs
}
if (threshold > 0.0 && buffered >= threshold && !_paused) {
[_player playImmediatelyAtRate:_rate];
}
}
}
} else if (object == _player) {
if([keyPath isEqualToString:playbackRate]) {
Expand Down Expand Up @@ -1012,7 +1050,7 @@ - (void)setSeek:(NSDictionary *)info
@"target": self.reactTag});
}
}];

_seeked = YES;
_pendingSeek = false;
}

Expand Down Expand Up @@ -1070,12 +1108,14 @@ - (void)applyModifiers
[_player setMuted:NO];
}

[self setBufferConfig:_bufferConfig];

if (@available(iOS 12.0, *)) {
self->_player.preventsDisplaySleepDuringVideoPlayback = _preventsDisplaySleepDuringVideoPlayback;
} else {
// Fallback on earlier versions
}

[self setMaxBitRate:_maxBitRate];
[self setSelectedAudioTrack:_selectedAudioTrack];
[self setSelectedTextTrack:_selectedTextTrack];
Expand Down Expand Up @@ -1134,6 +1174,9 @@ - (void)setMediaSelectionTrackForCharacteristic:(AVMediaCharacteristic)character
// If a match isn't found, option will be nil and text tracks will be disabled
[_player.currentItem selectMediaOption:mediaOption inMediaSelectionGroup:group];
}
- (void)setBufferConfig:(NSDictionary *)bufferConfig {
_bufferConfig = bufferConfig;
}

- (void)setSelectedAudioTrack:(NSDictionary *)selectedAudioTrack {
_selectedAudioTrack = selectedAudioTrack;
Expand Down
1 change: 1 addition & 0 deletions ios/Video/RCTVideoManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -48,6 +48,7 @@ - (dispatch_queue_t)methodQueue
RCT_EXPORT_VIEW_PROPERTY(filter, NSString);
RCT_EXPORT_VIEW_PROPERTY(filterEnabled, BOOL);
RCT_EXPORT_VIEW_PROPERTY(progressUpdateInterval, float);
RCT_EXPORT_VIEW_PROPERTY(bufferConfig, NSDictionary);
RCT_EXPORT_VIEW_PROPERTY(restoreUserInterfaceForPIPStopCompletionHandler, BOOL);
/* Should support: onLoadStart, onLoad, and onError to stay consistent with Image */
RCT_EXPORT_VIEW_PROPERTY(onVideoLoadStart, RCTDirectEventBlock);
Expand Down