mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Added condition to RtspMediaPeriod.isLoading to check RtspState
If a RtspMediaSource is used within a combining source like MergingMediaSource, then it could become stuck in a loop of calling continueLoading when the source has not actually started loading yet. Issue: androidx/media#577 PiperOrigin-RevId: 561322900
This commit is contained in:
parent
b4ee896847
commit
cf3fd1f4dd
@ -45,6 +45,9 @@
|
|||||||
* RTSP Extension:
|
* RTSP Extension:
|
||||||
* Fix a race condition that could lead to `IndexOutOfBoundsException` when
|
* Fix a race condition that could lead to `IndexOutOfBoundsException` when
|
||||||
falling back to TCP, or playback hanging in some situations.
|
falling back to TCP, or playback hanging in some situations.
|
||||||
|
* Check state in RTSP setup when returning loading state of
|
||||||
|
`RtspMediaPeriod`
|
||||||
|
([#577](https://github.com/androidx/media/issues/577)).
|
||||||
* Decoder Extensions (FFmpeg, VP9, AV1, etc.):
|
* Decoder Extensions (FFmpeg, VP9, AV1, etc.):
|
||||||
* MIDI extension:
|
* MIDI extension:
|
||||||
* Cast Extension:
|
* Cast Extension:
|
||||||
|
@ -380,7 +380,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean isLoading() {
|
public boolean isLoading() {
|
||||||
return !loadingFinished;
|
return !loadingFinished
|
||||||
|
&& (rtspClient.getState() == RtspClient.RTSP_STATE_PLAYING
|
||||||
|
|| rtspClient.getState() == RtspClient.RTSP_STATE_READY);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -18,18 +18,26 @@ package androidx.media3.exoplayer.rtsp;
|
|||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
|
import androidx.media3.common.TrackGroup;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import androidx.media3.exoplayer.LoadingInfo;
|
import androidx.media3.exoplayer.LoadingInfo;
|
||||||
import androidx.media3.exoplayer.source.MediaPeriod;
|
import androidx.media3.exoplayer.source.MediaPeriod;
|
||||||
|
import androidx.media3.exoplayer.source.SampleStream;
|
||||||
|
import androidx.media3.exoplayer.trackselection.ExoTrackSelection;
|
||||||
|
import androidx.media3.exoplayer.trackselection.FixedTrackSelection;
|
||||||
import androidx.media3.exoplayer.upstream.DefaultAllocator;
|
import androidx.media3.exoplayer.upstream.DefaultAllocator;
|
||||||
import androidx.media3.test.utils.robolectric.RobolectricUtil;
|
import androidx.media3.test.utils.robolectric.RobolectricUtil;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import java.io.IOException;
|
||||||
import java.util.concurrent.atomic.AtomicBoolean;
|
import java.util.concurrent.atomic.AtomicBoolean;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import javax.net.SocketFactory;
|
import javax.net.SocketFactory;
|
||||||
import org.junit.After;
|
import org.junit.After;
|
||||||
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
@ -39,46 +47,44 @@ public final class RtspMediaPeriodTest {
|
|||||||
|
|
||||||
private static final long DEFAULT_TIMEOUT_MS = 8000;
|
private static final long DEFAULT_TIMEOUT_MS = 8000;
|
||||||
|
|
||||||
private RtspMediaPeriod mediaPeriod;
|
private final AtomicReference<TrackGroup> trackGroupAtomicReference = new AtomicReference<>();
|
||||||
|
;
|
||||||
|
private final MediaPeriod.Callback mediaPeriodCallback =
|
||||||
|
new MediaPeriod.Callback() {
|
||||||
|
@Override
|
||||||
|
public void onPrepared(MediaPeriod mediaPeriod) {
|
||||||
|
trackGroupAtomicReference.set(mediaPeriod.getTrackGroups().get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onContinueLoadingRequested(MediaPeriod source) {
|
||||||
|
source.continueLoading(new LoadingInfo.Builder().setPlaybackPositionUs(0).build());
|
||||||
|
}
|
||||||
|
};
|
||||||
|
private RtpPacketStreamDump rtpPacketStreamDump;
|
||||||
private RtspServer rtspServer;
|
private RtspServer rtspServer;
|
||||||
|
private RtspMediaPeriod mediaPeriod;
|
||||||
|
|
||||||
|
@Before
|
||||||
|
public void setUp() throws IOException {
|
||||||
|
rtpPacketStreamDump = RtspTestUtils.readRtpPacketStreamDump("media/rtsp/aac-dump.json");
|
||||||
|
}
|
||||||
|
|
||||||
@After
|
@After
|
||||||
public void tearDown() {
|
public void tearDown() {
|
||||||
|
mediaPeriod.release();
|
||||||
Util.closeQuietly(rtspServer);
|
Util.closeQuietly(rtspServer);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void prepareMediaPeriod_refreshesSourceInfoAndCallsOnPrepared() throws Exception {
|
public void prepareMediaPeriod_refreshesSourceInfoAndCallsOnPrepared() throws Exception {
|
||||||
RtpPacketStreamDump rtpPacketStreamDump =
|
AtomicLong refreshedSourceDurationMs = new AtomicLong();
|
||||||
RtspTestUtils.readRtpPacketStreamDump("media/rtsp/aac-dump.json");
|
|
||||||
|
|
||||||
rtspServer =
|
rtspServer =
|
||||||
new RtspServer(
|
new RtspServer(
|
||||||
new RtspServer.ResponseProvider() {
|
new TestResponseProvider(
|
||||||
@Override
|
rtpPacketStreamDump,
|
||||||
public RtspResponse getOptionsResponse() {
|
/* getPlayResponseReference= */ null,
|
||||||
return new RtspResponse(
|
/* isWwwAuthenticationMode= */ false));
|
||||||
/* status= */ 200,
|
|
||||||
new RtspHeaders.Builder().add(RtspHeaders.PUBLIC, "OPTIONS, DESCRIBE").build());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public RtspResponse getDescribeResponse(Uri requestedUri, RtspHeaders headers) {
|
|
||||||
return RtspTestUtils.newDescribeResponseWithSdpMessage(
|
|
||||||
"v=0\r\n"
|
|
||||||
+ "o=- 1606776316530225 1 IN IP4 127.0.0.1\r\n"
|
|
||||||
+ "s=Exoplayer test\r\n"
|
|
||||||
+ "t=0 0\r\n"
|
|
||||||
// The session is 50.46s long.
|
|
||||||
+ "a=range:npt=0-50.46\r\n",
|
|
||||||
ImmutableList.of(rtpPacketStreamDump),
|
|
||||||
requestedUri);
|
|
||||||
}
|
|
||||||
});
|
|
||||||
|
|
||||||
AtomicBoolean prepareCallbackCalled = new AtomicBoolean();
|
|
||||||
AtomicLong refreshedSourceDurationMs = new AtomicLong();
|
|
||||||
|
|
||||||
mediaPeriod =
|
mediaPeriod =
|
||||||
new RtspMediaPeriod(
|
new RtspMediaPeriod(
|
||||||
new DefaultAllocator(/* trimOnReset= */ true, C.DEFAULT_BUFFER_SEGMENT_SIZE),
|
new DefaultAllocator(/* trimOnReset= */ true, C.DEFAULT_BUFFER_SEGMENT_SIZE),
|
||||||
@ -89,21 +95,8 @@ public final class RtspMediaPeriodTest {
|
|||||||
/* socketFactory= */ SocketFactory.getDefault(),
|
/* socketFactory= */ SocketFactory.getDefault(),
|
||||||
/* debugLoggingEnabled= */ false);
|
/* debugLoggingEnabled= */ false);
|
||||||
|
|
||||||
mediaPeriod.prepare(
|
mediaPeriod.prepare(mediaPeriodCallback, /* positionUs= */ 0);
|
||||||
new MediaPeriod.Callback() {
|
RobolectricUtil.runMainLooperUntil(() -> trackGroupAtomicReference.get() != null);
|
||||||
@Override
|
|
||||||
public void onPrepared(MediaPeriod mediaPeriod) {
|
|
||||||
prepareCallbackCalled.set(true);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onContinueLoadingRequested(MediaPeriod source) {
|
|
||||||
source.continueLoading(new LoadingInfo.Builder().setPlaybackPositionUs(0).build());
|
|
||||||
}
|
|
||||||
},
|
|
||||||
/* positionUs= */ 0);
|
|
||||||
RobolectricUtil.runMainLooperUntil(prepareCallbackCalled::get);
|
|
||||||
mediaPeriod.release();
|
|
||||||
|
|
||||||
assertThat(refreshedSourceDurationMs.get()).isEqualTo(50_460);
|
assertThat(refreshedSourceDurationMs.get()).isEqualTo(50_460);
|
||||||
}
|
}
|
||||||
@ -111,21 +104,139 @@ public final class RtspMediaPeriodTest {
|
|||||||
@Test
|
@Test
|
||||||
public void prepareMediaPeriod_withWwwAuthentication_refreshesSourceInfoAndCallsOnPrepared()
|
public void prepareMediaPeriod_withWwwAuthentication_refreshesSourceInfoAndCallsOnPrepared()
|
||||||
throws Exception {
|
throws Exception {
|
||||||
RtpPacketStreamDump rtpPacketStreamDump =
|
AtomicLong refreshedSourceDurationMs = new AtomicLong();
|
||||||
RtspTestUtils.readRtpPacketStreamDump("media/rtsp/aac-dump.json");
|
|
||||||
|
|
||||||
rtspServer =
|
rtspServer =
|
||||||
new RtspServer(
|
new RtspServer(
|
||||||
new RtspServer.ResponseProvider() {
|
new TestResponseProvider(
|
||||||
|
rtpPacketStreamDump,
|
||||||
|
/* getPlayResponseReference= */ null,
|
||||||
|
/* isWwwAuthenticationMode= */ true));
|
||||||
|
mediaPeriod =
|
||||||
|
new RtspMediaPeriod(
|
||||||
|
new DefaultAllocator(/* trimOnReset= */ true, C.DEFAULT_BUFFER_SEGMENT_SIZE),
|
||||||
|
new TransferRtpDataChannelFactory(DEFAULT_TIMEOUT_MS),
|
||||||
|
RtspTestUtils.getTestUriWithUserInfo(
|
||||||
|
"username", "password", rtspServer.startAndGetPortNumber()),
|
||||||
|
/* listener= */ timing -> refreshedSourceDurationMs.set(timing.getDurationMs()),
|
||||||
|
/* userAgent= */ "ExoPlayer:RtspPeriodTest",
|
||||||
|
/* socketFactory= */ SocketFactory.getDefault(),
|
||||||
|
/* debugLoggingEnabled= */ false);
|
||||||
|
|
||||||
|
mediaPeriod.prepare(mediaPeriodCallback, /* positionUs= */ 0);
|
||||||
|
RobolectricUtil.runMainLooperUntil(() -> trackGroupAtomicReference.get() != null);
|
||||||
|
|
||||||
|
assertThat(refreshedSourceDurationMs.get()).isEqualTo(50_460);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isLoading_afterInit_returnsFalse() throws Exception {
|
||||||
|
rtspServer =
|
||||||
|
new RtspServer(
|
||||||
|
() ->
|
||||||
|
new RtspResponse(
|
||||||
|
/* status= */ 200,
|
||||||
|
new RtspHeaders.Builder().add(RtspHeaders.PUBLIC, "OPTIONS").build()));
|
||||||
|
mediaPeriod =
|
||||||
|
new RtspMediaPeriod(
|
||||||
|
new DefaultAllocator(/* trimOnReset= */ true, C.DEFAULT_BUFFER_SEGMENT_SIZE),
|
||||||
|
new TransferRtpDataChannelFactory(DEFAULT_TIMEOUT_MS),
|
||||||
|
RtspTestUtils.getTestUri(rtspServer.startAndGetPortNumber()),
|
||||||
|
/* listener= */ timing -> {},
|
||||||
|
/* userAgent= */ "ExoPlayer:RtspPeriodTest",
|
||||||
|
/* socketFactory= */ SocketFactory.getDefault(),
|
||||||
|
/* debugLoggingEnabled= */ false);
|
||||||
|
|
||||||
|
assertThat(mediaPeriod.isLoading()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isLoading_afterPrepare_returnsFalse() throws Exception {
|
||||||
|
rtspServer =
|
||||||
|
new RtspServer(
|
||||||
|
new TestResponseProvider(
|
||||||
|
rtpPacketStreamDump,
|
||||||
|
/* getPlayResponseReference= */ null,
|
||||||
|
/* isWwwAuthenticationMode= */ false));
|
||||||
|
mediaPeriod =
|
||||||
|
new RtspMediaPeriod(
|
||||||
|
new DefaultAllocator(/* trimOnReset= */ true, C.DEFAULT_BUFFER_SEGMENT_SIZE),
|
||||||
|
new TransferRtpDataChannelFactory(DEFAULT_TIMEOUT_MS),
|
||||||
|
RtspTestUtils.getTestUri(rtspServer.startAndGetPortNumber()),
|
||||||
|
/* listener= */ timing -> {},
|
||||||
|
/* userAgent= */ "ExoPlayer:RtspPeriodTest",
|
||||||
|
/* socketFactory= */ SocketFactory.getDefault(),
|
||||||
|
/* debugLoggingEnabled= */ false);
|
||||||
|
|
||||||
|
mediaPeriod.prepare(mediaPeriodCallback, /* positionUs= */ 0);
|
||||||
|
RobolectricUtil.runMainLooperUntil(() -> trackGroupAtomicReference.get() != null);
|
||||||
|
|
||||||
|
assertThat(mediaPeriod.isLoading()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void isLoading_afterSelectTracksAndSendPlayRequest_returnsTrue() throws Exception {
|
||||||
|
AtomicBoolean getPlayResponseReference = new AtomicBoolean();
|
||||||
|
rtspServer =
|
||||||
|
new RtspServer(
|
||||||
|
new TestResponseProvider(
|
||||||
|
rtpPacketStreamDump,
|
||||||
|
/* getPlayResponseReference= */ getPlayResponseReference,
|
||||||
|
/* isWwwAuthenticationMode= */ false));
|
||||||
|
mediaPeriod =
|
||||||
|
new RtspMediaPeriod(
|
||||||
|
new DefaultAllocator(/* trimOnReset= */ true, C.DEFAULT_BUFFER_SEGMENT_SIZE),
|
||||||
|
new TransferRtpDataChannelFactory(DEFAULT_TIMEOUT_MS),
|
||||||
|
RtspTestUtils.getTestUri(rtspServer.startAndGetPortNumber()),
|
||||||
|
/* listener= */ timing -> {},
|
||||||
|
/* userAgent= */ "ExoPlayer:RtspPeriodTest",
|
||||||
|
/* socketFactory= */ SocketFactory.getDefault(),
|
||||||
|
/* debugLoggingEnabled= */ false);
|
||||||
|
mediaPeriod.prepare(mediaPeriodCallback, /* positionUs= */ 0);
|
||||||
|
RobolectricUtil.runMainLooperUntil(() -> trackGroupAtomicReference.get() != null);
|
||||||
|
|
||||||
|
SampleStream[] sampleStreams = new SampleStream[1];
|
||||||
|
// Call selectTracks to initiate RTSP SETUP Request
|
||||||
|
mediaPeriod.selectTracks(
|
||||||
|
new ExoTrackSelection[] {
|
||||||
|
new FixedTrackSelection(trackGroupAtomicReference.get(), /* track= */ 0)
|
||||||
|
},
|
||||||
|
/* mayRetainStreamFlags= */ new boolean[] {false},
|
||||||
|
sampleStreams,
|
||||||
|
/* streamResetFlags= */ new boolean[] {true},
|
||||||
|
/* positionUs= */ 0);
|
||||||
|
RobolectricUtil.runMainLooperUntil(getPlayResponseReference::get);
|
||||||
|
|
||||||
|
assertThat(mediaPeriod.isLoading()).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class TestResponseProvider implements RtspServer.ResponseProvider {
|
||||||
|
private static final String SESSION_ID = "00000000";
|
||||||
|
|
||||||
|
private final ImmutableList<RtpPacketStreamDump> rtpPacketStreamDumps;
|
||||||
|
@Nullable private final AtomicBoolean getPlayResponseReference;
|
||||||
|
private final boolean isWwwAuthenticationMode;
|
||||||
|
|
||||||
|
private TestResponseProvider(
|
||||||
|
RtpPacketStreamDump rtpPacketStreamDump,
|
||||||
|
@Nullable AtomicBoolean getPlayResponseReference,
|
||||||
|
boolean isWwwAuthenticationMode) {
|
||||||
|
this.rtpPacketStreamDumps = ImmutableList.of(rtpPacketStreamDump);
|
||||||
|
this.getPlayResponseReference = getPlayResponseReference;
|
||||||
|
this.isWwwAuthenticationMode = isWwwAuthenticationMode;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RtspResponse getOptionsResponse() {
|
public RtspResponse getOptionsResponse() {
|
||||||
return new RtspResponse(
|
return new RtspResponse(
|
||||||
/* status= */ 200,
|
/* status= */ 200,
|
||||||
new RtspHeaders.Builder().add(RtspHeaders.PUBLIC, "OPTIONS, DESCRIBE").build());
|
new RtspHeaders.Builder()
|
||||||
|
.add(RtspHeaders.PUBLIC, "OPTIONS, DESCRIBE, SETUP, PLAY")
|
||||||
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public RtspResponse getDescribeResponse(Uri requestedUri, RtspHeaders headers) {
|
public RtspResponse getDescribeResponse(Uri requestedUri, RtspHeaders headers) {
|
||||||
|
if (isWwwAuthenticationMode) {
|
||||||
String authorizationHeader = headers.get(RtspHeaders.AUTHORIZATION);
|
String authorizationHeader = headers.get(RtspHeaders.AUTHORIZATION);
|
||||||
if (authorizationHeader == null) {
|
if (authorizationHeader == null) {
|
||||||
return new RtspResponse(
|
return new RtspResponse(
|
||||||
@ -148,6 +259,7 @@ public final class RtspMediaPeriodTest {
|
|||||||
.add(RtspHeaders.CSEQ, headers.get(RtspHeaders.CSEQ))
|
.add(RtspHeaders.CSEQ, headers.get(RtspHeaders.CSEQ))
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return RtspTestUtils.newDescribeResponseWithSdpMessage(
|
return RtspTestUtils.newDescribeResponseWithSdpMessage(
|
||||||
"v=0\r\n"
|
"v=0\r\n"
|
||||||
@ -156,40 +268,27 @@ public final class RtspMediaPeriodTest {
|
|||||||
+ "t=0 0\r\n"
|
+ "t=0 0\r\n"
|
||||||
// The session is 50.46s long.
|
// The session is 50.46s long.
|
||||||
+ "a=range:npt=0-50.46\r\n",
|
+ "a=range:npt=0-50.46\r\n",
|
||||||
ImmutableList.of(rtpPacketStreamDump),
|
rtpPacketStreamDumps,
|
||||||
requestedUri);
|
requestedUri);
|
||||||
}
|
}
|
||||||
});
|
|
||||||
AtomicBoolean prepareCallbackCalled = new AtomicBoolean();
|
|
||||||
AtomicLong refreshedSourceDurationMs = new AtomicLong();
|
|
||||||
|
|
||||||
mediaPeriod =
|
|
||||||
new RtspMediaPeriod(
|
|
||||||
new DefaultAllocator(/* trimOnReset= */ true, C.DEFAULT_BUFFER_SEGMENT_SIZE),
|
|
||||||
new TransferRtpDataChannelFactory(DEFAULT_TIMEOUT_MS),
|
|
||||||
RtspTestUtils.getTestUriWithUserInfo(
|
|
||||||
"username", "password", rtspServer.startAndGetPortNumber()),
|
|
||||||
/* listener= */ timing -> refreshedSourceDurationMs.set(timing.getDurationMs()),
|
|
||||||
/* userAgent= */ "ExoPlayer:RtspPeriodTest",
|
|
||||||
/* socketFactory= */ SocketFactory.getDefault(),
|
|
||||||
/* debugLoggingEnabled= */ false);
|
|
||||||
|
|
||||||
mediaPeriod.prepare(
|
|
||||||
new MediaPeriod.Callback() {
|
|
||||||
@Override
|
@Override
|
||||||
public void onPrepared(MediaPeriod mediaPeriod) {
|
public RtspResponse getSetupResponse(Uri requestedUri, RtspHeaders headers) {
|
||||||
prepareCallbackCalled.set(true);
|
return new RtspResponse(
|
||||||
|
/* status= */ 200, headers.buildUpon().add(RtspHeaders.SESSION, SESSION_ID).build());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onContinueLoadingRequested(MediaPeriod source) {
|
public RtspResponse getPlayResponse() {
|
||||||
source.continueLoading(new LoadingInfo.Builder().setPlaybackPositionUs(0).build());
|
if (getPlayResponseReference != null) {
|
||||||
|
getPlayResponseReference.set(true);
|
||||||
}
|
}
|
||||||
},
|
|
||||||
/* positionUs= */ 0);
|
|
||||||
RobolectricUtil.runMainLooperUntil(prepareCallbackCalled::get);
|
|
||||||
mediaPeriod.release();
|
|
||||||
|
|
||||||
assertThat(refreshedSourceDurationMs.get()).isEqualTo(50_460);
|
return new RtspResponse(
|
||||||
|
/* status= */ 200,
|
||||||
|
new RtspHeaders.Builder()
|
||||||
|
.add(RtspHeaders.RTP_INFO, RtspTestUtils.getRtpInfoForDumps(rtpPacketStreamDumps))
|
||||||
|
.build());
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
Loading…
x
Reference in New Issue
Block a user