Merge pull request #5455 from google/dev-v2-r2.9.5

r2.9.5
This commit is contained in:
Oliver Woodman 2019-02-10 19:21:53 +00:00 committed by GitHub
commit de39925ce9
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 624 additions and 306 deletions

View File

@ -1,5 +1,22 @@
# Release notes # # Release notes #
### 2.9.5 ###
* HLS: Parse `CHANNELS` attribute from `EXT-X-MEDIA` tag.
* ConcatenatingMediaSource:
* Add `Handler` parameter to methods that take a callback `Runnable`.
* Fix issue with dropped messages when releasing the source
([#5464](https://github.com/google/ExoPlayer/issues/5464)).
* ExtractorMediaSource: Fix issue that could cause the player to get stuck
buffering at the end of the media.
* PlayerView: Fix issue preventing `OnClickListener` from receiving events
([#5433](https://github.com/google/ExoPlayer/issues/5433)).
* IMA extension: Upgrade IMA dependency to 3.10.6.
* Cronet extension: Upgrade Cronet dependency to 71.3578.98.
* OkHttp extension: Upgrade OkHttp dependency to 3.12.1.
* MP3: Wider fix for issue where streams would play twice on some Samsung
devices ([#4519](https://github.com/google/ExoPlayer/issues/4519)).
### 2.9.4 ### ### 2.9.4 ###
* IMA extension: Clear ads loader listeners on release * IMA extension: Clear ads loader listeners on release
@ -1160,7 +1177,7 @@
[here](https://medium.com/google-exoplayer/customizing-exoplayers-ui-components-728cf55ee07a#.9ewjg7avi). [here](https://medium.com/google-exoplayer/customizing-exoplayers-ui-components-728cf55ee07a#.9ewjg7avi).
* Robustness improvements when handling MediaSource timeline changes and * Robustness improvements when handling MediaSource timeline changes and
MediaPeriod transitions. MediaPeriod transitions.
* EIA608: Support for caption styling and positioning. * CEA-608: Support for caption styling and positioning.
* MPEG-TS: Improved support: * MPEG-TS: Improved support:
* Support injection of custom TS payload readers. * Support injection of custom TS payload readers.
* Support injection of custom section payload readers. * Support injection of custom section payload readers.
@ -1404,8 +1421,8 @@ V2 release.
(#801). (#801).
* MP3: Fix playback of some streams when stream length is unknown. * MP3: Fix playback of some streams when stream length is unknown.
* ID3: Support multiple frames of the same type in a single tag. * ID3: Support multiple frames of the same type in a single tag.
* EIA608: Correctly handle repeated control characters, fixing an issue in which * CEA-608: Correctly handle repeated control characters, fixing an issue in
captions would immediately disappear. which captions would immediately disappear.
* AVC3: Fix decoder failures on some MediaTek devices in the case where the * AVC3: Fix decoder failures on some MediaTek devices in the case where the
first buffer fed to the decoder does not start with SPS/PPS NAL units. first buffer fed to the decoder does not start with SPS/PPS NAL units.
* Misc bug fixes. * Misc bug fixes.

View File

@ -13,8 +13,8 @@
// limitations under the License. // limitations under the License.
project.ext { project.ext {
// ExoPlayer version and version code. // ExoPlayer version and version code.
releaseVersion = '2.9.4' releaseVersion = '2.9.5'
releaseVersionCode = 2009004 releaseVersionCode = 2009005
// Important: ExoPlayer specifies a minSdkVersion of 14 because various // Important: ExoPlayer specifies a minSdkVersion of 14 because various
// components provided by the library may be of use on older devices. // components provided by the library may be of use on older devices.
// However, please note that the core media playback functionality provided // However, please note that the core media playback functionality provided

View File

@ -30,7 +30,7 @@ android {
} }
dependencies { dependencies {
api 'org.chromium.net:cronet-embedded:66.3359.158' api 'org.chromium.net:cronet-embedded:71.3578.98'
implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'library-core')
implementation 'com.android.support:support-annotations:' + supportLibraryVersion implementation 'com.android.support:support-annotations:' + supportLibraryVersion
testImplementation project(modulePrefix + 'library') testImplementation project(modulePrefix + 'library')

View File

@ -31,13 +31,13 @@ android {
} }
dependencies { dependencies {
api 'com.google.ads.interactivemedia.v3:interactivemedia:3.10.2' api 'com.google.ads.interactivemedia.v3:interactivemedia:3.10.6'
implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'library-core')
implementation 'com.google.android.gms:play-services-ads:17.1.1' implementation 'com.google.android.gms:play-services-ads:17.1.2'
// These dependencies are necessary to force the supportLibraryVersion of // These dependencies are necessary to force the supportLibraryVersion of
// com.android.support:support-v4 and com.android.support:customtabs to be // com.android.support:support-v4 and com.android.support:customtabs to be
// used. Else older versions are used, for example via: // used. Else older versions are used, for example via:
// com.google.android.gms:play-services-ads:17.1.1 // com.google.android.gms:play-services-ads:17.1.2
// |-- com.android.support:customtabs:26.1.0 // |-- com.android.support:customtabs:26.1.0
implementation 'com.android.support:support-v4:' + supportLibraryVersion implementation 'com.android.support:support-v4:' + supportLibraryVersion
implementation 'com.android.support:customtabs:' + supportLibraryVersion implementation 'com.android.support:customtabs:' + supportLibraryVersion

View File

@ -34,7 +34,7 @@ dependencies {
implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'library-core')
implementation 'com.android.support:support-annotations:' + supportLibraryVersion implementation 'com.android.support:support-annotations:' + supportLibraryVersion
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
api 'com.squareup.okhttp3:okhttp:3.11.0' api 'com.squareup.okhttp3:okhttp:3.12.1'
} }
ext { ext {

View File

@ -16,6 +16,7 @@
package com.google.android.exoplayer2; package com.google.android.exoplayer2;
import android.content.Context; import android.content.Context;
import android.media.MediaCodec;
import android.os.Handler; import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.support.annotation.IntDef; import android.support.annotation.IntDef;
@ -85,15 +86,18 @@ public class DefaultRenderersFactory implements RenderersFactory {
protected static final int MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY = 50; protected static final int MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY = 50;
private final Context context; private final Context context;
private final @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager; @Nullable private DrmSessionManager<FrameworkMediaCrypto> drmSessionManager;
private final @ExtensionRendererMode int extensionRendererMode; @ExtensionRendererMode private int extensionRendererMode;
private final long allowedVideoJoiningTimeMs; private long allowedVideoJoiningTimeMs;
private boolean playClearSamplesWithoutKeys;
private MediaCodecSelector mediaCodecSelector;
/** /** @param context A {@link Context}. */
* @param context A {@link Context}.
*/
public DefaultRenderersFactory(Context context) { public DefaultRenderersFactory(Context context) {
this(context, EXTENSION_RENDERER_MODE_OFF); this.context = context;
extensionRendererMode = EXTENSION_RENDERER_MODE_OFF;
allowedVideoJoiningTimeMs = DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS;
mediaCodecSelector = MediaCodecSelector.DEFAULT;
} }
/** /**
@ -108,19 +112,20 @@ public class DefaultRenderersFactory implements RenderersFactory {
} }
/** /**
* @param context A {@link Context}. * @deprecated Use {@link #DefaultRenderersFactory(Context)} and {@link
* @param extensionRendererMode The extension renderer mode, which determines if and how available * #setExtensionRendererMode(int)}.
* extension renderers are used. Note that extensions must be included in the application
* build for them to be considered available.
*/ */
@Deprecated
@SuppressWarnings("deprecation")
public DefaultRenderersFactory( public DefaultRenderersFactory(
Context context, @ExtensionRendererMode int extensionRendererMode) { Context context, @ExtensionRendererMode int extensionRendererMode) {
this(context, extensionRendererMode, DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS); this(context, extensionRendererMode, DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS);
} }
/** /**
* @deprecated Use {@link #DefaultRenderersFactory(Context, int)} and pass {@link * @deprecated Use {@link #DefaultRenderersFactory(Context)} and {@link
* DrmSessionManager} directly to {@link SimpleExoPlayer} or {@link ExoPlayerFactory}. * #setExtensionRendererMode(int)}, and pass {@link DrmSessionManager} directly to {@link
* SimpleExoPlayer} or {@link ExoPlayerFactory}.
*/ */
@Deprecated @Deprecated
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation")
@ -132,26 +137,22 @@ public class DefaultRenderersFactory implements RenderersFactory {
} }
/** /**
* @param context A {@link Context}. * @deprecated Use {@link #DefaultRenderersFactory(Context)}, {@link
* @param extensionRendererMode The extension renderer mode, which determines if and how available * #setExtensionRendererMode(int)} and {@link #setAllowedVideoJoiningTimeMs(long)}.
* extension renderers are used. Note that extensions must be included in the application
* build for them to be considered available.
* @param allowedVideoJoiningTimeMs The maximum duration for which video renderers can attempt to
* seamlessly join an ongoing playback.
*/ */
@Deprecated
@SuppressWarnings("deprecation")
public DefaultRenderersFactory( public DefaultRenderersFactory(
Context context, Context context,
@ExtensionRendererMode int extensionRendererMode, @ExtensionRendererMode int extensionRendererMode,
long allowedVideoJoiningTimeMs) { long allowedVideoJoiningTimeMs) {
this.context = context; this(context, null, extensionRendererMode, allowedVideoJoiningTimeMs);
this.extensionRendererMode = extensionRendererMode;
this.allowedVideoJoiningTimeMs = allowedVideoJoiningTimeMs;
this.drmSessionManager = null;
} }
/** /**
* @deprecated Use {@link #DefaultRenderersFactory(Context, int, long)} and pass {@link * @deprecated Use {@link #DefaultRenderersFactory(Context)}, {@link
* DrmSessionManager} directly to {@link SimpleExoPlayer} or {@link ExoPlayerFactory}. * #setExtensionRendererMode(int)} and {@link #setAllowedVideoJoiningTimeMs(long)}, and pass
* {@link DrmSessionManager} directly to {@link SimpleExoPlayer} or {@link ExoPlayerFactory}.
*/ */
@Deprecated @Deprecated
public DefaultRenderersFactory( public DefaultRenderersFactory(
@ -163,6 +164,70 @@ public class DefaultRenderersFactory implements RenderersFactory {
this.extensionRendererMode = extensionRendererMode; this.extensionRendererMode = extensionRendererMode;
this.allowedVideoJoiningTimeMs = allowedVideoJoiningTimeMs; this.allowedVideoJoiningTimeMs = allowedVideoJoiningTimeMs;
this.drmSessionManager = drmSessionManager; this.drmSessionManager = drmSessionManager;
mediaCodecSelector = MediaCodecSelector.DEFAULT;
}
/**
* Sets the extension renderer mode, which determines if and how available extension renderers are
* used. Note that extensions must be included in the application build for them to be considered
* available.
*
* <p>The default value is {@link #EXTENSION_RENDERER_MODE_OFF}.
*
* @param extensionRendererMode The extension renderer mode.
* @return This factory, for convenience.
*/
public DefaultRenderersFactory setExtensionRendererMode(
@ExtensionRendererMode int extensionRendererMode) {
this.extensionRendererMode = extensionRendererMode;
return this;
}
/**
* Sets whether renderers are permitted to play clear regions of encrypted media prior to having
* obtained the keys necessary to decrypt encrypted regions of the media. For encrypted media that
* starts with a short clear region, this allows playback to begin in parallel with key
* acquisition, which can reduce startup latency.
*
* <p>The default value is {@code false}.
*
* @param playClearSamplesWithoutKeys Whether renderers are permitted to play clear regions of
* encrypted media prior to having obtained the keys necessary to decrypt encrypted regions of
* the media.
* @return This factory, for convenience.
*/
public DefaultRenderersFactory setPlayClearSamplesWithoutKeys(
boolean playClearSamplesWithoutKeys) {
this.playClearSamplesWithoutKeys = playClearSamplesWithoutKeys;
return this;
}
/**
* Sets a {@link MediaCodecSelector} for use by {@link MediaCodec} based renderers.
*
* <p>The default value is {@link MediaCodecSelector#DEFAULT}.
*
* @param mediaCodecSelector The {@link MediaCodecSelector}.
* @return This factory, for convenience.
*/
public DefaultRenderersFactory setMediaCodecSelector(MediaCodecSelector mediaCodecSelector) {
this.mediaCodecSelector = mediaCodecSelector;
return this;
}
/**
* Sets the maximum duration for which video renderers can attempt to seamlessly join an ongoing
* playback.
*
* <p>The default value is {@link #DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS}.
*
* @param allowedVideoJoiningTimeMs The maximum duration for which video renderers can attempt to
* seamlessly join an ongoing playback, in milliseconds.
* @return This factory, for convenience.
*/
public DefaultRenderersFactory setAllowedVideoJoiningTimeMs(long allowedVideoJoiningTimeMs) {
this.allowedVideoJoiningTimeMs = allowedVideoJoiningTimeMs;
return this;
} }
@Override @Override
@ -177,10 +242,26 @@ public class DefaultRenderersFactory implements RenderersFactory {
drmSessionManager = this.drmSessionManager; drmSessionManager = this.drmSessionManager;
} }
ArrayList<Renderer> renderersList = new ArrayList<>(); ArrayList<Renderer> renderersList = new ArrayList<>();
buildVideoRenderers(context, drmSessionManager, allowedVideoJoiningTimeMs, buildVideoRenderers(
eventHandler, videoRendererEventListener, extensionRendererMode, renderersList); context,
buildAudioRenderers(context, drmSessionManager, buildAudioProcessors(), extensionRendererMode,
eventHandler, audioRendererEventListener, extensionRendererMode, renderersList); mediaCodecSelector,
drmSessionManager,
playClearSamplesWithoutKeys,
eventHandler,
videoRendererEventListener,
allowedVideoJoiningTimeMs,
renderersList);
buildAudioRenderers(
context,
extensionRendererMode,
mediaCodecSelector,
drmSessionManager,
playClearSamplesWithoutKeys,
buildAudioProcessors(),
eventHandler,
audioRendererEventListener,
renderersList);
buildTextRenderers(context, textRendererOutput, eventHandler.getLooper(), buildTextRenderers(context, textRendererOutput, eventHandler.getLooper(),
extensionRendererMode, renderersList); extensionRendererMode, renderersList);
buildMetadataRenderers(context, metadataRendererOutput, eventHandler.getLooper(), buildMetadataRenderers(context, metadataRendererOutput, eventHandler.getLooper(),
@ -194,27 +275,36 @@ public class DefaultRenderersFactory implements RenderersFactory {
* Builds video renderers for use by the player. * Builds video renderers for use by the player.
* *
* @param context The {@link Context} associated with the player. * @param context The {@link Context} associated with the player.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the player * @param extensionRendererMode The extension renderer mode.
* will not be used for DRM protected playbacks. * @param mediaCodecSelector A decoder selector.
* @param allowedVideoJoiningTimeMs The maximum duration in milliseconds for which video * @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the player will
* renderers can attempt to seamlessly join an ongoing playback. * not be used for DRM protected playbacks.
* @param playClearSamplesWithoutKeys Whether renderers are permitted to play clear regions of
* encrypted media prior to having obtained the keys necessary to decrypt encrypted regions of
* the media.
* @param eventHandler A handler associated with the main thread's looper. * @param eventHandler A handler associated with the main thread's looper.
* @param eventListener An event listener. * @param eventListener An event listener.
* @param extensionRendererMode The extension renderer mode. * @param allowedVideoJoiningTimeMs The maximum duration for which video renderers can attempt to
* seamlessly join an ongoing playback, in milliseconds.
* @param out An array to which the built renderers should be appended. * @param out An array to which the built renderers should be appended.
*/ */
protected void buildVideoRenderers(Context context, protected void buildVideoRenderers(
Context context,
@ExtensionRendererMode int extensionRendererMode,
MediaCodecSelector mediaCodecSelector,
@Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
long allowedVideoJoiningTimeMs, Handler eventHandler, boolean playClearSamplesWithoutKeys,
VideoRendererEventListener eventListener, @ExtensionRendererMode int extensionRendererMode, Handler eventHandler,
VideoRendererEventListener eventListener,
long allowedVideoJoiningTimeMs,
ArrayList<Renderer> out) { ArrayList<Renderer> out) {
out.add( out.add(
new MediaCodecVideoRenderer( new MediaCodecVideoRenderer(
context, context,
MediaCodecSelector.DEFAULT, mediaCodecSelector,
allowedVideoJoiningTimeMs, allowedVideoJoiningTimeMs,
drmSessionManager, drmSessionManager,
/* playClearSamplesWithoutKeys= */ false, playClearSamplesWithoutKeys,
eventHandler, eventHandler,
eventListener, eventListener,
MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY)); MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY));
@ -261,26 +351,35 @@ public class DefaultRenderersFactory implements RenderersFactory {
* Builds audio renderers for use by the player. * Builds audio renderers for use by the player.
* *
* @param context The {@link Context} associated with the player. * @param context The {@link Context} associated with the player.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the player * @param extensionRendererMode The extension renderer mode.
* will not be used for DRM protected playbacks. * @param mediaCodecSelector A decoder selector.
* @param audioProcessors An array of {@link AudioProcessor}s that will process PCM audio * @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the player will
* buffers before output. May be empty. * not be used for DRM protected playbacks.
* @param playClearSamplesWithoutKeys Whether renderers are permitted to play clear regions of
* encrypted media prior to having obtained the keys necessary to decrypt encrypted regions of
* the media.
* @param audioProcessors An array of {@link AudioProcessor}s that will process PCM audio buffers
* before output. May be empty.
* @param eventHandler A handler to use when invoking event listeners and outputs. * @param eventHandler A handler to use when invoking event listeners and outputs.
* @param eventListener An event listener. * @param eventListener An event listener.
* @param extensionRendererMode The extension renderer mode.
* @param out An array to which the built renderers should be appended. * @param out An array to which the built renderers should be appended.
*/ */
protected void buildAudioRenderers(Context context, protected void buildAudioRenderers(
Context context,
@ExtensionRendererMode int extensionRendererMode,
MediaCodecSelector mediaCodecSelector,
@Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
AudioProcessor[] audioProcessors, Handler eventHandler, boolean playClearSamplesWithoutKeys,
AudioRendererEventListener eventListener, @ExtensionRendererMode int extensionRendererMode, AudioProcessor[] audioProcessors,
Handler eventHandler,
AudioRendererEventListener eventListener,
ArrayList<Renderer> out) { ArrayList<Renderer> out) {
out.add( out.add(
new MediaCodecAudioRenderer( new MediaCodecAudioRenderer(
context, context,
MediaCodecSelector.DEFAULT, mediaCodecSelector,
drmSessionManager, drmSessionManager,
/* playClearSamplesWithoutKeys= */ false, playClearSamplesWithoutKeys,
eventHandler, eventHandler,
eventListener, eventListener,
AudioCapabilities.getCapabilities(context), AudioCapabilities.getCapabilities(context),

View File

@ -97,7 +97,8 @@ public final class ExoPlayerFactory {
LoadControl loadControl, LoadControl loadControl,
@Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
@DefaultRenderersFactory.ExtensionRendererMode int extensionRendererMode) { @DefaultRenderersFactory.ExtensionRendererMode int extensionRendererMode) {
RenderersFactory renderersFactory = new DefaultRenderersFactory(context, extensionRendererMode); RenderersFactory renderersFactory =
new DefaultRenderersFactory(context).setExtensionRendererMode(extensionRendererMode);
return newSimpleInstance( return newSimpleInstance(
context, renderersFactory, trackSelector, loadControl, drmSessionManager); context, renderersFactory, trackSelector, loadControl, drmSessionManager);
} }
@ -127,7 +128,9 @@ public final class ExoPlayerFactory {
@DefaultRenderersFactory.ExtensionRendererMode int extensionRendererMode, @DefaultRenderersFactory.ExtensionRendererMode int extensionRendererMode,
long allowedVideoJoiningTimeMs) { long allowedVideoJoiningTimeMs) {
RenderersFactory renderersFactory = RenderersFactory renderersFactory =
new DefaultRenderersFactory(context, extensionRendererMode, allowedVideoJoiningTimeMs); new DefaultRenderersFactory(context)
.setExtensionRendererMode(extensionRendererMode)
.setAllowedVideoJoiningTimeMs(allowedVideoJoiningTimeMs);
return newSimpleInstance( return newSimpleInstance(
context, renderersFactory, trackSelector, loadControl, drmSessionManager); context, renderersFactory, trackSelector, loadControl, drmSessionManager);
} }

View File

@ -29,11 +29,11 @@ public final class ExoPlayerLibraryInfo {
/** The version of the library expressed as a string, for example "1.2.3". */ /** The version of the library expressed as a string, for example "1.2.3". */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa. // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
public static final String VERSION = "2.9.4"; public static final String VERSION = "2.9.5";
/** The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */ /** The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
public static final String VERSION_SLASHY = "ExoPlayerLib/2.9.4"; public static final String VERSION_SLASHY = "ExoPlayerLib/2.9.5";
/** /**
* The version of the library expressed as an integer, for example 1002003. * The version of the library expressed as an integer, for example 1002003.
@ -43,7 +43,7 @@ public final class ExoPlayerLibraryInfo {
* integer version 123045006 (123-045-006). * integer version 123045006 (123-045-006).
*/ */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
public static final int VERSION_INT = 2009004; public static final int VERSION_INT = 2009005;
/** /**
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions} * Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}

View File

@ -419,7 +419,7 @@ public final class DefaultAudioSink implements AudioSink {
isInputPcm = Util.isEncodingLinearPcm(inputEncoding); isInputPcm = Util.isEncodingLinearPcm(inputEncoding);
shouldConvertHighResIntPcmToFloat = shouldConvertHighResIntPcmToFloat =
enableConvertHighResIntPcmToFloat enableConvertHighResIntPcmToFloat
&& supportsOutput(channelCount, C.ENCODING_PCM_32BIT) && supportsOutput(channelCount, C.ENCODING_PCM_FLOAT)
&& Util.isEncodingHighResolutionIntegerPcm(inputEncoding); && Util.isEncodingHighResolutionIntegerPcm(inputEncoding);
if (isInputPcm) { if (isInputPcm) {
pcmFrameSize = Util.getPcmFrameSize(inputEncoding, channelCount); pcmFrameSize = Util.getPcmFrameSize(inputEncoding, channelCount);

View File

@ -50,7 +50,8 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
FLAG_IGNORE_H264_STREAM, FLAG_IGNORE_H264_STREAM,
FLAG_DETECT_ACCESS_UNITS, FLAG_DETECT_ACCESS_UNITS,
FLAG_IGNORE_SPLICE_INFO_STREAM, FLAG_IGNORE_SPLICE_INFO_STREAM,
FLAG_OVERRIDE_CAPTION_DESCRIPTORS FLAG_OVERRIDE_CAPTION_DESCRIPTORS,
FLAG_IGNORE_HDMV_DTS_STREAM
}) })
public @interface Flags {} public @interface Flags {}
@ -86,6 +87,12 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
* closedCaptionFormats} will be ignored if the PMT contains closed captions service descriptors. * closedCaptionFormats} will be ignored if the PMT contains closed captions service descriptors.
*/ */
public static final int FLAG_OVERRIDE_CAPTION_DESCRIPTORS = 1 << 5; public static final int FLAG_OVERRIDE_CAPTION_DESCRIPTORS = 1 << 5;
/**
* Prevents the creation of {@link DtsReader} instances when receiving {@link
* TsExtractor#TS_STREAM_TYPE_HDMV_DTS} as stream type. Enabling this flag prevents a stream type
* collision between HDMV DTS audio and SCTE-35 subtitles.
*/
public static final int FLAG_IGNORE_HDMV_DTS_STREAM = 1 << 6;
private static final int DESCRIPTOR_TAG_CAPTION_SERVICE = 0x86; private static final int DESCRIPTOR_TAG_CAPTION_SERVICE = 0x86;
@ -142,8 +149,12 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
case TsExtractor.TS_STREAM_TYPE_AC3: case TsExtractor.TS_STREAM_TYPE_AC3:
case TsExtractor.TS_STREAM_TYPE_E_AC3: case TsExtractor.TS_STREAM_TYPE_E_AC3:
return new PesReader(new Ac3Reader(esInfo.language)); return new PesReader(new Ac3Reader(esInfo.language));
case TsExtractor.TS_STREAM_TYPE_DTS:
case TsExtractor.TS_STREAM_TYPE_HDMV_DTS: case TsExtractor.TS_STREAM_TYPE_HDMV_DTS:
if (isSet(FLAG_IGNORE_HDMV_DTS_STREAM)) {
return null;
}
// Fall through.
case TsExtractor.TS_STREAM_TYPE_DTS:
return new PesReader(new DtsReader(esInfo.language)); return new PesReader(new DtsReader(esInfo.language));
case TsExtractor.TS_STREAM_TYPE_H262: case TsExtractor.TS_STREAM_TYPE_H262:
return new PesReader(new H262Reader(buildUserDataReader(esInfo))); return new PesReader(new H262Reader(buildUserDataReader(esInfo)));

View File

@ -59,8 +59,6 @@ public final class MediaCodecUtil {
private static final String TAG = "MediaCodecUtil"; private static final String TAG = "MediaCodecUtil";
private static final Pattern PROFILE_PATTERN = Pattern.compile("^\\D?(\\d+)$"); private static final Pattern PROFILE_PATTERN = Pattern.compile("^\\D?(\\d+)$");
private static final RawAudioCodecComparator RAW_AUDIO_CODEC_COMPARATOR =
new RawAudioCodecComparator();
private static final HashMap<CodecKey, List<MediaCodecInfo>> decoderInfosCache = new HashMap<>(); private static final HashMap<CodecKey, List<MediaCodecInfo>> decoderInfosCache = new HashMap<>();
@ -312,32 +310,6 @@ public final class MediaCodecUtil {
return false; return false;
} }
// Work around https://github.com/google/ExoPlayer/issues/398.
if (Util.SDK_INT < 18 && "OMX.SEC.MP3.Decoder".equals(name)) {
return false;
}
// Work around https://github.com/google/ExoPlayer/issues/4519.
if ("OMX.SEC.mp3.dec".equals(name)
&& (Util.MODEL.startsWith("GT-I9152")
|| Util.MODEL.startsWith("GT-I9515")
|| Util.MODEL.startsWith("GT-P5220")
|| Util.MODEL.startsWith("GT-S7580")
|| Util.MODEL.startsWith("SM-G350")
|| Util.MODEL.startsWith("SM-G386")
|| Util.MODEL.startsWith("SM-T231")
|| Util.MODEL.startsWith("SM-T530")
|| Util.MODEL.startsWith("SCH-I535")
|| Util.MODEL.startsWith("SPH-L710"))) {
return false;
}
if ("OMX.brcm.audio.mp3.decoder".equals(name)
&& (Util.MODEL.startsWith("GT-I9152")
|| Util.MODEL.startsWith("GT-S7580")
|| Util.MODEL.startsWith("SM-G350"))) {
return false;
}
// Work around https://github.com/google/ExoPlayer/issues/1528 and // Work around https://github.com/google/ExoPlayer/issues/1528 and
// https://github.com/google/ExoPlayer/issues/3171. // https://github.com/google/ExoPlayer/issues/3171.
if (Util.SDK_INT < 18 && "OMX.MTK.AUDIO.DECODER.AAC".equals(name) if (Util.SDK_INT < 18 && "OMX.MTK.AUDIO.DECODER.AAC".equals(name)
@ -424,7 +396,18 @@ public final class MediaCodecUtil {
*/ */
private static void applyWorkarounds(String mimeType, List<MediaCodecInfo> decoderInfos) { private static void applyWorkarounds(String mimeType, List<MediaCodecInfo> decoderInfos) {
if (MimeTypes.AUDIO_RAW.equals(mimeType)) { if (MimeTypes.AUDIO_RAW.equals(mimeType)) {
Collections.sort(decoderInfos, RAW_AUDIO_CODEC_COMPARATOR); Collections.sort(decoderInfos, new RawAudioCodecComparator());
} else if (Util.SDK_INT < 21 && decoderInfos.size() > 1) {
String firstCodecName = decoderInfos.get(0).name;
if ("OMX.SEC.mp3.dec".equals(firstCodecName)
|| "OMX.SEC.MP3.Decoder".equals(firstCodecName)
|| "OMX.brcm.audio.mp3.decoder".equals(firstCodecName)) {
// Prefer OMX.google codecs over OMX.SEC.mp3.dec, OMX.SEC.MP3.Decoder and
// OMX.brcm.audio.mp3.decoder on older devices. See:
// https://github.com/google/ExoPlayer/issues/398 and
// https://github.com/google/ExoPlayer/issues/4519.
Collections.sort(decoderInfos, new PreferOmxGoogleCodecComparator());
}
} }
} }
@ -730,6 +713,18 @@ public final class MediaCodecUtil {
} }
} }
/** Comparator for preferring OMX.google media codecs. */
private static final class PreferOmxGoogleCodecComparator implements Comparator<MediaCodecInfo> {
@Override
public int compare(MediaCodecInfo a, MediaCodecInfo b) {
return scoreMediaCodecInfo(a) - scoreMediaCodecInfo(b);
}
private static int scoreMediaCodecInfo(MediaCodecInfo mediaCodecInfo) {
return mediaCodecInfo.name.startsWith("OMX.google") ? -1 : 0;
}
}
static { static {
AVC_PROFILE_NUMBER_TO_CONST = new SparseIntArray(); AVC_PROFILE_NUMBER_TO_CONST = new SparseIntArray();
AVC_PROFILE_NUMBER_TO_CONST.put(66, CodecProfileLevel.AVCProfileBaseline); AVC_PROFILE_NUMBER_TO_CONST.put(66, CodecProfileLevel.AVCProfileBaseline);

View File

@ -17,6 +17,7 @@ package com.google.android.exoplayer2.source;
import android.os.Handler; import android.os.Handler;
import android.os.Message; import android.os.Message;
import android.support.annotation.GuardedBy;
import android.support.annotation.NonNull; import android.support.annotation.NonNull;
import android.support.annotation.Nullable; import android.support.annotation.Nullable;
import android.util.Pair; import android.util.Pair;
@ -35,9 +36,11 @@ import java.util.Arrays;
import java.util.Collection; import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap; import java.util.IdentityHashMap;
import java.util.List; import java.util.List;
import java.util.Map; import java.util.Map;
import java.util.Set;
/** /**
* Concatenates multiple {@link MediaSource}s. The list of {@link MediaSource}s can be modified * Concatenates multiple {@link MediaSource}s. The list of {@link MediaSource}s can be modified
@ -50,25 +53,31 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
private static final int MSG_REMOVE = 1; private static final int MSG_REMOVE = 1;
private static final int MSG_MOVE = 2; private static final int MSG_MOVE = 2;
private static final int MSG_SET_SHUFFLE_ORDER = 3; private static final int MSG_SET_SHUFFLE_ORDER = 3;
private static final int MSG_NOTIFY_LISTENER = 4; private static final int MSG_UPDATE_TIMELINE = 4;
private static final int MSG_ON_COMPLETION = 5; private static final int MSG_ON_COMPLETION = 5;
// Accessed on the app thread. // Accessed on any thread.
@GuardedBy("this")
private final List<MediaSourceHolder> mediaSourcesPublic; private final List<MediaSourceHolder> mediaSourcesPublic;
// Accessed on the playback thread. @GuardedBy("this")
private final Set<HandlerAndRunnable> pendingOnCompletionActions;
@GuardedBy("this")
@Nullable
private Handler playbackThreadHandler;
// Accessed on the playback thread only.
private final List<MediaSourceHolder> mediaSourceHolders; private final List<MediaSourceHolder> mediaSourceHolders;
private final Map<MediaPeriod, MediaSourceHolder> mediaSourceByMediaPeriod; private final Map<MediaPeriod, MediaSourceHolder> mediaSourceByMediaPeriod;
private final Map<Object, MediaSourceHolder> mediaSourceByUid; private final Map<Object, MediaSourceHolder> mediaSourceByUid;
private final List<Runnable> pendingOnCompletionActions;
private final boolean isAtomic; private final boolean isAtomic;
private final boolean useLazyPreparation; private final boolean useLazyPreparation;
private final Timeline.Window window; private final Timeline.Window window;
private final Timeline.Period period; private final Timeline.Period period;
@Nullable private Handler playbackThreadHandler; private boolean timelineUpdateScheduled;
@Nullable private Handler applicationThreadHandler; private Set<HandlerAndRunnable> nextTimelineUpdateOnCompletionActions;
private boolean listenerNotificationScheduled;
private ShuffleOrder shuffleOrder; private ShuffleOrder shuffleOrder;
private int windowCount; private int windowCount;
private int periodCount; private int periodCount;
@ -127,7 +136,8 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
this.mediaSourceByUid = new HashMap<>(); this.mediaSourceByUid = new HashMap<>();
this.mediaSourcesPublic = new ArrayList<>(); this.mediaSourcesPublic = new ArrayList<>();
this.mediaSourceHolders = new ArrayList<>(); this.mediaSourceHolders = new ArrayList<>();
this.pendingOnCompletionActions = new ArrayList<>(); this.nextTimelineUpdateOnCompletionActions = new HashSet<>();
this.pendingOnCompletionActions = new HashSet<>();
this.isAtomic = isAtomic; this.isAtomic = isAtomic;
this.useLazyPreparation = useLazyPreparation; this.useLazyPreparation = useLazyPreparation;
window = new Timeline.Window(); window = new Timeline.Window();
@ -141,19 +151,20 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
* @param mediaSource The {@link MediaSource} to be added to the list. * @param mediaSource The {@link MediaSource} to be added to the list.
*/ */
public final synchronized void addMediaSource(MediaSource mediaSource) { public final synchronized void addMediaSource(MediaSource mediaSource) {
addMediaSource(mediaSourcesPublic.size(), mediaSource, null); addMediaSource(mediaSourcesPublic.size(), mediaSource);
} }
/** /**
* Appends a {@link MediaSource} to the playlist and executes a custom action on completion. * Appends a {@link MediaSource} to the playlist and executes a custom action on completion.
* *
* @param mediaSource The {@link MediaSource} to be added to the list. * @param mediaSource The {@link MediaSource} to be added to the list.
* @param actionOnCompletion A {@link Runnable} which is executed immediately after the media * @param handler The {@link Handler} to run {@code onCompletionAction}.
* @param onCompletionAction A {@link Runnable} which is executed immediately after the media
* source has been added to the playlist. * source has been added to the playlist.
*/ */
public final synchronized void addMediaSource( public final synchronized void addMediaSource(
MediaSource mediaSource, @Nullable Runnable actionOnCompletion) { MediaSource mediaSource, Handler handler, Runnable onCompletionAction) {
addMediaSource(mediaSourcesPublic.size(), mediaSource, actionOnCompletion); addMediaSource(mediaSourcesPublic.size(), mediaSource, handler, onCompletionAction);
} }
/** /**
@ -164,7 +175,11 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
* @param mediaSource The {@link MediaSource} to be added to the list. * @param mediaSource The {@link MediaSource} to be added to the list.
*/ */
public final synchronized void addMediaSource(int index, MediaSource mediaSource) { public final synchronized void addMediaSource(int index, MediaSource mediaSource) {
addMediaSource(index, mediaSource, null); addPublicMediaSources(
index,
Collections.singletonList(mediaSource),
/* handler= */ null,
/* onCompletionAction= */ null);
} }
/** /**
@ -173,12 +188,14 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
* @param index The index at which the new {@link MediaSource} will be inserted. This index must * @param index The index at which the new {@link MediaSource} will be inserted. This index must
* be in the range of 0 &lt;= index &lt;= {@link #getSize()}. * be in the range of 0 &lt;= index &lt;= {@link #getSize()}.
* @param mediaSource The {@link MediaSource} to be added to the list. * @param mediaSource The {@link MediaSource} to be added to the list.
* @param actionOnCompletion A {@link Runnable} which is executed immediately after the media * @param handler The {@link Handler} to run {@code onCompletionAction}.
* @param onCompletionAction A {@link Runnable} which is executed immediately after the media
* source has been added to the playlist. * source has been added to the playlist.
*/ */
public final synchronized void addMediaSource( public final synchronized void addMediaSource(
int index, MediaSource mediaSource, @Nullable Runnable actionOnCompletion) { int index, MediaSource mediaSource, Handler handler, Runnable onCompletionAction) {
addMediaSources(index, Collections.singletonList(mediaSource), actionOnCompletion); addPublicMediaSources(
index, Collections.singletonList(mediaSource), handler, onCompletionAction);
} }
/** /**
@ -188,7 +205,11 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
* sources are added in the order in which they appear in this collection. * sources are added in the order in which they appear in this collection.
*/ */
public final synchronized void addMediaSources(Collection<MediaSource> mediaSources) { public final synchronized void addMediaSources(Collection<MediaSource> mediaSources) {
addMediaSources(mediaSourcesPublic.size(), mediaSources, null); addPublicMediaSources(
mediaSourcesPublic.size(),
mediaSources,
/* handler= */ null,
/* onCompletionAction= */ null);
} }
/** /**
@ -197,12 +218,13 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
* *
* @param mediaSources A collection of {@link MediaSource}s to be added to the list. The media * @param mediaSources A collection of {@link MediaSource}s to be added to the list. The media
* sources are added in the order in which they appear in this collection. * sources are added in the order in which they appear in this collection.
* @param actionOnCompletion A {@link Runnable} which is executed immediately after the media * @param handler The {@link Handler} to run {@code onCompletionAction}.
* @param onCompletionAction A {@link Runnable} which is executed immediately after the media
* sources have been added to the playlist. * sources have been added to the playlist.
*/ */
public final synchronized void addMediaSources( public final synchronized void addMediaSources(
Collection<MediaSource> mediaSources, @Nullable Runnable actionOnCompletion) { Collection<MediaSource> mediaSources, Handler handler, Runnable onCompletionAction) {
addMediaSources(mediaSourcesPublic.size(), mediaSources, actionOnCompletion); addPublicMediaSources(mediaSourcesPublic.size(), mediaSources, handler, onCompletionAction);
} }
/** /**
@ -214,7 +236,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
* sources are added in the order in which they appear in this collection. * sources are added in the order in which they appear in this collection.
*/ */
public final synchronized void addMediaSources(int index, Collection<MediaSource> mediaSources) { public final synchronized void addMediaSources(int index, Collection<MediaSource> mediaSources) {
addMediaSources(index, mediaSources, null); addPublicMediaSources(index, mediaSources, /* handler= */ null, /* onCompletionAction= */ null);
} }
/** /**
@ -224,26 +246,16 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
* be in the range of 0 &lt;= index &lt;= {@link #getSize()}. * be in the range of 0 &lt;= index &lt;= {@link #getSize()}.
* @param mediaSources A collection of {@link MediaSource}s to be added to the list. The media * @param mediaSources A collection of {@link MediaSource}s to be added to the list. The media
* sources are added in the order in which they appear in this collection. * sources are added in the order in which they appear in this collection.
* @param actionOnCompletion A {@link Runnable} which is executed immediately after the media * @param handler The {@link Handler} to run {@code onCompletionAction}.
* @param onCompletionAction A {@link Runnable} which is executed immediately after the media
* sources have been added to the playlist. * sources have been added to the playlist.
*/ */
public final synchronized void addMediaSources( public final synchronized void addMediaSources(
int index, Collection<MediaSource> mediaSources, @Nullable Runnable actionOnCompletion) { int index,
for (MediaSource mediaSource : mediaSources) { Collection<MediaSource> mediaSources,
Assertions.checkNotNull(mediaSource); Handler handler,
} Runnable onCompletionAction) {
List<MediaSourceHolder> mediaSourceHolders = new ArrayList<>(mediaSources.size()); addPublicMediaSources(index, mediaSources, handler, onCompletionAction);
for (MediaSource mediaSource : mediaSources) {
mediaSourceHolders.add(new MediaSourceHolder(mediaSource));
}
mediaSourcesPublic.addAll(index, mediaSourceHolders);
if (playbackThreadHandler != null && !mediaSources.isEmpty()) {
playbackThreadHandler
.obtainMessage(MSG_ADD, new MessageData<>(index, mediaSourceHolders, actionOnCompletion))
.sendToTarget();
} else if (actionOnCompletion != null) {
actionOnCompletion.run();
}
} }
/** /**
@ -259,26 +271,27 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
* range of 0 &lt;= index &lt; {@link #getSize()}. * range of 0 &lt;= index &lt; {@link #getSize()}.
*/ */
public final synchronized void removeMediaSource(int index) { public final synchronized void removeMediaSource(int index) {
removeMediaSource(index, null); removePublicMediaSources(index, index + 1, /* handler= */ null, /* onCompletionAction= */ null);
} }
/** /**
* Removes a {@link MediaSource} from the playlist and executes a custom action on completion. * Removes a {@link MediaSource} from the playlist and executes a custom action on completion.
* *
* <p>Note: If you want to move the instance, it's preferable to use {@link #moveMediaSource(int, * <p>Note: If you want to move the instance, it's preferable to use {@link #moveMediaSource(int,
* int, Runnable)} instead. * int, Handler, Runnable)} instead.
* *
* <p>Note: If you want to remove a set of contiguous sources, it's preferable to use {@link * <p>Note: If you want to remove a set of contiguous sources, it's preferable to use {@link
* #removeMediaSourceRange(int, int, Runnable)} instead. * #removeMediaSourceRange(int, int, Handler, Runnable)} instead.
* *
* @param index The index at which the media source will be removed. This index must be in the * @param index The index at which the media source will be removed. This index must be in the
* range of 0 &lt;= index &lt; {@link #getSize()}. * range of 0 &lt;= index &lt; {@link #getSize()}.
* @param actionOnCompletion A {@link Runnable} which is executed immediately after the media * @param handler The {@link Handler} to run {@code onCompletionAction}.
* @param onCompletionAction A {@link Runnable} which is executed immediately after the media
* source has been removed from the playlist. * source has been removed from the playlist.
*/ */
public final synchronized void removeMediaSource( public final synchronized void removeMediaSource(
int index, @Nullable Runnable actionOnCompletion) { int index, Handler handler, Runnable onCompletionAction) {
removeMediaSourceRange(index, index + 1, actionOnCompletion); removePublicMediaSources(index, index + 1, handler, onCompletionAction);
} }
/** /**
@ -296,7 +309,8 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
* {@code toIndex} &gt; {@link #getSize()}, {@code fromIndex} &gt; {@code toIndex} * {@code toIndex} &gt; {@link #getSize()}, {@code fromIndex} &gt; {@code toIndex}
*/ */
public final synchronized void removeMediaSourceRange(int fromIndex, int toIndex) { public final synchronized void removeMediaSourceRange(int fromIndex, int toIndex) {
removeMediaSourceRange(fromIndex, toIndex, null); removePublicMediaSources(
fromIndex, toIndex, /* handler= */ null, /* onCompletionAction= */ null);
} }
/** /**
@ -310,27 +324,15 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
* removed. This index must be in the range of 0 &lt;= index &lt;= {@link #getSize()}. * removed. This index must be in the range of 0 &lt;= index &lt;= {@link #getSize()}.
* @param toIndex The final range index, pointing to the first media source that will be left * @param toIndex The final range index, pointing to the first media source that will be left
* untouched. This index must be in the range of 0 &lt;= index &lt;= {@link #getSize()}. * untouched. This index must be in the range of 0 &lt;= index &lt;= {@link #getSize()}.
* @param actionOnCompletion A {@link Runnable} which is executed immediately after the media * @param handler The {@link Handler} to run {@code onCompletionAction}.
* @param onCompletionAction A {@link Runnable} which is executed immediately after the media
* source range has been removed from the playlist. * source range has been removed from the playlist.
* @throws IllegalArgumentException When the range is malformed, i.e. {@code fromIndex} &lt; 0, * @throws IllegalArgumentException When the range is malformed, i.e. {@code fromIndex} &lt; 0,
* {@code toIndex} &gt; {@link #getSize()}, {@code fromIndex} &gt; {@code toIndex} * {@code toIndex} &gt; {@link #getSize()}, {@code fromIndex} &gt; {@code toIndex}
*/ */
public final synchronized void removeMediaSourceRange( public final synchronized void removeMediaSourceRange(
int fromIndex, int toIndex, @Nullable Runnable actionOnCompletion) { int fromIndex, int toIndex, Handler handler, Runnable onCompletionAction) {
Util.removeRange(mediaSourcesPublic, fromIndex, toIndex); removePublicMediaSources(fromIndex, toIndex, handler, onCompletionAction);
if (fromIndex == toIndex) {
if (actionOnCompletion != null) {
actionOnCompletion.run();
}
return;
}
if (playbackThreadHandler != null) {
playbackThreadHandler
.obtainMessage(MSG_REMOVE, new MessageData<>(fromIndex, toIndex, actionOnCompletion))
.sendToTarget();
} else if (actionOnCompletion != null) {
actionOnCompletion.run();
}
} }
/** /**
@ -342,7 +344,8 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
* range of 0 &lt;= index &lt; {@link #getSize()}. * range of 0 &lt;= index &lt; {@link #getSize()}.
*/ */
public final synchronized void moveMediaSource(int currentIndex, int newIndex) { public final synchronized void moveMediaSource(int currentIndex, int newIndex) {
moveMediaSource(currentIndex, newIndex, null); movePublicMediaSource(
currentIndex, newIndex, /* handler= */ null, /* onCompletionAction= */ null);
} }
/** /**
@ -353,40 +356,29 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
* in the range of 0 &lt;= index &lt; {@link #getSize()}. * in the range of 0 &lt;= index &lt; {@link #getSize()}.
* @param newIndex The target index of the media source in the playlist. This index must be in the * @param newIndex The target index of the media source in the playlist. This index must be in the
* range of 0 &lt;= index &lt; {@link #getSize()}. * range of 0 &lt;= index &lt; {@link #getSize()}.
* @param actionOnCompletion A {@link Runnable} which is executed immediately after the media * @param handler The {@link Handler} to run {@code onCompletionAction}.
* @param onCompletionAction A {@link Runnable} which is executed immediately after the media
* source has been moved. * source has been moved.
*/ */
public final synchronized void moveMediaSource( public final synchronized void moveMediaSource(
int currentIndex, int newIndex, @Nullable Runnable actionOnCompletion) { int currentIndex, int newIndex, Handler handler, Runnable onCompletionAction) {
if (currentIndex == newIndex) { movePublicMediaSource(currentIndex, newIndex, handler, onCompletionAction);
if (actionOnCompletion != null) {
actionOnCompletion.run();
}
return;
}
mediaSourcesPublic.add(newIndex, mediaSourcesPublic.remove(currentIndex));
if (playbackThreadHandler != null) {
playbackThreadHandler
.obtainMessage(MSG_MOVE, new MessageData<>(currentIndex, newIndex, actionOnCompletion))
.sendToTarget();
} else if (actionOnCompletion != null) {
actionOnCompletion.run();
}
} }
/** Clears the playlist. */ /** Clears the playlist. */
public final synchronized void clear() { public final synchronized void clear() {
clear(/* actionOnCompletion= */ null); removeMediaSourceRange(0, getSize());
} }
/** /**
* Clears the playlist and executes a custom action on completion. * Clears the playlist and executes a custom action on completion.
* *
* @param actionOnCompletion A {@link Runnable} which is executed immediately after the playlist * @param handler The {@link Handler} to run {@code onCompletionAction}.
* @param onCompletionAction A {@link Runnable} which is executed immediately after the playlist
* has been cleared. * has been cleared.
*/ */
public final synchronized void clear(@Nullable Runnable actionOnCompletion) { public final synchronized void clear(Handler handler, Runnable onCompletionAction) {
removeMediaSourceRange(0, getSize(), actionOnCompletion); removeMediaSourceRange(0, getSize(), handler, onCompletionAction);
} }
/** Returns the number of media sources in the playlist. */ /** Returns the number of media sources in the playlist. */
@ -410,41 +402,24 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
* @param shuffleOrder A {@link ShuffleOrder}. * @param shuffleOrder A {@link ShuffleOrder}.
*/ */
public final synchronized void setShuffleOrder(ShuffleOrder shuffleOrder) { public final synchronized void setShuffleOrder(ShuffleOrder shuffleOrder) {
setShuffleOrder(shuffleOrder, /* actionOnCompletion= */ null); setPublicShuffleOrder(shuffleOrder, /* handler= */ null, /* onCompletionAction= */ null);
} }
/** /**
* Sets a new shuffle order to use when shuffling the child media sources. * Sets a new shuffle order to use when shuffling the child media sources.
* *
* @param shuffleOrder A {@link ShuffleOrder}. * @param shuffleOrder A {@link ShuffleOrder}.
* @param actionOnCompletion A {@link Runnable} which is executed immediately after the shuffle * @param handler The {@link Handler} to run {@code onCompletionAction}.
* @param onCompletionAction A {@link Runnable} which is executed immediately after the shuffle
* order has been changed. * order has been changed.
*/ */
public final synchronized void setShuffleOrder( public final synchronized void setShuffleOrder(
ShuffleOrder shuffleOrder, @Nullable Runnable actionOnCompletion) { ShuffleOrder shuffleOrder, Handler handler, Runnable onCompletionAction) {
Handler playbackThreadHandler = this.playbackThreadHandler; setPublicShuffleOrder(shuffleOrder, handler, onCompletionAction);
if (playbackThreadHandler != null) {
int size = getSize();
if (shuffleOrder.getLength() != size) {
shuffleOrder =
shuffleOrder
.cloneAndClear()
.cloneAndInsert(/* insertionIndex= */ 0, /* insertionCount= */ size);
}
playbackThreadHandler
.obtainMessage(
MSG_SET_SHUFFLE_ORDER,
new MessageData<>(/* index= */ 0, shuffleOrder, actionOnCompletion))
.sendToTarget();
} else {
this.shuffleOrder =
shuffleOrder.getLength() > 0 ? shuffleOrder.cloneAndClear() : shuffleOrder;
if (actionOnCompletion != null) {
actionOnCompletion.run();
}
}
} }
// CompositeMediaSource implementation.
@Override @Override
@Nullable @Nullable
public Object getTag() { public Object getTag() {
@ -458,13 +433,12 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
@Nullable TransferListener mediaTransferListener) { @Nullable TransferListener mediaTransferListener) {
super.prepareSourceInternal(player, isTopLevelSource, mediaTransferListener); super.prepareSourceInternal(player, isTopLevelSource, mediaTransferListener);
playbackThreadHandler = new Handler(/* callback= */ this::handleMessage); playbackThreadHandler = new Handler(/* callback= */ this::handleMessage);
applicationThreadHandler = new Handler(player.getApplicationLooper());
if (mediaSourcesPublic.isEmpty()) { if (mediaSourcesPublic.isEmpty()) {
notifyListener(); updateTimelineAndScheduleOnCompletionActions();
} else { } else {
shuffleOrder = shuffleOrder.cloneAndInsert(0, mediaSourcesPublic.size()); shuffleOrder = shuffleOrder.cloneAndInsert(0, mediaSourcesPublic.size());
addMediaSourcesInternal(0, mediaSourcesPublic); addMediaSourcesInternal(0, mediaSourcesPublic);
scheduleListenerNotification(/* actionOnCompletion= */ null); scheduleTimelineUpdate();
} }
} }
@ -509,15 +483,20 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
} }
@Override @Override
public final void releaseSourceInternal() { public final synchronized void releaseSourceInternal() {
super.releaseSourceInternal(); super.releaseSourceInternal();
mediaSourceHolders.clear(); mediaSourceHolders.clear();
mediaSourceByUid.clear(); mediaSourceByUid.clear();
playbackThreadHandler = null;
applicationThreadHandler = null;
shuffleOrder = shuffleOrder.cloneAndClear(); shuffleOrder = shuffleOrder.cloneAndClear();
windowCount = 0; windowCount = 0;
periodCount = 0; periodCount = 0;
if (playbackThreadHandler != null) {
playbackThreadHandler.removeCallbacksAndMessages(null);
playbackThreadHandler = null;
}
timelineUpdateScheduled = false;
nextTimelineUpdateOnCompletionActions.clear();
dispatchOnCompletionActions(pendingOnCompletionActions);
} }
@Override @Override
@ -550,19 +529,123 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
return windowIndex + mediaSourceHolder.firstWindowIndexInChild; return windowIndex + mediaSourceHolder.firstWindowIndexInChild;
} }
// Internal methods. Called from any thread.
@GuardedBy("this")
private void addPublicMediaSources(
int index,
Collection<MediaSource> mediaSources,
@Nullable Handler handler,
@Nullable Runnable onCompletionAction) {
Assertions.checkArgument((handler == null) == (onCompletionAction == null));
Handler playbackThreadHandler = this.playbackThreadHandler;
for (MediaSource mediaSource : mediaSources) {
Assertions.checkNotNull(mediaSource);
}
List<MediaSourceHolder> mediaSourceHolders = new ArrayList<>(mediaSources.size());
for (MediaSource mediaSource : mediaSources) {
mediaSourceHolders.add(new MediaSourceHolder(mediaSource));
}
mediaSourcesPublic.addAll(index, mediaSourceHolders);
if (playbackThreadHandler != null && !mediaSources.isEmpty()) {
HandlerAndRunnable callbackAction = createOnCompletionAction(handler, onCompletionAction);
playbackThreadHandler
.obtainMessage(MSG_ADD, new MessageData<>(index, mediaSourceHolders, callbackAction))
.sendToTarget();
} else if (onCompletionAction != null && handler != null) {
handler.post(onCompletionAction);
}
}
@GuardedBy("this")
private void removePublicMediaSources(
int fromIndex,
int toIndex,
@Nullable Handler handler,
@Nullable Runnable onCompletionAction) {
Assertions.checkArgument((handler == null) == (onCompletionAction == null));
Handler playbackThreadHandler = this.playbackThreadHandler;
Util.removeRange(mediaSourcesPublic, fromIndex, toIndex);
if (playbackThreadHandler != null) {
HandlerAndRunnable callbackAction = createOnCompletionAction(handler, onCompletionAction);
playbackThreadHandler
.obtainMessage(MSG_REMOVE, new MessageData<>(fromIndex, toIndex, callbackAction))
.sendToTarget();
} else if (onCompletionAction != null && handler != null) {
handler.post(onCompletionAction);
}
}
@GuardedBy("this")
private void movePublicMediaSource(
int currentIndex,
int newIndex,
@Nullable Handler handler,
@Nullable Runnable onCompletionAction) {
Assertions.checkArgument((handler == null) == (onCompletionAction == null));
Handler playbackThreadHandler = this.playbackThreadHandler;
mediaSourcesPublic.add(newIndex, mediaSourcesPublic.remove(currentIndex));
if (playbackThreadHandler != null) {
HandlerAndRunnable callbackAction = createOnCompletionAction(handler, onCompletionAction);
playbackThreadHandler
.obtainMessage(MSG_MOVE, new MessageData<>(currentIndex, newIndex, callbackAction))
.sendToTarget();
} else if (onCompletionAction != null && handler != null) {
handler.post(onCompletionAction);
}
}
@GuardedBy("this")
private void setPublicShuffleOrder(
ShuffleOrder shuffleOrder, @Nullable Handler handler, @Nullable Runnable onCompletionAction) {
Assertions.checkArgument((handler == null) == (onCompletionAction == null));
Handler playbackThreadHandler = this.playbackThreadHandler;
if (playbackThreadHandler != null) {
int size = getSize();
if (shuffleOrder.getLength() != size) {
shuffleOrder =
shuffleOrder
.cloneAndClear()
.cloneAndInsert(/* insertionIndex= */ 0, /* insertionCount= */ size);
}
HandlerAndRunnable callbackAction = createOnCompletionAction(handler, onCompletionAction);
playbackThreadHandler
.obtainMessage(
MSG_SET_SHUFFLE_ORDER,
new MessageData<>(/* index= */ 0, shuffleOrder, callbackAction))
.sendToTarget();
} else {
this.shuffleOrder =
shuffleOrder.getLength() > 0 ? shuffleOrder.cloneAndClear() : shuffleOrder;
if (onCompletionAction != null && handler != null) {
handler.post(onCompletionAction);
}
}
}
@GuardedBy("this")
@Nullable
private HandlerAndRunnable createOnCompletionAction(
@Nullable Handler handler, @Nullable Runnable runnable) {
if (handler == null || runnable == null) {
return null;
}
HandlerAndRunnable handlerAndRunnable = new HandlerAndRunnable(handler, runnable);
pendingOnCompletionActions.add(handlerAndRunnable);
return handlerAndRunnable;
}
// Internal methods. Called on the playback thread.
@SuppressWarnings("unchecked") @SuppressWarnings("unchecked")
private boolean handleMessage(Message msg) { private boolean handleMessage(Message msg) {
if (playbackThreadHandler == null) {
// Stale event.
return false;
}
switch (msg.what) { switch (msg.what) {
case MSG_ADD: case MSG_ADD:
MessageData<Collection<MediaSourceHolder>> addMessage = MessageData<Collection<MediaSourceHolder>> addMessage =
(MessageData<Collection<MediaSourceHolder>>) Util.castNonNull(msg.obj); (MessageData<Collection<MediaSourceHolder>>) Util.castNonNull(msg.obj);
shuffleOrder = shuffleOrder.cloneAndInsert(addMessage.index, addMessage.customData.size()); shuffleOrder = shuffleOrder.cloneAndInsert(addMessage.index, addMessage.customData.size());
addMediaSourcesInternal(addMessage.index, addMessage.customData); addMediaSourcesInternal(addMessage.index, addMessage.customData);
scheduleListenerNotification(addMessage.actionOnCompletion); scheduleTimelineUpdate(addMessage.onCompletionAction);
break; break;
case MSG_REMOVE: case MSG_REMOVE:
MessageData<Integer> removeMessage = (MessageData<Integer>) Util.castNonNull(msg.obj); MessageData<Integer> removeMessage = (MessageData<Integer>) Util.castNonNull(msg.obj);
@ -576,30 +659,27 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
for (int index = toIndex - 1; index >= fromIndex; index--) { for (int index = toIndex - 1; index >= fromIndex; index--) {
removeMediaSourceInternal(index); removeMediaSourceInternal(index);
} }
scheduleListenerNotification(removeMessage.actionOnCompletion); scheduleTimelineUpdate(removeMessage.onCompletionAction);
break; break;
case MSG_MOVE: case MSG_MOVE:
MessageData<Integer> moveMessage = (MessageData<Integer>) Util.castNonNull(msg.obj); MessageData<Integer> moveMessage = (MessageData<Integer>) Util.castNonNull(msg.obj);
shuffleOrder = shuffleOrder.cloneAndRemove(moveMessage.index, moveMessage.index + 1); shuffleOrder = shuffleOrder.cloneAndRemove(moveMessage.index, moveMessage.index + 1);
shuffleOrder = shuffleOrder.cloneAndInsert(moveMessage.customData, 1); shuffleOrder = shuffleOrder.cloneAndInsert(moveMessage.customData, 1);
moveMediaSourceInternal(moveMessage.index, moveMessage.customData); moveMediaSourceInternal(moveMessage.index, moveMessage.customData);
scheduleListenerNotification(moveMessage.actionOnCompletion); scheduleTimelineUpdate(moveMessage.onCompletionAction);
break; break;
case MSG_SET_SHUFFLE_ORDER: case MSG_SET_SHUFFLE_ORDER:
MessageData<ShuffleOrder> shuffleOrderMessage = MessageData<ShuffleOrder> shuffleOrderMessage =
(MessageData<ShuffleOrder>) Util.castNonNull(msg.obj); (MessageData<ShuffleOrder>) Util.castNonNull(msg.obj);
shuffleOrder = shuffleOrderMessage.customData; shuffleOrder = shuffleOrderMessage.customData;
scheduleListenerNotification(shuffleOrderMessage.actionOnCompletion); scheduleTimelineUpdate(shuffleOrderMessage.onCompletionAction);
break; break;
case MSG_NOTIFY_LISTENER: case MSG_UPDATE_TIMELINE:
notifyListener(); updateTimelineAndScheduleOnCompletionActions();
break; break;
case MSG_ON_COMPLETION: case MSG_ON_COMPLETION:
List<Runnable> actionsOnCompletion = (List<Runnable>) Util.castNonNull(msg.obj); Set<HandlerAndRunnable> actions = (Set<HandlerAndRunnable>) Util.castNonNull(msg.obj);
Handler handler = Assertions.checkNotNull(applicationThreadHandler); dispatchOnCompletionActions(actions);
for (int i = 0; i < actionsOnCompletion.size(); i++) {
handler.post(actionsOnCompletion.get(i));
}
break; break;
default: default:
throw new IllegalStateException(); throw new IllegalStateException();
@ -607,34 +687,46 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
return true; return true;
} }
private void scheduleListenerNotification(@Nullable Runnable actionOnCompletion) { private void scheduleTimelineUpdate() {
if (!listenerNotificationScheduled) { scheduleTimelineUpdate(/* onCompletionAction= */ null);
Assertions.checkNotNull(playbackThreadHandler)
.obtainMessage(MSG_NOTIFY_LISTENER)
.sendToTarget();
listenerNotificationScheduled = true;
} }
if (actionOnCompletion != null) {
pendingOnCompletionActions.add(actionOnCompletion); private void scheduleTimelineUpdate(@Nullable HandlerAndRunnable onCompletionAction) {
if (!timelineUpdateScheduled) {
getPlaybackThreadHandlerOnPlaybackThread().obtainMessage(MSG_UPDATE_TIMELINE).sendToTarget();
timelineUpdateScheduled = true;
}
if (onCompletionAction != null) {
nextTimelineUpdateOnCompletionActions.add(onCompletionAction);
} }
} }
private void notifyListener() { private void updateTimelineAndScheduleOnCompletionActions() {
listenerNotificationScheduled = false; timelineUpdateScheduled = false;
List<Runnable> actionsOnCompletion = Set<HandlerAndRunnable> onCompletionActions = nextTimelineUpdateOnCompletionActions;
pendingOnCompletionActions.isEmpty() nextTimelineUpdateOnCompletionActions = new HashSet<>();
? Collections.emptyList()
: new ArrayList<>(pendingOnCompletionActions);
pendingOnCompletionActions.clear();
refreshSourceInfo( refreshSourceInfo(
new ConcatenatedTimeline( new ConcatenatedTimeline(
mediaSourceHolders, windowCount, periodCount, shuffleOrder, isAtomic), mediaSourceHolders, windowCount, periodCount, shuffleOrder, isAtomic),
/* manifest= */ null); /* manifest= */ null);
if (!actionsOnCompletion.isEmpty()) { getPlaybackThreadHandlerOnPlaybackThread()
Assertions.checkNotNull(playbackThreadHandler) .obtainMessage(MSG_ON_COMPLETION, onCompletionActions)
.obtainMessage(MSG_ON_COMPLETION, actionsOnCompletion)
.sendToTarget(); .sendToTarget();
} }
@SuppressWarnings("GuardedBy")
private Handler getPlaybackThreadHandlerOnPlaybackThread() {
// Write access to this value happens on the playback thread only, so playback thread reads
// don't need to be synchronized.
return Assertions.checkNotNull(playbackThreadHandler);
}
private synchronized void dispatchOnCompletionActions(
Set<HandlerAndRunnable> onCompletionActions) {
for (HandlerAndRunnable pendingAction : onCompletionActions) {
pendingAction.dispatch();
}
pendingOnCompletionActions.removeAll(onCompletionActions);
} }
private void addMediaSourcesInternal( private void addMediaSourcesInternal(
@ -733,7 +825,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
} }
} }
mediaSourceHolder.isPrepared = true; mediaSourceHolder.isPrepared = true;
scheduleListenerNotification(/* actionOnCompletion= */ null); scheduleTimelineUpdate();
} }
private void removeMediaSourceInternal(int index) { private void removeMediaSourceInternal(int index) {
@ -846,12 +938,12 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
public final int index; public final int index;
public final T customData; public final T customData;
public final @Nullable Runnable actionOnCompletion; @Nullable public final HandlerAndRunnable onCompletionAction;
public MessageData(int index, T customData, @Nullable Runnable actionOnCompletion) { public MessageData(int index, T customData, @Nullable HandlerAndRunnable onCompletionAction) {
this.index = index; this.index = index;
this.actionOnCompletion = actionOnCompletion;
this.customData = customData; this.customData = customData;
this.onCompletionAction = onCompletionAction;
} }
} }
@ -1104,5 +1196,20 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
// Do nothing. // Do nothing.
} }
} }
private static final class HandlerAndRunnable {
private final Handler handler;
private final Runnable runnable;
public HandlerAndRunnable(Handler handler, Runnable runnable) {
this.handler = handler;
this.runnable = runnable;
}
public void dispatch() {
handler.post(runnable);
}
}
} }

View File

@ -346,10 +346,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
} else if (isPendingReset()) { } else if (isPendingReset()) {
return pendingResetPositionUs; return pendingResetPositionUs;
} }
long largestQueuedTimestampUs = C.TIME_UNSET; long largestQueuedTimestampUs = Long.MAX_VALUE;
if (haveAudioVideoTracks) { if (haveAudioVideoTracks) {
// Ignore non-AV tracks, which may be sparse or poorly interleaved. // Ignore non-AV tracks, which may be sparse or poorly interleaved.
largestQueuedTimestampUs = Long.MAX_VALUE;
int trackCount = sampleQueues.length; int trackCount = sampleQueues.length;
for (int i = 0; i < trackCount; i++) { for (int i = 0; i < trackCount; i++) {
if (trackIsAudioVideoFlags[i] && !sampleQueues[i].isLastSampleQueued()) { if (trackIsAudioVideoFlags[i] && !sampleQueues[i].isLastSampleQueued()) {
@ -358,7 +357,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
} }
} }
} }
if (largestQueuedTimestampUs == C.TIME_UNSET) { if (largestQueuedTimestampUs == Long.MAX_VALUE) {
largestQueuedTimestampUs = getLargestQueuedTimestampUs(); largestQueuedTimestampUs = getLargestQueuedTimestampUs();
} }
return largestQueuedTimestampUs == Long.MIN_VALUE ? lastSeekPositionUs return largestQueuedTimestampUs == Long.MIN_VALUE ? lastSeekPositionUs

View File

@ -31,7 +31,7 @@ import java.util.Map;
* Loops a {@link MediaSource} a specified number of times. * Loops a {@link MediaSource} a specified number of times.
* *
* <p>Note: To loop a {@link MediaSource} indefinitely, it is usually better to use {@link * <p>Note: To loop a {@link MediaSource} indefinitely, it is usually better to use {@link
* ExoPlayer#setRepeatMode(int)}. * ExoPlayer#setRepeatMode(int)} instead of this class.
*/ */
public final class LoopingMediaSource extends CompositeMediaSource<Void> { public final class LoopingMediaSource extends CompositeMediaSource<Void> {

View File

@ -1319,8 +1319,9 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
} }
synchronized (MediaCodecVideoRenderer.class) { synchronized (MediaCodecVideoRenderer.class) {
if (!evaluatedDeviceNeedsSetOutputSurfaceWorkaround) { if (!evaluatedDeviceNeedsSetOutputSurfaceWorkaround) {
if (Util.SDK_INT <= 27 && "dangal".equals(Util.DEVICE)) { if (Util.SDK_INT <= 27 && ("dangal".equals(Util.DEVICE) || "HWEML".equals(Util.DEVICE))) {
// Dangal is affected on API level 27: https://github.com/google/ExoPlayer/issues/5169. // A small number of devices are affected on API level 27:
// https://github.com/google/ExoPlayer/issues/5169.
deviceNeedsSetOutputSurfaceWorkaround = true; deviceNeedsSetOutputSurfaceWorkaround = true;
} else if (Util.SDK_INT >= 27) { } else if (Util.SDK_INT >= 27) {
// In general, devices running API level 27 or later should be unaffected. Do nothing. // In general, devices running API level 27 or later should be unaffected. Do nothing.

View File

@ -17,13 +17,16 @@ package com.google.android.exoplayer2.source;
import static com.google.common.truth.Truth.assertThat; import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.fail; import static org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify; import static org.mockito.Mockito.verify;
import android.os.ConditionVariable; import android.os.ConditionVariable;
import android.os.Handler;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Player; import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.source.MediaSource.SourceInfoRefreshListener;
import com.google.android.exoplayer2.source.ShuffleOrder.DefaultShuffleOrder; import com.google.android.exoplayer2.source.ShuffleOrder.DefaultShuffleOrder;
import com.google.android.exoplayer2.testutil.DummyMainThread; import com.google.android.exoplayer2.testutil.DummyMainThread;
import com.google.android.exoplayer2.testutil.FakeMediaSource; import com.google.android.exoplayer2.testutil.FakeMediaSource;
@ -41,7 +44,6 @@ import org.junit.After;
import org.junit.Before; import org.junit.Before;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.robolectric.RobolectricTestRunner; import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
@ -415,57 +417,59 @@ public final class ConcatenatingMediaSourceTest {
@Test @Test
public void testCustomCallbackBeforePreparationAddSingle() { public void testCustomCallbackBeforePreparationAddSingle() {
Runnable runnable = Mockito.mock(Runnable.class); Runnable runnable = mock(Runnable.class);
mediaSource.addMediaSource(createFakeMediaSource(), runnable); mediaSource.addMediaSource(createFakeMediaSource(), new Handler(), runnable);
verify(runnable).run(); verify(runnable).run();
} }
@Test @Test
public void testCustomCallbackBeforePreparationAddMultiple() { public void testCustomCallbackBeforePreparationAddMultiple() {
Runnable runnable = Mockito.mock(Runnable.class); Runnable runnable = mock(Runnable.class);
mediaSource.addMediaSources( mediaSource.addMediaSources(
Arrays.asList(new MediaSource[] {createFakeMediaSource(), createFakeMediaSource()}), Arrays.asList(new MediaSource[] {createFakeMediaSource(), createFakeMediaSource()}),
new Handler(),
runnable); runnable);
verify(runnable).run(); verify(runnable).run();
} }
@Test @Test
public void testCustomCallbackBeforePreparationAddSingleWithIndex() { public void testCustomCallbackBeforePreparationAddSingleWithIndex() {
Runnable runnable = Mockito.mock(Runnable.class); Runnable runnable = mock(Runnable.class);
mediaSource.addMediaSource(/* index */ 0, createFakeMediaSource(), runnable); mediaSource.addMediaSource(/* index */ 0, createFakeMediaSource(), new Handler(), runnable);
verify(runnable).run(); verify(runnable).run();
} }
@Test @Test
public void testCustomCallbackBeforePreparationAddMultipleWithIndex() { public void testCustomCallbackBeforePreparationAddMultipleWithIndex() {
Runnable runnable = Mockito.mock(Runnable.class); Runnable runnable = mock(Runnable.class);
mediaSource.addMediaSources( mediaSource.addMediaSources(
/* index */ 0, /* index */ 0,
Arrays.asList(new MediaSource[] {createFakeMediaSource(), createFakeMediaSource()}), Arrays.asList(new MediaSource[] {createFakeMediaSource(), createFakeMediaSource()}),
new Handler(),
runnable); runnable);
verify(runnable).run(); verify(runnable).run();
} }
@Test @Test
public void testCustomCallbackBeforePreparationRemove() { public void testCustomCallbackBeforePreparationRemove() {
Runnable runnable = Mockito.mock(Runnable.class); Runnable runnable = mock(Runnable.class);
mediaSource.addMediaSource(createFakeMediaSource()); mediaSource.addMediaSource(createFakeMediaSource());
mediaSource.removeMediaSource(/* index */ 0, runnable); mediaSource.removeMediaSource(/* index */ 0, new Handler(), runnable);
verify(runnable).run(); verify(runnable).run();
} }
@Test @Test
public void testCustomCallbackBeforePreparationMove() { public void testCustomCallbackBeforePreparationMove() {
Runnable runnable = Mockito.mock(Runnable.class); Runnable runnable = mock(Runnable.class);
mediaSource.addMediaSources( mediaSource.addMediaSources(
Arrays.asList(new MediaSource[] {createFakeMediaSource(), createFakeMediaSource()})); Arrays.asList(new MediaSource[] {createFakeMediaSource(), createFakeMediaSource()}));
mediaSource.moveMediaSource(/* fromIndex */ 1, /* toIndex */ 0, runnable); mediaSource.moveMediaSource(/* fromIndex */ 1, /* toIndex */ 0, new Handler(), runnable);
verify(runnable).run(); verify(runnable).run();
} }
@ -476,7 +480,8 @@ public final class ConcatenatingMediaSourceTest {
testRunner.prepareSource(); testRunner.prepareSource();
final TimelineGrabber timelineGrabber = new TimelineGrabber(testRunner); final TimelineGrabber timelineGrabber = new TimelineGrabber(testRunner);
dummyMainThread.runOnMainThread( dummyMainThread.runOnMainThread(
() -> mediaSource.addMediaSource(createFakeMediaSource(), timelineGrabber)); () ->
mediaSource.addMediaSource(createFakeMediaSource(), new Handler(), timelineGrabber));
Timeline timeline = timelineGrabber.assertTimelineChangeBlocking(); Timeline timeline = timelineGrabber.assertTimelineChangeBlocking();
assertThat(timeline.getWindowCount()).isEqualTo(1); assertThat(timeline.getWindowCount()).isEqualTo(1);
} finally { } finally {
@ -495,6 +500,7 @@ public final class ConcatenatingMediaSourceTest {
mediaSource.addMediaSources( mediaSource.addMediaSources(
Arrays.asList( Arrays.asList(
new MediaSource[] {createFakeMediaSource(), createFakeMediaSource()}), new MediaSource[] {createFakeMediaSource(), createFakeMediaSource()}),
new Handler(),
timelineGrabber)); timelineGrabber));
Timeline timeline = timelineGrabber.assertTimelineChangeBlocking(); Timeline timeline = timelineGrabber.assertTimelineChangeBlocking();
assertThat(timeline.getWindowCount()).isEqualTo(2); assertThat(timeline.getWindowCount()).isEqualTo(2);
@ -511,7 +517,8 @@ public final class ConcatenatingMediaSourceTest {
final TimelineGrabber timelineGrabber = new TimelineGrabber(testRunner); final TimelineGrabber timelineGrabber = new TimelineGrabber(testRunner);
dummyMainThread.runOnMainThread( dummyMainThread.runOnMainThread(
() -> () ->
mediaSource.addMediaSource(/* index */ 0, createFakeMediaSource(), timelineGrabber)); mediaSource.addMediaSource(
/* index */ 0, createFakeMediaSource(), new Handler(), timelineGrabber));
Timeline timeline = timelineGrabber.assertTimelineChangeBlocking(); Timeline timeline = timelineGrabber.assertTimelineChangeBlocking();
assertThat(timeline.getWindowCount()).isEqualTo(1); assertThat(timeline.getWindowCount()).isEqualTo(1);
} finally { } finally {
@ -531,6 +538,7 @@ public final class ConcatenatingMediaSourceTest {
/* index */ 0, /* index */ 0,
Arrays.asList( Arrays.asList(
new MediaSource[] {createFakeMediaSource(), createFakeMediaSource()}), new MediaSource[] {createFakeMediaSource(), createFakeMediaSource()}),
new Handler(),
timelineGrabber)); timelineGrabber));
Timeline timeline = timelineGrabber.assertTimelineChangeBlocking(); Timeline timeline = timelineGrabber.assertTimelineChangeBlocking();
assertThat(timeline.getWindowCount()).isEqualTo(2); assertThat(timeline.getWindowCount()).isEqualTo(2);
@ -549,7 +557,7 @@ public final class ConcatenatingMediaSourceTest {
final TimelineGrabber timelineGrabber = new TimelineGrabber(testRunner); final TimelineGrabber timelineGrabber = new TimelineGrabber(testRunner);
dummyMainThread.runOnMainThread( dummyMainThread.runOnMainThread(
() -> mediaSource.removeMediaSource(/* index */ 0, timelineGrabber)); () -> mediaSource.removeMediaSource(/* index */ 0, new Handler(), timelineGrabber));
Timeline timeline = timelineGrabber.assertTimelineChangeBlocking(); Timeline timeline = timelineGrabber.assertTimelineChangeBlocking();
assertThat(timeline.getWindowCount()).isEqualTo(0); assertThat(timeline.getWindowCount()).isEqualTo(0);
} finally { } finally {
@ -571,7 +579,9 @@ public final class ConcatenatingMediaSourceTest {
final TimelineGrabber timelineGrabber = new TimelineGrabber(testRunner); final TimelineGrabber timelineGrabber = new TimelineGrabber(testRunner);
dummyMainThread.runOnMainThread( dummyMainThread.runOnMainThread(
() -> mediaSource.moveMediaSource(/* fromIndex */ 1, /* toIndex */ 0, timelineGrabber)); () ->
mediaSource.moveMediaSource(
/* fromIndex */ 1, /* toIndex */ 0, new Handler(), timelineGrabber));
Timeline timeline = timelineGrabber.assertTimelineChangeBlocking(); Timeline timeline = timelineGrabber.assertTimelineChangeBlocking();
assertThat(timeline.getWindowCount()).isEqualTo(2); assertThat(timeline.getWindowCount()).isEqualTo(2);
} finally { } finally {
@ -819,7 +829,7 @@ public final class ConcatenatingMediaSourceTest {
testRunner.prepareSource(); testRunner.prepareSource();
final TimelineGrabber timelineGrabber = new TimelineGrabber(testRunner); final TimelineGrabber timelineGrabber = new TimelineGrabber(testRunner);
dummyMainThread.runOnMainThread(() -> mediaSource.clear(timelineGrabber)); dummyMainThread.runOnMainThread(() -> mediaSource.clear(new Handler(), timelineGrabber));
Timeline timeline = timelineGrabber.assertTimelineChangeBlocking(); Timeline timeline = timelineGrabber.assertTimelineChangeBlocking();
assertThat(timeline.isEmpty()).isTrue(); assertThat(timeline.isEmpty()).isTrue();
@ -964,8 +974,9 @@ public final class ConcatenatingMediaSourceTest {
@Test @Test
public void testCustomCallbackBeforePreparationSetShuffleOrder() throws Exception { public void testCustomCallbackBeforePreparationSetShuffleOrder() throws Exception {
Runnable runnable = Mockito.mock(Runnable.class); Runnable runnable = mock(Runnable.class);
mediaSource.setShuffleOrder(new ShuffleOrder.UnshuffledShuffleOrder(/* length= */ 0), runnable); mediaSource.setShuffleOrder(
new ShuffleOrder.UnshuffledShuffleOrder(/* length= */ 0), new Handler(), runnable);
verify(runnable).run(); verify(runnable).run();
} }
@ -981,7 +992,9 @@ public final class ConcatenatingMediaSourceTest {
dummyMainThread.runOnMainThread( dummyMainThread.runOnMainThread(
() -> () ->
mediaSource.setShuffleOrder( mediaSource.setShuffleOrder(
new ShuffleOrder.UnshuffledShuffleOrder(/* length= */ 3), timelineGrabber)); new ShuffleOrder.UnshuffledShuffleOrder(/* length= */ 3),
new Handler(),
timelineGrabber));
Timeline timeline = timelineGrabber.assertTimelineChangeBlocking(); Timeline timeline = timelineGrabber.assertTimelineChangeBlocking();
assertThat(timeline.getFirstWindowIndex(/* shuffleModeEnabled= */ true)).isEqualTo(0); assertThat(timeline.getFirstWindowIndex(/* shuffleModeEnabled= */ true)).isEqualTo(0);
} finally { } finally {

View File

@ -101,6 +101,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
Pattern.compile("AVERAGE-BANDWIDTH=(\\d+)\\b"); Pattern.compile("AVERAGE-BANDWIDTH=(\\d+)\\b");
private static final Pattern REGEX_AUDIO = Pattern.compile("AUDIO=\"(.+?)\""); private static final Pattern REGEX_AUDIO = Pattern.compile("AUDIO=\"(.+?)\"");
private static final Pattern REGEX_BANDWIDTH = Pattern.compile("[^-]BANDWIDTH=(\\d+)\\b"); private static final Pattern REGEX_BANDWIDTH = Pattern.compile("[^-]BANDWIDTH=(\\d+)\\b");
private static final Pattern REGEX_CHANNELS = Pattern.compile("CHANNELS=\"(.+?)\"");
private static final Pattern REGEX_CODECS = Pattern.compile("CODECS=\"(.+?)\""); private static final Pattern REGEX_CODECS = Pattern.compile("CODECS=\"(.+?)\"");
private static final Pattern REGEX_RESOLUTION = Pattern.compile("RESOLUTION=(\\d+x\\d+)"); private static final Pattern REGEX_RESOLUTION = Pattern.compile("RESOLUTION=(\\d+x\\d+)");
private static final Pattern REGEX_FRAME_RATE = Pattern.compile("FRAME-RATE=([\\d\\.]+)\\b"); private static final Pattern REGEX_FRAME_RATE = Pattern.compile("FRAME-RATE=([\\d\\.]+)\\b");
@ -346,6 +347,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
switch (parseStringAttr(line, REGEX_TYPE, variableDefinitions)) { switch (parseStringAttr(line, REGEX_TYPE, variableDefinitions)) {
case TYPE_AUDIO: case TYPE_AUDIO:
String codecs = audioGroupIdToCodecs.get(groupId); String codecs = audioGroupIdToCodecs.get(groupId);
int channelCount = parseChannelsAttribute(line, variableDefinitions);
String sampleMimeType = codecs != null ? MimeTypes.getMediaMimeType(codecs) : null; String sampleMimeType = codecs != null ? MimeTypes.getMediaMimeType(codecs) : null;
format = format =
Format.createAudioContainerFormat( Format.createAudioContainerFormat(
@ -355,7 +357,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
sampleMimeType, sampleMimeType,
codecs, codecs,
/* bitrate= */ Format.NO_VALUE, /* bitrate= */ Format.NO_VALUE,
/* channelCount= */ Format.NO_VALUE, channelCount,
/* sampleRate= */ Format.NO_VALUE, /* sampleRate= */ Format.NO_VALUE,
/* initializationData= */ null, /* initializationData= */ null,
selectionFlags, selectionFlags,
@ -426,21 +428,6 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
variableDefinitions); variableDefinitions);
} }
@C.SelectionFlags
private static int parseSelectionFlags(String line) {
int flags = 0;
if (parseOptionalBooleanAttribute(line, REGEX_DEFAULT, false)) {
flags |= C.SELECTION_FLAG_DEFAULT;
}
if (parseOptionalBooleanAttribute(line, REGEX_FORCED, false)) {
flags |= C.SELECTION_FLAG_FORCED;
}
if (parseOptionalBooleanAttribute(line, REGEX_AUTOSELECT, false)) {
flags |= C.SELECTION_FLAG_AUTOSELECT;
}
return flags;
}
private static HlsMediaPlaylist parseMediaPlaylist( private static HlsMediaPlaylist parseMediaPlaylist(
HlsMasterPlaylist masterPlaylist, LineIterator iterator, String baseUri) throws IOException { HlsMasterPlaylist masterPlaylist, LineIterator iterator, String baseUri) throws IOException {
@HlsMediaPlaylist.PlaylistType int playlistType = HlsMediaPlaylist.PLAYLIST_TYPE_UNKNOWN; @HlsMediaPlaylist.PlaylistType int playlistType = HlsMediaPlaylist.PLAYLIST_TYPE_UNKNOWN;
@ -661,6 +648,28 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
segments); segments);
} }
@C.SelectionFlags
private static int parseSelectionFlags(String line) {
int flags = 0;
if (parseOptionalBooleanAttribute(line, REGEX_DEFAULT, false)) {
flags |= C.SELECTION_FLAG_DEFAULT;
}
if (parseOptionalBooleanAttribute(line, REGEX_FORCED, false)) {
flags |= C.SELECTION_FLAG_FORCED;
}
if (parseOptionalBooleanAttribute(line, REGEX_AUTOSELECT, false)) {
flags |= C.SELECTION_FLAG_AUTOSELECT;
}
return flags;
}
private static int parseChannelsAttribute(String line, Map<String, String> variableDefinitions) {
String channelsString = parseOptionalStringAttr(line, REGEX_CHANNELS, variableDefinitions);
return channelsString != null
? Integer.parseInt(Util.splitAtFirst(channelsString, "/")[0])
: Format.NO_VALUE;
}
private static @Nullable SchemeData parsePlayReadySchemeData( private static @Nullable SchemeData parsePlayReadySchemeData(
String line, Map<String, String> variableDefinitions) throws ParserException { String line, Map<String, String> variableDefinitions) throws ParserException {
String keyFormatVersions = String keyFormatVersions =

View File

@ -81,6 +81,18 @@ public class HlsMasterPlaylistParserTest {
+ "CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n" + "CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\n"
+ "http://example.com/low.m3u8\n"; + "http://example.com/low.m3u8\n";
private static final String PLAYLIST_WITH_CHANNELS_ATTRIBUTE =
" #EXTM3U \n"
+ "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"audio\",CHANNELS=\"6\",NAME=\"Eng6\","
+ "URI=\"something.m3u8\"\n"
+ "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"audio\",CHANNELS=\"2/6\",NAME=\"Eng26\","
+ "URI=\"something2.m3u8\"\n"
+ "#EXT-X-MEDIA:TYPE=AUDIO,GROUP-ID=\"audio\",NAME=\"Eng\","
+ "URI=\"something3.m3u8\"\n"
+ "#EXT-X-STREAM-INF:BANDWIDTH=1280000,"
+ "CODECS=\"mp4a.40.2,avc1.66.30\",AUDIO=\"audio\",RESOLUTION=304x128\n"
+ "http://example.com/low.m3u8\n";
private static final String PLAYLIST_WITHOUT_CC = private static final String PLAYLIST_WITHOUT_CC =
" #EXTM3U \n" " #EXTM3U \n"
+ "#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS," + "#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,"
@ -216,6 +228,17 @@ public class HlsMasterPlaylistParserTest {
assertThat(closedCaptionFormat.language).isEqualTo("es"); assertThat(closedCaptionFormat.language).isEqualTo("es");
} }
@Test
public void testPlaylistWithChannelsAttribute() throws IOException {
HlsMasterPlaylist playlist =
parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITH_CHANNELS_ATTRIBUTE);
List<HlsMasterPlaylist.HlsUrl> audios = playlist.audios;
assertThat(audios).hasSize(3);
assertThat(audios.get(0).format.channelCount).isEqualTo(6);
assertThat(audios.get(1).format.channelCount).isEqualTo(2);
assertThat(audios.get(2).format.channelCount).isEqualTo(Format.NO_VALUE);
}
@Test @Test
public void testPlaylistWithoutClosedCaptions() throws IOException { public void testPlaylistWithoutClosedCaptions() throws IOException {
HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITHOUT_CC); HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITHOUT_CC);

View File

@ -758,10 +758,6 @@ public class PlayerView extends FrameLayout {
@Override @Override
public boolean dispatchKeyEvent(KeyEvent event) { public boolean dispatchKeyEvent(KeyEvent event) {
if (player != null && player.isPlayingAd()) { if (player != null && player.isPlayingAd()) {
// Focus any overlay UI now, in case it's provided by a WebView whose contents may update
// dynamically. This is needed to make the "Skip ad" button focused on Android TV when using
// IMA [Internal: b/62371030].
overlayFrameLayout.requestFocus();
return super.dispatchKeyEvent(event); return super.dispatchKeyEvent(event);
} }
boolean isDpadWhenControlHidden = boolean isDpadWhenControlHidden =
@ -1035,6 +1031,12 @@ public class PlayerView extends FrameLayout {
if (ev.getActionMasked() != MotionEvent.ACTION_DOWN) { if (ev.getActionMasked() != MotionEvent.ACTION_DOWN) {
return false; return false;
} }
return performClick();
}
@Override
public boolean performClick() {
super.performClick();
return toggleControllerVisibility(); return toggleControllerVisibility();
} }

View File

@ -20,6 +20,7 @@ import android.content.Context;
import android.media.MediaCodec; import android.media.MediaCodec;
import android.media.MediaCrypto; import android.media.MediaCrypto;
import android.os.Handler; import android.os.Handler;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.DefaultRenderersFactory; import com.google.android.exoplayer2.DefaultRenderersFactory;
import com.google.android.exoplayer2.ExoPlaybackException; import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format; import com.google.android.exoplayer2.Format;
@ -37,22 +38,37 @@ import java.util.ArrayList;
/** /**
* A debug extension of {@link DefaultRenderersFactory}. Provides a video renderer that performs * A debug extension of {@link DefaultRenderersFactory}. Provides a video renderer that performs
* video buffer timestamp assertions. * video buffer timestamp assertions, and modifies the default value for {@link
* #setAllowedVideoJoiningTimeMs(long)} to be {@code 0}.
*/ */
@TargetApi(16) @TargetApi(16)
public class DebugRenderersFactory extends DefaultRenderersFactory { public class DebugRenderersFactory extends DefaultRenderersFactory {
public DebugRenderersFactory(Context context) { public DebugRenderersFactory(Context context) {
super(context, DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF, 0); super(context);
setAllowedVideoJoiningTimeMs(0);
} }
@Override @Override
protected void buildVideoRenderers(Context context, protected void buildVideoRenderers(
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, long allowedVideoJoiningTimeMs, Context context,
Handler eventHandler, VideoRendererEventListener eventListener, @ExtensionRendererMode int extensionRendererMode,
@ExtensionRendererMode int extensionRendererMode, ArrayList<Renderer> out) { MediaCodecSelector mediaCodecSelector,
out.add(new DebugMediaCodecVideoRenderer(context, MediaCodecSelector.DEFAULT, @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
allowedVideoJoiningTimeMs, drmSessionManager, eventHandler, eventListener, boolean playClearSamplesWithoutKeys,
Handler eventHandler,
VideoRendererEventListener eventListener,
long allowedVideoJoiningTimeMs,
ArrayList<Renderer> out) {
out.add(
new DebugMediaCodecVideoRenderer(
context,
mediaCodecSelector,
allowedVideoJoiningTimeMs,
drmSessionManager,
playClearSamplesWithoutKeys,
eventHandler,
eventListener,
MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY)); MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY));
} }
@ -72,12 +88,24 @@ public class DebugRenderersFactory extends DefaultRenderersFactory {
private int minimumInsertIndex; private int minimumInsertIndex;
private boolean skipToPositionBeforeRenderingFirstFrame; private boolean skipToPositionBeforeRenderingFirstFrame;
public DebugMediaCodecVideoRenderer(Context context, MediaCodecSelector mediaCodecSelector, public DebugMediaCodecVideoRenderer(
long allowedJoiningTimeMs, DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, Context context,
Handler eventHandler, VideoRendererEventListener eventListener, MediaCodecSelector mediaCodecSelector,
long allowedJoiningTimeMs,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
boolean playClearSamplesWithoutKeys,
Handler eventHandler,
VideoRendererEventListener eventListener,
int maxDroppedFrameCountToNotify) { int maxDroppedFrameCountToNotify) {
super(context, mediaCodecSelector, allowedJoiningTimeMs, drmSessionManager, false, super(
eventHandler, eventListener, maxDroppedFrameCountToNotify); context,
mediaCodecSelector,
allowedJoiningTimeMs,
drmSessionManager,
playClearSamplesWithoutKeys,
eventHandler,
eventListener,
maxDroppedFrameCountToNotify);
} }
@Override @Override

View File

@ -37,6 +37,7 @@ import org.robolectric.shadows.ShadowMessageQueue;
public final class RobolectricUtil { public final class RobolectricUtil {
private static final AtomicLong sequenceNumberGenerator = new AtomicLong(0); private static final AtomicLong sequenceNumberGenerator = new AtomicLong(0);
private static final int ANY_MESSAGE = Integer.MIN_VALUE;
private RobolectricUtil() {} private RobolectricUtil() {}
@ -110,7 +111,8 @@ public final class RobolectricUtil {
boolean isRemoved = false; boolean isRemoved = false;
for (RemovedMessage removedMessage : removedMessages) { for (RemovedMessage removedMessage : removedMessages) {
if (removedMessage.handler == target if (removedMessage.handler == target
&& removedMessage.what == pendingMessage.message.what && (removedMessage.what == ANY_MESSAGE
|| removedMessage.what == pendingMessage.message.what)
&& (removedMessage.object == null && (removedMessage.object == null
|| removedMessage.object == pendingMessage.message.obj) || removedMessage.object == pendingMessage.message.obj)
&& pendingMessage.sequenceNumber < removedMessage.sequenceNumber) { && pendingMessage.sequenceNumber < removedMessage.sequenceNumber) {
@ -179,6 +181,15 @@ public final class RobolectricUtil {
((CustomLooper) shadowOf(looper)).removeMessages(handler, what, object); ((CustomLooper) shadowOf(looper)).removeMessages(handler, what, object);
} }
} }
@Implementation
public void removeCallbacksAndMessages(Handler handler, Object object) {
Looper looper = ShadowLooper.getLooperForThread(looperThread);
if (shadowOf(looper) instanceof CustomLooper
&& shadowOf(looper) != ShadowLooper.getShadowMainLooper()) {
((CustomLooper) shadowOf(looper)).removeMessages(handler, ANY_MESSAGE, object);
}
}
} }
private static final class PendingMessage implements Comparable<PendingMessage> { private static final class PendingMessage implements Comparable<PendingMessage> {