Merge branch 'dev-v2' into release-v2

This commit is contained in:
Oliver Woodman 2017-08-07 14:45:41 +01:00
commit 0425ff6d41
183 changed files with 2827 additions and 659 deletions

View file

@ -11,7 +11,7 @@
// 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.
apply from: '../constants.gradle'
apply from: '../../constants.gradle'
apply plugin: 'com.android.application'
android {

View file

Before

Width:  |  Height:  |  Size: 6.7 KiB

After

Width:  |  Height:  |  Size: 6.7 KiB

View file

Before

Width:  |  Height:  |  Size: 3.4 KiB

After

Width:  |  Height:  |  Size: 3.4 KiB

View file

Before

Width:  |  Height:  |  Size: 2.1 KiB

After

Width:  |  Height:  |  Size: 2.1 KiB

View file

Before

Width:  |  Height:  |  Size: 4.8 KiB

After

Width:  |  Height:  |  Size: 4.8 KiB

View file

Before

Width:  |  Height:  |  Size: 7.6 KiB

After

Width:  |  Height:  |  Size: 7.6 KiB

View file

Before

Width:  |  Height:  |  Size: 11 KiB

After

Width:  |  Height:  |  Size: 11 KiB

View file

@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.ext.cronet;
import static org.junit.Assert.assertArrayEquals;

View file

@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.ext.cronet;
import static org.junit.Assert.assertArrayEquals;

View file

@ -472,13 +472,13 @@ public final class ImaAdsLoader implements Player.EventListener, VideoAdPlayer,
}
if (!imaPlayingAd) {
imaPlayingAd = true;
for (VideoAdPlayerCallback callback : adCallbacks) {
callback.onPlay();
for (int i = 0; i < adCallbacks.size(); i++) {
adCallbacks.get(i).onPlay();
}
} else if (imaPausedInAd) {
imaPausedInAd = false;
for (VideoAdPlayerCallback callback : adCallbacks) {
callback.onResume();
for (int i = 0; i < adCallbacks.size(); i++) {
adCallbacks.get(i).onResume();
}
}
}
@ -509,8 +509,8 @@ public final class ImaAdsLoader implements Player.EventListener, VideoAdPlayer,
return;
}
imaPausedInAd = true;
for (VideoAdPlayerCallback callback : adCallbacks) {
callback.onPause();
for (int i = 0; i < adCallbacks.size(); i++) {
adCallbacks.get(i).onPause();
}
}
@ -555,8 +555,8 @@ public final class ImaAdsLoader implements Player.EventListener, VideoAdPlayer,
} else if (imaPlayingAd && playbackState == Player.STATE_ENDED) {
// IMA is waiting for the ad playback to finish so invoke the callback now.
// Either CONTENT_RESUME_REQUESTED will be passed next, or playAd will be called again.
for (VideoAdPlayerCallback callback : adCallbacks) {
callback.onEnded();
for (int i = 0; i < adCallbacks.size(); i++) {
adCallbacks.get(i).onEnded();
}
}
}
@ -569,8 +569,8 @@ public final class ImaAdsLoader implements Player.EventListener, VideoAdPlayer,
@Override
public void onPlayerError(ExoPlaybackException error) {
if (playingAd) {
for (VideoAdPlayerCallback callback : adCallbacks) {
callback.onError();
for (int i = 0; i < adCallbacks.size(); i++) {
adCallbacks.get(i).onError();
}
}
}
@ -630,8 +630,8 @@ public final class ImaAdsLoader implements Player.EventListener, VideoAdPlayer,
if (adFinished) {
// IMA is waiting for the ad playback to finish so invoke the callback now.
// Either CONTENT_RESUME_REQUESTED will be passed next, or playAd will be called again.
for (VideoAdPlayerCallback callback : adCallbacks) {
callback.onEnded();
for (int i = 0; i < adCallbacks.size(); i++) {
adCallbacks.get(i).onEnded();
}
}
if (!wasPlayingAd && playingAd) {

View file

@ -17,14 +17,14 @@ package com.google.android.exoplayer2.ext.ima;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.ForwardingTimeline;
import com.google.android.exoplayer2.util.Assertions;
/**
* A {@link Timeline} for sources that have ads.
*/
/* package */ final class SinglePeriodAdTimeline extends Timeline {
/* package */ final class SinglePeriodAdTimeline extends ForwardingTimeline {
private final Timeline contentTimeline;
private final long[] adGroupTimesUs;
private final int[] adCounts;
private final int[] adsLoadedCounts;
@ -52,9 +52,9 @@ import com.google.android.exoplayer2.util.Assertions;
public SinglePeriodAdTimeline(Timeline contentTimeline, long[] adGroupTimesUs, int[] adCounts,
int[] adsLoadedCounts, int[] adsPlayedCounts, long[][] adDurationsUs,
long adResumePositionUs) {
super(contentTimeline);
Assertions.checkState(contentTimeline.getPeriodCount() == 1);
Assertions.checkState(contentTimeline.getWindowCount() == 1);
this.contentTimeline = contentTimeline;
this.adGroupTimesUs = adGroupTimesUs;
this.adCounts = adCounts;
this.adsLoadedCounts = adsLoadedCounts;
@ -63,34 +63,13 @@ import com.google.android.exoplayer2.util.Assertions;
this.adResumePositionUs = adResumePositionUs;
}
@Override
public int getWindowCount() {
return 1;
}
@Override
public Window getWindow(int windowIndex, Window window, boolean setIds,
long defaultPositionProjectionUs) {
return contentTimeline.getWindow(windowIndex, window, setIds, defaultPositionProjectionUs);
}
@Override
public int getPeriodCount() {
return 1;
}
@Override
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
contentTimeline.getPeriod(periodIndex, period, setIds);
timeline.getPeriod(periodIndex, period, setIds);
period.set(period.id, period.uid, period.windowIndex, period.durationUs,
period.getPositionInWindowUs(), adGroupTimesUs, adCounts, adsLoadedCounts, adsPlayedCounts,
adDurationsUs, adResumePositionUs);
return period;
}
@Override
public int getIndexOfPeriod(Object uid) {
return contentTimeline.getIndexOfPeriod(uid);
}
}

View file

@ -14,7 +14,6 @@ package com.google.android.exoplayer2.ext.mediasession;
* See the License for the specific language governing permissions and
* limitations under the License.
*/
import android.content.Context;
import android.os.Bundle;
import android.support.v4.media.session.PlaybackStateCompat;

View file

@ -15,20 +15,18 @@
*/
package com.google.android.exoplayer2;
import android.util.Pair;
import com.google.android.exoplayer2.source.MediaSource;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.testutil.ExoPlayerWrapper;
import com.google.android.exoplayer2.testutil.ActionSchedule;
import com.google.android.exoplayer2.testutil.ExoPlayerTestRunner;
import com.google.android.exoplayer2.testutil.ExoPlayerTestRunner.Builder;
import com.google.android.exoplayer2.testutil.FakeMediaClockRenderer;
import com.google.android.exoplayer2.testutil.FakeMediaSource;
import com.google.android.exoplayer2.testutil.FakeRenderer;
import com.google.android.exoplayer2.testutil.FakeTimeline;
import com.google.android.exoplayer2.testutil.FakeTimeline.TimelineWindowDefinition;
import com.google.android.exoplayer2.util.MimeTypes;
import java.util.LinkedList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import junit.framework.TestCase;
/**
@ -43,67 +41,59 @@ public final class ExoPlayerTest extends TestCase {
*/
private static final int TIMEOUT_MS = 10000;
private static final Format TEST_VIDEO_FORMAT = Format.createVideoSampleFormat(null,
MimeTypes.VIDEO_H264, null, Format.NO_VALUE, Format.NO_VALUE, 1280, 720, Format.NO_VALUE,
null, null);
private static final Format TEST_AUDIO_FORMAT = Format.createAudioSampleFormat(null,
MimeTypes.AUDIO_AAC, null, Format.NO_VALUE, Format.NO_VALUE, 2, 44100, null, null, 0, null);
/**
* Tests playback of a source that exposes an empty timeline. Playback is expected to end without
* error.
*/
public void testPlayEmptyTimeline() throws Exception {
ExoPlayerWrapper playerWrapper = new ExoPlayerWrapper();
Timeline timeline = Timeline.EMPTY;
MediaSource mediaSource = new FakeMediaSource(timeline, null);
FakeRenderer renderer = new FakeRenderer();
playerWrapper.setup(mediaSource, renderer);
playerWrapper.blockUntilEnded(TIMEOUT_MS);
assertEquals(0, playerWrapper.positionDiscontinuityCount);
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder()
.setTimeline(timeline).setRenderers(renderer)
.build().start().blockUntilEnded(TIMEOUT_MS);
testRunner.assertPositionDiscontinuityCount(0);
testRunner.assertTimelinesEqual(timeline);
assertEquals(0, renderer.formatReadCount);
assertEquals(0, renderer.bufferReadCount);
assertFalse(renderer.isEnded);
playerWrapper.assertSourceInfosEquals(Pair.create(timeline, null));
}
/**
* Tests playback of a source that exposes a single period.
*/
public void testPlaySinglePeriodTimeline() throws Exception {
ExoPlayerWrapper playerWrapper = new ExoPlayerWrapper();
Timeline timeline = new FakeTimeline(new TimelineWindowDefinition(false, false, 0));
Object manifest = new Object();
MediaSource mediaSource = new FakeMediaSource(timeline, manifest, TEST_VIDEO_FORMAT);
FakeRenderer renderer = new FakeRenderer(TEST_VIDEO_FORMAT);
playerWrapper.setup(mediaSource, renderer);
playerWrapper.blockUntilEnded(TIMEOUT_MS);
assertEquals(0, playerWrapper.positionDiscontinuityCount);
FakeRenderer renderer = new FakeRenderer(Builder.VIDEO_FORMAT);
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder()
.setTimeline(timeline).setManifest(manifest).setRenderers(renderer)
.build().start().blockUntilEnded(TIMEOUT_MS);
testRunner.assertPositionDiscontinuityCount(0);
testRunner.assertTimelinesEqual(timeline);
testRunner.assertManifestsEqual(manifest);
testRunner.assertTrackGroupsEqual(new TrackGroupArray(new TrackGroup(Builder.VIDEO_FORMAT)));
assertEquals(1, renderer.formatReadCount);
assertEquals(1, renderer.bufferReadCount);
assertTrue(renderer.isEnded);
assertEquals(new TrackGroupArray(new TrackGroup(TEST_VIDEO_FORMAT)), playerWrapper.trackGroups);
playerWrapper.assertSourceInfosEquals(Pair.create(timeline, manifest));
}
/**
* Tests playback of a source that exposes three periods.
*/
public void testPlayMultiPeriodTimeline() throws Exception {
ExoPlayerWrapper playerWrapper = new ExoPlayerWrapper();
Timeline timeline = new FakeTimeline(
new TimelineWindowDefinition(false, false, 0),
new TimelineWindowDefinition(false, false, 0),
new TimelineWindowDefinition(false, false, 0));
MediaSource mediaSource = new FakeMediaSource(timeline, null, TEST_VIDEO_FORMAT);
FakeRenderer renderer = new FakeRenderer(TEST_VIDEO_FORMAT);
playerWrapper.setup(mediaSource, renderer);
playerWrapper.blockUntilEnded(TIMEOUT_MS);
assertEquals(2, playerWrapper.positionDiscontinuityCount);
FakeRenderer renderer = new FakeRenderer(Builder.VIDEO_FORMAT);
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder()
.setTimeline(timeline).setRenderers(renderer)
.build().start().blockUntilEnded(TIMEOUT_MS);
testRunner.assertPositionDiscontinuityCount(2);
testRunner.assertTimelinesEqual(timeline);
assertEquals(3, renderer.formatReadCount);
assertEquals(1, renderer.bufferReadCount);
assertTrue(renderer.isEnded);
playerWrapper.assertSourceInfosEquals(Pair.create(timeline, null));
}
/**
@ -111,16 +101,12 @@ public final class ExoPlayerTest extends TestCase {
* source.
*/
public void testReadAheadToEndDoesNotResetRenderer() throws Exception {
final ExoPlayerWrapper playerWrapper = new ExoPlayerWrapper();
Timeline timeline = new FakeTimeline(
new TimelineWindowDefinition(false, false, 10),
new TimelineWindowDefinition(false, false, 10),
new TimelineWindowDefinition(false, false, 10));
MediaSource mediaSource = new FakeMediaSource(timeline, null, TEST_VIDEO_FORMAT,
TEST_AUDIO_FORMAT);
FakeRenderer videoRenderer = new FakeRenderer(TEST_VIDEO_FORMAT);
FakeMediaClockRenderer audioRenderer = new FakeMediaClockRenderer(TEST_AUDIO_FORMAT) {
final FakeRenderer videoRenderer = new FakeRenderer(Builder.VIDEO_FORMAT);
FakeMediaClockRenderer audioRenderer = new FakeMediaClockRenderer(Builder.AUDIO_FORMAT) {
@Override
public long getPositionUs() {
@ -143,35 +129,30 @@ public final class ExoPlayerTest extends TestCase {
@Override
public boolean isEnded() {
// Allow playback to end once the final period is playing.
return playerWrapper.positionDiscontinuityCount == 2;
return videoRenderer.isEnded();
}
};
playerWrapper.setup(mediaSource, videoRenderer, audioRenderer);
playerWrapper.blockUntilEnded(TIMEOUT_MS);
assertEquals(2, playerWrapper.positionDiscontinuityCount);
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder()
.setTimeline(timeline).setRenderers(videoRenderer, audioRenderer)
.setSupportedFormats(Builder.VIDEO_FORMAT, Builder.AUDIO_FORMAT)
.build().start().blockUntilEnded(TIMEOUT_MS);
testRunner.assertPositionDiscontinuityCount(2);
testRunner.assertTimelinesEqual(timeline);
assertEquals(1, audioRenderer.positionResetCount);
assertTrue(videoRenderer.isEnded);
assertTrue(audioRenderer.isEnded);
playerWrapper.assertSourceInfosEquals(Pair.create(timeline, null));
}
public void testRepreparationGivesFreshSourceInfo() throws Exception {
ExoPlayerWrapper playerWrapper = new ExoPlayerWrapper();
Timeline timeline = new FakeTimeline(new TimelineWindowDefinition(false, false, 0));
FakeRenderer renderer = new FakeRenderer(TEST_VIDEO_FORMAT);
// Prepare the player with a source with the first manifest and a non-empty timeline
FakeRenderer renderer = new FakeRenderer(Builder.VIDEO_FORMAT);
Object firstSourceManifest = new Object();
playerWrapper.setup(new FakeMediaSource(timeline, firstSourceManifest, TEST_VIDEO_FORMAT),
renderer);
playerWrapper.blockUntilSourceInfoRefreshed(TIMEOUT_MS);
// Prepare the player again with a source and a new manifest, which will never be exposed.
MediaSource firstSource = new FakeMediaSource(timeline, firstSourceManifest,
Builder.VIDEO_FORMAT);
final CountDownLatch queuedSourceInfoCountDownLatch = new CountDownLatch(1);
final CountDownLatch completePreparationCountDownLatch = new CountDownLatch(1);
playerWrapper.prepare(new FakeMediaSource(timeline, new Object(), TEST_VIDEO_FORMAT) {
MediaSource secondSource = new FakeMediaSource(timeline, new Object(), Builder.VIDEO_FORMAT) {
@Override
public void prepareSource(ExoPlayer player, boolean isTopLevelSource, Listener listener) {
super.prepareSource(player, isTopLevelSource, listener);
@ -185,29 +166,49 @@ public final class ExoPlayerTest extends TestCase {
throw new IllegalStateException(e);
}
}
});
// Prepare the player again with a third source.
queuedSourceInfoCountDownLatch.await();
};
Object thirdSourceManifest = new Object();
playerWrapper.prepare(new FakeMediaSource(timeline, thirdSourceManifest, TEST_VIDEO_FORMAT));
completePreparationCountDownLatch.countDown();
// Wait for playback to complete.
playerWrapper.blockUntilEnded(TIMEOUT_MS);
assertEquals(0, playerWrapper.positionDiscontinuityCount);
assertEquals(1, renderer.formatReadCount);
assertEquals(1, renderer.bufferReadCount);
assertTrue(renderer.isEnded);
assertEquals(new TrackGroupArray(new TrackGroup(TEST_VIDEO_FORMAT)), playerWrapper.trackGroups);
MediaSource thirdSource = new FakeMediaSource(timeline, thirdSourceManifest,
Builder.VIDEO_FORMAT);
// Prepare the player with a source with the first manifest and a non-empty timeline. Prepare
// the player again with a source and a new manifest, which will never be exposed. Allow the
// test thread to prepare the player with a third source, and block the playback thread until
// the test thread's call to prepare() has returned.
ActionSchedule actionSchedule = new ActionSchedule.Builder("testRepreparation")
.waitForTimelineChanged(timeline)
.prepareSource(secondSource)
.executeRunnable(new Runnable() {
@Override
public void run() {
try {
queuedSourceInfoCountDownLatch.await();
} catch (InterruptedException e) {
// Ignore.
}
}
})
.prepareSource(thirdSource)
.executeRunnable(new Runnable() {
@Override
public void run() {
completePreparationCountDownLatch.countDown();
}
})
.build();
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder()
.setMediaSource(firstSource).setRenderers(renderer).setActionSchedule(actionSchedule)
.build().start().blockUntilEnded(TIMEOUT_MS);
testRunner.assertPositionDiscontinuityCount(0);
// The first source's preparation completed with a non-empty timeline. When the player was
// re-prepared with the second source, it immediately exposed an empty timeline, but the source
// info refresh from the second source was suppressed as we re-prepared with the third source.
playerWrapper.assertSourceInfosEquals(
Pair.create(timeline, firstSourceManifest),
Pair.create(Timeline.EMPTY, null),
Pair.create(timeline, thirdSourceManifest));
testRunner.assertTimelinesEqual(timeline, Timeline.EMPTY, timeline);
testRunner.assertManifestsEqual(firstSourceManifest, null, thirdSourceManifest);
testRunner.assertTrackGroupsEqual(new TrackGroupArray(new TrackGroup(Builder.VIDEO_FORMAT)));
assertEquals(1, renderer.formatReadCount);
assertEquals(1, renderer.bufferReadCount);
assertTrue(renderer.isEnded);
}
public void testRepeatModeChanges() throws Exception {
@ -215,49 +216,22 @@ public final class ExoPlayerTest extends TestCase {
new TimelineWindowDefinition(true, false, 100000),
new TimelineWindowDefinition(true, false, 100000),
new TimelineWindowDefinition(true, false, 100000));
final int[] actionSchedule = { // 0 -> 1
Player.REPEAT_MODE_ONE, // 1 -> 1
Player.REPEAT_MODE_OFF, // 1 -> 2
Player.REPEAT_MODE_ONE, // 2 -> 2
Player.REPEAT_MODE_ALL, // 2 -> 0
Player.REPEAT_MODE_ONE, // 0 -> 0
-1, // 0 -> 0
Player.REPEAT_MODE_OFF, // 0 -> 1
-1, // 1 -> 2
-1 // 2 -> ended
};
int[] expectedWindowIndices = {1, 1, 2, 2, 0, 0, 0, 1, 2};
final LinkedList<Integer> windowIndices = new LinkedList<>();
final CountDownLatch actionCounter = new CountDownLatch(actionSchedule.length);
ExoPlayerWrapper playerWrapper = new ExoPlayerWrapper() {
@Override
@SuppressWarnings("ResourceType")
public void onPositionDiscontinuity() {
super.onPositionDiscontinuity();
int actionIndex = actionSchedule.length - (int) actionCounter.getCount();
if (actionSchedule[actionIndex] != -1) {
player.setRepeatMode(actionSchedule[actionIndex]);
}
windowIndices.add(player.getCurrentWindowIndex());
actionCounter.countDown();
}
};
MediaSource mediaSource = new FakeMediaSource(timeline, null, TEST_VIDEO_FORMAT);
FakeRenderer renderer = new FakeRenderer(TEST_VIDEO_FORMAT);
playerWrapper.setup(mediaSource, renderer);
boolean finished = actionCounter.await(TIMEOUT_MS, TimeUnit.MILLISECONDS);
playerWrapper.release();
assertTrue("Test playback timed out waiting for action schedule to end.", finished);
if (playerWrapper.exception != null) {
throw playerWrapper.exception;
}
assertEquals(expectedWindowIndices.length, windowIndices.size());
for (int i = 0; i < expectedWindowIndices.length; i++) {
assertEquals(expectedWindowIndices[i], windowIndices.get(i).intValue());
}
assertEquals(9, playerWrapper.positionDiscontinuityCount);
FakeRenderer renderer = new FakeRenderer(Builder.VIDEO_FORMAT);
ActionSchedule actionSchedule = new ActionSchedule.Builder("testRepeatMode") // 0 -> 1
.waitForPositionDiscontinuity().setRepeatMode(Player.REPEAT_MODE_ONE) // 1 -> 1
.waitForPositionDiscontinuity().setRepeatMode(Player.REPEAT_MODE_OFF) // 1 -> 2
.waitForPositionDiscontinuity().setRepeatMode(Player.REPEAT_MODE_ONE) // 2 -> 2
.waitForPositionDiscontinuity().setRepeatMode(Player.REPEAT_MODE_ALL) // 2 -> 0
.waitForPositionDiscontinuity().setRepeatMode(Player.REPEAT_MODE_ONE) // 0 -> 0
.waitForPositionDiscontinuity() // 0 -> 0
.waitForPositionDiscontinuity().setRepeatMode(Player.REPEAT_MODE_OFF) // 0 -> end
.build();
ExoPlayerTestRunner testRunner = new ExoPlayerTestRunner.Builder()
.setTimeline(timeline).setRenderers(renderer).setActionSchedule(actionSchedule)
.build().start().blockUntilEnded(TIMEOUT_MS);
testRunner.assertPlayedPeriodIndices(0, 1, 1, 2, 2, 0, 0, 0, 1, 2);
testRunner.assertTimelinesEqual(timeline);
assertTrue(renderer.isEnded);
playerWrapper.assertSourceInfosEquals(Pair.create(timeline, null));
}
}

View file

@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.drm;
import static org.mockito.Matchers.any;

View file

@ -34,7 +34,7 @@ public class DefaultOggSeekerUtilMethodsTest extends TestCase {
FakeExtractorInput extractorInput = TestData.createInput(
TestUtil.joinByteArrays(
TestUtil.buildTestData(4000, random),
new byte[]{'O', 'g', 'g', 'S'},
new byte[] {'O', 'g', 'g', 'S'},
TestUtil.buildTestData(4000, random)
), false);
skipToNextPage(extractorInput);
@ -45,7 +45,7 @@ public class DefaultOggSeekerUtilMethodsTest extends TestCase {
FakeExtractorInput extractorInput = TestData.createInput(
TestUtil.joinByteArrays(
TestUtil.buildTestData(2046, random),
new byte[]{'O', 'g', 'g', 'S'},
new byte[] {'O', 'g', 'g', 'S'},
TestUtil.buildTestData(4000, random)
), false);
skipToNextPage(extractorInput);
@ -55,7 +55,7 @@ public class DefaultOggSeekerUtilMethodsTest extends TestCase {
public void testSkipToNextPageInputShorterThanPeekLength() throws Exception {
FakeExtractorInput extractorInput = TestData.createInput(
TestUtil.joinByteArrays(
new byte[]{'x', 'O', 'g', 'g', 'S'}
new byte[] {'x', 'O', 'g', 'g', 'S'}
), false);
skipToNextPage(extractorInput);
assertEquals(1, extractorInput.getPosition());
@ -63,7 +63,7 @@ public class DefaultOggSeekerUtilMethodsTest extends TestCase {
public void testSkipToNextPageNoMatch() throws Exception {
FakeExtractorInput extractorInput = TestData.createInput(
new byte[]{'g', 'g', 'S', 'O', 'g', 'g'}, false);
new byte[] {'g', 'g', 'S', 'O', 'g', 'g'}, false);
try {
skipToNextPage(extractorInput);
fail();

View file

@ -16,6 +16,7 @@
package com.google.android.exoplayer2.extractor.ts;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.testutil.FakeExtractorOutput;
import com.google.android.exoplayer2.testutil.FakeTrackOutput;
@ -154,20 +155,20 @@ public class AdtsReaderTest extends TestCase {
}
}
public void testAdtsDataOnly() throws Exception {
public void testAdtsDataOnly() throws ParserException {
data.setPosition(ID3_DATA_1.length + ID3_DATA_2.length);
feed();
assertSampleCounts(0, 1);
adtsOutput.assertSample(0, ADTS_CONTENT, 0, C.BUFFER_FLAG_KEY_FRAME, null);
}
private void feedLimited(int limit) {
private void feedLimited(int limit) throws ParserException {
maybeStartPacket();
data.setLimit(limit);
feed();
}
private void feed() {
private void feed() throws ParserException {
maybeStartPacket();
adtsReader.consume(data);
}

View file

@ -258,45 +258,45 @@ public class SampleQueueTest extends TestCase {
public void testAdvanceToBeforeBuffer() {
writeTestData();
boolean result = sampleQueue.advanceTo(TEST_SAMPLE_TIMESTAMPS[0] - 1, true, false);
int skipCount = sampleQueue.advanceTo(TEST_SAMPLE_TIMESTAMPS[0] - 1, true, false);
// Should fail and have no effect.
assertFalse(result);
assertEquals(SampleQueue.ADVANCE_FAILED, skipCount);
assertReadTestData();
assertNoSamplesToRead(TEST_FORMAT_2);
}
public void testAdvanceToStartOfBuffer() {
writeTestData();
boolean result = sampleQueue.advanceTo(TEST_SAMPLE_TIMESTAMPS[0], true, false);
int skipCount = sampleQueue.advanceTo(TEST_SAMPLE_TIMESTAMPS[0], true, false);
// Should succeed but have no effect (we're already at the first frame).
assertTrue(result);
assertEquals(0, skipCount);
assertReadTestData();
assertNoSamplesToRead(TEST_FORMAT_2);
}
public void testAdvanceToEndOfBuffer() {
writeTestData();
boolean result = sampleQueue.advanceTo(LAST_SAMPLE_TIMESTAMP, true, false);
// Should succeed and skip to 2nd keyframe.
assertTrue(result);
int skipCount = sampleQueue.advanceTo(LAST_SAMPLE_TIMESTAMP, true, false);
// Should succeed and skip to 2nd keyframe (the 4th frame).
assertEquals(4, skipCount);
assertReadTestData(null, TEST_DATA_SECOND_KEYFRAME_INDEX);
assertNoSamplesToRead(TEST_FORMAT_2);
}
public void testAdvanceToAfterBuffer() {
writeTestData();
boolean result = sampleQueue.advanceTo(LAST_SAMPLE_TIMESTAMP + 1, true, false);
int skipCount = sampleQueue.advanceTo(LAST_SAMPLE_TIMESTAMP + 1, true, false);
// Should fail and have no effect.
assertFalse(result);
assertEquals(SampleQueue.ADVANCE_FAILED, skipCount);
assertReadTestData();
assertNoSamplesToRead(TEST_FORMAT_2);
}
public void testAdvanceToAfterBufferAllowed() {
writeTestData();
boolean result = sampleQueue.advanceTo(LAST_SAMPLE_TIMESTAMP + 1, true, true);
// Should succeed and skip to 2nd keyframe.
assertTrue(result);
int skipCount = sampleQueue.advanceTo(LAST_SAMPLE_TIMESTAMP + 1, true, true);
// Should succeed and skip to 2nd keyframe (the 4th frame).
assertEquals(4, skipCount);
assertReadTestData(null, TEST_DATA_SECOND_KEYFRAME_INDEX);
assertNoSamplesToRead(TEST_FORMAT_2);
}

View file

@ -0,0 +1,134 @@
/*
* 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.source;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.source.ShuffleOrder.DefaultShuffleOrder;
import com.google.android.exoplayer2.source.ShuffleOrder.UnshuffledShuffleOrder;
import junit.framework.TestCase;
/**
* Unit test for {@link ShuffleOrder}.
*/
public final class ShuffleOrderTest extends TestCase {
public static final long RANDOM_SEED = 1234567890L;
public void testDefaultShuffleOrder() {
assertShuffleOrderCorrectness(new DefaultShuffleOrder(0, RANDOM_SEED), 0);
assertShuffleOrderCorrectness(new DefaultShuffleOrder(1, RANDOM_SEED), 1);
assertShuffleOrderCorrectness(new DefaultShuffleOrder(5, RANDOM_SEED), 5);
for (int initialLength = 0; initialLength < 4; initialLength++) {
for (int insertionPoint = 0; insertionPoint <= initialLength; insertionPoint += 2) {
testCloneAndInsert(new DefaultShuffleOrder(initialLength, RANDOM_SEED), insertionPoint, 0);
testCloneAndInsert(new DefaultShuffleOrder(initialLength, RANDOM_SEED), insertionPoint, 1);
testCloneAndInsert(new DefaultShuffleOrder(initialLength, RANDOM_SEED), insertionPoint, 5);
}
}
testCloneAndRemove(new DefaultShuffleOrder(5, RANDOM_SEED), 0);
testCloneAndRemove(new DefaultShuffleOrder(5, RANDOM_SEED), 2);
testCloneAndRemove(new DefaultShuffleOrder(5, RANDOM_SEED), 4);
testCloneAndRemove(new DefaultShuffleOrder(1, RANDOM_SEED), 0);
}
public void testUnshuffledShuffleOrder() {
assertShuffleOrderCorrectness(new UnshuffledShuffleOrder(0), 0);
assertShuffleOrderCorrectness(new UnshuffledShuffleOrder(1), 1);
assertShuffleOrderCorrectness(new UnshuffledShuffleOrder(5), 5);
for (int initialLength = 0; initialLength < 4; initialLength++) {
for (int insertionPoint = 0; insertionPoint <= initialLength; insertionPoint += 2) {
testCloneAndInsert(new UnshuffledShuffleOrder(initialLength), insertionPoint, 0);
testCloneAndInsert(new UnshuffledShuffleOrder(initialLength), insertionPoint, 1);
testCloneAndInsert(new UnshuffledShuffleOrder(initialLength), insertionPoint, 5);
}
}
testCloneAndRemove(new UnshuffledShuffleOrder(5), 0);
testCloneAndRemove(new UnshuffledShuffleOrder(5), 2);
testCloneAndRemove(new UnshuffledShuffleOrder(5), 4);
testCloneAndRemove(new UnshuffledShuffleOrder(1), 0);
}
public void testUnshuffledShuffleOrderIsUnshuffled() {
ShuffleOrder shuffleOrder = new UnshuffledShuffleOrder(5);
assertEquals(0, shuffleOrder.getFirstIndex());
assertEquals(4, shuffleOrder.getLastIndex());
for (int i = 0; i < 4; i++) {
assertEquals(i + 1, shuffleOrder.getNextIndex(i));
}
}
private static void assertShuffleOrderCorrectness(ShuffleOrder shuffleOrder, int length) {
assertEquals(length, shuffleOrder.getLength());
if (length == 0) {
assertEquals(C.INDEX_UNSET, shuffleOrder.getFirstIndex());
assertEquals(C.INDEX_UNSET, shuffleOrder.getLastIndex());
} else {
int[] indices = new int[length];
indices[0] = shuffleOrder.getFirstIndex();
assertEquals(C.INDEX_UNSET, shuffleOrder.getPreviousIndex(indices[0]));
for (int i = 1; i < length; i++) {
indices[i] = shuffleOrder.getNextIndex(indices[i - 1]);
assertEquals(indices[i - 1], shuffleOrder.getPreviousIndex(indices[i]));
for (int j = 0; j < i; j++) {
assertTrue(indices[i] != indices[j]);
}
}
assertEquals(indices[length - 1], shuffleOrder.getLastIndex());
assertEquals(C.INDEX_UNSET, shuffleOrder.getNextIndex(indices[length - 1]));
for (int i = 0; i < length; i++) {
assertTrue(indices[i] >= 0 && indices[i] < length);
}
}
}
private static void testCloneAndInsert(ShuffleOrder shuffleOrder, int position, int count) {
ShuffleOrder newOrder = shuffleOrder.cloneAndInsert(position, count);
assertShuffleOrderCorrectness(newOrder, shuffleOrder.getLength() + count);
// Assert all elements still have the relative same order
for (int i = 0; i < shuffleOrder.getLength(); i++) {
int expectedNextIndex = shuffleOrder.getNextIndex(i);
if (expectedNextIndex != C.INDEX_UNSET && expectedNextIndex >= position) {
expectedNextIndex += count;
}
int newNextIndex = newOrder.getNextIndex(i < position ? i : i + count);
while (newNextIndex >= position && newNextIndex < position + count) {
newNextIndex = newOrder.getNextIndex(newNextIndex);
}
assertEquals(expectedNextIndex, newNextIndex);
}
}
private static void testCloneAndRemove(ShuffleOrder shuffleOrder, int position) {
ShuffleOrder newOrder = shuffleOrder.cloneAndRemove(position);
assertShuffleOrderCorrectness(newOrder, shuffleOrder.getLength() - 1);
// Assert all elements still have the relative same order
for (int i = 0; i < shuffleOrder.getLength(); i++) {
if (i == position) {
continue;
}
int expectedNextIndex = shuffleOrder.getNextIndex(i);
if (expectedNextIndex == position) {
expectedNextIndex = shuffleOrder.getNextIndex(expectedNextIndex);
}
if (expectedNextIndex != C.INDEX_UNSET && expectedNextIndex >= position) {
expectedNextIndex--;
}
int newNextIndex = newOrder.getNextIndex(i < position ? i : i - 1);
assertEquals(expectedNextIndex, newNextIndex);
}
}
}

View file

@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.util;
import android.test.InstrumentationTestCase;

View file

@ -16,7 +16,6 @@
package com.google.android.exoplayer2.util;
import android.test.MoreAsserts;
import junit.framework.TestCase;
/**
@ -27,8 +26,14 @@ 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};
private ParsableBitArray testArray;
@Override
public void setUp() {
testArray = new ParsableBitArray(TEST_DATA);
}
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);
@ -37,13 +42,12 @@ public final class ParsableBitArrayTest extends TestCase {
}
public void testReadBit() {
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
assertReadBitsToEnd(0, testArray);
assertReadBitsToEnd(0);
}
public void testReadBits() {
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
assertEquals(getTestDataBits(0, 5), testArray.readBits(5));
assertEquals(getTestDataBits(5, 0), testArray.readBits(0));
assertEquals(getTestDataBits(5, 3), testArray.readBits(3));
assertEquals(getTestDataBits(8, 16), testArray.readBits(16));
assertEquals(getTestDataBits(24, 3), testArray.readBits(3));
@ -52,67 +56,101 @@ public final class ParsableBitArrayTest extends TestCase {
assertEquals(getTestDataBits(50, 14), testArray.readBits(14));
}
public void testReadBitsToByteArray() {
byte[] result = new byte[TEST_DATA.length];
// Test read within byte boundaries.
testArray.readBits(result, 0, 6);
assertEquals(TEST_DATA[0] & 0xFC, result[0]);
// Test read across byte boundaries.
testArray.readBits(result, 0, 8);
assertEquals(((TEST_DATA[0] & 0x03) << 6) | ((TEST_DATA[1] & 0xFC) >> 2), result[0]);
// Test reading across multiple bytes.
testArray.readBits(result, 1, 50);
for (int i = 1; i < 7; i++) {
assertEquals((byte) (((TEST_DATA[i] & 0x03) << 6) | ((TEST_DATA[i + 1] & 0xFC) >> 2)),
result[i]);
}
assertEquals((byte) (TEST_DATA[7] & 0x03) << 6, result[7]);
assertEquals(0, testArray.bitsLeft());
// Test read last buffer byte across input data bytes.
testArray.setPosition(31);
result[3] = 0;
testArray.readBits(result, 3, 3);
assertEquals((byte) 0xE0, result[3]);
// Test read bits in the middle of a input data byte.
result[0] = 0;
assertEquals(34, testArray.getPosition());
testArray.readBits(result, 0, 3);
assertEquals((byte) 0xE0, result[0]);
// Test read 0 bits.
testArray.setPosition(32);
result[1] = 0;
testArray.readBits(result, 1, 0);
assertEquals(0, result[1]);
// Test reading a number of bits divisible by 8.
testArray.setPosition(0);
testArray.readBits(result, 0, 16);
assertEquals(TEST_DATA[0], result[0]);
assertEquals(TEST_DATA[1], result[1]);
// Test least significant bits are unmodified.
result[1] = (byte) 0xFF;
testArray.readBits(result, 0, 9);
assertEquals(0x5F, result[0]);
assertEquals(0x7F, result[1]);
}
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);
assertReadBitsToEnd(16);
}
public void testSkipBitsByteAligned() {
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
testArray.skipBits(16);
assertReadBitsToEnd(16, testArray);
assertReadBitsToEnd(16);
}
public void testSkipBitsNonByteAligned() {
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
testArray.skipBits(5);
assertReadBitsToEnd(5, testArray);
assertReadBitsToEnd(5);
}
public void testSetPositionByteAligned() {
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
testArray.setPosition(16);
assertReadBitsToEnd(16, testArray);
assertReadBitsToEnd(16);
}
public void testSetPositionNonByteAligned() {
ParsableBitArray testArray = new ParsableBitArray(TEST_DATA);
testArray.setPosition(5);
assertReadBitsToEnd(5, testArray);
assertReadBitsToEnd(5);
}
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);
assertReadBitsToEnd(16);
}
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);
assertReadBitsToEnd(16);
}
private static void assertReadBitsToEnd(int expectedStartPosition, ParsableBitArray testArray) {
private void assertReadBitsToEnd(int expectedStartPosition) {
int position = testArray.getPosition();
assertEquals(expectedStartPosition, position);
for (int i = position; i < TEST_DATA.length * 8; i++) {

View file

@ -279,7 +279,7 @@ public class ParsableByteArrayTest extends TestCase {
}
public void testReadLittleEndianLong() {
ParsableByteArray byteArray = new ParsableByteArray(new byte[]{
ParsableByteArray byteArray = new ParsableByteArray(new byte[] {
0x01, 0x00, 0x00, 0x00,
0x00, 0x00, 0x00, (byte) 0xFF
});
@ -296,7 +296,7 @@ public class ParsableByteArrayTest extends TestCase {
}
public void testReadLittleEndianInt() {
ParsableByteArray byteArray = new ParsableByteArray(new byte[]{
ParsableByteArray byteArray = new ParsableByteArray(new byte[] {
0x01, 0x00, 0x00, (byte) 0xFF
});
assertEquals(0xFF000001, byteArray.readLittleEndianInt());
@ -311,7 +311,7 @@ public class ParsableByteArrayTest extends TestCase {
}
public void testReadLittleEndianUnsignedShort() {
ParsableByteArray byteArray = new ParsableByteArray(new byte[]{
ParsableByteArray byteArray = new ParsableByteArray(new byte[] {
0x01, (byte) 0xFF, 0x02, (byte) 0xFF
});
assertEquals(0xFF01, byteArray.readLittleEndianUnsignedShort());
@ -321,7 +321,7 @@ public class ParsableByteArrayTest extends TestCase {
}
public void testReadLittleEndianShort() {
ParsableByteArray byteArray = new ParsableByteArray(new byte[]{
ParsableByteArray byteArray = new ParsableByteArray(new byte[] {
0x01, (byte) 0xFF, 0x02, (byte) 0xFF
});
assertEquals((short) 0xFF01, byteArray.readLittleEndianShort());

View file

@ -296,9 +296,10 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
* {@code positionUs} is beyond it.
*
* @param positionUs The position in microseconds.
* @return The number of samples that were skipped.
*/
protected void skipSource(long positionUs) {
stream.skipData(positionUs - streamOffsetUs);
protected int skipSource(long positionUs) {
return stream.skipData(positionUs - streamOffsetUs);
}
/**

View file

@ -141,7 +141,7 @@ public class SimpleExoPlayer implements ExoPlayer {
videoScalingMode = C.VIDEO_SCALING_MODE_DEFAULT;
// Build the player and associated objects.
player = new ExoPlayerImpl(renderers, trackSelector, loadControl);
player = createExoPlayerImpl(renderers, trackSelector, loadControl);
}
/**
@ -723,6 +723,19 @@ public class SimpleExoPlayer implements ExoPlayer {
// Internal methods.
/**
* Creates the ExoPlayer implementation used by this {@link SimpleExoPlayer}.
*
* @param renderers The {@link Renderer}s that will be used by the instance.
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
* @param loadControl The {@link LoadControl} that will be used by the instance.
* @return A new {@link ExoPlayer} instance.
*/
protected ExoPlayer createExoPlayerImpl(Renderer[] renderers, TrackSelector trackSelector,
LoadControl loadControl) {
return new ExoPlayerImpl(renderers, trackSelector, loadControl);
}
private void removeSurfaceCallbacks() {
if (textureView != null) {
if (textureView.getSurfaceTextureListener() != componentListener) {

View file

@ -36,6 +36,12 @@ public final class DecoderCounters {
* The number of queued input buffers.
*/
public int inputBufferCount;
/**
* The number of skipped input buffers.
* <p>
* A skipped input buffer is an input buffer that was deliberately not sent to the decoder.
*/
public int skippedInputBufferCount;
/**
* The number of rendered output buffers.
*/
@ -79,6 +85,7 @@ public final class DecoderCounters {
decoderInitCount += other.decoderInitCount;
decoderReleaseCount += other.decoderReleaseCount;
inputBufferCount += other.inputBufferCount;
skippedInputBufferCount += other.skippedInputBufferCount;
renderedOutputBufferCount += other.renderedOutputBufferCount;
skippedOutputBufferCount += other.skippedOutputBufferCount;
droppedOutputBufferCount += other.droppedOutputBufferCount;

View file

@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.drm;
import android.media.MediaDrm;

View file

@ -18,6 +18,7 @@ package com.google.android.exoplayer2.extractor.flv;
import android.util.Pair;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.util.CodecSpecificDataUtil;
import com.google.android.exoplayer2.util.MimeTypes;
@ -85,7 +86,7 @@ import java.util.Collections;
}
@Override
protected void parsePayload(ParsableByteArray data, long timeUs) {
protected void parsePayload(ParsableByteArray data, long timeUs) throws ParserException {
if (audioFormat == AUDIO_FORMAT_MP3) {
int sampleSize = data.bytesLeft();
output.sampleData(data, sampleSize);

View file

@ -816,7 +816,7 @@ import java.util.List;
private static void parseAudioSampleEntry(ParsableByteArray parent, int atomType, int position,
int size, int trackId, String language, boolean isQuickTime, DrmInitData drmInitData,
StsdData out, int entryIndex) {
StsdData out, int entryIndex) throws ParserException {
parent.setPosition(position + Atom.HEADER_SIZE + StsdData.STSD_HEADER_SIZE);
int quickTimeSoundDescriptionVersion = 0;

View file

@ -19,6 +19,7 @@ import android.util.Log;
import android.util.Pair;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.DummyTrackOutput;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput;
@ -128,7 +129,7 @@ public final class AdtsReader implements ElementaryStreamReader {
}
@Override
public void consume(ParsableByteArray data) {
public void consume(ParsableByteArray data) throws ParserException {
while (data.bytesLeft() > 0) {
switch (state) {
case STATE_FINDING_SAMPLE:
@ -276,7 +277,7 @@ public final class AdtsReader implements ElementaryStreamReader {
/**
* Parses the sample header.
*/
private void parseAdtsHeader() {
private void parseAdtsHeader() throws ParserException {
adtsScratch.setPosition(0);
if (!hasOutputFormat) {

View file

@ -94,9 +94,12 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
case TsExtractor.TS_STREAM_TYPE_MPA:
case TsExtractor.TS_STREAM_TYPE_MPA_LSF:
return new PesReader(new MpegAudioReader(esInfo.language));
case TsExtractor.TS_STREAM_TYPE_AAC:
case TsExtractor.TS_STREAM_TYPE_AAC_ADTS:
return isSet(FLAG_IGNORE_AAC_STREAM)
? null : new PesReader(new AdtsReader(false, esInfo.language));
case TsExtractor.TS_STREAM_TYPE_AAC_LATM:
return isSet(FLAG_IGNORE_AAC_STREAM)
? null : new PesReader(new LatmReader(esInfo.language));
case TsExtractor.TS_STREAM_TYPE_AC3:
case TsExtractor.TS_STREAM_TYPE_E_AC3:
return new PesReader(new Ac3Reader(esInfo.language));

View file

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.extractor.ts;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.util.ParsableByteArray;
@ -50,8 +51,9 @@ public interface ElementaryStreamReader {
* Consumes (possibly partial) data from the current packet.
*
* @param data The data to consume.
* @throws ParserException If the data could not be parsed.
*/
void consume(ParsableByteArray data);
void consume(ParsableByteArray data) throws ParserException;
/**
* Called when a packet ends.

View file

@ -0,0 +1,307 @@
/*
* 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.extractor.ts;
import android.support.annotation.Nullable;
import android.util.Pair;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader.TrackIdGenerator;
import com.google.android.exoplayer2.util.CodecSpecificDataUtil;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray;
import java.util.Collections;
/**
* Parses and extracts samples from an AAC/LATM elementary stream.
*/
public final class LatmReader implements ElementaryStreamReader {
private static final int STATE_FINDING_SYNC_1 = 0;
private static final int STATE_FINDING_SYNC_2 = 1;
private static final int STATE_READING_HEADER = 2;
private static final int STATE_READING_SAMPLE = 3;
private static final int INITIAL_BUFFER_SIZE = 1024;
private static final int SYNC_BYTE_FIRST = 0x56;
private static final int SYNC_BYTE_SECOND = 0xE0;
private final String language;
private final ParsableByteArray sampleDataBuffer;
private final ParsableBitArray sampleBitArray;
// Track output info.
private TrackOutput output;
private Format format;
private String formatId;
// Parser state info.
private int state;
private int bytesRead;
private int sampleSize;
private int secondHeaderByte;
private long timeUs;
// Container data.
private boolean streamMuxRead;
private int audioMuxVersion;
private int audioMuxVersionA;
private int numSubframes;
private int frameLengthType;
private boolean otherDataPresent;
private long otherDataLenBits;
private int sampleRateHz;
private long sampleDurationUs;
private int channelCount;
/**
* @param language Track language.
*/
public LatmReader(@Nullable String language) {
this.language = language;
sampleDataBuffer = new ParsableByteArray(INITIAL_BUFFER_SIZE);
sampleBitArray = new ParsableBitArray(sampleDataBuffer.data);
}
@Override
public void seek() {
state = STATE_FINDING_SYNC_1;
streamMuxRead = false;
}
@Override
public void createTracks(ExtractorOutput extractorOutput, TrackIdGenerator idGenerator) {
idGenerator.generateNewId();
output = extractorOutput.track(idGenerator.getTrackId(), C.TRACK_TYPE_AUDIO);
formatId = idGenerator.getFormatId();
}
@Override
public void packetStarted(long pesTimeUs, boolean dataAlignmentIndicator) {
timeUs = pesTimeUs;
}
@Override
public void consume(ParsableByteArray data) throws ParserException {
int bytesToRead;
while (data.bytesLeft() > 0) {
switch (state) {
case STATE_FINDING_SYNC_1:
if (data.readUnsignedByte() == SYNC_BYTE_FIRST) {
state = STATE_FINDING_SYNC_2;
}
break;
case STATE_FINDING_SYNC_2:
int secondByte = data.readUnsignedByte();
if ((secondByte & SYNC_BYTE_SECOND) == SYNC_BYTE_SECOND) {
secondHeaderByte = secondByte;
state = STATE_READING_HEADER;
} else if (secondByte != SYNC_BYTE_FIRST) {
state = STATE_FINDING_SYNC_1;
}
break;
case STATE_READING_HEADER:
sampleSize = ((secondHeaderByte & ~SYNC_BYTE_SECOND) << 8) | data.readUnsignedByte();
if (sampleSize > sampleDataBuffer.data.length) {
resetBufferForSize(sampleSize);
}
bytesRead = 0;
state = STATE_READING_SAMPLE;
break;
case STATE_READING_SAMPLE:
bytesToRead = Math.min(data.bytesLeft(), sampleSize - bytesRead);
data.readBytes(sampleBitArray.data, bytesRead, bytesToRead);
bytesRead += bytesToRead;
if (bytesRead == sampleSize) {
sampleBitArray.setPosition(0);
parseAudioMuxElement(sampleBitArray);
state = STATE_FINDING_SYNC_1;
}
break;
}
}
}
@Override
public void packetFinished() {
// Do nothing.
}
/**
* Parses an AudioMuxElement as defined in 14496-3:2009, Section 1.7.3.1, Table 1.41.
*
* @param data A {@link ParsableBitArray} containing the AudioMuxElement's bytes.
*/
private void parseAudioMuxElement(ParsableBitArray data) throws ParserException {
boolean useSameStreamMux = data.readBit();
if (!useSameStreamMux) {
streamMuxRead = true;
parseStreamMuxConfig(data);
} else if (!streamMuxRead) {
return; // Parsing cannot continue without StreamMuxConfig information.
}
if (audioMuxVersionA == 0) {
if (numSubframes != 0) {
throw new ParserException();
}
int muxSlotLengthBytes = parsePayloadLengthInfo(data);
parsePayloadMux(data, muxSlotLengthBytes);
if (otherDataPresent) {
data.skipBits((int) otherDataLenBits);
}
} else {
throw new ParserException(); // Not defined by ISO/IEC 14496-3:2009.
}
}
/**
* Parses a StreamMuxConfig as defined in ISO/IEC 14496-3:2009 Section 1.7.3.1, Table 1.42.
*/
private void parseStreamMuxConfig(ParsableBitArray data) throws ParserException {
audioMuxVersion = data.readBits(1);
audioMuxVersionA = audioMuxVersion == 1 ? data.readBits(1) : 0;
if (audioMuxVersionA == 0) {
if (audioMuxVersion == 1) {
latmGetValue(data); // Skip taraBufferFullness.
}
if (!data.readBit()) {
throw new ParserException();
}
numSubframes = data.readBits(6);
int numProgram = data.readBits(4);
int numLayer = data.readBits(3);
if (numProgram != 0 || numLayer != 0) {
throw new ParserException();
}
if (audioMuxVersion == 0) {
int startPosition = data.getPosition();
int readBits = parseAudioSpecificConfig(data);
data.setPosition(startPosition);
byte[] initData = new byte[(readBits + 7) / 8];
data.readBits(initData, 0, readBits);
Format format = Format.createAudioSampleFormat(formatId, MimeTypes.AUDIO_AAC, null,
Format.NO_VALUE, Format.NO_VALUE, channelCount, sampleRateHz,
Collections.singletonList(initData), null, 0, language);
if (!format.equals(this.format)) {
this.format = format;
sampleDurationUs = (C.MICROS_PER_SECOND * 1024) / format.sampleRate;
output.format(format);
}
} else {
int ascLen = (int) latmGetValue(data);
int bitsRead = parseAudioSpecificConfig(data);
data.skipBits(ascLen - bitsRead); // fillBits.
}
parseFrameLength(data);
otherDataPresent = data.readBit();
otherDataLenBits = 0;
if (otherDataPresent) {
if (audioMuxVersion == 1) {
otherDataLenBits = latmGetValue(data);
} else {
boolean otherDataLenEsc;
do {
otherDataLenEsc = data.readBit();
otherDataLenBits = (otherDataLenBits << 8) + data.readBits(8);
} while (otherDataLenEsc);
}
}
boolean crcCheckPresent = data.readBit();
if (crcCheckPresent) {
data.skipBits(8); // crcCheckSum.
}
} else {
throw new ParserException(); // This is not defined by ISO/IEC 14496-3:2009.
}
}
private void parseFrameLength(ParsableBitArray data) {
frameLengthType = data.readBits(3);
switch (frameLengthType) {
case 0:
data.skipBits(8); // latmBufferFullness.
break;
case 1:
data.skipBits(9); // frameLength.
break;
case 3:
case 4:
case 5:
data.skipBits(6); // CELPframeLengthTableIndex.
break;
case 6:
case 7:
data.skipBits(1); // HVXCframeLengthTableIndex.
break;
}
}
private int parseAudioSpecificConfig(ParsableBitArray data) throws ParserException {
int bitsLeft = data.bitsLeft();
Pair<Integer, Integer> config = CodecSpecificDataUtil.parseAacAudioSpecificConfig(data, true);
sampleRateHz = config.first;
channelCount = config.second;
return bitsLeft - data.bitsLeft();
}
private int parsePayloadLengthInfo(ParsableBitArray data) throws ParserException {
int muxSlotLengthBytes = 0;
// Assuming single program and single layer.
if (frameLengthType == 0) {
int tmp;
do {
tmp = data.readBits(8);
muxSlotLengthBytes += tmp;
} while (tmp == 255);
return muxSlotLengthBytes;
} else {
throw new ParserException();
}
}
private void parsePayloadMux(ParsableBitArray data, int muxLengthBytes) {
// The start of sample data in
int bitPosition = data.getPosition();
if ((bitPosition & 0x07) == 0) {
// Sample data is byte-aligned. We can output it directly.
sampleDataBuffer.setPosition(bitPosition >> 3);
} else {
// Sample data is not byte-aligned and we need align it ourselves before outputting.
// Byte alignment is needed because LATM framing is not supported by MediaCodec.
data.readBits(sampleDataBuffer.data, 0, muxLengthBytes * 8);
sampleDataBuffer.setPosition(0);
}
output.sampleData(sampleDataBuffer, muxLengthBytes);
output.sampleMetadata(timeUs, C.BUFFER_FLAG_KEY_FRAME, muxLengthBytes, 0, null);
timeUs += sampleDurationUs;
}
private void resetBufferForSize(int newSize) {
sampleDataBuffer.reset(newSize);
sampleBitArray.reset(sampleDataBuffer.data);
}
private static long latmGetValue(ParsableBitArray data) {
int bytesForValue = data.readBits(2);
return data.readBits((bytesForValue + 1) * 8);
}
}

View file

@ -17,6 +17,7 @@ package com.google.android.exoplayer2.extractor.ts;
import android.util.Log;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray;
@ -77,7 +78,8 @@ public final class PesReader implements TsPayloadReader {
}
@Override
public final void consume(ParsableByteArray data, boolean payloadUnitStartIndicator) {
public final void consume(ParsableByteArray data, boolean payloadUnitStartIndicator)
throws ParserException {
if (payloadUnitStartIndicator) {
switch (state) {
case STATE_FINDING_HEADER:

View file

@ -17,6 +17,7 @@ package com.google.android.exoplayer2.extractor.ts;
import android.util.SparseArray;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
@ -30,7 +31,7 @@ import com.google.android.exoplayer2.util.TimestampAdjuster;
import java.io.IOException;
/**
* Facilitates the extraction of data from the MPEG-2 TS container format.
* Facilitates the extraction of data from the MPEG-2 PS container format.
*/
public final class PsExtractor implements Extractor {
@ -275,8 +276,9 @@ public final class PsExtractor implements Extractor {
* Consumes the payload of a PS packet.
*
* @param data The PES packet. The position will be set to the start of the payload.
* @throws ParserException If the payload could not be parsed.
*/
public void consume(ParsableByteArray data) {
public void consume(ParsableByteArray data) throws ParserException {
data.readBytes(pesScratch.data, 0, 3);
pesScratch.setPosition(0);
parseHeader();

View file

@ -84,7 +84,8 @@ public final class TsExtractor implements Extractor {
public static final int TS_STREAM_TYPE_MPA = 0x03;
public static final int TS_STREAM_TYPE_MPA_LSF = 0x04;
public static final int TS_STREAM_TYPE_AAC = 0x0F;
public static final int TS_STREAM_TYPE_AAC_ADTS = 0x0F;
public static final int TS_STREAM_TYPE_AAC_LATM = 0x11;
public static final int TS_STREAM_TYPE_AC3 = 0x81;
public static final int TS_STREAM_TYPE_DTS = 0x8A;
public static final int TS_STREAM_TYPE_HDMV_DTS = 0x82;

View file

@ -16,6 +16,7 @@
package com.google.android.exoplayer2.extractor.ts;
import android.util.SparseArray;
import com.google.android.exoplayer2.ParserException;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.util.ParsableByteArray;
@ -196,7 +197,8 @@ public interface TsPayloadReader {
*
* @param data The TS packet. The position will be set to the start of the payload.
* @param payloadUnitStartIndicator Whether payloadUnitStartIndicator was set on the TS packet.
* @throws ParserException If the payload could not be parsed.
*/
void consume(ParsableByteArray data, boolean payloadUnitStartIndicator);
void consume(ParsableByteArray data, boolean payloadUnitStartIndicator) throws ParserException;
}

View file

@ -530,7 +530,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
while (feedInputBuffer()) {}
TraceUtil.endSection();
} else {
skipSource(positionUs);
decoderCounters.skippedInputBufferCount += skipSource(positionUs);
// We need to read any format changes despite not having a codec so that drmSession can be
// updated, and so that we have the most recent format should the codec be initialized. We may
// also reach the end of the stream. Note that readSource will not read a sample into a

View file

@ -286,8 +286,8 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
}
@Override
public void skipData(long positionUs) {
stream.skipData(startUs + positionUs);
public int skipData(long positionUs) {
return stream.skipData(startUs + positionUs);
}
}

View file

@ -17,7 +17,6 @@ package com.google.android.exoplayer2.source;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ExoPlayer;
import com.google.android.exoplayer2.Player.RepeatMode;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.util.Assertions;
@ -128,9 +127,8 @@ public final class ClippingMediaSource implements MediaSource, MediaSource.Liste
/**
* Provides a clipped view of a specified timeline.
*/
private static final class ClippingTimeline extends Timeline {
private static final class ClippingTimeline extends ForwardingTimeline {
private final Timeline timeline;
private final long startUs;
private final long endUs;
@ -143,6 +141,7 @@ public final class ClippingMediaSource implements MediaSource, MediaSource.Liste
* of {@code timeline}, or {@link C#TIME_END_OF_SOURCE} to clip no samples from the end.
*/
public ClippingTimeline(Timeline timeline, long startUs, long endUs) {
super(timeline);
Assertions.checkArgument(timeline.getWindowCount() == 1);
Assertions.checkArgument(timeline.getPeriodCount() == 1);
Window window = timeline.getWindow(0, new Window(), false);
@ -155,26 +154,10 @@ public final class ClippingMediaSource implements MediaSource, MediaSource.Liste
}
Period period = timeline.getPeriod(0, new Period());
Assertions.checkArgument(period.getPositionInWindowUs() == 0);
this.timeline = timeline;
this.startUs = startUs;
this.endUs = resolvedEndUs;
}
@Override
public int getWindowCount() {
return 1;
}
@Override
public int getNextWindowIndex(int windowIndex, @RepeatMode int repeatMode) {
return timeline.getNextWindowIndex(windowIndex, repeatMode);
}
@Override
public int getPreviousWindowIndex(int windowIndex, @RepeatMode int repeatMode) {
return timeline.getPreviousWindowIndex(windowIndex, repeatMode);
}
@Override
public Window getWindow(int windowIndex, Window window, boolean setIds,
long defaultPositionProjectionUs) {
@ -196,11 +179,6 @@ public final class ClippingMediaSource implements MediaSource, MediaSource.Liste
return window;
}
@Override
public int getPeriodCount() {
return 1;
}
@Override
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
period = timeline.getPeriod(0, period, setIds);
@ -208,11 +186,6 @@ public final class ClippingMediaSource implements MediaSource, MediaSource.Liste
return period;
}
@Override
public int getIndexOfPeriod(Object uid) {
return timeline.getIndexOfPeriod(uid);
}
}
}

View file

@ -186,8 +186,8 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, ExoPl
@Override
public void maybeThrowSourceInfoRefreshError() throws IOException {
for (MediaSourceHolder mediaSourceHolder : mediaSourceHolders) {
mediaSourceHolder.mediaSource.maybeThrowSourceInfoRefreshError();
for (int i = 0; i < mediaSourceHolders.size(); i++) {
mediaSourceHolders.get(i).mediaSource.maybeThrowSourceInfoRefreshError();
}
}
@ -221,8 +221,8 @@ public final class DynamicConcatenatingMediaSource implements MediaSource, ExoPl
@Override
public void releaseSource() {
for (MediaSourceHolder mediaSourceHolder : mediaSourceHolders) {
mediaSourceHolder.mediaSource.releaseSource();
for (int i = 0; i < mediaSourceHolders.size(); i++) {
mediaSourceHolders.get(i).mediaSource.releaseSource();
}
}

View file

@ -43,8 +43,8 @@ public final class EmptySampleStream implements SampleStream {
}
@Override
public void skipData(long positionUs) {
// Do nothing.
public int skipData(long positionUs) {
return 0;
}
}

View file

@ -238,7 +238,7 @@ import java.util.Arrays;
// sample queue, or if we haven't read anything from the queue since the previous seek
// (this case is common for sparse tracks such as metadata tracks). In all other cases a
// seek is required.
seekRequired = !sampleQueue.advanceTo(positionUs, true, true)
seekRequired = sampleQueue.advanceTo(positionUs, true, true) == SampleQueue.ADVANCE_FAILED
&& sampleQueue.getReadIndex() != 0;
}
}
@ -371,12 +371,13 @@ import java.util.Arrays;
lastSeekPositionUs);
}
/* package */ void skipData(int track, long positionUs) {
/* package */ int skipData(int track, long positionUs) {
SampleQueue sampleQueue = sampleQueues[track];
if (loadingFinished && positionUs > sampleQueue.getLargestQueuedTimestampUs()) {
sampleQueue.advanceToEnd();
return sampleQueue.advanceToEnd();
} else {
sampleQueue.advanceTo(positionUs, true, true);
int skipCount = sampleQueue.advanceTo(positionUs, true, true);
return skipCount == SampleQueue.ADVANCE_FAILED ? 0 : skipCount;
}
}
@ -558,7 +559,8 @@ import java.util.Arrays;
for (int i = 0; i < trackCount; i++) {
SampleQueue sampleQueue = sampleQueues[i];
sampleQueue.rewind();
boolean seekInsideQueue = sampleQueue.advanceTo(positionUs, true, false);
boolean seekInsideQueue = sampleQueue.advanceTo(positionUs, true, false)
!= SampleQueue.ADVANCE_FAILED;
// If we have AV tracks then an in-buffer seek is successful if the seek into every AV queue
// is successful. We ignore whether seeks within non-AV queues are successful in this case, as
// they may be sparse or poorly interleaved. If we only have non-AV tracks then a seek is
@ -632,8 +634,8 @@ import java.util.Arrays;
}
@Override
public void skipData(long positionUs) {
ExtractorMediaPeriod.this.skipData(track, positionUs);
public int skipData(long positionUs) {
return ExtractorMediaPeriod.this.skipData(track, positionUs);
}
}

View file

@ -0,0 +1,68 @@
/*
* 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.source;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
/**
* An overridable {@link Timeline} implementation forwarding all methods to another timeline.
*/
public abstract class ForwardingTimeline extends Timeline {
protected final Timeline timeline;
public ForwardingTimeline(Timeline timeline) {
this.timeline = timeline;
}
@Override
public int getWindowCount() {
return timeline.getWindowCount();
}
@Override
public int getNextWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode) {
return timeline.getNextWindowIndex(windowIndex, repeatMode);
}
@Override
public int getPreviousWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode) {
return timeline.getPreviousWindowIndex(windowIndex, repeatMode);
}
@Override
public Window getWindow(int windowIndex, Window window, boolean setIds,
long defaultPositionProjectionUs) {
return timeline.getWindow(windowIndex, window, setIds, defaultPositionProjectionUs);
}
@Override
public int getPeriodCount() {
return timeline.getPeriodCount();
}
@Override
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
return timeline.getPeriod(periodIndex, period, setIds);
}
@Override
public int getIndexOfPeriod(Object uid) {
return timeline.getIndexOfPeriod(uid);
}
}

View file

@ -160,53 +160,25 @@ public final class LoopingMediaSource implements MediaSource {
}
private static final class InfinitelyLoopingTimeline extends Timeline {
private static final class InfinitelyLoopingTimeline extends ForwardingTimeline {
private final Timeline childTimeline;
public InfinitelyLoopingTimeline(Timeline childTimeline) {
this.childTimeline = childTimeline;
}
@Override
public int getWindowCount() {
return childTimeline.getWindowCount();
public InfinitelyLoopingTimeline(Timeline timeline) {
super(timeline);
}
@Override
public int getNextWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode) {
int childNextWindowIndex = childTimeline.getNextWindowIndex(windowIndex, repeatMode);
int childNextWindowIndex = timeline.getNextWindowIndex(windowIndex, repeatMode);
return childNextWindowIndex == C.INDEX_UNSET ? 0 : childNextWindowIndex;
}
@Override
public int getPreviousWindowIndex(int windowIndex, @Player.RepeatMode int repeatMode) {
int childPreviousWindowIndex = childTimeline.getPreviousWindowIndex(windowIndex, repeatMode);
int childPreviousWindowIndex = timeline.getPreviousWindowIndex(windowIndex, repeatMode);
return childPreviousWindowIndex == C.INDEX_UNSET ? getWindowCount() - 1
: childPreviousWindowIndex;
}
@Override
public Window getWindow(int windowIndex, Window window, boolean setIds,
long defaultPositionProjectionUs) {
return childTimeline.getWindow(windowIndex, window, setIds, defaultPositionProjectionUs);
}
@Override
public int getPeriodCount() {
return childTimeline.getPeriodCount();
}
@Override
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
return childTimeline.getPeriod(periodIndex, period, setIds);
}
@Override
public int getIndexOfPeriod(Object uid) {
return childTimeline.getIndexOfPeriod(uid);
}
}
}

View file

@ -253,32 +253,35 @@ import com.google.android.exoplayer2.util.Util;
* @param allowTimeBeyondBuffer Whether the operation can succeed if {@code timeUs} is beyond the
* end of the queue, by advancing the read position to the last sample (or keyframe) in the
* queue.
* @return Whether the operation was a success. A successful advance is one in which the read
* position was unchanged or advanced, and is now at a sample meeting the specified criteria.
* @return The number of samples that were skipped if the operation was successful, which may be
* equal to 0, or {@link SampleQueue#ADVANCE_FAILED} if the operation was not successful. A
* successful advance is one in which the read position was unchanged or advanced, and is now
* at a sample meeting the specified criteria.
*/
public synchronized boolean advanceTo(long timeUs, boolean toKeyframe,
public synchronized int advanceTo(long timeUs, boolean toKeyframe,
boolean allowTimeBeyondBuffer) {
int relativeReadIndex = getRelativeIndex(readPosition);
if (!hasNextSample() || timeUs < timesUs[relativeReadIndex]
|| (timeUs > largestQueuedTimestampUs && !allowTimeBeyondBuffer)) {
return false;
return SampleQueue.ADVANCE_FAILED;
}
int offset = findSampleBefore(relativeReadIndex, length - readPosition, timeUs, toKeyframe);
if (offset == -1) {
return false;
return SampleQueue.ADVANCE_FAILED;
}
readPosition += offset;
return true;
return offset;
}
/**
* Advances the read position to the end of the queue.
*
* @return The number of samples that were skipped.
*/
public synchronized void advanceToEnd() {
if (!hasNextSample()) {
return;
}
public synchronized int advanceToEnd() {
int skipCount = length - readPosition;
readPosition = length;
return skipCount;
}
/**

View file

@ -49,6 +49,8 @@ public final class SampleQueue implements TrackOutput {
}
public static final int ADVANCE_FAILED = -1;
private static final int INITIAL_SCRATCH_SIZE = 32;
private final Allocator allocator;
@ -255,9 +257,11 @@ public final class SampleQueue implements TrackOutput {
/**
* Advances the read position to the end of the queue.
*
* @return The number of samples that were skipped.
*/
public void advanceToEnd() {
metadataQueue.advanceToEnd();
public int advanceToEnd() {
return metadataQueue.advanceToEnd();
}
/**
@ -268,10 +272,12 @@ public final class SampleQueue implements TrackOutput {
* time, rather than to any sample before or at that time.
* @param allowTimeBeyondBuffer Whether the operation can succeed if {@code timeUs} is beyond the
* end of the queue, by advancing the read position to the last sample (or keyframe).
* @return Whether the operation was a success. A successful advance is one in which the read
* position was unchanged or advanced, and is now at a sample meeting the specified criteria.
* @return The number of samples that were skipped if the operation was successful, which may be
* equal to 0, or {@link #ADVANCE_FAILED} if the operation was not successful. A successful
* advance is one in which the read position was unchanged or advanced, and is now at a sample
* meeting the specified criteria.
*/
public boolean advanceTo(long timeUs, boolean toKeyframe, boolean allowTimeBeyondBuffer) {
public int advanceTo(long timeUs, boolean toKeyframe, boolean allowTimeBeyondBuffer) {
return metadataQueue.advanceTo(timeUs, toKeyframe, allowTimeBeyondBuffer);
}

View file

@ -70,7 +70,8 @@ public interface SampleStream {
* {@code positionUs} is beyond it.
*
* @param positionUs The specified time.
* @return The number of samples that were skipped.
*/
void skipData(long positionUs);
int skipData(long positionUs);
}

View file

@ -0,0 +1,256 @@
/*
* 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.source;
import com.google.android.exoplayer2.C;
import java.util.Arrays;
import java.util.Random;
/**
* Shuffled order of indices.
*/
public interface ShuffleOrder {
/**
* The default {@link ShuffleOrder} implementation for random shuffle order.
*/
class DefaultShuffleOrder implements ShuffleOrder {
private final Random random;
private final int[] shuffled;
private final int[] indexInShuffled;
/**
* Creates an instance with a specified length.
*
* @param length The length of the shuffle order.
*/
public DefaultShuffleOrder(int length) {
this(length, new Random());
}
/**
* Creates an instance with a specified length and the specified random seed. Shuffle orders of
* the same length initialized with the same random seed are guaranteed to be equal.
*
* @param length The length of the shuffle order.
* @param randomSeed A random seed.
*/
public DefaultShuffleOrder(int length, long randomSeed) {
this(length, new Random(randomSeed));
}
private DefaultShuffleOrder(int length, Random random) {
this(createShuffledList(length, random), random);
}
private DefaultShuffleOrder(int[] shuffled, Random random) {
this.shuffled = shuffled;
this.random = random;
this.indexInShuffled = new int[shuffled.length];
for (int i = 0; i < shuffled.length; i++) {
indexInShuffled[shuffled[i]] = i;
}
}
@Override
public int getLength() {
return shuffled.length;
}
@Override
public int getNextIndex(int index) {
int shuffledIndex = indexInShuffled[index];
return ++shuffledIndex < shuffled.length ? shuffled[shuffledIndex] : C.INDEX_UNSET;
}
@Override
public int getPreviousIndex(int index) {
int shuffledIndex = indexInShuffled[index];
return --shuffledIndex >= 0 ? shuffled[shuffledIndex] : C.INDEX_UNSET;
}
@Override
public int getLastIndex() {
return shuffled.length > 0 ? shuffled[shuffled.length - 1] : C.INDEX_UNSET;
}
@Override
public int getFirstIndex() {
return shuffled.length > 0 ? shuffled[0] : C.INDEX_UNSET;
}
@Override
public ShuffleOrder cloneAndInsert(int insertionIndex, int insertionCount) {
int[] insertionPoints = new int[insertionCount];
int[] insertionValues = new int[insertionCount];
for (int i = 0; i < insertionCount; i++) {
insertionPoints[i] = random.nextInt(shuffled.length + 1);
int swapIndex = random.nextInt(i + 1);
insertionValues[i] = insertionValues[swapIndex];
insertionValues[swapIndex] = i + insertionIndex;
}
Arrays.sort(insertionPoints);
int[] newShuffled = new int[shuffled.length + insertionCount];
int indexInOldShuffled = 0;
int indexInInsertionList = 0;
for (int i = 0; i < shuffled.length + insertionCount; i++) {
if (indexInInsertionList < insertionCount
&& indexInOldShuffled == insertionPoints[indexInInsertionList]) {
newShuffled[i] = insertionValues[indexInInsertionList++];
} else {
newShuffled[i] = shuffled[indexInOldShuffled++];
if (newShuffled[i] >= insertionIndex) {
newShuffled[i] += insertionCount;
}
}
}
return new DefaultShuffleOrder(newShuffled, new Random(random.nextLong()));
}
@Override
public ShuffleOrder cloneAndRemove(int removalIndex) {
int[] newShuffled = new int[shuffled.length - 1];
boolean foundRemovedElement = false;
for (int i = 0; i < shuffled.length; i++) {
if (shuffled[i] == removalIndex) {
foundRemovedElement = true;
} else {
newShuffled[foundRemovedElement ? i - 1 : i] = shuffled[i] > removalIndex
? shuffled[i] - 1 : shuffled[i];
}
}
return new DefaultShuffleOrder(newShuffled, new Random(random.nextLong()));
}
private static int[] createShuffledList(int length, Random random) {
int[] shuffled = new int[length];
for (int i = 0; i < length; i++) {
int swapIndex = random.nextInt(i + 1);
shuffled[i] = shuffled[swapIndex];
shuffled[swapIndex] = i;
}
return shuffled;
}
}
/**
* A {@link ShuffleOrder} implementation which does not shuffle.
*/
final class UnshuffledShuffleOrder implements ShuffleOrder {
private final int length;
/**
* Creates an instance with a specified length.
*
* @param length The length of the shuffle order.
*/
public UnshuffledShuffleOrder(int length) {
this.length = length;
}
@Override
public int getLength() {
return length;
}
@Override
public int getNextIndex(int index) {
return ++index < length ? index : C.INDEX_UNSET;
}
@Override
public int getPreviousIndex(int index) {
return --index >= 0 ? index : C.INDEX_UNSET;
}
@Override
public int getLastIndex() {
return length > 0 ? length - 1 : C.INDEX_UNSET;
}
@Override
public int getFirstIndex() {
return length > 0 ? 0 : C.INDEX_UNSET;
}
@Override
public ShuffleOrder cloneAndInsert(int insertionIndex, int insertionCount) {
return new UnshuffledShuffleOrder(length + insertionCount);
}
@Override
public ShuffleOrder cloneAndRemove(int removalIndex) {
return new UnshuffledShuffleOrder(length - 1);
}
}
/**
* Returns length of shuffle order.
*/
int getLength();
/**
* Returns the next index in the shuffle order.
*
* @param index An index.
* @return The index after {@code index}, or {@link C#INDEX_UNSET} if {@code index} is the last
* element.
*/
int getNextIndex(int index);
/**
* Returns the previous index in the shuffle order.
*
* @param index An index.
* @return The index before {@code index}, or {@link C#INDEX_UNSET} if {@code index} is the first
* element.
*/
int getPreviousIndex(int index);
/**
* Returns the last index in the shuffle order, or {@link C#INDEX_UNSET} if the shuffle order is
* empty.
*/
int getLastIndex();
/**
* Returns the first index in the shuffle order, or {@link C#INDEX_UNSET} if the shuffle order is
* empty.
*/
int getFirstIndex();
/**
* Return a copy of the shuffle order with newly inserted elements.
*
* @param insertionIndex The index in the unshuffled order at which elements are inserted.
* @param insertionCount The number of elements inserted at {@code insertionIndex}.
* @return A copy of this {@link ShuffleOrder} with newly inserted elements.
*/
ShuffleOrder cloneAndInsert(int insertionIndex, int insertionCount);
/**
* Return a copy of the shuffle order with one element removed.
*
* @param removalIndex The index of the element in the unshuffled order which is to be removed.
* @return A copy of this {@link ShuffleOrder} without the removed element.
*/
ShuffleOrder cloneAndRemove(int removalIndex);
}

View file

@ -235,10 +235,12 @@ import java.util.Arrays;
}
@Override
public void skipData(long positionUs) {
if (positionUs > 0) {
public int skipData(long positionUs) {
if (positionUs > 0 && streamState != STREAM_STATE_END_OF_STREAM) {
streamState = STREAM_STATE_END_OF_STREAM;
return 1;
}
return 0;
}
}

View file

@ -160,6 +160,7 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
* @return An estimate of the absolute position in microseconds up to which data is buffered, or
* {@link C#TIME_END_OF_SOURCE} if the track is fully buffered.
*/
@Override
public long getBufferedPositionUs() {
if (loadingFinished) {
return C.TIME_END_OF_SOURCE;
@ -185,8 +186,8 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
public void seekToUs(long positionUs) {
lastSeekPositionUs = positionUs;
// If we're not pending a reset, see if we can seek within the primary sample queue.
boolean seekInsideBuffer = !isPendingReset() && primarySampleQueue.advanceTo(positionUs, true,
positionUs < getNextLoadPositionUs());
boolean seekInsideBuffer = !isPendingReset() && (primarySampleQueue.advanceTo(positionUs, true,
positionUs < getNextLoadPositionUs()) != SampleQueue.ADVANCE_FAILED);
if (seekInsideBuffer) {
// We succeeded. Discard samples and corresponding chunks prior to the seek position.
discardDownstreamMediaChunks(primarySampleQueue.getReadIndex());
@ -266,13 +267,19 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
}
@Override
public void skipData(long positionUs) {
public int skipData(long positionUs) {
int skipCount;
if (loadingFinished && positionUs > primarySampleQueue.getLargestQueuedTimestampUs()) {
primarySampleQueue.advanceToEnd();
skipCount = primarySampleQueue.advanceToEnd();
} else {
primarySampleQueue.advanceTo(positionUs, true, true);
skipCount = primarySampleQueue.advanceTo(positionUs, true, true);
if (skipCount == SampleQueue.ADVANCE_FAILED) {
skipCount = 0;
}
}
primarySampleQueue.discardToRead();
return skipCount;
}
// Loader.Callback implementation.
@ -470,11 +477,12 @@ public class ChunkSampleStream<T extends ChunkSource> implements SampleStream, S
}
@Override
public void skipData(long positionUs) {
public int skipData(long positionUs) {
if (loadingFinished && positionUs > sampleQueue.getLargestQueuedTimestampUs()) {
sampleQueue.advanceToEnd();
return sampleQueue.advanceToEnd();
} else {
sampleQueue.advanceTo(positionUs, true, true);
int skipCount = sampleQueue.advanceTo(positionUs, true, true);
return skipCount == SampleQueue.ADVANCE_FAILED ? 0 : skipCount;
}
}

View file

@ -811,43 +811,43 @@ public final class Cea708Decoder extends CeaDecoder {
private static final int PEN_OFFSET_NORMAL = 1;
// The window style properties are specified in the CEA-708 specification.
private static final int[] WINDOW_STYLE_JUSTIFICATION = new int[]{
private static final int[] WINDOW_STYLE_JUSTIFICATION = new int[] {
JUSTIFICATION_LEFT, JUSTIFICATION_LEFT, JUSTIFICATION_LEFT,
JUSTIFICATION_LEFT, JUSTIFICATION_LEFT, JUSTIFICATION_CENTER,
JUSTIFICATION_LEFT
};
private static final int[] WINDOW_STYLE_PRINT_DIRECTION = new int[]{
private static final int[] WINDOW_STYLE_PRINT_DIRECTION = new int[] {
DIRECTION_LEFT_TO_RIGHT, DIRECTION_LEFT_TO_RIGHT, DIRECTION_LEFT_TO_RIGHT,
DIRECTION_LEFT_TO_RIGHT, DIRECTION_LEFT_TO_RIGHT, DIRECTION_LEFT_TO_RIGHT,
DIRECTION_TOP_TO_BOTTOM
};
private static final int[] WINDOW_STYLE_SCROLL_DIRECTION = new int[]{
private static final int[] WINDOW_STYLE_SCROLL_DIRECTION = new int[] {
DIRECTION_BOTTOM_TO_TOP, DIRECTION_BOTTOM_TO_TOP, DIRECTION_BOTTOM_TO_TOP,
DIRECTION_BOTTOM_TO_TOP, DIRECTION_BOTTOM_TO_TOP, DIRECTION_BOTTOM_TO_TOP,
DIRECTION_RIGHT_TO_LEFT
};
private static final boolean[] WINDOW_STYLE_WORD_WRAP = new boolean[]{
private static final boolean[] WINDOW_STYLE_WORD_WRAP = new boolean[] {
false, false, false, true, true, true, false
};
private static final int[] WINDOW_STYLE_FILL = new int[]{
private static final int[] WINDOW_STYLE_FILL = new int[] {
COLOR_SOLID_BLACK, COLOR_TRANSPARENT, COLOR_SOLID_BLACK, COLOR_SOLID_BLACK,
COLOR_TRANSPARENT, COLOR_SOLID_BLACK, COLOR_SOLID_BLACK
};
// The pen style properties are specified in the CEA-708 specification.
private static final int[] PEN_STYLE_FONT_STYLE = new int[]{
private static final int[] PEN_STYLE_FONT_STYLE = new int[] {
PEN_FONT_STYLE_DEFAULT, PEN_FONT_STYLE_MONOSPACED_WITH_SERIFS,
PEN_FONT_STYLE_PROPORTIONALLY_SPACED_WITH_SERIFS, PEN_FONT_STYLE_MONOSPACED_WITHOUT_SERIFS,
PEN_FONT_STYLE_PROPORTIONALLY_SPACED_WITHOUT_SERIFS,
PEN_FONT_STYLE_MONOSPACED_WITHOUT_SERIFS,
PEN_FONT_STYLE_PROPORTIONALLY_SPACED_WITHOUT_SERIFS
};
private static final int[] PEN_STYLE_EDGE_TYPE = new int[]{
private static final int[] PEN_STYLE_EDGE_TYPE = new int[] {
BORDER_AND_EDGE_TYPE_NONE, BORDER_AND_EDGE_TYPE_NONE, BORDER_AND_EDGE_TYPE_NONE,
BORDER_AND_EDGE_TYPE_NONE, BORDER_AND_EDGE_TYPE_NONE, BORDER_AND_EDGE_TYPE_UNIFORM,
BORDER_AND_EDGE_TYPE_UNIFORM
};
private static final int[] PEN_STYLE_BACKGROUND = new int[]{
private static final int[] PEN_STYLE_BACKGROUND = new int[] {
COLOR_SOLID_BLACK, COLOR_SOLID_BLACK, COLOR_SOLID_BLACK, COLOR_SOLID_BLACK,
COLOR_SOLID_BLACK, COLOR_TRANSPARENT, COLOR_TRANSPARENT};

View file

@ -34,9 +34,9 @@ import java.io.OutputStream;
import java.security.InvalidAlgorithmParameterException;
import java.security.InvalidKeyException;
import java.security.NoSuchAlgorithmException;
import java.util.ArrayList;
import java.util.Collection;
import java.util.HashMap;
import java.util.LinkedList;
import java.util.Random;
import java.util.Set;
import javax.crypto.Cipher;
@ -176,14 +176,14 @@ import javax.crypto.spec.SecretKeySpec;
/** Removes empty {@link CachedContent} instances from index. */
public void removeEmpty() {
LinkedList<String> cachedContentToBeRemoved = new LinkedList<>();
ArrayList<String> cachedContentToBeRemoved = new ArrayList<>();
for (CachedContent cachedContent : keyToContent.values()) {
if (cachedContent.isEmpty()) {
cachedContentToBeRemoved.add(cachedContent.key);
}
}
for (String key : cachedContentToBeRemoved) {
removeEmpty(key);
for (int i = 0; i < cachedContentToBeRemoved.size(); i++) {
removeEmpty(cachedContentToBeRemoved.get(i));
}
}

View file

@ -22,7 +22,6 @@ import java.io.File;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.LinkedList;
import java.util.NavigableSet;
import java.util.Set;
import java.util.TreeSet;
@ -308,7 +307,7 @@ public final class SimpleCache implements Cache {
* no longer exist.
*/
private void removeStaleSpansAndCachedContents() throws CacheException {
LinkedList<CacheSpan> spansToBeRemoved = new LinkedList<>();
ArrayList<CacheSpan> spansToBeRemoved = new ArrayList<>();
for (CachedContent cachedContent : index.getAll()) {
for (CacheSpan span : cachedContent.getSpans()) {
if (!span.file.exists()) {
@ -316,9 +315,9 @@ public final class SimpleCache implements Cache {
}
}
}
for (CacheSpan span : spansToBeRemoved) {
for (int i = 0; i < spansToBeRemoved.size(); i++) {
// Remove span but not CachedContent to prevent multiple index.store() calls.
removeSpan(span, false);
removeSpan(spansToBeRemoved.get(i), false);
}
index.removeEmpty();
index.store();

View file

@ -13,7 +13,6 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.util;
import android.support.annotation.NonNull;

View file

@ -17,6 +17,7 @@ package com.google.android.exoplayer2.util;
import android.util.Pair;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.ParserException;
import java.util.ArrayList;
import java.util.List;
@ -83,11 +84,27 @@ public final class CodecSpecificDataUtil {
/**
* Parses an AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1
*
* @param audioSpecificConfig The AudioSpecificConfig to parse.
* @param audioSpecificConfig A byte array containing the AudioSpecificConfig to parse.
* @return A pair consisting of the sample rate in Hz and the channel count.
* @throws ParserException If the AudioSpecificConfig cannot be parsed as it's not supported.
*/
public static Pair<Integer, Integer> parseAacAudioSpecificConfig(byte[] audioSpecificConfig) {
ParsableBitArray bitArray = new ParsableBitArray(audioSpecificConfig);
public static Pair<Integer, Integer> parseAacAudioSpecificConfig(byte[] audioSpecificConfig)
throws ParserException {
return parseAacAudioSpecificConfig(new ParsableBitArray(audioSpecificConfig), false);
}
/**
* Parses an AudioSpecificConfig, as defined in ISO 14496-3 1.6.2.1
*
* @param bitArray A {@link ParsableBitArray} containing the AudioSpecificConfig to parse. The
* position is advanced to the end of the AudioSpecificConfig.
* @param forceReadToEnd Whether the entire AudioSpecificConfig should be read. Required for
* knowing the length of the configuration payload.
* @return A pair consisting of the sample rate in Hz and the channel count.
* @throws ParserException If the AudioSpecificConfig cannot be parsed as it's not supported.
*/
public static Pair<Integer, Integer> parseAacAudioSpecificConfig(ParsableBitArray bitArray,
boolean forceReadToEnd) throws ParserException {
int audioObjectType = getAacAudioObjectType(bitArray);
int sampleRate = getAacSamplingFrequency(bitArray);
int channelConfiguration = bitArray.readBits(4);
@ -104,6 +121,41 @@ public final class CodecSpecificDataUtil {
channelConfiguration = bitArray.readBits(4);
}
}
if (forceReadToEnd) {
switch (audioObjectType) {
case 1:
case 2:
case 3:
case 4:
case 6:
case 7:
case 17:
case 19:
case 20:
case 21:
case 22:
case 23:
parseGaSpecificConfig(bitArray, audioObjectType, channelConfiguration);
break;
default:
throw new ParserException("Unsupported audio object type: " + audioObjectType);
}
switch (audioObjectType) {
case 17:
case 19:
case 20:
case 21:
case 22:
case 23:
int epConfig = bitArray.readBits(2);
if (epConfig == 2 || epConfig == 3) {
throw new ParserException("Unsupported epConfig: " + epConfig);
}
break;
}
}
// For supported containers, bits_to_decode() is always 0.
int channelCount = AUDIO_SPECIFIC_CONFIG_CHANNEL_COUNT_TABLE[channelConfiguration];
Assertions.checkArgument(channelCount != AUDIO_SPECIFIC_CONFIG_CHANNEL_CONFIGURATION_INVALID);
return Pair.create(sampleRate, channelCount);
@ -269,4 +321,32 @@ public final class CodecSpecificDataUtil {
return samplingFrequency;
}
private static void parseGaSpecificConfig(ParsableBitArray bitArray, int audioObjectType,
int channelConfiguration) {
bitArray.skipBits(1); // frameLengthFlag.
boolean dependsOnCoreDecoder = bitArray.readBit();
if (dependsOnCoreDecoder) {
bitArray.skipBits(14); // coreCoderDelay.
}
boolean extensionFlag = bitArray.readBit();
if (channelConfiguration == 0) {
throw new UnsupportedOperationException(); // TODO: Implement programConfigElement();
}
if (audioObjectType == 6 || audioObjectType == 20) {
bitArray.skipBits(3); // layerNr.
}
if (extensionFlag) {
if (audioObjectType == 22) {
bitArray.skipBits(16); // numOfSubFrame (5), layer_length(11).
}
if (audioObjectType == 17 || audioObjectType == 19 || audioObjectType == 20
|| audioObjectType == 23) {
// aacSectionDataResilienceFlag, aacScalefactorDataResilienceFlag,
// aacSpectralDataResilienceFlag.
bitArray.skipBits(3);
}
bitArray.skipBits(1); // extensionFlag3.
}
}
}

View file

@ -174,6 +174,43 @@ public final class ParsableBitArray {
return returnValue;
}
/**
* Reads {@code numBits} bits into {@code buffer}.
*
* @param buffer The array into which the read data should be written. The trailing
* {@code numBits % 8} bits are written into the most significant bits of the last modified
* {@code buffer} byte. The remaining ones are unmodified.
* @param offset The offset in {@code buffer} at which the read data should be written.
* @param numBits The number of bits to read.
*/
public void readBits(byte[] buffer, int offset, int numBits) {
// Whole bytes.
int to = offset + (numBits >> 3) /* numBits / 8 */;
for (int i = offset; i < to; i++) {
buffer[i] = (byte) (data[byteOffset++] << bitOffset);
buffer[i] |= (data[byteOffset] & 0xFF) >> (8 - bitOffset);
}
// Trailing bits.
int bitsLeft = numBits & 7 /* numBits % 8 */;
if (bitsLeft == 0) {
return;
}
buffer[to] &= 0xFF >> bitsLeft; // Set to 0 the bits that are going to be overwritten.
if (bitOffset + bitsLeft > 8) {
// We read the rest of data[byteOffset] and increase byteOffset.
buffer[to] |= (byte) ((data[byteOffset++] & 0xFF) << bitOffset);
bitOffset -= 8;
}
bitOffset += bitsLeft;
int lastDataByteTrailingBits = (data[byteOffset] & 0xFF) >> (8 - bitOffset);
buffer[to] |= (byte) (lastDataByteTrailingBits << (8 - bitsLeft));
if (bitOffset == 8) {
bitOffset = 0;
byteOffset++;
}
assertValidOffset();
}
/**
* Aligns the position to the next byte boundary. Does nothing if the position is already aligned.
*/

View file

@ -76,7 +76,7 @@ public final class DashUtilTest extends TestCase {
private static DrmInitData newDrmInitData() {
return new DrmInitData(new SchemeData(C.WIDEVINE_UUID, null, "mimeType",
new byte[]{1, 4, 7, 0, 3, 6}));
new byte[] {1, 4, 7, 0, 3, 6}));
}
}

View file

@ -35,6 +35,7 @@ android {
dependencies {
compile project(modulePrefix + 'library-core')
compile 'com.android.support:support-annotations:' + supportLibraryVersion
androidTestCompile project(modulePrefix + 'testutils')
androidTestCompile 'com.google.dexmaker:dexmaker:' + dexmakerVersion
androidTestCompile 'com.google.dexmaker:dexmaker-mockito:' + dexmakerVersion
androidTestCompile 'org.mockito:mockito-core:' + mockitoVersion

View file

@ -50,8 +50,8 @@ import java.io.IOException;
}
@Override
public void skipData(long positionUs) {
sampleStreamWrapper.skipData(group, positionUs);
public int skipData(long positionUs) {
return sampleStreamWrapper.skipData(group, positionUs);
}
}

View file

@ -229,7 +229,7 @@ import java.util.LinkedList;
// sample queue, or if we haven't read anything from the queue since the previous seek
// (this case is common for sparse tracks such as metadata tracks). In all other cases a
// seek is required.
seekRequired = !sampleQueue.advanceTo(positionUs, true, true)
seekRequired = sampleQueue.advanceTo(positionUs, true, true) == SampleQueue.ADVANCE_FAILED
&& sampleQueue.getReadIndex() != 0;
}
}
@ -320,6 +320,7 @@ import java.util.LinkedList;
return true;
}
@Override
public long getBufferedPositionUs() {
if (loadingFinished) {
return C.TIME_END_OF_SOURCE;
@ -402,12 +403,13 @@ import java.util.LinkedList;
lastSeekPositionUs);
}
/* package */ void skipData(int trackGroupIndex, long positionUs) {
/* package */ int skipData(int trackGroupIndex, long positionUs) {
SampleQueue sampleQueue = sampleQueues[trackGroupIndex];
if (loadingFinished && positionUs > sampleQueue.getLargestQueuedTimestampUs()) {
sampleQueue.advanceToEnd();
return sampleQueue.advanceToEnd();
} else {
sampleQueue.advanceTo(positionUs, true, true);
int skipCount = sampleQueue.advanceTo(positionUs, true, true);
return skipCount == SampleQueue.ADVANCE_FAILED ? 0 : skipCount;
}
}
@ -760,7 +762,8 @@ import java.util.LinkedList;
for (int i = 0; i < trackCount; i++) {
SampleQueue sampleQueue = sampleQueues[i];
sampleQueue.rewind();
boolean seekInsideQueue = sampleQueue.advanceTo(positionUs, true, false);
boolean seekInsideQueue = sampleQueue.advanceTo(positionUs, true, false)
!= SampleQueue.ADVANCE_FAILED;
// If we have AV tracks then an in-queue seek is successful if the seek into every AV queue
// is successful. We ignore whether seeks within non-AV queues are successful in this case, as
// they may be sparse or poorly interleaved. If we only have non-AV tracks then a seek is

View file

@ -17,6 +17,7 @@ package com.google.android.exoplayer2.source.hls.playlist;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.util.MimeTypes;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
@ -108,6 +109,20 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
? Collections.unmodifiableList(muxedCaptionFormats) : null;
}
/**
* Returns a copy of this playlist which includes only the renditions identified by the given
* urls.
*
* @param renditionUrls List of rendition urls.
* @return A copy of this playlist which includes only the renditions identified by the given
* urls.
*/
public HlsMasterPlaylist copy(List<String> renditionUrls) {
return new HlsMasterPlaylist(baseUri, tags, copyRenditionsList(variants, renditionUrls),
copyRenditionsList(audios, renditionUrls), copyRenditionsList(subtitles, renditionUrls),
muxedAudioFormat, muxedCaptionFormats);
}
/**
* Creates a playlist with a single variant.
*
@ -121,4 +136,15 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
emptyList, null, null);
}
private static List<HlsUrl> copyRenditionsList(List<HlsUrl> renditions, List<String> urls) {
List<HlsUrl> copiedRenditions = new ArrayList<>(urls.size());
for (int i = 0; i < renditions.size(); i++) {
HlsUrl rendition = renditions.get(i);
if (urls.contains(rendition.url)) {
copiedRenditions.add(rendition);
}
}
return copiedRenditions;
}
}

View file

@ -186,8 +186,9 @@ public final class DebugTextViewHelper implements Runnable, Player.EventListener
return "";
}
counters.ensureUpdated();
return " rb:" + counters.renderedOutputBufferCount
return " sib:" + counters.skippedInputBufferCount
+ " sb:" + counters.skippedOutputBufferCount
+ " rb:" + counters.renderedOutputBufferCount
+ " db:" + counters.droppedOutputBufferCount
+ " mcdb:" + counters.maxConsecutiveDroppedOutputBufferCount;
}

View file

@ -0,0 +1,26 @@
<!-- 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.
-->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
android:width="32dp"
android:height="32dp"
android:viewportWidth="24.0"
android:viewportHeight="24.0">
<path
android:fillColor="#FFFFFFFF"
android:pathData="M10.59 9.17L5.41 4 4 5.41l5.17 5.17 1.42-1.41zM14.5 4l2.04 2.04L4 18.59 5.41 20
17.96 7.46 20 9.5V4h-5.5zm.33 9.41l-1.41 1.41 3.13 3.13L14.5 20H20v-5.5l-2.04
2.04-3.13-3.13z" />
</vector>

Binary file not shown.

After

Width:  |  Height:  |  Size: 268 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 187 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 230 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 342 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 436 B

View file

@ -25,4 +25,5 @@
<string name="exo_controls_repeat_all_description">"Herhaal alles"</string>
<string name="exo_controls_repeat_off_description">"Herhaal niks"</string>
<string name="exo_controls_repeat_one_description">"Herhaal een"</string>
<string name="exo_controls_shuffle_description">"Skommel"</string>
</resources>

View file

@ -25,4 +25,5 @@
<string name="exo_controls_repeat_all_description">"ሁሉንም ድገም"</string>
<string name="exo_controls_repeat_off_description">"ምንም አትድገም"</string>
<string name="exo_controls_repeat_one_description">"አንዱን ድገም"</string>
<string name="exo_controls_shuffle_description">"በው"</string>
</resources>

View file

@ -25,4 +25,5 @@
<string name="exo_controls_repeat_all_description">"تكرار الكل"</string>
<string name="exo_controls_repeat_off_description">"عدم التكرار"</string>
<string name="exo_controls_repeat_one_description">"تكرار مقطع واحد"</string>
<string name="exo_controls_shuffle_description">"ترتيب عشوائي"</string>
</resources>

View file

@ -25,4 +25,5 @@
<string name="exo_controls_repeat_all_description">"Bütün təkrarlayın"</string>
<string name="exo_controls_repeat_one_description">"Təkrar bir"</string>
<string name="exo_controls_repeat_off_description">"Heç bir təkrar"</string>
<string name="exo_controls_shuffle_description">"Qarışdır"</string>
</resources>

View file

@ -25,4 +25,5 @@
<string name="exo_controls_repeat_all_description">"Ponovi sve"</string>
<string name="exo_controls_repeat_off_description">"Ne ponavljaj nijednu"</string>
<string name="exo_controls_repeat_one_description">"Ponovi jednu"</string>
<string name="exo_controls_shuffle_description">"Pusti nasumično"</string>
</resources>

View file

@ -25,4 +25,5 @@
<string name="exo_controls_repeat_all_description">"Паўтарыць усё"</string>
<string name="exo_controls_repeat_off_description">"Паўтараць ні"</string>
<string name="exo_controls_repeat_one_description">"Паўтарыць адзін"</string>
<string name="exo_controls_shuffle_description">"Перамяшаць"</string>
</resources>

View file

@ -25,4 +25,5 @@
<string name="exo_controls_repeat_all_description">"Повтаряне на всички"</string>
<string name="exo_controls_repeat_off_description">"Без повтаряне"</string>
<string name="exo_controls_repeat_one_description">"Повтаряне на един елемент"</string>
<string name="exo_controls_shuffle_description">"Разбъркване"</string>
</resources>

View file

@ -25,4 +25,5 @@
<string name="exo_controls_repeat_all_description">"সবগুলির পুনরাবৃত্তি করুন"</string>
<string name="exo_controls_repeat_off_description">"একটিরও পুনরাবৃত্তি করবেন না"</string>
<string name="exo_controls_repeat_one_description">"একটির পুনরাবৃত্তি করুন"</string>
<string name="exo_controls_shuffle_description">"অদলবদল"</string>
</resources>

View file

@ -25,4 +25,5 @@
<string name="exo_controls_repeat_all_description">"Ponovite sve"</string>
<string name="exo_controls_repeat_off_description">"Ne ponavljaju"</string>
<string name="exo_controls_repeat_one_description">"Ponovite jedan"</string>
<string name="exo_controls_shuffle_description">"Izmiješaj"</string>
</resources>

View file

@ -25,4 +25,5 @@
<string name="exo_controls_repeat_all_description">"Repeteix-ho tot"</string>
<string name="exo_controls_repeat_off_description">"No en repeteixis cap"</string>
<string name="exo_controls_repeat_one_description">"Repeteix-ne un"</string>
<string name="exo_controls_shuffle_description">"Reprodueix aleatòriament"</string>
</resources>

View file

@ -25,4 +25,5 @@
<string name="exo_controls_repeat_all_description">"Opakovat vše"</string>
<string name="exo_controls_repeat_off_description">"Neopakovat"</string>
<string name="exo_controls_repeat_one_description">"Opakovat jednu položku"</string>
<string name="exo_controls_shuffle_description">"Náhodně"</string>
</resources>

View file

@ -25,4 +25,5 @@
<string name="exo_controls_repeat_all_description">"Gentag alle"</string>
<string name="exo_controls_repeat_off_description">"Gentag ingen"</string>
<string name="exo_controls_repeat_one_description">"Gentag en"</string>
<string name="exo_controls_shuffle_description">"Bland"</string>
</resources>

View file

@ -25,4 +25,5 @@
<string name="exo_controls_repeat_all_description">"Alle wiederholen"</string>
<string name="exo_controls_repeat_off_description">"Keinen Titel wiederholen"</string>
<string name="exo_controls_repeat_one_description">"Einen Titel wiederholen"</string>
<string name="exo_controls_shuffle_description">"Zufallsmix"</string>
</resources>

View file

@ -25,4 +25,5 @@
<string name="exo_controls_repeat_all_description">"Επανάληψη όλων"</string>
<string name="exo_controls_repeat_off_description">"Καμία επανάληψη"</string>
<string name="exo_controls_repeat_one_description">"Επανάληψη ενός στοιχείου"</string>
<string name="exo_controls_shuffle_description">"Τυχαία αναπαραγωγή"</string>
</resources>

View file

@ -25,4 +25,5 @@
<string name="exo_controls_repeat_all_description">"Repeat all"</string>
<string name="exo_controls_repeat_off_description">"Repeat none"</string>
<string name="exo_controls_repeat_one_description">"Repeat one"</string>
<string name="exo_controls_shuffle_description">"Shuffle"</string>
</resources>

View file

@ -25,4 +25,5 @@
<string name="exo_controls_repeat_all_description">"Repeat all"</string>
<string name="exo_controls_repeat_off_description">"Repeat none"</string>
<string name="exo_controls_repeat_one_description">"Repeat one"</string>
<string name="exo_controls_shuffle_description">"Shuffle"</string>
</resources>

View file

@ -25,4 +25,5 @@
<string name="exo_controls_repeat_all_description">"Repeat all"</string>
<string name="exo_controls_repeat_off_description">"Repeat none"</string>
<string name="exo_controls_repeat_one_description">"Repeat one"</string>
<string name="exo_controls_shuffle_description">"Shuffle"</string>
</resources>

View file

@ -25,4 +25,5 @@
<string name="exo_controls_repeat_all_description">"Repetir todo"</string>
<string name="exo_controls_repeat_off_description">"No repetir"</string>
<string name="exo_controls_repeat_one_description">"Repetir uno"</string>
<string name="exo_controls_shuffle_description">"Reproducir aleatoriamente"</string>
</resources>

Some files were not shown because too many files have changed in this diff Show more