From 1ca0efdd9bbc41e8296681d99d99e076bcc0eaf8 Mon Sep 17 00:00:00 2001 From: claincly Date: Tue, 8 Jun 2021 15:40:50 +0100 Subject: [PATCH] Move RtspClient creation into RtspPeriod. RtspMediaSource uses the timeline update paradigm from ProgressiveMediaPeriod. #minor-release PiperOrigin-RevId: 378150758 --- .../exoplayer2/source/rtsp/RtspClient.java | 39 +++--- .../source/rtsp/RtspMediaPeriod.java | 103 ++++++++++---- .../source/rtsp/RtspMediaSource.java | 126 +++++++++++------- .../source/rtsp/RtspClientTest.java | 13 ++ .../source/rtsp/RtspMediaPeriodTest.java | 105 --------------- 5 files changed, 183 insertions(+), 203 deletions(-) delete mode 100644 library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspMediaPeriodTest.java diff --git a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspClient.java b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspClient.java index 3f5f233a2e6..71c94c2db9b 100644 --- a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspClient.java +++ b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspClient.java @@ -96,16 +96,16 @@ public interface PlaybackEventListener { } private final SessionInfoListener sessionInfoListener; + private final PlaybackEventListener playbackEventListener; private final Uri uri; @Nullable private final RtspAuthUserInfo rtspAuthUserInfo; - @Nullable private final String userAgent; + private final String userAgent; private final ArrayDeque pendingSetupRtpLoadInfos; // TODO(b/172331505) Add a timeout monitor for pending requests. private final SparseArray pendingRequests; private final MessageSender messageSender; private RtspMessageChannel messageChannel; - private @MonotonicNonNull PlaybackEventListener playbackEventListener; @Nullable private String sessionId; @Nullable private KeepAliveMonitor keepAliveMonitor; @Nullable private RtspAuthenticationInfo rtspAuthenticationInfo; @@ -123,12 +123,17 @@ public interface PlaybackEventListener { *

