mirror of
				https://github.com/recloudstream/cloudstream.git
				synced 2024-08-15 01:53:11 +00:00 
			
		
		
		
	Fixed subtitle elevation again
This commit is contained in:
		
							parent
							
								
									e215747749
								
							
						
					
					
						commit
						b79e2d768f
					
				
					 1 changed files with 366 additions and 333 deletions
				
			
		|  | @ -15,6 +15,8 @@ | |||
|  */ | ||||
| package com.lagradost.cloudstream3.ui.player; | ||||
| 
 | ||||
| import static com.google.android.exoplayer2.text.Cue.DIMEN_UNSET; | ||||
| import static com.google.android.exoplayer2.text.Cue.LINE_TYPE_NUMBER; | ||||
| import static com.google.android.exoplayer2.util.Assertions.checkNotNull; | ||||
| import static com.google.android.exoplayer2.util.Assertions.checkState; | ||||
| import static java.lang.annotation.ElementType.TYPE_USE; | ||||
|  | @ -23,8 +25,10 @@ import android.os.Handler; | |||
| import android.os.Handler.Callback; | ||||
| import android.os.Looper; | ||||
| import android.os.Message; | ||||
| 
 | ||||
| import androidx.annotation.IntDef; | ||||
| import androidx.annotation.Nullable; | ||||
| 
 | ||||
| import com.google.android.exoplayer2.BaseRenderer; | ||||
| import com.google.android.exoplayer2.C; | ||||
| import com.google.android.exoplayer2.Format; | ||||
|  | @ -43,13 +47,17 @@ import com.google.android.exoplayer2.text.TextOutput; | |||
| import com.google.android.exoplayer2.util.Log; | ||||
| import com.google.android.exoplayer2.util.MimeTypes; | ||||
| import com.google.android.exoplayer2.util.Util; | ||||
| 
 | ||||
| import java.lang.annotation.Documented; | ||||
| import java.lang.annotation.Retention; | ||||
| import java.lang.annotation.RetentionPolicy; | ||||
| import java.lang.annotation.Target; | ||||
| import java.util.Collections; | ||||
| import java.util.List; | ||||
| import java.util.stream.Collectors; | ||||
| // DO NOT CONVERT TO KOTLIN AUTOMATICALLY, IT FUCKS UP AND DOES NOT DISPLAY SUBS FOR SOME REASON | ||||
| // IF YOU CHANGE THE CODE MAKE SURE YOU GET THE CUES CORRECT! | ||||
| 
 | ||||
