mirror of
https://github.com/androidx/media.git
synced 2025-05-15 11:39:56 +08:00
commit
9f81485053
@ -1,5 +1,28 @@
|
|||||||
# Release notes #
|
# Release notes #
|
||||||
|
|
||||||
|
### r2.4.1 ###
|
||||||
|
|
||||||
|
* Stability: Avoid OutOfMemoryError in extractors when parsing malformed media
|
||||||
|
([#2780](https://github.com/google/ExoPlayer/issues/2780)).
|
||||||
|
* Stability: Avoid native crash on Galaxy Nexus. Avoid unnecessarily large codec
|
||||||
|
input buffer allocations on all devices
|
||||||
|
([#2607](https://github.com/google/ExoPlayer/issues/2607)).
|
||||||
|
* Variable speed playback: Fix interpolation for rate/pitch adjustment
|
||||||
|
([#2774](https://github.com/google/ExoPlayer/issues/2774)).
|
||||||
|
* HLS: Include EXT-X-DATERANGE tags in HlsMediaPlaylist.
|
||||||
|
* HLS: Don't expose CEA-608 track if CLOSED-CAPTIONS=NONE
|
||||||
|
([#2743](https://github.com/google/ExoPlayer/issues/2743)).
|
||||||
|
* HLS: Correctly propagate errors loading the media playlist
|
||||||
|
([#2623](https://github.com/google/ExoPlayer/issues/2623)).
|
||||||
|
* UI: DefaultTimeBar enhancements and bug fixes
|
||||||
|
([#2740](https://github.com/google/ExoPlayer/issues/2740)).
|
||||||
|
* Ogg: Fix failure to play some Ogg files
|
||||||
|
([#2782](https://github.com/google/ExoPlayer/issues/2782)).
|
||||||
|
* Captions: Don't select text tack with no language by default.
|
||||||
|
* Captions: TTML positioning fixes
|
||||||
|
([#2824](https://github.com/google/ExoPlayer/issues/2824)).
|
||||||
|
* Misc bugfixes.
|
||||||
|
|
||||||
### r2.4.0 ###
|
### r2.4.0 ###
|
||||||
|
|
||||||
* New modular library structure. You can read more about depending on individual
|
* New modular library structure. You can read more about depending on individual
|
||||||
|
@ -16,7 +16,7 @@ buildscript {
|
|||||||
jcenter()
|
jcenter()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:2.3.0'
|
classpath 'com.android.tools.build:gradle:2.3.1'
|
||||||
classpath 'com.novoda:bintray-release:0.4.0'
|
classpath 'com.novoda:bintray-release:0.4.0'
|
||||||
}
|
}
|
||||||
// Workaround for the following test coverage issue. Remove when fixed:
|
// Workaround for the following test coverage issue. Remove when fixed:
|
||||||
@ -48,7 +48,7 @@ allprojects {
|
|||||||
releaseRepoName = getBintrayRepo()
|
releaseRepoName = getBintrayRepo()
|
||||||
releaseUserOrg = 'google'
|
releaseUserOrg = 'google'
|
||||||
releaseGroupId = 'com.google.android.exoplayer'
|
releaseGroupId = 'com.google.android.exoplayer'
|
||||||
releaseVersion = 'r2.4.0'
|
releaseVersion = 'r2.4.1'
|
||||||
releaseWebsite = 'https://github.com/google/ExoPlayer'
|
releaseWebsite = 'https://github.com/google/ExoPlayer'
|
||||||
}
|
}
|
||||||
if (it.hasProperty('externalBuildDir')) {
|
if (it.hasProperty('externalBuildDir')) {
|
||||||
|
@ -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="2400"
|
android:versionCode="2401"
|
||||||
android:versionName="2.4.0">
|
android:versionName="2.4.1">
|
||||||
|
|
||||||
<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"/>
|
||||||
|
@ -234,7 +234,6 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
|
|||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
boolean needNewPlayer = player == null;
|
boolean needNewPlayer = player == null;
|
||||||
if (needNewPlayer) {
|
if (needNewPlayer) {
|
||||||
boolean preferExtensionDecoders = intent.getBooleanExtra(PREFER_EXTENSION_DECODERS, false);
|
|
||||||
UUID drmSchemeUuid = intent.hasExtra(DRM_SCHEME_UUID_EXTRA)
|
UUID drmSchemeUuid = intent.hasExtra(DRM_SCHEME_UUID_EXTRA)
|
||||||
? UUID.fromString(intent.getStringExtra(DRM_SCHEME_UUID_EXTRA)) : null;
|
? UUID.fromString(intent.getStringExtra(DRM_SCHEME_UUID_EXTRA)) : null;
|
||||||
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager = null;
|
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager = null;
|
||||||
@ -253,6 +252,7 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
boolean preferExtensionDecoders = intent.getBooleanExtra(PREFER_EXTENSION_DECODERS, false);
|
||||||
@DefaultRenderersFactory.ExtensionRendererMode int extensionRendererMode =
|
@DefaultRenderersFactory.ExtensionRendererMode int extensionRendererMode =
|
||||||
((DemoApplication) getApplication()).useExtensionRenderers()
|
((DemoApplication) getApplication()).useExtensionRenderers()
|
||||||
? (preferExtensionDecoders ? DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER
|
? (preferExtensionDecoders ? DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER
|
||||||
@ -261,10 +261,10 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
|
|||||||
DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(this,
|
DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(this,
|
||||||
drmSessionManager, extensionRendererMode);
|
drmSessionManager, extensionRendererMode);
|
||||||
|
|
||||||
TrackSelection.Factory videoTrackSelectionFactory =
|
TrackSelection.Factory adaptiveTrackSelectionFactory =
|
||||||
new AdaptiveTrackSelection.Factory(BANDWIDTH_METER);
|
new AdaptiveTrackSelection.Factory(BANDWIDTH_METER);
|
||||||
trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
|
trackSelector = new DefaultTrackSelector(adaptiveTrackSelectionFactory);
|
||||||
trackSelectionHelper = new TrackSelectionHelper(trackSelector, videoTrackSelectionFactory);
|
trackSelectionHelper = new TrackSelectionHelper(trackSelector, adaptiveTrackSelectionFactory);
|
||||||
lastSeenTrackGroupArray = null;
|
lastSeenTrackGroupArray = null;
|
||||||
|
|
||||||
player = ExoPlayerFactory.newSimpleInstance(renderersFactory, trackSelector);
|
player = ExoPlayerFactory.newSimpleInstance(renderersFactory, trackSelector);
|
||||||
|
@ -286,7 +286,7 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
|
|||||||
*
|
*
|
||||||
* @param outputBufferTimeUs The timestamp of the current output buffer.
|
* @param outputBufferTimeUs The timestamp of the current output buffer.
|
||||||
* @param nextOutputBufferTimeUs The timestamp of the next output buffer or
|
* @param nextOutputBufferTimeUs The timestamp of the next output buffer or
|
||||||
* {@link TIME_UNSET} if the next output buffer is unavailable.
|
* {@link C#TIME_UNSET} if the next output buffer is unavailable.
|
||||||
* @param positionUs The current playback position.
|
* @param positionUs The current playback position.
|
||||||
* @param joiningDeadlineMs The joining deadline.
|
* @param joiningDeadlineMs The joining deadline.
|
||||||
* @return Returns whether to drop the current output buffer.
|
* @return Returns whether to drop the current output buffer.
|
||||||
|
@ -51,8 +51,7 @@ config[3]+=" --disable-sse3 --disable-ssse3 --disable-sse4_1 --disable-avx"
|
|||||||
config[3]+=" --disable-avx2 --enable-pic"
|
config[3]+=" --disable-avx2 --enable-pic"
|
||||||
|
|
||||||
arch[4]="arm64-v8a"
|
arch[4]="arm64-v8a"
|
||||||
config[4]="--force-target=armv8-android-gcc --sdk-path=$ndk --disable-neon"
|
config[4]="--force-target=armv8-android-gcc --sdk-path=$ndk --enable-neon"
|
||||||
config[4]+=" --disable-neon-asm"
|
|
||||||
|
|
||||||
arch[5]="x86_64"
|
arch[5]="x86_64"
|
||||||
config[5]="--force-target=x86_64-android-gcc --sdk-path=$ndk --disable-sse2"
|
config[5]="--force-target=x86_64-android-gcc --sdk-path=$ndk --disable-sse2"
|
||||||
|
@ -9,7 +9,7 @@ track 0:
|
|||||||
id = null
|
id = null
|
||||||
containerMimeType = null
|
containerMimeType = null
|
||||||
sampleMimeType = audio/vorbis
|
sampleMimeType = audio/vorbis
|
||||||
maxInputSize = 65025
|
maxInputSize = -1
|
||||||
width = -1
|
width = -1
|
||||||
height = -1
|
height = -1
|
||||||
frameRate = -1.0
|
frameRate = -1.0
|
||||||
|
@ -9,7 +9,7 @@ track 0:
|
|||||||
id = null
|
id = null
|
||||||
containerMimeType = null
|
containerMimeType = null
|
||||||
sampleMimeType = audio/vorbis
|
sampleMimeType = audio/vorbis
|
||||||
maxInputSize = 65025
|
maxInputSize = -1
|
||||||
width = -1
|
width = -1
|
||||||
height = -1
|
height = -1
|
||||||
frameRate = -1.0
|
frameRate = -1.0
|
||||||
|
@ -9,7 +9,7 @@ track 0:
|
|||||||
id = null
|
id = null
|
||||||
containerMimeType = null
|
containerMimeType = null
|
||||||
sampleMimeType = audio/vorbis
|
sampleMimeType = audio/vorbis
|
||||||
maxInputSize = 65025
|
maxInputSize = -1
|
||||||
width = -1
|
width = -1
|
||||||
height = -1
|
height = -1
|
||||||
frameRate = -1.0
|
frameRate = -1.0
|
||||||
|
@ -9,7 +9,7 @@ track 0:
|
|||||||
id = null
|
id = null
|
||||||
containerMimeType = null
|
containerMimeType = null
|
||||||
sampleMimeType = audio/vorbis
|
sampleMimeType = audio/vorbis
|
||||||
maxInputSize = 65025
|
maxInputSize = -1
|
||||||
width = -1
|
width = -1
|
||||||
height = -1
|
height = -1
|
||||||
frameRate = -1.0
|
frameRate = -1.0
|
||||||
|
@ -9,7 +9,7 @@ track 0:
|
|||||||
id = null
|
id = null
|
||||||
containerMimeType = null
|
containerMimeType = null
|
||||||
sampleMimeType = audio/vorbis
|
sampleMimeType = audio/vorbis
|
||||||
maxInputSize = 65025
|
maxInputSize = -1
|
||||||
width = -1
|
width = -1
|
||||||
height = -1
|
height = -1
|
||||||
frameRate = -1.0
|
frameRate = -1.0
|
||||||
|
@ -75,7 +75,7 @@ public final class TsExtractorTest extends InstrumentationTestCase {
|
|||||||
|
|
||||||
public void testCustomPesReader() throws Exception {
|
public void testCustomPesReader() throws Exception {
|
||||||
CustomTsPayloadReaderFactory factory = new CustomTsPayloadReaderFactory(true, false);
|
CustomTsPayloadReaderFactory factory = new CustomTsPayloadReaderFactory(true, false);
|
||||||
TsExtractor tsExtractor = new TsExtractor(TsExtractor.MODE_NORMAL, new TimestampAdjuster(0),
|
TsExtractor tsExtractor = new TsExtractor(TsExtractor.MODE_MULTI_PMT, new TimestampAdjuster(0),
|
||||||
factory);
|
factory);
|
||||||
FakeExtractorInput input = new FakeExtractorInput.Builder()
|
FakeExtractorInput input = new FakeExtractorInput.Builder()
|
||||||
.setData(TestUtil.getByteArray(getInstrumentation(), "ts/sample.ts"))
|
.setData(TestUtil.getByteArray(getInstrumentation(), "ts/sample.ts"))
|
||||||
@ -100,7 +100,7 @@ public final class TsExtractorTest extends InstrumentationTestCase {
|
|||||||
|
|
||||||
public void testCustomInitialSectionReader() throws Exception {
|
public void testCustomInitialSectionReader() throws Exception {
|
||||||
CustomTsPayloadReaderFactory factory = new CustomTsPayloadReaderFactory(false, true);
|
CustomTsPayloadReaderFactory factory = new CustomTsPayloadReaderFactory(false, true);
|
||||||
TsExtractor tsExtractor = new TsExtractor(TsExtractor.MODE_NORMAL, new TimestampAdjuster(0),
|
TsExtractor tsExtractor = new TsExtractor(TsExtractor.MODE_MULTI_PMT, new TimestampAdjuster(0),
|
||||||
factory);
|
factory);
|
||||||
FakeExtractorInput input = new FakeExtractorInput.Builder()
|
FakeExtractorInput input = new FakeExtractorInput.Builder()
|
||||||
.setData(TestUtil.getByteArray(getInstrumentation(), "ts/sample_with_sdt.ts"))
|
.setData(TestUtil.getByteArray(getInstrumentation(), "ts/sample_with_sdt.ts"))
|
||||||
|
@ -157,39 +157,39 @@ public final class TtmlDecoderTest extends InstrumentationTestCase {
|
|||||||
assertEquals(2, output.size());
|
assertEquals(2, output.size());
|
||||||
Cue ttmlCue = output.get(0);
|
Cue ttmlCue = output.get(0);
|
||||||
assertEquals("lorem", ttmlCue.text.toString());
|
assertEquals("lorem", ttmlCue.text.toString());
|
||||||
assertEquals(10.f / 100.f, ttmlCue.position);
|
assertEquals(10f / 100f, ttmlCue.position);
|
||||||
assertEquals(10.f / 100.f, ttmlCue.line);
|
assertEquals(10f / 100f, ttmlCue.line);
|
||||||
assertEquals(20.f / 100.f, ttmlCue.size);
|
assertEquals(20f / 100f, ttmlCue.size);
|
||||||
|
|
||||||
ttmlCue = output.get(1);
|
ttmlCue = output.get(1);
|
||||||
assertEquals("amet", ttmlCue.text.toString());
|
assertEquals("amet", ttmlCue.text.toString());
|
||||||
assertEquals(60.f / 100.f, ttmlCue.position);
|
assertEquals(60f / 100f, ttmlCue.position);
|
||||||
assertEquals(10.f / 100.f, ttmlCue.line);
|
assertEquals(10f / 100f, ttmlCue.line);
|
||||||
assertEquals(20.f / 100.f, ttmlCue.size);
|
assertEquals(20f / 100f, ttmlCue.size);
|
||||||
|
|
||||||
output = subtitle.getCues(5000000);
|
output = subtitle.getCues(5000000);
|
||||||
assertEquals(1, output.size());
|
assertEquals(1, output.size());
|
||||||
ttmlCue = output.get(0);
|
ttmlCue = output.get(0);
|
||||||
assertEquals("ipsum", ttmlCue.text.toString());
|
assertEquals("ipsum", ttmlCue.text.toString());
|
||||||
assertEquals(40.f / 100.f, ttmlCue.position);
|
assertEquals(40f / 100f, ttmlCue.position);
|
||||||
assertEquals(40.f / 100.f, ttmlCue.line);
|
assertEquals(40f / 100f, ttmlCue.line);
|
||||||
assertEquals(20.f / 100.f, ttmlCue.size);
|
assertEquals(20f / 100f, ttmlCue.size);
|
||||||
|
|
||||||
output = subtitle.getCues(9000000);
|
output = subtitle.getCues(9000000);
|
||||||
assertEquals(1, output.size());
|
assertEquals(1, output.size());
|
||||||
ttmlCue = output.get(0);
|
ttmlCue = output.get(0);
|
||||||
assertEquals("dolor", ttmlCue.text.toString());
|
assertEquals("dolor", ttmlCue.text.toString());
|
||||||
assertEquals(10.f / 100.f, ttmlCue.position);
|
assertEquals(10f / 100f, ttmlCue.position);
|
||||||
assertEquals(80.f / 100.f, ttmlCue.line);
|
assertEquals(80f / 100f, ttmlCue.line);
|
||||||
assertEquals(Cue.DIMEN_UNSET, ttmlCue.size);
|
assertEquals(1f, ttmlCue.size);
|
||||||
|
|
||||||
output = subtitle.getCues(21000000);
|
output = subtitle.getCues(21000000);
|
||||||
assertEquals(1, output.size());
|
assertEquals(1, output.size());
|
||||||
ttmlCue = output.get(0);
|
ttmlCue = output.get(0);
|
||||||
assertEquals("She first said this", ttmlCue.text.toString());
|
assertEquals("She first said this", ttmlCue.text.toString());
|
||||||
assertEquals(45.f / 100.f, ttmlCue.position);
|
assertEquals(45f / 100f, ttmlCue.position);
|
||||||
assertEquals(45.f / 100.f, ttmlCue.line);
|
assertEquals(45f / 100f, ttmlCue.line);
|
||||||
assertEquals(35.f / 100.f, ttmlCue.size);
|
assertEquals(35f / 100f, ttmlCue.size);
|
||||||
output = subtitle.getCues(25000000);
|
output = subtitle.getCues(25000000);
|
||||||
ttmlCue = output.get(0);
|
ttmlCue = output.get(0);
|
||||||
assertEquals("She first said this\nThen this", ttmlCue.text.toString());
|
assertEquals("She first said this\nThen this", ttmlCue.text.toString());
|
||||||
@ -197,8 +197,8 @@ public final class TtmlDecoderTest extends InstrumentationTestCase {
|
|||||||
assertEquals(1, output.size());
|
assertEquals(1, output.size());
|
||||||
ttmlCue = output.get(0);
|
ttmlCue = output.get(0);
|
||||||
assertEquals("She first said this\nThen this\nFinally this", ttmlCue.text.toString());
|
assertEquals("She first said this\nThen this\nFinally this", ttmlCue.text.toString());
|
||||||
assertEquals(45.f / 100.f, ttmlCue.position);
|
assertEquals(45f / 100f, ttmlCue.position);
|
||||||
assertEquals(45.f / 100.f, ttmlCue.line);
|
assertEquals(45f / 100f, ttmlCue.line);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void testEmptyStyleAttribute() throws IOException, SubtitleDecoderException {
|
public void testEmptyStyleAttribute() throws IOException, SubtitleDecoderException {
|
||||||
|
@ -142,9 +142,9 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
|
|||||||
public final void disable() {
|
public final void disable() {
|
||||||
Assertions.checkState(state == STATE_ENABLED);
|
Assertions.checkState(state == STATE_ENABLED);
|
||||||
state = STATE_DISABLED;
|
state = STATE_DISABLED;
|
||||||
onDisabled();
|
|
||||||
stream = null;
|
stream = null;
|
||||||
streamIsFinal = false;
|
streamIsFinal = false;
|
||||||
|
onDisabled();
|
||||||
}
|
}
|
||||||
|
|
||||||
// RendererCapabilities implementation.
|
// RendererCapabilities implementation.
|
||||||
@ -300,8 +300,6 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the upstream source is ready.
|
* Returns whether the upstream source is ready.
|
||||||
*
|
|
||||||
* @return Whether the source is ready.
|
|
||||||
*/
|
*/
|
||||||
protected final boolean isSourceReady() {
|
protected final boolean isSourceReady() {
|
||||||
return readEndOfStream ? streamIsFinal : stream.isReady();
|
return readEndOfStream ? streamIsFinal : stream.isReady();
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2;
|
package com.google.android.exoplayer2;
|
||||||
|
|
||||||
|
import android.os.Looper;
|
||||||
|
import android.support.annotation.IntDef;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer;
|
import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer;
|
||||||
import com.google.android.exoplayer2.metadata.MetadataRenderer;
|
import com.google.android.exoplayer2.metadata.MetadataRenderer;
|
||||||
@ -88,7 +90,9 @@ import com.google.android.exoplayer2.video.MediaCodecVideoRenderer;
|
|||||||
* thread. The application's main thread is ideal. Accessing an instance from multiple threads is
|
* thread. The application's main thread is ideal. Accessing an instance from multiple threads is
|
||||||
* discouraged, however if an application does wish to do this then it may do so provided that it
|
* discouraged, however if an application does wish to do this then it may do so provided that it
|
||||||
* ensures accesses are synchronized.</li>
|
* ensures accesses are synchronized.</li>
|
||||||
* <li>Registered listeners are called on the thread that created the ExoPlayer instance.</li>
|
* <li>Registered listeners are called on the thread that created the ExoPlayer instance, unless
|
||||||
|
* the thread that created the ExoPlayer instance does not have a {@link Looper}. In that case,
|
||||||
|
* registered listeners will be called on the application's main thread.</li>
|
||||||
* <li>An internal playback thread is responsible for playback. Injected player components such as
|
* <li>An internal playback thread is responsible for playback. Injected player components such as
|
||||||
* Renderers, MediaSources, TrackSelectors and LoadControls are called by the player on this
|
* Renderers, MediaSources, TrackSelectors and LoadControls are called by the player on this
|
||||||
* thread.</li>
|
* thread.</li>
|
||||||
@ -113,8 +117,8 @@ public interface ExoPlayer {
|
|||||||
* Called when the timeline and/or manifest has been refreshed.
|
* Called when the timeline and/or manifest has been refreshed.
|
||||||
* <p>
|
* <p>
|
||||||
* Note that if the timeline has changed then a position discontinuity may also have occurred.
|
* Note that if the timeline has changed then a position discontinuity may also have occurred.
|
||||||
* For example the current period index may have changed as a result of periods being added or
|
* For example, the current period index may have changed as a result of periods being added or
|
||||||
* removed from the timeline. The will <em>not</em> be reported via a separate call to
|
* removed from the timeline. This will <em>not</em> be reported via a separate call to
|
||||||
* {@link #onPositionDiscontinuity()}.
|
* {@link #onPositionDiscontinuity()}.
|
||||||
*
|
*
|
||||||
* @param timeline The latest timeline. Never null, but may be empty.
|
* @param timeline The latest timeline. Never null, but may be empty.
|
||||||
@ -253,7 +257,8 @@ public interface ExoPlayer {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Register a listener to receive events from the player. The listener's methods will be called on
|
* Register a listener to receive events from the player. The listener's methods will be called on
|
||||||
* the thread that was used to construct the player.
|
* the thread that was used to construct the player. However, if the thread used to construct the
|
||||||
|
* player does not have a {@link Looper}, then the listener will be called on the main thread.
|
||||||
*
|
*
|
||||||
* @param listener The listener to register.
|
* @param listener The listener to register.
|
||||||
*/
|
*/
|
||||||
|
@ -16,7 +16,6 @@
|
|||||||
package com.google.android.exoplayer2;
|
package com.google.android.exoplayer2;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Looper;
|
|
||||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||||
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
|
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
|
||||||
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
||||||
@ -29,8 +28,7 @@ public final class ExoPlayerFactory {
|
|||||||
private ExoPlayerFactory() {}
|
private ExoPlayerFactory() {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated
|
* Creates a {@link SimpleExoPlayer} instance.
|
||||||
* {@link Looper}.
|
|
||||||
*
|
*
|
||||||
* @param context A {@link Context}.
|
* @param context A {@link Context}.
|
||||||
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
|
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
|
||||||
@ -45,8 +43,7 @@ public final class ExoPlayerFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated
|
* Creates a {@link SimpleExoPlayer} instance. Available extension renderers are not used.
|
||||||
* {@link Looper}. Available extension renderers are not used.
|
|
||||||
*
|
*
|
||||||
* @param context A {@link Context}.
|
* @param context A {@link Context}.
|
||||||
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
|
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
|
||||||
@ -63,8 +60,7 @@ public final class ExoPlayerFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated
|
* Creates a {@link SimpleExoPlayer} instance.
|
||||||
* {@link Looper}.
|
|
||||||
*
|
*
|
||||||
* @param context A {@link Context}.
|
* @param context A {@link Context}.
|
||||||
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
|
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
|
||||||
@ -86,8 +82,7 @@ public final class ExoPlayerFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated
|
* Creates a {@link SimpleExoPlayer} instance.
|
||||||
* {@link Looper}.
|
|
||||||
*
|
*
|
||||||
* @param context A {@link Context}.
|
* @param context A {@link Context}.
|
||||||
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
|
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
|
||||||
@ -112,8 +107,7 @@ public final class ExoPlayerFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated
|
* Creates a {@link SimpleExoPlayer} instance.
|
||||||
* {@link Looper}.
|
|
||||||
*
|
*
|
||||||
* @param context A {@link Context}.
|
* @param context A {@link Context}.
|
||||||
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
|
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
|
||||||
@ -123,8 +117,7 @@ public final class ExoPlayerFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated
|
* Creates a {@link SimpleExoPlayer} instance.
|
||||||
* {@link Looper}.
|
|
||||||
*
|
*
|
||||||
* @param renderersFactory A factory for creating {@link Renderer}s to be used by the instance.
|
* @param renderersFactory A factory for creating {@link Renderer}s to be used by the instance.
|
||||||
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
|
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
|
||||||
@ -135,8 +128,7 @@ public final class ExoPlayerFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated
|
* Creates a {@link SimpleExoPlayer} instance.
|
||||||
* {@link Looper}.
|
|
||||||
*
|
*
|
||||||
* @param renderersFactory A factory for creating {@link Renderer}s to be used by the instance.
|
* @param renderersFactory A factory for creating {@link Renderer}s to be used by the instance.
|
||||||
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
|
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
|
||||||
@ -148,8 +140,7 @@ public final class ExoPlayerFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an {@link ExoPlayer} instance. Must be called from a thread that has an associated
|
* Creates an {@link ExoPlayer} instance.
|
||||||
* {@link Looper}.
|
|
||||||
*
|
*
|
||||||
* @param renderers The {@link Renderer}s that will be used by the instance.
|
* @param renderers The {@link Renderer}s that will be used by the instance.
|
||||||
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
|
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
|
||||||
@ -159,8 +150,7 @@ public final class ExoPlayerFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an {@link ExoPlayer} instance. Must be called from a thread that has an associated
|
* Creates an {@link ExoPlayer} instance.
|
||||||
* {@link Looper}.
|
|
||||||
*
|
*
|
||||||
* @param renderers The {@link Renderer}s that will be used by the instance.
|
* @param renderers The {@link Renderer}s that will be used by the instance.
|
||||||
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
|
* @param trackSelector The {@link TrackSelector} that will be used by the instance.
|
||||||
|
@ -92,7 +92,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
|||||||
trackGroups = TrackGroupArray.EMPTY;
|
trackGroups = TrackGroupArray.EMPTY;
|
||||||
trackSelections = emptyTrackSelections;
|
trackSelections = emptyTrackSelections;
|
||||||
playbackParameters = PlaybackParameters.DEFAULT;
|
playbackParameters = PlaybackParameters.DEFAULT;
|
||||||
eventHandler = new Handler() {
|
Looper eventLooper = Looper.myLooper() != null ? Looper.myLooper() : Looper.getMainLooper();
|
||||||
|
eventHandler = new Handler(eventLooper) {
|
||||||
@Override
|
@Override
|
||||||
public void handleMessage(Message msg) {
|
public void handleMessage(Message msg) {
|
||||||
ExoPlayerImpl.this.handleEvent(msg);
|
ExoPlayerImpl.this.handleEvent(msg);
|
||||||
|
@ -24,13 +24,13 @@ public interface ExoPlayerLibraryInfo {
|
|||||||
* The version of the library expressed as a string, for example "1.2.3".
|
* The version of the library expressed as a string, for example "1.2.3".
|
||||||
*/
|
*/
|
||||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
|
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
|
||||||
String VERSION = "2.4.0";
|
String VERSION = "2.4.1";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}.
|
* The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}.
|
||||||
*/
|
*/
|
||||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
||||||
String VERSION_SLASHY = "ExoPlayerLib/2.4.0";
|
String VERSION_SLASHY = "ExoPlayerLib/2.4.1";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The version of the library expressed as an integer, for example 1002003.
|
* The version of the library expressed as an integer, for example 1002003.
|
||||||
@ -40,7 +40,7 @@ public interface ExoPlayerLibraryInfo {
|
|||||||
* integer version 123045006 (123-045-006).
|
* integer version 123045006 (123-045-006).
|
||||||
*/
|
*/
|
||||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
||||||
int VERSION_INT = 2004000;
|
int VERSION_INT = 2004001;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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}
|
||||||
|
@ -20,6 +20,7 @@ import android.graphics.SurfaceTexture;
|
|||||||
import android.media.MediaCodec;
|
import android.media.MediaCodec;
|
||||||
import android.media.PlaybackParams;
|
import android.media.PlaybackParams;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.os.Looper;
|
||||||
import android.support.annotation.Nullable;
|
import android.support.annotation.Nullable;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
@ -111,8 +112,10 @@ public class SimpleExoPlayer implements ExoPlayer {
|
|||||||
protected SimpleExoPlayer(RenderersFactory renderersFactory, TrackSelector trackSelector,
|
protected SimpleExoPlayer(RenderersFactory renderersFactory, TrackSelector trackSelector,
|
||||||
LoadControl loadControl) {
|
LoadControl loadControl) {
|
||||||
componentListener = new ComponentListener();
|
componentListener = new ComponentListener();
|
||||||
renderers = renderersFactory.createRenderers(new Handler(), componentListener,
|
Looper eventLooper = Looper.myLooper() != null ? Looper.myLooper() : Looper.getMainLooper();
|
||||||
componentListener, componentListener, componentListener);
|
Handler eventHandler = new Handler(eventLooper);
|
||||||
|
renderers = renderersFactory.createRenderers(eventHandler, componentListener, componentListener,
|
||||||
|
componentListener, componentListener);
|
||||||
|
|
||||||
// Obtain counts of video and audio renderers.
|
// Obtain counts of video and audio renderers.
|
||||||
int videoRendererCount = 0;
|
int videoRendererCount = 0;
|
||||||
|
@ -374,8 +374,8 @@ import java.util.Arrays;
|
|||||||
}
|
}
|
||||||
|
|
||||||
private short interpolate(short[] in, int inPos, int oldSampleRate, int newSampleRate) {
|
private short interpolate(short[] in, int inPos, int oldSampleRate, int newSampleRate) {
|
||||||
short left = in[inPos * numChannels];
|
short left = in[inPos];
|
||||||
short right = in[inPos * numChannels + numChannels];
|
short right = in[inPos + numChannels];
|
||||||
int position = newRatePosition * oldSampleRate;
|
int position = newRatePosition * oldSampleRate;
|
||||||
int leftPosition = oldRatePosition * newSampleRate;
|
int leftPosition = oldRatePosition * newSampleRate;
|
||||||
int rightPosition = (oldRatePosition + 1) * newSampleRate;
|
int rightPosition = (oldRatePosition + 1) * newSampleRate;
|
||||||
@ -402,7 +402,7 @@ import java.util.Arrays;
|
|||||||
enlargeOutputBufferIfNeeded(1);
|
enlargeOutputBufferIfNeeded(1);
|
||||||
for (int i = 0; i < numChannels; i++) {
|
for (int i = 0; i < numChannels; i++) {
|
||||||
outputBuffer[numOutputSamples * numChannels + i] =
|
outputBuffer[numOutputSamples * numChannels + i] =
|
||||||
interpolate(pitchBuffer, position + i, oldSampleRate, newSampleRate);
|
interpolate(pitchBuffer, position * numChannels + i, oldSampleRate, newSampleRate);
|
||||||
}
|
}
|
||||||
newRatePosition++;
|
newRatePosition++;
|
||||||
numOutputSamples++;
|
numOutputSamples++;
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package com.google.android.exoplayer2.decoder;
|
package com.google.android.exoplayer2.decoder;
|
||||||
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Buffer for {@link SimpleDecoder} output.
|
* Buffer for {@link SimpleDecoder} output.
|
||||||
@ -40,7 +41,7 @@ public class SimpleOutputBuffer extends OutputBuffer {
|
|||||||
public ByteBuffer init(long timeUs, int size) {
|
public ByteBuffer init(long timeUs, int size) {
|
||||||
this.timeUs = timeUs;
|
this.timeUs = timeUs;
|
||||||
if (data == null || data.capacity() < size) {
|
if (data == null || data.capacity() < size) {
|
||||||
data = ByteBuffer.allocateDirect(size);
|
data = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());
|
||||||
}
|
}
|
||||||
data.position(0);
|
data.position(0);
|
||||||
data.limit(size);
|
data.limit(size);
|
||||||
|
@ -26,7 +26,9 @@ import com.google.android.exoplayer2.extractor.ts.AdtsExtractor;
|
|||||||
import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory;
|
import com.google.android.exoplayer2.extractor.ts.DefaultTsPayloadReaderFactory;
|
||||||
import com.google.android.exoplayer2.extractor.ts.PsExtractor;
|
import com.google.android.exoplayer2.extractor.ts.PsExtractor;
|
||||||
import com.google.android.exoplayer2.extractor.ts.TsExtractor;
|
import com.google.android.exoplayer2.extractor.ts.TsExtractor;
|
||||||
|
import com.google.android.exoplayer2.extractor.ts.TsPayloadReader;
|
||||||
import com.google.android.exoplayer2.extractor.wav.WavExtractor;
|
import com.google.android.exoplayer2.extractor.wav.WavExtractor;
|
||||||
|
import com.google.android.exoplayer2.util.TimestampAdjuster;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -67,8 +69,13 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
|
|||||||
private @MatroskaExtractor.Flags int matroskaFlags;
|
private @MatroskaExtractor.Flags int matroskaFlags;
|
||||||
private @FragmentedMp4Extractor.Flags int fragmentedMp4Flags;
|
private @FragmentedMp4Extractor.Flags int fragmentedMp4Flags;
|
||||||
private @Mp3Extractor.Flags int mp3Flags;
|
private @Mp3Extractor.Flags int mp3Flags;
|
||||||
|
private @TsExtractor.Mode int tsMode;
|
||||||
private @DefaultTsPayloadReaderFactory.Flags int tsFlags;
|
private @DefaultTsPayloadReaderFactory.Flags int tsFlags;
|
||||||
|
|
||||||
|
public DefaultExtractorsFactory() {
|
||||||
|
tsMode = TsExtractor.MODE_SINGLE_PMT;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets flags for {@link MatroskaExtractor} instances created by the factory.
|
* Sets flags for {@link MatroskaExtractor} instances created by the factory.
|
||||||
*
|
*
|
||||||
@ -107,6 +114,18 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the mode for {@link TsExtractor} instances created by the factory.
|
||||||
|
*
|
||||||
|
* @see TsExtractor#TsExtractor(int, TimestampAdjuster, TsPayloadReader.Factory).
|
||||||
|
* @param mode The mode to use.
|
||||||
|
* @return The factory, for convenience.
|
||||||
|
*/
|
||||||
|
public synchronized DefaultExtractorsFactory setTsExtractorMode(@TsExtractor.Mode int mode) {
|
||||||
|
tsMode = mode;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets flags for {@link DefaultTsPayloadReaderFactory}s used by {@link TsExtractor} instances
|
* Sets flags for {@link DefaultTsPayloadReaderFactory}s used by {@link TsExtractor} instances
|
||||||
* created by the factory.
|
* created by the factory.
|
||||||
@ -130,7 +149,7 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
|
|||||||
extractors[3] = new Mp3Extractor(mp3Flags);
|
extractors[3] = new Mp3Extractor(mp3Flags);
|
||||||
extractors[4] = new AdtsExtractor();
|
extractors[4] = new AdtsExtractor();
|
||||||
extractors[5] = new Ac3Extractor();
|
extractors[5] = new Ac3Extractor();
|
||||||
extractors[6] = new TsExtractor(tsFlags);
|
extractors[6] = new TsExtractor(tsMode, tsFlags);
|
||||||
extractors[7] = new FlvExtractor();
|
extractors[7] = new FlvExtractor();
|
||||||
extractors[8] = new OggExtractor();
|
extractors[8] = new OggExtractor();
|
||||||
extractors[9] = new PsExtractor();
|
extractors[9] = new PsExtractor();
|
||||||
|
@ -20,6 +20,7 @@ import com.google.android.exoplayer2.extractor.ExtractorInput;
|
|||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.ParsableByteArray;
|
import com.google.android.exoplayer2.util.ParsableByteArray;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Arrays;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* OGG packet class.
|
* OGG packet class.
|
||||||
@ -27,8 +28,8 @@ import java.io.IOException;
|
|||||||
/* package */ final class OggPacket {
|
/* package */ final class OggPacket {
|
||||||
|
|
||||||
private final OggPageHeader pageHeader = new OggPageHeader();
|
private final OggPageHeader pageHeader = new OggPageHeader();
|
||||||
private final ParsableByteArray packetArray =
|
private final ParsableByteArray packetArray = new ParsableByteArray(
|
||||||
new ParsableByteArray(new byte[OggPageHeader.MAX_PAGE_PAYLOAD], 0);
|
new byte[OggPageHeader.MAX_PAGE_PAYLOAD], 0);
|
||||||
|
|
||||||
private int currentSegmentIndex = C.INDEX_UNSET;
|
private int currentSegmentIndex = C.INDEX_UNSET;
|
||||||
private int segmentCount;
|
private int segmentCount;
|
||||||
@ -85,6 +86,9 @@ import java.io.IOException;
|
|||||||
int size = calculatePacketSize(currentSegmentIndex);
|
int size = calculatePacketSize(currentSegmentIndex);
|
||||||
int segmentIndex = currentSegmentIndex + segmentCount;
|
int segmentIndex = currentSegmentIndex + segmentCount;
|
||||||
if (size > 0) {
|
if (size > 0) {
|
||||||
|
if (packetArray.capacity() < packetArray.limit() + size) {
|
||||||
|
packetArray.data = Arrays.copyOf(packetArray.data, packetArray.limit() + size);
|
||||||
|
}
|
||||||
input.readFully(packetArray.data, packetArray.limit(), size);
|
input.readFully(packetArray.data, packetArray.limit(), size);
|
||||||
packetArray.setLimit(packetArray.limit() + size);
|
packetArray.setLimit(packetArray.limit() + size);
|
||||||
populated = pageHeader.laces[segmentIndex - 1] != 255;
|
populated = pageHeader.laces[segmentIndex - 1] != 255;
|
||||||
@ -118,6 +122,17 @@ import java.io.IOException;
|
|||||||
return packetArray;
|
return packetArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Trims the packet data array.
|
||||||
|
*/
|
||||||
|
public void trimPayload() {
|
||||||
|
if (packetArray.data.length == OggPageHeader.MAX_PAGE_PAYLOAD) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
packetArray.data = Arrays.copyOf(packetArray.data, Math.max(OggPageHeader.MAX_PAGE_PAYLOAD,
|
||||||
|
packetArray.limit()));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Calculates the size of the packet starting from {@code startSegmentIndex}.
|
* Calculates the size of the packet starting from {@code startSegmentIndex}.
|
||||||
*
|
*
|
||||||
|
@ -103,15 +103,12 @@ import java.io.IOException;
|
|||||||
switch (state) {
|
switch (state) {
|
||||||
case STATE_READ_HEADERS:
|
case STATE_READ_HEADERS:
|
||||||
return readHeaders(input);
|
return readHeaders(input);
|
||||||
|
|
||||||
case STATE_SKIP_HEADERS:
|
case STATE_SKIP_HEADERS:
|
||||||
input.skipFully((int) payloadStartPosition);
|
input.skipFully((int) payloadStartPosition);
|
||||||
state = STATE_READ_PAYLOAD;
|
state = STATE_READ_PAYLOAD;
|
||||||
return Extractor.RESULT_CONTINUE;
|
return Extractor.RESULT_CONTINUE;
|
||||||
|
|
||||||
case STATE_READ_PAYLOAD:
|
case STATE_READ_PAYLOAD:
|
||||||
return readPayload(input, seekPosition);
|
return readPayload(input, seekPosition);
|
||||||
|
|
||||||
default:
|
default:
|
||||||
// Never happens.
|
// Never happens.
|
||||||
throw new IllegalStateException();
|
throw new IllegalStateException();
|
||||||
@ -152,6 +149,8 @@ import java.io.IOException;
|
|||||||
|
|
||||||
setupData = null;
|
setupData = null;
|
||||||
state = STATE_READ_PAYLOAD;
|
state = STATE_READ_PAYLOAD;
|
||||||
|
// First payload packet. Trim the payload array of the ogg packet after headers have been read.
|
||||||
|
oggPacket.trimPayload();
|
||||||
return Extractor.RESULT_CONTINUE;
|
return Extractor.RESULT_CONTINUE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -101,7 +101,7 @@ import java.util.ArrayList;
|
|||||||
codecInitialisationData.add(vorbisSetup.setupHeaderData);
|
codecInitialisationData.add(vorbisSetup.setupHeaderData);
|
||||||
|
|
||||||
setupData.format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_VORBIS, null,
|
setupData.format = Format.createAudioSampleFormat(null, MimeTypes.AUDIO_VORBIS, null,
|
||||||
this.vorbisSetup.idHeader.bitrateNominal, OggPageHeader.MAX_PAGE_PAYLOAD,
|
this.vorbisSetup.idHeader.bitrateNominal, Format.NO_VALUE,
|
||||||
this.vorbisSetup.idHeader.channels, (int) this.vorbisSetup.idHeader.sampleRate,
|
this.vorbisSetup.idHeader.channels, (int) this.vorbisSetup.idHeader.sampleRate,
|
||||||
codecInitialisationData, null, 0, null);
|
codecInitialisationData, null, 0, null);
|
||||||
return true;
|
return true;
|
||||||
|
@ -65,13 +65,13 @@ public final class TsExtractor implements Extractor {
|
|||||||
* Modes for the extractor.
|
* Modes for the extractor.
|
||||||
*/
|
*/
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@IntDef({MODE_NORMAL, MODE_SINGLE_PMT, MODE_HLS})
|
@IntDef({MODE_MULTI_PMT, MODE_SINGLE_PMT, MODE_HLS})
|
||||||
public @interface Mode {}
|
public @interface Mode {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Behave as defined in ISO/IEC 13818-1.
|
* Behave as defined in ISO/IEC 13818-1.
|
||||||
*/
|
*/
|
||||||
public static final int MODE_NORMAL = 0;
|
public static final int MODE_MULTI_PMT = 0;
|
||||||
/**
|
/**
|
||||||
* Assume only one PMT will be contained in the stream, even if more are declared by the PAT.
|
* Assume only one PMT will be contained in the stream, even if more are declared by the PAT.
|
||||||
*/
|
*/
|
||||||
@ -132,12 +132,23 @@ public final class TsExtractor implements Extractor {
|
|||||||
* {@code FLAG_*} values that control the behavior of the payload readers.
|
* {@code FLAG_*} values that control the behavior of the payload readers.
|
||||||
*/
|
*/
|
||||||
public TsExtractor(@Flags int defaultTsPayloadReaderFlags) {
|
public TsExtractor(@Flags int defaultTsPayloadReaderFlags) {
|
||||||
this(MODE_NORMAL, new TimestampAdjuster(0),
|
this(MODE_SINGLE_PMT, defaultTsPayloadReaderFlags);
|
||||||
new DefaultTsPayloadReaderFactory(defaultTsPayloadReaderFlags));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @param mode Mode for the extractor. One of {@link #MODE_NORMAL}, {@link #MODE_SINGLE_PMT}
|
* @param mode Mode for the extractor. One of {@link #MODE_MULTI_PMT}, {@link #MODE_SINGLE_PMT}
|
||||||
|
* and {@link #MODE_HLS}.
|
||||||
|
* @param defaultTsPayloadReaderFlags A combination of {@link DefaultTsPayloadReaderFactory}
|
||||||
|
* {@code FLAG_*} values that control the behavior of the payload readers.
|
||||||
|
*/
|
||||||
|
public TsExtractor(@Mode int mode, @Flags int defaultTsPayloadReaderFlags) {
|
||||||
|
this(mode, new TimestampAdjuster(0),
|
||||||
|
new DefaultTsPayloadReaderFactory(defaultTsPayloadReaderFlags));
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param mode Mode for the extractor. One of {@link #MODE_MULTI_PMT}, {@link #MODE_SINGLE_PMT}
|
||||||
* and {@link #MODE_HLS}.
|
* and {@link #MODE_HLS}.
|
||||||
* @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps.
|
* @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps.
|
||||||
* @param payloadReaderFactory Factory for injecting a custom set of payload readers.
|
* @param payloadReaderFactory Factory for injecting a custom set of payload readers.
|
||||||
|
@ -488,7 +488,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
}
|
}
|
||||||
if (format == null) {
|
if (format == null) {
|
||||||
// We don't have a format yet, so try and read one.
|
// We don't have a format yet, so try and read one.
|
||||||
buffer.clear();
|
flagsOnlyBuffer.clear();
|
||||||
int result = readSource(formatHolder, flagsOnlyBuffer, true);
|
int result = readSource(formatHolder, flagsOnlyBuffer, true);
|
||||||
if (result == C.RESULT_FORMAT_READ) {
|
if (result == C.RESULT_FORMAT_READ) {
|
||||||
onInputFormatChanged(formatHolder.format);
|
onInputFormatChanged(formatHolder.format);
|
||||||
|
@ -429,6 +429,7 @@ public final class MediaCodecUtil {
|
|||||||
case CodecProfileLevel.AVCLevel42: return 8704 * 16 * 16;
|
case CodecProfileLevel.AVCLevel42: return 8704 * 16 * 16;
|
||||||
case CodecProfileLevel.AVCLevel5: return 22080 * 16 * 16;
|
case CodecProfileLevel.AVCLevel5: return 22080 * 16 * 16;
|
||||||
case CodecProfileLevel.AVCLevel51: return 36864 * 16 * 16;
|
case CodecProfileLevel.AVCLevel51: return 36864 * 16 * 16;
|
||||||
|
case CodecProfileLevel.AVCLevel52: return 36864 * 16 * 16;
|
||||||
default: return -1;
|
default: return -1;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -153,7 +153,6 @@ public final class MetadataRenderer extends BaseRenderer implements Callback {
|
|||||||
protected void onDisabled() {
|
protected void onDisabled() {
|
||||||
flushPendingMetadata();
|
flushPendingMetadata();
|
||||||
decoder = null;
|
decoder = null;
|
||||||
super.onDisabled();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -254,7 +254,6 @@ public final class TextRenderer extends BaseRenderer implements Callback {
|
|||||||
streamFormat = null;
|
streamFormat = null;
|
||||||
clearOutput();
|
clearOutput();
|
||||||
releaseDecoder();
|
releaseDecoder();
|
||||||
super.onDisabled();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -17,7 +17,6 @@ package com.google.android.exoplayer2.text.ttml;
|
|||||||
|
|
||||||
import android.text.Layout;
|
import android.text.Layout;
|
||||||
import android.util.Log;
|
import android.util.Log;
|
||||||
import android.util.Pair;
|
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.text.Cue;
|
import com.google.android.exoplayer2.text.Cue;
|
||||||
import com.google.android.exoplayer2.text.SimpleSubtitleDecoder;
|
import com.google.android.exoplayer2.text.SimpleSubtitleDecoder;
|
||||||
@ -100,7 +99,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
|||||||
XmlPullParser xmlParser = xmlParserFactory.newPullParser();
|
XmlPullParser xmlParser = xmlParserFactory.newPullParser();
|
||||||
Map<String, TtmlStyle> globalStyles = new HashMap<>();
|
Map<String, TtmlStyle> globalStyles = new HashMap<>();
|
||||||
Map<String, TtmlRegion> regionMap = new HashMap<>();
|
Map<String, TtmlRegion> regionMap = new HashMap<>();
|
||||||
regionMap.put(TtmlNode.ANONYMOUS_REGION_ID, new TtmlRegion());
|
regionMap.put(TtmlNode.ANONYMOUS_REGION_ID, new TtmlRegion(null));
|
||||||
ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes, 0, length);
|
ByteArrayInputStream inputStream = new ByteArrayInputStream(bytes, 0, length);
|
||||||
xmlParser.setInput(inputStream, null);
|
xmlParser.setInput(inputStream, null);
|
||||||
TtmlSubtitle ttmlSubtitle = null;
|
TtmlSubtitle ttmlSubtitle = null;
|
||||||
@ -211,9 +210,9 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
|||||||
globalStyles.put(style.getId(), style);
|
globalStyles.put(style.getId(), style);
|
||||||
}
|
}
|
||||||
} else if (XmlPullParserUtil.isStartTag(xmlParser, TtmlNode.TAG_REGION)) {
|
} else if (XmlPullParserUtil.isStartTag(xmlParser, TtmlNode.TAG_REGION)) {
|
||||||
Pair<String, TtmlRegion> ttmlRegionInfo = parseRegionAttributes(xmlParser);
|
TtmlRegion ttmlRegion = parseRegionAttributes(xmlParser);
|
||||||
if (ttmlRegionInfo != null) {
|
if (ttmlRegion != null) {
|
||||||
globalRegions.put(ttmlRegionInfo.first, ttmlRegionInfo.second);
|
globalRegions.put(ttmlRegion.id, ttmlRegion);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} while (!XmlPullParserUtil.isEndTag(xmlParser, TtmlNode.TAG_HEAD));
|
} while (!XmlPullParserUtil.isEndTag(xmlParser, TtmlNode.TAG_HEAD));
|
||||||
@ -221,41 +220,84 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses a region declaration. Supports origin and extent definition but only when defined in
|
* Parses a region declaration.
|
||||||
* terms of percentage of the viewport. Regions that do not correctly declare origin are ignored.
|
* <p>
|
||||||
|
* If the region defines an origin and/or extent, it is required that they're defined as
|
||||||
|
* percentages of the viewport. Region declarations that define origin and/or extent in other
|
||||||
|
* formats are unsupported, and null is returned.
|
||||||
*/
|
*/
|
||||||
private Pair<String, TtmlRegion> parseRegionAttributes(XmlPullParser xmlParser) {
|
private TtmlRegion parseRegionAttributes(XmlPullParser xmlParser) {
|
||||||
String regionId = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_ID);
|
String regionId = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_ID);
|
||||||
String regionOrigin = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_ORIGIN);
|
if (regionId == null) {
|
||||||
String regionExtent = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_EXTENT);
|
|
||||||
if (regionOrigin == null || regionId == null) {
|
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
float position = Cue.DIMEN_UNSET;
|
|
||||||
float line = Cue.DIMEN_UNSET;
|
float position;
|
||||||
Matcher originMatcher = PERCENTAGE_COORDINATES.matcher(regionOrigin);
|
float line;
|
||||||
if (originMatcher.matches()) {
|
String regionOrigin = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_ORIGIN);
|
||||||
try {
|
if (regionOrigin != null) {
|
||||||
position = Float.parseFloat(originMatcher.group(1)) / 100.f;
|
Matcher originMatcher = PERCENTAGE_COORDINATES.matcher(regionOrigin);
|
||||||
line = Float.parseFloat(originMatcher.group(2)) / 100.f;
|
if (originMatcher.matches()) {
|
||||||
} catch (NumberFormatException e) {
|
try {
|
||||||
Log.w(TAG, "Ignoring region with malformed origin: '" + regionOrigin + "'", e);
|
position = Float.parseFloat(originMatcher.group(1)) / 100f;
|
||||||
position = Cue.DIMEN_UNSET;
|
line = Float.parseFloat(originMatcher.group(2)) / 100f;
|
||||||
|
} catch (NumberFormatException e) {
|
||||||
|
Log.w(TAG, "Ignoring region with malformed origin: " + regionOrigin);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Ignoring region with unsupported origin: " + regionOrigin);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
// Origin is omitted. Default to top left.
|
||||||
|
position = 0;
|
||||||
|
line = 0;
|
||||||
}
|
}
|
||||||
float width = Cue.DIMEN_UNSET;
|
|
||||||
|
float width;
|
||||||
|
float height;
|
||||||
|
String regionExtent = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_EXTENT);
|
||||||
if (regionExtent != null) {
|
if (regionExtent != null) {
|
||||||
Matcher extentMatcher = PERCENTAGE_COORDINATES.matcher(regionExtent);
|
Matcher extentMatcher = PERCENTAGE_COORDINATES.matcher(regionExtent);
|
||||||
if (extentMatcher.matches()) {
|
if (extentMatcher.matches()) {
|
||||||
try {
|
try {
|
||||||
width = Float.parseFloat(extentMatcher.group(1)) / 100.f;
|
width = Float.parseFloat(extentMatcher.group(1)) / 100f;
|
||||||
|
height = Float.parseFloat(extentMatcher.group(2)) / 100f;
|
||||||
} catch (NumberFormatException e) {
|
} catch (NumberFormatException e) {
|
||||||
Log.w(TAG, "Ignoring malformed region extent: '" + regionExtent + "'", e);
|
Log.w(TAG, "Ignoring region with malformed extent: " + regionOrigin);
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
} else {
|
||||||
|
Log.w(TAG, "Ignoring region with unsupported extent: " + regionOrigin);
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
// Extent is omitted. Default to extent of parent.
|
||||||
|
width = 1;
|
||||||
|
height = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Cue.AnchorType int lineAnchor = Cue.ANCHOR_TYPE_START;
|
||||||
|
String displayAlign = XmlPullParserUtil.getAttributeValue(xmlParser,
|
||||||
|
TtmlNode.ATTR_TTS_DISPLAY_ALIGN);
|
||||||
|
if (displayAlign != null) {
|
||||||
|
switch (displayAlign.toLowerCase()) {
|
||||||
|
case "center":
|
||||||
|
lineAnchor = Cue.ANCHOR_TYPE_MIDDLE;
|
||||||
|
line += height / 2;
|
||||||
|
break;
|
||||||
|
case "after":
|
||||||
|
lineAnchor = Cue.ANCHOR_TYPE_END;
|
||||||
|
line += height;
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
// Default "before" case. Do nothing.
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return position != Cue.DIMEN_UNSET ? new Pair<>(regionId, new TtmlRegion(position, line,
|
|
||||||
Cue.LINE_TYPE_FRACTION, width)) : null;
|
return new TtmlRegion(regionId, position, line, Cue.LINE_TYPE_FRACTION, lineAnchor, width);
|
||||||
}
|
}
|
||||||
|
|
||||||
private String[] parseStyleIds(String parentStyleIds) {
|
private String[] parseStyleIds(String parentStyleIds) {
|
||||||
@ -277,7 +319,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
|||||||
try {
|
try {
|
||||||
style.setBackgroundColor(ColorParser.parseTtmlColor(attributeValue));
|
style.setBackgroundColor(ColorParser.parseTtmlColor(attributeValue));
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
Log.w(TAG, "failed parsing background value: '" + attributeValue + "'");
|
Log.w(TAG, "Failed parsing background value: " + attributeValue);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TtmlNode.ATTR_TTS_COLOR:
|
case TtmlNode.ATTR_TTS_COLOR:
|
||||||
@ -285,7 +327,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
|||||||
try {
|
try {
|
||||||
style.setFontColor(ColorParser.parseTtmlColor(attributeValue));
|
style.setFontColor(ColorParser.parseTtmlColor(attributeValue));
|
||||||
} catch (IllegalArgumentException e) {
|
} catch (IllegalArgumentException e) {
|
||||||
Log.w(TAG, "failed parsing color value: '" + attributeValue + "'");
|
Log.w(TAG, "Failed parsing color value: " + attributeValue);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TtmlNode.ATTR_TTS_FONT_FAMILY:
|
case TtmlNode.ATTR_TTS_FONT_FAMILY:
|
||||||
@ -296,7 +338,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
|||||||
style = createIfNull(style);
|
style = createIfNull(style);
|
||||||
parseFontSize(attributeValue, style);
|
parseFontSize(attributeValue, style);
|
||||||
} catch (SubtitleDecoderException e) {
|
} catch (SubtitleDecoderException e) {
|
||||||
Log.w(TAG, "failed parsing fontSize value: '" + attributeValue + "'");
|
Log.w(TAG, "Failed parsing fontSize value: " + attributeValue);
|
||||||
}
|
}
|
||||||
break;
|
break;
|
||||||
case TtmlNode.ATTR_TTS_FONT_WEIGHT:
|
case TtmlNode.ATTR_TTS_FONT_WEIGHT:
|
||||||
|
@ -50,14 +50,15 @@ import java.util.TreeSet;
|
|||||||
|
|
||||||
public static final String ANONYMOUS_REGION_ID = "";
|
public static final String ANONYMOUS_REGION_ID = "";
|
||||||
public static final String ATTR_ID = "id";
|
public static final String ATTR_ID = "id";
|
||||||
public static final String ATTR_TTS_BACKGROUND_COLOR = "backgroundColor";
|
public static final String ATTR_TTS_ORIGIN = "origin";
|
||||||
public static final String ATTR_TTS_EXTENT = "extent";
|
public static final String ATTR_TTS_EXTENT = "extent";
|
||||||
|
public static final String ATTR_TTS_DISPLAY_ALIGN = "displayAlign";
|
||||||
|
public static final String ATTR_TTS_BACKGROUND_COLOR = "backgroundColor";
|
||||||
public static final String ATTR_TTS_FONT_STYLE = "fontStyle";
|
public static final String ATTR_TTS_FONT_STYLE = "fontStyle";
|
||||||
public static final String ATTR_TTS_FONT_SIZE = "fontSize";
|
public static final String ATTR_TTS_FONT_SIZE = "fontSize";
|
||||||
public static final String ATTR_TTS_FONT_FAMILY = "fontFamily";
|
public static final String ATTR_TTS_FONT_FAMILY = "fontFamily";
|
||||||
public static final String ATTR_TTS_FONT_WEIGHT = "fontWeight";
|
public static final String ATTR_TTS_FONT_WEIGHT = "fontWeight";
|
||||||
public static final String ATTR_TTS_COLOR = "color";
|
public static final String ATTR_TTS_COLOR = "color";
|
||||||
public static final String ATTR_TTS_ORIGIN = "origin";
|
|
||||||
public static final String ATTR_TTS_TEXT_DECORATION = "textDecoration";
|
public static final String ATTR_TTS_TEXT_DECORATION = "textDecoration";
|
||||||
public static final String ATTR_TTS_TEXT_ALIGN = "textAlign";
|
public static final String ATTR_TTS_TEXT_ALIGN = "textAlign";
|
||||||
|
|
||||||
@ -179,7 +180,7 @@ import java.util.TreeSet;
|
|||||||
for (Entry<String, SpannableStringBuilder> entry : regionOutputs.entrySet()) {
|
for (Entry<String, SpannableStringBuilder> entry : regionOutputs.entrySet()) {
|
||||||
TtmlRegion region = regionMap.get(entry.getKey());
|
TtmlRegion region = regionMap.get(entry.getKey());
|
||||||
cues.add(new Cue(cleanUpText(entry.getValue()), null, region.line, region.lineType,
|
cues.add(new Cue(cleanUpText(entry.getValue()), null, region.line, region.lineType,
|
||||||
Cue.TYPE_UNSET, region.position, Cue.TYPE_UNSET, region.width));
|
region.lineAnchor, region.position, Cue.TYPE_UNSET, region.width));
|
||||||
}
|
}
|
||||||
return cues;
|
return cues;
|
||||||
}
|
}
|
||||||
|
@ -22,20 +22,24 @@ import com.google.android.exoplayer2.text.Cue;
|
|||||||
*/
|
*/
|
||||||
/* package */ final class TtmlRegion {
|
/* package */ final class TtmlRegion {
|
||||||
|
|
||||||
|
public final String id;
|
||||||
public final float position;
|
public final float position;
|
||||||
public final float line;
|
public final float line;
|
||||||
@Cue.LineType
|
@Cue.LineType public final int lineType;
|
||||||
public final int lineType;
|
@Cue.AnchorType public final int lineAnchor;
|
||||||
public final float width;
|
public final float width;
|
||||||
|
|
||||||
public TtmlRegion() {
|
public TtmlRegion(String id) {
|
||||||
this(Cue.DIMEN_UNSET, Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.DIMEN_UNSET);
|
this(id, Cue.DIMEN_UNSET, Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.TYPE_UNSET, Cue.DIMEN_UNSET);
|
||||||
}
|
}
|
||||||
|
|
||||||
public TtmlRegion(float position, float line, @Cue.LineType int lineType, float width) {
|
public TtmlRegion(String id, float position, float line, @Cue.LineType int lineType,
|
||||||
|
@Cue.AnchorType int lineAnchor, float width) {
|
||||||
|
this.id = id;
|
||||||
this.position = position;
|
this.position = position;
|
||||||
this.line = line;
|
this.line = line;
|
||||||
this.lineType = lineType;
|
this.lineType = lineType;
|
||||||
|
this.lineAnchor = lineAnchor;
|
||||||
this.width = width;
|
this.width = width;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -24,6 +24,7 @@ import com.google.android.exoplayer2.Format;
|
|||||||
import com.google.android.exoplayer2.RendererCapabilities;
|
import com.google.android.exoplayer2.RendererCapabilities;
|
||||||
import com.google.android.exoplayer2.source.TrackGroup;
|
import com.google.android.exoplayer2.source.TrackGroup;
|
||||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||||
|
import com.google.android.exoplayer2.upstream.BandwidthMeter;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -376,10 +377,21 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
|||||||
private final AtomicReference<Parameters> paramsReference;
|
private final AtomicReference<Parameters> paramsReference;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance that does not support adaptive tracks.
|
* Constructs an instance that does not support adaptive track selection.
|
||||||
*/
|
*/
|
||||||
public DefaultTrackSelector() {
|
public DefaultTrackSelector() {
|
||||||
this(null);
|
this((TrackSelection.Factory) null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance that supports adaptive track selection. Adaptive track selections use
|
||||||
|
* the provided {@link BandwidthMeter} to determine which individual track should be used during
|
||||||
|
* playback.
|
||||||
|
*
|
||||||
|
* @param bandwidthMeter The {@link BandwidthMeter}.
|
||||||
|
*/
|
||||||
|
public DefaultTrackSelector(BandwidthMeter bandwidthMeter) {
|
||||||
|
this(new AdaptiveTrackSelection.Factory(bandwidthMeter));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -867,7 +879,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
protected static boolean formatHasLanguage(Format format, String language) {
|
protected static boolean formatHasLanguage(Format format, String language) {
|
||||||
return TextUtils.equals(language, Util.normalizeLanguageCode(format.language));
|
return language != null
|
||||||
|
&& TextUtils.equals(language, Util.normalizeLanguageCode(format.language));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Viewport size util methods.
|
// Viewport size util methods.
|
||||||
|
@ -33,11 +33,11 @@ import java.util.concurrent.ExecutorService;
|
|||||||
public final class Loader implements LoaderErrorThrower {
|
public final class Loader implements LoaderErrorThrower {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Thrown when an unexpected exception is encountered during loading.
|
* Thrown when an unexpected exception or error is encountered during loading.
|
||||||
*/
|
*/
|
||||||
public static final class UnexpectedLoaderException extends IOException {
|
public static final class UnexpectedLoaderException extends IOException {
|
||||||
|
|
||||||
public UnexpectedLoaderException(Exception cause) {
|
public UnexpectedLoaderException(Throwable cause) {
|
||||||
super("Unexpected " + cause.getClass().getSimpleName() + ": " + cause.getMessage(), cause);
|
super("Unexpected " + cause.getClass().getSimpleName() + ": " + cause.getMessage(), cause);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -316,6 +316,14 @@ public final class Loader implements LoaderErrorThrower {
|
|||||||
if (!released) {
|
if (!released) {
|
||||||
obtainMessage(MSG_IO_EXCEPTION, new UnexpectedLoaderException(e)).sendToTarget();
|
obtainMessage(MSG_IO_EXCEPTION, new UnexpectedLoaderException(e)).sendToTarget();
|
||||||
}
|
}
|
||||||
|
} catch (OutOfMemoryError e) {
|
||||||
|
// This can occur if a stream is malformed in a way that causes an extractor to think it
|
||||||
|
// needs to allocate a large amount of memory. We don't want the process to die in this
|
||||||
|
// case, but we do want the playback to fail.
|
||||||
|
Log.e(TAG, "OutOfMemory error loading stream", e);
|
||||||
|
if (!released) {
|
||||||
|
obtainMessage(MSG_IO_EXCEPTION, new UnexpectedLoaderException(e)).sendToTarget();
|
||||||
|
}
|
||||||
} catch (Error e) {
|
} catch (Error e) {
|
||||||
// We'd hope that the platform would kill the process if an Error is thrown here, but the
|
// We'd hope that the platform would kill the process if an Error is thrown here, but the
|
||||||
// executor may catch the error (b/20616433). Throw it here, but also pass and throw it from
|
// executor may catch the error (b/20616433). Throw it here, but also pass and throw it from
|
||||||
|
@ -54,8 +54,8 @@ public final class CacheDataSource implements DataSource {
|
|||||||
FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS})
|
FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS})
|
||||||
public @interface Flags {}
|
public @interface Flags {}
|
||||||
/**
|
/**
|
||||||
* A flag indicating whether we will block reads if the cache key is locked. If this flag is
|
* A flag indicating whether we will block reads if the cache key is locked. If unset then data is
|
||||||
* set, then we will read from upstream if the cache key is locked.
|
* read from upstream if the cache key is locked, regardless of whether the data is cached.
|
||||||
*/
|
*/
|
||||||
public static final int FLAG_BLOCK_ON_CACHE = 1 << 0;
|
public static final int FLAG_BLOCK_ON_CACHE = 1 << 0;
|
||||||
|
|
||||||
@ -110,7 +110,23 @@ public final class CacheDataSource implements DataSource {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance with default {@link DataSource} and {@link DataSink} instances for
|
* Constructs an instance with default {@link DataSource} and {@link DataSink} instances for
|
||||||
* reading and writing the cache and with {@link #DEFAULT_MAX_CACHE_FILE_SIZE}.
|
* reading and writing the cache.
|
||||||
|
*
|
||||||
|
* @param cache The cache.
|
||||||
|
* @param upstream A {@link DataSource} for reading data not in the cache.
|
||||||
|
*/
|
||||||
|
public CacheDataSource(Cache cache, DataSource upstream) {
|
||||||
|
this(cache, upstream, 0, DEFAULT_MAX_CACHE_FILE_SIZE);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance with default {@link DataSource} and {@link DataSink} instances for
|
||||||
|
* reading and writing the cache.
|
||||||
|
*
|
||||||
|
* @param cache The cache.
|
||||||
|
* @param upstream A {@link DataSource} for reading data not in the cache.
|
||||||
|
* @param flags A combination of {@link #FLAG_BLOCK_ON_CACHE}, {@link #FLAG_IGNORE_CACHE_ON_ERROR}
|
||||||
|
* and {@link #FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS}, or 0.
|
||||||
*/
|
*/
|
||||||
public CacheDataSource(Cache cache, DataSource upstream, @Flags int flags) {
|
public CacheDataSource(Cache cache, DataSource upstream, @Flags int flags) {
|
||||||
this(cache, upstream, flags, DEFAULT_MAX_CACHE_FILE_SIZE);
|
this(cache, upstream, flags, DEFAULT_MAX_CACHE_FILE_SIZE);
|
||||||
@ -123,8 +139,8 @@ public final class CacheDataSource implements DataSource {
|
|||||||
*
|
*
|
||||||
* @param cache The cache.
|
* @param cache The cache.
|
||||||
* @param upstream A {@link DataSource} for reading data not in the cache.
|
* @param upstream A {@link DataSource} for reading data not in the cache.
|
||||||
* @param flags A combination of {@link #FLAG_BLOCK_ON_CACHE} and {@link
|
* @param flags A combination of {@link #FLAG_BLOCK_ON_CACHE}, {@link #FLAG_IGNORE_CACHE_ON_ERROR}
|
||||||
* #FLAG_IGNORE_CACHE_ON_ERROR} or 0.
|
* and {@link #FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS}, or 0.
|
||||||
* @param maxCacheFileSize The maximum size of a cache file, in bytes. If the cached data size
|
* @param maxCacheFileSize The maximum size of a cache file, in bytes. If the cached data size
|
||||||
* exceeds this value, then the data will be fragmented into multiple cache files. The
|
* exceeds this value, then the data will be fragmented into multiple cache files. The
|
||||||
* finer-grained this is the finer-grained the eviction policy can be.
|
* finer-grained this is the finer-grained the eviction policy can be.
|
||||||
@ -145,8 +161,8 @@ public final class CacheDataSource implements DataSource {
|
|||||||
* @param cacheReadDataSource A {@link DataSource} for reading data from the cache.
|
* @param cacheReadDataSource A {@link DataSource} for reading data from the cache.
|
||||||
* @param cacheWriteDataSink A {@link DataSink} for writing data to the cache. If null, cache is
|
* @param cacheWriteDataSink A {@link DataSink} for writing data to the cache. If null, cache is
|
||||||
* accessed read-only.
|
* accessed read-only.
|
||||||
* @param flags A combination of {@link #FLAG_BLOCK_ON_CACHE} and {@link
|
* @param flags A combination of {@link #FLAG_BLOCK_ON_CACHE}, {@link #FLAG_IGNORE_CACHE_ON_ERROR}
|
||||||
* #FLAG_IGNORE_CACHE_ON_ERROR} or 0.
|
* and {@link #FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS}, or 0.
|
||||||
* @param eventListener An optional {@link EventListener} to receive events.
|
* @param eventListener An optional {@link EventListener} to receive events.
|
||||||
*/
|
*/
|
||||||
public CacheDataSource(Cache cache, DataSource upstream, DataSource cacheReadDataSource,
|
public CacheDataSource(Cache cache, DataSource upstream, DataSource cacheReadDataSource,
|
||||||
|
@ -33,18 +33,26 @@ public final class CacheDataSourceFactory implements DataSource.Factory {
|
|||||||
private final int flags;
|
private final int flags;
|
||||||
private final EventListener eventListener;
|
private final EventListener eventListener;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @see CacheDataSource#CacheDataSource(Cache, DataSource)
|
||||||
|
*/
|
||||||
|
public CacheDataSourceFactory(Cache cache, DataSource.Factory upstreamFactory) {
|
||||||
|
this(cache, upstreamFactory, 0);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see CacheDataSource#CacheDataSource(Cache, DataSource, int)
|
* @see CacheDataSource#CacheDataSource(Cache, DataSource, int)
|
||||||
*/
|
*/
|
||||||
public CacheDataSourceFactory(Cache cache, DataSource.Factory upstreamFactory, int flags) {
|
public CacheDataSourceFactory(Cache cache, DataSource.Factory upstreamFactory,
|
||||||
|
@CacheDataSource.Flags int flags) {
|
||||||
this(cache, upstreamFactory, flags, CacheDataSource.DEFAULT_MAX_CACHE_FILE_SIZE);
|
this(cache, upstreamFactory, flags, CacheDataSource.DEFAULT_MAX_CACHE_FILE_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @see CacheDataSource#CacheDataSource(Cache, DataSource, int, long)
|
* @see CacheDataSource#CacheDataSource(Cache, DataSource, int, long)
|
||||||
*/
|
*/
|
||||||
public CacheDataSourceFactory(Cache cache, DataSource.Factory upstreamFactory, int flags,
|
public CacheDataSourceFactory(Cache cache, DataSource.Factory upstreamFactory,
|
||||||
long maxCacheFileSize) {
|
@CacheDataSource.Flags int flags, long maxCacheFileSize) {
|
||||||
this(cache, upstreamFactory, new FileDataSourceFactory(),
|
this(cache, upstreamFactory, new FileDataSourceFactory(),
|
||||||
new CacheDataSinkFactory(cache, maxCacheFileSize), flags, null);
|
new CacheDataSinkFactory(cache, maxCacheFileSize), flags, null);
|
||||||
}
|
}
|
||||||
@ -54,8 +62,8 @@ public final class CacheDataSourceFactory implements DataSource.Factory {
|
|||||||
* EventListener)
|
* EventListener)
|
||||||
*/
|
*/
|
||||||
public CacheDataSourceFactory(Cache cache, Factory upstreamFactory,
|
public CacheDataSourceFactory(Cache cache, Factory upstreamFactory,
|
||||||
Factory cacheReadDataSourceFactory,
|
Factory cacheReadDataSourceFactory, DataSink.Factory cacheWriteDataSinkFactory,
|
||||||
DataSink.Factory cacheWriteDataSinkFactory, int flags, EventListener eventListener) {
|
@CacheDataSource.Flags int flags, EventListener eventListener) {
|
||||||
this.cache = cache;
|
this.cache = cache;
|
||||||
this.upstreamFactory = upstreamFactory;
|
this.upstreamFactory = upstreamFactory;
|
||||||
this.cacheReadDataSourceFactory = cacheReadDataSourceFactory;
|
this.cacheReadDataSourceFactory = cacheReadDataSourceFactory;
|
||||||
|
@ -64,7 +64,7 @@ public final class CacheUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns already cached and missing bytes in the {@cache} for the data defined by {@code
|
* Returns already cached and missing bytes in the {@code cache} for the data defined by {@code
|
||||||
* dataSpec}.
|
* dataSpec}.
|
||||||
*
|
*
|
||||||
* @param dataSpec Defines the data to be checked.
|
* @param dataSpec Defines the data to be checked.
|
||||||
|
@ -286,7 +286,9 @@ public final class SimpleCache implements Cache {
|
|||||||
|
|
||||||
private void removeSpan(CacheSpan span, boolean removeEmptyCachedContent) throws CacheException {
|
private void removeSpan(CacheSpan span, boolean removeEmptyCachedContent) throws CacheException {
|
||||||
CachedContent cachedContent = index.get(span.key);
|
CachedContent cachedContent = index.get(span.key);
|
||||||
Assertions.checkState(cachedContent.removeSpan(span));
|
if (cachedContent == null || !cachedContent.removeSpan(span)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
totalSpace -= span.length;
|
totalSpace -= span.length;
|
||||||
if (removeEmptyCachedContent && cachedContent.isEmpty()) {
|
if (removeEmptyCachedContent && cachedContent.isEmpty()) {
|
||||||
index.removeEmpty(cachedContent.key);
|
index.removeEmpty(cachedContent.key);
|
||||||
|
@ -0,0 +1,306 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 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.video;
|
||||||
|
|
||||||
|
import static android.opengl.EGL14.EGL_ALPHA_SIZE;
|
||||||
|
import static android.opengl.EGL14.EGL_BLUE_SIZE;
|
||||||
|
import static android.opengl.EGL14.EGL_CONFIG_CAVEAT;
|
||||||
|
import static android.opengl.EGL14.EGL_CONTEXT_CLIENT_VERSION;
|
||||||
|
import static android.opengl.EGL14.EGL_DEFAULT_DISPLAY;
|
||||||
|
import static android.opengl.EGL14.EGL_DEPTH_SIZE;
|
||||||
|
import static android.opengl.EGL14.EGL_GREEN_SIZE;
|
||||||
|
import static android.opengl.EGL14.EGL_HEIGHT;
|
||||||
|
import static android.opengl.EGL14.EGL_NONE;
|
||||||
|
import static android.opengl.EGL14.EGL_OPENGL_ES2_BIT;
|
||||||
|
import static android.opengl.EGL14.EGL_RED_SIZE;
|
||||||
|
import static android.opengl.EGL14.EGL_RENDERABLE_TYPE;
|
||||||
|
import static android.opengl.EGL14.EGL_SURFACE_TYPE;
|
||||||
|
import static android.opengl.EGL14.EGL_TRUE;
|
||||||
|
import static android.opengl.EGL14.EGL_WIDTH;
|
||||||
|
import static android.opengl.EGL14.EGL_WINDOW_BIT;
|
||||||
|
import static android.opengl.EGL14.eglChooseConfig;
|
||||||
|
import static android.opengl.EGL14.eglCreateContext;
|
||||||
|
import static android.opengl.EGL14.eglCreatePbufferSurface;
|
||||||
|
import static android.opengl.EGL14.eglGetDisplay;
|
||||||
|
import static android.opengl.EGL14.eglInitialize;
|
||||||
|
import static android.opengl.EGL14.eglMakeCurrent;
|
||||||
|
import static android.opengl.GLES20.glDeleteTextures;
|
||||||
|
import static android.opengl.GLES20.glGenTextures;
|
||||||
|
|
||||||
|
import android.annotation.TargetApi;
|
||||||
|
import android.graphics.SurfaceTexture;
|
||||||
|
import android.graphics.SurfaceTexture.OnFrameAvailableListener;
|
||||||
|
import android.opengl.EGL14;
|
||||||
|
import android.opengl.EGLConfig;
|
||||||
|
import android.opengl.EGLContext;
|
||||||
|
import android.opengl.EGLDisplay;
|
||||||
|
import android.opengl.EGLSurface;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Handler.Callback;
|
||||||
|
import android.os.HandlerThread;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.util.Log;
|
||||||
|
import android.view.Surface;
|
||||||
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
import javax.microedition.khronos.egl.EGL10;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A dummy {@link Surface}.
|
||||||
|
*/
|
||||||
|
@TargetApi(17)
|
||||||
|
public final class DummySurface extends Surface {
|
||||||
|
|
||||||
|
private static final String TAG = "DummySurface";
|
||||||
|
|
||||||
|
private static final int EGL_PROTECTED_CONTENT_EXT = 0x32C0;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the device supports secure dummy surfaces.
|
||||||
|
*/
|
||||||
|
public static final boolean SECURE_SUPPORTED;
|
||||||
|
static {
|
||||||
|
if (Util.SDK_INT >= 17) {
|
||||||
|
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||||
|
String extensions = EGL14.eglQueryString(display, EGL10.EGL_EXTENSIONS);
|
||||||
|
SECURE_SUPPORTED = extensions.contains("EGL_EXT_protected_content");
|
||||||
|
} else {
|
||||||
|
SECURE_SUPPORTED = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the surface is secure.
|
||||||
|
*/
|
||||||
|
public final boolean secure;
|
||||||
|
|
||||||
|
private final DummySurfaceThread thread;
|
||||||
|
private boolean threadReleased;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a newly created dummy surface. The surface must be released by calling {@link #release}
|
||||||
|
* when it's no longer required.
|
||||||
|
* <p>
|
||||||
|
* Must only be called if {@link Util#SDK_INT} is 17 or higher.
|
||||||
|
*
|
||||||
|
* @param secure Whether a secure surface is required. Must only be requested if
|
||||||
|
* {@link #SECURE_SUPPORTED} is {@code true}.
|
||||||
|
*/
|
||||||
|
public static DummySurface newInstanceV17(boolean secure) {
|
||||||
|
assertApiLevel17OrHigher();
|
||||||
|
Assertions.checkState(!secure || SECURE_SUPPORTED);
|
||||||
|
DummySurfaceThread thread = new DummySurfaceThread();
|
||||||
|
return thread.init(secure);
|
||||||
|
}
|
||||||
|
|
||||||
|
private DummySurface(DummySurfaceThread thread, SurfaceTexture surfaceTexture, boolean secure) {
|
||||||
|
super(surfaceTexture);
|
||||||
|
this.thread = thread;
|
||||||
|
this.secure = secure;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void release() {
|
||||||
|
super.release();
|
||||||
|
// The Surface may be released multiple times (explicitly and by Surface.finalize()). The
|
||||||
|
// implementation of super.release() has its own deduplication logic. Below we need to
|
||||||
|
// deduplicate ourselves. Synchronization is required as we don't control the thread on which
|
||||||
|
// Surface.finalize() is called.
|
||||||
|
synchronized (thread) {
|
||||||
|
if (!threadReleased) {
|
||||||
|
thread.release();
|
||||||
|
threadReleased = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void assertApiLevel17OrHigher() {
|
||||||
|
if (Util.SDK_INT < 17) {
|
||||||
|
throw new UnsupportedOperationException("Unsupported prior to API level 17");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class DummySurfaceThread extends HandlerThread implements OnFrameAvailableListener,
|
||||||
|
Callback {
|
||||||
|
|
||||||
|
private static final int MSG_INIT = 1;
|
||||||
|
private static final int MSG_UPDATE_TEXTURE = 2;
|
||||||
|
private static final int MSG_RELEASE = 3;
|
||||||
|
|
||||||
|
private final int[] textureIdHolder;
|
||||||
|
private Handler handler;
|
||||||
|
private SurfaceTexture surfaceTexture;
|
||||||
|
|
||||||
|
private Error initError;
|
||||||
|
private RuntimeException initException;
|
||||||
|
private DummySurface surface;
|
||||||
|
|
||||||
|
public DummySurfaceThread() {
|
||||||
|
super("dummySurface");
|
||||||
|
textureIdHolder = new int[1];
|
||||||
|
}
|
||||||
|
|
||||||
|
public DummySurface init(boolean secure) {
|
||||||
|
start();
|
||||||
|
handler = new Handler(getLooper(), this);
|
||||||
|
boolean wasInterrupted = false;
|
||||||
|
synchronized (this) {
|
||||||
|
handler.obtainMessage(MSG_INIT, secure ? 1 : 0, 0).sendToTarget();
|
||||||
|
while (surface == null && initException == null && initError == null) {
|
||||||
|
try {
|
||||||
|
wait();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
wasInterrupted = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (wasInterrupted) {
|
||||||
|
// Restore the interrupted status.
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
}
|
||||||
|
if (initException != null) {
|
||||||
|
throw initException;
|
||||||
|
} else if (initError != null) {
|
||||||
|
throw initError;
|
||||||
|
} else {
|
||||||
|
return surface;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
public void release() {
|
||||||
|
handler.sendEmptyMessage(MSG_RELEASE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onFrameAvailable(SurfaceTexture surfaceTexture) {
|
||||||
|
handler.sendEmptyMessage(MSG_UPDATE_TEXTURE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean handleMessage(Message msg) {
|
||||||
|
switch (msg.what) {
|
||||||
|
case MSG_INIT:
|
||||||
|
try {
|
||||||
|
initInternal(msg.arg1 != 0);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
Log.e(TAG, "Failed to initialize dummy surface", e);
|
||||||
|
initException = e;
|
||||||
|
} catch (Error e) {
|
||||||
|
Log.e(TAG, "Failed to initialize dummy surface", e);
|
||||||
|
initError = e;
|
||||||
|
} finally {
|
||||||
|
synchronized (this) {
|
||||||
|
notify();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
case MSG_UPDATE_TEXTURE:
|
||||||
|
surfaceTexture.updateTexImage();
|
||||||
|
return true;
|
||||||
|
case MSG_RELEASE:
|
||||||
|
try {
|
||||||
|
releaseInternal();
|
||||||
|
} catch (Throwable e) {
|
||||||
|
Log.e(TAG, "Failed to release dummy surface", e);
|
||||||
|
} finally {
|
||||||
|
quit();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
default:
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void initInternal(boolean secure) {
|
||||||
|
EGLDisplay display = eglGetDisplay(EGL_DEFAULT_DISPLAY);
|
||||||
|
Assertions.checkState(display != null, "eglGetDisplay failed");
|
||||||
|
|
||||||
|
int[] version = new int[2];
|
||||||
|
boolean eglInitialized = eglInitialize(display, version, 0, version, 1);
|
||||||
|
Assertions.checkState(eglInitialized, "eglInitialize failed");
|
||||||
|
|
||||||
|
int[] eglAttributes = new int[] {
|
||||||
|
EGL_RENDERABLE_TYPE, EGL_OPENGL_ES2_BIT,
|
||||||
|
EGL_RED_SIZE, 8,
|
||||||
|
EGL_GREEN_SIZE, 8,
|
||||||
|
EGL_BLUE_SIZE, 8,
|
||||||
|
EGL_ALPHA_SIZE, 8,
|
||||||
|
EGL_DEPTH_SIZE, 0,
|
||||||
|
EGL_CONFIG_CAVEAT, EGL_NONE,
|
||||||
|
EGL_SURFACE_TYPE, EGL_WINDOW_BIT,
|
||||||
|
EGL_NONE
|
||||||
|
};
|
||||||
|
EGLConfig[] configs = new EGLConfig[1];
|
||||||
|
int[] numConfigs = new int[1];
|
||||||
|
boolean eglChooseConfigSuccess = eglChooseConfig(display, eglAttributes, 0, configs, 0, 1,
|
||||||
|
numConfigs, 0);
|
||||||
|
Assertions.checkState(eglChooseConfigSuccess && numConfigs[0] > 0 && configs[0] != null,
|
||||||
|
"eglChooseConfig failed");
|
||||||
|
|
||||||
|
EGLConfig config = configs[0];
|
||||||
|
int[] glAttributes;
|
||||||
|
if (secure) {
|
||||||
|
glAttributes = new int[] {
|
||||||
|
EGL_CONTEXT_CLIENT_VERSION, 2,
|
||||||
|
EGL_PROTECTED_CONTENT_EXT,
|
||||||
|
EGL_TRUE, EGL_NONE};
|
||||||
|
} else {
|
||||||
|
glAttributes = new int[] {
|
||||||
|
EGL_CONTEXT_CLIENT_VERSION, 2,
|
||||||
|
EGL_NONE};
|
||||||
|
}
|
||||||
|
EGLContext context = eglCreateContext(display, config, android.opengl.EGL14.EGL_NO_CONTEXT,
|
||||||
|
glAttributes, 0);
|
||||||
|
Assertions.checkState(context != null, "eglCreateContext failed");
|
||||||
|
|
||||||
|
int[] pbufferAttributes;
|
||||||
|
if (secure) {
|
||||||
|
pbufferAttributes = new int[] {
|
||||||
|
EGL_WIDTH, 1,
|
||||||
|
EGL_HEIGHT, 1,
|
||||||
|
EGL_PROTECTED_CONTENT_EXT, EGL_TRUE,
|
||||||
|
EGL_NONE};
|
||||||
|
} else {
|
||||||
|
pbufferAttributes = new int[] {
|
||||||
|
EGL_WIDTH, 1,
|
||||||
|
EGL_HEIGHT, 1,
|
||||||
|
EGL_NONE};
|
||||||
|
}
|
||||||
|
EGLSurface pbuffer = eglCreatePbufferSurface(display, config, pbufferAttributes, 0);
|
||||||
|
Assertions.checkState(pbuffer != null, "eglCreatePbufferSurface failed");
|
||||||
|
|
||||||
|
boolean eglMadeCurrent = eglMakeCurrent(display, pbuffer, pbuffer, context);
|
||||||
|
Assertions.checkState(eglMadeCurrent, "eglMakeCurrent failed");
|
||||||
|
|
||||||
|
glGenTextures(1, textureIdHolder, 0);
|
||||||
|
surfaceTexture = new SurfaceTexture(textureIdHolder[0]);
|
||||||
|
surfaceTexture.setOnFrameAvailableListener(this);
|
||||||
|
surface = new DummySurface(this, surfaceTexture, secure);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void releaseInternal() {
|
||||||
|
try {
|
||||||
|
surfaceTexture.release();
|
||||||
|
} finally {
|
||||||
|
surface = null;
|
||||||
|
surfaceTexture = null;
|
||||||
|
glDeleteTextures(1, textureIdHolder, 0);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
@ -376,7 +376,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onOutputFormatChanged(MediaCodec codec, android.media.MediaFormat outputFormat) {
|
protected void onOutputFormatChanged(MediaCodec codec, MediaFormat outputFormat) {
|
||||||
boolean hasCrop = outputFormat.containsKey(KEY_CROP_RIGHT)
|
boolean hasCrop = outputFormat.containsKey(KEY_CROP_RIGHT)
|
||||||
&& outputFormat.containsKey(KEY_CROP_LEFT) && outputFormat.containsKey(KEY_CROP_BOTTOM)
|
&& outputFormat.containsKey(KEY_CROP_LEFT) && outputFormat.containsKey(KEY_CROP_BOTTOM)
|
||||||
&& outputFormat.containsKey(KEY_CROP_TOP);
|
&& outputFormat.containsKey(KEY_CROP_TOP);
|
||||||
@ -408,11 +408,9 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
|||||||
@Override
|
@Override
|
||||||
protected boolean canReconfigureCodec(MediaCodec codec, boolean codecIsAdaptive,
|
protected boolean canReconfigureCodec(MediaCodec codec, boolean codecIsAdaptive,
|
||||||
Format oldFormat, Format newFormat) {
|
Format oldFormat, Format newFormat) {
|
||||||
return areAdaptationCompatible(oldFormat, newFormat)
|
return areAdaptationCompatible(codecIsAdaptive, oldFormat, newFormat)
|
||||||
&& newFormat.width <= codecMaxValues.width && newFormat.height <= codecMaxValues.height
|
&& newFormat.width <= codecMaxValues.width && newFormat.height <= codecMaxValues.height
|
||||||
&& newFormat.maxInputSize <= codecMaxValues.inputSize
|
&& newFormat.maxInputSize <= codecMaxValues.inputSize;
|
||||||
&& (codecIsAdaptive
|
|
||||||
|| (oldFormat.width == newFormat.width && oldFormat.height == newFormat.height));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -664,7 +662,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
|||||||
}
|
}
|
||||||
boolean haveUnknownDimensions = false;
|
boolean haveUnknownDimensions = false;
|
||||||
for (Format streamFormat : streamFormats) {
|
for (Format streamFormat : streamFormats) {
|
||||||
if (areAdaptationCompatible(format, streamFormat)) {
|
if (areAdaptationCompatible(codecInfo.adaptive, format, streamFormat)) {
|
||||||
haveUnknownDimensions |= (streamFormat.width == Format.NO_VALUE
|
haveUnknownDimensions |= (streamFormat.width == Format.NO_VALUE
|
||||||
|| streamFormat.height == Format.NO_VALUE);
|
|| streamFormat.height == Format.NO_VALUE);
|
||||||
maxWidth = Math.max(maxWidth, streamFormat.width);
|
maxWidth = Math.max(maxWidth, streamFormat.width);
|
||||||
@ -817,17 +815,19 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether an adaptive codec with suitable {@link CodecMaxValues} will support adaptation
|
* Returns whether a codec with suitable {@link CodecMaxValues} will support adaptation between
|
||||||
* between two {@link Format}s.
|
* two {@link Format}s.
|
||||||
*
|
*
|
||||||
|
* @param codecIsAdaptive Whether the codec supports seamless resolution switches.
|
||||||
* @param first The first format.
|
* @param first The first format.
|
||||||
* @param second The second format.
|
* @param second The second format.
|
||||||
* @return Whether an adaptive codec with suitable {@link CodecMaxValues} will support adaptation
|
* @return Whether the codec will support adaptation between the two {@link Format}s.
|
||||||
* between two {@link Format}s.
|
|
||||||
*/
|
*/
|
||||||
private static boolean areAdaptationCompatible(Format first, Format second) {
|
private static boolean areAdaptationCompatible(boolean codecIsAdaptive, Format first,
|
||||||
|
Format second) {
|
||||||
return first.sampleMimeType.equals(second.sampleMimeType)
|
return first.sampleMimeType.equals(second.sampleMimeType)
|
||||||
&& getRotationDegrees(first) == getRotationDegrees(second);
|
&& getRotationDegrees(first) == getRotationDegrees(second)
|
||||||
|
&& (codecIsAdaptive || (first.width == second.width && first.height == second.height));
|
||||||
}
|
}
|
||||||
|
|
||||||
private static float getPixelWidthHeightRatio(Format format) {
|
private static float getPixelWidthHeightRatio(Format format) {
|
||||||
|
@ -23,6 +23,7 @@ import com.google.android.exoplayer2.util.MimeTypes;
|
|||||||
import java.io.ByteArrayInputStream;
|
import java.io.ByteArrayInputStream;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import junit.framework.TestCase;
|
import junit.framework.TestCase;
|
||||||
|
|
||||||
@ -56,16 +57,22 @@ public class HlsMasterPlaylistParserTest extends TestCase {
|
|||||||
|
|
||||||
private static final String MASTER_PLAYLIST_WITH_CC = " #EXTM3U \n"
|
private static final String MASTER_PLAYLIST_WITH_CC = " #EXTM3U \n"
|
||||||
+ "#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,LANGUAGE=\"es\",NAME=\"Eng\",INSTREAM-ID=\"SERVICE4\"\n"
|
+ "#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,LANGUAGE=\"es\",NAME=\"Eng\",INSTREAM-ID=\"SERVICE4\"\n"
|
||||||
+ "\n"
|
|
||||||
+ "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n"
|
+ "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n"
|
||||||
+ "http://example.com/low.m3u8\n";
|
+ "http://example.com/low.m3u8\n";
|
||||||
|
|
||||||
|
private static final String MASTER_PLAYLIST_WITHOUT_CC = " #EXTM3U \n"
|
||||||
|
+ "#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,LANGUAGE=\"es\",NAME=\"Eng\",INSTREAM-ID=\"SERVICE4\"\n"
|
||||||
|
+ "#EXT-X-STREAM-INF:BANDWIDTH=1280000,CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128,"
|
||||||
|
+ "CLOSED-CAPTIONS=NONE\n"
|
||||||
|
+ "http://example.com/low.m3u8\n";
|
||||||
|
|
||||||
public void testParseMasterPlaylist() throws IOException{
|
public void testParseMasterPlaylist() throws IOException{
|
||||||
HlsMasterPlaylist masterPlaylist = parseMasterPlaylist(PLAYLIST_URI, MASTER_PLAYLIST);
|
HlsMasterPlaylist masterPlaylist = parseMasterPlaylist(PLAYLIST_URI, MASTER_PLAYLIST);
|
||||||
|
|
||||||
List<HlsMasterPlaylist.HlsUrl> variants = masterPlaylist.variants;
|
List<HlsMasterPlaylist.HlsUrl> variants = masterPlaylist.variants;
|
||||||
assertNotNull(variants);
|
assertNotNull(variants);
|
||||||
assertEquals(5, variants.size());
|
assertEquals(5, variants.size());
|
||||||
|
assertNull(masterPlaylist.muxedCaptionFormats);
|
||||||
|
|
||||||
assertEquals(1280000, variants.get(0).format.bitrate);
|
assertEquals(1280000, variants.get(0).format.bitrate);
|
||||||
assertNotNull(variants.get(0).format.codecs);
|
assertNotNull(variants.get(0).format.codecs);
|
||||||
@ -117,6 +124,11 @@ public class HlsMasterPlaylistParserTest extends TestCase {
|
|||||||
assertEquals("es", closedCaptionFormat.language);
|
assertEquals("es", closedCaptionFormat.language);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public void testPlaylistWithoutClosedCaptions() throws IOException {
|
||||||
|
HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, MASTER_PLAYLIST_WITHOUT_CC);
|
||||||
|
assertEquals(Collections.emptyList(), playlist.muxedCaptionFormats);
|
||||||
|
}
|
||||||
|
|
||||||
private static HlsMasterPlaylist parseMasterPlaylist(String uri, String playlistString)
|
private static HlsMasterPlaylist parseMasterPlaylist(String uri, String playlistString)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
Uri playlistUri = Uri.parse(uri);
|
Uri playlistUri = Uri.parse(uri);
|
||||||
|
@ -92,6 +92,7 @@ import java.util.Locale;
|
|||||||
private boolean isTimestampMaster;
|
private boolean isTimestampMaster;
|
||||||
private byte[] scratchSpace;
|
private byte[] scratchSpace;
|
||||||
private IOException fatalError;
|
private IOException fatalError;
|
||||||
|
private HlsUrl expectedPlaylistUrl;
|
||||||
|
|
||||||
private Uri encryptionKeyUri;
|
private Uri encryptionKeyUri;
|
||||||
private byte[] encryptionKey;
|
private byte[] encryptionKey;
|
||||||
@ -111,7 +112,8 @@ import java.util.Locale;
|
|||||||
* @param timestampAdjusterProvider A provider of {@link TimestampAdjuster} instances. If
|
* @param timestampAdjusterProvider A provider of {@link TimestampAdjuster} instances. If
|
||||||
* multiple {@link HlsChunkSource}s are used for a single playback, they should all share the
|
* multiple {@link HlsChunkSource}s are used for a single playback, they should all share the
|
||||||
* same provider.
|
* same provider.
|
||||||
* @param muxedCaptionFormats List of muxed caption {@link Format}s.
|
* @param muxedCaptionFormats List of muxed caption {@link Format}s. Null if no closed caption
|
||||||
|
* information is available in the master playlist.
|
||||||
*/
|
*/
|
||||||
public HlsChunkSource(HlsPlaylistTracker playlistTracker, HlsUrl[] variants,
|
public HlsChunkSource(HlsPlaylistTracker playlistTracker, HlsUrl[] variants,
|
||||||
HlsDataSourceFactory dataSourceFactory, TimestampAdjusterProvider timestampAdjusterProvider,
|
HlsDataSourceFactory dataSourceFactory, TimestampAdjusterProvider timestampAdjusterProvider,
|
||||||
@ -142,6 +144,9 @@ import java.util.Locale;
|
|||||||
if (fatalError != null) {
|
if (fatalError != null) {
|
||||||
throw fatalError;
|
throw fatalError;
|
||||||
}
|
}
|
||||||
|
if (expectedPlaylistUrl != null) {
|
||||||
|
playlistTracker.maybeThrowPlaylistRefreshError(expectedPlaylistUrl);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -194,6 +199,7 @@ import java.util.Locale;
|
|||||||
public void getNextChunk(HlsMediaChunk previous, long playbackPositionUs, HlsChunkHolder out) {
|
public void getNextChunk(HlsMediaChunk previous, long playbackPositionUs, HlsChunkHolder out) {
|
||||||
int oldVariantIndex = previous == null ? C.INDEX_UNSET
|
int oldVariantIndex = previous == null ? C.INDEX_UNSET
|
||||||
: trackGroup.indexOf(previous.trackFormat);
|
: trackGroup.indexOf(previous.trackFormat);
|
||||||
|
expectedPlaylistUrl = null;
|
||||||
// Use start time of the previous chunk rather than its end time because switching format will
|
// Use start time of the previous chunk rather than its end time because switching format will
|
||||||
// require downloading overlapping segments.
|
// require downloading overlapping segments.
|
||||||
long bufferedDurationUs = previous == null ? 0
|
long bufferedDurationUs = previous == null ? 0
|
||||||
@ -207,6 +213,7 @@ import java.util.Locale;
|
|||||||
HlsUrl selectedUrl = variants[selectedVariantIndex];
|
HlsUrl selectedUrl = variants[selectedVariantIndex];
|
||||||
if (!playlistTracker.isSnapshotValid(selectedUrl)) {
|
if (!playlistTracker.isSnapshotValid(selectedUrl)) {
|
||||||
out.playlist = selectedUrl;
|
out.playlist = selectedUrl;
|
||||||
|
expectedPlaylistUrl = selectedUrl;
|
||||||
// Retry when playlist is refreshed.
|
// Retry when playlist is refreshed.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -246,6 +253,7 @@ import java.util.Locale;
|
|||||||
out.endOfStream = true;
|
out.endOfStream = true;
|
||||||
} else /* Live */ {
|
} else /* Live */ {
|
||||||
out.playlist = selectedUrl;
|
out.playlist = selectedUrl;
|
||||||
|
expectedPlaylistUrl = selectedUrl;
|
||||||
}
|
}
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -39,6 +39,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
|
|||||||
import com.google.android.exoplayer2.util.TimestampAdjuster;
|
import com.google.android.exoplayer2.util.TimestampAdjuster;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.concurrent.atomic.AtomicInteger;
|
import java.util.concurrent.atomic.AtomicInteger;
|
||||||
|
|
||||||
@ -104,7 +105,8 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
* @param dataSpec Defines the data to be loaded.
|
* @param dataSpec Defines the data to be loaded.
|
||||||
* @param initDataSpec Defines the initialization data to be fed to new extractors. May be null.
|
* @param initDataSpec Defines the initialization data to be fed to new extractors. May be null.
|
||||||
* @param hlsUrl The url of the playlist from which this chunk was obtained.
|
* @param hlsUrl The url of the playlist from which this chunk was obtained.
|
||||||
* @param muxedCaptionFormats List of muxed caption {@link Format}s.
|
* @param muxedCaptionFormats List of muxed caption {@link Format}s. Null if no closed caption
|
||||||
|
* information is available in the master playlist.
|
||||||
* @param trackSelectionReason See {@link #trackSelectionReason}.
|
* @param trackSelectionReason See {@link #trackSelectionReason}.
|
||||||
* @param trackSelectionData See {@link #trackSelectionData}.
|
* @param trackSelectionData See {@link #trackSelectionData}.
|
||||||
* @param startTimeUs The start time of the chunk in microseconds.
|
* @param startTimeUs The start time of the chunk in microseconds.
|
||||||
@ -356,9 +358,12 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
// 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.
|
||||||
@DefaultTsPayloadReaderFactory.Flags
|
@DefaultTsPayloadReaderFactory.Flags
|
||||||
int esReaderFactoryFlags = DefaultTsPayloadReaderFactory.FLAG_IGNORE_SPLICE_INFO_STREAM;
|
int esReaderFactoryFlags = DefaultTsPayloadReaderFactory.FLAG_IGNORE_SPLICE_INFO_STREAM;
|
||||||
if (!muxedCaptionFormats.isEmpty()) {
|
List<Format> closedCaptionFormats = muxedCaptionFormats;
|
||||||
|
if (closedCaptionFormats != null) {
|
||||||
// The playlist declares closed caption renditions, we should ignore descriptors.
|
// The playlist declares closed caption renditions, we should ignore descriptors.
|
||||||
esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_OVERRIDE_CAPTION_DESCRIPTORS;
|
esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_OVERRIDE_CAPTION_DESCRIPTORS;
|
||||||
|
} else {
|
||||||
|
closedCaptionFormats = Collections.emptyList();
|
||||||
}
|
}
|
||||||
String codecs = trackFormat.codecs;
|
String codecs = trackFormat.codecs;
|
||||||
if (!TextUtils.isEmpty(codecs)) {
|
if (!TextUtils.isEmpty(codecs)) {
|
||||||
@ -373,7 +378,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
extractor = new TsExtractor(TsExtractor.MODE_HLS, timestampAdjuster,
|
extractor = new TsExtractor(TsExtractor.MODE_HLS, timestampAdjuster,
|
||||||
new DefaultTsPayloadReaderFactory(esReaderFactoryFlags, muxedCaptionFormats));
|
new DefaultTsPayloadReaderFactory(esReaderFactoryFlags, closedCaptionFormats));
|
||||||
}
|
}
|
||||||
if (usingNewExtractor) {
|
if (usingNewExtractor) {
|
||||||
extractor.init(extractorOutput);
|
extractor.init(extractorOutput);
|
||||||
|
@ -84,7 +84,7 @@ public final class HlsMediaSource implements MediaSource,
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void maybeThrowSourceInfoRefreshError() throws IOException {
|
public void maybeThrowSourceInfoRefreshError() throws IOException {
|
||||||
playlistTracker.maybeThrowPlaylistRefreshError();
|
playlistTracker.maybeThrowPrimaryPlaylistRefreshError();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -30,15 +30,31 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
|
|||||||
*/
|
*/
|
||||||
public static final class HlsUrl {
|
public static final class HlsUrl {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The http url from which the media playlist can be obtained.
|
||||||
|
*/
|
||||||
public final String url;
|
public final String url;
|
||||||
|
/**
|
||||||
|
* Format information associated with the HLS url.
|
||||||
|
*/
|
||||||
public final Format format;
|
public final Format format;
|
||||||
|
|
||||||
public static HlsUrl createMediaPlaylistHlsUrl(String baseUri) {
|
/**
|
||||||
|
* Creates an HLS url from a given http url.
|
||||||
|
*
|
||||||
|
* @param url The url.
|
||||||
|
* @return An HLS url.
|
||||||
|
*/
|
||||||
|
public static HlsUrl createMediaPlaylistHlsUrl(String url) {
|
||||||
Format format = Format.createContainerFormat("0", MimeTypes.APPLICATION_M3U8, null, null,
|
Format format = Format.createContainerFormat("0", MimeTypes.APPLICATION_M3U8, null, null,
|
||||||
Format.NO_VALUE, 0, null);
|
Format.NO_VALUE, 0, null);
|
||||||
return new HlsUrl(baseUri, format);
|
return new HlsUrl(url, format);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param url See {@link #url}.
|
||||||
|
* @param format See {@link #format}.
|
||||||
|
*/
|
||||||
public HlsUrl(String url, Format format) {
|
public HlsUrl(String url, Format format) {
|
||||||
this.url = url;
|
this.url = url;
|
||||||
this.format = format;
|
this.format = format;
|
||||||
@ -46,13 +62,39 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The list of variants declared by the playlist.
|
||||||
|
*/
|
||||||
public final List<HlsUrl> variants;
|
public final List<HlsUrl> variants;
|
||||||
|
/**
|
||||||
|
* The list of demuxed audios declared by the playlist.
|
||||||
|
*/
|
||||||
public final List<HlsUrl> audios;
|
public final List<HlsUrl> audios;
|
||||||
|
/**
|
||||||
|
* The list of subtitles declared by the playlist.
|
||||||
|
*/
|
||||||
public final List<HlsUrl> subtitles;
|
public final List<HlsUrl> subtitles;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The format of the audio muxed in the variants. May be null if the playlist does not declare any
|
||||||
|
* muxed audio.
|
||||||
|
*/
|
||||||
public final Format muxedAudioFormat;
|
public final Format muxedAudioFormat;
|
||||||
|
/**
|
||||||
|
* The format of the closed captions declared by the playlist. May be empty if the playlist
|
||||||
|
* explicitly declares no captions are available, or null if the playlist does not declare any
|
||||||
|
* captions information.
|
||||||
|
*/
|
||||||
public final List<Format> muxedCaptionFormats;
|
public final List<Format> muxedCaptionFormats;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @param baseUri The base uri. Used to resolve relative paths.
|
||||||
|
* @param variants See {@link #variants}.
|
||||||
|
* @param audios See {@link #audios}.
|
||||||
|
* @param subtitles See {@link #subtitles}.
|
||||||
|
* @param muxedAudioFormat See {@link #muxedAudioFormat}.
|
||||||
|
* @param muxedCaptionFormats See {@link #muxedCaptionFormats}.
|
||||||
|
*/
|
||||||
public HlsMasterPlaylist(String baseUri, List<HlsUrl> variants, List<HlsUrl> audios,
|
public HlsMasterPlaylist(String baseUri, List<HlsUrl> variants, List<HlsUrl> audios,
|
||||||
List<HlsUrl> subtitles, Format muxedAudioFormat, List<Format> muxedCaptionFormats) {
|
List<HlsUrl> subtitles, Format muxedAudioFormat, List<Format> muxedCaptionFormats) {
|
||||||
super(baseUri);
|
super(baseUri);
|
||||||
@ -60,14 +102,20 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
|
|||||||
this.audios = Collections.unmodifiableList(audios);
|
this.audios = Collections.unmodifiableList(audios);
|
||||||
this.subtitles = Collections.unmodifiableList(subtitles);
|
this.subtitles = Collections.unmodifiableList(subtitles);
|
||||||
this.muxedAudioFormat = muxedAudioFormat;
|
this.muxedAudioFormat = muxedAudioFormat;
|
||||||
this.muxedCaptionFormats = Collections.unmodifiableList(muxedCaptionFormats);
|
this.muxedCaptionFormats = muxedCaptionFormats != null
|
||||||
|
? Collections.unmodifiableList(muxedCaptionFormats) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static HlsMasterPlaylist createSingleVariantMasterPlaylist(String variantUri) {
|
/**
|
||||||
List<HlsUrl> variant = Collections.singletonList(HlsUrl.createMediaPlaylistHlsUrl(variantUri));
|
* Creates a playlist with a single variant.
|
||||||
|
*
|
||||||
|
* @param variantUrl The url of the single variant.
|
||||||
|
* @return A master playlist with a single variant for the provided url.
|
||||||
|
*/
|
||||||
|
public static HlsMasterPlaylist createSingleVariantMasterPlaylist(String variantUrl) {
|
||||||
|
List<HlsUrl> variant = Collections.singletonList(HlsUrl.createMediaPlaylistHlsUrl(variantUrl));
|
||||||
List<HlsUrl> emptyList = Collections.emptyList();
|
List<HlsUrl> emptyList = Collections.emptyList();
|
||||||
return new HlsMasterPlaylist(null, variant, emptyList, emptyList, null,
|
return new HlsMasterPlaylist(null, variant, emptyList, emptyList, null, null);
|
||||||
Collections.<Format>emptyList());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -91,12 +91,14 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
|||||||
public final boolean hasProgramDateTime;
|
public final boolean hasProgramDateTime;
|
||||||
public final Segment initializationSegment;
|
public final Segment initializationSegment;
|
||||||
public final List<Segment> segments;
|
public final List<Segment> segments;
|
||||||
|
public final List<String> dateRanges;
|
||||||
public final long durationUs;
|
public final long durationUs;
|
||||||
|
|
||||||
public HlsMediaPlaylist(@PlaylistType int playlistType, String baseUri, long startOffsetUs,
|
public HlsMediaPlaylist(@PlaylistType int playlistType, String baseUri, long startOffsetUs,
|
||||||
long startTimeUs, boolean hasDiscontinuitySequence, int discontinuitySequence,
|
long startTimeUs, boolean hasDiscontinuitySequence, int discontinuitySequence,
|
||||||
int mediaSequence, int version, long targetDurationUs, boolean hasEndTag,
|
int mediaSequence, int version, long targetDurationUs, boolean hasEndTag,
|
||||||
boolean hasProgramDateTime, Segment initializationSegment, List<Segment> segments) {
|
boolean hasProgramDateTime, Segment initializationSegment, List<Segment> segments,
|
||||||
|
List<String> dateRanges) {
|
||||||
super(baseUri);
|
super(baseUri);
|
||||||
this.playlistType = playlistType;
|
this.playlistType = playlistType;
|
||||||
this.startTimeUs = startTimeUs;
|
this.startTimeUs = startTimeUs;
|
||||||
@ -117,6 +119,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
|||||||
}
|
}
|
||||||
this.startOffsetUs = startOffsetUs == C.TIME_UNSET ? C.TIME_UNSET
|
this.startOffsetUs = startOffsetUs == C.TIME_UNSET ? C.TIME_UNSET
|
||||||
: startOffsetUs >= 0 ? startOffsetUs : durationUs + startOffsetUs;
|
: startOffsetUs >= 0 ? startOffsetUs : durationUs + startOffsetUs;
|
||||||
|
this.dateRanges = Collections.unmodifiableList(dateRanges);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -155,7 +158,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
|||||||
public HlsMediaPlaylist copyWith(long startTimeUs, int discontinuitySequence) {
|
public HlsMediaPlaylist copyWith(long startTimeUs, int discontinuitySequence) {
|
||||||
return new HlsMediaPlaylist(playlistType, baseUri, startOffsetUs, startTimeUs, true,
|
return new HlsMediaPlaylist(playlistType, baseUri, startOffsetUs, startTimeUs, true,
|
||||||
discontinuitySequence, mediaSequence, version, targetDurationUs, hasEndTag,
|
discontinuitySequence, mediaSequence, version, targetDurationUs, hasEndTag,
|
||||||
hasProgramDateTime, initializationSegment, segments);
|
hasProgramDateTime, initializationSegment, segments, dateRanges);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -170,7 +173,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
|||||||
}
|
}
|
||||||
return new HlsMediaPlaylist(playlistType, baseUri, startOffsetUs, startTimeUs,
|
return new HlsMediaPlaylist(playlistType, baseUri, startOffsetUs, startTimeUs,
|
||||||
hasDiscontinuitySequence, discontinuitySequence, mediaSequence, version, targetDurationUs,
|
hasDiscontinuitySequence, discontinuitySequence, mediaSequence, version, targetDurationUs,
|
||||||
true, hasProgramDateTime, initializationSegment, segments);
|
true, hasProgramDateTime, initializationSegment, segments, dateRanges);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,7 @@ import java.io.IOException;
|
|||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
import java.util.LinkedList;
|
import java.util.LinkedList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
@ -57,6 +58,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||||||
private static final String TAG_ENDLIST = "#EXT-X-ENDLIST";
|
private static final String TAG_ENDLIST = "#EXT-X-ENDLIST";
|
||||||
private static final String TAG_KEY = "#EXT-X-KEY";
|
private static final String TAG_KEY = "#EXT-X-KEY";
|
||||||
private static final String TAG_BYTERANGE = "#EXT-X-BYTERANGE";
|
private static final String TAG_BYTERANGE = "#EXT-X-BYTERANGE";
|
||||||
|
private static final String TAG_DATERANGE = "#EXT-X-DATERANGE";
|
||||||
|
|
||||||
private static final String TYPE_AUDIO = "AUDIO";
|
private static final String TYPE_AUDIO = "AUDIO";
|
||||||
private static final String TYPE_VIDEO = "VIDEO";
|
private static final String TYPE_VIDEO = "VIDEO";
|
||||||
@ -69,6 +71,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||||||
private static final String BOOLEAN_TRUE = "YES";
|
private static final String BOOLEAN_TRUE = "YES";
|
||||||
private static final String BOOLEAN_FALSE = "NO";
|
private static final String BOOLEAN_FALSE = "NO";
|
||||||
|
|
||||||
|
private static final String ATTR_CLOSED_CAPTIONS_NONE = "CLOSED-CAPTIONS=NONE";
|
||||||
|
|
||||||
private static final Pattern REGEX_BANDWIDTH = Pattern.compile("BANDWIDTH=(\\d+)\\b");
|
private static final Pattern REGEX_BANDWIDTH = Pattern.compile("BANDWIDTH=(\\d+)\\b");
|
||||||
private static final Pattern REGEX_CODECS = Pattern.compile("CODECS=\"(.+?)\"");
|
private static final Pattern REGEX_CODECS = Pattern.compile("CODECS=\"(.+?)\"");
|
||||||
private static final Pattern REGEX_RESOLUTION = Pattern.compile("RESOLUTION=(\\d+x\\d+)");
|
private static final Pattern REGEX_RESOLUTION = Pattern.compile("RESOLUTION=(\\d+x\\d+)");
|
||||||
@ -172,7 +176,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||||||
ArrayList<HlsMasterPlaylist.HlsUrl> audios = new ArrayList<>();
|
ArrayList<HlsMasterPlaylist.HlsUrl> audios = new ArrayList<>();
|
||||||
ArrayList<HlsMasterPlaylist.HlsUrl> subtitles = new ArrayList<>();
|
ArrayList<HlsMasterPlaylist.HlsUrl> subtitles = new ArrayList<>();
|
||||||
Format muxedAudioFormat = null;
|
Format muxedAudioFormat = null;
|
||||||
ArrayList<Format> muxedCaptionFormats = new ArrayList<>();
|
List<Format> muxedCaptionFormats = null;
|
||||||
|
boolean noClosedCaptions = false;
|
||||||
|
|
||||||
String line;
|
String line;
|
||||||
while (iterator.hasNext()) {
|
while (iterator.hasNext()) {
|
||||||
@ -209,6 +214,9 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||||||
mimeType = MimeTypes.APPLICATION_CEA708;
|
mimeType = MimeTypes.APPLICATION_CEA708;
|
||||||
accessibilityChannel = Integer.parseInt(instreamId.substring(7));
|
accessibilityChannel = Integer.parseInt(instreamId.substring(7));
|
||||||
}
|
}
|
||||||
|
if (muxedCaptionFormats == null) {
|
||||||
|
muxedCaptionFormats = new ArrayList<>();
|
||||||
|
}
|
||||||
muxedCaptionFormats.add(Format.createTextContainerFormat(id, null, mimeType, null,
|
muxedCaptionFormats.add(Format.createTextContainerFormat(id, null, mimeType, null,
|
||||||
Format.NO_VALUE, selectionFlags, language, accessibilityChannel));
|
Format.NO_VALUE, selectionFlags, language, accessibilityChannel));
|
||||||
break;
|
break;
|
||||||
@ -220,6 +228,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||||||
int bitrate = parseIntAttr(line, REGEX_BANDWIDTH);
|
int bitrate = parseIntAttr(line, REGEX_BANDWIDTH);
|
||||||
String codecs = parseOptionalStringAttr(line, REGEX_CODECS);
|
String codecs = parseOptionalStringAttr(line, REGEX_CODECS);
|
||||||
String resolutionString = parseOptionalStringAttr(line, REGEX_RESOLUTION);
|
String resolutionString = parseOptionalStringAttr(line, REGEX_RESOLUTION);
|
||||||
|
noClosedCaptions |= line.contains(ATTR_CLOSED_CAPTIONS_NONE);
|
||||||
int width;
|
int width;
|
||||||
int height;
|
int height;
|
||||||
if (resolutionString != null) {
|
if (resolutionString != null) {
|
||||||
@ -242,6 +251,9 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||||||
variants.add(new HlsMasterPlaylist.HlsUrl(line, format));
|
variants.add(new HlsMasterPlaylist.HlsUrl(line, format));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
if (noClosedCaptions) {
|
||||||
|
muxedCaptionFormats = Collections.emptyList();
|
||||||
|
}
|
||||||
return new HlsMasterPlaylist(baseUri, variants, audios, subtitles, muxedAudioFormat,
|
return new HlsMasterPlaylist(baseUri, variants, audios, subtitles, muxedAudioFormat,
|
||||||
muxedCaptionFormats);
|
muxedCaptionFormats);
|
||||||
}
|
}
|
||||||
@ -263,6 +275,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||||||
boolean hasEndTag = false;
|
boolean hasEndTag = false;
|
||||||
Segment initializationSegment = null;
|
Segment initializationSegment = null;
|
||||||
List<Segment> segments = new ArrayList<>();
|
List<Segment> segments = new ArrayList<>();
|
||||||
|
List<String> dateRanges = new ArrayList<>();
|
||||||
|
|
||||||
long segmentDurationUs = 0;
|
long segmentDurationUs = 0;
|
||||||
boolean hasDiscontinuitySequence = false;
|
boolean hasDiscontinuitySequence = false;
|
||||||
@ -343,6 +356,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||||||
C.msToUs(Util.parseXsDateTime(line.substring(line.indexOf(':') + 1)));
|
C.msToUs(Util.parseXsDateTime(line.substring(line.indexOf(':') + 1)));
|
||||||
playlistStartTimeUs = programDatetimeUs - segmentStartTimeUs;
|
playlistStartTimeUs = programDatetimeUs - segmentStartTimeUs;
|
||||||
}
|
}
|
||||||
|
} else if (line.startsWith(TAG_DATERANGE)) {
|
||||||
|
dateRanges.add(line);
|
||||||
} else if (!line.startsWith("#")) {
|
} else if (!line.startsWith("#")) {
|
||||||
String segmentEncryptionIV;
|
String segmentEncryptionIV;
|
||||||
if (!isEncrypted) {
|
if (!isEncrypted) {
|
||||||
@ -371,7 +386,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
|||||||
}
|
}
|
||||||
return new HlsMediaPlaylist(playlistType, baseUri, startOffsetUs, playlistStartTimeUs,
|
return new HlsMediaPlaylist(playlistType, baseUri, startOffsetUs, playlistStartTimeUs,
|
||||||
hasDiscontinuitySequence, playlistDiscontinuitySequence, mediaSequence, version,
|
hasDiscontinuitySequence, playlistDiscontinuitySequence, mediaSequence, version,
|
||||||
targetDurationUs, hasEndTag, playlistStartTimeUs != 0, initializationSegment, segments);
|
targetDurationUs, hasEndTag, playlistStartTimeUs != 0, initializationSegment, segments,
|
||||||
|
dateRanges);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String parseStringAttr(String line, Pattern pattern) throws ParserException {
|
private static String parseStringAttr(String line, Pattern pattern) throws ParserException {
|
||||||
|
@ -200,18 +200,29 @@ public final class HlsPlaylistTracker implements Loader.Callback<ParsingLoadable
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If the tracker is having trouble refreshing the primary playlist or loading an irreplaceable
|
* If the tracker is having trouble refreshing the master playlist or the primary playlist, this
|
||||||
* playlist, this method throws the underlying error. Otherwise, does nothing.
|
* method throws the underlying error. Otherwise, does nothing.
|
||||||
*
|
*
|
||||||
* @throws IOException The underlying error.
|
* @throws IOException The underlying error.
|
||||||
*/
|
*/
|
||||||
public void maybeThrowPlaylistRefreshError() throws IOException {
|
public void maybeThrowPrimaryPlaylistRefreshError() throws IOException {
|
||||||
initialPlaylistLoader.maybeThrowError();
|
initialPlaylistLoader.maybeThrowError();
|
||||||
if (primaryHlsUrl != null) {
|
if (primaryHlsUrl != null) {
|
||||||
playlistBundles.get(primaryHlsUrl).mediaPlaylistLoader.maybeThrowError();
|
maybeThrowPlaylistRefreshError(primaryHlsUrl);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* If the playlist is having trouble loading the playlist referenced by the given {@link HlsUrl},
|
||||||
|
* this method throws the underlying error.
|
||||||
|
*
|
||||||
|
* @param url The {@link HlsUrl}.
|
||||||
|
* @throws IOException The underyling error.
|
||||||
|
*/
|
||||||
|
public void maybeThrowPlaylistRefreshError(HlsUrl url) throws IOException {
|
||||||
|
playlistBundles.get(url).mediaPlaylistLoader.maybeThrowError();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Triggers a playlist refresh and whitelists it.
|
* Triggers a playlist refresh and whitelists it.
|
||||||
*
|
*
|
||||||
|
@ -61,22 +61,21 @@ public class DefaultTimeBar extends View implements TimeBar {
|
|||||||
private static final int DEFAULT_INCREMENT_COUNT = 20;
|
private static final int DEFAULT_INCREMENT_COUNT = 20;
|
||||||
private static final int DEFAULT_BAR_HEIGHT = 4;
|
private static final int DEFAULT_BAR_HEIGHT = 4;
|
||||||
private static final int DEFAULT_TOUCH_TARGET_HEIGHT = 26;
|
private static final int DEFAULT_TOUCH_TARGET_HEIGHT = 26;
|
||||||
private static final int DEFAULT_PLAYED_COLOR = 0x33FFFFFF;
|
private static final int DEFAULT_PLAYED_COLOR = 0xFFFFFFFF;
|
||||||
private static final int DEFAULT_BUFFERED_COLOR = 0xCCFFFFFF;
|
|
||||||
private static final int DEFAULT_AD_MARKER_COLOR = 0xB2FFFF00;
|
private static final int DEFAULT_AD_MARKER_COLOR = 0xB2FFFF00;
|
||||||
private static final int DEFAULT_AD_MARKER_WIDTH = 4;
|
private static final int DEFAULT_AD_MARKER_WIDTH = 4;
|
||||||
private static final int DEFAULT_SCRUBBER_ENABLED_SIZE = 12;
|
private static final int DEFAULT_SCRUBBER_ENABLED_SIZE = 12;
|
||||||
private static final int DEFAULT_SCRUBBER_DISABLED_SIZE = 0;
|
private static final int DEFAULT_SCRUBBER_DISABLED_SIZE = 0;
|
||||||
private static final int DEFAULT_SCRUBBER_DRAGGED_SIZE = 16;
|
private static final int DEFAULT_SCRUBBER_DRAGGED_SIZE = 16;
|
||||||
private static final int OPAQUE_COLOR = 0xFF000000;
|
|
||||||
|
|
||||||
private final Rect seekBounds;
|
private final Rect seekBounds;
|
||||||
private final Rect progressBar;
|
private final Rect progressBar;
|
||||||
private final Rect bufferedBar;
|
private final Rect bufferedBar;
|
||||||
private final Rect scrubberBar;
|
private final Rect scrubberBar;
|
||||||
private final Paint progressPaint;
|
private final Paint playedPaint;
|
||||||
private final Paint bufferedPaint;
|
|
||||||
private final Paint scrubberPaint;
|
private final Paint scrubberPaint;
|
||||||
|
private final Paint bufferedPaint;
|
||||||
|
private final Paint unplayedPaint;
|
||||||
private final Paint adMarkerPaint;
|
private final Paint adMarkerPaint;
|
||||||
private final int barHeight;
|
private final int barHeight;
|
||||||
private final int touchTargetHeight;
|
private final int touchTargetHeight;
|
||||||
@ -115,9 +114,10 @@ public class DefaultTimeBar extends View implements TimeBar {
|
|||||||
progressBar = new Rect();
|
progressBar = new Rect();
|
||||||
bufferedBar = new Rect();
|
bufferedBar = new Rect();
|
||||||
scrubberBar = new Rect();
|
scrubberBar = new Rect();
|
||||||
progressPaint = new Paint();
|
playedPaint = new Paint();
|
||||||
bufferedPaint = new Paint();
|
|
||||||
scrubberPaint = new Paint();
|
scrubberPaint = new Paint();
|
||||||
|
bufferedPaint = new Paint();
|
||||||
|
unplayedPaint = new Paint();
|
||||||
adMarkerPaint = new Paint();
|
adMarkerPaint = new Paint();
|
||||||
|
|
||||||
// Calculate the dimensions and paints for drawn elements.
|
// Calculate the dimensions and paints for drawn elements.
|
||||||
@ -147,13 +147,18 @@ public class DefaultTimeBar extends View implements TimeBar {
|
|||||||
scrubberDraggedSize = a.getDimensionPixelSize(
|
scrubberDraggedSize = a.getDimensionPixelSize(
|
||||||
R.styleable.DefaultTimeBar_scrubber_dragged_size, defaultScrubberDraggedSize);
|
R.styleable.DefaultTimeBar_scrubber_dragged_size, defaultScrubberDraggedSize);
|
||||||
int playedColor = a.getInt(R.styleable.DefaultTimeBar_played_color, DEFAULT_PLAYED_COLOR);
|
int playedColor = a.getInt(R.styleable.DefaultTimeBar_played_color, DEFAULT_PLAYED_COLOR);
|
||||||
|
int scrubberColor = a.getInt(R.styleable.DefaultTimeBar_scrubber_color,
|
||||||
|
getDefaultScrubberColor(playedColor));
|
||||||
int bufferedColor = a.getInt(R.styleable.DefaultTimeBar_buffered_color,
|
int bufferedColor = a.getInt(R.styleable.DefaultTimeBar_buffered_color,
|
||||||
DEFAULT_BUFFERED_COLOR);
|
getDefaultBufferedColor(playedColor));
|
||||||
|
int unplayedColor = a.getInt(R.styleable.DefaultTimeBar_unplayed_color,
|
||||||
|
getDefaultUnplayedColor(playedColor));
|
||||||
int adMarkerColor = a.getInt(R.styleable.DefaultTimeBar_ad_marker_color,
|
int adMarkerColor = a.getInt(R.styleable.DefaultTimeBar_ad_marker_color,
|
||||||
DEFAULT_AD_MARKER_COLOR);
|
DEFAULT_AD_MARKER_COLOR);
|
||||||
progressPaint.setColor(playedColor);
|
playedPaint.setColor(playedColor);
|
||||||
scrubberPaint.setColor(OPAQUE_COLOR | playedColor);
|
scrubberPaint.setColor(scrubberColor);
|
||||||
bufferedPaint.setColor(bufferedColor);
|
bufferedPaint.setColor(bufferedColor);
|
||||||
|
unplayedPaint.setColor(unplayedColor);
|
||||||
adMarkerPaint.setColor(adMarkerColor);
|
adMarkerPaint.setColor(adMarkerColor);
|
||||||
} finally {
|
} finally {
|
||||||
a.recycle();
|
a.recycle();
|
||||||
@ -165,9 +170,10 @@ public class DefaultTimeBar extends View implements TimeBar {
|
|||||||
scrubberEnabledSize = defaultScrubberEnabledSize;
|
scrubberEnabledSize = defaultScrubberEnabledSize;
|
||||||
scrubberDisabledSize = defaultScrubberDisabledSize;
|
scrubberDisabledSize = defaultScrubberDisabledSize;
|
||||||
scrubberDraggedSize = defaultScrubberDraggedSize;
|
scrubberDraggedSize = defaultScrubberDraggedSize;
|
||||||
scrubberPaint.setColor(OPAQUE_COLOR | DEFAULT_PLAYED_COLOR);
|
playedPaint.setColor(DEFAULT_PLAYED_COLOR);
|
||||||
progressPaint.setColor(DEFAULT_PLAYED_COLOR);
|
scrubberPaint.setColor(getDefaultScrubberColor(DEFAULT_PLAYED_COLOR));
|
||||||
bufferedPaint.setColor(DEFAULT_BUFFERED_COLOR);
|
bufferedPaint.setColor(getDefaultBufferedColor(DEFAULT_PLAYED_COLOR));
|
||||||
|
unplayedPaint.setColor(getDefaultUnplayedColor(DEFAULT_PLAYED_COLOR));
|
||||||
adMarkerPaint.setColor(DEFAULT_AD_MARKER_COLOR);
|
adMarkerPaint.setColor(DEFAULT_AD_MARKER_COLOR);
|
||||||
}
|
}
|
||||||
formatBuilder = new StringBuilder();
|
formatBuilder = new StringBuilder();
|
||||||
@ -337,16 +343,18 @@ public class DefaultTimeBar extends View implements TimeBar {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||||
int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
|
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
|
||||||
int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
|
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
|
||||||
setMeasuredDimension(measureWidth, measureHeight);
|
int height = heightMode == MeasureSpec.UNSPECIFIED ? touchTargetHeight
|
||||||
|
: heightMode == MeasureSpec.EXACTLY ? heightSize : Math.min(touchTargetHeight, heightSize);
|
||||||
|
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), height);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||||
int width = right - left;
|
int width = right - left;
|
||||||
int height = bottom - top;
|
int height = bottom - top;
|
||||||
int barY = height - touchTargetHeight;
|
int barY = (height - touchTargetHeight) / 2;
|
||||||
int seekLeft = getPaddingLeft();
|
int seekLeft = getPaddingLeft();
|
||||||
int seekRight = width - getPaddingRight();
|
int seekRight = width - getPaddingRight();
|
||||||
int progressY = barY + (touchTargetHeight - barHeight) / 2;
|
int progressY = barY + (touchTargetHeight - barHeight) / 2;
|
||||||
@ -457,12 +465,10 @@ public class DefaultTimeBar extends View implements TimeBar {
|
|||||||
scrubberBar.set(progressBar);
|
scrubberBar.set(progressBar);
|
||||||
long newScrubberTime = scrubbing ? scrubPosition : position;
|
long newScrubberTime = scrubbing ? scrubPosition : position;
|
||||||
if (duration > 0) {
|
if (duration > 0) {
|
||||||
int bufferedPixelWidth =
|
int bufferedPixelWidth = (int) ((progressBar.width() * bufferedPosition) / duration);
|
||||||
(int) ((progressBar.width() * bufferedPosition) / duration);
|
bufferedBar.right = Math.min(progressBar.left + bufferedPixelWidth, progressBar.right);
|
||||||
bufferedBar.right = progressBar.left + bufferedPixelWidth;
|
int scrubberPixelPosition = (int) ((progressBar.width() * newScrubberTime) / duration);
|
||||||
int scrubberPixelPosition =
|
scrubberBar.right = Math.min(progressBar.left + scrubberPixelPosition, progressBar.right);
|
||||||
(int) ((progressBar.width() * newScrubberTime) / duration);
|
|
||||||
scrubberBar.right = progressBar.left + scrubberPixelPosition;
|
|
||||||
} else {
|
} else {
|
||||||
bufferedBar.right = progressBar.left;
|
bufferedBar.right = progressBar.left;
|
||||||
scrubberBar.right = progressBar.left;
|
scrubberBar.right = progressBar.left;
|
||||||
@ -502,21 +508,21 @@ public class DefaultTimeBar extends View implements TimeBar {
|
|||||||
int barTop = progressBar.centerY() - progressBarHeight / 2;
|
int barTop = progressBar.centerY() - progressBarHeight / 2;
|
||||||
int barBottom = barTop + progressBarHeight;
|
int barBottom = barTop + progressBarHeight;
|
||||||
if (duration <= 0) {
|
if (duration <= 0) {
|
||||||
canvas.drawRect(progressBar.left, barTop, progressBar.right, barBottom, progressPaint);
|
canvas.drawRect(progressBar.left, barTop, progressBar.right, barBottom, unplayedPaint);
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
int bufferedLeft = bufferedBar.left;
|
int bufferedLeft = bufferedBar.left;
|
||||||
int bufferedRight = bufferedBar.right;
|
int bufferedRight = bufferedBar.right;
|
||||||
int progressLeft = Math.max(Math.max(progressBar.left, bufferedRight), scrubberBar.right);
|
int progressLeft = Math.max(Math.max(progressBar.left, bufferedRight), scrubberBar.right);
|
||||||
if (progressLeft < progressBar.right) {
|
if (progressLeft < progressBar.right) {
|
||||||
canvas.drawRect(progressLeft, barTop, progressBar.right, barBottom, progressPaint);
|
canvas.drawRect(progressLeft, barTop, progressBar.right, barBottom, unplayedPaint);
|
||||||
}
|
}
|
||||||
bufferedLeft = Math.max(bufferedLeft, scrubberBar.right);
|
bufferedLeft = Math.max(bufferedLeft, scrubberBar.right);
|
||||||
if (bufferedRight > bufferedLeft) {
|
if (bufferedRight > bufferedLeft) {
|
||||||
canvas.drawRect(bufferedLeft, barTop, bufferedRight, barBottom, bufferedPaint);
|
canvas.drawRect(bufferedLeft, barTop, bufferedRight, barBottom, bufferedPaint);
|
||||||
}
|
}
|
||||||
if (scrubberBar.width() > 0) {
|
if (scrubberBar.width() > 0) {
|
||||||
canvas.drawRect(scrubberBar.left, barTop, scrubberBar.right, barBottom, scrubberPaint);
|
canvas.drawRect(scrubberBar.left, barTop, scrubberBar.right, barBottom, playedPaint);
|
||||||
}
|
}
|
||||||
int adMarkerOffset = adMarkerWidth / 2;
|
int adMarkerOffset = adMarkerWidth / 2;
|
||||||
for (int i = 0; i < adBreakCount; i++) {
|
for (int i = 0; i < adBreakCount; i++) {
|
||||||
@ -577,4 +583,16 @@ public class DefaultTimeBar extends View implements TimeBar {
|
|||||||
return (int) (dps * displayMetrics.density + 0.5f);
|
return (int) (dps * displayMetrics.density + 0.5f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static int getDefaultScrubberColor(int playedColor) {
|
||||||
|
return 0xFF000000 | playedColor;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getDefaultUnplayedColor(int playedColor) {
|
||||||
|
return 0x33000000 | (playedColor & 0x00FFFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getDefaultBufferedColor(int playedColor) {
|
||||||
|
return 0xCC000000 | (playedColor & 0x00FFFFFF);
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -68,7 +68,9 @@
|
|||||||
<attr name="scrubber_disabled_size" format="dimension"/>
|
<attr name="scrubber_disabled_size" format="dimension"/>
|
||||||
<attr name="scrubber_dragged_size" format="dimension"/>
|
<attr name="scrubber_dragged_size" format="dimension"/>
|
||||||
<attr name="played_color" format="color"/>
|
<attr name="played_color" format="color"/>
|
||||||
|
<attr name="scrubber_color" format="color"/>
|
||||||
<attr name="buffered_color" format="color"/>
|
<attr name="buffered_color" format="color"/>
|
||||||
|
<attr name="unplayed_color" format="color"/>
|
||||||
<attr name="ad_marker_color" format="color"/>
|
<attr name="ad_marker_color" format="color"/>
|
||||||
</declare-styleable>
|
</declare-styleable>
|
||||||
|
|
||||||
|
Loading…
x
Reference in New Issue
Block a user