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