mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Move HlsSampleQueue to be static inner-class
Backout making the original `FormatAdjustingSampleQueue` an outer class and combining the new timestamp checking logic. This way the diff from `HlsSampleStreamWrapper` to dev-v2 are easier to see.
This commit is contained in:
parent
0d46e24f94
commit
ccf2ba3e1b
@ -1,159 +0,0 @@
|
||||
/*
|
||||
* 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 androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.drm.DrmInitData;
|
||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||
import com.google.android.exoplayer2.metadata.Metadata;
|
||||
import com.google.android.exoplayer2.metadata.id3.PrivFrame;
|
||||
import com.google.android.exoplayer2.source.SampleQueue;
|
||||
import com.google.android.exoplayer2.source.UnexpectedDiscontinuityException;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.Log;
|
||||
import com.google.android.exoplayer2.util.MediaSourceEventDispatcher;
|
||||
import com.google.android.exoplayer2.util.TimestampAdjuster;
|
||||
import java.util.Map;
|
||||
|
||||
/**
|
||||
* Extend base SampleQueue to add HLS specific processing of the samples, including:
|
||||
*
|
||||
* <ul>
|
||||
* <li>segment time boundary checks on timestamps of committed samples</li>
|
||||
* <li>cleaning the {@link Format#metadata} to avoid excessive format changes</li>
|
||||
* </ul>
|
||||
*
|
||||
* The timestamp check verifies that the adjusted sample time (via {@link TimestampAdjuster}) does not
|
||||
* fall outside of a set percentage ({@link #MAX_TIMESTAMP_DEVIATION_PERCENTAGE}) of the time
|
||||
* boundaries of the segment as expressed by the segment duration ((@link HlsMediaChunk#endTimeUs} -
|
||||
* {@link HlsMediaChunk#startTimeUs}). This is loosely mandated by the Pantos spec and checked by
|
||||
* Apple's mediastreamvalidator.
|
||||
*
|
||||
*/
|
||||
public class HlsSampleQueue extends SampleQueue {
|
||||
|
||||
private static final String TAG = "HlsSampleQueue";
|
||||
|
||||
/**
|
||||
* largest timestamp deviation from the segment time bounds expressed as a percentage of
|
||||
* the segment duration.
|
||||
*/
|
||||
public static double MAX_TIMESTAMP_DEVIATION_PERCENTAGE = 0.50;
|
||||
|
||||
private long lowestTimeUs = C.TIME_UNSET;
|
||||
private long highestTimeUs = C.TIME_UNSET;
|
||||
|
||||
@Nullable private HlsMediaChunk chunk;
|
||||
private long lastValidTimeUs;
|
||||
|
||||
private final Map<String, DrmInitData> overridingDrmInitData;
|
||||
@Nullable private DrmInitData drmInitData;
|
||||
|
||||
public HlsSampleQueue(Allocator allocator,
|
||||
DrmSessionManager<?> drmSessionManager,
|
||||
MediaSourceEventDispatcher eventDispatcher,
|
||||
Map<String, DrmInitData> overridingDrmInitData) {
|
||||
super(allocator, drmSessionManager, eventDispatcher);
|
||||
this.overridingDrmInitData = overridingDrmInitData;
|
||||
}
|
||||
|
||||
void setCurrentLoadingChunk(HlsMediaChunk chunk) {
|
||||
double tolerance = (chunk.endTimeUs - chunk.startTimeUs) * MAX_TIMESTAMP_DEVIATION_PERCENTAGE;
|
||||
this.lowestTimeUs = (long) (chunk.startTimeUs - tolerance);
|
||||
this.highestTimeUs = (long) (chunk.endTimeUs + tolerance);
|
||||
this.chunk = chunk;
|
||||
lastValidTimeUs = C.TIME_UNSET;
|
||||
}
|
||||
|
||||
public void setDrmInitData(@Nullable DrmInitData drmInitData) {
|
||||
this.drmInitData = drmInitData;
|
||||
invalidateUpstreamFormatAdjustment();
|
||||
}
|
||||
|
||||
@SuppressWarnings("ReferenceEquality")
|
||||
@Override
|
||||
public Format getAdjustedUpstreamFormat(Format format) {
|
||||
@Nullable
|
||||
DrmInitData drmInitData = this.drmInitData != null ? this.drmInitData : format.drmInitData;
|
||||
if (drmInitData != null) {
|
||||
@Nullable
|
||||
DrmInitData overridingDrmInitData = this.overridingDrmInitData.get(drmInitData.schemeType);
|
||||
if (overridingDrmInitData != null) {
|
||||
drmInitData = overridingDrmInitData;
|
||||
}
|
||||
}
|
||||
@Nullable Metadata metadata = getAdjustedMetadata(format.metadata);
|
||||
if (drmInitData != format.drmInitData || metadata != format.metadata) {
|
||||
format = format.buildUpon().setDrmInitData(drmInitData).setMetadata(metadata).build();
|
||||
}
|
||||
return super.getAdjustedUpstreamFormat(format);
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips the private timestamp frame from metadata, if present. See:
|
||||
* https://github.com/google/ExoPlayer/issues/5063
|
||||
*/
|
||||
@Nullable
|
||||
private Metadata getAdjustedMetadata(@Nullable Metadata metadata) {
|
||||
if (metadata == null) {
|
||||
return null;
|
||||
}
|
||||
int length = metadata.length();
|
||||
int transportStreamTimestampMetadataIndex = C.INDEX_UNSET;
|
||||
for (int i = 0; i < length; i++) {
|
||||
Metadata.Entry metadataEntry = metadata.get(i);
|
||||
if (metadataEntry instanceof PrivFrame) {
|
||||
PrivFrame privFrame = (PrivFrame) metadataEntry;
|
||||
if (HlsMediaChunk.PRIV_TIMESTAMP_FRAME_OWNER.equals(privFrame.owner)) {
|
||||
transportStreamTimestampMetadataIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (transportStreamTimestampMetadataIndex == C.INDEX_UNSET) {
|
||||
return metadata;
|
||||
}
|
||||
if (length == 1) {
|
||||
return null;
|
||||
}
|
||||
Metadata.Entry[] newMetadataEntries = new Metadata.Entry[length - 1];
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (i != transportStreamTimestampMetadataIndex) {
|
||||
int newIndex = i < transportStreamTimestampMetadataIndex ? i : i - 1;
|
||||
newMetadataEntries[newIndex] = metadata.get(i);
|
||||
}
|
||||
}
|
||||
return new Metadata(newMetadataEntries);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sampleMetadata(long timeUs, int flags, int size, int offset, @Nullable CryptoData cryptoData) {
|
||||
// TODO - chunkless prepare, sampleQueue list is not yet initialized for first chunk
|
||||
// Assertions.checkNotNull(chunk, "sampleMetadata without a MediaChunk?");
|
||||
if (chunk == null) {
|
||||
super.sampleMetadata(timeUs, flags, size, offset, cryptoData);
|
||||
} else if (timeUs > highestTimeUs || timeUs < lowestTimeUs) {
|
||||
throw new UnexpectedDiscontinuityException(chunk, lastValidTimeUs, timeUs);
|
||||
} else {
|
||||
lastValidTimeUs = timeUs;
|
||||
super.sampleMetadata(timeUs, flags, size, offset, cryptoData);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
@ -43,6 +43,7 @@ import com.google.android.exoplayer2.source.SampleStream;
|
||||
import com.google.android.exoplayer2.source.SequenceableLoader;
|
||||
import com.google.android.exoplayer2.source.TrackGroup;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.source.UnexpectedDiscontinuityException;
|
||||
import com.google.android.exoplayer2.source.chunk.Chunk;
|
||||
import com.google.android.exoplayer2.source.chunk.MediaChunkIterator;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
@ -56,6 +57,7 @@ import com.google.android.exoplayer2.util.Log;
|
||||
import com.google.android.exoplayer2.util.MediaSourceEventDispatcher;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||
import com.google.android.exoplayer2.util.TimestampAdjuster;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.io.EOFException;
|
||||
import java.io.IOException;
|
||||
@ -1338,6 +1340,134 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Extend base SampleQueue to add HLS specific processing of the samples, including:
|
||||
*
|
||||
* <ul>
|
||||
* <li>segment time boundary checks on timestamps of committed samples</li>
|
||||
* <li>cleaning the {@link Format#metadata} to avoid excessive format changes</li>
|
||||
* </ul>
|
||||
*
|
||||
* The timestamp check verifies that the adjusted sample time (via {@link TimestampAdjuster}) does not
|
||||
* fall outside of a set percentage ({@link #MAX_TIMESTAMP_DEVIATION_PERCENTAGE}) of the time
|
||||
* boundaries of the segment as expressed by the segment duration ((@link HlsMediaChunk#endTimeUs} -
|
||||
* {@link HlsMediaChunk#startTimeUs}). This is loosely mandated by the Pantos spec and checked by
|
||||
* Apple's mediastreamvalidator.
|
||||
*
|
||||
*/
|
||||
private static final class HlsSampleQueue extends SampleQueue {
|
||||
|
||||
private static final String TAG = "HlsSampleQueue";
|
||||
|
||||
/**
|
||||
* largest timestamp deviation from the segment time bounds expressed as a percentage of
|
||||
* the segment duration.
|
||||
*/
|
||||
private static double MAX_TIMESTAMP_DEVIATION_PERCENTAGE = 0.50;
|
||||
|
||||
private long lowestTimeUs = C.TIME_UNSET;
|
||||
private long highestTimeUs = C.TIME_UNSET;
|
||||
|
||||
@Nullable private HlsMediaChunk chunk;
|
||||
private long lastValidTimeUs;
|
||||
|
||||
private final Map<String, DrmInitData> overridingDrmInitData;
|
||||
@Nullable private DrmInitData drmInitData;
|
||||
|
||||
private HlsSampleQueue(Allocator allocator,
|
||||
DrmSessionManager<?> drmSessionManager,
|
||||
MediaSourceEventDispatcher eventDispatcher,
|
||||
Map<String, DrmInitData> overridingDrmInitData) {
|
||||
super(allocator, drmSessionManager, eventDispatcher);
|
||||
this.overridingDrmInitData = overridingDrmInitData;
|
||||
}
|
||||
|
||||
void setCurrentLoadingChunk(HlsMediaChunk chunk) {
|
||||
double tolerance = (chunk.endTimeUs - chunk.startTimeUs) * MAX_TIMESTAMP_DEVIATION_PERCENTAGE;
|
||||
this.lowestTimeUs = (long) (chunk.startTimeUs - tolerance);
|
||||
this.highestTimeUs = (long) (chunk.endTimeUs + tolerance);
|
||||
this.chunk = chunk;
|
||||
lastValidTimeUs = C.TIME_UNSET;
|
||||
}
|
||||
|
||||
private void setDrmInitData(@Nullable DrmInitData drmInitData) {
|
||||
this.drmInitData = drmInitData;
|
||||
invalidateUpstreamFormatAdjustment();
|
||||
}
|
||||
|
||||
@SuppressWarnings("ReferenceEquality")
|
||||
@Override
|
||||
public Format getAdjustedUpstreamFormat(Format format) {
|
||||
@Nullable
|
||||
DrmInitData drmInitData = this.drmInitData != null ? this.drmInitData : format.drmInitData;
|
||||
if (drmInitData != null) {
|
||||
@Nullable
|
||||
DrmInitData overridingDrmInitData = this.overridingDrmInitData.get(drmInitData.schemeType);
|
||||
if (overridingDrmInitData != null) {
|
||||
drmInitData = overridingDrmInitData;
|
||||
}
|
||||
}
|
||||
@Nullable Metadata metadata = getAdjustedMetadata(format.metadata);
|
||||
if (drmInitData != format.drmInitData || metadata != format.metadata) {
|
||||
format = format.buildUpon().setDrmInitData(drmInitData).setMetadata(metadata).build();
|
||||
}
|
||||
return super.getAdjustedUpstreamFormat(format);
|
||||
}
|
||||
|
||||
/**
|
||||
* Strips the private timestamp frame from metadata, if present. See:
|
||||
* https://github.com/google/ExoPlayer/issues/5063
|
||||
*/
|
||||
@Nullable
|
||||
private Metadata getAdjustedMetadata(@Nullable Metadata metadata) {
|
||||
if (metadata == null) {
|
||||
return null;
|
||||
}
|
||||
int length = metadata.length();
|
||||
int transportStreamTimestampMetadataIndex = C.INDEX_UNSET;
|
||||
for (int i = 0; i < length; i++) {
|
||||
Metadata.Entry metadataEntry = metadata.get(i);
|
||||
if (metadataEntry instanceof PrivFrame) {
|
||||
PrivFrame privFrame = (PrivFrame) metadataEntry;
|
||||
if (HlsMediaChunk.PRIV_TIMESTAMP_FRAME_OWNER.equals(privFrame.owner)) {
|
||||
transportStreamTimestampMetadataIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
if (transportStreamTimestampMetadataIndex == C.INDEX_UNSET) {
|
||||
return metadata;
|
||||
}
|
||||
if (length == 1) {
|
||||
return null;
|
||||
}
|
||||
Metadata.Entry[] newMetadataEntries = new Metadata.Entry[length - 1];
|
||||
for (int i = 0; i < length; i++) {
|
||||
if (i != transportStreamTimestampMetadataIndex) {
|
||||
int newIndex = i < transportStreamTimestampMetadataIndex ? i : i - 1;
|
||||
newMetadataEntries[newIndex] = metadata.get(i);
|
||||
}
|
||||
}
|
||||
return new Metadata(newMetadataEntries);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sampleMetadata(long timeUs, int flags, int size, int offset, @Nullable CryptoData cryptoData) {
|
||||
// TODO - chunkless prepare, sampleQueue list is not yet initialized for first chunk
|
||||
// Assertions.checkNotNull(chunk, "sampleMetadata without a MediaChunk?");
|
||||
if (chunk == null) {
|
||||
super.sampleMetadata(timeUs, flags, size, offset, cryptoData);
|
||||
} else if (timeUs > highestTimeUs || timeUs < lowestTimeUs) {
|
||||
throw new UnexpectedDiscontinuityException(chunk, lastValidTimeUs, timeUs);
|
||||
} else {
|
||||
lastValidTimeUs = timeUs;
|
||||
super.sampleMetadata(timeUs, flags, size, offset, cryptoData);
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
|
||||
private static DummyTrackOutput createDummyTrackOutput(int id, int type) {
|
||||
Log.w(TAG, "Unmapped track with id " + id + " of type " + type);
|
||||
return new DummyTrackOutput();
|
||||
|
Loading…
x
Reference in New Issue
Block a user