1010static NSString *const playbackLikelyToKeepUpKeyPath = @" playbackLikelyToKeepUp" ;
1111static NSString *const playbackBufferEmptyKeyPath = @" playbackBufferEmpty" ;
1212static NSString *const readyForDisplayKeyPath = @" readyForDisplay" ;
13+ static NSString *const loadedTimeRangesKeyPath = @" loadedTimeRanges" ;
1314static NSString *const playbackRate = @" rate" ;
1415static NSString *const timedMetadata = @" timedMetadata" ;
1516
@@ -45,6 +46,11 @@ @implementation RCTVideo
4546 Float64 _progressUpdateInterval;
4647 BOOL _controls;
4748 id _timeObserver;
49+
50+ /* For keeping track of buffer states */
51+ BOOL _playbackStarted;
52+ BOOL _seeked;
53+ Float64 _previousTime;
4854
4955 /* Keep track of any modifiers, need to be applied after each play */
5056 float _volume;
@@ -53,6 +59,7 @@ @implementation RCTVideo
5359 BOOL _paused;
5460 BOOL _repeat;
5561 BOOL _allowsExternalPlayback;
62+ NSDictionary * _bufferConfig;
5663 NSArray * _textTracks;
5764 NSDictionary * _selectedTextTrack;
5865 NSDictionary * _selectedAudioTrack;
@@ -86,6 +93,9 @@ - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
8693 _allowsExternalPlayback = YES ;
8794 _playWhenInactive = false ;
8895 _ignoreSilentSwitch = @" inherit" ; // inherit, ignore, obey
96+ _playbackStarted = NO ;
97+ _seeked = NO ;
98+ _previousTime = 0.0 ;
8999 _videoCache = [RCTVideoCache sharedInstance ];
90100 [[NSNotificationCenter defaultCenter ] addObserver: self
91101 selector: @selector (applicationWillResignActive: )
@@ -236,16 +246,24 @@ - (void)sendProgressUpdate
236246
237247 [[NSNotificationCenter defaultCenter ] postNotificationName: @" RCTVideo_progress" object: nil userInfo: @{@" progress" : [NSNumber numberWithDouble: currentTimeSecs / duration]}];
238248
239- if ( currentTimeSecs >= 0 && self.onVideoProgress ) {
240- self.onVideoProgress (@{
241- @" currentTime" : [NSNumber numberWithFloat: CMTimeGetSeconds (currentTime)],
242- @" playableDuration" : [self calculatePlayableDuration ],
243- @" atValue" : [NSNumber numberWithLongLong: currentTime.value],
244- @" atTimescale" : [NSNumber numberWithInt: currentTime.timescale],
245- @" target" : self.reactTag ,
246- @" seekableDuration" : [self calculateSeekableDuration ],
247- });
249+ if ( currentTimeSecs >= 0 ) {
250+ _playbackStarted = YES ;
251+ if (self.onVideoProgress ) {
252+ self.onVideoProgress (@{
253+ @" currentTime" : [NSNumber numberWithFloat: CMTimeGetSeconds (currentTime)],
254+ @" playableDuration" : [self calculatePlayableDuration ],
255+ @" atValue" : [NSNumber numberWithLongLong: currentTime.value],
256+ @" atTimescale" : [NSNumber numberWithInt: currentTime.timescale],
257+ @" target" : self.reactTag ,
258+ @" seekableDuration" : [self calculateSeekableDuration ],
259+ });
260+ }
248261 }
262+ if (_previousTime != currentTimeSecs) {
263+ // video has progressed
264+ _seeked = NO ; // seeked has completed and video has enough data in buffer to play again
265+ }
266+ _previousTime = currentTimeSecs;
249267}
250268
251269/* !
@@ -288,6 +306,7 @@ - (void)addPlayerItemObservers
288306 [_playerItem addObserver: self forKeyPath: statusKeyPath options: 0 context: nil ];
289307 [_playerItem addObserver: self forKeyPath: playbackBufferEmptyKeyPath options: 0 context: nil ];
290308 [_playerItem addObserver: self forKeyPath: playbackLikelyToKeepUpKeyPath options: 0 context: nil ];
309+ [_playerItem addObserver: self forKeyPath: loadedTimeRangesKeyPath options: 0 context: nil ];
291310 [_playerItem addObserver: self forKeyPath: timedMetadata options: NSKeyValueObservingOptionNew context: nil ];
292311 _playerItemObserversSet = YES ;
293312}
@@ -301,6 +320,7 @@ - (void)removePlayerItemObservers
301320 [_playerItem removeObserver: self forKeyPath: statusKeyPath];
302321 [_playerItem removeObserver: self forKeyPath: playbackBufferEmptyKeyPath];
303322 [_playerItem removeObserver: self forKeyPath: playbackLikelyToKeepUpKeyPath];
323+ [_playerItem removeObserver: self forKeyPath: loadedTimeRangesKeyPath];
304324 [_playerItem removeObserver: self forKeyPath: timedMetadata];
305325 _playerItemObserversSet = NO ;
306326 }
@@ -329,13 +349,13 @@ - (void)setSrc:(NSDictionary *)source
329349 [_player removeObserver: self forKeyPath: playbackRate context: nil ];
330350 _playbackRateObserverRegistered = NO ;
331351 }
332-
352+
333353 _player = [AVPlayer playerWithPlayerItem: _playerItem];
334354 _player.actionAtItemEnd = AVPlayerActionAtItemEndNone;
335355
336356 [_player addObserver: self forKeyPath: playbackRate options: 0 context: nil ];
337357 _playbackRateObserverRegistered = YES ;
338-
358+
339359 [self addPlayerTimeObserver ];
340360
341361 // Perform on next run loop, otherwise onVideoLoadStart is nil
@@ -621,6 +641,23 @@ - (void)observeValueForKeyPath:(NSString *)keyPath ofObject:(id)object change:(N
621641 }
622642 _playerBufferEmpty = NO ;
623643 self.onVideoBuffer (@{@" isBuffering" : @(NO ), @" target" : self.reactTag });
644+ } else if ([keyPath isEqualToString: loadedTimeRangesKeyPath]) {
645+ if (_bufferConfig) {
646+ double buffered = [[self calculatePlayableDuration ] doubleValue ] - [[NSNumber numberWithFloat: CMTimeGetSeconds (_player.currentTime)] doubleValue ];
647+ double threshold = 0.0 ;
648+ if (_bufferConfig[@" bufferForPlaybackAfterRebufferMs" ]) {
649+ double playbackAfterRebufferMs = [_bufferConfig[@" bufferForPlaybackAfterRebufferMs" ] doubleValue ];
650+ threshold = playbackAfterRebufferMs / 1000 ; // default to playbackAfterRebufferMs
651+ }
652+ if ((!_playbackStarted || _seeked) && _bufferConfig[@" bufferForPlaybackMs" ]) {
653+ // video is yet to start playback, or user has interrupted video with a seek event
654+ double bufferForPlaybackMs = [_bufferConfig[@" bufferForPlaybackMs" ] doubleValue ];
655+ threshold = bufferForPlaybackMs / 1000 ; // bufferForPlaybackMs
656+ }
657+ if (threshold > 0.0 && buffered >= threshold && !_paused) {
658+ [_player playImmediatelyAtRate: 1 ];
659+ }
660+ }
624661 }
625662 } else if (object == _playerLayer) {
626663 if ([keyPath isEqualToString: readyForDisplayKeyPath] && [change objectForKey: NSKeyValueChangeNewKey ]) {
@@ -791,7 +828,7 @@ - (void)setSeek:(NSDictionary *)info
791828 @" target" : self.reactTag });
792829 }
793830 }];
794-
831+ _seeked = YES ;
795832 _pendingSeek = false ;
796833 }
797834
@@ -830,6 +867,7 @@ - (void)applyModifiers
830867 [_player setMuted: NO ];
831868 }
832869
870+ [self setBufferConfig: _bufferConfig];
833871 [self setSelectedAudioTrack: _selectedAudioTrack];
834872 [self setSelectedTextTrack: _selectedTextTrack];
835873 [self setResizeMode: _resizeMode];
@@ -888,6 +926,10 @@ - (void)setMediaSelectionTrackForCharacteristic:(AVMediaCharacteristic)character
888926 [_player.currentItem selectMediaOption: mediaOption inMediaSelectionGroup: group];
889927}
890928
929+ - (void )setBufferConfig : (NSDictionary *)bufferConfig {
930+ _bufferConfig = bufferConfig;
931+ }
932+
891933- (void )setSelectedAudioTrack : (NSDictionary *)selectedAudioTrack {
892934 _selectedAudioTrack = selectedAudioTrack;
893935 [self setMediaSelectionTrackForCharacteristic: AVMediaCharacteristicAudible
0 commit comments