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:
parent
e3009c60e1
commit
0b914ef2b9
@ -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
|
||||
|
||||
|
20
README.md
20
README.md
@ -277,6 +277,7 @@ var styles = StyleSheet.create({
|
||||
* [automaticallyWaitsToMinimizeStalling](#automaticallyWaitsToMinimizeStalling)
|
||||
* [bufferConfig](#bufferconfig)
|
||||
* [controls](#controls)
|
||||
* [currentPlaybackTime](#currentPlaybackTime)
|
||||
* [disableFocus](#disableFocus)
|
||||
* [filter](#filter)
|
||||
* [filterEnabled](#filterEnabled)
|
||||
@ -297,6 +298,7 @@ var styles = StyleSheet.create({
|
||||
* [playWhenInactive](#playwheninactive)
|
||||
* [poster](#poster)
|
||||
* [posterResizeMode](#posterresizemode)
|
||||
* [preferredForwardBufferDuration](#preferredForwardBufferDuration)
|
||||
* [progressUpdateInterval](#progressupdateinterval)
|
||||
* [rate](#rate)
|
||||
* [repeat](#repeat)
|
||||
@ -308,6 +310,7 @@ var styles = StyleSheet.create({
|
||||
* [source](#source)
|
||||
* [stereoPan](#stereopan)
|
||||
* [textTracks](#texttracks)
|
||||
* [trackId](#trackId)
|
||||
* [useTextureView](#usetextureview)
|
||||
* [volume](#volume)
|
||||
|
||||
@ -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
|
||||
@ -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.
|
||||
|
||||
@ -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.
|
||||
|
||||
|
1
Video.js
1
Video.js
@ -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,
|
||||
|
@ -64,6 +64,7 @@ import com.google.android.exoplayer2.upstream.BandwidthMeter;
|
||||
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;
|
||||
@ -161,7 +162,7 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
) {
|
||||
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));
|
||||
}
|
||||
@ -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) {
|
||||
super(context);
|
||||
@ -257,7 +266,15 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
@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);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@ -749,8 +766,9 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
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);
|
||||
}
|
||||
}
|
||||
|
||||
@ -889,7 +907,7 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
|
||||
@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();
|
||||
@ -914,12 +932,9 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
}
|
||||
}
|
||||
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();
|
||||
@ -930,12 +945,14 @@ class ReactExoplayerView extends FrameLayout implements
|
||||
}
|
||||
|
||||
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();
|
||||
|
@ -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";
|
||||
@ -137,7 +139,7 @@ class VideoEventEmitter {
|
||||
}
|
||||
|
||||
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);
|
||||
@ -151,7 +153,7 @@ class VideoEventEmitter {
|
||||
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);
|
||||
@ -168,17 +170,21 @@ class VideoEventEmitter {
|
||||
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);
|
||||
}
|
||||
|
||||
@ -226,7 +232,7 @@ class VideoEventEmitter {
|
||||
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);
|
||||
|
@ -64,6 +64,7 @@ static int const RCTVideoUnset = -1;
|
||||
NSDictionary * _selectedAudioTrack;
|
||||
BOOL _playbackStalled;
|
||||
BOOL _playInBackground;
|
||||
float _preferredForwardBufferDuration;
|
||||
BOOL _playWhenInactive;
|
||||
BOOL _pictureInPicture;
|
||||
NSString * _ignoreSilentSwitch;
|
||||
@ -105,6 +106,7 @@ static int const RCTVideoUnset = -1;
|
||||
_controls = NO;
|
||||
_playerBufferEmpty = YES;
|
||||
_playInBackground = false;
|
||||
_preferredForwardBufferDuration = 0.0f;
|
||||
_allowsExternalPlayback = YES;
|
||||
_playWhenInactive = false;
|
||||
_pictureInPicture = false;
|
||||
@ -265,6 +267,7 @@ static int const RCTVideoUnset = -1;
|
||||
}
|
||||
|
||||
CMTime currentTime = _player.currentTime;
|
||||
NSDate *currentPlaybackTime = _player.currentItem.currentDate;
|
||||
const Float64 duration = CMTimeGetSeconds(playerDuration);
|
||||
const Float64 currentTimeSecs = CMTimeGetSeconds(currentTime);
|
||||
|
||||
@ -276,6 +279,7 @@ static int const RCTVideoUnset = -1;
|
||||
@"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],
|
||||
});
|
||||
@ -354,6 +358,7 @@ static int const RCTVideoUnset = -1;
|
||||
// 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];
|
||||
@ -995,6 +1000,12 @@ static int const RCTVideoUnset = -1;
|
||||
_playerItem.preferredPeakBitRate = maxBitRate;
|
||||
}
|
||||
|
||||
- (void)setPreferredForwardBufferDuration:(float) preferredForwardBufferDuration
|
||||
{
|
||||
_preferredForwardBufferDuration = preferredForwardBufferDuration;
|
||||
_playerItem.preferredForwardBufferDuration = preferredForwardBufferDuration;
|
||||
}
|
||||
|
||||
- (void)setAutomaticallyWaitsToMinimizeStalling:(BOOL)waits
|
||||
{
|
||||
_automaticallyWaitsToMinimizeStalling = waits;
|
||||
|
@ -32,6 +32,7 @@ RCT_EXPORT_VIEW_PROPERTY(muted, BOOL);
|
||||
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);
|
||||
|
Loading…
Reference in New Issue
Block a user