Commit f94218a7 by ojw28 Committed by GitHub

Merge pull request #1905 from google/dev-v2

r2.0.2
parents f8a8302f 6c12ec62
Showing with 693 additions and 246 deletions
...@@ -20,8 +20,6 @@ and extend, and can be updated through Play Store application updates. ...@@ -20,8 +20,6 @@ and extend, and can be updated through Play Store application updates.
## Using ExoPlayer ## ## Using ExoPlayer ##
#### Via jCenter ####
The easiest way to get started using ExoPlayer is to add it as a gradle The easiest way to get started using ExoPlayer is to add it as a gradle
dependency. You need to make sure you have the jcenter repository included in dependency. You need to make sure you have the jcenter repository included in
the `build.gradle` file in the root of your project: the `build.gradle` file in the root of your project:
...@@ -44,31 +42,6 @@ project's [Releases][]. For more details, see the project on [Bintray][]. ...@@ -44,31 +42,6 @@ project's [Releases][]. For more details, see the project on [Bintray][].
[Releases]: https://github.com/google/ExoPlayer/releases [Releases]: https://github.com/google/ExoPlayer/releases
[Bintray]: https://bintray.com/google/exoplayer/exoplayer/view [Bintray]: https://bintray.com/google/exoplayer/exoplayer/view
#### As source ####
ExoPlayer can also be built from source using Gradle. You can include it as a
dependent project like so:
```gradle
// settings.gradle
include ':app', ':..:ExoPlayer:library'
// app/build.gradle
dependencies {
compile project(':..:ExoPlayer:library')
}
```
#### As a jar ####
If you want to use ExoPlayer as a jar, run:
```sh
./gradlew jarRelease
```
and copy `library.jar` to the libs folder of your new project.
## Developing ExoPlayer ## ## Developing ExoPlayer ##
#### Project branches #### #### Project branches ####
......
# Release notes # # Release notes #
### r2.0.2 ###
* Fixes for MergingMediaSource and sideloaded subtitles.
([#1882](https://github.com/google/ExoPlayer/issues/1882),
[#1854](https://github.com/google/ExoPlayer/issues/1854),
[#1900](https://github.com/google/ExoPlayer/issues/1900)).
* Reduced effect of application code leaking player references
([#1855](https://github.com/google/ExoPlayer/issues/1855)).
* Initial support for fragmented MP4 in HLS.
* Misc bug fixes and minor features.
### r2.0.1 ### ### r2.0.1 ###
* Fix playback of short duration content * Fix playback of short duration content
......
...@@ -16,7 +16,6 @@ ...@@ -16,7 +16,6 @@
buildscript { buildscript {
repositories { repositories {
mavenCentral()
jcenter() jcenter()
} }
dependencies { dependencies {
...@@ -27,11 +26,16 @@ buildscript { ...@@ -27,11 +26,16 @@ buildscript {
allprojects { allprojects {
repositories { repositories {
mavenCentral() jcenter()
} }
project.ext { project.ext {
compileSdkVersion=24 compileSdkVersion=24
targetSdkVersion=24 targetSdkVersion=24
buildToolsVersion='23.0.3' buildToolsVersion='23.0.3'
releaseRepoName = 'exoplayer'
releaseUserOrg = 'google'
releaseGroupId = 'com.google.android.exoplayer'
releaseVersion = 'r2.0.2'
releaseWebsite = 'https://github.com/google/ExoPlayer'
} }
} }
...@@ -16,8 +16,8 @@ ...@@ -16,8 +16,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="com.google.android.exoplayer2.demo" package="com.google.android.exoplayer2.demo"
android:versionCode="2001" android:versionCode="2002"
android:versionName="2.0.1"> android:versionName="2.0.2">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/> <uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
......
...@@ -303,10 +303,14 @@ ...@@ -303,10 +303,14 @@
"uri": "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8" "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" "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", "name": "Apple TS media playlist",
"uri": "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear1/prog_index.m3u8" "uri": "https://devimages.apple.com.edgekey.net/streaming/examples/bipbop_4x3/gear1/prog_index.m3u8"
}, },
......
...@@ -2,8 +2,29 @@ ...@@ -2,8 +2,29 @@
## Description ## ## Description ##
The OkHttp Extension is an [HttpDataSource][] implementation using Square's [OkHttp][]. The OkHttp Extension is an [HttpDataSource][] implementation using Square's
[OkHttp][].
## Using the extension ##
The easiest way to use the extension is to add it as a gradle dependency. You
need to make sure you have the jcenter repository included in the `build.gradle`
file in the root of your project:
```gradle
repositories {
jcenter()
}
```
Next, include the following in your module's `build.gradle` file:
```gradle
compile 'com.google.android.exoplayer:extension-okhttp:rX.X.X'
```
where `rX.X.X` is the version, which must match the version of the ExoPlayer
library being used.
[HttpDataSource]: https://google.github.io/ExoPlayer/doc/reference/com/google/android/exoplayer2/upstream/HttpDataSource.html [HttpDataSource]: https://google.github.io/ExoPlayer/doc/reference/com/google/android/exoplayer2/upstream/HttpDataSource.html
[OkHttp]: https://square.github.io/okhttp/ [OkHttp]: https://square.github.io/okhttp/
...@@ -12,6 +12,7 @@ ...@@ -12,6 +12,7 @@
// 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.
apply plugin: 'com.android.library' apply plugin: 'com.android.library'
apply plugin: 'bintray-release'
android { android {
compileSdkVersion project.ext.compileSdkVersion compileSdkVersion project.ext.compileSdkVersion
...@@ -40,3 +41,13 @@ dependencies { ...@@ -40,3 +41,13 @@ dependencies {
exclude group: 'org.json' exclude group: 'org.json'
} }
} }
publish {
artifactId = 'extension-okhttp'
description = 'An OkHttp extension for ExoPlayer.'
repoName = releaseRepoName
userOrg = releaseUserOrg
groupId = releaseGroupId
version = releaseVersion
website = releaseWebsite
}
...@@ -338,17 +338,21 @@ public class OkHttpDataSource implements HttpDataSource { ...@@ -338,17 +338,21 @@ public class OkHttpDataSource implements HttpDataSource {
* @throws IOException If an error occurs reading from the source. * @throws IOException If an error occurs reading from the source.
*/ */
private int readInternal(byte[] buffer, int offset, int readLength) throws IOException { private int readInternal(byte[] buffer, int offset, int readLength) throws IOException {
readLength = bytesToRead == C.LENGTH_UNSET ? readLength
: (int) Math.min(readLength, bytesToRead - bytesRead);
if (readLength == 0) { if (readLength == 0) {
// We've read all of the requested data. return 0;
return C.RESULT_END_OF_INPUT; }
if (bytesToRead != C.LENGTH_UNSET) {
long bytesRemaining = bytesToRead - bytesRead;
if (bytesRemaining == 0) {
return C.RESULT_END_OF_INPUT;
}
readLength = (int) Math.min(readLength, bytesRemaining);
} }
int read = responseByteStream.read(buffer, offset, readLength); int read = responseByteStream.read(buffer, offset, readLength);
if (read == -1) { if (read == -1) {
if (bytesToRead != C.LENGTH_UNSET && bytesToRead != bytesRead) { if (bytesToRead != C.LENGTH_UNSET) {
// The server closed the connection having not sent sufficient data. // End of stream reached having not read sufficient data.
throw new EOFException(); throw new EOFException();
} }
return C.RESULT_END_OF_INPUT; return C.RESULT_END_OF_INPUT;
......
...@@ -92,11 +92,11 @@ android.libraryVariants.all { variant -> ...@@ -92,11 +92,11 @@ android.libraryVariants.all { variant ->
} }
publish { publish {
repoName = 'exoplayer'
userOrg = 'google'
groupId = 'com.google.android.exoplayer'
artifactId = 'exoplayer' artifactId = 'exoplayer'
version = 'r2.0.1'
description = 'The ExoPlayer library.' description = 'The ExoPlayer library.'
website = 'https://github.com/google/ExoPlayer' repoName = releaseRepoName
userOrg = releaseUserOrg
groupId = releaseGroupId
version = releaseVersion
website = releaseWebsite
} }
...@@ -20,6 +20,7 @@ import com.google.android.exoplayer2.Format; ...@@ -20,6 +20,7 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.PositionHolder; 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.extractor.TrackOutput;
import com.google.android.exoplayer2.testutil.FakeExtractorInput; import com.google.android.exoplayer2.testutil.FakeExtractorInput;
import com.google.android.exoplayer2.testutil.FakeExtractorOutput; import com.google.android.exoplayer2.testutil.FakeExtractorOutput;
......
...@@ -68,7 +68,7 @@ public final class DefaultLoadControl implements LoadControl { ...@@ -68,7 +68,7 @@ public final class DefaultLoadControl implements LoadControl {
* Constructs a new instance, using the {@code DEFAULT_*} constants defined in this class. * Constructs a new instance, using the {@code DEFAULT_*} constants defined in this class.
*/ */
public DefaultLoadControl() { public DefaultLoadControl() {
this(new DefaultAllocator(C.DEFAULT_BUFFER_SEGMENT_SIZE)); this(new DefaultAllocator(true, C.DEFAULT_BUFFER_SEGMENT_SIZE));
} }
/** /**
...@@ -105,6 +105,11 @@ public final class DefaultLoadControl implements LoadControl { ...@@ -105,6 +105,11 @@ public final class DefaultLoadControl implements LoadControl {
} }
@Override @Override
public void onPrepared() {
reset(false);
}
@Override
public void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups, public void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups,
TrackSelections<?> trackSelections) { TrackSelections<?> trackSelections) {
targetBufferSize = 0; targetBufferSize = 0;
...@@ -117,9 +122,13 @@ public final class DefaultLoadControl implements LoadControl { ...@@ -117,9 +122,13 @@ public final class DefaultLoadControl implements LoadControl {
} }
@Override @Override
public void onTracksDisabled() { public void onStopped() {
targetBufferSize = 0; reset(true);
isBuffering = false; }
@Override
public void onReleased() {
reset(true);
} }
@Override @Override
...@@ -147,4 +156,12 @@ public final class DefaultLoadControl implements LoadControl { ...@@ -147,4 +156,12 @@ public final class DefaultLoadControl implements LoadControl {
: (bufferedDurationUs < minBufferUs ? BELOW_LOW_WATERMARK : BETWEEN_WATERMARKS); : (bufferedDurationUs < minBufferUs ? BELOW_LOW_WATERMARK : BETWEEN_WATERMARKS);
} }
private void reset(boolean resetAllocator) {
targetBufferSize = 0;
isBuffering = false;
if (resetAllocator) {
allocator.reset();
}
}
} }
...@@ -106,7 +106,7 @@ import java.util.concurrent.CopyOnWriteArraySet; ...@@ -106,7 +106,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
@Override @Override
public void prepare(MediaSource mediaSource, boolean resetPosition) { public void prepare(MediaSource mediaSource, boolean resetPosition) {
timeline = null; timeline = null;
internalPlayer.setMediaSource(mediaSource, resetPosition); internalPlayer.prepare(mediaSource, resetPosition);
} }
@Override @Override
......
...@@ -75,7 +75,7 @@ import java.io.IOException; ...@@ -75,7 +75,7 @@ import java.io.IOException;
public static final int MSG_ERROR = 6; public static final int MSG_ERROR = 6;
// Internal messages // Internal messages
private static final int MSG_SET_MEDIA_SOURCE = 0; private static final int MSG_PREPARE = 0;
private static final int MSG_SET_PLAY_WHEN_READY = 1; private static final int MSG_SET_PLAY_WHEN_READY = 1;
private static final int MSG_DO_SOME_WORK = 2; private static final int MSG_DO_SOME_WORK = 2;
private static final int MSG_SEEK_TO = 3; private static final int MSG_SEEK_TO = 3;
...@@ -164,8 +164,8 @@ import java.io.IOException; ...@@ -164,8 +164,8 @@ import java.io.IOException;
handler = new Handler(internalPlaybackThread.getLooper(), this); handler = new Handler(internalPlaybackThread.getLooper(), this);
} }
public void setMediaSource(MediaSource mediaSource, boolean resetPosition) { public void prepare(MediaSource mediaSource, boolean resetPosition) {
handler.obtainMessage(MSG_SET_MEDIA_SOURCE, resetPosition ? 1 : 0, 0, mediaSource) handler.obtainMessage(MSG_PREPARE, resetPosition ? 1 : 0, 0, mediaSource)
.sendToTarget(); .sendToTarget();
} }
...@@ -253,8 +253,8 @@ import java.io.IOException; ...@@ -253,8 +253,8 @@ import java.io.IOException;
public boolean handleMessage(Message msg) { public boolean handleMessage(Message msg) {
try { try {
switch (msg.what) { switch (msg.what) {
case MSG_SET_MEDIA_SOURCE: { case MSG_PREPARE: {
setMediaSourceInternal((MediaSource) msg.obj, msg.arg1 != 0); prepareInternal((MediaSource) msg.obj, msg.arg1 != 0);
return true; return true;
} }
case MSG_SET_PLAY_WHEN_READY: { case MSG_SET_PLAY_WHEN_READY: {
...@@ -335,9 +335,10 @@ import java.io.IOException; ...@@ -335,9 +335,10 @@ import java.io.IOException;
} }
} }
private void setMediaSourceInternal(MediaSource mediaSource, boolean resetPosition) private void prepareInternal(MediaSource mediaSource, boolean resetPosition)
throws ExoPlaybackException { throws ExoPlaybackException {
resetInternal(); resetInternal();
loadControl.onPrepared();
if (resetPosition) { if (resetPosition) {
playbackInfo = new PlaybackInfo(0, C.TIME_UNSET); playbackInfo = new PlaybackInfo(0, C.TIME_UNSET);
} }
...@@ -597,11 +598,13 @@ import java.io.IOException; ...@@ -597,11 +598,13 @@ import java.io.IOException;
private void stopInternal() { private void stopInternal() {
resetInternal(); resetInternal();
loadControl.onStopped();
setState(ExoPlayer.STATE_IDLE); setState(ExoPlayer.STATE_IDLE);
} }
private void releaseInternal() { private void releaseInternal() {
resetInternal(); resetInternal();
loadControl.onReleased();
setState(ExoPlayer.STATE_IDLE); setState(ExoPlayer.STATE_IDLE);
synchronized (this) { synchronized (this) {
released = true; released = true;
...@@ -638,7 +641,6 @@ import java.io.IOException; ...@@ -638,7 +641,6 @@ import java.io.IOException;
loadingPeriodHolder = null; loadingPeriodHolder = null;
timeline = null; timeline = null;
bufferAheadPeriodCount = 0; bufferAheadPeriodCount = 0;
loadControl.onTracksDisabled();
setIsLoading(false); setIsLoading(false);
} }
...@@ -1262,11 +1264,14 @@ import java.io.IOException; ...@@ -1262,11 +1264,14 @@ import java.io.IOException;
sampleStreams, streamResetFlags, positionUs); sampleStreams, streamResetFlags, positionUs);
periodTrackSelections = trackSelections; periodTrackSelections = trackSelections;
// Update whether we have enabled tracks and sanity check the expected streams are non-null.
hasEnabledTracks = false; hasEnabledTracks = false;
for (int i = 0; i < sampleStreams.length; i++) { for (int i = 0; i < sampleStreams.length; i++) {
if (sampleStreams[i] != null) { if (sampleStreams[i] != null) {
Assertions.checkState(trackSelections.get(i) != null);
hasEnabledTracks = true; hasEnabledTracks = true;
break; } else {
Assertions.checkState(trackSelections.get(i) == null);
} }
} }
......
...@@ -23,7 +23,7 @@ public interface ExoPlayerLibraryInfo { ...@@ -23,7 +23,7 @@ public interface ExoPlayerLibraryInfo {
/** /**
* The version of the library, expressed as a string. * The version of the library, expressed as a string.
*/ */
String VERSION = "2.0.1"; String VERSION = "2.0.2";
/** /**
* The version of the library, expressed as an integer. * The version of the library, expressed as an integer.
...@@ -32,7 +32,7 @@ public interface ExoPlayerLibraryInfo { ...@@ -32,7 +32,7 @@ public interface ExoPlayerLibraryInfo {
* corresponding integer version 1002003 (001-002-003), and "123.45.6" has the corresponding * corresponding integer version 1002003 (001-002-003), and "123.45.6" has the corresponding
* integer version 123045006 (123-045-006). * integer version 123045006 (123-045-006).
*/ */
int VERSION_INT = 2000001; int VERSION_INT = 2000002;
/** /**
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions} * Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}
......
...@@ -26,6 +26,11 @@ import com.google.android.exoplayer2.upstream.Allocator; ...@@ -26,6 +26,11 @@ import com.google.android.exoplayer2.upstream.Allocator;
public interface LoadControl { public interface LoadControl {
/** /**
* Called by the player when prepared with a new source.
*/
void onPrepared();
/**
* Called by the player when a track selection occurs. * Called by the player when a track selection occurs.
* *
* @param renderers The renderers. * @param renderers The renderers.
...@@ -36,9 +41,14 @@ public interface LoadControl { ...@@ -36,9 +41,14 @@ public interface LoadControl {
TrackSelections<?> trackSelections); TrackSelections<?> trackSelections);
/** /**
* Called by the player when all tracks are disabled. * Called by the player when stopped.
*/
void onStopped();
/**
* Called by the player when released.
*/ */
void onTracksDisabled(); void onReleased();
/** /**
* Returns the {@link Allocator} that should be used to obtain media buffer allocations. * Returns the {@link Allocator} that should be used to obtain media buffer allocations.
......
...@@ -184,6 +184,14 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -184,6 +184,14 @@ public final class SimpleExoPlayer implements ExoPlayer {
} }
/** /**
* Clears any {@link Surface}, {@link SurfaceHolder}, {@link SurfaceView} or {@link TextureView}
* currently set on the player.
*/
public void clearVideoSurface() {
setVideoSurface(null);
}
/**
* Sets the {@link Surface} onto which video will be rendered. The caller is responsible for * Sets the {@link Surface} onto which video will be rendered. The caller is responsible for
* tracking the lifecycle of the surface, and must clear the surface by calling * tracking the lifecycle of the surface, and must clear the surface by calling
* {@code setVideoSurface(null)} if the surface is destroyed. * {@code setVideoSurface(null)} if the surface is destroyed.
...@@ -240,6 +248,9 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -240,6 +248,9 @@ public final class SimpleExoPlayer implements ExoPlayer {
if (textureView == null) { if (textureView == null) {
setVideoSurfaceInternal(null); setVideoSurfaceInternal(null);
} else { } else {
if (textureView.getSurfaceTextureListener() != null) {
Log.w(TAG, "Replacing existing SurfaceTextureListener.");
}
SurfaceTexture surfaceTexture = textureView.getSurfaceTexture(); SurfaceTexture surfaceTexture = textureView.getSurfaceTexture();
setVideoSurfaceInternal(surfaceTexture == null ? null : new Surface(surfaceTexture)); setVideoSurfaceInternal(surfaceTexture == null ? null : new Surface(surfaceTexture));
textureView.setSurfaceTextureListener(componentListener); textureView.setSurfaceTextureListener(componentListener);
...@@ -456,6 +467,7 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -456,6 +467,7 @@ public final class SimpleExoPlayer implements ExoPlayer {
@Override @Override
public void release() { public void release() {
player.release(); player.release();
removeSurfaceCallbacks();
} }
@Override @Override
...@@ -592,13 +604,17 @@ public final class SimpleExoPlayer implements ExoPlayer { ...@@ -592,13 +604,17 @@ public final class SimpleExoPlayer implements ExoPlayer {
} }
private void removeSurfaceCallbacks() { private void removeSurfaceCallbacks() {
if (this.textureView != null) { if (textureView != null) {
this.textureView.setSurfaceTextureListener(null); if (textureView.getSurfaceTextureListener() != componentListener) {
this.textureView = null; Log.w(TAG, "SurfaceTextureListener already unset or replaced.");
} else {
textureView.setSurfaceTextureListener(null);
}
textureView = null;
} }
if (this.surfaceHolder != null) { if (surfaceHolder != null) {
this.surfaceHolder.removeCallback(componentListener); surfaceHolder.removeCallback(componentListener);
this.surfaceHolder = null; surfaceHolder = null;
} }
} }
......
...@@ -15,6 +15,7 @@ ...@@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.audio; package com.google.android.exoplayer2.audio;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.drm.DrmInitData; import com.google.android.exoplayer2.drm.DrmInitData;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
...@@ -175,9 +176,12 @@ public final class Ac3Util { ...@@ -175,9 +176,12 @@ public final class Ac3Util {
* Returns the size in bytes of the given AC-3 syncframe. * Returns the size in bytes of the given AC-3 syncframe.
* *
* @param data The syncframe to parse. * @param data The syncframe to parse.
* @return The syncframe size in bytes. * @return The syncframe size in bytes. {@link C#LENGTH_UNSET} if the input is invalid.
*/ */
public static int parseAc3SyncframeSize(byte[] data) { public static int parseAc3SyncframeSize(byte[] data) {
if (data.length < 5) {
return C.LENGTH_UNSET;
}
int fscod = (data[4] & 0xC0) >> 6; int fscod = (data[4] & 0xC0) >> 6;
int frmsizecod = data[4] & 0x3F; int frmsizecod = data[4] & 0x3F;
return getAc3SyncframeSize(fscod, frmsizecod); return getAc3SyncframeSize(fscod, frmsizecod);
...@@ -227,11 +231,17 @@ public final class Ac3Util { ...@@ -227,11 +231,17 @@ public final class Ac3Util {
} }
private static int getAc3SyncframeSize(int fscod, int frmsizecod) { private static int getAc3SyncframeSize(int fscod, int frmsizecod) {
int halfFrmsizecod = frmsizecod / 2;
if (fscod < 0 || fscod >= SAMPLE_RATE_BY_FSCOD.length || frmsizecod < 0
|| halfFrmsizecod >= SYNCFRAME_SIZE_WORDS_BY_HALF_FRMSIZECOD_44_1.length) {
// Invalid values provided.
return C.LENGTH_UNSET;
}
int sampleRate = SAMPLE_RATE_BY_FSCOD[fscod]; int sampleRate = SAMPLE_RATE_BY_FSCOD[fscod];
if (sampleRate == 44100) { if (sampleRate == 44100) {
return 2 * (SYNCFRAME_SIZE_WORDS_BY_HALF_FRMSIZECOD_44_1[frmsizecod / 2] + (frmsizecod % 2)); return 2 * (SYNCFRAME_SIZE_WORDS_BY_HALF_FRMSIZECOD_44_1[halfFrmsizecod] + (frmsizecod % 2));
} }
int bitrate = BITRATE_BY_HALF_FRMSIZECOD[frmsizecod / 2]; int bitrate = BITRATE_BY_HALF_FRMSIZECOD[halfFrmsizecod];
if (sampleRate == 32000) { if (sampleRate == 32000) {
return 6 * bitrate; return 6 * bitrate;
} else { // sampleRate == 48000 } else { // sampleRate == 48000
......
...@@ -13,7 +13,7 @@ ...@@ -13,7 +13,7 @@
* 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.exoplayer2.extractor.ts; package com.google.android.exoplayer2.extractor;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
......
...@@ -30,6 +30,7 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput; ...@@ -30,6 +30,7 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.SeekMap; 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.TrackOutput;
import com.google.android.exoplayer2.extractor.mp4.Atom.ContainerAtom; import com.google.android.exoplayer2.extractor.mp4.Atom.ContainerAtom;
import com.google.android.exoplayer2.extractor.mp4.Atom.LeafAtom; import com.google.android.exoplayer2.extractor.mp4.Atom.LeafAtom;
...@@ -115,6 +116,9 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -115,6 +116,9 @@ public final class FragmentedMp4Extractor implements Extractor {
private final ParsableByteArray nalLength; private final ParsableByteArray nalLength;
private final ParsableByteArray encryptionSignalByte; private final ParsableByteArray encryptionSignalByte;
// Adjusts sample timestamps.
private final TimestampAdjuster timestampAdjuster;
// Parser state. // Parser state.
private final ParsableByteArray atomHeader; private final ParsableByteArray atomHeader;
private final byte[] extendedTypeScratch; private final byte[] extendedTypeScratch;
...@@ -140,24 +144,28 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -140,24 +144,28 @@ public final class FragmentedMp4Extractor implements Extractor {
private boolean haveOutputSeekMap; private boolean haveOutputSeekMap;
public FragmentedMp4Extractor() { public FragmentedMp4Extractor() {
this(0); this(0, null);
} }
/** /**
* @param flags Flags that control the extractor's behavior. * @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) { public FragmentedMp4Extractor(@Flags int flags, TimestampAdjuster timestampAdjuster) {
this(flags, null); this(flags, null, timestampAdjuster);
} }
/** /**
* @param flags Flags that control the extractor's behavior. * @param flags Flags that control the extractor's behavior.
* @param sideloadedTrack Sideloaded track information, in the case that the extractor * @param sideloadedTrack Sideloaded track information, in the case that the extractor
* will not receive a moov box in the input data. * 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.sideloadedTrack = sideloadedTrack;
this.flags = flags | (sideloadedTrack != null ? FLAG_SIDELOADED : 0); this.flags = flags | (sideloadedTrack != null ? FLAG_SIDELOADED : 0);
this.timestampAdjuster = timestampAdjuster;
atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE); atomHeader = new ParsableByteArray(Atom.LONG_HEADER_SIZE);
nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE); nalStartCode = new ParsableByteArray(NalUnitUtil.NAL_START_CODE);
nalLength = new ParsableByteArray(4); nalLength = new ParsableByteArray(4);
...@@ -1012,6 +1020,9 @@ public final class FragmentedMp4Extractor implements Extractor { ...@@ -1012,6 +1020,9 @@ public final class FragmentedMp4Extractor implements Extractor {
? fragment.trackEncryptionBox.keyId ? fragment.trackEncryptionBox.keyId
: track.sampleDescriptionEncryptionBoxes[sampleDescriptionIndex].keyId; : track.sampleDescriptionEncryptionBoxes[sampleDescriptionIndex].keyId;
} }
if (timestampAdjuster != null) {
sampleTimeUs = timestampAdjuster.adjustSampleTimestamp(sampleTimeUs);
}
output.sampleMetadata(sampleTimeUs, sampleFlags, sampleSize, 0, encryptionKey); output.sampleMetadata(sampleTimeUs, sampleFlags, sampleSize, 0, encryptionKey);
currentTrackBundle.currentSampleIndex++; currentTrackBundle.currentSampleIndex++;
......
...@@ -107,6 +107,9 @@ public final class Ac3Extractor implements Extractor { ...@@ -107,6 +107,9 @@ public final class Ac3Extractor implements Extractor {
return true; return true;
} }
int frameSize = Ac3Util.parseAc3SyncframeSize(scratch.data); int frameSize = Ac3Util.parseAc3SyncframeSize(scratch.data);
if (frameSize == C.LENGTH_UNSET) {
return false;
}
input.advancePeekPosition(frameSize - 5); input.advancePeekPosition(frameSize - 5);
} }
} }
......
...@@ -23,6 +23,7 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput; ...@@ -23,6 +23,7 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.SeekMap; 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.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
import java.io.IOException; import java.io.IOException;
......
...@@ -25,6 +25,7 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput; ...@@ -25,6 +25,7 @@ import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.SeekMap; 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.Assertions;
import com.google.android.exoplayer2.util.ParsableBitArray; import com.google.android.exoplayer2.util.ParsableBitArray;
import com.google.android.exoplayer2.util.ParsableByteArray; import com.google.android.exoplayer2.util.ParsableByteArray;
......
...@@ -456,7 +456,7 @@ import java.util.Arrays; ...@@ -456,7 +456,7 @@ import java.util.Arrays;
lastSeekPositionUs = 0; lastSeekPositionUs = 0;
notifyReset = prepared; notifyReset = prepared;
for (int i = 0; i < sampleQueues.length; i++) { for (int i = 0; i < sampleQueues.length; i++) {
sampleQueues[i].reset(trackEnabledStates[i]); sampleQueues[i].reset(!prepared || trackEnabledStates[i]);
} }
loadable.setLoadPosition(0); loadable.setLoadPosition(0);
} }
......
...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.source; ...@@ -17,6 +17,7 @@ package com.google.android.exoplayer2.source;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.IdentityHashMap; import java.util.IdentityHashMap;
...@@ -84,7 +85,8 @@ import java.util.IdentityHashMap; ...@@ -84,7 +85,8 @@ import java.util.IdentityHashMap;
} }
} }
streamPeriodIndices.clear(); streamPeriodIndices.clear();
// Select tracks for each child, copying the resulting streams back into the streams array. // Select tracks for each child, copying the resulting streams back into a new streams array.
SampleStream[] newStreams = new SampleStream[selections.length];
SampleStream[] childStreams = new SampleStream[selections.length]; SampleStream[] childStreams = new SampleStream[selections.length];
TrackSelection[] childSelections = new TrackSelection[selections.length]; TrackSelection[] childSelections = new TrackSelection[selections.length];
ArrayList<MediaPeriod> enabledPeriodsList = new ArrayList<>(periods.length); ArrayList<MediaPeriod> enabledPeriodsList = new ArrayList<>(periods.length);
...@@ -103,17 +105,22 @@ import java.util.IdentityHashMap; ...@@ -103,17 +105,22 @@ import java.util.IdentityHashMap;
boolean periodEnabled = false; boolean periodEnabled = false;
for (int j = 0; j < selections.length; j++) { for (int j = 0; j < selections.length; j++) {
if (selectionChildIndices[j] == i) { if (selectionChildIndices[j] == i) {
streams[j] = childStreams[j]; // Assert that the child provided a stream for the selection.
if (childStreams[j] != null) { Assertions.checkState(childStreams[j] != null);
periodEnabled = true; newStreams[j] = childStreams[j];
streamPeriodIndices.put(childStreams[j], i); periodEnabled = true;
} streamPeriodIndices.put(childStreams[j], i);
} else if (streamChildIndices[j] == i) {
// Assert that the child cleared any previous stream.
Assertions.checkState(childStreams[j] == null);
} }
} }
if (periodEnabled) { if (periodEnabled) {
enabledPeriodsList.add(periods[i]); enabledPeriodsList.add(periods[i]);
} }
} }
// Copy the new streams back into the streams array.
System.arraycopy(newStreams, 0, streams, 0, newStreams.length);
// Update the local state. // Update the local state.
enabledPeriods = new MediaPeriod[enabledPeriodsList.size()]; enabledPeriods = new MediaPeriod[enabledPeriodsList.size()];
enabledPeriodsList.toArray(enabledPeriods); enabledPeriodsList.toArray(enabledPeriods);
...@@ -133,21 +140,22 @@ import java.util.IdentityHashMap; ...@@ -133,21 +140,22 @@ import java.util.IdentityHashMap;
@Override @Override
public long readDiscontinuity() { public long readDiscontinuity() {
long positionUs = enabledPeriods[0].readDiscontinuity(); long positionUs = periods[0].readDiscontinuity();
// Periods other than the first one are not allowed to report discontinuities.
for (int i = 1; i < periods.length; i++) {
if (periods[i].readDiscontinuity() != C.TIME_UNSET) {
throw new IllegalStateException("Child reported discontinuity");
}
}
// It must be possible to seek enabled periods to the new position, if there is one.
if (positionUs != C.TIME_UNSET) { if (positionUs != C.TIME_UNSET) {
// It must be possible to seek additional periods to the new position. for (int i = 0; i < enabledPeriods.length; i++) {
for (int i = 1; i < enabledPeriods.length; i++) { if (enabledPeriods[i] != periods[0]
if (enabledPeriods[i].seekToUs(positionUs) != positionUs) { && enabledPeriods[i].seekToUs(positionUs) != positionUs) {
throw new IllegalStateException("Children seeked to different positions"); throw new IllegalStateException("Children seeked to different positions");
} }
} }
} }
// Additional periods are not allowed to report discontinuities.
for (int i = 1; i < enabledPeriods.length; i++) {
if (enabledPeriods[i].readDiscontinuity() != C.TIME_UNSET) {
throw new IllegalStateException("Child reported discontinuity");
}
}
return positionUs; return positionUs;
} }
......
...@@ -41,7 +41,7 @@ import java.util.Arrays; ...@@ -41,7 +41,7 @@ import java.util.Arrays;
/** /**
* The initial size of the allocation used to hold the sample data. * The initial size of the allocation used to hold the sample data.
*/ */
private static final int INITIAL_SAMPLE_SIZE = 1; private static final int INITIAL_SAMPLE_SIZE = 1024;
private final Uri uri; private final Uri uri;
private final DataSource.Factory dataSourceFactory; private final DataSource.Factory dataSourceFactory;
...@@ -71,7 +71,6 @@ import java.util.Arrays; ...@@ -71,7 +71,6 @@ import java.util.Arrays;
tracks = new TrackGroupArray(new TrackGroup(format)); tracks = new TrackGroupArray(new TrackGroup(format));
sampleStreams = new ArrayList<>(); sampleStreams = new ArrayList<>();
loader = new Loader("Loader:SingleSampleMediaPeriod"); loader = new Loader("Loader:SingleSampleMediaPeriod");
sampleData = new byte[INITIAL_SAMPLE_SIZE];
} }
public void release() { public void release() {
...@@ -269,7 +268,9 @@ import java.util.Arrays; ...@@ -269,7 +268,9 @@ import java.util.Arrays;
int result = 0; int result = 0;
while (result != C.RESULT_END_OF_INPUT) { while (result != C.RESULT_END_OF_INPUT) {
sampleSize += result; sampleSize += result;
if (sampleSize == sampleData.length) { if (sampleData == null) {
sampleData = new byte[INITIAL_SAMPLE_SIZE];
} else if (sampleSize == sampleData.length) {
sampleData = Arrays.copyOf(sampleData, sampleData.length * 2); sampleData = Arrays.copyOf(sampleData, sampleData.length * 2);
} }
result = dataSource.read(sampleData, sampleSize, sampleData.length - sampleSize); result = dataSource.read(sampleData, sampleSize, sampleData.length - sampleSize);
......
...@@ -654,10 +654,10 @@ public class DashManifestParser extends DefaultHandler ...@@ -654,10 +654,10 @@ public class DashManifestParser extends DefaultHandler
} else if (MimeTypes.isVideo(containerMimeType)) { } else if (MimeTypes.isVideo(containerMimeType)) {
return MimeTypes.getVideoMediaMimeType(codecs); return MimeTypes.getVideoMediaMimeType(codecs);
} else if (MimeTypes.APPLICATION_RAWCC.equals(containerMimeType)) { } else if (MimeTypes.APPLICATION_RAWCC.equals(containerMimeType)) {
// We currently only support CEA-608 through RawCC if (codecs != null) {
if (codecs != null if (codecs.contains("eia608") || codecs.contains("cea608")) {
&& (codecs.contains("eia608") || codecs.contains("cea608"))) { return MimeTypes.APPLICATION_CEA608;
return MimeTypes.APPLICATION_CEA608; }
} }
return null; return null;
} else if (mimeTypeIsRawText(containerMimeType)) { } else if (mimeTypeIsRawText(containerMimeType)) {
......
...@@ -21,11 +21,12 @@ import android.text.TextUtils; ...@@ -21,11 +21,12 @@ import android.text.TextUtils;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.Extractor; 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.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.Ac3Extractor;
import com.google.android.exoplayer2.extractor.ts.AdtsExtractor; import com.google.android.exoplayer2.extractor.ts.AdtsExtractor;
import com.google.android.exoplayer2.extractor.ts.DefaultStreamReaderFactory; 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.extractor.ts.TsExtractor;
import com.google.android.exoplayer2.source.BehindLiveWindowException; import com.google.android.exoplayer2.source.BehindLiveWindowException;
import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroup;
...@@ -34,6 +35,7 @@ import com.google.android.exoplayer2.source.chunk.ChunkedTrackBlacklistUtil; ...@@ -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.chunk.DataChunk;
import com.google.android.exoplayer2.source.hls.playlist.HlsMasterPlaylist; 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;
import com.google.android.exoplayer2.source.hls.playlist.HlsMediaPlaylist.Segment;
import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParser; import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistParser;
import com.google.android.exoplayer2.trackselection.BaseTrackSelection; import com.google.android.exoplayer2.trackselection.BaseTrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelection; import com.google.android.exoplayer2.trackselection.TrackSelection;
...@@ -101,6 +103,7 @@ import java.util.Locale; ...@@ -101,6 +103,7 @@ import java.util.Locale;
private static final String AC3_FILE_EXTENSION = ".ac3"; private static final String AC3_FILE_EXTENSION = ".ac3";
private static final String EC3_FILE_EXTENSION = ".ec3"; private static final String EC3_FILE_EXTENSION = ".ec3";
private static final String MP3_FILE_EXTENSION = ".mp3"; 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 VTT_FILE_EXTENSION = ".vtt";
private static final String WEBVTT_FILE_EXTENSION = ".webvtt"; private static final String WEBVTT_FILE_EXTENSION = ".webvtt";
...@@ -118,6 +121,7 @@ import java.util.Locale; ...@@ -118,6 +121,7 @@ import java.util.Locale;
private long durationUs; private long durationUs;
private IOException fatalError; private IOException fatalError;
private HlsInitializationChunk lastLoadedInitializationChunk;
private Uri encryptionKeyUri; private Uri encryptionKeyUri;
private byte[] encryptionKey; private byte[] encryptionKey;
private String encryptionIvString; private String encryptionIvString;
...@@ -289,7 +293,6 @@ import java.util.Locale; ...@@ -289,7 +293,6 @@ import java.util.Locale;
} }
HlsMediaPlaylist.Segment segment = mediaPlaylist.segments.get(chunkIndex); HlsMediaPlaylist.Segment segment = mediaPlaylist.segments.get(chunkIndex);
Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url);
// Check if encryption is specified. // Check if encryption is specified.
if (segment.isEncrypted) { if (segment.isEncrypted) {
...@@ -307,10 +310,6 @@ import java.util.Locale; ...@@ -307,10 +310,6 @@ import java.util.Locale;
clearEncryptionData(); 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. // Compute start and end times, and the sequence number of the next chunk.
long startTimeUs; long startTimeUs;
if (live) { if (live) {
...@@ -327,8 +326,15 @@ import java.util.Locale; ...@@ -327,8 +326,15 @@ import java.util.Locale;
long endTimeUs = startTimeUs + (long) (segment.durationSecs * C.MICROS_PER_SECOND); long endTimeUs = startTimeUs + (long) (segment.durationSecs * C.MICROS_PER_SECOND);
Format format = variants[newVariantIndex].format; Format format = variants[newVariantIndex].format;
Uri chunkUri = UriUtil.resolveToUri(mediaPlaylist.baseUri, segment.url);
// Configure the extractor that will read the chunk. // Configure the extractor that will read the chunk.
Extractor extractor; Extractor extractor;
boolean useInitializedExtractor = lastLoadedInitializationChunk != null
&& lastLoadedInitializationChunk.format == format;
boolean needNewExtractor = previous == null
|| previous.discontinuitySequenceNumber != segment.discontinuitySequenceNumber
|| format != previous.trackFormat;
boolean extractorNeedsInit = true; boolean extractorNeedsInit = true;
boolean isTimestampMaster = false; boolean isTimestampMaster = false;
TimestampAdjuster timestampAdjuster = null; TimestampAdjuster timestampAdjuster = null;
...@@ -348,13 +354,21 @@ import java.util.Locale; ...@@ -348,13 +354,21 @@ import java.util.Locale;
timestampAdjuster = timestampAdjusterProvider.getAdjuster(segment.discontinuitySequenceNumber, timestampAdjuster = timestampAdjusterProvider.getAdjuster(segment.discontinuitySequenceNumber,
startTimeUs); startTimeUs);
extractor = new WebvttExtractor(format.language, timestampAdjuster); extractor = new WebvttExtractor(format.language, timestampAdjuster);
} else if (previous == null } else if (lastPathSegment.endsWith(MP4_FILE_EXTENSION)) {
|| previous.discontinuitySequenceNumber != segment.discontinuitySequenceNumber
|| format != previous.trackFormat) {
// MPEG-2 TS segments, but we need a new extractor.
isTimestampMaster = true; isTimestampMaster = true;
timestampAdjuster = timestampAdjusterProvider.getAdjuster(segment.discontinuitySequenceNumber, if (needNewExtractor) {
startTimeUs); 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. // This flag ensures the change of pid between streams does not affect the sample queues.
@DefaultStreamReaderFactory.WorkaroundFlags @DefaultStreamReaderFactory.WorkaroundFlags
int workaroundFlags = DefaultStreamReaderFactory.WORKAROUND_MAP_BY_TYPE; int workaroundFlags = DefaultStreamReaderFactory.WORKAROUND_MAP_BY_TYPE;
...@@ -370,14 +384,31 @@ import java.util.Locale; ...@@ -370,14 +384,31 @@ import java.util.Locale;
workaroundFlags |= DefaultStreamReaderFactory.WORKAROUND_IGNORE_H264_STREAM; workaroundFlags |= DefaultStreamReaderFactory.WORKAROUND_IGNORE_H264_STREAM;
} }
} }
extractor = new TsExtractor(timestampAdjuster, isTimestampMaster = true;
new DefaultStreamReaderFactory(workaroundFlags)); if (useInitializedExtractor) {
extractor = lastLoadedInitializationChunk.extractor;
} else {
timestampAdjuster = timestampAdjusterProvider.getAdjuster(
segment.discontinuitySequenceNumber, startTimeUs);
extractor = new TsExtractor(timestampAdjuster,
new DefaultStreamReaderFactory(workaroundFlags));
}
} else { } else {
// MPEG-2 TS segments, and we need to continue using the same extractor. // MPEG-2 TS segments, and we need to continue using the same extractor.
extractor = previous.extractor; extractor = previous.extractor;
extractorNeedsInit = false; 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, out.chunk = new HlsMediaChunk(dataSource, dataSpec, format,
trackSelection.getSelectionReason(), trackSelection.getSelectionData(), trackSelection.getSelectionReason(), trackSelection.getSelectionData(),
startTimeUs, endTimeUs, chunkMediaSequence, segment.discontinuitySequenceNumber, startTimeUs, endTimeUs, chunkMediaSequence, segment.discontinuitySequenceNumber,
...@@ -434,7 +465,9 @@ import java.util.Locale; ...@@ -434,7 +465,9 @@ import java.util.Locale;
* @param chunk The chunk whose load has been completed. * @param chunk The chunk whose load has been completed.
*/ */
public void onChunkLoadCompleted(Chunk chunk) { 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; MediaPlaylistChunk mediaPlaylistChunk = (MediaPlaylistChunk) chunk;
scratchSpace = mediaPlaylistChunk.getDataHolder(); scratchSpace = mediaPlaylistChunk.getDataHolder();
setMediaPlaylist(mediaPlaylistChunk.variantIndex, mediaPlaylistChunk.getResult()); setMediaPlaylist(mediaPlaylistChunk.variantIndex, mediaPlaylistChunk.getResult());
...@@ -462,6 +495,18 @@ import java.util.Locale; ...@@ -462,6 +495,18 @@ import java.util.Locale;
// Private methods. // 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) { private long msToRerequestLiveMediaPlaylist(int variantIndex) {
HlsMediaPlaylist mediaPlaylist = variantPlaylists[variantIndex]; HlsMediaPlaylist mediaPlaylist = variantPlaylists[variantIndex];
long timeSinceLastMediaPlaylistLoadMs = 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; ...@@ -19,7 +19,7 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.extractor.DefaultExtractorInput; import com.google.android.exoplayer2.extractor.DefaultExtractorInput;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorInput; 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.source.chunk.MediaChunk;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
......
...@@ -39,6 +39,7 @@ import com.google.android.exoplayer2.upstream.Allocator; ...@@ -39,6 +39,7 @@ import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.Loader; import com.google.android.exoplayer2.upstream.Loader;
import com.google.android.exoplayer2.upstream.ParsingLoadable; import com.google.android.exoplayer2.upstream.ParsingLoadable;
import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.IdentityHashMap; import java.util.IdentityHashMap;
...@@ -154,7 +155,8 @@ import java.util.List; ...@@ -154,7 +155,8 @@ import java.util.List;
} }
boolean selectedNewTracks = false; boolean selectedNewTracks = false;
streamWrapperIndices.clear(); streamWrapperIndices.clear();
// Select tracks for each child, copying the resulting streams back into the streams array. // Select tracks for each child, copying the resulting streams back into a new streams array.
SampleStream[] newStreams = new SampleStream[selections.length];
SampleStream[] childStreams = new SampleStream[selections.length]; SampleStream[] childStreams = new SampleStream[selections.length];
TrackSelection[] childSelections = new TrackSelection[selections.length]; TrackSelection[] childSelections = new TrackSelection[selections.length];
ArrayList<HlsSampleStreamWrapper> enabledSampleStreamWrapperList = new ArrayList<>( ArrayList<HlsSampleStreamWrapper> enabledSampleStreamWrapperList = new ArrayList<>(
...@@ -168,19 +170,23 @@ import java.util.List; ...@@ -168,19 +170,23 @@ import java.util.List;
mayRetainStreamFlags, childStreams, streamResetFlags, !seenFirstTrackSelection); mayRetainStreamFlags, childStreams, streamResetFlags, !seenFirstTrackSelection);
boolean wrapperEnabled = false; boolean wrapperEnabled = false;
for (int j = 0; j < selections.length; j++) { for (int j = 0; j < selections.length; j++) {
if (selectionChildIndices[j] == i if (selectionChildIndices[j] == i) {
|| (selectionChildIndices[j] == C.INDEX_UNSET && streamChildIndices[j] == i)) { // Assert that the child provided a stream for the selection.
streams[j] = childStreams[j]; Assertions.checkState(childStreams[j] != null);
if (childStreams[j] != null) { newStreams[j] = childStreams[j];
wrapperEnabled = true; wrapperEnabled = true;
streamWrapperIndices.put(childStreams[j], i); streamWrapperIndices.put(childStreams[j], i);
} } else if (streamChildIndices[j] == i) {
// Assert that the child cleared any previous stream.
Assertions.checkState(childStreams[j] == null);
} }
} }
if (wrapperEnabled) { if (wrapperEnabled) {
enabledSampleStreamWrapperList.add(sampleStreamWrappers[i]); enabledSampleStreamWrapperList.add(sampleStreamWrappers[i]);
} }
} }
// Copy the new streams back into the streams array.
System.arraycopy(newStreams, 0, streams, 0, newStreams.length);
// Update the local state. // Update the local state.
enabledSampleStreamWrappers = new HlsSampleStreamWrapper[enabledSampleStreamWrapperList.size()]; enabledSampleStreamWrappers = new HlsSampleStreamWrapper[enabledSampleStreamWrapperList.size()];
enabledSampleStreamWrapperList.toArray(enabledSampleStreamWrappers); enabledSampleStreamWrapperList.toArray(enabledSampleStreamWrappers);
......
...@@ -358,6 +358,8 @@ import java.util.LinkedList; ...@@ -358,6 +358,8 @@ import java.util.LinkedList;
HlsMediaChunk mediaChunk = (HlsMediaChunk) loadable; HlsMediaChunk mediaChunk = (HlsMediaChunk) loadable;
mediaChunk.init(this); mediaChunk.init(this);
mediaChunks.add(mediaChunk); mediaChunks.add(mediaChunk);
} else if (loadable instanceof HlsInitializationChunk) {
((HlsInitializationChunk) loadable).init(this);
} }
long elapsedRealtimeMs = loader.startLoading(loadable, this, minLoadableRetryCount); long elapsedRealtimeMs = loader.startLoading(loadable, this, minLoadableRetryCount);
eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, trackType, loadable.trackFormat, eventDispatcher.loadStarted(loadable.dataSpec, loadable.type, trackType, loadable.trackFormat,
......
...@@ -16,7 +16,7 @@ ...@@ -16,7 +16,7 @@
package com.google.android.exoplayer2.source.hls; package com.google.android.exoplayer2.source.hls;
import android.util.SparseArray; 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. * Provides {@link TimestampAdjuster} instances for use during HLS playbacks.
......
...@@ -24,8 +24,8 @@ import com.google.android.exoplayer2.extractor.ExtractorInput; ...@@ -24,8 +24,8 @@ import com.google.android.exoplayer2.extractor.ExtractorInput;
import com.google.android.exoplayer2.extractor.ExtractorOutput; import com.google.android.exoplayer2.extractor.ExtractorOutput;
import com.google.android.exoplayer2.extractor.PositionHolder; import com.google.android.exoplayer2.extractor.PositionHolder;
import com.google.android.exoplayer2.extractor.SeekMap; 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.TrackOutput;
import com.google.android.exoplayer2.extractor.ts.TimestampAdjuster;
import com.google.android.exoplayer2.text.SubtitleDecoderException; import com.google.android.exoplayer2.text.SubtitleDecoderException;
import com.google.android.exoplayer2.text.webvtt.WebvttParserUtil; import com.google.android.exoplayer2.text.webvtt.WebvttParserUtil;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
......
...@@ -38,6 +38,10 @@ public final class HlsMediaPlaylist extends HlsPlaylist { ...@@ -38,6 +38,10 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
public final long byterangeOffset; public final long byterangeOffset;
public final long byterangeLength; 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, public Segment(String uri, double durationSecs, int discontinuitySequenceNumber,
long startTimeUs, boolean isEncrypted, String encryptionKeyUri, String encryptionIV, long startTimeUs, boolean isEncrypted, String encryptionKeyUri, String encryptionIV,
long byterangeOffset, long byterangeLength) { long byterangeOffset, long byterangeLength) {
...@@ -64,17 +68,19 @@ public final class HlsMediaPlaylist extends HlsPlaylist { ...@@ -64,17 +68,19 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
public final int mediaSequence; public final int mediaSequence;
public final int targetDurationSecs; public final int targetDurationSecs;
public final int version; public final int version;
public final Segment initializationSegment;
public final List<Segment> segments; public final List<Segment> segments;
public final boolean live; public final boolean live;
public final long durationUs; public final long durationUs;
public HlsMediaPlaylist(String baseUri, int mediaSequence, int targetDurationSecs, int version, 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); super(baseUri, HlsPlaylist.TYPE_MEDIA);
this.mediaSequence = mediaSequence; this.mediaSequence = mediaSequence;
this.targetDurationSecs = targetDurationSecs; this.targetDurationSecs = targetDurationSecs;
this.version = version; this.version = version;
this.live = live; this.live = live;
this.initializationSegment = initializationSegment;
this.segments = segments; this.segments = segments;
if (!segments.isEmpty()) { if (!segments.isEmpty()) {
......
...@@ -44,6 +44,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -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_MEDIA = "#EXT-X-MEDIA";
private static final String TAG_DISCONTINUITY = "#EXT-X-DISCONTINUITY"; 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_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_DURATION = "#EXTINF";
private static final String TAG_MEDIA_SEQUENCE = "#EXT-X-MEDIA-SEQUENCE"; private static final String TAG_MEDIA_SEQUENCE = "#EXT-X-MEDIA-SEQUENCE";
private static final String TAG_TARGET_DURATION = "#EXT-X-TARGETDURATION"; private static final String TAG_TARGET_DURATION = "#EXT-X-TARGETDURATION";
...@@ -79,6 +80,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -79,6 +80,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
+ ":([\\d\\.]+)\\b"); + ":([\\d\\.]+)\\b");
private static final Pattern REGEX_BYTERANGE = Pattern.compile(TAG_BYTERANGE private static final Pattern REGEX_BYTERANGE = Pattern.compile(TAG_BYTERANGE
+ ":(\\d+(?:@\\d+)?)\\b"); + ":(\\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 + "|" private static final Pattern REGEX_METHOD = Pattern.compile("METHOD=(" + METHOD_NONE + "|"
+ METHOD_AES128 + ")"); + METHOD_AES128 + ")");
private static final Pattern REGEX_URI = Pattern.compile("URI=\"(.+?)\""); private static final Pattern REGEX_URI = Pattern.compile("URI=\"(.+?)\"");
...@@ -212,13 +215,14 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -212,13 +215,14 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
int targetDurationSecs = 0; int targetDurationSecs = 0;
int version = 1; // Default version == 1. int version = 1; // Default version == 1.
boolean live = true; boolean live = true;
Segment initializationSegment = null;
List<Segment> segments = new ArrayList<>(); List<Segment> segments = new ArrayList<>();
double segmentDurationSecs = 0.0; double segmentDurationSecs = 0.0;
int discontinuitySequenceNumber = 0; int discontinuitySequenceNumber = 0;
long segmentStartTimeUs = 0; long segmentStartTimeUs = 0;
long segmentByterangeOffset = 0; long segmentByteRangeOffset = 0;
long segmentByterangeLength = C.LENGTH_UNSET; long segmentByteRangeLength = C.LENGTH_UNSET;
int segmentMediaSequence = 0; int segmentMediaSequence = 0;
boolean isEncrypted = false; boolean isEncrypted = false;
...@@ -228,7 +232,20 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -228,7 +232,20 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
String line; String line;
while (iterator.hasNext()) { while (iterator.hasNext()) {
line = iterator.next(); 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); targetDurationSecs = parseIntAttr(line, REGEX_TARGET_DURATION);
} else if (line.startsWith(TAG_MEDIA_SEQUENCE)) { } else if (line.startsWith(TAG_MEDIA_SEQUENCE)) {
mediaSequence = parseIntAttr(line, REGEX_MEDIA_SEQUENCE); mediaSequence = parseIntAttr(line, REGEX_MEDIA_SEQUENCE);
...@@ -250,9 +267,9 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -250,9 +267,9 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
} else if (line.startsWith(TAG_BYTERANGE)) { } else if (line.startsWith(TAG_BYTERANGE)) {
String byteRange = parseStringAttr(line, REGEX_BYTERANGE); String byteRange = parseStringAttr(line, REGEX_BYTERANGE);
String[] splitByteRange = byteRange.split("@"); String[] splitByteRange = byteRange.split("@");
segmentByterangeLength = Long.parseLong(splitByteRange[0]); segmentByteRangeLength = Long.parseLong(splitByteRange[0]);
if (splitByteRange.length > 1) { if (splitByteRange.length > 1) {
segmentByterangeOffset = Long.parseLong(splitByteRange[1]); segmentByteRangeOffset = Long.parseLong(splitByteRange[1]);
} }
} else if (line.startsWith(TAG_DISCONTINUITY_SEQUENCE)) { } else if (line.startsWith(TAG_DISCONTINUITY_SEQUENCE)) {
discontinuitySequenceNumber = Integer.parseInt(line.substring(line.indexOf(':') + 1)); discontinuitySequenceNumber = Integer.parseInt(line.substring(line.indexOf(':') + 1));
...@@ -268,24 +285,24 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli ...@@ -268,24 +285,24 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
segmentEncryptionIV = Integer.toHexString(segmentMediaSequence); segmentEncryptionIV = Integer.toHexString(segmentMediaSequence);
} }
segmentMediaSequence++; segmentMediaSequence++;
if (segmentByterangeLength == C.LENGTH_UNSET) { if (segmentByteRangeLength == C.LENGTH_UNSET) {
segmentByterangeOffset = 0; segmentByteRangeOffset = 0;
} }
segments.add(new Segment(line, segmentDurationSecs, discontinuitySequenceNumber, segments.add(new Segment(line, segmentDurationSecs, discontinuitySequenceNumber,
segmentStartTimeUs, isEncrypted, encryptionKeyUri, segmentEncryptionIV, segmentStartTimeUs, isEncrypted, encryptionKeyUri, segmentEncryptionIV,
segmentByterangeOffset, segmentByterangeLength)); segmentByteRangeOffset, segmentByteRangeLength));
segmentStartTimeUs += (long) (segmentDurationSecs * C.MICROS_PER_SECOND); segmentStartTimeUs += (long) (segmentDurationSecs * C.MICROS_PER_SECOND);
segmentDurationSecs = 0.0; segmentDurationSecs = 0.0;
if (segmentByterangeLength != C.LENGTH_UNSET) { if (segmentByteRangeLength != C.LENGTH_UNSET) {
segmentByterangeOffset += segmentByterangeLength; segmentByteRangeOffset += segmentByteRangeLength;
} }
segmentByterangeLength = C.LENGTH_UNSET; segmentByteRangeLength = C.LENGTH_UNSET;
} else if (line.equals(TAG_ENDLIST)) { } else if (line.equals(TAG_ENDLIST)) {
live = false; live = false;
} }
} }
return new HlsMediaPlaylist(baseUri, mediaSequence, targetDurationSecs, version, live, 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 { private static String parseStringAttr(String line, Pattern pattern) throws ParserException {
......
...@@ -101,7 +101,7 @@ public class DefaultSsChunkSource implements SsChunkSource { ...@@ -101,7 +101,7 @@ public class DefaultSsChunkSource implements SsChunkSource {
trackEncryptionBoxes, nalUnitLengthFieldLength, null, null); trackEncryptionBoxes, nalUnitLengthFieldLength, null, null);
FragmentedMp4Extractor extractor = new FragmentedMp4Extractor( FragmentedMp4Extractor extractor = new FragmentedMp4Extractor(
FragmentedMp4Extractor.FLAG_WORKAROUND_EVERY_VIDEO_FRAME_IS_SYNC_FRAME 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); extractorWrappers[i] = new ChunkExtractorWrapper(extractor, format, false, false);
} }
} }
......
...@@ -160,21 +160,27 @@ public final class TextRenderer extends BaseRenderer implements Callback { ...@@ -160,21 +160,27 @@ public final class TextRenderer extends BaseRenderer implements Callback {
} }
} }
if (nextSubtitle != null && nextSubtitle.timeUs <= positionUs) { if (nextSubtitle != null) {
// Advance to the next subtitle. Sync the next event index and trigger an update. if (nextSubtitle.isEndOfStream()) {
if (subtitle != null) { if (!textRendererNeedsUpdate && getNextEventTime() == Long.MAX_VALUE) {
subtitle.release(); if (subtitle != null) {
} subtitle.release();
subtitle = nextSubtitle; subtitle = null;
nextSubtitle = null; }
if (subtitle.isEndOfStream()) { nextSubtitle.release();
outputStreamEnded = true; nextSubtitle = null;
subtitle.release(); outputStreamEnded = true;
subtitle = null; }
return; } else if (nextSubtitle.timeUs <= positionUs) {
// Advance to the next subtitle. Sync the next event index and trigger an update.
if (subtitle != null) {
subtitle.release();
}
subtitle = nextSubtitle;
nextSubtitle = null;
nextSubtitleEventIndex = subtitle.getNextEventTimeIndex(positionUs);
textRendererNeedsUpdate = true;
} }
nextSubtitleEventIndex = subtitle.getNextEventTimeIndex(positionUs);
textRendererNeedsUpdate = true;
} }
if (textRendererNeedsUpdate) { if (textRendererNeedsUpdate) {
......
...@@ -16,15 +16,31 @@ ...@@ -16,15 +16,31 @@
package com.google.android.exoplayer2.ui; package com.google.android.exoplayer2.ui;
import android.content.Context; import android.content.Context;
import android.content.res.TypedArray;
import android.util.AttributeSet; import android.util.AttributeSet;
import android.widget.FrameLayout; import android.widget.FrameLayout;
import com.google.android.exoplayer2.R;
/** /**
* A {@link FrameLayout} that resizes itself to match a specified aspect ratio. * A {@link FrameLayout} that resizes itself to match a specified aspect ratio.
*/ */
public final class AspectRatioFrameLayout extends FrameLayout { public final class AspectRatioFrameLayout extends FrameLayout {
/** /**
* Either the width or height is decreased to obtain the desired aspect ratio.
*/
public static final int RESIZE_MODE_FIT = 0;
/**
* The width is fixed and the height is increased or decreased to obtain the desired aspect ratio.
*/
public static final int RESIZE_MODE_FIXED_WIDTH = 1;
/**
* The height is fixed and the width is increased or decreased to obtain the desired aspect ratio.
*/
public static final int RESIZE_MODE_FIXED_HEIGHT = 2;
/**
* The {@link FrameLayout} will not resize itself if the fractional difference between its natural * The {@link FrameLayout} will not resize itself if the fractional difference between its natural
* aspect ratio and the requested aspect ratio falls below this threshold. * aspect ratio and the requested aspect ratio falls below this threshold.
* <p> * <p>
...@@ -36,13 +52,24 @@ public final class AspectRatioFrameLayout extends FrameLayout { ...@@ -36,13 +52,24 @@ public final class AspectRatioFrameLayout extends FrameLayout {
private static final float MAX_ASPECT_RATIO_DEFORMATION_FRACTION = 0.01f; private static final float MAX_ASPECT_RATIO_DEFORMATION_FRACTION = 0.01f;
private float videoAspectRatio; private float videoAspectRatio;
private int resizeMode;
public AspectRatioFrameLayout(Context context) { public AspectRatioFrameLayout(Context context) {
super(context); this(context, null);
} }
public AspectRatioFrameLayout(Context context, AttributeSet attrs) { public AspectRatioFrameLayout(Context context, AttributeSet attrs) {
super(context, attrs); super(context, attrs);
resizeMode = RESIZE_MODE_FIT;
if (attrs != null) {
TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.AspectRatioFrameLayout, 0, 0);
try {
resizeMode = a.getInt(R.styleable.AspectRatioFrameLayout_resize_mode, RESIZE_MODE_FIT);
} finally {
a.recycle();
}
}
} }
/** /**
...@@ -57,6 +84,19 @@ public final class AspectRatioFrameLayout extends FrameLayout { ...@@ -57,6 +84,19 @@ public final class AspectRatioFrameLayout extends FrameLayout {
} }
} }
/**
* Sets the resize mode which can be of value {@link #RESIZE_MODE_FIT},
* {@link #RESIZE_MODE_FIXED_HEIGHT} or {@link #RESIZE_MODE_FIXED_WIDTH}.
*
* @param resizeMode The resize mode.
*/
public void setResizeMode(int resizeMode) {
if (this.resizeMode != resizeMode) {
this.resizeMode = resizeMode;
requestLayout();
}
}
@Override @Override
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) { protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
super.onMeasure(widthMeasureSpec, heightMeasureSpec); super.onMeasure(widthMeasureSpec, heightMeasureSpec);
...@@ -74,10 +114,20 @@ public final class AspectRatioFrameLayout extends FrameLayout { ...@@ -74,10 +114,20 @@ public final class AspectRatioFrameLayout extends FrameLayout {
return; return;
} }
if (aspectDeformation > 0) { switch (resizeMode) {
height = (int) (width / videoAspectRatio); case RESIZE_MODE_FIXED_WIDTH:
} else { height = (int) (width / videoAspectRatio);
width = (int) (height * videoAspectRatio); break;
case RESIZE_MODE_FIXED_HEIGHT:
width = (int) (height * videoAspectRatio);
break;
default:
if (aspectDeformation > 0) {
height = (int) (width / videoAspectRatio);
} else {
width = (int) (height * videoAspectRatio);
}
break;
} }
super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY), super.onMeasure(MeasureSpec.makeMeasureSpec(width, MeasureSpec.EXACTLY),
MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY)); MeasureSpec.makeMeasureSpec(height, MeasureSpec.EXACTLY));
......
...@@ -128,11 +128,21 @@ public class PlaybackControlView extends FrameLayout { ...@@ -128,11 +128,21 @@ public class PlaybackControlView extends FrameLayout {
} }
/** /**
* Returns the player currently being controlled by this view, or null if no player is set.
*/
public ExoPlayer getPlayer() {
return player;
}
/**
* Sets the {@link ExoPlayer} to control. * Sets the {@link ExoPlayer} to control.
* *
* @param player the {@code ExoPlayer} to control. * @param player the {@code ExoPlayer} to control.
*/ */
public void setPlayer(ExoPlayer player) { public void setPlayer(ExoPlayer player) {
if (this.player == player) {
return;
}
if (this.player != null) { if (this.player != null) {
this.player.removeListener(componentListener); this.player.removeListener(componentListener);
} }
......
...@@ -63,6 +63,7 @@ public final class SimpleExoPlayerView extends FrameLayout { ...@@ -63,6 +63,7 @@ public final class SimpleExoPlayerView extends FrameLayout {
super(context, attrs, defStyleAttr); super(context, attrs, defStyleAttr);
boolean useTextureView = false; boolean useTextureView = false;
int resizeMode = AspectRatioFrameLayout.RESIZE_MODE_FIT;
if (attrs != null) { if (attrs != null) {
TypedArray a = context.getTheme().obtainStyledAttributes(attrs, TypedArray a = context.getTheme().obtainStyledAttributes(attrs,
R.styleable.SimpleExoPlayerView, 0, 0); R.styleable.SimpleExoPlayerView, 0, 0);
...@@ -70,6 +71,8 @@ public final class SimpleExoPlayerView extends FrameLayout { ...@@ -70,6 +71,8 @@ public final class SimpleExoPlayerView extends FrameLayout {
useController = a.getBoolean(R.styleable.SimpleExoPlayerView_use_controller, useController); useController = a.getBoolean(R.styleable.SimpleExoPlayerView_use_controller, useController);
useTextureView = a.getBoolean(R.styleable.SimpleExoPlayerView_use_texture_view, useTextureView = a.getBoolean(R.styleable.SimpleExoPlayerView_use_texture_view,
useTextureView); useTextureView);
resizeMode = a.getInt(R.styleable.SimpleExoPlayerView_resize_mode,
AspectRatioFrameLayout.RESIZE_MODE_FIT);
} finally { } finally {
a.recycle(); a.recycle();
} }
...@@ -78,6 +81,7 @@ public final class SimpleExoPlayerView extends FrameLayout { ...@@ -78,6 +81,7 @@ public final class SimpleExoPlayerView extends FrameLayout {
LayoutInflater.from(context).inflate(R.layout.exo_simple_player_view, this); LayoutInflater.from(context).inflate(R.layout.exo_simple_player_view, this);
componentListener = new ComponentListener(); componentListener = new ComponentListener();
layout = (AspectRatioFrameLayout) findViewById(R.id.video_frame); layout = (AspectRatioFrameLayout) findViewById(R.id.video_frame);
layout.setResizeMode(resizeMode);
controller = (PlaybackControlView) findViewById(R.id.control); controller = (PlaybackControlView) findViewById(R.id.control);
shutterView = findViewById(R.id.shutter); shutterView = findViewById(R.id.shutter);
subtitleLayout = (SubtitleView) findViewById(R.id.subtitles); subtitleLayout = (SubtitleView) findViewById(R.id.subtitles);
...@@ -94,6 +98,13 @@ public final class SimpleExoPlayerView extends FrameLayout { ...@@ -94,6 +98,13 @@ public final class SimpleExoPlayerView extends FrameLayout {
} }
/** /**
* Returns the player currently set on this view, or null if no player is set.
*/
public SimpleExoPlayer getPlayer() {
return player;
}
/**
* Set the {@link SimpleExoPlayer} to use. The {@link SimpleExoPlayer#setTextOutput} and * Set the {@link SimpleExoPlayer} to use. The {@link SimpleExoPlayer#setTextOutput} and
* {@link SimpleExoPlayer#setVideoListener} method of the player will be called and previous * {@link SimpleExoPlayer#setVideoListener} method of the player will be called and previous
* assignments are overridden. * assignments are overridden.
...@@ -101,6 +112,9 @@ public final class SimpleExoPlayerView extends FrameLayout { ...@@ -101,6 +112,9 @@ public final class SimpleExoPlayerView extends FrameLayout {
* @param player The {@link SimpleExoPlayer} to use. * @param player The {@link SimpleExoPlayer} to use.
*/ */
public void setPlayer(SimpleExoPlayer player) { public void setPlayer(SimpleExoPlayer player) {
if (this.player == player) {
return;
}
if (this.player != null) { if (this.player != null) {
this.player.setTextOutput(null); this.player.setTextOutput(null);
this.player.setVideoListener(null); this.player.setVideoListener(null);
...@@ -120,7 +134,9 @@ public final class SimpleExoPlayerView extends FrameLayout { ...@@ -120,7 +134,9 @@ public final class SimpleExoPlayerView extends FrameLayout {
} else { } else {
shutterView.setVisibility(VISIBLE); shutterView.setVisibility(VISIBLE);
} }
setUseController(useController); if (useController) {
controller.setPlayer(player);
}
} }
/** /**
...@@ -131,6 +147,9 @@ public final class SimpleExoPlayerView extends FrameLayout { ...@@ -131,6 +147,9 @@ public final class SimpleExoPlayerView extends FrameLayout {
* @param useController If {@code false} the playback control is never used. * @param useController If {@code false} the playback control is never used.
*/ */
public void setUseController(boolean useController) { public void setUseController(boolean useController) {
if (this.useController == useController) {
return;
}
this.useController = useController; this.useController = useController;
if (useController) { if (useController) {
controller.setPlayer(player); controller.setPlayer(player);
...@@ -141,6 +160,17 @@ public final class SimpleExoPlayerView extends FrameLayout { ...@@ -141,6 +160,17 @@ public final class SimpleExoPlayerView extends FrameLayout {
} }
/** /**
* Sets the resize mode which can be of value {@link AspectRatioFrameLayout#RESIZE_MODE_FIT},
* {@link AspectRatioFrameLayout#RESIZE_MODE_FIXED_HEIGHT} or
* {@link AspectRatioFrameLayout#RESIZE_MODE_FIXED_WIDTH}.
*
* @param resizeMode The resize mode.
*/
public void setResizeMode(int resizeMode) {
layout.setResizeMode(resizeMode);
}
/**
* Set the {@link PlaybackControlView.VisibilityListener}. * Set the {@link PlaybackControlView.VisibilityListener}.
* *
* @param listener The listener to be notified about visibility changes. * @param listener The listener to be notified about visibility changes.
......
...@@ -104,29 +104,35 @@ public final class AssetDataSource implements DataSource { ...@@ -104,29 +104,35 @@ public final class AssetDataSource implements DataSource {
@Override @Override
public int read(byte[] buffer, int offset, int readLength) throws AssetDataSourceException { public int read(byte[] buffer, int offset, int readLength) throws AssetDataSourceException {
if (bytesRemaining == 0) { if (readLength == 0) {
return 0;
} else if (bytesRemaining == 0) {
return C.RESULT_END_OF_INPUT; return C.RESULT_END_OF_INPUT;
} else { }
int bytesRead;
try {
int bytesToRead = bytesRemaining == C.LENGTH_UNSET ? readLength
: (int) Math.min(bytesRemaining, readLength);
bytesRead = inputStream.read(buffer, offset, bytesToRead);
} catch (IOException e) {
throw new AssetDataSourceException(e);
}
if (bytesRead > 0) { int bytesRead;
if (bytesRemaining != C.LENGTH_UNSET) { try {
bytesRemaining -= bytesRead; int bytesToRead = bytesRemaining == C.LENGTH_UNSET ? readLength
} : (int) Math.min(bytesRemaining, readLength);
if (listener != null) { bytesRead = inputStream.read(buffer, offset, bytesToRead);
listener.onBytesTransferred(this, bytesRead); } catch (IOException e) {
} throw new AssetDataSourceException(e);
} }
return bytesRead; if (bytesRead == -1) {
if (bytesRemaining != C.LENGTH_UNSET) {
// End of stream reached having not read sufficient data.
throw new AssetDataSourceException(new EOFException());
}
return C.RESULT_END_OF_INPUT;
}
if (bytesRemaining != C.LENGTH_UNSET) {
bytesRemaining -= bytesRead;
}
if (listener != null) {
listener.onBytesTransferred(this, bytesRead);
} }
return bytesRead;
} }
@Override @Override
......
...@@ -54,15 +54,18 @@ public final class ByteArrayDataSource implements DataSource { ...@@ -54,15 +54,18 @@ public final class ByteArrayDataSource implements DataSource {
} }
@Override @Override
public int read(byte[] buffer, int offset, int length) throws IOException { public int read(byte[] buffer, int offset, int readLength) throws IOException {
if (bytesRemaining == 0) { if (readLength == 0) {
return 0;
} else if (bytesRemaining == 0) {
return C.RESULT_END_OF_INPUT; return C.RESULT_END_OF_INPUT;
} }
length = Math.min(length, bytesRemaining);
System.arraycopy(data, readPosition, buffer, offset, length); readLength = Math.min(readLength, bytesRemaining);
readPosition += length; System.arraycopy(data, readPosition, buffer, offset, readLength);
bytesRemaining -= length; readPosition += readLength;
return length; bytesRemaining -= readLength;
return readLength;
} }
@Override @Override
......
...@@ -103,29 +103,35 @@ public final class ContentDataSource implements DataSource { ...@@ -103,29 +103,35 @@ public final class ContentDataSource implements DataSource {
@Override @Override
public int read(byte[] buffer, int offset, int readLength) throws ContentDataSourceException { public int read(byte[] buffer, int offset, int readLength) throws ContentDataSourceException {
if (bytesRemaining == 0) { if (readLength == 0) {
return 0;
} else if (bytesRemaining == 0) {
return C.RESULT_END_OF_INPUT; return C.RESULT_END_OF_INPUT;
} else { }
int bytesRead;
try {
int bytesToRead = bytesRemaining == C.LENGTH_UNSET ? readLength
: (int) Math.min(bytesRemaining, readLength);
bytesRead = inputStream.read(buffer, offset, bytesToRead);
} catch (IOException e) {
throw new ContentDataSourceException(e);
}
if (bytesRead > 0) { int bytesRead;
if (bytesRemaining != C.LENGTH_UNSET) { try {
bytesRemaining -= bytesRead; int bytesToRead = bytesRemaining == C.LENGTH_UNSET ? readLength
} : (int) Math.min(bytesRemaining, readLength);
if (listener != null) { bytesRead = inputStream.read(buffer, offset, bytesToRead);
listener.onBytesTransferred(this, bytesRead); } catch (IOException e) {
} throw new ContentDataSourceException(e);
} }
return bytesRead; if (bytesRead == -1) {
if (bytesRemaining != C.LENGTH_UNSET) {
// End of stream reached having not read sufficient data.
throw new ContentDataSourceException(new EOFException());
}
return C.RESULT_END_OF_INPUT;
}
if (bytesRemaining != C.LENGTH_UNSET) {
bytesRemaining -= bytesRead;
}
if (listener != null) {
listener.onBytesTransferred(this, bytesRead);
} }
return bytesRead;
} }
@Override @Override
......
...@@ -55,14 +55,18 @@ public interface DataSource { ...@@ -55,14 +55,18 @@ public interface DataSource {
/** /**
* Reads up to {@code length} bytes of data and stores them into {@code buffer}, starting at * Reads up to {@code length} bytes of data and stores them into {@code buffer}, starting at
* index {@code offset}. Blocks until at least one byte of data can be read, the end of the opened * index {@code offset}.
* range is detected, or an exception is thrown. * <p>
* If {@code length} is zero then 0 is returned. Otherwise, if no data is available because the
* end of the opened range has been reached, then {@link C#RESULT_END_OF_INPUT} is returned.
* Otherwise, the call will block until at least one byte of data has been read and the number of
* bytes read is returned.
* *
* @param buffer The buffer into which the read data should be stored. * @param buffer The buffer into which the read data should be stored.
* @param offset The start offset into {@code buffer} at which data should be written. * @param offset The start offset into {@code buffer} at which data should be written.
* @param readLength The maximum number of bytes to read. * @param readLength The maximum number of bytes to read.
* @return The number of bytes read, or {@link C#RESULT_END_OF_INPUT} if the end of the opened * @return The number of bytes read, or {@link C#RESULT_END_OF_INPUT} if no data is avaliable
* range is reached. * because the end of the opened range has been reached.
* @throws IOException If an error occurs reading from the source. * @throws IOException If an error occurs reading from the source.
*/ */
int read(byte[] buffer, int offset, int readLength) throws IOException; int read(byte[] buffer, int offset, int readLength) throws IOException;
......
...@@ -26,6 +26,7 @@ public final class DefaultAllocator implements Allocator { ...@@ -26,6 +26,7 @@ public final class DefaultAllocator implements Allocator {
private static final int AVAILABLE_EXTRA_CAPACITY = 100; private static final int AVAILABLE_EXTRA_CAPACITY = 100;
private final boolean trimOnReset;
private final int individualAllocationSize; private final int individualAllocationSize;
private final byte[] initialAllocationBlock; private final byte[] initialAllocationBlock;
private final Allocation[] singleAllocationReleaseHolder; private final Allocation[] singleAllocationReleaseHolder;
...@@ -38,10 +39,12 @@ public final class DefaultAllocator implements Allocator { ...@@ -38,10 +39,12 @@ public final class DefaultAllocator implements Allocator {
/** /**
* Constructs an instance without creating any {@link Allocation}s up front. * Constructs an instance without creating any {@link Allocation}s up front.
* *
* @param trimOnReset Whether memory is freed when the allocator is reset. Should be true unless
* the allocator will be re-used by multiple player instances.
* @param individualAllocationSize The length of each individual {@link Allocation}. * @param individualAllocationSize The length of each individual {@link Allocation}.
*/ */
public DefaultAllocator(int individualAllocationSize) { public DefaultAllocator(boolean trimOnReset, int individualAllocationSize) {
this(individualAllocationSize, 0); this(trimOnReset, individualAllocationSize, 0);
} }
/** /**
...@@ -49,12 +52,16 @@ public final class DefaultAllocator implements Allocator { ...@@ -49,12 +52,16 @@ public final class DefaultAllocator implements Allocator {
* <p> * <p>
* Note: {@link Allocation}s created up front will never be discarded by {@link #trim()}. * Note: {@link Allocation}s created up front will never be discarded by {@link #trim()}.
* *
* @param trimOnReset Whether memory is freed when the allocator is reset. Should be true unless
* the allocator will be re-used by multiple player instances.
* @param individualAllocationSize The length of each individual {@link Allocation}. * @param individualAllocationSize The length of each individual {@link Allocation}.
* @param initialAllocationCount The number of allocations to create up front. * @param initialAllocationCount The number of allocations to create up front.
*/ */
public DefaultAllocator(int individualAllocationSize, int initialAllocationCount) { public DefaultAllocator(boolean trimOnReset, int individualAllocationSize,
int initialAllocationCount) {
Assertions.checkArgument(individualAllocationSize > 0); Assertions.checkArgument(individualAllocationSize > 0);
Assertions.checkArgument(initialAllocationCount >= 0); Assertions.checkArgument(initialAllocationCount >= 0);
this.trimOnReset = trimOnReset;
this.individualAllocationSize = individualAllocationSize; this.individualAllocationSize = individualAllocationSize;
this.availableCount = initialAllocationCount; this.availableCount = initialAllocationCount;
this.availableAllocations = new Allocation[initialAllocationCount + AVAILABLE_EXTRA_CAPACITY]; this.availableAllocations = new Allocation[initialAllocationCount + AVAILABLE_EXTRA_CAPACITY];
...@@ -70,6 +77,12 @@ public final class DefaultAllocator implements Allocator { ...@@ -70,6 +77,12 @@ public final class DefaultAllocator implements Allocator {
singleAllocationReleaseHolder = new Allocation[1]; singleAllocationReleaseHolder = new Allocation[1];
} }
public synchronized void reset() {
if (trimOnReset) {
setTargetBufferSize(0);
}
}
public synchronized void setTargetBufferSize(int targetBufferSize) { public synchronized void setTargetBufferSize(int targetBufferSize) {
boolean targetBufferSizeReduced = targetBufferSize < this.targetBufferSize; boolean targetBufferSizeReduced = targetBufferSize < this.targetBufferSize;
this.targetBufferSize = targetBufferSize; this.targetBufferSize = targetBufferSize;
......
...@@ -550,11 +550,18 @@ public class DefaultHttpDataSource implements HttpDataSource { ...@@ -550,11 +550,18 @@ public class DefaultHttpDataSource implements HttpDataSource {
if (readLength == 0) { if (readLength == 0) {
return 0; return 0;
} }
if (bytesToRead != C.LENGTH_UNSET) {
long bytesRemaining = bytesToRead - bytesRead;
if (bytesRemaining == 0) {
return C.RESULT_END_OF_INPUT;
}
readLength = (int) Math.min(readLength, bytesRemaining);
}
int read = inputStream.read(buffer, offset, readLength); int read = inputStream.read(buffer, offset, readLength);
if (read == -1) { if (read == -1) {
if (bytesToRead != C.LENGTH_UNSET && bytesToRead != bytesRead) { if (bytesToRead != C.LENGTH_UNSET) {
// The server closed the connection having not sent sufficient data. // End of stream reached having not read sufficient data.
throw new EOFException(); throw new EOFException();
} }
return C.RESULT_END_OF_INPUT; return C.RESULT_END_OF_INPUT;
......
...@@ -80,7 +80,9 @@ public final class FileDataSource implements DataSource { ...@@ -80,7 +80,9 @@ public final class FileDataSource implements DataSource {
@Override @Override
public int read(byte[] buffer, int offset, int readLength) throws FileDataSourceException { public int read(byte[] buffer, int offset, int readLength) throws FileDataSourceException {
if (bytesRemaining == 0) { if (readLength == 0) {
return 0;
} else if (bytesRemaining == 0) {
return C.RESULT_END_OF_INPUT; return C.RESULT_END_OF_INPUT;
} else { } else {
int bytesRead; int bytesRead;
......
...@@ -132,29 +132,35 @@ public final class RawResourceDataSource implements DataSource { ...@@ -132,29 +132,35 @@ public final class RawResourceDataSource implements DataSource {
@Override @Override
public int read(byte[] buffer, int offset, int readLength) throws RawResourceDataSourceException { public int read(byte[] buffer, int offset, int readLength) throws RawResourceDataSourceException {
if (bytesRemaining == 0) { if (readLength == 0) {
return 0;
} else if (bytesRemaining == 0) {
return C.RESULT_END_OF_INPUT; return C.RESULT_END_OF_INPUT;
} else { }
int bytesRead;
try {
int bytesToRead = bytesRemaining == C.LENGTH_UNSET ? readLength
: (int) Math.min(bytesRemaining, readLength);
bytesRead = inputStream.read(buffer, offset, bytesToRead);
} catch (IOException e) {
throw new RawResourceDataSourceException(e);
}
if (bytesRead > 0) { int bytesRead;
if (bytesRemaining != C.LENGTH_UNSET) { try {
bytesRemaining -= bytesRead; int bytesToRead = bytesRemaining == C.LENGTH_UNSET ? readLength
} : (int) Math.min(bytesRemaining, readLength);
if (listener != null) { bytesRead = inputStream.read(buffer, offset, bytesToRead);
listener.onBytesTransferred(this, bytesRead); } catch (IOException e) {
} throw new RawResourceDataSourceException(e);
} }
return bytesRead; if (bytesRead == -1) {
if (bytesRemaining != C.LENGTH_UNSET) {
// End of stream reached having not read sufficient data.
throw new RawResourceDataSourceException(new EOFException());
}
return C.RESULT_END_OF_INPUT;
}
if (bytesRemaining != C.LENGTH_UNSET) {
bytesRemaining -= bytesRead;
}
if (listener != null) {
listener.onBytesTransferred(this, bytesRead);
} }
return bytesRead;
} }
@Override @Override
......
...@@ -129,6 +129,10 @@ public final class UdpDataSource implements DataSource { ...@@ -129,6 +129,10 @@ public final class UdpDataSource implements DataSource {
@Override @Override
public int read(byte[] buffer, int offset, int readLength) throws UdpDataSourceException { public int read(byte[] buffer, int offset, int readLength) throws UdpDataSourceException {
if (readLength == 0) {
return 0;
}
if (packetRemaining == 0) { if (packetRemaining == 0) {
// We've read all of the data from the current packet. Get another. // We've read all of the data from the current packet. Get another.
try { try {
...@@ -136,7 +140,6 @@ public final class UdpDataSource implements DataSource { ...@@ -136,7 +140,6 @@ public final class UdpDataSource implements DataSource {
} catch (IOException e) { } catch (IOException e) {
throw new UdpDataSourceException(e); throw new UdpDataSourceException(e);
} }
packetRemaining = packet.getLength(); packetRemaining = packet.getLength();
if (listener != null) { if (listener != null) {
listener.onBytesTransferred(this, packetRemaining); listener.onBytesTransferred(this, packetRemaining);
......
...@@ -194,12 +194,15 @@ public final class CacheDataSource implements DataSource { ...@@ -194,12 +194,15 @@ public final class CacheDataSource implements DataSource {
} }
@Override @Override
public int read(byte[] buffer, int offset, int max) throws IOException { public int read(byte[] buffer, int offset, int readLength) throws IOException {
if (readLength == 0) {
return 0;
}
if (bytesRemaining == 0) { if (bytesRemaining == 0) {
return C.RESULT_END_OF_INPUT; return C.RESULT_END_OF_INPUT;
} }
try { try {
int bytesRead = currentDataSource.read(buffer, offset, max); int bytesRead = currentDataSource.read(buffer, offset, readLength);
if (bytesRead >= 0) { if (bytesRead >= 0) {
if (currentDataSource == cacheReadDataSource) { if (currentDataSource == cacheReadDataSource) {
totalCachedBytesRead += bytesRead; totalCachedBytesRead += bytesRead;
...@@ -218,7 +221,7 @@ public final class CacheDataSource implements DataSource { ...@@ -218,7 +221,7 @@ public final class CacheDataSource implements DataSource {
closeCurrentSource(); closeCurrentSource();
if (bytesRemaining > 0 || bytesRemaining == C.LENGTH_UNSET) { if (bytesRemaining > 0 || bytesRemaining == C.LENGTH_UNSET) {
if (openNextSource(false)) { if (openNextSource(false)) {
return read(buffer, offset, max); return read(buffer, offset, readLength);
} }
} }
} }
......
...@@ -22,17 +22,17 @@ ...@@ -22,17 +22,17 @@
android:layout_height="match_parent" android:layout_height="match_parent"
android:layout_gravity="center"> android:layout_gravity="center">
<View android:id="@+id/shutter"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"/>
<com.google.android.exoplayer2.ui.SubtitleView android:id="@+id/subtitles" <com.google.android.exoplayer2.ui.SubtitleView android:id="@+id/subtitles"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"/> android:layout_height="match_parent"/>
</com.google.android.exoplayer2.ui.AspectRatioFrameLayout> </com.google.android.exoplayer2.ui.AspectRatioFrameLayout>
<View android:id="@+id/shutter"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@android:color/black"/>
<com.google.android.exoplayer2.ui.PlaybackControlView android:id="@+id/control" <com.google.android.exoplayer2.ui.PlaybackControlView android:id="@+id/control"
android:layout_width="match_parent" android:layout_width="match_parent"
android:layout_height="match_parent"/> android:layout_height="match_parent"/>
......
...@@ -14,8 +14,21 @@ ...@@ -14,8 +14,21 @@
limitations under the License. limitations under the License.
--> -->
<resources> <resources>
<attr name="resize_mode" format="enum">
<enum name="fit" value="0"/>
<enum name="fixed_width" value="1"/>
<enum name="fixed_height" value="2"/>
</attr>
<declare-styleable name="SimpleExoPlayerView"> <declare-styleable name="SimpleExoPlayerView">
<attr name="use_controller" format="boolean"/> <attr name="use_controller" format="boolean"/>
<attr name="use_texture_view" format="boolean"/> <attr name="use_texture_view" format="boolean"/>
<attr name="resize_mode"/>
</declare-styleable> </declare-styleable>
<declare-styleable name="AspectRatioFrameLayout">
<attr name="resize_mode"/>
</declare-styleable>
</resources> </resources>
...@@ -17,8 +17,8 @@ ...@@ -17,8 +17,8 @@
<manifest xmlns:android="http://schemas.android.com/apk/res/android" <manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools" xmlns:tools="http://schemas.android.com/tools"
package="com.google.android.exoplayer2.playbacktests" package="com.google.android.exoplayer2.playbacktests"
android:versionCode="2001" android:versionCode="2002"
android:versionName="2.0.1"> android:versionName="2.0.2">
<uses-permission android:name="android.permission.INTERNET"/> <uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.WAKE_LOCK"/> <uses-permission android:name="android.permission.WAKE_LOCK"/>
......
...@@ -19,6 +19,8 @@ import android.annotation.TargetApi; ...@@ -19,6 +19,8 @@ import android.annotation.TargetApi;
import android.media.MediaDrm; import android.media.MediaDrm;
import android.media.UnsupportedSchemeException; import android.media.UnsupportedSchemeException;
import android.net.Uri; import android.net.Uri;
import android.os.Handler;
import android.os.Looper;
import android.test.ActivityInstrumentationTestCase2; import android.test.ActivityInstrumentationTestCase2;
import android.util.Log; import android.util.Log;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
...@@ -801,7 +803,7 @@ public final class DashTest extends ActivityInstrumentationTestCase2<HostActivit ...@@ -801,7 +803,7 @@ public final class DashTest extends ActivityInstrumentationTestCase2<HostActivit
private DashTestTrackSelector(String audioFormatId, String[] videoFormatIds, private DashTestTrackSelector(String audioFormatId, String[] videoFormatIds,
boolean canIncludeAdditionalVideoFormats) { boolean canIncludeAdditionalVideoFormats) {
super(null); super(new Handler(Looper.getMainLooper()));
this.audioFormatId = audioFormatId; this.audioFormatId = audioFormatId;
this.videoFormatIds = videoFormatIds; this.videoFormatIds = videoFormatIds;
this.canIncludeAdditionalVideoFormats = canIncludeAdditionalVideoFormats; this.canIncludeAdditionalVideoFormats = canIncludeAdditionalVideoFormats;
......
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