mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
commit
cbffc14fa1
22 changed files with 364 additions and 347 deletions
|
|
@ -1,5 +1,16 @@
|
||||||
# Release notes #
|
# Release notes #
|
||||||
|
|
||||||
|
### r2.4.4 ###
|
||||||
|
|
||||||
|
* HLS/MPEG-TS: Some initial optimizations of MPEG-TS extractor performance
|
||||||
|
([#3040](https://github.com/google/ExoPlayer/issues/3040)).
|
||||||
|
* HLS: Fix propagation of format identifier for CEA-608
|
||||||
|
([#3033](https://github.com/google/ExoPlayer/issues/3033)).
|
||||||
|
* HLS: Detect playlist stuck and reset conditions
|
||||||
|
([#2872](https://github.com/google/ExoPlayer/issues/2872)).
|
||||||
|
* Video: Fix video dimension reporting on some devices
|
||||||
|
([#3007](https://github.com/google/ExoPlayer/issues/3007)).
|
||||||
|
|
||||||
### r2.4.3 ###
|
### r2.4.3 ###
|
||||||
|
|
||||||
* Audio: Workaround custom audio decoders misreporting their maximum supported
|
* Audio: Workaround custom audio decoders misreporting their maximum supported
|
||||||
|
|
|
||||||
|
|
@ -48,7 +48,7 @@ allprojects {
|
||||||
releaseRepoName = getBintrayRepo()
|
releaseRepoName = getBintrayRepo()
|
||||||
releaseUserOrg = 'google'
|
releaseUserOrg = 'google'
|
||||||
releaseGroupId = 'com.google.android.exoplayer'
|
releaseGroupId = 'com.google.android.exoplayer'
|
||||||
releaseVersion = 'r2.4.3'
|
releaseVersion = 'r2.4.4'
|
||||||
releaseWebsite = 'https://github.com/google/ExoPlayer'
|
releaseWebsite = 'https://github.com/google/ExoPlayer'
|
||||||
}
|
}
|
||||||
if (it.hasProperty('externalBuildDir')) {
|
if (it.hasProperty('externalBuildDir')) {
|
||||||
|
|
|
||||||
|
|
@ -16,8 +16,8 @@
|
||||||
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.google.android.exoplayer2.demo"
|
package="com.google.android.exoplayer2.demo"
|
||||||
android:versionCode="2403"
|
android:versionCode="2404"
|
||||||
android:versionName="2.4.3">
|
android:versionName="2.4.4">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
|
|
|
||||||
|
|
@ -136,7 +136,6 @@ import java.util.Arrays;
|
||||||
root.addView(defaultView);
|
root.addView(defaultView);
|
||||||
|
|
||||||
// Per-track views.
|
// Per-track views.
|
||||||
boolean haveSupportedTracks = false;
|
|
||||||
boolean haveAdaptiveTracks = false;
|
boolean haveAdaptiveTracks = false;
|
||||||
trackViews = new CheckedTextView[trackGroups.length][];
|
trackViews = new CheckedTextView[trackGroups.length][];
|
||||||
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
|
for (int groupIndex = 0; groupIndex < trackGroups.length; groupIndex++) {
|
||||||
|
|
@ -159,7 +158,6 @@ import java.util.Arrays;
|
||||||
trackView.setFocusable(true);
|
trackView.setFocusable(true);
|
||||||
trackView.setTag(Pair.create(groupIndex, trackIndex));
|
trackView.setTag(Pair.create(groupIndex, trackIndex));
|
||||||
trackView.setOnClickListener(this);
|
trackView.setOnClickListener(this);
|
||||||
haveSupportedTracks = true;
|
|
||||||
} else {
|
} else {
|
||||||
trackView.setFocusable(false);
|
trackView.setFocusable(false);
|
||||||
trackView.setEnabled(false);
|
trackView.setEnabled(false);
|
||||||
|
|
@ -169,10 +167,7 @@ import java.util.Arrays;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!haveSupportedTracks) {
|
if (haveAdaptiveTracks) {
|
||||||
// Indicate that the default selection will be nothing.
|
|
||||||
defaultView.setText(R.string.selection_default_none);
|
|
||||||
} else if (haveAdaptiveTracks) {
|
|
||||||
// View for using random adaptation.
|
// View for using random adaptation.
|
||||||
enableRandomAdaptationView = (CheckedTextView) inflater.inflate(
|
enableRandomAdaptationView = (CheckedTextView) inflater.inflate(
|
||||||
android.R.layout.simple_list_item_multiple_choice, root, false);
|
android.R.layout.simple_list_item_multiple_choice, root, false);
|
||||||
|
|
|
||||||
|
|
@ -30,8 +30,6 @@
|
||||||
|
|
||||||
<string name="selection_default">Default</string>
|
<string name="selection_default">Default</string>
|
||||||
|
|
||||||
<string name="selection_default_none">Default (none)</string>
|
|
||||||
|
|
||||||
<string name="unexpected_intent_action">Unexpected intent action: <xliff:g id="action">%1$s</xliff:g></string>
|
<string name="unexpected_intent_action">Unexpected intent action: <xliff:g id="action">%1$s</xliff:g></string>
|
||||||
|
|
||||||
<string name="enable_random_adaptation">Enable random adaptation</string>
|
<string name="enable_random_adaptation">Enable random adaptation</string>
|
||||||
|
|
|
||||||
|
|
@ -25,36 +25,26 @@ public final class VorbisBitArrayTest extends TestCase {
|
||||||
|
|
||||||
public void testReadBit() {
|
public void testReadBit() {
|
||||||
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0x5c, 0x50));
|
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0x5c, 0x50));
|
||||||
|
|
||||||
assertFalse(bitArray.readBit());
|
assertFalse(bitArray.readBit());
|
||||||
assertFalse(bitArray.readBit());
|
assertFalse(bitArray.readBit());
|
||||||
assertTrue(bitArray.readBit());
|
assertTrue(bitArray.readBit());
|
||||||
assertTrue(bitArray.readBit());
|
assertTrue(bitArray.readBit());
|
||||||
|
|
||||||
assertTrue(bitArray.readBit());
|
assertTrue(bitArray.readBit());
|
||||||
assertFalse(bitArray.readBit());
|
assertFalse(bitArray.readBit());
|
||||||
assertTrue(bitArray.readBit());
|
assertTrue(bitArray.readBit());
|
||||||
assertFalse(bitArray.readBit());
|
assertFalse(bitArray.readBit());
|
||||||
|
|
||||||
assertFalse(bitArray.readBit());
|
assertFalse(bitArray.readBit());
|
||||||
assertFalse(bitArray.readBit());
|
assertFalse(bitArray.readBit());
|
||||||
assertFalse(bitArray.readBit());
|
assertFalse(bitArray.readBit());
|
||||||
assertFalse(bitArray.readBit());
|
assertFalse(bitArray.readBit());
|
||||||
|
|
||||||
assertTrue(bitArray.readBit());
|
assertTrue(bitArray.readBit());
|
||||||
assertFalse(bitArray.readBit());
|
assertFalse(bitArray.readBit());
|
||||||
assertTrue(bitArray.readBit());
|
assertTrue(bitArray.readBit());
|
||||||
assertFalse(bitArray.readBit());
|
assertFalse(bitArray.readBit());
|
||||||
|
|
||||||
try {
|
|
||||||
assertFalse(bitArray.readBit());
|
|
||||||
fail();
|
|
||||||
} catch (IllegalStateException e) {/* ignored */}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testSkipBits() {
|
public void testSkipBits() {
|
||||||
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xF0, 0x0F));
|
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xF0, 0x0F));
|
||||||
|
|
||||||
bitArray.skipBits(10);
|
bitArray.skipBits(10);
|
||||||
assertEquals(10, bitArray.getPosition());
|
assertEquals(10, bitArray.getPosition());
|
||||||
assertTrue(bitArray.readBit());
|
assertTrue(bitArray.readBit());
|
||||||
|
|
@ -64,27 +54,10 @@ public final class VorbisBitArrayTest extends TestCase {
|
||||||
assertEquals(14, bitArray.getPosition());
|
assertEquals(14, bitArray.getPosition());
|
||||||
assertFalse(bitArray.readBit());
|
assertFalse(bitArray.readBit());
|
||||||
assertFalse(bitArray.readBit());
|
assertFalse(bitArray.readBit());
|
||||||
try {
|
|
||||||
bitArray.readBit();
|
|
||||||
fail();
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
// ignored
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
|
|
||||||
public void testSkipBitsThrowsErrorIfEOB() {
|
|
||||||
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xF0, 0x0F));
|
|
||||||
|
|
||||||
try {
|
|
||||||
bitArray.skipBits(17);
|
|
||||||
fail();
|
|
||||||
} catch (IllegalStateException e) {/* ignored */}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testGetPosition() throws Exception {
|
public void testGetPosition() throws Exception {
|
||||||
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xF0, 0x0F));
|
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xF0, 0x0F));
|
||||||
|
|
||||||
assertEquals(0, bitArray.getPosition());
|
assertEquals(0, bitArray.getPosition());
|
||||||
bitArray.readBit();
|
bitArray.readBit();
|
||||||
assertEquals(1, bitArray.getPosition());
|
assertEquals(1, bitArray.getPosition());
|
||||||
|
|
@ -96,35 +69,11 @@ public final class VorbisBitArrayTest extends TestCase {
|
||||||
|
|
||||||
public void testSetPosition() throws Exception {
|
public void testSetPosition() throws Exception {
|
||||||
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xF0, 0x0F));
|
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xF0, 0x0F));
|
||||||
|
|
||||||
assertEquals(0, bitArray.getPosition());
|
assertEquals(0, bitArray.getPosition());
|
||||||
bitArray.setPosition(4);
|
bitArray.setPosition(4);
|
||||||
assertEquals(4, bitArray.getPosition());
|
assertEquals(4, bitArray.getPosition());
|
||||||
|
|
||||||
bitArray.setPosition(15);
|
bitArray.setPosition(15);
|
||||||
assertFalse(bitArray.readBit());
|
assertFalse(bitArray.readBit());
|
||||||
try {
|
|
||||||
bitArray.readBit();
|
|
||||||
fail();
|
|
||||||
} catch (IllegalStateException e) {/* ignored */}
|
|
||||||
|
|
||||||
}
|
|
||||||
public void testSetPositionIllegalPositions() throws Exception {
|
|
||||||
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xF0, 0x0F));
|
|
||||||
|
|
||||||
try {
|
|
||||||
bitArray.setPosition(16);
|
|
||||||
fail();
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
assertEquals(0, bitArray.getPosition());
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
bitArray.setPosition(-1);
|
|
||||||
fail();
|
|
||||||
} catch (IllegalArgumentException e) {
|
|
||||||
assertEquals(0, bitArray.getPosition());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testReadInt32() {
|
public void testReadInt32() {
|
||||||
|
|
@ -136,13 +85,11 @@ public final class VorbisBitArrayTest extends TestCase {
|
||||||
|
|
||||||
public void testReadBits() throws Exception {
|
public void testReadBits() throws Exception {
|
||||||
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0x03, 0x22));
|
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0x03, 0x22));
|
||||||
|
|
||||||
assertEquals(3, bitArray.readBits(2));
|
assertEquals(3, bitArray.readBits(2));
|
||||||
bitArray.skipBits(6);
|
bitArray.skipBits(6);
|
||||||
assertEquals(2, bitArray.readBits(2));
|
assertEquals(2, bitArray.readBits(2));
|
||||||
bitArray.skipBits(2);
|
bitArray.skipBits(2);
|
||||||
assertEquals(2, bitArray.readBits(2));
|
assertEquals(2, bitArray.readBits(2));
|
||||||
|
|
||||||
bitArray.reset();
|
bitArray.reset();
|
||||||
assertEquals(0x2203, bitArray.readBits(16));
|
assertEquals(0x2203, bitArray.readBits(16));
|
||||||
}
|
}
|
||||||
|
|
@ -156,7 +103,6 @@ public final class VorbisBitArrayTest extends TestCase {
|
||||||
|
|
||||||
public void testReadBitsBeyondByteBoundaries() throws Exception {
|
public void testReadBitsBeyondByteBoundaries() throws Exception {
|
||||||
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xFF, 0x0F, 0xFF, 0x0F));
|
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xFF, 0x0F, 0xFF, 0x0F));
|
||||||
|
|
||||||
assertEquals(0x0FFF0FFF, bitArray.readBits(32));
|
assertEquals(0x0FFF0FFF, bitArray.readBits(32));
|
||||||
|
|
||||||
bitArray.reset();
|
bitArray.reset();
|
||||||
|
|
@ -188,83 +134,6 @@ public final class VorbisBitArrayTest extends TestCase {
|
||||||
assertEquals(0, bitArray.getPosition());
|
assertEquals(0, bitArray.getPosition());
|
||||||
bitArray.readBit();
|
bitArray.readBit();
|
||||||
assertEquals(1, bitArray.getPosition());
|
assertEquals(1, bitArray.getPosition());
|
||||||
|
|
||||||
try {
|
|
||||||
bitArray.readBits(24);
|
|
||||||
fail();
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
assertEquals(1, bitArray.getPosition());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testLimit() {
|
|
||||||
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xc0, 0x02), 1);
|
|
||||||
|
|
||||||
try {
|
|
||||||
bitArray.skipBits(9);
|
|
||||||
fail();
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
assertEquals(0, bitArray.getPosition());
|
|
||||||
}
|
|
||||||
|
|
||||||
try {
|
|
||||||
bitArray.readBits(9);
|
|
||||||
fail();
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
assertEquals(0, bitArray.getPosition());
|
|
||||||
}
|
|
||||||
|
|
||||||
int byteValue = bitArray.readBits(8);
|
|
||||||
assertEquals(0xc0, byteValue);
|
|
||||||
assertEquals(8, bitArray.getPosition());
|
|
||||||
try {
|
|
||||||
bitArray.readBit();
|
|
||||||
fail();
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
assertEquals(8, bitArray.getPosition());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public void testBitsLeft() {
|
|
||||||
VorbisBitArray bitArray = new VorbisBitArray(TestUtil.createByteArray(0xc0, 0x02));
|
|
||||||
|
|
||||||
assertEquals(16, bitArray.bitsLeft());
|
|
||||||
assertEquals(bitArray.limit(), bitArray.getPosition() + bitArray.bitsLeft());
|
|
||||||
|
|
||||||
bitArray.skipBits(1);
|
|
||||||
assertEquals(15, bitArray.bitsLeft());
|
|
||||||
assertEquals(bitArray.limit(), bitArray.getPosition() + bitArray.bitsLeft());
|
|
||||||
|
|
||||||
bitArray.skipBits(3);
|
|
||||||
assertEquals(12, bitArray.bitsLeft());
|
|
||||||
assertEquals(bitArray.limit(), bitArray.getPosition() + bitArray.bitsLeft());
|
|
||||||
|
|
||||||
bitArray.setPosition(6);
|
|
||||||
assertEquals(10, bitArray.bitsLeft());
|
|
||||||
assertEquals(bitArray.limit(), bitArray.getPosition() + bitArray.bitsLeft());
|
|
||||||
|
|
||||||
bitArray.skipBits(1);
|
|
||||||
assertEquals(9, bitArray.bitsLeft());
|
|
||||||
assertEquals(bitArray.limit(), bitArray.getPosition() + bitArray.bitsLeft());
|
|
||||||
|
|
||||||
bitArray.skipBits(1);
|
|
||||||
assertEquals(8, bitArray.bitsLeft());
|
|
||||||
assertEquals(bitArray.limit(), bitArray.getPosition() + bitArray.bitsLeft());
|
|
||||||
|
|
||||||
bitArray.readBits(4);
|
|
||||||
assertEquals(4, bitArray.bitsLeft());
|
|
||||||
assertEquals(bitArray.limit(), bitArray.getPosition() + bitArray.bitsLeft());
|
|
||||||
|
|
||||||
bitArray.readBits(4);
|
|
||||||
assertEquals(0, bitArray.bitsLeft());
|
|
||||||
assertEquals(bitArray.limit(), bitArray.getPosition() + bitArray.bitsLeft());
|
|
||||||
|
|
||||||
try {
|
|
||||||
bitArray.readBit();
|
|
||||||
fail();
|
|
||||||
} catch (IllegalStateException e) {
|
|
||||||
assertEquals(0, bitArray.bitsLeft());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,139 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2017 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.util;
|
||||||
|
|
||||||
|
import android.test.MoreAsserts;
|
||||||
|
|
||||||
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests for {@link ParsableBitArray}.
|
||||||
|
*/
|
||||||
|
public final class ParsableBitArrayTest extends TestCase {
|
||||||
|
|
||||||
|
private static final byte[] TEST_DATA = new byte[] {0x3C, (byte) 0xD2, (byte) 0x5F, (byte) 0x01,
|
||||||
|
(byte) 0xFF, (byte) 0x14, (byte) 0x60, (byte) 0x99};
|
||||||
|
|
||||||
|
public void testReadAllBytes() {
|
||||||
|
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
|
||||||
|
byte[] bytesRead = new byte[TEST_DATA.length];
|
||||||
|
testArray.readBytes(bytesRead, 0, TEST_DATA.length);
|
||||||
|
MoreAsserts.assertEquals(TEST_DATA, bytesRead);
|
||||||
|
assertEquals(TEST_DATA.length * 8, testArray.getPosition());
|
||||||
|
assertEquals(TEST_DATA.length, testArray.getBytePosition());
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testReadBit() {
|
||||||
|
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
|
||||||
|
assertReadBitsToEnd(0, testArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testReadBits() {
|
||||||
|
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
|
||||||
|
assertEquals(getTestDataBits(0, 5), testArray.readBits(5));
|
||||||
|
assertEquals(getTestDataBits(5, 3), testArray.readBits(3));
|
||||||
|
assertEquals(getTestDataBits(8, 16), testArray.readBits(16));
|
||||||
|
assertEquals(getTestDataBits(24, 3), testArray.readBits(3));
|
||||||
|
assertEquals(getTestDataBits(27, 18), testArray.readBits(18));
|
||||||
|
assertEquals(getTestDataBits(45, 5), testArray.readBits(5));
|
||||||
|
assertEquals(getTestDataBits(50, 14), testArray.readBits(14));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testRead32BitsByteAligned() {
|
||||||
|
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
|
||||||
|
assertEquals(getTestDataBits(0, 32), testArray.readBits(32));
|
||||||
|
assertEquals(getTestDataBits(32, 32), testArray.readBits(32));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testRead32BitsNonByteAligned() {
|
||||||
|
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
|
||||||
|
assertEquals(getTestDataBits(0, 5), testArray.readBits(5));
|
||||||
|
assertEquals(getTestDataBits(5, 32), testArray.readBits(32));
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testSkipBytes() {
|
||||||
|
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
|
||||||
|
testArray.skipBytes(2);
|
||||||
|
assertReadBitsToEnd(16, testArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testSkipBitsByteAligned() {
|
||||||
|
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
|
||||||
|
testArray.skipBits(16);
|
||||||
|
assertReadBitsToEnd(16, testArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testSkipBitsNonByteAligned() {
|
||||||
|
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
|
||||||
|
testArray.skipBits(5);
|
||||||
|
assertReadBitsToEnd(5, testArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testSetPositionByteAligned() {
|
||||||
|
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
|
||||||
|
testArray.setPosition(16);
|
||||||
|
assertReadBitsToEnd(16, testArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testSetPositionNonByteAligned() {
|
||||||
|
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
|
||||||
|
testArray.setPosition(5);
|
||||||
|
assertReadBitsToEnd(5, testArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testByteAlignFromNonByteAligned() {
|
||||||
|
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
|
||||||
|
testArray.setPosition(11);
|
||||||
|
testArray.byteAlign();
|
||||||
|
assertEquals(2, testArray.getBytePosition());
|
||||||
|
assertEquals(16, testArray.getPosition());
|
||||||
|
assertReadBitsToEnd(16, testArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
public void testByteAlignFromByteAligned() {
|
||||||
|
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
|
||||||
|
testArray.setPosition(16);
|
||||||
|
testArray.byteAlign(); // Should be a no-op.
|
||||||
|
assertEquals(2, testArray.getBytePosition());
|
||||||
|
assertEquals(16, testArray.getPosition());
|
||||||
|
assertReadBitsToEnd(16, testArray);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertReadBitsToEnd(int expectedStartPosition, ParsableBitArray testArray) {
|
||||||
|
int position = testArray.getPosition();
|
||||||
|
assertEquals(expectedStartPosition, position);
|
||||||
|
for (int i = position; i < TEST_DATA.length * 8; i++) {
|
||||||
|
assertEquals(getTestDataBit(i), testArray.readBit());
|
||||||
|
assertEquals(i + 1, testArray.getPosition());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getTestDataBits(int bitPosition, int length) {
|
||||||
|
int result = 0;
|
||||||
|
for (int i = 0; i < length; i++) {
|
||||||
|
result = result << 1;
|
||||||
|
if (getTestDataBit(bitPosition++)) {
|
||||||
|
result |= 0x1;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean getTestDataBit(int bitPosition) {
|
||||||
|
return (TEST_DATA[bitPosition / 8] & (0x80 >>> (bitPosition % 8))) != 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
@ -95,7 +95,7 @@ public final class ParsableNalUnitBitArrayTest extends TestCase {
|
||||||
ParsableNalUnitBitArray array =
|
ParsableNalUnitBitArray array =
|
||||||
new ParsableNalUnitBitArray(createByteArray(0, 0, 3, 128, 0), 0, 5);
|
new ParsableNalUnitBitArray(createByteArray(0, 0, 3, 128, 0), 0, 5);
|
||||||
assertFalse(array.canReadExpGolombCodedNum());
|
assertFalse(array.canReadExpGolombCodedNum());
|
||||||
array.skipBits(1);
|
array.skipBit();
|
||||||
assertTrue(array.canReadExpGolombCodedNum());
|
assertTrue(array.canReadExpGolombCodedNum());
|
||||||
assertEquals(32767, array.readUnsignedExpGolombCodedInt());
|
assertEquals(32767, array.readUnsignedExpGolombCodedInt());
|
||||||
assertFalse(array.canReadBits(1));
|
assertFalse(array.canReadBits(1));
|
||||||
|
|
|
||||||
|
|
@ -24,13 +24,13 @@ public interface 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.
|
||||||
String VERSION = "2.4.3";
|
String VERSION = "2.4.4";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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.
|
||||||
String VERSION_SLASHY = "ExoPlayerLib/2.4.3";
|
String VERSION_SLASHY = "ExoPlayerLib/2.4.4";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The version of the library expressed as an integer, for example 1002003.
|
* The version of the library expressed as an integer, for example 1002003.
|
||||||
|
|
@ -40,7 +40,7 @@ public interface 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.
|
||||||
int VERSION_INT = 2004003;
|
int VERSION_INT = 2004004;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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}
|
||||||
|
|
|
||||||
|
|
@ -25,8 +25,9 @@ import com.google.android.exoplayer2.util.Assertions;
|
||||||
*/
|
*/
|
||||||
/* package */ final class VorbisBitArray {
|
/* package */ final class VorbisBitArray {
|
||||||
|
|
||||||
public final byte[] data;
|
private final byte[] data;
|
||||||
private final int limit;
|
private final int byteLimit;
|
||||||
|
|
||||||
private int byteOffset;
|
private int byteOffset;
|
||||||
private int bitOffset;
|
private int bitOffset;
|
||||||
|
|
||||||
|
|
@ -36,18 +37,8 @@ import com.google.android.exoplayer2.util.Assertions;
|
||||||
* @param data the array to wrap.
|
* @param data the array to wrap.
|
||||||
*/
|
*/
|
||||||
public VorbisBitArray(byte[] data) {
|
public VorbisBitArray(byte[] data) {
|
||||||
this(data, data.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new instance that wraps an existing array.
|
|
||||||
*
|
|
||||||
* @param data the array to wrap.
|
|
||||||
* @param limit the limit in bytes.
|
|
||||||
*/
|
|
||||||
public VorbisBitArray(byte[] data, int limit) {
|
|
||||||
this.data = data;
|
this.data = data;
|
||||||
this.limit = limit * 8;
|
byteLimit = data.length;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -64,7 +55,9 @@ import com.google.android.exoplayer2.util.Assertions;
|
||||||
* @return {@code true} if the bit is set, {@code false} otherwise.
|
* @return {@code true} if the bit is set, {@code false} otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean readBit() {
|
public boolean readBit() {
|
||||||
return readBits(1) == 1;
|
boolean returnValue = (((data[byteOffset] & 0xFF) >> bitOffset) & 0x01) == 1;
|
||||||
|
skipBits(1);
|
||||||
|
return returnValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -74,53 +67,32 @@ import com.google.android.exoplayer2.util.Assertions;
|
||||||
* @return An integer whose bottom {@code numBits} bits hold the read data.
|
* @return An integer whose bottom {@code numBits} bits hold the read data.
|
||||||
*/
|
*/
|
||||||
public int readBits(int numBits) {
|
public int readBits(int numBits) {
|
||||||
Assertions.checkState(getPosition() + numBits <= limit);
|
int tempByteOffset = byteOffset;
|
||||||
if (numBits == 0) {
|
int bitsRead = Math.min(numBits, 8 - bitOffset);
|
||||||
return 0;
|
int returnValue = ((data[tempByteOffset++] & 0xFF) >> bitOffset) & (0xFF >> (8 - bitsRead));
|
||||||
|
while (bitsRead < numBits) {
|
||||||
|
returnValue |= (data[tempByteOffset++] & 0xFF) << bitsRead;
|
||||||
|
bitsRead += 8;
|
||||||
}
|
}
|
||||||
int result = 0;
|
returnValue &= 0xFFFFFFFF >>> (32 - numBits);
|
||||||
int bitCount = 0;
|
skipBits(numBits);
|
||||||
if (bitOffset != 0) {
|
return returnValue;
|
||||||
bitCount = Math.min(numBits, 8 - bitOffset);
|
|
||||||
int mask = 0xFF >>> (8 - bitCount);
|
|
||||||
result = (data[byteOffset] >>> bitOffset) & mask;
|
|
||||||
bitOffset += bitCount;
|
|
||||||
if (bitOffset == 8) {
|
|
||||||
byteOffset++;
|
|
||||||
bitOffset = 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (numBits - bitCount > 7) {
|
|
||||||
int numBytes = (numBits - bitCount) / 8;
|
|
||||||
for (int i = 0; i < numBytes; i++) {
|
|
||||||
result |= (data[byteOffset++] & 0xFFL) << bitCount;
|
|
||||||
bitCount += 8;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
if (numBits > bitCount) {
|
|
||||||
int bitsOnNextByte = numBits - bitCount;
|
|
||||||
int mask = 0xFF >>> (8 - bitsOnNextByte);
|
|
||||||
result |= (data[byteOffset] & mask) << bitCount;
|
|
||||||
bitOffset += bitsOnNextByte;
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Skips {@code numberOfBits} bits.
|
* Skips {@code numberOfBits} bits.
|
||||||
*
|
*
|
||||||
* @param numberOfBits The number of bits to skip.
|
* @param numBits The number of bits to skip.
|
||||||
*/
|
*/
|
||||||
public void skipBits(int numberOfBits) {
|
public void skipBits(int numBits) {
|
||||||
Assertions.checkState(getPosition() + numberOfBits <= limit);
|
int numBytes = numBits / 8;
|
||||||
byteOffset += numberOfBits / 8;
|
byteOffset += numBytes;
|
||||||
bitOffset += numberOfBits % 8;
|
bitOffset += numBits - (numBytes * 8);
|
||||||
if (bitOffset > 7) {
|
if (bitOffset > 7) {
|
||||||
byteOffset++;
|
byteOffset++;
|
||||||
bitOffset -= 8;
|
bitOffset -= 8;
|
||||||
}
|
}
|
||||||
|
assertValidOffset();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -136,23 +108,22 @@ import com.google.android.exoplayer2.util.Assertions;
|
||||||
* @param position The new reading position in bits.
|
* @param position The new reading position in bits.
|
||||||
*/
|
*/
|
||||||
public void setPosition(int position) {
|
public void setPosition(int position) {
|
||||||
Assertions.checkArgument(position < limit && position >= 0);
|
|
||||||
byteOffset = position / 8;
|
byteOffset = position / 8;
|
||||||
bitOffset = position - (byteOffset * 8);
|
bitOffset = position - (byteOffset * 8);
|
||||||
|
assertValidOffset();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the number of remaining bits.
|
* Returns the number of remaining bits.
|
||||||
*/
|
*/
|
||||||
public int bitsLeft() {
|
public int bitsLeft() {
|
||||||
return limit - getPosition();
|
return (byteLimit - byteOffset) * 8 - bitOffset;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
private void assertValidOffset() {
|
||||||
* Returns the limit in bits.
|
// It is fine for position to be at the end of the array, but no further.
|
||||||
**/
|
Assertions.checkState(byteOffset >= 0
|
||||||
public int limit() {
|
&& (byteOffset < byteLimit || (byteOffset == byteLimit && bitOffset == 0)));
|
||||||
return limit;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -316,7 +316,7 @@ public final class H264Reader implements ElementaryStreamReader {
|
||||||
if (!bitArray.canReadBits(8)) {
|
if (!bitArray.canReadBits(8)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
bitArray.skipBits(1); // forbidden_zero_bit
|
bitArray.skipBit(); // forbidden_zero_bit
|
||||||
int nalRefIdc = bitArray.readBits(2);
|
int nalRefIdc = bitArray.readBits(2);
|
||||||
bitArray.skipBits(5); // nal_unit_type
|
bitArray.skipBits(5); // nal_unit_type
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -225,7 +225,7 @@ public final class H265Reader implements ElementaryStreamReader {
|
||||||
ParsableNalUnitBitArray bitArray = new ParsableNalUnitBitArray(sps.nalData, 0, sps.nalLength);
|
ParsableNalUnitBitArray bitArray = new ParsableNalUnitBitArray(sps.nalData, 0, sps.nalLength);
|
||||||
bitArray.skipBits(40 + 4); // NAL header, sps_video_parameter_set_id
|
bitArray.skipBits(40 + 4); // NAL header, sps_video_parameter_set_id
|
||||||
int maxSubLayersMinus1 = bitArray.readBits(3);
|
int maxSubLayersMinus1 = bitArray.readBits(3);
|
||||||
bitArray.skipBits(1); // sps_temporal_id_nesting_flag
|
bitArray.skipBit(); // sps_temporal_id_nesting_flag
|
||||||
|
|
||||||
// profile_tier_level(1, sps_max_sub_layers_minus1)
|
// profile_tier_level(1, sps_max_sub_layers_minus1)
|
||||||
bitArray.skipBits(88); // if (profilePresentFlag) {...}
|
bitArray.skipBits(88); // if (profilePresentFlag) {...}
|
||||||
|
|
@ -247,7 +247,7 @@ public final class H265Reader implements ElementaryStreamReader {
|
||||||
bitArray.readUnsignedExpGolombCodedInt(); // sps_seq_parameter_set_id
|
bitArray.readUnsignedExpGolombCodedInt(); // sps_seq_parameter_set_id
|
||||||
int chromaFormatIdc = bitArray.readUnsignedExpGolombCodedInt();
|
int chromaFormatIdc = bitArray.readUnsignedExpGolombCodedInt();
|
||||||
if (chromaFormatIdc == 3) {
|
if (chromaFormatIdc == 3) {
|
||||||
bitArray.skipBits(1); // separate_colour_plane_flag
|
bitArray.skipBit(); // separate_colour_plane_flag
|
||||||
}
|
}
|
||||||
int picWidthInLumaSamples = bitArray.readUnsignedExpGolombCodedInt();
|
int picWidthInLumaSamples = bitArray.readUnsignedExpGolombCodedInt();
|
||||||
int picHeightInLumaSamples = bitArray.readUnsignedExpGolombCodedInt();
|
int picHeightInLumaSamples = bitArray.readUnsignedExpGolombCodedInt();
|
||||||
|
|
@ -288,7 +288,7 @@ public final class H265Reader implements ElementaryStreamReader {
|
||||||
bitArray.skipBits(8);
|
bitArray.skipBits(8);
|
||||||
bitArray.readUnsignedExpGolombCodedInt(); // log2_min_pcm_luma_coding_block_size_minus3
|
bitArray.readUnsignedExpGolombCodedInt(); // log2_min_pcm_luma_coding_block_size_minus3
|
||||||
bitArray.readUnsignedExpGolombCodedInt(); // log2_diff_max_min_pcm_luma_coding_block_size
|
bitArray.readUnsignedExpGolombCodedInt(); // log2_diff_max_min_pcm_luma_coding_block_size
|
||||||
bitArray.skipBits(1); // pcm_loop_filter_disabled_flag
|
bitArray.skipBit(); // pcm_loop_filter_disabled_flag
|
||||||
}
|
}
|
||||||
// Skips all short term reference picture sets.
|
// Skips all short term reference picture sets.
|
||||||
skipShortTermRefPicSets(bitArray);
|
skipShortTermRefPicSets(bitArray);
|
||||||
|
|
@ -365,11 +365,11 @@ public final class H265Reader implements ElementaryStreamReader {
|
||||||
interRefPicSetPredictionFlag = bitArray.readBit();
|
interRefPicSetPredictionFlag = bitArray.readBit();
|
||||||
}
|
}
|
||||||
if (interRefPicSetPredictionFlag) {
|
if (interRefPicSetPredictionFlag) {
|
||||||
bitArray.skipBits(1); // delta_rps_sign
|
bitArray.skipBit(); // delta_rps_sign
|
||||||
bitArray.readUnsignedExpGolombCodedInt(); // abs_delta_rps_minus1
|
bitArray.readUnsignedExpGolombCodedInt(); // abs_delta_rps_minus1
|
||||||
for (int j = 0; j <= previousNumDeltaPocs; j++) {
|
for (int j = 0; j <= previousNumDeltaPocs; j++) {
|
||||||
if (bitArray.readBit()) { // used_by_curr_pic_flag[j]
|
if (bitArray.readBit()) { // used_by_curr_pic_flag[j]
|
||||||
bitArray.skipBits(1); // use_delta_flag[j]
|
bitArray.skipBit(); // use_delta_flag[j]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
|
|
@ -378,11 +378,11 @@ public final class H265Reader implements ElementaryStreamReader {
|
||||||
previousNumDeltaPocs = numNegativePics + numPositivePics;
|
previousNumDeltaPocs = numNegativePics + numPositivePics;
|
||||||
for (int i = 0; i < numNegativePics; i++) {
|
for (int i = 0; i < numNegativePics; i++) {
|
||||||
bitArray.readUnsignedExpGolombCodedInt(); // delta_poc_s0_minus1[i]
|
bitArray.readUnsignedExpGolombCodedInt(); // delta_poc_s0_minus1[i]
|
||||||
bitArray.skipBits(1); // used_by_curr_pic_s0_flag[i]
|
bitArray.skipBit(); // used_by_curr_pic_s0_flag[i]
|
||||||
}
|
}
|
||||||
for (int i = 0; i < numPositivePics; i++) {
|
for (int i = 0; i < numPositivePics; i++) {
|
||||||
bitArray.readUnsignedExpGolombCodedInt(); // delta_poc_s1_minus1[i]
|
bitArray.readUnsignedExpGolombCodedInt(); // delta_poc_s1_minus1[i]
|
||||||
bitArray.skipBits(1); // used_by_curr_pic_s1_flag[i]
|
bitArray.skipBit(); // used_by_curr_pic_s1_flag[i]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -51,9 +51,10 @@ import java.util.List;
|
||||||
Assertions.checkArgument(MimeTypes.APPLICATION_CEA608.equals(channelMimeType)
|
Assertions.checkArgument(MimeTypes.APPLICATION_CEA608.equals(channelMimeType)
|
||||||
|| MimeTypes.APPLICATION_CEA708.equals(channelMimeType),
|
|| MimeTypes.APPLICATION_CEA708.equals(channelMimeType),
|
||||||
"Invalid closed caption mime type provided: " + channelMimeType);
|
"Invalid closed caption mime type provided: " + channelMimeType);
|
||||||
output.format(Format.createTextSampleFormat(idGenerator.getFormatId(), channelMimeType, null,
|
String formatId = channelFormat.id != null ? channelFormat.id : idGenerator.getFormatId();
|
||||||
Format.NO_VALUE, channelFormat.selectionFlags, channelFormat.language,
|
output.format(Format.createTextSampleFormat(formatId, channelMimeType, null, Format.NO_VALUE,
|
||||||
channelFormat.accessibilityChannel, null));
|
channelFormat.selectionFlags, channelFormat.language, channelFormat.accessibilityChannel,
|
||||||
|
null));
|
||||||
outputs[i] = output;
|
outputs[i] = output;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -111,7 +111,6 @@ public final class TsExtractor implements Extractor {
|
||||||
@Mode private final int mode;
|
@Mode private final int mode;
|
||||||
private final List<TimestampAdjuster> timestampAdjusters;
|
private final List<TimestampAdjuster> timestampAdjusters;
|
||||||
private final ParsableByteArray tsPacketBuffer;
|
private final ParsableByteArray tsPacketBuffer;
|
||||||
private final ParsableBitArray tsScratch;
|
|
||||||
private final SparseIntArray continuityCounters;
|
private final SparseIntArray continuityCounters;
|
||||||
private final TsPayloadReader.Factory payloadReaderFactory;
|
private final TsPayloadReader.Factory payloadReaderFactory;
|
||||||
private final SparseArray<TsPayloadReader> tsPayloadReaders; // Indexed by pid
|
private final SparseArray<TsPayloadReader> tsPayloadReaders; // Indexed by pid
|
||||||
|
|
@ -164,7 +163,6 @@ public final class TsExtractor implements Extractor {
|
||||||
timestampAdjusters.add(timestampAdjuster);
|
timestampAdjusters.add(timestampAdjuster);
|
||||||
}
|
}
|
||||||
tsPacketBuffer = new ParsableByteArray(BUFFER_SIZE);
|
tsPacketBuffer = new ParsableByteArray(BUFFER_SIZE);
|
||||||
tsScratch = new ParsableBitArray(new byte[3]);
|
|
||||||
trackIds = new SparseBooleanArray();
|
trackIds = new SparseBooleanArray();
|
||||||
tsPayloadReaders = new SparseArray<>();
|
tsPayloadReaders = new SparseArray<>();
|
||||||
continuityCounters = new SparseIntArray();
|
continuityCounters = new SparseIntArray();
|
||||||
|
|
@ -250,24 +248,23 @@ public final class TsExtractor implements Extractor {
|
||||||
return RESULT_CONTINUE;
|
return RESULT_CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
tsPacketBuffer.skipBytes(1);
|
int tsPacketHeader = tsPacketBuffer.readInt();
|
||||||
tsPacketBuffer.readBytes(tsScratch, 3);
|
if ((tsPacketHeader & 0x800000) != 0) { // transport_error_indicator
|
||||||
if (tsScratch.readBit()) { // transport_error_indicator
|
|
||||||
// There are uncorrectable errors in this packet.
|
// There are uncorrectable errors in this packet.
|
||||||
tsPacketBuffer.setPosition(endOfPacket);
|
tsPacketBuffer.setPosition(endOfPacket);
|
||||||
return RESULT_CONTINUE;
|
return RESULT_CONTINUE;
|
||||||
}
|
}
|
||||||
boolean payloadUnitStartIndicator = tsScratch.readBit();
|
boolean payloadUnitStartIndicator = (tsPacketHeader & 0x400000) != 0;
|
||||||
tsScratch.skipBits(1); // transport_priority
|
// Ignoring transport_priority (tsPacketHeader & 0x200000)
|
||||||
int pid = tsScratch.readBits(13);
|
int pid = (tsPacketHeader & 0x1FFF00) >> 8;
|
||||||
tsScratch.skipBits(2); // transport_scrambling_control
|
// Ignoring transport_scrambling_control (tsPacketHeader & 0xC0)
|
||||||
boolean adaptationFieldExists = tsScratch.readBit();
|
boolean adaptationFieldExists = (tsPacketHeader & 0x20) != 0;
|
||||||
boolean payloadExists = tsScratch.readBit();
|
boolean payloadExists = (tsPacketHeader & 0x10) != 0;
|
||||||
|
|
||||||
// Discontinuity check.
|
// Discontinuity check.
|
||||||
boolean discontinuityFound = false;
|
boolean discontinuityFound = false;
|
||||||
int continuityCounter = tsScratch.readBits(4);
|
|
||||||
if (mode != MODE_HLS) {
|
if (mode != MODE_HLS) {
|
||||||
|
int continuityCounter = tsPacketHeader & 0xF;
|
||||||
int previousCounter = continuityCounters.get(pid, continuityCounter - 1);
|
int previousCounter = continuityCounters.get(pid, continuityCounter - 1);
|
||||||
continuityCounters.put(pid, continuityCounter);
|
continuityCounters.put(pid, continuityCounter);
|
||||||
if (previousCounter == continuityCounter) {
|
if (previousCounter == continuityCounter) {
|
||||||
|
|
@ -276,7 +273,7 @@ public final class TsExtractor implements Extractor {
|
||||||
tsPacketBuffer.setPosition(endOfPacket);
|
tsPacketBuffer.setPosition(endOfPacket);
|
||||||
return RESULT_CONTINUE;
|
return RESULT_CONTINUE;
|
||||||
}
|
}
|
||||||
} else if (continuityCounter != (previousCounter + 1) % 16) {
|
} else if (continuityCounter != ((previousCounter + 1) & 0xF)) {
|
||||||
discontinuityFound = true;
|
discontinuityFound = true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
@ -296,7 +293,6 @@ public final class TsExtractor implements Extractor {
|
||||||
}
|
}
|
||||||
tsPacketBuffer.setLimit(endOfPacket);
|
tsPacketBuffer.setLimit(endOfPacket);
|
||||||
payloadReader.consume(tsPacketBuffer, payloadUnitStartIndicator);
|
payloadReader.consume(tsPacketBuffer, payloadUnitStartIndicator);
|
||||||
Assertions.checkState(tsPacketBuffer.getPosition() <= endOfPacket);
|
|
||||||
tsPacketBuffer.setLimit(limit);
|
tsPacketBuffer.setLimit(limit);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -47,7 +47,8 @@ public final class FlacStreamInfo {
|
||||||
this.sampleRate = scratch.readBits(20);
|
this.sampleRate = scratch.readBits(20);
|
||||||
this.channels = scratch.readBits(3) + 1;
|
this.channels = scratch.readBits(3) + 1;
|
||||||
this.bitsPerSample = scratch.readBits(5) + 1;
|
this.bitsPerSample = scratch.readBits(5) + 1;
|
||||||
this.totalSamples = scratch.readBits(36);
|
this.totalSamples = ((scratch.readBits(4) & 0xFL) << 32)
|
||||||
|
| (scratch.readBits(32) & 0xFFFFFFFFL);
|
||||||
// Remaining 16 bytes is md5 value
|
// Remaining 16 bytes is md5 value
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -265,7 +265,7 @@ public final class NalUnitUtil {
|
||||||
}
|
}
|
||||||
data.readUnsignedExpGolombCodedInt(); // bit_depth_luma_minus8
|
data.readUnsignedExpGolombCodedInt(); // bit_depth_luma_minus8
|
||||||
data.readUnsignedExpGolombCodedInt(); // bit_depth_chroma_minus8
|
data.readUnsignedExpGolombCodedInt(); // bit_depth_chroma_minus8
|
||||||
data.skipBits(1); // qpprime_y_zero_transform_bypass_flag
|
data.skipBit(); // qpprime_y_zero_transform_bypass_flag
|
||||||
boolean seqScalingMatrixPresentFlag = data.readBit();
|
boolean seqScalingMatrixPresentFlag = data.readBit();
|
||||||
if (seqScalingMatrixPresentFlag) {
|
if (seqScalingMatrixPresentFlag) {
|
||||||
int limit = (chromaFormatIdc != 3) ? 8 : 12;
|
int limit = (chromaFormatIdc != 3) ? 8 : 12;
|
||||||
|
|
@ -295,17 +295,17 @@ public final class NalUnitUtil {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
data.readUnsignedExpGolombCodedInt(); // max_num_ref_frames
|
data.readUnsignedExpGolombCodedInt(); // max_num_ref_frames
|
||||||
data.skipBits(1); // gaps_in_frame_num_value_allowed_flag
|
data.skipBit(); // gaps_in_frame_num_value_allowed_flag
|
||||||
|
|
||||||
int picWidthInMbs = data.readUnsignedExpGolombCodedInt() + 1;
|
int picWidthInMbs = data.readUnsignedExpGolombCodedInt() + 1;
|
||||||
int picHeightInMapUnits = data.readUnsignedExpGolombCodedInt() + 1;
|
int picHeightInMapUnits = data.readUnsignedExpGolombCodedInt() + 1;
|
||||||
boolean frameMbsOnlyFlag = data.readBit();
|
boolean frameMbsOnlyFlag = data.readBit();
|
||||||
int frameHeightInMbs = (2 - (frameMbsOnlyFlag ? 1 : 0)) * picHeightInMapUnits;
|
int frameHeightInMbs = (2 - (frameMbsOnlyFlag ? 1 : 0)) * picHeightInMapUnits;
|
||||||
if (!frameMbsOnlyFlag) {
|
if (!frameMbsOnlyFlag) {
|
||||||
data.skipBits(1); // mb_adaptive_frame_field_flag
|
data.skipBit(); // mb_adaptive_frame_field_flag
|
||||||
}
|
}
|
||||||
|
|
||||||
data.skipBits(1); // direct_8x8_inference_flag
|
data.skipBit(); // direct_8x8_inference_flag
|
||||||
int frameWidth = picWidthInMbs * 16;
|
int frameWidth = picWidthInMbs * 16;
|
||||||
int frameHeight = frameHeightInMbs * 16;
|
int frameHeight = frameHeightInMbs * 16;
|
||||||
boolean frameCroppingFlag = data.readBit();
|
boolean frameCroppingFlag = data.readBit();
|
||||||
|
|
@ -368,7 +368,7 @@ public final class NalUnitUtil {
|
||||||
data.skipBits(8); // nal_unit
|
data.skipBits(8); // nal_unit
|
||||||
int picParameterSetId = data.readUnsignedExpGolombCodedInt();
|
int picParameterSetId = data.readUnsignedExpGolombCodedInt();
|
||||||
int seqParameterSetId = data.readUnsignedExpGolombCodedInt();
|
int seqParameterSetId = data.readUnsignedExpGolombCodedInt();
|
||||||
data.skipBits(1); // entropy_coding_mode_flag
|
data.skipBit(); // entropy_coding_mode_flag
|
||||||
boolean bottomFieldPicOrderInFramePresentFlag = data.readBit();
|
boolean bottomFieldPicOrderInFramePresentFlag = data.readBit();
|
||||||
return new PpsData(picParameterSetId, seqParameterSetId, bottomFieldPicOrderInFramePresentFlag);
|
return new PpsData(picParameterSetId, seqParameterSetId, bottomFieldPicOrderInFramePresentFlag);
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -110,14 +110,26 @@ public final class ParsableBitArray {
|
||||||
assertValidOffset();
|
assertValidOffset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skips a single bit.
|
||||||
|
*/
|
||||||
|
public void skipBit() {
|
||||||
|
if (++bitOffset == 8) {
|
||||||
|
bitOffset = 0;
|
||||||
|
byteOffset++;
|
||||||
|
}
|
||||||
|
assertValidOffset();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Skips bits and moves current reading position forward.
|
* Skips bits and moves current reading position forward.
|
||||||
*
|
*
|
||||||
* @param n The number of bits to skip.
|
* @param numBits The number of bits to skip.
|
||||||
*/
|
*/
|
||||||
public void skipBits(int n) {
|
public void skipBits(int numBits) {
|
||||||
byteOffset += (n / 8);
|
int numBytes = numBits / 8;
|
||||||
bitOffset += (n % 8);
|
byteOffset += numBytes;
|
||||||
|
bitOffset += numBits - (numBytes * 8);
|
||||||
if (bitOffset > 7) {
|
if (bitOffset > 7) {
|
||||||
byteOffset++;
|
byteOffset++;
|
||||||
bitOffset -= 8;
|
bitOffset -= 8;
|
||||||
|
|
@ -131,7 +143,9 @@ public final class ParsableBitArray {
|
||||||
* @return Whether the bit is set.
|
* @return Whether the bit is set.
|
||||||
*/
|
*/
|
||||||
public boolean readBit() {
|
public boolean readBit() {
|
||||||
return readBits(1) == 1;
|
boolean returnValue = (data[byteOffset] & (0x80 >> bitOffset)) != 0;
|
||||||
|
skipBit();
|
||||||
|
return returnValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -141,48 +155,18 @@ public final class ParsableBitArray {
|
||||||
* @return An integer whose bottom n bits hold the read data.
|
* @return An integer whose bottom n bits hold the read data.
|
||||||
*/
|
*/
|
||||||
public int readBits(int numBits) {
|
public int readBits(int numBits) {
|
||||||
if (numBits == 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int returnValue = 0;
|
int returnValue = 0;
|
||||||
|
bitOffset += numBits;
|
||||||
// Read as many whole bytes as we can.
|
while (bitOffset > 8) {
|
||||||
int wholeBytes = (numBits / 8);
|
bitOffset -= 8;
|
||||||
for (int i = 0; i < wholeBytes; i++) {
|
returnValue |= (data[byteOffset++] & 0xFF) << bitOffset;
|
||||||
int byteValue;
|
}
|
||||||
if (bitOffset != 0) {
|
returnValue |= (data[byteOffset] & 0xFF) >> 8 - bitOffset;
|
||||||
byteValue = ((data[byteOffset] & 0xFF) << bitOffset)
|
returnValue &= 0xFFFFFFFF >>> (32 - numBits);
|
||||||
| ((data[byteOffset + 1] & 0xFF) >>> (8 - bitOffset));
|
if (bitOffset == 8) {
|
||||||
} else {
|
bitOffset = 0;
|
||||||
byteValue = data[byteOffset];
|
|
||||||
}
|
|
||||||
numBits -= 8;
|
|
||||||
returnValue |= (byteValue & 0xFF) << numBits;
|
|
||||||
byteOffset++;
|
byteOffset++;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Read any remaining bits.
|
|
||||||
if (numBits > 0) {
|
|
||||||
int nextBit = bitOffset + numBits;
|
|
||||||
byte writeMask = (byte) (0xFF >> (8 - numBits));
|
|
||||||
|
|
||||||
if (nextBit > 8) {
|
|
||||||
// Combine bits from current byte and next byte.
|
|
||||||
returnValue |= ((((data[byteOffset] & 0xFF) << (nextBit - 8)
|
|
||||||
| ((data[byteOffset + 1] & 0xFF) >> (16 - nextBit))) & writeMask));
|
|
||||||
byteOffset++;
|
|
||||||
} else {
|
|
||||||
// Bits to be read only within current byte.
|
|
||||||
returnValue |= (((data[byteOffset] & 0xFF) >> (8 - nextBit)) & writeMask);
|
|
||||||
if (nextBit == 8) {
|
|
||||||
byteOffset++;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bitOffset = nextBit % 8;
|
|
||||||
}
|
|
||||||
|
|
||||||
assertValidOffset();
|
assertValidOffset();
|
||||||
return returnValue;
|
return returnValue;
|
||||||
}
|
}
|
||||||
|
|
@ -231,7 +215,6 @@ public final class ParsableBitArray {
|
||||||
private void assertValidOffset() {
|
private void assertValidOffset() {
|
||||||
// It is fine for position to be at the end of the array, but no further.
|
// It is fine for position to be at the end of the array, but no further.
|
||||||
Assertions.checkState(byteOffset >= 0
|
Assertions.checkState(byteOffset >= 0
|
||||||
&& (bitOffset >= 0 && bitOffset < 8)
|
|
||||||
&& (byteOffset < byteLimit || (byteOffset == byteLimit && bitOffset == 0)));
|
&& (byteOffset < byteLimit || (byteOffset == byteLimit && bitOffset == 0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -54,15 +54,27 @@ public final class ParsableNalUnitBitArray {
|
||||||
assertValidOffset();
|
assertValidOffset();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Skips a single bit.
|
||||||
|
*/
|
||||||
|
public void skipBit() {
|
||||||
|
if (++bitOffset == 8) {
|
||||||
|
bitOffset = 0;
|
||||||
|
byteOffset += shouldSkipByte(byteOffset + 1) ? 2 : 1;
|
||||||
|
}
|
||||||
|
assertValidOffset();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Skips bits and moves current reading position forward.
|
* Skips bits and moves current reading position forward.
|
||||||
*
|
*
|
||||||
* @param n The number of bits to skip.
|
* @param numBits The number of bits to skip.
|
||||||
*/
|
*/
|
||||||
public void skipBits(int n) {
|
public void skipBits(int numBits) {
|
||||||
int oldByteOffset = byteOffset;
|
int oldByteOffset = byteOffset;
|
||||||
byteOffset += (n / 8);
|
int numBytes = numBits / 8;
|
||||||
bitOffset += (n % 8);
|
byteOffset += numBytes;
|
||||||
|
bitOffset += numBits - (numBytes * 8);
|
||||||
if (bitOffset > 7) {
|
if (bitOffset > 7) {
|
||||||
byteOffset++;
|
byteOffset++;
|
||||||
bitOffset -= 8;
|
bitOffset -= 8;
|
||||||
|
|
@ -81,13 +93,14 @@ public final class ParsableNalUnitBitArray {
|
||||||
* Returns whether it's possible to read {@code n} bits starting from the current offset. The
|
* Returns whether it's possible to read {@code n} bits starting from the current offset. The
|
||||||
* offset is not modified.
|
* offset is not modified.
|
||||||
*
|
*
|
||||||
* @param n The number of bits.
|
* @param numBits The number of bits.
|
||||||
* @return Whether it is possible to read {@code n} bits.
|
* @return Whether it is possible to read {@code n} bits.
|
||||||
*/
|
*/
|
||||||
public boolean canReadBits(int n) {
|
public boolean canReadBits(int numBits) {
|
||||||
int oldByteOffset = byteOffset;
|
int oldByteOffset = byteOffset;
|
||||||
int newByteOffset = byteOffset + (n / 8);
|
int numBytes = numBits / 8;
|
||||||
int newBitOffset = bitOffset + (n % 8);
|
int newByteOffset = byteOffset + numBytes;
|
||||||
|
int newBitOffset = bitOffset + numBits - (numBytes * 8);
|
||||||
if (newBitOffset > 7) {
|
if (newBitOffset > 7) {
|
||||||
newByteOffset++;
|
newByteOffset++;
|
||||||
newBitOffset -= 8;
|
newBitOffset -= 8;
|
||||||
|
|
@ -108,7 +121,9 @@ public final class ParsableNalUnitBitArray {
|
||||||
* @return Whether the bit is set.
|
* @return Whether the bit is set.
|
||||||
*/
|
*/
|
||||||
public boolean readBit() {
|
public boolean readBit() {
|
||||||
return readBits(1) == 1;
|
boolean returnValue = (data[byteOffset] & (0x80 >> bitOffset)) != 0;
|
||||||
|
skipBit();
|
||||||
|
return returnValue;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -118,50 +133,19 @@ public final class ParsableNalUnitBitArray {
|
||||||
* @return An integer whose bottom n bits hold the read data.
|
* @return An integer whose bottom n bits hold the read data.
|
||||||
*/
|
*/
|
||||||
public int readBits(int numBits) {
|
public int readBits(int numBits) {
|
||||||
if (numBits == 0) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
|
|
||||||
int returnValue = 0;
|
int returnValue = 0;
|
||||||
|
bitOffset += numBits;
|
||||||
// Read as many whole bytes as we can.
|
while (bitOffset > 8) {
|
||||||
int wholeBytes = (numBits / 8);
|
bitOffset -= 8;
|
||||||
for (int i = 0; i < wholeBytes; i++) {
|
returnValue |= (data[byteOffset] & 0xFF) << bitOffset;
|
||||||
int nextByteOffset = shouldSkipByte(byteOffset + 1) ? byteOffset + 2 : byteOffset + 1;
|
byteOffset += shouldSkipByte(byteOffset + 1) ? 2 : 1;
|
||||||
int byteValue;
|
|
||||||
if (bitOffset != 0) {
|
|
||||||
byteValue = ((data[byteOffset] & 0xFF) << bitOffset)
|
|
||||||
| ((data[nextByteOffset] & 0xFF) >>> (8 - bitOffset));
|
|
||||||
} else {
|
|
||||||
byteValue = data[byteOffset];
|
|
||||||
}
|
|
||||||
numBits -= 8;
|
|
||||||
returnValue |= (byteValue & 0xFF) << numBits;
|
|
||||||
byteOffset = nextByteOffset;
|
|
||||||
}
|
}
|
||||||
|
returnValue |= (data[byteOffset] & 0xFF) >> 8 - bitOffset;
|
||||||
// Read any remaining bits.
|
returnValue &= 0xFFFFFFFF >>> (32 - numBits);
|
||||||
if (numBits > 0) {
|
if (bitOffset == 8) {
|
||||||
int nextBit = bitOffset + numBits;
|
bitOffset = 0;
|
||||||
byte writeMask = (byte) (0xFF >> (8 - numBits));
|
byteOffset += shouldSkipByte(byteOffset + 1) ? 2 : 1;
|
||||||
int nextByteOffset = shouldSkipByte(byteOffset + 1) ? byteOffset + 2 : byteOffset + 1;
|
|
||||||
|
|
||||||
if (nextBit > 8) {
|
|
||||||
// Combine bits from current byte and next byte.
|
|
||||||
returnValue |= ((((data[byteOffset] & 0xFF) << (nextBit - 8)
|
|
||||||
| ((data[nextByteOffset] & 0xFF) >> (16 - nextBit))) & writeMask));
|
|
||||||
byteOffset = nextByteOffset;
|
|
||||||
} else {
|
|
||||||
// Bits to be read only within current byte.
|
|
||||||
returnValue |= (((data[byteOffset] & 0xFF) >> (8 - nextBit)) & writeMask);
|
|
||||||
if (nextBit == 8) {
|
|
||||||
byteOffset = nextByteOffset;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
bitOffset = nextBit % 8;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
assertValidOffset();
|
assertValidOffset();
|
||||||
return returnValue;
|
return returnValue;
|
||||||
}
|
}
|
||||||
|
|
@ -220,7 +204,6 @@ public final class ParsableNalUnitBitArray {
|
||||||
private void assertValidOffset() {
|
private void assertValidOffset() {
|
||||||
// It is fine for position to be at the end of the array, but no further.
|
// It is fine for position to be at the end of the array, but no further.
|
||||||
Assertions.checkState(byteOffset >= 0
|
Assertions.checkState(byteOffset >= 0
|
||||||
&& (bitOffset >= 0 && bitOffset < 8)
|
|
||||||
&& (byteOffset < byteLimit || (byteOffset == byteLimit && bitOffset == 0)));
|
&& (byteOffset < byteLimit || (byteOffset == byteLimit && bitOffset == 0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -578,9 +578,10 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
}
|
}
|
||||||
|
|
||||||
private void maybeNotifyVideoSizeChanged() {
|
private void maybeNotifyVideoSizeChanged() {
|
||||||
if (reportedWidth != currentWidth || reportedHeight != currentHeight
|
if ((currentWidth != Format.NO_VALUE || currentHeight != Format.NO_VALUE)
|
||||||
|
&& (reportedWidth != currentWidth || reportedHeight != currentHeight
|
||||||
|| reportedUnappliedRotationDegrees != currentUnappliedRotationDegrees
|
|| reportedUnappliedRotationDegrees != currentUnappliedRotationDegrees
|
||||||
|| reportedPixelWidthHeightRatio != currentPixelWidthHeightRatio) {
|
|| reportedPixelWidthHeightRatio != currentPixelWidthHeightRatio)) {
|
||||||
eventDispatcher.videoSizeChanged(currentWidth, currentHeight, currentUnappliedRotationDegrees,
|
eventDispatcher.videoSizeChanged(currentWidth, currentHeight, currentUnappliedRotationDegrees,
|
||||||
currentPixelWidthHeightRatio);
|
currentPixelWidthHeightRatio);
|
||||||
reportedWidth = currentWidth;
|
reportedWidth = currentWidth;
|
||||||
|
|
@ -592,8 +593,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||||
|
|
||||||
private void maybeRenotifyVideoSizeChanged() {
|
private void maybeRenotifyVideoSizeChanged() {
|
||||||
if (reportedWidth != Format.NO_VALUE || reportedHeight != Format.NO_VALUE) {
|
if (reportedWidth != Format.NO_VALUE || reportedHeight != Format.NO_VALUE) {
|
||||||
eventDispatcher.videoSizeChanged(currentWidth, currentHeight, currentUnappliedRotationDegrees,
|
eventDispatcher.videoSizeChanged(reportedWidth, reportedHeight,
|
||||||
currentPixelWidthHeightRatio);
|
reportedUnappliedRotationDegrees, reportedPixelWidthHeightRatio);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,38 @@ import java.util.List;
|
||||||
*/
|
*/
|
||||||
public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable<HlsPlaylist>> {
|
public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable<HlsPlaylist>> {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when a playlist is considered to be stuck due to a server side error.
|
||||||
|
*/
|
||||||
|
public static final class PlaylistStuckException extends IOException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The url of the stuck playlist.
|
||||||
|
*/
|
||||||
|
public final String url;
|
||||||
|
|
||||||
|
private PlaylistStuckException(String url) {
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when the media sequence of a new snapshot indicates the server has reset.
|
||||||
|
*/
|
||||||
|
public static final class PlaylistResetException extends IOException {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The url of the reset playlist.
|
||||||
|
*/
|
||||||
|
public final String url;
|
||||||
|
|
||||||
|
private PlaylistResetException(String url) {
|
||||||
|
this.url = url;
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Listener for primary playlist changes.
|
* Listener for primary playlist changes.
|
||||||
*/
|
*/
|
||||||
|
|
@ -75,6 +107,11 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Coefficient applied on the target duration of a playlist to determine the amount of time after
|
||||||
|
* which an unchanging playlist is considered stuck.
|
||||||
|
*/
|
||||||
|
private static final double PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT = 3.5;
|
||||||
/**
|
/**
|
||||||
* The minimum number of milliseconds that a url is kept as primary url, if no
|
* The minimum number of milliseconds that a url is kept as primary url, if no
|
||||||
* {@link #getPlaylistSnapshot} call is made for that url.
|
* {@link #getPlaylistSnapshot} call is made for that url.
|
||||||
|
|
@ -213,14 +250,14 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the playlist is having trouble loading the playlist referenced by the given {@link HlsUrl},
|
* If the playlist is having trouble refreshing the playlist referenced by the given
|
||||||
* this method throws the underlying error.
|
* {@link HlsUrl}, this method throws the underlying error.
|
||||||
*
|
*
|
||||||
* @param url The {@link HlsUrl}.
|
* @param url The {@link HlsUrl}.
|
||||||
* @throws IOException The underyling error.
|
* @throws IOException The underyling error.
|
||||||
*/
|
*/
|
||||||
public void maybeThrowPlaylistRefreshError(HlsUrl url) throws IOException {
|
public void maybeThrowPlaylistRefreshError(HlsUrl url) throws IOException {
|
||||||
playlistBundles.get(url).mediaPlaylistLoader.maybeThrowError();
|
playlistBundles.get(url).maybeThrowPlaylistRefreshError();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
|
@ -441,9 +478,11 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
|
||||||
|
|
||||||
private HlsMediaPlaylist playlistSnapshot;
|
private HlsMediaPlaylist playlistSnapshot;
|
||||||
private long lastSnapshotLoadMs;
|
private long lastSnapshotLoadMs;
|
||||||
|
private long lastSnapshotChangeMs;
|
||||||
private long lastSnapshotAccessTimeMs;
|
private long lastSnapshotAccessTimeMs;
|
||||||
private long blacklistUntilMs;
|
private long blacklistUntilMs;
|
||||||
private boolean pendingRefresh;
|
private boolean pendingRefresh;
|
||||||
|
private IOException playlistError;
|
||||||
|
|
||||||
public MediaPlaylistBundle(HlsUrl playlistUrl, long initialLastSnapshotAccessTimeMs) {
|
public MediaPlaylistBundle(HlsUrl playlistUrl, long initialLastSnapshotAccessTimeMs) {
|
||||||
this.playlistUrl = playlistUrl;
|
this.playlistUrl = playlistUrl;
|
||||||
|
|
@ -483,6 +522,13 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void maybeThrowPlaylistRefreshError() throws IOException {
|
||||||
|
mediaPlaylistLoader.maybeThrowError();
|
||||||
|
if (playlistError != null) {
|
||||||
|
throw playlistError;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Loader.Callback implementation.
|
// Loader.Callback implementation.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -494,8 +540,7 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
|
||||||
eventDispatcher.loadCompleted(loadable.dataSpec, C.DATA_TYPE_MANIFEST, elapsedRealtimeMs,
|
eventDispatcher.loadCompleted(loadable.dataSpec, C.DATA_TYPE_MANIFEST, elapsedRealtimeMs,
|
||||||
loadDurationMs, loadable.bytesLoaded());
|
loadDurationMs, loadable.bytesLoaded());
|
||||||
} else {
|
} else {
|
||||||
onLoadError(loadable, elapsedRealtimeMs, loadDurationMs,
|
playlistError = new ParserException("Loaded playlist has unexpected type.");
|
||||||
new ParserException("Loaded playlist has unexpected type."));
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -517,10 +562,7 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
|
||||||
}
|
}
|
||||||
boolean shouldRetry = true;
|
boolean shouldRetry = true;
|
||||||
if (ChunkedTrackBlacklistUtil.shouldBlacklist(error)) {
|
if (ChunkedTrackBlacklistUtil.shouldBlacklist(error)) {
|
||||||
blacklistUntilMs =
|
blacklistPlaylist();
|
||||||
SystemClock.elapsedRealtime() + ChunkedTrackBlacklistUtil.DEFAULT_TRACK_BLACKLIST_MS;
|
|
||||||
notifyPlaylistBlacklisting(playlistUrl,
|
|
||||||
ChunkedTrackBlacklistUtil.DEFAULT_TRACK_BLACKLIST_MS);
|
|
||||||
shouldRetry = primaryHlsUrl == playlistUrl && !maybeSelectNewPrimaryUrl();
|
shouldRetry = primaryHlsUrl == playlistUrl && !maybeSelectNewPrimaryUrl();
|
||||||
}
|
}
|
||||||
return shouldRetry ? Loader.RETRY : Loader.DONT_RETRY;
|
return shouldRetry ? Loader.RETRY : Loader.DONT_RETRY;
|
||||||
|
|
@ -538,14 +580,28 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
|
||||||
|
|
||||||
private void processLoadedPlaylist(HlsMediaPlaylist loadedPlaylist) {
|
private void processLoadedPlaylist(HlsMediaPlaylist loadedPlaylist) {
|
||||||
HlsMediaPlaylist oldPlaylist = playlistSnapshot;
|
HlsMediaPlaylist oldPlaylist = playlistSnapshot;
|
||||||
lastSnapshotLoadMs = SystemClock.elapsedRealtime();
|
long currentTimeMs = SystemClock.elapsedRealtime();
|
||||||
|
lastSnapshotLoadMs = currentTimeMs;
|
||||||
playlistSnapshot = getLatestPlaylistSnapshot(oldPlaylist, loadedPlaylist);
|
playlistSnapshot = getLatestPlaylistSnapshot(oldPlaylist, loadedPlaylist);
|
||||||
long refreshDelayUs = C.TIME_UNSET;
|
long refreshDelayUs = C.TIME_UNSET;
|
||||||
if (playlistSnapshot != oldPlaylist) {
|
if (playlistSnapshot != oldPlaylist) {
|
||||||
|
playlistError = null;
|
||||||
|
lastSnapshotChangeMs = currentTimeMs;
|
||||||
if (onPlaylistUpdated(playlistUrl, playlistSnapshot)) {
|
if (onPlaylistUpdated(playlistUrl, playlistSnapshot)) {
|
||||||
refreshDelayUs = playlistSnapshot.targetDurationUs;
|
refreshDelayUs = playlistSnapshot.targetDurationUs;
|
||||||
}
|
}
|
||||||
} else if (!playlistSnapshot.hasEndTag) {
|
} else if (!playlistSnapshot.hasEndTag) {
|
||||||
|
if (currentTimeMs - lastSnapshotChangeMs
|
||||||
|
> C.usToMs(playlistSnapshot.targetDurationUs)
|
||||||
|
* PLAYLIST_STUCK_TARGET_DURATION_COEFFICIENT) {
|
||||||
|
// The playlist seems to be stuck, we blacklist it.
|
||||||
|
playlistError = new PlaylistStuckException(playlistUrl.url);
|
||||||
|
blacklistPlaylist();
|
||||||
|
} else if (loadedPlaylist.mediaSequence + loadedPlaylist.segments.size()
|
||||||
|
< playlistSnapshot.mediaSequence) {
|
||||||
|
// The media sequence has jumped backwards. The server has likely reset.
|
||||||
|
playlistError = new PlaylistResetException(playlistUrl.url);
|
||||||
|
}
|
||||||
refreshDelayUs = playlistSnapshot.targetDurationUs / 2;
|
refreshDelayUs = playlistSnapshot.targetDurationUs / 2;
|
||||||
}
|
}
|
||||||
if (refreshDelayUs != C.TIME_UNSET) {
|
if (refreshDelayUs != C.TIME_UNSET) {
|
||||||
|
|
@ -554,6 +610,12 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void blacklistPlaylist() {
|
||||||
|
blacklistUntilMs = SystemClock.elapsedRealtime()
|
||||||
|
+ ChunkedTrackBlacklistUtil.DEFAULT_TRACK_BLACKLIST_MS;
|
||||||
|
notifyPlaylistBlacklisting(playlistUrl, ChunkedTrackBlacklistUtil.DEFAULT_TRACK_BLACKLIST_MS);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -712,7 +712,12 @@ public class PlaybackControlView extends FrameLayout {
|
||||||
if (fastForwardMs <= 0) {
|
if (fastForwardMs <= 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
seekTo(Math.min(player.getCurrentPosition() + fastForwardMs, player.getDuration()));
|
long durationMs = player.getDuration();
|
||||||
|
long seekPositionMs = player.getCurrentPosition() + fastForwardMs;
|
||||||
|
if (durationMs != C.TIME_UNSET) {
|
||||||
|
seekPositionMs = Math.min(seekPositionMs, durationMs);
|
||||||
|
}
|
||||||
|
seekTo(seekPositionMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void seekTo(long positionMs) {
|
private void seekTo(long positionMs) {
|
||||||
|
|
|
||||||
|
|
@ -574,7 +574,8 @@ public final class SimpleExoPlayerView extends FrameLayout {
|
||||||
/**
|
/**
|
||||||
* Sets the rewind increment in milliseconds.
|
* Sets the rewind increment in milliseconds.
|
||||||
*
|
*
|
||||||
* @param rewindMs The rewind increment in milliseconds.
|
* @param rewindMs The rewind increment in milliseconds. A non-positive value will cause the
|
||||||
|
* rewind button to be disabled.
|
||||||
*/
|
*/
|
||||||
public void setRewindIncrementMs(int rewindMs) {
|
public void setRewindIncrementMs(int rewindMs) {
|
||||||
Assertions.checkState(controller != null);
|
Assertions.checkState(controller != null);
|
||||||
|
|
@ -584,7 +585,8 @@ public final class SimpleExoPlayerView extends FrameLayout {
|
||||||
/**
|
/**
|
||||||
* Sets the fast forward increment in milliseconds.
|
* Sets the fast forward increment in milliseconds.
|
||||||
*
|
*
|
||||||
* @param fastForwardMs The fast forward increment in milliseconds.
|
* @param fastForwardMs The fast forward increment in milliseconds. A non-positive value will
|
||||||
|
* cause the fast forward button to be disabled.
|
||||||
*/
|
*/
|
||||||
public void setFastForwardIncrementMs(int fastForwardMs) {
|
public void setFastForwardIncrementMs(int fastForwardMs) {
|
||||||
Assertions.checkState(controller != null);
|
Assertions.checkState(controller != null);
|
||||||
|
|
|
||||||
Loading…
Reference in a new issue