Expose currentPlaybackTime when live stream video (#1944)

* 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 <anu.bansal@curefit.com>
Co-authored-by: Sivakumar J <sivakumar@curefit.com>
Co-authored-by: parikshit <parikshit@curefit.com>
Co-authored-by: anubansal92 <40559524+anubansal92@users.noreply.github.com>
Co-authored-by: Rishu Agrawal <rishu.agrawal@v.curefit.com>
Co-authored-by: rishu-curefit <54575330+rishu-curefit@users.noreply.github.com>
This commit is contained in:
Param Aggarwal 2020-05-15 12:55:19 +05:30 committed by GitHub
parent e3009c60e1
commit 0b914ef2b9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
7 changed files with 73 additions and 14 deletions

View File

@ -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) - 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) - 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) - 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 ### Version 5.1.0-alpha5

View File

@ -277,6 +277,7 @@ var styles = StyleSheet.create({
* [automaticallyWaitsToMinimizeStalling](#automaticallyWaitsToMinimizeStalling) * [automaticallyWaitsToMinimizeStalling](#automaticallyWaitsToMinimizeStalling)
* [bufferConfig](#bufferconfig) * [bufferConfig](#bufferconfig)
* [controls](#controls) * [controls](#controls)
* [currentPlaybackTime](#currentPlaybackTime)
* [disableFocus](#disableFocus) * [disableFocus](#disableFocus)
* [filter](#filter) * [filter](#filter)
* [filterEnabled](#filterEnabled) * [filterEnabled](#filterEnabled)
@ -297,6 +298,7 @@ var styles = StyleSheet.create({
* [playWhenInactive](#playwheninactive) * [playWhenInactive](#playwheninactive)
* [poster](#poster) * [poster](#poster)
* [posterResizeMode](#posterresizemode) * [posterResizeMode](#posterresizemode)
* [preferredForwardBufferDuration](#preferredForwardBufferDuration)
* [progressUpdateInterval](#progressupdateinterval) * [progressUpdateInterval](#progressupdateinterval)
* [rate](#rate) * [rate](#rate)
* [repeat](#repeat) * [repeat](#repeat)
@ -308,6 +310,7 @@ var styles = StyleSheet.create({
* [source](#source) * [source](#source)
* [stereoPan](#stereopan) * [stereoPan](#stereopan)
* [textTracks](#texttracks) * [textTracks](#texttracks)
* [trackId](#trackId)
* [useTextureView](#usetextureview) * [useTextureView](#usetextureview)
* [volume](#volume) * [volume](#volume)
@ -386,6 +389,11 @@ bufferConfig={{
Platforms: Android ExoPlayer 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 #### controls
Determines whether to show player controls. Determines whether to show player controls.
* ** false (default)** - Don't show player controls * ** false (default)** - Don't show player controls
@ -596,6 +604,13 @@ Determines how to resize the poster image when the frame doesn't match the raw v
Platforms: all 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 #### progressUpdateInterval
Delay in milliseconds between onProgress events in milliseconds. Delay in milliseconds between onProgress events in milliseconds.
@ -831,6 +846,11 @@ textTracks={[
Platforms: Android ExoPlayer, iOS 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 #### useTextureView
Controls whether to output to a TextureView or SurfaceView. Controls whether to output to a TextureView or SurfaceView.

View File

@ -479,6 +479,7 @@ Video.propTypes = {
rate: PropTypes.number, rate: PropTypes.number,
pictureInPicture: PropTypes.bool, pictureInPicture: PropTypes.bool,
playInBackground: PropTypes.bool, playInBackground: PropTypes.bool,
preferredForwardBufferDuration: PropTypes.number,
playWhenInactive: PropTypes.bool, playWhenInactive: PropTypes.bool,
ignoreSilentSwitch: PropTypes.oneOf(['ignore', 'obey']), ignoreSilentSwitch: PropTypes.oneOf(['ignore', 'obey']),
reportBandwidth: PropTypes.bool, reportBandwidth: PropTypes.bool,

View File

@ -64,6 +64,7 @@ import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DefaultAllocator; import com.google.android.exoplayer2.upstream.DefaultAllocator;
import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter; import com.google.android.exoplayer2.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.net.CookieHandler; import java.net.CookieHandler;
@ -161,7 +162,7 @@ class ReactExoplayerView extends FrameLayout implements
) { ) {
long pos = player.getCurrentPosition(); long pos = player.getCurrentPosition();
long bufferedDuration = player.getBufferedPercentage() * player.getDuration() / 100; 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); msg = obtainMessage(SHOW_PROGRESS);
sendMessageDelayed(msg, Math.round(mProgressUpdateInterval)); sendMessageDelayed(msg, Math.round(mProgressUpdateInterval));
} }
@ -169,6 +170,14 @@ class ReactExoplayerView extends FrameLayout implements
} }
} }
}; };
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) { public ReactExoplayerView(ThemedReactContext context, ReactExoplayerConfig config) {
super(context); super(context);
@ -257,7 +266,15 @@ class ReactExoplayerView extends FrameLayout implements
@Override @Override
public void onBandwidthSample(int elapsedMs, long bytes, long bitrate) { public void onBandwidthSample(int elapsedMs, long bytes, long bitrate) {
if (mReportBandwidth) { 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);
}
} }
} }
@ -749,8 +766,9 @@ class ReactExoplayerView extends FrameLayout implements
Format videoFormat = player.getVideoFormat(); Format videoFormat = player.getVideoFormat();
int width = videoFormat != null ? videoFormat.width : 0; int width = videoFormat != null ? videoFormat.width : 0;
int height = videoFormat != null ? videoFormat.height : 0; int height = videoFormat != null ? videoFormat.height : 0;
String trackId = videoFormat != null ? videoFormat.id : "-1";
eventEmitter.load(player.getDuration(), player.getCurrentPosition(), width, height, eventEmitter.load(player.getDuration(), player.getCurrentPosition(), width, height,
getAudioTrackInfo(), getTextTrackInfo(), getVideoTrackInfo()); getAudioTrackInfo(), getTextTrackInfo(), getVideoTrackInfo(), trackId);
} }
} }
@ -889,7 +907,7 @@ class ReactExoplayerView extends FrameLayout implements
@Override @Override
public void onPlayerError(ExoPlaybackException e) { public void onPlayerError(ExoPlaybackException e) {
String errorString = null; String errorString = "ExoPlaybackException type : " + e.type;
Exception ex = e; Exception ex = e;
if (e.type == ExoPlaybackException.TYPE_RENDERER) { if (e.type == ExoPlaybackException.TYPE_RENDERER) {
Exception cause = e.getRendererException(); Exception cause = e.getRendererException();
@ -914,12 +932,9 @@ class ReactExoplayerView extends FrameLayout implements
} }
} }
else if (e.type == ExoPlaybackException.TYPE_SOURCE) { else if (e.type == ExoPlaybackException.TYPE_SOURCE) {
ex = e.getSourceException();
errorString = getResources().getString(R.string.unrecognized_media_format); errorString = getResources().getString(R.string.unrecognized_media_format);
} }
if (errorString != null) { eventEmitter.error(errorString, ex);
eventEmitter.error(errorString, ex);
}
playerNeedsSource = true; playerNeedsSource = true;
if (isBehindLiveWindow(e)) { if (isBehindLiveWindow(e)) {
clearResumePosition(); clearResumePosition();
@ -930,12 +945,14 @@ class ReactExoplayerView extends FrameLayout implements
} }
private static boolean isBehindLiveWindow(ExoPlaybackException e) { private static boolean isBehindLiveWindow(ExoPlaybackException e) {
Log.e("ExoPlayer Exception", e.toString());
if (e.type != ExoPlaybackException.TYPE_SOURCE) { if (e.type != ExoPlaybackException.TYPE_SOURCE) {
return false; return false;
} }
Throwable cause = e.getSourceException(); Throwable cause = e.getSourceException();
while (cause != null) { while (cause != null) {
if (cause instanceof BehindLiveWindowException) { if (cause instanceof BehindLiveWindowException ||
cause instanceof HttpDataSource.HttpDataSourceException) {
return true; return true;
} }
cause = cause.getCause(); cause = cause.getCause();

View File

@ -107,8 +107,10 @@ class VideoEventEmitter {
private static final String EVENT_PROP_PLAYABLE_DURATION = "playableDuration"; private static final String EVENT_PROP_PLAYABLE_DURATION = "playableDuration";
private static final String EVENT_PROP_SEEKABLE_DURATION = "seekableDuration"; 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_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_SEEK_TIME = "seekTime";
private static final String EVENT_PROP_NATURAL_SIZE = "naturalSize"; 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_WIDTH = "width";
private static final String EVENT_PROP_HEIGHT = "height"; private static final String EVENT_PROP_HEIGHT = "height";
private static final String EVENT_PROP_ORIENTATION = "orientation"; private static final String EVENT_PROP_ORIENTATION = "orientation";
@ -137,7 +139,7 @@ class VideoEventEmitter {
} }
void load(double duration, double currentPosition, int videoWidth, int videoHeight, 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(); WritableMap event = Arguments.createMap();
event.putDouble(EVENT_PROP_DURATION, duration / 1000D); event.putDouble(EVENT_PROP_DURATION, duration / 1000D);
event.putDouble(EVENT_PROP_CURRENT_TIME, currentPosition / 1000D); event.putDouble(EVENT_PROP_CURRENT_TIME, currentPosition / 1000D);
@ -151,7 +153,7 @@ class VideoEventEmitter {
naturalSize.putString(EVENT_PROP_ORIENTATION, "portrait"); naturalSize.putString(EVENT_PROP_ORIENTATION, "portrait");
} }
event.putMap(EVENT_PROP_NATURAL_SIZE, naturalSize); 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_VIDEO_TRACKS, videoTracks);
event.putArray(EVENT_PROP_AUDIO_TRACKS, audioTracks); event.putArray(EVENT_PROP_AUDIO_TRACKS, audioTracks);
event.putArray(EVENT_PROP_TEXT_TRACKS, textTracks); event.putArray(EVENT_PROP_TEXT_TRACKS, textTracks);
@ -168,17 +170,21 @@ class VideoEventEmitter {
receiveEvent(EVENT_LOAD, event); 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(); WritableMap event = Arguments.createMap();
event.putDouble(EVENT_PROP_CURRENT_TIME, currentPosition / 1000D); event.putDouble(EVENT_PROP_CURRENT_TIME, currentPosition / 1000D);
event.putDouble(EVENT_PROP_PLAYABLE_DURATION, bufferedDuration / 1000D); event.putDouble(EVENT_PROP_PLAYABLE_DURATION, bufferedDuration / 1000D);
event.putDouble(EVENT_PROP_SEEKABLE_DURATION, seekableDuration / 1000D); event.putDouble(EVENT_PROP_SEEKABLE_DURATION, seekableDuration / 1000D);
event.putDouble(EVENT_PROP_CURRENT_PLAYBACK_TIME, currentPlaybackTime);
receiveEvent(EVENT_PROGRESS, event); receiveEvent(EVENT_PROGRESS, event);
} }
void bandwidthReport(double bitRateEstimate) { void bandwidthReport(double bitRateEstimate, int height, int width, String id) {
WritableMap event = Arguments.createMap(); WritableMap event = Arguments.createMap();
event.putDouble(EVENT_PROP_BITRATE, bitRateEstimate); 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); receiveEvent(EVENT_BANDWIDTH, event);
} }
@ -226,7 +232,7 @@ class VideoEventEmitter {
void error(String errorString, Exception exception) { void error(String errorString, Exception exception) {
WritableMap error = Arguments.createMap(); WritableMap error = Arguments.createMap();
error.putString(EVENT_PROP_ERROR_STRING, errorString); 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(); WritableMap event = Arguments.createMap();
event.putMap(EVENT_PROP_ERROR, error); event.putMap(EVENT_PROP_ERROR, error);
receiveEvent(EVENT_ERROR, event); receiveEvent(EVENT_ERROR, event);

View File

@ -64,6 +64,7 @@ static int const RCTVideoUnset = -1;
NSDictionary * _selectedAudioTrack; NSDictionary * _selectedAudioTrack;
BOOL _playbackStalled; BOOL _playbackStalled;
BOOL _playInBackground; BOOL _playInBackground;
float _preferredForwardBufferDuration;
BOOL _playWhenInactive; BOOL _playWhenInactive;
BOOL _pictureInPicture; BOOL _pictureInPicture;
NSString * _ignoreSilentSwitch; NSString * _ignoreSilentSwitch;
@ -105,6 +106,7 @@ static int const RCTVideoUnset = -1;
_controls = NO; _controls = NO;
_playerBufferEmpty = YES; _playerBufferEmpty = YES;
_playInBackground = false; _playInBackground = false;
_preferredForwardBufferDuration = 0.0f;
_allowsExternalPlayback = YES; _allowsExternalPlayback = YES;
_playWhenInactive = false; _playWhenInactive = false;
_pictureInPicture = false; _pictureInPicture = false;
@ -265,6 +267,7 @@ static int const RCTVideoUnset = -1;
} }
CMTime currentTime = _player.currentTime; CMTime currentTime = _player.currentTime;
NSDate *currentPlaybackTime = _player.currentItem.currentDate;
const Float64 duration = CMTimeGetSeconds(playerDuration); const Float64 duration = CMTimeGetSeconds(playerDuration);
const Float64 currentTimeSecs = CMTimeGetSeconds(currentTime); const Float64 currentTimeSecs = CMTimeGetSeconds(currentTime);
@ -276,6 +279,7 @@ static int const RCTVideoUnset = -1;
@"playableDuration": [self calculatePlayableDuration], @"playableDuration": [self calculatePlayableDuration],
@"atValue": [NSNumber numberWithLongLong:currentTime.value], @"atValue": [NSNumber numberWithLongLong:currentTime.value],
@"atTimescale": [NSNumber numberWithInt:currentTime.timescale], @"atTimescale": [NSNumber numberWithInt:currentTime.timescale],
@"currentPlaybackTime": [NSNumber numberWithLongLong:[@(floor([currentPlaybackTime timeIntervalSince1970] * 1000)) longLongValue]],
@"target": self.reactTag, @"target": self.reactTag,
@"seekableDuration": [self calculateSeekableDuration], @"seekableDuration": [self calculateSeekableDuration],
}); });
@ -354,6 +358,7 @@ static int const RCTVideoUnset = -1;
// perform on next run loop, otherwise other passed react-props may not be set // perform on next run loop, otherwise other passed react-props may not be set
[self playerItemForSource:source withCallback:^(AVPlayerItem * playerItem) { [self playerItemForSource:source withCallback:^(AVPlayerItem * playerItem) {
_playerItem = playerItem; _playerItem = playerItem;
[self setPreferredForwardBufferDuration:_preferredForwardBufferDuration];
[self addPlayerItemObservers]; [self addPlayerItemObservers];
[self setFilter:_filterName]; [self setFilter:_filterName];
[self setMaxBitRate:_maxBitRate]; [self setMaxBitRate:_maxBitRate];
@ -995,6 +1000,12 @@ static int const RCTVideoUnset = -1;
_playerItem.preferredPeakBitRate = maxBitRate; _playerItem.preferredPeakBitRate = maxBitRate;
} }
- (void)setPreferredForwardBufferDuration:(float) preferredForwardBufferDuration
{
_preferredForwardBufferDuration = preferredForwardBufferDuration;
_playerItem.preferredForwardBufferDuration = preferredForwardBufferDuration;
}
- (void)setAutomaticallyWaitsToMinimizeStalling:(BOOL)waits - (void)setAutomaticallyWaitsToMinimizeStalling:(BOOL)waits
{ {
_automaticallyWaitsToMinimizeStalling = waits; _automaticallyWaitsToMinimizeStalling = waits;

View File

@ -32,6 +32,7 @@ RCT_EXPORT_VIEW_PROPERTY(muted, BOOL);
RCT_EXPORT_VIEW_PROPERTY(controls, BOOL); RCT_EXPORT_VIEW_PROPERTY(controls, BOOL);
RCT_EXPORT_VIEW_PROPERTY(volume, float); RCT_EXPORT_VIEW_PROPERTY(volume, float);
RCT_EXPORT_VIEW_PROPERTY(playInBackground, BOOL); RCT_EXPORT_VIEW_PROPERTY(playInBackground, BOOL);
RCT_EXPORT_VIEW_PROPERTY(preferredForwardBufferDuration, float);
RCT_EXPORT_VIEW_PROPERTY(playWhenInactive, BOOL); RCT_EXPORT_VIEW_PROPERTY(playWhenInactive, BOOL);
RCT_EXPORT_VIEW_PROPERTY(pictureInPicture, BOOL); RCT_EXPORT_VIEW_PROPERTY(pictureInPicture, BOOL);
RCT_EXPORT_VIEW_PROPERTY(ignoreSilentSwitch, NSString); RCT_EXPORT_VIEW_PROPERTY(ignoreSilentSwitch, NSString);