diff --git a/RELEASENOTES.md b/RELEASENOTES.md index dfed41bfdb..2b984f4350 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -84,6 +84,12 @@ This release corresponds to the ([#233](https://github.com/androidx/media/issues/233)). * Make `QueueTimeline` more robust in case of a shady legacy session state ([#241](https://github.com/androidx/media/issues/241)). + * Fix a bug where notification play/pause button doesn't update with + player state ([#192](https://github.com/androidx/media/issues/192)). +* RTSP: + * Catch the IllegalArgumentException thrown in parsing of invalid RTSP + Describe response messages + ([#10971](https://github.com/google/ExoPlayer/issues/10971)). * Metadata: * Parse multiple null-separated values from ID3 frames, as permitted by ID3 v2.4. diff --git a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspClient.java b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspClient.java index 84bcc4cd8d..c8ac907a8d 100644 --- a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspClient.java +++ b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspClient.java @@ -652,7 +652,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; default: throw new IllegalStateException(); } - } catch (ParserException e) { + } catch (ParserException | IllegalArgumentException e) { dispatchRtspError(new RtspPlaybackException(e)); } } diff --git a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspMediaTrack.java b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspMediaTrack.java index b3d79404a2..b96d3d54b9 100644 --- a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspMediaTrack.java +++ b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/RtspMediaTrack.java @@ -159,7 +159,8 @@ import com.google.common.collect.ImmutableMap; * @param sessionUri The {@link Uri} of the RTSP playback session. */ public RtspMediaTrack(MediaDescription mediaDescription, Uri sessionUri) { - checkArgument(mediaDescription.attributes.containsKey(ATTR_CONTROL)); + checkArgument( + mediaDescription.attributes.containsKey(ATTR_CONTROL), "missing attribute control"); payloadFormat = generatePayloadFormat(mediaDescription); uri = extractTrackUri(sessionUri, castNonNull(mediaDescription.attributes.get(ATTR_CONTROL))); } @@ -210,7 +211,7 @@ import com.google.common.collect.ImmutableMap; switch (mimeType) { case MimeTypes.AUDIO_AAC: checkArgument(channelCount != C.INDEX_UNSET); - checkArgument(!fmtpParameters.isEmpty()); + checkArgument(!fmtpParameters.isEmpty(), "missing attribute fmtp"); if (mediaEncoding.equals(RtpPayloadFormat.RTP_MEDIA_MPEG4_LATM_AUDIO)) { // cpresent is defined in RFC3016 Section 5.3. cpresent=0 means the config fmtp parameter // must exist. @@ -259,11 +260,11 @@ import com.google.common.collect.ImmutableMap; formatBuilder.setWidth(DEFAULT_H263_WIDTH).setHeight(DEFAULT_H263_HEIGHT); break; case MimeTypes.VIDEO_H264: - checkArgument(!fmtpParameters.isEmpty()); + checkArgument(!fmtpParameters.isEmpty(), "missing attribute fmtp"); processH264FmtpAttribute(formatBuilder, fmtpParameters); break; case MimeTypes.VIDEO_H265: - checkArgument(!fmtpParameters.isEmpty()); + checkArgument(!fmtpParameters.isEmpty(), "missing attribute fmtp"); processH265FmtpAttribute(formatBuilder, fmtpParameters); break; case MimeTypes.VIDEO_VP8: @@ -312,7 +313,8 @@ import com.google.common.collect.ImmutableMap; ImmutableMap fmtpAttributes, int channelCount, int sampleRate) { - checkArgument(fmtpAttributes.containsKey(PARAMETER_PROFILE_LEVEL_ID)); + checkArgument( + fmtpAttributes.containsKey(PARAMETER_PROFILE_LEVEL_ID), "missing profile-level-id param"); String profileLevel = checkNotNull(fmtpAttributes.get(PARAMETER_PROFILE_LEVEL_ID)); formatBuilder.setCodecs(AAC_CODECS_PREFIX + profileLevel); formatBuilder.setInitializationData( @@ -380,10 +382,10 @@ import com.google.common.collect.ImmutableMap; private static void processH264FmtpAttribute( Format.Builder formatBuilder, ImmutableMap fmtpAttributes) { - checkArgument(fmtpAttributes.containsKey(PARAMETER_SPROP_PARAMS)); + checkArgument(fmtpAttributes.containsKey(PARAMETER_SPROP_PARAMS), "missing sprop parameter"); String spropParameterSets = checkNotNull(fmtpAttributes.get(PARAMETER_SPROP_PARAMS)); String[] parameterSets = Util.split(spropParameterSets, ","); - checkArgument(parameterSets.length == 2); + checkArgument(parameterSets.length == 2, "empty sprop value"); ImmutableList initializationData = ImmutableList.of( getInitializationDataFromParameterSet(parameterSets[0]), @@ -418,11 +420,14 @@ import com.google.common.collect.ImmutableMap; maxDonDiff == 0, "non-zero sprop-max-don-diff " + maxDonDiff + " is not supported"); } - checkArgument(fmtpAttributes.containsKey(PARAMETER_H265_SPROP_VPS)); + checkArgument( + fmtpAttributes.containsKey(PARAMETER_H265_SPROP_VPS), "missing sprop-vps parameter"); String spropVPS = checkNotNull(fmtpAttributes.get(PARAMETER_H265_SPROP_VPS)); - checkArgument(fmtpAttributes.containsKey(PARAMETER_H265_SPROP_SPS)); + checkArgument( + fmtpAttributes.containsKey(PARAMETER_H265_SPROP_SPS), "missing sprop-sps parameter"); String spropSPS = checkNotNull(fmtpAttributes.get(PARAMETER_H265_SPROP_SPS)); - checkArgument(fmtpAttributes.containsKey(PARAMETER_H265_SPROP_PPS)); + checkArgument( + fmtpAttributes.containsKey(PARAMETER_H265_SPROP_PPS), "missing sprop-pps parameter"); String spropPPS = checkNotNull(fmtpAttributes.get(PARAMETER_H265_SPROP_PPS)); ImmutableList initializationData = ImmutableList.of( diff --git a/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspClientTest.java b/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspClientTest.java index f7b7ab41b8..104f6ae9c3 100644 --- a/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspClientTest.java +++ b/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/RtspClientTest.java @@ -389,4 +389,68 @@ public final class RtspClientTest { RobolectricUtil.runMainLooperUntil(timelineRequestFailed::get); assertThat(rtspClient.getState()).isEqualTo(RtspClient.RTSP_STATE_UNINITIALIZED); } + + @Test + public void connectServerAndClient_sdpInDescribeResponseHasInvalidFmtpAttr_doesNotUpdateTimeline() + throws Exception { + class ResponseProvider implements RtspServer.ResponseProvider { + @Override + public RtspResponse getOptionsResponse() { + return new RtspResponse( + /* status= */ 200, + new RtspHeaders.Builder().add(RtspHeaders.PUBLIC, "OPTIONS, DESCRIBE").build()); + } + + @Override + public RtspResponse getDescribeResponse(Uri requestedUri, RtspHeaders headers) { + String testMediaSdpInfo = + "v=0\r\n" + + "o=- 1600785369059721 1 IN IP4 192.168.2.176\r\n" + + "s=video, streamed by ExoPlayer\r\n" + + "i=test.mkv\r\n" + + "t=0 0\r\n" + + "a=tool:ExoPlayer\r\n" + + "a=type:broadcast\r\n" + + "a=control:*\r\n" + + "a=range:npt=0-30.102\r\n" + + "m=video 0 RTP/AVP 96\r\n" + + "c=IN IP4 0.0.0.0\r\n" + + "b=AS:500\r\n" + + "a=rtpmap:96 H264/90000\r\n" + + "a=fmtp:96" + + " packetization-mode=1;profile-level-id=64001F;sprop-parameter-sets=\r\n" + + "a=control:track1\r\n"; + return RtspTestUtils.newDescribeResponseWithSdpMessage( + /* sessionDescription= */ testMediaSdpInfo, + // This session description has no tracks. + /* rtpPacketStreamDumps= */ ImmutableList.of(), + requestedUri); + } + } + rtspServer = new RtspServer(new ResponseProvider()); + + AtomicBoolean timelineRequestFailed = new AtomicBoolean(); + rtspClient = + new RtspClient( + new SessionInfoListener() { + @Override + public void onSessionTimelineUpdated( + RtspSessionTiming timing, ImmutableList tracks) {} + + @Override + public void onSessionTimelineRequestFailed( + String message, @Nullable Throwable cause) { + timelineRequestFailed.set(true); + } + }, + EMPTY_PLAYBACK_LISTENER, + /* userAgent= */ "ExoPlayer:RtspClientTest", + RtspTestUtils.getTestUri(rtspServer.startAndGetPortNumber()), + SocketFactory.getDefault(), + /* debugLoggingEnabled= */ false); + rtspClient.start(); + + RobolectricUtil.runMainLooperUntil(timelineRequestFailed::get); + assertThat(rtspClient.getState()).isEqualTo(RtspClient.RTSP_STATE_UNINITIALIZED); + } }