Note: all method invocations must be made from the playback thread. * * @param sessionInfoListener The {@link SessionInfoListener}. - * @param userAgent The user agent that will be used if needed, or {@code null} for the fallback - * to use the default user agent of the underlying platform. + * @param playbackEventListener The {@link PlaybackEventListener}. + * @param userAgent The user agent. * @param uri The RTSP playback URI. */ - public RtspClient(SessionInfoListener sessionInfoListener, @Nullable String userAgent, Uri uri) { + public RtspClient( + SessionInfoListener sessionInfoListener, + PlaybackEventListener playbackEventListener, + String userAgent, + Uri uri) { this.sessionInfoListener = sessionInfoListener; + this.playbackEventListener = playbackEventListener; this.uri = RtspMessageUtil.removeUserInfo(uri); this.rtspAuthUserInfo = RtspMessageUtil.parseUserInfo(uri); this.userAgent = userAgent; @@ -157,17 +162,10 @@ public void start() throws IOException { messageSender.sendOptionsRequest(uri, sessionId); } - /** Sets the {@link PlaybackEventListener} to receive playback events. */ - public void setPlaybackEventListener(PlaybackEventListener playbackEventListener) { - this.playbackEventListener = playbackEventListener; - } - /** * Triggers RTSP SETUP requests after track selection. * - *

A {@link PlaybackEventListener} must be set via {@link #setPlaybackEventListener} before - * calling this method. All selected tracks (represented by {@link RtpLoadInfo}) must have valid - * transport. + *

All selected tracks (represented by {@link RtpLoadInfo}) must have valid transport. * * @param loadInfos A list of selected tracks represented by {@link RtpLoadInfo}. */ @@ -224,7 +222,7 @@ public void retryWithRtpTcp() { receivedAuthorizationRequest = false; rtspAuthenticationInfo = null; } catch (IOException e) { - checkNotNull(playbackEventListener).onPlaybackError(new RtspPlaybackException(e)); + playbackEventListener.onPlaybackError(new RtspPlaybackException(e)); } } @@ -237,7 +235,7 @@ public void registerInterleavedDataChannel( private void continueSetupRtspTrack() { @Nullable RtpLoadInfo loadInfo = pendingSetupRtpLoadInfos.pollFirst(); if (loadInfo == null) { - checkNotNull(playbackEventListener).onRtspSetupCompleted(); + playbackEventListener.onRtspSetupCompleted(); return; } messageSender.sendSetupRequest(loadInfo.getTrackUri(), loadInfo.getTransport(), sessionId); @@ -258,7 +256,7 @@ private void dispatchRtspError(Throwable error) { if (hasUpdatedTimelineAndTracks) { // Playback event listener must be non-null after timeline has been updated. - checkNotNull(playbackEventListener).onPlaybackError(playbackException); + playbackEventListener.onPlaybackError(playbackException); } else { sessionInfoListener.onSessionTimelineRequestFailed(nullToEmpty(error.getMessage()), error); } @@ -373,9 +371,7 @@ private RtspRequest getRequestWithCommonHeaders( Uri uri) { RtspHeaders.Builder headersBuilder = new RtspHeaders.Builder(); headersBuilder.add(RtspHeaders.CSEQ, String.valueOf(cSeq++)); - if (userAgent != null) { - headersBuilder.add(RtspHeaders.USER_AGENT, userAgent); - } + headersBuilder.add(RtspHeaders.USER_AGENT, userAgent); if (sessionId != null) { headersBuilder.add(RtspHeaders.SESSION, sessionId); @@ -574,9 +570,8 @@ private void onPlayResponseReceived(RtspPlayResponse response) { keepAliveMonitor.start(); } - checkNotNull(playbackEventListener) - .onPlaybackStarted( - C.msToUs(response.sessionTiming.startTimeMs), response.trackTimingList); + playbackEventListener.onPlaybackStarted( + C.msToUs(response.sessionTiming.startTimeMs), response.trackTimingList); pendingSeekPositionUs = C.TIME_UNSET; } diff --git a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMediaPeriod.java b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMediaPeriod.java index ccdd8f47426..19ce014c2be 100644 --- a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMediaPeriod.java +++ b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMediaPeriod.java @@ -42,6 +42,7 @@ import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.rtsp.RtspClient.PlaybackEventListener; +import com.google.android.exoplayer2.source.rtsp.RtspClient.SessionInfoListener; import com.google.android.exoplayer2.source.rtsp.RtspMediaSource.RtspPlaybackException; import com.google.android.exoplayer2.trackselection.ExoTrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection; @@ -62,22 +63,31 @@ /** A {@link MediaPeriod} that loads an RTSP stream. */ /* package */ final class RtspMediaPeriod implements MediaPeriod { + /** Listener for information about the period. */ + interface Listener { + + /** Called when the {@link RtspSessionTiming} is available. */ + void onSourceInfoRefreshed(RtspSessionTiming timing); + } + /** The maximum times to retry if the underlying data channel failed to bind. */ private static final int PORT_BINDING_MAX_RETRY_COUNT = 3; private final Allocator allocator; private final Handler handler; - private final InternalListener internalListener; private final RtspClient rtspClient; private final List rtspLoaderWrappers; private final List selectedLoadInfos; + private final Listener listener; + private final RtpDataChannel.Factory rtpDataChannelFactory; private @MonotonicNonNull Callback callback; private @MonotonicNonNull ImmutableList trackGroups; @Nullable private IOException preparationError; @Nullable private RtspPlaybackException playbackException; + private long lastSeekPositionUs; private long pendingSeekPositionUs; private boolean loadingFinished; private boolean released; @@ -90,29 +100,31 @@ * Creates an RTSP media period. * * @param allocator An {@link Allocator} from which to obtain media buffer allocations. - * @param rtspTracks A list of tracks in an RTSP playback session. - * @param rtspClient The {@link RtspClient} for the current RTSP playback. * @param rtpDataChannelFactory A {@link RtpDataChannel.Factory} for {@link RtpDataChannel}. + * @param uri The RTSP playback {@link Uri}. + * @param listener A {@link Listener} to receive session information updates. */ public RtspMediaPeriod( Allocator allocator, - List rtspTracks, - RtspClient rtspClient, - RtpDataChannel.Factory rtpDataChannelFactory) { + RtpDataChannel.Factory rtpDataChannelFactory, + Uri uri, + Listener listener, + String userAgent) { this.allocator = allocator; - handler = Util.createHandlerForCurrentLooper(); + this.rtpDataChannelFactory = rtpDataChannelFactory; + this.listener = listener; + handler = Util.createHandlerForCurrentLooper(); internalListener = new InternalListener(); - rtspLoaderWrappers = new ArrayList<>(rtspTracks.size()); - this.rtspClient = rtspClient; - this.rtspClient.setPlaybackEventListener(internalListener); + rtspClient = + new RtspClient( + /* sessionInfoListener= */ internalListener, + /* playbackEventListener= */ internalListener, + /* userAgent= */ userAgent, + /* uri= */ uri); + rtspLoaderWrappers = new ArrayList<>(); + selectedLoadInfos = new ArrayList<>(); - for (int i = 0; i < rtspTracks.size(); i++) { - RtspMediaTrack rtspMediaTrack = rtspTracks.get(i); - rtspLoaderWrappers.add( - new RtspLoaderWrapper(rtspMediaTrack, /* trackId= */ i, rtpDataChannelFactory)); - } - selectedLoadInfos = new ArrayList<>(rtspTracks.size()); pendingSeekPositionUs = C.TIME_UNSET; } @@ -121,6 +133,7 @@ public void release() { for (int i = 0; i < rtspLoaderWrappers.size(); i++) { rtspLoaderWrappers.get(i).release(); } + Util.closeQuietly(rtspClient); released = true; } @@ -128,8 +141,11 @@ public void release() { public void prepare(Callback callback, long positionUs) { this.callback = callback; - for (int i = 0; i < rtspLoaderWrappers.size(); i++) { - rtspLoaderWrappers.get(i).startLoading(); + try { + rtspClient.start(); + } catch (IOException e) { + preparationError = e; + Util.closeQuietly(rtspClient); } } @@ -233,6 +249,7 @@ public long seekToUs(long positionUs) { return positionUs; } + lastSeekPositionUs = positionUs; pendingSeekPositionUs = positionUs; rtspClient.seekToUs(positionUs); for (int i = 0; i < rtspLoaderWrappers.size(); i++) { @@ -256,14 +273,19 @@ public long getBufferedPositionUs() { return pendingSeekPositionUs; } - long bufferedPositionUs = rtspLoaderWrappers.get(0).sampleQueue.getLargestQueuedTimestampUs(); - for (int i = 1; i < rtspLoaderWrappers.size(); i++) { - bufferedPositionUs = - min( - bufferedPositionUs, - checkNotNull(rtspLoaderWrappers.get(i)).sampleQueue.getLargestQueuedTimestampUs()); + boolean allLoaderWrappersAreCanceled = true; + long bufferedPositionUs = Long.MAX_VALUE; + for (int i = 0; i < rtspLoaderWrappers.size(); i++) { + RtspLoaderWrapper loaderWrapper = rtspLoaderWrappers.get(i); + if (!loaderWrapper.canceled) { + bufferedPositionUs = min(bufferedPositionUs, loaderWrapper.getBufferedPositionUs()); + allLoaderWrappersAreCanceled = false; + } } - return bufferedPositionUs; + + return allLoaderWrappersAreCanceled || bufferedPositionUs == Long.MIN_VALUE + ? lastSeekPositionUs + : bufferedPositionUs; } @Override @@ -386,6 +408,7 @@ private final class InternalListener implements ExtractorOutput, Loader.Callback, UpstreamFormatChangedListener, + SessionInfoListener, PlaybackEventListener { // ExtractorOutput implementation. @@ -515,7 +538,7 @@ public void onPlaybackError(RtspPlaybackException error) { /** Handles the {@link Loadable} whose {@link RtpDataChannel} timed out. */ private LoadErrorAction handleSocketTimeout(RtpDataLoadable loadable) { // TODO(b/172331505) Allow for retry when loading is not ending. - if (getBufferedPositionUs() == Long.MIN_VALUE) { + if (getBufferedPositionUs() == 0) { if (!isUsingRtpTcp) { // Retry playback with TCP if no sample has been received so far, and we are not already // using TCP. Retrying will setup new loadables, so will not retry with the current @@ -533,9 +556,27 @@ private LoadErrorAction handleSocketTimeout(RtpDataLoadable loadable) { break; } } - playbackException = new RtspPlaybackException("Unknown loadable timed out."); return Loader.DONT_RETRY; } + + @Override + public void onSessionTimelineUpdated( + RtspSessionTiming timing, ImmutableList tracks) { + for (int i = 0; i < tracks.size(); i++) { + RtspMediaTrack rtspMediaTrack = tracks.get(i); + RtspLoaderWrapper loaderWrapper = + new RtspLoaderWrapper(rtspMediaTrack, /* trackId= */ i, rtpDataChannelFactory); + loaderWrapper.startLoading(); + rtspLoaderWrappers.add(loaderWrapper); + } + + listener.onSourceInfoRefreshed(timing); + } + + @Override + public void onSessionTimelineRequestFailed(String message, @Nullable Throwable cause) { + preparationError = cause == null ? new IOException(message) : new IOException(message, cause); + } } private void retryWithRtpTcp() { @@ -632,6 +673,14 @@ public RtspLoaderWrapper( sampleQueue.setUpstreamFormatChangeListener(internalListener); } + /** + * Returns the largest buffered position in microseconds; or {@link Long#MIN_VALUE} if no sample + * has been queued. + */ + public long getBufferedPositionUs() { + return sampleQueue.getLargestQueuedTimestampUs(); + } + /** Starts loading. */ public void startLoading() { loader.startLoading( diff --git a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMediaSource.java b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMediaSource.java index 4c28629b0df..a038b533158 100644 --- a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMediaSource.java +++ b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMediaSource.java @@ -16,30 +16,27 @@ package com.google.android.exoplayer2.source.rtsp; -import static com.google.android.exoplayer2.ExoPlayerLibraryInfo.VERSION_SLASHY; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; -import static com.google.android.exoplayer2.util.Util.castNonNull; +import android.net.Uri; import androidx.annotation.Nullable; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.ExoPlayerLibraryInfo; import com.google.android.exoplayer2.MediaItem; +import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.drm.DrmSessionManager; import com.google.android.exoplayer2.drm.DrmSessionManagerProvider; import com.google.android.exoplayer2.source.BaseMediaSource; +import com.google.android.exoplayer2.source.ForwardingTimeline; import com.google.android.exoplayer2.source.MediaPeriod; import com.google.android.exoplayer2.source.MediaSource; import com.google.android.exoplayer2.source.MediaSourceFactory; import com.google.android.exoplayer2.source.SinglePeriodTimeline; -import com.google.android.exoplayer2.source.rtsp.RtspClient.SessionInfoListener; import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.HttpDataSource; import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; import com.google.android.exoplayer2.upstream.TransferListener; -import com.google.android.exoplayer2.util.Util; -import com.google.common.collect.ImmutableList; import java.io.IOException; -import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** An Rtsp {@link MediaSource} */ public final class RtspMediaSource extends BaseMediaSource { @@ -63,8 +60,13 @@ public final class RtspMediaSource extends BaseMediaSource { */ public static final class Factory implements MediaSourceFactory { + private String userAgent; private boolean forceUseRtpTcp; + public Factory() { + userAgent = ExoPlayerLibraryInfo.VERSION_SLASHY; + } + /** * Sets whether to force using TCP as the default RTP transport. * @@ -81,6 +83,17 @@ public Factory setForceUseRtpTcp(boolean forceUseRtpTcp) { return this; } + /** + * Sets the user agent, the default value is {@link ExoPlayerLibraryInfo#VERSION_SLASHY}. + * + * @param userAgent The user agent. + * @return This Factory, for convenience. + */ + public Factory setUserAgent(String userAgent) { + this.userAgent = userAgent; + return this; + } + /** Does nothing. {@link RtspMediaSource} does not support DRM. */ @Override public Factory setDrmSessionManagerProvider( @@ -149,7 +162,8 @@ public RtspMediaSource createMediaSource(MediaItem mediaItem) { mediaItem, forceUseRtpTcp ? new TransferRtpDataChannelFactory() - : new UdpDataSourceRtpDataChannelFactory()); + : new UdpDataSourceRtpDataChannelFactory(), + userAgent); } } @@ -170,34 +184,32 @@ public RtspPlaybackException(String message, Throwable e) { private final MediaItem mediaItem; private final RtpDataChannel.Factory rtpDataChannelFactory; - private @MonotonicNonNull RtspClient rtspClient; + private final String userAgent; + private final Uri uri; - @Nullable private ImmutableList rtspMediaTracks; - @Nullable private IOException sourcePrepareException; + private long timelineDurationUs; + private boolean timelineIsSeekable; + private boolean timelineIsLive; + private boolean timelineIsPlaceholder; - private RtspMediaSource(MediaItem mediaItem, RtpDataChannel.Factory rtpDataChannelFactory) { + private RtspMediaSource( + MediaItem mediaItem, RtpDataChannel.Factory rtpDataChannelFactory, String userAgent) { this.mediaItem = mediaItem; this.rtpDataChannelFactory = rtpDataChannelFactory; + this.userAgent = userAgent; + this.uri = checkNotNull(this.mediaItem.playbackProperties).uri; + this.timelineDurationUs = C.TIME_UNSET; + this.timelineIsPlaceholder = true; } @Override protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) { - checkNotNull(mediaItem.playbackProperties); - try { - rtspClient = - new RtspClient( - new SessionInfoListenerImpl(), - /* userAgent= */ VERSION_SLASHY, - mediaItem.playbackProperties.uri); - rtspClient.start(); - } catch (IOException e) { - sourcePrepareException = new RtspPlaybackException("RtspClient not opened.", e); - } + notifySourceInfoRefreshed(); } @Override protected void releaseSourceInternal() { - Util.closeQuietly(rtspClient); + // Do nothing. } @Override @@ -206,16 +218,24 @@ public MediaItem getMediaItem() { } @Override - public void maybeThrowSourceInfoRefreshError() throws IOException { - if (sourcePrepareException != null) { - throw sourcePrepareException; - } + public void maybeThrowSourceInfoRefreshError() { + // Do nothing. } @Override public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) { return new RtspMediaPeriod( - allocator, checkNotNull(rtspMediaTracks), checkNotNull(rtspClient), rtpDataChannelFactory); + allocator, + rtpDataChannelFactory, + uri, + (timing) -> { + timelineDurationUs = C.msToUs(timing.getDurationMs()); + timelineIsSeekable = !timing.isLive(); + timelineIsLive = timing.isLive(); + timelineIsPlaceholder = false; + notifySourceInfoRefreshed(); + }, + userAgent); } @Override @@ -223,28 +243,36 @@ public void releasePeriod(MediaPeriod mediaPeriod) { ((RtspMediaPeriod) mediaPeriod).release(); } - private final class SessionInfoListenerImpl implements SessionInfoListener { - @Override - public void onSessionTimelineUpdated( - RtspSessionTiming timing, ImmutableList tracks) { - rtspMediaTracks = tracks; - refreshSourceInfo( - new SinglePeriodTimeline( - /* durationUs= */ C.msToUs(timing.getDurationMs()), - /* isSeekable= */ !timing.isLive(), - /* isDynamic= */ false, - /* useLiveConfiguration= */ timing.isLive(), - /* manifest= */ null, - mediaItem)); - } + // Internal methods. - @Override - public void onSessionTimelineRequestFailed(String message, @Nullable Throwable cause) { - if (cause == null) { - sourcePrepareException = new RtspPlaybackException(message); - } else { - sourcePrepareException = new RtspPlaybackException(message, castNonNull(cause)); - } + private void notifySourceInfoRefreshed() { + Timeline timeline = + new SinglePeriodTimeline( + timelineDurationUs, + timelineIsSeekable, + /* isDynamic= */ false, + /* useLiveConfiguration= */ timelineIsLive, + /* manifest= */ null, + mediaItem); + if (timelineIsPlaceholder) { + timeline = + new ForwardingTimeline(timeline) { + @Override + public Window getWindow( + int windowIndex, Window window, long defaultPositionProjectionUs) { + super.getWindow(windowIndex, window, defaultPositionProjectionUs); + window.isPlaceholder = true; + return window; + } + + @Override + public Period getPeriod(int periodIndex, Period period, boolean setIds) { + super.getPeriod(periodIndex, period, setIds); + period.isPlaceholder = true; + return period; + } + }; } + refreshSourceInfo(timeline); } } diff --git a/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspClientTest.java b/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspClientTest.java index 2dca1ff695a..e75dd117dcf 100644 --- a/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspClientTest.java +++ b/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspClientTest.java @@ -22,7 +22,9 @@ import androidx.test.core.app.ApplicationProvider; import androidx.test.ext.junit.runners.AndroidJUnit4; import com.google.android.exoplayer2.robolectric.RobolectricUtil; +import com.google.android.exoplayer2.source.rtsp.RtspClient.PlaybackEventListener; import com.google.android.exoplayer2.source.rtsp.RtspClient.SessionInfoListener; +import com.google.android.exoplayer2.source.rtsp.RtspMediaSource.RtspPlaybackException; import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.util.Util; import com.google.common.collect.ImmutableList; @@ -76,6 +78,17 @@ public void onSessionTimelineUpdated( public void onSessionTimelineRequestFailed( String message, @Nullable Throwable cause) {} }, + new PlaybackEventListener() { + @Override + public void onRtspSetupCompleted() {} + + @Override + public void onPlaybackStarted( + long startPositionUs, ImmutableList trackTimingList) {} + + @Override + public void onPlaybackError(RtspPlaybackException error) {} + }, /* userAgent= */ "ExoPlayer:RtspClientTest", /* uri= */ Uri.parse( Util.formatInvariant("rtsp://localhost:%d/test", serverRtspPortNumber))); diff --git a/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspMediaPeriodTest.java b/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspMediaPeriodTest.java deleted file mode 100644 index 1753d424844..00000000000 --- a/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspMediaPeriodTest.java +++ /dev/null @@ -1,105 +0,0 @@ -/* - * Copyright 2021 The Android Open Source Project - * - * Licensed under the Apache License, Version 2.0 (the "License"); - * you may not use this file except in compliance with the License. - * You may obtain a copy of the License at - * - * http://www.apache.org/licenses/LICENSE-2.0 - * - * Unless required by applicable law or agreed to in writing, software - * distributed under the License is distributed on an "AS IS" BASIS, - * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. - * See the License for the specific language governing permissions and - * limitations under the License. - */ -package com.google.android.exoplayer2.source.rtsp; - -import static com.google.android.exoplayer2.robolectric.RobolectricUtil.runMainLooperUntil; -import static com.google.common.truth.Truth.assertThat; - -import android.net.Uri; -import androidx.annotation.Nullable; -import androidx.test.ext.junit.runners.AndroidJUnit4; -import com.google.android.exoplayer2.C; -import com.google.android.exoplayer2.source.MediaPeriod; -import com.google.android.exoplayer2.upstream.DefaultAllocator; -import com.google.common.collect.ImmutableList; -import java.util.concurrent.atomic.AtomicBoolean; -import org.junit.Test; -import org.junit.runner.RunWith; -import org.robolectric.annotation.internal.DoNotInstrument; - -/** Unit test for {@link RtspMediaPeriod}. */ -@RunWith(AndroidJUnit4.class) -@DoNotInstrument -public class RtspMediaPeriodTest { - - private static final RtspClient PLACEHOLDER_RTSP_CLIENT = - new RtspClient( - new RtspClient.SessionInfoListener() { - @Override - public void onSessionTimelineUpdated( - RtspSessionTiming timing, ImmutableList tracks) {} - - @Override - public void onSessionTimelineRequestFailed(String message, @Nullable Throwable cause) {} - }, - /* userAgent= */ null, - Uri.EMPTY); - - @Test - public void prepare_startsLoading() throws Exception { - RtspMediaPeriod rtspMediaPeriod = - new RtspMediaPeriod( - new DefaultAllocator(/* trimOnReset= */ true, C.DEFAULT_BUFFER_SEGMENT_SIZE), - ImmutableList.of( - new RtspMediaTrack( - new MediaDescription.Builder( - /* mediaType= */ MediaDescription.MEDIA_TYPE_VIDEO, - /* port= */ 0, - /* transportProtocol= */ MediaDescription.RTP_AVP_PROFILE, - /* payloadType= */ 96) - .setConnection("IN IP4 0.0.0.0") - .setBitrate(500_000) - .addAttribute(SessionDescription.ATTR_RTPMAP, "96 H264/90000") - .addAttribute( - SessionDescription.ATTR_FMTP, - "96 packetization-mode=1;profile-level-id=64001F;sprop-parameter-sets=Z2QAH6zZQPARabIAAAMACAAAAwGcHjBjLA==,aOvjyyLA") - .addAttribute(SessionDescription.ATTR_CONTROL, "track1") - .build(), - Uri.parse("rtsp://localhost/test"))), - PLACEHOLDER_RTSP_CLIENT, - new UdpDataSourceRtpDataChannelFactory()); - - AtomicBoolean prepareCallbackCalled = new AtomicBoolean(false); - rtspMediaPeriod.prepare( - new MediaPeriod.Callback() { - @Override - public void onPrepared(MediaPeriod mediaPeriod) { - prepareCallbackCalled.set(true); - } - - @Override - public void onContinueLoadingRequested(MediaPeriod source) { - source.continueLoading(/* positionUs= */ 0); - } - }, - /* positionUs= */ 0); - - runMainLooperUntil(prepareCallbackCalled::get); - rtspMediaPeriod.release(); - } - - @Test - public void getBufferedPositionUs_withNoRtspMediaTracks_returnsEndOfSource() { - RtspMediaPeriod rtspMediaPeriod = - new RtspMediaPeriod( - new DefaultAllocator(/* trimOnReset= */ true, C.DEFAULT_BUFFER_SEGMENT_SIZE), - ImmutableList.of(), - PLACEHOLDER_RTSP_CLIENT, - new UdpDataSourceRtpDataChannelFactory()); - - assertThat(rtspMediaPeriod.getBufferedPositionUs()).isEqualTo(C.TIME_END_OF_SOURCE); - } -}