mirror of
https://github.com/androidx/media.git
synced 2025-04-29 22:36:54 +08:00
Use base Uri from the RTSP DESCRIBE response header for relative paths
Issue: google/ExoPlayer#11160 #minor-release PiperOrigin-RevId: 534896789
This commit is contained in:
parent
abf649cdfa
commit
85f83b1208
@ -66,6 +66,9 @@
|
||||
* HLS Extension:
|
||||
* Smooth Streaming Extension:
|
||||
* RTSP Extension:
|
||||
* Use base Uri for relative path resolution from the RTSP session if
|
||||
present in DESCRIBE response header
|
||||
([#11160](https://github.com/google/ExoPlayer/issues/11160)).
|
||||
* Decoder Extensions (FFmpeg, VP9, AV1, etc.):
|
||||
* Cast Extension:
|
||||
* Test Utilities:
|
||||
|
@ -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.
|
||||
*/
|
||||
private static ImmutableList<RtspMediaTrack> buildTrackList(
|
||||
SessionDescription sessionDescription, Uri uri) {
|
||||
RtspDescribeResponse rtspDescribeResponse, Uri uri) {
|
||||
ImmutableList.Builder<RtspMediaTrack> 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 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
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 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
}
|
||||
}
|
||||
|
||||
ImmutableList<RtspMediaTrack> tracks = buildTrackList(response.sessionDescription, uri);
|
||||
ImmutableList<RtspMediaTrack> tracks = buildTrackList(response, uri);
|
||||
if (tracks.isEmpty()) {
|
||||
sessionInfoListener.onSessionTimelineRequestFailed("No playable track.", /* cause= */ null);
|
||||
return;
|
||||
|
@ -20,6 +20,8 @@ import androidx.media3.common.util.UnstableApi;
|
||||
/** Represents an RTSP DESCRIBE response. */
|
||||
@UnstableApi
|
||||
/* 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. */
|
||||
@ -28,10 +30,13 @@ import androidx.media3.common.util.UnstableApi;
|
||||
/**
|
||||
* 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;
|
||||
}
|
||||
|
@ -21,9 +21,12 @@ import static androidx.media3.common.util.Util.castNonNull;
|
||||
import static androidx.media3.container.NalUnitUtil.NAL_START_CODE;
|
||||
import static androidx.media3.exoplayer.rtsp.MediaDescription.MEDIA_TYPE_AUDIO;
|
||||
import static androidx.media3.exoplayer.rtsp.RtpPayloadFormat.getMimeTypeFromRtpMediaType;
|
||||
import static androidx.media3.exoplayer.rtsp.RtspHeaders.CONTENT_BASE;
|
||||
import static androidx.media3.exoplayer.rtsp.RtspHeaders.CONTENT_LOCATION;
|
||||
import static androidx.media3.exoplayer.rtsp.SessionDescription.ATTR_CONTROL;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
import android.util.Base64;
|
||||
import android.util.Pair;
|
||||
import androidx.annotation.Nullable;
|
||||
@ -155,14 +158,18 @@ import com.google.common.collect.ImmutableMap;
|
||||
/**
|
||||
* 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
|
||||
@ -466,15 +473,25 @@ import com.google.common.collect.ImmutableMap;
|
||||
*
|
||||
* <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 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();
|
||||
|
@ -39,6 +39,16 @@ import org.junit.runner.RunWith;
|
||||
@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 class RtspMediaTrackTest {
|
||||
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 class RtspMediaTrackTest {
|
||||
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 class RtspMediaTrackTest {
|
||||
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() {
|
||||
|
Loading…
x
Reference in New Issue
Block a user