Handle seek after playback ends
Reusing the loader wrappers allows us to use the current RTSP connection without having to set up a new RTSP connection. Consequently, the Extractors, RTP readers are also preserved. PiperOrigin-RevId: 524663012
This commit is contained in:
parent
011fc9d5d3
commit
29fc16484a
@ -53,6 +53,9 @@ import java.io.IOException;
|
||||
*/
|
||||
int getLocalPort();
|
||||
|
||||
/** Returns whether the {@code RtpDataChannel} needs to be closed when loading completes. */
|
||||
boolean needsClosingOnLoadCompletion();
|
||||
|
||||
/**
|
||||
* Returns a {@link InterleavedBinaryDataListener} if the implementation supports receiving RTP
|
||||
* packets on a side-band protocol, for example RTP-over-RTSP; otherwise {@code null}.
|
||||
|
@ -27,7 +27,6 @@ import androidx.media3.datasource.DataSourceUtil;
|
||||
import androidx.media3.exoplayer.upstream.Loader;
|
||||
import androidx.media3.extractor.DefaultExtractorInput;
|
||||
import androidx.media3.extractor.Extractor;
|
||||
import androidx.media3.extractor.ExtractorInput;
|
||||
import androidx.media3.extractor.ExtractorOutput;
|
||||
import androidx.media3.extractor.PositionHolder;
|
||||
import java.io.IOException;
|
||||
@ -77,7 +76,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
private final Handler playbackThreadHandler;
|
||||
private final RtpDataChannel.Factory rtpDataChannelFactory;
|
||||
|
||||
@Nullable private RtpDataChannel dataChannel;
|
||||
private @MonotonicNonNull RtpExtractor extractor;
|
||||
private @MonotonicNonNull DefaultExtractorInput extractorInput;
|
||||
|
||||
private volatile boolean loadCancelled;
|
||||
private volatile long pendingSeekPositionUs;
|
||||
@ -142,36 +143,49 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
@Override
|
||||
public void load() throws IOException {
|
||||
@Nullable RtpDataChannel dataChannel = null;
|
||||
// Allows to resume loading after canceling load.
|
||||
if (loadCancelled) {
|
||||
loadCancelled = false;
|
||||
}
|
||||
|
||||
try {
|
||||
dataChannel = rtpDataChannelFactory.createAndOpenDataChannel(trackId);
|
||||
String transport = dataChannel.getTransport();
|
||||
if (dataChannel == null) {
|
||||
dataChannel = rtpDataChannelFactory.createAndOpenDataChannel(trackId);
|
||||
String transport = dataChannel.getTransport();
|
||||
|
||||
RtpDataChannel finalDataChannel = dataChannel;
|
||||
playbackThreadHandler.post(() -> eventListener.onTransportReady(transport, finalDataChannel));
|
||||
RtpDataChannel finalDataChannel = dataChannel;
|
||||
playbackThreadHandler.post(
|
||||
() -> eventListener.onTransportReady(transport, finalDataChannel));
|
||||
|
||||
// Sets up the extractor.
|
||||
ExtractorInput extractorInput =
|
||||
new DefaultExtractorInput(
|
||||
checkNotNull(dataChannel), /* position= */ 0, /* length= */ C.LENGTH_UNSET);
|
||||
extractor = new RtpExtractor(rtspMediaTrack.payloadFormat, trackId);
|
||||
extractor.init(output);
|
||||
extractorInput =
|
||||
new DefaultExtractorInput(
|
||||
checkNotNull(dataChannel), /* position= */ 0, /* length= */ C.LENGTH_UNSET);
|
||||
extractor = new RtpExtractor(rtspMediaTrack.payloadFormat, trackId);
|
||||
extractor.init(output);
|
||||
}
|
||||
|
||||
while (!loadCancelled) {
|
||||
if (pendingSeekPositionUs != C.TIME_UNSET) {
|
||||
extractor.seek(nextRtpTimestamp, pendingSeekPositionUs);
|
||||
checkNotNull(extractor).seek(nextRtpTimestamp, pendingSeekPositionUs);
|
||||
pendingSeekPositionUs = C.TIME_UNSET;
|
||||
}
|
||||
|
||||
@Extractor.ReadResult
|
||||
int readResult = extractor.read(extractorInput, /* seekPosition= */ new PositionHolder());
|
||||
int readResult =
|
||||
checkNotNull(extractor)
|
||||
.read(checkNotNull(extractorInput), /* seekPosition= */ new PositionHolder());
|
||||
if (readResult == Extractor.RESULT_END_OF_INPUT) {
|
||||
// Loading is finished.
|
||||
break;
|
||||
}
|
||||
}
|
||||
// Resets the flag if user cancels loading.
|
||||
loadCancelled = false;
|
||||
} finally {
|
||||
DataSourceUtil.closeQuietly(dataChannel);
|
||||
if (checkNotNull(dataChannel).needsClosingOnLoadCompletion()) {
|
||||
DataSourceUtil.closeQuietly(dataChannel);
|
||||
dataChannel = null;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -230,6 +230,10 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
messageSender.sendPlayRequest(uri, offsetMs, checkNotNull(sessionId));
|
||||
}
|
||||
|
||||
public void signalPlaybackEnded() {
|
||||
rtspState = RTSP_STATE_READY;
|
||||
}
|
||||
|
||||
/**
|
||||
* Seeks to a specific time using RTSP.
|
||||
*
|
||||
@ -723,7 +727,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
}
|
||||
|
||||
private void onPlayResponseReceived(RtspPlayResponse response) {
|
||||
checkState(rtspState == RTSP_STATE_READY);
|
||||
checkState(rtspState == RTSP_STATE_READY || rtspState == RTSP_STATE_PLAYING);
|
||||
|
||||
rtspState = RTSP_STATE_PLAYING;
|
||||
if (keepAliveMonitor == null) {
|
||||
|
@ -19,6 +19,7 @@ package androidx.media3.exoplayer.rtsp;
|
||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||
import static androidx.media3.common.util.Assertions.checkState;
|
||||
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||
import static androidx.media3.common.util.Util.usToMs;
|
||||
import static java.lang.Math.min;
|
||||
|
||||
import android.net.Uri;
|
||||
@ -314,7 +315,22 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
}
|
||||
|
||||
pendingSeekPositionUs = positionUs;
|
||||
rtspClient.seekToUs(positionUs);
|
||||
|
||||
if (loadingFinished) {
|
||||
for (int i = 0; i < rtspLoaderWrappers.size(); i++) {
|
||||
rtspLoaderWrappers.get(i).resumeLoad();
|
||||
}
|
||||
|
||||
if (isUsingRtpTcp) {
|
||||
rtspClient.startPlayback(/* offsetMs= */ usToMs(positionUs));
|
||||
} else {
|
||||
rtspClient.seekToUs(positionUs);
|
||||
}
|
||||
|
||||
} else {
|
||||
rtspClient.seekToUs(positionUs);
|
||||
}
|
||||
|
||||
for (int i = 0; i < rtspLoaderWrappers.size(); i++) {
|
||||
rtspLoaderWrappers.get(i).seekTo(positionUs);
|
||||
}
|
||||
@ -530,6 +546,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
rtspClient.signalPlaybackEnded();
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -817,6 +835,14 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
}
|
||||
}
|
||||
|
||||
/** Resumes loading after {@linkplain #cancelLoad() loading is canceled}. */
|
||||
public void resumeLoad() {
|
||||
checkState(canceled);
|
||||
canceled = false;
|
||||
updateLoadingFinished();
|
||||
startLoading();
|
||||
}
|
||||
|
||||
/** Resets the {@link Loadable} and {@link SampleQueue} to prepare for an RTSP seek. */
|
||||
public void seekTo(long positionUs) {
|
||||
if (!canceled) {
|
||||
|
@ -69,6 +69,12 @@ import java.util.concurrent.LinkedBlockingQueue;
|
||||
return channelNumber;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsClosingOnLoadCompletion() {
|
||||
// TCP channel is managed by the RTSP mesasge channel and does not need closing from here.
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public InterleavedBinaryDataListener getInterleavedBinaryDataListener() {
|
||||
return this;
|
||||
|
@ -64,6 +64,11 @@ import java.io.IOException;
|
||||
return port == UdpDataSource.UDP_PORT_UNSET ? C.INDEX_UNSET : port;
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsClosingOnLoadCompletion() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
@Override
|
||||
public RtspMessageChannel.InterleavedBinaryDataListener getInterleavedBinaryDataListener() {
|
||||
|
@ -441,6 +441,11 @@ public final class RtspPlaybackTest {
|
||||
return Util.formatInvariant("RTP/AVP;unicast;client_port=%d-%d", LOCAL_PORT, LOCAL_PORT + 1);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsClosingOnLoadCompletion() {
|
||||
return false;
|
||||
}
|
||||
|
||||
@Override
|
||||
public RtspMessageChannel.InterleavedBinaryDataListener getInterleavedBinaryDataListener() {
|
||||
return null;
|
||||
@ -454,6 +459,11 @@ public final class RtspPlaybackTest {
|
||||
return Util.formatInvariant(
|
||||
"RTP/AVP/TCP;unicast;interleaved=%d-%d", LOCAL_PORT + 2, LOCAL_PORT + 3);
|
||||
}
|
||||
|
||||
@Override
|
||||
public boolean needsClosingOnLoadCompletion() {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
private static class ForwardingRtpDataChannelFactory implements RtpDataChannel.Factory {
|
||||
|
Loading…
x
Reference in New Issue
Block a user