mirror of
https://github.com/samsonjs/media.git
synced 2026-04-27 15:07:40 +00:00
Merge pull request #7034 from TiVo:p-exception-unreported-discontinuity
PiperOrigin-RevId: 305006564
This commit is contained in:
commit
8991586c3b
3 changed files with 150 additions and 25 deletions
|
|
@ -488,7 +488,7 @@ public class SampleQueue implements TrackOutput {
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void sampleMetadata(
|
public void sampleMetadata(
|
||||||
long timeUs,
|
long timeUs,
|
||||||
@C.BufferFlags int flags,
|
@C.BufferFlags int flags,
|
||||||
int size,
|
int size,
|
||||||
|
|
|
||||||
|
|
@ -129,7 +129,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
private final ArrayList<HlsSampleStream> hlsSampleStreams;
|
private final ArrayList<HlsSampleStream> hlsSampleStreams;
|
||||||
private final Map<String, DrmInitData> overridingDrmInitData;
|
private final Map<String, DrmInitData> overridingDrmInitData;
|
||||||
|
|
||||||
private FormatAdjustingSampleQueue[] sampleQueues;
|
private HlsSampleQueue[] sampleQueues;
|
||||||
private int[] sampleQueueTrackIds;
|
private int[] sampleQueueTrackIds;
|
||||||
private Set<Integer> sampleQueueMappingDoneByType;
|
private Set<Integer> sampleQueueMappingDoneByType;
|
||||||
private SparseIntArray sampleQueueIndicesByType;
|
private SparseIntArray sampleQueueIndicesByType;
|
||||||
|
|
@ -164,7 +164,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
private boolean tracksEnded;
|
private boolean tracksEnded;
|
||||||
private long sampleOffsetUs;
|
private long sampleOffsetUs;
|
||||||
@Nullable private DrmInitData drmInitData;
|
@Nullable private DrmInitData drmInitData;
|
||||||
private int sourceId;
|
@Nullable private HlsMediaChunk sourceChunk;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param trackType The type of the track. One of the {@link C} {@code TRACK_TYPE_*} constants.
|
* @param trackType The type of the track. One of the {@link C} {@code TRACK_TYPE_*} constants.
|
||||||
|
|
@ -209,7 +209,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
sampleQueueTrackIds = new int[0];
|
sampleQueueTrackIds = new int[0];
|
||||||
sampleQueueMappingDoneByType = new HashSet<>(MAPPABLE_TYPES.size());
|
sampleQueueMappingDoneByType = new HashSet<>(MAPPABLE_TYPES.size());
|
||||||
sampleQueueIndicesByType = new SparseIntArray(MAPPABLE_TYPES.size());
|
sampleQueueIndicesByType = new SparseIntArray(MAPPABLE_TYPES.size());
|
||||||
sampleQueues = new FormatAdjustingSampleQueue[0];
|
sampleQueues = new HlsSampleQueue[0];
|
||||||
sampleQueueIsAudioVideoFlags = new boolean[0];
|
sampleQueueIsAudioVideoFlags = new boolean[0];
|
||||||
sampleQueuesEnabledStates = new boolean[0];
|
sampleQueuesEnabledStates = new boolean[0];
|
||||||
mediaChunks = new ArrayList<>();
|
mediaChunks = new ArrayList<>();
|
||||||
|
|
@ -817,19 +817,19 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
/**
|
/**
|
||||||
* Performs initialization for a media chunk that's about to start loading.
|
* Performs initialization for a media chunk that's about to start loading.
|
||||||
*
|
*
|
||||||
* @param mediaChunk The media chunk that's about to start loading.
|
* @param chunk The media chunk that's about to start loading.
|
||||||
*/
|
*/
|
||||||
private void initMediaChunkLoad(HlsMediaChunk mediaChunk) {
|
private void initMediaChunkLoad(HlsMediaChunk chunk) {
|
||||||
sourceId = mediaChunk.uid;
|
sourceChunk = chunk;
|
||||||
upstreamTrackFormat = mediaChunk.trackFormat;
|
upstreamTrackFormat = chunk.trackFormat;
|
||||||
pendingResetPositionUs = C.TIME_UNSET;
|
pendingResetPositionUs = C.TIME_UNSET;
|
||||||
mediaChunks.add(mediaChunk);
|
mediaChunks.add(chunk);
|
||||||
|
|
||||||
mediaChunk.init(this);
|
chunk.init(this);
|
||||||
for (SampleQueue sampleQueue : sampleQueues) {
|
for (HlsSampleQueue sampleQueue : sampleQueues) {
|
||||||
sampleQueue.sourceId(sourceId);
|
sampleQueue.setSourceChunk(chunk);
|
||||||
}
|
}
|
||||||
if (mediaChunk.shouldSpliceIn) {
|
if (chunk.shouldSpliceIn) {
|
||||||
for (SampleQueue sampleQueue : sampleQueues) {
|
for (SampleQueue sampleQueue : sampleQueues) {
|
||||||
sampleQueue.splice();
|
sampleQueue.splice();
|
||||||
}
|
}
|
||||||
|
|
@ -906,18 +906,19 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
int trackCount = sampleQueues.length;
|
int trackCount = sampleQueues.length;
|
||||||
|
|
||||||
boolean isAudioVideo = type == C.TRACK_TYPE_AUDIO || type == C.TRACK_TYPE_VIDEO;
|
boolean isAudioVideo = type == C.TRACK_TYPE_AUDIO || type == C.TRACK_TYPE_VIDEO;
|
||||||
FormatAdjustingSampleQueue trackOutput =
|
HlsSampleQueue sampleQueue =
|
||||||
new FormatAdjustingSampleQueue(
|
new HlsSampleQueue(allocator, drmSessionManager, eventDispatcher, overridingDrmInitData);
|
||||||
allocator, drmSessionManager, eventDispatcher, overridingDrmInitData);
|
|
||||||
if (isAudioVideo) {
|
if (isAudioVideo) {
|
||||||
trackOutput.setDrmInitData(drmInitData);
|
sampleQueue.setDrmInitData(drmInitData);
|
||||||
}
|
}
|
||||||
trackOutput.setSampleOffsetUs(sampleOffsetUs);
|
sampleQueue.setSampleOffsetUs(sampleOffsetUs);
|
||||||
trackOutput.sourceId(sourceId);
|
if (sourceChunk != null) {
|
||||||
trackOutput.setUpstreamFormatChangeListener(this);
|
sampleQueue.setSourceChunk(sourceChunk);
|
||||||
|
}
|
||||||
|
sampleQueue.setUpstreamFormatChangeListener(this);
|
||||||
sampleQueueTrackIds = Arrays.copyOf(sampleQueueTrackIds, trackCount + 1);
|
sampleQueueTrackIds = Arrays.copyOf(sampleQueueTrackIds, trackCount + 1);
|
||||||
sampleQueueTrackIds[trackCount] = id;
|
sampleQueueTrackIds[trackCount] = id;
|
||||||
sampleQueues = Util.nullSafeArrayAppend(sampleQueues, trackOutput);
|
sampleQueues = Util.nullSafeArrayAppend(sampleQueues, sampleQueue);
|
||||||
sampleQueueIsAudioVideoFlags = Arrays.copyOf(sampleQueueIsAudioVideoFlags, trackCount + 1);
|
sampleQueueIsAudioVideoFlags = Arrays.copyOf(sampleQueueIsAudioVideoFlags, trackCount + 1);
|
||||||
sampleQueueIsAudioVideoFlags[trackCount] = isAudioVideo;
|
sampleQueueIsAudioVideoFlags[trackCount] = isAudioVideo;
|
||||||
haveAudioVideoSampleQueues |= sampleQueueIsAudioVideoFlags[trackCount];
|
haveAudioVideoSampleQueues |= sampleQueueIsAudioVideoFlags[trackCount];
|
||||||
|
|
@ -928,7 +929,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
primarySampleQueueType = type;
|
primarySampleQueueType = type;
|
||||||
}
|
}
|
||||||
sampleQueuesEnabledStates = Arrays.copyOf(sampleQueuesEnabledStates, trackCount + 1);
|
sampleQueuesEnabledStates = Arrays.copyOf(sampleQueuesEnabledStates, trackCount + 1);
|
||||||
return trackOutput;
|
return sampleQueue;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
@ -1341,12 +1342,40 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
return new DummyTrackOutput();
|
return new DummyTrackOutput();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static final class FormatAdjustingSampleQueue extends SampleQueue {
|
/**
|
||||||
|
* A {@link SampleQueue} that adds HLS specific functionality:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>Detection of spurious discontinuities, by checking sample timestamps against the range
|
||||||
|
* expected for the currently loading chunk.
|
||||||
|
* <li>Stripping private timestamp metadata from {@link Format Formats} to avoid an excessive
|
||||||
|
* number of format switches in the queue.
|
||||||
|
* <li>Overriding of {@link Format#drmInitData}.
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
private static final class HlsSampleQueue extends SampleQueue {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The fraction of the chunk duration from which timestamps of samples loaded from within a
|
||||||
|
* chunk are allowed to deviate from the expected range.
|
||||||
|
*/
|
||||||
|
private static final double MAX_TIMESTAMP_DEVIATION_FRACTION = 0.5;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A minimum tolerance for sample timestamps in microseconds. Timestamps of samples loaded from
|
||||||
|
* within a chunk are always allowed to deviate up to this amount from the expected range.
|
||||||
|
*/
|
||||||
|
private static final long MIN_TIMESTAMP_DEVIATION_TOLERANCE_US = 4_000_000;
|
||||||
|
|
||||||
|
@Nullable private HlsMediaChunk sourceChunk;
|
||||||
|
private long sourceChunkLastSampleTimeUs;
|
||||||
|
private long minAllowedSampleTimeUs;
|
||||||
|
private long maxAllowedSampleTimeUs;
|
||||||
|
|
||||||
private final Map<String, DrmInitData> overridingDrmInitData;
|
private final Map<String, DrmInitData> overridingDrmInitData;
|
||||||
@Nullable private DrmInitData drmInitData;
|
@Nullable private DrmInitData drmInitData;
|
||||||
|
|
||||||
public FormatAdjustingSampleQueue(
|
private HlsSampleQueue(
|
||||||
Allocator allocator,
|
Allocator allocator,
|
||||||
DrmSessionManager drmSessionManager,
|
DrmSessionManager drmSessionManager,
|
||||||
MediaSourceEventDispatcher eventDispatcher,
|
MediaSourceEventDispatcher eventDispatcher,
|
||||||
|
|
@ -1355,6 +1384,19 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
this.overridingDrmInitData = overridingDrmInitData;
|
this.overridingDrmInitData = overridingDrmInitData;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void setSourceChunk(HlsMediaChunk chunk) {
|
||||||
|
sourceChunk = chunk;
|
||||||
|
sourceChunkLastSampleTimeUs = C.TIME_UNSET;
|
||||||
|
sourceId(chunk.uid);
|
||||||
|
|
||||||
|
long allowedDeviationUs =
|
||||||
|
Math.max(
|
||||||
|
(long) ((chunk.endTimeUs - chunk.startTimeUs) * MAX_TIMESTAMP_DEVIATION_FRACTION),
|
||||||
|
MIN_TIMESTAMP_DEVIATION_TOLERANCE_US);
|
||||||
|
minAllowedSampleTimeUs = chunk.startTimeUs - allowedDeviationUs;
|
||||||
|
maxAllowedSampleTimeUs = chunk.endTimeUs + allowedDeviationUs;
|
||||||
|
}
|
||||||
|
|
||||||
public void setDrmInitData(@Nullable DrmInitData drmInitData) {
|
public void setDrmInitData(@Nullable DrmInitData drmInitData) {
|
||||||
this.drmInitData = drmInitData;
|
this.drmInitData = drmInitData;
|
||||||
invalidateUpstreamFormatAdjustment();
|
invalidateUpstreamFormatAdjustment();
|
||||||
|
|
@ -1415,13 +1457,31 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
}
|
}
|
||||||
return new Metadata(newMetadataEntries);
|
return new Metadata(newMetadataEntries);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void sampleMetadata(
|
||||||
|
long timeUs,
|
||||||
|
@C.BufferFlags int flags,
|
||||||
|
int size,
|
||||||
|
int offset,
|
||||||
|
@Nullable CryptoData cryptoData) {
|
||||||
|
// TODO: Uncomment this to reject samples with unexpected timestamps. See
|
||||||
|
// https://github.com/google/ExoPlayer/issues/7030.
|
||||||
|
// if (timeUs < minAllowedSampleTimeUs || timeUs > maxAllowedSampleTimeUs) {
|
||||||
|
// Util.sneakyThrow(
|
||||||
|
// new UnexpectedSampleTimestampException(
|
||||||
|
// sourceChunk, sourceChunkLastSampleTimeUs, timeUs));
|
||||||
|
// }
|
||||||
|
sourceChunkLastSampleTimeUs = timeUs;
|
||||||
|
super.sampleMetadata(timeUs, flags, size, offset, cryptoData);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static class EmsgUnwrappingTrackOutput implements TrackOutput {
|
private static class EmsgUnwrappingTrackOutput implements TrackOutput {
|
||||||
|
|
||||||
private static final String TAG = "EmsgUnwrappingTrackOutput";
|
private static final String TAG = "EmsgUnwrappingTrackOutput";
|
||||||
|
|
||||||
// TODO(ibaker): Create a Formats util class with common constants like this.
|
// TODO: Create a Formats util class with common constants like this.
|
||||||
private static final Format ID3_FORMAT =
|
private static final Format ID3_FORMAT =
|
||||||
new Format.Builder().setSampleMimeType(MimeTypes.APPLICATION_ID3).build();
|
new Format.Builder().setSampleMimeType(MimeTypes.APPLICATION_ID3).build();
|
||||||
private static final Format EMSG_FORMAT =
|
private static final Format EMSG_FORMAT =
|
||||||
|
|
|
||||||
|
|
@ -0,0 +1,65 @@
|
||||||
|
/*
|
||||||
|
* Copyright (C) 2020 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package com.google.android.exoplayer2.source.hls;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
|
import com.google.android.exoplayer2.source.SampleQueue;
|
||||||
|
import com.google.android.exoplayer2.source.chunk.MediaChunk;
|
||||||
|
import java.io.IOException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Thrown when an attempt is made to write a sample to a {@link SampleQueue} whose timestamp is
|
||||||
|
* inconsistent with the chunk from which it originates.
|
||||||
|
*/
|
||||||
|
/* package */ final class UnexpectedSampleTimestampException extends IOException {
|
||||||
|
|
||||||
|
/** The {@link MediaChunk} that contained the rejected sample. */
|
||||||
|
public final MediaChunk mediaChunk;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The timestamp of the last sample that was loaded from {@link #mediaChunk} and successfully
|
||||||
|
* written to the {@link SampleQueue}, in microseconds. {@link C#TIME_UNSET} if the first sample
|
||||||
|
* in the chunk was rejected.
|
||||||
|
*/
|
||||||
|
public final long lastAcceptedSampleTimeUs;
|
||||||
|
|
||||||
|
/** The timestamp of the rejected sample, in microseconds. */
|
||||||
|
public final long rejectedSampleTimeUs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance.
|
||||||
|
*
|
||||||
|
* @param mediaChunk The {@link MediaChunk} with the unexpected sample timestamp.
|
||||||
|
* @param lastAcceptedSampleTimeUs The timestamp of the last sample that was loaded from the chunk
|
||||||
|
* and successfully written to the {@link SampleQueue}, in microseconds. {@link C#TIME_UNSET}
|
||||||
|
* if the first sample in the chunk was rejected.
|
||||||
|
* @param rejectedSampleTimeUs The timestamp of the rejected sample, in microseconds.
|
||||||
|
*/
|
||||||
|
public UnexpectedSampleTimestampException(
|
||||||
|
MediaChunk mediaChunk, long lastAcceptedSampleTimeUs, long rejectedSampleTimeUs) {
|
||||||
|
super(
|
||||||
|
"Unexpected sample timestamp: "
|
||||||
|
+ C.usToMs(rejectedSampleTimeUs)
|
||||||
|
+ " in chunk ["
|
||||||
|
+ mediaChunk.startTimeUs
|
||||||
|
+ ", "
|
||||||
|
+ mediaChunk.endTimeUs
|
||||||
|
+ "]");
|
||||||
|
this.mediaChunk = mediaChunk;
|
||||||
|
this.lastAcceptedSampleTimeUs = lastAcceptedSampleTimeUs;
|
||||||
|
this.rejectedSampleTimeUs = rejectedSampleTimeUs;
|
||||||
|
}
|
||||||
|
}
|
||||||
Loading…
Reference in a new issue