diff --git a/RELEASENOTES.md b/RELEASENOTES.md index fd20664692..018da272f6 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -22,6 +22,8 @@ * HLS: * Fix playback of livestreams with EXT-X-PROGRAM-DATE-TIME tags ([#4239](https://github.com/google/ExoPlayer/issues/4239)). + * Fix playback of clipped streams starting from non-keyframe positions + ([#4241](https://github.com/google/ExoPlayer/issues/4241)). * Caption: * TTML: * Fix a styling issue when there are multiple regions displayed at the same diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStream.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStream.java index 5d4d953372..f43d119018 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStream.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStream.java @@ -19,6 +19,7 @@ import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.FormatHolder; import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.source.SampleStream; +import com.google.android.exoplayer2.util.Assertions; import java.io.IOException; /** @@ -36,6 +37,11 @@ import java.io.IOException; sampleQueueIndex = HlsSampleStreamWrapper.SAMPLE_QUEUE_INDEX_PENDING; } + public void bindSampleQueue() { + Assertions.checkArgument(sampleQueueIndex == HlsSampleStreamWrapper.SAMPLE_QUEUE_INDEX_PENDING); + sampleQueueIndex = sampleStreamWrapper.bindSampleQueueToSampleStream(trackGroupIndex); + } + public void unbindSampleQueue() { if (sampleQueueIndex != HlsSampleStreamWrapper.SAMPLE_QUEUE_INDEX_PENDING) { sampleStreamWrapper.unbindSampleQueue(trackGroupIndex); @@ -48,12 +54,11 @@ import java.io.IOException; @Override public boolean isReady() { return sampleQueueIndex == HlsSampleStreamWrapper.SAMPLE_QUEUE_INDEX_NO_MAPPING_NON_FATAL - || (maybeMapToSampleQueue() && sampleStreamWrapper.isReady(sampleQueueIndex)); + || (hasValidSampleQueueIndex() && sampleStreamWrapper.isReady(sampleQueueIndex)); } @Override public void maybeThrowError() throws IOException { - maybeMapToSampleQueue(); if (sampleQueueIndex == HlsSampleStreamWrapper.SAMPLE_QUEUE_INDEX_NO_MAPPING_FATAL) { throw new SampleQueueMappingException( sampleStreamWrapper.getTrackGroups().get(trackGroupIndex).getFormat(0).sampleMimeType); @@ -63,22 +68,21 @@ import java.io.IOException; @Override public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, boolean requireFormat) { - return maybeMapToSampleQueue() + return hasValidSampleQueueIndex() ? sampleStreamWrapper.readData(sampleQueueIndex, formatHolder, buffer, requireFormat) : C.RESULT_NOTHING_READ; } @Override public int skipData(long positionUs) { - return maybeMapToSampleQueue() ? sampleStreamWrapper.skipData(sampleQueueIndex, positionUs) : 0; + return hasValidSampleQueueIndex() + ? sampleStreamWrapper.skipData(sampleQueueIndex, positionUs) + : 0; } // Internal methods. - private boolean maybeMapToSampleQueue() { - if (sampleQueueIndex == HlsSampleStreamWrapper.SAMPLE_QUEUE_INDEX_PENDING) { - sampleQueueIndex = sampleStreamWrapper.bindSampleQueueToSampleStream(trackGroupIndex); - } + private boolean hasValidSampleQueueIndex() { return sampleQueueIndex != HlsSampleStreamWrapper.SAMPLE_QUEUE_INDEX_PENDING && sampleQueueIndex != HlsSampleStreamWrapper.SAMPLE_QUEUE_INDEX_NO_MAPPING_NON_FATAL && sampleQueueIndex != HlsSampleStreamWrapper.SAMPLE_QUEUE_INDEX_NO_MAPPING_FATAL; diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java index 0de4faa9c0..705320bdad 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/HlsSampleStreamWrapper.java @@ -102,6 +102,7 @@ import java.util.Arrays; private final Runnable maybeFinishPrepareRunnable; private final Runnable onTracksEndedRunnable; private final Handler handler; + private final ArrayList hlsSampleStreams; private SampleQueue[] sampleQueues; private int[] sampleQueueTrackIds; @@ -166,6 +167,7 @@ import java.util.Arrays; sampleQueueIsAudioVideoFlags = new boolean[0]; sampleQueuesEnabledStates = new boolean[0]; mediaChunks = new ArrayList<>(); + hlsSampleStreams = new ArrayList<>(); maybeFinishPrepareRunnable = new Runnable() { @Override @@ -219,9 +221,6 @@ import java.util.Arrays; } public int bindSampleQueueToSampleStream(int trackGroupIndex) { - if (trackGroupToSampleQueueIndex == null) { - return SAMPLE_QUEUE_INDEX_PENDING; - } int sampleQueueIndex = trackGroupToSampleQueueIndex[trackGroupIndex]; if (sampleQueueIndex == C.INDEX_UNSET) { return optionalTrackGroups.indexOf(trackGroups.get(trackGroupIndex)) == C.INDEX_UNSET @@ -295,6 +294,9 @@ import java.util.Arrays; } streams[i] = new HlsSampleStream(this, trackGroupIndex); streamResetFlags[i] = true; + if (trackGroupToSampleQueueIndex != null) { + ((HlsSampleStream) streams[i]).bindSampleQueue(); + } // If there's still a chance of avoiding a seek, try and seek within the sample queue. if (sampleQueuesBuilt && !seekRequired) { SampleQueue sampleQueue = sampleQueues[trackGroupToSampleQueueIndex[trackGroupIndex]]; @@ -360,6 +362,7 @@ import java.util.Arrays; } } + updateSampleStreams(streams); seenFirstTrackSelection = true; return seekRequired; } @@ -411,6 +414,7 @@ import java.util.Arrays; loader.release(this); handler.removeCallbacksAndMessages(null); released = true; + hlsSampleStreams.clear(); } @Override @@ -750,6 +754,15 @@ import java.util.Arrays; // Internal methods. + private void updateSampleStreams(SampleStream[] streams) { + hlsSampleStreams.clear(); + for (SampleStream stream : streams) { + if (stream != null) { + hlsSampleStreams.add((HlsSampleStream) stream); + } + } + } + private boolean finishedReadingChunk(HlsMediaChunk chunk) { int chunkUid = chunk.uid; int sampleQueueCount = sampleQueues.length; @@ -807,6 +820,9 @@ import java.util.Arrays; } } } + for (HlsSampleStream sampleStream : hlsSampleStreams) { + sampleStream.bindSampleQueue(); + } } /** diff --git a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/SampleQueueMappingException.java b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/SampleQueueMappingException.java index 2d430d2c79..9c9cb532a6 100644 --- a/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/SampleQueueMappingException.java +++ b/library/hls/src/main/java/com/google/android/exoplayer2/source/hls/SampleQueueMappingException.java @@ -15,6 +15,7 @@ */ package com.google.android.exoplayer2.source.hls; +import android.support.annotation.Nullable; import com.google.android.exoplayer2.source.SampleQueue; import com.google.android.exoplayer2.source.TrackGroup; import java.io.IOException; @@ -23,7 +24,7 @@ import java.io.IOException; public final class SampleQueueMappingException extends IOException { /** @param mimeType The mime type of the track group whose mapping failed. */ - public SampleQueueMappingException(String mimeType) { + public SampleQueueMappingException(@Nullable String mimeType) { super("Unable to bind a sample queue to TrackGroup with mime type " + mimeType + "."); } }