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

fix: improve Progress Data management #3297

Closed
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
19 changes: 19 additions & 0 deletions android/src/main/java/com/brentvatne/common/API/ProgressData.kt
Original file line number Diff line number Diff line change
@@ -0,0 +1,19 @@
package com.brentvatne.common.API

/*
* This class is a helper to build onProgress notification
*/

class ProgressData {
var position: Long
var bufferedDuration: Long
var duration: Long
var isOnLivePoint: Boolean

init {
position = -1
bufferedDuration = -1
duration = -1
isOnLivePoint = false
}
}
18 changes: 2 additions & 16 deletions android/src/main/java/com/brentvatne/common/API/ResizeMode.kt
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
package com.brentvatne.common.API

import androidx.annotation.IntDef
import java.lang.annotation.Retention
import java.lang.annotation.RetentionPolicy

internal object ResizeMode {
/**
Expand All @@ -29,20 +27,8 @@ internal object ResizeMode {
* Keeps the aspect ratio but takes up the view's size.
*/
const val RESIZE_MODE_CENTER_CROP = 4
@JvmStatic
@Mode
fun toResizeMode(ordinal: Int): Int {
return when (ordinal) {
RESIZE_MODE_FIXED_WIDTH -> RESIZE_MODE_FIXED_WIDTH
RESIZE_MODE_FIXED_HEIGHT -> RESIZE_MODE_FIXED_HEIGHT
RESIZE_MODE_FILL -> RESIZE_MODE_FILL
RESIZE_MODE_CENTER_CROP -> RESIZE_MODE_CENTER_CROP
RESIZE_MODE_FIT -> RESIZE_MODE_FIT
else -> RESIZE_MODE_FIT
}
}

@Retention(RetentionPolicy.SOURCE)
@Retention(AnnotationRetention.SOURCE)
@IntDef(
RESIZE_MODE_FIT,
RESIZE_MODE_FIXED_WIDTH,
Expand All @@ -51,4 +37,4 @@ internal object ResizeMode {
RESIZE_MODE_CENTER_CROP
)
annotation class Mode
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import com.brentvatne.common.API.TimedMetadata;
import com.brentvatne.common.API.Track;
import com.brentvatne.common.API.VideoTrack;
import com.brentvatne.common.toolbox.DebugLog;
import com.facebook.react.bridge.Arguments;
import com.facebook.react.bridge.ReactContext;
import com.facebook.react.bridge.WritableArray;
Expand All @@ -22,6 +23,8 @@ public class VideoEventEmitter {

private final RCTEventEmitter eventEmitter;

private final String TAG = "VideoEventEmitter";

private int viewId = View.NO_ID;

public VideoEventEmitter(ReactContext reactContext) {
Expand Down Expand Up @@ -411,11 +414,11 @@ public void audioBecomingNoisy() {
public void receiveAdEvent(String event) {
WritableMap map = Arguments.createMap();
map.putString("event", event);

receiveEvent(EVENT_ON_RECEIVE_AD_EVENT, map);
}

private void receiveEvent(@VideoEvents String type, WritableMap event) {
DebugLog.d(TAG,"send event viewId: " + viewId + " type: " + type + " " + (event == null ? "" : event.toString()) );
eventEmitter.receiveEvent(viewId, type, event);
}
}
171 changes: 162 additions & 9 deletions android/src/main/java/com/brentvatne/exoplayer/ReactExoplayerView.java
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@
import androidx.annotation.WorkerThread;
import androidx.activity.OnBackPressedCallback;

import com.brentvatne.common.API.ProgressData;
import com.brentvatne.common.API.ResizeMode;
import com.brentvatne.common.API.SubtitleStyle;
import com.brentvatne.common.API.TimedMetadata;
Expand Down Expand Up @@ -176,7 +177,6 @@ public class ReactExoplayerView extends FrameLayout implements
private float audioVolume = 1f;
private int minLoadRetryCount = 3;
private int maxBitRate = 0;
private long seekTime = C.TIME_UNSET;
private boolean hasDrmFailed = false;
private boolean isUsingContentResolution = false;
private boolean selectTrackWhenReady = false;
Expand Down Expand Up @@ -232,6 +232,94 @@ public class ReactExoplayerView extends FrameLayout implements
private long lastBufferDuration = -1;
private long lastDuration = -1;


// Seek handling
private long seekTime = C.TIME_UNSET;
private long nextSeekTime = C.TIME_UNSET;
private boolean waitForDiscontinuity = false;
private boolean waitForSeekProcessed = false;
private boolean waitForPlayWhenReady = false;
private boolean initialSeekInProgress = false;

public ProgressData getProgressData() {
ProgressData data = new ProgressData();
data.setPosition(getCurrentPosition());
data.setOnLivePoint(isOnLivePoint());
long bufferedDuration = data.getBufferedDuration();
if (bufferedDuration < 0) {
bufferedDuration = 0;
}
data.setBufferedDuration(bufferedDuration);
long duration = getDuration();
if (duration < 0) {
duration = 0;
}
data.setDuration(duration);
return data;
}

public boolean isOnLivePoint() {
if (player == null || !isOnLivePlaylist())
return false;

final Timeline.Window window = new Timeline.Window();
Timeline timeline = player.getCurrentTimeline();
if (timeline.isEmpty()) {
return false;
}
timeline.getWindow(player.getCurrentWindowIndex(), window);
long offsetFromDefaultPositionMs = window.getDefaultPositionMs() - player.getCurrentPosition();
if (window.getDefaultPositionMs() != 0 && offsetFromDefaultPositionMs < 7000) {
return true;
}
return false;
}

public long getBufferedDuration() {
if (player == null)
return 0;
if (isOnLivePlaylist()) {
return getDuration();
}
return player.getBufferedPercentage() * player.getDuration() / 100;
}

boolean isOnLivePlaylist() {
if (player != null) {
return player.isCurrentMediaItemLive();
}
return false;
}

public long getCurrentPosition() {
DebugLog.checkUIThread(TAG, "position");

if (player == null)
return 0;
if (isOnLivePlaylist()) {
// TODO on exoplayer 2.13 there is a new api getCurrentLiveOffset To be integrated once this version available
if (isOnLivePoint()) {
return player.getDuration();
} else {
return player.getCurrentPosition();
}
}
if (nextSeekTime != C.TIME_UNSET) {
// multiple seek in progress
return nextSeekTime;
} else if (seekTime != C.TIME_UNSET) {
// We are seeking
return seekTime;
}
return player.getCurrentPosition();
}

public long getDuration() {
if (player == null)
return 0;
return player.getDuration();
}

private final Handler progressHandler = new Handler(Looper.getMainLooper()) {
@Override
public void handleMessage(Message msg) {
Expand All @@ -251,7 +339,8 @@ public void handleMessage(Message msg) {
lastPos = pos;
lastBufferDuration = bufferedDuration;
lastDuration = duration;
eventEmitter.progressChanged(pos, bufferedDuration, player.getDuration(), getPositionInFirstPeriodMsForCurrentWindow(pos));
ProgressData data = getProgressData();
eventEmitter.progressChanged(lastPos, lastBufferDuration, lastBufferDuration, lastDuration);
}
msg = obtainMessage(SHOW_PROGRESS);
sendMessageDelayed(msg, Math.round(mProgressUpdateInterval));
Expand Down Expand Up @@ -716,11 +805,32 @@ private void initializePlayerSource(ReactExoplayerView self, DrmSessionManager d
}
}

boolean haveResumePosition = resumeWindow != C.INDEX_UNSET;
if (haveResumePosition) {
player.seekTo(resumeWindow, resumePosition);
seekTime = C.TIME_UNSET;
nextSeekTime = C.TIME_UNSET;
waitForSeekProcessed = false;
waitForDiscontinuity = false;
waitForPlayWhenReady = false;
initialSeekInProgress = false;

boolean needInitialSeek = false;

long startPosition = 0; // FIXME genericPlayerView.getStartPosition();
if (startPosition >= 0 || resumeWindow != C.INDEX_UNSET) {
needInitialSeek = true;
}
loadVideoStarted = true;
DebugLog.d(TAG, "player initialized");
if (needInitialSeek) {
boolean haveResumePosition = startPosition >= 0;
if (haveResumePosition) {
DebugLog.d(TAG, "resume position at " + startPosition);
seekTo(startPosition);
initialSeekInProgress = true;
}
}
player.prepare(mediaSource, !haveResumePosition, false);
player.prepare();
player.setMediaSource(mediaSource);

playerNeedsSource = false;

reLayout(exoPlayerView);
Expand Down Expand Up @@ -1078,6 +1188,10 @@ public void onEvents(@NonNull Player player, Player.Events events) {
boolean playWhenReady = player.getPlayWhenReady();
String text = "onStateChanged: playWhenReady=" + playWhenReady + ", playbackState=";
eventEmitter.playbackRateChange(playWhenReady && playbackState == ExoPlayer.STATE_READY ? 1.0f : 0.0f);
if (playWhenReady && playbackState == Player.STATE_READY) {
waitForPlayWhenReady = false;
tryNotifyEndOfSeek();
}
switch (playbackState) {
case Player.STATE_IDLE:
text += "idle";
Expand Down Expand Up @@ -1383,8 +1497,10 @@ public void onPositionDiscontinuity(@NonNull Player.PositionInfo oldPosition, @N
if (reason == Player.DISCONTINUITY_REASON_AUTO_TRANSITION
&& player.getRepeatMode() == Player.REPEAT_MODE_ONE) {
eventEmitter.end();
} else if (reason == Player.DISCONTINUITY_REASON_SEEK) {
waitForDiscontinuity = false;
tryNotifyEndOfSeek();
}

}

@Override
Expand Down Expand Up @@ -1871,9 +1987,46 @@ public void setVolumeModifier(float volume) {
}

public void seekTo(long positionMs) {
if (player != null) {
if (player != null && !waitForDiscontinuity && !waitForSeekProcessed && !waitForPlayWhenReady) {
seekTime = positionMs;
waitForDiscontinuity = true;
waitForSeekProcessed = true;
waitForPlayWhenReady = true;
player.seekTo(positionMs);
eventEmitter.seek(player.getCurrentPosition(), positionMs);
} else {
nextSeekTime = positionMs;
DebugLog.d(TAG, "don't seek now : nextSeekTime " + nextSeekTime + "ms");
}
}

void tryNotifyEndOfSeek() {
if (!waitForDiscontinuity && !waitForSeekProcessed && !waitForPlayWhenReady && seekTime != C.TIME_UNSET) {
long saveSeekTime = seekTime;
seekTime = C.TIME_UNSET;
if (nextSeekTime != C.TIME_UNSET) {
DebugLog.d(TAG, "seek buffered " + nextSeekTime);
seekTo(nextSeekTime);
nextSeekTime = C.TIME_UNSET;
} else if (player != null) {
DebugLog.d(TAG, "seek finish " + saveSeekTime + "ms");
eventEmitter.seek(player.getCurrentPosition(), saveSeekTime);
}
}
}

// Seeks are processed without delay. Listen to onPositionDiscontinuity(Player.PositionInfo, Player.PositionInfo, int) with reason DISCONTINUITY_REASON_SEEK instead.

@Override
public void onSeekProcessed() {
if (player != null) {
waitForSeekProcessed = false;
tryNotifyEndOfSeek();
if (initialSeekInProgress) {
initialSeekInProgress = false;
player.setPlayWhenReady(!isPaused);
// onStartPlayback();
videoLoaded();
}
}
}

Expand Down