Improving handling of atoms with size less than header in FragmentedMp4Extractor.
These currently lead to cryptic ArrayIndexOutOfBoundsExceptions being thrown from System.arraycopy() so my proposal is to throw a more useful ParserException instead. ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=142087132
@ -1,20 +1,10 @@
|
|||||||
# Release notes #
|
# Release notes #
|
||||||
|
|
||||||
### r2.1.1 ###
|
|
||||||
|
|
||||||
Bugfix release only. Users of r2.1.0 and r2.0.x should proactively update to
|
|
||||||
this version.
|
|
||||||
|
|
||||||
* Fix some subtitle types (e.g. WebVTT) being displayed out of sync
|
|
||||||
([#2208](https://github.com/google/ExoPlayer/issues/2208)).
|
|
||||||
* Fix incorrect position reporting for on-demand HLS media that includes
|
|
||||||
EXT-X-PROGRAM-DATE-TIME tags
|
|
||||||
([#2224](https://github.com/google/ExoPlayer/issues/2224)).
|
|
||||||
* Fix issue where playbacks could get stuck in the initial buffering state if
|
|
||||||
over 1MB of data needs to be read to initialize the playback.
|
|
||||||
|
|
||||||
### r2.1.0 ###
|
### r2.1.0 ###
|
||||||
|
|
||||||
|
This release contains important bug fixes. Users of r2.0.x should proactively
|
||||||
|
update to this version.
|
||||||
|
|
||||||
* HLS: Support for seeking in live streams
|
* HLS: Support for seeking in live streams
|
||||||
([#87](https://github.com/google/ExoPlayer/issues/87)).
|
([#87](https://github.com/google/ExoPlayer/issues/87)).
|
||||||
* HLS: Improved support:
|
* HLS: Improved support:
|
||||||
|
@ -35,7 +35,7 @@ allprojects {
|
|||||||
releaseRepoName = 'exoplayer'
|
releaseRepoName = 'exoplayer'
|
||||||
releaseUserOrg = 'google'
|
releaseUserOrg = 'google'
|
||||||
releaseGroupId = 'com.google.android.exoplayer'
|
releaseGroupId = 'com.google.android.exoplayer'
|
||||||
releaseVersion = 'r2.1.1'
|
releaseVersion = 'r2.1.0'
|
||||||
releaseWebsite = 'https://github.com/google/ExoPlayer'
|
releaseWebsite = 'https://github.com/google/ExoPlayer'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -16,8 +16,8 @@
|
|||||||
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
package="com.google.android.exoplayer2.demo"
|
package="com.google.android.exoplayer2.demo"
|
||||||
android:versionCode="2101"
|
android:versionCode="2100"
|
||||||
android:versionName="2.1.1">
|
android:versionName="2.1.0">
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
|
@ -183,53 +183,29 @@
|
|||||||
"uri": "https://storage.googleapis.com/wvmedia/clear/vp9/tears/tears_uhd.mpd"
|
"uri": "https://storage.googleapis.com/wvmedia/clear/vp9/tears/tears_uhd.mpd"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "WV: Secure Fullsample SD & HD (WebM,VP9)",
|
"name": "WV: Secure SD & HD (WebM,VP9)",
|
||||||
"uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/tears/tears.mpd",
|
"uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/tears/tears.mpd",
|
||||||
"drm_scheme": "widevine",
|
"drm_scheme": "widevine",
|
||||||
"drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
|
"drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "WV: Secure Fullsample SD (WebM,VP9)",
|
"name": "WV: Secure SD (WebM,VP9)",
|
||||||
"uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/tears/tears_sd.mpd",
|
"uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/tears/tears_sd.mpd",
|
||||||
"drm_scheme": "widevine",
|
"drm_scheme": "widevine",
|
||||||
"drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
|
"drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "WV: Secure Fullsample HD (WebM,VP9)",
|
"name": "WV: Secure HD (WebM,VP9)",
|
||||||
"uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/tears/tears_hd.mpd",
|
"uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/tears/tears_hd.mpd",
|
||||||
"drm_scheme": "widevine",
|
"drm_scheme": "widevine",
|
||||||
"drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
|
"drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "WV: Secure Fullsample UHD (WebM,VP9)",
|
"name": "WV: Secure UHD (WebM,VP9)",
|
||||||
"uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/tears/tears_uhd.mpd",
|
"uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/tears/tears_uhd.mpd",
|
||||||
"drm_scheme": "widevine",
|
"drm_scheme": "widevine",
|
||||||
"drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
|
"drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "WV: Secure Subsample SD & HD (WebM,VP9)",
|
|
||||||
"uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/subsample/24fps/tears/tears.mpd",
|
|
||||||
"drm_scheme": "widevine",
|
|
||||||
"drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "WV: Secure Subsample SD (WebM,VP9)",
|
|
||||||
"uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/subsample/24fps/tears/tears_sd.mpd",
|
|
||||||
"drm_scheme": "widevine",
|
|
||||||
"drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "WV: Secure Subsample HD (WebM,VP9)",
|
|
||||||
"uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/subsample/24fps/tears/tears_hd.mpd",
|
|
||||||
"drm_scheme": "widevine",
|
|
||||||
"drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "WV: Secure Subsample UHD (WebM,VP9)",
|
|
||||||
"uri": "https://storage.googleapis.com/wvmedia/cenc/vp9/subsample/24fps/tears/tears_uhd.mpd",
|
|
||||||
"drm_scheme": "widevine",
|
|
||||||
"drm_license_url": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "WV: Secure Subsample (WebM, VP9 with altref)",
|
"name": "WV: Secure Subsample (WebM, VP9 with altref)",
|
||||||
"uri": "https://storage.googleapis.com/widevine_test/vp9/sintel_1080p_vp9_altref_subsample/sintel_1080p_vp9_altref_subsample.mpd",
|
"uri": "https://storage.googleapis.com/widevine_test/vp9/sintel_1080p_vp9_altref_subsample/sintel_1080p_vp9_altref_subsample.mpd",
|
||||||
|
@ -31,7 +31,6 @@ import com.google.android.exoplayer2.upstream.Allocator;
|
|||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import java.util.concurrent.TimeoutException;
|
import java.util.concurrent.TimeoutException;
|
||||||
@ -49,25 +48,12 @@ public final class ExoPlayerTest extends TestCase {
|
|||||||
*/
|
*/
|
||||||
private static final int TIMEOUT_MS = 10000;
|
private static final int TIMEOUT_MS = 10000;
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests playback of a source that exposes a single period.
|
|
||||||
*/
|
|
||||||
public void testPlayToEnd() throws Exception {
|
public void testPlayToEnd() throws Exception {
|
||||||
PlayerWrapper playerWrapper = new PlayerWrapper();
|
PlayerWrapper playerWrapper = new PlayerWrapper();
|
||||||
Format format = Format.createVideoSampleFormat(null, MimeTypes.VIDEO_H264, null,
|
Format format = Format.createVideoSampleFormat(null, MimeTypes.VIDEO_H264, null,
|
||||||
Format.NO_VALUE, Format.NO_VALUE, 1280, 720, Format.NO_VALUE, null, null);
|
Format.NO_VALUE, Format.NO_VALUE, 1280, 720, Format.NO_VALUE, null, null);
|
||||||
playerWrapper.setup(new SinglePeriodTimeline(0, false), null, format);
|
playerWrapper.setup(new SinglePeriodTimeline(0, false), new Object(), format);
|
||||||
playerWrapper.blockUntilEnded(TIMEOUT_MS);
|
playerWrapper.blockUntilEndedOrError(TIMEOUT_MS);
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Tests playback of a source that exposes an empty timeline. Playback is expected to end without
|
|
||||||
* error.
|
|
||||||
*/
|
|
||||||
public void testPlayEmptyTimeline() throws Exception {
|
|
||||||
PlayerWrapper playerWrapper = new PlayerWrapper();
|
|
||||||
playerWrapper.setup(Timeline.EMPTY, null, null);
|
|
||||||
playerWrapper.blockUntilEnded(TIMEOUT_MS);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -84,6 +70,7 @@ public final class ExoPlayerTest extends TestCase {
|
|||||||
private Format expectedFormat;
|
private Format expectedFormat;
|
||||||
private ExoPlayer player;
|
private ExoPlayer player;
|
||||||
private Exception exception;
|
private Exception exception;
|
||||||
|
private boolean seenPositionDiscontinuity;
|
||||||
|
|
||||||
public PlayerWrapper() {
|
public PlayerWrapper() {
|
||||||
endedCountDownLatch = new CountDownLatch(1);
|
endedCountDownLatch = new CountDownLatch(1);
|
||||||
@ -94,11 +81,12 @@ public final class ExoPlayerTest extends TestCase {
|
|||||||
|
|
||||||
// Called on the test thread.
|
// Called on the test thread.
|
||||||
|
|
||||||
public void blockUntilEnded(long timeoutMs) throws Exception {
|
public void blockUntilEndedOrError(long timeoutMs) throws Exception {
|
||||||
if (!endedCountDownLatch.await(timeoutMs, TimeUnit.MILLISECONDS)) {
|
if (!endedCountDownLatch.await(timeoutMs, TimeUnit.MILLISECONDS)) {
|
||||||
exception = new TimeoutException("Test playback timed out.");
|
exception = new TimeoutException("Test playback timed out.");
|
||||||
}
|
}
|
||||||
release();
|
release();
|
||||||
|
|
||||||
// Throw any pending exception (from playback, timing out or releasing).
|
// Throw any pending exception (from playback, timing out or releasing).
|
||||||
if (exception != null) {
|
if (exception != null) {
|
||||||
throw exception;
|
throw exception;
|
||||||
@ -120,7 +108,7 @@ public final class ExoPlayerTest extends TestCase {
|
|||||||
player.setPlayWhenReady(true);
|
player.setPlayWhenReady(true);
|
||||||
player.prepare(new FakeMediaSource(timeline, manifest, format));
|
player.prepare(new FakeMediaSource(timeline, manifest, format));
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
handleError(e);
|
handlePlayerException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
@ -135,7 +123,7 @@ public final class ExoPlayerTest extends TestCase {
|
|||||||
player.release();
|
player.release();
|
||||||
}
|
}
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
handleError(e);
|
handlePlayerException(e);
|
||||||
} finally {
|
} finally {
|
||||||
playerThread.quit();
|
playerThread.quit();
|
||||||
}
|
}
|
||||||
@ -144,7 +132,7 @@ public final class ExoPlayerTest extends TestCase {
|
|||||||
playerThread.join();
|
playerThread.join();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleError(Exception exception) {
|
private void handlePlayerException(Exception exception) {
|
||||||
if (this.exception == null) {
|
if (this.exception == null) {
|
||||||
this.exception = exception;
|
this.exception = exception;
|
||||||
}
|
}
|
||||||
@ -179,13 +167,20 @@ public final class ExoPlayerTest extends TestCase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlayerError(ExoPlaybackException exception) {
|
public void onPlayerError(ExoPlaybackException exception) {
|
||||||
handleError(exception);
|
this.exception = exception;
|
||||||
|
endedCountDownLatch.countDown();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPositionDiscontinuity() {
|
public void onPositionDiscontinuity() {
|
||||||
// Should never happen.
|
assertFalse(seenPositionDiscontinuity);
|
||||||
handleError(new IllegalStateException("Received position discontinuity"));
|
assertEquals(0, player.getCurrentWindowIndex());
|
||||||
|
assertEquals(0, player.getCurrentPeriodIndex());
|
||||||
|
assertEquals(0, player.getCurrentPosition());
|
||||||
|
assertEquals(0, player.getBufferedPosition());
|
||||||
|
assertEquals(expectedTimeline, player.getCurrentTimeline());
|
||||||
|
assertEquals(expectedManifest, player.getCurrentManifest());
|
||||||
|
seenPositionDiscontinuity = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -199,16 +194,17 @@ public final class ExoPlayerTest extends TestCase {
|
|||||||
private final Timeline timeline;
|
private final Timeline timeline;
|
||||||
private final Object manifest;
|
private final Object manifest;
|
||||||
private final Format format;
|
private final Format format;
|
||||||
private final ArrayList<FakeMediaPeriod> activeMediaPeriods;
|
|
||||||
|
|
||||||
|
private FakeMediaPeriod mediaPeriod;
|
||||||
private boolean preparedSource;
|
private boolean preparedSource;
|
||||||
|
private boolean releasedPeriod;
|
||||||
private boolean releasedSource;
|
private boolean releasedSource;
|
||||||
|
|
||||||
public FakeMediaSource(Timeline timeline, Object manifest, Format format) {
|
public FakeMediaSource(Timeline timeline, Object manifest, Format format) {
|
||||||
|
Assertions.checkArgument(timeline.getPeriodCount() == 1);
|
||||||
this.timeline = timeline;
|
this.timeline = timeline;
|
||||||
this.manifest = manifest;
|
this.manifest = manifest;
|
||||||
this.format = format;
|
this.format = format;
|
||||||
activeMediaPeriods = new ArrayList<>();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -225,29 +221,33 @@ public final class ExoPlayerTest extends TestCase {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
|
public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
|
||||||
Assertions.checkIndex(index, 0, timeline.getPeriodCount());
|
|
||||||
assertTrue(preparedSource);
|
assertTrue(preparedSource);
|
||||||
|
assertNull(mediaPeriod);
|
||||||
|
assertFalse(releasedPeriod);
|
||||||
assertFalse(releasedSource);
|
assertFalse(releasedSource);
|
||||||
assertEquals(0, index);
|
assertEquals(0, index);
|
||||||
assertEquals(0, positionUs);
|
assertEquals(0, positionUs);
|
||||||
FakeMediaPeriod mediaPeriod = new FakeMediaPeriod(format);
|
mediaPeriod = new FakeMediaPeriod(format);
|
||||||
activeMediaPeriods.add(mediaPeriod);
|
|
||||||
return mediaPeriod;
|
return mediaPeriod;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void releasePeriod(MediaPeriod mediaPeriod) {
|
public void releasePeriod(MediaPeriod mediaPeriod) {
|
||||||
assertTrue(preparedSource);
|
assertTrue(preparedSource);
|
||||||
|
assertNotNull(this.mediaPeriod);
|
||||||
|
assertFalse(releasedPeriod);
|
||||||
assertFalse(releasedSource);
|
assertFalse(releasedSource);
|
||||||
assertTrue(activeMediaPeriods.remove(mediaPeriod));
|
assertEquals(this.mediaPeriod, mediaPeriod);
|
||||||
((FakeMediaPeriod) mediaPeriod).release();
|
this.mediaPeriod.release();
|
||||||
|
releasedPeriod = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void releaseSource() {
|
public void releaseSource() {
|
||||||
assertTrue(preparedSource);
|
assertTrue(preparedSource);
|
||||||
|
assertNotNull(this.mediaPeriod);
|
||||||
|
assertTrue(releasedPeriod);
|
||||||
assertFalse(releasedSource);
|
assertFalse(releasedSource);
|
||||||
assertTrue(activeMediaPeriods.isEmpty());
|
|
||||||
releasedSource = true;
|
releasedSource = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -400,6 +400,7 @@ public final class ExoPlayerTest extends TestCase {
|
|||||||
|
|
||||||
public FakeVideoRenderer(Format expectedFormat) {
|
public FakeVideoRenderer(Format expectedFormat) {
|
||||||
super(C.TRACK_TYPE_VIDEO);
|
super(C.TRACK_TYPE_VIDEO);
|
||||||
|
Assertions.checkArgument(MimeTypes.isVideo(expectedFormat.sampleMimeType));
|
||||||
this.expectedFormat = expectedFormat;
|
this.expectedFormat = expectedFormat;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -267,12 +267,6 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
|
|||||||
return streamIsFinal ? C.RESULT_BUFFER_READ : C.RESULT_NOTHING_READ;
|
return streamIsFinal ? C.RESULT_BUFFER_READ : C.RESULT_NOTHING_READ;
|
||||||
}
|
}
|
||||||
buffer.timeUs += streamOffsetUs;
|
buffer.timeUs += streamOffsetUs;
|
||||||
} else if (result == C.RESULT_FORMAT_READ) {
|
|
||||||
Format format = formatHolder.format;
|
|
||||||
if (format.subsampleOffsetUs != Format.OFFSET_SAMPLE_RELATIVE) {
|
|
||||||
format = format.copyWithSubsampleOffsetUs(format.subsampleOffsetUs + streamOffsetUs);
|
|
||||||
formatHolder.format = format;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
@ -96,13 +96,6 @@ public final class C {
|
|||||||
@SuppressWarnings("InlinedApi")
|
@SuppressWarnings("InlinedApi")
|
||||||
public static final int CRYPTO_MODE_AES_CBC = MediaCodec.CRYPTO_MODE_AES_CBC;
|
public static final int CRYPTO_MODE_AES_CBC = MediaCodec.CRYPTO_MODE_AES_CBC;
|
||||||
|
|
||||||
/**
|
|
||||||
* Represents an unset {@link android.media.AudioTrack} session identifier. Equal to
|
|
||||||
* {@link AudioManager#AUDIO_SESSION_ID_GENERATE}.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("InlinedApi")
|
|
||||||
public static final int AUDIO_SESSION_ID_UNSET = AudioManager.AUDIO_SESSION_ID_GENERATE;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents an audio encoding, or an invalid or unset value.
|
* Represents an audio encoding, or an invalid or unset value.
|
||||||
*/
|
*/
|
||||||
|
@ -332,10 +332,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||||||
case ExoPlayerImplInternal.MSG_SEEK_ACK: {
|
case ExoPlayerImplInternal.MSG_SEEK_ACK: {
|
||||||
if (--pendingSeekAcks == 0) {
|
if (--pendingSeekAcks == 0) {
|
||||||
playbackInfo = (ExoPlayerImplInternal.PlaybackInfo) msg.obj;
|
playbackInfo = (ExoPlayerImplInternal.PlaybackInfo) msg.obj;
|
||||||
if (msg.arg1 != 0) {
|
for (EventListener listener : listeners) {
|
||||||
for (EventListener listener : listeners) {
|
listener.onPositionDiscontinuity();
|
||||||
listener.onPositionDiscontinuity();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
|
@ -559,7 +559,7 @@ import java.io.IOException;
|
|||||||
// The seek position was valid for the timeline that it was performed into, but the
|
// The seek position was valid for the timeline that it was performed into, but the
|
||||||
// timeline has changed and a suitable seek position could not be resolved in the new one.
|
// timeline has changed and a suitable seek position could not be resolved in the new one.
|
||||||
playbackInfo = new PlaybackInfo(0, 0);
|
playbackInfo = new PlaybackInfo(0, 0);
|
||||||
eventHandler.obtainMessage(MSG_SEEK_ACK, 1, 0, playbackInfo).sendToTarget();
|
eventHandler.obtainMessage(MSG_SEEK_ACK, playbackInfo).sendToTarget();
|
||||||
// Set the internal position to (0,TIME_UNSET) so that a subsequent seek to (0,0) isn't
|
// Set the internal position to (0,TIME_UNSET) so that a subsequent seek to (0,0) isn't
|
||||||
// ignored.
|
// ignored.
|
||||||
playbackInfo = new PlaybackInfo(0, C.TIME_UNSET);
|
playbackInfo = new PlaybackInfo(0, C.TIME_UNSET);
|
||||||
@ -569,7 +569,6 @@ import java.io.IOException;
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean seekPositionAdjusted = seekPosition.windowPositionUs == C.TIME_UNSET;
|
|
||||||
int periodIndex = periodPosition.first;
|
int periodIndex = periodPosition.first;
|
||||||
long periodPositionUs = periodPosition.second;
|
long periodPositionUs = periodPosition.second;
|
||||||
|
|
||||||
@ -579,13 +578,10 @@ import java.io.IOException;
|
|||||||
// Seek position equals the current position. Do nothing.
|
// Seek position equals the current position. Do nothing.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
long newPeriodPositionUs = seekToPeriodPosition(periodIndex, periodPositionUs);
|
periodPositionUs = seekToPeriodPosition(periodIndex, periodPositionUs);
|
||||||
seekPositionAdjusted |= periodPositionUs != newPeriodPositionUs;
|
|
||||||
periodPositionUs = newPeriodPositionUs;
|
|
||||||
} finally {
|
} finally {
|
||||||
playbackInfo = new PlaybackInfo(periodIndex, periodPositionUs);
|
playbackInfo = new PlaybackInfo(periodIndex, periodPositionUs);
|
||||||
eventHandler.obtainMessage(MSG_SEEK_ACK, seekPositionAdjusted ? 1 : 0, 0, playbackInfo)
|
eventHandler.obtainMessage(MSG_SEEK_ACK, playbackInfo).sendToTarget();
|
||||||
.sendToTarget();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -680,7 +676,6 @@ import java.io.IOException;
|
|||||||
standaloneMediaClock.stop();
|
standaloneMediaClock.stop();
|
||||||
rendererMediaClock = null;
|
rendererMediaClock = null;
|
||||||
rendererMediaClockSource = null;
|
rendererMediaClockSource = null;
|
||||||
rendererPositionUs = RENDERER_TIMESTAMP_OFFSET_US;
|
|
||||||
for (Renderer renderer : enabledRenderers) {
|
for (Renderer renderer : enabledRenderers) {
|
||||||
try {
|
try {
|
||||||
ensureStopped(renderer);
|
ensureStopped(renderer);
|
||||||
@ -828,6 +823,9 @@ import java.io.IOException;
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean haveSufficientBuffer(boolean rebuffering) {
|
private boolean haveSufficientBuffer(boolean rebuffering) {
|
||||||
|
if (loadingPeriodHolder == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
long loadingPeriodBufferedPositionUs = !loadingPeriodHolder.prepared
|
long loadingPeriodBufferedPositionUs = !loadingPeriodHolder.prepared
|
||||||
? loadingPeriodHolder.startPositionUs
|
? loadingPeriodHolder.startPositionUs
|
||||||
: loadingPeriodHolder.mediaPeriod.getBufferedPositionUs();
|
: loadingPeriodHolder.mediaPeriod.getBufferedPositionUs();
|
||||||
@ -1289,8 +1287,7 @@ import java.io.IOException;
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void maybeContinueLoading() {
|
private void maybeContinueLoading() {
|
||||||
long nextLoadPositionUs = !loadingPeriodHolder.prepared ? 0
|
long nextLoadPositionUs = loadingPeriodHolder.mediaPeriod.getNextLoadPositionUs();
|
||||||
: loadingPeriodHolder.mediaPeriod.getNextLoadPositionUs();
|
|
||||||
if (nextLoadPositionUs == C.TIME_END_OF_SOURCE) {
|
if (nextLoadPositionUs == C.TIME_END_OF_SOURCE) {
|
||||||
setIsLoading(false);
|
setIsLoading(false);
|
||||||
} else {
|
} else {
|
||||||
|
@ -23,7 +23,7 @@ public interface ExoPlayerLibraryInfo {
|
|||||||
/**
|
/**
|
||||||
* The version of the library, expressed as a string.
|
* The version of the library, expressed as a string.
|
||||||
*/
|
*/
|
||||||
String VERSION = "2.1.1";
|
String VERSION = "2.1.0";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The version of the library, expressed as an integer.
|
* The version of the library, expressed as an integer.
|
||||||
@ -32,7 +32,7 @@ public interface ExoPlayerLibraryInfo {
|
|||||||
* corresponding integer version 1002003 (001-002-003), and "123.45.6" has the corresponding
|
* corresponding integer version 1002003 (001-002-003), and "123.45.6" has the corresponding
|
||||||
* integer version 123045006 (123-045-006).
|
* integer version 123045006 (123-045-006).
|
||||||
*/
|
*/
|
||||||
int VERSION_INT = 2001001;
|
int VERSION_INT = 2001000;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}
|
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}
|
||||||
|
@ -29,6 +29,7 @@ import android.view.SurfaceView;
|
|||||||
import android.view.TextureView;
|
import android.view.TextureView;
|
||||||
import com.google.android.exoplayer2.audio.AudioCapabilities;
|
import com.google.android.exoplayer2.audio.AudioCapabilities;
|
||||||
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
|
import com.google.android.exoplayer2.audio.AudioRendererEventListener;
|
||||||
|
import com.google.android.exoplayer2.audio.AudioTrack;
|
||||||
import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer;
|
import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer;
|
||||||
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
import com.google.android.exoplayer2.decoder.DecoderCounters;
|
||||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||||
@ -177,7 +178,7 @@ public class SimpleExoPlayer implements ExoPlayer {
|
|||||||
|
|
||||||
// Set initial values.
|
// Set initial values.
|
||||||
audioVolume = 1;
|
audioVolume = 1;
|
||||||
audioSessionId = C.AUDIO_SESSION_ID_UNSET;
|
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
|
||||||
audioStreamType = C.STREAM_TYPE_DEFAULT;
|
audioStreamType = C.STREAM_TYPE_DEFAULT;
|
||||||
videoScalingMode = C.VIDEO_SCALING_MODE_DEFAULT;
|
videoScalingMode = C.VIDEO_SCALING_MODE_DEFAULT;
|
||||||
|
|
||||||
@ -392,7 +393,7 @@ public class SimpleExoPlayer implements ExoPlayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the audio session identifier, or {@link C#AUDIO_SESSION_ID_UNSET} if not set.
|
* Returns the audio session identifier, or {@code AudioTrack.SESSION_ID_NOT_SET} if not set.
|
||||||
*/
|
*/
|
||||||
public int getAudioSessionId() {
|
public int getAudioSessionId() {
|
||||||
return audioSessionId;
|
return audioSessionId;
|
||||||
@ -948,7 +949,7 @@ public class SimpleExoPlayer implements ExoPlayer {
|
|||||||
}
|
}
|
||||||
audioFormat = null;
|
audioFormat = null;
|
||||||
audioDecoderCounters = null;
|
audioDecoderCounters = null;
|
||||||
audioSessionId = C.AUDIO_SESSION_ID_UNSET;
|
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TextRenderer.Output implementation
|
// TextRenderer.Output implementation
|
||||||
|
@ -17,7 +17,6 @@ package com.google.android.exoplayer2.audio;
|
|||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
import android.annotation.TargetApi;
|
import android.annotation.TargetApi;
|
||||||
import android.media.AudioAttributes;
|
|
||||||
import android.media.AudioFormat;
|
import android.media.AudioFormat;
|
||||||
import android.media.AudioTimestamp;
|
import android.media.AudioTimestamp;
|
||||||
import android.media.PlaybackParams;
|
import android.media.PlaybackParams;
|
||||||
@ -31,28 +30,26 @@ import com.google.android.exoplayer2.util.MimeTypes;
|
|||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Plays audio data. The implementation delegates to an {@link android.media.AudioTrack} and handles
|
* Plays audio data. The implementation delegates to an {@link android.media.AudioTrack} and handles
|
||||||
* playback position smoothing, non-blocking writes and reconfiguration.
|
* playback position smoothing, non-blocking writes and reconfiguration.
|
||||||
* <p>
|
* <p>
|
||||||
* Before starting playback, specify the input format by calling
|
* Before starting playback, specify the input format by calling
|
||||||
* {@link #configure(String, int, int, int, int)}. Next call {@link #initialize(int)} or
|
* {@link #configure(String, int, int, int, int)}. Next call {@link #initialize(int)}, optionally
|
||||||
* {@link #initializeV21(int, boolean)}, optionally specifying an audio session and whether the
|
* specifying an audio session.
|
||||||
* track is to be used with tunneling video playback.
|
|
||||||
* <p>
|
* <p>
|
||||||
* Call {@link #handleBuffer(ByteBuffer, long)} to write data, and {@link #handleDiscontinuity()}
|
* Call {@link #handleBuffer(ByteBuffer, long)} to write data, and {@link #handleDiscontinuity()}
|
||||||
* when the data being fed is discontinuous. Call {@link #play()} to start playing the written data.
|
* when the data being fed is discontinuous. Call {@link #play()} to start playing the written data.
|
||||||
* <p>
|
* <p>
|
||||||
* Call {@link #configure(String, int, int, int, int)} whenever the input format changes. If
|
* Call {@link #configure(String, int, int, int, int)} whenever the input format changes. If
|
||||||
* {@link #isInitialized()} returns {@code false} after the call, it is necessary to call
|
* {@link #isInitialized()} returns {@code false} after the call, it is necessary to call
|
||||||
* {@link #initialize(int)} or {@link #initializeV21(int, boolean)} before writing more data.
|
* {@link #initialize(int)} before writing more data.
|
||||||
* <p>
|
* <p>
|
||||||
* The underlying {@link android.media.AudioTrack} is created by {@link #initialize(int)} and
|
* The underlying {@link android.media.AudioTrack} is created by {@link #initialize(int)} and
|
||||||
* released by {@link #reset()} (and {@link #configure(String, int, int, int, int)} unless the input
|
* released by {@link #reset()} (and {@link #configure(String, int, int, int, int)} unless the input
|
||||||
* format is unchanged). It is safe to call {@link #initialize(int)} or
|
* format is unchanged). It is safe to call {@link #initialize(int)} after calling {@link #reset()}
|
||||||
* {@link #initializeV21(int, boolean)} after calling {@link #reset()} without reconfiguration.
|
* without reconfiguration.
|
||||||
* <p>
|
* <p>
|
||||||
* Call {@link #release()} when the instance is no longer required.
|
* Call {@link #release()} when the instance is no longer required.
|
||||||
*/
|
*/
|
||||||
@ -146,6 +143,11 @@ public final class AudioTrack {
|
|||||||
*/
|
*/
|
||||||
public static final int RESULT_BUFFER_CONSUMED = 2;
|
public static final int RESULT_BUFFER_CONSUMED = 2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents an unset {@link android.media.AudioTrack} session identifier.
|
||||||
|
*/
|
||||||
|
public static final int SESSION_ID_NOT_SET = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returned by {@link #getCurrentPositionUs} when the position is not set.
|
* Returned by {@link #getCurrentPositionUs} when the position is not set.
|
||||||
*/
|
*/
|
||||||
@ -271,10 +273,6 @@ public final class AudioTrack {
|
|||||||
private int bufferSize;
|
private int bufferSize;
|
||||||
private long bufferSizeUs;
|
private long bufferSizeUs;
|
||||||
|
|
||||||
private boolean useHwAvSync;
|
|
||||||
private ByteBuffer avSyncHeader;
|
|
||||||
private int bytesUntilNextAvSync;
|
|
||||||
|
|
||||||
private int nextPlayheadOffsetIndex;
|
private int nextPlayheadOffsetIndex;
|
||||||
private int playheadOffsetCount;
|
private int playheadOffsetCount;
|
||||||
private long smoothedPlayheadOffsetUs;
|
private long smoothedPlayheadOffsetUs;
|
||||||
@ -343,8 +341,8 @@ public final class AudioTrack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the audio track has been successfully initialized via {@link #initialize} or
|
* Returns whether the audio track has been successfully initialized via {@link #initialize} and
|
||||||
* {@link #initializeV21(int, boolean)}, and has not yet been {@link #reset}.
|
* not yet {@link #reset}.
|
||||||
*/
|
*/
|
||||||
public boolean isInitialized() {
|
public boolean isInitialized() {
|
||||||
return audioTrack != null;
|
return audioTrack != null;
|
||||||
@ -444,21 +442,6 @@ public final class AudioTrack {
|
|||||||
throw new IllegalArgumentException("Unsupported channel count: " + channelCount);
|
throw new IllegalArgumentException("Unsupported channel count: " + channelCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Workaround for overly strict channel configuration checks on nVidia Shield.
|
|
||||||
if (Util.SDK_INT <= 23 && "foster".equals(Util.DEVICE) && "NVIDIA".equals(Util.MANUFACTURER)) {
|
|
||||||
switch(channelCount) {
|
|
||||||
case 7:
|
|
||||||
channelConfig = C.CHANNEL_OUT_7POINT1_SURROUND;
|
|
||||||
break;
|
|
||||||
case 3:
|
|
||||||
case 5:
|
|
||||||
channelConfig = AudioFormat.CHANNEL_OUT_5POINT1;
|
|
||||||
break;
|
|
||||||
default:
|
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
boolean passthrough = !MimeTypes.AUDIO_RAW.equals(mimeType);
|
boolean passthrough = !MimeTypes.AUDIO_RAW.equals(mimeType);
|
||||||
@C.Encoding int sourceEncoding;
|
@C.Encoding int sourceEncoding;
|
||||||
if (passthrough) {
|
if (passthrough) {
|
||||||
@ -515,28 +498,11 @@ public final class AudioTrack {
|
|||||||
/**
|
/**
|
||||||
* Initializes the audio track for writing new buffers using {@link #handleBuffer}.
|
* Initializes the audio track for writing new buffers using {@link #handleBuffer}.
|
||||||
*
|
*
|
||||||
* @param sessionId Audio track session identifier, or {@link C#AUDIO_SESSION_ID_UNSET} to create
|
* @param sessionId Audio track session identifier to re-use, or {@link #SESSION_ID_NOT_SET} to
|
||||||
* one.
|
* create a new one.
|
||||||
* @return The audio track session identifier.
|
* @return The new (or re-used) session identifier.
|
||||||
*/
|
*/
|
||||||
public int initialize(int sessionId) throws InitializationException {
|
public int initialize(int sessionId) throws InitializationException {
|
||||||
return initializeInternal(sessionId, false);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Initializes the audio track for writing new buffers using {@link #handleBuffer}.
|
|
||||||
*
|
|
||||||
* @param sessionId Audio track session identifier, or {@link C#AUDIO_SESSION_ID_UNSET} to create
|
|
||||||
* one.
|
|
||||||
* @param tunneling Whether the audio track is to be used with tunneling video playback.
|
|
||||||
* @return The audio track session identifier.
|
|
||||||
*/
|
|
||||||
public int initializeV21(int sessionId, boolean tunneling) throws InitializationException {
|
|
||||||
Assertions.checkState(Util.SDK_INT >= 21);
|
|
||||||
return initializeInternal(sessionId, tunneling);
|
|
||||||
}
|
|
||||||
|
|
||||||
private int initializeInternal(int sessionId, boolean tunneling) throws InitializationException {
|
|
||||||
// If we're asynchronously releasing a previous audio track then we block until it has been
|
// If we're asynchronously releasing a previous audio track then we block until it has been
|
||||||
// released. This guarantees that we cannot end up in a state where we have multiple audio
|
// released. This guarantees that we cannot end up in a state where we have multiple audio
|
||||||
// track instances. Without this guarantee it would be possible, in extreme cases, to exhaust
|
// track instances. Without this guarantee it would be possible, in extreme cases, to exhaust
|
||||||
@ -544,11 +510,7 @@ public final class AudioTrack {
|
|||||||
// initialization of the audio track to fail.
|
// initialization of the audio track to fail.
|
||||||
releasingConditionVariable.block();
|
releasingConditionVariable.block();
|
||||||
|
|
||||||
useHwAvSync = tunneling;
|
if (sessionId == SESSION_ID_NOT_SET) {
|
||||||
if (useHwAvSync) {
|
|
||||||
audioTrack = createHwAvSyncAudioTrackV21(sampleRate, channelConfig, targetEncoding,
|
|
||||||
bufferSize, sessionId);
|
|
||||||
} else if (sessionId == C.AUDIO_SESSION_ID_UNSET) {
|
|
||||||
audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig,
|
audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig,
|
||||||
targetEncoding, bufferSize, MODE_STREAM);
|
targetEncoding, bufferSize, MODE_STREAM);
|
||||||
} else {
|
} else {
|
||||||
@ -730,9 +692,7 @@ public final class AudioTrack {
|
|||||||
buffer.position(buffer.position() + bytesWritten);
|
buffer.position(buffer.position() + bytesWritten);
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
bytesWritten = useHwAvSync
|
bytesWritten = writeNonBlockingV21(audioTrack, buffer, bytesRemaining);
|
||||||
? writeNonBlockingWithAvSyncV21(audioTrack, buffer, bytesRemaining, presentationTimeUs)
|
|
||||||
: writeNonBlockingV21(audioTrack, buffer, bytesRemaining);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
if (bytesWritten < 0) {
|
if (bytesWritten < 0) {
|
||||||
@ -758,7 +718,6 @@ public final class AudioTrack {
|
|||||||
public void handleEndOfStream() {
|
public void handleEndOfStream() {
|
||||||
if (isInitialized()) {
|
if (isInitialized()) {
|
||||||
audioTrackUtil.handleEndOfStream(getSubmittedFrames());
|
audioTrackUtil.handleEndOfStream(getSubmittedFrames());
|
||||||
bytesUntilNextAvSync = 0;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -784,27 +743,19 @@ public final class AudioTrack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the stream type for audio track. If the stream type has changed and if the audio track
|
* Sets the stream type for audio track. If the stream type has changed, {@link #isInitialized()}
|
||||||
* is not configured for use with video tunneling, then the audio track is reset and the caller
|
* will return {@code false} and the caller must re-{@link #initialize(int)} the audio track
|
||||||
* must re-initialize the audio track before writing more data. The caller must not reuse the
|
* before writing more data. The caller must not reuse the audio session identifier when
|
||||||
* audio session identifier when re-initializing with a new stream type.
|
* re-initializing with a new stream type.
|
||||||
* <p>
|
|
||||||
* If the audio track is configured for use with video tunneling then the stream type is ignored
|
|
||||||
* and the audio track is not reset. The passed stream type will be used if the audio track is
|
|
||||||
* later re-configured into non-tunneled mode.
|
|
||||||
*
|
*
|
||||||
* @param streamType The {@link C.StreamType} to use for audio output.
|
* @param streamType The {@link C.StreamType} to use for audio output.
|
||||||
* @return Whether the audio track was reset as a result of this call.
|
* @return Whether the stream type changed.
|
||||||
*/
|
*/
|
||||||
public boolean setStreamType(@C.StreamType int streamType) {
|
public boolean setStreamType(@C.StreamType int streamType) {
|
||||||
if (this.streamType == streamType) {
|
if (this.streamType == streamType) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
this.streamType = streamType;
|
this.streamType = streamType;
|
||||||
if (useHwAvSync) {
|
|
||||||
// The stream type is ignored in tunneling mode, so no need to reset.
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
reset();
|
reset();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
@ -844,9 +795,9 @@ public final class AudioTrack {
|
|||||||
/**
|
/**
|
||||||
* Releases the underlying audio track asynchronously.
|
* Releases the underlying audio track asynchronously.
|
||||||
* <p>
|
* <p>
|
||||||
* Calling {@link #initialize(int)} or {@link #initializeV21(int, boolean)} will block until the
|
* Calling {@link #initialize(int)} will block until the audio track has been released, so it is
|
||||||
* audio track has been released, so it is safe to initialize immediately after a reset. The audio
|
* safe to initialize immediately after a reset. The audio session may remain active until
|
||||||
* session may remain active until {@link #release()} is called.
|
* {@link #release()} is called.
|
||||||
*/
|
*/
|
||||||
public void reset() {
|
public void reset() {
|
||||||
if (isInitialized()) {
|
if (isInitialized()) {
|
||||||
@ -854,7 +805,6 @@ public final class AudioTrack {
|
|||||||
submittedEncodedFrames = 0;
|
submittedEncodedFrames = 0;
|
||||||
framesPerEncodedSample = 0;
|
framesPerEncodedSample = 0;
|
||||||
currentSourceBuffer = null;
|
currentSourceBuffer = null;
|
||||||
avSyncHeader = null;
|
|
||||||
startMediaTimeState = START_NOT_SET;
|
startMediaTimeState = START_NOT_SET;
|
||||||
latencyUs = 0;
|
latencyUs = 0;
|
||||||
resetSyncParams();
|
resetSyncParams();
|
||||||
@ -1070,26 +1020,6 @@ public final class AudioTrack {
|
|||||||
&& audioTrack.getPlaybackHeadPosition() == 0;
|
&& audioTrack.getPlaybackHeadPosition() == 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Instantiates an {@link android.media.AudioTrack} to be used with tunneling video playback.
|
|
||||||
*/
|
|
||||||
@TargetApi(21)
|
|
||||||
private static android.media.AudioTrack createHwAvSyncAudioTrackV21(int sampleRate,
|
|
||||||
int channelConfig, int encoding, int bufferSize, int sessionId) {
|
|
||||||
AudioAttributes attributesBuilder = new AudioAttributes.Builder()
|
|
||||||
.setUsage(AudioAttributes.USAGE_MEDIA)
|
|
||||||
.setContentType(AudioAttributes.CONTENT_TYPE_MOVIE)
|
|
||||||
.setFlags(AudioAttributes.FLAG_HW_AV_SYNC)
|
|
||||||
.build();
|
|
||||||
AudioFormat format = new AudioFormat.Builder()
|
|
||||||
.setChannelMask(channelConfig)
|
|
||||||
.setEncoding(encoding)
|
|
||||||
.setSampleRate(sampleRate)
|
|
||||||
.build();
|
|
||||||
return new android.media.AudioTrack(attributesBuilder, format, bufferSize, MODE_STREAM,
|
|
||||||
sessionId);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the provided buffer into 16-bit PCM.
|
* Converts the provided buffer into 16-bit PCM.
|
||||||
*
|
*
|
||||||
@ -1195,50 +1125,11 @@ public final class AudioTrack {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(21)
|
@TargetApi(21)
|
||||||
private static int writeNonBlockingV21(android.media.AudioTrack audioTrack, ByteBuffer buffer,
|
private static int writeNonBlockingV21(
|
||||||
int size) {
|
android.media.AudioTrack audioTrack, ByteBuffer buffer, int size) {
|
||||||
return audioTrack.write(buffer, size, WRITE_NON_BLOCKING);
|
return audioTrack.write(buffer, size, WRITE_NON_BLOCKING);
|
||||||
}
|
}
|
||||||
|
|
||||||
@TargetApi(21)
|
|
||||||
private int writeNonBlockingWithAvSyncV21(android.media.AudioTrack audioTrack,
|
|
||||||
ByteBuffer buffer, int size, long presentationTimeUs) {
|
|
||||||
// TODO: Uncomment this when [Internal ref b/33627517] is clarified or fixed.
|
|
||||||
// if (Util.SDK_INT >= 23) {
|
|
||||||
// // The underlying platform AudioTrack writes AV sync headers directly.
|
|
||||||
// return audioTrack.write(buffer, size, WRITE_NON_BLOCKING, presentationTimeUs * 1000);
|
|
||||||
// }
|
|
||||||
if (avSyncHeader == null) {
|
|
||||||
avSyncHeader = ByteBuffer.allocate(16);
|
|
||||||
avSyncHeader.order(ByteOrder.BIG_ENDIAN);
|
|
||||||
avSyncHeader.putInt(0x55550001);
|
|
||||||
}
|
|
||||||
if (bytesUntilNextAvSync == 0) {
|
|
||||||
avSyncHeader.putInt(4, size);
|
|
||||||
avSyncHeader.putLong(8, presentationTimeUs * 1000);
|
|
||||||
avSyncHeader.position(0);
|
|
||||||
bytesUntilNextAvSync = size;
|
|
||||||
}
|
|
||||||
int avSyncHeaderBytesRemaining = avSyncHeader.remaining();
|
|
||||||
if (avSyncHeaderBytesRemaining > 0) {
|
|
||||||
int result = audioTrack.write(avSyncHeader, avSyncHeaderBytesRemaining, WRITE_NON_BLOCKING);
|
|
||||||
if (result < 0) {
|
|
||||||
bytesUntilNextAvSync = 0;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
if (result < avSyncHeaderBytesRemaining) {
|
|
||||||
return 0;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
int result = writeNonBlockingV21(audioTrack, buffer, size);
|
|
||||||
if (result < 0) {
|
|
||||||
bytesUntilNextAvSync = 0;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
bytesUntilNextAvSync -= result;
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
@TargetApi(21)
|
@TargetApi(21)
|
||||||
private static void setAudioTrackVolumeV21(android.media.AudioTrack audioTrack, float volume) {
|
private static void setAudioTrackVolumeV21(android.media.AudioTrack audioTrack, float volume) {
|
||||||
audioTrack.setVolume(volume);
|
audioTrack.setVolume(volume);
|
||||||
|
@ -129,7 +129,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||||||
boolean playClearSamplesWithoutKeys, Handler eventHandler,
|
boolean playClearSamplesWithoutKeys, Handler eventHandler,
|
||||||
AudioRendererEventListener eventListener, AudioCapabilities audioCapabilities) {
|
AudioRendererEventListener eventListener, AudioCapabilities audioCapabilities) {
|
||||||
super(C.TRACK_TYPE_AUDIO, mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys);
|
super(C.TRACK_TYPE_AUDIO, mediaCodecSelector, drmSessionManager, playClearSamplesWithoutKeys);
|
||||||
audioSessionId = C.AUDIO_SESSION_ID_UNSET;
|
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
|
||||||
audioTrack = new AudioTrack(audioCapabilities, this);
|
audioTrack = new AudioTrack(audioCapabilities, this);
|
||||||
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
|
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
|
||||||
}
|
}
|
||||||
@ -274,7 +274,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onDisabled() {
|
protected void onDisabled() {
|
||||||
audioSessionId = C.AUDIO_SESSION_ID_UNSET;
|
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
|
||||||
try {
|
try {
|
||||||
audioTrack.release();
|
audioTrack.release();
|
||||||
} finally {
|
} finally {
|
||||||
@ -328,8 +328,8 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||||||
if (!audioTrack.isInitialized()) {
|
if (!audioTrack.isInitialized()) {
|
||||||
// Initialize the AudioTrack now.
|
// Initialize the AudioTrack now.
|
||||||
try {
|
try {
|
||||||
if (audioSessionId == C.AUDIO_SESSION_ID_UNSET) {
|
if (audioSessionId == AudioTrack.SESSION_ID_NOT_SET) {
|
||||||
audioSessionId = audioTrack.initialize(C.AUDIO_SESSION_ID_UNSET);
|
audioSessionId = audioTrack.initialize(AudioTrack.SESSION_ID_NOT_SET);
|
||||||
eventDispatcher.audioSessionId(audioSessionId);
|
eventDispatcher.audioSessionId(audioSessionId);
|
||||||
onAudioSessionId(audioSessionId);
|
onAudioSessionId(audioSessionId);
|
||||||
} else {
|
} else {
|
||||||
@ -387,7 +387,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||||||
case C.MSG_SET_STREAM_TYPE:
|
case C.MSG_SET_STREAM_TYPE:
|
||||||
@C.StreamType int streamType = (Integer) message;
|
@C.StreamType int streamType = (Integer) message;
|
||||||
if (audioTrack.setStreamType(streamType)) {
|
if (audioTrack.setStreamType(streamType)) {
|
||||||
audioSessionId = C.AUDIO_SESSION_ID_UNSET;
|
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -145,7 +145,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
|
|||||||
this.drmSessionManager = drmSessionManager;
|
this.drmSessionManager = drmSessionManager;
|
||||||
formatHolder = new FormatHolder();
|
formatHolder = new FormatHolder();
|
||||||
this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys;
|
this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys;
|
||||||
audioSessionId = C.AUDIO_SESSION_ID_UNSET;
|
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
|
||||||
decoderReinitializationState = REINITIALIZATION_STATE_NONE;
|
decoderReinitializationState = REINITIALIZATION_STATE_NONE;
|
||||||
audioTrackNeedsConfigure = true;
|
audioTrackNeedsConfigure = true;
|
||||||
}
|
}
|
||||||
@ -245,8 +245,8 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (!audioTrack.isInitialized()) {
|
if (!audioTrack.isInitialized()) {
|
||||||
if (audioSessionId == C.AUDIO_SESSION_ID_UNSET) {
|
if (audioSessionId == AudioTrack.SESSION_ID_NOT_SET) {
|
||||||
audioSessionId = audioTrack.initialize(C.AUDIO_SESSION_ID_UNSET);
|
audioSessionId = audioTrack.initialize(AudioTrack.SESSION_ID_NOT_SET);
|
||||||
eventDispatcher.audioSessionId(audioSessionId);
|
eventDispatcher.audioSessionId(audioSessionId);
|
||||||
onAudioSessionId(audioSessionId);
|
onAudioSessionId(audioSessionId);
|
||||||
} else {
|
} else {
|
||||||
@ -425,7 +425,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
|
|||||||
@Override
|
@Override
|
||||||
protected void onDisabled() {
|
protected void onDisabled() {
|
||||||
inputFormat = null;
|
inputFormat = null;
|
||||||
audioSessionId = C.AUDIO_SESSION_ID_UNSET;
|
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
|
||||||
audioTrackNeedsConfigure = true;
|
audioTrackNeedsConfigure = true;
|
||||||
waitingForKeys = false;
|
waitingForKeys = false;
|
||||||
try {
|
try {
|
||||||
@ -554,7 +554,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
|
|||||||
case C.MSG_SET_STREAM_TYPE:
|
case C.MSG_SET_STREAM_TYPE:
|
||||||
@C.StreamType int streamType = (Integer) message;
|
@C.StreamType int streamType = (Integer) message;
|
||||||
if (audioTrack.setStreamType(streamType)) {
|
if (audioTrack.setStreamType(streamType)) {
|
||||||
audioSessionId = C.AUDIO_SESSION_ID_UNSET;
|
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
|
@ -244,7 +244,7 @@ import java.io.IOException;
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public long getNextLoadPositionUs() {
|
public long getNextLoadPositionUs() {
|
||||||
return enabledTrackCount == 0 ? C.TIME_END_OF_SOURCE : getBufferedPositionUs();
|
return getBufferedPositionUs();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -133,32 +133,4 @@ public interface MediaPeriod extends SequenceableLoader {
|
|||||||
*/
|
*/
|
||||||
long seekToUs(long positionUs);
|
long seekToUs(long positionUs);
|
||||||
|
|
||||||
// SequenceableLoader interface. Overridden to provide more specific documentation.
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the next load time, or {@link C#TIME_END_OF_SOURCE} if loading has finished.
|
|
||||||
* <p>
|
|
||||||
* This method should only be called after the period has been prepared. It may be called when no
|
|
||||||
* tracks are selected.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
long getNextLoadPositionUs();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to continue loading.
|
|
||||||
* <p>
|
|
||||||
* This method may be called both during and after the period has been prepared.
|
|
||||||
* <p>
|
|
||||||
* A period may call {@link Callback#onContinueLoadingRequested(SequenceableLoader)} on the
|
|
||||||
* {@link Callback} passed to {@link #prepare(Callback)} to request that this method be called
|
|
||||||
* when the period is permitted to continue loading data. A period may do this both during and
|
|
||||||
* after preparation.
|
|
||||||
*
|
|
||||||
* @param positionUs The current playback position.
|
|
||||||
* @return True if progress was made, meaning that {@link #getNextLoadPositionUs()} will return
|
|
||||||
* a different value than prior to the call. False otherwise.
|
|
||||||
*/
|
|
||||||
@Override
|
|
||||||
boolean continueLoading(long positionUs);
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -104,14 +104,15 @@ public final class HlsMediaSource implements MediaSource,
|
|||||||
SinglePeriodTimeline timeline;
|
SinglePeriodTimeline timeline;
|
||||||
if (playlistTracker.isLive()) {
|
if (playlistTracker.isLive()) {
|
||||||
// TODO: fix windowPositionInPeriodUs when playlist is empty.
|
// TODO: fix windowPositionInPeriodUs when playlist is empty.
|
||||||
|
long windowPositionInPeriodUs = playlist.startTimeUs;
|
||||||
List<HlsMediaPlaylist.Segment> segments = playlist.segments;
|
List<HlsMediaPlaylist.Segment> segments = playlist.segments;
|
||||||
long windowDefaultStartPositionUs = segments.isEmpty() ? 0
|
long windowDefaultStartPositionUs = segments.isEmpty() ? 0
|
||||||
: segments.get(Math.max(0, segments.size() - 3)).relativeStartTimeUs;
|
: segments.get(Math.max(0, segments.size() - 3)).relativeStartTimeUs;
|
||||||
timeline = new SinglePeriodTimeline(C.TIME_UNSET, playlist.durationUs,
|
timeline = new SinglePeriodTimeline(C.TIME_UNSET, playlist.durationUs,
|
||||||
playlist.startTimeUs, windowDefaultStartPositionUs, true, !playlist.hasEndTag);
|
windowPositionInPeriodUs, windowDefaultStartPositionUs, true, !playlist.hasEndTag);
|
||||||
} else /* not live */ {
|
} else /* not live */ {
|
||||||
timeline = new SinglePeriodTimeline(playlist.startTimeUs + playlist.durationUs,
|
timeline = new SinglePeriodTimeline(playlist.durationUs, playlist.durationUs, 0, 0, true,
|
||||||
playlist.durationUs, playlist.startTimeUs, 0, true, false);
|
false);
|
||||||
}
|
}
|
||||||
sourceListener.onSourceInfoRefreshed(timeline, playlist);
|
sourceListener.onSourceInfoRefreshed(timeline, playlist);
|
||||||
}
|
}
|
||||||
|
@ -15,7 +15,6 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.text;
|
package com.google.android.exoplayer2.text;
|
||||||
|
|
||||||
import android.graphics.Color;
|
|
||||||
import android.support.annotation.IntDef;
|
import android.support.annotation.IntDef;
|
||||||
import android.text.Layout.Alignment;
|
import android.text.Layout.Alignment;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
@ -37,23 +36,19 @@ public class Cue {
|
|||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@IntDef({TYPE_UNSET, ANCHOR_TYPE_START, ANCHOR_TYPE_MIDDLE, ANCHOR_TYPE_END})
|
@IntDef({TYPE_UNSET, ANCHOR_TYPE_START, ANCHOR_TYPE_MIDDLE, ANCHOR_TYPE_END})
|
||||||
public @interface AnchorType {}
|
public @interface AnchorType {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An unset anchor or line type value.
|
* An unset anchor or line type value.
|
||||||
*/
|
*/
|
||||||
public static final int TYPE_UNSET = Integer.MIN_VALUE;
|
public static final int TYPE_UNSET = Integer.MIN_VALUE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Anchors the left (for horizontal positions) or top (for vertical positions) edge of the cue
|
* Anchors the left (for horizontal positions) or top (for vertical positions) edge of the cue
|
||||||
* box.
|
* box.
|
||||||
*/
|
*/
|
||||||
public static final int ANCHOR_TYPE_START = 0;
|
public static final int ANCHOR_TYPE_START = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Anchors the middle of the cue box.
|
* Anchors the middle of the cue box.
|
||||||
*/
|
*/
|
||||||
public static final int ANCHOR_TYPE_MIDDLE = 1;
|
public static final int ANCHOR_TYPE_MIDDLE = 1;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Anchors the right (for horizontal positions) or bottom (for vertical positions) edge of the cue
|
* Anchors the right (for horizontal positions) or bottom (for vertical positions) edge of the cue
|
||||||
* box.
|
* box.
|
||||||
@ -66,12 +61,10 @@ public class Cue {
|
|||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@IntDef({TYPE_UNSET, LINE_TYPE_FRACTION, LINE_TYPE_NUMBER})
|
@IntDef({TYPE_UNSET, LINE_TYPE_FRACTION, LINE_TYPE_NUMBER})
|
||||||
public @interface LineType {}
|
public @interface LineType {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Value for {@link #lineType} when {@link #line} is a fractional position.
|
* Value for {@link #lineType} when {@link #line} is a fractional position.
|
||||||
*/
|
*/
|
||||||
public static final int LINE_TYPE_FRACTION = 0;
|
public static final int LINE_TYPE_FRACTION = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Value for {@link #lineType} when {@link #line} is a line number.
|
* Value for {@link #lineType} when {@link #line} is a line number.
|
||||||
*/
|
*/
|
||||||
@ -81,12 +74,10 @@ public class Cue {
|
|||||||
* The cue text. Note the {@link CharSequence} may be decorated with styling spans.
|
* The cue text. Note the {@link CharSequence} may be decorated with styling spans.
|
||||||
*/
|
*/
|
||||||
public final CharSequence text;
|
public final CharSequence text;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The alignment of the cue text within the cue box, or null if the alignment is undefined.
|
* The alignment of the cue text within the cue box, or null if the alignment is undefined.
|
||||||
*/
|
*/
|
||||||
public final Alignment textAlignment;
|
public final Alignment textAlignment;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The position of the {@link #lineAnchor} of the cue box within the viewport in the direction
|
* The position of the {@link #lineAnchor} of the cue box within the viewport in the direction
|
||||||
* orthogonal to the writing direction, or {@link #DIMEN_UNSET}. When set, the interpretation of
|
* orthogonal to the writing direction, or {@link #DIMEN_UNSET}. When set, the interpretation of
|
||||||
@ -95,7 +86,6 @@ public class Cue {
|
|||||||
* For horizontal text and {@link #lineType} equal to {@link #LINE_TYPE_FRACTION}, this is the
|
* For horizontal text and {@link #lineType} equal to {@link #LINE_TYPE_FRACTION}, this is the
|
||||||
* fractional vertical position relative to the top of the viewport.
|
* fractional vertical position relative to the top of the viewport.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
public final float line;
|
public final float line;
|
||||||
/**
|
/**
|
||||||
* The type of the {@link #line} value.
|
* The type of the {@link #line} value.
|
||||||
@ -122,7 +112,6 @@ public class Cue {
|
|||||||
* {@code (line == -2 && lineAnchor == ANCHOR_TYPE_START)} position a cue so that only its first
|
* {@code (line == -2 && lineAnchor == ANCHOR_TYPE_START)} position a cue so that only its first
|
||||||
* line is visible at the bottom of the viewport.
|
* line is visible at the bottom of the viewport.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@LineType
|
@LineType
|
||||||
public final int lineType;
|
public final int lineType;
|
||||||
/**
|
/**
|
||||||
@ -133,7 +122,6 @@ public class Cue {
|
|||||||
* and {@link #ANCHOR_TYPE_END} correspond to the top, middle and bottom of the cue box
|
* and {@link #ANCHOR_TYPE_END} correspond to the top, middle and bottom of the cue box
|
||||||
* respectively.
|
* respectively.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
@AnchorType
|
@AnchorType
|
||||||
public final int lineAnchor;
|
public final int lineAnchor;
|
||||||
/**
|
/**
|
||||||
@ -145,7 +133,6 @@ public class Cue {
|
|||||||
* text.
|
* text.
|
||||||
*/
|
*/
|
||||||
public final float position;
|
public final float position;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The cue box anchor positioned by {@link #position}. One of {@link #ANCHOR_TYPE_START},
|
* The cue box anchor positioned by {@link #position}. One of {@link #ANCHOR_TYPE_START},
|
||||||
* {@link #ANCHOR_TYPE_MIDDLE}, {@link #ANCHOR_TYPE_END} and {@link #TYPE_UNSET}.
|
* {@link #ANCHOR_TYPE_MIDDLE}, {@link #ANCHOR_TYPE_END} and {@link #TYPE_UNSET}.
|
||||||
@ -156,23 +143,12 @@ public class Cue {
|
|||||||
*/
|
*/
|
||||||
@AnchorType
|
@AnchorType
|
||||||
public final int positionAnchor;
|
public final int positionAnchor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The size of the cue box in the writing direction specified as a fraction of the viewport size
|
* The size of the cue box in the writing direction specified as a fraction of the viewport size
|
||||||
* in that direction, or {@link #DIMEN_UNSET}.
|
* in that direction, or {@link #DIMEN_UNSET}.
|
||||||
*/
|
*/
|
||||||
public final float size;
|
public final float size;
|
||||||
|
|
||||||
/**
|
|
||||||
* Specifies whether or not the {@link #windowColor} property is set.
|
|
||||||
*/
|
|
||||||
public final boolean windowColorSet;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The fill color of the window.
|
|
||||||
*/
|
|
||||||
public final int windowColor;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a cue whose {@link #textAlignment} is null, whose type parameters are set to
|
* Constructs a cue whose {@link #textAlignment} is null, whose type parameters are set to
|
||||||
* {@link #TYPE_UNSET} and whose dimension parameters are set to {@link #DIMEN_UNSET}.
|
* {@link #TYPE_UNSET} and whose dimension parameters are set to {@link #DIMEN_UNSET}.
|
||||||
@ -195,25 +171,6 @@ public class Cue {
|
|||||||
*/
|
*/
|
||||||
public Cue(CharSequence text, Alignment textAlignment, float line, @LineType int lineType,
|
public Cue(CharSequence text, Alignment textAlignment, float line, @LineType int lineType,
|
||||||
@AnchorType int lineAnchor, float position, @AnchorType int positionAnchor, float size) {
|
@AnchorType int lineAnchor, float position, @AnchorType int positionAnchor, float size) {
|
||||||
this(text, textAlignment, line, lineType, lineAnchor, position, positionAnchor, size, false,
|
|
||||||
Color.BLACK);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @param text See {@link #text}.
|
|
||||||
* @param textAlignment See {@link #textAlignment}.
|
|
||||||
* @param line See {@link #line}.
|
|
||||||
* @param lineType See {@link #lineType}.
|
|
||||||
* @param lineAnchor See {@link #lineAnchor}.
|
|
||||||
* @param position See {@link #position}.
|
|
||||||
* @param positionAnchor See {@link #positionAnchor}.
|
|
||||||
* @param size See {@link #size}.
|
|
||||||
* @param windowColorSet See {@link #windowColorSet}.
|
|
||||||
* @param windowColor See {@link #windowColor}.
|
|
||||||
*/
|
|
||||||
public Cue(CharSequence text, Alignment textAlignment, float line, @LineType int lineType,
|
|
||||||
@AnchorType int lineAnchor, float position, @AnchorType int positionAnchor, float size,
|
|
||||||
boolean windowColorSet, int windowColor) {
|
|
||||||
this.text = text;
|
this.text = text;
|
||||||
this.textAlignment = textAlignment;
|
this.textAlignment = textAlignment;
|
||||||
this.line = line;
|
this.line = line;
|
||||||
@ -222,8 +179,6 @@ public class Cue {
|
|||||||
this.position = position;
|
this.position = position;
|
||||||
this.positionAnchor = positionAnchor;
|
this.positionAnchor = positionAnchor;
|
||||||
this.size = size;
|
this.size = size;
|
||||||
this.windowColorSet = windowColorSet;
|
|
||||||
this.windowColor = windowColor;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -65,13 +65,6 @@ import java.util.List;
|
|||||||
* <li>Default: {@code true}</li>
|
* <li>Default: {@code true}</li>
|
||||||
* </ul>
|
* </ul>
|
||||||
* </li>
|
* </li>
|
||||||
* <li><b>{@code default_artwork}</b> - Default artwork to use if no artwork available in audio
|
|
||||||
* streams.
|
|
||||||
* <ul>
|
|
||||||
* <li>Corresponding method: {@link #setDefaultArtwork(Bitmap)}</li>
|
|
||||||
* <li>Default: {@code null}</li>
|
|
||||||
* </ul>
|
|
||||||
* </li>
|
|
||||||
* <li><b>{@code use_controller}</b> - Whether playback controls are displayed.
|
* <li><b>{@code use_controller}</b> - Whether playback controls are displayed.
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>Corresponding method: {@link #setUseController(boolean)}</li>
|
* <li>Corresponding method: {@link #setUseController(boolean)}</li>
|
||||||
@ -186,7 +179,6 @@ public final class SimpleExoPlayerView extends FrameLayout {
|
|||||||
private SimpleExoPlayer player;
|
private SimpleExoPlayer player;
|
||||||
private boolean useController;
|
private boolean useController;
|
||||||
private boolean useArtwork;
|
private boolean useArtwork;
|
||||||
private Bitmap defaultArtwork;
|
|
||||||
private int controllerShowTimeoutMs;
|
private int controllerShowTimeoutMs;
|
||||||
|
|
||||||
public SimpleExoPlayerView(Context context) {
|
public SimpleExoPlayerView(Context context) {
|
||||||
@ -202,7 +194,6 @@ public final class SimpleExoPlayerView extends FrameLayout {
|
|||||||
|
|
||||||
int playerLayoutId = R.layout.exo_simple_player_view;
|
int playerLayoutId = R.layout.exo_simple_player_view;
|
||||||
boolean useArtwork = true;
|
boolean useArtwork = true;
|
||||||
int defaultArtwork = 0;
|
|
||||||
boolean useController = true;
|
boolean useController = true;
|
||||||
int surfaceType = SURFACE_TYPE_SURFACE_VIEW;
|
int surfaceType = SURFACE_TYPE_SURFACE_VIEW;
|
||||||
int resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT;
|
int resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT;
|
||||||
@ -214,8 +205,6 @@ public final class SimpleExoPlayerView extends FrameLayout {
|
|||||||
playerLayoutId = a.getResourceId(R.styleable.SimpleExoPlayerView_player_layout_id,
|
playerLayoutId = a.getResourceId(R.styleable.SimpleExoPlayerView_player_layout_id,
|
||||||
playerLayoutId);
|
playerLayoutId);
|
||||||
useArtwork = a.getBoolean(R.styleable.SimpleExoPlayerView_use_artwork, useArtwork);
|
useArtwork = a.getBoolean(R.styleable.SimpleExoPlayerView_use_artwork, useArtwork);
|
||||||
defaultArtwork = a.getResourceId(R.styleable.SimpleExoPlayerView_default_artwork,
|
|
||||||
defaultArtwork);
|
|
||||||
useController = a.getBoolean(R.styleable.SimpleExoPlayerView_use_controller, useController);
|
useController = a.getBoolean(R.styleable.SimpleExoPlayerView_use_controller, useController);
|
||||||
surfaceType = a.getInt(R.styleable.SimpleExoPlayerView_surface_type, surfaceType);
|
surfaceType = a.getInt(R.styleable.SimpleExoPlayerView_surface_type, surfaceType);
|
||||||
resizeMode = a.getInt(R.styleable.SimpleExoPlayerView_resize_mode, resizeMode);
|
resizeMode = a.getInt(R.styleable.SimpleExoPlayerView_resize_mode, resizeMode);
|
||||||
@ -257,9 +246,6 @@ public final class SimpleExoPlayerView extends FrameLayout {
|
|||||||
// Artwork view.
|
// Artwork view.
|
||||||
artworkView = (ImageView) findViewById(R.id.exo_artwork);
|
artworkView = (ImageView) findViewById(R.id.exo_artwork);
|
||||||
this.useArtwork = useArtwork && artworkView != null;
|
this.useArtwork = useArtwork && artworkView != null;
|
||||||
if (defaultArtwork != 0) {
|
|
||||||
this.defaultArtwork = BitmapFactory.decodeResource(context.getResources(), defaultArtwork);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Subtitle view.
|
// Subtitle view.
|
||||||
subtitleView = (SubtitleView) findViewById(R.id.exo_subtitles);
|
subtitleView = (SubtitleView) findViewById(R.id.exo_subtitles);
|
||||||
@ -365,26 +351,6 @@ public final class SimpleExoPlayerView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the default artwork to display.
|
|
||||||
*/
|
|
||||||
public Bitmap getDefaultArtwork() {
|
|
||||||
return defaultArtwork;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the default artwork to display if {@code useArtwork} is {@code true} and no artwork is
|
|
||||||
* present in the media.
|
|
||||||
*
|
|
||||||
* @param defaultArtwork the default artwork to display.
|
|
||||||
*/
|
|
||||||
public void setDefaultArtwork(Bitmap defaultArtwork) {
|
|
||||||
if (this.defaultArtwork != defaultArtwork) {
|
|
||||||
this.defaultArtwork = defaultArtwork;
|
|
||||||
updateForCurrentTrackSelections();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the playback controls are enabled.
|
* Returns whether the playback controls are enabled.
|
||||||
*/
|
*/
|
||||||
@ -603,9 +569,6 @@ public final class SimpleExoPlayerView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (setArtworkFromBitmap(defaultArtwork)) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
// Artwork disabled or unavailable.
|
// Artwork disabled or unavailable.
|
||||||
hideArtwork();
|
hideArtwork();
|
||||||
@ -617,23 +580,18 @@ public final class SimpleExoPlayerView extends FrameLayout {
|
|||||||
if (metadataEntry instanceof ApicFrame) {
|
if (metadataEntry instanceof ApicFrame) {
|
||||||
byte[] bitmapData = ((ApicFrame) metadataEntry).pictureData;
|
byte[] bitmapData = ((ApicFrame) metadataEntry).pictureData;
|
||||||
Bitmap bitmap = BitmapFactory.decodeByteArray(bitmapData, 0, bitmapData.length);
|
Bitmap bitmap = BitmapFactory.decodeByteArray(bitmapData, 0, bitmapData.length);
|
||||||
return setArtworkFromBitmap(bitmap);
|
if (bitmap != null) {
|
||||||
}
|
int bitmapWidth = bitmap.getWidth();
|
||||||
}
|
int bitmapHeight = bitmap.getHeight();
|
||||||
return false;
|
if (bitmapWidth > 0 && bitmapHeight > 0) {
|
||||||
}
|
if (contentFrame != null) {
|
||||||
|
contentFrame.setAspectRatio((float) bitmapWidth / bitmapHeight);
|
||||||
private boolean setArtworkFromBitmap(Bitmap bitmap) {
|
}
|
||||||
if (bitmap != null) {
|
artworkView.setImageBitmap(bitmap);
|
||||||
int bitmapWidth = bitmap.getWidth();
|
artworkView.setVisibility(VISIBLE);
|
||||||
int bitmapHeight = bitmap.getHeight();
|
return true;
|
||||||
if (bitmapWidth > 0 && bitmapHeight > 0) {
|
}
|
||||||
if (contentFrame != null) {
|
|
||||||
contentFrame.setAspectRatio((float) bitmapWidth / bitmapHeight);
|
|
||||||
}
|
}
|
||||||
artworkView.setImageBitmap(bitmap);
|
|
||||||
artworkView.setVisibility(VISIBLE);
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -146,13 +146,9 @@ import com.google.android.exoplayer2.util.Util;
|
|||||||
// Nothing to draw.
|
// Nothing to draw.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
int windowColor = cue.windowColorSet ? cue.windowColor : style.windowColor;
|
|
||||||
|
|
||||||
if (!applyEmbeddedStyles) {
|
if (!applyEmbeddedStyles) {
|
||||||
// Strip out any embedded styling.
|
// Strip out any embedded styling.
|
||||||
cueText = cueText.toString();
|
cueText = cueText.toString();
|
||||||
windowColor = style.windowColor;
|
|
||||||
}
|
}
|
||||||
if (areCharSequencesEqual(this.cueText, cueText)
|
if (areCharSequencesEqual(this.cueText, cueText)
|
||||||
&& Util.areEqual(this.cueTextAlignment, cue.textAlignment)
|
&& Util.areEqual(this.cueTextAlignment, cue.textAlignment)
|
||||||
@ -165,7 +161,7 @@ import com.google.android.exoplayer2.util.Util;
|
|||||||
&& this.applyEmbeddedStyles == applyEmbeddedStyles
|
&& this.applyEmbeddedStyles == applyEmbeddedStyles
|
||||||
&& this.foregroundColor == style.foregroundColor
|
&& this.foregroundColor == style.foregroundColor
|
||||||
&& this.backgroundColor == style.backgroundColor
|
&& this.backgroundColor == style.backgroundColor
|
||||||
&& this.windowColor == windowColor
|
&& this.windowColor == style.windowColor
|
||||||
&& this.edgeType == style.edgeType
|
&& this.edgeType == style.edgeType
|
||||||
&& this.edgeColor == style.edgeColor
|
&& this.edgeColor == style.edgeColor
|
||||||
&& Util.areEqual(this.textPaint.getTypeface(), style.typeface)
|
&& Util.areEqual(this.textPaint.getTypeface(), style.typeface)
|
||||||
@ -191,7 +187,7 @@ import com.google.android.exoplayer2.util.Util;
|
|||||||
this.applyEmbeddedStyles = applyEmbeddedStyles;
|
this.applyEmbeddedStyles = applyEmbeddedStyles;
|
||||||
this.foregroundColor = style.foregroundColor;
|
this.foregroundColor = style.foregroundColor;
|
||||||
this.backgroundColor = style.backgroundColor;
|
this.backgroundColor = style.backgroundColor;
|
||||||
this.windowColor = windowColor;
|
this.windowColor = style.windowColor;
|
||||||
this.edgeType = style.edgeType;
|
this.edgeType = style.edgeType;
|
||||||
this.edgeColor = style.edgeColor;
|
this.edgeColor = style.edgeColor;
|
||||||
this.textPaint.setTypeface(style.typeface);
|
this.textPaint.setTypeface(style.typeface);
|
||||||
|
@ -403,7 +403,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
|||||||
bufferPresentationTimeUs, unadjustedFrameReleaseTimeNs);
|
bufferPresentationTimeUs, unadjustedFrameReleaseTimeNs);
|
||||||
earlyUs = (adjustedReleaseTimeNs - systemTimeNs) / 1000;
|
earlyUs = (adjustedReleaseTimeNs - systemTimeNs) / 1000;
|
||||||
|
|
||||||
if (shouldDropOutputBuffer(earlyUs, elapsedRealtimeUs)) {
|
if (earlyUs < -30000) {
|
||||||
// We're more than 30ms late rendering the frame.
|
// We're more than 30ms late rendering the frame.
|
||||||
dropOutputBuffer(codec, bufferIndex);
|
dropOutputBuffer(codec, bufferIndex);
|
||||||
return true;
|
return true;
|
||||||
@ -437,17 +437,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns true if the current frame should be dropped.
|
|
||||||
*
|
|
||||||
* @param earlyUs Time indicating how early the frame is. Negative values indicate late frame.
|
|
||||||
* @param elapsedRealtimeUs Wall clock time.
|
|
||||||
*/
|
|
||||||
protected boolean shouldDropOutputBuffer(long earlyUs, long elapsedRealtimeUs) {
|
|
||||||
// Drop the frame if we're more than 30ms late rendering the frame.
|
|
||||||
return earlyUs < -30000;
|
|
||||||
}
|
|
||||||
|
|
||||||
private void skipOutputBuffer(MediaCodec codec, int bufferIndex) {
|
private void skipOutputBuffer(MediaCodec codec, int bufferIndex) {
|
||||||
TraceUtil.beginSection("skipVideoBuffer");
|
TraceUtil.beginSection("skipVideoBuffer");
|
||||||
codec.releaseOutputBuffer(bufferIndex, false);
|
codec.releaseOutputBuffer(bufferIndex, false);
|
||||||
|
Before Width: | Height: | Size: 354 B After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 323 B After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 108 B After Width: | Height: | Size: 599 B |
Before Width: | Height: | Size: 286 B After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 292 B After Width: | Height: | Size: 1.0 KiB |
Before Width: | Height: | Size: 347 B After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 192 B After Width: | Height: | Size: 886 B |
Before Width: | Height: | Size: 167 B After Width: | Height: | Size: 735 B |
Before Width: | Height: | Size: 91 B After Width: | Height: | Size: 3.2 KiB |
Before Width: | Height: | Size: 182 B After Width: | Height: | Size: 673 B |
Before Width: | Height: | Size: 187 B After Width: | Height: | Size: 770 B |
Before Width: | Height: | Size: 214 B After Width: | Height: | Size: 906 B |
Before Width: | Height: | Size: 255 B After Width: | Height: | Size: 929 B |
Before Width: | Height: | Size: 276 B After Width: | Height: | Size: 843 B |
Before Width: | Height: | Size: 153 B After Width: | Height: | Size: 540 B |
Before Width: | Height: | Size: 228 B After Width: | Height: | Size: 897 B |
Before Width: | Height: | Size: 227 B After Width: | Height: | Size: 837 B |
Before Width: | Height: | Size: 273 B After Width: | Height: | Size: 997 B |
@ -1,8 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="32dp"
|
|
||||||
android:height="32dp"
|
|
||||||
android:viewportHeight="24.0"
|
|
||||||
android:viewportWidth="24.0">
|
|
||||||
|
|
||||||
<path android:fillColor="#FFF" android:pathData="M4,18l8.5,-6L4,6v12zM13,6v12l8.5,-6L13,6z"/>
|
|
||||||
</vector>
|
|
@ -1,8 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="32dp"
|
|
||||||
android:height="32dp"
|
|
||||||
android:viewportHeight="24.0"
|
|
||||||
android:viewportWidth="24.0">
|
|
||||||
|
|
||||||
<path android:fillColor="#FFF" android:pathData="M6,18l8.5,-6L6,6v12zM16,6v12h2V6h-2z"/>
|
|
||||||
</vector>
|
|
@ -1,8 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="32dp"
|
|
||||||
android:height="32dp"
|
|
||||||
android:viewportHeight="24.0"
|
|
||||||
android:viewportWidth="24.0">
|
|
||||||
|
|
||||||
<path android:fillColor="#FFF" android:pathData="M6,19h4L10,5L6,5v14zM14,5v14h4L18,5h-4z"/>
|
|
||||||
</vector>
|
|
@ -1,8 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="32dp"
|
|
||||||
android:height="32dp"
|
|
||||||
android:viewportHeight="24.0"
|
|
||||||
android:viewportWidth="24.0">
|
|
||||||
|
|
||||||
<path android:fillColor="#FFF" android:pathData="M8,5v14l11,-7z"/>
|
|
||||||
</vector>
|
|
@ -1,8 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="32dp"
|
|
||||||
android:height="32dp"
|
|
||||||
android:viewportHeight="24.0"
|
|
||||||
android:viewportWidth="24.0">
|
|
||||||
|
|
||||||
<path android:fillColor="#FFF" android:pathData="M6,6h2v12L6,18zM9.5,12l8.5,6L18,6z"/>
|
|
||||||
</vector>
|
|
@ -1,8 +0,0 @@
|
|||||||
<vector xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
android:width="32dp"
|
|
||||||
android:height="32dp"
|
|
||||||
android:viewportHeight="24.0"
|
|
||||||
android:viewportWidth="24.0">
|
|
||||||
|
|
||||||
<path android:fillColor="#FFF" android:pathData="M11,18L11,6l-8.5,6 8.5,6zM11.5,12l8.5,6L20,6l-8.5,6z"/>
|
|
||||||
</vector>
|
|
Before Width: | Height: | Size: 392 B After Width: | Height: | Size: 1.4 KiB |
Before Width: | Height: | Size: 334 B After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 164 B After Width: | Height: | Size: 685 B |
Before Width: | Height: | Size: 343 B After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 339 B After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 400 B After Width: | Height: | Size: 1.6 KiB |
Before Width: | Height: | Size: 584 B After Width: | Height: | Size: 1.1 KiB |
Before Width: | Height: | Size: 391 B After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 113 B After Width: | Height: | Size: 611 B |
Before Width: | Height: | Size: 384 B After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 464 B After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 571 B After Width: | Height: | Size: 1.2 KiB |
Before Width: | Height: | Size: 712 B |
Before Width: | Height: | Size: 532 B |
Before Width: | Height: | Size: 188 B |
Before Width: | Height: | Size: 484 B |
Before Width: | Height: | Size: 522 B |
Before Width: | Height: | Size: 690 B |
@ -37,7 +37,6 @@
|
|||||||
|
|
||||||
<declare-styleable name="SimpleExoPlayerView">
|
<declare-styleable name="SimpleExoPlayerView">
|
||||||
<attr name="use_artwork" format="boolean"/>
|
<attr name="use_artwork" format="boolean"/>
|
||||||
<attr name="default_artwork" format="reference"/>
|
|
||||||
<attr name="use_controller" format="boolean"/>
|
<attr name="use_controller" format="boolean"/>
|
||||||
<attr name="surface_type"/>
|
<attr name="surface_type"/>
|
||||||
<attr name="show_timeout"/>
|
<attr name="show_timeout"/>
|
||||||
|