Add option to send messages at end of stream.

This simplifies sending messages to the end of a stream. This was
already possible, but users needed to wait until the duration is known
before sending the message. This duration resolution can happen as part of the message position resolution.

PiperOrigin-RevId: 294626984
This commit is contained in:
tonihei 2020-02-12 10:57:37 +00:00 committed by Oliver Woodman
parent 7a8ab7ce68
commit 40101094ba
4 changed files with 96 additions and 34 deletions

View file

@ -20,6 +20,7 @@
* Move player message-related constants from `C` to `Renderer`, to avoid
having the constants class depend on player/renderer classes.
* Split out `common` and `extractor` submodules.
* Allow to explicitly send `PlayerMessage`s at the end of a stream.
* Add `DataSpec.Builder` and deprecate most `DataSpec` constructors.
* Add `DataSpec.customData` to allow applications to pass custom data through
`DataSource` chains.

View file

@ -2241,6 +2241,10 @@ import java.util.concurrent.atomic.AtomicBoolean;
Timeline.Period period) {
if (pendingMessageInfo.resolvedPeriodUid == null) {
// Position is still unresolved. Try to find window in new timeline.
long requestPositionUs =
pendingMessageInfo.message.getPositionMs() == C.TIME_END_OF_SOURCE
? C.TIME_UNSET
: C.msToUs(pendingMessageInfo.message.getPositionMs());
@Nullable
Pair<Object, Long> periodPosition =
resolveSeekPosition(
@ -2248,7 +2252,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
new SeekPosition(
pendingMessageInfo.message.getTimeline(),
pendingMessageInfo.message.getWindowIndex(),
C.msToUs(pendingMessageInfo.message.getPositionMs())),
requestPositionUs),
/* trySubsequentPeriods= */ false,
repeatMode,
shuffleModeEnabled,
@ -2261,32 +2265,62 @@ import java.util.concurrent.atomic.AtomicBoolean;
/* periodIndex= */ newTimeline.getIndexOfPeriod(periodPosition.first),
/* periodTimeUs= */ periodPosition.second,
/* periodUid= */ periodPosition.first);
} else {
// Position has been resolved for a previous timeline. Try to find the updated period index.
int index = newTimeline.getIndexOfPeriod(pendingMessageInfo.resolvedPeriodUid);
if (index == C.INDEX_UNSET) {
return false;
}
pendingMessageInfo.resolvedPeriodIndex = index;
previousTimeline.getPeriodByUid(pendingMessageInfo.resolvedPeriodUid, period);
if (previousTimeline.getWindow(period.windowIndex, window).isPlaceholder) {
// The position needs to be re-resolved because the window in the previous timeline wasn't
// fully prepared.
long windowPositionUs =
pendingMessageInfo.resolvedPeriodTimeUs + period.getPositionInWindowUs();
int windowIndex =
newTimeline.getPeriodByUid(pendingMessageInfo.resolvedPeriodUid, period).windowIndex;
Pair<Object, Long> periodPosition =
newTimeline.getPeriodPosition(window, period, windowIndex, windowPositionUs);
pendingMessageInfo.setResolvedPosition(
/* periodIndex= */ newTimeline.getIndexOfPeriod(periodPosition.first),
/* periodTimeUs= */ periodPosition.second,
/* periodUid= */ periodPosition.first);
if (pendingMessageInfo.message.getPositionMs() == C.TIME_END_OF_SOURCE) {
resolvePendingMessageEndOfStreamPosition(newTimeline, pendingMessageInfo, window, period);
}
return true;
}
// Position has been resolved for a previous timeline. Try to find the updated period index.
int index = newTimeline.getIndexOfPeriod(pendingMessageInfo.resolvedPeriodUid);
if (index == C.INDEX_UNSET) {
return false;
}
if (pendingMessageInfo.message.getPositionMs() == C.TIME_END_OF_SOURCE) {
// Re-resolve end of stream in case the duration changed.
resolvePendingMessageEndOfStreamPosition(newTimeline, pendingMessageInfo, window, period);
return true;
}
pendingMessageInfo.resolvedPeriodIndex = index;
previousTimeline.getPeriodByUid(pendingMessageInfo.resolvedPeriodUid, period);
if (previousTimeline.getWindow(period.windowIndex, window).isPlaceholder) {
// The position needs to be re-resolved because the window in the previous timeline wasn't
// fully prepared.
long windowPositionUs =
pendingMessageInfo.resolvedPeriodTimeUs + period.getPositionInWindowUs();
int windowIndex =
newTimeline.getPeriodByUid(pendingMessageInfo.resolvedPeriodUid, period).windowIndex;
Pair<Object, Long> periodPosition =
newTimeline.getPeriodPosition(window, period, windowIndex, windowPositionUs);
pendingMessageInfo.setResolvedPosition(
/* periodIndex= */ newTimeline.getIndexOfPeriod(periodPosition.first),
/* periodTimeUs= */ periodPosition.second,
/* periodUid= */ periodPosition.first);
}
return true;
}
private static void resolvePendingMessageEndOfStreamPosition(
Timeline timeline,
PendingMessageInfo messageInfo,
Timeline.Window window,
Timeline.Period period) {
int windowIndex = timeline.getPeriodByUid(messageInfo.resolvedPeriodUid, period).windowIndex;
timeline.getWindow(windowIndex, window);
if (!window.isDynamic && window.durationUs != C.TIME_UNSET) {
Pair<Object, Long> periodPosition =
timeline.getPeriodPosition(window, period, windowIndex, window.durationUs);
messageInfo.setResolvedPosition(
/* periodIndex= */ timeline.getIndexOfPeriod(periodPosition.first),
/* periodTimeUs= */ periodPosition.second,
/* periodUid= */ periodPosition.first);
} else {
int lastPeriodIndex = timeline.getWindow(windowIndex, window).lastPeriodIndex;
Object lastPeriodUid = timeline.getUidOfPeriod(lastPeriodIndex);
messageInfo.setResolvedPosition(
lastPeriodIndex, /* periodTimeUs= */ Long.MAX_VALUE, lastPeriodUid);
}
}
/**
* Converts a {@link SeekPosition} into the corresponding (periodUid, periodPositionUs) for the
* internal timeline.

View file

@ -162,7 +162,9 @@ public final class PlayerMessage {
/**
* Returns position in window at {@link #getWindowIndex()} at which the message will be delivered,
* in milliseconds. If {@link C#TIME_UNSET}, the message will be delivered immediately.
* in milliseconds. If {@link C#TIME_UNSET}, the message will be delivered immediately. If {@link
* C#TIME_END_OF_SOURCE}, the message will be delivered at the end of the window at {@link
* #getWindowIndex()}.
*/
public long getPositionMs() {
return positionMs;
@ -172,7 +174,8 @@ public final class PlayerMessage {
* Sets a position in the current window at which the message will be delivered.
*
* @param positionMs The position in the current window at which the message will be sent, in
* milliseconds.
* milliseconds, or {@link C#TIME_END_OF_SOURCE} to deliver the message at the end of the
* current window.
* @return This message.
* @throws IllegalStateException If {@link #send()} has already been called.
*/
@ -187,7 +190,8 @@ public final class PlayerMessage {
*
* @param windowIndex The index of the window at which the message will be sent.
* @param positionMs The position in the window with index {@code windowIndex} at which the
* message will be sent, in milliseconds.
* message will be sent, in milliseconds, or {@link C#TIME_END_OF_SOURCE} to deliver the
* message at the end of the window with index {@code windowIndex}.
* @return This message.
* @throws IllegalSeekPositionException If the timeline returned by {@link #getTimeline()} is not
* empty and the provided window index is not within the bounds of the timeline.

View file

@ -1878,18 +1878,33 @@ public final class ExoPlayerTest {
public void testSendMessagesAtStartAndEndOfPeriod() throws Exception {
Timeline timeline = new FakeTimeline(/* windowCount= */ 2);
PositionGrabbingMessageTarget targetStartFirstPeriod = new PositionGrabbingMessageTarget();
PositionGrabbingMessageTarget targetEndMiddlePeriod = new PositionGrabbingMessageTarget();
PositionGrabbingMessageTarget targetEndMiddlePeriodResolved =
new PositionGrabbingMessageTarget();
PositionGrabbingMessageTarget targetEndMiddlePeriodUnresolved =
new PositionGrabbingMessageTarget();
PositionGrabbingMessageTarget targetStartMiddlePeriod = new PositionGrabbingMessageTarget();
PositionGrabbingMessageTarget targetEndLastPeriod = new PositionGrabbingMessageTarget();
PositionGrabbingMessageTarget targetEndLastPeriodResolved = new PositionGrabbingMessageTarget();
PositionGrabbingMessageTarget targetEndLastPeriodUnresolved =
new PositionGrabbingMessageTarget();
long duration1Ms = timeline.getWindow(0, new Window()).getDurationMs();
long duration2Ms = timeline.getWindow(1, new Window()).getDurationMs();
ActionSchedule actionSchedule =
new ActionSchedule.Builder("testSendMessagesAtStartAndEndOfPeriod")
.sendMessage(targetStartFirstPeriod, /* windowIndex= */ 0, /* positionMs= */ 0)
.sendMessage(targetEndMiddlePeriod, /* windowIndex= */ 0, /* positionMs= */ duration1Ms)
.sendMessage(
targetEndMiddlePeriodResolved, /* windowIndex= */ 0, /* positionMs= */ duration1Ms)
.sendMessage(
targetEndMiddlePeriodUnresolved,
/* windowIndex= */ 0,
/* positionMs= */ C.TIME_END_OF_SOURCE)
.sendMessage(targetStartMiddlePeriod, /* windowIndex= */ 1, /* positionMs= */ 0)
.sendMessage(targetEndLastPeriod, /* windowIndex= */ 1, /* positionMs= */ duration2Ms)
.waitForMessage(targetEndLastPeriod)
.sendMessage(
targetEndLastPeriodResolved, /* windowIndex= */ 1, /* positionMs= */ duration2Ms)
.sendMessage(
targetEndLastPeriodUnresolved,
/* windowIndex= */ 1,
/* positionMs= */ C.TIME_END_OF_SOURCE)
.waitForMessage(targetEndLastPeriodUnresolved)
.build();
new Builder()
.setTimeline(timeline)
@ -1900,12 +1915,20 @@ public final class ExoPlayerTest {
.blockUntilEnded(TIMEOUT_MS);
assertThat(targetStartFirstPeriod.windowIndex).isEqualTo(0);
assertThat(targetStartFirstPeriod.positionMs).isAtLeast(0L);
assertThat(targetEndMiddlePeriod.windowIndex).isEqualTo(0);
assertThat(targetEndMiddlePeriod.positionMs).isAtLeast(duration1Ms);
assertThat(targetEndMiddlePeriodResolved.windowIndex).isEqualTo(0);
assertThat(targetEndMiddlePeriodResolved.positionMs).isAtLeast(duration1Ms);
assertThat(targetEndMiddlePeriodUnresolved.windowIndex).isEqualTo(0);
assertThat(targetEndMiddlePeriodUnresolved.positionMs).isAtLeast(duration1Ms);
assertThat(targetEndMiddlePeriodResolved.positionMs)
.isEqualTo(targetEndMiddlePeriodUnresolved.positionMs);
assertThat(targetStartMiddlePeriod.windowIndex).isEqualTo(1);
assertThat(targetStartMiddlePeriod.positionMs).isAtLeast(0L);
assertThat(targetEndLastPeriod.windowIndex).isEqualTo(1);
assertThat(targetEndLastPeriod.positionMs).isAtLeast(duration2Ms);
assertThat(targetEndLastPeriodResolved.windowIndex).isEqualTo(1);
assertThat(targetEndLastPeriodResolved.positionMs).isAtLeast(duration2Ms);
assertThat(targetEndLastPeriodUnresolved.windowIndex).isEqualTo(1);
assertThat(targetEndLastPeriodUnresolved.positionMs).isAtLeast(duration2Ms);
assertThat(targetEndLastPeriodResolved.positionMs)
.isEqualTo(targetEndLastPeriodUnresolved.positionMs);
}
@Test