Commit fd519016 by Oliver Woodman

Big HLS update. Add start of adaptive support, but leave disabled for now.

parent 6c6ba900
Showing with 283 additions and 235 deletions
...@@ -15,8 +15,6 @@ ...@@ -15,8 +15,6 @@
*/ */
package com.google.android.exoplayer.demo.full.player; package com.google.android.exoplayer.demo.full.player;
import com.google.android.exoplayer.DefaultLoadControl;
import com.google.android.exoplayer.LoadControl;
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer; import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer; import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
import com.google.android.exoplayer.TrackRenderer; import com.google.android.exoplayer.TrackRenderer;
...@@ -25,13 +23,13 @@ import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilder; ...@@ -25,13 +23,13 @@ import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilder;
import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilderCallback; import com.google.android.exoplayer.demo.full.player.DemoPlayer.RendererBuilderCallback;
import com.google.android.exoplayer.hls.HlsChunkSource; import com.google.android.exoplayer.hls.HlsChunkSource;
import com.google.android.exoplayer.hls.HlsMasterPlaylist; import com.google.android.exoplayer.hls.HlsMasterPlaylist;
import com.google.android.exoplayer.hls.HlsMasterPlaylist.Variant;
import com.google.android.exoplayer.hls.HlsMasterPlaylistParser; import com.google.android.exoplayer.hls.HlsMasterPlaylistParser;
import com.google.android.exoplayer.hls.HlsSampleSource; import com.google.android.exoplayer.hls.HlsSampleSource;
import com.google.android.exoplayer.hls.Variant;
import com.google.android.exoplayer.metadata.Id3Parser; import com.google.android.exoplayer.metadata.Id3Parser;
import com.google.android.exoplayer.metadata.MetadataTrackRenderer; import com.google.android.exoplayer.metadata.MetadataTrackRenderer;
import com.google.android.exoplayer.upstream.BufferPool;
import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer.upstream.UriDataSource; import com.google.android.exoplayer.upstream.UriDataSource;
import com.google.android.exoplayer.util.ManifestFetcher; import com.google.android.exoplayer.util.ManifestFetcher;
import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback; import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback;
...@@ -47,9 +45,6 @@ import java.util.Collections; ...@@ -47,9 +45,6 @@ import java.util.Collections;
*/ */
public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<HlsMasterPlaylist> { public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<HlsMasterPlaylist> {
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
private static final int VIDEO_BUFFER_SEGMENTS = 200;
private final String userAgent; private final String userAgent;
private final String url; private final String url;
private final String contentId; private final String contentId;
...@@ -89,12 +84,12 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls ...@@ -89,12 +84,12 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls
@Override @Override
public void onManifest(String contentId, HlsMasterPlaylist manifest) { public void onManifest(String contentId, HlsMasterPlaylist manifest) {
LoadControl loadControl = new DefaultLoadControl(new BufferPool(BUFFER_SEGMENT_SIZE)); DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
DataSource dataSource = new UriDataSource(userAgent, null); DataSource dataSource = new UriDataSource(userAgent, bandwidthMeter);
HlsChunkSource chunkSource = new HlsChunkSource(dataSource, manifest); HlsChunkSource chunkSource = new HlsChunkSource(dataSource, manifest, bandwidthMeter, null,
HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, loadControl, false);
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, 3); HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, true, 3);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource, MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, player.getMainHandler(), player, 50); MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, player.getMainHandler(), player, 50);
MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource); MediaCodecAudioTrackRenderer audioRenderer = new MediaCodecAudioTrackRenderer(sampleSource);
...@@ -111,7 +106,7 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls ...@@ -111,7 +106,7 @@ public class HlsRendererBuilder implements RendererBuilder, ManifestCallback<Hls
private HlsMasterPlaylist newSimpleMasterPlaylist(String mediaPlaylistUrl) { private HlsMasterPlaylist newSimpleMasterPlaylist(String mediaPlaylistUrl) {
return new HlsMasterPlaylist(Uri.parse(""), return new HlsMasterPlaylist(Uri.parse(""),
Collections.singletonList(new Variant(mediaPlaylistUrl, 0, null, -1, -1))); Collections.singletonList(new Variant(0, mediaPlaylistUrl, 0, null, -1, -1)));
} }
} }
...@@ -15,8 +15,6 @@ ...@@ -15,8 +15,6 @@
*/ */
package com.google.android.exoplayer.demo.simple; package com.google.android.exoplayer.demo.simple;
import com.google.android.exoplayer.DefaultLoadControl;
import com.google.android.exoplayer.LoadControl;
import com.google.android.exoplayer.MediaCodecAudioTrackRenderer; import com.google.android.exoplayer.MediaCodecAudioTrackRenderer;
import com.google.android.exoplayer.MediaCodecVideoTrackRenderer; import com.google.android.exoplayer.MediaCodecVideoTrackRenderer;
import com.google.android.exoplayer.TrackRenderer; import com.google.android.exoplayer.TrackRenderer;
...@@ -26,11 +24,11 @@ import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBui ...@@ -26,11 +24,11 @@ import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBui
import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilderCallback; import com.google.android.exoplayer.demo.simple.SimplePlayerActivity.RendererBuilderCallback;
import com.google.android.exoplayer.hls.HlsChunkSource; import com.google.android.exoplayer.hls.HlsChunkSource;
import com.google.android.exoplayer.hls.HlsMasterPlaylist; import com.google.android.exoplayer.hls.HlsMasterPlaylist;
import com.google.android.exoplayer.hls.HlsMasterPlaylist.Variant;
import com.google.android.exoplayer.hls.HlsMasterPlaylistParser; import com.google.android.exoplayer.hls.HlsMasterPlaylistParser;
import com.google.android.exoplayer.hls.HlsSampleSource; import com.google.android.exoplayer.hls.HlsSampleSource;
import com.google.android.exoplayer.upstream.BufferPool; import com.google.android.exoplayer.hls.Variant;
import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DefaultBandwidthMeter;
import com.google.android.exoplayer.upstream.UriDataSource; import com.google.android.exoplayer.upstream.UriDataSource;
import com.google.android.exoplayer.util.ManifestFetcher; import com.google.android.exoplayer.util.ManifestFetcher;
import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback; import com.google.android.exoplayer.util.ManifestFetcher.ManifestCallback;
...@@ -47,9 +45,6 @@ import java.util.Collections; ...@@ -47,9 +45,6 @@ import java.util.Collections;
/* package */ class HlsRendererBuilder implements RendererBuilder, /* package */ class HlsRendererBuilder implements RendererBuilder,
ManifestCallback<HlsMasterPlaylist> { ManifestCallback<HlsMasterPlaylist> {
private static final int BUFFER_SEGMENT_SIZE = 64 * 1024;
private static final int VIDEO_BUFFER_SEGMENTS = 200;
private final SimplePlayerActivity playerActivity; private final SimplePlayerActivity playerActivity;
private final String userAgent; private final String userAgent;
private final String url; private final String url;
...@@ -90,12 +85,11 @@ import java.util.Collections; ...@@ -90,12 +85,11 @@ import java.util.Collections;
@Override @Override
public void onManifest(String contentId, HlsMasterPlaylist manifest) { public void onManifest(String contentId, HlsMasterPlaylist manifest) {
LoadControl loadControl = new DefaultLoadControl(new BufferPool(BUFFER_SEGMENT_SIZE)); DefaultBandwidthMeter bandwidthMeter = new DefaultBandwidthMeter();
DataSource dataSource = new UriDataSource(userAgent, bandwidthMeter);
DataSource dataSource = new UriDataSource(userAgent, null); HlsChunkSource chunkSource = new HlsChunkSource(dataSource, manifest, bandwidthMeter, null,
HlsChunkSource chunkSource = new HlsChunkSource(dataSource, manifest); false);
HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, loadControl, HlsSampleSource sampleSource = new HlsSampleSource(chunkSource, true, 2);
VIDEO_BUFFER_SEGMENTS * BUFFER_SEGMENT_SIZE, true, 2);
MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource, MediaCodecVideoTrackRenderer videoRenderer = new MediaCodecVideoTrackRenderer(sampleSource,
MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, playerActivity.getMainHandler(), MediaCodec.VIDEO_SCALING_MODE_SCALE_TO_FIT, 0, playerActivity.getMainHandler(),
playerActivity, 50); playerActivity, 50);
...@@ -109,7 +103,7 @@ import java.util.Collections; ...@@ -109,7 +103,7 @@ import java.util.Collections;
private HlsMasterPlaylist newSimpleMasterPlaylist(String mediaPlaylistUrl) { private HlsMasterPlaylist newSimpleMasterPlaylist(String mediaPlaylistUrl) {
return new HlsMasterPlaylist(Uri.parse(""), return new HlsMasterPlaylist(Uri.parse(""),
Collections.singletonList(new Variant(mediaPlaylistUrl, 0, null, -1, -1))); Collections.singletonList(new Variant(0, mediaPlaylistUrl, 0, null, -1, -1)));
} }
} }
/*
* Copyright (C) 2014 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.exoplayer.hls;
import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSpec;
import com.google.android.exoplayer.util.BitArray;
import java.io.IOException;
/**
* An abstract base class for {@link HlsChunk} implementations where the data should be loaded into
* a {@link BitArray} and subsequently consumed.
*/
public abstract class BitArrayChunk extends HlsChunk {
private static final int READ_GRANULARITY = 16 * 1024;
private final BitArray bitArray;
private volatile boolean loadFinished;
private volatile boolean loadCanceled;
/**
* @param dataSource The source from which the data should be loaded.
* @param dataSpec Defines the data to be loaded. {@code dataSpec.length} must not exceed
* {@link Integer#MAX_VALUE}. If {@code dataSpec.length == C.LENGTH_UNBOUNDED} then
* the length resolved by {@code dataSource.open(dataSpec)} must not exceed
* {@link Integer#MAX_VALUE}.
* @param bitArray The {@link BitArray} into which the data should be loaded.
*/
public BitArrayChunk(DataSource dataSource, DataSpec dataSpec, BitArray bitArray) {
super(dataSource, dataSpec);
this.bitArray = bitArray;
}
@Override
public void consume() throws IOException {
consume(bitArray);
}
/**
* Invoked by {@link #consume()}. Implementations should override this method to consume the
* loaded data.
*
* @param bitArray The {@link BitArray} containing the loaded data.
* @throws IOException If an error occurs consuming the loaded data.
*/
protected abstract void consume(BitArray bitArray) throws IOException;
/**
* Whether the whole of the chunk has been loaded.
*
* @return True if the whole of the chunk has been loaded. False otherwise.
*/
@Override
public boolean isLoadFinished() {
return loadFinished;
}
// Loadable implementation
@Override
public final void cancelLoad() {
loadCanceled = true;
}
@Override
public final boolean isLoadCanceled() {
return loadCanceled;
}
@Override
public final void load() throws IOException, InterruptedException {
try {
bitArray.reset();
dataSource.open(dataSpec);
int bytesRead = 0;
while (bytesRead != -1 && !loadCanceled) {
bytesRead = bitArray.append(dataSource, READ_GRANULARITY);
}
loadFinished = !loadCanceled;
} finally {
dataSource.close();
}
}
}
...@@ -15,38 +15,21 @@ ...@@ -15,38 +15,21 @@
*/ */
package com.google.android.exoplayer.hls; package com.google.android.exoplayer.hls;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.upstream.Allocation;
import com.google.android.exoplayer.upstream.Allocator;
import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSourceStream;
import com.google.android.exoplayer.upstream.DataSpec; import com.google.android.exoplayer.upstream.DataSpec;
import com.google.android.exoplayer.upstream.Loader.Loadable; import com.google.android.exoplayer.upstream.Loader.Loadable;
import com.google.android.exoplayer.upstream.NonBlockingInputStream;
import com.google.android.exoplayer.util.Assertions; import com.google.android.exoplayer.util.Assertions;
import java.io.IOException; import java.io.IOException;
/** /**
* An abstract base class for {@link Loadable} implementations that load chunks of data required * An abstract base class for {@link Loadable} implementations that load chunks of data required
* for the playback of streams. * for the playback of HLS streams.
* <p>
* TODO: Figure out whether this should merge with the chunk package, or whether the hls
* implementation is going to naturally diverge.
*/ */
public abstract class HlsChunk implements Loadable { public abstract class HlsChunk implements Loadable {
/** protected final DataSource dataSource;
* The reason for a {@link HlsChunkSource} having generated this chunk. For reporting only. protected final DataSpec dataSpec;
* Possible values for this variable are defined by the specific {@link HlsChunkSource}
* implementations.
*/
public final int trigger;
private final DataSource dataSource;
private final DataSpec dataSpec;
private DataSourceStream dataSourceStream;
/** /**
* @param dataSource The source from which the data should be loaded. * @param dataSource The source from which the data should be loaded.
...@@ -54,123 +37,15 @@ public abstract class HlsChunk implements Loadable { ...@@ -54,123 +37,15 @@ public abstract class HlsChunk implements Loadable {
* {@link Integer#MAX_VALUE}. If {@code dataSpec.length == C.LENGTH_UNBOUNDED} then * {@link Integer#MAX_VALUE}. If {@code dataSpec.length == C.LENGTH_UNBOUNDED} then
* the length resolved by {@code dataSource.open(dataSpec)} must not exceed * the length resolved by {@code dataSource.open(dataSpec)} must not exceed
* {@link Integer#MAX_VALUE}. * {@link Integer#MAX_VALUE}.
* @param trigger See {@link #trigger}.
*/ */
public HlsChunk(DataSource dataSource, DataSpec dataSpec, int trigger) { public HlsChunk(DataSource dataSource, DataSpec dataSpec) {
Assertions.checkState(dataSpec.length <= Integer.MAX_VALUE); Assertions.checkState(dataSpec.length <= Integer.MAX_VALUE);
this.dataSource = Assertions.checkNotNull(dataSource); this.dataSource = Assertions.checkNotNull(dataSource);
this.dataSpec = Assertions.checkNotNull(dataSpec); this.dataSpec = Assertions.checkNotNull(dataSpec);
this.trigger = trigger;
}
/**
* Initializes the {@link HlsChunk}.
*
* @param allocator An {@link Allocator} from which the {@link Allocation} needed to contain the
* data can be obtained.
*/
public final void init(Allocator allocator) {
Assertions.checkState(dataSourceStream == null);
dataSourceStream = new DataSourceStream(dataSource, dataSpec, allocator);
}
/**
* Releases the {@link HlsChunk}, releasing any backing {@link Allocation}s.
*/
public final void release() {
if (dataSourceStream != null) {
dataSourceStream.close();
dataSourceStream = null;
}
} }
/** public abstract void consume() throws IOException;
* Gets the length of the chunk in bytes.
*
* @return The length of the chunk in bytes, or {@link C#LENGTH_UNBOUNDED} if the length has yet
* to be determined.
*/
public final long getLength() {
return dataSourceStream.getLength();
}
/**
* Whether the whole of the data has been consumed.
*
* @return True if the whole of the data has been consumed. False otherwise.
*/
public final boolean isReadFinished() {
return dataSourceStream.isEndOfStream();
}
/**
* Whether the whole of the chunk has been loaded.
*
* @return True if the whole of the chunk has been loaded. False otherwise.
*/
public final boolean isLoadFinished() {
return dataSourceStream.isLoadFinished();
}
/** public abstract boolean isLoadFinished();
* Gets the number of bytes that have been loaded.
*
* @return The number of bytes that have been loaded.
*/
public final long bytesLoaded() {
return dataSourceStream.getLoadPosition();
}
/**
* Causes loaded data to be consumed.
*
* @throws IOException If an error occurs consuming the loaded data.
*/
public final void consume() throws IOException {
Assertions.checkState(dataSourceStream != null);
consumeStream(dataSourceStream);
}
/**
* Invoked by {@link #consume()}. Implementations may override this method if they wish to
* consume the loaded data at this point.
* <p>
* The default implementation is a no-op.
*
* @param stream The stream of loaded data.
* @throws IOException If an error occurs consuming the loaded data.
*/
protected void consumeStream(NonBlockingInputStream stream) throws IOException {
// Do nothing.
}
protected final NonBlockingInputStream getNonBlockingInputStream() {
return dataSourceStream;
}
protected final void resetReadPosition() {
if (dataSourceStream != null) {
dataSourceStream.resetReadPosition();
} else {
// We haven't been initialized yet, so the read position must already be 0.
}
}
// Loadable implementation
@Override
public final void cancelLoad() {
dataSourceStream.cancelLoad();
}
@Override
public final boolean isLoadCanceled() {
return dataSourceStream.isLoadCanceled();
}
@Override
public final void load() throws IOException, InterruptedException {
dataSourceStream.load();
}
} }
...@@ -24,25 +24,6 @@ import java.util.List; ...@@ -24,25 +24,6 @@ import java.util.List;
*/ */
public final class HlsMasterPlaylist { public final class HlsMasterPlaylist {
/**
* Variant stream reference.
*/
public static final class Variant {
public final int bandwidth;
public final String url;
public final String[] codecs;
public final int width;
public final int height;
public Variant(String url, int bandwidth, String[] codecs, int width, int height) {
this.bandwidth = bandwidth;
this.url = url;
this.codecs = codecs;
this.width = width;
this.height = height;
}
}
public final Uri baseUri; public final Uri baseUri;
public final List<Variant> variants; public final List<Variant> variants;
......
...@@ -15,7 +15,6 @@ ...@@ -15,7 +15,6 @@
*/ */
package com.google.android.exoplayer.hls; package com.google.android.exoplayer.hls;
import com.google.android.exoplayer.hls.HlsMasterPlaylist.Variant;
import com.google.android.exoplayer.util.ManifestParser; import com.google.android.exoplayer.util.ManifestParser;
import android.net.Uri; import android.net.Uri;
...@@ -61,6 +60,7 @@ public final class HlsMasterPlaylistParser implements ManifestParser<HlsMasterPl ...@@ -61,6 +60,7 @@ public final class HlsMasterPlaylistParser implements ManifestParser<HlsMasterPl
String[] codecs = null; String[] codecs = null;
int width = -1; int width = -1;
int height = -1; int height = -1;
int variantIndex = 0;
String line; String line;
while ((line = reader.readLine()) != null) { while ((line = reader.readLine()) != null) {
...@@ -70,15 +70,14 @@ public final class HlsMasterPlaylistParser implements ManifestParser<HlsMasterPl ...@@ -70,15 +70,14 @@ public final class HlsMasterPlaylistParser implements ManifestParser<HlsMasterPl
} }
if (line.startsWith(STREAM_INF_TAG)) { if (line.startsWith(STREAM_INF_TAG)) {
bandwidth = HlsParserUtil.parseIntAttr(line, BANDWIDTH_ATTR_REGEX, BANDWIDTH_ATTR); bandwidth = HlsParserUtil.parseIntAttr(line, BANDWIDTH_ATTR_REGEX, BANDWIDTH_ATTR);
String codecsString = HlsParserUtil.parseOptionalStringAttr(line, CODECS_ATTR_REGEX, String codecsString = HlsParserUtil.parseOptionalStringAttr(line, CODECS_ATTR_REGEX);
CODECS_ATTR);
if (codecsString != null) { if (codecsString != null) {
codecs = codecsString.split("(\\s*,\\s*)|(\\s*$)"); codecs = codecsString.split("(\\s*,\\s*)|(\\s*$)");
} else { } else {
codecs = null; codecs = null;
} }
String resolutionString = HlsParserUtil.parseOptionalStringAttr(line, RESOLUTION_ATTR_REGEX, String resolutionString = HlsParserUtil.parseOptionalStringAttr(line,
RESOLUTION_ATTR); RESOLUTION_ATTR_REGEX);
if (resolutionString != null) { if (resolutionString != null) {
String[] widthAndHeight = resolutionString.split("x"); String[] widthAndHeight = resolutionString.split("x");
width = Integer.parseInt(widthAndHeight[0]); width = Integer.parseInt(widthAndHeight[0]);
...@@ -88,7 +87,7 @@ public final class HlsMasterPlaylistParser implements ManifestParser<HlsMasterPl ...@@ -88,7 +87,7 @@ public final class HlsMasterPlaylistParser implements ManifestParser<HlsMasterPl
height = -1; height = -1;
} }
} else if (!line.startsWith("#")) { } else if (!line.startsWith("#")) {
variants.add(new Variant(line, bandwidth, codecs, width, height)); variants.add(new Variant(variantIndex++, line, bandwidth, codecs, width, height));
bandwidth = 0; bandwidth = 0;
codecs = null; codecs = null;
width = -1; width = -1;
......
...@@ -114,8 +114,7 @@ public final class HlsMediaPlaylistParser implements ManifestParser<HlsMediaPlay ...@@ -114,8 +114,7 @@ public final class HlsMediaPlaylistParser implements ManifestParser<HlsMediaPlay
} else { } else {
segmentEncryptionKeyUri = HlsParserUtil.parseStringAttr(line, URI_ATTR_REGEX, segmentEncryptionKeyUri = HlsParserUtil.parseStringAttr(line, URI_ATTR_REGEX,
URI_ATTR); URI_ATTR);
segmentEncryptionIV = HlsParserUtil.parseOptionalStringAttr(line, IV_ATTR_REGEX, segmentEncryptionIV = HlsParserUtil.parseOptionalStringAttr(line, IV_ATTR_REGEX);
IV_ATTR);
if (segmentEncryptionIV == null) { if (segmentEncryptionIV == null) {
segmentEncryptionIV = Integer.toHexString(segmentMediaSequence); segmentEncryptionIV = Integer.toHexString(segmentMediaSequence);
} }
......
...@@ -36,7 +36,7 @@ import java.util.regex.Pattern; ...@@ -36,7 +36,7 @@ import java.util.regex.Pattern;
throw new ParserException(String.format("Couldn't match %s tag in %s", tag, line)); throw new ParserException(String.format("Couldn't match %s tag in %s", tag, line));
} }
public static String parseOptionalStringAttr(String line, Pattern pattern, String tag) { public static String parseOptionalStringAttr(String line, Pattern pattern) {
Matcher matcher = pattern.matcher(line); Matcher matcher = pattern.matcher(line);
if (matcher.find() && matcher.groupCount() == 1) { if (matcher.find() && matcher.groupCount() == 1) {
return matcher.group(1); return matcher.group(1);
......
...@@ -15,9 +15,12 @@ ...@@ -15,9 +15,12 @@
*/ */
package com.google.android.exoplayer.hls; package com.google.android.exoplayer.hls;
import com.google.android.exoplayer.C;
import com.google.android.exoplayer.upstream.DataSource; import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.upstream.DataSpec; import com.google.android.exoplayer.upstream.DataSpec;
import java.io.IOException;
/** /**
* A MPEG2TS chunk. * A MPEG2TS chunk.
*/ */
...@@ -40,40 +43,87 @@ public final class TsChunk extends HlsChunk { ...@@ -40,40 +43,87 @@ public final class TsChunk extends HlsChunk {
*/ */
public final int nextChunkIndex; public final int nextChunkIndex;
/** /**
* The encoding discontinuity indicator. * True if this is the final chunk being loaded for the current variant, as we splice to another
* one. False otherwise.
*/ */
public final boolean discontinuity; public final boolean splicingOut;
/** /**
* For each track, whether samples from the first keyframe (inclusive) should be discarded. * The extractor into which this chunk is being consumed.
*/ */
public final boolean discardFromFirstKeyframes; public final TsExtractor extractor;
private volatile int loadPosition;
private volatile boolean loadFinished;
private volatile boolean loadCanceled;
/** /**
* @param dataSource A {@link DataSource} for loading the data. * @param dataSource A {@link DataSource} for loading the data.
* @param dataSpec Defines the data to be loaded. * @param dataSpec Defines the data to be loaded.
* @param trigger The reason for this chunk being selected.
* @param variantIndex The index of the variant in the master playlist. * @param variantIndex The index of the variant in the master playlist.
* @param startTimeUs The start time of the media contained by the chunk, in microseconds. * @param startTimeUs The start time of the media contained by the chunk, in microseconds.
* @param endTimeUs The end time of the media contained by the chunk, in microseconds. * @param endTimeUs The end time of the media contained by the chunk, in microseconds.
* @param nextChunkIndex The index of the next chunk, or -1 if this is the last chunk. * @param nextChunkIndex The index of the next chunk, or -1 if this is the last chunk.
* @param discontinuity The encoding discontinuity indicator. * @param splicingOut True if this is the final chunk being loaded for the current variant, as we
* @param discardFromFirstKeyframes For each contained media stream, whether samples from the * splice to another one. False otherwise.
* first keyframe (inclusive) should be discarded.
*/ */
public TsChunk(DataSource dataSource, DataSpec dataSpec, int trigger, int variantIndex, public TsChunk(DataSource dataSource, DataSpec dataSpec, TsExtractor tsExtractor,
long startTimeUs, long endTimeUs, int nextChunkIndex, boolean discontinuity, int variantIndex, long startTimeUs, long endTimeUs, int nextChunkIndex, boolean splicingOut) {
boolean discardFromFirstKeyframes) { super(dataSource, dataSpec);
super(dataSource, dataSpec, trigger); this.extractor = tsExtractor;
this.variantIndex = variantIndex; this.variantIndex = variantIndex;
this.startTimeUs = startTimeUs; this.startTimeUs = startTimeUs;
this.endTimeUs = endTimeUs; this.endTimeUs = endTimeUs;
this.nextChunkIndex = nextChunkIndex; this.nextChunkIndex = nextChunkIndex;
this.discontinuity = discontinuity; this.splicingOut = splicingOut;
this.discardFromFirstKeyframes = discardFromFirstKeyframes; }
@Override
public void consume() throws IOException {
// Do nothing.
} }
public boolean isLastChunk() { public boolean isLastChunk() {
return nextChunkIndex == -1; return nextChunkIndex == -1;
} }
@Override
public boolean isLoadFinished() {
return loadFinished;
}
// Loadable implementation
@Override
public void cancelLoad() {
loadCanceled = true;
}
@Override
public boolean isLoadCanceled() {
return loadCanceled;
}
@Override
public void load() throws IOException, InterruptedException {
DataSpec loadDataSpec;
if (loadPosition == 0) {
loadDataSpec = dataSpec;
} else {
long remainingLength = dataSpec.length != C.LENGTH_UNBOUNDED
? dataSpec.length - loadPosition : C.LENGTH_UNBOUNDED;
loadDataSpec = new DataSpec(dataSpec.uri, dataSpec.position + loadPosition,
remainingLength, dataSpec.key);
}
try {
dataSource.open(loadDataSpec);
int bytesRead = 0;
while (bytesRead != -1 && !loadCanceled) {
bytesRead = extractor.read(dataSource);
}
loadFinished = !loadCanceled;
} finally {
dataSource.close();
}
}
} }
...@@ -15,23 +15,42 @@ ...@@ -15,23 +15,42 @@
*/ */
package com.google.android.exoplayer.hls; package com.google.android.exoplayer.hls;
import java.util.Comparator;
/** /**
* Holds a hls chunk operation, which consists of a {@link HlsChunk} to load together with the * Variant stream reference.
* number of {@link TsChunk}s that should be retained on the queue.
* <p>
* TODO: Figure out whether this should merge with the chunk package, or whether the hls
* implementation is going to naturally diverge.
*/ */
public final class HlsChunkOperationHolder { public final class Variant {
/** /**
* The number of {@link TsChunk}s to retain in a queue. * Sorts {@link Variant} objects in order of decreasing bandwidth.
* <p>
* When two {@link Variant}s have the same bandwidth, the one with the lowest index comes first.
*/ */
public int queueSize; public static final class DecreasingBandwidthComparator implements Comparator<Variant> {
/** @Override
* The chunk. public int compare(Variant a, Variant b) {
*/ int bandwidthDifference = b.bandwidth - a.bandwidth;
public HlsChunk chunk; return bandwidthDifference != 0 ? bandwidthDifference : a.index - b.index;
}
}
public final int index;
public final int bandwidth;
public final String url;
public final String[] codecs;
public final int width;
public final int height;
public Variant(int index, String url, int bandwidth, String[] codecs, int width, int height) {
this.index = index;
this.bandwidth = bandwidth;
this.url = url;
this.codecs = codecs;
this.width = width;
this.height = height;
}
} }
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
package com.google.android.exoplayer.metadata; package com.google.android.exoplayer.metadata;
import com.google.android.exoplayer.ParserException; import com.google.android.exoplayer.ParserException;
import com.google.android.exoplayer.parser.ts.BitsArray; import com.google.android.exoplayer.util.BitArray;
import com.google.android.exoplayer.util.MimeTypes; import com.google.android.exoplayer.util.MimeTypes;
import java.io.UnsupportedEncodingException; import java.io.UnsupportedEncodingException;
...@@ -37,7 +37,7 @@ public class Id3Parser implements MetadataParser { ...@@ -37,7 +37,7 @@ public class Id3Parser implements MetadataParser {
@Override @Override
public Map<String, Object> parse(byte[] data, int size) public Map<String, Object> parse(byte[] data, int size)
throws UnsupportedEncodingException, ParserException { throws UnsupportedEncodingException, ParserException {
BitsArray id3Buffer = new BitsArray(data, size); BitArray id3Buffer = new BitArray(data, size);
int id3Size = parseId3Header(id3Buffer); int id3Size = parseId3Header(id3Buffer);
Map<String, Object> metadata = new HashMap<String, Object>(); Map<String, Object> metadata = new HashMap<String, Object>();
...@@ -102,11 +102,11 @@ public class Id3Parser implements MetadataParser { ...@@ -102,11 +102,11 @@ public class Id3Parser implements MetadataParser {
/** /**
* Parses ID3 header. * Parses ID3 header.
* @param id3Buffer A {@link BitsArray} with raw ID3 data. * @param id3Buffer A {@link BitArray} with raw ID3 data.
* @return The size of data that contains ID3 frames without header and footer. * @return The size of data that contains ID3 frames without header and footer.
* @throws ParserException If ID3 file identifier != "ID3". * @throws ParserException If ID3 file identifier != "ID3".
*/ */
private static int parseId3Header(BitsArray id3Buffer) throws ParserException { private static int parseId3Header(BitArray id3Buffer) throws ParserException {
int id1 = id3Buffer.readUnsignedByte(); int id1 = id3Buffer.readUnsignedByte();
int id2 = id3Buffer.readUnsignedByte(); int id2 = id3Buffer.readUnsignedByte();
int id3 = id3Buffer.readUnsignedByte(); int id3 = id3Buffer.readUnsignedByte();
......
...@@ -19,7 +19,7 @@ import java.io.IOException; ...@@ -19,7 +19,7 @@ import java.io.IOException;
import java.util.Map; import java.util.Map;
/** /**
* Parses {@link Metadata}s from binary data. * Parses metadata objects from binary data.
*/ */
public interface MetadataParser { public interface MetadataParser {
......
...@@ -13,15 +13,16 @@ ...@@ -13,15 +13,16 @@
* See the License for the specific language governing permissions and * See the License for the specific language governing permissions and
* limitations under the License. * limitations under the License.
*/ */
package com.google.android.exoplayer.parser.ts; package com.google.android.exoplayer.util;
import com.google.android.exoplayer.upstream.NonBlockingInputStream; import com.google.android.exoplayer.upstream.DataSource;
import com.google.android.exoplayer.util.Assertions;
import java.io.IOException;
/** /**
* Wraps a byte array, providing methods that allow it to be read as a bitstream. * Wraps a byte array, providing methods that allow it to be read as a bitstream.
*/ */
public final class BitsArray { public final class BitArray {
private byte[] data; private byte[] data;
...@@ -33,16 +34,16 @@ public final class BitsArray { ...@@ -33,16 +34,16 @@ public final class BitsArray {
private int byteOffset; private int byteOffset;
private int bitOffset; private int bitOffset;
public BitsArray() { public BitArray() {
} }
public BitsArray(byte[] data, int limit) { public BitArray(byte[] data, int limit) {
this.data = data; this.data = data;
this.limit = limit; this.limit = limit;
} }
/** /**
* Resets the state. * Clears all data, setting the offset and limit to zero.
*/ */
public void reset() { public void reset() {
byteOffset = 0; byteOffset = 0;
...@@ -51,6 +52,28 @@ public final class BitsArray { ...@@ -51,6 +52,28 @@ public final class BitsArray {
} }
/** /**
* Resets to wrap the specified data, setting the offset to zero.
*
* @param data The data to wrap.
* @param limit The limit to set.
*/
public void reset(byte[] data, int limit) {
this.data = data;
this.limit = limit;
byteOffset = 0;
bitOffset = 0;
}
/**
* Gets the backing byte array.
*
* @return The backing byte array.
*/
public byte[] getData() {
return data;
}
/**
* Gets the current byte offset. * Gets the current byte offset.
* *
* @return The current byte offset. * @return The current byte offset.
...@@ -69,16 +92,16 @@ public final class BitsArray { ...@@ -69,16 +92,16 @@ public final class BitsArray {
} }
/** /**
* Appends data from a {@link NonBlockingInputStream}. * Appends data from a {@link DataSource}.
* *
* @param inputStream The {@link NonBlockingInputStream} whose data should be appended. * @param dataSource The {@link DataSource} from which to read.
* @param length The maximum number of bytes to read and append. * @param length The maximum number of bytes to read and append.
* @return The number of bytes that were read and appended. May be 0 if no data was available * @return The number of bytes that were read and appended, or -1 if no more data is available.
* from the stream. -1 is returned if the end of the stream has been reached. * @throws IOException If an error occurs reading from the source.
*/ */
public int append(NonBlockingInputStream inputStream, int length) { public int append(DataSource dataSource, int length) throws IOException {
expand(length); expand(length);
int bytesRead = inputStream.read(data, limit, length); int bytesRead = dataSource.read(data, limit, length);
if (bytesRead == -1) { if (bytesRead == -1) {
return -1; return -1;
} }
...@@ -87,12 +110,12 @@ public final class BitsArray { ...@@ -87,12 +110,12 @@ public final class BitsArray {
} }
/** /**
* Appends data from another {@link BitsArray}. * Appends data from another {@link BitArray}.
* *
* @param bitsArray The {@link BitsArray} whose data should be appended. * @param bitsArray The {@link BitArray} whose data should be appended.
* @param length The number of bytes to read and append. * @param length The number of bytes to read and append.
*/ */
public void append(BitsArray bitsArray, int length) { public void append(BitArray bitsArray, int length) {
expand(length); expand(length);
bitsArray.readBytes(data, limit, length); bitsArray.readBytes(data, limit, length);
limit += length; limit += length;
...@@ -257,6 +280,19 @@ public final class BitsArray { ...@@ -257,6 +280,19 @@ public final class BitsArray {
} }
/** /**
* Reads an Exp-Golomb-coded format integer.
*
* @return The value of the parsed Exp-Golomb-coded integer.
*/
public int readExpGolombCodedInt() {
int leadingZeros = 0;
while (!readBit()) {
leadingZeros++;
}
return (1 << leadingZeros) - 1 + (leadingZeros > 0 ? readBits(leadingZeros) : 0);
}
/**
* Reads a Synchsafe integer. * Reads a Synchsafe integer.
* Synchsafe integers are integers that keep the highest bit of every byte zeroed. * Synchsafe integers are integers that keep the highest bit of every byte zeroed.
* A 32 bit synchsafe integer can store 28 bits of information. * A 32 bit synchsafe integer can store 28 bits of information.
...@@ -293,7 +329,7 @@ public final class BitsArray { ...@@ -293,7 +329,7 @@ public final class BitsArray {
/** /**
* Finds the next NAL unit. * Finds the next NAL unit.
* *
* @param nalUnitType The type of the NAL unit to search for. * @param nalUnitType The type of the NAL unit to search for, or -1 for any NAL unit.
* @param offset The additional offset in the data to start the search from. * @param offset The additional offset in the data to start the search from.
* @return The offset from the current position to the start of the NAL unit. If a NAL unit is * @return The offset from the current position to the start of the NAL unit. If a NAL unit is
* not found, then the offset to the end of the data is returned. * not found, then the offset to the end of the data is returned.
...@@ -302,7 +338,7 @@ public final class BitsArray { ...@@ -302,7 +338,7 @@ public final class BitsArray {
for (int i = byteOffset + offset; i < limit - 3; i++) { for (int i = byteOffset + offset; i < limit - 3; i++) {
// Check for NAL unit start code prefix == 0x000001. // Check for NAL unit start code prefix == 0x000001.
if ((data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1) if ((data[i] == 0 && data[i + 1] == 0 && data[i + 2] == 1)
&& (nalUnitType == (data[i + 3] & 0x1F))) { && (nalUnitType == -1 || (nalUnitType == (data[i + 3] & 0x1F)))) {
return i - byteOffset; return i - byteOffset;
} }
} }
......
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