mirror of
https://github.com/androidx/media.git
synced 2025-05-14 02:59:52 +08:00
commit
9f81485053
@ -1,5 +1,28 @@
|
||||
# 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 ###
|
||||
|
||||
* New modular library structure. You can read more about depending on individual
|
||||
|
@ -16,7 +16,7 @@ buildscript {
|
||||
jcenter()
|
||||
}
|
||||
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'
|
||||
}
|
||||
// Workaround for the following test coverage issue. Remove when fixed:
|
||||
@ -48,7 +48,7 @@ allprojects {
|
||||
releaseRepoName = getBintrayRepo()
|
||||
releaseUserOrg = 'google'
|
||||
releaseGroupId = 'com.google.android.exoplayer'
|
||||
releaseVersion = 'r2.4.0'
|
||||
releaseVersion = 'r2.4.1'
|
||||
releaseWebsite = 'https://github.com/google/ExoPlayer'
|
||||
}
|
||||
if (it.hasProperty('externalBuildDir')) {
|
||||
|
@ -16,8 +16,8 @@
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.google.android.exoplayer2.demo"
|
||||
android:versionCode="2400"
|
||||
android:versionName="2.4.0">
|
||||
android:versionCode="2401"
|
||||
android:versionName="2.4.1">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
|
@ -234,7 +234,6 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
|
||||
Intent intent = getIntent();
|
||||
boolean needNewPlayer = player == null;
|
||||
if (needNewPlayer) {
|
||||
boolean preferExtensionDecoders = intent.getBooleanExtra(PREFER_EXTENSION_DECODERS, false);
|
||||
UUID drmSchemeUuid = intent.hasExtra(DRM_SCHEME_UUID_EXTRA)
|
||||
? UUID.fromString(intent.getStringExtra(DRM_SCHEME_UUID_EXTRA)) : 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 =
|
||||
((DemoApplication) getApplication()).useExtensionRenderers()
|
||||
? (preferExtensionDecoders ? DefaultRenderersFactory.EXTENSION_RENDERER_MODE_PREFER
|
||||
@ -261,10 +261,10 @@ public class PlayerActivity extends Activity implements OnClickListener, ExoPlay
|
||||
DefaultRenderersFactory renderersFactory = new DefaultRenderersFactory(this,
|
||||
drmSessionManager, extensionRendererMode);
|
||||
|
||||
TrackSelection.Factory videoTrackSelectionFactory =
|
||||
TrackSelection.Factory adaptiveTrackSelectionFactory =
|
||||
new AdaptiveTrackSelection.Factory(BANDWIDTH_METER);
|
||||
trackSelector = new DefaultTrackSelector(videoTrackSelectionFactory);
|
||||
trackSelectionHelper = new TrackSelectionHelper(trackSelector, videoTrackSelectionFactory);
|
||||
trackSelector = new DefaultTrackSelector(adaptiveTrackSelectionFactory);
|
||||
trackSelectionHelper = new TrackSelectionHelper(trackSelector, adaptiveTrackSelectionFactory);
|
||||
lastSeenTrackGroupArray = null;
|
||||
|
||||
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 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 joiningDeadlineMs The joining deadline.
|
||||
* @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"
|
||||
|
||||
arch[4]="arm64-v8a"
|
||||
config[4]="--force-target=armv8-android-gcc --sdk-path=$ndk --disable-neon"
|
||||
config[4]+=" --disable-neon-asm"
|
||||
config[4]="--force-target=armv8-android-gcc --sdk-path=$ndk --enable-neon"
|
||||
|
||||
arch[5]="x86_64"
|
||||
config[5]="--force-target=x86_64-android-gcc --sdk-path=$ndk --disable-sse2"
|
||||
|
@ -9,7 +9,7 @@ track 0:
|
||||
id = null
|
||||
containerMimeType = null
|
||||
sampleMimeType = audio/vorbis
|
||||
maxInputSize = 65025
|
||||
maxInputSize = -1
|
||||
width = -1
|
||||
height = -1
|
||||
frameRate = -1.0
|
||||
|
@ -9,7 +9,7 @@ track 0:
|
||||
id = null
|
||||
containerMimeType = null
|
||||
sampleMimeType = audio/vorbis
|
||||
maxInputSize = 65025
|
||||
maxInputSize = -1
|
||||
width = -1
|
||||
height = -1
|
||||
frameRate = -1.0
|
||||
|
@ -9,7 +9,7 @@ track 0:
|
||||
id = null
|
||||
containerMimeType = null
|
||||
sampleMimeType = audio/vorbis
|
||||
maxInputSize = 65025
|
||||
maxInputSize = -1
|
||||
width = -1
|
||||
height = -1
|
||||
frameRate = -1.0
|
||||
|
@ -9,7 +9,7 @@ track 0:
|
||||
id = null
|
||||
containerMimeType = null
|
||||
sampleMimeType = audio/vorbis
|
||||
maxInputSize = 65025
|
||||
maxInputSize = -1
|
||||
width = -1
|
||||
height = -1
|
||||
frameRate = -1.0
|
||||
|
@ -9,7 +9,7 @@ track 0:
|
||||
id = null
|
||||
containerMimeType = null
|
||||
sampleMimeType = audio/vorbis
|
||||
maxInputSize = 65025
|
||||
maxInputSize = -1
|
||||
width = -1
|
||||
height = -1
|
||||
frameRate = -1.0
|
||||
|
@ -75,7 +75,7 @@ public final class TsExtractorTest extends InstrumentationTestCase {
|
||||
|
||||
public void testCustomPesReader() throws Exception {
|
||||
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);
|
||||
FakeExtractorInput input = new FakeExtractorInput.Builder()
|
||||
.setData(TestUtil.getByteArray(getInstrumentation(), "ts/sample.ts"))
|
||||
@ -100,7 +100,7 @@ public final class TsExtractorTest extends InstrumentationTestCase {
|
||||
|
||||
public void testCustomInitialSectionReader() throws Exception {
|
||||
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);
|
||||
FakeExtractorInput input = new FakeExtractorInput.Builder()
|
||||
.setData(TestUtil.getByteArray(getInstrumentation(), "ts/sample_with_sdt.ts"))
|
||||
|
@ -157,39 +157,39 @@ public final class TtmlDecoderTest extends InstrumentationTestCase {
|
||||
assertEquals(2, output.size());
|
||||
Cue ttmlCue = output.get(0);
|
||||
assertEquals("lorem", ttmlCue.text.toString());
|
||||
assertEquals(10.f / 100.f, ttmlCue.position);
|
||||
assertEquals(10.f / 100.f, ttmlCue.line);
|
||||
assertEquals(20.f / 100.f, ttmlCue.size);
|
||||
assertEquals(10f / 100f, ttmlCue.position);
|
||||
assertEquals(10f / 100f, ttmlCue.line);
|
||||
assertEquals(20f / 100f, ttmlCue.size);
|
||||
|
||||
ttmlCue = output.get(1);
|
||||
assertEquals("amet", ttmlCue.text.toString());
|
||||
assertEquals(60.f / 100.f, ttmlCue.position);
|
||||
assertEquals(10.f / 100.f, ttmlCue.line);
|
||||
assertEquals(20.f / 100.f, ttmlCue.size);
|
||||
assertEquals(60f / 100f, ttmlCue.position);
|
||||
assertEquals(10f / 100f, ttmlCue.line);
|
||||
assertEquals(20f / 100f, ttmlCue.size);
|
||||
|
||||
output = subtitle.getCues(5000000);
|
||||
assertEquals(1, output.size());
|
||||
ttmlCue = output.get(0);
|
||||
assertEquals("ipsum", ttmlCue.text.toString());
|
||||
assertEquals(40.f / 100.f, ttmlCue.position);
|
||||
assertEquals(40.f / 100.f, ttmlCue.line);
|
||||
assertEquals(20.f / 100.f, ttmlCue.size);
|
||||
assertEquals(40f / 100f, ttmlCue.position);
|
||||
assertEquals(40f / 100f, ttmlCue.line);
|
||||
assertEquals(20f / 100f, ttmlCue.size);
|
||||
|
||||
output = subtitle.getCues(9000000);
|
||||
assertEquals(1, output.size());
|
||||
ttmlCue = output.get(0);
|
||||
assertEquals("dolor", ttmlCue.text.toString());
|
||||
assertEquals(10.f / 100.f, ttmlCue.position);
|
||||
assertEquals(80.f / 100.f, ttmlCue.line);
|
||||
assertEquals(Cue.DIMEN_UNSET, ttmlCue.size);
|
||||
assertEquals(10f / 100f, ttmlCue.position);
|
||||
assertEquals(80f / 100f, ttmlCue.line);
|
||||
assertEquals(1f, ttmlCue.size);
|
||||
|
||||
output = subtitle.getCues(21000000);
|
||||
assertEquals(1, output.size());
|
||||
ttmlCue = output.get(0);
|
||||
assertEquals("She first said this", ttmlCue.text.toString());
|
||||
assertEquals(45.f / 100.f, ttmlCue.position);
|
||||
assertEquals(45.f / 100.f, ttmlCue.line);
|
||||
assertEquals(35.f / 100.f, ttmlCue.size);
|
||||
assertEquals(45f / 100f, ttmlCue.position);
|
||||
assertEquals(45f / 100f, ttmlCue.line);
|
||||
assertEquals(35f / 100f, ttmlCue.size);
|
||||
output = subtitle.getCues(25000000);
|
||||
ttmlCue = output.get(0);
|
||||
assertEquals("She first said this\nThen this", ttmlCue.text.toString());
|
||||
@ -197,8 +197,8 @@ public final class TtmlDecoderTest extends InstrumentationTestCase {
|
||||
assertEquals(1, output.size());
|
||||
ttmlCue = output.get(0);
|
||||
assertEquals("She first said this\nThen this\nFinally this", ttmlCue.text.toString());
|
||||
assertEquals(45.f / 100.f, ttmlCue.position);
|
||||
assertEquals(45.f / 100.f, ttmlCue.line);
|
||||
assertEquals(45f / 100f, ttmlCue.position);
|
||||
assertEquals(45f / 100f, ttmlCue.line);
|
||||
}
|
||||
|
||||
public void testEmptyStyleAttribute() throws IOException, SubtitleDecoderException {
|
||||
|
@ -142,9 +142,9 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
|
||||
public final void disable() {
|
||||
Assertions.checkState(state == STATE_ENABLED);
|
||||
state = STATE_DISABLED;
|
||||
onDisabled();
|
||||
stream = null;
|
||||
streamIsFinal = false;
|
||||
onDisabled();
|
||||
}
|
||||
|
||||
// RendererCapabilities implementation.
|
||||
@ -300,8 +300,6 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
|
||||
|
||||
/**
|
||||
* Returns whether the upstream source is ready.
|
||||
*
|
||||
* @return Whether the source is ready.
|
||||
*/
|
||||
protected final boolean isSourceReady() {
|
||||
return readEndOfStream ? streamIsFinal : stream.isReady();
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2;
|
||||
|
||||
import android.os.Looper;
|
||||
import android.support.annotation.IntDef;
|
||||
import android.support.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.audio.MediaCodecAudioRenderer;
|
||||
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
|
||||
* discouraged, however if an application does wish to do this then it may do so provided that it
|
||||
* 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
|
||||
* Renderers, MediaSources, TrackSelectors and LoadControls are called by the player on this
|
||||
* thread.</li>
|
||||
@ -113,8 +117,8 @@ public interface ExoPlayer {
|
||||
* Called when the timeline and/or manifest has been refreshed.
|
||||
* <p>
|
||||
* 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
|
||||
* removed from the timeline. The will <em>not</em> be reported via a separate call to
|
||||
* For example, the current period index may have changed as a result of periods being added or
|
||||
* removed from the timeline. This will <em>not</em> be reported via a separate call to
|
||||
* {@link #onPositionDiscontinuity()}.
|
||||
*
|
||||
* @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
|
||||
* 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.
|
||||
*/
|
||||
|
@ -16,7 +16,6 @@
|
||||
package com.google.android.exoplayer2;
|
||||
|
||||
import android.content.Context;
|
||||
import android.os.Looper;
|
||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||
import com.google.android.exoplayer2.drm.FrameworkMediaCrypto;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
||||
@ -29,8 +28,7 @@ public final class ExoPlayerFactory {
|
||||
private ExoPlayerFactory() {}
|
||||
|
||||
/**
|
||||
* Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated
|
||||
* {@link Looper}.
|
||||
* Creates a {@link SimpleExoPlayer} instance.
|
||||
*
|
||||
* @param context A {@link Context}.
|
||||
* @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
|
||||
* {@link Looper}. Available extension renderers are not used.
|
||||
* Creates a {@link SimpleExoPlayer} instance. Available extension renderers are not used.
|
||||
*
|
||||
* @param context A {@link Context}.
|
||||
* @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
|
||||
* {@link Looper}.
|
||||
* Creates a {@link SimpleExoPlayer} instance.
|
||||
*
|
||||
* @param context A {@link Context}.
|
||||
* @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
|
||||
* {@link Looper}.
|
||||
* Creates a {@link SimpleExoPlayer} instance.
|
||||
*
|
||||
* @param context A {@link Context}.
|
||||
* @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
|
||||
* {@link Looper}.
|
||||
* Creates a {@link SimpleExoPlayer} instance.
|
||||
*
|
||||
* @param context A {@link Context}.
|
||||
* @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
|
||||
* {@link Looper}.
|
||||
* Creates a {@link SimpleExoPlayer} 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.
|
||||
@ -135,8 +128,7 @@ public final class ExoPlayerFactory {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a {@link SimpleExoPlayer} instance. Must be called from a thread that has an associated
|
||||
* {@link Looper}.
|
||||
* Creates a {@link SimpleExoPlayer} 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.
|
||||
@ -148,8 +140,7 @@ public final class ExoPlayerFactory {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link ExoPlayer} instance. Must be called from a thread that has an associated
|
||||
* {@link Looper}.
|
||||
* Creates an {@link ExoPlayer} 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.
|
||||
@ -159,8 +150,7 @@ public final class ExoPlayerFactory {
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an {@link ExoPlayer} instance. Must be called from a thread that has an associated
|
||||
* {@link Looper}.
|
||||
* Creates an {@link ExoPlayer} 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.
|
||||
|
@ -92,7 +92,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
|
||||
trackGroups = TrackGroupArray.EMPTY;
|
||||
trackSelections = emptyTrackSelections;
|
||||
playbackParameters = PlaybackParameters.DEFAULT;
|
||||
eventHandler = new Handler() {
|
||||
Looper eventLooper = Looper.myLooper() != null ? Looper.myLooper() : Looper.getMainLooper();
|
||||
eventHandler = new Handler(eventLooper) {
|
||||
@Override
|
||||
public void handleMessage(Message 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".
|
||||
*/
|
||||
// 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}.
|
||||
*/
|
||||
// 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.
|
||||
@ -40,7 +40,7 @@ public interface ExoPlayerLibraryInfo {
|
||||
* integer version 123045006 (123-045-006).
|
||||
*/
|
||||
// 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}
|
||||
|
@ -20,6 +20,7 @@ import android.graphics.SurfaceTexture;
|
||||
import android.media.MediaCodec;
|
||||
import android.media.PlaybackParams;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.support.annotation.Nullable;
|
||||
import android.util.Log;
|
||||
import android.view.Surface;
|
||||
@ -111,8 +112,10 @@ public class SimpleExoPlayer implements ExoPlayer {
|
||||
protected SimpleExoPlayer(RenderersFactory renderersFactory, TrackSelector trackSelector,
|
||||
LoadControl loadControl) {
|
||||
componentListener = new ComponentListener();
|
||||
renderers = renderersFactory.createRenderers(new Handler(), componentListener,
|
||||
componentListener, componentListener, componentListener);
|
||||
Looper eventLooper = Looper.myLooper() != null ? Looper.myLooper() : Looper.getMainLooper();
|
||||
Handler eventHandler = new Handler(eventLooper);
|
||||
renderers = renderersFactory.createRenderers(eventHandler, componentListener, componentListener,
|
||||
componentListener, componentListener);
|
||||
|
||||
// Obtain counts of video and audio renderers.
|
||||
int videoRendererCount = 0;
|
||||
|
@ -374,8 +374,8 @@ import java.util.Arrays;
|
||||
}
|
||||
|
||||
private short interpolate(short[] in, int inPos, int oldSampleRate, int newSampleRate) {
|
||||
short left = in[inPos * numChannels];
|
||||
short right = in[inPos * numChannels + numChannels];
|
||||
short left = in[inPos];
|
||||
short right = in[inPos + numChannels];
|
||||
int position = newRatePosition * oldSampleRate;
|
||||
int leftPosition = oldRatePosition * newSampleRate;
|
||||
int rightPosition = (oldRatePosition + 1) * newSampleRate;
|
||||
@ -402,7 +402,7 @@ import java.util.Arrays;
|
||||
enlargeOutputBufferIfNeeded(1);
|
||||
for (int i = 0; i < numChannels; i++) {
|
||||
outputBuffer[numOutputSamples * numChannels + i] =
|
||||
interpolate(pitchBuffer, position + i, oldSampleRate, newSampleRate);
|
||||
interpolate(pitchBuffer, position * numChannels + i, oldSampleRate, newSampleRate);
|
||||
}
|
||||
newRatePosition++;
|
||||
numOutputSamples++;
|
||||
|
@ -16,6 +16,7 @@
|
||||
package com.google.android.exoplayer2.decoder;
|
||||
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
|
||||
/**
|
||||
* Buffer for {@link SimpleDecoder} output.
|
||||
@ -40,7 +41,7 @@ public class SimpleOutputBuffer extends OutputBuffer {
|
||||
public ByteBuffer init(long timeUs, int size) {
|
||||
this.timeUs = timeUs;
|
||||
if (data == null || data.capacity() < size) {
|
||||
data = ByteBuffer.allocateDirect(size);
|
||||
data = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());
|
||||
}
|
||||
data.position(0);
|
||||
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.PsExtractor;
|
||||
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.util.TimestampAdjuster;
|
||||
import java.lang.reflect.Constructor;
|
||||
|
||||
/**
|
||||
@ -67,8 +69,13 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
|
||||
private @MatroskaExtractor.Flags int matroskaFlags;
|
||||
private @FragmentedMp4Extractor.Flags int fragmentedMp4Flags;
|
||||
private @Mp3Extractor.Flags int mp3Flags;
|
||||
private @TsExtractor.Mode int tsMode;
|
||||
private @DefaultTsPayloadReaderFactory.Flags int tsFlags;
|
||||
|
||||
public DefaultExtractorsFactory() {
|
||||
tsMode = TsExtractor.MODE_SINGLE_PMT;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets flags for {@link MatroskaExtractor} instances created by the factory.
|
||||
*
|
||||
@ -107,6 +114,18 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
|
||||
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
|
||||
* created by the factory.
|
||||
@ -130,7 +149,7 @@ public final class DefaultExtractorsFactory implements ExtractorsFactory {
|
||||
extractors[3] = new Mp3Extractor(mp3Flags);
|
||||
extractors[4] = new AdtsExtractor();
|
||||
extractors[5] = new Ac3Extractor();
|
||||
extractors[6] = new TsExtractor(tsFlags);
|
||||
extractors[6] = new TsExtractor(tsMode, tsFlags);
|
||||
extractors[7] = new FlvExtractor();
|
||||
extractors[8] = new OggExtractor();
|
||||
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.ParsableByteArray;
|
||||
import java.io.IOException;
|
||||
import java.util.Arrays;
|
||||
|
||||
/**
|
||||
* OGG packet class.
|
||||
@ -27,8 +28,8 @@ import java.io.IOException;
|
||||
/* package */ final class OggPacket {
|
||||
|
||||
private final OggPageHeader pageHeader = new OggPageHeader();
|
||||
private final ParsableByteArray packetArray =
|
||||
new ParsableByteArray(new byte[OggPageHeader.MAX_PAGE_PAYLOAD], 0);
|
||||
private final ParsableByteArray packetArray = new ParsableByteArray(
|
||||
new byte[OggPageHeader.MAX_PAGE_PAYLOAD], 0);
|
||||
|
||||
private int currentSegmentIndex = C.INDEX_UNSET;
|
||||
private int segmentCount;
|
||||
@ -85,6 +86,9 @@ import java.io.IOException;
|
||||
int size = calculatePacketSize(currentSegmentIndex);
|
||||
int segmentIndex = currentSegmentIndex + segmentCount;
|
||||
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);
|
||||
packetArray.setLimit(packetArray.limit() + size);
|
||||
populated = pageHeader.laces[segmentIndex - 1] != 255;
|
||||
@ -118,6 +122,17 @@ import java.io.IOException;
|
||||
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}.
|
||||
*
|
||||
|
@ -103,15 +103,12 @@ import java.io.IOException;
|
||||
switch (state) {
|
||||
case STATE_READ_HEADERS:
|
||||
return readHeaders(input);
|
||||
|
||||
case STATE_SKIP_HEADERS:
|
||||
input.skipFully((int) payloadStartPosition);
|
||||
state = STATE_READ_PAYLOAD;
|
||||
return Extractor.RESULT_CONTINUE;
|
||||
|
||||
case STATE_READ_PAYLOAD:
|
||||
return readPayload(input, seekPosition);
|
||||
|
||||
default:
|
||||
// Never happens.
|
||||
throw new IllegalStateException();
|
||||
@ -152,6 +149,8 @@ import java.io.IOException;
|
||||
|
||||
setupData = null;
|
||||
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;
|
||||
}
|
||||
|
||||
|
@ -101,7 +101,7 @@ import java.util.ArrayList;
|
||||
codecInitialisationData.add(vorbisSetup.setupHeaderData);
|
||||
|
||||
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,
|
||||
codecInitialisationData, null, 0, null);
|
||||
return true;
|
||||
|
@ -65,13 +65,13 @@ public final class TsExtractor implements Extractor {
|
||||
* Modes for the extractor.
|
||||
*/
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({MODE_NORMAL, MODE_SINGLE_PMT, MODE_HLS})
|
||||
@IntDef({MODE_MULTI_PMT, MODE_SINGLE_PMT, MODE_HLS})
|
||||
public @interface Mode {}
|
||||
|
||||
/**
|
||||
* 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.
|
||||
*/
|
||||
@ -132,12 +132,23 @@ public final class TsExtractor implements Extractor {
|
||||
* {@code FLAG_*} values that control the behavior of the payload readers.
|
||||
*/
|
||||
public TsExtractor(@Flags int defaultTsPayloadReaderFlags) {
|
||||
this(MODE_NORMAL, new TimestampAdjuster(0),
|
||||
new DefaultTsPayloadReaderFactory(defaultTsPayloadReaderFlags));
|
||||
this(MODE_SINGLE_PMT, 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}.
|
||||
* @param timestampAdjuster A timestamp adjuster for offsetting and scaling sample timestamps.
|
||||
* @param payloadReaderFactory Factory for injecting a custom set of payload readers.
|
||||
|
@ -488,7 +488,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||
}
|
||||
if (format == null) {
|
||||
// We don't have a format yet, so try and read one.
|
||||
buffer.clear();
|
||||
flagsOnlyBuffer.clear();
|
||||
int result = readSource(formatHolder, flagsOnlyBuffer, true);
|
||||
if (result == C.RESULT_FORMAT_READ) {
|
||||
onInputFormatChanged(formatHolder.format);
|
||||
|
@ -429,6 +429,7 @@ public final class MediaCodecUtil {
|
||||
case CodecProfileLevel.AVCLevel42: return 8704 * 16 * 16;
|
||||
case CodecProfileLevel.AVCLevel5: return 22080 * 16 * 16;
|
||||
case CodecProfileLevel.AVCLevel51: return 36864 * 16 * 16;
|
||||
case CodecProfileLevel.AVCLevel52: return 36864 * 16 * 16;
|
||||
default: return -1;
|
||||
}
|
||||
}
|
||||
|
@ -153,7 +153,6 @@ public final class MetadataRenderer extends BaseRenderer implements Callback {
|
||||
protected void onDisabled() {
|
||||
flushPendingMetadata();
|
||||
decoder = null;
|
||||
super.onDisabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -254,7 +254,6 @@ public final class TextRenderer extends BaseRenderer implements Callback {
|
||||
streamFormat = null;
|
||||
clearOutput();
|
||||
releaseDecoder();
|
||||
super.onDisabled();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -17,7 +17,6 @@ package com.google.android.exoplayer2.text.ttml;
|
||||
|
||||
import android.text.Layout;
|
||||
import android.util.Log;
|
||||
import android.util.Pair;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.text.Cue;
|
||||
import com.google.android.exoplayer2.text.SimpleSubtitleDecoder;
|
||||
@ -100,7 +99,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||
XmlPullParser xmlParser = xmlParserFactory.newPullParser();
|
||||
Map<String, TtmlStyle> globalStyles = 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);
|
||||
xmlParser.setInput(inputStream, null);
|
||||
TtmlSubtitle ttmlSubtitle = null;
|
||||
@ -211,9 +210,9 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||
globalStyles.put(style.getId(), style);
|
||||
}
|
||||
} else if (XmlPullParserUtil.isStartTag(xmlParser, TtmlNode.TAG_REGION)) {
|
||||
Pair<String, TtmlRegion> ttmlRegionInfo = parseRegionAttributes(xmlParser);
|
||||
if (ttmlRegionInfo != null) {
|
||||
globalRegions.put(ttmlRegionInfo.first, ttmlRegionInfo.second);
|
||||
TtmlRegion ttmlRegion = parseRegionAttributes(xmlParser);
|
||||
if (ttmlRegion != null) {
|
||||
globalRegions.put(ttmlRegion.id, ttmlRegion);
|
||||
}
|
||||
}
|
||||
} 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
|
||||
* terms of percentage of the viewport. Regions that do not correctly declare origin are ignored.
|
||||
* Parses a region declaration.
|
||||
* <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 regionOrigin = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_ORIGIN);
|
||||
String regionExtent = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_EXTENT);
|
||||
if (regionOrigin == null || regionId == null) {
|
||||
if (regionId == null) {
|
||||
return null;
|
||||
}
|
||||
float position = Cue.DIMEN_UNSET;
|
||||
float line = Cue.DIMEN_UNSET;
|
||||
Matcher originMatcher = PERCENTAGE_COORDINATES.matcher(regionOrigin);
|
||||
if (originMatcher.matches()) {
|
||||
try {
|
||||
position = Float.parseFloat(originMatcher.group(1)) / 100.f;
|
||||
line = Float.parseFloat(originMatcher.group(2)) / 100.f;
|
||||
} catch (NumberFormatException e) {
|
||||
Log.w(TAG, "Ignoring region with malformed origin: '" + regionOrigin + "'", e);
|
||||
position = Cue.DIMEN_UNSET;
|
||||
|
||||
float position;
|
||||
float line;
|
||||
String regionOrigin = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_TTS_ORIGIN);
|
||||
if (regionOrigin != null) {
|
||||
Matcher originMatcher = PERCENTAGE_COORDINATES.matcher(regionOrigin);
|
||||
if (originMatcher.matches()) {
|
||||
try {
|
||||
position = Float.parseFloat(originMatcher.group(1)) / 100f;
|
||||
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) {
|
||||
Matcher extentMatcher = PERCENTAGE_COORDINATES.matcher(regionExtent);
|
||||
if (extentMatcher.matches()) {
|
||||
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) {
|
||||
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) {
|
||||
@ -277,7 +319,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||
try {
|
||||
style.setBackgroundColor(ColorParser.parseTtmlColor(attributeValue));
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.w(TAG, "failed parsing background value: '" + attributeValue + "'");
|
||||
Log.w(TAG, "Failed parsing background value: " + attributeValue);
|
||||
}
|
||||
break;
|
||||
case TtmlNode.ATTR_TTS_COLOR:
|
||||
@ -285,7 +327,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||
try {
|
||||
style.setFontColor(ColorParser.parseTtmlColor(attributeValue));
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.w(TAG, "failed parsing color value: '" + attributeValue + "'");
|
||||
Log.w(TAG, "Failed parsing color value: " + attributeValue);
|
||||
}
|
||||
break;
|
||||
case TtmlNode.ATTR_TTS_FONT_FAMILY:
|
||||
@ -296,7 +338,7 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||
style = createIfNull(style);
|
||||
parseFontSize(attributeValue, style);
|
||||
} catch (SubtitleDecoderException e) {
|
||||
Log.w(TAG, "failed parsing fontSize value: '" + attributeValue + "'");
|
||||
Log.w(TAG, "Failed parsing fontSize value: " + attributeValue);
|
||||
}
|
||||
break;
|
||||
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 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_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_SIZE = "fontSize";
|
||||
public static final String ATTR_TTS_FONT_FAMILY = "fontFamily";
|
||||
public static final String ATTR_TTS_FONT_WEIGHT = "fontWeight";
|
||||
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_ALIGN = "textAlign";
|
||||
|
||||
@ -179,7 +180,7 @@ import java.util.TreeSet;
|
||||
for (Entry<String, SpannableStringBuilder> entry : regionOutputs.entrySet()) {
|
||||
TtmlRegion region = regionMap.get(entry.getKey());
|
||||
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;
|
||||
}
|
||||
|
@ -22,20 +22,24 @@ import com.google.android.exoplayer2.text.Cue;
|
||||
*/
|
||||
/* package */ final class TtmlRegion {
|
||||
|
||||
public final String id;
|
||||
public final float position;
|
||||
public final float line;
|
||||
@Cue.LineType
|
||||
public final int lineType;
|
||||
@Cue.LineType public final int lineType;
|
||||
@Cue.AnchorType public final int lineAnchor;
|
||||
public final float width;
|
||||
|
||||
public TtmlRegion() {
|
||||
this(Cue.DIMEN_UNSET, Cue.DIMEN_UNSET, Cue.TYPE_UNSET, Cue.DIMEN_UNSET);
|
||||
public TtmlRegion(String id) {
|
||||
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.line = line;
|
||||
this.lineType = lineType;
|
||||
this.lineAnchor = lineAnchor;
|
||||
this.width = width;
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,7 @@ import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.RendererCapabilities;
|
||||
import com.google.android.exoplayer2.source.TrackGroup;
|
||||
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.Util;
|
||||
import java.util.ArrayList;
|
||||
@ -376,10 +377,21 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
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() {
|
||||
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) {
|
||||
return TextUtils.equals(language, Util.normalizeLanguageCode(format.language));
|
||||
return language != null
|
||||
&& TextUtils.equals(language, Util.normalizeLanguageCode(format.language));
|
||||
}
|
||||
|
||||
// Viewport size util methods.
|
||||
|
@ -33,11 +33,11 @@ import java.util.concurrent.ExecutorService;
|
||||
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 UnexpectedLoaderException(Exception cause) {
|
||||
public UnexpectedLoaderException(Throwable cause) {
|
||||
super("Unexpected " + cause.getClass().getSimpleName() + ": " + cause.getMessage(), cause);
|
||||
}
|
||||
|
||||
@ -316,6 +316,14 @@ public final class Loader implements LoaderErrorThrower {
|
||||
if (!released) {
|
||||
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) {
|
||||
// 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
|
||||
|
@ -54,8 +54,8 @@ public final class CacheDataSource implements DataSource {
|
||||
FLAG_IGNORE_CACHE_FOR_UNSET_LENGTH_REQUESTS})
|
||||
public @interface Flags {}
|
||||
/**
|
||||
* A flag indicating whether we will block reads if the cache key is locked. If this flag is
|
||||
* set, then we will read from upstream if the cache key is locked.
|
||||
* A flag indicating whether we will block reads if the cache key is locked. If unset then data is
|
||||
* 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;
|
||||
|
||||
@ -110,7 +110,23 @@ public final class CacheDataSource implements DataSource {
|
||||
|
||||
/**
|
||||
* 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) {
|
||||
this(cache, upstream, flags, DEFAULT_MAX_CACHE_FILE_SIZE);
|
||||
@ -123,8 +139,8 @@ public final class CacheDataSource implements DataSource {
|
||||
*
|
||||
* @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} and {@link
|
||||
* #FLAG_IGNORE_CACHE_ON_ERROR} or 0.
|
||||
* @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.
|
||||
* @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
|
||||
* 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 cacheWriteDataSink A {@link DataSink} for writing data to the cache. If null, cache is
|
||||
* accessed read-only.
|
||||
* @param flags A combination of {@link #FLAG_BLOCK_ON_CACHE} and {@link
|
||||
* #FLAG_IGNORE_CACHE_ON_ERROR} or 0.
|
||||
* @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.
|
||||
* @param eventListener An optional {@link EventListener} to receive events.
|
||||
*/
|
||||
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 EventListener eventListener;
|
||||
|
||||
/**
|
||||
* @see CacheDataSource#CacheDataSource(Cache, DataSource)
|
||||
*/
|
||||
public CacheDataSourceFactory(Cache cache, DataSource.Factory upstreamFactory) {
|
||||
this(cache, upstreamFactory, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* @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);
|
||||
}
|
||||
|
||||
/**
|
||||
* @see CacheDataSource#CacheDataSource(Cache, DataSource, int, long)
|
||||
*/
|
||||
public CacheDataSourceFactory(Cache cache, DataSource.Factory upstreamFactory, int flags,
|
||||
long maxCacheFileSize) {
|
||||
public CacheDataSourceFactory(Cache cache, DataSource.Factory upstreamFactory,
|
||||
@CacheDataSource.Flags int flags, long maxCacheFileSize) {
|
||||
this(cache, upstreamFactory, new FileDataSourceFactory(),
|
||||
new CacheDataSinkFactory(cache, maxCacheFileSize), flags, null);
|
||||
}
|
||||
@ -54,8 +62,8 @@ public final class CacheDataSourceFactory implements DataSource.Factory {
|
||||
* EventListener)
|
||||
*/
|
||||
public CacheDataSourceFactory(Cache cache, Factory upstreamFactory,
|
||||
Factory cacheReadDataSourceFactory,
|
||||
DataSink.Factory cacheWriteDataSinkFactory, int flags, EventListener eventListener) {
|
||||
Factory cacheReadDataSourceFactory, DataSink.Factory cacheWriteDataSinkFactory,
|
||||
@CacheDataSource.Flags int flags, EventListener eventListener) {
|
||||
this.cache = cache;
|
||||
this.upstreamFactory = upstreamFactory;
|
||||
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}.
|
||||
*
|
||||
* @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 {
|
||||
CachedContent cachedContent = index.get(span.key);
|
||||
Assertions.checkState(cachedContent.removeSpan(span));
|
||||
if (cachedContent == null || !cachedContent.removeSpan(span)) {
|
||||
return;
|
||||
}
|
||||
totalSpace -= span.length;
|
||||
if (removeEmptyCachedContent && cachedContent.isEmpty()) {
|
||||
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
|
||||
protected void onOutputFormatChanged(MediaCodec codec, android.media.MediaFormat outputFormat) {
|
||||
protected void onOutputFormatChanged(MediaCodec codec, MediaFormat outputFormat) {
|
||||
boolean hasCrop = outputFormat.containsKey(KEY_CROP_RIGHT)
|
||||
&& outputFormat.containsKey(KEY_CROP_LEFT) && outputFormat.containsKey(KEY_CROP_BOTTOM)
|
||||
&& outputFormat.containsKey(KEY_CROP_TOP);
|
||||
@ -408,11 +408,9 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||
@Override
|
||||
protected boolean canReconfigureCodec(MediaCodec codec, boolean codecIsAdaptive,
|
||||
Format oldFormat, Format newFormat) {
|
||||
return areAdaptationCompatible(oldFormat, newFormat)
|
||||
return areAdaptationCompatible(codecIsAdaptive, oldFormat, newFormat)
|
||||
&& newFormat.width <= codecMaxValues.width && newFormat.height <= codecMaxValues.height
|
||||
&& newFormat.maxInputSize <= codecMaxValues.inputSize
|
||||
&& (codecIsAdaptive
|
||||
|| (oldFormat.width == newFormat.width && oldFormat.height == newFormat.height));
|
||||
&& newFormat.maxInputSize <= codecMaxValues.inputSize;
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -664,7 +662,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||
}
|
||||
boolean haveUnknownDimensions = false;
|
||||
for (Format streamFormat : streamFormats) {
|
||||
if (areAdaptationCompatible(format, streamFormat)) {
|
||||
if (areAdaptationCompatible(codecInfo.adaptive, format, streamFormat)) {
|
||||
haveUnknownDimensions |= (streamFormat.width == Format.NO_VALUE
|
||||
|| streamFormat.height == Format.NO_VALUE);
|
||||
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
|
||||
* between two {@link Format}s.
|
||||
* Returns whether a codec with suitable {@link CodecMaxValues} will support adaptation between
|
||||
* two {@link Format}s.
|
||||
*
|
||||
* @param codecIsAdaptive Whether the codec supports seamless resolution switches.
|
||||
* @param first The first format.
|
||||
* @param second The second format.
|
||||
* @return Whether an adaptive codec with suitable {@link CodecMaxValues} will support adaptation
|
||||
* between two {@link Format}s.
|
||||
* @return Whether the codec will support adaptation between the 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)
|
||||
&& getRotationDegrees(first) == getRotationDegrees(second);
|
||||
&& getRotationDegrees(first) == getRotationDegrees(second)
|
||||
&& (codecIsAdaptive || (first.width == second.width && first.height == second.height));
|
||||
}
|
||||
|
||||
private static float getPixelWidthHeightRatio(Format format) {
|
||||
|
@ -23,6 +23,7 @@ import com.google.android.exoplayer2.util.MimeTypes;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.io.IOException;
|
||||
import java.nio.charset.Charset;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
@ -56,16 +57,22 @@ public class HlsMasterPlaylistParserTest extends TestCase {
|
||||
|
||||
private static final String MASTER_PLAYLIST_WITH_CC = " #EXTM3U \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"
|
||||
+ "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{
|
||||
HlsMasterPlaylist masterPlaylist = parseMasterPlaylist(PLAYLIST_URI, MASTER_PLAYLIST);
|
||||
|
||||
List<HlsMasterPlaylist.HlsUrl> variants = masterPlaylist.variants;
|
||||
assertNotNull(variants);
|
||||
assertEquals(5, variants.size());
|
||||
assertNull(masterPlaylist.muxedCaptionFormats);
|
||||
|
||||
assertEquals(1280000, variants.get(0).format.bitrate);
|
||||
assertNotNull(variants.get(0).format.codecs);
|
||||
@ -117,6 +124,11 @@ public class HlsMasterPlaylistParserTest extends TestCase {
|
||||
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)
|
||||
throws IOException {
|
||||
Uri playlistUri = Uri.parse(uri);
|
||||
|
@ -92,6 +92,7 @@ import java.util.Locale;
|
||||
private boolean isTimestampMaster;
|
||||
private byte[] scratchSpace;
|
||||
private IOException fatalError;
|
||||
private HlsUrl expectedPlaylistUrl;
|
||||
|
||||
private Uri encryptionKeyUri;
|
||||
private byte[] encryptionKey;
|
||||
@ -111,7 +112,8 @@ import java.util.Locale;
|
||||
* @param timestampAdjusterProvider A provider of {@link TimestampAdjuster} instances. If
|
||||
* multiple {@link HlsChunkSource}s are used for a single playback, they should all share the
|
||||
* 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,
|
||||
HlsDataSourceFactory dataSourceFactory, TimestampAdjusterProvider timestampAdjusterProvider,
|
||||
@ -142,6 +144,9 @@ import java.util.Locale;
|
||||
if (fatalError != null) {
|
||||
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) {
|
||||
int oldVariantIndex = previous == null ? C.INDEX_UNSET
|
||||
: trackGroup.indexOf(previous.trackFormat);
|
||||
expectedPlaylistUrl = null;
|
||||
// Use start time of the previous chunk rather than its end time because switching format will
|
||||
// require downloading overlapping segments.
|
||||
long bufferedDurationUs = previous == null ? 0
|
||||
@ -207,6 +213,7 @@ import java.util.Locale;
|
||||
HlsUrl selectedUrl = variants[selectedVariantIndex];
|
||||
if (!playlistTracker.isSnapshotValid(selectedUrl)) {
|
||||
out.playlist = selectedUrl;
|
||||
expectedPlaylistUrl = selectedUrl;
|
||||
// Retry when playlist is refreshed.
|
||||
return;
|
||||
}
|
||||
@ -246,6 +253,7 @@ import java.util.Locale;
|
||||
out.endOfStream = true;
|
||||
} else /* Live */ {
|
||||
out.playlist = selectedUrl;
|
||||
expectedPlaylistUrl = selectedUrl;
|
||||
}
|
||||
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.Util;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
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 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 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 trackSelectionData See {@link #trackSelectionData}.
|
||||
* @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.
|
||||
@DefaultTsPayloadReaderFactory.Flags
|
||||
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.
|
||||
esReaderFactoryFlags |= DefaultTsPayloadReaderFactory.FLAG_OVERRIDE_CAPTION_DESCRIPTORS;
|
||||
} else {
|
||||
closedCaptionFormats = Collections.emptyList();
|
||||
}
|
||||
String codecs = trackFormat.codecs;
|
||||
if (!TextUtils.isEmpty(codecs)) {
|
||||
@ -373,7 +378,7 @@ import java.util.concurrent.atomic.AtomicInteger;
|
||||
}
|
||||
}
|
||||
extractor = new TsExtractor(TsExtractor.MODE_HLS, timestampAdjuster,
|
||||
new DefaultTsPayloadReaderFactory(esReaderFactoryFlags, muxedCaptionFormats));
|
||||
new DefaultTsPayloadReaderFactory(esReaderFactoryFlags, closedCaptionFormats));
|
||||
}
|
||||
if (usingNewExtractor) {
|
||||
extractor.init(extractorOutput);
|
||||
|
@ -84,7 +84,7 @@ public final class HlsMediaSource implements MediaSource,
|
||||
|
||||
@Override
|
||||
public void maybeThrowSourceInfoRefreshError() throws IOException {
|
||||
playlistTracker.maybeThrowPlaylistRefreshError();
|
||||
playlistTracker.maybeThrowPrimaryPlaylistRefreshError();
|
||||
}
|
||||
|
||||
@Override
|
||||
|
@ -30,15 +30,31 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
|
||||
*/
|
||||
public static final class HlsUrl {
|
||||
|
||||
/**
|
||||
* The http url from which the media playlist can be obtained.
|
||||
*/
|
||||
public final String url;
|
||||
/**
|
||||
* Format information associated with the HLS url.
|
||||
*/
|
||||
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.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) {
|
||||
this.url = url;
|
||||
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;
|
||||
/**
|
||||
* The list of demuxed audios declared by the playlist.
|
||||
*/
|
||||
public final List<HlsUrl> audios;
|
||||
/**
|
||||
* The list of subtitles declared by the playlist.
|
||||
*/
|
||||
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;
|
||||
/**
|
||||
* 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;
|
||||
|
||||
/**
|
||||
* @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,
|
||||
List<HlsUrl> subtitles, Format muxedAudioFormat, List<Format> muxedCaptionFormats) {
|
||||
super(baseUri);
|
||||
@ -60,14 +102,20 @@ public final class HlsMasterPlaylist extends HlsPlaylist {
|
||||
this.audios = Collections.unmodifiableList(audios);
|
||||
this.subtitles = Collections.unmodifiableList(subtitles);
|
||||
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();
|
||||
return new HlsMasterPlaylist(null, variant, emptyList, emptyList, null,
|
||||
Collections.<Format>emptyList());
|
||||
return new HlsMasterPlaylist(null, variant, emptyList, emptyList, null, null);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -91,12 +91,14 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
||||
public final boolean hasProgramDateTime;
|
||||
public final Segment initializationSegment;
|
||||
public final List<Segment> segments;
|
||||
public final List<String> dateRanges;
|
||||
public final long durationUs;
|
||||
|
||||
public HlsMediaPlaylist(@PlaylistType int playlistType, String baseUri, long startOffsetUs,
|
||||
long startTimeUs, boolean hasDiscontinuitySequence, int discontinuitySequence,
|
||||
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);
|
||||
this.playlistType = playlistType;
|
||||
this.startTimeUs = startTimeUs;
|
||||
@ -117,6 +119,7 @@ public final class HlsMediaPlaylist extends HlsPlaylist {
|
||||
}
|
||||
this.startOffsetUs = startOffsetUs == C.TIME_UNSET ? C.TIME_UNSET
|
||||
: 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) {
|
||||
return new HlsMediaPlaylist(playlistType, baseUri, startOffsetUs, startTimeUs, true,
|
||||
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,
|
||||
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.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
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_KEY = "#EXT-X-KEY";
|
||||
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_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_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_CODECS = Pattern.compile("CODECS=\"(.+?)\"");
|
||||
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> subtitles = new ArrayList<>();
|
||||
Format muxedAudioFormat = null;
|
||||
ArrayList<Format> muxedCaptionFormats = new ArrayList<>();
|
||||
List<Format> muxedCaptionFormats = null;
|
||||
boolean noClosedCaptions = false;
|
||||
|
||||
String line;
|
||||
while (iterator.hasNext()) {
|
||||
@ -209,6 +214,9 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||
mimeType = MimeTypes.APPLICATION_CEA708;
|
||||
accessibilityChannel = Integer.parseInt(instreamId.substring(7));
|
||||
}
|
||||
if (muxedCaptionFormats == null) {
|
||||
muxedCaptionFormats = new ArrayList<>();
|
||||
}
|
||||
muxedCaptionFormats.add(Format.createTextContainerFormat(id, null, mimeType, null,
|
||||
Format.NO_VALUE, selectionFlags, language, accessibilityChannel));
|
||||
break;
|
||||
@ -220,6 +228,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||
int bitrate = parseIntAttr(line, REGEX_BANDWIDTH);
|
||||
String codecs = parseOptionalStringAttr(line, REGEX_CODECS);
|
||||
String resolutionString = parseOptionalStringAttr(line, REGEX_RESOLUTION);
|
||||
noClosedCaptions |= line.contains(ATTR_CLOSED_CAPTIONS_NONE);
|
||||
int width;
|
||||
int height;
|
||||
if (resolutionString != null) {
|
||||
@ -242,6 +251,9 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||
variants.add(new HlsMasterPlaylist.HlsUrl(line, format));
|
||||
}
|
||||
}
|
||||
if (noClosedCaptions) {
|
||||
muxedCaptionFormats = Collections.emptyList();
|
||||
}
|
||||
return new HlsMasterPlaylist(baseUri, variants, audios, subtitles, muxedAudioFormat,
|
||||
muxedCaptionFormats);
|
||||
}
|
||||
@ -263,6 +275,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||
boolean hasEndTag = false;
|
||||
Segment initializationSegment = null;
|
||||
List<Segment> segments = new ArrayList<>();
|
||||
List<String> dateRanges = new ArrayList<>();
|
||||
|
||||
long segmentDurationUs = 0;
|
||||
boolean hasDiscontinuitySequence = false;
|
||||
@ -343,6 +356,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||
C.msToUs(Util.parseXsDateTime(line.substring(line.indexOf(':') + 1)));
|
||||
playlistStartTimeUs = programDatetimeUs - segmentStartTimeUs;
|
||||
}
|
||||
} else if (line.startsWith(TAG_DATERANGE)) {
|
||||
dateRanges.add(line);
|
||||
} else if (!line.startsWith("#")) {
|
||||
String segmentEncryptionIV;
|
||||
if (!isEncrypted) {
|
||||
@ -371,7 +386,8 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||
}
|
||||
return new HlsMediaPlaylist(playlistType, baseUri, startOffsetUs, playlistStartTimeUs,
|
||||
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 {
|
||||
|
@ -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
|
||||
* playlist, this method throws the underlying error. Otherwise, does nothing.
|
||||
* If the tracker is having trouble refreshing the master playlist or the primary playlist, this
|
||||
* method throws the underlying error. Otherwise, does nothing.
|
||||
*
|
||||
* @throws IOException The underlying error.
|
||||
*/
|
||||
public void maybeThrowPlaylistRefreshError() throws IOException {
|
||||
public void maybeThrowPrimaryPlaylistRefreshError() throws IOException {
|
||||
initialPlaylistLoader.maybeThrowError();
|
||||
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.
|
||||
*
|
||||
|
@ -61,22 +61,21 @@ public class DefaultTimeBar extends View implements TimeBar {
|
||||
private static final int DEFAULT_INCREMENT_COUNT = 20;
|
||||
private static final int DEFAULT_BAR_HEIGHT = 4;
|
||||
private static final int DEFAULT_TOUCH_TARGET_HEIGHT = 26;
|
||||
private static final int DEFAULT_PLAYED_COLOR = 0x33FFFFFF;
|
||||
private static final int DEFAULT_BUFFERED_COLOR = 0xCCFFFFFF;
|
||||
private static final int DEFAULT_PLAYED_COLOR = 0xFFFFFFFF;
|
||||
private static final int DEFAULT_AD_MARKER_COLOR = 0xB2FFFF00;
|
||||
private static final int DEFAULT_AD_MARKER_WIDTH = 4;
|
||||
private static final int DEFAULT_SCRUBBER_ENABLED_SIZE = 12;
|
||||
private static final int DEFAULT_SCRUBBER_DISABLED_SIZE = 0;
|
||||
private static final int DEFAULT_SCRUBBER_DRAGGED_SIZE = 16;
|
||||
private static final int OPAQUE_COLOR = 0xFF000000;
|
||||
|
||||
private final Rect seekBounds;
|
||||
private final Rect progressBar;
|
||||
private final Rect bufferedBar;
|
||||
private final Rect scrubberBar;
|
||||
private final Paint progressPaint;
|
||||
private final Paint bufferedPaint;
|
||||
private final Paint playedPaint;
|
||||
private final Paint scrubberPaint;
|
||||
private final Paint bufferedPaint;
|
||||
private final Paint unplayedPaint;
|
||||
private final Paint adMarkerPaint;
|
||||
private final int barHeight;
|
||||
private final int touchTargetHeight;
|
||||
@ -115,9 +114,10 @@ public class DefaultTimeBar extends View implements TimeBar {
|
||||
progressBar = new Rect();
|
||||
bufferedBar = new Rect();
|
||||
scrubberBar = new Rect();
|
||||
progressPaint = new Paint();
|
||||
bufferedPaint = new Paint();
|
||||
playedPaint = new Paint();
|
||||
scrubberPaint = new Paint();
|
||||
bufferedPaint = new Paint();
|
||||
unplayedPaint = new Paint();
|
||||
adMarkerPaint = new Paint();
|
||||
|
||||
// Calculate the dimensions and paints for drawn elements.
|
||||
@ -147,13 +147,18 @@ public class DefaultTimeBar extends View implements TimeBar {
|
||||
scrubberDraggedSize = a.getDimensionPixelSize(
|
||||
R.styleable.DefaultTimeBar_scrubber_dragged_size, defaultScrubberDraggedSize);
|
||||
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,
|
||||
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,
|
||||
DEFAULT_AD_MARKER_COLOR);
|
||||
progressPaint.setColor(playedColor);
|
||||
scrubberPaint.setColor(OPAQUE_COLOR | playedColor);
|
||||
playedPaint.setColor(playedColor);
|
||||
scrubberPaint.setColor(scrubberColor);
|
||||
bufferedPaint.setColor(bufferedColor);
|
||||
unplayedPaint.setColor(unplayedColor);
|
||||
adMarkerPaint.setColor(adMarkerColor);
|
||||
} finally {
|
||||
a.recycle();
|
||||
@ -165,9 +170,10 @@ public class DefaultTimeBar extends View implements TimeBar {
|
||||
scrubberEnabledSize = defaultScrubberEnabledSize;
|
||||
scrubberDisabledSize = defaultScrubberDisabledSize;
|
||||
scrubberDraggedSize = defaultScrubberDraggedSize;
|
||||
scrubberPaint.setColor(OPAQUE_COLOR | DEFAULT_PLAYED_COLOR);
|
||||
progressPaint.setColor(DEFAULT_PLAYED_COLOR);
|
||||
bufferedPaint.setColor(DEFAULT_BUFFERED_COLOR);
|
||||
playedPaint.setColor(DEFAULT_PLAYED_COLOR);
|
||||
scrubberPaint.setColor(getDefaultScrubberColor(DEFAULT_PLAYED_COLOR));
|
||||
bufferedPaint.setColor(getDefaultBufferedColor(DEFAULT_PLAYED_COLOR));
|
||||
unplayedPaint.setColor(getDefaultUnplayedColor(DEFAULT_PLAYED_COLOR));
|
||||
adMarkerPaint.setColor(DEFAULT_AD_MARKER_COLOR);
|
||||
}
|
||||
formatBuilder = new StringBuilder();
|
||||
@ -337,16 +343,18 @@ public class DefaultTimeBar extends View implements TimeBar {
|
||||
|
||||
@Override
|
||||
protected void onMeasure(int widthMeasureSpec, int heightMeasureSpec) {
|
||||
int measureWidth = MeasureSpec.getSize(widthMeasureSpec);
|
||||
int measureHeight = MeasureSpec.getSize(heightMeasureSpec);
|
||||
setMeasuredDimension(measureWidth, measureHeight);
|
||||
int heightMode = MeasureSpec.getMode(heightMeasureSpec);
|
||||
int heightSize = MeasureSpec.getSize(heightMeasureSpec);
|
||||
int height = heightMode == MeasureSpec.UNSPECIFIED ? touchTargetHeight
|
||||
: heightMode == MeasureSpec.EXACTLY ? heightSize : Math.min(touchTargetHeight, heightSize);
|
||||
setMeasuredDimension(MeasureSpec.getSize(widthMeasureSpec), height);
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onLayout(boolean changed, int left, int top, int right, int bottom) {
|
||||
int width = right - left;
|
||||
int height = bottom - top;
|
||||
int barY = height - touchTargetHeight;
|
||||
int barY = (height - touchTargetHeight) / 2;
|
||||
int seekLeft = getPaddingLeft();
|
||||
int seekRight = width - getPaddingRight();
|
||||
int progressY = barY + (touchTargetHeight - barHeight) / 2;
|
||||
@ -457,12 +465,10 @@ public class DefaultTimeBar extends View implements TimeBar {
|
||||
scrubberBar.set(progressBar);
|
||||
long newScrubberTime = scrubbing ? scrubPosition : position;
|
||||
if (duration > 0) {
|
||||
int bufferedPixelWidth =
|
||||
(int) ((progressBar.width() * bufferedPosition) / duration);
|
||||
bufferedBar.right = progressBar.left + bufferedPixelWidth;
|
||||
int scrubberPixelPosition =
|
||||
(int) ((progressBar.width() * newScrubberTime) / duration);
|
||||
scrubberBar.right = progressBar.left + scrubberPixelPosition;
|
||||
int bufferedPixelWidth = (int) ((progressBar.width() * bufferedPosition) / duration);
|
||||
bufferedBar.right = Math.min(progressBar.left + bufferedPixelWidth, progressBar.right);
|
||||
int scrubberPixelPosition = (int) ((progressBar.width() * newScrubberTime) / duration);
|
||||
scrubberBar.right = Math.min(progressBar.left + scrubberPixelPosition, progressBar.right);
|
||||
} else {
|
||||
bufferedBar.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 barBottom = barTop + progressBarHeight;
|
||||
if (duration <= 0) {
|
||||
canvas.drawRect(progressBar.left, barTop, progressBar.right, barBottom, progressPaint);
|
||||
canvas.drawRect(progressBar.left, barTop, progressBar.right, barBottom, unplayedPaint);
|
||||
return;
|
||||
}
|
||||
int bufferedLeft = bufferedBar.left;
|
||||
int bufferedRight = bufferedBar.right;
|
||||
int progressLeft = Math.max(Math.max(progressBar.left, bufferedRight), scrubberBar.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);
|
||||
if (bufferedRight > bufferedLeft) {
|
||||
canvas.drawRect(bufferedLeft, barTop, bufferedRight, barBottom, bufferedPaint);
|
||||
}
|
||||
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;
|
||||
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);
|
||||
}
|
||||
|
||||
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_dragged_size" format="dimension"/>
|
||||
<attr name="played_color" format="color"/>
|
||||
<attr name="scrubber_color" format="color"/>
|
||||
<attr name="buffered_color" format="color"/>
|
||||
<attr name="unplayed_color" format="color"/>
|
||||
<attr name="ad_marker_color" format="color"/>
|
||||
</declare-styleable>
|
||||
|
||||
|
Loading…
x
Reference in New Issue
Block a user