Commit 6306c269 by aquilescanta Committed by Oliver Woodman

Add support for fragmented MP4 in HLS

This CL adds support for initialization segments in HLS. This is required mainly for(but not limited to) usage of fMP4. The fMP4 support only consists in creating the required extractor if the extension is .mp4, provided the initialization segment is correctly
loaded and passed to the extractor.

-------------
Created by MOE: https://github.com/google/moe
MOE_MIGRATED_REVID=134997636
parent 69af3897
......@@ -303,10 +303,14 @@
"uri": "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8"
},
{
"name": "Apple master playlist advanced",
"name": "Apple master playlist advanced (TS)",
"uri": "https://tungsten.aaplimg.com/VOD/bipbop_adv_example_v2/master.m3u8"
},
{
"name": "Apple master playlist advanced (fMP4)",
"uri": "https://tungsten.aaplimg.com/VOD/bipbop_adv_fmp4_example/master.m3u8"
},
{
"name": "Apple TS media playlist",
"uri": "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear1/prog_index.m3u8"
},
......
......@@ -20,6 +20,7 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.TimestampAdjuster;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.testutil.FakeExtractorInput;
import com.google.android.exoplayer2.testutil.FakeExtractorOutput;
......
......@@ -13,7 +13,7 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.extractor.ts;
package com.google.android.exoplayer2.extractor;
import com.google.android.exoplayer2.C;
......
......@@ -30,6 +30,7 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.extractor.TimestampAdjuster;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.mp4.Atom.ContainerAtom;
import com.google.android.exoplayer2.extractor.mp4.Atom.LeafAtom;
......@@ -115,6 +116,9 @@ public final class FragmentedMp4Extractor implements Extractor {
private final ParsableByteArray nalLength;
private final ParsableByteArray encryptionSignalByte;
// Adjusts sample timestamps.
private final TimestampAdjuster timestampAdjuster;
// Parser state.
private final ParsableByteArray atomHeader;
private final byte[] extendedTypeScratch;
......@@ -140,24 +144,28 @@ public final class FragmentedMp4Extractor implements Extractor {
private boolean haveOutputSeekMap;
public FragmentedMp4Extractor() {
this(0);
this(0, null);
}
/**
* @param flags Flags that control the extractor's behavior.
* @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed.
*/
public FragmentedMp4Extractor(@Flags int flags) {
this(flags, null);
public FragmentedMp4Extractor(@Flags int flags, TimestampAdjuster timestampAdjuster) {
this(flags, null, timestampAdjuster);
}
/**
* @param flags Flags that control the extractor's behavior.
* @param sideloadedTrack Sideloaded track information, in the case that the extractor
* will not receive a moov box in the input data.
* @param timestampAdjuster Adjusts sample timestamps. May be null if no adjustment is needed.
*/
public FragmentedMp4Extractor(@Flags int flags, Track sideloadedTrack) {
public FragmentedMp4Extractor(@Flags int flags, Track sideloadedTrack,
TimestampAdjuster timestampAdjuster) {
this.sideloadedTrack = sideloadedTrack;
this.flags = flags | (sideloadedTrack != null ? FLAG_SIDELOADED : 0);
this.timestampAdjuster = timestampAdjuster;
atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE);
nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE);
nalLength = new ParsableByteArray(4);
......@@ -1012,6 +1020,9 @@ public final class FragmentedMp4Extractor implements Extractor {
? fragment.trackEncryptionBox.keyId
: track.sampleDescriptionEncryptionBoxes[sampleDescriptionIndex].keyId;
}
if (timestampAdjuster != null) {
sampleTimeUs = timestampAdjuster.adjustSampleTimestamp(sampleTimeUs);
}
output.sampleMetadata(sampleTimeUs, sampleFlags, sampleSize, 0, encryptionKey);
currentTrackBundle.currentSampleIndex++;
......
......@@ -23,6 +23,7 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.extractor.TimestampAdjuster;
import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray;
import java.io.IOException;
......
......@@ -25,6 +25,7 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.extractor.TimestampAdjuster;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray;
......
......@@ -21,11 +21,12 @@ import android.text.TextUtils;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.TimestampAdjuster;
import com.google.android.exoplayer2.extractor.mp3.Mp3Extractor;
import com.google.android.exoplayer2.extractor.mp4.FragmentedMp4Extractor;
import com.google.android.exoplayer2.extractor.ts.Ac3Extractor;
import com.google.android.exoplayer2.extractor.ts.AdtsExtractor;
import com.google.android.exoplayer2.extractor.ts.DefaultStreamReaderFactory;
import com.google.android.exoplayer2.extractor.ts.TimestampAdjuster;
import com.google.android.exoplayer2.extractor.ts.TsExtractor;
import com.google.android.exoplayer2.source.BehindLiveWindowException;
import com.google.android.exoplayer2.source.TrackGroup;
......@@ -34,6 +35,7 @@ import com.google.android.exoplayer2.source.chunk.ChunkedTrackBlacklistUtil;
import com.google.android.exoplayer2.source.chunk.DataChunk;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment;
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParser;
import com.google.android.exoplayer2.trackselection.BaseTrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelection;
......@@ -101,6 +103,7 @@ import java.util.Locale;
private static final String AC3_FILE_EXTENSION = ".ac3";
private static final String EC3_FILE_EXTENSION = ".ec3";
private static final String MP3_FILE_EXTENSION = ".mp3";
private static final String MP4_FILE_EXTENSION = ".mp4";
private static final String VTT_FILE_EXTENSION = ".vtt";
private static final String WEBVTT_FILE_EXTENSION = ".webvtt";
......@@ -118,6 +121,7 @@ import java.util.Locale;
private long durationUs;
private IOException fatalError;
private HlsInitializationChunk lastLoadedInitializationChunk;
private Uri encryptionKeyUri;
private byte[] encryptionKey;
private String encryptionIvString;
......@@ -289,7 +293,6 @@ import java.util.Locale;
}
HlsMediaPlaylist.Segment segment = mediaPlaylist.segments.get(chunkIndex);
Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url);
// Check if encryption is specified.
if (segment.isEncrypted) {
......@@ -307,10 +310,6 @@ import java.util.Locale;
clearEncryptionData();
}
// Configure the data source and spec for the chunk.
DataSpec dataSpec = new DataSpec(chunkUri, segment.byterangeOffset, segment.byterangeLength,
null);
// Compute start and end times, and the sequence number of the next chunk.
long startTimeUs;
if (live) {
......@@ -327,8 +326,15 @@ import java.util.Locale;
long endTimeUs = startTimeUs + (long) (segment.durationSecs * C.MICROS_PER_SECOND);
Format format = variants[newVariantIndex].format;
Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url);
// Configure the extractor that will read the chunk.
Extractor extractor;
boolean useInitializedExtractor = lastLoadedInitializationChunk != null
&& lastLoadedInitializationChunk.format == format;
boolean needNewExtractor = previous == null
|| previous.discontinuitySequenceNumber != segment.discontinuitySequenceNumber
|| format != previous.trackFormat;
boolean extractorNeedsInit = true;
boolean isTimestampMaster = false;
TimestampAdjuster timestampAdjuster = null;
......@@ -348,13 +354,21 @@ import java.util.Locale;
timestampAdjuster = timestampAdjusterProvider.getAdjuster(segment.discontinuitySequenceNumber,
startTimeUs);
extractor = new WebvttExtractor(format.language, timestampAdjuster);
} else if (previous == null
|| previous.discontinuitySequenceNumber != segment.discontinuitySequenceNumber
|| format != previous.trackFormat) {
// MPEG-2 TS segments, but we need a new extractor.
} else if (lastPathSegment.endsWith(MP4_FILE_EXTENSION)) {
isTimestampMaster = true;
timestampAdjuster = timestampAdjusterProvider.getAdjuster(segment.discontinuitySequenceNumber,
startTimeUs);
if (needNewExtractor) {
if (useInitializedExtractor) {
extractor = lastLoadedInitializationChunk.extractor;
} else {
timestampAdjuster = timestampAdjusterProvider.getAdjuster(
segment.discontinuitySequenceNumber, startTimeUs);
extractor = new FragmentedMp4Extractor(0, timestampAdjuster);
}
} else {
extractor = previous.extractor;
}
} else if (needNewExtractor) {
// MPEG-2 TS segments, but we need a new extractor.
// This flag ensures the change of pid between streams does not affect the sample queues.
@DefaultStreamReaderFactory.WorkaroundFlags
int workaroundFlags = DefaultStreamReaderFactory.WORKAROUND_MAP_BY_TYPE;
......@@ -370,14 +384,31 @@ import java.util.Locale;
workaroundFlags |= DefaultStreamReaderFactory.WORKAROUND_IGNORE_H264_STREAM;
}
}
extractor = new TsExtractor(timestampAdjuster,
new DefaultStreamReaderFactory(workaroundFlags));
isTimestampMaster = true;
if (useInitializedExtractor) {
extractor = lastLoadedInitializationChunk.extractor;
} else {
timestampAdjuster = timestampAdjusterProvider.getAdjuster(
segment.discontinuitySequenceNumber, startTimeUs);
extractor = new TsExtractor(timestampAdjuster,
new DefaultStreamReaderFactory(workaroundFlags));
}
} else {
// MPEG-2 TS segments, and we need to continue using the same extractor.
extractor = previous.extractor;
extractorNeedsInit = false;
}
if (needNewExtractor && mediaPlaylist.initializationSegment != null
&& !useInitializedExtractor) {
out.chunk = buildInitializationChunk(mediaPlaylist, extractor, format);
return;
}
lastLoadedInitializationChunk = null;
// Configure the data source and spec for the chunk.
DataSpec dataSpec = new DataSpec(chunkUri, segment.byterangeOffset, segment.byterangeLength,
null);
out.chunk = new HlsMediaChunk(dataSource, dataSpec, format,
trackSelection.getSelectionReason(), trackSelection.getSelectionData(),
startTimeUs, endTimeUs, chunkMediaSequence, segment.discontinuitySequenceNumber,
......@@ -434,7 +465,9 @@ import java.util.Locale;
* @param chunk The chunk whose load has been completed.
*/
public void onChunkLoadCompleted(Chunk chunk) {
if (chunk instanceof MediaPlaylistChunk) {
if (chunk instanceof HlsInitializationChunk) {
lastLoadedInitializationChunk = (HlsInitializationChunk) chunk;
} else if (chunk instanceof MediaPlaylistChunk) {
MediaPlaylistChunk mediaPlaylistChunk = (MediaPlaylistChunk) chunk;
scratchSpace = mediaPlaylistChunk.getDataHolder();
setMediaPlaylist(mediaPlaylistChunk.variantIndex, mediaPlaylistChunk.getResult());
......@@ -462,6 +495,18 @@ import java.util.Locale;
// Private methods.
private HlsInitializationChunk buildInitializationChunk(HlsMediaPlaylist mediaPlaylist,
Extractor extractor, Format format) {
Segment initSegment = mediaPlaylist.initializationSegment;
// The initialization segment is required before the actual media chunk.
Uri initSegmentUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, initSegment.url);
DataSpec initDataSpec = new DataSpec(initSegmentUri, initSegment.byterangeOffset,
initSegment.byterangeLength, null);
return new HlsInitializationChunk(dataSource, initDataSpec,
trackSelection.getSelectionReason(), trackSelection.getSelectionData(), extractor,
format);
}
private long msToRerequestLiveMediaPlaylist(int variantIndex) {
HlsMediaPlaylist mediaPlaylist = variantPlaylists[variantIndex];
long timeSinceLastMediaPlaylistLoadMs =
......
/*
* Copyright (C) 2016 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.Format;
import com.google.android.exoplayer2.extractor.DefaultExtractorInput;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.source.chunk.Chunk;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
/**
* An HLS initialization chunk. Provides the extractor with information required for extracting the
* samples.
*/
/* package */ final class HlsInitializationChunk extends Chunk {
public final Format format;
public final Extractor extractor;
private int bytesLoaded;
private volatile boolean loadCanceled;
public HlsInitializationChunk(DataSource dataSource, DataSpec dataSpec, int trackSelectionReason,
Object trackSelectionData, Extractor extractor, Format format) {
super(dataSource, dataSpec, C.TRACK_TYPE_DEFAULT, null, trackSelectionReason,
trackSelectionData, C.TIME_UNSET, C.TIME_UNSET);
this.extractor = extractor;
this.format = format;
}
/**
* Sets the {@link HlsSampleStreamWrapper} that will receive the sample format information from
* the initialization chunk.
*
* @param output The output that will receive the format information.
*/
public void init(HlsSampleStreamWrapper output) {
extractor.init(output);
}
@Override
public long bytesLoaded() {
return bytesLoaded;
}
@Override
public void cancelLoad() {
loadCanceled = true;
}
@Override
public boolean isLoadCanceled() {
return loadCanceled;
}
@Override
public void load() throws IOException, InterruptedException {
DataSpec loadDataSpec = Util.getRemainderDataSpec(dataSpec, bytesLoaded);
try {
ExtractorInput input = new DefaultExtractorInput(dataSource,
loadDataSpec.absoluteStreamPosition, dataSource.open(loadDataSpec));
try {
int result = Extractor.RESULT_CONTINUE;
while (result == Extractor.RESULT_CONTINUE && !loadCanceled) {
result = extractor.read(input, null);
}
} finally {
bytesLoaded = (int) (input.getPosition() - dataSpec.absoluteStreamPosition);
}
} finally {
dataSource.close();
}
}
}
......@@ -19,7 +19,7 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.DefaultExtractorInput;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.ts.TimestampAdjuster;
import com.google.android.exoplayer2.extractor.TimestampAdjuster;
import com.google.android.exoplayer2.source.chunk.MediaChunk;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
......
......@@ -358,6 +358,8 @@ import java.util.LinkedList;
HlsMediaChunk mediaChunk = (HlsMediaChunk) loadable;
mediaChunk.init(this);
mediaChunks.add(mediaChunk);
} else if (loadable instanceof HlsInitializationChunk) {
((HlsInitializationChunk) loadable).init(this);
}
long elapsedRealtimeMs = loader.startLoading(loadable, this, minLoadableRetryCount);
eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, trackType, loadable.trackFormat,
......
......@@ -16,7 +16,7 @@
package com.google.android.exoplayer2.source.hls;
import android.util.SparseArray;
import com.google.android.exoplayer2.extractor.ts.TimestampAdjuster;
import com.google.android.exoplayer2.extractor.TimestampAdjuster;
/**
* Provides {@link TimestampAdjuster} instances for use during HLS playbacks.
......
......@@ -24,8 +24,8 @@ import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.SeekMap;
import com.google.android.exoplayer2.extractor.TimestampAdjuster;
import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TimestampAdjuster;
import com.google.android.exoplayer2.text.SubtitleDecoderException;
import com.google.android.exoplayer2.text.webvtt.WebvttParserUtil;
import com.google.android.exoplayer2.util.MimeTypes;
......
......@@ -38,6 +38,10 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
public final long byterangeOffset;
public final long byterangeLength;
public Segment(String uri, long byterangeOffset, long byterangeLength) {
this(uri, 0, -1, C.TIME_UNSET, false, null, null, byterangeOffset, byterangeLength);
}
public Segment(String uri, double durationSecs, int discontinuitySequenceNumber,
long startTimeUs, boolean isEncrypted, String encryptionKeyUri, String encryptionIV,
long byterangeOffset, long byterangeLength) {
......@@ -64,17 +68,19 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
public final int mediaSequence;
public final int targetDurationSecs;
public final int version;
public final Segment initializationSegment;
public final List<Segment> segments;
public final boolean live;
public final long durationUs;
public HlsMediaPlaylist(String baseUri, int mediaSequence, int targetDurationSecs, int version,
boolean live, List<Segment> segments) {
boolean live, Segment initializationSegment, List<Segment> segments) {
super(baseUri, HlsPlaylist.TYPE_MEDIA);
this.mediaSequence = mediaSequence;
this.targetDurationSecs = targetDurationSecs;
this.version = version;
this.live = live;
this.initializationSegment = initializationSegment;
this.segments = segments;
if (!segments.isEmpty()) {
......
......@@ -44,6 +44,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
private static final String TAG_MEDIA = "#EXT-X-MEDIA";
private static final String TAG_DISCONTINUITY = "#EXT-X-DISCONTINUITY";
private static final String TAG_DISCONTINUITY_SEQUENCE = "#EXT-X-DISCONTINUITY-SEQUENCE";
private static final String TAG_INIT_SEGMENT = "#EXT-X-MAP";
private static final String TAG_MEDIA_DURATION = "#EXTINF";
private static final String TAG_MEDIA_SEQUENCE = "#EXT-X-MEDIA-SEQUENCE";
private static final String TAG_TARGET_DURATION = "#EXT-X-TARGETDURATION";
......@@ -79,6 +80,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
+ ":([\\d\\.]+)\\b");
private static final Pattern REGEX_BYTERANGE = Pattern.compile(TAG_BYTERANGE
+ ":(\\d+(?:@\\d+)?)\\b");
private static final Pattern REGEX_ATTR_BYTERANGE =
Pattern.compile("BYTERANGE=\"(\\d+(?:@\\d+)?)\\b\"");
private static final Pattern REGEX_METHOD = Pattern.compile("METHOD=(" + METHOD_NONE + "|"
+ METHOD_AES128 + ")");
private static final Pattern REGEX_URI = Pattern.compile("URI=\"(.+?)\"");
......@@ -212,13 +215,14 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
int targetDurationSecs = 0;
int version = 1; // Default version == 1.
boolean live = true;
Segment initializationSegment = null;
List<Segment> segments = new ArrayList<>();
double segmentDurationSecs = 0.0;
int discontinuitySequenceNumber = 0;
long segmentStartTimeUs = 0;
long segmentByterangeOffset = 0;
long segmentByterangeLength = C.LENGTH_UNSET;
long segmentByteRangeOffset = 0;
long segmentByteRangeLength = C.LENGTH_UNSET;
int segmentMediaSequence = 0;
boolean isEncrypted = false;
......@@ -228,7 +232,20 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
String line;
while (iterator.hasNext()) {
line = iterator.next();
if (line.startsWith(TAG_TARGET_DURATION)) {
if (line.startsWith(TAG_INIT_SEGMENT)) {
String uri = parseStringAttr(line, REGEX_URI);
String byteRange = parseOptionalStringAttr(line, REGEX_ATTR_BYTERANGE);
if (byteRange != null) {
String[] splitByteRange = byteRange.split("@");
segmentByteRangeLength = Long.parseLong(splitByteRange[0]);
if (splitByteRange.length > 1) {
segmentByteRangeOffset = Long.parseLong(splitByteRange[1]);
}
}
initializationSegment = new Segment(uri, segmentByteRangeOffset, segmentByteRangeLength);
segmentByteRangeOffset = 0;
segmentByteRangeLength = C.LENGTH_UNSET;
} else if (line.startsWith(TAG_TARGET_DURATION)) {
targetDurationSecs = parseIntAttr(line, REGEX_TARGET_DURATION);
} else if (line.startsWith(TAG_MEDIA_SEQUENCE)) {
mediaSequence = parseIntAttr(line, REGEX_MEDIA_SEQUENCE);
......@@ -250,9 +267,9 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
} else if (line.startsWith(TAG_BYTERANGE)) {
String byteRange = parseStringAttr(line, REGEX_BYTERANGE);
String[] splitByteRange = byteRange.split("@");
segmentByterangeLength = Long.parseLong(splitByteRange[0]);
segmentByteRangeLength = Long.parseLong(splitByteRange[0]);
if (splitByteRange.length > 1) {
segmentByterangeOffset = Long.parseLong(splitByteRange[1]);
segmentByteRangeOffset = Long.parseLong(splitByteRange[1]);
}
} else if (line.startsWith(TAG_DISCONTINUITY_SEQUENCE)) {
discontinuitySequenceNumber = Integer.parseInt(line.substring(line.indexOf(':') + 1));
......@@ -268,24 +285,24 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
segmentEncryptionIV = Integer.toHexString(segmentMediaSequence);
}
segmentMediaSequence++;
if (segmentByterangeLength == C.LENGTH_UNSET) {
segmentByterangeOffset = 0;
if (segmentByteRangeLength == C.LENGTH_UNSET) {
segmentByteRangeOffset = 0;
}
segments.add(new Segment(line, segmentDurationSecs, discontinuitySequenceNumber,
segmentStartTimeUs, isEncrypted, encryptionKeyUri, segmentEncryptionIV,
segmentByterangeOffset, segmentByterangeLength));
segmentByteRangeOffset, segmentByteRangeLength));
segmentStartTimeUs += (long) (segmentDurationSecs * C.MICROS_PER_SECOND);
segmentDurationSecs = 0.0;
if (segmentByterangeLength != C.LENGTH_UNSET) {
segmentByterangeOffset += segmentByterangeLength;
if (segmentByteRangeLength != C.LENGTH_UNSET) {
segmentByteRangeOffset += segmentByteRangeLength;
}
segmentByterangeLength = C.LENGTH_UNSET;
segmentByteRangeLength = C.LENGTH_UNSET;
} else if (line.equals(TAG_ENDLIST)) {
live = false;
}
}
return new HlsMediaPlaylist(baseUri, mediaSequence, targetDurationSecs, version, live,
Collections.unmodifiableList(segments));
initializationSegment, Collections.unmodifiableList(segments));
}
private static String parseStringAttr(String line, Pattern pattern) throws ParserException {
......
......@@ -101,7 +101,7 @@ public class DefaultSsChunkSource implements SsChunkSource {
trackEncryptionBoxes, nalUnitLengthFieldLength, null, null);
FragmentedMp4Extractor extractor = new FragmentedMp4Extractor(
FragmentedMp4Extractor.FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME
| FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_TFDT_BOX, track);
| FragmentedMp4Extractor.FLAG_WORKAROUND_IGNORE_TFDT_BOX, track, null);
extractorWrappers[i] = new ChunkExtractorWrapper(extractor, format, false, false);
}
}
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment