From 5a60db3328264f1c2c27ff94133d3226328dcde7 Mon Sep 17 00:00:00 2001 From: claincly Date: Fri, 21 Jan 2022 11:22:53 +0000 Subject: [PATCH] Handle when RTSP track timing is not available. Issue: google/ExoPlayer#9775 We got a few issues for this on GH already. Some RTSP servers do not provide track timing in PLAY responses, or the timings are invalid. Missing timing means the RTSP stream is not seekable. Added method to 1. Update the timeline that seek is not possible 2. Report read discontinuity so that playback can start from the beginning. PiperOrigin-RevId: 423281439 --- RELEASENOTES.md | 4 +- .../source/rtsp/RtspMediaPeriod.java | 51 ++++++++++++++++--- .../source/rtsp/RtspMediaSource.java | 21 +++++--- 3 files changed, 62 insertions(+), 14 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 4bcf00f27e9..13c13e8795b 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -106,8 +106,10 @@ * RTSP: * Provide a client API to override the `SocketFactory` used for any server connection ([#9606](https://github.com/google/ExoPlayer/pull/9606)). - * Prefers DIGEST authentication method over BASIC if both are present. + * Prefers DIGEST authentication method over BASIC if both are present ([#9800](https://github.com/google/ExoPlayer/issues/9800)). + * Handle when RTSP track timing is not available + ([#9775](https://github.com/google/ExoPlayer/issues/9775)). * Cast extension * Fix bug that prevented `CastPlayer` from calling `onIsPlayingChanged` correctly ([#9792](https://github.com/google/ExoPlayer/issues/9792)). 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 6113cd3089a..0f1c94c0971 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 @@ -67,6 +67,9 @@ interface Listener { /** Called when the {@link RtspSessionTiming} is available. */ void onSourceInfoRefreshed(RtspSessionTiming timing); + + /** Called when the RTSP server does not support seeking. */ + default void onSeekingUnsupported() {} } /** The maximum times to retry if the underlying data channel failed to bind. */ @@ -90,6 +93,7 @@ interface Listener { private long pendingSeekPositionUs; private long pendingSeekPositionUsForTcpRetry; private boolean loadingFinished; + private boolean notifyDiscontinuity; private boolean released; private boolean prepared; private boolean trackSelected; @@ -243,6 +247,14 @@ public void discardBuffer(long positionUs, boolean toKeyframe) { @Override public long readDiscontinuity() { + // Discontinuity only happens in RTSP when seeking an unexpectedly un-seekable RTSP server (a + // server that doesn't include the required RTP-Info header in its PLAY responses). This only + // applies to seeks made before receiving the first RTSP PLAY response. The playback can only + // start from time zero in this case. + if (notifyDiscontinuity) { + notifyDiscontinuity = false; + return 0; + } return C.TIME_UNSET; } @@ -355,7 +367,7 @@ public void reevaluateBuffer(long positionUs) { // SampleStream methods. /* package */ boolean isReady(int trackGroupIndex) { - return rtspLoaderWrappers.get(trackGroupIndex).isSampleQueueReady(); + return !suppressRead() && rtspLoaderWrappers.get(trackGroupIndex).isSampleQueueReady(); } @ReadDataResult @@ -364,9 +376,23 @@ public void reevaluateBuffer(long positionUs) { FormatHolder formatHolder, DecoderInputBuffer buffer, @ReadFlags int readFlags) { + if (suppressRead()) { + return C.RESULT_NOTHING_READ; + } return rtspLoaderWrappers.get(sampleQueueIndex).read(formatHolder, buffer, readFlags); } + /* package */ int skipData(int sampleQueueIndex, long positionUs) { + if (suppressRead()) { + return C.RESULT_NOTHING_READ; + } + return rtspLoaderWrappers.get(sampleQueueIndex).skipData(positionUs); + } + + private boolean suppressRead() { + return notifyDiscontinuity; + } + // Internal methods. @Nullable @@ -549,7 +575,9 @@ public void onRtspSetupCompleted() { @Override public void onPlaybackStarted( long startPositionUs, ImmutableList trackTimingList) { - // Validate that the trackTimingList contains timings for the selected tracks. + + // Validate that the trackTimingList contains timings for the selected tracks, and notify the + // listener. ArrayList trackUrisWithTiming = new ArrayList<>(trackTimingList.size()); for (int i = 0; i < trackTimingList.size(); i++) { trackUrisWithTiming.add(checkNotNull(trackTimingList.get(i).uri.getPath())); @@ -557,10 +585,13 @@ public void onPlaybackStarted( for (int i = 0; i < selectedLoadInfos.size(); i++) { RtpLoadInfo loadInfo = selectedLoadInfos.get(i); if (!trackUrisWithTiming.contains(loadInfo.getTrackUri().getPath())) { - playbackException = - new RtspPlaybackException( - "Server did not provide timing for track " + loadInfo.getTrackUri()); - return; + listener.onSeekingUnsupported(); + if (isSeekPending()) { + notifyDiscontinuity = true; + pendingSeekPositionUs = C.TIME_UNSET; + requestedSeekPositionUs = C.TIME_UNSET; + pendingSeekPositionUsForTcpRetry = C.TIME_UNSET; + } } } @@ -697,7 +728,7 @@ public int readData( @Override public int skipData(long positionUs) { - return 0; + return RtspMediaPeriod.this.skipData(track, positionUs); } } @@ -748,6 +779,12 @@ public int read( return sampleQueue.read(formatHolder, buffer, readFlags, /* loadingFinished= */ canceled); } + public int skipData(long positionUs) { + int skipCount = sampleQueue.getSkipCount(positionUs, /* allowEndOfQueue= */ canceled); + sampleQueue.skip(skipCount); + return skipCount; + } + /** Cancels loading. */ public void cancelLoad() { if (!canceled) { 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 5fd1e6cb04d..05301cdd542 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 @@ -255,12 +255,21 @@ public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long star allocator, rtpDataChannelFactory, uri, - /* listener= */ timing -> { - timelineDurationUs = Util.msToUs(timing.getDurationMs()); - timelineIsSeekable = !timing.isLive(); - timelineIsLive = timing.isLive(); - timelineIsPlaceholder = false; - notifySourceInfoRefreshed(); + new RtspMediaPeriod.Listener() { + @Override + public void onSourceInfoRefreshed(RtspSessionTiming timing) { + timelineDurationUs = Util.msToUs(timing.getDurationMs()); + timelineIsSeekable = !timing.isLive(); + timelineIsLive = timing.isLive(); + timelineIsPlaceholder = false; + notifySourceInfoRefreshed(); + } + + @Override + public void onSeekingUnsupported() { + timelineIsSeekable = false; + notifySourceInfoRefreshed(); + } }, userAgent, socketFactory,