Commit f9030987 by Oliver Woodman

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

parents 51de6e53 fb88087a
Showing with 538 additions and 84 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.
......
...@@ -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);
......
...@@ -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