/* * 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 org.mozilla.thirdparty.com.google.android.exoplayer2; import androidx.annotation.Nullable; import org.mozilla.thirdparty.com.google.android.exoplayer2.util.Clock; import org.mozilla.thirdparty.com.google.android.exoplayer2.util.MediaClock; import org.mozilla.thirdparty.com.google.android.exoplayer2.util.StandaloneMediaClock; /** * Default {@link MediaClock} which uses a renderer media clock and falls back to a * {@link StandaloneMediaClock} if necessary. */ /* package */ final class DefaultMediaClock implements MediaClock { /** * Listener interface to be notified of changes to the active playback parameters. */ public interface PlaybackParameterListener { /** * Called when the active playback parameters changed. Will not be called for {@link * #setPlaybackParameters(PlaybackParameters)}. * * @param newPlaybackParameters The newly active {@link PlaybackParameters}. */ void onPlaybackParametersChanged(PlaybackParameters newPlaybackParameters); } private final StandaloneMediaClock standaloneClock; private final PlaybackParameterListener listener; @Nullable private Renderer rendererClockSource; @Nullable private MediaClock rendererClock; private boolean isUsingStandaloneClock; private boolean standaloneClockIsStarted; /** * Creates a new instance with listener for playback parameter changes and a {@link Clock} to use * for the standalone clock implementation. * * @param listener A {@link PlaybackParameterListener} to listen for playback parameter * changes. * @param clock A {@link Clock}. */ public DefaultMediaClock(PlaybackParameterListener listener, Clock clock) { this.listener = listener; this.standaloneClock = new StandaloneMediaClock(clock); isUsingStandaloneClock = true; } /** * Starts the standalone fallback clock. */ public void start() { standaloneClockIsStarted = true; standaloneClock.start(); } /** * Stops the standalone fallback clock. */ public void stop() { standaloneClockIsStarted = false; standaloneClock.stop(); } /** * Resets the position of the standalone fallback clock. * * @param positionUs The position to set in microseconds. */ public void resetPosition(long positionUs) { standaloneClock.resetPosition(positionUs); } /** * Notifies the media clock that a renderer has been enabled. Starts using the media clock of the * provided renderer if available. * * @param renderer The renderer which has been enabled. * @throws ExoPlaybackException If the renderer provides a media clock and another renderer media * clock is already provided. */ public void onRendererEnabled(Renderer renderer) throws ExoPlaybackException { MediaClock rendererMediaClock = renderer.getMediaClock(); if (rendererMediaClock != null && rendererMediaClock != rendererClock) { if (rendererClock != null) { throw ExoPlaybackException.createForUnexpected( new IllegalStateException("Multiple renderer media clocks enabled.")); } this.rendererClock = rendererMediaClock; this.rendererClockSource = renderer; rendererClock.setPlaybackParameters(standaloneClock.getPlaybackParameters()); } } /** * Notifies the media clock that a renderer has been disabled. Stops using the media clock of this * renderer if used. * * @param renderer The renderer which has been disabled. */ public void onRendererDisabled(Renderer renderer) { if (renderer == rendererClockSource) { this.rendererClock = null; this.rendererClockSource = null; isUsingStandaloneClock = true; } } /** * Syncs internal clock if needed and returns current clock position in microseconds. * * @param isReadingAhead Whether the renderers are reading ahead. */ public long syncAndGetPositionUs(boolean isReadingAhead) { syncClocks(isReadingAhead); return getPositionUs(); } // MediaClock implementation. @Override public long getPositionUs() { return isUsingStandaloneClock ? standaloneClock.getPositionUs() : rendererClock.getPositionUs(); } @Override public void setPlaybackParameters(PlaybackParameters playbackParameters) { if (rendererClock != null) { rendererClock.setPlaybackParameters(playbackParameters); playbackParameters = rendererClock.getPlaybackParameters(); } standaloneClock.setPlaybackParameters(playbackParameters); } @Override public PlaybackParameters getPlaybackParameters() { return rendererClock != null ? rendererClock.getPlaybackParameters() : standaloneClock.getPlaybackParameters(); } private void syncClocks(boolean isReadingAhead) { if (shouldUseStandaloneClock(isReadingAhead)) { isUsingStandaloneClock = true; if (standaloneClockIsStarted) { standaloneClock.start(); } return; } long rendererClockPositionUs = rendererClock.getPositionUs(); if (isUsingStandaloneClock) { // Ensure enabling the renderer clock doesn't jump backwards in time. if (rendererClockPositionUs < standaloneClock.getPositionUs()) { standaloneClock.stop(); return; } isUsingStandaloneClock = false; if (standaloneClockIsStarted) { standaloneClock.start(); } } // Continuously sync stand-alone clock to renderer clock so that it can take over if needed. standaloneClock.resetPosition(rendererClockPositionUs); PlaybackParameters playbackParameters = rendererClock.getPlaybackParameters(); if (!playbackParameters.equals(standaloneClock.getPlaybackParameters())) { standaloneClock.setPlaybackParameters(playbackParameters); listener.onPlaybackParametersChanged(playbackParameters); } } private boolean shouldUseStandaloneClock(boolean isReadingAhead) { // Use the standalone clock if the clock providing renderer is not set or has ended. Also use // the standalone clock if the renderer is not ready and we have finished reading the stream or // are reading ahead to avoid getting stuck if tracks in the current period have uneven // durations. See: https://github.com/google/ExoPlayer/issues/1874. return rendererClockSource == null || rendererClockSource.isEnded() || (!rendererClockSource.isReady() && (isReadingAhead || rendererClockSource.hasReadStreamToEnd())); } }