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 113 additions and 450 deletions
# 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 ###
This release contains important bug fixes. Users of r2.0.x should proactively
update to this version.
* HLS: Support for seeking in live streams
([#87](https://github.com/google/ExoPlayer/issues/87)).
* HLS: Improved support:
......
......@@ -35,7 +35,7 @@ allprojects {
releaseRepoName = 'exoplayer'
releaseUserOrg = 'google'
releaseGroupId = 'com.google.android.exoplayer'
releaseVersion = 'r2.1.1'
releaseVersion = 'r2.1.0'
releaseWebsite = 'https://github.com/google/ExoPlayer'
}
}
......@@ -16,8 +16,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.exoplayer2.demo"
android:versionCode="2101"
android:versionName="2.1.1">
android:versionCode="2100"
android:versionName="2.1.0">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
......
......@@ -183,54 +183,30 @@
"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",
"drm_scheme": "widevine",
"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",
"drm_scheme": "widevine",
"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",
"drm_scheme": "widevine",
"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",
"drm_scheme": "widevine",
"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)",
"uri": "https://storage.googleapis.com/widevine_test/vp9/sintel_1080p_vp9_altref_subsample/sintel_1080p_vp9_altref_subsample.mpd",
"drm_scheme": "widevine",
......
......@@ -31,7 +31,6 @@ import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes;
import java.io.IOException;
import java.util.ArrayList;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.TimeUnit;
import java.util.concurrent.TimeoutException;
......@@ -49,25 +48,12 @@ public final class ExoPlayerTest extends TestCase {
*/
private static final int TIMEOUT_MS = 10000;
/**
* Tests playback of a source that exposes a single period.
*/
public void testPlayToEnd() throws Exception {
PlayerWrapper playerWrapper = new PlayerWrapper();
Format format = Format.createVideoSampleFormat(null, MimeTypes.VIDEO_H264, null,
Format.NO_VALUE, Format.NO_VALUE, 1280, 720, Format.NO_VALUE, null, null);
playerWrapper.setup(new SinglePeriodTimeline(0, false), null, format);
playerWrapper.blockUntilEnded(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);
playerWrapper.setup(new SinglePeriodTimeline(0, false), new Object(), format);
playerWrapper.blockUntilEndedOrError(TIMEOUT_MS);
}
/**
......@@ -84,6 +70,7 @@ public final class ExoPlayerTest extends TestCase {
private Format expectedFormat;
private ExoPlayer player;
private Exception exception;
private boolean seenPositionDiscontinuity;
public PlayerWrapper() {
endedCountDownLatch = new CountDownLatch(1);
......@@ -94,11 +81,12 @@ public final class ExoPlayerTest extends TestCase {
// 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)) {
exception = new TimeoutException("Test playback timed out.");
}
release();
// Throw any pending exception (from playback, timing out or releasing).
if (exception != null) {
throw exception;
......@@ -120,7 +108,7 @@ public final class ExoPlayerTest extends TestCase {
player.setPlayWhenReady(true);
player.prepare(new FakeMediaSource(timeline, manifest, format));
} catch (Exception e) {
handleError(e);
handlePlayerException(e);
}
}
});
......@@ -135,7 +123,7 @@ public final class ExoPlayerTest extends TestCase {
player.release();
}
} catch (Exception e) {
handleError(e);
handlePlayerException(e);
} finally {
playerThread.quit();
}
......@@ -144,7 +132,7 @@ public final class ExoPlayerTest extends TestCase {
playerThread.join();
}
private void handleError(Exception exception) {
private void handlePlayerException(Exception exception) {
if (this.exception == null) {
this.exception = exception;
}
......@@ -179,13 +167,20 @@ public final class ExoPlayerTest extends TestCase {
@Override
public void onPlayerError(ExoPlaybackException exception) {
handleError(exception);
this.exception = exception;
endedCountDownLatch.countDown();
}
@Override
public void onPositionDiscontinuity() {
// Should never happen.
handleError(new IllegalStateException("Received position discontinuity"));
assertFalse(seenPositionDiscontinuity);
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 Object manifest;
private final Format format;
private final ArrayList<FakeMediaPeriod> activeMediaPeriods;
private FakeMediaPeriod mediaPeriod;
private boolean preparedSource;
private boolean releasedPeriod;
private boolean releasedSource;
public FakeMediaSource(Timeline timeline, Object manifest, Format format) {
Assertions.checkArgument(timeline.getPeriodCount() == 1);
this.timeline = timeline;
this.manifest = manifest;
this.format = format;
activeMediaPeriods = new ArrayList<>();
}
@Override
......@@ -225,29 +221,33 @@ public final class ExoPlayerTest extends TestCase {
@Override
public MediaPeriod createPeriod(int index, Allocator allocator, long positionUs) {
Assertions.checkIndex(index, 0, timeline.getPeriodCount());
assertTrue(preparedSource);
assertNull(mediaPeriod);
assertFalse(releasedPeriod);
assertFalse(releasedSource);
assertEquals(0, index);
assertEquals(0, positionUs);
FakeMediaPeriod mediaPeriod = new FakeMediaPeriod(format);
activeMediaPeriods.add(mediaPeriod);
mediaPeriod = new FakeMediaPeriod(format);
return mediaPeriod;
}
@Override
public void releasePeriod(MediaPeriod mediaPeriod) {
assertTrue(preparedSource);
assertNotNull(this.mediaPeriod);
assertFalse(releasedPeriod);
assertFalse(releasedSource);
assertTrue(activeMediaPeriods.remove(mediaPeriod));
((FakeMediaPeriod) mediaPeriod).release();
assertEquals(this.mediaPeriod, mediaPeriod);
this.mediaPeriod.release();
releasedPeriod = true;
}
@Override
public void releaseSource() {
assertTrue(preparedSource);
assertNotNull(this.mediaPeriod);
assertTrue(releasedPeriod);
assertFalse(releasedSource);
assertTrue(activeMediaPeriods.isEmpty());
releasedSource = true;
}
......@@ -400,6 +400,7 @@ public final class ExoPlayerTest extends TestCase {
public FakeVideoRenderer(Format expectedFormat) {
super(C.TRACK_TYPE_VIDEO);
Assertions.checkArgument(MimeTypes.isVideo(expectedFormat.sampleMimeType));
this.expectedFormat = expectedFormat;
}
......
......@@ -267,12 +267,6 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
return streamIsFinal ? C.RESULT_BUFFER_READ : C.RESULT_NOTHING_READ;
}
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;
}
......
......@@ -97,13 +97,6 @@ public final class C {
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.
*/
@Retention(RetentionPolicy.SOURCE)
......
......@@ -332,10 +332,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
case ExoPlayerImplInternal.MSG_SEEK_ACK: {
if (--pendingSeekAcks == 0) {
playbackInfo = (ExoPlayerImplInternal.PlaybackInfo) msg.obj;
if (msg.arg1 != 0) {
for (EventListener listener : listeners) {
listener.onPositionDiscontinuity();
}
for (EventListener listener : listeners) {
listener.onPositionDiscontinuity();
}
}
break;
......
......@@ -559,7 +559,7 @@ import java.io.IOException;
// 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.
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
// ignored.
playbackInfo = new PlaybackInfo(0, C.TIME_UNSET);
......@@ -569,7 +569,6 @@ import java.io.IOException;
return;
}
boolean seekPositionAdjusted = seekPosition.windowPositionUs == C.TIME_UNSET;
int periodIndex = periodPosition.first;
long periodPositionUs = periodPosition.second;
......@@ -579,13 +578,10 @@ import java.io.IOException;
// Seek position equals the current position. Do nothing.
return;
}
long newPeriodPositionUs = seekToPeriodPosition(periodIndex, periodPositionUs);
seekPositionAdjusted |= periodPositionUs != newPeriodPositionUs;
periodPositionUs = newPeriodPositionUs;
periodPositionUs = seekToPeriodPosition(periodIndex, periodPositionUs);
} finally {
playbackInfo = new PlaybackInfo(periodIndex, periodPositionUs);
eventHandler.obtainMessage(MSG_SEEK_ACK, seekPositionAdjusted ? 1 : 0, 0, playbackInfo)
.sendToTarget();
eventHandler.obtainMessage(MSG_SEEK_ACK, playbackInfo).sendToTarget();
}
}
......@@ -680,7 +676,6 @@ import java.io.IOException;
standaloneMediaClock.stop();
rendererMediaClock = null;
rendererMediaClockSource = null;
rendererPositionUs = RENDERER_TIMESTAMP_OFFSET_US;
for (Renderer renderer : enabledRenderers) {
try {
ensureStopped(renderer);
......@@ -828,6 +823,9 @@ import java.io.IOException;
}
private boolean haveSufficientBuffer(boolean rebuffering) {
if (loadingPeriodHolder == null) {
return false;
}
long loadingPeriodBufferedPositionUs = !loadingPeriodHolder.prepared
? loadingPeriodHolder.startPositionUs
: loadingPeriodHolder.mediaPeriod.getBufferedPositionUs();
......@@ -1289,8 +1287,7 @@ import java.io.IOException;
}
private void maybeContinueLoading() {
long nextLoadPositionUs = !loadingPeriodHolder.prepared ? 0
: loadingPeriodHolder.mediaPeriod.getNextLoadPositionUs();
long nextLoadPositionUs = loadingPeriodHolder.mediaPeriod.getNextLoadPositionUs();
if (nextLoadPositionUs == C.TIME_END_OF_SOURCE) {
setIsLoading(false);
} else {
......
......@@ -23,7 +23,7 @@ public interface ExoPlayerLibraryInfo {
/**
* 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.
......@@ -32,7 +32,7 @@ public interface ExoPlayerLibraryInfo {
* corresponding integer version 1002003 (001-002-003), and "123.45.6" has the corresponding
* 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}
......
......@@ -29,6 +29,7 @@ import android.view.SurfaceView;
import android.view.TextureView;
import com.google.android.exoplayer2.audio.AudioCapabilities;
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.decoder.DecoderCounters;
import com.google.android.exoplayer2.drm.DrmSessionManager;
......@@ -177,7 +178,7 @@ public class SimpleExoPlayer implements ExoPlayer {
// Set initial values.
audioVolume = 1;
audioSessionId = C.AUDIO_SESSION_ID_UNSET;
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
audioStreamType = C.STREAM_TYPE_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() {
return audioSessionId;
......@@ -948,7 +949,7 @@ public class SimpleExoPlayer implements ExoPlayer {
}
audioFormat = null;
audioDecoderCounters = null;
audioSessionId = C.AUDIO_SESSION_ID_UNSET;
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
}
// TextRenderer.Output implementation
......
......@@ -17,7 +17,6 @@ package com.google.android.exoplayer2.audio;
import android.annotation.SuppressLint;
import android.annotation.TargetApi;
import android.media.AudioAttributes;
import android.media.AudioFormat;
import android.media.AudioTimestamp;
import android.media.PlaybackParams;
......@@ -31,28 +30,26 @@ import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import java.lang.reflect.Method;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
/**
* Plays audio data. The implementation delegates to an {@link android.media.AudioTrack} and handles
* playback position smoothing, non-blocking writes and reconfiguration.
* <p>
* Before starting playback, specify the input format by calling
* {@link #configure(String, int, int, int, int)}. Next call {@link #initialize(int)} or
* {@link #initializeV21(int, boolean)}, optionally specifying an audio session and whether the
* track is to be used with tunneling video playback.
* {@link #configure(String, int, int, int, int)}. Next call {@link #initialize(int)}, optionally
* specifying an audio session.
* <p>
* 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.
* <p>
* 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 #initialize(int)} or {@link #initializeV21(int, boolean)} before writing more data.
* {@link #initialize(int)} before writing more data.
* <p>
* 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
* format is unchanged). It is safe to call {@link #initialize(int)} or
* {@link #initializeV21(int, boolean)} after calling {@link #reset()} without reconfiguration.
* format is unchanged). It is safe to call {@link #initialize(int)} after calling {@link #reset()}
* without reconfiguration.
* <p>
* Call {@link #release()} when the instance is no longer required.
*/
......@@ -147,6 +144,11 @@ public final class AudioTrack {
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.
*/
public static final long CURRENT_POSITION_NOT_SET = Long.MIN_VALUE;
......@@ -271,10 +273,6 @@ public final class AudioTrack {
private int bufferSize;
private long bufferSizeUs;
private boolean useHwAvSync;
private ByteBuffer avSyncHeader;
private int bytesUntilNextAvSync;
private int nextPlayheadOffsetIndex;
private int playheadOffsetCount;
private long smoothedPlayheadOffsetUs;
......@@ -343,8 +341,8 @@ public final class AudioTrack {
}
/**
* Returns whether the audio track has been successfully initialized via {@link #initialize} or
* {@link #initializeV21(int, boolean)}, and has not yet been {@link #reset}.
* Returns whether the audio track has been successfully initialized via {@link #initialize} and
* not yet {@link #reset}.
*/
public boolean isInitialized() {
return audioTrack != null;
......@@ -444,21 +442,6 @@ public final class AudioTrack {
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);
@C.Encoding int sourceEncoding;
if (passthrough) {
......@@ -515,28 +498,11 @@ public final class AudioTrack {
/**
* 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.
* @return The audio track session identifier.
* @param sessionId Audio track session identifier to re-use, or {@link #SESSION_ID_NOT_SET} to
* create a new one.
* @return The new (or re-used) session identifier.
*/
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
// 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
......@@ -544,11 +510,7 @@ public final class AudioTrack {
// initialization of the audio track to fail.
releasingConditionVariable.block();
useHwAvSync = tunneling;
if (useHwAvSync) {
audioTrack = createHwAvSyncAudioTrackV21(sampleRate, channelConfig, targetEncoding,
bufferSize, sessionId);
} else if (sessionId == C.AUDIO_SESSION_ID_UNSET) {
if (sessionId == SESSION_ID_NOT_SET) {
audioTrack = new android.media.AudioTrack(streamType, sampleRate, channelConfig,
targetEncoding, bufferSize, MODE_STREAM);
} else {
......@@ -730,9 +692,7 @@ public final class AudioTrack {
buffer.position(buffer.position() + bytesWritten);
}
} else {
bytesWritten = useHwAvSync
? writeNonBlockingWithAvSyncV21(audioTrack, buffer, bytesRemaining, presentationTimeUs)
: writeNonBlockingV21(audioTrack, buffer, bytesRemaining);
bytesWritten = writeNonBlockingV21(audioTrack, buffer, bytesRemaining);
}
if (bytesWritten < 0) {
......@@ -758,7 +718,6 @@ public final class AudioTrack {
public void handleEndOfStream() {
if (isInitialized()) {
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
* is not configured for use with video tunneling, then the audio track is reset and the caller
* must re-initialize the audio track before writing more data. The caller must not reuse the
* audio session identifier when 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.
* Sets the stream type for audio track. If the stream type has changed, {@link #isInitialized()}
* will return {@code false} and the caller must re-{@link #initialize(int)} the audio track
* before writing more data. The caller must not reuse the audio session identifier when
* re-initializing with a new stream type.
*
* @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) {
if (this.streamType == streamType) {
return false;
}
this.streamType = streamType;
if (useHwAvSync) {
// The stream type is ignored in tunneling mode, so no need to reset.
return false;
}
reset();
return true;
}
......@@ -844,9 +795,9 @@ public final class AudioTrack {
/**
* Releases the underlying audio track asynchronously.
* <p>
* Calling {@link #initialize(int)} or {@link #initializeV21(int, boolean)} will block until the
* audio track has been released, so it is safe to initialize immediately after a reset. The audio
* session may remain active until {@link #release()} is called.
* Calling {@link #initialize(int)} will block until the audio track has been released, so it is
* safe to initialize immediately after a reset. The audio session may remain active until
* {@link #release()} is called.
*/
public void reset() {
if (isInitialized()) {
......@@ -854,7 +805,6 @@ public final class AudioTrack {
submittedEncodedFrames = 0;
framesPerEncodedSample = 0;
currentSourceBuffer = null;
avSyncHeader = null;
startMediaTimeState = START_NOT_SET;
latencyUs = 0;
resetSyncParams();
......@@ -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.
*
* @param buffer The buffer containing the data to convert.
......@@ -1195,51 +1125,12 @@ public final class AudioTrack {
}
@TargetApi(21)
private static int writeNonBlockingV21(android.media.AudioTrack audioTrack, ByteBuffer buffer,
int size) {
private static int writeNonBlockingV21(
android.media.AudioTrack audioTrack, ByteBuffer buffer, int size) {
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)
private static void setAudioTrackVolumeV21(android.media.AudioTrack audioTrack, float volume) {
audioTrack.setVolume(volume);
}
......
......@@ -129,7 +129,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
boolean playClearSamplesWithoutKeys, Handler eventHandler,
AudioRendererEventListener eventListener, AudioCapabilities audioCapabilities) {
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);
eventDispatcher = new EventDispatcher(eventHandler, eventListener);
}
......@@ -274,7 +274,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
@Override
protected void onDisabled() {
audioSessionId = C.AUDIO_SESSION_ID_UNSET;
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
try {
audioTrack.release();
} finally {
......@@ -328,8 +328,8 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
if (!audioTrack.isInitialized()) {
// Initialize the AudioTrack now.
try {
if (audioSessionId == C.AUDIO_SESSION_ID_UNSET) {
audioSessionId = audioTrack.initialize(C.AUDIO_SESSION_ID_UNSET);
if (audioSessionId == AudioTrack.SESSION_ID_NOT_SET) {
audioSessionId = audioTrack.initialize(AudioTrack.SESSION_ID_NOT_SET);
eventDispatcher.audioSessionId(audioSessionId);
onAudioSessionId(audioSessionId);
} else {
......@@ -387,7 +387,7 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
case C.MSG_SET_STREAM_TYPE:
@C.StreamType int streamType = (Integer) message;
if (audioTrack.setStreamType(streamType)) {
audioSessionId = C.AUDIO_SESSION_ID_UNSET;
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
}
break;
default:
......
......@@ -145,7 +145,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
this.drmSessionManager = drmSessionManager;
formatHolder = new FormatHolder();
this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys;
audioSessionId = C.AUDIO_SESSION_ID_UNSET;
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
decoderReinitializationState = REINITIALIZATION_STATE_NONE;
audioTrackNeedsConfigure = true;
}
......@@ -245,8 +245,8 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
}
if (!audioTrack.isInitialized()) {
if (audioSessionId == C.AUDIO_SESSION_ID_UNSET) {
audioSessionId = audioTrack.initialize(C.AUDIO_SESSION_ID_UNSET);
if (audioSessionId == AudioTrack.SESSION_ID_NOT_SET) {
audioSessionId = audioTrack.initialize(AudioTrack.SESSION_ID_NOT_SET);
eventDispatcher.audioSessionId(audioSessionId);
onAudioSessionId(audioSessionId);
} else {
......@@ -425,7 +425,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
@Override
protected void onDisabled() {
inputFormat = null;
audioSessionId = C.AUDIO_SESSION_ID_UNSET;
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
audioTrackNeedsConfigure = true;
waitingForKeys = false;
try {
......@@ -554,7 +554,7 @@ public abstract class SimpleDecoderAudioRenderer extends BaseRenderer implements
case C.MSG_SET_STREAM_TYPE:
@C.StreamType int streamType = (Integer) message;
if (audioTrack.setStreamType(streamType)) {
audioSessionId = C.AUDIO_SESSION_ID_UNSET;
audioSessionId = AudioTrack.SESSION_ID_NOT_SET;
}
break;
default:
......
......@@ -244,7 +244,7 @@ import java.io.IOException;
@Override
public long getNextLoadPositionUs() {
return enabledTrackCount == 0 ? C.TIME_END_OF_SOURCE : getBufferedPositionUs();
return getBufferedPositionUs();
}
@Override
......
......@@ -133,32 +133,4 @@ public interface MediaPeriod extends SequenceableLoader {
*/
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;
if (playlistTracker.isLive()) {
// TODO: fix windowPositionInPeriodUs when playlist is empty.
long windowPositionInPeriodUs = playlist.startTimeUs;
List<HlsMediaPlaylist.Segment> segments = playlist.segments;
long windowDefaultStartPositionUs = segments.isEmpty() ? 0
: segments.get(Math.max(0, segments.size() - 3)).relativeStartTimeUs;
timeline = new SinglePeriodTimeline(C.TIME_UNSET, playlist.durationUs,
playlist.startTimeUs, windowDefaultStartPositionUs, true, !playlist.hasEndTag);
windowPositionInPeriodUs, windowDefaultStartPositionUs, true, !playlist.hasEndTag);
} else /* not live */ {
timeline = new SinglePeriodTimeline(playlist.startTimeUs + playlist.durationUs,
playlist.durationUs, playlist.startTimeUs, 0, true, false);
timeline = new SinglePeriodTimeline(playlist.durationUs, playlist.durationUs, 0, 0, true,
false);
}
sourceListener.onSourceInfoRefreshed(timeline, playlist);
}
......
......@@ -15,7 +15,6 @@
*/
package com.google.android.exoplayer2.text;
import android.graphics.Color;
import android.support.annotation.IntDef;
import android.text.Layout.Alignment;
import java.lang.annotation.Retention;
......@@ -37,23 +36,19 @@ public class Cue {
@Retention(RetentionPolicy.SOURCE)
@IntDef({TYPE_UNSET, ANCHOR_TYPE_START, ANCHOR_TYPE_MIDDLE, ANCHOR_TYPE_END})
public @interface AnchorType {}
/**
* An unset anchor or line type 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
* box.
*/
public static final int ANCHOR_TYPE_START = 0;
/**
* Anchors the middle of the cue box.
*/
public static final int ANCHOR_TYPE_MIDDLE = 1;
/**
* Anchors the right (for horizontal positions) or bottom (for vertical positions) edge of the cue
* box.
......@@ -66,12 +61,10 @@ public class Cue {
@Retention(RetentionPolicy.SOURCE)
@IntDef({TYPE_UNSET, LINE_TYPE_FRACTION, LINE_TYPE_NUMBER})
public @interface LineType {}
/**
* Value for {@link #lineType} when {@link #line} is a fractional position.
*/
public static final int LINE_TYPE_FRACTION = 0;
/**
* 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.
*/
public final CharSequence text;
/**
* The alignment of the cue text within the cue box, or null if the alignment is undefined.
*/
public final Alignment textAlignment;
/**
* 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
......@@ -95,7 +86,6 @@ public class Cue {
* 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.
*/
public final float line;
/**
* 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
* line is visible at the bottom of the viewport.
*/
@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
* respectively.
*/
@AnchorType
public final int lineAnchor;
/**
......@@ -145,7 +133,6 @@ public class Cue {
* text.
*/
public final float position;
/**
* 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}.
......@@ -156,7 +143,6 @@ public class Cue {
*/
@AnchorType
public final int positionAnchor;
/**
* 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}.
......@@ -164,16 +150,6 @@ public class Cue {
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
* {@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,
@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.textAlignment = textAlignment;
this.line = line;
......@@ -222,8 +179,6 @@ public class Cue {
this.position = position;
this.positionAnchor = positionAnchor;
this.size = size;
this.windowColorSet = windowColorSet;
this.windowColor = windowColor;
}
}
......@@ -65,13 +65,6 @@ import java.util.List;
* <li>Default: {@code true}</li>
* </ul>
* </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.
* <ul>
* <li>Corresponding method: {@link #setUseController(boolean)}</li>
......@@ -186,7 +179,6 @@ public final class SimpleExoPlayerView extends FrameLayout {
private SimpleExoPlayer player;
private boolean useController;
private boolean useArtwork;
private Bitmap defaultArtwork;
private int controllerShowTimeoutMs;
public SimpleExoPlayerView(Context context) {
......@@ -202,7 +194,6 @@ public final class SimpleExoPlayerView extends FrameLayout {
int playerLayoutId = R.layout.exo_simple_player_view;
boolean useArtwork = true;
int defaultArtwork = 0;
boolean useController = true;
int surfaceType = SURFACE_TYPE_SURFACE_VIEW;
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);
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);
surfaceType = a.getInt(R.styleable.SimpleExoPlayerView_surface_type, surfaceType);
resizeMode = a.getInt(R.styleable.SimpleExoPlayerView_resize_mode, resizeMode);
......@@ -257,9 +246,6 @@ public final class SimpleExoPlayerView extends FrameLayout {
// Artwork view.
artworkView = (ImageView) findViewById(R.id.exo_artwork);
this.useArtwork = useArtwork && artworkView != null;
if (defaultArtwork != 0) {
this.defaultArtwork = BitmapFactory.decodeResource(context.getResources(), defaultArtwork);
}
// Subtitle view.
subtitleView = (SubtitleView) findViewById(R.id.exo_subtitles);
......@@ -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.
*/
public boolean getUseController() {
......@@ -603,9 +569,6 @@ public final class SimpleExoPlayerView extends FrameLayout {
}
}
}
if (setArtworkFromBitmap(defaultArtwork)) {
return;
}
}
// Artwork disabled or unavailable.
hideArtwork();
......@@ -617,23 +580,18 @@ public final class SimpleExoPlayerView extends FrameLayout {
if (metadataEntry instanceof ApicFrame) {
byte[] bitmapData = ((ApicFrame) metadataEntry).pictureData;
Bitmap bitmap = BitmapFactory.decodeByteArray(bitmapData, 0, bitmapData.length);
return setArtworkFromBitmap(bitmap);
}
}
return false;
}
private boolean setArtworkFromBitmap(Bitmap bitmap) {
if (bitmap != null) {
int bitmapWidth = bitmap.getWidth();
int bitmapHeight = bitmap.getHeight();
if (bitmapWidth > 0 && bitmapHeight > 0) {
if (contentFrame != null) {
contentFrame.setAspectRatio((float) bitmapWidth / bitmapHeight);
if (bitmap != null) {
int bitmapWidth = bitmap.getWidth();
int bitmapHeight = bitmap.getHeight();
if (bitmapWidth > 0 && bitmapHeight > 0) {
if (contentFrame != null) {
contentFrame.setAspectRatio((float) bitmapWidth / bitmapHeight);
}
artworkView.setImageBitmap(bitmap);
artworkView.setVisibility(VISIBLE);
return true;
}
}
artworkView.setImageBitmap(bitmap);
artworkView.setVisibility(VISIBLE);
return true;
}
}
return false;
......
......@@ -146,13 +146,9 @@ import com.google.android.exoplayer2.util.Util;
// Nothing to draw.
return;
}
int windowColor = cue.windowColorSet ? cue.windowColor : style.windowColor;
if (!applyEmbeddedStyles) {
// Strip out any embedded styling.
cueText = cueText.toString();
windowColor = style.windowColor;
}
if (areCharSequencesEqual(this.cueText, cueText)
&& Util.areEqual(this.cueTextAlignment, cue.textAlignment)
......@@ -165,7 +161,7 @@ import com.google.android.exoplayer2.util.Util;
&& this.applyEmbeddedStyles == applyEmbeddedStyles
&& this.foregroundColor == style.foregroundColor
&& this.backgroundColor == style.backgroundColor
&& this.windowColor == windowColor
&& this.windowColor == style.windowColor
&& this.edgeType == style.edgeType
&& this.edgeColor == style.edgeColor
&& Util.areEqual(this.textPaint.getTypeface(), style.typeface)
......@@ -191,7 +187,7 @@ import com.google.android.exoplayer2.util.Util;
this.applyEmbeddedStyles = applyEmbeddedStyles;
this.foregroundColor = style.foregroundColor;
this.backgroundColor = style.backgroundColor;
this.windowColor = windowColor;
this.windowColor = style.windowColor;
this.edgeType = style.edgeType;
this.edgeColor = style.edgeColor;
this.textPaint.setTypeface(style.typeface);
......
......@@ -403,7 +403,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
bufferPresentationTimeUs, unadjustedFrameReleaseTimeNs);
earlyUs = (adjustedReleaseTimeNs - systemTimeNs) / 1000;
if (shouldDropOutputBuffer(earlyUs, elapsedRealtimeUs)) {
if (earlyUs < -30000) {
// We're more than 30ms late rendering the frame.
dropOutputBuffer(codec, bufferIndex);
return true;
......@@ -437,17 +437,6 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
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) {
TraceUtil.beginSection("skipVideoBuffer");
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 @@
<declare-styleable name="SimpleExoPlayerView">
<attr name="use_artwork" format="boolean"/>
<attr name="default_artwork" format="reference"/>
<attr name="use_controller" format="boolean"/>
<attr name="surface_type"/>
<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