From 522adc3772e8a1397941fffc054885b92d7f2ef3 Mon Sep 17 00:00:00 2001 From: tonihei Date: Mon, 9 Jul 2018 05:44:39 -0700 Subject: [PATCH] Restructure track selection in DashMediaPeriod. Until now, the streams were released and re-enabled for each type of stream (primary, event, embedded) in that order. That leads to problems when replacing streams from one type to another (for example embedded to primary). This change restructures the track selection to: 1. Release and reset all streams that need to be released or replaced. 1(a). Including embedded orphan streams. 2. Select new streams. Issue:#4477 ------------- Created by MOE: https://github.com/google/moe MOE_MIGRATED_REVID=203751233 --- RELEASENOTES.md | 2 + .../source/dash/DashMediaPeriod.java | 257 ++++++++++-------- 2 files changed, 140 insertions(+), 119 deletions(-) diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 70fd33db6dc..86ddada1f07 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -21,6 +21,8 @@ * Add workaround for track index mismatches between tfhd and tkhd boxes in fragmented MP4 files ([#4083](https://github.com/google/ExoPlayer/issues/4083)). +* Fix issue when switching track selection from an embedded track to a primary + track in DASH ([#4477](https://github.com/google/ExoPlayer/issues/4477)). ### 2.8.2 ### diff --git a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java index f80ff89fc15..dd41db29e9d 100644 --- a/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java +++ b/library/dash/src/main/java/com/google/android/exoplayer2/source/dash/DashMediaPeriod.java @@ -18,7 +18,6 @@ import android.support.annotation.IntDef; import android.support.annotation.Nullable; import android.util.Pair; -import android.util.SparseArray; import android.util.SparseIntArray; import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.Format; @@ -186,126 +185,34 @@ public TrackGroupArray getTrackGroups() { @Override public long selectTracks(TrackSelection[] selections, boolean[] mayRetainStreamFlags, SampleStream[] streams, boolean[] streamResetFlags, long positionUs) { - SparseArray> primarySampleStreams = new SparseArray<>(); - List eventSampleStreamList = new ArrayList<>(); - - selectPrimarySampleStreams(selections, mayRetainStreamFlags, streams, streamResetFlags, - positionUs, primarySampleStreams); - selectEventSampleStreams(selections, mayRetainStreamFlags, streams, - streamResetFlags, eventSampleStreamList); - selectEmbeddedSampleStreams(selections, mayRetainStreamFlags, streams, streamResetFlags, - positionUs, primarySampleStreams); - - sampleStreams = newSampleStreamArray(primarySampleStreams.size()); - for (int i = 0; i < sampleStreams.length; i++) { - sampleStreams[i] = primarySampleStreams.valueAt(i); + int[] streamIndexToTrackGroupIndex = getStreamIndexToTrackGroupIndex(selections); + releaseDisabledStreams(selections, mayRetainStreamFlags, streams); + releaseOrphanEmbeddedStreams(selections, streams, streamIndexToTrackGroupIndex); + selectNewStreams( + selections, streams, streamResetFlags, positionUs, streamIndexToTrackGroupIndex); + + ArrayList> sampleStreamList = new ArrayList<>(); + ArrayList eventSampleStreamList = new ArrayList<>(); + for (SampleStream sampleStream : sampleStreams) { + if (sampleStream instanceof ChunkSampleStream) { + @SuppressWarnings("unchecked") + ChunkSampleStream stream = + (ChunkSampleStream) sampleStream; + sampleStreamList.add(stream); + } else if (sampleStream instanceof EventSampleStream) { + eventSampleStreamList.add((EventSampleStream) sampleStream); + } } + sampleStreams = newSampleStreamArray(sampleStreamList.size()); + sampleStreamList.toArray(sampleStreams); eventSampleStreams = new EventSampleStream[eventSampleStreamList.size()]; eventSampleStreamList.toArray(eventSampleStreams); + compositeSequenceableLoader = compositeSequenceableLoaderFactory.createCompositeSequenceableLoader(sampleStreams); return positionUs; } - private void selectPrimarySampleStreams( - TrackSelection[] selections, - boolean[] mayRetainStreamFlags, - SampleStream[] streams, - boolean[] streamResetFlags, - long positionUs, - SparseArray> primarySampleStreams) { - for (int i = 0; i < selections.length; i++) { - if (streams[i] instanceof ChunkSampleStream) { - @SuppressWarnings("unchecked") - ChunkSampleStream stream = (ChunkSampleStream) streams[i]; - if (selections[i] == null || !mayRetainStreamFlags[i]) { - stream.release(this); - streams[i] = null; - } else { - int trackGroupIndex = trackGroups.indexOf(selections[i].getTrackGroup()); - primarySampleStreams.put(trackGroupIndex, stream); - } - } - - if (streams[i] == null && selections[i] != null) { - int trackGroupIndex = trackGroups.indexOf(selections[i].getTrackGroup()); - TrackGroupInfo trackGroupInfo = trackGroupInfos[trackGroupIndex]; - if (trackGroupInfo.trackGroupCategory == TrackGroupInfo.CATEGORY_PRIMARY) { - ChunkSampleStream stream = buildSampleStream(trackGroupInfo, - selections[i], positionUs); - primarySampleStreams.put(trackGroupIndex, stream); - streams[i] = stream; - streamResetFlags[i] = true; - } - } - } - } - - private void selectEventSampleStreams(TrackSelection[] selections, boolean[] mayRetainStreamFlags, - SampleStream[] streams, boolean[] streamResetFlags, - List eventSampleStreamsList) { - for (int i = 0; i < selections.length; i++) { - if (streams[i] instanceof EventSampleStream) { - EventSampleStream stream = (EventSampleStream) streams[i]; - if (selections[i] == null || !mayRetainStreamFlags[i]) { - streams[i] = null; - } else { - eventSampleStreamsList.add(stream); - } - } - - if (streams[i] == null && selections[i] != null) { - int trackGroupIndex = trackGroups.indexOf(selections[i].getTrackGroup()); - TrackGroupInfo trackGroupInfo = trackGroupInfos[trackGroupIndex]; - if (trackGroupInfo.trackGroupCategory == TrackGroupInfo.CATEGORY_MANIFEST_EVENTS) { - EventStream eventStream = eventStreams.get(trackGroupInfo.eventStreamGroupIndex); - Format format = selections[i].getTrackGroup().getFormat(0); - EventSampleStream stream = new EventSampleStream(eventStream, format, manifest.dynamic); - streams[i] = stream; - streamResetFlags[i] = true; - eventSampleStreamsList.add(stream); - } - } - } - } - - private void selectEmbeddedSampleStreams( - TrackSelection[] selections, - boolean[] mayRetainStreamFlags, - SampleStream[] streams, - boolean[] streamResetFlags, - long positionUs, - SparseArray> primarySampleStreams) { - for (int i = 0; i < selections.length; i++) { - if ((streams[i] instanceof EmbeddedSampleStream || streams[i] instanceof EmptySampleStream) - && (selections[i] == null || !mayRetainStreamFlags[i])) { - // The stream is for an embedded track and is either no longer selected or needs replacing. - releaseIfEmbeddedSampleStream(streams[i]); - streams[i] = null; - } - // We need to consider replacing the stream even if it's non-null because the primary stream - // may have been replaced, selected or deselected. - if (selections[i] != null) { - int trackGroupIndex = trackGroups.indexOf(selections[i].getTrackGroup()); - TrackGroupInfo trackGroupInfo = trackGroupInfos[trackGroupIndex]; - if (trackGroupInfo.trackGroupCategory == TrackGroupInfo.CATEGORY_EMBEDDED) { - ChunkSampleStream primaryStream = primarySampleStreams.get( - trackGroupInfo.primaryTrackGroupIndex); - SampleStream stream = streams[i]; - boolean mayRetainStream = primaryStream == null ? stream instanceof EmptySampleStream - : (stream instanceof EmbeddedSampleStream - && ((EmbeddedSampleStream) stream).parent == primaryStream); - if (!mayRetainStream) { - releaseIfEmbeddedSampleStream(stream); - streams[i] = primaryStream == null ? new EmptySampleStream() - : primaryStream.selectEmbeddedTrack(positionUs, trackGroupInfo.trackType); - streamResetFlags[i] = true; - } - } - } - } - } - @Override public void discardBuffer(long positionUs, boolean toKeyframe) { for (ChunkSampleStream sampleStream : sampleStreams) { @@ -372,6 +279,124 @@ public void onContinueLoadingRequested(ChunkSampleStream sample // Internal methods. + private int[] getStreamIndexToTrackGroupIndex(TrackSelection[] selections) { + int[] streamIndexToTrackGroupIndex = new int[selections.length]; + for (int i = 0; i < selections.length; i++) { + if (selections[i] != null) { + streamIndexToTrackGroupIndex[i] = trackGroups.indexOf(selections[i].getTrackGroup()); + } else { + streamIndexToTrackGroupIndex[i] = C.INDEX_UNSET; + } + } + return streamIndexToTrackGroupIndex; + } + + private void releaseDisabledStreams( + TrackSelection[] selections, boolean[] mayRetainStreamFlags, SampleStream[] streams) { + for (int i = 0; i < selections.length; i++) { + if (selections[i] == null || !mayRetainStreamFlags[i]) { + if (streams[i] instanceof ChunkSampleStream) { + @SuppressWarnings("unchecked") + ChunkSampleStream stream = + (ChunkSampleStream) streams[i]; + stream.release(this); + } else if (streams[i] instanceof EmbeddedSampleStream) { + ((EmbeddedSampleStream) streams[i]).release(); + } + streams[i] = null; + } + } + } + + private void releaseOrphanEmbeddedStreams( + TrackSelection[] selections, SampleStream[] streams, int[] streamIndexToTrackGroupIndex) { + for (int i = 0; i < selections.length; i++) { + if (streams[i] instanceof EmptySampleStream || streams[i] instanceof EmbeddedSampleStream) { + // We need to release an embedded stream if the corresponding primary stream is released. + int primaryStreamIndex = getPrimaryStreamIndex(i, streamIndexToTrackGroupIndex); + boolean mayRetainStream; + if (primaryStreamIndex == C.INDEX_UNSET) { + // If the corresponding primary stream is not selected, we may retain an existing + // EmptySampleStream. + mayRetainStream = streams[i] instanceof EmptySampleStream; + } else { + // If the corresponding primary stream is selected, we may retain the embedded stream if + // the stream's parent still matches. + mayRetainStream = + (streams[i] instanceof EmbeddedSampleStream) + && ((EmbeddedSampleStream) streams[i]).parent == streams[primaryStreamIndex]; + } + if (!mayRetainStream) { + if (streams[i] instanceof EmbeddedSampleStream) { + ((EmbeddedSampleStream) streams[i]).release(); + } + streams[i] = null; + } + } + } + } + + private void selectNewStreams( + TrackSelection[] selections, + SampleStream[] streams, + boolean[] streamResetFlags, + long positionUs, + int[] streamIndexToTrackGroupIndex) { + // Create newly selected primary and event streams. + for (int i = 0; i < selections.length; i++) { + if (streams[i] == null && selections[i] != null) { + streamResetFlags[i] = true; + int trackGroupIndex = streamIndexToTrackGroupIndex[i]; + TrackGroupInfo trackGroupInfo = trackGroupInfos[trackGroupIndex]; + if (trackGroupInfo.trackGroupCategory == TrackGroupInfo.CATEGORY_PRIMARY) { + streams[i] = buildSampleStream(trackGroupInfo, selections[i], positionUs); + } else if (trackGroupInfo.trackGroupCategory == TrackGroupInfo.CATEGORY_MANIFEST_EVENTS) { + EventStream eventStream = eventStreams.get(trackGroupInfo.eventStreamGroupIndex); + Format format = selections[i].getTrackGroup().getFormat(0); + streams[i] = new EventSampleStream(eventStream, format, manifest.dynamic); + } + } + } + // Create newly selected embedded streams from the corresponding primary stream. Note that this + // second pass is needed because the primary stream may not have been created yet in a first + // pass if the index of the primary stream is greater than the index of the embedded stream. + for (int i = 0; i < selections.length; i++) { + if (streams[i] == null && selections[i] != null) { + int trackGroupIndex = streamIndexToTrackGroupIndex[i]; + TrackGroupInfo trackGroupInfo = trackGroupInfos[trackGroupIndex]; + if (trackGroupInfo.trackGroupCategory == TrackGroupInfo.CATEGORY_EMBEDDED) { + int primaryStreamIndex = getPrimaryStreamIndex(i, streamIndexToTrackGroupIndex); + if (primaryStreamIndex == C.INDEX_UNSET) { + // If an embedded track is selected without the corresponding primary track, create an + // empty sample stream instead. + streams[i] = new EmptySampleStream(); + } else { + streams[i] = + ((ChunkSampleStream) streams[primaryStreamIndex]) + .selectEmbeddedTrack(positionUs, trackGroupInfo.trackType); + } + } + } + } + } + + private int getPrimaryStreamIndex(int embeddedStreamIndex, int[] streamIndexToTrackGroupIndex) { + int embeddedTrackGroupIndex = streamIndexToTrackGroupIndex[embeddedStreamIndex]; + if (embeddedTrackGroupIndex == C.INDEX_UNSET) { + return C.INDEX_UNSET; + } + int primaryTrackGroupIndex = trackGroupInfos[embeddedTrackGroupIndex].primaryTrackGroupIndex; + for (int i = 0; i < streamIndexToTrackGroupIndex.length; i++) { + int trackGroupIndex = streamIndexToTrackGroupIndex[i]; + if (trackGroupIndex == primaryTrackGroupIndex + && trackGroupInfos[trackGroupIndex].trackGroupCategory + == TrackGroupInfo.CATEGORY_PRIMARY) { + return i; + } + } + return C.INDEX_UNSET; + } + private static Pair buildTrackGroups( List adaptationSets, List eventStreams) { int[][] groupedAdaptationSetIndices = getGroupedAdaptationSetIndices(adaptationSets); @@ -624,12 +649,6 @@ private static ChunkSampleStream[] newSampleStreamArray(int len return new ChunkSampleStream[length]; } - private static void releaseIfEmbeddedSampleStream(SampleStream sampleStream) { - if (sampleStream instanceof EmbeddedSampleStream) { - ((EmbeddedSampleStream) sampleStream).release(); - } - } - private static final class TrackGroupInfo { @Retention(RetentionPolicy.SOURCE)