• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1 /*
2  * Copyright (C) 2016 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.decoder.DecoderInputBuffer;
20 import com.google.android.exoplayer2.source.SampleStream;
21 import com.google.android.exoplayer2.util.Assertions;
22 import com.google.android.exoplayer2.util.MediaClock;
23 import java.io.IOException;
24 
25 /**
26  * An abstract base class suitable for most {@link Renderer} implementations.
27  */
28 public abstract class BaseRenderer implements Renderer, RendererCapabilities {
29 
30   private final int trackType;
31   private final FormatHolder formatHolder;
32 
33   private RendererConfiguration configuration;
34   private int index;
35   private int state;
36   private SampleStream stream;
37   private Format[] streamFormats;
38   private long streamOffsetUs;
39   private long readingPositionUs;
40   private boolean streamIsFinal;
41   private boolean throwRendererExceptionIsExecuting;
42 
43   /**
44    * @param trackType The track type that the renderer handles. One of the {@link C}
45    * {@code TRACK_TYPE_*} constants.
46    */
BaseRenderer(int trackType)47   public BaseRenderer(int trackType) {
48     this.trackType = trackType;
49     formatHolder = new FormatHolder();
50     readingPositionUs = C.TIME_END_OF_SOURCE;
51   }
52 
53   @Override
getTrackType()54   public final int getTrackType() {
55     return trackType;
56   }
57 
58   @Override
getCapabilities()59   public final RendererCapabilities getCapabilities() {
60     return this;
61   }
62 
63   @Override
setIndex(int index)64   public final void setIndex(int index) {
65     this.index = index;
66   }
67 
68   @Override
69   @Nullable
getMediaClock()70   public MediaClock getMediaClock() {
71     return null;
72   }
73 
74   @Override
getState()75   public final int getState() {
76     return state;
77   }
78 
79   @Override
enable( RendererConfiguration configuration, Format[] formats, SampleStream stream, long positionUs, boolean joining, boolean mayRenderStartOfStream, long offsetUs)80   public final void enable(
81       RendererConfiguration configuration,
82       Format[] formats,
83       SampleStream stream,
84       long positionUs,
85       boolean joining,
86       boolean mayRenderStartOfStream,
87       long offsetUs)
88       throws ExoPlaybackException {
89     Assertions.checkState(state == STATE_DISABLED);
90     this.configuration = configuration;
91     state = STATE_ENABLED;
92     onEnabled(joining, mayRenderStartOfStream);
93     replaceStream(formats, stream, offsetUs);
94     onPositionReset(positionUs, joining);
95   }
96 
97   @Override
start()98   public final void start() throws ExoPlaybackException {
99     Assertions.checkState(state == STATE_ENABLED);
100     state = STATE_STARTED;
101     onStarted();
102   }
103 
104   @Override
replaceStream(Format[] formats, SampleStream stream, long offsetUs)105   public final void replaceStream(Format[] formats, SampleStream stream, long offsetUs)
106       throws ExoPlaybackException {
107     Assertions.checkState(!streamIsFinal);
108     this.stream = stream;
109     readingPositionUs = offsetUs;
110     streamFormats = formats;
111     streamOffsetUs = offsetUs;
112     onStreamChanged(formats, offsetUs);
113   }
114 
115   @Override
116   @Nullable
getStream()117   public final SampleStream getStream() {
118     return stream;
119   }
120 
121   @Override
hasReadStreamToEnd()122   public final boolean hasReadStreamToEnd() {
123     return readingPositionUs == C.TIME_END_OF_SOURCE;
124   }
125 
126   @Override
getReadingPositionUs()127   public final long getReadingPositionUs() {
128     return readingPositionUs;
129   }
130 
131   @Override
setCurrentStreamFinal()132   public final void setCurrentStreamFinal() {
133     streamIsFinal = true;
134   }
135 
136   @Override
isCurrentStreamFinal()137   public final boolean isCurrentStreamFinal() {
138     return streamIsFinal;
139   }
140 
141   @Override
maybeThrowStreamError()142   public final void maybeThrowStreamError() throws IOException {
143     stream.maybeThrowError();
144   }
145 
146   @Override
resetPosition(long positionUs)147   public final void resetPosition(long positionUs) throws ExoPlaybackException {
148     streamIsFinal = false;
149     readingPositionUs = positionUs;
150     onPositionReset(positionUs, false);
151   }
152 
153   @Override
stop()154   public final void stop() throws ExoPlaybackException {
155     Assertions.checkState(state == STATE_STARTED);
156     state = STATE_ENABLED;
157     onStopped();
158   }
159 
160   @Override
disable()161   public final void disable() {
162     Assertions.checkState(state == STATE_ENABLED);
163     formatHolder.clear();
164     state = STATE_DISABLED;
165     stream = null;
166     streamFormats = null;
167     streamIsFinal = false;
168     onDisabled();
169   }
170 
171   @Override
reset()172   public final void reset() {
173     Assertions.checkState(state == STATE_DISABLED);
174     formatHolder.clear();
175     onReset();
176   }
177 
178   // RendererCapabilities implementation.
179 
180   @Override
181   @AdaptiveSupport
supportsMixedMimeTypeAdaptation()182   public int supportsMixedMimeTypeAdaptation() throws ExoPlaybackException {
183     return ADAPTIVE_NOT_SUPPORTED;
184   }
185 
186   // PlayerMessage.Target implementation.
187 
188   @Override
handleMessage(int what, @Nullable Object object)189   public void handleMessage(int what, @Nullable Object object) throws ExoPlaybackException {
190     // Do nothing.
191   }
192 
193   // Methods to be overridden by subclasses.
194 
195   /**
196    * Called when the renderer is enabled.
197    *
198    * <p>The default implementation is a no-op.
199    *
200    * @param joining Whether this renderer is being enabled to join an ongoing playback.
201    * @param mayRenderStartOfStream Whether this renderer is allowed to render the start of the
202    *     stream even if the state is not {@link #STATE_STARTED} yet.
203    * @throws ExoPlaybackException If an error occurs.
204    */
onEnabled(boolean joining, boolean mayRenderStartOfStream)205   protected void onEnabled(boolean joining, boolean mayRenderStartOfStream)
206       throws ExoPlaybackException {
207     // Do nothing.
208   }
209 
210   /**
211    * Called when the renderer's stream has changed. This occurs when the renderer is enabled after
212    * {@link #onEnabled(boolean, boolean)} has been called, and also when the stream has been
213    * replaced whilst the renderer is enabled or started.
214    *
215    * <p>The default implementation is a no-op.
216    *
217    * @param formats The enabled formats.
218    * @param offsetUs The offset that will be added to the timestamps of buffers read via {@link
219    *     #readSource(FormatHolder, DecoderInputBuffer, boolean)} so that decoder input buffers have
220    *     monotonically increasing timestamps.
221    * @throws ExoPlaybackException If an error occurs.
222    */
onStreamChanged(Format[] formats, long offsetUs)223   protected void onStreamChanged(Format[] formats, long offsetUs) throws ExoPlaybackException {
224     // Do nothing.
225   }
226 
227   /**
228    * Called when the position is reset. This occurs when the renderer is enabled after
229    * {@link #onStreamChanged(Format[], long)} has been called, and also when a position
230    * discontinuity is encountered.
231    * <p>
232    * After a position reset, the renderer's {@link SampleStream} is guaranteed to provide samples
233    * starting from a key frame.
234    * <p>
235    * The default implementation is a no-op.
236    *
237    * @param positionUs The new playback position in microseconds.
238    * @param joining Whether this renderer is being enabled to join an ongoing playback.
239    * @throws ExoPlaybackException If an error occurs.
240    */
onPositionReset(long positionUs, boolean joining)241   protected void onPositionReset(long positionUs, boolean joining) throws ExoPlaybackException {
242     // Do nothing.
243   }
244 
245   /**
246    * Called when the renderer is started.
247    * <p>
248    * The default implementation is a no-op.
249    *
250    * @throws ExoPlaybackException If an error occurs.
251    */
onStarted()252   protected void onStarted() throws ExoPlaybackException {
253     // Do nothing.
254   }
255 
256   /**
257    * Called when the renderer is stopped.
258    * <p>
259    * The default implementation is a no-op.
260    *
261    * @throws ExoPlaybackException If an error occurs.
262    */
onStopped()263   protected void onStopped() throws ExoPlaybackException {
264     // Do nothing.
265   }
266 
267   /**
268    * Called when the renderer is disabled.
269    * <p>
270    * The default implementation is a no-op.
271    */
onDisabled()272   protected void onDisabled() {
273     // Do nothing.
274   }
275 
276   /**
277    * Called when the renderer is reset.
278    *
279    * <p>The default implementation is a no-op.
280    */
onReset()281   protected void onReset() {
282     // Do nothing.
283   }
284 
285   // Methods to be called by subclasses.
286 
287   /** Returns a clear {@link FormatHolder}. */
getFormatHolder()288   protected final FormatHolder getFormatHolder() {
289     formatHolder.clear();
290     return formatHolder;
291   }
292 
293   /** Returns the formats of the currently enabled stream. */
getStreamFormats()294   protected final Format[] getStreamFormats() {
295     return streamFormats;
296   }
297 
298   /**
299    * Returns the configuration set when the renderer was most recently enabled.
300    */
getConfiguration()301   protected final RendererConfiguration getConfiguration() {
302     return configuration;
303   }
304 
305   /**
306    * Returns the index of the renderer within the player.
307    */
getIndex()308   protected final int getIndex() {
309     return index;
310   }
311 
312   /**
313    * Creates an {@link ExoPlaybackException} of type {@link ExoPlaybackException#TYPE_RENDERER} for
314    * this renderer.
315    *
316    * @param cause The cause of the exception.
317    * @param format The current format used by the renderer. May be null.
318    */
createRendererException( Exception cause, @Nullable Format format)319   protected final ExoPlaybackException createRendererException(
320       Exception cause, @Nullable Format format) {
321     @FormatSupport int formatSupport = RendererCapabilities.FORMAT_HANDLED;
322     if (format != null && !throwRendererExceptionIsExecuting) {
323       // Prevent recursive re-entry from subclass supportsFormat implementations.
324       throwRendererExceptionIsExecuting = true;
325       try {
326         formatSupport = RendererCapabilities.getFormatSupport(supportsFormat(format));
327       } catch (ExoPlaybackException e) {
328         // Ignore, we are already failing.
329       } finally {
330         throwRendererExceptionIsExecuting = false;
331       }
332     }
333     return ExoPlaybackException.createForRenderer(
334         cause, getName(), getIndex(), format, formatSupport);
335   }
336 
337   /**
338    * Reads from the enabled upstream source. If the upstream source has been read to the end then
339    * {@link C#RESULT_BUFFER_READ} is only returned if {@link #setCurrentStreamFinal()} has been
340    * called. {@link C#RESULT_NOTHING_READ} is returned otherwise.
341    *
342    * @param formatHolder A {@link FormatHolder} to populate in the case of reading a format.
343    * @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the
344    *     end of the stream. If the end of the stream has been reached, the {@link
345    *     C#BUFFER_FLAG_END_OF_STREAM} flag will be set on the buffer.
346    * @param formatRequired Whether the caller requires that the format of the stream be read even if
347    *     it's not changing. A sample will never be read if set to true, however it is still possible
348    *     for the end of stream or nothing to be read.
349    * @return The status of read, one of {@link SampleStream.ReadDataResult}.
350    */
351   @SampleStream.ReadDataResult
readSource( FormatHolder formatHolder, DecoderInputBuffer buffer, boolean formatRequired)352   protected final int readSource(
353       FormatHolder formatHolder, DecoderInputBuffer buffer, boolean formatRequired) {
354     @SampleStream.ReadDataResult int result = stream.readData(formatHolder, buffer, formatRequired);
355     if (result == C.RESULT_BUFFER_READ) {
356       if (buffer.isEndOfStream()) {
357         readingPositionUs = C.TIME_END_OF_SOURCE;
358         return streamIsFinal ? C.RESULT_BUFFER_READ : C.RESULT_NOTHING_READ;
359       }
360       buffer.timeUs += streamOffsetUs;
361       readingPositionUs = Math.max(readingPositionUs, buffer.timeUs);
362     } else if (result == C.RESULT_FORMAT_READ) {
363       Format format = formatHolder.format;
364       if (format.subsampleOffsetUs != Format.OFFSET_SAMPLE_RELATIVE) {
365         format =
366             format
367                 .buildUpon()
368                 .setSubsampleOffsetUs(format.subsampleOffsetUs + streamOffsetUs)
369                 .build();
370         formatHolder.format = format;
371       }
372     }
373     return result;
374   }
375 
376   /**
377    * Attempts to skip to the keyframe before the specified position, or to the end of the stream if
378    * {@code positionUs} is beyond it.
379    *
380    * @param positionUs The position in microseconds.
381    * @return The number of samples that were skipped.
382    */
skipSource(long positionUs)383   protected int skipSource(long positionUs) {
384     return stream.skipData(positionUs - streamOffsetUs);
385   }
386 
387   /**
388    * Returns whether the upstream source is ready.
389    */
isSourceReady()390   protected final boolean isSourceReady() {
391     return hasReadStreamToEnd() ? streamIsFinal : stream.isReady();
392   }
393 }
394