diff options
Diffstat (limited to 'mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/DefaultMediaClock.java')
-rw-r--r-- | mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/DefaultMediaClock.java | 197 |
1 files changed, 197 insertions, 0 deletions
diff --git a/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/DefaultMediaClock.java b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/DefaultMediaClock.java new file mode 100644 index 0000000000..9967bfeb9e --- /dev/null +++ b/mobile/android/exoplayer2/src/main/java/org/mozilla/thirdparty/com/google/android/exoplayer2/DefaultMediaClock.java @@ -0,0 +1,197 @@ +/* + * 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())); + } +} |