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 #
### 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 ###
* IMA extension: Clear ads loader listeners on release
@ -1160,7 +1177,7 @@
[here](https://medium.com/google-exoplayer/customizing-exoplayers-ui-components-728cf55ee07a#.9ewjg7avi).
* Robustness improvements when handling MediaSource timeline changes and
MediaPeriod transitions.
* EIA608: Support for caption styling and positioning.
* CEA-608: Support for caption styling and positioning.
* MPEG-TS: Improved support:
* Support injection of custom TS payload readers.
* Support injection of custom section payload readers.
@ -1404,8 +1421,8 @@ V2 release.
(#801).
* MP3: Fix playback of some streams when stream length is unknown.
* ID3: Support multiple frames of the same type in a single tag.
* EIA608: Correctly handle repeated control characters, fixing an issue in which
captions would immediately disappear.
* CEA-608: Correctly handle repeated control characters, fixing an issue in
which captions would immediately disappear.
* 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.
* Misc bug fixes.

View File

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

View File

@ -30,7 +30,7 @@ android {
}
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 'com.android.support:support-annotations:' + supportLibraryVersion
testImplementation project(modulePrefix + 'library')

View File

@ -31,13 +31,13 @@ android {
}
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 '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
// com.android.support:support-v4 and com.android.support:customtabs to be
// 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
implementation 'com.android.support:support-v4:' + supportLibraryVersion
implementation 'com.android.support:customtabs:' + supportLibraryVersion

View File

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

View File

@ -16,6 +16,7 @@
package com.google.android.exoplayer2;
import android.content.Context;
import android.media.MediaCodec;
import android.os.Handler;
import android.os.Looper;
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;
private final Context context;
private final @Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager;
private final @ExtensionRendererMode int extensionRendererMode;
private final long allowedVideoJoiningTimeMs;
@Nullable private DrmSessionManager<FrameworkMediaCrypto> drmSessionManager;
@ExtensionRendererMode private int extensionRendererMode;
private long allowedVideoJoiningTimeMs;
private boolean playClearSamplesWithoutKeys;
private MediaCodecSelector mediaCodecSelector;
/**
* @param context A {@link Context}.
*/
/** @param context A {@link 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}.
* @param extensionRendererMode 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.
* @deprecated Use {@link #DefaultRenderersFactory(Context)} and {@link
* #setExtensionRendererMode(int)}.
*/
@Deprecated
@SuppressWarnings("deprecation")
public DefaultRenderersFactory(
Context context, @ExtensionRendererMode int extensionRendererMode) {
this(context, extensionRendererMode, DEFAULT_ALLOWED_VIDEO_JOINING_TIME_MS);
}
/**
* @deprecated Use {@link #DefaultRenderersFactory(Context, int)} and pass {@link
* DrmSessionManager} directly to {@link SimpleExoPlayer} or {@link ExoPlayerFactory}.
* @deprecated Use {@link #DefaultRenderersFactory(Context)} and {@link
* #setExtensionRendererMode(int)}, and pass {@link DrmSessionManager} directly to {@link
* SimpleExoPlayer} or {@link ExoPlayerFactory}.
*/
@Deprecated
@SuppressWarnings("deprecation")
@ -132,26 +137,22 @@ public class DefaultRenderersFactory implements RenderersFactory {
}
/**
* @param context A {@link Context}.
* @param extensionRendererMode 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.
* @param allowedVideoJoiningTimeMs The maximum duration for which video renderers can attempt to
* seamlessly join an ongoing playback.
* @deprecated Use {@link #DefaultRenderersFactory(Context)}, {@link
* #setExtensionRendererMode(int)} and {@link #setAllowedVideoJoiningTimeMs(long)}.
*/
@Deprecated
@SuppressWarnings("deprecation")
public DefaultRenderersFactory(
Context context,
@ExtensionRendererMode int extensionRendererMode,
long allowedVideoJoiningTimeMs) {
this.context = context;
this.extensionRendererMode = extensionRendererMode;
this.allowedVideoJoiningTimeMs = allowedVideoJoiningTimeMs;
this.drmSessionManager = null;
this(context, null, extensionRendererMode, allowedVideoJoiningTimeMs);
}
/**
* @deprecated Use {@link #DefaultRenderersFactory(Context, int, long)} and pass {@link
* DrmSessionManager} directly to {@link SimpleExoPlayer} or {@link ExoPlayerFactory}.
* @deprecated Use {@link #DefaultRenderersFactory(Context)}, {@link
* #setExtensionRendererMode(int)} and {@link #setAllowedVideoJoiningTimeMs(long)}, and pass
* {@link DrmSessionManager} directly to {@link SimpleExoPlayer} or {@link ExoPlayerFactory}.
*/
@Deprecated
public DefaultRenderersFactory(
@ -163,6 +164,70 @@ public class DefaultRenderersFactory implements RenderersFactory {
this.extensionRendererMode = extensionRendererMode;
this.allowedVideoJoiningTimeMs = allowedVideoJoiningTimeMs;
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
@ -177,10 +242,26 @@ public class DefaultRenderersFactory implements RenderersFactory {
drmSessionManager = this.drmSessionManager;
}
ArrayList<Renderer> renderersList = new ArrayList<>();
buildVideoRenderers(context, drmSessionManager, allowedVideoJoiningTimeMs,
eventHandler, videoRendererEventListener, extensionRendererMode, renderersList);
buildAudioRenderers(context, drmSessionManager, buildAudioProcessors(),
eventHandler, audioRendererEventListener, extensionRendererMode, renderersList);
buildVideoRenderers(
context,
extensionRendererMode,
mediaCodecSelector,
drmSessionManager,
playClearSamplesWithoutKeys,
eventHandler,
videoRendererEventListener,
allowedVideoJoiningTimeMs,
renderersList);
buildAudioRenderers(
context,
extensionRendererMode,
mediaCodecSelector,
drmSessionManager,
playClearSamplesWithoutKeys,
buildAudioProcessors(),
eventHandler,
audioRendererEventListener,
renderersList);
buildTextRenderers(context, textRendererOutput, eventHandler.getLooper(),
extensionRendererMode, renderersList);
buildMetadataRenderers(context, metadataRendererOutput, eventHandler.getLooper(),
@ -194,27 +275,36 @@ public class DefaultRenderersFactory implements RenderersFactory {
* Builds video renderers for use by the player.
*
* @param context The {@link Context} associated with the player.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the player
* will not be used for DRM protected playbacks.
* @param allowedVideoJoiningTimeMs The maximum duration in milliseconds for which video
* renderers can attempt to seamlessly join an ongoing playback.
* @param extensionRendererMode The extension renderer mode.
* @param mediaCodecSelector A decoder selector.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the player will
* 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 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.
*/
protected void buildVideoRenderers(Context context,
protected void buildVideoRenderers(
Context context,
@ExtensionRendererMode int extensionRendererMode,
MediaCodecSelector mediaCodecSelector,
@Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
long allowedVideoJoiningTimeMs, Handler eventHandler,
VideoRendererEventListener eventListener, @ExtensionRendererMode int extensionRendererMode,
boolean playClearSamplesWithoutKeys,
Handler eventHandler,
VideoRendererEventListener eventListener,
long allowedVideoJoiningTimeMs,
ArrayList<Renderer> out) {
out.add(
new MediaCodecVideoRenderer(
context,
MediaCodecSelector.DEFAULT,
mediaCodecSelector,
allowedVideoJoiningTimeMs,
drmSessionManager,
/* playClearSamplesWithoutKeys= */ false,
playClearSamplesWithoutKeys,
eventHandler,
eventListener,
MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY));
@ -261,26 +351,35 @@ public class DefaultRenderersFactory implements RenderersFactory {
* Builds audio renderers for use by the player.
*
* @param context The {@link Context} associated with the player.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the player
* will not be used for DRM protected playbacks.
* @param audioProcessors An array of {@link AudioProcessor}s that will process PCM audio
* buffers before output. May be empty.
* @param extensionRendererMode The extension renderer mode.
* @param mediaCodecSelector A decoder selector.
* @param drmSessionManager An optional {@link DrmSessionManager}. May be null if the player will
* 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 eventListener An event listener.
* @param extensionRendererMode The extension renderer mode.
* @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,
AudioProcessor[] audioProcessors, Handler eventHandler,
AudioRendererEventListener eventListener, @ExtensionRendererMode int extensionRendererMode,
boolean playClearSamplesWithoutKeys,
AudioProcessor[] audioProcessors,
Handler eventHandler,
AudioRendererEventListener eventListener,
ArrayList<Renderer> out) {
out.add(
new MediaCodecAudioRenderer(
context,
MediaCodecSelector.DEFAULT,
mediaCodecSelector,
drmSessionManager,
/* playClearSamplesWithoutKeys= */ false,
playClearSamplesWithoutKeys,
eventHandler,
eventListener,
AudioCapabilities.getCapabilities(context),

View File

@ -97,7 +97,8 @@ public final class ExoPlayerFactory {
LoadControl loadControl,
@Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
@DefaultRenderersFactory.ExtensionRendererMode int extensionRendererMode) {
RenderersFactory renderersFactory = new DefaultRenderersFactory(context, extensionRendererMode);
RenderersFactory renderersFactory =
new DefaultRenderersFactory(context).setExtensionRendererMode(extensionRendererMode);
return newSimpleInstance(
context, renderersFactory, trackSelector, loadControl, drmSessionManager);
}
@ -127,7 +128,9 @@ public final class ExoPlayerFactory {
@DefaultRenderersFactory.ExtensionRendererMode int extensionRendererMode,
long allowedVideoJoiningTimeMs) {
RenderersFactory renderersFactory =
new DefaultRenderersFactory(context, extensionRendererMode, allowedVideoJoiningTimeMs);
new DefaultRenderersFactory(context)
.setExtensionRendererMode(extensionRendererMode)
.setAllowedVideoJoiningTimeMs(allowedVideoJoiningTimeMs);
return newSimpleInstance(
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". */
// 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}. */
// 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.
@ -43,7 +43,7 @@ public final class ExoPlayerLibraryInfo {
* integer version 123045006 (123-045-006).
*/
// 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}

View File

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

View File

@ -50,7 +50,8 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
FLAG_IGNORE_H264_STREAM,
FLAG_DETECT_ACCESS_UNITS,
FLAG_IGNORE_SPLICE_INFO_STREAM,
FLAG_OVERRIDE_CAPTION_DESCRIPTORS
FLAG_OVERRIDE_CAPTION_DESCRIPTORS,
FLAG_IGNORE_HDMV_DTS_STREAM
})
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.
*/
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;
@ -142,8 +149,12 @@ public final class DefaultTsPayloadReaderFactory implements TsPayloadReader.Fact
case TsExtractor.TS_STREAM_TYPE_AC3:
case TsExtractor.TS_STREAM_TYPE_E_AC3:
return new PesReader(new Ac3Reader(esInfo.language));
case TsExtractor.TS_STREAM_TYPE_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));
case TsExtractor.TS_STREAM_TYPE_H262:
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 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<>();
@ -312,32 +310,6 @@ public final class MediaCodecUtil {
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
// https://github.com/google/ExoPlayer/issues/3171.
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) {
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 {
AVC_PROFILE_NUMBER_TO_CONST = new SparseIntArray();
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.Message;
import android.support.annotation.GuardedBy;
import android.support.annotation.NonNull;
import android.support.annotation.Nullable;
import android.util.Pair;
@ -35,9 +36,11 @@ import java.util.Arrays;
import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.HashSet;
import java.util.IdentityHashMap;
import java.util.List;
import java.util.Map;
import java.util.Set;
/**
* 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_MOVE = 2;
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;
// Accessed on the app thread.
// Accessed on any thread.
@GuardedBy("this")
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 Map<MediaPeriod, MediaSourceHolder> mediaSourceByMediaPeriod;
private final Map<Object, MediaSourceHolder> mediaSourceByUid;
private final List<Runnable> pendingOnCompletionActions;
private final boolean isAtomic;
private final boolean useLazyPreparation;
private final Timeline.Window window;
private final Timeline.Period period;
@Nullable private Handler playbackThreadHandler;
@Nullable private Handler applicationThreadHandler;
private boolean listenerNotificationScheduled;
private boolean timelineUpdateScheduled;
private Set<HandlerAndRunnable> nextTimelineUpdateOnCompletionActions;
private ShuffleOrder shuffleOrder;
private int windowCount;
private int periodCount;
@ -127,7 +136,8 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
this.mediaSourceByUid = new HashMap<>();
this.mediaSourcesPublic = new ArrayList<>();
this.mediaSourceHolders = new ArrayList<>();
this.pendingOnCompletionActions = new ArrayList<>();
this.nextTimelineUpdateOnCompletionActions = new HashSet<>();
this.pendingOnCompletionActions = new HashSet<>();
this.isAtomic = isAtomic;
this.useLazyPreparation = useLazyPreparation;
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.
*/
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.
*
* @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.
*/
public final synchronized void addMediaSource(
MediaSource mediaSource, @Nullable Runnable actionOnCompletion) {
addMediaSource(mediaSourcesPublic.size(), mediaSource, actionOnCompletion);
MediaSource mediaSource, Handler handler, Runnable onCompletionAction) {
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.
*/
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
* be in the range of 0 &lt;= index &lt;= {@link #getSize()}.
* @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.
*/
public final synchronized void addMediaSource(
int index, MediaSource mediaSource, @Nullable Runnable actionOnCompletion) {
addMediaSources(index, Collections.singletonList(mediaSource), actionOnCompletion);
int index, MediaSource mediaSource, Handler handler, Runnable onCompletionAction) {
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.
*/
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
* 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.
*/
public final synchronized void addMediaSources(
Collection<MediaSource> mediaSources, @Nullable Runnable actionOnCompletion) {
addMediaSources(mediaSourcesPublic.size(), mediaSources, actionOnCompletion);
Collection<MediaSource> mediaSources, Handler handler, Runnable onCompletionAction) {
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.
*/
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()}.
* @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.
* @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.
*/
public final synchronized void addMediaSources(
int index, Collection<MediaSource> mediaSources, @Nullable Runnable actionOnCompletion) {
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()) {
playbackThreadHandler
.obtainMessage(MSG_ADD, new MessageData<>(index, mediaSourceHolders, actionOnCompletion))
.sendToTarget();
} else if (actionOnCompletion != null) {
actionOnCompletion.run();
}
int index,
Collection<MediaSource> mediaSources,
Handler handler,
Runnable onCompletionAction) {
addPublicMediaSources(index, mediaSources, handler, onCompletionAction);
}
/**
@ -259,26 +271,27 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
* range of 0 &lt;= index &lt; {@link #getSize()}.
*/
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.
*
* <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
* #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
* 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.
*/
public final synchronized void removeMediaSource(
int index, @Nullable Runnable actionOnCompletion) {
removeMediaSourceRange(index, index + 1, actionOnCompletion);
int index, Handler handler, Runnable onCompletionAction) {
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}
*/
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()}.
* @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()}.
* @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.
* @throws IllegalArgumentException When the range is malformed, i.e. {@code fromIndex} &lt; 0,
* {@code toIndex} &gt; {@link #getSize()}, {@code fromIndex} &gt; {@code toIndex}
*/
public final synchronized void removeMediaSourceRange(
int fromIndex, int toIndex, @Nullable Runnable actionOnCompletion) {
Util.removeRange(mediaSourcesPublic, fromIndex, toIndex);
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();
}
int fromIndex, int toIndex, Handler handler, Runnable onCompletionAction) {
removePublicMediaSources(fromIndex, toIndex, handler, onCompletionAction);
}
/**
@ -342,7 +344,8 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
* range of 0 &lt;= index &lt; {@link #getSize()}.
*/
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()}.
* @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()}.
* @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.
*/
public final synchronized void moveMediaSource(
int currentIndex, int newIndex, @Nullable Runnable actionOnCompletion) {
if (currentIndex == newIndex) {
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();
}
int currentIndex, int newIndex, Handler handler, Runnable onCompletionAction) {
movePublicMediaSource(currentIndex, newIndex, handler, onCompletionAction);
}
/** Clears the playlist. */
public final synchronized void clear() {
clear(/* actionOnCompletion= */ null);
removeMediaSourceRange(0, getSize());
}
/**
* 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.
*/
public final synchronized void clear(@Nullable Runnable actionOnCompletion) {
removeMediaSourceRange(0, getSize(), actionOnCompletion);
public final synchronized void clear(Handler handler, Runnable onCompletionAction) {
removeMediaSourceRange(0, getSize(), handler, onCompletionAction);
}
/** Returns the number of media sources in the playlist. */
@ -410,41 +402,24 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
* @param shuffleOrder A {@link 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.
*
* @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.
*/
public final synchronized void setShuffleOrder(
ShuffleOrder shuffleOrder, @Nullable Runnable actionOnCompletion) {
Handler playbackThreadHandler = this.playbackThreadHandler;
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();
}
}
ShuffleOrder shuffleOrder, Handler handler, Runnable onCompletionAction) {
setPublicShuffleOrder(shuffleOrder, handler, onCompletionAction);
}
// CompositeMediaSource implementation.
@Override
@Nullable
public Object getTag() {
@ -458,13 +433,12 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
@Nullable TransferListener mediaTransferListener) {
super.prepareSourceInternal(player, isTopLevelSource, mediaTransferListener);
playbackThreadHandler = new Handler(/* callback= */ this::handleMessage);
applicationThreadHandler = new Handler(player.getApplicationLooper());
if (mediaSourcesPublic.isEmpty()) {
notifyListener();
updateTimelineAndScheduleOnCompletionActions();
} else {
shuffleOrder = shuffleOrder.cloneAndInsert(0, mediaSourcesPublic.size());
addMediaSourcesInternal(0, mediaSourcesPublic);
scheduleListenerNotification(/* actionOnCompletion= */ null);
scheduleTimelineUpdate();
}
}
@ -509,15 +483,20 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
}
@Override
public final void releaseSourceInternal() {
public final synchronized void releaseSourceInternal() {
super.releaseSourceInternal();
mediaSourceHolders.clear();
mediaSourceByUid.clear();
playbackThreadHandler = null;
applicationThreadHandler = null;
shuffleOrder = shuffleOrder.cloneAndClear();
windowCount = 0;
periodCount = 0;
if (playbackThreadHandler != null) {
playbackThreadHandler.removeCallbacksAndMessages(null);
playbackThreadHandler = null;
}
timelineUpdateScheduled = false;
nextTimelineUpdateOnCompletionActions.clear();
dispatchOnCompletionActions(pendingOnCompletionActions);
}
@Override
@ -550,19 +529,123 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
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")
private boolean handleMessage(Message msg) {
if (playbackThreadHandler == null) {
// Stale event.
return false;
}
switch (msg.what) {
case MSG_ADD:
MessageData<Collection<MediaSourceHolder>> addMessage =
(MessageData<Collection<MediaSourceHolder>>) Util.castNonNull(msg.obj);
shuffleOrder = shuffleOrder.cloneAndInsert(addMessage.index, addMessage.customData.size());
addMediaSourcesInternal(addMessage.index, addMessage.customData);
scheduleListenerNotification(addMessage.actionOnCompletion);
scheduleTimelineUpdate(addMessage.onCompletionAction);
break;
case MSG_REMOVE:
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--) {
removeMediaSourceInternal(index);
}
scheduleListenerNotification(removeMessage.actionOnCompletion);
scheduleTimelineUpdate(removeMessage.onCompletionAction);
break;
case MSG_MOVE:
MessageData<Integer> moveMessage = (MessageData<Integer>) Util.castNonNull(msg.obj);
shuffleOrder = shuffleOrder.cloneAndRemove(moveMessage.index, moveMessage.index + 1);
shuffleOrder = shuffleOrder.cloneAndInsert(moveMessage.customData, 1);
moveMediaSourceInternal(moveMessage.index, moveMessage.customData);
scheduleListenerNotification(moveMessage.actionOnCompletion);
scheduleTimelineUpdate(moveMessage.onCompletionAction);
break;
case MSG_SET_SHUFFLE_ORDER:
MessageData<ShuffleOrder> shuffleOrderMessage =
(MessageData<ShuffleOrder>) Util.castNonNull(msg.obj);
shuffleOrder = shuffleOrderMessage.customData;
scheduleListenerNotification(shuffleOrderMessage.actionOnCompletion);
scheduleTimelineUpdate(shuffleOrderMessage.onCompletionAction);
break;
case MSG_NOTIFY_LISTENER:
notifyListener();
case MSG_UPDATE_TIMELINE:
updateTimelineAndScheduleOnCompletionActions();
break;
case MSG_ON_COMPLETION:
List<Runnable> actionsOnCompletion = (List<Runnable>) Util.castNonNull(msg.obj);
Handler handler = Assertions.checkNotNull(applicationThreadHandler);
for (int i = 0; i < actionsOnCompletion.size(); i++) {
handler.post(actionsOnCompletion.get(i));
}
Set<HandlerAndRunnable> actions = (Set<HandlerAndRunnable>) Util.castNonNull(msg.obj);
dispatchOnCompletionActions(actions);
break;
default:
throw new IllegalStateException();
@ -607,34 +687,46 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
return true;
}
private void scheduleListenerNotification(@Nullable Runnable actionOnCompletion) {
if (!listenerNotificationScheduled) {
Assertions.checkNotNull(playbackThreadHandler)
.obtainMessage(MSG_NOTIFY_LISTENER)
.sendToTarget();
listenerNotificationScheduled = true;
private void scheduleTimelineUpdate() {
scheduleTimelineUpdate(/* onCompletionAction= */ null);
}
private void scheduleTimelineUpdate(@Nullable HandlerAndRunnable onCompletionAction) {
if (!timelineUpdateScheduled) {
getPlaybackThreadHandlerOnPlaybackThread().obtainMessage(MSG_UPDATE_TIMELINE).sendToTarget();
timelineUpdateScheduled = true;
}
if (actionOnCompletion != null) {
pendingOnCompletionActions.add(actionOnCompletion);
if (onCompletionAction != null) {
nextTimelineUpdateOnCompletionActions.add(onCompletionAction);
}
}
private void notifyListener() {
listenerNotificationScheduled = false;
List<Runnable> actionsOnCompletion =
pendingOnCompletionActions.isEmpty()
? Collections.emptyList()
: new ArrayList<>(pendingOnCompletionActions);
pendingOnCompletionActions.clear();
private void updateTimelineAndScheduleOnCompletionActions() {
timelineUpdateScheduled = false;
Set<HandlerAndRunnable> onCompletionActions = nextTimelineUpdateOnCompletionActions;
nextTimelineUpdateOnCompletionActions = new HashSet<>();
refreshSourceInfo(
new ConcatenatedTimeline(
mediaSourceHolders, windowCount, periodCount, shuffleOrder, isAtomic),
/* manifest= */ null);
if (!actionsOnCompletion.isEmpty()) {
Assertions.checkNotNull(playbackThreadHandler)
.obtainMessage(MSG_ON_COMPLETION, actionsOnCompletion)
.sendToTarget();
getPlaybackThreadHandlerOnPlaybackThread()
.obtainMessage(MSG_ON_COMPLETION, onCompletionActions)
.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(
@ -733,7 +825,7 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
}
}
mediaSourceHolder.isPrepared = true;
scheduleListenerNotification(/* actionOnCompletion= */ null);
scheduleTimelineUpdate();
}
private void removeMediaSourceInternal(int index) {
@ -846,12 +938,12 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
public final int index;
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.actionOnCompletion = actionOnCompletion;
this.customData = customData;
this.onCompletionAction = onCompletionAction;
}
}
@ -1104,5 +1196,20 @@ public class ConcatenatingMediaSource extends CompositeMediaSource<MediaSourceHo
// 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()) {
return pendingResetPositionUs;
}
long largestQueuedTimestampUs = C.TIME_UNSET;
long largestQueuedTimestampUs = Long.MAX_VALUE;
if (haveAudioVideoTracks) {
// Ignore non-AV tracks, which may be sparse or poorly interleaved.
largestQueuedTimestampUs = Long.MAX_VALUE;
int trackCount = sampleQueues.length;
for (int i = 0; i < trackCount; i++) {
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();
}
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.
*
* <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> {

View File

@ -1319,8 +1319,9 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
}
synchronized (MediaCodecVideoRenderer.class) {
if (!evaluatedDeviceNeedsSetOutputSurfaceWorkaround) {
if (Util.SDK_INT <= 27 && "dangal".equals(Util.DEVICE)) {
// Dangal is affected on API level 27: https://github.com/google/ExoPlayer/issues/5169.
if (Util.SDK_INT <= 27 && ("dangal".equals(Util.DEVICE) || "HWEML".equals(Util.DEVICE))) {
// A small number of devices are affected on API level 27:
// https://github.com/google/ExoPlayer/issues/5169.
deviceNeedsSetOutputSurfaceWorkaround = true;
} else if (Util.SDK_INT >= 27) {
// 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 org.junit.Assert.fail;
import static org.mockito.Mockito.mock;
import static org.mockito.Mockito.verify;
import android.os.ConditionVariable;
import android.os.Handler;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
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.testutil.DummyMainThread;
import com.google.android.exoplayer2.testutil.FakeMediaSource;
@ -41,7 +44,6 @@ import org.junit.After;
import org.junit.Before;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.mockito.Mockito;
import org.robolectric.RobolectricTestRunner;
import org.robolectric.annotation.Config;
@ -415,57 +417,59 @@ public final class ConcatenatingMediaSourceTest {
@Test
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();
}
@Test
public void testCustomCallbackBeforePreparationAddMultiple() {
Runnable runnable = Mockito.mock(Runnable.class);
Runnable runnable = mock(Runnable.class);
mediaSource.addMediaSources(
Arrays.asList(new MediaSource[] {createFakeMediaSource(), createFakeMediaSource()}),
new Handler(),
runnable);
verify(runnable).run();
}
@Test
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();
}
@Test
public void testCustomCallbackBeforePreparationAddMultipleWithIndex() {
Runnable runnable = Mockito.mock(Runnable.class);
Runnable runnable = mock(Runnable.class);
mediaSource.addMediaSources(
/* index */ 0,
Arrays.asList(new MediaSource[] {createFakeMediaSource(), createFakeMediaSource()}),
new Handler(),
runnable);
verify(runnable).run();
}
@Test
public void testCustomCallbackBeforePreparationRemove() {
Runnable runnable = Mockito.mock(Runnable.class);
Runnable runnable = mock(Runnable.class);
mediaSource.addMediaSource(createFakeMediaSource());
mediaSource.removeMediaSource(/* index */ 0, runnable);
mediaSource.removeMediaSource(/* index */ 0, new Handler(), runnable);
verify(runnable).run();
}
@Test
public void testCustomCallbackBeforePreparationMove() {
Runnable runnable = Mockito.mock(Runnable.class);
Runnable runnable = mock(Runnable.class);
mediaSource.addMediaSources(
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();
}
@ -476,7 +480,8 @@ public final class ConcatenatingMediaSourceTest {
testRunner.prepareSource();
final TimelineGrabber timelineGrabber = new TimelineGrabber(testRunner);
dummyMainThread.runOnMainThread(
() -> mediaSource.addMediaSource(createFakeMediaSource(), timelineGrabber));
() ->
mediaSource.addMediaSource(createFakeMediaSource(), new Handler(), timelineGrabber));
Timeline timeline = timelineGrabber.assertTimelineChangeBlocking();
assertThat(timeline.getWindowCount()).isEqualTo(1);
} finally {
@ -495,6 +500,7 @@ public final class ConcatenatingMediaSourceTest {
mediaSource.addMediaSources(
Arrays.asList(
new MediaSource[] {createFakeMediaSource(), createFakeMediaSource()}),
new Handler(),
timelineGrabber));
Timeline timeline = timelineGrabber.assertTimelineChangeBlocking();
assertThat(timeline.getWindowCount()).isEqualTo(2);
@ -511,7 +517,8 @@ public final class ConcatenatingMediaSourceTest {
final TimelineGrabber timelineGrabber = new TimelineGrabber(testRunner);
dummyMainThread.runOnMainThread(
() ->
mediaSource.addMediaSource(/* index */ 0, createFakeMediaSource(), timelineGrabber));
mediaSource.addMediaSource(
/* index */ 0, createFakeMediaSource(), new Handler(), timelineGrabber));
Timeline timeline = timelineGrabber.assertTimelineChangeBlocking();
assertThat(timeline.getWindowCount()).isEqualTo(1);
} finally {
@ -531,6 +538,7 @@ public final class ConcatenatingMediaSourceTest {
/* index */ 0,
Arrays.asList(
new MediaSource[] {createFakeMediaSource(), createFakeMediaSource()}),
new Handler(),
timelineGrabber));
Timeline timeline = timelineGrabber.assertTimelineChangeBlocking();
assertThat(timeline.getWindowCount()).isEqualTo(2);
@ -549,7 +557,7 @@ public final class ConcatenatingMediaSourceTest {
final TimelineGrabber timelineGrabber = new TimelineGrabber(testRunner);
dummyMainThread.runOnMainThread(
() -> mediaSource.removeMediaSource(/* index */ 0, timelineGrabber));
() -> mediaSource.removeMediaSource(/* index */ 0, new Handler(), timelineGrabber));
Timeline timeline = timelineGrabber.assertTimelineChangeBlocking();
assertThat(timeline.getWindowCount()).isEqualTo(0);
} finally {
@ -571,7 +579,9 @@ public final class ConcatenatingMediaSourceTest {
final TimelineGrabber timelineGrabber = new TimelineGrabber(testRunner);
dummyMainThread.runOnMainThread(
() -> mediaSource.moveMediaSource(/* fromIndex */ 1, /* toIndex */ 0, timelineGrabber));
() ->
mediaSource.moveMediaSource(
/* fromIndex */ 1, /* toIndex */ 0, new Handler(), timelineGrabber));
Timeline timeline = timelineGrabber.assertTimelineChangeBlocking();
assertThat(timeline.getWindowCount()).isEqualTo(2);
} finally {
@ -819,7 +829,7 @@ public final class ConcatenatingMediaSourceTest {
testRunner.prepareSource();
final TimelineGrabber timelineGrabber = new TimelineGrabber(testRunner);
dummyMainThread.runOnMainThread(() -> mediaSource.clear(timelineGrabber));
dummyMainThread.runOnMainThread(() -> mediaSource.clear(new Handler(), timelineGrabber));
Timeline timeline = timelineGrabber.assertTimelineChangeBlocking();
assertThat(timeline.isEmpty()).isTrue();
@ -964,8 +974,9 @@ public final class ConcatenatingMediaSourceTest {
@Test
public void testCustomCallbackBeforePreparationSetShuffleOrder() throws Exception {
Runnable runnable = Mockito.mock(Runnable.class);
mediaSource.setShuffleOrder(new ShuffleOrder.UnshuffledShuffleOrder(/* length= */ 0), runnable);
Runnable runnable = mock(Runnable.class);
mediaSource.setShuffleOrder(
new ShuffleOrder.UnshuffledShuffleOrder(/* length= */ 0), new Handler(), runnable);
verify(runnable).run();
}
@ -981,7 +992,9 @@ public final class ConcatenatingMediaSourceTest {
dummyMainThread.runOnMainThread(
() ->
mediaSource.setShuffleOrder(
new ShuffleOrder.UnshuffledShuffleOrder(/* length= */ 3), timelineGrabber));
new ShuffleOrder.UnshuffledShuffleOrder(/* length= */ 3),
new Handler(),
timelineGrabber));
Timeline timeline = timelineGrabber.assertTimelineChangeBlocking();
assertThat(timeline.getFirstWindowIndex(/* shuffleModeEnabled= */ true)).isEqualTo(0);
} finally {

View File

@ -101,6 +101,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
Pattern.compile("AVERAGE-BANDWIDTH=(\\d+)\\b");
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_CHANNELS = Pattern.compile("CHANNELS=\"(.+?)\"");
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_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)) {
case TYPE_AUDIO:
String codecs = audioGroupIdToCodecs.get(groupId);
int channelCount = parseChannelsAttribute(line, variableDefinitions);
String sampleMimeType = codecs != null ? MimeTypes.getMediaMimeType(codecs) : null;
format =
Format.createAudioContainerFormat(
@ -355,7 +357,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
sampleMimeType,
codecs,
/* bitrate= */ Format.NO_VALUE,
/* channelCount= */ Format.NO_VALUE,
channelCount,
/* sampleRate= */ Format.NO_VALUE,
/* initializationData= */ null,
selectionFlags,
@ -426,21 +428,6 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
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(
HlsMasterPlaylist masterPlaylist, LineIterator iterator, String baseUri) throws IOException {
@HlsMediaPlaylist.PlaylistType int playlistType = HlsMediaPlaylist.PLAYLIST_TYPE_UNKNOWN;
@ -661,6 +648,28 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
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(
String line, Map<String, String> variableDefinitions) throws ParserException {
String keyFormatVersions =

View File

@ -81,6 +81,18 @@ public class HlsMasterPlaylistParserTest {
+ "CODECS=\"mp4a.40.2,avc1.66.30\",RESOLUTION=304x128\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 =
" #EXTM3U \n"
+ "#EXT-X-MEDIA:TYPE=CLOSED-CAPTIONS,"
@ -216,6 +228,17 @@ public class HlsMasterPlaylistParserTest {
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
public void testPlaylistWithoutClosedCaptions() throws IOException {
HlsMasterPlaylist playlist = parseMasterPlaylist(PLAYLIST_URI, PLAYLIST_WITHOUT_CC);

View File

@ -758,10 +758,6 @@ public class PlayerView extends FrameLayout {
@Override
public boolean dispatchKeyEvent(KeyEvent event) {
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);
}
boolean isDpadWhenControlHidden =
@ -1035,6 +1031,12 @@ public class PlayerView extends FrameLayout {
if (ev.getActionMasked() != MotionEvent.ACTION_DOWN) {
return false;
}
return performClick();
}
@Override
public boolean performClick() {
super.performClick();
return toggleControllerVisibility();
}

View File

@ -20,6 +20,7 @@ import android.content.Context;
import android.media.MediaCodec;
import android.media.MediaCrypto;
import android.os.Handler;
import android.support.annotation.Nullable;
import com.google.android.exoplayer2.DefaultRenderersFactory;
import com.google.android.exoplayer2.ExoPlaybackException;
import com.google.android.exoplayer2.Format;
@ -37,23 +38,38 @@ import java.util.ArrayList;
/**
* 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)
public class DebugRenderersFactory extends DefaultRenderersFactory {
public DebugRenderersFactory(Context context) {
super(context, DefaultRenderersFactory.EXTENSION_RENDERER_MODE_OFF, 0);
super(context);
setAllowedVideoJoiningTimeMs(0);
}
@Override
protected void buildVideoRenderers(Context context,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager, long allowedVideoJoiningTimeMs,
Handler eventHandler, VideoRendererEventListener eventListener,
@ExtensionRendererMode int extensionRendererMode, ArrayList<Renderer> out) {
out.add(new DebugMediaCodecVideoRenderer(context, MediaCodecSelector.DEFAULT,
allowedVideoJoiningTimeMs, drmSessionManager, eventHandler, eventListener,
MAX_DROPPED_VIDEO_FRAME_COUNT_TO_NOTIFY));
protected void buildVideoRenderers(
Context context,
@ExtensionRendererMode int extensionRendererMode,
MediaCodecSelector mediaCodecSelector,
@Nullable DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
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));
}
/**
@ -72,12 +88,24 @@ public class DebugRenderersFactory extends DefaultRenderersFactory {
private int minimumInsertIndex;
private boolean skipToPositionBeforeRenderingFirstFrame;
public DebugMediaCodecVideoRenderer(Context context, MediaCodecSelector mediaCodecSelector,
long allowedJoiningTimeMs, DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
Handler eventHandler, VideoRendererEventListener eventListener,
public DebugMediaCodecVideoRenderer(
Context context,
MediaCodecSelector mediaCodecSelector,
long allowedJoiningTimeMs,
DrmSessionManager<FrameworkMediaCrypto> drmSessionManager,
boolean playClearSamplesWithoutKeys,
Handler eventHandler,
VideoRendererEventListener eventListener,
int maxDroppedFrameCountToNotify) {
super(context, mediaCodecSelector, allowedJoiningTimeMs, drmSessionManager, false,
eventHandler, eventListener, maxDroppedFrameCountToNotify);
super(
context,
mediaCodecSelector,
allowedJoiningTimeMs,
drmSessionManager,
playClearSamplesWithoutKeys,
eventHandler,
eventListener,
maxDroppedFrameCountToNotify);
}
@Override

View File

@ -37,6 +37,7 @@ import org.robolectric.shadows.ShadowMessageQueue;
public final class RobolectricUtil {
private static final AtomicLong sequenceNumberGenerator = new AtomicLong(0);
private static final int ANY_MESSAGE = Integer.MIN_VALUE;
private RobolectricUtil() {}
@ -110,7 +111,8 @@ public final class RobolectricUtil {
boolean isRemoved = false;
for (RemovedMessage removedMessage : removedMessages) {
if (removedMessage.handler == target
&& removedMessage.what == pendingMessage.message.what
&& (removedMessage.what == ANY_MESSAGE
|| removedMessage.what == pendingMessage.message.what)
&& (removedMessage.object == null
|| removedMessage.object == pendingMessage.message.obj)
&& pendingMessage.sequenceNumber < removedMessage.sequenceNumber) {
@ -179,6 +181,15 @@ public final class RobolectricUtil {
((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> {