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 f4bcb904ffe..af865c8bba2 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 @@ -337,19 +337,22 @@ private static boolean serverSupportsDescribe(List serverSupportedMetho } /** - * Gets the included {@link RtspMediaTrack RtspMediaTracks} from a {@link SessionDescription}. + * Returns the included {@link RtspMediaTrack RtspMediaTracks} from parsing the {@link + * SessionDescription} within the {@link RtspDescribeResponse}. * - * @param sessionDescription The {@link SessionDescription}. + * @param rtspDescribeResponse The {@link RtspDescribeResponse} from which to retrieve the tracks. * @param uri The RTSP playback URI. */ private static ImmutableList buildTrackList( - SessionDescription sessionDescription, Uri uri) { + RtspDescribeResponse rtspDescribeResponse, Uri uri) { ImmutableList.Builder trackListBuilder = new ImmutableList.Builder<>(); - for (int i = 0; i < sessionDescription.mediaDescriptionList.size(); i++) { - MediaDescription mediaDescription = sessionDescription.mediaDescriptionList.get(i); + for (int i = 0; i < rtspDescribeResponse.sessionDescription.mediaDescriptionList.size(); i++) { + MediaDescription mediaDescription = + rtspDescribeResponse.sessionDescription.mediaDescriptionList.get(i); // Includes tracks with supported formats only. if (RtpPayloadFormat.isFormatSupported(mediaDescription)) { - trackListBuilder.add(new RtspMediaTrack(mediaDescription, uri)); + trackListBuilder.add( + new RtspMediaTrack(rtspDescribeResponse.headers, mediaDescription, uri)); } } return trackListBuilder.build(); @@ -618,7 +621,9 @@ private void handleRtspResponse(List message) { case METHOD_DESCRIBE: onDescribeResponseReceived( new RtspDescribeResponse( - response.status, SessionDescriptionParser.parse(response.messageBody))); + response.headers, + response.status, + SessionDescriptionParser.parse(response.messageBody))); break; case METHOD_SETUP: @@ -708,7 +713,7 @@ private void onDescribeResponseReceived(RtspDescribeResponse response) { } } - ImmutableList tracks = buildTrackList(response.sessionDescription, uri); + ImmutableList tracks = buildTrackList(response, uri); if (tracks.isEmpty()) { sessionInfoListener.onSessionTimelineRequestFailed("No playable track.", /* cause= */ null); return; diff --git a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspDescribeResponse.java b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspDescribeResponse.java index 04d8273ca89..0012c93b6dd 100644 --- a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspDescribeResponse.java +++ b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspDescribeResponse.java @@ -17,6 +17,8 @@ /** Represents an RTSP DESCRIBE response. */ /* package */ final class RtspDescribeResponse { + /** The response's headers. */ + public final RtspHeaders headers; /** The response's status code. */ public final int status; /** The {@link SessionDescription} (see RFC2327) in the DESCRIBE response. */ @@ -25,10 +27,13 @@ /** * Creates a new instance. * + * @param headers The response's headers. * @param status The response's status code. * @param sessionDescription The {@link SessionDescription} in the DESCRIBE response. */ - public RtspDescribeResponse(int status, SessionDescription sessionDescription) { + public RtspDescribeResponse( + RtspHeaders headers, int status, SessionDescription sessionDescription) { + this.headers = headers; this.status = status; this.sessionDescription = sessionDescription; } diff --git a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMediaTrack.java b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMediaTrack.java index 63d5d81c0cf..74a051c04e2 100644 --- a/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMediaTrack.java +++ b/library/rtsp/src/main/java/com/google/android/exoplayer2/source/rtsp/RtspMediaTrack.java @@ -17,6 +17,8 @@ import static com.google.android.exoplayer2.source.rtsp.MediaDescription.MEDIA_TYPE_AUDIO; import static com.google.android.exoplayer2.source.rtsp.RtpPayloadFormat.getMimeTypeFromRtpMediaType; +import static com.google.android.exoplayer2.source.rtsp.RtspHeaders.CONTENT_BASE; +import static com.google.android.exoplayer2.source.rtsp.RtspHeaders.CONTENT_LOCATION; import static com.google.android.exoplayer2.source.rtsp.SessionDescription.ATTR_CONTROL; import static com.google.android.exoplayer2.util.Assertions.checkArgument; import static com.google.android.exoplayer2.util.Assertions.checkNotNull; @@ -24,6 +26,7 @@ import static com.google.android.exoplayer2.util.Util.castNonNull; import android.net.Uri; +import android.text.TextUtils; import android.util.Base64; import android.util.Pair; import androidx.annotation.Nullable; @@ -153,14 +156,18 @@ /** * Creates a new instance from a {@link MediaDescription}. * + * @param rtspHeaders The {@link RtspHeaders} from the session's DESCRIBE response. * @param mediaDescription The {@link MediaDescription} of this track. * @param sessionUri The {@link Uri} of the RTSP playback session. */ - public RtspMediaTrack(MediaDescription mediaDescription, Uri sessionUri) { + public RtspMediaTrack( + RtspHeaders rtspHeaders, MediaDescription mediaDescription, Uri sessionUri) { checkArgument( mediaDescription.attributes.containsKey(ATTR_CONTROL), "missing attribute control"); payloadFormat = generatePayloadFormat(mediaDescription); - uri = extractTrackUri(sessionUri, castNonNull(mediaDescription.attributes.get(ATTR_CONTROL))); + uri = + extractTrackUri( + rtspHeaders, sessionUri, castNonNull(mediaDescription.attributes.get(ATTR_CONTROL))); } @Override @@ -464,15 +471,25 @@ private static void processH265FmtpAttribute( * *

The processing logic is specified in RFC2326 Section C.1.1. * + * @param rtspHeaders The {@link RtspHeaders} from the session's DESCRIBE response. * @param sessionUri The session URI. * @param controlAttributeString The control attribute from the track's {@link MediaDescription}. * @return The extracted track URI. */ - private static Uri extractTrackUri(Uri sessionUri, String controlAttributeString) { + private static Uri extractTrackUri( + RtspHeaders rtspHeaders, Uri sessionUri, String controlAttributeString) { Uri controlAttributeUri = Uri.parse(controlAttributeString); if (controlAttributeUri.isAbsolute()) { return controlAttributeUri; - } else if (controlAttributeString.equals(GENERIC_CONTROL_ATTR)) { + } + + if (!TextUtils.isEmpty(rtspHeaders.get(CONTENT_BASE))) { + sessionUri = Uri.parse(rtspHeaders.get(CONTENT_BASE)); + } else if (!TextUtils.isEmpty(rtspHeaders.get(CONTENT_LOCATION))) { + sessionUri = Uri.parse(rtspHeaders.get(CONTENT_LOCATION)); + } + + if (controlAttributeString.equals(GENERIC_CONTROL_ATTR)) { return sessionUri; } else { return sessionUri.buildUpon().appendEncodedPath(controlAttributeString).build(); diff --git a/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspMediaTrackTest.java b/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspMediaTrackTest.java index d96c7c30c49..14a68bce387 100644 --- a/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspMediaTrackTest.java +++ b/library/rtsp/src/test/java/com/google/android/exoplayer2/source/rtsp/RtspMediaTrackTest.java @@ -39,6 +39,16 @@ @RunWith(AndroidJUnit4.class) public class RtspMediaTrackTest { + private static final RtspHeaders RTSP_DESCRIBE_RESPONSE_HEADERS = + new RtspHeaders.Builder() + .addAll( + ImmutableList.of( + "Accept: application/sdp", + "CSeq: 3", + "Content-Length: 707", + "Transport: RTP/AVP;unicast;client_port=65458-65459\r\n")) + .build(); + @Test public void generatePayloadFormat_withH264MediaDescription_succeeds() { MediaDescription mediaDescription = @@ -361,7 +371,9 @@ public void rtspMediaTrack_mediaDescriptionContainsRelativeUri_setsCorrectTrackU MediaDescription mediaDescription = createGenericMediaDescriptionWithControlAttribute("path1/track2"); - RtspMediaTrack mediaTrack = new RtspMediaTrack(mediaDescription, Uri.parse("rtsp://test.com")); + RtspMediaTrack mediaTrack = + new RtspMediaTrack( + RTSP_DESCRIBE_RESPONSE_HEADERS, mediaDescription, Uri.parse("rtsp://test.com")); assertThat(mediaTrack.uri).isEqualTo(Uri.parse("rtsp://test.com/path1/track2")); } @@ -371,7 +383,9 @@ public void rtspMediaTrack_mediaDescriptionContainsAbsoluteUri_setsCorrectTrackU MediaDescription mediaDescription = createGenericMediaDescriptionWithControlAttribute("rtsp://test.com/foo"); - RtspMediaTrack mediaTrack = new RtspMediaTrack(mediaDescription, Uri.parse("rtsp://test.com")); + RtspMediaTrack mediaTrack = + new RtspMediaTrack( + RTSP_DESCRIBE_RESPONSE_HEADERS, mediaDescription, Uri.parse("rtsp://test.com")); assertThat(mediaTrack.uri).isEqualTo(Uri.parse("rtsp://test.com/foo")); } @@ -380,11 +394,113 @@ public void rtspMediaTrack_mediaDescriptionContainsAbsoluteUri_setsCorrectTrackU public void rtspMediaTrack_mediaDescriptionContainsGenericUri_setsCorrectTrackUri() { MediaDescription mediaDescription = createGenericMediaDescriptionWithControlAttribute("*"); - RtspMediaTrack mediaTrack = new RtspMediaTrack(mediaDescription, Uri.parse("rtsp://test.com")); + RtspMediaTrack mediaTrack = + new RtspMediaTrack( + RTSP_DESCRIBE_RESPONSE_HEADERS, mediaDescription, Uri.parse("rtsp://test.com")); assertThat(mediaTrack.uri).isEqualTo(Uri.parse("rtsp://test.com")); } + @Test + public void rtspMediaTrack_withContentBaseAndRelativeUri_setsCorrectTrackUri() { + RtspHeaders rtspHeaders = + RTSP_DESCRIBE_RESPONSE_HEADERS + .buildUpon() + .addAll(ImmutableList.of("Content-Base: rtsp://test.com/path1")) + .build(); + MediaDescription mediaDescription = + createGenericMediaDescriptionWithControlAttribute("path2/track3"); + + RtspMediaTrack mediaTrack = + new RtspMediaTrack(rtspHeaders, mediaDescription, Uri.parse("rtsp://test.com")); + + assertThat(mediaTrack.uri).isEqualTo(Uri.parse("rtsp://test.com/path1/path2/track3")); + } + + @Test + public void rtspMediaTrack_withContentLocationAndRelativeUri_setsCorrectTrackUri() { + RtspHeaders rtspHeaders = + RTSP_DESCRIBE_RESPONSE_HEADERS + .buildUpon() + .addAll(ImmutableList.of("Content-Location: rtsp://test.com/path1")) + .build(); + MediaDescription mediaDescription = + createGenericMediaDescriptionWithControlAttribute("path2/track3"); + + RtspMediaTrack mediaTrack = + new RtspMediaTrack(rtspHeaders, mediaDescription, Uri.parse("rtsp://test.com")); + + assertThat(mediaTrack.uri).isEqualTo(Uri.parse("rtsp://test.com/path1/path2/track3")); + } + + @Test + public void rtspMediaTrack_withBothContentBaseAndLocation_setsCorrectTrackUri() { + RtspHeaders rtspHeaders = + RTSP_DESCRIBE_RESPONSE_HEADERS + .buildUpon() + .addAll( + ImmutableList.of( + "Content-Base: rtsp://test.com/path1", + "Content-Location: rtsp://test.com/path2")) + .build(); + MediaDescription mediaDescription = + createGenericMediaDescriptionWithControlAttribute("path2/track3"); + + RtspMediaTrack mediaTrack = + new RtspMediaTrack(rtspHeaders, mediaDescription, Uri.parse("rtsp://test.com")); + + assertThat(mediaTrack.uri).isEqualTo(Uri.parse("rtsp://test.com/path1/path2/track3")); + } + + @Test + public void + rtspMediaTrack_withContentLocationAndEmptyContentBaseAndRelativeUri_setsCorrectTrackUri() { + RtspHeaders rtspHeaders = + RTSP_DESCRIBE_RESPONSE_HEADERS + .buildUpon() + .addAll(ImmutableList.of("Content-Base:", "Content-Location: rtsp://test.com/path1")) + .build(); + MediaDescription mediaDescription = + createGenericMediaDescriptionWithControlAttribute("path2/track3"); + + RtspMediaTrack mediaTrack = + new RtspMediaTrack(rtspHeaders, mediaDescription, Uri.parse("rtsp://test.com")); + + assertThat(mediaTrack.uri).isEqualTo(Uri.parse("rtsp://test.com/path1/path2/track3")); + } + + @Test + public void rtspMediaTrack_withEmptyContentLocationAndRelativeUri_setsCorrectTrackUri() { + RtspHeaders rtspHeaders = + RTSP_DESCRIBE_RESPONSE_HEADERS + .buildUpon() + .addAll(ImmutableList.of("Content-Location:")) + .build(); + MediaDescription mediaDescription = + createGenericMediaDescriptionWithControlAttribute("path2/track3"); + + RtspMediaTrack mediaTrack = + new RtspMediaTrack(rtspHeaders, mediaDescription, Uri.parse("rtsp://test.com")); + + assertThat(mediaTrack.uri).isEqualTo(Uri.parse("rtsp://test.com/path2/track3")); + } + + @Test + public void rtspMediaTrack_withContentBaseAndAbsoluteUri_setsCorrectTrackUri() { + RtspHeaders rtspHeaders = + RTSP_DESCRIBE_RESPONSE_HEADERS + .buildUpon() + .addAll(ImmutableList.of("Content-Base: rtsp://test.com/path1")) + .build(); + MediaDescription mediaDescription = + createGenericMediaDescriptionWithControlAttribute("rtsp://test.com/foo"); + + RtspMediaTrack mediaTrack = + new RtspMediaTrack(rtspHeaders, mediaDescription, Uri.parse("rtsp://test.com")); + + assertThat(mediaTrack.uri).isEqualTo(Uri.parse("rtsp://test.com/foo")); + } + @Test public void generatePayloadFormat_withH264MediaDescriptionMissingProfileLevel_generatesCorrectProfileLevel() {