Commit f9030987 by Oliver Woodman

Merge branch 'dev-v2' of persistent-https://github.com/google/ExoPlayer into dev-v2

parents 51de6e53 fb88087a
Showing with 824 additions and 127 deletions
...@@ -262,11 +262,11 @@ public final class LibvpxVideoRenderer extends BaseRenderer { ...@@ -262,11 +262,11 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
return false; return false;
} }
// Drop the frame if we're joining and are more than 30ms late, or if we have the next frame final long nextOutputBufferTimeUs =
// and that's also late. Else we'll render what we have. nextOutputBuffer != null && !nextOutputBuffer.isEndOfStream()
if ((joiningDeadlineMs != C.TIME_UNSET && outputBuffer.timeUs < positionUs - 30000) ? nextOutputBuffer.timeUs : C.TIME_UNSET;
|| (nextOutputBuffer != null && !nextOutputBuffer.isEndOfStream() if (shouldDropOutputBuffer(
&& nextOutputBuffer.timeUs < positionUs)) { outputBuffer.timeUs, nextOutputBufferTimeUs, positionUs, joiningDeadlineMs)) {
dropBuffer(); dropBuffer();
return true; return true;
} }
...@@ -280,6 +280,25 @@ public final class LibvpxVideoRenderer extends BaseRenderer { ...@@ -280,6 +280,25 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
return false; return false;
} }
/**
* Returns whether the current frame should be dropped.
*
* @param outputBufferTimeUs The timestamp of the current output buffer.
* @param nextOutputBufferTimeUs The timestamp of the next output buffer or
* {@link TIME_UNSET} if the next output buffer is unavailable.
* @param positionUs The current playback position.
* @param joiningDeadlineMs The joining deadline.
* @return Returns whether to drop the current output buffer.
*/
protected boolean shouldDropOutputBuffer(long outputBufferTimeUs, long nextOutputBufferTimeUs,
long positionUs, long joiningDeadlineMs) {
// Drop the frame if we're joining and are more than 30ms late, or if we have the next frame
// and that's also late. Else we'll render what we have.
return (joiningDeadlineMs != C.TIME_UNSET && outputBufferTimeUs < positionUs - 30000)
|| (nextOutputBufferTimeUs != C.TIME_UNSET && nextOutputBufferTimeUs < positionUs);
}
private void renderBuffer() { private void renderBuffer() {
int bufferMode = outputBuffer.mode; int bufferMode = outputBuffer.mode;
boolean renderRgb = bufferMode == VpxDecoder.OUTPUT_MODE_RGB && surface != null; boolean renderRgb = bufferMode == VpxDecoder.OUTPUT_MODE_RGB && surface != null;
...@@ -445,13 +464,16 @@ public final class LibvpxVideoRenderer extends BaseRenderer { ...@@ -445,13 +464,16 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
protected void onPositionReset(long positionUs, boolean joining) { protected void onPositionReset(long positionUs, boolean joining) {
inputStreamEnded = false; inputStreamEnded = false;
outputStreamEnded = false; outputStreamEnded = false;
renderedFirstFrame = false; clearRenderedFirstFrame();
consecutiveDroppedFrameCount = 0; consecutiveDroppedFrameCount = 0;
if (decoder != null) { if (decoder != null) {
flushDecoder(); flushDecoder();
} }
joiningDeadlineMs = joining && allowedJoiningTimeMs > 0 if (joining) {
? (SystemClock.elapsedRealtime() + allowedJoiningTimeMs) : C.TIME_UNSET; setJoiningDeadlineMs();
} else {
joiningDeadlineMs = C.TIME_UNSET;
}
} }
@Override @Override
...@@ -473,6 +495,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer { ...@@ -473,6 +495,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
format = null; format = null;
waitingForKeys = false; waitingForKeys = false;
clearReportedVideoSize(); clearReportedVideoSize();
clearRenderedFirstFrame();
try { try {
releaseDecoder(); releaseDecoder();
} finally { } finally {
...@@ -549,28 +572,44 @@ public final class LibvpxVideoRenderer extends BaseRenderer { ...@@ -549,28 +572,44 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
private void setOutput(Surface surface, VpxOutputBufferRenderer outputBufferRenderer) { private void setOutput(Surface surface, VpxOutputBufferRenderer outputBufferRenderer) {
// At most one output may be non-null. Both may be null if the output is being cleared. // At most one output may be non-null. Both may be null if the output is being cleared.
Assertions.checkState(surface == null || outputBufferRenderer == null); Assertions.checkState(surface == null || outputBufferRenderer == null);
// We only need to update the decoder if the output has changed.
if (this.surface != surface || this.outputBufferRenderer != outputBufferRenderer) { if (this.surface != surface || this.outputBufferRenderer != outputBufferRenderer) {
// The output has changed.
this.surface = surface; this.surface = surface;
this.outputBufferRenderer = outputBufferRenderer; this.outputBufferRenderer = outputBufferRenderer;
outputMode = outputBufferRenderer != null ? VpxDecoder.OUTPUT_MODE_YUV outputMode = outputBufferRenderer != null ? VpxDecoder.OUTPUT_MODE_YUV
: surface != null ? VpxDecoder.OUTPUT_MODE_RGB : VpxDecoder.OUTPUT_MODE_NONE; : surface != null ? VpxDecoder.OUTPUT_MODE_RGB : VpxDecoder.OUTPUT_MODE_NONE;
// If outputMode is OUTPUT_MODE_NONE we leave the mode of the underlying decoder unchanged in if (outputMode != VpxDecoder.OUTPUT_MODE_NONE) {
// anticipation that a subsequent output will likely be of the same type as the one that was if (decoder != null) {
// set previously. decoder.setOutputMode(outputMode);
if (decoder != null && outputMode != VpxDecoder.OUTPUT_MODE_NONE) { }
decoder.setOutputMode(outputMode); // If we know the video size, report it again immediately.
maybeRenotifyVideoSizeChanged();
// We haven't rendered to the new output yet.
clearRenderedFirstFrame();
if (getState() == STATE_STARTED) {
setJoiningDeadlineMs();
}
} else {
// The output has been removed. We leave the outputMode of the underlying decoder unchanged
// in anticipation that a subsequent output will likely be of the same type.
clearReportedVideoSize();
clearRenderedFirstFrame();
} }
} else if (outputMode != VpxDecoder.OUTPUT_MODE_NONE) {
// The output is unchanged and non-null. If we know the video size and/or have already
// rendered to the output, report these again immediately.
maybeRenotifyVideoSizeChanged();
maybeRenotifyRenderedFirstFrame();
} }
// Clear state so that we always call the event listener with the video size and when a frame
// is rendered, even if the output hasn't changed.
renderedFirstFrame = false;
clearReportedVideoSize();
} }
private void clearReportedVideoSize() { private void setJoiningDeadlineMs() {
reportedWidth = Format.NO_VALUE; joiningDeadlineMs = allowedJoiningTimeMs > 0
reportedHeight = Format.NO_VALUE; ? (SystemClock.elapsedRealtime() + allowedJoiningTimeMs) : C.TIME_UNSET;
}
private void clearRenderedFirstFrame() {
renderedFirstFrame = false;
} }
private void maybeNotifyRenderedFirstFrame() { private void maybeNotifyRenderedFirstFrame() {
...@@ -580,6 +619,17 @@ public final class LibvpxVideoRenderer extends BaseRenderer { ...@@ -580,6 +619,17 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
} }
} }
private void maybeRenotifyRenderedFirstFrame() {
if (renderedFirstFrame) {
eventDispatcher.renderedFirstFrame(surface);
}
}
private void clearReportedVideoSize() {
reportedWidth = Format.NO_VALUE;
reportedHeight = Format.NO_VALUE;
}
private void maybeNotifyVideoSizeChanged(int width, int height) { private void maybeNotifyVideoSizeChanged(int width, int height) {
if (reportedWidth != width || reportedHeight != height) { if (reportedWidth != width || reportedHeight != height) {
reportedWidth = width; reportedWidth = width;
...@@ -588,6 +638,12 @@ public final class LibvpxVideoRenderer extends BaseRenderer { ...@@ -588,6 +638,12 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
} }
} }
private void maybeRenotifyVideoSizeChanged() {
if (reportedWidth != Format.NO_VALUE || reportedHeight != Format.NO_VALUE) {
eventDispatcher.videoSizeChanged(reportedWidth, reportedHeight, 0, 1);
}
}
private void maybeNotifyDroppedFrames() { private void maybeNotifyDroppedFrames() {
if (droppedFrames > 0) { if (droppedFrames > 0) {
long now = SystemClock.elapsedRealtime(); long now = SystemClock.elapsedRealtime();
......
...@@ -29,6 +29,7 @@ import com.google.android.exoplayer2.metadata.id3.TextInformationFrame; ...@@ -29,6 +29,7 @@ import com.google.android.exoplayer2.metadata.id3.TextInformationFrame;
import com.google.android.exoplayer2.testutil.TestUtil; import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.ColorInfo;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
...@@ -61,11 +62,13 @@ public final class FormatTest extends TestCase { ...@@ -61,11 +62,13 @@ public final class FormatTest extends TestCase {
Metadata metadata = new Metadata( Metadata metadata = new Metadata(
new TextInformationFrame("id1", "description1", "value1"), new TextInformationFrame("id1", "description1", "value1"),
new TextInformationFrame("id2", "description2", "value2")); new TextInformationFrame("id2", "description2", "value2"));
ColorInfo colorInfo = new ColorInfo(C.COLOR_SPACE_BT709,
C.COLOR_RANGE_LIMITED, C.COLOR_TRANSFER_SDR, new byte[] {1, 2, 3, 4, 5, 6, 7});
Format formatToParcel = new Format("id", MimeTypes.VIDEO_MP4, MimeTypes.VIDEO_H264, null, Format formatToParcel = new Format("id", MimeTypes.VIDEO_MP4, MimeTypes.VIDEO_H264, null,
1024, 2048, 1920, 1080, 24, 90, 2, projectionData, C.STEREO_MODE_TOP_BOTTOM, 6, 44100, 1024, 2048, 1920, 1080, 24, 90, 2, projectionData, C.STEREO_MODE_TOP_BOTTOM, colorInfo, 6,
C.ENCODING_PCM_24BIT, 1001, 1002, 0, "und", Format.NO_VALUE, Format.OFFSET_SAMPLE_RELATIVE, 44100, C.ENCODING_PCM_24BIT, 1001, 1002, 0, "und", Format.NO_VALUE,
INIT_DATA, drmInitData, metadata); Format.OFFSET_SAMPLE_RELATIVE, INIT_DATA, drmInitData, metadata);
Parcel parcel = Parcel.obtain(); Parcel parcel = Parcel.obtain();
formatToParcel.writeToParcel(parcel, 0); formatToParcel.writeToParcel(parcel, 0);
......
...@@ -20,6 +20,7 @@ import android.content.Context; ...@@ -20,6 +20,7 @@ import android.content.Context;
import android.media.AudioFormat; import android.media.AudioFormat;
import android.media.AudioManager; import android.media.AudioManager;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.MediaFormat;
import android.support.annotation.IntDef; import android.support.annotation.IntDef;
import android.view.Surface; import android.view.Surface;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
...@@ -555,6 +556,67 @@ public final class C { ...@@ -555,6 +556,67 @@ public final class C {
public static final int STEREO_MODE_STEREO_MESH = 3; public static final int STEREO_MODE_STEREO_MESH = 3;
/** /**
* Video colorspaces.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({Format.NO_VALUE, COLOR_SPACE_BT709, COLOR_SPACE_BT601, COLOR_SPACE_BT2020})
public @interface ColorSpace {}
/**
* @see MediaFormat#COLOR_STANDARD_BT709
*/
@SuppressWarnings("InlinedApi")
public static final int COLOR_SPACE_BT709 = MediaFormat.COLOR_STANDARD_BT709;
/**
* @see MediaFormat#COLOR_STANDARD_BT601_PAL
*/
@SuppressWarnings("InlinedApi")
public static final int COLOR_SPACE_BT601 = MediaFormat.COLOR_STANDARD_BT601_PAL;
/**
* @see MediaFormat#COLOR_STANDARD_BT2020
*/
@SuppressWarnings("InlinedApi")
public static final int COLOR_SPACE_BT2020 = MediaFormat.COLOR_STANDARD_BT2020;
/**
* Video color transfer characteristics.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({Format.NO_VALUE, COLOR_TRANSFER_SDR, COLOR_TRANSFER_ST2084, COLOR_TRANSFER_HLG})
public @interface ColorTransfer {}
/**
* @see MediaFormat#COLOR_TRANSFER_SDR_VIDEO
*/
@SuppressWarnings("InlinedApi")
public static final int COLOR_TRANSFER_SDR = MediaFormat.COLOR_TRANSFER_SDR_VIDEO;
/**
* @see MediaFormat#COLOR_TRANSFER_ST2084
*/
@SuppressWarnings("InlinedApi")
public static final int COLOR_TRANSFER_ST2084 = MediaFormat.COLOR_TRANSFER_ST2084;
/**
* @see MediaFormat#COLOR_TRANSFER_HLG
*/
@SuppressWarnings("InlinedApi")
public static final int COLOR_TRANSFER_HLG = MediaFormat.COLOR_TRANSFER_HLG;
/**
* Video color range.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({Format.NO_VALUE, COLOR_RANGE_LIMITED, COLOR_RANGE_FULL})
public @interface ColorRange {}
/**
* @see MediaFormat#COLOR_RANGE_LIMITED
*/
@SuppressWarnings("InlinedApi")
public static final int COLOR_RANGE_LIMITED = MediaFormat.COLOR_RANGE_LIMITED;
/**
* @see MediaFormat#COLOR_RANGE_FULL
*/
@SuppressWarnings("InlinedApi")
public static final int COLOR_RANGE_FULL = MediaFormat.COLOR_RANGE_FULL;
/**
* Priority for media playback. * Priority for media playback.
* *
* <p>Larger values indicate higher priorities. * <p>Larger values indicate higher priorities.
......
...@@ -24,6 +24,7 @@ import com.google.android.exoplayer2.drm.DrmInitData; ...@@ -24,6 +24,7 @@ import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.ColorInfo;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Arrays; import java.util.Arrays;
...@@ -128,6 +129,10 @@ public final class Format implements Parcelable { ...@@ -128,6 +129,10 @@ public final class Format implements Parcelable {
* The projection data for 360/VR video, or null if not applicable. * The projection data for 360/VR video, or null if not applicable.
*/ */
public final byte[] projectionData; public final byte[] projectionData;
/**
* The color metadata associated with the video, helps with accurate color reproduction.
*/
public final ColorInfo colorInfo;
// Audio specific. // Audio specific.
...@@ -192,7 +197,7 @@ public final class Format implements Parcelable { ...@@ -192,7 +197,7 @@ public final class Format implements Parcelable {
String sampleMimeType, String codecs, int bitrate, int width, int height, String sampleMimeType, String codecs, int bitrate, int width, int height,
float frameRate, List<byte[]> initializationData, @C.SelectionFlags int selectionFlags) { float frameRate, List<byte[]> initializationData, @C.SelectionFlags int selectionFlags) {
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, width, return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, width,
height, frameRate, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, height, frameRate, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, selectionFlags, null, NO_VALUE, OFFSET_SAMPLE_RELATIVE, NO_VALUE, NO_VALUE, selectionFlags, null, NO_VALUE, OFFSET_SAMPLE_RELATIVE,
initializationData, null, null); initializationData, null, null);
} }
...@@ -210,17 +215,18 @@ public final class Format implements Parcelable { ...@@ -210,17 +215,18 @@ public final class Format implements Parcelable {
DrmInitData drmInitData) { DrmInitData drmInitData) {
return createVideoSampleFormat(id, sampleMimeType, codecs, bitrate, maxInputSize, width, return createVideoSampleFormat(id, sampleMimeType, codecs, bitrate, maxInputSize, width,
height, frameRate, initializationData, rotationDegrees, pixelWidthHeightRatio, null, height, frameRate, initializationData, rotationDegrees, pixelWidthHeightRatio, null,
NO_VALUE, drmInitData); NO_VALUE, null, drmInitData);
} }
public static Format createVideoSampleFormat(String id, String sampleMimeType, String codecs, public static Format createVideoSampleFormat(String id, String sampleMimeType, String codecs,
int bitrate, int maxInputSize, int width, int height, float frameRate, int bitrate, int maxInputSize, int width, int height, float frameRate,
List<byte[]> initializationData, int rotationDegrees, float pixelWidthHeightRatio, List<byte[]> initializationData, int rotationDegrees, float pixelWidthHeightRatio,
byte[] projectionData, @C.StereoMode int stereoMode, DrmInitData drmInitData) { byte[] projectionData, @C.StereoMode int stereoMode, ColorInfo colorInfo,
DrmInitData drmInitData) {
return new Format(id, null, sampleMimeType, codecs, bitrate, maxInputSize, width, height, return new Format(id, null, sampleMimeType, codecs, bitrate, maxInputSize, width, height,
frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, NO_VALUE, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode,
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, 0, null, NO_VALUE, OFFSET_SAMPLE_RELATIVE, colorInfo, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, 0, null, NO_VALUE,
initializationData, drmInitData, null); OFFSET_SAMPLE_RELATIVE, initializationData, drmInitData, null);
} }
// Audio. // Audio.
...@@ -229,8 +235,8 @@ public final class Format implements Parcelable { ...@@ -229,8 +235,8 @@ public final class Format implements Parcelable {
String sampleMimeType, String codecs, int bitrate, int channelCount, int sampleRate, String sampleMimeType, String codecs, int bitrate, int channelCount, int sampleRate,
List<byte[]> initializationData, @C.SelectionFlags int selectionFlags, String language) { List<byte[]> initializationData, @C.SelectionFlags int selectionFlags, String language) {
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, channelCount, sampleRate, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, channelCount, sampleRate,
NO_VALUE, NO_VALUE, selectionFlags, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE, NO_VALUE, NO_VALUE, NO_VALUE, selectionFlags, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE,
initializationData, null, null); initializationData, null, null);
} }
...@@ -257,7 +263,7 @@ public final class Format implements Parcelable { ...@@ -257,7 +263,7 @@ public final class Format implements Parcelable {
List<byte[]> initializationData, DrmInitData drmInitData, List<byte[]> initializationData, DrmInitData drmInitData,
@C.SelectionFlags int selectionFlags, String language, Metadata metadata) { @C.SelectionFlags int selectionFlags, String language, Metadata metadata) {
return new Format(id, null, sampleMimeType, codecs, bitrate, maxInputSize, NO_VALUE, NO_VALUE, return new Format(id, null, sampleMimeType, codecs, bitrate, maxInputSize, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, channelCount, sampleRate, pcmEncoding, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, channelCount, sampleRate, pcmEncoding,
encoderDelay, encoderPadding, selectionFlags, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE, encoderDelay, encoderPadding, selectionFlags, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE,
initializationData, drmInitData, metadata); initializationData, drmInitData, metadata);
} }
...@@ -275,8 +281,8 @@ public final class Format implements Parcelable { ...@@ -275,8 +281,8 @@ public final class Format implements Parcelable {
String sampleMimeType, String codecs, int bitrate, @C.SelectionFlags int selectionFlags, String sampleMimeType, String codecs, int bitrate, @C.SelectionFlags int selectionFlags,
String language, int accessibilityChannel) { String language, int accessibilityChannel) {
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, selectionFlags, language, accessibilityChannel, NO_VALUE, NO_VALUE, NO_VALUE, selectionFlags, language, accessibilityChannel,
OFFSET_SAMPLE_RELATIVE, null, null, null); OFFSET_SAMPLE_RELATIVE, null, null, null);
} }
...@@ -305,7 +311,7 @@ public final class Format implements Parcelable { ...@@ -305,7 +311,7 @@ public final class Format implements Parcelable {
int accessibilityChannel, DrmInitData drmInitData, long subsampleOffsetUs, int accessibilityChannel, DrmInitData drmInitData, long subsampleOffsetUs,
List<byte[]> initializationData) { List<byte[]> initializationData) {
return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, NO_VALUE, selectionFlags, language, accessibilityChannel, subsampleOffsetUs,
initializationData, drmInitData, null); initializationData, drmInitData, null);
} }
...@@ -315,7 +321,7 @@ public final class Format implements Parcelable { ...@@ -315,7 +321,7 @@ public final class Format implements Parcelable {
public static Format createImageSampleFormat(String id, String sampleMimeType, String codecs, public static Format createImageSampleFormat(String id, String sampleMimeType, String codecs,
int bitrate, List<byte[]> initializationData, String language, DrmInitData drmInitData) { int bitrate, List<byte[]> initializationData, String language, DrmInitData drmInitData) {
return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, 0, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE, initializationData, drmInitData, NO_VALUE, 0, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE, initializationData, drmInitData,
null); null);
} }
...@@ -326,7 +332,7 @@ public final class Format implements Parcelable { ...@@ -326,7 +332,7 @@ public final class Format implements Parcelable {
String sampleMimeType, String codecs, int bitrate, @C.SelectionFlags int selectionFlags, String sampleMimeType, String codecs, int bitrate, @C.SelectionFlags int selectionFlags,
String language) { String language) {
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, selectionFlags, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE, null, null, NO_VALUE, NO_VALUE, selectionFlags, language, NO_VALUE, OFFSET_SAMPLE_RELATIVE, null, null,
null); null);
} }
...@@ -334,22 +340,22 @@ public final class Format implements Parcelable { ...@@ -334,22 +340,22 @@ public final class Format implements Parcelable {
public static Format createSampleFormat(String id, String sampleMimeType, public static Format createSampleFormat(String id, String sampleMimeType,
long subsampleOffsetUs) { long subsampleOffsetUs) {
return new Format(id, null, sampleMimeType, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, return new Format(id, null, sampleMimeType, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, 0, null, NO_VALUE, subsampleOffsetUs, null, null, null); NO_VALUE, 0, null, NO_VALUE, subsampleOffsetUs, null, null, null);
} }
public static Format createSampleFormat(String id, String sampleMimeType, String codecs, public static Format createSampleFormat(String id, String sampleMimeType, String codecs,
int bitrate, DrmInitData drmInitData) { int bitrate, DrmInitData drmInitData) {
return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE, return new Format(id, null, sampleMimeType, codecs, bitrate, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE, null, NO_VALUE, null, NO_VALUE, NO_VALUE, NO_VALUE, NO_VALUE,
NO_VALUE, 0, null, NO_VALUE, OFFSET_SAMPLE_RELATIVE, null, drmInitData, null); NO_VALUE, 0, null, NO_VALUE, OFFSET_SAMPLE_RELATIVE, null, drmInitData, null);
} }
/* package */ Format(String id, String containerMimeType, String sampleMimeType, String codecs, /* package */ Format(String id, String containerMimeType, String sampleMimeType, String codecs,
int bitrate, int maxInputSize, int width, int height, float frameRate, int rotationDegrees, int bitrate, int maxInputSize, int width, int height, float frameRate, int rotationDegrees,
float pixelWidthHeightRatio, byte[] projectionData, @C.StereoMode int stereoMode, float pixelWidthHeightRatio, byte[] projectionData, @C.StereoMode int stereoMode,
int channelCount, int sampleRate, @C.PcmEncoding int pcmEncoding, int encoderDelay, ColorInfo colorInfo, int channelCount, int sampleRate, @C.PcmEncoding int pcmEncoding,
int encoderPadding, @C.SelectionFlags int selectionFlags, String language, int encoderDelay, int encoderPadding, @C.SelectionFlags int selectionFlags, String language,
int accessibilityChannel, long subsampleOffsetUs, List<byte[]> initializationData, int accessibilityChannel, long subsampleOffsetUs, List<byte[]> initializationData,
DrmInitData drmInitData, Metadata metadata) { DrmInitData drmInitData, Metadata metadata) {
this.id = id; this.id = id;
...@@ -365,6 +371,7 @@ public final class Format implements Parcelable { ...@@ -365,6 +371,7 @@ public final class Format implements Parcelable {
this.pixelWidthHeightRatio = pixelWidthHeightRatio; this.pixelWidthHeightRatio = pixelWidthHeightRatio;
this.projectionData = projectionData; this.projectionData = projectionData;
this.stereoMode = stereoMode; this.stereoMode = stereoMode;
this.colorInfo = colorInfo;
this.channelCount = channelCount; this.channelCount = channelCount;
this.sampleRate = sampleRate; this.sampleRate = sampleRate;
this.pcmEncoding = pcmEncoding; this.pcmEncoding = pcmEncoding;
...@@ -396,6 +403,7 @@ public final class Format implements Parcelable { ...@@ -396,6 +403,7 @@ public final class Format implements Parcelable {
boolean hasProjectionData = in.readInt() != 0; boolean hasProjectionData = in.readInt() != 0;
projectionData = hasProjectionData ? in.createByteArray() : null; projectionData = hasProjectionData ? in.createByteArray() : null;
stereoMode = in.readInt(); stereoMode = in.readInt();
colorInfo = in.readParcelable(ColorInfo.class.getClassLoader());
channelCount = in.readInt(); channelCount = in.readInt();
sampleRate = in.readInt(); sampleRate = in.readInt();
pcmEncoding = in.readInt(); pcmEncoding = in.readInt();
...@@ -417,26 +425,26 @@ public final class Format implements Parcelable { ...@@ -417,26 +425,26 @@ public final class Format implements Parcelable {
public Format copyWithMaxInputSize(int maxInputSize) { public Format copyWithMaxInputSize(int maxInputSize) {
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize,
width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData,
stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay,
selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs,
drmInitData, metadata); initializationData, drmInitData, metadata);
} }
public Format copyWithSubsampleOffsetUs(long subsampleOffsetUs) { public Format copyWithSubsampleOffsetUs(long subsampleOffsetUs) {
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize,
width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData,
stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay,
selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs,
drmInitData, metadata); initializationData, drmInitData, metadata);
} }
public Format copyWithContainerInfo(String id, String codecs, int bitrate, int width, int height, public Format copyWithContainerInfo(String id, String codecs, int bitrate, int width, int height,
@C.SelectionFlags int selectionFlags, String language) { @C.SelectionFlags int selectionFlags, String language) {
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize,
width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData,
stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay,
selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs,
drmInitData, metadata); initializationData, drmInitData, metadata);
} }
@SuppressWarnings("ReferenceEquality") @SuppressWarnings("ReferenceEquality")
...@@ -455,33 +463,33 @@ public final class Format implements Parcelable { ...@@ -455,33 +463,33 @@ public final class Format implements Parcelable {
: this.drmInitData; : this.drmInitData;
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, width, return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, width,
height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode,
channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, selectionFlags, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding,
language, accessibilityChannel, subsampleOffsetUs, initializationData, drmInitData, selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData,
metadata); drmInitData, metadata);
} }
public Format copyWithGaplessInfo(int encoderDelay, int encoderPadding) { public Format copyWithGaplessInfo(int encoderDelay, int encoderPadding) {
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize,
width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData,
stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay,
selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs,
drmInitData, metadata); initializationData, drmInitData, metadata);
} }
public Format copyWithDrmInitData(DrmInitData drmInitData) { public Format copyWithDrmInitData(DrmInitData drmInitData) {
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize,
width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData,
stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay,
selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs,
drmInitData, metadata); initializationData, drmInitData, metadata);
} }
public Format copyWithMetadata(Metadata metadata) { public Format copyWithMetadata(Metadata metadata) {
return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize, return new Format(id, containerMimeType, sampleMimeType, codecs, bitrate, maxInputSize,
width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData, width, height, frameRate, rotationDegrees, pixelWidthHeightRatio, projectionData,
stereoMode, channelCount, sampleRate, pcmEncoding, encoderDelay, encoderPadding, stereoMode, colorInfo, channelCount, sampleRate, pcmEncoding, encoderDelay,
selectionFlags, language, accessibilityChannel, subsampleOffsetUs, initializationData, encoderPadding, selectionFlags, language, accessibilityChannel, subsampleOffsetUs,
drmInitData, metadata); initializationData, drmInitData, metadata);
} }
/** /**
...@@ -513,6 +521,7 @@ public final class Format implements Parcelable { ...@@ -513,6 +521,7 @@ public final class Format implements Parcelable {
for (int i = 0; i < initializationData.size(); i++) { for (int i = 0; i < initializationData.size(); i++) {
format.setByteBuffer("csd-" + i, ByteBuffer.wrap(initializationData.get(i))); format.setByteBuffer("csd-" + i, ByteBuffer.wrap(initializationData.get(i)));
} }
maybeSetColorInfoV24(format, colorInfo);
return format; return format;
} }
...@@ -569,6 +578,7 @@ public final class Format implements Parcelable { ...@@ -569,6 +578,7 @@ public final class Format implements Parcelable {
|| !Util.areEqual(codecs, other.codecs) || !Util.areEqual(codecs, other.codecs)
|| !Util.areEqual(drmInitData, other.drmInitData) || !Util.areEqual(drmInitData, other.drmInitData)
|| !Util.areEqual(metadata, other.metadata) || !Util.areEqual(metadata, other.metadata)
|| !Util.areEqual(colorInfo, other.colorInfo)
|| !Arrays.equals(projectionData, other.projectionData) || !Arrays.equals(projectionData, other.projectionData)
|| initializationData.size() != other.initializationData.size()) { || initializationData.size() != other.initializationData.size()) {
return false; return false;
...@@ -581,6 +591,17 @@ public final class Format implements Parcelable { ...@@ -581,6 +591,17 @@ public final class Format implements Parcelable {
return true; return true;
} }
@TargetApi(24)
private static void maybeSetColorInfoV24(MediaFormat format, ColorInfo colorInfo) {
if (colorInfo == null) {
return;
}
maybeSetIntegerV16(format, MediaFormat.KEY_COLOR_TRANSFER, colorInfo.colorTransfer);
maybeSetIntegerV16(format, MediaFormat.KEY_COLOR_STANDARD, colorInfo.colorSpace);
maybeSetIntegerV16(format, MediaFormat.KEY_COLOR_RANGE, colorInfo.colorRange);
maybeSetByteBufferV16(format, MediaFormat.KEY_HDR_STATIC_INFO, colorInfo.hdrStaticInfo);
}
@TargetApi(16) @TargetApi(16)
private static void maybeSetStringV16(MediaFormat format, String key, String value) { private static void maybeSetStringV16(MediaFormat format, String key, String value) {
if (value != null) { if (value != null) {
...@@ -602,6 +623,13 @@ public final class Format implements Parcelable { ...@@ -602,6 +623,13 @@ public final class Format implements Parcelable {
} }
} }
@TargetApi(16)
private static void maybeSetByteBufferV16(MediaFormat format, String key, byte[] value) {
if (value != null) {
format.setByteBuffer(key, ByteBuffer.wrap(value));
}
}
// Utility methods // Utility methods
/** /**
...@@ -659,6 +687,7 @@ public final class Format implements Parcelable { ...@@ -659,6 +687,7 @@ public final class Format implements Parcelable {
dest.writeByteArray(projectionData); dest.writeByteArray(projectionData);
} }
dest.writeInt(stereoMode); dest.writeInt(stereoMode);
dest.writeParcelable(colorInfo, flags);
dest.writeInt(channelCount); dest.writeInt(channelCount);
dest.writeInt(sampleRate); dest.writeInt(sampleRate);
dest.writeInt(pcmEncoding); dest.writeInt(pcmEncoding);
......
...@@ -240,6 +240,18 @@ public class SimpleExoPlayer implements ExoPlayer { ...@@ -240,6 +240,18 @@ public class SimpleExoPlayer implements ExoPlayer {
} }
/** /**
* Clears the {@link Surface} onto which video is being rendered if it matches the one passed.
* Else does nothing.
*
* @param surface The surface to clear.
*/
public void clearVideoSurface(Surface surface) {
if (surface != null && surface == this.surface) {
setVideoSurface(null);
}
}
/**
* Sets the {@link SurfaceHolder} that holds the {@link Surface} onto which video will be * Sets the {@link SurfaceHolder} that holds the {@link Surface} onto which video will be
* rendered. The player will track the lifecycle of the surface automatically. * rendered. The player will track the lifecycle of the surface automatically.
* *
...@@ -257,13 +269,35 @@ public class SimpleExoPlayer implements ExoPlayer { ...@@ -257,13 +269,35 @@ public class SimpleExoPlayer implements ExoPlayer {
} }
/** /**
* Clears the {@link SurfaceHolder} that holds the {@link Surface} onto which video is being
* rendered if it matches the one passed. Else does nothing.
*
* @param surfaceHolder The surface holder to clear.
*/
public void clearVideoSurfaceHolder(SurfaceHolder surfaceHolder) {
if (surfaceHolder != null && surfaceHolder == this.surfaceHolder) {
setVideoSurfaceHolder(null);
}
}
/**
* Sets the {@link SurfaceView} onto which video will be rendered. The player will track the * Sets the {@link SurfaceView} onto which video will be rendered. The player will track the
* lifecycle of the surface automatically. * lifecycle of the surface automatically.
* *
* @param surfaceView The surface view. * @param surfaceView The surface view.
*/ */
public void setVideoSurfaceView(SurfaceView surfaceView) { public void setVideoSurfaceView(SurfaceView surfaceView) {
setVideoSurfaceHolder(surfaceView.getHolder()); setVideoSurfaceHolder(surfaceView == null ? null : surfaceView.getHolder());
}
/**
* Clears the {@link SurfaceView} onto which video is being rendered if it matches the one passed.
* Else does nothing.
*
* @param surfaceView The texture view to clear.
*/
public void clearVideoSurfaceView(SurfaceView surfaceView) {
clearVideoSurfaceHolder(surfaceView == null ? null : surfaceView.getHolder());
} }
/** /**
...@@ -288,6 +322,18 @@ public class SimpleExoPlayer implements ExoPlayer { ...@@ -288,6 +322,18 @@ public class SimpleExoPlayer implements ExoPlayer {
} }
/** /**
* Clears the {@link TextureView} onto which video is being rendered if it matches the one passed.
* Else does nothing.
*
* @param textureView The texture view to clear.
*/
public void clearVideoTextureView(TextureView textureView) {
if (textureView != null && textureView == this.textureView) {
setVideoTextureView(null);
}
}
/**
* Sets the stream type for audio playback (see {@link C.StreamType} and * Sets the stream type for audio playback (see {@link C.StreamType} and
* {@link android.media.AudioTrack#AudioTrack(int, int, int, int, int, int)}). If the stream type * {@link android.media.AudioTrack#AudioTrack(int, int, int, int, int, int)}). If the stream type
* is not set, audio renderers use {@link C#STREAM_TYPE_DEFAULT}. * is not set, audio renderers use {@link C#STREAM_TYPE_DEFAULT}.
...@@ -405,30 +451,34 @@ public class SimpleExoPlayer implements ExoPlayer { ...@@ -405,30 +451,34 @@ public class SimpleExoPlayer implements ExoPlayer {
} }
/** /**
* Sets a listener to receive debug events from the video renderer. * Clears the listener receiving video events if it matches the one passed. Else does nothing.
* *
* @param listener The listener. * @param listener The listener to clear.
*/ */
public void setVideoDebugListener(VideoRendererEventListener listener) { public void clearVideoListener(VideoListener listener) {
videoDebugListener = listener; if (videoListener == listener) {
videoListener = null;
}
} }
/** /**
* Sets a listener to receive debug events from the audio renderer. * Sets an output to receive text events.
* *
* @param listener The listener. * @param output The output.
*/ */
public void setAudioDebugListener(AudioRendererEventListener listener) { public void setTextOutput(TextRenderer.Output output) {
audioDebugListener = listener; textOutput = output;
} }
/** /**
* Sets an output to receive text events. * Clears the output receiving text events if it matches the one passed. Else does nothing.
* *
* @param output The output. * @param output The output to clear.
*/ */
public void setTextOutput(TextRenderer.Output output) { public void clearTextOutput(TextRenderer.Output output) {
textOutput = output; if (textOutput == output) {
textOutput = null;
}
} }
/** /**
...@@ -440,6 +490,35 @@ public class SimpleExoPlayer implements ExoPlayer { ...@@ -440,6 +490,35 @@ public class SimpleExoPlayer implements ExoPlayer {
metadataOutput = output; metadataOutput = output;
} }
/**
* Clears the output receiving metadata events if it matches the one passed. Else does nothing.
*
* @param output The output to clear.
*/
public void clearMetadataOutput(MetadataRenderer.Output output) {
if (metadataOutput == output) {
metadataOutput = null;
}
}
/**
* Sets a listener to receive debug events from the video renderer.
*
* @param listener The listener.
*/
public void setVideoDebugListener(VideoRendererEventListener listener) {
videoDebugListener = listener;
}
/**
* Sets a listener to receive debug events from the audio renderer.
*
* @param listener The listener.
*/
public void setAudioDebugListener(AudioRendererEventListener listener) {
audioDebugListener = listener;
}
// ExoPlayer implementation // ExoPlayer implementation
@Override @Override
......
...@@ -64,11 +64,25 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory { ...@@ -64,11 +64,25 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
FLAC_EXTRACTOR_CONSTRUCTOR = flacExtractorConstructor; FLAC_EXTRACTOR_CONSTRUCTOR = flacExtractorConstructor;
} }
private @MatroskaExtractor.Flags int matroskaFlags;
private @FragmentedMp4Extractor.Flags int fragmentedMp4Flags; private @FragmentedMp4Extractor.Flags int fragmentedMp4Flags;
private @Mp3Extractor.Flags int mp3Flags; private @Mp3Extractor.Flags int mp3Flags;
private @DefaultTsPayloadReaderFactory.Flags int tsFlags; private @DefaultTsPayloadReaderFactory.Flags int tsFlags;
/** /**
* Sets flags for {@link MatroskaExtractor} instances created by the factory.
*
* @see MatroskaExtractor#MatroskaExtractor(int)
* @param flags The flags to use.
* @return The factory, for convenience.
*/
public synchronized DefaultExtractorsFactory setMatroskaExtractorFlags(
@MatroskaExtractor.Flags int flags) {
this.matroskaFlags = flags;
return this;
}
/**
* Sets flags for {@link FragmentedMp4Extractor} instances created by the factory. * Sets flags for {@link FragmentedMp4Extractor} instances created by the factory.
* *
* @see FragmentedMp4Extractor#FragmentedMp4Extractor(int) * @see FragmentedMp4Extractor#FragmentedMp4Extractor(int)
...@@ -110,7 +124,7 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory { ...@@ -110,7 +124,7 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
@Override @Override
public synchronized Extractor[] createExtractors() { public synchronized Extractor[] createExtractors() {
Extractor[] extractors = new Extractor[FLAC_EXTRACTOR_CONSTRUCTOR == null ? 11 : 12]; Extractor[] extractors = new Extractor[FLAC_EXTRACTOR_CONSTRUCTOR == null ? 11 : 12];
extractors[0] = new MatroskaExtractor(); extractors[0] = new MatroskaExtractor(matroskaFlags);
extractors[1] = new FragmentedMp4Extractor(fragmentedMp4Flags); extractors[1] = new FragmentedMp4Extractor(fragmentedMp4Flags);
extractors[2] = new Mp4Extractor(); extractors[2] = new Mp4Extractor();
extractors[3] = new Mp3Extractor(mp3Flags); extractors[3] = new Mp3Extractor(mp3Flags);
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.extractor.mkv; package com.google.android.exoplayer2.extractor.mkv;
import android.support.annotation.IntDef;
import android.util.SparseArray; import android.util.SparseArray;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
...@@ -36,8 +37,11 @@ import com.google.android.exoplayer2.util.NalUnitUtil; ...@@ -36,8 +37,11 @@ import com.google.android.exoplayer2.util.NalUnitUtil;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.AvcConfig; import com.google.android.exoplayer2.video.AvcConfig;
import com.google.android.exoplayer2.video.ColorInfo;
import com.google.android.exoplayer2.video.HevcConfig; import com.google.android.exoplayer2.video.HevcConfig;
import java.io.IOException; import java.io.IOException;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.nio.ByteBuffer; import java.nio.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.util.ArrayList; import java.util.ArrayList;
...@@ -64,6 +68,22 @@ public final class MatroskaExtractor implements Extractor { ...@@ -64,6 +68,22 @@ public final class MatroskaExtractor implements Extractor {
}; };
/**
* Flags controlling the behavior of the extractor.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef(flag = true, value = {FLAG_DISABLE_SEEK_FOR_CUES})
public @interface Flags {}
/**
* Flag to disable seeking for cues.
* <p>
* Normally (i.e. when this flag is not set) the extractor will seek to the cues element if its
* position is specified in the seek head and if it's after the first cluster. Setting this flag
* disables seeking to the cues element. If the cues element is after the first cluster then the
* media is treated as being unseekable.
*/
public static final int FLAG_DISABLE_SEEK_FOR_CUES = 1;
private static final int UNSET_ENTRY_ID = -1; private static final int UNSET_ENTRY_ID = -1;
private static final int BLOCK_STATE_START = 0; private static final int BLOCK_STATE_START = 0;
...@@ -168,6 +188,23 @@ public final class MatroskaExtractor implements Extractor { ...@@ -168,6 +188,23 @@ public final class MatroskaExtractor implements Extractor {
private static final int ID_PROJECTION = 0x7670; private static final int ID_PROJECTION = 0x7670;
private static final int ID_PROJECTION_PRIVATE = 0x7672; private static final int ID_PROJECTION_PRIVATE = 0x7672;
private static final int ID_STEREO_MODE = 0x53B8; private static final int ID_STEREO_MODE = 0x53B8;
private static final int ID_COLOUR = 0x55B0;
private static final int ID_COLOUR_RANGE = 0x55B9;
private static final int ID_COLOUR_TRANSFER = 0x55BA;
private static final int ID_COLOUR_PRIMARIES = 0x55BB;
private static final int ID_MAX_CLL = 0x55BC;
private static final int ID_MAX_FALL = 0x55BD;
private static final int ID_MASTERING_METADATA = 0x55D0;
private static final int ID_PRIMARY_R_CHROMATICITY_X = 0x55D1;
private static final int ID_PRIMARY_R_CHROMATICITY_Y = 0x55D2;
private static final int ID_PRIMARY_G_CHROMATICITY_X = 0x55D3;
private static final int ID_PRIMARY_G_CHROMATICITY_Y = 0x55D4;
private static final int ID_PRIMARY_B_CHROMATICITY_X = 0x55D5;
private static final int ID_PRIMARY_B_CHROMATICITY_Y = 0x55D6;
private static final int ID_WHITE_POINT_CHROMATICITY_X = 0x55D7;
private static final int ID_WHITE_POINT_CHROMATICITY_Y = 0x55D8;
private static final int ID_LUMNINANCE_MAX = 0x55D9;
private static final int ID_LUMNINANCE_MIN = 0x55DA;
private static final int LACING_NONE = 0; private static final int LACING_NONE = 0;
private static final int LACING_XIPH = 1; private static final int LACING_XIPH = 1;
...@@ -222,6 +259,7 @@ public final class MatroskaExtractor implements Extractor { ...@@ -222,6 +259,7 @@ public final class MatroskaExtractor implements Extractor {
private final EbmlReader reader; private final EbmlReader reader;
private final VarintReader varintReader; private final VarintReader varintReader;
private final SparseArray<Track> tracks; private final SparseArray<Track> tracks;
private final boolean seekForCuesEnabled;
// Temporary arrays. // Temporary arrays.
private final ParsableByteArray nalStartCode; private final ParsableByteArray nalStartCode;
...@@ -289,12 +327,17 @@ public final class MatroskaExtractor implements Extractor { ...@@ -289,12 +327,17 @@ public final class MatroskaExtractor implements Extractor {
private ExtractorOutput extractorOutput; private ExtractorOutput extractorOutput;
public MatroskaExtractor() { public MatroskaExtractor() {
this(new DefaultEbmlReader()); this(0);
}
public MatroskaExtractor(@Flags int flags) {
this(new DefaultEbmlReader(), flags);
} }
/* package */ MatroskaExtractor(EbmlReader reader) { /* package */ MatroskaExtractor(EbmlReader reader, @Flags int flags) {
this.reader = reader; this.reader = reader;
this.reader.init(new InnerEbmlReaderOutput()); this.reader.init(new InnerEbmlReaderOutput());
seekForCuesEnabled = (flags & FLAG_DISABLE_SEEK_FOR_CUES) == 0;
varintReader = new VarintReader(); varintReader = new VarintReader();
tracks = new SparseArray<>(); tracks = new SparseArray<>();
scratch = new ParsableByteArray(4); scratch = new ParsableByteArray(4);
...@@ -368,6 +411,8 @@ public final class MatroskaExtractor implements Extractor { ...@@ -368,6 +411,8 @@ public final class MatroskaExtractor implements Extractor {
case ID_CUE_TRACK_POSITIONS: case ID_CUE_TRACK_POSITIONS:
case ID_BLOCK_GROUP: case ID_BLOCK_GROUP:
case ID_PROJECTION: case ID_PROJECTION:
case ID_COLOUR:
case ID_MASTERING_METADATA:
return EbmlReader.TYPE_MASTER; return EbmlReader.TYPE_MASTER;
case ID_EBML_READ_VERSION: case ID_EBML_READ_VERSION:
case ID_DOC_TYPE_READ_VERSION: case ID_DOC_TYPE_READ_VERSION:
...@@ -398,6 +443,11 @@ public final class MatroskaExtractor implements Extractor { ...@@ -398,6 +443,11 @@ public final class MatroskaExtractor implements Extractor {
case ID_CUE_CLUSTER_POSITION: case ID_CUE_CLUSTER_POSITION:
case ID_REFERENCE_BLOCK: case ID_REFERENCE_BLOCK:
case ID_STEREO_MODE: case ID_STEREO_MODE:
case ID_COLOUR_RANGE:
case ID_COLOUR_TRANSFER:
case ID_COLOUR_PRIMARIES:
case ID_MAX_CLL:
case ID_MAX_FALL:
return EbmlReader.TYPE_UNSIGNED_INT; return EbmlReader.TYPE_UNSIGNED_INT;
case ID_DOC_TYPE: case ID_DOC_TYPE:
case ID_CODEC_ID: case ID_CODEC_ID:
...@@ -413,6 +463,16 @@ public final class MatroskaExtractor implements Extractor { ...@@ -413,6 +463,16 @@ public final class MatroskaExtractor implements Extractor {
return EbmlReader.TYPE_BINARY; return EbmlReader.TYPE_BINARY;
case ID_DURATION: case ID_DURATION:
case ID_SAMPLING_FREQUENCY: case ID_SAMPLING_FREQUENCY:
case ID_PRIMARY_R_CHROMATICITY_X:
case ID_PRIMARY_R_CHROMATICITY_Y:
case ID_PRIMARY_G_CHROMATICITY_X:
case ID_PRIMARY_G_CHROMATICITY_Y:
case ID_PRIMARY_B_CHROMATICITY_X:
case ID_PRIMARY_B_CHROMATICITY_Y:
case ID_WHITE_POINT_CHROMATICITY_X:
case ID_WHITE_POINT_CHROMATICITY_Y:
case ID_LUMNINANCE_MAX:
case ID_LUMNINANCE_MIN:
return EbmlReader.TYPE_FLOAT; return EbmlReader.TYPE_FLOAT;
default: default:
return EbmlReader.TYPE_UNKNOWN; return EbmlReader.TYPE_UNKNOWN;
...@@ -448,7 +508,7 @@ public final class MatroskaExtractor implements Extractor { ...@@ -448,7 +508,7 @@ public final class MatroskaExtractor implements Extractor {
case ID_CLUSTER: case ID_CLUSTER:
if (!sentSeekMap) { if (!sentSeekMap) {
// We need to build cues before parsing the cluster. // We need to build cues before parsing the cluster.
if (cuesContentPosition != C.POSITION_UNSET) { if (seekForCuesEnabled && cuesContentPosition != C.POSITION_UNSET) {
// We know where the Cues element is located. Seek to request it. // We know where the Cues element is located. Seek to request it.
seekForCues = true; seekForCues = true;
} else { } else {
...@@ -471,6 +531,9 @@ public final class MatroskaExtractor implements Extractor { ...@@ -471,6 +531,9 @@ public final class MatroskaExtractor implements Extractor {
case ID_TRACK_ENTRY: case ID_TRACK_ENTRY:
currentTrack = new Track(); currentTrack = new Track();
break; break;
case ID_MASTERING_METADATA:
currentTrack.hasColorInfo = true;
break;
default: default:
break; break;
} }
...@@ -681,6 +744,60 @@ public final class MatroskaExtractor implements Extractor { ...@@ -681,6 +744,60 @@ public final class MatroskaExtractor implements Extractor {
break; break;
} }
break; break;
case ID_COLOUR_PRIMARIES:
currentTrack.hasColorInfo = true;
switch ((int) value) {
case 1:
currentTrack.colorSpace = C.COLOR_SPACE_BT709;
break;
case 4: // BT.470M.
case 5: // BT.470BG.
case 6: // SMPTE 170M.
case 7: // SMPTE 240M.
currentTrack.colorSpace = C.COLOR_SPACE_BT601;
break;
case 9:
currentTrack.colorSpace = C.COLOR_SPACE_BT2020;
break;
default:
break;
}
break;
case ID_COLOUR_TRANSFER:
switch ((int) value) {
case 1: // BT.709.
case 6: // SMPTE 170M.
case 7: // SMPTE 240M.
currentTrack.colorTransfer = C.COLOR_TRANSFER_SDR;
break;
case 16:
currentTrack.colorTransfer = C.COLOR_TRANSFER_ST2084;
break;
case 18:
currentTrack.colorTransfer = C.COLOR_TRANSFER_HLG;
break;
default:
break;
}
break;
case ID_COLOUR_RANGE:
switch((int) value) {
case 1: // Broadcast range.
currentTrack.colorRange = C.COLOR_RANGE_LIMITED;
break;
case 2:
currentTrack.colorRange = C.COLOR_RANGE_FULL;
break;
default:
break;
}
break;
case ID_MAX_CLL:
currentTrack.maxContentLuminance = (int) value;
break;
case ID_MAX_FALL:
currentTrack.maxFrameAverageLuminance = (int) value;
break;
default: default:
break; break;
} }
...@@ -694,6 +811,36 @@ public final class MatroskaExtractor implements Extractor { ...@@ -694,6 +811,36 @@ public final class MatroskaExtractor implements Extractor {
case ID_SAMPLING_FREQUENCY: case ID_SAMPLING_FREQUENCY:
currentTrack.sampleRate = (int) value; currentTrack.sampleRate = (int) value;
break; break;
case ID_PRIMARY_R_CHROMATICITY_X:
currentTrack.primaryRChromaticityX = (float) value;
break;
case ID_PRIMARY_R_CHROMATICITY_Y:
currentTrack.primaryRChromaticityY = (float) value;
break;
case ID_PRIMARY_G_CHROMATICITY_X:
currentTrack.primaryGChromaticityX = (float) value;
break;
case ID_PRIMARY_G_CHROMATICITY_Y:
currentTrack.primaryGChromaticityY = (float) value;
break;
case ID_PRIMARY_B_CHROMATICITY_X:
currentTrack.primaryBChromaticityX = (float) value;
break;
case ID_PRIMARY_B_CHROMATICITY_Y:
currentTrack.primaryBChromaticityY = (float) value;
break;
case ID_WHITE_POINT_CHROMATICITY_X:
currentTrack.whitePointChromaticityX = (float) value;
break;
case ID_WHITE_POINT_CHROMATICITY_Y:
currentTrack.whitePointChromaticityY = (float) value;
break;
case ID_LUMNINANCE_MAX:
currentTrack.maxMasteringLuminance = (float) value;
break;
case ID_LUMNINANCE_MIN:
currentTrack.minMasteringLuminance = (float) value;
break;
default: default:
break; break;
} }
...@@ -1305,6 +1452,16 @@ public final class MatroskaExtractor implements Extractor { ...@@ -1305,6 +1452,16 @@ public final class MatroskaExtractor implements Extractor {
private static final class Track { private static final class Track {
private static final int DISPLAY_UNIT_PIXELS = 0; private static final int DISPLAY_UNIT_PIXELS = 0;
private static final int MAX_CHROMATICITY = 50000; // Defined in CTA-861.3.
/**
* Default max content light level (CLL) that should be encoded into hdrStaticInfo.
*/
private static final int DEFAULT_MAX_CLL = 1000; // nits.
/**
* Default frame-average light level (FALL) that should be encoded into hdrStaticInfo.
*/
private static final int DEFAULT_MAX_FALL = 200; // nits.
// Common elements. // Common elements.
public String codecId; public String codecId;
...@@ -1326,6 +1483,25 @@ public final class MatroskaExtractor implements Extractor { ...@@ -1326,6 +1483,25 @@ public final class MatroskaExtractor implements Extractor {
public byte[] projectionData = null; public byte[] projectionData = null;
@C.StereoMode @C.StereoMode
public int stereoMode = Format.NO_VALUE; public int stereoMode = Format.NO_VALUE;
public boolean hasColorInfo = false;
@C.ColorSpace
public int colorSpace = Format.NO_VALUE;
@C.ColorTransfer
public int colorTransfer = Format.NO_VALUE;
@C.ColorRange
public int colorRange = Format.NO_VALUE;
public int maxContentLuminance = DEFAULT_MAX_CLL;
public int maxFrameAverageLuminance = DEFAULT_MAX_FALL;
public float primaryRChromaticityX = Format.NO_VALUE;
public float primaryRChromaticityY = Format.NO_VALUE;
public float primaryGChromaticityX = Format.NO_VALUE;
public float primaryGChromaticityY = Format.NO_VALUE;
public float primaryBChromaticityX = Format.NO_VALUE;
public float primaryBChromaticityY = Format.NO_VALUE;
public float whitePointChromaticityX = Format.NO_VALUE;
public float whitePointChromaticityY = Format.NO_VALUE;
public float maxMasteringLuminance = Format.NO_VALUE;
public float minMasteringLuminance = Format.NO_VALUE;
// Audio elements. Initially set to their default values. // Audio elements. Initially set to their default values.
public int channelCount = 1; public int channelCount = 1;
...@@ -1495,9 +1671,15 @@ public final class MatroskaExtractor implements Extractor { ...@@ -1495,9 +1671,15 @@ public final class MatroskaExtractor implements Extractor {
if (displayWidth != Format.NO_VALUE && displayHeight != Format.NO_VALUE) { if (displayWidth != Format.NO_VALUE && displayHeight != Format.NO_VALUE) {
pixelWidthHeightRatio = ((float) (height * displayWidth)) / (width * displayHeight); pixelWidthHeightRatio = ((float) (height * displayWidth)) / (width * displayHeight);
} }
ColorInfo colorInfo = null;
if (hasColorInfo) {
byte[] hdrStaticInfo = getHdrStaticInfo();
colorInfo = new ColorInfo(colorSpace, colorRange, colorTransfer, hdrStaticInfo);
}
format = Format.createVideoSampleFormat(Integer.toString(trackId), mimeType, null, format = Format.createVideoSampleFormat(Integer.toString(trackId), mimeType, null,
Format.NO_VALUE, maxInputSize, width, height, Format.NO_VALUE, initializationData, Format.NO_VALUE, maxInputSize, width, height, Format.NO_VALUE, initializationData,
Format.NO_VALUE, pixelWidthHeightRatio, projectionData, stereoMode, drmInitData); Format.NO_VALUE, pixelWidthHeightRatio, projectionData, stereoMode, colorInfo,
drmInitData);
} else if (MimeTypes.APPLICATION_SUBRIP.equals(mimeType)) { } else if (MimeTypes.APPLICATION_SUBRIP.equals(mimeType)) {
type = C.TRACK_TYPE_TEXT; type = C.TRACK_TYPE_TEXT;
format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, null, format = Format.createTextSampleFormat(Integer.toString(trackId), mimeType, null,
...@@ -1517,6 +1699,38 @@ public final class MatroskaExtractor implements Extractor { ...@@ -1517,6 +1699,38 @@ public final class MatroskaExtractor implements Extractor {
} }
/** /**
* Returns the HDR Static Info as defined in CTA-861.3.
*/
private byte[] getHdrStaticInfo() {
// Are all fields present.
if (primaryRChromaticityX == Format.NO_VALUE || primaryRChromaticityY == Format.NO_VALUE
|| primaryGChromaticityX == Format.NO_VALUE || primaryGChromaticityY == Format.NO_VALUE
|| primaryBChromaticityX == Format.NO_VALUE || primaryBChromaticityY == Format.NO_VALUE
|| whitePointChromaticityX == Format.NO_VALUE
|| whitePointChromaticityY == Format.NO_VALUE || maxMasteringLuminance == Format.NO_VALUE
|| minMasteringLuminance == Format.NO_VALUE) {
return null;
}
byte[] hdrStaticInfoData = new byte[25];
ByteBuffer hdrStaticInfo = ByteBuffer.wrap(hdrStaticInfoData);
hdrStaticInfo.put((byte) 0); // Type.
hdrStaticInfo.putShort((short) ((primaryRChromaticityX * MAX_CHROMATICITY) + 0.5f));
hdrStaticInfo.putShort((short) ((primaryRChromaticityY * MAX_CHROMATICITY) + 0.5f));
hdrStaticInfo.putShort((short) ((primaryGChromaticityX * MAX_CHROMATICITY) + 0.5f));
hdrStaticInfo.putShort((short) ((primaryGChromaticityY * MAX_CHROMATICITY) + 0.5f));
hdrStaticInfo.putShort((short) ((primaryBChromaticityX * MAX_CHROMATICITY) + 0.5f));
hdrStaticInfo.putShort((short) ((primaryBChromaticityY * MAX_CHROMATICITY) + 0.5f));
hdrStaticInfo.putShort((short) ((whitePointChromaticityX * MAX_CHROMATICITY) + 0.5f));
hdrStaticInfo.putShort((short) ((whitePointChromaticityY * MAX_CHROMATICITY) + 0.5f));
hdrStaticInfo.putShort((short) (maxMasteringLuminance + 0.5f));
hdrStaticInfo.putShort((short) (minMasteringLuminance + 0.5f));
hdrStaticInfo.putShort((short) maxContentLuminance);
hdrStaticInfo.putShort((short) maxFrameAverageLuminance);
return hdrStaticInfoData;
}
/**
* Builds initialization data for a {@link Format} from FourCC codec private data. * Builds initialization data for a {@link Format} from FourCC codec private data.
* <p> * <p>
* VC1 is the only supported compression type. * VC1 is the only supported compression type.
......
...@@ -762,7 +762,7 @@ import java.util.List; ...@@ -762,7 +762,7 @@ import java.util.List;
out.format = Format.createVideoSampleFormat(Integer.toString(trackId), mimeType, null, out.format = Format.createVideoSampleFormat(Integer.toString(trackId), mimeType, null,
Format.NO_VALUE, Format.NO_VALUE, width, height, Format.NO_VALUE, initializationData, Format.NO_VALUE, Format.NO_VALUE, width, height, Format.NO_VALUE, initializationData,
rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, drmInitData); rotationDegrees, pixelWidthHeightRatio, projectionData, stereoMode, null, drmInitData);
} }
/** /**
......
/*
* Copyright (C) 2017 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.video;
import android.os.Parcel;
import android.os.Parcelable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import java.util.Arrays;
/**
* Stores color info.
*/
public final class ColorInfo implements Parcelable {
/**
* The color space of the video. Valid values are {@link C#COLOR_SPACE_BT601}, {@link
* C#COLOR_SPACE_BT709}, {@link C#COLOR_SPACE_BT2020} or {@link Format#NO_VALUE} if unknown.
*/
@C.ColorSpace
public final int colorSpace;
/**
* The color range of the video. Valid values are {@link C#COLOR_RANGE_LIMITED}, {@link
* C#COLOR_RANGE_FULL} or {@link Format#NO_VALUE} if unknown.
*/
@C.ColorRange
public final int colorRange;
/**
* The color transfer characteristicks of the video. Valid values are {@link
* C#COLOR_TRANSFER_HLG}, {@link C#COLOR_TRANSFER_ST2084}, {@link C#COLOR_TRANSFER_SDR} or {@link
* Format#NO_VALUE} if unknown.
*/
@C.ColorTransfer
public final int colorTransfer;
/**
* HdrStaticInfo as defined in CTA-861.3.
*/
public final byte[] hdrStaticInfo;
// Lazily initialized hashcode.
private int hashCode;
/**
* Constructs the ColorInfo.
*
* @param colorSpace The color space of the video.
* @param colorRange The color range of the video.
* @param colorTransfer The color transfer characteristics of the video.
* @param hdrStaticInfo HdrStaticInfo as defined in CTA-861.3.
*/
public ColorInfo(@C.ColorSpace int colorSpace, @C.ColorRange int colorRange,
@C.ColorTransfer int colorTransfer, byte[] hdrStaticInfo) {
this.colorSpace = colorSpace;
this.colorRange = colorRange;
this.colorTransfer = colorTransfer;
this.hdrStaticInfo = hdrStaticInfo;
}
@SuppressWarnings("ResourceType")
/* package */ ColorInfo(Parcel in) {
colorSpace = in.readInt();
colorRange = in.readInt();
colorTransfer = in.readInt();
boolean hasHdrStaticInfo = in.readInt() != 0;
hdrStaticInfo = hasHdrStaticInfo ? in.createByteArray() : null;
}
// Parcelable implementation.
@Override
public boolean equals(Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
ColorInfo other = (ColorInfo) obj;
if (colorSpace != other.colorSpace || colorRange != other.colorRange
|| colorTransfer != other.colorTransfer
|| !Arrays.equals(hdrStaticInfo, other.hdrStaticInfo)) {
return false;
}
return true;
}
@Override
public String toString() {
return "ColorInfo(" + colorSpace + ", " + colorRange + ", " + colorTransfer
+ ", " + (hdrStaticInfo != null) + ")";
}
@Override
public int hashCode() {
if (hashCode == 0) {
int result = 17;
result = 31 * result + colorSpace;
result = 31 * result + colorRange;
result = 31 * result + colorTransfer;
result = 31 * result + Arrays.hashCode(hdrStaticInfo);
hashCode = result;
}
return hashCode;
}
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(colorSpace);
dest.writeInt(colorRange);
dest.writeInt(colorTransfer);
dest.writeInt(hdrStaticInfo != null ? 1 : 0);
if (hdrStaticInfo != null) {
dest.writeByteArray(hdrStaticInfo);
}
}
public static final Parcelable.Creator<ColorInfo> CREATOR = new Parcelable.Creator<ColorInfo>() {
@Override
public ColorInfo createFromParcel(Parcel in) {
return new ColorInfo(in);
}
@Override
public ColorInfo[] newArray(int size) {
return new ColorInfo[0];
}
};
}
...@@ -166,7 +166,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -166,7 +166,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
currentPixelWidthHeightRatio = Format.NO_VALUE; currentPixelWidthHeightRatio = Format.NO_VALUE;
pendingPixelWidthHeightRatio = Format.NO_VALUE; pendingPixelWidthHeightRatio = Format.NO_VALUE;
scalingMode = C.VIDEO_SCALING_MODE_DEFAULT; scalingMode = C.VIDEO_SCALING_MODE_DEFAULT;
clearLastReportedVideoSize(); clearReportedVideoSize();
} }
@Override @Override
...@@ -229,8 +229,11 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -229,8 +229,11 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
super.onPositionReset(positionUs, joining); super.onPositionReset(positionUs, joining);
clearRenderedFirstFrame(); clearRenderedFirstFrame();
consecutiveDroppedFrameCount = 0; consecutiveDroppedFrameCount = 0;
joiningDeadlineMs = joining && allowedJoiningTimeMs > 0 if (joining) {
? (SystemClock.elapsedRealtime() + allowedJoiningTimeMs) : C.TIME_UNSET; setJoiningDeadlineMs();
} else {
joiningDeadlineMs = C.TIME_UNSET;
}
} }
@Override @Override
...@@ -272,7 +275,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -272,7 +275,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
currentHeight = Format.NO_VALUE; currentHeight = Format.NO_VALUE;
currentPixelWidthHeightRatio = Format.NO_VALUE; currentPixelWidthHeightRatio = Format.NO_VALUE;
pendingPixelWidthHeightRatio = Format.NO_VALUE; pendingPixelWidthHeightRatio = Format.NO_VALUE;
clearLastReportedVideoSize(); clearReportedVideoSize();
clearRenderedFirstFrame();
frameReleaseTimeHelper.disable(); frameReleaseTimeHelper.disable();
tunnelingOnFrameRenderedListener = null; tunnelingOnFrameRenderedListener = null;
try { try {
...@@ -312,11 +316,25 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -312,11 +316,25 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
maybeInitCodec(); maybeInitCodec();
} }
} }
if (surface != null) {
// If we know the video size, report it again immediately.
maybeRenotifyVideoSizeChanged();
// We haven't rendered to the new surface yet.
clearRenderedFirstFrame();
if (state == STATE_STARTED) {
setJoiningDeadlineMs();
}
} else {
// The surface has been removed.
clearReportedVideoSize();
clearRenderedFirstFrame();
}
} else if (surface != null) {
// The surface is unchanged and non-null. If we know the video size and/or have already
// rendered to the surface, report these again immediately.
maybeRenotifyVideoSizeChanged();
maybeRenotifyRenderedFirstFrame();
} }
// Clear state so that we always call the event listener with the video size and when a frame
// is rendered, even if the surface hasn't changed.
clearRenderedFirstFrame();
clearLastReportedVideoSize();
} }
@Override @Override
...@@ -521,6 +539,11 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -521,6 +539,11 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
maybeNotifyRenderedFirstFrame(); maybeNotifyRenderedFirstFrame();
} }
private void setJoiningDeadlineMs() {
joiningDeadlineMs = allowedJoiningTimeMs > 0
? (SystemClock.elapsedRealtime() + allowedJoiningTimeMs) : C.TIME_UNSET;
}
private void clearRenderedFirstFrame() { private void clearRenderedFirstFrame() {
renderedFirstFrame = false; renderedFirstFrame = false;
// The first frame notification is triggered by renderOutputBuffer or renderOutputBufferV21 for // The first frame notification is triggered by renderOutputBuffer or renderOutputBufferV21 for
...@@ -543,7 +566,13 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -543,7 +566,13 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
} }
} }
private void clearLastReportedVideoSize() { private void maybeRenotifyRenderedFirstFrame() {
if (renderedFirstFrame) {
eventDispatcher.renderedFirstFrame(surface);
}
}
private void clearReportedVideoSize() {
reportedWidth = Format.NO_VALUE; reportedWidth = Format.NO_VALUE;
reportedHeight = Format.NO_VALUE; reportedHeight = Format.NO_VALUE;
reportedPixelWidthHeightRatio = Format.NO_VALUE; reportedPixelWidthHeightRatio = Format.NO_VALUE;
...@@ -563,6 +592,13 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer { ...@@ -563,6 +592,13 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
} }
} }
private void maybeRenotifyVideoSizeChanged() {
if (reportedWidth != Format.NO_VALUE || reportedHeight != Format.NO_VALUE) {
eventDispatcher.videoSizeChanged(currentWidth, currentHeight, currentUnappliedRotationDegrees,
currentPixelWidthHeightRatio);
}
}
private void maybeNotifyDroppedFrames() { private void maybeNotifyDroppedFrames() {
if (droppedFrames > 0) { if (droppedFrames > 0) {
long now = SystemClock.elapsedRealtime(); long now = SystemClock.elapsedRealtime();
......
...@@ -393,7 +393,7 @@ public class DefaultDashChunkSource implements DashChunkSource { ...@@ -393,7 +393,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
if (MimeTypes.APPLICATION_RAWCC.equals(containerMimeType)) { if (MimeTypes.APPLICATION_RAWCC.equals(containerMimeType)) {
extractor = new RawCcExtractor(representation.format); extractor = new RawCcExtractor(representation.format);
} else if (mimeTypeIsWebm(containerMimeType)) { } else if (mimeTypeIsWebm(containerMimeType)) {
extractor = new MatroskaExtractor(); extractor = new MatroskaExtractor(MatroskaExtractor.FLAG_DISABLE_SEEK_FOR_CUES);
} else { } else {
int flags = 0; int flags = 0;
if (enableEventMessageTrack) { if (enableEventMessageTrack) {
......
...@@ -70,7 +70,6 @@ public class HlsMediaPlaylistParserTest extends TestCase { ...@@ -70,7 +70,6 @@ public class HlsMediaPlaylistParserTest extends TestCase {
try { try {
HlsPlaylist playlist = new HlsPlaylistParser().parse(playlistUri, inputStream); HlsPlaylist playlist = new HlsPlaylistParser().parse(playlistUri, inputStream);
assertNotNull(playlist); assertNotNull(playlist);
assertEquals(HlsPlaylist.TYPE_MEDIA, playlist.type);
HlsMediaPlaylist mediaPlaylist = (HlsMediaPlaylist) playlist; HlsMediaPlaylist mediaPlaylist = (HlsMediaPlaylist) playlist;
assertEquals(HlsMediaPlaylist.PLAYLIST_TYPE_VOD, mediaPlaylist.playlistType); assertEquals(HlsMediaPlaylist.PLAYLIST_TYPE_VOD, mediaPlaylist.playlistType);
......
...@@ -55,7 +55,7 @@ public final class HlsMasterPlaylist extends HlsPlaylist { ...@@ -55,7 +55,7 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
public HlsMasterPlaylist(String baseUri, List<HlsUrl> variants, List<HlsUrl> audios, public HlsMasterPlaylist(String baseUri, List<HlsUrl> variants, List<HlsUrl> audios,
List<HlsUrl> subtitles, Format muxedAudioFormat, List<Format> muxedCaptionFormats) { List<HlsUrl> subtitles, Format muxedAudioFormat, List<Format> muxedCaptionFormats) {
super(baseUri, HlsPlaylist.TYPE_MASTER); super(baseUri);
this.variants = Collections.unmodifiableList(variants); this.variants = Collections.unmodifiableList(variants);
this.audios = Collections.unmodifiableList(audios); this.audios = Collections.unmodifiableList(audios);
this.subtitles = Collections.unmodifiableList(subtitles); this.subtitles = Collections.unmodifiableList(subtitles);
......
...@@ -97,7 +97,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist { ...@@ -97,7 +97,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
long startTimeUs, boolean hasDiscontinuitySequence, int discontinuitySequence, long startTimeUs, boolean hasDiscontinuitySequence, int discontinuitySequence,
int mediaSequence, int version, long targetDurationUs, boolean hasEndTag, int mediaSequence, int version, long targetDurationUs, boolean hasEndTag,
boolean hasProgramDateTime, Segment initializationSegment, List<Segment> segments) { boolean hasProgramDateTime, Segment initializationSegment, List<Segment> segments) {
super(baseUri, HlsPlaylist.TYPE_MEDIA); super(baseUri);
this.playlistType = playlistType; this.playlistType = playlistType;
this.startTimeUs = startTimeUs; this.startTimeUs = startTimeUs;
this.hasDiscontinuitySequence = hasDiscontinuitySequence; this.hasDiscontinuitySequence = hasDiscontinuitySequence;
......
...@@ -15,30 +15,15 @@ ...@@ -15,30 +15,15 @@
*/ */
package com.google.android.exoplayer2.source.hls.playlist; package com.google.android.exoplayer2.source.hls.playlist;
import android.support.annotation.IntDef;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
/** /**
* Represents an HLS playlist. * Represents an HLS playlist.
*/ */
public abstract class HlsPlaylist { public abstract class HlsPlaylist {
/**
* The type of playlist.
*/
@Retention(RetentionPolicy.SOURCE)
@IntDef({TYPE_MASTER, TYPE_MEDIA})
public @interface Type {}
public static final int TYPE_MASTER = 0;
public static final int TYPE_MEDIA = 1;
public final String baseUri; public final String baseUri;
@Type public final int type;
protected HlsPlaylist(String baseUri, @Type int type) { protected HlsPlaylist(String baseUri) {
this.baseUri = baseUri; this.baseUri = baseUri;
this.type = type;
} }
} }
...@@ -477,9 +477,15 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable ...@@ -477,9 +477,15 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
@Override @Override
public void onLoadCompleted(ParsingLoadable<HlsPlaylist> loadable, long elapsedRealtimeMs, public void onLoadCompleted(ParsingLoadable<HlsPlaylist> loadable, long elapsedRealtimeMs,
long loadDurationMs) { long loadDurationMs) {
processLoadedPlaylist((HlsMediaPlaylist) loadable.getResult()); HlsPlaylist result = loadable.getResult();
eventDispatcher.loadCompleted(loadable.dataSpec, C.DATA_TYPE_MANIFEST, elapsedRealtimeMs, if (result instanceof HlsMediaPlaylist) {
loadDurationMs, loadable.bytesLoaded()); processLoadedPlaylist((HlsMediaPlaylist) result);
eventDispatcher.loadCompleted(loadable.dataSpec, C.DATA_TYPE_MANIFEST, elapsedRealtimeMs,
loadDurationMs, loadable.bytesLoaded());
} else {
onLoadError(loadable, elapsedRealtimeMs, loadDurationMs,
new ParserException("Loaded playlist has unexpected type."));
}
} }
@Override @Override
......
...@@ -21,6 +21,8 @@ import android.content.res.Resources; ...@@ -21,6 +21,8 @@ import android.content.res.Resources;
import android.content.res.TypedArray; import android.content.res.TypedArray;
import android.graphics.Bitmap; import android.graphics.Bitmap;
import android.graphics.BitmapFactory; import android.graphics.BitmapFactory;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.view.KeyEvent; import android.view.KeyEvent;
import android.view.LayoutInflater; import android.view.LayoutInflater;
...@@ -74,12 +76,18 @@ import java.util.List; ...@@ -74,12 +76,18 @@ import java.util.List;
* <li>Default: {@code null}</li> * <li>Default: {@code null}</li>
* </ul> * </ul>
* </li> * </li>
* <li><b>{@code use_controller}</b> - Whether playback controls are displayed. * <li><b>{@code use_controller}</b> - Whether the playback controls can be shown.
* <ul> * <ul>
* <li>Corresponding method: {@link #setUseController(boolean)}</li> * <li>Corresponding method: {@link #setUseController(boolean)}</li>
* <li>Default: {@code true}</li> * <li>Default: {@code true}</li>
* </ul> * </ul>
* </li> * </li>
* <li><b>{@code hide_on_touch}</b> - Whether the playback controls are hidden by touch events.
* <ul>
* <li>Corresponding method: {@link #setControllerHideOnTouch(boolean)}</li>
* <li>Default: {@code true}</li>
* </ul>
* </li>
* <li><b>{@code resize_mode}</b> - Controls how video and album art is resized within the view. * <li><b>{@code resize_mode}</b> - Controls how video and album art is resized within the view.
* Valid values are {@code fit}, {@code fixed_width}, {@code fixed_height} and {@code fill}. * Valid values are {@code fit}, {@code fixed_width}, {@code fixed_height} and {@code fill}.
* <ul> * <ul>
...@@ -190,6 +198,7 @@ public final class SimpleExoPlayerView extends FrameLayout { ...@@ -190,6 +198,7 @@ public final class SimpleExoPlayerView extends FrameLayout {
private boolean useArtwork; private boolean useArtwork;
private Bitmap defaultArtwork; private Bitmap defaultArtwork;
private int controllerShowTimeoutMs; private int controllerShowTimeoutMs;
private boolean controllerHideOnTouch;
public SimpleExoPlayerView(Context context) { public SimpleExoPlayerView(Context context) {
this(context, null); this(context, null);
...@@ -228,6 +237,7 @@ public final class SimpleExoPlayerView extends FrameLayout { ...@@ -228,6 +237,7 @@ public final class SimpleExoPlayerView extends FrameLayout {
int surfaceType = SURFACE_TYPE_SURFACE_VIEW; int surfaceType = SURFACE_TYPE_SURFACE_VIEW;
int resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT; int resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT;
int controllerShowTimeoutMs = PlaybackControlView.DEFAULT_SHOW_TIMEOUT_MS; int controllerShowTimeoutMs = PlaybackControlView.DEFAULT_SHOW_TIMEOUT_MS;
boolean controllerHideOnTouch = true;
if (attrs != null) { if (attrs != null) {
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.SimpleExoPlayerView, 0, 0); R.styleable.SimpleExoPlayerView, 0, 0);
...@@ -242,6 +252,8 @@ public final class SimpleExoPlayerView extends FrameLayout { ...@@ -242,6 +252,8 @@ public final class SimpleExoPlayerView extends FrameLayout {
resizeMode = a.getInt(R.styleable.SimpleExoPlayerView_resize_mode, resizeMode); resizeMode = a.getInt(R.styleable.SimpleExoPlayerView_resize_mode, resizeMode);
controllerShowTimeoutMs = a.getInt(R.styleable.SimpleExoPlayerView_show_timeout, controllerShowTimeoutMs = a.getInt(R.styleable.SimpleExoPlayerView_show_timeout,
controllerShowTimeoutMs); controllerShowTimeoutMs);
controllerHideOnTouch = a.getBoolean(R.styleable.SimpleExoPlayerView_hide_on_touch,
controllerHideOnTouch);
} finally { } finally {
a.recycle(); a.recycle();
} }
...@@ -304,11 +316,36 @@ public final class SimpleExoPlayerView extends FrameLayout { ...@@ -304,11 +316,36 @@ public final class SimpleExoPlayerView extends FrameLayout {
this.controller = null; this.controller = null;
} }
this.controllerShowTimeoutMs = controller != null ? controllerShowTimeoutMs : 0; this.controllerShowTimeoutMs = controller != null ? controllerShowTimeoutMs : 0;
this.controllerHideOnTouch = controllerHideOnTouch;
this.useController = useController && controller != null; this.useController = useController && controller != null;
hideController(); hideController();
} }
/** /**
* Switches the view targeted by a given {@link SimpleExoPlayer}.
*
* @param player The player whose target view is being switched.
* @param oldPlayerView The old view to detach from the player.
* @param newPlayerView The new view to attach to the player.
*/
public static void switchTargetView(@NonNull SimpleExoPlayer player,
@Nullable SimpleExoPlayerView oldPlayerView, @Nullable SimpleExoPlayerView newPlayerView) {
if (oldPlayerView == newPlayerView) {
return;
}
// We attach the new view before detaching the old one because this ordering allows the player
// to swap directly from one surface to another, without transitioning through a state where no
// surface is attached. This is significantly more efficient and achieves a more seamless
// transition when using platform provided video decoders.
if (newPlayerView != null) {
newPlayerView.setPlayer(player);
}
if (oldPlayerView != null) {
oldPlayerView.setPlayer(null);
}
}
/**
* Returns the player currently set on this view, or null if no player is set. * Returns the player currently set on this view, or null if no player is set.
*/ */
public SimpleExoPlayer getPlayer() { public SimpleExoPlayer getPlayer() {
...@@ -319,6 +356,12 @@ public final class SimpleExoPlayerView extends FrameLayout { ...@@ -319,6 +356,12 @@ public final class SimpleExoPlayerView extends FrameLayout {
* Set the {@link SimpleExoPlayer} to use. The {@link SimpleExoPlayer#setTextOutput} and * Set the {@link SimpleExoPlayer} to use. The {@link SimpleExoPlayer#setTextOutput} and
* {@link SimpleExoPlayer#setVideoListener} method of the player will be called and previous * {@link SimpleExoPlayer#setVideoListener} method of the player will be called and previous
* assignments are overridden. * assignments are overridden.
* <p>
* To transition a {@link SimpleExoPlayer} from targeting one view to another, it's recommended to
* use {@link #switchTargetView(SimpleExoPlayer, SimpleExoPlayerView, SimpleExoPlayerView)} rather
* than this method. If you do wish to use this method directly, be sure to attach the player to
* the new view <em>before</em> calling {@code setPlayer(null)} to detach it from the old one.
* This ordering is significantly more efficient and may allow for more seamless transitions.
* *
* @param player The {@link SimpleExoPlayer} to use. * @param player The {@link SimpleExoPlayer} to use.
*/ */
...@@ -327,10 +370,14 @@ public final class SimpleExoPlayerView extends FrameLayout { ...@@ -327,10 +370,14 @@ public final class SimpleExoPlayerView extends FrameLayout {
return; return;
} }
if (this.player != null) { if (this.player != null) {
this.player.setTextOutput(null);
this.player.setVideoListener(null);
this.player.removeListener(componentListener); this.player.removeListener(componentListener);
this.player.setVideoSurface(null); this.player.clearTextOutput(componentListener);
this.player.clearVideoListener(componentListener);
if (surfaceView instanceof TextureView) {
this.player.clearVideoTextureView((TextureView) surfaceView);
} else if (surfaceView instanceof SurfaceView) {
this.player.clearVideoSurfaceView((SurfaceView) surfaceView);
}
} }
this.player = player; this.player = player;
if (useController) { if (useController) {
...@@ -346,8 +393,8 @@ public final class SimpleExoPlayerView extends FrameLayout { ...@@ -346,8 +393,8 @@ public final class SimpleExoPlayerView extends FrameLayout {
player.setVideoSurfaceView((SurfaceView) surfaceView); player.setVideoSurfaceView((SurfaceView) surfaceView);
} }
player.setVideoListener(componentListener); player.setVideoListener(componentListener);
player.addListener(componentListener);
player.setTextOutput(componentListener); player.setTextOutput(componentListener);
player.addListener(componentListener);
maybeShowController(false); maybeShowController(false);
updateForCurrentTrackSelections(); updateForCurrentTrackSelections();
} else { } else {
...@@ -407,17 +454,17 @@ public final class SimpleExoPlayerView extends FrameLayout { ...@@ -407,17 +454,17 @@ public final class SimpleExoPlayerView extends FrameLayout {
} }
/** /**
* Returns whether the playback controls are enabled. * Returns whether the playback controls can be shown.
*/ */
public boolean getUseController() { public boolean getUseController() {
return useController; return useController;
} }
/** /**
* Sets whether playback controls are enabled. If set to {@code false} the playback controls are * Sets whether the playback controls can be shown. If set to {@code false} the playback controls
* never visible and are disconnected from the player. * are never visible and are disconnected from the player.
* *
* @param useController Whether playback controls should be enabled. * @param useController Whether the playback controls can be shown.
*/ */
public void setUseController(boolean useController) { public void setUseController(boolean useController) {
Assertions.checkState(!useController || controller != null); Assertions.checkState(!useController || controller != null);
...@@ -487,6 +534,23 @@ public final class SimpleExoPlayerView extends FrameLayout { ...@@ -487,6 +534,23 @@ public final class SimpleExoPlayerView extends FrameLayout {
} }
/** /**
* Returns whether the playback controls are hidden by touch events.
*/
public boolean getControllerHideOnTouch() {
return controllerHideOnTouch;
}
/**
* Sets whether the playback controls are hidden by touch events.
*
* @param controllerHideOnTouch Whether the playback controls are hidden by touch events.
*/
public void setControllerHideOnTouch(boolean controllerHideOnTouch) {
Assertions.checkState(controller != null);
this.controllerHideOnTouch = controllerHideOnTouch;
}
/**
* Set the {@link PlaybackControlView.VisibilityListener}. * Set the {@link PlaybackControlView.VisibilityListener}.
* *
* @param listener The listener to be notified about visibility changes. * @param listener The listener to be notified about visibility changes.
...@@ -573,10 +637,10 @@ public final class SimpleExoPlayerView extends FrameLayout { ...@@ -573,10 +637,10 @@ public final class SimpleExoPlayerView extends FrameLayout {
if (!useController || player == null || ev.getActionMasked() != MotionEvent.ACTION_DOWN) { if (!useController || player == null || ev.getActionMasked() != MotionEvent.ACTION_DOWN) {
return false; return false;
} }
if (controller.isVisible()) { if (!controller.isVisible()) {
controller.hide();
} else {
maybeShowController(true); maybeShowController(true);
} else if (controllerHideOnTouch) {
controller.hide();
} }
return true; return true;
} }
......
...@@ -39,11 +39,12 @@ ...@@ -39,11 +39,12 @@
<attr name="use_artwork" format="boolean"/> <attr name="use_artwork" format="boolean"/>
<attr name="default_artwork" format="reference"/> <attr name="default_artwork" format="reference"/>
<attr name="use_controller" format="boolean"/> <attr name="use_controller" format="boolean"/>
<attr name="hide_on_touch" format="boolean"/>
<attr name="resize_mode"/>
<attr name="surface_type"/> <attr name="surface_type"/>
<attr name="show_timeout"/> <attr name="show_timeout"/>
<attr name="rewind_increment"/> <attr name="rewind_increment"/>
<attr name="fastforward_increment"/> <attr name="fastforward_increment"/>
<attr name="resize_mode"/>
<attr name="player_layout_id"/> <attr name="player_layout_id"/>
<attr name="controller_layout_id"/> <attr name="controller_layout_id"/>
</declare-styleable> </declare-styleable>
......
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