Commit de39925c by Oliver Woodman Committed by GitHub

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

r2.9.5
parents 1c4ea26f 4d843da6
Showing with 219 additions and 107 deletions
# 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.
......
......@@ -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
......
......@@ -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')
......
......@@ -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
......
......@@ -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 {
......
......@@ -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);
}
......
......@@ -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}
......
......@@ -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);
......
......@@ -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)));
......
......@@ -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);
......
......@@ -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
......
......@@ -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> {
......
......@@ -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.
......
......@@ -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 {
......
......@@ -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 =
......
......@@ -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,"
......@@ -217,6 +229,17 @@ public class HlsMasterPlaylistParserTest {
}
@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);
assertThat(playlist.muxedCaptionFormats).isEmpty();
......
......@@ -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();
}
......
......@@ -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
......
......@@ -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> {
......
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or sign in to comment