Commit ebb9e75c by michaelkatz Committed by Tofunmi Adigun-Hameed

Use base Uri from the RTSP DESCRIBE response header for relative paths

Issue: google/ExoPlayer#11160

#minor-release

PiperOrigin-RevId: 534896789
(cherry picked from commit b72ef3e9b7ce14eaf98de5b2cc32be6d25a61ed8)
parent ff4bc623
...@@ -337,19 +337,22 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -337,19 +337,22 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
/** /**
* 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. * @param uri The RTSP playback URI.
*/ */
private static ImmutableList<RtspMediaTrack> buildTrackList( private static ImmutableList<RtspMediaTrack> buildTrackList(
SessionDescription sessionDescription, Uri uri) { RtspDescribeResponse rtspDescribeResponse, Uri uri) {
ImmutableList.Builder<RtspMediaTrack> trackListBuilder = new ImmutableList.Builder<>(); ImmutableList.Builder<RtspMediaTrack> trackListBuilder = new ImmutableList.Builder<>();
for (int i = 0; i < sessionDescription.mediaDescriptionList.size(); i++) { for (int i = 0; i < rtspDescribeResponse.sessionDescription.mediaDescriptionList.size(); i++) {
MediaDescription mediaDescription = sessionDescription.mediaDescriptionList.get(i); MediaDescription mediaDescription =
rtspDescribeResponse.sessionDescription.mediaDescriptionList.get(i);
// Includes tracks with supported formats only. // Includes tracks with supported formats only.
if (RtpPayloadFormat.isFormatSupported(mediaDescription)) { if (RtpPayloadFormat.isFormatSupported(mediaDescription)) {
trackListBuilder.add(new RtspMediaTrack(mediaDescription, uri)); trackListBuilder.add(
new RtspMediaTrack(rtspDescribeResponse.headers, mediaDescription, uri));
} }
} }
return trackListBuilder.build(); return trackListBuilder.build();
...@@ -618,7 +621,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -618,7 +621,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
case METHOD_DESCRIBE: case METHOD_DESCRIBE:
onDescribeResponseReceived( onDescribeResponseReceived(
new RtspDescribeResponse( new RtspDescribeResponse(
response.status, SessionDescriptionParser.parse(response.messageBody))); response.headers,
response.status,
SessionDescriptionParser.parse(response.messageBody)));
break; break;
case METHOD_SETUP: case METHOD_SETUP:
...@@ -708,7 +713,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; ...@@ -708,7 +713,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
} }
ImmutableList<RtspMediaTrack> tracks = buildTrackList(response.sessionDescription, uri); ImmutableList<RtspMediaTrack> tracks = buildTrackList(response, uri);
if (tracks.isEmpty()) { if (tracks.isEmpty()) {
sessionInfoListener.onSessionTimelineRequestFailed("No playable track.", /* cause= */ null); sessionInfoListener.onSessionTimelineRequestFailed("No playable track.", /* cause= */ null);
return; return;
......
...@@ -17,6 +17,8 @@ package com.google.android.exoplayer2.source.rtsp; ...@@ -17,6 +17,8 @@ package com.google.android.exoplayer2.source.rtsp;
/** Represents an RTSP DESCRIBE response. */ /** Represents an RTSP DESCRIBE response. */
/* package */ final class RtspDescribeResponse { /* package */ final class RtspDescribeResponse {
/** The response's headers. */
public final RtspHeaders headers;
/** The response's status code. */ /** The response's status code. */
public final int status; public final int status;
/** The {@link SessionDescription} (see RFC2327) in the DESCRIBE response. */ /** The {@link SessionDescription} (see RFC2327) in the DESCRIBE response. */
...@@ -25,10 +27,13 @@ package com.google.android.exoplayer2.source.rtsp; ...@@ -25,10 +27,13 @@ package com.google.android.exoplayer2.source.rtsp;
/** /**
* Creates a new instance. * Creates a new instance.
* *
* @param headers The response's headers.
* @param status The response's status code. * @param status The response's status code.
* @param sessionDescription The {@link SessionDescription} in the DESCRIBE response. * @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.status = status;
this.sessionDescription = sessionDescription; this.sessionDescription = sessionDescription;
} }
......
...@@ -17,6 +17,8 @@ package com.google.android.exoplayer2.source.rtsp; ...@@ -17,6 +17,8 @@ package com.google.android.exoplayer2.source.rtsp;
import static com.google.android.exoplayer2.source.rtsp.MediaDescription.MEDIA_TYPE_AUDIO; 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.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.source.rtsp.SessionDescription.ATTR_CONTROL;
import static com.google.android.exoplayer2.util.Assertions.checkArgument; import static com.google.android.exoplayer2.util.Assertions.checkArgument;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull; import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
...@@ -24,6 +26,7 @@ import static com.google.android.exoplayer2.util.NalUnitUtil.NAL_START_CODE; ...@@ -24,6 +26,7 @@ import static com.google.android.exoplayer2.util.NalUnitUtil.NAL_START_CODE;
import static com.google.android.exoplayer2.util.Util.castNonNull; import static com.google.android.exoplayer2.util.Util.castNonNull;
import android.net.Uri; import android.net.Uri;
import android.text.TextUtils;
import android.util.Base64; import android.util.Base64;
import android.util.Pair; import android.util.Pair;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
...@@ -153,14 +156,18 @@ import com.google.common.collect.ImmutableMap; ...@@ -153,14 +156,18 @@ import com.google.common.collect.ImmutableMap;
/** /**
* Creates a new instance from a {@link MediaDescription}. * 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 mediaDescription The {@link MediaDescription} of this track.
* @param sessionUri The {@link Uri} of the RTSP playback session. * @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( checkArgument(
mediaDescription.attributes.containsKey(ATTR_CONTROL), "missing attribute control"); mediaDescription.attributes.containsKey(ATTR_CONTROL), "missing attribute control");
payloadFormat = generatePayloadFormat(mediaDescription); payloadFormat = generatePayloadFormat(mediaDescription);
uri = extractTrackUri(sessionUri, castNonNull(mediaDescription.attributes.get(ATTR_CONTROL))); uri =
extractTrackUri(
rtspHeaders, sessionUri, castNonNull(mediaDescription.attributes.get(ATTR_CONTROL)));
} }
@Override @Override
...@@ -464,15 +471,25 @@ import com.google.common.collect.ImmutableMap; ...@@ -464,15 +471,25 @@ import com.google.common.collect.ImmutableMap;
* *
* <p>The processing logic is specified in RFC2326 Section C.1.1. * <p>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 sessionUri The session URI.
* @param controlAttributeString The control attribute from the track's {@link MediaDescription}. * @param controlAttributeString The control attribute from the track's {@link MediaDescription}.
* @return The extracted track URI. * @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); Uri controlAttributeUri = Uri.parse(controlAttributeString);
if (controlAttributeUri.isAbsolute()) { if (controlAttributeUri.isAbsolute()) {
return controlAttributeUri; 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; return sessionUri;
} else { } else {
return sessionUri.buildUpon().appendEncodedPath(controlAttributeString).build(); return sessionUri.buildUpon().appendEncodedPath(controlAttributeString).build();
......
...@@ -39,6 +39,16 @@ import org.junit.runner.RunWith; ...@@ -39,6 +39,16 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class) @RunWith(AndroidJUnit4.class)
public class RtspMediaTrackTest { 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 @Test
public void generatePayloadFormat_withH264MediaDescription_succeeds() { public void generatePayloadFormat_withH264MediaDescription_succeeds() {
MediaDescription mediaDescription = MediaDescription mediaDescription =
...@@ -361,7 +371,9 @@ public class RtspMediaTrackTest { ...@@ -361,7 +371,9 @@ public class RtspMediaTrackTest {
MediaDescription mediaDescription = MediaDescription mediaDescription =
createGenericMediaDescriptionWithControlAttribute("path1/track2"); 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")); assertThat(mediaTrack.uri).isEqualTo(Uri.parse("rtsp://test.com/path1/track2"));
} }
...@@ -371,7 +383,9 @@ public class RtspMediaTrackTest { ...@@ -371,7 +383,9 @@ public class RtspMediaTrackTest {
MediaDescription mediaDescription = MediaDescription mediaDescription =
createGenericMediaDescriptionWithControlAttribute("rtsp://test.com/foo"); 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")); assertThat(mediaTrack.uri).isEqualTo(Uri.parse("rtsp://test.com/foo"));
} }
...@@ -380,12 +394,114 @@ public class RtspMediaTrackTest { ...@@ -380,12 +394,114 @@ public class RtspMediaTrackTest {
public void rtspMediaTrack_mediaDescriptionContainsGenericUri_setsCorrectTrackUri() { public void rtspMediaTrack_mediaDescriptionContainsGenericUri_setsCorrectTrackUri() {
MediaDescription mediaDescription = createGenericMediaDescriptionWithControlAttribute("*"); 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")); assertThat(mediaTrack.uri).isEqualTo(Uri.parse("rtsp://test.com"));
} }
@Test @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 public void
generatePayloadFormat_withH264MediaDescriptionMissingProfileLevel_generatesCorrectProfileLevel() { generatePayloadFormat_withH264MediaDescriptionMissingProfileLevel_generatesCorrectProfileLevel() {
MediaDescription mediaDescription = MediaDescription mediaDescription =
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment