mirror of
https://github.com/androidx/media.git
synced 2025-05-03 20:59:50 +08:00
commit
2bebd526e1
@ -1,11 +1,29 @@
|
||||
# Release notes #
|
||||
|
||||
### r2.4.3 ###
|
||||
|
||||
* Audio: Workaround custom audio decoders misreporting their maximum supported
|
||||
channel counts ([#2940](https://github.com/google/ExoPlayer/issues/2940)).
|
||||
* Audio: Workaround for broken MediaTek raw decoder on some devices
|
||||
([#2873](https://github.com/google/ExoPlayer/issues/2873)).
|
||||
* Captions: Fix TTML captions appearing at the top of the screen
|
||||
([#2953](https://github.com/google/ExoPlayer/issues/2953)).
|
||||
* Captions: Fix handling of some DVB subtitles
|
||||
([#2957](https://github.com/google/ExoPlayer/issues/2957)).
|
||||
* Track selection: Fix setSelectionOverride(index, tracks, null)
|
||||
([#2988](https://github.com/google/ExoPlayer/issues/2988)).
|
||||
* GVR extension: Add support for mono input
|
||||
([#2710](https://github.com/google/ExoPlayer/issues/2710)).
|
||||
* FLAC extension: Fix failing build
|
||||
([#2977](https://github.com/google/ExoPlayer/pull/2977)).
|
||||
* Misc bugfixes.
|
||||
|
||||
### r2.4.2 ###
|
||||
|
||||
* Stability: Work around Nexus 10 reboot when playing certain content
|
||||
([2806](https://github.com/google/ExoPlayer/issues/2806)).
|
||||
([#2806](https://github.com/google/ExoPlayer/issues/2806)).
|
||||
* MP3: Correctly treat MP3s with INFO headers as constant bitrate
|
||||
([2895](https://github.com/google/ExoPlayer/issues/2895)).
|
||||
([#2895](https://github.com/google/ExoPlayer/issues/2895)).
|
||||
* HLS: Use average rather than peak bandwidth when available
|
||||
([#2863](https://github.com/google/ExoPlayer/issues/2863)).
|
||||
* SmoothStreaming: Fix timeline for live streams
|
||||
|
@ -48,7 +48,7 @@ allprojects {
|
||||
releaseRepoName = getBintrayRepo()
|
||||
releaseUserOrg = 'google'
|
||||
releaseGroupId = 'com.google.android.exoplayer'
|
||||
releaseVersion = 'r2.4.2'
|
||||
releaseVersion = 'r2.4.3'
|
||||
releaseWebsite = 'https://github.com/google/ExoPlayer'
|
||||
}
|
||||
if (it.hasProperty('externalBuildDir')) {
|
||||
|
@ -16,8 +16,8 @@
|
||||
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
package="com.google.android.exoplayer2.demo"
|
||||
android:versionCode="2402"
|
||||
android:versionName="2.4.2">
|
||||
android:versionCode="2403"
|
||||
android:versionName="2.4.3">
|
||||
|
||||
<uses-permission android:name="android.permission.INTERNET"/>
|
||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||
|
@ -22,6 +22,7 @@
|
||||
|
||||
#include <cassert>
|
||||
#include <cstdlib>
|
||||
#include <cstring>
|
||||
|
||||
#define LOG_TAG "FLACParser"
|
||||
#define ALOGE(...) \
|
||||
|
@ -25,7 +25,7 @@ android {
|
||||
|
||||
dependencies {
|
||||
compile project(':library-core')
|
||||
compile 'com.google.vr:sdk-audio:1.30.0'
|
||||
compile 'com.google.vr:sdk-audio:1.60.1'
|
||||
}
|
||||
|
||||
ext {
|
||||
|
@ -82,6 +82,9 @@ public final class GvrAudioProcessor implements AudioProcessor {
|
||||
maybeReleaseGvrAudioSurround();
|
||||
int surroundFormat;
|
||||
switch (channelCount) {
|
||||
case 1:
|
||||
surroundFormat = GvrAudioSurround.SurroundFormat.SURROUND_MONO;
|
||||
break;
|
||||
case 2:
|
||||
surroundFormat = GvrAudioSurround.SurroundFormat.SURROUND_STEREO;
|
||||
break;
|
||||
|
@ -16,6 +16,8 @@
|
||||
package com.google.android.exoplayer2.ext.okhttp;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.upstream.DataSourceException;
|
||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
||||
@ -45,13 +47,14 @@ public class OkHttpDataSource implements HttpDataSource {
|
||||
|
||||
private static final AtomicReference<byte[]> skipBufferReference = new AtomicReference<>();
|
||||
|
||||
private final Call.Factory callFactory;
|
||||
private final String userAgent;
|
||||
private final Predicate<String> contentTypePredicate;
|
||||
private final TransferListener<? super OkHttpDataSource> listener;
|
||||
private final CacheControl cacheControl;
|
||||
private final RequestProperties defaultRequestProperties;
|
||||
private final RequestProperties requestProperties;
|
||||
@NonNull private final Call.Factory callFactory;
|
||||
@NonNull private final RequestProperties requestProperties;
|
||||
|
||||
@Nullable private final String userAgent;
|
||||
@Nullable private final Predicate<String> contentTypePredicate;
|
||||
@Nullable private final TransferListener<? super OkHttpDataSource> listener;
|
||||
@Nullable private final CacheControl cacheControl;
|
||||
@Nullable private final RequestProperties defaultRequestProperties;
|
||||
|
||||
private DataSpec dataSpec;
|
||||
private Response response;
|
||||
@ -67,33 +70,34 @@ public class OkHttpDataSource implements HttpDataSource {
|
||||
/**
|
||||
* @param callFactory A {@link Call.Factory} (typically an {@link okhttp3.OkHttpClient}) for use
|
||||
* by the source.
|
||||
* @param userAgent The User-Agent string that should be used.
|
||||
* @param userAgent An optional User-Agent string.
|
||||
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
|
||||
* predicate then a InvalidContentTypeException} is thrown from {@link #open(DataSpec)}.
|
||||
*/
|
||||
public OkHttpDataSource(Call.Factory callFactory, String userAgent,
|
||||
Predicate<String> contentTypePredicate) {
|
||||
public OkHttpDataSource(@NonNull Call.Factory callFactory, @Nullable String userAgent,
|
||||
@Nullable Predicate<String> contentTypePredicate) {
|
||||
this(callFactory, userAgent, contentTypePredicate, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callFactory A {@link Call.Factory} (typically an {@link okhttp3.OkHttpClient}) for use
|
||||
* by the source.
|
||||
* @param userAgent The User-Agent string that should be used.
|
||||
* @param userAgent An optional User-Agent string.
|
||||
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
|
||||
* predicate then a {@link InvalidContentTypeException} is thrown from
|
||||
* {@link #open(DataSpec)}.
|
||||
* @param listener An optional listener.
|
||||
*/
|
||||
public OkHttpDataSource(Call.Factory callFactory, String userAgent,
|
||||
Predicate<String> contentTypePredicate, TransferListener<? super OkHttpDataSource> listener) {
|
||||
public OkHttpDataSource(@NonNull Call.Factory callFactory, @Nullable String userAgent,
|
||||
@Nullable Predicate<String> contentTypePredicate,
|
||||
@Nullable TransferListener<? super OkHttpDataSource> listener) {
|
||||
this(callFactory, userAgent, contentTypePredicate, listener, null, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callFactory A {@link Call.Factory} (typically an {@link okhttp3.OkHttpClient}) for use
|
||||
* by the source.
|
||||
* @param userAgent The User-Agent string that should be used.
|
||||
* @param userAgent An optional User-Agent string.
|
||||
* @param contentTypePredicate An optional {@link Predicate}. If a content type is rejected by the
|
||||
* predicate then a {@link InvalidContentTypeException} is thrown from
|
||||
* {@link #open(DataSpec)}.
|
||||
@ -102,11 +106,12 @@ public class OkHttpDataSource implements HttpDataSource {
|
||||
* @param defaultRequestProperties The optional default {@link RequestProperties} to be sent to
|
||||
* the server as HTTP headers on every request.
|
||||
*/
|
||||
public OkHttpDataSource(Call.Factory callFactory, String userAgent,
|
||||
Predicate<String> contentTypePredicate, TransferListener<? super OkHttpDataSource> listener,
|
||||
CacheControl cacheControl, RequestProperties defaultRequestProperties) {
|
||||
public OkHttpDataSource(@NonNull Call.Factory callFactory, @Nullable String userAgent,
|
||||
@Nullable Predicate<String> contentTypePredicate,
|
||||
@Nullable TransferListener<? super OkHttpDataSource> listener,
|
||||
@Nullable CacheControl cacheControl, @Nullable RequestProperties defaultRequestProperties) {
|
||||
this.callFactory = Assertions.checkNotNull(callFactory);
|
||||
this.userAgent = Assertions.checkNotEmpty(userAgent);
|
||||
this.userAgent = userAgent;
|
||||
this.contentTypePredicate = contentTypePredicate;
|
||||
this.listener = listener;
|
||||
this.cacheControl = cacheControl;
|
||||
@ -280,7 +285,10 @@ public class OkHttpDataSource implements HttpDataSource {
|
||||
}
|
||||
builder.addHeader("Range", rangeRequest);
|
||||
}
|
||||
builder.addHeader("User-Agent", userAgent);
|
||||
if (userAgent != null) {
|
||||
builder.addHeader("User-Agent", userAgent);
|
||||
}
|
||||
|
||||
if (!allowGzip) {
|
||||
builder.addHeader("Accept-Encoding", "identity");
|
||||
}
|
||||
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.ext.okhttp;
|
||||
|
||||
import android.support.annotation.NonNull;
|
||||
import android.support.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
||||
import com.google.android.exoplayer2.upstream.HttpDataSource.BaseFactory;
|
||||
@ -28,31 +30,32 @@ import okhttp3.Call;
|
||||
*/
|
||||
public final class OkHttpDataSourceFactory extends BaseFactory {
|
||||
|
||||
private final Call.Factory callFactory;
|
||||
private final String userAgent;
|
||||
private final TransferListener<? super DataSource> listener;
|
||||
private final CacheControl cacheControl;
|
||||
@NonNull private final Call.Factory callFactory;
|
||||
@Nullable private final String userAgent;
|
||||
@Nullable private final TransferListener<? super DataSource> listener;
|
||||
@Nullable private final CacheControl cacheControl;
|
||||
|
||||
/**
|
||||
* @param callFactory A {@link Call.Factory} (typically an {@link okhttp3.OkHttpClient}) for use
|
||||
* by the sources created by the factory.
|
||||
* @param userAgent The User-Agent string that should be used.
|
||||
* @param userAgent An optional User-Agent string.
|
||||
* @param listener An optional listener.
|
||||
*/
|
||||
public OkHttpDataSourceFactory(Call.Factory callFactory, String userAgent,
|
||||
TransferListener<? super DataSource> listener) {
|
||||
public OkHttpDataSourceFactory(@NonNull Call.Factory callFactory, @Nullable String userAgent,
|
||||
@Nullable TransferListener<? super DataSource> listener) {
|
||||
this(callFactory, userAgent, listener, null);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param callFactory A {@link Call.Factory} (typically an {@link okhttp3.OkHttpClient}) for use
|
||||
* by the sources created by the factory.
|
||||
* @param userAgent The User-Agent string that should be used.
|
||||
* @param userAgent An optional User-Agent string.
|
||||
* @param listener An optional listener.
|
||||
* @param cacheControl An optional {@link CacheControl} for setting the Cache-Control header.
|
||||
*/
|
||||
public OkHttpDataSourceFactory(Call.Factory callFactory, String userAgent,
|
||||
TransferListener<? super DataSource> listener, CacheControl cacheControl) {
|
||||
public OkHttpDataSourceFactory(@NonNull Call.Factory callFactory, @Nullable String userAgent,
|
||||
@Nullable TransferListener<? super DataSource> listener,
|
||||
@Nullable CacheControl cacheControl) {
|
||||
this.callFactory = callFactory;
|
||||
this.userAgent = userAgent;
|
||||
this.listener = listener;
|
||||
|
@ -480,11 +480,11 @@ public final class LibvpxVideoRenderer extends BaseRenderer {
|
||||
protected void onStarted() {
|
||||
droppedFrames = 0;
|
||||
droppedFrameAccumulationStartTimeMs = SystemClock.elapsedRealtime();
|
||||
joiningDeadlineMs = C.TIME_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStopped() {
|
||||
joiningDeadlineMs = C.TIME_UNSET;
|
||||
maybeNotifyDroppedFrames();
|
||||
}
|
||||
|
||||
|
@ -24,6 +24,9 @@
|
||||
android:allowBackup="false"
|
||||
tools:ignore="MissingApplicationIcon,HardcodedDebugMode">
|
||||
<uses-library android:name="android.test.runner"/>
|
||||
<provider
|
||||
android:authorities="com.google.android.exoplayer2.core.test"
|
||||
android:name="com.google.android.exoplayer2.upstream.ContentDataSourceTest$TestContentProvider"/>
|
||||
</application>
|
||||
|
||||
<instrumentation
|
||||
|
Binary file not shown.
@ -8,7 +8,7 @@ track 0:
|
||||
bitrate = -1
|
||||
id = null
|
||||
containerMimeType = null
|
||||
sampleMimeType = audio/x-flac
|
||||
sampleMimeType = audio/flac
|
||||
maxInputSize = 768000
|
||||
width = -1
|
||||
height = -1
|
||||
|
@ -8,7 +8,7 @@ track 0:
|
||||
bitrate = -1
|
||||
id = null
|
||||
containerMimeType = null
|
||||
sampleMimeType = audio/x-flac
|
||||
sampleMimeType = audio/flac
|
||||
maxInputSize = 768000
|
||||
width = -1
|
||||
height = -1
|
||||
|
@ -8,7 +8,7 @@ track 0:
|
||||
bitrate = -1
|
||||
id = null
|
||||
containerMimeType = null
|
||||
sampleMimeType = audio/x-flac
|
||||
sampleMimeType = audio/flac
|
||||
maxInputSize = 768000
|
||||
width = -1
|
||||
height = -1
|
||||
|
@ -8,7 +8,7 @@ track 0:
|
||||
bitrate = -1
|
||||
id = null
|
||||
containerMimeType = null
|
||||
sampleMimeType = audio/x-flac
|
||||
sampleMimeType = audio/flac
|
||||
maxInputSize = 768000
|
||||
width = -1
|
||||
height = -1
|
||||
|
@ -8,7 +8,7 @@ track 0:
|
||||
bitrate = -1
|
||||
id = null
|
||||
containerMimeType = null
|
||||
sampleMimeType = audio/x-flac
|
||||
sampleMimeType = audio/flac
|
||||
maxInputSize = 768000
|
||||
width = -1
|
||||
height = -1
|
||||
|
@ -8,7 +8,7 @@ track 0:
|
||||
bitrate = -1
|
||||
id = null
|
||||
containerMimeType = null
|
||||
sampleMimeType = audio/x-flac
|
||||
sampleMimeType = audio/flac
|
||||
maxInputSize = 768000
|
||||
width = -1
|
||||
height = -1
|
||||
|
@ -8,7 +8,7 @@ track 0:
|
||||
bitrate = -1
|
||||
id = null
|
||||
containerMimeType = null
|
||||
sampleMimeType = audio/x-flac
|
||||
sampleMimeType = audio/flac
|
||||
maxInputSize = 768000
|
||||
width = -1
|
||||
height = -1
|
||||
|
@ -8,7 +8,7 @@ track 0:
|
||||
bitrate = -1
|
||||
id = null
|
||||
containerMimeType = null
|
||||
sampleMimeType = audio/x-flac
|
||||
sampleMimeType = audio/flac
|
||||
maxInputSize = 768000
|
||||
width = -1
|
||||
height = -1
|
||||
|
@ -8,7 +8,7 @@ track 0:
|
||||
bitrate = -1
|
||||
id = null
|
||||
containerMimeType = null
|
||||
sampleMimeType = audio/x-flac
|
||||
sampleMimeType = audio/flac
|
||||
maxInputSize = 768000
|
||||
width = -1
|
||||
height = -1
|
||||
|
@ -8,7 +8,7 @@ track 0:
|
||||
bitrate = -1
|
||||
id = null
|
||||
containerMimeType = null
|
||||
sampleMimeType = audio/x-flac
|
||||
sampleMimeType = audio/flac
|
||||
maxInputSize = 768000
|
||||
width = -1
|
||||
height = -1
|
||||
|
@ -179,9 +179,13 @@ public final class TtmlDecoderTest extends InstrumentationTestCase {
|
||||
assertEquals(1, output.size());
|
||||
ttmlCue = output.get(0);
|
||||
assertEquals("dolor", ttmlCue.text.toString());
|
||||
assertEquals(10f / 100f, ttmlCue.position);
|
||||
assertEquals(80f / 100f, ttmlCue.line);
|
||||
assertEquals(1f, ttmlCue.size);
|
||||
assertEquals(Cue.DIMEN_UNSET, ttmlCue.position);
|
||||
assertEquals(Cue.DIMEN_UNSET, ttmlCue.line);
|
||||
assertEquals(Cue.DIMEN_UNSET, ttmlCue.size);
|
||||
// TODO: Should be as below, once https://github.com/google/ExoPlayer/issues/2953 is fixed.
|
||||
// assertEquals(10f / 100f, ttmlCue.position);
|
||||
// assertEquals(80f / 100f, ttmlCue.line);
|
||||
// assertEquals(1f, ttmlCue.size);
|
||||
|
||||
output = subtitle.getCues(21000000);
|
||||
assertEquals(1, output.size());
|
||||
|
@ -0,0 +1,196 @@
|
||||
/*
|
||||
* Copyright (C) 2016 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.trackselection;
|
||||
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||
import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.RendererCapabilities;
|
||||
import com.google.android.exoplayer2.source.TrackGroup;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
import junit.framework.TestCase;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link MappingTrackSelector}.
|
||||
*/
|
||||
public final class MappingTrackSelectorTest extends TestCase {
|
||||
|
||||
private static final RendererCapabilities VIDEO_CAPABILITIES =
|
||||
new FakeRendererCapabilities(C.TRACK_TYPE_VIDEO);
|
||||
private static final RendererCapabilities AUDIO_CAPABILITIES =
|
||||
new FakeRendererCapabilities(C.TRACK_TYPE_AUDIO);
|
||||
private static final RendererCapabilities[] RENDERER_CAPABILITIES = new RendererCapabilities[] {
|
||||
VIDEO_CAPABILITIES, AUDIO_CAPABILITIES
|
||||
};
|
||||
|
||||
private static final TrackGroup VIDEO_TRACK_GROUP = new TrackGroup(
|
||||
Format.createVideoSampleFormat("video", MimeTypes.VIDEO_H264, null, Format.NO_VALUE,
|
||||
Format.NO_VALUE, 1024, 768, Format.NO_VALUE, null, null));
|
||||
private static final TrackGroup AUDIO_TRACK_GROUP = new TrackGroup(
|
||||
Format.createAudioSampleFormat("audio", MimeTypes.AUDIO_AAC, null, Format.NO_VALUE,
|
||||
Format.NO_VALUE, 2, 44100, null, null, 0, null));
|
||||
private static final TrackGroupArray TRACK_GROUPS = new TrackGroupArray(
|
||||
VIDEO_TRACK_GROUP, AUDIO_TRACK_GROUP);
|
||||
|
||||
private static final TrackSelection[] TRACK_SELECTIONS = new TrackSelection[] {
|
||||
new FixedTrackSelection(VIDEO_TRACK_GROUP, 0),
|
||||
new FixedTrackSelection(AUDIO_TRACK_GROUP, 0)
|
||||
};
|
||||
|
||||
/**
|
||||
* Tests that the video and audio track groups are mapped onto the correct renderers.
|
||||
*/
|
||||
public void testMapping() throws ExoPlaybackException {
|
||||
FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector();
|
||||
trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS);
|
||||
trackSelector.assertMappedTrackGroups(0, VIDEO_TRACK_GROUP);
|
||||
trackSelector.assertMappedTrackGroups(1, AUDIO_TRACK_GROUP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that the video and audio track groups are mapped onto the correct renderers when the
|
||||
* renderer ordering is reversed.
|
||||
*/
|
||||
public void testMappingReverseOrder() throws ExoPlaybackException {
|
||||
FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector();
|
||||
RendererCapabilities[] reverseOrderRendererCapabilities = new RendererCapabilities[] {
|
||||
AUDIO_CAPABILITIES, VIDEO_CAPABILITIES};
|
||||
trackSelector.selectTracks(reverseOrderRendererCapabilities, TRACK_GROUPS);
|
||||
trackSelector.assertMappedTrackGroups(0, AUDIO_TRACK_GROUP);
|
||||
trackSelector.assertMappedTrackGroups(1, VIDEO_TRACK_GROUP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests video and audio track groups are mapped onto the correct renderers when there are
|
||||
* multiple track groups of the same type.
|
||||
*/
|
||||
public void testMappingMulti() throws ExoPlaybackException {
|
||||
FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector();
|
||||
TrackGroupArray multiTrackGroups = new TrackGroupArray(VIDEO_TRACK_GROUP, AUDIO_TRACK_GROUP,
|
||||
VIDEO_TRACK_GROUP);
|
||||
trackSelector.selectTracks(RENDERER_CAPABILITIES, multiTrackGroups);
|
||||
trackSelector.assertMappedTrackGroups(0, VIDEO_TRACK_GROUP, VIDEO_TRACK_GROUP);
|
||||
trackSelector.assertMappedTrackGroups(1, AUDIO_TRACK_GROUP);
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests the result of {@link MappingTrackSelector#selectTracks(RendererCapabilities[],
|
||||
* TrackGroupArray[], int[][][])} is propagated correctly to the result of
|
||||
* {@link MappingTrackSelector#selectTracks(RendererCapabilities[], TrackGroupArray)}.
|
||||
*/
|
||||
public void testSelectTracks() throws ExoPlaybackException {
|
||||
FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector(TRACK_SELECTIONS);
|
||||
TrackSelectorResult result = trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS);
|
||||
assertEquals(TRACK_SELECTIONS[0], result.selections.get(0));
|
||||
assertEquals(TRACK_SELECTIONS[1], result.selections.get(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a null override clears a track selection.
|
||||
*/
|
||||
public void testSelectTracksWithNullOverride() throws ExoPlaybackException {
|
||||
FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector(TRACK_SELECTIONS);
|
||||
trackSelector.setSelectionOverride(0, new TrackGroupArray(VIDEO_TRACK_GROUP), null);
|
||||
TrackSelectorResult result = trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS);
|
||||
assertNull(result.selections.get(0));
|
||||
assertEquals(TRACK_SELECTIONS[1], result.selections.get(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that a null override can be cleared.
|
||||
*/
|
||||
public void testSelectTracksWithClearedNullOverride() throws ExoPlaybackException {
|
||||
FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector(TRACK_SELECTIONS);
|
||||
trackSelector.setSelectionOverride(0, new TrackGroupArray(VIDEO_TRACK_GROUP), null);
|
||||
trackSelector.clearSelectionOverride(0, new TrackGroupArray(VIDEO_TRACK_GROUP));
|
||||
TrackSelectorResult result = trackSelector.selectTracks(RENDERER_CAPABILITIES, TRACK_GROUPS);
|
||||
assertEquals(TRACK_SELECTIONS[0], result.selections.get(0));
|
||||
assertEquals(TRACK_SELECTIONS[1], result.selections.get(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* Tests that an override is not applied for a different set of available track groups.
|
||||
*/
|
||||
public void testSelectTracksWithNullOverrideForDifferentTracks() throws ExoPlaybackException {
|
||||
FakeMappingTrackSelector trackSelector = new FakeMappingTrackSelector(TRACK_SELECTIONS);
|
||||
trackSelector.setSelectionOverride(0, new TrackGroupArray(VIDEO_TRACK_GROUP), null);
|
||||
TrackSelectorResult result = trackSelector.selectTracks(RENDERER_CAPABILITIES,
|
||||
new TrackGroupArray(VIDEO_TRACK_GROUP, AUDIO_TRACK_GROUP, VIDEO_TRACK_GROUP));
|
||||
assertEquals(TRACK_SELECTIONS[0], result.selections.get(0));
|
||||
assertEquals(TRACK_SELECTIONS[1], result.selections.get(1));
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link MappingTrackSelector} that returns a fixed result from
|
||||
* {@link #selectTracks(RendererCapabilities[], TrackGroupArray[], int[][][])}.
|
||||
*/
|
||||
private static final class FakeMappingTrackSelector extends MappingTrackSelector {
|
||||
|
||||
private final TrackSelection[] result;
|
||||
private TrackGroupArray[] lastRendererTrackGroupArrays;
|
||||
|
||||
public FakeMappingTrackSelector(TrackSelection... result) {
|
||||
this.result = result.length == 0 ? null : result;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected TrackSelection[] selectTracks(RendererCapabilities[] rendererCapabilities,
|
||||
TrackGroupArray[] rendererTrackGroupArrays, int[][][] rendererFormatSupports)
|
||||
throws ExoPlaybackException {
|
||||
lastRendererTrackGroupArrays = rendererTrackGroupArrays;
|
||||
return result == null ? new TrackSelection[rendererCapabilities.length] : result;
|
||||
}
|
||||
|
||||
public void assertMappedTrackGroups(int rendererIndex, TrackGroup... expected) {
|
||||
assertEquals(expected.length, lastRendererTrackGroupArrays[rendererIndex].length);
|
||||
for (int i = 0; i < expected.length; i++) {
|
||||
assertEquals(expected[i], lastRendererTrackGroupArrays[rendererIndex].get(i));
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link RendererCapabilities} that advertises adaptive support for all tracks of a given type.
|
||||
*/
|
||||
private static final class FakeRendererCapabilities implements RendererCapabilities {
|
||||
|
||||
private final int trackType;
|
||||
|
||||
public FakeRendererCapabilities(int trackType) {
|
||||
this.trackType = trackType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int getTrackType() {
|
||||
return trackType;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int supportsFormat(Format format) throws ExoPlaybackException {
|
||||
return MimeTypes.getTrackType(format.sampleMimeType) == trackType
|
||||
? (FORMAT_HANDLED | ADAPTIVE_SEAMLESS) : FORMAT_UNSUPPORTED_TYPE;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int supportsMixedMimeTypeAdaptation() throws ExoPlaybackException {
|
||||
return ADAPTIVE_SEAMLESS;
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,43 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.upstream;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.test.InstrumentationTestCase;
|
||||
import com.google.android.exoplayer2.testutil.TestUtil;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link AssetDataSource}.
|
||||
*/
|
||||
public final class AssetDataSourceTest extends InstrumentationTestCase {
|
||||
|
||||
private static final String DATA_PATH = "binary/1024_incrementing_bytes.mp3";
|
||||
|
||||
public void testReadFileUri() throws Exception {
|
||||
AssetDataSource dataSource = new AssetDataSource(getInstrumentation().getContext());
|
||||
DataSpec dataSpec = new DataSpec(Uri.parse("file:///android_asset/" + DATA_PATH));
|
||||
TestUtil.assertDataSourceContent(dataSource, dataSpec,
|
||||
TestUtil.getByteArray(getInstrumentation(), DATA_PATH));
|
||||
}
|
||||
|
||||
public void testReadAssetUri() throws Exception {
|
||||
AssetDataSource dataSource = new AssetDataSource(getInstrumentation().getContext());
|
||||
DataSpec dataSpec = new DataSpec(Uri.parse("asset:///" + DATA_PATH));
|
||||
TestUtil.assertDataSourceContent(dataSource, dataSpec,
|
||||
TestUtil.getByteArray(getInstrumentation(), DATA_PATH));
|
||||
}
|
||||
|
||||
}
|
@ -0,0 +1,122 @@
|
||||
/*
|
||||
* Copyright (C) 2017 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
* You may obtain a copy of the License at
|
||||
*
|
||||
* http://www.apache.org/licenses/LICENSE-2.0
|
||||
*
|
||||
* Unless required by applicable law or agreed to in writing, software
|
||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.upstream;
|
||||
|
||||
import android.content.ContentProvider;
|
||||
import android.content.ContentResolver;
|
||||
import android.content.ContentValues;
|
||||
import android.content.res.AssetFileDescriptor;
|
||||
import android.database.Cursor;
|
||||
import android.net.Uri;
|
||||
import android.support.annotation.NonNull;
|
||||
import android.test.InstrumentationTestCase;
|
||||
import com.google.android.exoplayer2.testutil.TestUtil;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
|
||||
/**
|
||||
* Unit tests for {@link ContentDataSource}.
|
||||
*/
|
||||
public final class ContentDataSourceTest extends InstrumentationTestCase {
|
||||
|
||||
private static final String AUTHORITY = "com.google.android.exoplayer2.core.test";
|
||||
private static final String DATA_PATH = "binary/1024_incrementing_bytes.mp3";
|
||||
|
||||
public void testReadValidUri() throws Exception {
|
||||
ContentDataSource dataSource = new ContentDataSource(getInstrumentation().getContext());
|
||||
Uri contentUri = new Uri.Builder()
|
||||
.scheme(ContentResolver.SCHEME_CONTENT)
|
||||
.authority(AUTHORITY)
|
||||
.path(DATA_PATH).build();
|
||||
DataSpec dataSpec = new DataSpec(contentUri);
|
||||
TestUtil.assertDataSourceContent(dataSource, dataSpec,
|
||||
TestUtil.getByteArray(getInstrumentation(), DATA_PATH));
|
||||
}
|
||||
|
||||
public void testReadInvalidUri() throws Exception {
|
||||
ContentDataSource dataSource = new ContentDataSource(getInstrumentation().getContext());
|
||||
Uri contentUri = new Uri.Builder()
|
||||
.scheme(ContentResolver.SCHEME_CONTENT)
|
||||
.authority(AUTHORITY)
|
||||
.build();
|
||||
DataSpec dataSpec = new DataSpec(contentUri);
|
||||
try {
|
||||
dataSource.open(dataSpec);
|
||||
fail();
|
||||
} catch (ContentDataSource.ContentDataSourceException e) {
|
||||
// Expected.
|
||||
assertTrue(e.getCause() instanceof FileNotFoundException);
|
||||
} finally {
|
||||
dataSource.close();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* A {@link ContentProvider} for the test.
|
||||
*/
|
||||
public static final class TestContentProvider extends ContentProvider {
|
||||
|
||||
@Override
|
||||
public boolean onCreate() {
|
||||
return true;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Cursor query(@NonNull Uri uri, String[] projection, String selection,
|
||||
String[] selectionArgs, String sortOrder) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public AssetFileDescriptor openAssetFile(@NonNull Uri uri, @NonNull String mode)
|
||||
throws FileNotFoundException {
|
||||
if (uri.getPath() == null) {
|
||||
return null;
|
||||
}
|
||||
try {
|
||||
return getContext().getAssets().openFd(uri.getPath().replaceFirst("/", ""));
|
||||
} catch (IOException e) {
|
||||
FileNotFoundException exception = new FileNotFoundException(e.getMessage());
|
||||
exception.initCause(e);
|
||||
throw exception;
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public String getType(@NonNull Uri uri) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Uri insert(@NonNull Uri uri, ContentValues values) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int delete(@NonNull Uri uri, String selection,
|
||||
String[] selectionArgs) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
@Override
|
||||
public int update(@NonNull Uri uri, ContentValues values,
|
||||
String selection, String[] selectionArgs) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
@ -24,13 +24,13 @@ public interface ExoPlayerLibraryInfo {
|
||||
* The version of the library expressed as a string, for example "1.2.3".
|
||||
*/
|
||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
|
||||
String VERSION = "2.4.2";
|
||||
String VERSION = "2.4.3";
|
||||
|
||||
/**
|
||||
* The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}.
|
||||
*/
|
||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
||||
String VERSION_SLASHY = "ExoPlayerLib/2.4.2";
|
||||
String VERSION_SLASHY = "ExoPlayerLib/2.4.3";
|
||||
|
||||
/**
|
||||
* The version of the library expressed as an integer, for example 1002003.
|
||||
@ -40,7 +40,7 @@ public interface ExoPlayerLibraryInfo {
|
||||
* integer version 123045006 (123-045-006).
|
||||
*/
|
||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
||||
int VERSION_INT = 2004002;
|
||||
int VERSION_INT = 2004003;
|
||||
|
||||
/**
|
||||
* Whether the library was compiled with {@link com.google.android.exoplayer2.util.Assertions}
|
||||
|
@ -1292,7 +1292,7 @@ public final class AudioTrack {
|
||||
// The timestamp time base is probably wrong.
|
||||
String message = "Spurious audio timestamp (system clock mismatch): "
|
||||
+ audioTimestampFramePosition + ", " + audioTimestampUs + ", " + systemClockUs + ", "
|
||||
+ playbackPositionUs;
|
||||
+ playbackPositionUs + ", " + getSubmittedFrames() + ", " + getWrittenFrames();
|
||||
if (failOnSpuriousAudioTimestamp) {
|
||||
throw new InvalidAudioTrackTimestampException(message);
|
||||
}
|
||||
@ -1303,7 +1303,7 @@ public final class AudioTrack {
|
||||
// The timestamp frame position is probably wrong.
|
||||
String message = "Spurious audio timestamp (frame position mismatch): "
|
||||
+ audioTimestampFramePosition + ", " + audioTimestampUs + ", " + systemClockUs + ", "
|
||||
+ playbackPositionUs;
|
||||
+ playbackPositionUs + ", " + getSubmittedFrames() + ", " + getWrittenFrames();
|
||||
if (failOnSpuriousAudioTimestamp) {
|
||||
throw new InvalidAudioTrackTimestampException(message);
|
||||
}
|
||||
|
@ -28,11 +28,11 @@ import java.util.Map;
|
||||
@TargetApi(16)
|
||||
public interface DrmSession<T extends ExoMediaCrypto> {
|
||||
|
||||
/** Wraps the exception which is the cause of the error state. */
|
||||
/** Wraps the throwable which is the cause of the error state. */
|
||||
class DrmSessionException extends Exception {
|
||||
|
||||
public DrmSessionException(Exception e) {
|
||||
super(e);
|
||||
public DrmSessionException(Throwable cause) {
|
||||
super(cause);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -67,7 +67,7 @@ import java.util.Collections;
|
||||
hasOutputFormat = true;
|
||||
} else if (audioFormat == AUDIO_FORMAT_ALAW || audioFormat == AUDIO_FORMAT_ULAW) {
|
||||
String type = audioFormat == AUDIO_FORMAT_ALAW ? MimeTypes.AUDIO_ALAW
|
||||
: MimeTypes.AUDIO_ULAW;
|
||||
: MimeTypes.AUDIO_MLAW;
|
||||
int pcmEncoding = (header & 0x01) == 1 ? C.ENCODING_PCM_16BIT : C.ENCODING_PCM_8BIT;
|
||||
Format format = Format.createAudioSampleFormat(null, type, null, Format.NO_VALUE,
|
||||
Format.NO_VALUE, 1, 8000, pcmEncoding, null, null, 0, null);
|
||||
|
@ -264,7 +264,9 @@ public final class MediaCodecInfo {
|
||||
logNoSupport("channelCount.aCaps");
|
||||
return false;
|
||||
}
|
||||
if (audioCapabilities.getMaxInputChannelCount() < channelCount) {
|
||||
int maxInputChannelCount = adjustMaxInputChannelCount(name, mimeType,
|
||||
audioCapabilities.getMaxInputChannelCount());
|
||||
if (maxInputChannelCount < channelCount) {
|
||||
logNoSupport("channelCount.support, " + channelCount);
|
||||
return false;
|
||||
}
|
||||
@ -281,6 +283,40 @@ public final class MediaCodecInfo {
|
||||
+ Util.DEVICE_DEBUG_INFO + "]");
|
||||
}
|
||||
|
||||
private static int adjustMaxInputChannelCount(String name, String mimeType, int maxChannelCount) {
|
||||
if (maxChannelCount > 1 || (Util.SDK_INT >= 26 && maxChannelCount > 0)) {
|
||||
// The maximum channel count looks like it's been set correctly.
|
||||
return maxChannelCount;
|
||||
}
|
||||
if (MimeTypes.AUDIO_MPEG.equals(mimeType)
|
||||
|| MimeTypes.AUDIO_AMR_NB.equals(mimeType)
|
||||
|| MimeTypes.AUDIO_AMR_WB.equals(mimeType)
|
||||
|| MimeTypes.AUDIO_AAC.equals(mimeType)
|
||||
|| MimeTypes.AUDIO_VORBIS.equals(mimeType)
|
||||
|| MimeTypes.AUDIO_OPUS.equals(mimeType)
|
||||
|| MimeTypes.AUDIO_RAW.equals(mimeType)
|
||||
|| MimeTypes.AUDIO_FLAC.equals(mimeType)
|
||||
|| MimeTypes.AUDIO_ALAW.equals(mimeType)
|
||||
|| MimeTypes.AUDIO_MLAW.equals(mimeType)
|
||||
|| MimeTypes.AUDIO_MSGSM.equals(mimeType)) {
|
||||
// Platform code should have set a default.
|
||||
return maxChannelCount;
|
||||
}
|
||||
// The maximum channel count looks incorrect. Adjust it to an assumed default.
|
||||
int assumedMaxChannelCount;
|
||||
if (MimeTypes.AUDIO_AC3.equals(mimeType)) {
|
||||
assumedMaxChannelCount = 6;
|
||||
} else if (MimeTypes.AUDIO_E_AC3.equals(mimeType)) {
|
||||
assumedMaxChannelCount = 16;
|
||||
} else {
|
||||
// Default to the platform limit, which is 30.
|
||||
assumedMaxChannelCount = 30;
|
||||
}
|
||||
Log.w(TAG, "AssumedMaxChannelAdjustment: " + name + ", [" + maxChannelCount + " to "
|
||||
+ assumedMaxChannelCount + "]");
|
||||
return assumedMaxChannelCount;
|
||||
}
|
||||
|
||||
private static boolean isAdaptive(CodecCapabilities capabilities) {
|
||||
return Util.SDK_INT >= 19 && isAdaptiveV19(capabilities);
|
||||
}
|
||||
|
@ -56,8 +56,10 @@ public final class MediaCodecUtil {
|
||||
}
|
||||
|
||||
private static final String TAG = "MediaCodecUtil";
|
||||
private static final String GOOGLE_RAW_DECODER_NAME = "OMX.google.raw.decoder";
|
||||
private static final String MTK_RAW_DECODER_NAME = "OMX.MTK.AUDIO.DECODER.RAW";
|
||||
private static final MediaCodecInfo PASSTHROUGH_DECODER_INFO =
|
||||
MediaCodecInfo.newPassthroughInstance("OMX.google.raw.decoder");
|
||||
MediaCodecInfo.newPassthroughInstance(GOOGLE_RAW_DECODER_NAME);
|
||||
private static final Pattern PROFILE_PATTERN = Pattern.compile("^\\D?(\\d+)$");
|
||||
|
||||
private static final HashMap<CodecKey, List<MediaCodecInfo>> decoderInfosCache = new HashMap<>();
|
||||
@ -155,6 +157,7 @@ public final class MediaCodecUtil {
|
||||
+ ". Assuming: " + decoderInfos.get(0).name);
|
||||
}
|
||||
}
|
||||
applyWorkarounds(decoderInfos);
|
||||
decoderInfos = Collections.unmodifiableList(decoderInfos);
|
||||
decoderInfosCache.put(key, decoderInfos);
|
||||
return decoderInfos;
|
||||
@ -339,6 +342,27 @@ public final class MediaCodecUtil {
|
||||
return true;
|
||||
}
|
||||
|
||||
/**
|
||||
* Modifies a list of {@link MediaCodecInfo}s to apply workarounds where we know better than the
|
||||
* platform.
|
||||
*
|
||||
* @param decoderInfos The list to modify.
|
||||
*/
|
||||
private static void applyWorkarounds(List<MediaCodecInfo> decoderInfos) {
|
||||
if (Util.SDK_INT < 26 && decoderInfos.size() > 1
|
||||
&& MTK_RAW_DECODER_NAME.equals(decoderInfos.get(0).name)) {
|
||||
// Prefer the Google raw decoder over the MediaTek one [Internal: b/62337687].
|
||||
for (int i = 1; i < decoderInfos.size(); i++) {
|
||||
MediaCodecInfo decoderInfo = decoderInfos.get(i);
|
||||
if (GOOGLE_RAW_DECODER_NAME.equals(decoderInfo.name)) {
|
||||
decoderInfos.remove(i);
|
||||
decoderInfos.add(0, decoderInfo);
|
||||
break;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the decoder is known to fail when adapting, despite advertising itself as an
|
||||
* adaptive decoder.
|
||||
|
@ -106,6 +106,8 @@ public interface MediaPeriod extends SequenceableLoader {
|
||||
|
||||
/**
|
||||
* Discards buffered media up to the specified position.
|
||||
* <p>
|
||||
* This method should only be called after the period has been prepared.
|
||||
*
|
||||
* @param positionUs The position in microseconds.
|
||||
*/
|
||||
@ -116,6 +118,8 @@ public interface MediaPeriod extends SequenceableLoader {
|
||||
* <p>
|
||||
* After this method has returned a value other than {@link C#TIME_UNSET}, all
|
||||
* {@link SampleStream}s provided by the period are guaranteed to start from a key frame.
|
||||
* <p>
|
||||
* This method should only be called after the period has been prepared.
|
||||
*
|
||||
* @return If a discontinuity was read then the playback position in microseconds after the
|
||||
* discontinuity. Else {@link C#TIME_UNSET}.
|
||||
|
@ -42,7 +42,7 @@ public final class TrackGroup {
|
||||
private int hashCode;
|
||||
|
||||
/**
|
||||
* @param formats The track formats. Must not be null or contain null elements.
|
||||
* @param formats The track formats. Must not be null, contain null elements or be of length 0.
|
||||
*/
|
||||
public TrackGroup(Format... formats) {
|
||||
Assertions.checkState(formats.length > 0);
|
||||
|
@ -667,13 +667,15 @@ import java.util.List;
|
||||
int runLength = 0;
|
||||
int clutIndex = 0;
|
||||
int peek = data.readBits(2);
|
||||
if (!data.readBit()) {
|
||||
if (peek != 0x00) {
|
||||
runLength = 1;
|
||||
clutIndex = peek;
|
||||
} else if (data.readBit()) {
|
||||
runLength = 3 + data.readBits(3);
|
||||
clutIndex = data.readBits(2);
|
||||
} else if (!data.readBit()) {
|
||||
} else if (data.readBit()) {
|
||||
runLength = 1;
|
||||
} else {
|
||||
switch (data.readBits(2)) {
|
||||
case 0x00:
|
||||
endOfPixelCodeString = true;
|
||||
|
@ -222,9 +222,9 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||
/**
|
||||
* Parses a region declaration.
|
||||
* <p>
|
||||
* If the region defines an origin and/or extent, it is required that they're defined as
|
||||
* percentages of the viewport. Region declarations that define origin and/or extent in other
|
||||
* formats are unsupported, and null is returned.
|
||||
* If the region defines an origin and extent, it is required that they're defined as percentages
|
||||
* of the viewport. Region declarations that define origin and extent in other formats are
|
||||
* unsupported, and null is returned.
|
||||
*/
|
||||
private TtmlRegion parseRegionAttributes(XmlPullParser xmlParser) {
|
||||
String regionId = XmlPullParserUtil.getAttributeValue(xmlParser, TtmlNode.ATTR_ID);
|
||||
@ -250,9 +250,13 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "Ignoring region without an origin");
|
||||
return null;
|
||||
// TODO: Should default to top left as below in this case, but need to fix
|
||||
// https://github.com/google/ExoPlayer/issues/2953 first.
|
||||
// Origin is omitted. Default to top left.
|
||||
position = 0;
|
||||
line = 0;
|
||||
// position = 0;
|
||||
// line = 0;
|
||||
}
|
||||
|
||||
float width;
|
||||
@ -273,9 +277,13 @@ public final class TtmlDecoder extends SimpleSubtitleDecoder {
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
Log.w(TAG, "Ignoring region without an extent");
|
||||
return null;
|
||||
// TODO: Should default to extent of parent as below in this case, but need to fix
|
||||
// https://github.com/google/ExoPlayer/issues/2953 first.
|
||||
// Extent is omitted. Default to extent of parent.
|
||||
width = 1;
|
||||
height = 1;
|
||||
// width = 1;
|
||||
// height = 1;
|
||||
}
|
||||
|
||||
@Cue.AnchorType int lineAnchor = Cue.ANCHOR_TYPE_START;
|
||||
|
@ -304,10 +304,10 @@ public abstract class MappingTrackSelector extends TrackSelector {
|
||||
trackSelections[i] = null;
|
||||
} else {
|
||||
TrackGroupArray rendererTrackGroup = rendererTrackGroupArrays[i];
|
||||
Map<TrackGroupArray, SelectionOverride> overrides = selectionOverrides.get(i);
|
||||
SelectionOverride override = overrides == null ? null : overrides.get(rendererTrackGroup);
|
||||
if (override != null) {
|
||||
trackSelections[i] = override.createTrackSelection(rendererTrackGroup);
|
||||
if (hasSelectionOverride(i, rendererTrackGroup)) {
|
||||
SelectionOverride override = selectionOverrides.get(i).get(rendererTrackGroup);
|
||||
trackSelections[i] = override == null ? null
|
||||
: override.createTrackSelection(rendererTrackGroup);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -22,6 +22,7 @@ import android.net.Uri;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import java.io.EOFException;
|
||||
import java.io.FileInputStream;
|
||||
import java.io.FileNotFoundException;
|
||||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
|
||||
@ -71,9 +72,13 @@ public final class ContentDataSource implements DataSource {
|
||||
try {
|
||||
uri = dataSpec.uri;
|
||||
assetFileDescriptor = resolver.openAssetFileDescriptor(uri, "r");
|
||||
if (assetFileDescriptor == null) {
|
||||
throw new FileNotFoundException("Could not open file descriptor for: " + uri);
|
||||
}
|
||||
inputStream = new FileInputStream(assetFileDescriptor.getFileDescriptor());
|
||||
long skipped = inputStream.skip(dataSpec.position);
|
||||
if (skipped < dataSpec.position) {
|
||||
long assertStartOffset = assetFileDescriptor.getStartOffset();
|
||||
long skipped = inputStream.skip(assertStartOffset + dataSpec.position) - assertStartOffset;
|
||||
if (skipped != dataSpec.position) {
|
||||
// We expect the skip to be satisfied in full. If it isn't then we're probably trying to
|
||||
// skip beyond the end of the data.
|
||||
throw new EOFException();
|
||||
@ -81,12 +86,16 @@ public final class ContentDataSource implements DataSource {
|
||||
if (dataSpec.length != C.LENGTH_UNSET) {
|
||||
bytesRemaining = dataSpec.length;
|
||||
} else {
|
||||
bytesRemaining = inputStream.available();
|
||||
if (bytesRemaining == 0) {
|
||||
// FileInputStream.available() returns 0 if the remaining length cannot be determined, or
|
||||
// if it's greater than Integer.MAX_VALUE. We don't know the true length in either case,
|
||||
// so treat as unbounded.
|
||||
bytesRemaining = C.LENGTH_UNSET;
|
||||
bytesRemaining = assetFileDescriptor.getLength();
|
||||
if (bytesRemaining == AssetFileDescriptor.UNKNOWN_LENGTH) {
|
||||
// The asset must extend to the end of the file.
|
||||
bytesRemaining = inputStream.available();
|
||||
if (bytesRemaining == 0) {
|
||||
// FileInputStream.available() returns 0 if the remaining length cannot be determined,
|
||||
// or if it's greater than Integer.MAX_VALUE. We don't know the true length in either
|
||||
// case, so treat as unbounded.
|
||||
bytesRemaining = C.LENGTH_UNSET;
|
||||
}
|
||||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
|
@ -48,7 +48,7 @@ public final class MimeTypes {
|
||||
public static final String AUDIO_MPEG_L2 = BASE_TYPE_AUDIO + "/mpeg-L2";
|
||||
public static final String AUDIO_RAW = BASE_TYPE_AUDIO + "/raw";
|
||||
public static final String AUDIO_ALAW = BASE_TYPE_AUDIO + "/g711-alaw";
|
||||
public static final String AUDIO_ULAW = BASE_TYPE_AUDIO + "/g711-mlaw";
|
||||
public static final String AUDIO_MLAW = BASE_TYPE_AUDIO + "/g711-mlaw";
|
||||
public static final String AUDIO_AC3 = BASE_TYPE_AUDIO + "/ac3";
|
||||
public static final String AUDIO_E_AC3 = BASE_TYPE_AUDIO + "/eac3";
|
||||
public static final String AUDIO_TRUEHD = BASE_TYPE_AUDIO + "/true-hd";
|
||||
@ -59,8 +59,9 @@ public final class MimeTypes {
|
||||
public static final String AUDIO_OPUS = BASE_TYPE_AUDIO + "/opus";
|
||||
public static final String AUDIO_AMR_NB = BASE_TYPE_AUDIO + "/3gpp";
|
||||
public static final String AUDIO_AMR_WB = BASE_TYPE_AUDIO + "/amr-wb";
|
||||
public static final String AUDIO_FLAC = BASE_TYPE_AUDIO + "/x-flac";
|
||||
public static final String AUDIO_FLAC = BASE_TYPE_AUDIO + "/flac";
|
||||
public static final String AUDIO_ALAC = BASE_TYPE_AUDIO + "/alac";
|
||||
public static final String AUDIO_MSGSM = BASE_TYPE_AUDIO + "/gsm";
|
||||
public static final String AUDIO_UNKNOWN = BASE_TYPE_AUDIO + "/x-unknown";
|
||||
|
||||
public static final String TEXT_VTT = BASE_TYPE_TEXT + "/vtt";
|
||||
|
@ -260,11 +260,11 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
||||
super.onStarted();
|
||||
droppedFrames = 0;
|
||||
droppedFrameAccumulationStartTimeMs = SystemClock.elapsedRealtime();
|
||||
joiningDeadlineMs = C.TIME_UNSET;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onStopped() {
|
||||
joiningDeadlineMs = C.TIME_UNSET;
|
||||
maybeNotifyDroppedFrames();
|
||||
super.onStopped();
|
||||
}
|
||||
|
@ -30,6 +30,7 @@ import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Collections;
|
||||
import java.util.HashSet;
|
||||
import java.util.LinkedList;
|
||||
import java.util.List;
|
||||
import java.util.Queue;
|
||||
@ -174,6 +175,7 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||
|
||||
private static HlsMasterPlaylist parseMasterPlaylist(LineIterator iterator, String baseUri)
|
||||
throws IOException {
|
||||
HashSet<String> variantUrls = new HashSet<>();
|
||||
ArrayList<HlsMasterPlaylist.HlsUrl> variants = new ArrayList<>();
|
||||
ArrayList<HlsMasterPlaylist.HlsUrl> audios = new ArrayList<>();
|
||||
ArrayList<HlsMasterPlaylist.HlsUrl> subtitles = new ArrayList<>();
|
||||
@ -251,11 +253,13 @@ public final class HlsPlaylistParser implements ParsingLoadable.Parser<HlsPlayli
|
||||
width = Format.NO_VALUE;
|
||||
height = Format.NO_VALUE;
|
||||
}
|
||||
line = iterator.next();
|
||||
Format format = Format.createVideoContainerFormat(Integer.toString(variants.size()),
|
||||
MimeTypes.APPLICATION_M3U8, null, codecs, bitrate, width, height, Format.NO_VALUE, null,
|
||||
0);
|
||||
variants.add(new HlsMasterPlaylist.HlsUrl(line, format));
|
||||
line = iterator.next(); // #EXT-X-STREAM-INF's URI.
|
||||
if (variantUrls.add(line)) {
|
||||
Format format = Format.createVideoContainerFormat(Integer.toString(variants.size()),
|
||||
MimeTypes.APPLICATION_M3U8, null, codecs, bitrate, width, height, Format.NO_VALUE,
|
||||
null, 0);
|
||||
variants.add(new HlsMasterPlaylist.HlsUrl(line, format));
|
||||
}
|
||||
}
|
||||
}
|
||||
if (noClosedCaptions) {
|
||||
|
@ -89,7 +89,6 @@ public class DefaultTimeBar extends View implements TimeBar {
|
||||
private final Formatter formatter;
|
||||
private final Runnable stopScrubbingRunnable;
|
||||
|
||||
private int scrubberSize;
|
||||
private OnScrubListener listener;
|
||||
private int keyCountIncrement;
|
||||
private long keyTimeIncrement;
|
||||
@ -184,7 +183,6 @@ public class DefaultTimeBar extends View implements TimeBar {
|
||||
stopScrubbing(false);
|
||||
}
|
||||
};
|
||||
scrubberSize = scrubberEnabledSize;
|
||||
scrubberPadding =
|
||||
(Math.max(scrubberDisabledSize, Math.max(scrubberEnabledSize, scrubberDraggedSize)) + 1)
|
||||
/ 2;
|
||||
@ -234,8 +232,6 @@ public class DefaultTimeBar extends View implements TimeBar {
|
||||
this.duration = duration;
|
||||
if (scrubbing && duration == C.TIME_UNSET) {
|
||||
stopScrubbing(true);
|
||||
} else {
|
||||
updateScrubberState();
|
||||
}
|
||||
update();
|
||||
}
|
||||
@ -251,7 +247,6 @@ public class DefaultTimeBar extends View implements TimeBar {
|
||||
@Override
|
||||
public void setEnabled(boolean enabled) {
|
||||
super.setEnabled(enabled);
|
||||
updateScrubberState();
|
||||
if (scrubbing && !enabled) {
|
||||
stopScrubbing(true);
|
||||
}
|
||||
@ -436,7 +431,6 @@ public class DefaultTimeBar extends View implements TimeBar {
|
||||
|
||||
private void startScrubbing() {
|
||||
scrubbing = true;
|
||||
updateScrubberState();
|
||||
ViewParent parent = getParent();
|
||||
if (parent != null) {
|
||||
parent.requestDisallowInterceptTouchEvent(true);
|
||||
@ -452,18 +446,12 @@ public class DefaultTimeBar extends View implements TimeBar {
|
||||
if (parent != null) {
|
||||
parent.requestDisallowInterceptTouchEvent(false);
|
||||
}
|
||||
updateScrubberState();
|
||||
invalidate();
|
||||
if (listener != null) {
|
||||
listener.onScrubStop(this, getScrubberPosition(), canceled);
|
||||
}
|
||||
}
|
||||
|
||||
private void updateScrubberState() {
|
||||
scrubberSize = scrubbing ? scrubberDraggedSize
|
||||
: (isEnabled() && duration >= 0 ? scrubberEnabledSize : scrubberDisabledSize);
|
||||
}
|
||||
|
||||
private void update() {
|
||||
bufferedBar.set(progressBar);
|
||||
scrubberBar.set(progressBar);
|
||||
@ -543,6 +531,8 @@ public class DefaultTimeBar extends View implements TimeBar {
|
||||
if (duration <= 0) {
|
||||
return;
|
||||
}
|
||||
int scrubberSize = (scrubbing || isFocused()) ? scrubberDraggedSize
|
||||
: (isEnabled() ? scrubberEnabledSize : scrubberDisabledSize);
|
||||
int playheadRadius = scrubberSize / 2;
|
||||
int playheadCenter = Util.constrainValue(scrubberBar.right, scrubberBar.left,
|
||||
progressBar.right);
|
||||
|
@ -17,11 +17,14 @@ package com.google.android.exoplayer2.testutil;
|
||||
|
||||
import android.app.Instrumentation;
|
||||
import android.test.InstrumentationTestCase;
|
||||
import android.test.MoreAsserts;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.extractor.Extractor;
|
||||
import com.google.android.exoplayer2.extractor.PositionHolder;
|
||||
import com.google.android.exoplayer2.extractor.SeekMap;
|
||||
import com.google.android.exoplayer2.testutil.FakeExtractorInput.SimulatedIOException;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.io.IOException;
|
||||
@ -64,6 +67,22 @@ public class TestUtil {
|
||||
}
|
||||
}
|
||||
|
||||
public static byte[] readToEnd(DataSource dataSource) throws IOException {
|
||||
byte[] data = new byte[1024];
|
||||
int position = 0;
|
||||
int bytesRead = 0;
|
||||
while (bytesRead != C.RESULT_END_OF_INPUT) {
|
||||
if (position == data.length) {
|
||||
data = Arrays.copyOf(data, data.length * 2);
|
||||
}
|
||||
bytesRead = dataSource.read(data, position, data.length - position);
|
||||
if (bytesRead != C.RESULT_END_OF_INPUT) {
|
||||
position += bytesRead;
|
||||
}
|
||||
}
|
||||
return Arrays.copyOf(data, position);
|
||||
}
|
||||
|
||||
public static FakeExtractorOutput consumeTestData(Extractor extractor, FakeExtractorInput input,
|
||||
long timeUs) throws IOException, InterruptedException {
|
||||
return consumeTestData(extractor, input, timeUs, false);
|
||||
@ -373,4 +392,24 @@ public class TestUtil {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that data read from a {@link DataSource} matches {@code expected}.
|
||||
*
|
||||
* @param dataSource The {@link DataSource} through which to read.
|
||||
* @param dataSpec The {@link DataSpec} to use when opening the {@link DataSource}.
|
||||
* @param expectedData The expected data.
|
||||
* @throws IOException If an error occurs reading fom the {@link DataSource}.
|
||||
*/
|
||||
public static void assertDataSourceContent(DataSource dataSource, DataSpec dataSpec,
|
||||
byte[] expectedData) throws IOException {
|
||||
try {
|
||||
long length = dataSource.open(dataSpec);
|
||||
Assert.assertEquals(length, expectedData.length);
|
||||
byte[] readData = TestUtil.readToEnd(dataSource);
|
||||
MoreAsserts.assertEquals(expectedData, readData);
|
||||
} finally {
|
||||
dataSource.close();
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
Loading…
x
Reference in New Issue
Block a user