blob: 671d66c31c4192c6ac6bbc2d166a81ba5e9f3461 [file] [log] [blame]
/*
* Copyright (C) 2016 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 static com.google.android.exoplayer2.util.Util.castNonNull;
import android.os.Handler;
import android.os.SystemClock;
import android.view.Surface;
import android.view.TextureView;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.Renderer;
import com.google.android.exoplayer2.decoder.DecoderCounters;
import com.google.android.exoplayer2.util.Assertions;
/**
* Listener of video {@link Renderer} events. All methods have no-op default implementations to
* allow selective overrides.
*/
public interface VideoRendererEventListener {
/**
* Called when the renderer is enabled.
*
* @param counters {@link DecoderCounters} that will be updated by the renderer for as long as it
* remains enabled.
*/
default void onVideoEnabled(DecoderCounters counters) {}
/**
* Called when a decoder is created.
*
* @param decoderName The decoder that was created.
* @param initializedTimestampMs {@link SystemClock#elapsedRealtime()} when initialization
* finished.
* @param initializationDurationMs The time taken to initialize the decoder in milliseconds.
*/
default void onVideoDecoderInitialized(
String decoderName, long initializedTimestampMs, long initializationDurationMs) {}
/**
* Called when the format of the media being consumed by the renderer changes.
*
* @param format The new format.
*/
default void onVideoInputFormatChanged(Format format) {}
/**
* Called to report the number of frames dropped by the renderer. Dropped frames are reported
* whenever the renderer is stopped having dropped frames, and optionally, whenever the count
* reaches a specified threshold whilst the renderer is started.
*
* @param count The number of dropped frames.
* @param elapsedMs The duration in milliseconds over which the frames were dropped. This duration
* is timed from when the renderer was started or from when dropped frames were last reported
* (whichever was more recent), and not from when the first of the reported drops occurred.
*/
default void onDroppedFrames(int count, long elapsedMs) {}
/**
* Called to report the video processing offset of video frames processed by the video renderer.
*
* <p>Video processing offset represents how early a video frame is processed compared to the
* player's current position. For each video frame, the offset is calculated as <em>P<sub>vf</sub>
* - P<sub>pl</sub></em> where <em>P<sub>vf</sub></em> is the presentation timestamp of the video
* frame and <em>P<sub>pl</sub></em> is the current position of the player. Positive values
* indicate the frame was processed early enough whereas negative values indicate that the
* player's position had progressed beyond the frame's timestamp when the frame was processed (and
* the frame was probably dropped).
*
* <p>The renderer reports the sum of video processing offset samples (one sample per processed
* video frame: dropped, skipped or rendered) and the total number of samples.
*
* @param totalProcessingOffsetUs The sum of all video frame processing offset samples for the
* video frames processed by the renderer in microseconds.
* @param frameCount The number of samples included in the {@code totalProcessingOffsetUs}.
* @param format The {@link Format} that is currently output.
*/
default void onVideoFrameProcessingOffset(
long totalProcessingOffsetUs, int frameCount, Format format) {}
/**
* Called before a frame is rendered for the first time since setting the surface, and each time
* there's a change in the size, rotation or pixel aspect ratio of the video being rendered.
*
* @param width The video width in pixels.
* @param height The video height in pixels.
* @param unappliedRotationDegrees For videos that require a rotation, this is the clockwise
* rotation in degrees that the application should apply for the video for it to be rendered
* in the correct orientation. This value will always be zero on API levels 21 and above,
* since the renderer will apply all necessary rotations internally. On earlier API levels
* this is not possible. Applications that use {@link TextureView} can apply the rotation by
* calling {@link TextureView#setTransform}. Applications that do not expect to encounter
* rotated videos can safely ignore this parameter.
* @param pixelWidthHeightRatio The width to height ratio of each pixel. For the normal case of
* square pixels this will be equal to 1.0. Different values are indicative of anamorphic
* content.
*/
default void onVideoSizeChanged(
int width, int height, int unappliedRotationDegrees, float pixelWidthHeightRatio) {}
/**
* Called when a frame is rendered for the first time since setting the surface, and when a frame
* is rendered for the first time since the renderer was reset.
*
* @param surface The {@link Surface} to which a first frame has been rendered, or {@code null} if
* the renderer renders to something that isn't a {@link Surface}.
*/
default void onRenderedFirstFrame(@Nullable Surface surface) {}
/**
* Called when the renderer is disabled.
*
* @param counters {@link DecoderCounters} that were updated by the renderer.
*/
default void onVideoDisabled(DecoderCounters counters) {}
/**
* Dispatches events to a {@link VideoRendererEventListener}.
*/
final class EventDispatcher {
@Nullable private final Handler handler;
@Nullable private final VideoRendererEventListener listener;
/**
* @param handler A handler for dispatching events, or null if creating a dummy instance.
* @param listener The listener to which events should be dispatched, or null if creating a
* dummy instance.
*/
public EventDispatcher(@Nullable Handler handler,
@Nullable VideoRendererEventListener listener) {
this.handler = listener != null ? Assertions.checkNotNull(handler) : null;
this.listener = listener;
}
/** Invokes {@link VideoRendererEventListener#onVideoEnabled(DecoderCounters)}. */
public void enabled(DecoderCounters decoderCounters) {
if (handler != null) {
handler.post(() -> castNonNull(listener).onVideoEnabled(decoderCounters));
}
}
/** Invokes {@link VideoRendererEventListener#onVideoDecoderInitialized(String, long, long)}. */
public void decoderInitialized(
String decoderName, long initializedTimestampMs, long initializationDurationMs) {
if (handler != null) {
handler.post(
() ->
castNonNull(listener)
.onVideoDecoderInitialized(
decoderName, initializedTimestampMs, initializationDurationMs));
}
}
/** Invokes {@link VideoRendererEventListener#onVideoInputFormatChanged(Format)}. */
public void inputFormatChanged(Format format) {
if (handler != null) {
handler.post(() -> castNonNull(listener).onVideoInputFormatChanged(format));
}
}
/** Invokes {@link VideoRendererEventListener#onDroppedFrames(int, long)}. */
public void droppedFrames(int droppedFrameCount, long elapsedMs) {
if (handler != null) {
handler.post(() -> castNonNull(listener).onDroppedFrames(droppedFrameCount, elapsedMs));
}
}
/** Invokes {@link VideoRendererEventListener#onVideoFrameProcessingOffset}. */
public void reportVideoFrameProcessingOffset(
long totalProcessingOffsetUs, int frameCount, Format format) {
if (handler != null) {
handler.post(
() ->
castNonNull(listener)
.onVideoFrameProcessingOffset(totalProcessingOffsetUs, frameCount, format));
}
}
/** Invokes {@link VideoRendererEventListener#onVideoSizeChanged(int, int, int, float)}. */
public void videoSizeChanged(
int width,
int height,
final int unappliedRotationDegrees,
final float pixelWidthHeightRatio) {
if (handler != null) {
handler.post(
() ->
castNonNull(listener)
.onVideoSizeChanged(
width, height, unappliedRotationDegrees, pixelWidthHeightRatio));
}
}
/** Invokes {@link VideoRendererEventListener#onRenderedFirstFrame(Surface)}. */
public void renderedFirstFrame(@Nullable Surface surface) {
if (handler != null) {
handler.post(() -> castNonNull(listener).onRenderedFirstFrame(surface));
}
}
/** Invokes {@link VideoRendererEventListener#onVideoDisabled(DecoderCounters)}. */
public void disabled(DecoderCounters counters) {
counters.ensureUpdated();
if (handler != null) {
handler.post(
() -> {
counters.ensureUpdated();
castNonNull(listener).onVideoDisabled(counters);
});
}
}
}
}