• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
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