public MediaPeriodHolder(Renderer[] renderers, RendererCapabilities[] rendererCapabilities, long rendererPositionOffsetUs, TrackSelector trackSelector, LoadControl loadControl, MediaSource mediaSource, Object periodUid, int periodIndex, boolean isLastPeriod, long startPositionUs) { this.renderers = renderers; this.rendererCapabilities = rendererCapabilities; this.rendererPositionOffsetUs = rendererPositionOffsetUs; this.trackSelector = trackSelector; this.loadControl = loadControl; this.mediaSource = mediaSource; this.uid = Assertions.checkNotNull(periodUid); this.index = periodIndex; this.isLast = isLastPeriod; this.startPositionUs = startPositionUs; sampleStreams = new SampleStream[renderers.length]; mayRetainStreamFlags = new boolean[renderers.length]; mediaPeriod = mediaSource.createPeriod(periodIndex, loadControl.getAllocator(), startPositionUs); }
@Override public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags, SampleStream[] streams, boolean[] streamResetFlags, long positionUs) { Assert.assertTrue(preparedPeriod); int rendererCount = selections.length; for (int i = 0; i < rendererCount; i++) { if (streams[i] != null && (selections[i] == null || !mayRetainStreamFlags[i])) { streams[i] = null; } if (streams[i] == null && selections[i] != null) { TrackSelection selection = selections[i]; Assert.assertTrue(1 <= selection.length()); TrackGroup trackGroup = selection.getTrackGroup(); Assert.assertTrue(trackGroupArray.indexOf(trackGroup) != C.INDEX_UNSET); int indexInTrackGroup = selection.getIndexInTrackGroup(selection.getSelectedIndex()); Assert.assertTrue(0 <= indexInTrackGroup); Assert.assertTrue(indexInTrackGroup < trackGroup.length); streams[i] = createSampleStream(selection); streamResetFlags[i] = true; } } return positionUs; }
@Override @SuppressWarnings("unchecked") public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags, SampleStream[] streams, boolean[] streamResetFlags, long positionUs) { long returnPositionUs = super.selectTracks(selections, mayRetainStreamFlags, streams, streamResetFlags, positionUs); List<ChunkSampleStream<FakeChunkSource>> validStreams = new ArrayList<>(); for (SampleStream stream : streams) { if (stream != null) { validStreams.add((ChunkSampleStream<FakeChunkSource>) stream); } } this.sampleStreams = validStreams.toArray(new ChunkSampleStream[validStreams.size()]); this.sequenceableLoader = new CompositeSequenceableLoader(sampleStreams); return returnPositionUs; }
public MediaPeriodHolder(Renderer[] renderers, RendererCapabilities[] rendererCapabilities, long rendererPositionOffsetUs, TrackSelector trackSelector, LoadControl loadControl, MediaSource mediaSource, Object periodUid, int index, MediaPeriodInfo info) { this.renderers = renderers; this.rendererCapabilities = rendererCapabilities; this.rendererPositionOffsetUs = rendererPositionOffsetUs; this.trackSelector = trackSelector; this.loadControl = loadControl; this.mediaSource = mediaSource; this.uid = Assertions.checkNotNull(periodUid); this.index = index; this.info = info; sampleStreams = new SampleStream[renderers.length]; mayRetainStreamFlags = new boolean[renderers.length]; MediaPeriod mediaPeriod = mediaSource.createPeriod(info.id, loadControl.getAllocator()); if (info.endPositionUs != C.TIME_END_OF_SOURCE) { ClippingMediaPeriod clippingMediaPeriod = new ClippingMediaPeriod(mediaPeriod, true); clippingMediaPeriod.setClipping(0, info.endPositionUs); mediaPeriod = clippingMediaPeriod; } this.mediaPeriod = mediaPeriod; }
@Override public final void enable(RendererConfiguration configuration, Format[] formats, SampleStream stream, long positionUs, boolean joining, long offsetUs) throws ExoPlaybackException { Assertions.checkState(state == STATE_DISABLED); this.configuration = configuration; state = STATE_ENABLED; onEnabled(joining); replaceStream(formats, stream, offsetUs); onPositionReset(positionUs, joining); }
@Override public final void replaceStream(Format[] formats, SampleStream stream, long offsetUs) throws ExoPlaybackException { Assertions.checkState(!streamIsFinal); this.stream = stream; readEndOfStream = false; streamOffsetUs = offsetUs; onStreamChanged(formats); }
@Override public final void enable(Format[] formats, SampleStream stream, long positionUs, boolean joining, long offsetUs) throws ExoPlaybackException { Assertions.checkState(state == STATE_DISABLED); state = STATE_ENABLED; onEnabled(joining); replaceStream(formats, stream, offsetUs); onPositionReset(positionUs, joining); }
public MediaPeriodHolder(Renderer[] renderers, RendererCapabilities[] rendererCapabilities, TrackSelector<T> trackSelector, MediaSource mediaSource, MediaPeriod mediaPeriod, Object uid, long positionUs) { this.renderers = renderers; this.rendererCapabilities = rendererCapabilities; this.trackSelector = trackSelector; this.mediaSource = mediaSource; this.mediaPeriod = mediaPeriod; this.uid = Assertions.checkNotNull(uid); sampleStreams = new SampleStream[renderers.length]; mayRetainStreamFlags = new boolean[renderers.length]; startPositionUs = positionUs; }
@Override protected SampleStream createSampleStream(TrackSelection trackSelection) { FakeChunkSource chunkSource = chunkSourceFactory.createChunkSource(trackSelection, durationUs); return new ChunkSampleStream<>( MimeTypes.getTrackType(trackSelection.getSelectedFormat().sampleMimeType), null, chunkSource, this, allocator, 0, 3, eventDispatcher); }
@Override public final void replaceStream(Format[] formats, SampleStream stream, long offsetUs) throws ExoPlaybackException { Assertions.checkState(!streamIsFinal); this.stream = stream; readEndOfStream = false; streamOffsetUs = offsetUs; onStreamChanged(formats, offsetUs); }
/** * For each renderer of type {@link C#TRACK_TYPE_NONE}, we will remove the dummy * {@link EmptySampleStream} that was associated with it. */ private void disassociateNoSampleRenderersWithEmptySampleStream(SampleStream[] sampleStreams) { for (int i = 0; i < rendererCapabilities.length; i++) { if (rendererCapabilities[i].getTrackType() == C.TRACK_TYPE_NONE) { sampleStreams[i] = null; } } }
/** * For each renderer of type {@link C#TRACK_TYPE_NONE} that was enabled, we will * associate it with a dummy {@link EmptySampleStream}. */ private void associateNoSampleRenderersWithEmptySampleStream(SampleStream[] sampleStreams) { for (int i = 0; i < rendererCapabilities.length; i++) { if (rendererCapabilities[i].getTrackType() == C.TRACK_TYPE_NONE && trackSelectorResult.renderersEnabled[i]) { sampleStreams[i] = new EmptySampleStream(); } } }
@Override public final SampleStream getStream() { return stream; }
private static void releaseIfEmbeddedSampleStream(SampleStream sampleStream) { if (sampleStream instanceof EmbeddedSampleStream) { ((EmbeddedSampleStream) sampleStream).release(); } }
@Override public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags, SampleStream[] streams, boolean[] streamResetFlags, long positionUs) { // Map each selection and stream onto a child period index. int[] streamChildIndices = new int[selections.length]; int[] selectionChildIndices = new int[selections.length]; for (int i = 0; i < selections.length; i++) { streamChildIndices[i] = streams[i] == null ? C.INDEX_UNSET : streamWrapperIndices.get(streams[i]); selectionChildIndices[i] = C.INDEX_UNSET; if (selections[i] != null) { TrackGroup trackGroup = selections[i].getTrackGroup(); for (int j = 0; j < sampleStreamWrappers.length; j++) { if (sampleStreamWrappers[j].getTrackGroups().indexOf(trackGroup) != C.INDEX_UNSET) { selectionChildIndices[i] = j; break; } } } } boolean selectedNewTracks = false; streamWrapperIndices.clear(); // Select tracks for each child, copying the resulting streams back into a new streams array. SampleStream[] newStreams = new SampleStream[selections.length]; SampleStream[] childStreams = new SampleStream[selections.length]; TrackSelection[] childSelections = new TrackSelection[selections.length]; ArrayList<HlsSampleStreamWrapper> enabledSampleStreamWrapperList = new ArrayList<>( sampleStreamWrappers.length); for (int i = 0; i < sampleStreamWrappers.length; i++) { for (int j = 0; j < selections.length; j++) { childStreams[j] = streamChildIndices[j] == i ? streams[j] : null; childSelections[j] = selectionChildIndices[j] == i ? selections[j] : null; } selectedNewTracks |= sampleStreamWrappers[i].selectTracks(childSelections, mayRetainStreamFlags, childStreams, streamResetFlags, !seenFirstTrackSelection); boolean wrapperEnabled = false; for (int j = 0; j < selections.length; j++) { if (selectionChildIndices[j] == i) { // Assert that the child provided a stream for the selection. Assertions.checkState(childStreams[j] != null); newStreams[j] = childStreams[j]; wrapperEnabled = true; streamWrapperIndices.put(childStreams[j], i); } else if (streamChildIndices[j] == i) { // Assert that the child cleared any previous stream. Assertions.checkState(childStreams[j] == null); } } if (wrapperEnabled) { enabledSampleStreamWrapperList.add(sampleStreamWrappers[i]); } } // Copy the new streams back into the streams array. System.arraycopy(newStreams, 0, streams, 0, newStreams.length); // Update the local state. enabledSampleStreamWrappers = new HlsSampleStreamWrapper[enabledSampleStreamWrapperList.size()]; enabledSampleStreamWrapperList.toArray(enabledSampleStreamWrappers); sequenceableLoader = new CompositeSequenceableLoader(enabledSampleStreamWrappers); if (seenFirstTrackSelection && selectedNewTracks) { seekToUs(positionUs); // We'll need to reset renderers consuming from all streams due to the seek. for (int i = 0; i < selections.length; i++) { if (streams[i] != null) { streamResetFlags[i] = true; } } } seenFirstTrackSelection = true; return positionUs; }
private void initializePlaybackLoop() throws ExoPlaybackException { Assertions.checkNotNull(clock); trackSelector.init(new InvalidationListener() { @Override public void onTrackSelectionsInvalidated() { throw new IllegalStateException(); } }); RendererCapabilities[] rendererCapabilities = new RendererCapabilities[renderers.length]; for (int i = 0; i < renderers.length; i++) { rendererCapabilities[i] = renderers[i].getCapabilities(); } selectorResult = trackSelector.selectTracks(rendererCapabilities, mediaPeriod.getTrackGroups()); SampleStream[] sampleStreams = new SampleStream[renderers.length]; boolean[] mayRetainStreamFlags = new boolean[renderers.length]; Arrays.fill(mayRetainStreamFlags, true); mediaPeriod.selectTracks(selectorResult.selections.getAll(), mayRetainStreamFlags, sampleStreams, new boolean[renderers.length], 0); eventListenerHandler.post(new Runnable() { @Override public void run() { for (Player.EventListener eventListener : eventListeners) { eventListener.onTracksChanged(selectorResult.groups, selectorResult.selections); } } }); loadControl.onPrepared(); loadControl.onTracksSelected(renderers, selectorResult.groups, selectorResult.selections); for (int i = 0; i < renderers.length; i++) { TrackSelection selection = selectorResult.selections.get(i); Format[] formats = new Format[selection.length()]; for (int j = 0; j < formats.length; j++) { formats[j] = selection.getFormat(j); } renderers[i].enable(selectorResult.rendererConfigurations[i], formats, sampleStreams[i], 0, false, 0); renderers[i].setCurrentStreamFinal(); } rendererPositionUs = 0; changePlaybackState(Player.STATE_BUFFERING); playbackHandler.post(this); }
protected SampleStream createSampleStream(TrackSelection selection) { return new FakeSampleStream(selection.getSelectedFormat()); }
@Override public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags, SampleStream[] streams, boolean[] streamResetFlags, long positionUs) { // Map each selection and stream onto a child period index. int[] streamChildIndices = new int[selections.length]; int[] selectionChildIndices = new int[selections.length]; for (int i = 0; i < selections.length; i++) { streamChildIndices[i] = streams[i] == null ? C.INDEX_UNSET : streamWrapperIndices.get(streams[i]); selectionChildIndices[i] = C.INDEX_UNSET; if (selections[i] != null) { TrackGroup trackGroup = selections[i].getTrackGroup(); for (int j = 0; j < sampleStreamWrappers.length; j++) { if (sampleStreamWrappers[j].getTrackGroups().indexOf(trackGroup) != C.INDEX_UNSET) { selectionChildIndices[i] = j; break; } } } } boolean forceReset = false; streamWrapperIndices.clear(); // Select tracks for each child, copying the resulting streams back into a new streams array. SampleStream[] newStreams = new SampleStream[selections.length]; SampleStream[] childStreams = new SampleStream[selections.length]; TrackSelection[] childSelections = new TrackSelection[selections.length]; int newEnabledSampleStreamWrapperCount = 0; HlsSampleStreamWrapper[] newEnabledSampleStreamWrappers = new HlsSampleStreamWrapper[sampleStreamWrappers.length]; for (int i = 0; i < sampleStreamWrappers.length; i++) { for (int j = 0; j < selections.length; j++) { childStreams[j] = streamChildIndices[j] == i ? streams[j] : null; childSelections[j] = selectionChildIndices[j] == i ? selections[j] : null; } HlsSampleStreamWrapper sampleStreamWrapper = sampleStreamWrappers[i]; boolean wasReset = sampleStreamWrapper.selectTracks(childSelections, mayRetainStreamFlags, childStreams, streamResetFlags, positionUs, forceReset); boolean wrapperEnabled = false; for (int j = 0; j < selections.length; j++) { if (selectionChildIndices[j] == i) { // Assert that the child provided a stream for the selection. Assertions.checkState(childStreams[j] != null); newStreams[j] = childStreams[j]; wrapperEnabled = true; streamWrapperIndices.put(childStreams[j], i); } else if (streamChildIndices[j] == i) { // Assert that the child cleared any previous stream. Assertions.checkState(childStreams[j] == null); } } if (wrapperEnabled) { newEnabledSampleStreamWrappers[newEnabledSampleStreamWrapperCount] = sampleStreamWrapper; if (newEnabledSampleStreamWrapperCount++ == 0) { // The first enabled wrapper is responsible for initializing timestamp adjusters. This // way, if enabled, variants are responsible. Else audio renditions. Else text renditions. sampleStreamWrapper.setIsTimestampMaster(true); if (wasReset || enabledSampleStreamWrappers.length == 0 || sampleStreamWrapper != enabledSampleStreamWrappers[0]) { // The wrapper responsible for initializing the timestamp adjusters was reset or // changed. We need to reset the timestamp adjuster provider and all other wrappers. timestampAdjusterProvider.reset(); forceReset = true; } } else { sampleStreamWrapper.setIsTimestampMaster(false); } } } // Copy the new streams back into the streams array. System.arraycopy(newStreams, 0, streams, 0, newStreams.length); // Update the local state. enabledSampleStreamWrappers = Arrays.copyOf(newEnabledSampleStreamWrappers, newEnabledSampleStreamWrapperCount); sequenceableLoader = new CompositeSequenceableLoader(enabledSampleStreamWrappers); return positionUs; }
/** * Replaces the {@link SampleStream} that will be associated with this renderer. * <p> * This method may be called when the renderer is in the following states: * {@link #STATE_DISABLED}. * * @param configuration The renderer configuration. * @param formats The enabled formats. Should be empty. * @param stream The {@link SampleStream} from which the renderer should consume. * @param positionUs The player's current position. * @param joining Whether this renderer is being enabled to join an ongoing playback. * @param offsetUs The offset that should be subtracted from {@code positionUs} * to get the playback position with respect to the media. * @throws ExoPlaybackException If an error occurs. */ @Override public final void enable(RendererConfiguration configuration, Format[] formats, SampleStream stream, long positionUs, boolean joining, long offsetUs) throws ExoPlaybackException { Assertions.checkState(state == STATE_DISABLED); this.configuration = configuration; state = STATE_ENABLED; onEnabled(joining); replaceStream(formats, stream, offsetUs); onPositionReset(positionUs, joining); }
/** * Replaces the {@link SampleStream} that will be associated with this renderer. * <p> * This method may be called when the renderer is in the following states: * {@link #STATE_ENABLED}, {@link #STATE_STARTED}. * * @param formats The enabled formats. Should be empty. * @param stream The {@link SampleStream} to be associated with this renderer. * @param offsetUs The offset that should be subtracted from {@code positionUs} in * {@link #render(long, long)} to get the playback position with respect to the media. * @throws ExoPlaybackException If an error occurs. */ @Override public final void replaceStream(Format[] formats, SampleStream stream, long offsetUs) throws ExoPlaybackException { Assertions.checkState(!streamIsFinal); this.stream = stream; onRendererOffsetChanged(offsetUs); }
/** * Enables the renderer to consume from the specified {@link SampleStream}. * <p> * This method may be called when the renderer is in the following states: * {@link #STATE_DISABLED}. * * @param configuration The renderer configuration. * @param formats The enabled formats. * @param stream The {@link SampleStream} from which the renderer should consume. * @param positionUs The player's current position. * @param joining Whether this renderer is being enabled to join an ongoing playback. * @param offsetUs The offset to be added to timestamps of buffers read from {@code stream} * before they are rendered. * @throws ExoPlaybackException If an error occurs. */ void enable(RendererConfiguration configuration, Format[] formats, SampleStream stream, long positionUs, boolean joining, long offsetUs) throws ExoPlaybackException;
/** * Replaces the {@link SampleStream} from which samples will be consumed. * <p> * This method may be called when the renderer is in the following states: * {@link #STATE_ENABLED}, {@link #STATE_STARTED}. * * @param formats The enabled formats. * @param stream The {@link SampleStream} from which the renderer should consume. * @param offsetUs The offset to be added to timestamps of buffers read from {@code stream} before * they are rendered. * @throws ExoPlaybackException If an error occurs. */ void replaceStream(Format[] formats, SampleStream stream, long offsetUs) throws ExoPlaybackException;
/** * Returns the {@link SampleStream} being consumed, or null if the renderer is disabled. */ SampleStream getStream();
/** * Enables the renderer to consume from the specified {@link SampleStream}. * <p> * This method may be called when the renderer is in the following states: * {@link #STATE_DISABLED}. * * @param formats The enabled formats. * @param stream The {@link SampleStream} from which the renderer should consume. * @param positionUs The player's current position. * @param joining Whether this renderer is being enabled to join an ongoing playback. * @param offsetUs The offset to be added to timestamps of buffers read from {@code stream} * before they are rendered. * @throws ExoPlaybackException If an error occurs. */ void enable(Format[] formats, SampleStream stream, long positionUs, boolean joining, long offsetUs) throws ExoPlaybackException;