commit
e822196336
@ -1,5 +1,31 @@
|
|||||||
# Release notes #
|
# Release notes #
|
||||||
|
|
||||||
|
### 2.11.8 (2020-08-25) ###
|
||||||
|
|
||||||
|
* Fix distorted playback of floating point audio when samples exceed the
|
||||||
|
`[-1, 1]` nominal range.
|
||||||
|
* MP4:
|
||||||
|
* Add support for `piff` and `isml` brands
|
||||||
|
([#7584](https://github.com/google/ExoPlayer/issues/7584)).
|
||||||
|
* Fix playback of very short MP4 files.
|
||||||
|
* FMP4:
|
||||||
|
* Fix `saiz` and `senc` sample count checks, resolving a "length
|
||||||
|
mismatch" `ParserException` when playing certain protected FMP4 streams
|
||||||
|
([#7592](https://github.com/google/ExoPlayer/issues/7592)).
|
||||||
|
* Fix handling of `traf` boxes containing multiple `sbgp` or `sgpd`
|
||||||
|
boxes.
|
||||||
|
* FLV: Ignore `SCRIPTDATA` segments with invalid name types, rather than
|
||||||
|
failing playback ([#7675](https://github.com/google/ExoPlayer/issues/7675)).
|
||||||
|
* Better infer the content type of `.ism` and `.isml` streaming URLs.
|
||||||
|
* Workaround an issue on Broadcom based devices where playbacks would not
|
||||||
|
transition to `STATE_ENDED` when using video tunneling mode
|
||||||
|
([#7647](https://github.com/google/ExoPlayer/issues/7647)).
|
||||||
|
* IMA extension: Upgrade to IMA SDK 3.19.4, bringing in a fix for setting the
|
||||||
|
media load timeout
|
||||||
|
([#7170](https://github.com/google/ExoPlayer/issues/7170)).
|
||||||
|
* Demo app: Fix playback of ClearKey protected content on API level 26 and
|
||||||
|
earlier ([#7735](https://github.com/google/ExoPlayer/issues/7735)).
|
||||||
|
|
||||||
### 2.11.7 (2020-06-29) ###
|
### 2.11.7 (2020-06-29) ###
|
||||||
|
|
||||||
* IMA extension: Fix the way postroll "content complete" notifications are
|
* IMA extension: Fix the way postroll "content complete" notifications are
|
||||||
|
@ -13,8 +13,8 @@
|
|||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
project.ext {
|
project.ext {
|
||||||
// ExoPlayer version and version code.
|
// ExoPlayer version and version code.
|
||||||
releaseVersion = '2.11.7'
|
releaseVersion = '2.11.8'
|
||||||
releaseVersionCode = 2011007
|
releaseVersionCode = 2011008
|
||||||
minSdkVersion = 16
|
minSdkVersion = 16
|
||||||
appTargetSdkVersion = 29
|
appTargetSdkVersion = 29
|
||||||
targetSdkVersion = 28 // TODO: Bump once b/143232359 is resolved
|
targetSdkVersion = 28 // TODO: Bump once b/143232359 is resolved
|
||||||
|
@ -17,7 +17,6 @@ package com.google.android.exoplayer2.demo;
|
|||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.media.MediaDrm;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
@ -47,6 +46,7 @@ import com.google.android.exoplayer2.drm.ExoMediaCrypto;
|
|||||||
import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
|
import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
|
||||||
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
|
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
|
||||||
import com.google.android.exoplayer2.drm.MediaDrmCallback;
|
import com.google.android.exoplayer2.drm.MediaDrmCallback;
|
||||||
|
import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
|
||||||
import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.DecoderInitializationException;
|
import com.google.android.exoplayer2.mediacodec.MediaCodecRenderer.DecoderInitializationException;
|
||||||
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException;
|
import com.google.android.exoplayer2.mediacodec.MediaCodecUtil.DecoderQueryException;
|
||||||
import com.google.android.exoplayer2.offline.DownloadHelper;
|
import com.google.android.exoplayer2.offline.DownloadHelper;
|
||||||
@ -485,7 +485,7 @@ public class PlayerActivity extends AppCompatActivity
|
|||||||
drmSessionManager = DrmSessionManager.getDummyDrmSessionManager();
|
drmSessionManager = DrmSessionManager.getDummyDrmSessionManager();
|
||||||
} else if (Util.SDK_INT < 18) {
|
} else if (Util.SDK_INT < 18) {
|
||||||
errorStringId = R.string.error_drm_unsupported_before_api_18;
|
errorStringId = R.string.error_drm_unsupported_before_api_18;
|
||||||
} else if (!MediaDrm.isCryptoSchemeSupported(drmInfo.drmScheme)) {
|
} else if (!FrameworkMediaDrm.isCryptoSchemeSupported(drmInfo.drmScheme)) {
|
||||||
errorStringId = R.string.error_drm_unsupported_scheme;
|
errorStringId = R.string.error_drm_unsupported_scheme;
|
||||||
} else {
|
} else {
|
||||||
MediaDrmCallback mediaDrmCallback =
|
MediaDrmCallback mediaDrmCallback =
|
||||||
|
@ -32,7 +32,7 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api 'com.google.ads.interactivemedia.v3:interactivemedia:3.19.0'
|
api 'com.google.ads.interactivemedia.v3:interactivemedia:3.19.4'
|
||||||
implementation project(modulePrefix + 'library-core')
|
implementation project(modulePrefix + 'library-core')
|
||||||
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
|
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
|
||||||
implementation 'com.google.android.gms:play-services-ads-identifier:17.0.0'
|
implementation 'com.google.android.gms:play-services-ads-identifier:17.0.0'
|
||||||
|
@ -29,11 +29,11 @@ public final class ExoPlayerLibraryInfo {
|
|||||||
|
|
||||||
/** The version of the library expressed as a string, for example "1.2.3". */
|
/** The version of the library expressed as a string, for example "1.2.3". */
|
||||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
|
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
|
||||||
public static final String VERSION = "2.11.7";
|
public static final String VERSION = "2.11.8";
|
||||||
|
|
||||||
/** The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */
|
/** The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */
|
||||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
||||||
public static final String VERSION_SLASHY = "ExoPlayerLib/2.11.7";
|
public static final String VERSION_SLASHY = "ExoPlayerLib/2.11.8";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The version of the library expressed as an integer, for example 1002003.
|
* The version of the library expressed as an integer, for example 1002003.
|
||||||
@ -43,7 +43,7 @@ public final class ExoPlayerLibraryInfo {
|
|||||||
* integer version 123045006 (123-045-006).
|
* integer version 123045006 (123-045-006).
|
||||||
*/
|
*/
|
||||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
||||||
public static final int VERSION_INT = 2011007;
|
public static final int VERSION_INT = 2011008;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}
|
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}
|
||||||
|
@ -17,6 +17,7 @@ package com.google.android.exoplayer2.audio;
|
|||||||
|
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -115,9 +116,13 @@ import java.nio.ByteBuffer;
|
|||||||
// 32 bit floating point -> 16 bit resampling. Floating point values are in the range
|
// 32 bit floating point -> 16 bit resampling. Floating point values are in the range
|
||||||
// [-1.0, 1.0], so need to be scaled by Short.MAX_VALUE.
|
// [-1.0, 1.0], so need to be scaled by Short.MAX_VALUE.
|
||||||
for (int i = position; i < limit; i += 4) {
|
for (int i = position; i < limit; i += 4) {
|
||||||
short value = (short) (inputBuffer.getFloat(i) * Short.MAX_VALUE);
|
// Clamp to avoid integer overflow if the floating point values exceed their nominal range
|
||||||
buffer.put((byte) (value & 0xFF));
|
// [Internal ref: b/161204847].
|
||||||
buffer.put((byte) ((value >> 8) & 0xFF));
|
float floatValue =
|
||||||
|
Util.constrainValue(inputBuffer.getFloat(i), /* min= */ -1, /* max= */ 1);
|
||||||
|
short shortValue = (short) (floatValue * Short.MAX_VALUE);
|
||||||
|
buffer.put((byte) (shortValue & 0xFF));
|
||||||
|
buffer.put((byte) ((shortValue >> 8) & 0xFF));
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case C.ENCODING_PCM_16BIT:
|
case C.ENCODING_PCM_16BIT:
|
||||||
|
@ -75,6 +75,15 @@ public final class FrameworkMediaDrm implements ExoMediaDrm<FrameworkMediaCrypto
|
|||||||
private final MediaDrm mediaDrm;
|
private final MediaDrm mediaDrm;
|
||||||
private int referenceCount;
|
private int referenceCount;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the DRM scheme with the given UUID is supported on this device.
|
||||||
|
*
|
||||||
|
* @see MediaDrm#isCryptoSchemeSupported(UUID)
|
||||||
|
*/
|
||||||
|
public static boolean isCryptoSchemeSupported(UUID uuid) {
|
||||||
|
return MediaDrm.isCryptoSchemeSupported(adjustUuid(uuid));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance with an initial reference count of 1. {@link #release()} must be called on
|
* Creates an instance with an initial reference count of 1. {@link #release()} must be called on
|
||||||
* the instance when it's no longer required.
|
* the instance when it's no longer required.
|
||||||
|
@ -17,7 +17,6 @@ package com.google.android.exoplayer2.extractor.flv;
|
|||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.ParserException;
|
|
||||||
import com.google.android.exoplayer2.extractor.DummyTrackOutput;
|
import com.google.android.exoplayer2.extractor.DummyTrackOutput;
|
||||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -65,11 +64,11 @@ import java.util.Map;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected boolean parsePayload(ParsableByteArray data, long timeUs) throws ParserException {
|
protected boolean parsePayload(ParsableByteArray data, long timeUs) {
|
||||||
int nameType = readAmfType(data);
|
int nameType = readAmfType(data);
|
||||||
if (nameType != AMF_TYPE_STRING) {
|
if (nameType != AMF_TYPE_STRING) {
|
||||||
// Should never happen.
|
// Ignore segments with unexpected name type.
|
||||||
throw new ParserException();
|
return false;
|
||||||
}
|
}
|
||||||
String name = readAmfString(data);
|
String name = readAmfString(data);
|
||||||
if (!NAME_METADATA.equals(name)) {
|
if (!NAME_METADATA.equals(name)) {
|
||||||
|
@ -735,12 +735,7 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
parseSenc(senc.data, fragment);
|
parseSenc(senc.data, fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
LeafAtom sbgp = traf.getLeafAtomOfType(Atom.TYPE_sbgp);
|
parseSampleGroups(traf, encryptionBox != null ? encryptionBox.schemeType : null, fragment);
|
||||||
LeafAtom sgpd = traf.getLeafAtomOfType(Atom.TYPE_sgpd);
|
|
||||||
if (sbgp != null && sgpd != null) {
|
|
||||||
parseSgpd(sbgp.data, sgpd.data, encryptionBox != null ? encryptionBox.schemeType : null,
|
|
||||||
fragment);
|
|
||||||
}
|
|
||||||
|
|
||||||
int leafChildrenSize = traf.leafChildren.size();
|
int leafChildrenSize = traf.leafChildren.size();
|
||||||
for (int i = 0; i < leafChildrenSize; i++) {
|
for (int i = 0; i < leafChildrenSize; i++) {
|
||||||
@ -798,8 +793,12 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
int defaultSampleInfoSize = saiz.readUnsignedByte();
|
int defaultSampleInfoSize = saiz.readUnsignedByte();
|
||||||
|
|
||||||
int sampleCount = saiz.readUnsignedIntToInt();
|
int sampleCount = saiz.readUnsignedIntToInt();
|
||||||
if (sampleCount != out.sampleCount) {
|
if (sampleCount > out.sampleCount) {
|
||||||
throw new ParserException("Length mismatch: " + sampleCount + ", " + out.sampleCount);
|
throw new ParserException(
|
||||||
|
"Saiz sample count "
|
||||||
|
+ sampleCount
|
||||||
|
+ " is greater than fragment sample count"
|
||||||
|
+ out.sampleCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
int totalSize = 0;
|
int totalSize = 0;
|
||||||
@ -815,7 +814,10 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
totalSize += defaultSampleInfoSize * sampleCount;
|
totalSize += defaultSampleInfoSize * sampleCount;
|
||||||
Arrays.fill(out.sampleHasSubsampleEncryptionTable, 0, sampleCount, subsampleEncryption);
|
Arrays.fill(out.sampleHasSubsampleEncryptionTable, 0, sampleCount, subsampleEncryption);
|
||||||
}
|
}
|
||||||
out.initEncryptionData(totalSize);
|
Arrays.fill(out.sampleHasSubsampleEncryptionTable, sampleCount, out.sampleCount, false);
|
||||||
|
if (totalSize > 0) {
|
||||||
|
out.initEncryptionData(totalSize);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -990,8 +992,10 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
checkNonNegative(sampleDurationsPresent ? trun.readInt() : defaultSampleValues.duration);
|
checkNonNegative(sampleDurationsPresent ? trun.readInt() : defaultSampleValues.duration);
|
||||||
int sampleSize =
|
int sampleSize =
|
||||||
checkNonNegative(sampleSizesPresent ? trun.readInt() : defaultSampleValues.size);
|
checkNonNegative(sampleSizesPresent ? trun.readInt() : defaultSampleValues.size);
|
||||||
int sampleFlags = (i == 0 && firstSampleFlagsPresent) ? firstSampleFlags
|
int sampleFlags =
|
||||||
: sampleFlagsPresent ? trun.readInt() : defaultSampleValues.flags;
|
sampleFlagsPresent
|
||||||
|
? trun.readInt()
|
||||||
|
: (i == 0 && firstSampleFlagsPresent) ? firstSampleFlags : defaultSampleValues.flags;
|
||||||
if (sampleCompositionTimeOffsetsPresent) {
|
if (sampleCompositionTimeOffsetsPresent) {
|
||||||
// The BMFF spec (ISO 14496-12) states that sample offsets should be unsigned integers in
|
// The BMFF spec (ISO 14496-12) states that sample offsets should be unsigned integers in
|
||||||
// version 0 trun boxes, however a significant number of streams violate the spec and use
|
// version 0 trun boxes, however a significant number of streams violate the spec and use
|
||||||
@ -1055,8 +1059,16 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
|
|
||||||
boolean subsampleEncryption = (flags & 0x02 /* use_subsample_encryption */) != 0;
|
boolean subsampleEncryption = (flags & 0x02 /* use_subsample_encryption */) != 0;
|
||||||
int sampleCount = senc.readUnsignedIntToInt();
|
int sampleCount = senc.readUnsignedIntToInt();
|
||||||
if (sampleCount != out.sampleCount) {
|
if (sampleCount == 0) {
|
||||||
throw new ParserException("Length mismatch: " + sampleCount + ", " + out.sampleCount);
|
// Samples are unencrypted.
|
||||||
|
Arrays.fill(out.sampleHasSubsampleEncryptionTable, 0, out.sampleCount, false);
|
||||||
|
return;
|
||||||
|
} else if (sampleCount != out.sampleCount) {
|
||||||
|
throw new ParserException(
|
||||||
|
"Senc sample count "
|
||||||
|
+ sampleCount
|
||||||
|
+ " is different from fragment sample count"
|
||||||
|
+ out.sampleCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
Arrays.fill(out.sampleHasSubsampleEncryptionTable, 0, sampleCount, subsampleEncryption);
|
Arrays.fill(out.sampleHasSubsampleEncryptionTable, 0, sampleCount, subsampleEncryption);
|
||||||
@ -1064,28 +1076,43 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
out.fillEncryptionData(senc);
|
out.fillEncryptionData(senc);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void parseSgpd(ParsableByteArray sbgp, ParsableByteArray sgpd, String schemeType,
|
private static void parseSampleGroups(
|
||||||
TrackFragment out) throws ParserException {
|
ContainerAtom traf, @Nullable String schemeType, TrackFragment out) throws ParserException {
|
||||||
sbgp.setPosition(Atom.HEADER_SIZE);
|
// Find sbgp and sgpd boxes with grouping_type == seig.
|
||||||
int sbgpFullAtom = sbgp.readInt();
|
@Nullable ParsableByteArray sbgp = null;
|
||||||
if (sbgp.readInt() != SAMPLE_GROUP_TYPE_seig) {
|
@Nullable ParsableByteArray sgpd = null;
|
||||||
// Only seig grouping type is supported.
|
for (int i = 0; i < traf.leafChildren.size(); i++) {
|
||||||
|
LeafAtom leafAtom = traf.leafChildren.get(i);
|
||||||
|
ParsableByteArray leafAtomData = leafAtom.data;
|
||||||
|
if (leafAtom.type == Atom.TYPE_sbgp) {
|
||||||
|
leafAtomData.setPosition(Atom.FULL_HEADER_SIZE);
|
||||||
|
if (leafAtomData.readInt() == SAMPLE_GROUP_TYPE_seig) {
|
||||||
|
sbgp = leafAtomData;
|
||||||
|
}
|
||||||
|
} else if (leafAtom.type == Atom.TYPE_sgpd) {
|
||||||
|
leafAtomData.setPosition(Atom.FULL_HEADER_SIZE);
|
||||||
|
if (leafAtomData.readInt() == SAMPLE_GROUP_TYPE_seig) {
|
||||||
|
sgpd = leafAtomData;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (sbgp == null || sgpd == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (Atom.parseFullAtomVersion(sbgpFullAtom) == 1) {
|
|
||||||
sbgp.skipBytes(4); // default_length.
|
sbgp.setPosition(Atom.HEADER_SIZE);
|
||||||
|
int sbgpVersion = Atom.parseFullAtomVersion(sbgp.readInt());
|
||||||
|
sbgp.skipBytes(4); // grouping_type == seig.
|
||||||
|
if (sbgpVersion == 1) {
|
||||||
|
sbgp.skipBytes(4); // grouping_type_parameter.
|
||||||
}
|
}
|
||||||
if (sbgp.readInt() != 1) { // entry_count.
|
if (sbgp.readInt() != 1) { // entry_count.
|
||||||
throw new ParserException("Entry count in sbgp != 1 (unsupported).");
|
throw new ParserException("Entry count in sbgp != 1 (unsupported).");
|
||||||
}
|
}
|
||||||
|
|
||||||
sgpd.setPosition(Atom.HEADER_SIZE);
|
sgpd.setPosition(Atom.HEADER_SIZE);
|
||||||
int sgpdFullAtom = sgpd.readInt();
|
int sgpdVersion = Atom.parseFullAtomVersion(sgpd.readInt());
|
||||||
if (sgpd.readInt() != SAMPLE_GROUP_TYPE_seig) {
|
sgpd.skipBytes(4); // grouping_type == seig.
|
||||||
// Only seig grouping type is supported.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
int sgpdVersion = Atom.parseFullAtomVersion(sgpdFullAtom);
|
|
||||||
if (sgpdVersion == 1) {
|
if (sgpdVersion == 1) {
|
||||||
if (sgpd.readUnsignedInt() == 0) {
|
if (sgpd.readUnsignedInt() == 0) {
|
||||||
throw new ParserException("Variable length description in sgpd found (unsupported)");
|
throw new ParserException("Variable length description in sgpd found (unsupported)");
|
||||||
@ -1096,6 +1123,7 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
if (sgpd.readUnsignedInt() != 1) { // entry_count.
|
if (sgpd.readUnsignedInt() != 1) { // entry_count.
|
||||||
throw new ParserException("Entry count in sgpd != 1 (unsupported).");
|
throw new ParserException("Entry count in sgpd != 1 (unsupported).");
|
||||||
}
|
}
|
||||||
|
|
||||||
// CencSampleEncryptionInformationGroupEntry
|
// CencSampleEncryptionInformationGroupEntry
|
||||||
sgpd.skipBytes(1); // reserved = 0.
|
sgpd.skipBytes(1); // reserved = 0.
|
||||||
int patternByte = sgpd.readUnsignedByte();
|
int patternByte = sgpd.readUnsignedByte();
|
||||||
|
@ -57,6 +57,8 @@ import java.io.IOException;
|
|||||||
0x71742020, // qt[space][space], Apple QuickTime
|
0x71742020, // qt[space][space], Apple QuickTime
|
||||||
0x4d534e56, // MSNV, Sony PSP
|
0x4d534e56, // MSNV, Sony PSP
|
||||||
0x64627931, // dby1, Dolby Vision
|
0x64627931, // dby1, Dolby Vision
|
||||||
|
0x69736d6c, // isml
|
||||||
|
0x70696666, // piff
|
||||||
};
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -101,7 +103,12 @@ import java.io.IOException;
|
|||||||
// Read an atom header.
|
// Read an atom header.
|
||||||
int headerSize = Atom.HEADER_SIZE;
|
int headerSize = Atom.HEADER_SIZE;
|
||||||
buffer.reset(headerSize);
|
buffer.reset(headerSize);
|
||||||
input.peekFully(buffer.data, 0, headerSize);
|
boolean success =
|
||||||
|
input.peekFully(buffer.data, 0, headerSize, /* allowEndOfInput= */ true);
|
||||||
|
if (!success) {
|
||||||
|
// We've reached the end of the file.
|
||||||
|
break;
|
||||||
|
}
|
||||||
long atomSize = buffer.readUnsignedInt();
|
long atomSize = buffer.readUnsignedInt();
|
||||||
int atomType = buffer.readInt();
|
int atomType = buffer.readInt();
|
||||||
if (atomSize == Atom.DEFINES_LARGE_SIZE) {
|
if (atomSize == Atom.DEFINES_LARGE_SIZE) {
|
||||||
|
@ -657,6 +657,10 @@ public final class TsExtractor implements Extractor {
|
|||||||
int descriptorTag = data.readUnsignedByte();
|
int descriptorTag = data.readUnsignedByte();
|
||||||
int descriptorLength = data.readUnsignedByte();
|
int descriptorLength = data.readUnsignedByte();
|
||||||
int positionOfNextDescriptor = data.getPosition() + descriptorLength;
|
int positionOfNextDescriptor = data.getPosition() + descriptorLength;
|
||||||
|
if (positionOfNextDescriptor > descriptorsEndPosition) {
|
||||||
|
// Descriptor claims to extend past the end position. Skip it.
|
||||||
|
break;
|
||||||
|
}
|
||||||
if (descriptorTag == TS_PMT_DESC_REGISTRATION) { // registration_descriptor
|
if (descriptorTag == TS_PMT_DESC_REGISTRATION) { // registration_descriptor
|
||||||
long formatIdentifier = data.readUnsignedInt();
|
long formatIdentifier = data.readUnsignedInt();
|
||||||
if (formatIdentifier == AC3_FORMAT_IDENTIFIER) {
|
if (formatIdentifier == AC3_FORMAT_IDENTIFIER) {
|
||||||
|
@ -1937,6 +1937,9 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
String name = codecInfo.name;
|
String name = codecInfo.name;
|
||||||
return (Util.SDK_INT <= 25 && "OMX.rk.video_decoder.avc".equals(name))
|
return (Util.SDK_INT <= 25 && "OMX.rk.video_decoder.avc".equals(name))
|
||||||
|| (Util.SDK_INT <= 17 && "OMX.allwinner.video.decoder.avc".equals(name))
|
|| (Util.SDK_INT <= 17 && "OMX.allwinner.video.decoder.avc".equals(name))
|
||||||
|
|| (Util.SDK_INT <= 29
|
||||||
|
&& ("OMX.broadcom.video_decoder.tunnel".equals(name)
|
||||||
|
|| "OMX.broadcom.video_decoder.tunnel.secure".equals(name)))
|
||||||
|| ("Amazon".equals(Util.MANUFACTURER) && "AFTS".equals(Util.MODEL) && codecInfo.secure);
|
|| ("Amazon".equals(Util.MANUFACTURER) && "AFTS".equals(Util.MODEL) && codecInfo.secure);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -83,7 +83,7 @@ public final class MediaFormatUtil {
|
|||||||
*
|
*
|
||||||
* @param format The {@link MediaFormat} being configured.
|
* @param format The {@link MediaFormat} being configured.
|
||||||
* @param key The key to set.
|
* @param key The key to set.
|
||||||
* @param value The {@link byte[]} that will be wrapped to obtain the value.
|
* @param value The byte array that will be wrapped to obtain the value.
|
||||||
*/
|
*/
|
||||||
public static void maybeSetByteBuffer(MediaFormat format, String key, @Nullable byte[] value) {
|
public static void maybeSetByteBuffer(MediaFormat format, String key, @Nullable byte[] value) {
|
||||||
if (value != null) {
|
if (value != null) {
|
||||||
|
@ -40,7 +40,7 @@ public final class ConditionVariable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance.
|
* Creates an instance, which starts closed.
|
||||||
*
|
*
|
||||||
* @param clock The {@link Clock} whose {@link Clock#elapsedRealtime()} method is used to
|
* @param clock The {@link Clock} whose {@link Clock#elapsedRealtime()} method is used to
|
||||||
* determine when {@link #block(long)} should time out.
|
* determine when {@link #block(long)} should time out.
|
||||||
|
@ -135,6 +135,11 @@ public final class Util {
|
|||||||
+ "(T(([0-9]*)H)?(([0-9]*)M)?(([0-9.]*)S)?)?$");
|
+ "(T(([0-9]*)H)?(([0-9]*)M)?(([0-9.]*)S)?)?$");
|
||||||
private static final Pattern ESCAPED_CHARACTER_PATTERN = Pattern.compile("%([A-Fa-f0-9]{2})");
|
private static final Pattern ESCAPED_CHARACTER_PATTERN = Pattern.compile("%([A-Fa-f0-9]{2})");
|
||||||
|
|
||||||
|
// https://docs.microsoft.com/en-us/azure/media-services/previous/media-services-deliver-content-overview#URLs.
|
||||||
|
private static final Pattern ISM_URL_PATTERN = Pattern.compile(".*\\.isml?(?:/(manifest(.*))?)?");
|
||||||
|
private static final String ISM_HLS_FORMAT_EXTENSION = "format=m3u8-aapl";
|
||||||
|
private static final String ISM_DASH_FORMAT_EXTENSION = "format=mpd-time-csf";
|
||||||
|
|
||||||
// Replacement map of ISO language codes used for normalization.
|
// Replacement map of ISO language codes used for normalization.
|
||||||
@Nullable private static HashMap<String, String> languageTagReplacementMap;
|
@Nullable private static HashMap<String, String> languageTagReplacementMap;
|
||||||
|
|
||||||
@ -1599,11 +1604,41 @@ public final class Util {
|
|||||||
return C.TYPE_DASH;
|
return C.TYPE_DASH;
|
||||||
} else if (fileName.endsWith(".m3u8")) {
|
} else if (fileName.endsWith(".m3u8")) {
|
||||||
return C.TYPE_HLS;
|
return C.TYPE_HLS;
|
||||||
} else if (fileName.matches(".*\\.ism(l)?(/manifest(\\(.+\\))?)?")) {
|
|
||||||
return C.TYPE_SS;
|
|
||||||
} else {
|
|
||||||
return C.TYPE_OTHER;
|
|
||||||
}
|
}
|
||||||
|
Matcher ismMatcher = ISM_URL_PATTERN.matcher(fileName);
|
||||||
|
if (ismMatcher.matches()) {
|
||||||
|
@Nullable String extensions = ismMatcher.group(2);
|
||||||
|
if (extensions != null) {
|
||||||
|
if (extensions.contains(ISM_DASH_FORMAT_EXTENSION)) {
|
||||||
|
return C.TYPE_DASH;
|
||||||
|
} else if (extensions.contains(ISM_HLS_FORMAT_EXTENSION)) {
|
||||||
|
return C.TYPE_HLS;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return C.TYPE_SS;
|
||||||
|
}
|
||||||
|
return C.TYPE_OTHER;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the provided URI is an ISM Presentation URI, returns the URI with "Manifest" appended to its
|
||||||
|
* path (i.e., the corresponding default manifest URI). Else returns the provided URI without
|
||||||
|
* modification. See [MS-SSTR] v20180912, section 2.2.1.
|
||||||
|
*
|
||||||
|
* @param uri The original URI.
|
||||||
|
* @return The fixed URI.
|
||||||
|
*/
|
||||||
|
public static Uri fixSmoothStreamingIsmManifestUri(Uri uri) {
|
||||||
|
@Nullable String path = toLowerInvariant(uri.getPath());
|
||||||
|
if (path == null) {
|
||||||
|
return uri;
|
||||||
|
}
|
||||||
|
Matcher ismMatcher = ISM_URL_PATTERN.matcher(path);
|
||||||
|
if (ismMatcher.matches() && ismMatcher.group(1) == null) {
|
||||||
|
// Add missing "Manifest" suffix.
|
||||||
|
return Uri.withAppendedPath(uri, "Manifest");
|
||||||
|
}
|
||||||
|
return uri;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -24,6 +24,7 @@ import static com.google.android.exoplayer2.util.Util.parseXsDuration;
|
|||||||
import static com.google.android.exoplayer2.util.Util.unescapeFileName;
|
import static com.google.android.exoplayer2.util.Util.unescapeFileName;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.testutil.TestUtil;
|
import com.google.android.exoplayer2.testutil.TestUtil;
|
||||||
@ -77,15 +78,77 @@ public class UtilTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void testInferContentType() {
|
public void inferContentType_handlesHlsIsmUris() {
|
||||||
|
assertThat(Util.inferContentType("http://a.b/c.ism/manifest(format=m3u8-aapl)"))
|
||||||
|
.isEqualTo(C.TYPE_HLS);
|
||||||
|
assertThat(Util.inferContentType("http://a.b/c.ism/manifest(format=m3u8-aapl,quality=hd)"))
|
||||||
|
.isEqualTo(C.TYPE_HLS);
|
||||||
|
assertThat(Util.inferContentType("http://a.b/c.ism/manifest(quality=hd,format=m3u8-aapl)"))
|
||||||
|
.isEqualTo(C.TYPE_HLS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void inferContentType_handlesHlsIsmV3Uris() {
|
||||||
|
assertThat(Util.inferContentType("http://a.b/c.ism/manifest(format=m3u8-aapl-v3)"))
|
||||||
|
.isEqualTo(C.TYPE_HLS);
|
||||||
|
assertThat(Util.inferContentType("http://a.b/c.ism/manifest(format=m3u8-aapl-v3,quality=hd)"))
|
||||||
|
.isEqualTo(C.TYPE_HLS);
|
||||||
|
assertThat(Util.inferContentType("http://a.b/c.ism/manifest(quality=hd,format=m3u8-aapl-v3)"))
|
||||||
|
.isEqualTo(C.TYPE_HLS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void inferContentType_handlesDashIsmUris() {
|
||||||
|
assertThat(Util.inferContentType("http://a.b/c.isml/manifest(format=mpd-time-csf)"))
|
||||||
|
.isEqualTo(C.TYPE_DASH);
|
||||||
|
assertThat(Util.inferContentType("http://a.b/c.isml/manifest(format=mpd-time-csf,quality=hd)"))
|
||||||
|
.isEqualTo(C.TYPE_DASH);
|
||||||
|
assertThat(Util.inferContentType("http://a.b/c.isml/manifest(quality=hd,format=mpd-time-csf)"))
|
||||||
|
.isEqualTo(C.TYPE_DASH);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void inferContentType_handlesSmoothStreamingIsmUris() {
|
||||||
assertThat(Util.inferContentType("http://a.b/c.ism")).isEqualTo(C.TYPE_SS);
|
assertThat(Util.inferContentType("http://a.b/c.ism")).isEqualTo(C.TYPE_SS);
|
||||||
assertThat(Util.inferContentType("http://a.b/c.isml")).isEqualTo(C.TYPE_SS);
|
assertThat(Util.inferContentType("http://a.b/c.isml")).isEqualTo(C.TYPE_SS);
|
||||||
|
assertThat(Util.inferContentType("http://a.b/c.ism/")).isEqualTo(C.TYPE_SS);
|
||||||
|
assertThat(Util.inferContentType("http://a.b/c.isml/")).isEqualTo(C.TYPE_SS);
|
||||||
assertThat(Util.inferContentType("http://a.b/c.ism/Manifest")).isEqualTo(C.TYPE_SS);
|
assertThat(Util.inferContentType("http://a.b/c.ism/Manifest")).isEqualTo(C.TYPE_SS);
|
||||||
assertThat(Util.inferContentType("http://a.b/c.isml/manifest")).isEqualTo(C.TYPE_SS);
|
assertThat(Util.inferContentType("http://a.b/c.isml/manifest")).isEqualTo(C.TYPE_SS);
|
||||||
assertThat(Util.inferContentType("http://a.b/c.isml/manifest(filter=x)")).isEqualTo(C.TYPE_SS);
|
assertThat(Util.inferContentType("http://a.b/c.isml/manifest(filter=x)")).isEqualTo(C.TYPE_SS);
|
||||||
|
assertThat(Util.inferContentType("http://a.b/c.isml/manifest_hd")).isEqualTo(C.TYPE_SS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void inferContentType_handlesOtherIsmUris() {
|
||||||
|
assertThat(Util.inferContentType("http://a.b/c.ism/video.mp4")).isEqualTo(C.TYPE_OTHER);
|
||||||
assertThat(Util.inferContentType("http://a.b/c.ism/prefix-manifest")).isEqualTo(C.TYPE_OTHER);
|
assertThat(Util.inferContentType("http://a.b/c.ism/prefix-manifest")).isEqualTo(C.TYPE_OTHER);
|
||||||
assertThat(Util.inferContentType("http://a.b/c.ism/manifest-suffix")).isEqualTo(C.TYPE_OTHER);
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fixSmoothStreamingIsmManifestUri_addsManifestSuffix() {
|
||||||
|
assertThat(Util.fixSmoothStreamingIsmManifestUri(Uri.parse("http://a.b/c.ism")))
|
||||||
|
.isEqualTo(Uri.parse("http://a.b/c.ism/Manifest"));
|
||||||
|
assertThat(Util.fixSmoothStreamingIsmManifestUri(Uri.parse("http://a.b/c.isml")))
|
||||||
|
.isEqualTo(Uri.parse("http://a.b/c.isml/Manifest"));
|
||||||
|
|
||||||
|
assertThat(Util.fixSmoothStreamingIsmManifestUri(Uri.parse("http://a.b/c.ism/")))
|
||||||
|
.isEqualTo(Uri.parse("http://a.b/c.ism/Manifest"));
|
||||||
|
assertThat(Util.fixSmoothStreamingIsmManifestUri(Uri.parse("http://a.b/c.isml/")))
|
||||||
|
.isEqualTo(Uri.parse("http://a.b/c.isml/Manifest"));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void fixSmoothStreamingIsmManifestUri_doesNotAlterManifestUri() {
|
||||||
|
assertThat(Util.fixSmoothStreamingIsmManifestUri(Uri.parse("http://a.b/c.ism/Manifest")))
|
||||||
|
.isEqualTo(Uri.parse("http://a.b/c.ism/Manifest"));
|
||||||
|
assertThat(Util.fixSmoothStreamingIsmManifestUri(Uri.parse("http://a.b/c.isml/Manifest")))
|
||||||
|
.isEqualTo(Uri.parse("http://a.b/c.isml/Manifest"));
|
||||||
|
assertThat(
|
||||||
|
Util.fixSmoothStreamingIsmManifestUri(Uri.parse("http://a.b/c.ism/Manifest(filter=x)")))
|
||||||
|
.isEqualTo(Uri.parse("http://a.b/c.ism/Manifest(filter=x)"));
|
||||||
|
assertThat(Util.fixSmoothStreamingIsmManifestUri(Uri.parse("http://a.b/c.ism/Manifest_hd")))
|
||||||
|
.isEqualTo(Uri.parse("http://a.b/c.ism/Manifest_hd"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -39,7 +39,6 @@ import com.google.android.exoplayer2.source.SinglePeriodTimeline;
|
|||||||
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest;
|
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest;
|
||||||
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement;
|
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement;
|
||||||
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestParser;
|
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestParser;
|
||||||
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsUtil;
|
|
||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
import com.google.android.exoplayer2.upstream.Allocator;
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
|
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
|
||||||
@ -50,6 +49,7 @@ import com.google.android.exoplayer2.upstream.LoaderErrorThrower;
|
|||||||
import com.google.android.exoplayer2.upstream.ParsingLoadable;
|
import com.google.android.exoplayer2.upstream.ParsingLoadable;
|
||||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -531,7 +531,7 @@ public final class SsMediaSource extends BaseMediaSource
|
|||||||
@Nullable Object tag) {
|
@Nullable Object tag) {
|
||||||
Assertions.checkState(manifest == null || !manifest.isLive);
|
Assertions.checkState(manifest == null || !manifest.isLive);
|
||||||
this.manifest = manifest;
|
this.manifest = manifest;
|
||||||
this.manifestUri = manifestUri == null ? null : SsUtil.fixManifestUri(manifestUri);
|
this.manifestUri = manifestUri == null ? null : Util.fixSmoothStreamingIsmManifestUri(manifestUri);
|
||||||
this.manifestDataSourceFactory = manifestDataSourceFactory;
|
this.manifestDataSourceFactory = manifestDataSourceFactory;
|
||||||
this.manifestParser = manifestParser;
|
this.manifestParser = manifestParser;
|
||||||
this.chunkSourceFactory = chunkSourceFactory;
|
this.chunkSourceFactory = chunkSourceFactory;
|
||||||
|
@ -1,35 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2018 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package com.google.android.exoplayer2.source.smoothstreaming.manifest;
|
|
||||||
|
|
||||||
import android.net.Uri;
|
|
||||||
import com.google.android.exoplayer2.util.Util;
|
|
||||||
|
|
||||||
/** SmoothStreaming related utility methods. */
|
|
||||||
public final class SsUtil {
|
|
||||||
|
|
||||||
/** Returns a fixed SmoothStreaming client manifest {@link Uri}. */
|
|
||||||
public static Uri fixManifestUri(Uri manifestUri) {
|
|
||||||
String lastPathSegment = manifestUri.getLastPathSegment();
|
|
||||||
if (lastPathSegment != null
|
|
||||||
&& Util.toLowerInvariant(lastPathSegment).matches("manifest(\\(.+\\))?")) {
|
|
||||||
return manifestUri;
|
|
||||||
}
|
|
||||||
return Uri.withAppendedPath(manifestUri, "Manifest");
|
|
||||||
}
|
|
||||||
|
|
||||||
private SsUtil() {}
|
|
||||||
}
|
|
@ -23,10 +23,10 @@ import com.google.android.exoplayer2.offline.StreamKey;
|
|||||||
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest;
|
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest;
|
||||||
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement;
|
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifest.StreamElement;
|
||||||
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestParser;
|
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestParser;
|
||||||
import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsUtil;
|
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
import com.google.android.exoplayer2.upstream.DataSpec;
|
||||||
import com.google.android.exoplayer2.upstream.ParsingLoadable;
|
import com.google.android.exoplayer2.upstream.ParsingLoadable;
|
||||||
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -64,7 +64,7 @@ public final class SsDownloader extends SegmentDownloader<SsManifest> {
|
|||||||
*/
|
*/
|
||||||
public SsDownloader(
|
public SsDownloader(
|
||||||
Uri manifestUri, List<StreamKey> streamKeys, DownloaderConstructorHelper constructorHelper) {
|
Uri manifestUri, List<StreamKey> streamKeys, DownloaderConstructorHelper constructorHelper) {
|
||||||
super(SsUtil.fixManifestUri(manifestUri), streamKeys, constructorHelper);
|
super(Util.fixSmoothStreamingIsmManifestUri(manifestUri), streamKeys, constructorHelper);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
Loading…
x
Reference in New Issue
Block a user