Skip to content

Commit

Permalink
Expose currentPlaybackTime when live stream video (TheWidlarzGroup#1944)
Browse files Browse the repository at this point in the history
* added trackId to exoplayer onLoad callback

* added trackInfo to bandwidth callback

* syntax fix

* syntax fix

* version update

* sending complete logcat for media playback exception ExoPlaybackException

* version bump

* package publish changes

* Live playback fix

* Version bump

* import fix

* version bump

* configurable preferredForwardBufferDuration

* configurable preferredForwardBufferDuration

* version update

* Exposing time

* exo player window current tsp

* return type

* Current window timestamp in epoch

* iOS changes

* version update

* Updated package.json

* updated version

* CurrentTime bug fix

* Updated package.json

* Updated currentPlaybackTime

* Updated currentPlayback logic

* Updated package.json

* Bug fix

* Added semicolon

* updated package.json

* Updated ReactVideoView

* updated verison

* Revert package.json changes

* Update ReactVideoView.java

* Use standard log

* Document preferredForwardBufferDuration (iOS)

* Document currentPlaybackTime

* Document trackId

* Update CHANGELOG.md

* Update CHANGELOG.md

* Update README.md

* Update CHANGELOG.md

Co-authored-by: anubansal <[email protected]>
Co-authored-by: Sivakumar J <[email protected]>
Co-authored-by: parikshit <[email protected]>
Co-authored-by: anubansal92 <[email protected]>
Co-authored-by: Rishu Agrawal <[email protected]>
Co-authored-by: rishu-curefit <[email protected]>
  • Loading branch information
7 people authored May 15, 2020
1 parent e3009c6 commit 0b914ef
Show file tree
Hide file tree
Showing 7 changed files with 73 additions and 14 deletions.
3 changes: 3 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,9 @@
- Fix video dimensions being undefined when playing HLS in ios. [#1992](https://github.com/react-native-community/react-native-video/pull/1992)
- Add support for audio mix with other apps for iOS. [#1978](https://github.com/react-native-community/react-native-video/pull/1978)
- Properly implement pending seek for iOS. [#1994](https://github.com/react-native-community/react-native-video/pull/1994)
- Added `preferredForwardBufferDuration` (iOS) - the duration the player should buffer media from the network ahead of the playhead to guard against playback disruption. (#1944)
- Added `currentPlaybackTime` (Android ExoPlayer, iOS) - 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. (#1944)
- Added `trackId` (Android ExoPlayer) - Configure an identifier for the video stream to link the playback context to the events emitted. (#1944)

### Version 5.1.0-alpha5

Expand Down
20 changes: 20 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -277,6 +277,7 @@ var styles = StyleSheet.create({
* [automaticallyWaitsToMinimizeStalling](#automaticallyWaitsToMinimizeStalling)
* [bufferConfig](#bufferconfig)
* [controls](#controls)
* [currentPlaybackTime](#currentPlaybackTime)
* [disableFocus](#disableFocus)
* [filter](#filter)
* [filterEnabled](#filterEnabled)
Expand All @@ -297,6 +298,7 @@ var styles = StyleSheet.create({
* [playWhenInactive](#playwheninactive)
* [poster](#poster)
* [posterResizeMode](#posterresizemode)
* [preferredForwardBufferDuration](#preferredForwardBufferDuration)
* [progressUpdateInterval](#progressupdateinterval)
* [rate](#rate)
* [repeat](#repeat)
Expand All @@ -308,6 +310,7 @@ var styles = StyleSheet.create({
* [source](#source)
* [stereoPan](#stereopan)
* [textTracks](#texttracks)
* [trackId](#trackId)
* [useTextureView](#usetextureview)
* [volume](#volume)

Expand Down Expand Up @@ -386,6 +389,11 @@ bufferConfig={{

Platforms: Android ExoPlayer

#### 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.

Platforms: Android ExoPlayer, iOS

#### controls
Determines whether to show player controls.
* ** false (default)** - Don't show player controls
Expand Down Expand Up @@ -596,6 +604,13 @@ Determines how to resize the poster image when the frame doesn't match the raw v

Platforms: all

#### preferredForwardBufferDuration
The duration the player should buffer media from the network ahead of the playhead to guard against playback disruption. Sets the [preferredForwardBufferDuration](https://developer.apple.com/documentation/avfoundation/avplayeritem/1643630-preferredforwardbufferduration) instance property on AVPlayerItem.

Default: 0

Platforms: iOS

#### progressUpdateInterval
Delay in milliseconds between onProgress events in milliseconds.

Expand Down Expand Up @@ -831,6 +846,11 @@ textTracks={[

Platforms: Android ExoPlayer, iOS

#### trackId
Configure an identifier for the video stream to link the playback context to the events emitted.

Platforms: Android ExoPlayer

#### useTextureView
Controls whether to output to a TextureView or SurfaceView.

Expand Down
1 change: 1 addition & 0 deletions Video.js
Original file line number Diff line number Diff line change
Expand Up @@ -479,6 +479,7 @@ Video.propTypes = {
rate: PropTypes.number,
pictureInPicture: PropTypes.bool,
playInBackground: PropTypes.bool,
preferredForwardBufferDuration: PropTypes.number,
playWhenInactive: PropTypes.bool,
ignoreSilentSwitch: PropTypes.oneOf(['ignore', 'obey']),
reportBandwidth: PropTypes.bool,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@
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.upstream.HttpDataSource;
import com.google.android.exoplayer2.util.Util;

import java.net.CookieHandler;
Expand Down Expand Up @@ -161,14 +162,22 @@ public void handleMessage(Message msg) {
) {
long pos = player.getCurrentPosition();
long bufferedDuration = player.getBufferedPercentage() * player.getDuration() / 100;
eventEmitter.progressChanged(pos, bufferedDuration, player.getDuration());
eventEmitter.progressChanged(pos, bufferedDuration, player.getDuration(), getPositionInFirstPeriodMsForCurrentWindow(pos));
msg = obtainMessage(SHOW_PROGRESS);
sendMessageDelayed(msg, Math.round(mProgressUpdateInterval));
}
break;
}
}
};

public double getPositionInFirstPeriodMsForCurrentWindow(long currentPosition) {
Timeline.Window window = new Timeline.Window();
if(!player.getCurrentTimeline().isEmpty()) {
player.getCurrentTimeline().getWindow(player.getCurrentWindowIndex(), window);
}
return window.windowStartTimeMs + currentPosition;
}

public ReactExoplayerView(ThemedReactContext context, ReactExoplayerConfig config) {
super(context);
Expand Down Expand Up @@ -257,7 +266,15 @@ public void cleanUpResources() {
@Override
public void onBandwidthSample(int elapsedMs, long bytes, long bitrate) {
if (mReportBandwidth) {
eventEmitter.bandwidthReport(bitrate);
if (player == null) {
eventEmitter.bandwidthReport(bitrate, 0, 0, "-1");
} else {
Format videoFormat = player.getVideoFormat();
int width = videoFormat != null ? videoFormat.width : 0;
int height = videoFormat != null ? videoFormat.height : 0;
String trackId = videoFormat != null ? videoFormat.id : "-1";
eventEmitter.bandwidthReport(bitrate, height, width, trackId);
}
}
}

Expand Down Expand Up @@ -749,8 +766,9 @@ private void videoLoaded() {
Format videoFormat = player.getVideoFormat();
int width = videoFormat != null ? videoFormat.width : 0;
int height = videoFormat != null ? videoFormat.height : 0;
String trackId = videoFormat != null ? videoFormat.id : "-1";
eventEmitter.load(player.getDuration(), player.getCurrentPosition(), width, height,
getAudioTrackInfo(), getTextTrackInfo(), getVideoTrackInfo());
getAudioTrackInfo(), getTextTrackInfo(), getVideoTrackInfo(), trackId);
}
}

Expand Down Expand Up @@ -889,7 +907,7 @@ public void onPlaybackParametersChanged(PlaybackParameters params) {

@Override
public void onPlayerError(ExoPlaybackException e) {
String errorString = null;
String errorString = "ExoPlaybackException type : " + e.type;
Exception ex = e;
if (e.type == ExoPlaybackException.TYPE_RENDERER) {
Exception cause = e.getRendererException();
Expand All @@ -914,12 +932,9 @@ public void onPlayerError(ExoPlaybackException e) {
}
}
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, ex);
playerNeedsSource = true;
if (isBehindLiveWindow(e)) {
clearResumePosition();
Expand All @@ -930,12 +945,14 @@ else if (e.type == ExoPlaybackException.TYPE_SOURCE) {
}

private static boolean isBehindLiveWindow(ExoPlaybackException e) {
Log.e("ExoPlayer Exception", e.toString());
if (e.type != ExoPlaybackException.TYPE_SOURCE) {
return false;
}
Throwable cause = e.getSourceException();
while (cause != null) {
if (cause instanceof BehindLiveWindowException) {
if (cause instanceof BehindLiveWindowException ||
cause instanceof HttpDataSource.HttpDataSourceException) {
return true;
}
cause = cause.getCause();
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -107,8 +107,10 @@ class VideoEventEmitter {
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_CURRENT_PLAYBACK_TIME = "currentPlaybackTime";
private static final String EVENT_PROP_SEEK_TIME = "seekTime";
private static final String EVENT_PROP_NATURAL_SIZE = "naturalSize";
private static final String EVENT_PROP_TRACK_ID = "trackId";
private static final String EVENT_PROP_WIDTH = "width";
private static final String EVENT_PROP_HEIGHT = "height";
private static final String EVENT_PROP_ORIENTATION = "orientation";
Expand Down Expand Up @@ -137,7 +139,7 @@ void loadStart() {
}

void load(double duration, double currentPosition, int videoWidth, int videoHeight,
WritableArray audioTracks, WritableArray textTracks, WritableArray videoTracks) {
WritableArray audioTracks, WritableArray textTracks, WritableArray videoTracks, String trackId) {
WritableMap event = Arguments.createMap();
event.putDouble(EVENT_PROP_DURATION, duration / 1000D);
event.putDouble(EVENT_PROP_CURRENT_TIME, currentPosition / 1000D);
Expand All @@ -151,7 +153,7 @@ void load(double duration, double currentPosition, int videoWidth, int videoHeig
naturalSize.putString(EVENT_PROP_ORIENTATION, "portrait");
}
event.putMap(EVENT_PROP_NATURAL_SIZE, naturalSize);

event.putString(EVENT_PROP_TRACK_ID, trackId);
event.putArray(EVENT_PROP_VIDEO_TRACKS, videoTracks);
event.putArray(EVENT_PROP_AUDIO_TRACKS, audioTracks);
event.putArray(EVENT_PROP_TEXT_TRACKS, textTracks);
Expand All @@ -168,17 +170,21 @@ void load(double duration, double currentPosition, int videoWidth, int videoHeig
receiveEvent(EVENT_LOAD, event);
}

void progressChanged(double currentPosition, double bufferedDuration, double seekableDuration) {
void progressChanged(double currentPosition, double bufferedDuration, double seekableDuration, double currentPlaybackTime) {
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);
event.putDouble(EVENT_PROP_CURRENT_PLAYBACK_TIME, currentPlaybackTime);
receiveEvent(EVENT_PROGRESS, event);
}

void bandwidthReport(double bitRateEstimate) {
void bandwidthReport(double bitRateEstimate, int height, int width, String id) {
WritableMap event = Arguments.createMap();
event.putDouble(EVENT_PROP_BITRATE, bitRateEstimate);
event.putInt(EVENT_PROP_WIDTH, width);
event.putInt(EVENT_PROP_HEIGHT, height);
event.putString(EVENT_PROP_TRACK_ID, id);
receiveEvent(EVENT_BANDWIDTH, event);
}

Expand Down Expand Up @@ -226,7 +232,7 @@ void fullscreenDidDismiss() {
void error(String errorString, Exception exception) {
WritableMap error = Arguments.createMap();
error.putString(EVENT_PROP_ERROR_STRING, errorString);
error.putString(EVENT_PROP_ERROR_EXCEPTION, exception.getMessage());
error.putString(EVENT_PROP_ERROR_EXCEPTION, exception.toString());
WritableMap event = Arguments.createMap();
event.putMap(EVENT_PROP_ERROR, error);
receiveEvent(EVENT_ERROR, event);
Expand Down
11 changes: 11 additions & 0 deletions ios/Video/RCTVideo.m
Original file line number Diff line number Diff line change
Expand Up @@ -64,6 +64,7 @@ @implementation RCTVideo
NSDictionary * _selectedAudioTrack;
BOOL _playbackStalled;
BOOL _playInBackground;
float _preferredForwardBufferDuration;
BOOL _playWhenInactive;
BOOL _pictureInPicture;
NSString * _ignoreSilentSwitch;
Expand Down Expand Up @@ -105,6 +106,7 @@ - (instancetype)initWithEventDispatcher:(RCTEventDispatcher *)eventDispatcher
_controls = NO;
_playerBufferEmpty = YES;
_playInBackground = false;
_preferredForwardBufferDuration = 0.0f;
_allowsExternalPlayback = YES;
_playWhenInactive = false;
_pictureInPicture = false;
Expand Down Expand Up @@ -265,6 +267,7 @@ - (void)sendProgressUpdate
}

CMTime currentTime = _player.currentTime;
NSDate *currentPlaybackTime = _player.currentItem.currentDate;
const Float64 duration = CMTimeGetSeconds(playerDuration);
const Float64 currentTimeSecs = CMTimeGetSeconds(currentTime);

Expand All @@ -276,6 +279,7 @@ - (void)sendProgressUpdate
@"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],
});
Expand Down Expand Up @@ -354,6 +358,7 @@ - (void)setSrc:(NSDictionary *)source
// perform on next run loop, otherwise other passed react-props may not be set
[self playerItemForSource:source withCallback:^(AVPlayerItem * playerItem) {
_playerItem = playerItem;
[self setPreferredForwardBufferDuration:_preferredForwardBufferDuration];
[self addPlayerItemObservers];
[self setFilter:_filterName];
[self setMaxBitRate:_maxBitRate];
Expand Down Expand Up @@ -995,6 +1000,12 @@ - (void)setMaxBitRate:(float) maxBitRate {
_playerItem.preferredPeakBitRate = maxBitRate;
}

- (void)setPreferredForwardBufferDuration:(float) preferredForwardBufferDuration
{
_preferredForwardBufferDuration = preferredForwardBufferDuration;
_playerItem.preferredForwardBufferDuration = preferredForwardBufferDuration;
}

- (void)setAutomaticallyWaitsToMinimizeStalling:(BOOL)waits
{
_automaticallyWaitsToMinimizeStalling = waits;
Expand Down
1 change: 1 addition & 0 deletions ios/Video/RCTVideoManager.m
Original file line number Diff line number Diff line change
Expand Up @@ -32,6 +32,7 @@ - (dispatch_queue_t)methodQueue
RCT_EXPORT_VIEW_PROPERTY(controls, BOOL);
RCT_EXPORT_VIEW_PROPERTY(volume, float);
RCT_EXPORT_VIEW_PROPERTY(playInBackground, BOOL);
RCT_EXPORT_VIEW_PROPERTY(preferredForwardBufferDuration, float);
RCT_EXPORT_VIEW_PROPERTY(playWhenInactive, BOOL);
RCT_EXPORT_VIEW_PROPERTY(pictureInPicture, BOOL);
RCT_EXPORT_VIEW_PROPERTY(ignoreSilentSwitch, NSString);
Expand Down

0 comments on commit 0b914ef

Please sign in to comment.