1 /* 2 * Copyright (C) 2017 The Android Open Source Project 3 * 4 * Licensed under the Apache License, Version 2.0 (the "License"); 5 * you may not use this file except in compliance with the License. 6 * You may obtain a copy of the License at 7 * 8 * http://www.apache.org/licenses/LICENSE-2.0 9 * 10 * Unless required by applicable law or agreed to in writing, software 11 * distributed under the License is distributed on an "AS IS" BASIS, 12 * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. 13 * See the License for the specific language governing permissions and 14 * limitations under the License. 15 */ 16 package com.google.android.exoplayer2; 17 18 import androidx.annotation.Nullable; 19 import com.google.android.exoplayer2.util.Clock; 20 import com.google.android.exoplayer2.util.MediaClock; 21 import com.google.android.exoplayer2.util.StandaloneMediaClock; 22 23 /** 24 * Default {@link MediaClock} which uses a renderer media clock and falls back to a 25 * {@link StandaloneMediaClock} if necessary. 26 */ 27 /* package */ final class DefaultMediaClock implements MediaClock { 28 29 /** Listener interface to be notified of changes to the active playback speed. */ 30 public interface PlaybackSpeedListener { 31 32 /** 33 * Called when the active playback speed changed. Will not be called for {@link 34 * #setPlaybackSpeed(float)}. 35 * 36 * @param newPlaybackSpeed The newly active playback speed. 37 */ onPlaybackSpeedChanged(float newPlaybackSpeed)38 void onPlaybackSpeedChanged(float newPlaybackSpeed); 39 } 40 41 private final StandaloneMediaClock standaloneClock; 42 private final PlaybackSpeedListener listener; 43 44 @Nullable private Renderer rendererClockSource; 45 @Nullable private MediaClock rendererClock; 46 private boolean isUsingStandaloneClock; 47 private boolean standaloneClockIsStarted; 48 49 /** 50 * Creates a new instance with listener for playback speed changes and a {@link Clock} to use for 51 * the standalone clock implementation. 52 * 53 * @param listener A {@link PlaybackSpeedListener} to listen for playback speed changes. 54 * @param clock A {@link Clock}. 55 */ DefaultMediaClock(PlaybackSpeedListener listener, Clock clock)56 public DefaultMediaClock(PlaybackSpeedListener listener, Clock clock) { 57 this.listener = listener; 58 this.standaloneClock = new StandaloneMediaClock(clock); 59 isUsingStandaloneClock = true; 60 } 61 62 /** 63 * Starts the standalone fallback clock. 64 */ start()65 public void start() { 66 standaloneClockIsStarted = true; 67 standaloneClock.start(); 68 } 69 70 /** 71 * Stops the standalone fallback clock. 72 */ stop()73 public void stop() { 74 standaloneClockIsStarted = false; 75 standaloneClock.stop(); 76 } 77 78 /** 79 * Resets the position of the standalone fallback clock. 80 * 81 * @param positionUs The position to set in microseconds. 82 */ resetPosition(long positionUs)83 public void resetPosition(long positionUs) { 84 standaloneClock.resetPosition(positionUs); 85 } 86 87 /** 88 * Notifies the media clock that a renderer has been enabled. Starts using the media clock of the 89 * provided renderer if available. 90 * 91 * @param renderer The renderer which has been enabled. 92 * @throws ExoPlaybackException If the renderer provides a media clock and another renderer media 93 * clock is already provided. 94 */ onRendererEnabled(Renderer renderer)95 public void onRendererEnabled(Renderer renderer) throws ExoPlaybackException { 96 @Nullable MediaClock rendererMediaClock = renderer.getMediaClock(); 97 if (rendererMediaClock != null && rendererMediaClock != rendererClock) { 98 if (rendererClock != null) { 99 throw ExoPlaybackException.createForUnexpected( 100 new IllegalStateException("Multiple renderer media clocks enabled.")); 101 } 102 this.rendererClock = rendererMediaClock; 103 this.rendererClockSource = renderer; 104 rendererClock.setPlaybackSpeed(standaloneClock.getPlaybackSpeed()); 105 } 106 } 107 108 /** 109 * Notifies the media clock that a renderer has been disabled. Stops using the media clock of this 110 * renderer if used. 111 * 112 * @param renderer The renderer which has been disabled. 113 */ onRendererDisabled(Renderer renderer)114 public void onRendererDisabled(Renderer renderer) { 115 if (renderer == rendererClockSource) { 116 this.rendererClock = null; 117 this.rendererClockSource = null; 118 isUsingStandaloneClock = true; 119 } 120 } 121 122 /** 123 * Syncs internal clock if needed and returns current clock position in microseconds. 124 * 125 * @param isReadingAhead Whether the renderers are reading ahead. 126 */ syncAndGetPositionUs(boolean isReadingAhead)127 public long syncAndGetPositionUs(boolean isReadingAhead) { 128 syncClocks(isReadingAhead); 129 return getPositionUs(); 130 } 131 132 // MediaClock implementation. 133 134 @Override getPositionUs()135 public long getPositionUs() { 136 return isUsingStandaloneClock ? standaloneClock.getPositionUs() : rendererClock.getPositionUs(); 137 } 138 139 @Override setPlaybackSpeed(float playbackSpeed)140 public void setPlaybackSpeed(float playbackSpeed) { 141 if (rendererClock != null) { 142 rendererClock.setPlaybackSpeed(playbackSpeed); 143 playbackSpeed = rendererClock.getPlaybackSpeed(); 144 } 145 standaloneClock.setPlaybackSpeed(playbackSpeed); 146 } 147 148 @Override getPlaybackSpeed()149 public float getPlaybackSpeed() { 150 return rendererClock != null 151 ? rendererClock.getPlaybackSpeed() 152 : standaloneClock.getPlaybackSpeed(); 153 } 154 syncClocks(boolean isReadingAhead)155 private void syncClocks(boolean isReadingAhead) { 156 if (shouldUseStandaloneClock(isReadingAhead)) { 157 isUsingStandaloneClock = true; 158 if (standaloneClockIsStarted) { 159 standaloneClock.start(); 160 } 161 return; 162 } 163 long rendererClockPositionUs = rendererClock.getPositionUs(); 164 if (isUsingStandaloneClock) { 165 // Ensure enabling the renderer clock doesn't jump backwards in time. 166 if (rendererClockPositionUs < standaloneClock.getPositionUs()) { 167 standaloneClock.stop(); 168 return; 169 } 170 isUsingStandaloneClock = false; 171 if (standaloneClockIsStarted) { 172 standaloneClock.start(); 173 } 174 } 175 // Continuously sync stand-alone clock to renderer clock so that it can take over if needed. 176 standaloneClock.resetPosition(rendererClockPositionUs); 177 float playbackSpeed = rendererClock.getPlaybackSpeed(); 178 if (playbackSpeed != standaloneClock.getPlaybackSpeed()) { 179 standaloneClock.setPlaybackSpeed(playbackSpeed); 180 listener.onPlaybackSpeedChanged(playbackSpeed); 181 } 182 } 183 shouldUseStandaloneClock(boolean isReadingAhead)184 private boolean shouldUseStandaloneClock(boolean isReadingAhead) { 185 // Use the standalone clock if the clock providing renderer is not set or has ended. Also use 186 // the standalone clock if the renderer is not ready and we have finished reading the stream or 187 // are reading ahead to avoid getting stuck if tracks in the current period have uneven 188 // durations. See: https://github.com/google/ExoPlayer/issues/1874. 189 return rendererClockSource == null 190 || rendererClockSource.isEnded() 191 || (!rendererClockSource.isReady() 192 && (isReadingAhead || rendererClockSource.hasReadStreamToEnd())); 193 } 194 } 195