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 {
return false;
}
// 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.
if ((joiningDeadlineMs != C.TIME_UNSET && outputBuffer.timeUs < positionUs - 30000)
|| (nextOutputBuffer != null && !nextOutputBuffer.isEndOfStream()
&& nextOutputBuffer.timeUs < positionUs)) {
final long nextOutputBufferTimeUs =
nextOutputBuffer != null && !nextOutputBuffer.isEndOfStream()
? nextOutputBuffer.timeUs : C.TIME_UNSET;
if (shouldDropOutputBuffer(
outputBuffer.timeUs, nextOutputBufferTimeUs, positionUs, joiningDeadlineMs)) {
dropBuffer();
return true;
}
......@@ -280,6 +280,25 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
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() {
int bufferMode = outputBuffer.mode;
boolean renderRgb = bufferMode == VpxDecoder.OUTPUT_MODE_RGB && surface != null;
......@@ -445,13 +464,16 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
protected void onPositionReset(long positionUs, boolean joining) {
inputStreamEnded = false;
outputStreamEnded = false;
renderedFirstFrame = false;
clearRenderedFirstFrame();
consecutiveDroppedFrameCount = 0;
if (decoder != null) {
flushDecoder();
}
joiningDeadlineMs = joining && allowedJoiningTimeMs > 0
? (SystemClock.elapsedRealtime() + allowedJoiningTimeMs) : C.TIME_UNSET;
if (joining) {
setJoiningDeadlineMs();
} else {
joiningDeadlineMs = C.TIME_UNSET;
}
}
@Override
......@@ -473,6 +495,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
format = null;
waitingForKeys = false;
clearReportedVideoSize();
clearRenderedFirstFrame();
try {
releaseDecoder();
} finally {
......@@ -549,28 +572,44 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
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.
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) {
// The output has changed.
this.surface = surface;
this.outputBufferRenderer = outputBufferRenderer;
outputMode = outputBufferRenderer != null ? VpxDecoder.OUTPUT_MODE_YUV
: 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
// anticipation that a subsequent output will likely be of the same type as the one that was
// set previously.
if (decoder != null && outputMode != VpxDecoder.OUTPUT_MODE_NONE) {
decoder.setOutputMode(outputMode);
if (outputMode != VpxDecoder.OUTPUT_MODE_NONE) {
if (decoder != null) {
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() {
reportedWidth = Format.NO_VALUE;
reportedHeight = Format.NO_VALUE;
private void setJoiningDeadlineMs() {
joiningDeadlineMs = allowedJoiningTimeMs > 0
? (SystemClock.elapsedRealtime() + allowedJoiningTimeMs) : C.TIME_UNSET;
}
private void clearRenderedFirstFrame() {
renderedFirstFrame = false;
}
private void maybeNotifyRenderedFirstFrame() {
......@@ -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) {
if (reportedWidth != width || reportedHeight != height) {
reportedWidth = width;
......@@ -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() {
if (droppedFrames > 0) {
long now = SystemClock.elapsedRealtime();
......
......@@ -29,6 +29,7 @@ import com.google.android.exoplayer2.metadata.id3.TextInformationFrame;
import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.ColorInfo;
import java.nio.ByteBuffer;
import java.util.ArrayList;
import java.util.Arrays;
......@@ -61,11 +62,13 @@ public final class FormatTest extends TestCase {
Metadata metadata = new Metadata(
new TextInformationFrame("id1", "description1", "value1"),
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,
1024, 2048, 1920, 1080, 24, 90, 2, projectionData, C.STEREO_MODE_TOP_BOTTOM, 6, 44100,
C.ENCODING_PCM_24BIT, 1001, 1002, 0, "und", Format.NO_VALUE, Format.OFFSET_SAMPLE_RELATIVE,
INIT_DATA, drmInitData, metadata);
1024, 2048, 1920, 1080, 24, 90, 2, projectionData, C.STEREO_MODE_TOP_BOTTOM, colorInfo, 6,
44100, C.ENCODING_PCM_24BIT, 1001, 1002, 0, "und", Format.NO_VALUE,
Format.OFFSET_SAMPLE_RELATIVE, INIT_DATA, drmInitData, metadata);
Parcel parcel = Parcel.obtain();
formatToParcel.writeToParcel(parcel, 0);
......
......@@ -20,6 +20,7 @@ import android.content.Context;
import android.media.AudioFormat;
import android.media.AudioManager;
import android.media.MediaCodec;
import android.media.MediaFormat;
import android.support.annotation.IntDef;
import android.view.Surface;
import com.google.android.exoplayer2.util.Util;
......@@ -555,6 +556,67 @@ public final class C {
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.
*
* <p>Larger values indicate higher priorities.
......
......@@ -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
* rendered. The player will track the lifecycle of the surface automatically.
*
......@@ -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
* lifecycle of the surface automatically.
*
* @param surfaceView The surface view.
*/
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 {
}
/**
* 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
* {@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}.
......@@ -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) {
videoDebugListener = listener;
public void clearVideoListener(VideoListener 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) {
audioDebugListener = listener;
public void setTextOutput(TextRenderer.Output output) {
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) {
textOutput = output;
public void clearTextOutput(TextRenderer.Output output) {
if (textOutput == output) {
textOutput = null;
}
}
/**
......@@ -440,6 +490,35 @@ public class SimpleExoPlayer implements ExoPlayer {
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
@Override
......
......@@ -64,11 +64,25 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
FLAC_EXTRACTOR_CONSTRUCTOR = flacExtractorConstructor;
}
private @MatroskaExtractor.Flags int matroskaFlags;
private @FragmentedMp4Extractor.Flags int fragmentedMp4Flags;
private @Mp3Extractor.Flags int mp3Flags;
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.
*
* @see FragmentedMp4Extractor#FragmentedMp4Extractor(int)
......@@ -110,7 +124,7 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
@Override
public synchronized Extractor[] createExtractors() {
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[2] = new Mp4Extractor();
extractors[3] = new Mp3Extractor(mp3Flags);
......
......@@ -762,7 +762,7 @@ import java.util.List;
out.format = Format.createVideoSampleFormat(Integer.toString(trackId), mimeType, null,
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 {
currentPixelWidthHeightRatio = Format.NO_VALUE;
pendingPixelWidthHeightRatio = Format.NO_VALUE;
scalingMode = C.VIDEO_SCALING_MODE_DEFAULT;
clearLastReportedVideoSize();
clearReportedVideoSize();
}
@Override
......@@ -229,8 +229,11 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
super.onPositionReset(positionUs, joining);
clearRenderedFirstFrame();
consecutiveDroppedFrameCount = 0;
joiningDeadlineMs = joining && allowedJoiningTimeMs > 0
? (SystemClock.elapsedRealtime() + allowedJoiningTimeMs) : C.TIME_UNSET;
if (joining) {
setJoiningDeadlineMs();
} else {
joiningDeadlineMs = C.TIME_UNSET;
}
}
@Override
......@@ -272,7 +275,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
currentHeight = Format.NO_VALUE;
currentPixelWidthHeightRatio = Format.NO_VALUE;
pendingPixelWidthHeightRatio = Format.NO_VALUE;
clearLastReportedVideoSize();
clearReportedVideoSize();
clearRenderedFirstFrame();
frameReleaseTimeHelper.disable();
tunnelingOnFrameRenderedListener = null;
try {
......@@ -312,11 +316,25 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
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
......@@ -521,6 +539,11 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
maybeNotifyRenderedFirstFrame();
}
private void setJoiningDeadlineMs() {
joiningDeadlineMs = allowedJoiningTimeMs > 0
? (SystemClock.elapsedRealtime() + allowedJoiningTimeMs) : C.TIME_UNSET;
}
private void clearRenderedFirstFrame() {
renderedFirstFrame = false;
// The first frame notification is triggered by renderOutputBuffer or renderOutputBufferV21 for
......@@ -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;
reportedHeight = Format.NO_VALUE;
reportedPixelWidthHeightRatio = Format.NO_VALUE;
......@@ -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() {
if (droppedFrames > 0) {
long now = SystemClock.elapsedRealtime();
......
......@@ -393,7 +393,7 @@ public class DefaultDashChunkSource implements DashChunkSource {
if (MimeTypes.APPLICATION_RAWCC.equals(containerMimeType)) {
extractor = new RawCcExtractor(representation.format);
} else if (mimeTypeIsWebm(containerMimeType)) {
extractor = new MatroskaExtractor();
extractor = new MatroskaExtractor(MatroskaExtractor.FLAG_DISABLE_SEEK_FOR_CUES);
} else {
int flags = 0;
if (enableEventMessageTrack) {
......
......@@ -70,7 +70,6 @@ public class HlsMediaPlaylistParserTest extends TestCase {
try {
HlsPlaylist playlist = new HlsPlaylistParser().parse(playlistUri, inputStream);
assertNotNull(playlist);
assertEquals(HlsPlaylist.TYPE_MEDIA, playlist.type);
HlsMediaPlaylist mediaPlaylist = (HlsMediaPlaylist) playlist;
assertEquals(HlsMediaPlaylist.PLAYLIST_TYPE_VOD, mediaPlaylist.playlistType);
......
......@@ -55,7 +55,7 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
public HlsMasterPlaylist(String baseUri, List<HlsUrl> variants, List<HlsUrl> audios,
List<HlsUrl> subtitles, Format muxedAudioFormat, List<Format> muxedCaptionFormats) {
super(baseUri, HlsPlaylist.TYPE_MASTER);
super(baseUri);
this.variants = Collections.unmodifiableList(variants);
this.audios = Collections.unmodifiableList(audios);
this.subtitles = Collections.unmodifiableList(subtitles);
......
......@@ -97,7 +97,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
long startTimeUs, boolean hasDiscontinuitySequence, int discontinuitySequence,
int mediaSequence, int version, long targetDurationUs, boolean hasEndTag,
boolean hasProgramDateTime, Segment initializationSegment, List<Segment> segments) {
super(baseUri, HlsPlaylist.TYPE_MEDIA);
super(baseUri);
this.playlistType = playlistType;
this.startTimeUs = startTimeUs;
this.hasDiscontinuitySequence = hasDiscontinuitySequence;
......
......@@ -15,30 +15,15 @@
*/
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.
*/
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;
@Type public final int type;
protected HlsPlaylist(String baseUri, @Type int type) {
protected HlsPlaylist(String baseUri) {
this.baseUri = baseUri;
this.type = type;
}
}
......@@ -477,9 +477,15 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
@Override
public void onLoadCompleted(ParsingLoadable<HlsPlaylist> loadable, long elapsedRealtimeMs,
long loadDurationMs) {
processLoadedPlaylist((HlsMediaPlaylist) loadable.getResult());
eventDispatcher.loadCompleted(loadable.dataSpec, C.DATA_TYPE_MANIFEST, elapsedRealtimeMs,
loadDurationMs, loadable.bytesLoaded());
HlsPlaylist result = loadable.getResult();
if (result instanceof HlsMediaPlaylist) {
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
......
......@@ -21,6 +21,8 @@ import android.content.res.Resources;
import android.content.res.TypedArray;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.AttributeSet;
import android.view.KeyEvent;
import android.view.LayoutInflater;
......@@ -74,12 +76,18 @@ import java.util.List;
* <li>Default: {@code null}</li>
* </ul>
* </li>
* <li><b>{@code use_controller}</b> - Whether playback controls are displayed.
* <li><b>{@code use_controller}</b> - Whether the playback controls can be shown.
* <ul>
* <li>Corresponding method: {@link #setUseController(boolean)}</li>
* <li>Default: {@code true}</li>
* </ul>
* </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.
* Valid values are {@code fit}, {@code fixed_width}, {@code fixed_height} and {@code fill}.
* <ul>
......@@ -190,6 +198,7 @@ public final class SimpleExoPlayerView extends FrameLayout {
private boolean useArtwork;
private Bitmap defaultArtwork;
private int controllerShowTimeoutMs;
private boolean controllerHideOnTouch;
public SimpleExoPlayerView(Context context) {
this(context, null);
......@@ -228,6 +237,7 @@ public final class SimpleExoPlayerView extends FrameLayout {
int surfaceType = SURFACE_TYPE_SURFACE_VIEW;
int resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT;
int controllerShowTimeoutMs = PlaybackControlView.DEFAULT_SHOW_TIMEOUT_MS;
boolean controllerHideOnTouch = true;
if (attrs != null) {
TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.SimpleExoPlayerView, 0, 0);
......@@ -242,6 +252,8 @@ public final class SimpleExoPlayerView extends FrameLayout {
resizeMode = a.getInt(R.styleable.SimpleExoPlayerView_resize_mode, resizeMode);
controllerShowTimeoutMs = a.getInt(R.styleable.SimpleExoPlayerView_show_timeout,
controllerShowTimeoutMs);
controllerHideOnTouch = a.getBoolean(R.styleable.SimpleExoPlayerView_hide_on_touch,
controllerHideOnTouch);
} finally {
a.recycle();
}
......@@ -304,11 +316,36 @@ public final class SimpleExoPlayerView extends FrameLayout {
this.controller = null;
}
this.controllerShowTimeoutMs = controller != null ? controllerShowTimeoutMs : 0;
this.controllerHideOnTouch = controllerHideOnTouch;
this.useController = useController && controller != null;
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.
*/
public SimpleExoPlayer getPlayer() {
......@@ -319,6 +356,12 @@ public final class SimpleExoPlayerView extends FrameLayout {
* Set the {@link SimpleExoPlayer} to use. The {@link SimpleExoPlayer#setTextOutput} and
* {@link SimpleExoPlayer#setVideoListener} method of the player will be called and previous
* 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.
*/
......@@ -327,10 +370,14 @@ public final class SimpleExoPlayerView extends FrameLayout {
return;
}
if (this.player != null) {
this.player.setTextOutput(null);
this.player.setVideoListener(null);
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;
if (useController) {
......@@ -346,8 +393,8 @@ public final class SimpleExoPlayerView extends FrameLayout {
player.setVideoSurfaceView((SurfaceView) surfaceView);
}
player.setVideoListener(componentListener);
player.addListener(componentListener);
player.setTextOutput(componentListener);
player.addListener(componentListener);
maybeShowController(false);
updateForCurrentTrackSelections();
} else {
......@@ -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() {
return useController;
}
/**
* Sets whether playback controls are enabled. If set to {@code false} the playback controls are
* never visible and are disconnected from the player.
* Sets whether the playback controls can be shown. If set to {@code false} the playback controls
* 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) {
Assertions.checkState(!useController || controller != null);
......@@ -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}.
*
* @param listener The listener to be notified about visibility changes.
......@@ -573,10 +637,10 @@ public final class SimpleExoPlayerView extends FrameLayout {
if (!useController || player == null || ev.getActionMasked() != MotionEvent.ACTION_DOWN) {
return false;
}
if (controller.isVisible()) {
controller.hide();
} else {
if (!controller.isVisible()) {
maybeShowController(true);
} else if (controllerHideOnTouch) {
controller.hide();
}
return true;
}
......
......@@ -39,11 +39,12 @@
<attr name="use_artwork" format="boolean"/>
<attr name="default_artwork" format="reference"/>
<attr name="use_controller" format="boolean"/>
<attr name="hide_on_touch" format="boolean"/>
<attr name="resize_mode"/>
<attr name="surface_type"/>
<attr name="show_timeout"/>
<attr name="rewind_increment"/>
<attr name="fastforward_increment"/>
<attr name="resize_mode"/>
<attr name="player_layout_id"/>
<attr name="controller_layout_id"/>
</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