diff --git a/RELEASENOTES.md b/RELEASENOTES.md index c3a55538c63..538e3ee11bf 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -42,6 +42,8 @@ * DASH Extension: * Smooth Streaming Extension: * RTSP Extension: + * Skip empty session information values (i-tags) in SDP parsing + ([#1087](https://github.com/androidx/media/issues/1087)). * Decoder Extensions (FFmpeg, VP9, AV1, MIDI, etc.): * Leanback extension: * Cast Extension: diff --git a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/SessionDescriptionParser.java b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/SessionDescriptionParser.java index cbbd844a1e7..0e28d66e69e 100644 --- a/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/SessionDescriptionParser.java +++ b/libraries/exoplayer_rtsp/src/main/java/androidx/media3/exoplayer/rtsp/SessionDescriptionParser.java @@ -26,6 +26,7 @@ import androidx.media3.common.ParserException; import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; +import java.util.Objects; import java.util.regex.Matcher; import java.util.regex.Pattern; @@ -35,6 +36,8 @@ // SDP line always starts with an one letter tag, followed by an equal sign. The information // under the given tag follows an optional space. private static final Pattern SDP_LINE_PATTERN = Pattern.compile("([a-z])=\\s?(.+)"); + // SDP line with a one letter tag, an equal sign, and an empty value. + private static final Pattern SDP_LINE_WITH_EMPTY_VALUE_PATTERN = Pattern.compile("^([a-z])=$"); // Matches an attribute line (with a= sdp tag removed. Example: range:npt=0-50.0). // Attribute can also be a flag, i.e. without a value, like recvonly. Reference RFC4566 Section 9 // Page 43, under "token-char". @@ -81,6 +84,11 @@ public static SessionDescription parse(String sdpString) throws ParserException Matcher matcher = SDP_LINE_PATTERN.matcher(line); if (!matcher.matches()) { + Matcher sdpTagMatcher = SDP_LINE_WITH_EMPTY_VALUE_PATTERN.matcher(line); + if (sdpTagMatcher.matches() && Objects.equals(sdpTagMatcher.group(1), INFORMATION_TYPE)) { + // Allow and skip empty Session Information (tag 'i') attributes + continue; + } throw ParserException.createForMalformedManifest( "Malformed SDP line: " + line, /* cause= */ null); } diff --git a/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/SessionDescriptionTest.java b/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/SessionDescriptionTest.java index 189fb5653ba..060e5727006 100644 --- a/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/SessionDescriptionTest.java +++ b/libraries/exoplayer_rtsp/src/test/java/androidx/media3/exoplayer/rtsp/SessionDescriptionTest.java @@ -29,6 +29,7 @@ import static org.junit.Assert.assertThrows; import android.net.Uri; +import androidx.media3.common.ParserException; import androidx.test.ext.junit.runners.AndroidJUnit4; import org.junit.Test; import org.junit.runner.RunWith; @@ -270,6 +271,38 @@ public void parse_sdpStringWithExtraSpaceInRtpMapAttribute_succeeds() throws Exc assertThat(rtpMapAttribute.clockRate).isEqualTo(44100); } + @Test + public void parse_sdpStringWithEmptyInformationAttribute_succeeds() throws Exception { + String testMediaSdpInfo = + "v=0\r\n" + + "o=MNobody 2890844526 2890842807 IN IP4 192.0.2.46\r\n" + + "s=SDP Seminar\r\n" + + "i=\r\n" + + "t=0 0\r\n" + + "a=control:*\r\n" + + "m=audio 3456 RTP/AVP 0\r\n" + + "i=\r\n" + + "a=rtpmap:97 AC3/44100 \r\n"; + + SessionDescription sessionDescription = SessionDescriptionParser.parse(testMediaSdpInfo); + + assertThat(sessionDescription.sessionInfo).isNull(); + assertThat(sessionDescription.mediaDescriptionList.get(0).mediaTitle).isNull(); + } + + @Test + public void parse_sdpStringWithEmptySessionAttribute_throwsParserException() { + String testMediaSdpInfo = + "v=0\r\n" + + "o=MNobody 2890844526 2890842807 IN IP4 192.0.2.46\r\n" + + "s=\r\n" + + "a=control:*\r\n" + + "m=audio 3456 RTP/AVP 0\r\n" + + "a=rtpmap:97 AC3/44100 \r\n"; + + assertThrows(ParserException.class, () -> SessionDescriptionParser.parse(testMediaSdpInfo)); + } + @Test public void buildMediaDescription_withInvalidRtpmapAttribute_throwsIllegalStateException() { assertThrows(