Commit 71186ef1 by cblay Committed by Oliver Woodman

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
parent da9c10a1
Showing with 102 additions and 439 deletions
# 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 { ...@@ -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 @@ ...@@ -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,54 +183,30 @@ ...@@ -183,54 +183,30 @@
"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",
"drm_scheme": "widevine", "drm_scheme": "widevine",
......
...@@ -31,7 +31,6 @@ import com.google.android.exoplayer2.upstream.Allocator; ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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;
} }
......
...@@ -97,13 +97,6 @@ public final class C { ...@@ -97,13 +97,6 @@ public final class C {
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.
*/ */
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
......
...@@ -332,12 +332,10 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -332,12 +332,10 @@ 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;
} }
case ExoPlayerImplInternal.MSG_POSITION_DISCONTINUITY: { case ExoPlayerImplInternal.MSG_POSITION_DISCONTINUITY: {
......
...@@ -559,7 +559,7 @@ import java.io.IOException; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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; ...@@ -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 { ...@@ -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 { ...@@ -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; ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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; ...@@ -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; ...@@ -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.
*/ */
...@@ -147,6 +144,11 @@ public final class AudioTrack { ...@@ -147,6 +144,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.
*/ */
public static final long CURRENT_POSITION_NOT_SET = Long.MIN_VALUE; public static final long CURRENT_POSITION_NOT_SET = Long.MIN_VALUE;
...@@ -271,10 +273,6 @@ public final class AudioTrack { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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();
...@@ -1071,26 +1021,6 @@ public final class AudioTrack { ...@@ -1071,26 +1021,6 @@ public final class AudioTrack {
} }
/** /**
* 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.
* *
* @param buffer The buffer containing the data to convert. * @param buffer The buffer containing the data to convert.
...@@ -1195,51 +1125,12 @@ public final class AudioTrack { ...@@ -1195,51 +1125,12 @@ 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) @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)
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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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 ...@@ -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; ...@@ -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 { ...@@ -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, ...@@ -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 @@ ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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,7 +143,6 @@ public class Cue { ...@@ -156,7 +143,6 @@ 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}.
...@@ -164,16 +150,6 @@ public class Cue { ...@@ -164,16 +150,6 @@ public class Cue {
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 { ...@@ -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 { ...@@ -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; ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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 { ...@@ -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);
...@@ -366,26 +352,6 @@ public final class SimpleExoPlayerView extends FrameLayout { ...@@ -366,26 +352,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.
*/ */
public boolean getUseController() { public boolean getUseController() {
...@@ -603,9 +569,6 @@ public final class SimpleExoPlayerView extends FrameLayout { ...@@ -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,13 +580,6 @@ public final class SimpleExoPlayerView extends FrameLayout { ...@@ -617,13 +580,6 @@ 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);
}
}
return false;
}
private boolean setArtworkFromBitmap(Bitmap bitmap) {
if (bitmap != null) { if (bitmap != null) {
int bitmapWidth = bitmap.getWidth(); int bitmapWidth = bitmap.getWidth();
int bitmapHeight = bitmap.getHeight(); int bitmapHeight = bitmap.getHeight();
...@@ -636,6 +592,8 @@ public final class SimpleExoPlayerView extends FrameLayout { ...@@ -636,6 +592,8 @@ public final class SimpleExoPlayerView extends FrameLayout {
return true; return true;
} }
} }
}
}
return false; return false;
} }
......
...@@ -146,13 +146,9 @@ import com.google.android.exoplayer2.util.Util; ...@@ -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; ...@@ -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; ...@@ -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 { ...@@ -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 { ...@@ -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);
......

354 Bytes | W: | H:

1.09 KB | W: | H:

library/src/main/res/drawable-hdpi/exo_controls_fastforward.png
library/src/main/res/drawable-hdpi/exo_controls_fastforward.png
library/src/main/res/drawable-hdpi/exo_controls_fastforward.png
library/src/main/res/drawable-hdpi/exo_controls_fastforward.png
  • 2-up
  • Swipe
  • Onion skin

323 Bytes | W: | H:

1.05 KB | W: | H:

library/src/main/res/drawable-hdpi/exo_controls_next.png
library/src/main/res/drawable-hdpi/exo_controls_next.png
library/src/main/res/drawable-hdpi/exo_controls_next.png
library/src/main/res/drawable-hdpi/exo_controls_next.png
  • 2-up
  • Swipe
  • Onion skin

108 Bytes | W: | H:

599 Bytes | W: | H:

library/src/main/res/drawable-hdpi/exo_controls_pause.png
library/src/main/res/drawable-hdpi/exo_controls_pause.png
library/src/main/res/drawable-hdpi/exo_controls_pause.png
library/src/main/res/drawable-hdpi/exo_controls_pause.png
  • 2-up
  • Swipe
  • Onion skin

286 Bytes | W: | H:

1.14 KB | W: | H:

library/src/main/res/drawable-hdpi/exo_controls_play.png
library/src/main/res/drawable-hdpi/exo_controls_play.png
library/src/main/res/drawable-hdpi/exo_controls_play.png
library/src/main/res/drawable-hdpi/exo_controls_play.png
  • 2-up
  • Swipe
  • Onion skin

292 Bytes | W: | H:

1.04 KB | W: | H:

library/src/main/res/drawable-hdpi/exo_controls_previous.png
library/src/main/res/drawable-hdpi/exo_controls_previous.png
library/src/main/res/drawable-hdpi/exo_controls_previous.png
library/src/main/res/drawable-hdpi/exo_controls_previous.png
  • 2-up
  • Swipe
  • Onion skin

347 Bytes | W: | H:

1.22 KB | W: | H:

library/src/main/res/drawable-hdpi/exo_controls_rewind.png
library/src/main/res/drawable-hdpi/exo_controls_rewind.png
library/src/main/res/drawable-hdpi/exo_controls_rewind.png
library/src/main/res/drawable-hdpi/exo_controls_rewind.png
  • 2-up
  • Swipe
  • Onion skin

192 Bytes | W: | H:

886 Bytes | W: | H:

library/src/main/res/drawable-ldpi/exo_controls_fastforward.png
library/src/main/res/drawable-ldpi/exo_controls_fastforward.png
library/src/main/res/drawable-ldpi/exo_controls_fastforward.png
library/src/main/res/drawable-ldpi/exo_controls_fastforward.png
  • 2-up
  • Swipe
  • Onion skin

167 Bytes | W: | H:

735 Bytes | W: | H:

library/src/main/res/drawable-ldpi/exo_controls_next.png
library/src/main/res/drawable-ldpi/exo_controls_next.png
library/src/main/res/drawable-ldpi/exo_controls_next.png
library/src/main/res/drawable-ldpi/exo_controls_next.png
  • 2-up
  • Swipe
  • Onion skin

91 Bytes | W: | H:

3.17 KB | W: | H:

library/src/main/res/drawable-ldpi/exo_controls_pause.png
library/src/main/res/drawable-ldpi/exo_controls_pause.png
library/src/main/res/drawable-ldpi/exo_controls_pause.png
library/src/main/res/drawable-ldpi/exo_controls_pause.png
  • 2-up
  • Swipe
  • Onion skin

182 Bytes | W: | H:

673 Bytes | W: | H:

library/src/main/res/drawable-ldpi/exo_controls_play.png
library/src/main/res/drawable-ldpi/exo_controls_play.png
library/src/main/res/drawable-ldpi/exo_controls_play.png
library/src/main/res/drawable-ldpi/exo_controls_play.png
  • 2-up
  • Swipe
  • Onion skin

187 Bytes | W: | H:

770 Bytes | W: | H:

library/src/main/res/drawable-ldpi/exo_controls_previous.png
library/src/main/res/drawable-ldpi/exo_controls_previous.png
library/src/main/res/drawable-ldpi/exo_controls_previous.png
library/src/main/res/drawable-ldpi/exo_controls_previous.png
  • 2-up
  • Swipe
  • Onion skin

214 Bytes | W: | H:

906 Bytes | W: | H:

library/src/main/res/drawable-ldpi/exo_controls_rewind.png
library/src/main/res/drawable-ldpi/exo_controls_rewind.png
library/src/main/res/drawable-ldpi/exo_controls_rewind.png
library/src/main/res/drawable-ldpi/exo_controls_rewind.png
  • 2-up
  • Swipe
  • Onion skin

255 Bytes | W: | H:

929 Bytes | W: | H:

library/src/main/res/drawable-mdpi/exo_controls_fastforward.png
library/src/main/res/drawable-mdpi/exo_controls_fastforward.png
library/src/main/res/drawable-mdpi/exo_controls_fastforward.png
library/src/main/res/drawable-mdpi/exo_controls_fastforward.png
  • 2-up
  • Swipe
  • Onion skin

276 Bytes | W: | H:

843 Bytes | W: | H:

library/src/main/res/drawable-mdpi/exo_controls_next.png
library/src/main/res/drawable-mdpi/exo_controls_next.png
library/src/main/res/drawable-mdpi/exo_controls_next.png
library/src/main/res/drawable-mdpi/exo_controls_next.png
  • 2-up
  • Swipe
  • Onion skin

153 Bytes | W: | H:

540 Bytes | W: | H:

library/src/main/res/drawable-mdpi/exo_controls_pause.png
library/src/main/res/drawable-mdpi/exo_controls_pause.png
library/src/main/res/drawable-mdpi/exo_controls_pause.png
library/src/main/res/drawable-mdpi/exo_controls_pause.png
  • 2-up
  • Swipe
  • Onion skin

228 Bytes | W: | H:

897 Bytes | W: | H:

library/src/main/res/drawable-mdpi/exo_controls_play.png
library/src/main/res/drawable-mdpi/exo_controls_play.png
library/src/main/res/drawable-mdpi/exo_controls_play.png
library/src/main/res/drawable-mdpi/exo_controls_play.png
  • 2-up
  • Swipe
  • Onion skin

227 Bytes | W: | H:

837 Bytes | W: | H:

library/src/main/res/drawable-mdpi/exo_controls_previous.png
library/src/main/res/drawable-mdpi/exo_controls_previous.png
library/src/main/res/drawable-mdpi/exo_controls_previous.png
library/src/main/res/drawable-mdpi/exo_controls_previous.png
  • 2-up
  • Swipe
  • Onion skin

273 Bytes | W: | H:

997 Bytes | W: | H:

library/src/main/res/drawable-mdpi/exo_controls_rewind.png
library/src/main/res/drawable-mdpi/exo_controls_rewind.png
library/src/main/res/drawable-mdpi/exo_controls_rewind.png
library/src/main/res/drawable-mdpi/exo_controls_rewind.png
  • 2-up
  • Swipe
  • Onion skin
<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>
<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>
<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>
<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>
<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>
<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>

392 Bytes | W: | H:

1.44 KB | W: | H:

library/src/main/res/drawable-xhdpi/exo_controls_fastforward.png
library/src/main/res/drawable-xhdpi/exo_controls_fastforward.png
library/src/main/res/drawable-xhdpi/exo_controls_fastforward.png
library/src/main/res/drawable-xhdpi/exo_controls_fastforward.png
  • 2-up
  • Swipe
  • Onion skin

334 Bytes | W: | H:

1.33 KB | W: | H:

library/src/main/res/drawable-xhdpi/exo_controls_next.png
library/src/main/res/drawable-xhdpi/exo_controls_next.png
library/src/main/res/drawable-xhdpi/exo_controls_next.png
library/src/main/res/drawable-xhdpi/exo_controls_next.png
  • 2-up
  • Swipe
  • Onion skin

164 Bytes | W: | H:

685 Bytes | W: | H:

library/src/main/res/drawable-xhdpi/exo_controls_pause.png
library/src/main/res/drawable-xhdpi/exo_controls_pause.png
library/src/main/res/drawable-xhdpi/exo_controls_pause.png
library/src/main/res/drawable-xhdpi/exo_controls_pause.png
  • 2-up
  • Swipe
  • Onion skin

343 Bytes | W: | H:

1.58 KB | W: | H:

library/src/main/res/drawable-xhdpi/exo_controls_play.png
library/src/main/res/drawable-xhdpi/exo_controls_play.png
library/src/main/res/drawable-xhdpi/exo_controls_play.png
library/src/main/res/drawable-xhdpi/exo_controls_play.png
  • 2-up
  • Swipe
  • Onion skin

339 Bytes | W: | H:

1.34 KB | W: | H:

library/src/main/res/drawable-xhdpi/exo_controls_previous.png
library/src/main/res/drawable-xhdpi/exo_controls_previous.png
library/src/main/res/drawable-xhdpi/exo_controls_previous.png
library/src/main/res/drawable-xhdpi/exo_controls_previous.png
  • 2-up
  • Swipe
  • Onion skin

400 Bytes | W: | H:

1.64 KB | W: | H:

library/src/main/res/drawable-xhdpi/exo_controls_rewind.png
library/src/main/res/drawable-xhdpi/exo_controls_rewind.png
library/src/main/res/drawable-xhdpi/exo_controls_rewind.png
library/src/main/res/drawable-xhdpi/exo_controls_rewind.png
  • 2-up
  • Swipe
  • Onion skin

391 Bytes | W: | H:

1.29 KB | W: | H:

library/src/main/res/drawable-xxhdpi/exo_controls_next.png
library/src/main/res/drawable-xxhdpi/exo_controls_next.png
library/src/main/res/drawable-xxhdpi/exo_controls_next.png
library/src/main/res/drawable-xxhdpi/exo_controls_next.png
  • 2-up
  • Swipe
  • Onion skin

113 Bytes | W: | H:

611 Bytes | W: | H:

library/src/main/res/drawable-xxhdpi/exo_controls_pause.png
library/src/main/res/drawable-xxhdpi/exo_controls_pause.png
library/src/main/res/drawable-xxhdpi/exo_controls_pause.png
library/src/main/res/drawable-xxhdpi/exo_controls_pause.png
  • 2-up
  • Swipe
  • Onion skin

384 Bytes | W: | H:

1.16 KB | W: | H:

library/src/main/res/drawable-xxhdpi/exo_controls_play.png
library/src/main/res/drawable-xxhdpi/exo_controls_play.png
library/src/main/res/drawable-xxhdpi/exo_controls_play.png
library/src/main/res/drawable-xxhdpi/exo_controls_play.png
  • 2-up
  • Swipe
  • Onion skin

464 Bytes | W: | H:

1.26 KB | W: | H:

library/src/main/res/drawable-xxhdpi/exo_controls_previous.png
library/src/main/res/drawable-xxhdpi/exo_controls_previous.png
library/src/main/res/drawable-xxhdpi/exo_controls_previous.png
library/src/main/res/drawable-xxhdpi/exo_controls_previous.png
  • 2-up
  • Swipe
  • Onion skin

571 Bytes | W: | H:

1.17 KB | W: | H:

library/src/main/res/drawable-xxhdpi/exo_controls_rewind.png
library/src/main/res/drawable-xxhdpi/exo_controls_rewind.png
library/src/main/res/drawable-xxhdpi/exo_controls_rewind.png
library/src/main/res/drawable-xxhdpi/exo_controls_rewind.png
  • 2-up
  • Swipe
  • Onion skin
...@@ -37,7 +37,6 @@ ...@@ -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"/>
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment