mirror of
https://github.com/samsonjs/media.git
synced 2026-03-26 09:35:47 +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
|
||||
public final void sampleMetadata(
|
||||
public void sampleMetadata(
|
||||
long timeUs,
|
||||
@C.BufferFlags int flags,
|
||||
int size,
|
||||
|
|
|
|||
|
|
@ -129,7 +129,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||
private final ArrayList<HlsSampleStream> hlsSampleStreams;
|
||||
private final Map<String, DrmInitData> overridingDrmInitData;
|
||||
|
||||
private FormatAdjustingSampleQueue[] sampleQueues;
|
||||
private HlsSampleQueue[] sampleQueues;
|
||||
private int[] sampleQueueTrackIds;
|
||||
private Set<Integer> sampleQueueMappingDoneByType;
|
||||
private SparseIntArray sampleQueueIndicesByType;
|
||||
|
|
@ -164,7 +164,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||
private boolean tracksEnded;
|
||||
private long sampleOffsetUs;
|
||||
@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.
|
||||
|
|
@ -209,7 +209,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||
sampleQueueTrackIds = new int[0];
|
||||
sampleQueueMappingDoneByType = new HashSet<>(MAPPABLE_TYPES.size());
|
||||
sampleQueueIndicesByType = new SparseIntArray(MAPPABLE_TYPES.size());
|
||||
sampleQueues = new FormatAdjustingSampleQueue[0];
|
||||
sampleQueues = new HlsSampleQueue[0];
|
||||
sampleQueueIsAudioVideoFlags = new boolean[0];
|
||||
sampleQueuesEnabledStates = new boolean[0];
|
||||
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.
|
||||
*
|
||||
* @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) {
|
||||
sourceId = mediaChunk.uid;
|
||||
upstreamTrackFormat = mediaChunk.trackFormat;
|
||||
private void initMediaChunkLoad(HlsMediaChunk chunk) {
|
||||
sourceChunk = chunk;
|
||||
upstreamTrackFormat = chunk.trackFormat;
|
||||
pendingResetPositionUs = C.TIME_UNSET;
|
||||
mediaChunks.add(mediaChunk);
|
||||
mediaChunks.add(chunk);
|
||||
|
||||
mediaChunk.init(this);
|
||||
for (SampleQueue sampleQueue : sampleQueues) {
|
||||
sampleQueue.sourceId(sourceId);
|
||||
chunk.init(this);
|
||||
for (HlsSampleQueue sampleQueue : sampleQueues) {
|
||||
sampleQueue.setSourceChunk(chunk);
|
||||
}
|
||||
if (mediaChunk.shouldSpliceIn) {
|
||||
if (chunk.shouldSpliceIn) {
|
||||
for (SampleQueue sampleQueue : sampleQueues) {
|
||||
sampleQueue.splice();
|
||||
}
|
||||
|
|
@ -906,18 +906,19 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||
int trackCount = sampleQueues.length;
|
||||
|
||||
boolean isAudioVideo = type == C.TRACK_TYPE_AUDIO || type == C.TRACK_TYPE_VIDEO;
|
||||
FormatAdjustingSampleQueue trackOutput =
|
||||
new FormatAdjustingSampleQueue(
|
||||
allocator, drmSessionManager, eventDispatcher, overridingDrmInitData);
|
||||
HlsSampleQueue sampleQueue =
|
||||
new HlsSampleQueue(allocator, drmSessionManager, eventDispatcher, overridingDrmInitData);
|
||||
if (isAudioVideo) {
|
||||
trackOutput.setDrmInitData(drmInitData);
|
||||
sampleQueue.setDrmInitData(drmInitData);
|
||||
}
|
||||
trackOutput.setSampleOffsetUs(sampleOffsetUs);
|
||||
trackOutput.sourceId(sourceId);
|
||||
trackOutput.setUpstreamFormatChangeListener(this);
|
||||
sampleQueue.setSampleOffsetUs(sampleOffsetUs);
|
||||
if (sourceChunk != null) {
|
||||
sampleQueue.setSourceChunk(sourceChunk);
|
||||
}
|
||||
sampleQueue.setUpstreamFormatChangeListener(this);
|
||||
sampleQueueTrackIds = Arrays.copyOf(sampleQueueTrackIds, trackCount + 1);
|
||||
sampleQueueTrackIds[trackCount] = id;
|
||||
sampleQueues = Util.nullSafeArrayAppend(sampleQueues, trackOutput);
|
||||
sampleQueues = Util.nullSafeArrayAppend(sampleQueues, sampleQueue);
|
||||
sampleQueueIsAudioVideoFlags = Arrays.copyOf(sampleQueueIsAudioVideoFlags, trackCount + 1);
|
||||
sampleQueueIsAudioVideoFlags[trackCount] = isAudioVideo;
|
||||
haveAudioVideoSampleQueues |= sampleQueueIsAudioVideoFlags[trackCount];
|
||||
|
|
@ -928,7 +929,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||
primarySampleQueueType = type;
|
||||
}
|
||||
sampleQueuesEnabledStates = Arrays.copyOf(sampleQueuesEnabledStates, trackCount + 1);
|
||||
return trackOutput;
|
||||
return sampleQueue;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
@ -1341,12 +1342,40 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||
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;
|
||||
@Nullable private DrmInitData drmInitData;
|
||||
|
||||
public FormatAdjustingSampleQueue(
|
||||
private HlsSampleQueue(
|
||||
Allocator allocator,
|
||||
DrmSessionManager drmSessionManager,
|
||||
MediaSourceEventDispatcher eventDispatcher,
|
||||
|
|
@ -1355,6 +1384,19 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||
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) {
|
||||
this.drmInitData = drmInitData;
|
||||
invalidateUpstreamFormatAdjustment();
|
||||
|
|
@ -1415,13 +1457,31 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
|||
}
|
||||
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 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 =
|
||||
new Format.Builder().setSampleMimeType(MimeTypes.APPLICATION_ID3).build();
|
||||
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