| /** | ||||
|  * A renderer for text. | ||||
|  * | ||||
|  | @ -59,365 +67,390 @@ import java.util.List; | |||
|  */ | ||||
| public class NonFinalTextRenderer extends BaseRenderer implements Callback { | ||||
| 
 | ||||
|   private static final String TAG = "TextRenderer"; | ||||
|     private static final String TAG = "TextRenderer"; | ||||
| 
 | ||||
|   /** | ||||
|    * @param trackType The track type that the renderer handles. One of the {@link C} {@code | ||||
|    *                  TRACK_TYPE_*} constants. | ||||
|    * @param outputHandler | ||||
|    */ | ||||
|   public NonFinalTextRenderer(int trackType, @Nullable Handler outputHandler) { | ||||
|     super(trackType); | ||||
|     this.outputHandler = outputHandler; | ||||
|   } | ||||
| 
 | ||||
|   @Documented | ||||
|   @Retention(RetentionPolicy.SOURCE) | ||||
|   @Target(TYPE_USE) | ||||
|   @IntDef({ | ||||
|     REPLACEMENT_STATE_NONE, | ||||
|     REPLACEMENT_STATE_SIGNAL_END_OF_STREAM, | ||||
|     REPLACEMENT_STATE_WAIT_END_OF_STREAM | ||||
|   }) | ||||
|   private @interface ReplacementState {} | ||||
|   /** The decoder does not need to be replaced. */ | ||||
|   private static final int REPLACEMENT_STATE_NONE = 0; | ||||
|   /** | ||||
|    * The decoder needs to be replaced, but we haven't yet signaled an end of stream to the existing | ||||
|    * decoder. We need to do so in order to ensure that it outputs any remaining buffers before we | ||||
|    * release it. | ||||
|    */ | ||||
|   private static final int REPLACEMENT_STATE_SIGNAL_END_OF_STREAM = 1; | ||||
|   /** | ||||
|    * The decoder needs to be replaced, and we've signaled an end of stream to the existing decoder. | ||||
|    * We're waiting for the decoder to output an end of stream signal to indicate that it has output | ||||
|    * any remaining buffers before we release it. | ||||
|    */ | ||||
|   private static final int REPLACEMENT_STATE_WAIT_END_OF_STREAM = 2; | ||||
| 
 | ||||
|   private static final int MSG_UPDATE_OUTPUT = 0; | ||||
| 
 | ||||
|   @Nullable private final Handler outputHandler; | ||||
|   private TextOutput output = null; | ||||
|   private SubtitleDecoderFactory decoderFactory = null; | ||||
|   private FormatHolder formatHolder = null; | ||||
| 
 | ||||
|   private boolean inputStreamEnded; | ||||
|   private boolean outputStreamEnded; | ||||
|   private boolean waitingForKeyFrame; | ||||
|   private @ReplacementState int decoderReplacementState; | ||||
|   @Nullable private Format streamFormat; | ||||
|   @Nullable private SubtitleDecoder decoder; | ||||
|   @Nullable private SubtitleInputBuffer nextInputBuffer; | ||||
|   @Nullable private SubtitleOutputBuffer subtitle; | ||||
|   @Nullable private SubtitleOutputBuffer nextSubtitle; | ||||
|   private int nextSubtitleEventIndex; | ||||
|   private long finalStreamEndPositionUs; | ||||
| 
 | ||||
|   /** | ||||
|    * @param output The output. | ||||
|    * @param outputLooper The looper associated with the thread on which the output should be called. | ||||
|    *     If the output makes use of standard Android UI components, then this should normally be the | ||||
|    *     looper associated with the application's main thread, which can be obtained using {@link | ||||
|    *     android.app.Activity#getMainLooper()}. Null may be passed if the output should be called | ||||
|    *     directly on the player's internal rendering thread. | ||||
|    */ | ||||
|   public NonFinalTextRenderer(TextOutput output, @Nullable Looper outputLooper) { | ||||
|     this(output, outputLooper, SubtitleDecoderFactory.DEFAULT); | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * @param output The output. | ||||
|    * @param outputLooper The looper associated with the thread on which the output should be called. | ||||
|    *     If the output makes use of standard Android UI components, then this should normally be the | ||||
|    *     looper associated with the application's main thread, which can be obtained using {@link | ||||
|    *     android.app.Activity#getMainLooper()}. Null may be passed if the output should be called | ||||
|    *     directly on the player's internal rendering thread. | ||||
|    * @param decoderFactory A factory from which to obtain {@link SubtitleDecoder} instances. | ||||
|    */ | ||||
|   public NonFinalTextRenderer( | ||||
|       TextOutput output, @Nullable Looper outputLooper, SubtitleDecoderFactory decoderFactory) { | ||||
|     super(C.TRACK_TYPE_TEXT); | ||||
|     this.output = checkNotNull(output); | ||||
|     this.outputHandler = | ||||
|         outputLooper == null ? null : Util.createHandler(outputLooper, /* callback= */ this); | ||||
|     this.decoderFactory = decoderFactory; | ||||
|     formatHolder = new FormatHolder(); | ||||
|     finalStreamEndPositionUs = C.TIME_UNSET; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public String getName() { | ||||
|     return TAG; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public @Capabilities int supportsFormat(Format format) { | ||||
|     if (decoderFactory.supportsFormat(format)) { | ||||
|       return RendererCapabilities.create( | ||||
|           format.cryptoType == C.CRYPTO_TYPE_NONE ? C.FORMAT_HANDLED : C.FORMAT_UNSUPPORTED_DRM); | ||||
|     } else if (MimeTypes.isText(format.sampleMimeType)) { | ||||
|       return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_SUBTYPE); | ||||
|     } else { | ||||
|       return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_TYPE); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   /** | ||||
|    * Sets the position at which to stop rendering the current stream. | ||||
|    * | ||||
|    * <p>Must be called after {@link #setCurrentStreamFinal()}. | ||||
|    * | ||||
|    * @param streamEndPositionUs The position to stop rendering at or {@link C#LENGTH_UNSET} to | ||||
|    *     render until the end of the current stream. | ||||
|    */ | ||||
|   // TODO(internal b/181312195): Remove this when it's no longer needed once subtitles are decoded | ||||
|   // on the loading side of SampleQueue. | ||||
|   public void setFinalStreamEndPositionUs(long streamEndPositionUs) { | ||||
|     checkState(isCurrentStreamFinal()); | ||||
|     this.finalStreamEndPositionUs = streamEndPositionUs; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) { | ||||
|     streamFormat = formats[0]; | ||||
|     if (decoder != null) { | ||||
|       decoderReplacementState = REPLACEMENT_STATE_SIGNAL_END_OF_STREAM; | ||||
|     } else { | ||||
|       initDecoder(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   protected void onPositionReset(long positionUs, boolean joining) { | ||||
|     clearOutput(); | ||||
|     inputStreamEnded = false; | ||||
|     outputStreamEnded = false; | ||||
|     finalStreamEndPositionUs = C.TIME_UNSET; | ||||
|     if (decoderReplacementState != REPLACEMENT_STATE_NONE) { | ||||
|       replaceDecoder(); | ||||
|     } else { | ||||
|       releaseBuffers(); | ||||
|       checkNotNull(decoder).flush(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public void render(long positionUs, long elapsedRealtimeUs) { | ||||
|     if (isCurrentStreamFinal() | ||||
|         && finalStreamEndPositionUs != C.TIME_UNSET | ||||
|         && positionUs >= finalStreamEndPositionUs) { | ||||
|       releaseBuffers(); | ||||
|       outputStreamEnded = true; | ||||
|     /** | ||||
|      * @param trackType     The track type that the renderer handles. One of the {@link C} {@code | ||||
|      *                      TRACK_TYPE_*} constants. | ||||
|      * @param outputHandler | ||||
|      */ | ||||
|     public NonFinalTextRenderer(int trackType, @Nullable Handler outputHandler) { | ||||
|         super(trackType); | ||||
|         this.outputHandler = outputHandler; | ||||
|     } | ||||
| 
 | ||||
|     if (outputStreamEnded) { | ||||
|       return; | ||||
|     @Documented | ||||
|     @Retention(RetentionPolicy.SOURCE) | ||||
|     @Target(TYPE_USE) | ||||
|     @IntDef({ | ||||
|             REPLACEMENT_STATE_NONE, | ||||
|             REPLACEMENT_STATE_SIGNAL_END_OF_STREAM, | ||||
|             REPLACEMENT_STATE_WAIT_END_OF_STREAM | ||||
|     }) | ||||
|     private @interface ReplacementState { | ||||
|     } | ||||
| 
 | ||||
|     if (nextSubtitle == null) { | ||||
|       checkNotNull(decoder).setPositionUs(positionUs); | ||||
|       try { | ||||
|         nextSubtitle = checkNotNull(decoder).dequeueOutputBuffer(); | ||||
|       } catch (SubtitleDecoderException e) { | ||||
|         handleDecoderError(e); | ||||
|         return; | ||||
|       } | ||||
|     /** | ||||
|      * The decoder does not need to be replaced. | ||||
|      */ | ||||
|     private static final int REPLACEMENT_STATE_NONE = 0; | ||||
|     /** | ||||
|      * The decoder needs to be replaced, but we haven't yet signaled an end of stream to the existing | ||||
|      * decoder. We need to do so in order to ensure that it outputs any remaining buffers before we | ||||
|      * release it. | ||||
|      */ | ||||
|     private static final int REPLACEMENT_STATE_SIGNAL_END_OF_STREAM = 1; | ||||
|     /** | ||||
|      * The decoder needs to be replaced, and we've signaled an end of stream to the existing decoder. | ||||
|      * We're waiting for the decoder to output an end of stream signal to indicate that it has output | ||||
|      * any remaining buffers before we release it. | ||||
|      */ | ||||
|     private static final int REPLACEMENT_STATE_WAIT_END_OF_STREAM = 2; | ||||
| 
 | ||||
|     private static final int MSG_UPDATE_OUTPUT = 0; | ||||
| 
 | ||||
|     @Nullable | ||||
|     private final Handler outputHandler; | ||||
|     private TextOutput output = null; | ||||
|     private SubtitleDecoderFactory decoderFactory = null; | ||||
|     private FormatHolder formatHolder = null; | ||||
| 
 | ||||
|     private boolean inputStreamEnded; | ||||
|     private boolean outputStreamEnded; | ||||
|     private boolean waitingForKeyFrame; | ||||
|     private @ReplacementState int decoderReplacementState; | ||||
|     @Nullable | ||||
|     private Format streamFormat; | ||||
|     @Nullable | ||||
|     private SubtitleDecoder decoder; | ||||
|     @Nullable | ||||
|     private SubtitleInputBuffer nextInputBuffer; | ||||
|     @Nullable | ||||
|     private SubtitleOutputBuffer subtitle; | ||||
|     @Nullable | ||||
|     private SubtitleOutputBuffer nextSubtitle; | ||||
|     private int nextSubtitleEventIndex; | ||||
|     private long finalStreamEndPositionUs; | ||||
| 
 | ||||
|     /** | ||||
|      * @param output       The output. | ||||
|      * @param outputLooper The looper associated with the thread on which the output should be called. | ||||
|      *                     If the output makes use of standard Android UI components, then this should normally be the | ||||
|      *                     looper associated with the application's main thread, which can be obtained using {@link | ||||
|      *                     android.app.Activity#getMainLooper()}. Null may be passed if the output should be called | ||||
|      *                     directly on the player's internal rendering thread. | ||||
|      */ | ||||
|     public NonFinalTextRenderer(TextOutput output, @Nullable Looper outputLooper) { | ||||
|         this(output, outputLooper, SubtitleDecoderFactory.DEFAULT); | ||||
|     } | ||||
| 
 | ||||
|     if (getState() != STATE_STARTED) { | ||||
|       return; | ||||
|     /** | ||||
|      * @param output         The output. | ||||
|      * @param outputLooper   The looper associated with the thread on which the output should be called. | ||||
|      *                       If the output makes use of standard Android UI components, then this should normally be the | ||||
|      *                       looper associated with the application's main thread, which can be obtained using {@link | ||||
|      *                       android.app.Activity#getMainLooper()}. Null may be passed if the output should be called | ||||
|      *                       directly on the player's internal rendering thread. | ||||
|      * @param decoderFactory A factory from which to obtain {@link SubtitleDecoder} instances. | ||||
|      */ | ||||
|     public NonFinalTextRenderer( | ||||
|             TextOutput output, @Nullable Looper outputLooper, SubtitleDecoderFactory decoderFactory) { | ||||
|         super(C.TRACK_TYPE_TEXT); | ||||
|         this.output = checkNotNull(output); | ||||
|         this.outputHandler = | ||||
|                 outputLooper == null ? null : Util.createHandler(outputLooper, /* callback= */ this); | ||||
|         this.decoderFactory = decoderFactory; | ||||
|         formatHolder = new FormatHolder(); | ||||
|         finalStreamEndPositionUs = C.TIME_UNSET; | ||||
|     } | ||||
| 
 | ||||
|     boolean textRendererNeedsUpdate = false; | ||||
|     if (subtitle != null) { | ||||
|       // We're iterating through the events in a subtitle. Set textRendererNeedsUpdate if we | ||||
|       // advance to the next event. | ||||
|       long subtitleNextEventTimeUs = getNextEventTime(); | ||||
|       while (subtitleNextEventTimeUs <= positionUs) { | ||||
|         nextSubtitleEventIndex++; | ||||
|         subtitleNextEventTimeUs = getNextEventTime(); | ||||
|         textRendererNeedsUpdate = true; | ||||
|       } | ||||
|     @Override | ||||
|     public String getName() { | ||||
|         return TAG; | ||||
|     } | ||||
|     if (nextSubtitle != null) { | ||||
|       SubtitleOutputBuffer nextSubtitle = this.nextSubtitle; | ||||
|       if (nextSubtitle.isEndOfStream()) { | ||||
|         if (!textRendererNeedsUpdate && getNextEventTime() == Long.MAX_VALUE) { | ||||
|           if (decoderReplacementState == REPLACEMENT_STATE_WAIT_END_OF_STREAM) { | ||||
| 
 | ||||
|     @Override | ||||
|     public @Capabilities int supportsFormat(Format format) { | ||||
|         if (decoderFactory.supportsFormat(format)) { | ||||
|             return RendererCapabilities.create( | ||||
|                     format.cryptoType == C.CRYPTO_TYPE_NONE ? C.FORMAT_HANDLED : C.FORMAT_UNSUPPORTED_DRM); | ||||
|         } else if (MimeTypes.isText(format.sampleMimeType)) { | ||||
|             return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_SUBTYPE); | ||||
|         } else { | ||||
|             return RendererCapabilities.create(C.FORMAT_UNSUPPORTED_TYPE); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Sets the position at which to stop rendering the current stream. | ||||
|      * | ||||
|      * <p>Must be called after {@link #setCurrentStreamFinal()}. | ||||
|      * | ||||
|      * @param streamEndPositionUs The position to stop rendering at or {@link C#LENGTH_UNSET} to | ||||
|      *                            render until the end of the current stream. | ||||
|      */ | ||||
|     // TODO(internal b/181312195): Remove this when it's no longer needed once subtitles are decoded | ||||
|     // on the loading side of SampleQueue. | ||||
|     public void setFinalStreamEndPositionUs(long streamEndPositionUs) { | ||||
|         checkState(isCurrentStreamFinal()); | ||||
|         this.finalStreamEndPositionUs = streamEndPositionUs; | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onStreamChanged(Format[] formats, long startPositionUs, long offsetUs) { | ||||
|         streamFormat = formats[0]; | ||||
|         if (decoder != null) { | ||||
|             decoderReplacementState = REPLACEMENT_STATE_SIGNAL_END_OF_STREAM; | ||||
|         } else { | ||||
|             initDecoder(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     protected void onPositionReset(long positionUs, boolean joining) { | ||||
|         clearOutput(); | ||||
|         inputStreamEnded = false; | ||||
|         outputStreamEnded = false; | ||||
|         finalStreamEndPositionUs = C.TIME_UNSET; | ||||
|         if (decoderReplacementState != REPLACEMENT_STATE_NONE) { | ||||
|             replaceDecoder(); | ||||
|           } else { | ||||
|         } else { | ||||
|             releaseBuffers(); | ||||
|             checkNotNull(decoder).flush(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     @Override | ||||
|     public void render(long positionUs, long elapsedRealtimeUs) { | ||||
|         if (isCurrentStreamFinal() | ||||
|                 && finalStreamEndPositionUs != C.TIME_UNSET | ||||
|                 && positionUs >= finalStreamEndPositionUs) { | ||||
|             releaseBuffers(); | ||||
|             outputStreamEnded = true; | ||||
|           } | ||||
|         } | ||||
|       } else if (nextSubtitle.timeUs <= positionUs) { | ||||
|         // Advance to the next subtitle. Sync the next event index and trigger an update. | ||||
|         if (subtitle != null) { | ||||
|           subtitle.release(); | ||||
|         } | ||||
|         nextSubtitleEventIndex = nextSubtitle.getNextEventTimeIndex(positionUs); | ||||
|         subtitle = nextSubtitle; | ||||
|         this.nextSubtitle = null; | ||||
|         textRendererNeedsUpdate = true; | ||||
|       } | ||||
|     } | ||||
| 
 | ||||
|     if (textRendererNeedsUpdate) { | ||||
|       // If textRendererNeedsUpdate then subtitle must be non-null. | ||||
|       checkNotNull(subtitle); | ||||
|       // textRendererNeedsUpdate is set and we're playing. Update the renderer. | ||||
|       updateOutput(subtitle.getCues(positionUs)); | ||||
|     } | ||||
| 
 | ||||
|     if (decoderReplacementState == REPLACEMENT_STATE_WAIT_END_OF_STREAM) { | ||||
|       return; | ||||
|     } | ||||
| 
 | ||||
|     try { | ||||
|       while (!inputStreamEnded) { | ||||
|         @Nullable SubtitleInputBuffer nextInputBuffer = this.nextInputBuffer; | ||||
|         if (nextInputBuffer == null) { | ||||
|           nextInputBuffer = checkNotNull(decoder).dequeueInputBuffer(); | ||||
|           if (nextInputBuffer == null) { | ||||
|         if (outputStreamEnded) { | ||||
|             return; | ||||
|           } | ||||
|           this.nextInputBuffer = nextInputBuffer; | ||||
|         } | ||||
|         if (decoderReplacementState == REPLACEMENT_STATE_SIGNAL_END_OF_STREAM) { | ||||
|           nextInputBuffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM); | ||||
|           checkNotNull(decoder).queueInputBuffer(nextInputBuffer); | ||||
|           this.nextInputBuffer = null; | ||||
|           decoderReplacementState = REPLACEMENT_STATE_WAIT_END_OF_STREAM; | ||||
|           return; | ||||
|         } | ||||
|         // Try and read the next subtitle from the source. | ||||
|         @ReadDataResult int result = readSource(formatHolder, nextInputBuffer, /* readFlags= */ 0); | ||||
|         if (result == C.RESULT_BUFFER_READ) { | ||||
|           if (nextInputBuffer.isEndOfStream()) { | ||||
|             inputStreamEnded = true; | ||||
|             waitingForKeyFrame = false; | ||||
|           } else { | ||||
|             @Nullable Format format = formatHolder.format; | ||||
|             if (format == null) { | ||||
|               // We haven't received a format yet. | ||||
|               return; | ||||
| 
 | ||||
|         if (nextSubtitle == null) { | ||||
|             checkNotNull(decoder).setPositionUs(positionUs); | ||||
|             try { | ||||
|                 nextSubtitle = checkNotNull(decoder).dequeueOutputBuffer(); | ||||
|             } catch (SubtitleDecoderException e) { | ||||
|                 handleDecoderError(e); | ||||
|                 return; | ||||
|             } | ||||
|             nextInputBuffer.subsampleOffsetUs = format.subsampleOffsetUs; | ||||
|             nextInputBuffer.flip(); | ||||
|             waitingForKeyFrame &= !nextInputBuffer.isKeyFrame(); | ||||
|           } | ||||
|           if (!waitingForKeyFrame) { | ||||
|             checkNotNull(decoder).queueInputBuffer(nextInputBuffer); | ||||
|             this.nextInputBuffer = null; | ||||
|           } | ||||
|         } else if (result == C.RESULT_NOTHING_READ) { | ||||
|           return; | ||||
|         } | ||||
|       } | ||||
|     } catch (SubtitleDecoderException e) { | ||||
|       handleDecoderError(e); | ||||
| 
 | ||||
|         if (getState() != STATE_STARTED) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         boolean textRendererNeedsUpdate = false; | ||||
|         if (subtitle != null) { | ||||
|             // We're iterating through the events in a subtitle. Set textRendererNeedsUpdate if we | ||||
|             // advance to the next event. | ||||
|             long subtitleNextEventTimeUs = getNextEventTime(); | ||||
|             while (subtitleNextEventTimeUs <= positionUs) { | ||||
|                 nextSubtitleEventIndex++; | ||||
|                 subtitleNextEventTimeUs = getNextEventTime(); | ||||
|                 textRendererNeedsUpdate = true; | ||||
|             } | ||||
|         } | ||||
|         if (nextSubtitle != null) { | ||||
|             SubtitleOutputBuffer nextSubtitle = this.nextSubtitle; | ||||
|             if (nextSubtitle.isEndOfStream()) { | ||||
|                 if (!textRendererNeedsUpdate && getNextEventTime() == Long.MAX_VALUE) { | ||||
|                     if (decoderReplacementState == REPLACEMENT_STATE_WAIT_END_OF_STREAM) { | ||||
|                         replaceDecoder(); | ||||
|                     } else { | ||||
|                         releaseBuffers(); | ||||
|                         outputStreamEnded = true; | ||||
|                     } | ||||
|                 } | ||||
|             } else if (nextSubtitle.timeUs <= positionUs) { | ||||
|                 // Advance to the next subtitle. Sync the next event index and trigger an update. | ||||
|                 if (subtitle != null) { | ||||
|                     subtitle.release(); | ||||
|                 } | ||||
|                 nextSubtitleEventIndex = nextSubtitle.getNextEventTimeIndex(positionUs); | ||||
|                 subtitle = nextSubtitle; | ||||
|                 this.nextSubtitle = null; | ||||
|                 textRendererNeedsUpdate = true; | ||||
|             } | ||||
|         } | ||||
| 
 | ||||
|         if (textRendererNeedsUpdate) { | ||||
|             // If textRendererNeedsUpdate then subtitle must be non-null. | ||||
|             checkNotNull(subtitle); | ||||
|             // textRendererNeedsUpdate is set and we're playing. Update the renderer. | ||||
|             updateOutput(subtitle.getCues(positionUs)); | ||||
|         } | ||||
| 
 | ||||
|         if (decoderReplacementState == REPLACEMENT_STATE_WAIT_END_OF_STREAM) { | ||||
|             return; | ||||
|         } | ||||
| 
 | ||||
|         try { | ||||
|             while (!inputStreamEnded) { | ||||
|                 @Nullable SubtitleInputBuffer nextInputBuffer = this.nextInputBuffer; | ||||
|                 if (nextInputBuffer == null) { | ||||
|                     nextInputBuffer = checkNotNull(decoder).dequeueInputBuffer(); | ||||
|                     if (nextInputBuffer == null) { | ||||
|                         return; | ||||
|                     } | ||||
|                     this.nextInputBuffer = nextInputBuffer; | ||||
|                 } | ||||
|                 if (decoderReplacementState == REPLACEMENT_STATE_SIGNAL_END_OF_STREAM) { | ||||
|                     nextInputBuffer.setFlags(C.BUFFER_FLAG_END_OF_STREAM); | ||||
|                     checkNotNull(decoder).queueInputBuffer(nextInputBuffer); | ||||
|                     this.nextInputBuffer = null; | ||||
|                     decoderReplacementState = REPLACEMENT_STATE_WAIT_END_OF_STREAM; | ||||
|                     return; | ||||
|                 } | ||||
|                 // Try and read the next subtitle from the source. | ||||
|                 @ReadDataResult int result = readSource(formatHolder, nextInputBuffer, /* readFlags= */ 0); | ||||
|                 if (result == C.RESULT_BUFFER_READ) { | ||||
|                     if (nextInputBuffer.isEndOfStream()) { | ||||
|                         inputStreamEnded = true; | ||||
|                         waitingForKeyFrame = false; | ||||
|                     } else { | ||||
|                         @Nullable Format format = formatHolder.format; | ||||
|                         if (format == null) { | ||||
|                             // We haven't received a format yet. | ||||
|                             return; | ||||
|                         } | ||||
|                         nextInputBuffer.subsampleOffsetUs = format.subsampleOffsetUs; | ||||
|                         nextInputBuffer.flip(); | ||||
|                         waitingForKeyFrame &= !nextInputBuffer.isKeyFrame(); | ||||
|                     } | ||||
|                     if (!waitingForKeyFrame) { | ||||
|                         checkNotNull(decoder).queueInputBuffer(nextInputBuffer); | ||||
|                         this.nextInputBuffer = null; | ||||
|                     } | ||||
|                 } else if (result == C.RESULT_NOTHING_READ) { | ||||
|                     return; | ||||
|                 } | ||||
|             } | ||||
|         } catch (SubtitleDecoderException e) { | ||||
|             handleDecoderError(e); | ||||
|         } | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   protected void onDisabled() { | ||||
|     streamFormat = null; | ||||
|     finalStreamEndPositionUs = C.TIME_UNSET; | ||||
|     clearOutput(); | ||||
|     releaseDecoder(); | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public boolean isEnded() { | ||||
|     return outputStreamEnded; | ||||
|   } | ||||
| 
 | ||||
|   @Override | ||||
|   public boolean isReady() { | ||||
|     // Don't block playback whilst subtitles are loading. | ||||
|     // Note: To change this behavior, it will be necessary to consider [Internal: b/12949941]. | ||||
|     return true; | ||||
|   } | ||||
| 
 | ||||
|   private void releaseBuffers() { | ||||
|     nextInputBuffer = null; | ||||
|     nextSubtitleEventIndex = C.INDEX_UNSET; | ||||
|     if (subtitle != null) { | ||||
|       subtitle.release(); | ||||
|       subtitle = null; | ||||
|     @Override | ||||
|     protected void onDisabled() { | ||||
|         streamFormat = null; | ||||
|         finalStreamEndPositionUs = C.TIME_UNSET; | ||||
|         clearOutput(); | ||||
|         releaseDecoder(); | ||||
|     } | ||||
|     if (nextSubtitle != null) { | ||||
|       nextSubtitle.release(); | ||||
|       nextSubtitle = null; | ||||
| 
 | ||||
|     @Override | ||||
|     public boolean isEnded() { | ||||
|         return outputStreamEnded; | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private void releaseDecoder() { | ||||
|     releaseBuffers(); | ||||
|     checkNotNull(decoder).release(); | ||||
|     decoder = null; | ||||
|     decoderReplacementState = REPLACEMENT_STATE_NONE; | ||||
|   } | ||||
| 
 | ||||
|   private void initDecoder() { | ||||
|     waitingForKeyFrame = true; | ||||
|     decoder = decoderFactory.createDecoder(checkNotNull(streamFormat)); | ||||
|   } | ||||
| 
 | ||||
|   private void replaceDecoder() { | ||||
|     releaseDecoder(); | ||||
|     initDecoder(); | ||||
|   } | ||||
| 
 | ||||
|   private long getNextEventTime() { | ||||
|     if (nextSubtitleEventIndex == C.INDEX_UNSET) { | ||||
|       return Long.MAX_VALUE; | ||||
|     } | ||||
|     checkNotNull(subtitle); | ||||
|     return nextSubtitleEventIndex >= subtitle.getEventTimeCount() | ||||
|         ? Long.MAX_VALUE | ||||
|         : subtitle.getEventTime(nextSubtitleEventIndex); | ||||
|   } | ||||
| 
 | ||||
|   private void updateOutput(List<Cue> cues) { | ||||
|     if (outputHandler != null) { | ||||
|       outputHandler.obtainMessage(MSG_UPDATE_OUTPUT, cues).sendToTarget(); | ||||
|     } else { | ||||
|       invokeUpdateOutputInternal(cues); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private void clearOutput() { | ||||
|     updateOutput(Collections.emptyList()); | ||||
|   } | ||||
| 
 | ||||
|   @SuppressWarnings("unchecked") | ||||
|   @Override | ||||
|   public boolean handleMessage(Message msg) { | ||||
|     switch (msg.what) { | ||||
|       case MSG_UPDATE_OUTPUT: | ||||
|         invokeUpdateOutputInternal((List<Cue>) msg.obj); | ||||
|     @Override | ||||
|     public boolean isReady() { | ||||
|         // Don't block playback whilst subtitles are loading. | ||||
|         // Note: To change this behavior, it will be necessary to consider [Internal: b/12949941]. | ||||
|         return true; | ||||
|       default: | ||||
|         throw new IllegalStateException(); | ||||
|     } | ||||
|   } | ||||
| 
 | ||||
|   private void invokeUpdateOutputInternal(List<Cue> cues) { | ||||
|     output.onCues(cues); | ||||
|     output.onCues(new CueGroup(cues)); | ||||
|   } | ||||
|     private void releaseBuffers() { | ||||
|         nextInputBuffer = null; | ||||
|         nextSubtitleEventIndex = C.INDEX_UNSET; | ||||
|         if (subtitle != null) { | ||||
|             subtitle.release(); | ||||
|             subtitle = null; | ||||
|         } | ||||
|         if (nextSubtitle != null) { | ||||
|             nextSubtitle.release(); | ||||
|             nextSubtitle = null; | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|   /** | ||||
|    * Called when {@link #decoder} throws an exception, so it can be logged and playback can | ||||
|    * continue. | ||||
|    * | ||||
|    * <p>Logs {@code e} and resets state to allow decoding the next sample. | ||||
|    */ | ||||
|   private void handleDecoderError(SubtitleDecoderException e) { | ||||
|     Log.e(TAG, "Subtitle decoding failed. streamFormat=" + streamFormat, e); | ||||
|     clearOutput(); | ||||
|     replaceDecoder(); | ||||
|   } | ||||
|     private void releaseDecoder() { | ||||
|         releaseBuffers(); | ||||
|         checkNotNull(decoder).release(); | ||||
|         decoder = null; | ||||
|         decoderReplacementState = REPLACEMENT_STATE_NONE; | ||||
|     } | ||||
| 
 | ||||
|     private void initDecoder() { | ||||
|         waitingForKeyFrame = true; | ||||
|         decoder = decoderFactory.createDecoder(checkNotNull(streamFormat)); | ||||
|     } | ||||
| 
 | ||||
|     private void replaceDecoder() { | ||||
|         releaseDecoder(); | ||||
|         initDecoder(); | ||||
|     } | ||||
| 
 | ||||
|     private long getNextEventTime() { | ||||
|         if (nextSubtitleEventIndex == C.INDEX_UNSET) { | ||||
|             return Long.MAX_VALUE; | ||||
|         } | ||||
|         checkNotNull(subtitle); | ||||
|         return nextSubtitleEventIndex >= subtitle.getEventTimeCount() | ||||
|                 ? Long.MAX_VALUE | ||||
|                 : subtitle.getEventTime(nextSubtitleEventIndex); | ||||
|     } | ||||
| 
 | ||||
|     private void updateOutput(List<Cue> cues) { | ||||
|         if (outputHandler != null) { | ||||
|             outputHandler.obtainMessage(MSG_UPDATE_OUTPUT, cues).sendToTarget(); | ||||
|         } else { | ||||
|             invokeUpdateOutputInternal(cues); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void clearOutput() { | ||||
|         updateOutput(Collections.emptyList()); | ||||
|     } | ||||
| 
 | ||||
|     @SuppressWarnings("unchecked") | ||||
|     @Override | ||||
|     public boolean handleMessage(Message msg) { | ||||
|         switch (msg.what) { | ||||
|             case MSG_UPDATE_OUTPUT: | ||||
|                 invokeUpdateOutputInternal((List<Cue>) msg.obj); | ||||
|                 return true; | ||||
|             default: | ||||
|                 throw new IllegalStateException(); | ||||
|         } | ||||
|     } | ||||
| 
 | ||||
|     private void invokeUpdateOutputInternal(List<Cue> cues) { | ||||
|         // See https://github.com/google/ExoPlayer/issues/7934 | ||||
|         // SubripDecoder texts tend to be DIMEN_UNSET which pushes up the | ||||
|         // subs unlike WEBVTT which creates an inconsistency | ||||
| 
 | ||||
|         List<Cue> fixedCues = cues.stream().map( | ||||
|                 cue -> { | ||||
|                     Cue.Builder builder = cue.buildUpon(); | ||||
| 
 | ||||
|                     if (cue.line == DIMEN_UNSET) | ||||
|                         builder.setLine(-1f, LINE_TYPE_NUMBER); | ||||
| 
 | ||||
|                     return builder.setSize(DIMEN_UNSET).build(); | ||||
|                 } | ||||
|         ).collect(Collectors.toList()); | ||||
| 
 | ||||
|         output.onCues(fixedCues); | ||||
|         output.onCues(new CueGroup(fixedCues)); | ||||
|     } | ||||
| 
 | ||||
|     /** | ||||
|      * Called when {@link #decoder} throws an exception, so it can be logged and playback can | ||||
|      * continue. | ||||
|      * | ||||
|      * <p>Logs {@code e} and resets state to allow decoding the next sample. | ||||
|      */ | ||||
|     private void handleDecoderError(SubtitleDecoderException e) { | ||||
|         Log.e(TAG, "Subtitle decoding failed. streamFormat=" + streamFormat, e); | ||||
|         clearOutput(); | ||||
|         replaceDecoder(); | ||||
|     } | ||||
| } | ||||
|  |  | |||
		Loading…
	
	Add table
		Add a link
		
	
		Reference in a new issue