Use a HashMap like behaviour in parsing SDP.
Some server will wrongly insert duplicated attributes. We used to treat this as a unrecoverable error, but it is better to treat the duplicated attributes in an "over-writable" fashion like HashMaps. Issue: #9080, Issue: #9014 PiperOrigin-RevId: 380547079
This commit is contained in:
parent
60bbe64ab3
commit
e2040a5893
@ -33,6 +33,7 @@ import com.google.common.collect.ImmutableMap;
|
|||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.Documented;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
/** Represents one media description section in a SDP message. */
|
/** Represents one media description section in a SDP message. */
|
||||||
/* package */ final class MediaDescription {
|
/* package */ final class MediaDescription {
|
||||||
@ -106,7 +107,7 @@ import java.lang.annotation.RetentionPolicy;
|
|||||||
private final int port;
|
private final int port;
|
||||||
private final String transportProtocol;
|
private final String transportProtocol;
|
||||||
private final int payloadType;
|
private final int payloadType;
|
||||||
private final ImmutableMap.Builder<String, String> attributesBuilder;
|
private final HashMap<String, String> attributes;
|
||||||
|
|
||||||
private int bitrate;
|
private int bitrate;
|
||||||
@Nullable private String mediaTitle;
|
@Nullable private String mediaTitle;
|
||||||
@ -126,7 +127,7 @@ import java.lang.annotation.RetentionPolicy;
|
|||||||
this.port = port;
|
this.port = port;
|
||||||
this.transportProtocol = transportProtocol;
|
this.transportProtocol = transportProtocol;
|
||||||
this.payloadType = payloadType;
|
this.payloadType = payloadType;
|
||||||
attributesBuilder = new ImmutableMap.Builder<>();
|
attributes = new HashMap<>();
|
||||||
bitrate = Format.NO_VALUE;
|
bitrate = Format.NO_VALUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -184,7 +185,7 @@ import java.lang.annotation.RetentionPolicy;
|
|||||||
* @return This builder.
|
* @return This builder.
|
||||||
*/
|
*/
|
||||||
public Builder addAttribute(String attributeName, String attributeValue) {
|
public Builder addAttribute(String attributeName, String attributeValue) {
|
||||||
attributesBuilder.put(attributeName, attributeValue);
|
attributes.put(attributeName, attributeValue);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -195,13 +196,12 @@ import java.lang.annotation.RetentionPolicy;
|
|||||||
* cannot be parsed.
|
* cannot be parsed.
|
||||||
*/
|
*/
|
||||||
public MediaDescription build() {
|
public MediaDescription build() {
|
||||||
ImmutableMap<String, String> attributes = attributesBuilder.build();
|
|
||||||
try {
|
try {
|
||||||
// rtpmap attribute is mandatory in RTSP (RFC2326 Section C.1.3).
|
// rtpmap attribute is mandatory in RTSP (RFC2326 Section C.1.3).
|
||||||
checkState(attributes.containsKey(ATTR_RTPMAP));
|
checkState(attributes.containsKey(ATTR_RTPMAP));
|
||||||
RtpMapAttribute rtpMapAttribute =
|
RtpMapAttribute rtpMapAttribute =
|
||||||
RtpMapAttribute.parse(castNonNull(attributes.get(ATTR_RTPMAP)));
|
RtpMapAttribute.parse(castNonNull(attributes.get(ATTR_RTPMAP)));
|
||||||
return new MediaDescription(this, attributes, rtpMapAttribute);
|
return new MediaDescription(this, ImmutableMap.copyOf(attributes), rtpMapAttribute);
|
||||||
} catch (ParserException e) {
|
} catch (ParserException e) {
|
||||||
throw new IllegalStateException(e);
|
throw new IllegalStateException(e);
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ import com.google.android.exoplayer2.Format;
|
|||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import java.util.HashMap;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Records all the information in a SDP message.
|
* Records all the information in a SDP message.
|
||||||
@ -35,7 +36,7 @@ import com.google.common.collect.ImmutableMap;
|
|||||||
|
|
||||||
/** Builder class for {@link SessionDescription}. */
|
/** Builder class for {@link SessionDescription}. */
|
||||||
public static final class Builder {
|
public static final class Builder {
|
||||||
private final ImmutableMap.Builder<String, String> attributesBuilder;
|
private final HashMap<String, String> attributes;
|
||||||
private final ImmutableList.Builder<MediaDescription> mediaDescriptionListBuilder;
|
private final ImmutableList.Builder<MediaDescription> mediaDescriptionListBuilder;
|
||||||
private int bitrate;
|
private int bitrate;
|
||||||
@Nullable private String sessionName;
|
@Nullable private String sessionName;
|
||||||
@ -50,7 +51,7 @@ import com.google.common.collect.ImmutableMap;
|
|||||||
|
|
||||||
/** Creates a new instance. */
|
/** Creates a new instance. */
|
||||||
public Builder() {
|
public Builder() {
|
||||||
attributesBuilder = new ImmutableMap.Builder<>();
|
attributes = new HashMap<>();
|
||||||
mediaDescriptionListBuilder = new ImmutableList.Builder<>();
|
mediaDescriptionListBuilder = new ImmutableList.Builder<>();
|
||||||
bitrate = Format.NO_VALUE;
|
bitrate = Format.NO_VALUE;
|
||||||
}
|
}
|
||||||
@ -179,7 +180,7 @@ import com.google.common.collect.ImmutableMap;
|
|||||||
* @return This builder.
|
* @return This builder.
|
||||||
*/
|
*/
|
||||||
public Builder addAttribute(String attributeName, String attributeValue) {
|
public Builder addAttribute(String attributeName, String attributeValue) {
|
||||||
attributesBuilder.put(attributeName, attributeValue);
|
attributes.put(attributeName, attributeValue);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,7 +260,7 @@ import com.google.common.collect.ImmutableMap;
|
|||||||
|
|
||||||
/** Creates a new instance. */
|
/** Creates a new instance. */
|
||||||
private SessionDescription(Builder builder) {
|
private SessionDescription(Builder builder) {
|
||||||
this.attributes = builder.attributesBuilder.build();
|
this.attributes = ImmutableMap.copyOf(builder.attributes);
|
||||||
this.mediaDescriptionList = builder.mediaDescriptionListBuilder.build();
|
this.mediaDescriptionList = builder.mediaDescriptionListBuilder.build();
|
||||||
this.sessionName = castNonNull(builder.sessionName);
|
this.sessionName = castNonNull(builder.sessionName);
|
||||||
this.origin = castNonNull(builder.origin);
|
this.origin = castNonNull(builder.origin);
|
||||||
|
@ -30,7 +30,6 @@ import static org.junit.Assert.assertThrows;
|
|||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import com.google.android.exoplayer2.ParserException;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
@ -151,32 +150,45 @@ public class SessionDescriptionTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void parse_sdpStringWithDuplicatedMediaAttribute_throwsParserException() {
|
public void parse_sdpStringWithDuplicatedMediaAttribute_recordsTheMostRecentValue()
|
||||||
|
throws Exception {
|
||||||
String testMediaSdpInfo =
|
String testMediaSdpInfo =
|
||||||
"v=0\r\n"
|
"v=0\r\n"
|
||||||
+ "o=MNobody 2890844526 2890842807 IN IP4 192.0.2.46\r\n"
|
+ "o=MNobody 2890844526 2890842807 IN IP4 192.0.2.46\r\n"
|
||||||
+ "s=SDP Seminar\r\n"
|
+ "s=SDP Seminar\r\n"
|
||||||
|
+ "t=0 0\r\n"
|
||||||
+ "i=A Seminar on the session description protocol\r\n"
|
+ "i=A Seminar on the session description protocol\r\n"
|
||||||
+ "m=audio 3456 RTP/AVP 0\r\n"
|
+ "m=audio 3456 RTP/AVP 0\r\n"
|
||||||
+ "a=control:audio\r\n"
|
+ "a=control:video\r\n"
|
||||||
|
+ "a=rtpmap:97 AC3/44100\r\n"
|
||||||
|
// Duplicate attribute.
|
||||||
+ "a=control:audio\r\n";
|
+ "a=control:audio\r\n";
|
||||||
|
|
||||||
assertThrows(ParserException.class, () -> SessionDescriptionParser.parse(testMediaSdpInfo));
|
SessionDescription sessionDescription = SessionDescriptionParser.parse(testMediaSdpInfo);
|
||||||
|
|
||||||
|
assertThat(sessionDescription.mediaDescriptionList.get(0).attributes)
|
||||||
|
.containsEntry(ATTR_CONTROL, "audio");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void parse_sdpStringWithDuplicatedSessionAttribute_throwsParserException() {
|
public void parse_sdpStringWithDuplicatedSessionAttribute_recordsTheMostRecentValue()
|
||||||
|
throws Exception {
|
||||||
String testMediaSdpInfo =
|
String testMediaSdpInfo =
|
||||||
"v=0\r\n"
|
"v=0\r\n"
|
||||||
+ "o=MNobody 2890844526 2890842807 IN IP4 192.0.2.46\r\n"
|
+ "o=MNobody 2890844526 2890842807 IN IP4 192.0.2.46\r\n"
|
||||||
+ "s=SDP Seminar\r\n"
|
+ "s=SDP Seminar\r\n"
|
||||||
|
+ "t=0 0\r\n"
|
||||||
+ "a=control:*\r\n"
|
+ "a=control:*\r\n"
|
||||||
+ "a=control:*\r\n"
|
// Duplicate attribute.
|
||||||
|
+ "a=control:session1\r\n"
|
||||||
+ "i=A Seminar on the session description protocol\r\n"
|
+ "i=A Seminar on the session description protocol\r\n"
|
||||||
+ "m=audio 3456 RTP/AVP 0\r\n"
|
+ "m=audio 3456 RTP/AVP 0\r\n"
|
||||||
|
+ "a=rtpmap:97 AC3/44100\r\n"
|
||||||
+ "a=control:audio\r\n";
|
+ "a=control:audio\r\n";
|
||||||
|
|
||||||
assertThrows(ParserException.class, () -> SessionDescriptionParser.parse(testMediaSdpInfo));
|
SessionDescription sessionDescription = SessionDescriptionParser.parse(testMediaSdpInfo);
|
||||||
|
|
||||||
|
assertThat(sessionDescription.attributes).containsEntry(ATTR_CONTROL, "session1");
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
Loading…
x
Reference in New Issue
Block a user