Merge branch 'dev-v2' of https://github.com/TadejZupancic/ExoPlayer into dev-v2

This commit is contained in:
TadejZupancic 2021-02-01 20:31:09 +01:00
commit d3b091b22a
240 changed files with 13253 additions and 1978 deletions

View File

@ -2,6 +2,18 @@
### dev-v2 (not yet released)
* Core library:
* Log a warning when `SingleSampleMediaPeriod` transforms a load error
into end-of-stream.
* Extractors:
* Fix Vorbis private codec data parsing in the Matroska extractor
([#8496](https://github.com/google/ExoPlayer/issues/8496)).
* Text:
* Add support for the SSA `primaryColour` style attribute
([#8435](https://github.com/google/ExoPlayer/issues/8435)).
### 2.13.0 (not yet released - targeted for 2021-02-TBD)
* Core library:
* Remove long deprecated symbols:
* `AdaptiveMediaSourceEventListener`. Use `MediaSourceEventListener`
@ -80,8 +92,8 @@
`MediaSourceEventListener` and `SingleSampleMediaSource.Factory`
* `SimpleExoPlayer.addVideoDebugListener`,
`SimpleExoPlayer.removeVideoDebugListener`,
`SimpleExoPlayer.addAudioDebugListener`
and `SimpleExoPlayer.removeAudioDebugListener`. Use
`SimpleExoPlayer.addAudioDebugListener` and
`SimpleExoPlayer.removeAudioDebugListener`. Use
`SimpleExoPlayer.addAnalyticsListener` and
`SimpleExoPlayer.removeAnalyticsListener` instead.
* `AdaptiveMediaSourceEventListener`. Use `MediaSourceEventListener`
@ -126,16 +138,18 @@
* Add option to `MergingMediaSource` to clip the durations of all sources
to have the same length
([#8422](https://github.com/google/ExoPlayer/issues/8422)).
* Fix propagation of `LoadErrorHandlingPolicy` from
`DefaultMediaSourceFactory` into `SingleSampleMediaSource.Factory` when
creating subtitle media sources from
`MediaItem.playbackProperties.subtitles`
([#8430](https://github.com/google/ExoPlayer/issues/8430)).
* Remove `ExoPlaybackException.OutOfMemoryError`.
* Remove `setVideoDecoderOutputBufferRenderer` from Player API. Clients
should use `setOutputSurface` directly instead.
* Default `SingleSampleMediaSource.treatLoadErrorsAsEndOfStream` to `true`
([#8430](https://github.com/google/ExoPlayer/issues/8430)).
* Remove `setVideoDecoderOutputBufferRenderer` from Player API. Use
`setVideoSurfaceView` and `clearVideoSurfaceView` instead.
* Replace `PlayerMessage.setHandler` with `PlayerMessage.setLooper`.
* Transformer:
* Add a library to transform media inputs. Available transformations are:
configuration of output container format, removal of audio or video
track and slow motion flattening.
* Extractors:
* Populate codecs string for H.264/AVC in MP4, Matroska and FLV streams to
allow decoder capability checks based on codec profile/level
@ -145,7 +159,10 @@
([#8393](https://github.com/google/ExoPlayer/issues/8393)).
* Handle sample size mismatches between raw audio `stsd` information and
`stsz` fixed sample size in MP4 extractors.
* Add support for playing JPEG motion photos
([#5405](https://github.com/google/ExoPlayer/issues/5405)).
* Track selection:
* Moved `Player.getTrackSelector` to the `ExoPlayer` interface.
* Allow parallel adaptation for video and audio
([#5111](https://github.com/google/ExoPlayer/issues/5111)).
* Simplified enabling tunneling with `DefaultTrackSelector`.
@ -157,6 +174,10 @@
([#8320](https://github.com/google/ExoPlayer/issues/8320)).
* Add option to specify preferred audio role flags.
* Forward `Timeline` and `MediaPeriodId` to `TrackSelection.Factory`.
* In order to make it immutable, `TrackSelection` in the `Player` API now
only contains methods related to static selection.
The rest of the methods have been moved to the child
class `ExoTrackSelection` which is used by all ExoPlayer components.
* DASH:
* Support low-latency DASH playback (`availabilityTimeOffset` and
`ServiceDescription` tags)
@ -181,12 +202,24 @@
Widevine or Clearkey protected content in a playlist.
* Add `ExoMediaDrm.KeyRequest.getRequestType`
([#7847](https://github.com/google/ExoPlayer/issues/7847)).
* Drop key & provision responses if `DefaultDrmSession` is released while
waiting for the response. This fixes (harmless) `IllegalStateException:
sending message to a Handler on a dead thread` log messages
([#8328](https://github.com/google/ExoPlayer/issues/8328)).
* Allow apps to fully customize DRM behaviour per-`MediaItem` by passing a
`DrmSessionManagerProvider` to `MediaSourceFactory`
([#8466](https://github.com/google/ExoPlayer/issues/8466)).
* Analytics:
* Pass a `DecoderReuseEvaluation` to `AnalyticsListener`'s
`onVideoInputFormatChanged` and `onAudioInputFormatChanged` methods. The
`DecoderReuseEvaluation` indicates whether it was possible to re-use an
existing decoder instance for the new format, and if not then the
reasons why.
* Video:
* Fix VP9 format capability checks on API level 23 and earlier. The
platform does not correctly report the VP9 level supported by the
decoder in this case, so we estimate it based on the decoder's maximum
supported bitrate.
* Audio:
* Fix handling of audio session IDs
([#8190](https://github.com/google/ExoPlayer/issues/8190)).
@ -199,10 +232,13 @@
`onAudioSessionIdChanged` is called in fewer cases than
`onAudioSessionId` was called, due to the improved handling of audio
session IDs as described above.
* Retry playback after some types of `AudioTrack` error.
* Create E-AC3 JOC passthrough `AudioTrack`s using the maximum supported
channel count (instead of assuming 6 channels) from API 29.
* Text:
* Gracefully handle null-terminated subtitle content in Matroska
containers.
* Fix CEA-708 anchor positioning
* Fix CEA-708 sequence number discontinuity handling
([#1807](https://github.com/google/ExoPlayer/issues/1807)).
* Fix CEA-708 handling of unexpectedly small packets
([#1807](https://github.com/google/ExoPlayer/issues/1807)).
* Data sources:
* Use the user agent of the underlying network stack by default.
@ -217,9 +253,17 @@
ad view group
([#7344](https://github.com/google/ExoPlayer/issues/7344)),
([#8339](https://github.com/google/ExoPlayer/issues/8339)).
* Fix a bug that could cause the next content position played after a
seek to snap back to the cue point of the preceding ad, rather than
the requested content position.
* Fix a bug that could cause the next content position played after a seek
to snap back to the cue point of the preceding ad, rather than the
requested content position.
* Fix a regression that caused an ad group to be skipped after an initial
seek to a non-zero position. Unsupported VPAID ads will still be
skipped but only after the preload timeout rather than instantly
([#8428](https://github.com/google/ExoPlayer/issues/8428)),
([#7832](https://github.com/google/ExoPlayer/issues/7832)).
* Fix a regression that caused a short ad followed by another ad to be
skipped due to playback being stuck buffering waiting for the second ad
to load ([#8492](https://github.com/google/ExoPlayer/issues/8492)).
* FFmpeg extension:
* Link the FFmpeg library statically, saving 350KB in binary size on
average.
@ -300,7 +344,6 @@
* Support enabling the previous and next actions individually in
`PlayerNotificationManager`.
* Audio:
* Retry playback after some types of `AudioTrack` error.
* Work around `AudioManager` crashes when calling `getStreamVolume`
([#8191](https://github.com/google/ExoPlayer/issues/8191)).
* Extractors:

View File

@ -13,8 +13,8 @@
// limitations under the License.
project.ext {
// ExoPlayer version and version code.
releaseVersion = '2.12.3'
releaseVersionCode = 2012003
releaseVersion = '2.13.0'
releaseVersionCode = 2013000
minSdkVersion = 16
appTargetSdkVersion = 29
targetSdkVersion = 28 // TODO: Bump once b/143232359 is resolved. Also fix TODOs in UtilTest.
@ -24,7 +24,7 @@ project.ext {
guavaVersion = '27.1-android'
mockitoVersion = '2.28.2'
mockWebServerVersion = '3.12.0'
robolectricVersion = '4.5-alpha-3'
robolectricVersion = '4.5'
checkerframeworkVersion = '3.3.0'
checkerframeworkCompatVersion = '2.5.0'
jsr305Version = '3.0.2'

View File

@ -28,6 +28,7 @@ include modulePrefix + 'library-dash'
include modulePrefix + 'library-extractor'
include modulePrefix + 'library-hls'
include modulePrefix + 'library-smoothstreaming'
include modulePrefix + 'library-transformer'
include modulePrefix + 'library-ui'
include modulePrefix + 'robolectricutils'
include modulePrefix + 'testutils'
@ -56,6 +57,7 @@ project(modulePrefix + 'library-dash').projectDir = new File(rootDir, 'library/d
project(modulePrefix + 'library-extractor').projectDir = new File(rootDir, 'library/extractor')
project(modulePrefix + 'library-hls').projectDir = new File(rootDir, 'library/hls')
project(modulePrefix + 'library-smoothstreaming').projectDir = new File(rootDir, 'library/smoothstreaming')
project(modulePrefix + 'library-transformer').projectDir = new File(rootDir, 'library/transformer')
project(modulePrefix + 'library-ui').projectDir = new File(rootDir, 'library/ui')
project(modulePrefix + 'robolectricutils').projectDir = new File(rootDir, 'robolectricutils')
project(modulePrefix + 'testutils').projectDir = new File(rootDir, 'testutils')

View File

@ -152,7 +152,7 @@ public final class MainActivity extends Activity {
.setUuidAndExoMediaDrmProvider(drmSchemeUuid, FrameworkMediaDrm.DEFAULT_PROVIDER)
.build(drmCallback);
} else {
drmSessionManager = DrmSessionManager.getDummyDrmSessionManager();
drmSessionManager = DrmSessionManager.DRM_UNSUPPORTED;
}
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(this);

View File

@ -197,7 +197,7 @@ public final class MainActivity extends Activity {
.setUuidAndExoMediaDrmProvider(drmSchemeUuid, FrameworkMediaDrm.DEFAULT_PROVIDER)
.build(drmCallback);
} else {
drmSessionManager = DrmSessionManager.getDummyDrmSessionManager();
drmSessionManager = DrmSessionManager.DRM_UNSUPPORTED;
}
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(this);

View File

@ -16,7 +16,7 @@ apply from: "$gradle.ext.exoplayerSettingsDir/common_library_config.gradle"
dependencies {
api 'com.google.android.gms:play-services-cast-framework:18.1.0'
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
implementation project(modulePrefix + 'library-core')
implementation project(modulePrefix + 'library-common')
implementation project(modulePrefix + 'library-ui')
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkCompatVersion

View File

@ -30,11 +30,10 @@ import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.FixedTrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.ListenerSet;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MimeTypes;
@ -138,6 +137,7 @@ public final class CastPlayer extends BasePlayer {
listeners =
new ListenerSet<>(
Looper.getMainLooper(),
Clock.DEFAULT,
Player.Events::new,
(listener, eventFlags) -> listener.onEvents(/* player= */ this, eventFlags));
@ -504,12 +504,6 @@ public final class CastPlayer extends BasePlayer {
}
}
@Override
@Nullable
public TrackSelector getTrackSelector() {
return null;
}
@Override
public void setRepeatMode(@RepeatMode int repeatMode) {
if (remoteMediaClient == null) {
@ -771,7 +765,7 @@ public final class CastPlayer extends BasePlayer {
int rendererIndex = getRendererIndexForTrackType(trackType);
if (isTrackActive(id, activeTrackIds) && rendererIndex != C.INDEX_UNSET
&& trackSelections[rendererIndex] == null) {
trackSelections[rendererIndex] = new FixedTrackSelection(trackGroups[i], 0);
trackSelections[rendererIndex] = new CastTrackSelection(trackGroups[i]);
}
}
TrackGroupArray newTrackGroups = new TrackGroupArray(trackGroups);

View File

@ -0,0 +1,91 @@
/*
* Copyright (C) 2021 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.ext.cast;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.util.Assertions;
/**
* {@link TrackSelection} that only selects the first track of the provided {@link TrackGroup}.
*
* <p>This relies on {@link CastPlayer} track groups only having one track.
*/
/* package */ class CastTrackSelection implements TrackSelection {
private final TrackGroup trackGroup;
/** @param trackGroup The {@link TrackGroup} from which the first track will only be selected. */
public CastTrackSelection(TrackGroup trackGroup) {
this.trackGroup = trackGroup;
}
@Override
public TrackGroup getTrackGroup() {
return trackGroup;
}
@Override
public int length() {
return 1;
}
@Override
public Format getFormat(int index) {
Assertions.checkArgument(index == 0);
return trackGroup.getFormat(0);
}
@Override
public int getIndexInTrackGroup(int index) {
return index == 0 ? 0 : C.INDEX_UNSET;
}
@Override
@SuppressWarnings("ReferenceEquality")
public int indexOf(Format format) {
return format == trackGroup.getFormat(0) ? 0 : C.INDEX_UNSET;
}
@Override
public int indexOf(int indexInTrackGroup) {
return indexInTrackGroup == 0 ? 0 : C.INDEX_UNSET;
}
// Object overrides.
@Override
public int hashCode() {
return System.identityHashCode(trackGroup);
}
// Track groups are compared by identity not value, as distinct groups may have the same value.
@Override
@SuppressWarnings({"ReferenceEquality", "EqualsGetClass"})
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (obj == null || getClass() != obj.getClass()) {
return false;
}
CastTrackSelection other = (CastTrackSelection) obj;
return trackGroup == other.trackGroup;
}
}

View File

@ -0,0 +1,77 @@
/*
* Copyright (C) 2018 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.ext.cast;
import static com.google.common.truth.Truth.assertThat;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.source.TrackGroup;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Test for {@link CastTrackSelection}. */
@RunWith(AndroidJUnit4.class)
public class CastTrackSelectionTest {
private static final TrackGroup TRACK_GROUP =
new TrackGroup(new Format.Builder().build(), new Format.Builder().build());
private static final CastTrackSelection SELECTION = new CastTrackSelection(TRACK_GROUP);
@Test
public void length_isOne() {
assertThat(SELECTION.length()).isEqualTo(1);
}
@Test
public void getTrackGroup_returnsSameGroup() {
assertThat(SELECTION.getTrackGroup()).isSameInstanceAs(TRACK_GROUP);
}
@Test
public void getFormatSelectedTrack_isFirstTrack() {
assertThat(SELECTION.getFormat(0)).isSameInstanceAs(TRACK_GROUP.getFormat(0));
}
@Test
public void getIndexInTrackGroup_ofSelectedTrack_returnsFirstTrack() {
assertThat(SELECTION.getIndexInTrackGroup(0)).isEqualTo(0);
}
@Test
public void getIndexInTrackGroup_onePastTheEnd_returnsIndexUnset() {
assertThat(SELECTION.getIndexInTrackGroup(1)).isEqualTo(C.INDEX_UNSET);
}
@Test
public void indexOf_selectedTrack_returnsFirstTrack() {
assertThat(SELECTION.indexOf(0)).isEqualTo(0);
}
@Test
public void indexOf_onePastTheEnd_returnsIndexUnset() {
assertThat(SELECTION.indexOf(1)).isEqualTo(C.INDEX_UNSET);
}
@Test(expected = Exception.class)
public void getFormat_outOfBound_throws() {
CastTrackSelection selection = new CastTrackSelection(TRACK_GROUP);
selection.getFormat(1);
}
}

View File

@ -13,12 +13,25 @@
// limitations under the License.
apply from: "$gradle.ext.exoplayerSettingsDir/common_library_config.gradle"
android {
defaultConfig {
multiDexEnabled true
}
}
dependencies {
api "com.google.android.gms:play-services-cronet:17.0.0"
implementation project(modulePrefix + 'library-core')
implementation project(modulePrefix + 'library-common')
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
androidTestImplementation 'androidx.test:rules:' + androidxTestRulesVersion
androidTestImplementation 'androidx.test:runner:' + androidxTestRunnerVersion
androidTestImplementation 'androidx.multidex:multidex:' + androidxMultidexVersion
// Emulator tests assume that an app-packaged version of cronet is
// available.
androidTestImplementation 'org.chromium.net:cronet-embedded:76.3809.111'
androidTestImplementation(project(modulePrefix + 'testutils'))
testImplementation project(modulePrefix + 'library')
testImplementation project(modulePrefix + 'testutils')
testImplementation 'com.squareup.okhttp3:mockwebserver:' + mockWebServerVersion

View File

@ -0,0 +1,34 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright (C) 2021 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.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="com.google.android.exoplayer2.ext.cronet">
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-sdk/>
<application
android:allowBackup="false"
android:usesCleartextTraffic="true"
tools:ignore="MissingApplicationIcon,HardcodedDebugMode"/>
<instrumentation
android:targetPackage="com.google.android.exoplayer2.ext.cronet"
android:name="androidx.test.runner.AndroidJUnitRunner"/>
</manifest>

View File

@ -0,0 +1,92 @@
/*
* Copyright (C) 2020 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.ext.cronet;
import static com.google.common.truth.Truth.assertThat;
import android.net.Uri;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.testutil.DataSourceContractTest;
import com.google.android.exoplayer2.testutil.HttpDataSourceTestEnv;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.common.collect.ImmutableList;
import java.util.Map;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.junit.After;
import org.junit.Rule;
import org.junit.runner.RunWith;
/** {@link DataSource} contract tests for {@link CronetDataSource}. */
@RunWith(AndroidJUnit4.class)
public class CronetDataSourceContractTest extends DataSourceContractTest {
@Rule public HttpDataSourceTestEnv httpDataSourceTestEnv = new HttpDataSourceTestEnv();
private final ExecutorService executorService = Executors.newSingleThreadExecutor();
@After
public void tearDown() {
executorService.shutdown();
}
@Override
protected DataSource createDataSource() {
CronetEngineWrapper cronetEngineWrapper =
new CronetEngineWrapper(
ApplicationProvider.getApplicationContext(),
/* userAgent= */ "test-agent",
/* preferGMSCoreCronet= */ false);
assertThat(cronetEngineWrapper.getCronetEngineSource())
.isEqualTo(CronetEngineWrapper.SOURCE_NATIVE);
return new CronetDataSource.Factory(cronetEngineWrapper, executorService)
.setFallbackFactory(new InvalidDataSourceFactory())
.createDataSource();
}
@Override
protected ImmutableList<TestResource> getTestResources() {
return httpDataSourceTestEnv.getServedResources();
}
@Override
protected Uri getNotFoundUri() {
return Uri.parse(httpDataSourceTestEnv.getNonexistentUrl());
}
/**
* An {@link HttpDataSource.Factory} that throws {@link UnsupportedOperationException} on every
* interaction.
*/
private static class InvalidDataSourceFactory implements HttpDataSource.Factory {
@Override
public HttpDataSource createDataSource() {
throw new UnsupportedOperationException();
}
@Override
public HttpDataSource.RequestProperties getDefaultRequestProperties() {
throw new UnsupportedOperationException();
}
@Override
public HttpDataSource.Factory setDefaultRequestProperties(
Map<String, String> defaultRequestProperties) {
throw new UnsupportedOperationException();
}
}
}

View File

@ -17,7 +17,6 @@ package com.google.android.exoplayer2.ext.cronet;
import static com.google.android.exoplayer2.util.Util.castNonNull;
import static java.lang.Math.max;
import static java.lang.Math.min;
import android.net.Uri;
import android.text.TextUtils;
@ -37,6 +36,7 @@ import com.google.android.exoplayer2.util.ConditionVariable;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util;
import com.google.common.base.Predicate;
import com.google.common.primitives.Ints;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.SocketTimeoutException;
@ -655,14 +655,21 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
readBuffer.flip();
Assertions.checkState(readBuffer.hasRemaining());
if (bytesToSkip > 0) {
int bytesSkipped = (int) min(readBuffer.remaining(), bytesToSkip);
int bytesSkipped = (int) Math.min(readBuffer.remaining(), bytesToSkip);
readBuffer.position(readBuffer.position() + bytesSkipped);
bytesToSkip -= bytesSkipped;
}
}
}
int bytesRead = min(readBuffer.remaining(), readLength);
// Ensure we read up to bytesRemaining, in case this was a Range request with finite end, but
// the server does not support Range requests and transmitted the entire resource.
int bytesRead =
Ints.min(
bytesRemaining != C.LENGTH_UNSET ? (int) bytesRemaining : Integer.MAX_VALUE,
readBuffer.remaining(),
readLength);
readBuffer.get(buffer, offset, bytesRead);
if (bytesRemaining != C.LENGTH_UNSET) {
@ -1039,7 +1046,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
// Copy as much as possible from the src buffer into dst buffer.
// Returns the number of bytes copied.
private static int copyByteBuffer(ByteBuffer src, ByteBuffer dst) {
int remaining = min(src.remaining(), dst.remaining());
int remaining = Math.min(src.remaining(), dst.remaining());
int limit = src.limit();
src.limit(src.position() + remaining);
dst.put(src);

View File

@ -17,7 +17,7 @@ package com.google.android.exoplayer2.ext.cronet;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer2.upstream.HttpDataSource;
import com.google.android.exoplayer2.upstream.HttpDataSource.BaseFactory;
import com.google.android.exoplayer2.upstream.TransferListener;
@ -25,8 +25,6 @@ import java.util.concurrent.Executor;
import org.chromium.net.CronetEngine;
/** @deprecated Use {@link CronetDataSource.Factory} instead. */
// Uses deprecated DefaultHttpDataSourceFactory
@SuppressWarnings("deprecation")
@Deprecated
public final class CronetDataSourceFactory extends BaseFactory {
@ -82,7 +80,7 @@ public final class CronetDataSourceFactory extends BaseFactory {
* Creates an instance.
*
* <p>If the {@link CronetEngineWrapper} fails to provide a {@link CronetEngine}, a {@link
* DefaultHttpDataSourceFactory} will be used instead.
* DefaultHttpDataSource.Factory} will be used instead.
*
* <p>Sets {@link CronetDataSource#DEFAULT_CONNECT_TIMEOUT_MILLIS} as the connection timeout,
* {@link CronetDataSource#DEFAULT_READ_TIMEOUT_MILLIS} as the read timeout.
@ -98,7 +96,7 @@ public final class CronetDataSourceFactory extends BaseFactory {
* Creates an instance.
*
* <p>If the {@link CronetEngineWrapper} fails to provide a {@link CronetEngine}, a {@link
* DefaultHttpDataSourceFactory} will be used instead.
* DefaultHttpDataSource.Factory} will be used instead.
*
* <p>Sets {@link CronetDataSource#DEFAULT_CONNECT_TIMEOUT_MILLIS} as the connection timeout,
* {@link CronetDataSource#DEFAULT_READ_TIMEOUT_MILLIS} as the read timeout.
@ -118,19 +116,14 @@ public final class CronetDataSourceFactory extends BaseFactory {
DEFAULT_CONNECT_TIMEOUT_MILLIS,
DEFAULT_READ_TIMEOUT_MILLIS,
false,
new DefaultHttpDataSourceFactory(
userAgent,
/* listener= */ null,
DEFAULT_CONNECT_TIMEOUT_MILLIS,
DEFAULT_READ_TIMEOUT_MILLIS,
false));
new DefaultHttpDataSource.Factory().setUserAgent(userAgent));
}
/**
* Creates an instance.
*
* <p>If the {@link CronetEngineWrapper} fails to provide a {@link CronetEngine}, a {@link
* DefaultHttpDataSourceFactory} will be used instead.
* DefaultHttpDataSource.Factory} will be used instead.
*
* @param cronetEngineWrapper A {@link CronetEngineWrapper}.
* @param executor The {@link java.util.concurrent.Executor} that will perform the requests.
@ -152,15 +145,13 @@ public final class CronetDataSourceFactory extends BaseFactory {
cronetEngineWrapper,
executor,
/* transferListener= */ null,
DEFAULT_CONNECT_TIMEOUT_MILLIS,
DEFAULT_READ_TIMEOUT_MILLIS,
connectTimeoutMs,
readTimeoutMs,
resetTimeoutOnRedirects,
new DefaultHttpDataSourceFactory(
userAgent,
/* listener= */ null,
connectTimeoutMs,
readTimeoutMs,
resetTimeoutOnRedirects));
new DefaultHttpDataSource.Factory()
.setUserAgent(userAgent)
.setConnectTimeoutMs(connectTimeoutMs)
.setReadTimeoutMs(readTimeoutMs));
}
/**
@ -228,7 +219,7 @@ public final class CronetDataSourceFactory extends BaseFactory {
* Creates an instance.
*
* <p>If the {@link CronetEngineWrapper} fails to provide a {@link CronetEngine}, a {@link
* DefaultHttpDataSourceFactory} will be used instead.
* DefaultHttpDataSource.Factory} will be used instead.
*
* <p>Sets {@link CronetDataSource#DEFAULT_CONNECT_TIMEOUT_MILLIS} as the connection timeout,
* {@link CronetDataSource#DEFAULT_READ_TIMEOUT_MILLIS} as the read timeout.
@ -248,7 +239,7 @@ public final class CronetDataSourceFactory extends BaseFactory {
* Creates an instance.
*
* <p>If the {@link CronetEngineWrapper} fails to provide a {@link CronetEngine}, a {@link
* DefaultHttpDataSourceFactory} will be used instead.
* DefaultHttpDataSource.Factory} will be used instead.
*
* <p>Sets {@link CronetDataSource#DEFAULT_CONNECT_TIMEOUT_MILLIS} as the connection timeout,
* {@link CronetDataSource#DEFAULT_READ_TIMEOUT_MILLIS} as the read timeout.
@ -272,19 +263,16 @@ public final class CronetDataSourceFactory extends BaseFactory {
DEFAULT_CONNECT_TIMEOUT_MILLIS,
DEFAULT_READ_TIMEOUT_MILLIS,
false,
new DefaultHttpDataSourceFactory(
userAgent,
transferListener,
DEFAULT_CONNECT_TIMEOUT_MILLIS,
DEFAULT_READ_TIMEOUT_MILLIS,
false));
new DefaultHttpDataSource.Factory()
.setUserAgent(userAgent)
.setTransferListener(transferListener));
}
/**
* Creates an instance.
*
* <p>If the {@link CronetEngineWrapper} fails to provide a {@link CronetEngine}, a {@link
* DefaultHttpDataSourceFactory} will be used instead.
* DefaultHttpDataSource.Factory} will be used instead.
*
* @param cronetEngineWrapper A {@link CronetEngineWrapper}.
* @param executor The {@link java.util.concurrent.Executor} that will perform the requests.
@ -308,11 +296,14 @@ public final class CronetDataSourceFactory extends BaseFactory {
cronetEngineWrapper,
executor,
transferListener,
DEFAULT_CONNECT_TIMEOUT_MILLIS,
DEFAULT_READ_TIMEOUT_MILLIS,
connectTimeoutMs,
readTimeoutMs,
resetTimeoutOnRedirects,
new DefaultHttpDataSourceFactory(
userAgent, transferListener, connectTimeoutMs, readTimeoutMs, resetTimeoutOnRedirects));
new DefaultHttpDataSource.Factory()
.setUserAgent(userAgent)
.setTransferListener(transferListener)
.setConnectTimeoutMs(connectTimeoutMs)
.setReadTimeoutMs(readTimeoutMs));
}
/**

View File

@ -451,25 +451,10 @@ import java.util.Map;
return;
}
if (playbackState == Player.STATE_BUFFERING && !player.isPlayingAd()) {
// Check whether we are waiting for an ad to preload.
int adGroupIndex = getLoadingAdGroupIndex();
if (adGroupIndex == C.INDEX_UNSET) {
return;
}
AdPlaybackState.AdGroup adGroup = adPlaybackState.adGroups[adGroupIndex];
if (adGroup.count != C.LENGTH_UNSET
&& adGroup.count != 0
&& adGroup.states[0] != AdPlaybackState.AD_STATE_UNAVAILABLE) {
// An ad is available already so we must be buffering for some other reason.
return;
}
long adGroupTimeMs = C.usToMs(adPlaybackState.adGroupTimesUs[adGroupIndex]);
long contentPositionMs = getContentPeriodPositionMs(player, timeline, period);
long timeUntilAdMs = adGroupTimeMs - contentPositionMs;
if (timeUntilAdMs < configuration.adPreloadTimeoutMs) {
waitingForPreloadElapsedRealtimeMs = SystemClock.elapsedRealtime();
}
if (playbackState == Player.STATE_BUFFERING
&& !player.isPlayingAd()
&& isWaitingForAdToLoad()) {
waitingForPreloadElapsedRealtimeMs = SystemClock.elapsedRealtime();
} else if (playbackState == Player.STATE_READY) {
waitingForPreloadElapsedRealtimeMs = C.TIME_UNSET;
}
@ -759,27 +744,35 @@ import java.util.Map;
if (imaAdInfo != null) {
adPlaybackState = adPlaybackState.withSkippedAdGroup(imaAdInfo.adGroupIndex);
updateAdPlaybackState();
} else {
// Mark any ads for the current/reported player position that haven't loaded as being in the
// error state, to force resuming content. This includes VPAID ads that never load.
long playerPositionUs;
if (player != null) {
playerPositionUs = C.msToUs(getContentPeriodPositionMs(player, timeline, period));
} else if (!VideoProgressUpdate.VIDEO_TIME_NOT_READY.equals(lastContentProgress)) {
// Playback is backgrounded so use the last reported content position.
playerPositionUs = C.msToUs(lastContentProgress.getCurrentTimeMs());
} else {
return;
}
int adGroupIndex =
adPlaybackState.getAdGroupIndexForPositionUs(
playerPositionUs, C.msToUs(contentDurationMs));
if (adGroupIndex != C.INDEX_UNSET) {
markAdGroupInErrorStateAndClearPendingContentPosition(adGroupIndex);
}
}
}
/**
* Returns whether this instance is expecting the first ad in an the upcoming ad group to load
* within the {@link ImaUtil.Configuration#adPreloadTimeoutMs preload timeout}.
*/
private boolean isWaitingForAdToLoad() {
@Nullable Player player = this.player;
if (player == null) {
return false;
}
int adGroupIndex = getLoadingAdGroupIndex();
if (adGroupIndex == C.INDEX_UNSET) {
return false;
}
AdPlaybackState.AdGroup adGroup = adPlaybackState.adGroups[adGroupIndex];
if (adGroup.count != C.LENGTH_UNSET
&& adGroup.count != 0
&& adGroup.states[0] != AdPlaybackState.AD_STATE_UNAVAILABLE) {
// An ad is available already.
return false;
}
long adGroupTimeMs = C.usToMs(adPlaybackState.adGroupTimesUs[adGroupIndex]);
long contentPositionMs = getContentPeriodPositionMs(player, timeline, period);
long timeUntilAdMs = adGroupTimeMs - contentPositionMs;
return timeUntilAdMs < configuration.adPreloadTimeoutMs;
}
private void handlePlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) {
if (playingAd && imaAdState == IMA_AD_STATE_PLAYING) {
if (!bufferingAd && playbackState == Player.STATE_BUFFERING) {
@ -1305,6 +1298,12 @@ import java.util.Map;
handleAdGroupLoadError(new IOException("Ad preloading timed out"));
maybeNotifyPendingAdLoadError();
}
} else if (pendingContentPositionMs != C.TIME_UNSET
&& player != null
&& player.getPlaybackState() == Player.STATE_BUFFERING
&& isWaitingForAdToLoad()) {
// Prepare to timeout the load of an ad for the pending seek operation.
waitingForPreloadElapsedRealtimeMs = SystemClock.elapsedRealtime();
}
return videoProgressUpdate;

View File

@ -60,7 +60,6 @@ import java.util.Collection;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Locale;
import java.util.Set;
/**
@ -700,7 +699,7 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
@Override
public ImaSdkSettings createImaSdkSettings() {
ImaSdkSettings settings = ImaSdkFactory.getInstance().createImaSdkSettings();
settings.setLanguage(getImaLanguageCodeForDefaultLocale());
settings.setLanguage(Util.getSystemLanguageCodes()[0]);
return settings;
}
@ -742,17 +741,5 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
return ImaSdkFactory.getInstance()
.createAdsLoader(context, imaSdkSettings, adDisplayContainer);
}
/**
* Returns a language code that's suitable for passing to {@link ImaSdkSettings#setLanguage} and
* corresponds to the device's {@link Locale#getDefault() default Locale}. IMA will fall back to
* its default language code ("en") if the value returned is unsupported.
*/
// TODO: It may be possible to define a better mapping onto IMA's supported language codes. See:
// https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/localization.
// [Internal ref: b/174042000] will help if implemented.
private static String getImaLanguageCodeForDefaultLocale() {
return Util.splitAtFirst(Util.getSystemLanguageCodes()[0], "-")[0];
}
}
}

View File

@ -21,6 +21,7 @@ import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.testutil.StubExoPlayer;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.ListenerSet;
/** A fake player for testing content/ad playback. */
@ -43,6 +44,7 @@ import com.google.android.exoplayer2.util.ListenerSet;
listeners =
new ListenerSet<>(
Looper.getMainLooper(),
Clock.DEFAULT,
Player.Events::new,
(listener, eventFlags) -> listener.onEvents(/* player= */ this, eventFlags));
period = new Timeline.Period();

View File

@ -450,6 +450,62 @@ public final class ImaAdsLoaderTest {
.withAdLoadError(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0));
}
@Test
public void startPlaybackAfterMidroll_doesNotSkipMidroll() {
// Simulate an ad at 2 seconds, and starting playback with an initial seek position at the ad.
long adGroupPositionInWindowUs = 2 * C.MICROS_PER_SECOND;
long adGroupTimeUs =
adGroupPositionInWindowUs
+ TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
ImmutableList<Float> cuePoints = ImmutableList.of((float) adGroupTimeUs / C.MICROS_PER_SECOND);
when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints);
fakePlayer.setState(Player.STATE_BUFFERING, /* playWhenReady= */ true);
fakePlayer.setPlayingContentPosition(/* periodIndex= */ 0, C.usToMs(adGroupPositionInWindowUs));
// Start ad loading while still buffering and simulate the calls from the IMA SDK to resume then
// immediately pause content playback.
imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
contentProgressProvider.getContentProgress();
adEventListener.onAdEvent(getAdEvent(AdEventType.CONTENT_RESUME_REQUESTED, /* ad= */ null));
contentProgressProvider.getContentProgress();
adEventListener.onAdEvent(getAdEvent(AdEventType.CONTENT_PAUSE_REQUESTED, /* ad= */ null));
contentProgressProvider.getContentProgress();
assertThat(getAdPlaybackState(/* periodIndex= */ 0))
.isEqualTo(
new AdPlaybackState(TEST_ADS_ID, getAdGroupTimesUsForCuePoints(cuePoints))
.withContentDurationUs(CONTENT_PERIOD_DURATION_US));
}
@Test
public void startPlaybackAfterMidroll_withAdNotPreloadingAfterTimeout_hasErrorAdGroup() {
// Simulate an ad at 2 seconds, and starting playback with an initial seek position at the ad.
long adGroupPositionInWindowUs = 2 * C.MICROS_PER_SECOND;
long adGroupTimeUs =
adGroupPositionInWindowUs
+ TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
ImmutableList<Float> cuePoints = ImmutableList.of((float) adGroupTimeUs / C.MICROS_PER_SECOND);
when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints);
fakePlayer.setState(Player.STATE_BUFFERING, /* playWhenReady= */ true);
fakePlayer.setPlayingContentPosition(/* periodIndex= */ 0, C.usToMs(adGroupPositionInWindowUs));
// Start ad loading while still buffering and poll progress without the ad loading.
imaAdsLoader.start(
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
contentProgressProvider.getContentProgress();
ShadowSystemClock.advanceBy(Duration.ofSeconds(5));
contentProgressProvider.getContentProgress();
assertThat(getAdPlaybackState(/* periodIndex= */ 0))
.isEqualTo(
new AdPlaybackState(TEST_ADS_ID, getAdGroupTimesUsForCuePoints(cuePoints))
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
.withAdDurationsUs(new long[][] {{TEST_AD_DURATION_US}})
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
.withAdLoadError(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0));
}
@Test
public void bufferingDuringAd_callsOnBuffering() {
// Load the preroll ad.

View File

@ -14,7 +14,7 @@
apply from: "$gradle.ext.exoplayerSettingsDir/common_library_config.gradle"
dependencies {
implementation project(modulePrefix + 'library-core')
implementation project(modulePrefix + 'library-common')
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion

View File

@ -0,0 +1,48 @@
/*
* Copyright (C) 2020 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.ext.okhttp;
import android.net.Uri;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.testutil.DataSourceContractTest;
import com.google.android.exoplayer2.testutil.HttpDataSourceTestEnv;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.common.collect.ImmutableList;
import okhttp3.OkHttpClient;
import org.junit.Rule;
import org.junit.runner.RunWith;
/** {@link DataSource} contract tests for {@link OkHttpDataSource}. */
@RunWith(AndroidJUnit4.class)
public class OkHttpDataSourceContractTest extends DataSourceContractTest {
@Rule public HttpDataSourceTestEnv httpDataSourceTestEnv = new HttpDataSourceTestEnv();
@Override
protected DataSource createDataSource() {
return new OkHttpDataSource.Factory(new OkHttpClient()).createDataSource();
}
@Override
protected ImmutableList<TestResource> getTestResources() {
return httpDataSourceTestEnv.getServedResources();
}
@Override
protected Uri getNotFoundUri() {
return Uri.parse(httpDataSourceTestEnv.getNonexistentUrl());
}
}

View File

@ -14,10 +14,11 @@
apply from: "$gradle.ext.exoplayerSettingsDir/common_library_config.gradle"
dependencies {
implementation project(modulePrefix + 'library-core')
implementation project(modulePrefix + 'library-common')
implementation 'net.butterflytv.utils:rtmp-client:3.1.0'
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
testImplementation project(modulePrefix + 'library-core')
testImplementation project(modulePrefix + 'testutils')
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
}

View File

@ -35,7 +35,9 @@ dependencies {
testImplementation 'androidx.test.ext:junit:' + androidxTestJUnitVersion
testImplementation 'junit:junit:' + junitVersion
testImplementation 'com.google.truth:truth:' + truthVersion
testImplementation 'com.squareup.okhttp3:mockwebserver:' + mockWebServerVersion
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
testImplementation project(modulePrefix + 'testutils')
}
ext {

View File

@ -1080,23 +1080,6 @@ public final class C {
/** Indicates the track is intended for trick play. */
public static final int ROLE_FLAG_TRICK_PLAY = 1 << 14;
// TODO(b/172315872) Move usage back to Player.RepeatMode when Player is moved in common.
/**
* Repeat modes for playback. One of {@link #REPEAT_MODE_OFF}, {@link #REPEAT_MODE_ONE} or {@link
* #REPEAT_MODE_ALL}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@IntDef({REPEAT_MODE_OFF, REPEAT_MODE_ONE, REPEAT_MODE_ALL})
static @interface RepeatMode {}
/** Normal playback without repetition. */
/* package */ static final int REPEAT_MODE_OFF = 0;
/** "Repeat One" mode to repeat the currently playing window infinitely. */
/* package */ static final int REPEAT_MODE_ONE = 1;
/** "Repeat All" mode to repeat the entire timeline infinitely. */
/* package */ static final int REPEAT_MODE_ALL = 2;
/**
* Level of renderer support for a format. One of {@link #FORMAT_HANDLED}, {@link
* #FORMAT_EXCEEDS_CAPABILITIES}, {@link #FORMAT_UNSUPPORTED_DRM}, {@link

View File

@ -30,11 +30,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.12.3";
public static final String VERSION = "2.13.0";
/** 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.12.3";
public static final String VERSION_SLASHY = "ExoPlayerLib/2.13.0";
/**
* The version of the library expressed as an integer, for example 1002003.
@ -44,7 +44,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 = 2012003;
public static final int VERSION_INT = 2013000;
/**
* The default user agent for requests made by the library.

View File

@ -34,7 +34,6 @@ import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.text.Cue;
import com.google.android.exoplayer2.text.TextOutput;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.TrackSelectorInterface;
import com.google.android.exoplayer2.util.MutableFlags;
import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.VideoFrameMetadataListener;
@ -618,11 +617,14 @@ public interface Player {
default void onSeekProcessed() {}
/**
* Called when the player has started or stopped offload scheduling after a call to {@link
* Called when the player has started or stopped offload scheduling.
*
* <p>If using ExoPlayer, this is done by calling {@code
* ExoPlayer#experimentalSetOffloadSchedulingEnabled(boolean)}.
*
* <p>This method is experimental, and will be renamed or removed in a future release.
*/
// TODO(b/172315872) Move this method in a new ExoPlayer.EventListener.
default void onExperimentalOffloadSchedulingEnabledChanged(boolean offloadSchedulingEnabled) {}
/**
@ -740,9 +742,7 @@ public interface Player {
@Retention(RetentionPolicy.SOURCE)
@IntDef({STATE_IDLE, STATE_BUFFERING, STATE_READY, STATE_ENDED})
@interface State {}
/**
* The player does not have any media to play.
*/
/** The player does not have any media to play. */
int STATE_IDLE = 1;
/**
* The player is not able to immediately play from its current position. This state typically
@ -754,9 +754,7 @@ public interface Player {
* {@link #getPlayWhenReady()} is true, and paused otherwise.
*/
int STATE_READY = 3;
/**
* The player has finished playing the media.
*/
/** The player has finished playing the media. */
int STATE_ENDED = 4;
/**
@ -817,20 +815,20 @@ public interface Player {
* Normal playback without repetition. "Previous" and "Next" actions move to the previous and next
* windows respectively, and do nothing when there is no previous or next window to move to.
*/
int REPEAT_MODE_OFF = C.REPEAT_MODE_OFF;
int REPEAT_MODE_OFF = 0;
/**
* Repeats the currently playing window infinitely during ongoing playback. "Previous" and "Next"
* actions behave as they do in {@link #REPEAT_MODE_OFF}, moving to the previous and next windows
* respectively, and doing nothing when there is no previous or next window to move to.
*/
int REPEAT_MODE_ONE = C.REPEAT_MODE_ONE;
int REPEAT_MODE_ONE = 1;
/**
* Repeats the entire timeline infinitely. "Previous" and "Next" actions behave as they do in
* {@link #REPEAT_MODE_OFF}, but with looping at the ends so that "Previous" when playing the
* first window will move to the last window, and "Next" when playing the last window will move to
* the first window.
*/
int REPEAT_MODE_ALL = C.REPEAT_MODE_ALL;
int REPEAT_MODE_ALL = 2;
/**
* Reasons for position discontinuities. One of {@link #DISCONTINUITY_REASON_PERIOD_TRANSITION},
@ -1218,7 +1216,8 @@ public interface Player {
*
* @return The current repeat mode.
*/
@RepeatMode int getRepeatMode();
@RepeatMode
int getRepeatMode();
/**
* Sets whether shuffling of windows is enabled.
@ -1227,9 +1226,7 @@ public interface Player {
*/
void setShuffleModeEnabled(boolean shuffleModeEnabled);
/**
* Returns whether shuffling of windows is enabled.
*/
/** Returns whether shuffling of windows is enabled. */
boolean getShuffleModeEnabled();
/**
@ -1364,9 +1361,7 @@ public interface Player {
*/
void release();
/**
* Returns the number of renderers.
*/
/** Returns the number of renderers. */
int getRendererCount();
/**
@ -1380,12 +1375,6 @@ public interface Player {
*/
int getRendererType(int index);
/**
* Returns the track selector that this player uses, or null if track selection is not supported.
*/
@Nullable
TrackSelectorInterface getTrackSelector();
/** Returns the available track groups. */
TrackGroupArray getCurrentTrackGroups();
@ -1411,14 +1400,10 @@ public interface Player {
@Nullable
Object getCurrentManifest();
/**
* Returns the current {@link Timeline}. Never null, but may be empty.
*/
/** Returns the current {@link Timeline}. Never null, but may be empty. */
Timeline getCurrentTimeline();
/**
* Returns the index of the period currently being played.
*/
/** Returns the index of the period currently being played. */
int getCurrentPeriodIndex();
/**
@ -1538,9 +1523,7 @@ public interface Player {
*/
boolean isCurrentWindowSeekable();
/**
* Returns whether the player is currently playing an ad.
*/
/** Returns whether the player is currently playing an ad. */
boolean isPlayingAd();
/**

View File

@ -734,14 +734,14 @@ public abstract class Timeline {
* @return The index of the next window, or {@link C#INDEX_UNSET} if this is the last window.
*/
public int getNextWindowIndex(
int windowIndex, @C.RepeatMode int repeatMode, boolean shuffleModeEnabled) {
int windowIndex, @Player.RepeatMode int repeatMode, boolean shuffleModeEnabled) {
switch (repeatMode) {
case C.REPEAT_MODE_OFF:
case Player.REPEAT_MODE_OFF:
return windowIndex == getLastWindowIndex(shuffleModeEnabled) ? C.INDEX_UNSET
: windowIndex + 1;
case C.REPEAT_MODE_ONE:
case Player.REPEAT_MODE_ONE:
return windowIndex;
case C.REPEAT_MODE_ALL:
case Player.REPEAT_MODE_ALL:
return windowIndex == getLastWindowIndex(shuffleModeEnabled)
? getFirstWindowIndex(shuffleModeEnabled) : windowIndex + 1;
default:
@ -759,14 +759,14 @@ public abstract class Timeline {
* @return The index of the previous window, or {@link C#INDEX_UNSET} if this is the first window.
*/
public int getPreviousWindowIndex(
int windowIndex, @C.RepeatMode int repeatMode, boolean shuffleModeEnabled) {
int windowIndex, @Player.RepeatMode int repeatMode, boolean shuffleModeEnabled) {
switch (repeatMode) {
case C.REPEAT_MODE_OFF:
case Player.REPEAT_MODE_OFF:
return windowIndex == getFirstWindowIndex(shuffleModeEnabled) ? C.INDEX_UNSET
: windowIndex - 1;
case C.REPEAT_MODE_ONE:
case Player.REPEAT_MODE_ONE:
return windowIndex;
case C.REPEAT_MODE_ALL:
case Player.REPEAT_MODE_ALL:
return windowIndex == getFirstWindowIndex(shuffleModeEnabled)
? getLastWindowIndex(shuffleModeEnabled) : windowIndex - 1;
default:
@ -847,7 +847,7 @@ public abstract class Timeline {
int periodIndex,
Period period,
Window window,
@C.RepeatMode int repeatMode,
@Player.RepeatMode int repeatMode,
boolean shuffleModeEnabled) {
int windowIndex = getPeriod(periodIndex, period).windowIndex;
if (getWindow(windowIndex, window).lastPeriodIndex == periodIndex) {
@ -875,7 +875,7 @@ public abstract class Timeline {
int periodIndex,
Period period,
Window window,
@C.RepeatMode int repeatMode,
@Player.RepeatMode int repeatMode,
boolean shuffleModeEnabled) {
return getNextPeriodIndex(periodIndex, period, window, repeatMode, shuffleModeEnabled)
== C.INDEX_UNSET;

View File

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.audio;
import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.C;
@ -33,6 +34,11 @@ import com.google.android.exoplayer2.util.Util;
*/
public final class AudioAttributes {
private static final String FIELD_CONTENT_TYPE = "contentType";
private static final String FIELD_FLAGS = "flags";
private static final String FIELD_USAGE = "usage";
private static final String FIELD_ALLOWED_CAPTURE_POLICY = "allowedCapturePolicy";
public static final AudioAttributes DEFAULT = new Builder().build();
/**
@ -159,4 +165,31 @@ public final class AudioAttributes {
return result;
}
/** Converts this instance into a {@link Bundle}. */
public Bundle toBundle() {
Bundle bundle = new Bundle();
bundle.putInt(FIELD_CONTENT_TYPE, contentType);
bundle.putInt(FIELD_FLAGS, flags);
bundle.putInt(FIELD_USAGE, usage);
bundle.putInt(FIELD_ALLOWED_CAPTURE_POLICY, allowedCapturePolicy);
return bundle;
}
/** Creates an {@link AudioAttributes} instance from a {@link Bundle}. */
public static AudioAttributes fromBundle(Bundle bundle) {
Builder builder = new Builder();
if (bundle.containsKey(FIELD_CONTENT_TYPE)) {
builder.setContentType(bundle.getInt(FIELD_CONTENT_TYPE));
}
if (bundle.containsKey(FIELD_FLAGS)) {
builder.setFlags(bundle.getInt(FIELD_FLAGS));
}
if (bundle.containsKey(FIELD_USAGE)) {
builder.setUsage(bundle.getInt(FIELD_USAGE));
}
if (bundle.containsKey(FIELD_ALLOWED_CAPTURE_POLICY)) {
builder.setAllowedCapturePolicy(bundle.getInt(FIELD_ALLOWED_CAPTURE_POLICY));
}
return builder.build();
}
}

View File

@ -15,6 +15,7 @@
*/
package com.google.android.exoplayer2.device;
import android.os.Bundle;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import java.lang.annotation.Documented;
@ -26,6 +27,10 @@ import java.lang.annotation.Target;
/** Information about the playback device. */
public final class DeviceInfo {
private static final String FIELD_PLAYBACK_TYPE = "playbackType";
private static final String FIELD_MIN_VOLUME = "minVolume";
private static final String FIELD_MAX_VOLUME = "maxVolume";
/** Types of playback. One of {@link #PLAYBACK_TYPE_LOCAL} or {@link #PLAYBACK_TYPE_REMOTE}. */
@Documented
@Retention(RetentionPolicy.SOURCE)
@ -80,4 +85,21 @@ public final class DeviceInfo {
result = 31 * result + maxVolume;
return result;
}
/** Converts this instance into a {@link Bundle}. */
public Bundle toBundle() {
Bundle bundle = new Bundle();
bundle.putInt(FIELD_PLAYBACK_TYPE, playbackType);
bundle.putInt(FIELD_MIN_VOLUME, minVolume);
bundle.putInt(FIELD_MAX_VOLUME, maxVolume);
return bundle;
}
/** Creates an {@link DeviceInfo} instance from a {@link Bundle}. */
public static DeviceInfo fromBundle(Bundle bundle) {
int playbackType = bundle.getInt(FIELD_PLAYBACK_TYPE, /* defaultValue= */ PLAYBACK_TYPE_LOCAL);
int minVolume = bundle.getInt(FIELD_MIN_VOLUME, /* defaultValue= */ 0);
int maxVolume = bundle.getInt(FIELD_MAX_VOLUME, /* defaultValue= */ 0);
return new DeviceInfo(playbackType, minVolume, maxVolume);
}
}

View File

@ -15,8 +15,9 @@
*/
package com.google.android.exoplayer2.device;
// TODO(b/172315872) change back to @link after player migration to common.
/** A listener for changes of {@code Player.DeviceComponent}. */
import com.google.android.exoplayer2.Player;
/** A listener for changes of {@link Player.DeviceComponent}. */
public interface DeviceListener {
/** Called when the device information changes. */

View File

@ -15,13 +15,17 @@
*/
package com.google.android.exoplayer2.metadata.mp4;
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.util.Util;
import com.google.common.base.Objects;
import com.google.common.collect.ComparisonChain;
import java.util.ArrayList;
import java.util.Comparator;
import java.util.List;
/** Holds information about the segments of slow motion playback within a track. */
@ -30,6 +34,14 @@ public final class SlowMotionData implements Metadata.Entry {
/** Holds information about a single segment of slow motion playback within a track. */
public static final class Segment implements Parcelable {
public static final Comparator<Segment> BY_START_THEN_END_THEN_DIVISOR =
(s1, s2) ->
ComparisonChain.start()
.compare(s1.startTimeMs, s2.startTimeMs)
.compare(s1.endTimeMs, s2.endTimeMs)
.compare(s1.speedDivisor, s2.speedDivisor)
.result();
/** The start time, in milliseconds, of the track segment that is intended to be slow motion. */
public final long startTimeMs;
/** The end time, in milliseconds, of the track segment that is intended to be slow motion. */
@ -45,11 +57,12 @@ public final class SlowMotionData implements Metadata.Entry {
/**
* Creates an instance.
*
* @param startTimeMs See {@link #startTimeMs}.
* @param startTimeMs See {@link #startTimeMs}. Must be less than endTimeMs.
* @param endTimeMs See {@link #endTimeMs}.
* @param speedDivisor See {@link #speedDivisor}.
*/
public Segment(long startTimeMs, long endTimeMs, int speedDivisor) {
checkArgument(startTimeMs < endTimeMs);
this.startTimeMs = startTimeMs;
this.endTimeMs = endTimeMs;
this.speedDivisor = speedDivisor;
@ -113,9 +126,15 @@ public final class SlowMotionData implements Metadata.Entry {
public final List<Segment> segments;
/** Creates an instance with a list of {@link Segment}s. */
/**
* Creates an instance with a list of {@link Segment}s.
*
* <p>The segments must not overlap, that is that the start time of a segment can not be between
* the start and end time of another segment.
*/
public SlowMotionData(List<Segment> segments) {
this.segments = segments;
checkArgument(!doSegmentsOverlap(segments));
}
@Override
@ -164,4 +183,19 @@ public final class SlowMotionData implements Metadata.Entry {
return new SlowMotionData[size];
}
};
private static boolean doSegmentsOverlap(List<Segment> segments) {
if (segments.isEmpty()) {
return false;
}
long previousEndTimeMs = segments.get(0).endTimeMs;
for (int i = 1; i < segments.size(); i++) {
if (segments.get(i).startTimeMs < previousEndTimeMs) {
return true;
}
previousEndTimeMs = segments.get(i).endTimeMs;
}
return false;
}
}

View File

@ -23,20 +23,10 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.util.Assertions;
import java.util.Arrays;
// TODO: Add an allowMultipleStreams boolean to indicate where the one stream per group restriction
// does not apply.
/**
* Defines a group of tracks exposed by a {@link MediaPeriod}.
*
* <p>A {@link MediaPeriod} is only able to provide one {@link SampleStream} corresponding to a
* group at any given time, however this {@link SampleStream} may adapt between multiple tracks
* within the group.
*/
/** Defines an immutable group of tracks identified by their format identity. */
public final class TrackGroup implements Parcelable {
/**
* The number of tracks in the group.
*/
/** The number of tracks in the group. */
public final int length;
private final Format[] formats;
@ -45,7 +35,7 @@ public final class TrackGroup implements Parcelable {
private int hashCode;
/**
* @param formats The track formats. Must not be null, contain null elements or be of length 0.
* @param formats The track formats. At least one {@link Format} must be provided.
*/
public TrackGroup(Format... formats) {
Assertions.checkState(formats.length > 0);

View File

@ -21,17 +21,13 @@ import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import java.util.Arrays;
/** An array of {@link TrackGroup}s exposed by a {@link MediaPeriod}. */
/** An immutable array of {@link TrackGroup}s. */
public final class TrackGroupArray implements Parcelable {
/**
* The empty array.
*/
/** The empty array. */
public static final TrackGroupArray EMPTY = new TrackGroupArray();
/**
* The number of groups in the array. Greater than or equal to zero.
*/
/** The number of groups in the array. Greater than or equal to zero. */
public final int length;
private final TrackGroup[] trackGroups;
@ -39,9 +35,7 @@ public final class TrackGroupArray implements Parcelable {
// Lazily initialized hashcode.
private int hashCode;
/**
* @param trackGroups The groups. Must not be null or contain null elements, but may be empty.
*/
/** @param trackGroups The groups. May be empty. */
public TrackGroupArray(TrackGroup... trackGroups) {
this.trackGroups = trackGroups;
this.length = trackGroups.length;
@ -83,9 +77,7 @@ public final class TrackGroupArray implements Parcelable {
return C.INDEX_UNSET;
}
/**
* Returns whether this track group array is empty.
*/
/** Returns whether this track group array is empty. */
public boolean isEmpty() {
return length == 0;
}

View File

@ -0,0 +1,73 @@
/*
* 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.Format;
import com.google.android.exoplayer2.source.TrackGroup;
/**
* A track selection consisting of a static subset of selected tracks belonging to a {@link
* TrackGroup}.
*
* <p>Tracks belonging to the subset are exposed in decreasing bandwidth order.
*/
public interface TrackSelection {
/** Returns the {@link TrackGroup} to which the selected tracks belong. */
TrackGroup getTrackGroup();
// Static subset of selected tracks.
/** Returns the number of tracks in the selection. */
int length();
/**
* Returns the format of the track at a given index in the selection.
*
* @param index The index in the selection.
* @return The format of the selected track.
*/
Format getFormat(int index);
/**
* Returns the index in the track group of the track at a given index in the selection.
*
* @param index The index in the selection.
* @return The index of the selected track.
*/
int getIndexInTrackGroup(int index);
/**
* Returns the index in the selection of the track with the specified format. The format is
* located by identity so, for example, {@code selection.indexOf(selection.getFormat(index)) ==
* index} even if multiple selected tracks have formats that contain the same values.
*
* @param format The format.
* @return The index in the selection, or {@link C#INDEX_UNSET} if the track with the specified
* format is not part of the selection.
*/
int indexOf(Format format);
/**
* Returns the index in the selection of the track with the specified index in the track group.
*
* @param indexInTrackGroup The index in the track group.
* @return The index in the selection, or {@link C#INDEX_UNSET} if the track with the specified
* index is not part of the selection.
*/
int indexOf(int indexInTrackGroup);
}

View File

@ -73,5 +73,4 @@ public final class TrackSelectionArray {
TrackSelectionArray other = (TrackSelectionArray) obj;
return Arrays.equals(trackSelections, other.trackSelections);
}
}

View File

@ -15,6 +15,8 @@
*/
package com.google.android.exoplayer2.upstream;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import static com.google.android.exoplayer2.util.Util.castNonNull;
import static java.lang.Math.max;
import static java.lang.Math.min;
@ -24,7 +26,6 @@ import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.upstream.DataSpec.HttpMethod;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util;
import com.google.common.base.Predicate;
@ -42,10 +43,10 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.concurrent.atomic.AtomicReference;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.GZIPInputStream;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* An {@link HttpDataSource} that uses Android's {@link HttpURLConnection}.
@ -208,7 +209,6 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
private static final long MAX_BYTES_TO_DRAIN = 2048;
private static final Pattern CONTENT_RANGE_HEADER =
Pattern.compile("^bytes (\\d+)-(\\d+)/(\\d+)$");
private static final AtomicReference<byte[]> skipBufferReference = new AtomicReference<>();
private final boolean allowCrossProtocolRedirects;
private final int connectTimeoutMillis;
@ -221,6 +221,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
@Nullable private DataSpec dataSpec;
@Nullable private HttpURLConnection connection;
@Nullable private InputStream inputStream;
private byte @MonotonicNonNull [] skipBuffer;
private boolean opened;
private int responseCode;
@ -318,14 +319,14 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
@Override
public void setRequestProperty(String name, String value) {
Assertions.checkNotNull(name);
Assertions.checkNotNull(value);
checkNotNull(name);
checkNotNull(value);
requestProperties.set(name, value);
}
@Override
public void clearRequestProperty(String name) {
Assertions.checkNotNull(name);
checkNotNull(name);
requestProperties.remove(name);
}
@ -343,6 +344,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
this.bytesRead = 0;
this.bytesSkipped = 0;
transferInitializing(dataSpec);
try {
connection = makeConnection(dataSpec);
} catch (IOException e) {
@ -355,6 +357,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
"Unable to connect", e, dataSpec, HttpDataSourceException.TYPE_OPEN);
}
HttpURLConnection connection = this.connection;
String responseMessage;
try {
responseCode = connection.getResponseCode();
@ -438,19 +441,22 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
skipInternal();
return readInternal(buffer, offset, readLength);
} catch (IOException e) {
throw new HttpDataSourceException(e, dataSpec, HttpDataSourceException.TYPE_READ);
throw new HttpDataSourceException(
e, castNonNull(dataSpec), HttpDataSourceException.TYPE_READ);
}
}
@Override
public void close() throws HttpDataSourceException {
try {
@Nullable InputStream inputStream = this.inputStream;
if (inputStream != null) {
maybeTerminateInputStream(connection, bytesRemaining());
try {
inputStream.close();
} catch (IOException e) {
throw new HttpDataSourceException(e, dataSpec, HttpDataSourceException.TYPE_CLOSE);
throw new HttpDataSourceException(
e, castNonNull(dataSpec), HttpDataSourceException.TYPE_CLOSE);
}
}
} finally {
@ -694,7 +700,9 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
if (matcher.find()) {
try {
long contentLengthFromRange =
Long.parseLong(matcher.group(2)) - Long.parseLong(matcher.group(1)) + 1;
Long.parseLong(checkNotNull(matcher.group(2)))
- Long.parseLong(checkNotNull(matcher.group(1)))
+ 1;
if (contentLength < 0) {
// Some proxy servers strip the Content-Length header. Fall back to the length
// calculated here in this case.
@ -729,15 +737,13 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
return;
}
// Acquire the shared skip buffer.
byte[] skipBuffer = skipBufferReference.getAndSet(null);
if (skipBuffer == null) {
skipBuffer = new byte[4096];
}
while (bytesSkipped != bytesToSkip) {
int readLength = (int) min(bytesToSkip - bytesSkipped, skipBuffer.length);
int read = inputStream.read(skipBuffer, 0, readLength);
int read = castNonNull(inputStream).read(skipBuffer, 0, readLength);
if (Thread.currentThread().isInterrupted()) {
throw new InterruptedIOException();
}
@ -747,9 +753,6 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
bytesSkipped += read;
bytesTransferred(read);
}
// Release the shared skip buffer.
skipBufferReference.set(skipBuffer);
}
/**
@ -778,7 +781,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
readLength = (int) min(readLength, bytesRemaining);
}
int read = inputStream.read(buffer, offset, readLength);
int read = castNonNull(inputStream).read(buffer, offset, readLength);
if (read == -1) {
if (bytesToRead != C.LENGTH_UNSET) {
// End of stream reached having not read sufficient data.
@ -803,8 +806,9 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
* @param bytesRemaining The number of bytes remaining to be read from the input stream if its
* length is known. {@link C#LENGTH_UNSET} otherwise.
*/
private static void maybeTerminateInputStream(HttpURLConnection connection, long bytesRemaining) {
if (Util.SDK_INT != 19 && Util.SDK_INT != 20) {
private static void maybeTerminateInputStream(
@Nullable HttpURLConnection connection, long bytesRemaining) {
if (connection == null || Util.SDK_INT < 19 || Util.SDK_INT > 20) {
return;
}
@ -825,7 +829,8 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
|| "com.android.okhttp.internal.http.HttpTransport$FixedLengthInputStream"
.equals(className)) {
Class<?> superclass = inputStream.getClass().getSuperclass();
Method unexpectedEndOfInput = superclass.getDeclaredMethod("unexpectedEndOfInput");
Method unexpectedEndOfInput =
checkNotNull(superclass).getDeclaredMethod("unexpectedEndOfInput");
unexpectedEndOfInput.setAccessible(true);
unexpectedEndOfInput.invoke(inputStream);
}
@ -836,7 +841,6 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
}
}
/**
* Closes the current connection quietly, if there is one.
*/

View File

@ -43,9 +43,6 @@ public interface Clock {
/** @see android.os.SystemClock#uptimeMillis() */
long uptimeMillis();
/** @see android.os.SystemClock#sleep(long) */
void sleep(long sleepTimeMs);
/**
* Creates a {@link HandlerWrapper} using a specified looper and a specified callback for handling
* messages.
@ -53,4 +50,12 @@ public interface Clock {
* @see Handler#Handler(Looper, Handler.Callback)
*/
HandlerWrapper createHandler(Looper looper, @Nullable Handler.Callback callback);
/**
* Notifies the clock that the current thread is about to be blocked and won't return until a
* condition on another thread becomes true.
*
* <p>Should be a no-op for all non-test cases.
*/
void onThreadBlocked();
}

View File

@ -17,7 +17,6 @@ package com.google.android.exoplayer2.util;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import androidx.annotation.Nullable;
/**
@ -26,39 +25,55 @@ import androidx.annotation.Nullable;
*/
public interface HandlerWrapper {
/** @see Handler#getLooper() */
/** A message obtained from the handler. */
interface Message {
/** See {@link android.os.Message#sendToTarget()}. */
void sendToTarget();
/** See {@link android.os.Message#getTarget()}. */
HandlerWrapper getTarget();
}
/** See {@link Handler#getLooper()}. */
Looper getLooper();
/** @see Handler#obtainMessage(int) */
/** See {@link Handler#hasMessages(int)}. */
boolean hasMessages(int what);
/** See {@link Handler#obtainMessage(int)}. */
Message obtainMessage(int what);
/** @see Handler#obtainMessage(int, Object) */
/** See {@link Handler#obtainMessage(int, Object)}. */
Message obtainMessage(int what, @Nullable Object obj);
/** @see Handler#obtainMessage(int, int, int) */
/** See {@link Handler#obtainMessage(int, int, int)}. */
Message obtainMessage(int what, int arg1, int arg2);
/** @see Handler#obtainMessage(int, int, int, Object) */
/** See {@link Handler#obtainMessage(int, int, int, Object)}. */
Message obtainMessage(int what, int arg1, int arg2, @Nullable Object obj);
/** @see Handler#sendEmptyMessage(int) */
/** See {@link Handler#sendMessageAtFrontOfQueue(android.os.Message)}. */
boolean sendMessageAtFrontOfQueue(Message message);
/** See {@link Handler#sendEmptyMessage(int)}. */
boolean sendEmptyMessage(int what);
/** @see Handler#sendEmptyMessageDelayed(int, long) */
/** See {@link Handler#sendEmptyMessageDelayed(int, long)}. */
boolean sendEmptyMessageDelayed(int what, int delayMs);
/** @see Handler#sendEmptyMessageAtTime(int, long) */
/** See {@link Handler#sendEmptyMessageAtTime(int, long)}. */
boolean sendEmptyMessageAtTime(int what, long uptimeMs);
/** @see Handler#removeMessages(int) */
/** See {@link Handler#removeMessages(int)}. */
void removeMessages(int what);
/** @see Handler#removeCallbacksAndMessages(Object) */
/** See {@link Handler#removeCallbacksAndMessages(Object)}. */
void removeCallbacksAndMessages(@Nullable Object token);
/** @see Handler#post(Runnable) */
/** See {@link Handler#post(Runnable)}. */
boolean post(Runnable runnable);
/** @see Handler#postDelayed(Runnable, long) */
/** See {@link Handler#postDelayed(Runnable, long)}. */
boolean postDelayed(Runnable runnable, long delayMs);
}

View File

@ -15,7 +15,6 @@
*/
package com.google.android.exoplayer2.util;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import androidx.annotation.CheckResult;
@ -72,7 +71,8 @@ public final class ListenerSet<T, E extends MutableFlags> {
private static final int MSG_ITERATION_FINISHED = 0;
private static final int MSG_LAZY_RELEASE = 1;
private final Handler handler;
private final Clock clock;
private final HandlerWrapper handler;
private final Supplier<E> eventFlagsSupplier;
private final IterationFinishedEvent<T, E> iterationFinishedEvent;
private final CopyOnWriteArraySet<ListenerHolder<T, E>> listeners;
@ -86,6 +86,7 @@ public final class ListenerSet<T, E extends MutableFlags> {
*
* @param looper A {@link Looper} used to call listeners on. The same {@link Looper} must be used
* to call all other methods of this class.
* @param clock A {@link Clock}.
* @param eventFlagsSupplier A {@link Supplier} for new instances of {@link E the event flags
* type}.
* @param iterationFinishedEvent An {@link IterationFinishedEvent} sent when all other events sent
@ -93,11 +94,13 @@ public final class ListenerSet<T, E extends MutableFlags> {
*/
public ListenerSet(
Looper looper,
Clock clock,
Supplier<E> eventFlagsSupplier,
IterationFinishedEvent<T, E> iterationFinishedEvent) {
this(
/* listeners= */ new CopyOnWriteArraySet<>(),
looper,
clock,
eventFlagsSupplier,
iterationFinishedEvent);
}
@ -105,8 +108,10 @@ public final class ListenerSet<T, E extends MutableFlags> {
private ListenerSet(
CopyOnWriteArraySet<ListenerHolder<T, E>> listeners,
Looper looper,
Clock clock,
Supplier<E> eventFlagsSupplier,
IterationFinishedEvent<T, E> iterationFinishedEvent) {
this.clock = clock;
this.listeners = listeners;
this.eventFlagsSupplier = eventFlagsSupplier;
this.iterationFinishedEvent = iterationFinishedEvent;
@ -114,7 +119,7 @@ public final class ListenerSet<T, E extends MutableFlags> {
queuedEvents = new ArrayDeque<>();
// It's safe to use "this" because we don't send a message before exiting the constructor.
@SuppressWarnings("methodref.receiver.bound.invalid")
Handler handler = Util.createHandler(looper, this::handleMessage);
HandlerWrapper handler = clock.createHandler(looper, this::handleMessage);
this.handler = handler;
}
@ -129,7 +134,7 @@ public final class ListenerSet<T, E extends MutableFlags> {
@CheckResult
public ListenerSet<T, E> copy(
Looper looper, IterationFinishedEvent<T, E> iterationFinishedEvent) {
return new ListenerSet<>(listeners, looper, eventFlagsSupplier, iterationFinishedEvent);
return new ListenerSet<>(listeners, looper, clock, eventFlagsSupplier, iterationFinishedEvent);
}
/**

View File

@ -22,6 +22,7 @@ import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.net.UnknownHostException;
import org.checkerframework.dataflow.qual.Pure;
/** Wrapper around {@link android.util.Log} which allows to set the log level. */
public final class Log {
@ -51,11 +52,13 @@ public final class Log {
private Log() {}
/** Returns current {@link LogLevel} for ExoPlayer logcat logging. */
@Pure
public static @LogLevel int getLogLevel() {
return logLevel;
}
/** Returns whether stack traces of {@link Throwable}s will be logged to logcat. */
@Pure
public boolean getLogStackTraces() {
return logStackTraces;
}
@ -80,6 +83,7 @@ public final class Log {
}
/** @see android.util.Log#d(String, String) */
@Pure
public static void d(String tag, String message) {
if (logLevel == LOG_LEVEL_ALL) {
android.util.Log.d(tag, message);
@ -87,11 +91,13 @@ public final class Log {
}
/** @see android.util.Log#d(String, String, Throwable) */
@Pure
public static void d(String tag, String message, @Nullable Throwable throwable) {
d(tag, appendThrowableString(message, throwable));
}
/** @see android.util.Log#i(String, String) */
@Pure
public static void i(String tag, String message) {
if (logLevel <= LOG_LEVEL_INFO) {
android.util.Log.i(tag, message);
@ -99,11 +105,13 @@ public final class Log {
}
/** @see android.util.Log#i(String, String, Throwable) */
@Pure
public static void i(String tag, String message, @Nullable Throwable throwable) {
i(tag, appendThrowableString(message, throwable));
}
/** @see android.util.Log#w(String, String) */
@Pure
public static void w(String tag, String message) {
if (logLevel <= LOG_LEVEL_WARNING) {
android.util.Log.w(tag, message);
@ -111,11 +119,13 @@ public final class Log {
}
/** @see android.util.Log#w(String, String, Throwable) */
@Pure
public static void w(String tag, String message, @Nullable Throwable throwable) {
w(tag, appendThrowableString(message, throwable));
}
/** @see android.util.Log#e(String, String) */
@Pure
public static void e(String tag, String message) {
if (logLevel <= LOG_LEVEL_ERROR) {
android.util.Log.e(tag, message);
@ -123,6 +133,7 @@ public final class Log {
}
/** @see android.util.Log#e(String, String, Throwable) */
@Pure
public static void e(String tag, String message, @Nullable Throwable throwable) {
e(tag, appendThrowableString(message, throwable));
}
@ -139,6 +150,7 @@ public final class Log {
* @return The string representation of the {@link Throwable}.
*/
@Nullable
@Pure
public static String getThrowableString(@Nullable Throwable throwable) {
if (throwable == null) {
return null;
@ -157,6 +169,7 @@ public final class Log {
}
}
@Pure
private static String appendThrowableString(String message, @Nullable Throwable throwable) {
@Nullable String throwableString = getThrowableString(throwable);
if (!TextUtils.isEmpty(throwableString)) {
@ -165,6 +178,7 @@ public final class Log {
return message;
}
@Pure
private static boolean isCausedByUnknownHostException(@Nullable Throwable throwable) {
while (throwable != null) {
if (throwable instanceof UnknownHostException) {

View File

@ -43,13 +43,13 @@ public class SystemClock implements Clock {
return android.os.SystemClock.uptimeMillis();
}
@Override
public void sleep(long sleepTimeMs) {
android.os.SystemClock.sleep(sleepTimeMs);
}
@Override
public HandlerWrapper createHandler(Looper looper, @Nullable Callback callback) {
return new SystemHandlerWrapper(new Handler(looper, callback));
}
@Override
public void onThreadBlocked() {
// Do nothing.
}
}

View File

@ -0,0 +1,163 @@
/*
* 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.util;
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.GuardedBy;
import androidx.annotation.Nullable;
import java.util.ArrayList;
import java.util.List;
/** The standard implementation of {@link HandlerWrapper}. */
/* package */ final class SystemHandlerWrapper implements HandlerWrapper {
private static final int MAX_POOL_SIZE = 50;
@GuardedBy("messagePool")
private static final List<SystemMessage> messagePool = new ArrayList<>(MAX_POOL_SIZE);
private final android.os.Handler handler;
public SystemHandlerWrapper(android.os.Handler handler) {
this.handler = handler;
}
@Override
public Looper getLooper() {
return handler.getLooper();
}
@Override
public boolean hasMessages(int what) {
return handler.hasMessages(what);
}
@Override
public Message obtainMessage(int what) {
return obtainSystemMessage().setMessage(handler.obtainMessage(what), /* handler= */ this);
}
@Override
public Message obtainMessage(int what, @Nullable Object obj) {
return obtainSystemMessage().setMessage(handler.obtainMessage(what, obj), /* handler= */ this);
}
@Override
public Message obtainMessage(int what, int arg1, int arg2) {
return obtainSystemMessage()
.setMessage(handler.obtainMessage(what, arg1, arg2), /* handler= */ this);
}
@Override
public Message obtainMessage(int what, int arg1, int arg2, @Nullable Object obj) {
return obtainSystemMessage()
.setMessage(handler.obtainMessage(what, arg1, arg2, obj), /* handler= */ this);
}
@Override
public boolean sendMessageAtFrontOfQueue(Message message) {
return ((SystemMessage) message).sendAtFrontOfQueue(handler);
}
@Override
public boolean sendEmptyMessage(int what) {
return handler.sendEmptyMessage(what);
}
@Override
public boolean sendEmptyMessageDelayed(int what, int delayMs) {
return handler.sendEmptyMessageDelayed(what, delayMs);
}
@Override
public boolean sendEmptyMessageAtTime(int what, long uptimeMs) {
return handler.sendEmptyMessageAtTime(what, uptimeMs);
}
@Override
public void removeMessages(int what) {
handler.removeMessages(what);
}
@Override
public void removeCallbacksAndMessages(@Nullable Object token) {
handler.removeCallbacksAndMessages(token);
}
@Override
public boolean post(Runnable runnable) {
return handler.post(runnable);
}
@Override
public boolean postDelayed(Runnable runnable, long delayMs) {
return handler.postDelayed(runnable, delayMs);
}
private static SystemMessage obtainSystemMessage() {
synchronized (messagePool) {
return messagePool.isEmpty()
? new SystemMessage()
: messagePool.remove(messagePool.size() - 1);
}
}
private static void recycleMessage(SystemMessage message) {
synchronized (messagePool) {
if (messagePool.size() < MAX_POOL_SIZE) {
messagePool.add(message);
}
}
}
private static final class SystemMessage implements Message {
@Nullable private android.os.Message message;
@Nullable private SystemHandlerWrapper handler;
public SystemMessage setMessage(android.os.Message message, SystemHandlerWrapper handler) {
this.message = message;
this.handler = handler;
return this;
}
public boolean sendAtFrontOfQueue(Handler handler) {
boolean success = handler.sendMessageAtFrontOfQueue(checkNotNull(message));
recycle();
return success;
}
@Override
public void sendToTarget() {
checkNotNull(message).sendToTarget();
recycle();
}
@Override
public HandlerWrapper getTarget() {
return checkNotNull(handler);
}
private void recycle() {
message = null;
handler = null;
recycleMessage(this);
}
}
}

View File

@ -91,6 +91,7 @@ import java.util.concurrent.Executors;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
import java.util.zip.DataFormatException;
import java.util.zip.GZIPOutputStream;
import java.util.zip.Inflater;
import org.checkerframework.checker.initialization.qual.UnknownInitialization;
import org.checkerframework.checker.nullness.compatqual.NullableType;
@ -2045,8 +2046,6 @@ public final class Util {
/** Returns a data URI with the specified MIME type and data. */
public static Uri getDataUriForString(String mimeType, String data) {
// TODO(internal: b/169937045): For now we don't pass the URL_SAFE flag as DataSchemeDataSource
// doesn't decode using it.
return Uri.parse(
"data:" + mimeType + ";base64," + Base64.encodeToString(data.getBytes(), Base64.NO_WRAP));
}
@ -2085,7 +2084,7 @@ public final class Util {
/** Creates a new empty file in the directory returned by {@link Context#getCacheDir()}. */
public static File createTempFile(Context context, String prefix) throws IOException {
return File.createTempFile(prefix, null, context.getCacheDir());
return File.createTempFile(prefix, null, checkNotNull(context.getCacheDir()));
}
/**
@ -2123,6 +2122,17 @@ public final class Util {
return initialValue;
}
/** Compresses {@code input} using gzip and returns the result in a newly allocated byte array. */
public static byte[] gzip(byte[] input) {
ByteArrayOutputStream output = new ByteArrayOutputStream();
try (GZIPOutputStream os = new GZIPOutputStream(output)) {
os.write(input);
} catch (IOException e) {
throw new AssertionError(e);
}
return output.toByteArray();
}
/**
* Absolute <i>get</i> method for reading an int value in {@link ByteOrder#BIG_ENDIAN} in a {@link
* ByteBuffer}. Same as {@link ByteBuffer#getInt(int)} except the buffer's order as returned by
@ -2179,7 +2189,7 @@ public final class Util {
return getMobileNetworkType(networkInfo);
case ConnectivityManager.TYPE_ETHERNET:
return C.NETWORK_TYPE_ETHERNET;
default: // VPN, Bluetooth, Dummy.
default:
return C.NETWORK_TYPE_OTHER;
}
}
@ -2610,7 +2620,7 @@ public final class Util {
"hsn", "zh-hsn"
};
// Legacy ("grandfathered") tags, replaced by modern equivalents (including macrolanguage)
// Legacy tags that have been replaced by modern equivalents (including macrolanguage)
// See https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry.
private static final String[] isoLegacyTagReplacements =
new String[] {

View File

@ -0,0 +1,41 @@
/*
* Copyright 2021 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.audio;
import static com.google.common.truth.Truth.assertThat;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Unit tests for {@link AudioAttributes}. */
@RunWith(AndroidJUnit4.class)
public class AudioAttributesTest {
@Test
public void roundtripViaBundle_yieldsEqualInstance() {
AudioAttributes audioAttributes =
new AudioAttributes.Builder()
.setContentType(C.CONTENT_TYPE_SONIFICATION)
.setFlags(C.FLAG_AUDIBILITY_ENFORCED)
.setUsage(C.USAGE_ALARM)
.setAllowedCapturePolicy(C.ALLOW_CAPTURE_BY_SYSTEM)
.build();
assertThat(AudioAttributes.fromBundle(audioAttributes.toBundle())).isEqualTo(audioAttributes);
}
}

View File

@ -0,0 +1,35 @@
/*
* Copyright 2021 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.device;
import static com.google.common.truth.Truth.assertThat;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Unit tests for {@link DeviceInfo}. */
@RunWith(AndroidJUnit4.class)
public class DeviceInfoTest {
@Test
public void roundtripViaBundle_yieldsEqualInstance() {
DeviceInfo deviceInfo =
new DeviceInfo(DeviceInfo.PLAYBACK_TYPE_REMOTE, /* minVolume= */ 1, /* maxVolume= */ 9);
assertThat(DeviceInfo.fromBundle(deviceInfo.toBundle())).isEqualTo(deviceInfo);
}
}

View File

@ -0,0 +1,46 @@
/*
* Copyright (C) 2020 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 androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.testutil.DataSourceContractTest;
import com.google.android.exoplayer2.testutil.HttpDataSourceTestEnv;
import com.google.common.collect.ImmutableList;
import org.junit.Rule;
import org.junit.runner.RunWith;
/** {@link DataSource} contract tests for {@link DefaultHttpDataSource}. */
@RunWith(AndroidJUnit4.class)
public class DefaultHttpDataSourceContractTest extends DataSourceContractTest {
@Rule public HttpDataSourceTestEnv httpDataSourceTestEnv = new HttpDataSourceTestEnv();
@Override
protected DataSource createDataSource() {
return new DefaultHttpDataSource.Factory().createDataSource();
}
@Override
protected ImmutableList<TestResource> getTestResources() {
return httpDataSourceTestEnv.getServedResources();
}
@Override
protected Uri getNotFoundUri() {
return Uri.parse(httpDataSourceTestEnv.getNonexistentUrl());
}
}

View File

@ -43,7 +43,8 @@ public class ListenerSetTest {
@Test
public void queueEvent_withoutFlush_sendsNoEvents() {
ListenerSet<TestListener, Flags> listenerSet =
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
new ListenerSet<>(
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
TestListener listener = mock(TestListener.class);
listenerSet.add(listener);
@ -57,7 +58,8 @@ public class ListenerSetTest {
@Test
public void flushEvents_sendsPreviouslyQueuedEventsToAllListeners() {
ListenerSet<TestListener, Flags> listenerSet =
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
new ListenerSet<>(
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
TestListener listener1 = mock(TestListener.class);
TestListener listener2 = mock(TestListener.class);
listenerSet.add(listener1);
@ -81,7 +83,8 @@ public class ListenerSetTest {
@Test
public void flushEvents_recursive_sendsEventsInCorrectOrder() {
ListenerSet<TestListener, Flags> listenerSet =
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
new ListenerSet<>(
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
// Listener1 sends callback3 recursively when receiving callback1.
TestListener listener1 =
spy(
@ -114,7 +117,8 @@ public class ListenerSetTest {
public void
flushEvents_withMultipleMessageQueueIterations_sendsIterationFinishedEventPerIteration() {
ListenerSet<TestListener, Flags> listenerSet =
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
new ListenerSet<>(
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
// Listener1 sends callback1 recursively when receiving callback3.
TestListener listener1 =
spy(
@ -170,7 +174,8 @@ public class ListenerSetTest {
@Test
public void flushEvents_calledFromIterationFinishedCallback_restartsIterationFinishedEvents() {
ListenerSet<TestListener, Flags> listenerSet =
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
new ListenerSet<>(
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
// Listener2 sends callback1 recursively when receiving the iteration finished event.
TestListener listener2 =
spy(
@ -212,7 +217,8 @@ public class ListenerSetTest {
@Test
public void flushEvents_withUnsetEventFlag_doesNotThrow() {
ListenerSet<TestListener, Flags> listenerSet =
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
new ListenerSet<>(
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
listenerSet.queueEvent(/* eventFlag= */ C.INDEX_UNSET, TestListener::callback1);
listenerSet.flushEvents();
@ -224,7 +230,8 @@ public class ListenerSetTest {
@Test
public void add_withRecursion_onlyReceivesUpdatesForFutureEvents() {
ListenerSet<TestListener, Flags> listenerSet =
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
new ListenerSet<>(
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
TestListener listener2 = mock(TestListener.class);
// Listener1 adds listener2 recursively.
TestListener listener1 =
@ -256,7 +263,8 @@ public class ListenerSetTest {
@Test
public void add_withQueueing_onlyReceivesUpdatesForFutureEvents() {
ListenerSet<TestListener, Flags> listenerSet =
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
new ListenerSet<>(
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
TestListener listener1 = mock(TestListener.class);
TestListener listener2 = mock(TestListener.class);
@ -281,7 +289,8 @@ public class ListenerSetTest {
@Test
public void remove_withRecursion_stopsReceivingEventsImmediately() {
ListenerSet<TestListener, Flags> listenerSet =
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
new ListenerSet<>(
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
TestListener listener2 = mock(TestListener.class);
// Listener1 removes listener2 recursively.
TestListener listener1 =
@ -309,7 +318,8 @@ public class ListenerSetTest {
@Test
public void remove_withQueueing_stopsReceivingEventsImmediately() {
ListenerSet<TestListener, Flags> listenerSet =
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
new ListenerSet<>(
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
TestListener listener1 = mock(TestListener.class);
TestListener listener2 = mock(TestListener.class);
listenerSet.add(listener1);
@ -330,7 +340,8 @@ public class ListenerSetTest {
@Test
public void release_stopsForwardingEventsImmediately() {
ListenerSet<TestListener, Flags> listenerSet =
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
new ListenerSet<>(
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
TestListener listener2 = mock(TestListener.class);
// Listener1 releases the set from within the callback.
TestListener listener1 =
@ -357,7 +368,8 @@ public class ListenerSetTest {
@Test
public void release_preventsRegisteringNewListeners() {
ListenerSet<TestListener, Flags> listenerSet =
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
new ListenerSet<>(
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
TestListener listener = mock(TestListener.class);
listenerSet.release();
@ -370,7 +382,8 @@ public class ListenerSetTest {
@Test
public void lazyRelease_stopsForwardingEventsFromNewHandlerMessagesAndCallsReleaseCallback() {
ListenerSet<TestListener, Flags> listenerSet =
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
new ListenerSet<>(
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
TestListener listener = mock(TestListener.class);
listenerSet.add(listener);

View File

@ -20,6 +20,7 @@ import static com.google.android.exoplayer2.util.Util.binarySearchFloor;
import static com.google.android.exoplayer2.util.Util.escapeFileName;
import static com.google.android.exoplayer2.util.Util.getCodecsOfType;
import static com.google.android.exoplayer2.util.Util.getStringForTime;
import static com.google.android.exoplayer2.util.Util.gzip;
import static com.google.android.exoplayer2.util.Util.minValue;
import static com.google.android.exoplayer2.util.Util.parseXsDateTime;
import static com.google.android.exoplayer2.util.Util.parseXsDuration;
@ -37,6 +38,9 @@ import android.text.style.UnderlineSpan;
import android.util.SparseLongArray;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.testutil.TestUtil;
import com.google.common.io.ByteStreams;
import java.io.ByteArrayInputStream;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
@ -45,6 +49,7 @@ import java.util.Formatter;
import java.util.NoSuchElementException;
import java.util.Random;
import java.util.zip.Deflater;
import java.util.zip.GZIPInputStream;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.annotation.Config;
@ -927,6 +932,17 @@ public class UtilTest {
assertThat(result).isEqualTo(0x4);
}
@Test
public void gzip_resultInflatesBackToOriginalValue() throws Exception {
byte[] input = TestUtil.buildTestData(20);
byte[] deflated = gzip(input);
byte[] inflated =
ByteStreams.toByteArray(new GZIPInputStream(new ByteArrayInputStream(deflated)));
assertThat(inflated).isEqualTo(input);
}
@Test
public void getBigEndianInt_fromBigEndian() {
byte[] bytes = {0x1F, 0x2E, 0x3D, 0x4C};

View File

@ -21,16 +21,14 @@ import static java.lang.Math.min;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DefaultAllocator;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util;
/**
* The default {@link LoadControl} implementation.
*/
/** The default {@link LoadControl} implementation. */
public class DefaultLoadControl implements LoadControl {
/**
@ -318,8 +316,8 @@ public class DefaultLoadControl implements LoadControl {
}
@Override
public void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups,
TrackSelectionArray trackSelections) {
public void onTracksSelected(
Renderer[] renderers, TrackGroupArray trackGroups, ExoTrackSelection[] trackSelections) {
targetBufferBytes =
targetBufferBytesOverwrite == C.LENGTH_UNSET
? calculateTargetBufferBytes(renderers, trackSelections)
@ -402,10 +400,10 @@ public class DefaultLoadControl implements LoadControl {
* @return The target buffer size in bytes.
*/
protected int calculateTargetBufferBytes(
Renderer[] renderers, TrackSelectionArray trackSelectionArray) {
Renderer[] renderers, ExoTrackSelection[] trackSelectionArray) {
int targetBufferSize = 0;
for (int i = 0; i < renderers.length; i++) {
if (trackSelectionArray.get(i) != null) {
if (trackSelectionArray[i] != null) {
targetBufferSize += getDefaultBufferSize(renderers[i].getTrackType());
}
}

View File

@ -449,13 +449,18 @@ public interface ExoPlayer extends Player {
}
}
@Override
/**
* Returns the track selector that this player uses, or null if track selection is not supported.
*/
@Nullable
TrackSelector getTrackSelector();
/** Returns the {@link Looper} associated with the playback thread. */
Looper getPlaybackLooper();
/** Returns the {@link Clock} used for playback. */
Clock getClock();
/** @deprecated Use {@link #prepare()} instead. */
@Deprecated
void retry();

View File

@ -34,13 +34,14 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.source.MediaSourceFactory;
import com.google.android.exoplayer2.source.ShuffleOrder;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.HandlerWrapper;
import com.google.android.exoplayer2.util.ListenerSet;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util;
@ -67,10 +68,9 @@ import java.util.List;
private final Renderer[] renderers;
private final TrackSelector trackSelector;
private final Handler playbackInfoUpdateHandler;
private final HandlerWrapper playbackInfoUpdateHandler;
private final ExoPlayerImplInternal.PlaybackInfoUpdateListener playbackInfoUpdateListener;
private final ExoPlayerImplInternal internalPlayer;
private final Handler internalPlayerHandler;
private final ListenerSet<Player.EventListener, Player.Events> listeners;
private final Timeline.Period period;
private final List<MediaSourceHolderSnapshot> mediaSourceHolderSnapshots;
@ -79,6 +79,7 @@ import java.util.List;
@Nullable private final AnalyticsCollector analyticsCollector;
private final Looper applicationLooper;
private final BandwidthMeter bandwidthMeter;
private final Clock clock;
@RepeatMode private int repeatMode;
private boolean shuffleModeEnabled;
@ -137,8 +138,15 @@ import java.util.List;
Clock clock,
Looper applicationLooper,
@Nullable Player wrappingPlayer) {
Log.i(TAG, "Init " + Integer.toHexString(System.identityHashCode(this)) + " ["
+ ExoPlayerLibraryInfo.VERSION_SLASHY + "] [" + Util.DEVICE_DEBUG_INFO + "]");
Log.i(
TAG,
"Init "
+ Integer.toHexString(System.identityHashCode(this))
+ " ["
+ ExoPlayerLibraryInfo.VERSION_SLASHY
+ "] ["
+ Util.DEVICE_DEBUG_INFO
+ "]");
checkState(renderers.length > 0);
this.renderers = checkNotNull(renderers);
this.trackSelector = checkNotNull(trackSelector);
@ -149,11 +157,13 @@ import java.util.List;
this.seekParameters = seekParameters;
this.pauseAtEndOfMediaItems = pauseAtEndOfMediaItems;
this.applicationLooper = applicationLooper;
this.clock = clock;
repeatMode = Player.REPEAT_MODE_OFF;
Player playerForListeners = wrappingPlayer != null ? wrappingPlayer : this;
listeners =
new ListenerSet<>(
applicationLooper,
clock,
Player.Events::new,
(listener, eventFlags) -> listener.onEvents(playerForListeners, eventFlags));
mediaSourceHolderSnapshots = new ArrayList<>();
@ -161,11 +171,11 @@ import java.util.List;
emptyTrackSelectorResult =
new TrackSelectorResult(
new RendererConfiguration[renderers.length],
new TrackSelection[renderers.length],
new ExoTrackSelection[renderers.length],
/* info= */ null);
period = new Timeline.Period();
maskingWindowIndex = C.INDEX_UNSET;
playbackInfoUpdateHandler = new Handler(applicationLooper);
playbackInfoUpdateHandler = clock.createHandler(applicationLooper, /* callback= */ null);
playbackInfoUpdateListener =
playbackInfoUpdate ->
playbackInfoUpdateHandler.post(() -> handlePlaybackInfo(playbackInfoUpdate));
@ -192,7 +202,6 @@ import java.util.List;
applicationLooper,
clock,
playbackInfoUpdateListener);
internalPlayerHandler = new Handler(internalPlayer.getPlaybackLooper());
}
/**
@ -259,6 +268,11 @@ import java.util.List;
return applicationLooper;
}
@Override
public Clock getClock() {
return clock;
}
@Override
public void addListener(Player.EventListener listener) {
listeners.add(listener);
@ -724,9 +738,17 @@ import java.util.List;
@Override
public void release() {
Log.i(TAG, "Release " + Integer.toHexString(System.identityHashCode(this)) + " ["
+ ExoPlayerLibraryInfo.VERSION_SLASHY + "] [" + Util.DEVICE_DEBUG_INFO + "] ["
+ ExoPlayerLibraryInfo.registeredModules() + "]");
Log.i(
TAG,
"Release "
+ Integer.toHexString(System.identityHashCode(this))
+ " ["
+ ExoPlayerLibraryInfo.VERSION_SLASHY
+ "] ["
+ Util.DEVICE_DEBUG_INFO
+ "] ["
+ ExoPlayerLibraryInfo.registeredModules()
+ "]");
if (!internalPlayer.release()) {
// One of the renderers timed out releasing its resources.
listeners.sendEvent(
@ -754,7 +776,8 @@ import java.util.List;
target,
playbackInfo.timeline,
getCurrentWindowIndex(),
internalPlayerHandler);
clock,
internalPlayer.getPlaybackLooper());
}
@Override
@ -882,7 +905,7 @@ import java.util.List;
@Override
public TrackSelectionArray getCurrentTrackSelections() {
return playbackInfo.trackSelectorResult.selections;
return new TrackSelectionArray(playbackInfo.trackSelectorResult.selections);
}
@Override
@ -1005,11 +1028,11 @@ import java.util.List;
}
if (previousPlaybackInfo.trackSelectorResult != newPlaybackInfo.trackSelectorResult) {
trackSelector.onSelectionActivated(newPlaybackInfo.trackSelectorResult.info);
TrackSelectionArray newSelection =
new TrackSelectionArray(newPlaybackInfo.trackSelectorResult.selections);
listeners.queueEvent(
Player.EVENT_TRACKS_CHANGED,
listener ->
listener.onTracksChanged(
newPlaybackInfo.trackGroups, newPlaybackInfo.trackSelectorResult.selections));
listener -> listener.onTracksChanged(newPlaybackInfo.trackGroups, newSelection));
}
if (!previousPlaybackInfo.staticMetadata.equals(newPlaybackInfo.staticMetadata)) {
listeners.queueEvent(

View File

@ -40,8 +40,7 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.source.SampleStream;
import com.google.android.exoplayer2.source.ShuffleOrder;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
@ -558,7 +557,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
if (e.isRecoverable && pendingRecoverableError == null) {
Log.w(TAG, "Recoverable playback error", e);
pendingRecoverableError = e;
Message message = handler.obtainMessage(MSG_ATTEMPT_ERROR_RECOVERY, e);
HandlerWrapper.Message message = handler.obtainMessage(MSG_ATTEMPT_ERROR_RECOVERY, e);
// Given that the player is now in an unhandled exception state, the error needs to be
// recovered or the player stopped before any other message is handled.
message.getTarget().sendMessageAtFrontOfQueue(message);
@ -625,6 +624,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
boolean wasInterrupted = false;
while (!condition.get() && remainingMs > 0) {
try {
clock.onThreadBlocked();
wait(remainingMs);
} catch (InterruptedException e) {
wasInterrupted = true;
@ -726,8 +726,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
private void notifyTrackSelectionPlayWhenReadyChanged(boolean playWhenReady) {
MediaPeriodHolder periodHolder = queue.getPlayingPeriod();
while (periodHolder != null) {
TrackSelection[] trackSelections = periodHolder.getTrackSelectorResult().selections.getAll();
for (TrackSelection trackSelection : trackSelections) {
for (ExoTrackSelection trackSelection : periodHolder.getTrackSelectorResult().selections) {
if (trackSelection != null) {
trackSelection.onPlayWhenReadyChanged(playWhenReady);
}
@ -901,8 +900,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
private void notifyTrackSelectionRebuffer() {
MediaPeriodHolder periodHolder = queue.getPlayingPeriod();
while (periodHolder != null) {
TrackSelection[] trackSelections = periodHolder.getTrackSelectorResult().selections.getAll();
for (TrackSelection trackSelection : trackSelections) {
for (ExoTrackSelection trackSelection : periodHolder.getTrackSelectorResult().selections) {
if (trackSelection != null) {
trackSelection.onRebuffer();
}
@ -1468,7 +1466,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
private void sendMessageToTarget(PlayerMessage message) throws ExoPlaybackException {
if (message.getHandler().getLooper() == playbackLooper) {
if (message.getLooper() == playbackLooper) {
deliverMessage(message);
if (playbackInfo.playbackState == Player.STATE_READY
|| playbackInfo.playbackState == Player.STATE_BUFFERING) {
@ -1481,21 +1479,23 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
private void sendMessageToTargetThread(final PlayerMessage message) {
Handler handler = message.getHandler();
if (!handler.getLooper().getThread().isAlive()) {
Looper looper = message.getLooper();
if (!looper.getThread().isAlive()) {
Log.w("TAG", "Trying to send message on a dead thread.");
message.markAsProcessed(/* isDelivered= */ false);
return;
}
handler.post(
() -> {
try {
deliverMessage(message);
} catch (ExoPlaybackException e) {
Log.e(TAG, "Unexpected error delivering message on external thread.", e);
throw new RuntimeException(e);
}
});
clock
.createHandler(looper, /* callback= */ null)
.post(
() -> {
try {
deliverMessage(message);
} catch (ExoPlaybackException e) {
Log.e(TAG, "Unexpected error delivering message on external thread.", e);
throw new RuntimeException(e);
}
});
}
private void deliverMessage(PlayerMessage message) throws ExoPlaybackException {
@ -1690,8 +1690,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
private void updateTrackSelectionPlaybackSpeed(float playbackSpeed) {
MediaPeriodHolder periodHolder = queue.getPlayingPeriod();
while (periodHolder != null) {
TrackSelection[] trackSelections = periodHolder.getTrackSelectorResult().selections.getAll();
for (TrackSelection trackSelection : trackSelections) {
for (ExoTrackSelection trackSelection : periodHolder.getTrackSelectorResult().selections) {
if (trackSelection != null) {
trackSelection.onPlaybackSpeed(playbackSpeed);
}
@ -1703,8 +1702,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
private void notifyTrackSelectionDiscontinuity() {
MediaPeriodHolder periodHolder = queue.getPlayingPeriod();
while (periodHolder != null) {
TrackSelection[] trackSelections = periodHolder.getTrackSelectorResult().selections.getAll();
for (TrackSelection trackSelection : trackSelections) {
for (ExoTrackSelection trackSelection : periodHolder.getTrackSelectorResult().selections) {
if (trackSelection != null) {
trackSelection.onDiscontinuity();
}
@ -1732,8 +1730,13 @@ import java.util.concurrent.atomic.AtomicBoolean;
? livePlaybackSpeedControl.getTargetLiveOffsetUs()
: C.TIME_UNSET;
MediaPeriodHolder loadingHolder = queue.getLoadingPeriod();
boolean bufferedToEnd = loadingHolder.isFullyBuffered() && loadingHolder.info.isFinal;
return bufferedToEnd
boolean isBufferedToEnd = loadingHolder.isFullyBuffered() && loadingHolder.info.isFinal;
// Ad loader implementations may only load ad media once playback has nearly reached the ad, but
// it is possible for playback to be stuck buffering waiting for this. Therefore, we start
// playback regardless of buffered duration if we are waiting for an ad media period to prepare.
boolean isAdPendingPreparation = loadingHolder.info.id.isAd() && !loadingHolder.prepared;
return isBufferedToEnd
|| isAdPendingPreparation
|| loadControl.shouldStartPlayback(
getTotalBufferedDurationUs(),
mediaClock.getPlaybackParameters().speed,
@ -2016,7 +2019,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
if (!renderer.isCurrentStreamFinal()) {
// The renderer stream is not final, so we can replace the sample streams immediately.
Format[] formats = getFormats(newTrackSelectorResult.selections.get(i));
Format[] formats = getFormats(newTrackSelectorResult.selections[i]);
renderer.replaceStream(
formats,
readingPeriodHolder.sampleStreams[i],
@ -2266,11 +2269,10 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
private ImmutableList<Metadata> extractMetadataFromTrackSelectionArray(
TrackSelectionArray trackSelectionArray) {
ExoTrackSelection[] trackSelections) {
ImmutableList.Builder<Metadata> result = new ImmutableList.Builder<>();
boolean seenNonEmptyMetadata = false;
for (int i = 0; i < trackSelectionArray.length; i++) {
@Nullable TrackSelection trackSelection = trackSelectionArray.get(i);
for (ExoTrackSelection trackSelection : trackSelections) {
if (trackSelection != null) {
Format format = trackSelection.getFormat(/* index= */ 0);
if (format.metadata == null) {
@ -2318,7 +2320,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
TrackSelectorResult trackSelectorResult = periodHolder.getTrackSelectorResult();
RendererConfiguration rendererConfiguration =
trackSelectorResult.rendererConfigurations[rendererIndex];
TrackSelection newSelection = trackSelectorResult.selections.get(rendererIndex);
ExoTrackSelection newSelection = trackSelectorResult.selections[rendererIndex];
Format[] formats = getFormats(newSelection);
// The renderer needs enabling with its new track selection.
boolean playing = shouldPlayWhenReady() && playbackInfo.playbackState == Player.STATE_READY;
@ -2792,7 +2794,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
return newPeriodIndex == C.INDEX_UNSET ? null : newTimeline.getUidOfPeriod(newPeriodIndex);
}
private static Format[] getFormats(TrackSelection newSelection) {
private static Format[] getFormats(ExoTrackSelection newSelection) {
// Build an array of formats contained by the selection.
int length = newSelection != null ? newSelection.length() : 0;
Format[] formats = new Format[length];

View File

@ -41,11 +41,9 @@ public final class ExoTimeoutException extends Exception {
/** The operation where this error occurred is not defined. */
public static final int TIMEOUT_OPERATION_UNDEFINED = 0;
// TODO(b/172315872) Change back @code to @link when the Player is in common.
/** The error occurred in {@code Player#release}. */
/** The error occurred in {@link Player#release}. */
public static final int TIMEOUT_OPERATION_RELEASE = 1;
/** The error occurred in {@code ExoPlayer#setForegroundMode}. */
// TODO(b/172315872) Set foregroundMode is an ExoPlayer method, NOT a player one.
/** The error occurred in {@link ExoPlayer#setForegroundMode}. */
public static final int TIMEOUT_OPERATION_SET_FOREGROUND_MODE = 2;
/** The error occurred while detaching a surface from the player. */
public static final int TIMEOUT_OPERATION_DETACH_SURFACE = 3;

View File

@ -17,12 +17,10 @@ package com.google.android.exoplayer2;
import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import com.google.android.exoplayer2.upstream.Allocator;
/**
* Controls buffering of media.
*/
/** Controls buffering of media. */
public interface LoadControl {
/** Called by the player when prepared with a new source. */
@ -35,33 +33,27 @@ public interface LoadControl {
* @param trackGroups The {@link TrackGroup}s from which the selection was made.
* @param trackSelections The track selections that were made.
*/
void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups,
TrackSelectionArray trackSelections);
void onTracksSelected(
Renderer[] renderers, TrackGroupArray trackGroups, ExoTrackSelection[] trackSelections);
/**
* Called by the player when stopped.
*/
/** Called by the player when stopped. */
void onStopped();
/**
* Called by the player when released.
*/
/** Called by the player when released. */
void onReleased();
/**
* Returns the {@link Allocator} that should be used to obtain media buffer allocations.
*/
/** Returns the {@link Allocator} that should be used to obtain media buffer allocations. */
Allocator getAllocator();
/**
* Returns the duration of media to retain in the buffer prior to the current playback position,
* for fast backward seeking.
* <p>
* Note: If {@link #retainBackBufferFromKeyframe()} is false then seeking in the back-buffer will
* only be fast if the back-buffer contains a keyframe prior to the seek position.
* <p>
* Note: Implementations should return a single value. Dynamic changes to the back-buffer are not
* currently supported.
*
* <p>Note: If {@link #retainBackBufferFromKeyframe()} is false then seeking in the back-buffer
* will only be fast if the back-buffer contains a keyframe prior to the seek position.
*
* <p>Note: Implementations should return a single value. Dynamic changes to the back-buffer are
* not currently supported.
*
* @return The duration of media to retain in the buffer prior to the current playback position,
* in microseconds.
@ -71,17 +63,19 @@ public interface LoadControl {
/**
* Returns whether media should be retained from the keyframe before the current playback position
* minus {@link #getBackBufferDurationUs()}, rather than any sample before or at that position.
* <p>
* Warning: Returning true will cause the back-buffer size to depend on the spacing of keyframes
* in the media being played. Returning true is not recommended unless you control the media and
* are comfortable with the back-buffer size exceeding {@link #getBackBufferDurationUs()} by as
* much as the maximum duration between adjacent keyframes in the media.
* <p>
* Note: Implementations should return a single value. Dynamic changes to the back-buffer are not
* currently supported.
*
* <p>Warning: Returning true will cause the back-buffer size to depend on the spacing of
* keyframes in the media being played. Returning true is not recommended unless you control the
* media and are comfortable with the back-buffer size exceeding {@link
* #getBackBufferDurationUs()} by as much as the maximum duration between adjacent keyframes in
* the media.
*
* <p>Note: Implementations should return a single value. Dynamic changes to the back-buffer are
* not currently supported.
*
* @return Whether media should be retained from the keyframe before the current playback position
* minus {@link #getBackBufferDurationUs()}, rather than any sample before or at that position.
* minus {@link #getBackBufferDurationUs()}, rather than any sample before or at that
* position.
*/
boolean retainBackBufferFromKeyframe();

View File

@ -24,8 +24,7 @@ import com.google.android.exoplayer2.source.MediaPeriod;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.source.SampleStream;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
import com.google.android.exoplayer2.upstream.Allocator;
@ -233,7 +232,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
throws ExoPlaybackException {
TrackSelectorResult selectorResult =
trackSelector.selectTracks(rendererCapabilities, getTrackGroups(), info.id, timeline);
for (TrackSelection trackSelection : selectorResult.selections.getAll()) {
for (ExoTrackSelection trackSelection : selectorResult.selections) {
if (trackSelection != null) {
trackSelection.onPlaybackSpeed(playbackSpeed);
}
@ -289,10 +288,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
trackSelectorResult = newTrackSelectorResult;
enableTrackSelectionsInResult();
// Disable streams on the period and get new streams for updated/newly-enabled tracks.
TrackSelectionArray trackSelections = newTrackSelectorResult.selections;
positionUs =
mediaPeriod.selectTracks(
trackSelections.getAll(),
newTrackSelectorResult.selections,
mayRetainStreamFlags,
sampleStreams,
streamResetFlags,
@ -309,7 +307,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
hasEnabledTracks = true;
}
} else {
Assertions.checkState(trackSelections.get(i) == null);
Assertions.checkState(newTrackSelectorResult.selections[i] == null);
}
}
return positionUs;
@ -361,7 +359,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
}
for (int i = 0; i < trackSelectorResult.length; i++) {
boolean rendererEnabled = trackSelectorResult.isRendererEnabled(i);
TrackSelection trackSelection = trackSelectorResult.selections.get(i);
ExoTrackSelection trackSelection = trackSelectorResult.selections[i];
if (rendererEnabled && trackSelection != null) {
trackSelection.enable();
}
@ -374,7 +372,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
}
for (int i = 0; i < trackSelectorResult.length; i++) {
boolean rendererEnabled = trackSelectorResult.isRendererEnabled(i);
TrackSelection trackSelection = trackSelectorResult.selections.get(i);
ExoTrackSelection trackSelection = trackSelectorResult.selections[i];
if (rendererEnabled && trackSelection != null) {
trackSelection.disable();
}

View File

@ -16,8 +16,8 @@
package com.google.android.exoplayer2;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Clock;
import java.util.concurrent.TimeoutException;
@ -55,11 +55,12 @@ public final class PlayerMessage {
private final Target target;
private final Sender sender;
private final Clock clock;
private final Timeline timeline;
private int type;
@Nullable private Object payload;
private Handler handler;
private Looper looper;
private int windowIndex;
private long positionMs;
private boolean deleteAfterDelivery;
@ -77,7 +78,8 @@ public final class PlayerMessage {
* set to {@link Timeline#EMPTY}, any position can be specified.
* @param defaultWindowIndex The default window index in the {@code timeline} when no other window
* index is specified.
* @param defaultHandler The default handler to send the message on when no other handler is
* @param clock The {@link Clock}.
* @param defaultLooper The default {@link Looper} to send the message on when no other looper is
* specified.
*/
public PlayerMessage(
@ -85,11 +87,13 @@ public final class PlayerMessage {
Target target,
Timeline timeline,
int defaultWindowIndex,
Handler defaultHandler) {
Clock clock,
Looper defaultLooper) {
this.sender = sender;
this.target = target;
this.timeline = timeline;
this.handler = defaultHandler;
this.looper = defaultLooper;
this.clock = clock;
this.windowIndex = defaultWindowIndex;
this.positionMs = C.TIME_UNSET;
this.deleteAfterDelivery = true;
@ -142,22 +146,28 @@ public final class PlayerMessage {
return payload;
}
/** @deprecated Use {@link #setLooper(Looper)} instead. */
@Deprecated
public PlayerMessage setHandler(Handler handler) {
return setLooper(handler.getLooper());
}
/**
* Sets the handler the message is delivered on.
* Sets the {@link Looper} the message is delivered on.
*
* @param handler A {@link Handler}.
* @param looper A {@link Looper}.
* @return This message.
* @throws IllegalStateException If {@link #send()} has already been called.
*/
public PlayerMessage setHandler(Handler handler) {
public PlayerMessage setLooper(Looper looper) {
Assertions.checkState(!isSent);
this.handler = handler;
this.looper = looper;
return this;
}
/** Returns the handler the message is delivered on. */
public Handler getHandler() {
return handler;
/** Returns the {@link Looper} the message is delivered on. */
public Looper getLooper() {
return looper;
}
/**
@ -287,19 +297,19 @@ public final class PlayerMessage {
* Blocks until after the message has been delivered or the player is no longer able to deliver
* the message.
*
* <p>Note that this method can't be called if the current thread is the same thread used by the
* message handler set with {@link #setHandler(Handler)} as it would cause a deadlock.
* <p>Note that this method must not be called if the current thread is the same thread used by
* the message {@link #getLooper() looper} as it would cause a deadlock.
*
* @return Whether the message was delivered successfully.
* @throws IllegalStateException If this method is called before {@link #send()}.
* @throws IllegalStateException If this method is called on the same thread used by the message
* handler set with {@link #setHandler(Handler)}.
* {@link #getLooper() looper}.
* @throws InterruptedException If the current thread is interrupted while waiting for the message
* to be delivered.
*/
public synchronized boolean blockUntilDelivered() throws InterruptedException {
Assertions.checkState(isSent);
Assertions.checkState(handler.getLooper().getThread() != Thread.currentThread());
Assertions.checkState(looper.getThread() != Thread.currentThread());
while (!isProcessed) {
wait();
}
@ -310,14 +320,14 @@ public final class PlayerMessage {
* Blocks until after the message has been delivered or the player is no longer able to deliver
* the message or the specified timeout elapsed.
*
* <p>Note that this method can't be called if the current thread is the same thread used by the
* message handler set with {@link #setHandler(Handler)} as it would cause a deadlock.
* <p>Note that this method must not be called if the current thread is the same thread used by
* the message {@link #getLooper() looper} as it would cause a deadlock.
*
* @param timeoutMs The timeout in milliseconds.
* @return Whether the message was delivered successfully.
* @throws IllegalStateException If this method is called before {@link #send()}.
* @throws IllegalStateException If this method is called on the same thread used by the message
* handler set with {@link #setHandler(Handler)}.
* {@link #getLooper() looper}.
* @throws TimeoutException If the {@code timeoutMs} elapsed and this message has not been
* delivered and the player is still able to deliver the message.
* @throws InterruptedException If the current thread is interrupted while waiting for the message
@ -325,26 +335,19 @@ public final class PlayerMessage {
*/
public synchronized boolean blockUntilDelivered(long timeoutMs)
throws InterruptedException, TimeoutException {
return blockUntilDelivered(timeoutMs, Clock.DEFAULT);
}
@VisibleForTesting()
/* package */ synchronized boolean blockUntilDelivered(long timeoutMs, Clock clock)
throws InterruptedException, TimeoutException {
Assertions.checkState(isSent);
Assertions.checkState(handler.getLooper().getThread() != Thread.currentThread());
Assertions.checkState(looper.getThread() != Thread.currentThread());
long deadlineMs = clock.elapsedRealtime() + timeoutMs;
long remainingMs = timeoutMs;
while (!isProcessed && remainingMs > 0) {
clock.onThreadBlocked();
wait(remainingMs);
remainingMs = deadlineMs - clock.elapsedRealtime();
}
if (!isProcessed) {
throw new TimeoutException("Message delivery timed out.");
}
return isDelivered;
}
}

View File

@ -1202,6 +1202,11 @@ public class SimpleExoPlayer extends BasePlayer
return player.getApplicationLooper();
}
@Override
public Clock getClock() {
return player.getClock();
}
@Override
public void addListener(Player.EventListener listener) {
// Don't verify application thread. We allow calls to this method from any thread.

View File

@ -91,6 +91,7 @@ public class AnalyticsCollector
listeners =
new ListenerSet<>(
Util.getCurrentOrMainLooper(),
clock,
AnalyticsListener.Events::new,
(listener, eventFlags) -> {});
period = new Period();

View File

@ -1457,28 +1457,69 @@ public final class DefaultAudioSink implements AudioSink {
if (!supportedEncoding) {
return null;
}
// E-AC3 JOC is object based, so any channel count specified in the format is arbitrary. Use 6,
// since the E-AC3 compatible part of the stream is 5.1.
int channelCount = encoding == C.ENCODING_E_AC3_JOC ? 6 : format.channelCount;
if (channelCount > audioCapabilities.getMaxChannelCount()) {
if (encoding == C.ENCODING_E_AC3_JOC
&& !audioCapabilities.supportsEncoding(C.ENCODING_E_AC3_JOC)) {
// E-AC3 receivers support E-AC3 JOC streams (but decode only the base layer).
encoding = C.ENCODING_E_AC3;
}
if (!audioCapabilities.supportsEncoding(encoding)) {
return null;
}
int channelCount;
if (encoding == C.ENCODING_E_AC3_JOC) {
// E-AC3 JOC is object based so the format channel count is arbitrary. From API 29 we can get
// the channel count for this encoding, but before then there is no way to query it so we
// assume 6 channel audio is supported.
if (Util.SDK_INT >= 29) {
channelCount =
getMaxSupportedChannelCountForPassthroughV29(C.ENCODING_E_AC3_JOC, format.sampleRate);
if (channelCount == 0) {
Log.w(TAG, "E-AC3 JOC encoding supported but no channel count supported");
return null;
}
} else {
channelCount = 6;
}
} else {
channelCount = format.channelCount;
if (channelCount > audioCapabilities.getMaxChannelCount()) {
return null;
}
}
int channelConfig = getChannelConfigForPassthrough(channelCount);
if (channelConfig == AudioFormat.CHANNEL_INVALID) {
return null;
}
if (audioCapabilities.supportsEncoding(encoding)) {
return Pair.create(encoding, channelConfig);
} else if (encoding == C.ENCODING_E_AC3_JOC
&& audioCapabilities.supportsEncoding(C.ENCODING_E_AC3)) {
// E-AC3 receivers support E-AC3 JOC streams (but decode in 2-D rather than 3-D).
return Pair.create(C.ENCODING_E_AC3, channelConfig);
}
return Pair.create(encoding, channelConfig);
}
return null;
/**
* Returns the maximum number of channels supported for passthrough playback of audio in the given
* format, or 0 if the format is unsupported.
*/
@RequiresApi(29)
private static int getMaxSupportedChannelCountForPassthroughV29(
@C.Encoding int encoding, int sampleRate) {
android.media.AudioAttributes audioAttributes =
new android.media.AudioAttributes.Builder()
.setUsage(android.media.AudioAttributes.USAGE_MEDIA)
.setContentType(android.media.AudioAttributes.CONTENT_TYPE_MOVIE)
.build();
// TODO(internal b/25994457): Query supported channel masks directly once it's supported.
for (int channelCount = 8; channelCount > 0; channelCount--) {
AudioFormat audioFormat =
new AudioFormat.Builder()
.setEncoding(encoding)
.setSampleRate(sampleRate)
.setChannelMask(Util.getAudioTrackChannelConfig(channelCount))
.build();
if (AudioTrack.isDirectPlaybackSupported(audioFormat, audioAttributes)) {
return channelCount;
}
}
return 0;
}
private static int getChannelConfigForPassthrough(int channelCount) {

View File

@ -26,6 +26,7 @@ import android.os.Looper;
import android.os.Message;
import android.os.SystemClock;
import android.util.Pair;
import androidx.annotation.GuardedBy;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.C;
@ -308,7 +309,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
// Assigning null to various non-null variables for clean-up.
state = STATE_RELEASED;
Util.castNonNull(responseHandler).removeCallbacksAndMessages(null);
Util.castNonNull(requestHandler).removeCallbacksAndMessages(null);
Util.castNonNull(requestHandler).release();
requestHandler = null;
Util.castNonNull(requestHandlerThread).quit();
requestHandlerThread = null;
@ -570,6 +571,9 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@SuppressLint("HandlerLeak")
private class RequestHandler extends Handler {
@GuardedBy("this")
private boolean isReleased;
public RequestHandler(Looper backgroundLooper) {
super(backgroundLooper);
}
@ -610,9 +614,13 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
response = e;
}
loadErrorHandlingPolicy.onLoadTaskConcluded(requestTask.taskId);
responseHandler
.obtainMessage(msg.what, Pair.create(requestTask.request, response))
.sendToTarget();
synchronized (this) {
if (!isReleased) {
responseHandler
.obtainMessage(msg.what, Pair.create(requestTask.request, response))
.sendToTarget();
}
}
}
private boolean maybeRetryRequest(Message originalMsg, MediaDrmCallbackException exception) {
@ -647,8 +655,18 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
// The error is fatal.
return false;
}
sendMessageDelayed(Message.obtain(originalMsg), retryDelayMs);
return true;
synchronized (this) {
if (!isReleased) {
sendMessageDelayed(Message.obtain(originalMsg), retryDelayMs);
return true;
}
}
return false;
}
public synchronized void release() {
removeCallbacksAndMessages(/* token= */ null);
isReleased = true;
}
}

View File

@ -36,6 +36,7 @@ import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
@ -632,12 +633,12 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
// ResourceBusyException is only available at API 19, so on earlier versions we always
// eagerly release regardless of the underlying error.
if (!keepaliveSessions.isEmpty()) {
// Make a local copy, because sessions are removed from this.timingOutSessions during
// Make a local copy, because sessions are removed from this.keepaliveSessions during
// release (via callback).
ImmutableList<DefaultDrmSession> timingOutSessions =
ImmutableList.copyOf(this.keepaliveSessions);
for (DrmSession timingOutSession : timingOutSessions) {
timingOutSession.release(/* eventDispatcher= */ null);
ImmutableSet<DefaultDrmSession> keepaliveSessions =
ImmutableSet.copyOf(this.keepaliveSessions);
for (DrmSession keepaliveSession : keepaliveSessions) {
keepaliveSession.release(/* eventDispatcher= */ null);
}
// Undo the acquisitions from createAndAcquireSession().
session.release(eventDispatcher);

View File

@ -13,16 +13,12 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.source;
package com.google.android.exoplayer2.drm;
import static com.google.android.exoplayer2.drm.DefaultDrmSessionManager.MODE_PLAYBACK;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
import com.google.android.exoplayer2.upstream.HttpDataSource;
@ -31,8 +27,8 @@ import com.google.android.exoplayer2.util.Util;
import com.google.common.primitives.Ints;
import java.util.Map;
/** A helper to create a {@link DrmSessionManager} from a {@link MediaItem}. */
public final class MediaSourceDrmHelper {
/** Default implementation of {@link DrmSessionManagerProvider}. */
public final class DefaultDrmSessionManagerProvider implements DrmSessionManagerProvider {
@Nullable private HttpDataSource.Factory drmHttpDataSourceFactory;
@Nullable private String userAgent;
@ -62,13 +58,13 @@ public final class MediaSourceDrmHelper {
this.userAgent = userAgent;
}
/** Creates a {@link DrmSessionManager} for the given media item. */
public DrmSessionManager create(MediaItem mediaItem) {
@Override
public DrmSessionManager get(MediaItem mediaItem) {
Assertions.checkNotNull(mediaItem.playbackProperties);
@Nullable
MediaItem.DrmConfiguration drmConfiguration = mediaItem.playbackProperties.drmConfiguration;
if (drmConfiguration == null || Util.SDK_INT < 18) {
return DrmSessionManager.getDummyDrmSessionManager();
return DrmSessionManager.DRM_UNSUPPORTED;
}
HttpDataSource.Factory dataSourceFactory =
drmHttpDataSourceFactory != null

View File

@ -22,13 +22,8 @@ import com.google.android.exoplayer2.Format;
/** Manages a DRM session. */
public interface DrmSessionManager {
/** Returns {@link #DUMMY}. */
static DrmSessionManager getDummyDrmSessionManager() {
return DUMMY;
}
/** {@link DrmSessionManager} that supports no DRM schemes. */
DrmSessionManager DUMMY =
/** An instance that supports no DRM schemes. */
DrmSessionManager DRM_UNSUPPORTED =
new DrmSessionManager() {
@Override
@ -54,6 +49,23 @@ public interface DrmSessionManager {
}
};
/**
* An instance that supports no DRM schemes.
*
* @deprecated Use {@link #DRM_UNSUPPORTED}.
*/
@Deprecated DrmSessionManager DUMMY = DRM_UNSUPPORTED;
/**
* Returns {@link #DRM_UNSUPPORTED}.
*
* @deprecated Use {@link #DRM_UNSUPPORTED}.
*/
@Deprecated
static DrmSessionManager getDummyDrmSessionManager() {
return DRM_UNSUPPORTED;
}
/**
* Acquires any required resources.
*

View File

@ -1,5 +1,5 @@
/*
* Copyright (C) 2020 The Android Open Source Project
* Copyright 2021 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.
@ -13,14 +13,16 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.android.exoplayer2.trackselection;
package com.google.android.exoplayer2.drm;
import com.google.android.exoplayer2.MediaItem;
// TODO(b/172315872) Replace @code by @link when Player has been migrated to common
/**
* The component of a {@code Player} responsible for selecting tracks to be played.
*
* <p>No Player agnostic track selection is currently supported. Clients should downcast to the
* implementation's track selection.
* A provider to obtain a {@link DrmSessionManager} suitable for playing the content described by a
* {@link MediaItem}.
*/
// TODO(b/172315872) Define an interface for track selection.
public interface TrackSelectorInterface {}
public interface DrmSessionManagerProvider {
/** Returns a {@link DrmSessionManager} for the given media item. */
DrmSessionManager get(MediaItem mediaItem);
}

View File

@ -30,7 +30,7 @@ import java.nio.ByteBuffer;
/* package */ final class C2Mp3TimestampTracker {
// Mirroring the actual codec, as can be found at
// https://cs.android.com/android/platform/superproject/+/master:frameworks/av/media/codec2/components/mp3/C2SoftMp3Dec.h;l=55;drc=3665390c9d32a917398b240c5a46ced07a3b65eb
// https://cs.android.com/android/platform/superproject/+/main:frameworks/av/media/codec2/components/mp3/C2SoftMp3Dec.h;l=55;drc=3665390c9d32a917398b240c5a46ced07a3b65eb
private static final long DECODER_DELAY_SAMPLES = 529;
private static final String TAG = "C2Mp3TimestampTracker";
@ -76,7 +76,7 @@ import java.nio.ByteBuffer;
}
// These calculations mirror the timestamp calculations in the Codec2 Mp3 Decoder.
// https://cs.android.com/android/platform/superproject/+/master:frameworks/av/media/codec2/components/mp3/C2SoftMp3Dec.cpp;l=464;drc=ed134640332fea70ca4b05694289d91a5265bb46
// https://cs.android.com/android/platform/superproject/+/main:frameworks/av/media/codec2/components/mp3/C2SoftMp3Dec.cpp;l=464;drc=ed134640332fea70ca4b05694289d91a5265bb46
if (processedSamples == 0) {
anchorTimestampUs = buffer.timeUs;
processedSamples = frameCount - DECODER_DELAY_SAMPLES;

View File

@ -298,8 +298,16 @@ public final class MediaCodecInfo {
// which may not be widely supported. See https://github.com/google/ExoPlayer/issues/5145.
return true;
}
for (CodecProfileLevel capabilities : getProfileLevels()) {
if (capabilities.profile == profile && capabilities.level >= level) {
CodecProfileLevel[] profileLevels = getProfileLevels();
if (Util.SDK_INT <= 23 && MimeTypes.VIDEO_VP9.equals(mimeType) && profileLevels.length == 0) {
// Some older devices don't report profile levels for VP9. Estimate them using other data in
// the codec capabilities.
profileLevels = estimateLegacyVp9ProfileLevels(capabilities);
}
for (CodecProfileLevel profileLevel : profileLevels) {
if (profileLevel.profile == profile && profileLevel.level >= level) {
return true;
}
}
@ -334,8 +342,8 @@ public final class MediaCodecInfo {
if (isVideo) {
return adaptive;
} else {
Pair<Integer, Integer> codecProfileLevel = MediaCodecUtil.getCodecProfileAndLevel(format);
return codecProfileLevel != null && codecProfileLevel.first == CodecProfileLevel.AACObjectXHE;
Pair<Integer, Integer> profileLevel = MediaCodecUtil.getCodecProfileAndLevel(format);
return profileLevel != null && profileLevel.first == CodecProfileLevel.AACObjectXHE;
}
}
@ -678,6 +686,60 @@ public final class MediaCodecInfo {
return capabilities.getMaxSupportedInstances();
}
/**
* Called on devices with {@link Util#SDK_INT} 23 and below, for VP9 decoders whose {@link
* CodecCapabilities} do not correctly report profile levels. The returned {@link
* CodecProfileLevel CodecProfileLevels} are estimated based on other data in the {@link
* CodecCapabilities}.
*
* @param capabilities The {@link CodecCapabilities} for a VP9 decoder, or {@code null} if not
* known.
* @return The estimated {@link CodecProfileLevel CodecProfileLevels} for the decoder.
*/
private static CodecProfileLevel[] estimateLegacyVp9ProfileLevels(
@Nullable CodecCapabilities capabilities) {
int maxBitrate = 0;
if (capabilities != null) {
@Nullable VideoCapabilities videoCapabilities = capabilities.getVideoCapabilities();
if (videoCapabilities != null) {
maxBitrate = videoCapabilities.getBitrateRange().getUpper();
}
}
// Values taken from https://www.webmproject.org/vp9/levels.
int level;
if (maxBitrate >= 180_000_000) {
level = CodecProfileLevel.VP9Level52;
} else if (maxBitrate >= 120_000_000) {
level = CodecProfileLevel.VP9Level51;
} else if (maxBitrate >= 60_000_000) {
level = CodecProfileLevel.VP9Level5;
} else if (maxBitrate >= 30_000_000) {
level = CodecProfileLevel.VP9Level41;
} else if (maxBitrate >= 18_000_000) {
level = CodecProfileLevel.VP9Level4;
} else if (maxBitrate >= 12_000_000) {
level = CodecProfileLevel.VP9Level31;
} else if (maxBitrate >= 7_200_000) {
level = CodecProfileLevel.VP9Level3;
} else if (maxBitrate >= 3_600_000) {
level = CodecProfileLevel.VP9Level21;
} else if (maxBitrate >= 1_800_000) {
level = CodecProfileLevel.VP9Level2;
} else if (maxBitrate >= 800_000) {
level = CodecProfileLevel.VP9Level11;
} else { // Assume level 1 is always supported.
level = CodecProfileLevel.VP9Level1;
}
CodecProfileLevel profileLevel = new CodecProfileLevel();
// Since this method is for legacy devices only, assume that only profile 0 is supported.
profileLevel.profile = CodecProfileLevel.VP9Profile0;
profileLevel.level = level;
return new CodecProfileLevel[] {profileLevel};
}
/**
* Returns whether the decoder is known to fail when adapting, despite advertising itself as an
* adaptive decoder.

View File

@ -2206,8 +2206,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
if (bypassBatchBuffer.hasSamples()) {
bypassBatchBuffer.flip();
}
// We can make more progress if we have batched data or the EOS to process.
return bypassBatchBuffer.hasSamples() || inputStreamEnded;
// We can make more progress if we have batched data, an EOS, or a re-initialization to process
// (note that one or more of the code blocks above will be executed during the next call).
return bypassBatchBuffer.hasSamples() || inputStreamEnded || bypassDrainAndReinitialize;
}
private void bypassRead() throws ExoPlaybackException {
@ -2221,7 +2223,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
switch (result) {
case C.RESULT_FORMAT_READ:
onInputFormatChanged(formatHolder);
break;
return;
case C.RESULT_NOTHING_READ:
return;
case C.RESULT_BUFFER_READ:

View File

@ -48,8 +48,8 @@ import com.google.android.exoplayer2.trackselection.BaseTrackSelection;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.Parameters;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride;
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.BandwidthMeter;
@ -465,8 +465,9 @@ public final class DownloadHelper {
private @MonotonicNonNull MediaPreparer mediaPreparer;
private TrackGroupArray @MonotonicNonNull [] trackGroupArrays;
private MappedTrackInfo @MonotonicNonNull [] mappedTrackInfos;
private List<TrackSelection> @MonotonicNonNull [][] trackSelectionsByPeriodAndRenderer;
private List<TrackSelection> @MonotonicNonNull [][] immutableTrackSelectionsByPeriodAndRenderer;
private List<ExoTrackSelection> @MonotonicNonNull [][] trackSelectionsByPeriodAndRenderer;
private List<ExoTrackSelection> @MonotonicNonNull [][]
immutableTrackSelectionsByPeriodAndRenderer;
/**
* Creates download helper.
@ -573,14 +574,14 @@ public final class DownloadHelper {
}
/**
* Returns all {@link TrackSelection track selections} for a period and renderer. Must not be
* Returns all {@link ExoTrackSelection track selections} for a period and renderer. Must not be
* called until after preparation completes.
*
* @param periodIndex The period index.
* @param rendererIndex The renderer index.
* @return A list of selected {@link TrackSelection track selections}.
* @return A list of selected {@link ExoTrackSelection track selections}.
*/
public List<TrackSelection> getTrackSelections(int periodIndex, int rendererIndex) {
public List<ExoTrackSelection> getTrackSelections(int periodIndex, int rendererIndex) {
assertPreparedWithMedia();
return immutableTrackSelectionsByPeriodAndRenderer[periodIndex][rendererIndex];
}
@ -751,7 +752,7 @@ public final class DownloadHelper {
}
assertPreparedWithMedia();
List<StreamKey> streamKeys = new ArrayList<>();
List<TrackSelection> allSelections = new ArrayList<>();
List<ExoTrackSelection> allSelections = new ArrayList<>();
int periodCount = trackSelectionsByPeriodAndRenderer.length;
for (int periodIndex = 0; periodIndex < periodCount; periodIndex++) {
allSelections.clear();
@ -773,9 +774,9 @@ public final class DownloadHelper {
int periodCount = mediaPreparer.mediaPeriods.length;
int rendererCount = rendererCapabilities.length;
trackSelectionsByPeriodAndRenderer =
(List<TrackSelection>[][]) new List<?>[periodCount][rendererCount];
(List<ExoTrackSelection>[][]) new List<?>[periodCount][rendererCount];
immutableTrackSelectionsByPeriodAndRenderer =
(List<TrackSelection>[][]) new List<?>[periodCount][rendererCount];
(List<ExoTrackSelection>[][]) new List<?>[periodCount][rendererCount];
for (int i = 0; i < periodCount; i++) {
for (int j = 0; j < rendererCount; j++) {
trackSelectionsByPeriodAndRenderer[i][j] = new ArrayList<>();
@ -847,15 +848,15 @@ public final class DownloadHelper {
new MediaPeriodId(mediaPreparer.timeline.getUidOfPeriod(periodIndex)),
mediaPreparer.timeline);
for (int i = 0; i < trackSelectorResult.length; i++) {
@Nullable TrackSelection newSelection = trackSelectorResult.selections.get(i);
@Nullable ExoTrackSelection newSelection = trackSelectorResult.selections[i];
if (newSelection == null) {
continue;
}
List<TrackSelection> existingSelectionList =
List<ExoTrackSelection> existingSelectionList =
trackSelectionsByPeriodAndRenderer[periodIndex][i];
boolean mergedWithExistingSelection = false;
for (int j = 0; j < existingSelectionList.size(); j++) {
TrackSelection existingSelection = existingSelectionList.get(j);
ExoTrackSelection existingSelection = existingSelectionList.get(j);
if (existingSelection.getTrackGroup() == newSelection.getTrackGroup()) {
// Merge with existing selection.
scratchSet.clear();
@ -1066,15 +1067,15 @@ public final class DownloadHelper {
private static final class DownloadTrackSelection extends BaseTrackSelection {
private static final class Factory implements TrackSelection.Factory {
private static final class Factory implements ExoTrackSelection.Factory {
@Override
public @NullableType TrackSelection[] createTrackSelections(
public @NullableType ExoTrackSelection[] createTrackSelections(
@NullableType Definition[] definitions,
BandwidthMeter bandwidthMeter,
MediaPeriodId mediaPeriodId,
Timeline timeline) {
@NullableType TrackSelection[] selections = new TrackSelection[definitions.length];
@NullableType ExoTrackSelection[] selections = new ExoTrackSelection[definitions.length];
for (int i = 0; i < definitions.length; i++) {
selections[i] =
definitions[i] == null

View File

@ -21,7 +21,7 @@ import com.google.android.exoplayer2.Format;
import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.SeekParameters;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
@ -34,9 +34,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
*/
public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callback {
/**
* The {@link MediaPeriod} wrapped by this clipping media period.
*/
/** The {@link MediaPeriod} wrapped by this clipping media period. */
public final MediaPeriod mediaPeriod;
@Nullable private MediaPeriod.Callback callback;
@ -98,7 +96,7 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
@Override
public long selectTracks(
@NullableType TrackSelection[] selections,
@NullableType ExoTrackSelection[] selections,
boolean[] mayRetainStreamFlags,
@NullableType SampleStream[] streams,
boolean[] streamResetFlags,
@ -250,7 +248,7 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
}
private static boolean shouldKeepInitialDiscontinuity(
long startUs, @NullableType TrackSelection[] selections) {
long startUs, @NullableType ExoTrackSelection[] selections) {
// If the clipping start position is non-zero, the clipping sample streams will adjust
// timestamps on buffers they read from the unclipped sample streams. These adjusted buffer
// timestamps can be negative, because sample streams provide buffers starting at a key-frame,
@ -261,7 +259,7 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
// However, for tracks where all samples are sync samples, we assume they have random access
// seek behaviour and do not need an initial discontinuity to reset the renderer.
if (startUs != 0) {
for (TrackSelection trackSelection : selections) {
for (ExoTrackSelection trackSelection : selections) {
if (trackSelection != null) {
Format selectedFormat = trackSelection.getSelectedFormat();
if (!MimeTypes.allSamplesAreSyncSamples(
@ -274,9 +272,7 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
return false;
}
/**
* Wraps a {@link SampleStream} and clips its samples.
*/
/** Wraps a {@link SampleStream} and clips its samples. */
private final class ClippingSampleStream implements SampleStream {
public final SampleStream childStream;
@ -302,8 +298,8 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
}
@Override
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer,
boolean requireFormat) {
public int readData(
FormatHolder formatHolder, DecoderInputBuffer buffer, boolean requireFormat) {
if (isPendingInitialDiscontinuity()) {
return C.RESULT_NOTHING_READ;
}

View File

@ -24,6 +24,7 @@ import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.DrmSessionManagerProvider;
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.offline.StreamKey;
@ -100,15 +101,12 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
private static final String TAG = "DefaultMediaSourceFactory";
private final MediaSourceDrmHelper mediaSourceDrmHelper;
private final DataSource.Factory dataSourceFactory;
private final SparseArray<MediaSourceFactory> mediaSourceFactories;
@C.ContentType private final int[] supportedTypes;
@Nullable private AdsLoaderProvider adsLoaderProvider;
@Nullable private AdViewProvider adViewProvider;
@Nullable private DrmSessionManager drmSessionManager;
@Nullable private List<StreamKey> streamKeys;
@Nullable private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private long liveTargetOffsetMs;
private long liveMinOffsetMs;
@ -157,7 +155,6 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
public DefaultMediaSourceFactory(
DataSource.Factory dataSourceFactory, ExtractorsFactory extractorsFactory) {
this.dataSourceFactory = dataSourceFactory;
mediaSourceDrmHelper = new MediaSourceDrmHelper();
mediaSourceFactories = loadDelegates(dataSourceFactory, extractorsFactory);
supportedTypes = new int[mediaSourceFactories.size()];
for (int i = 0; i < mediaSourceFactories.size(); i++) {
@ -254,23 +251,41 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
return this;
}
@SuppressWarnings("deprecation") // Calling through to the same deprecated method.
@Override
public DefaultMediaSourceFactory setDrmHttpDataSourceFactory(
@Nullable HttpDataSource.Factory drmHttpDataSourceFactory) {
mediaSourceDrmHelper.setDrmHttpDataSourceFactory(drmHttpDataSourceFactory);
for (int i = 0; i < mediaSourceFactories.size(); i++) {
mediaSourceFactories.valueAt(i).setDrmHttpDataSourceFactory(drmHttpDataSourceFactory);
}
return this;
}
@SuppressWarnings("deprecation") // Calling through to the same deprecated method.
@Override
public DefaultMediaSourceFactory setDrmUserAgent(@Nullable String userAgent) {
mediaSourceDrmHelper.setDrmUserAgent(userAgent);
for (int i = 0; i < mediaSourceFactories.size(); i++) {
mediaSourceFactories.valueAt(i).setDrmUserAgent(userAgent);
}
return this;
}
@SuppressWarnings("deprecation") // Calling through to the same deprecated method.
@Override
public DefaultMediaSourceFactory setDrmSessionManager(
@Nullable DrmSessionManager drmSessionManager) {
this.drmSessionManager = drmSessionManager;
for (int i = 0; i < mediaSourceFactories.size(); i++) {
mediaSourceFactories.valueAt(i).setDrmSessionManager(drmSessionManager);
}
return this;
}
@Override
public DefaultMediaSourceFactory setDrmSessionManagerProvider(
@Nullable DrmSessionManagerProvider drmSessionManagerProvider) {
for (int i = 0; i < mediaSourceFactories.size(); i++) {
mediaSourceFactories.valueAt(i).setDrmSessionManagerProvider(drmSessionManagerProvider);
}
return this;
}
@ -278,6 +293,9 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
public DefaultMediaSourceFactory setLoadErrorHandlingPolicy(
@Nullable LoadErrorHandlingPolicy loadErrorHandlingPolicy) {
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
for (int i = 0; i < mediaSourceFactories.size(); i++) {
mediaSourceFactories.valueAt(i).setLoadErrorHandlingPolicy(loadErrorHandlingPolicy);
}
return this;
}
@ -285,11 +303,13 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
* @deprecated Use {@link MediaItem.Builder#setStreamKeys(List)} and {@link
* #createMediaSource(MediaItem)} instead.
*/
@SuppressWarnings("deprecation")
@SuppressWarnings("deprecation") // Calling through to the same deprecated method.
@Deprecated
@Override
public DefaultMediaSourceFactory setStreamKeys(@Nullable List<StreamKey> streamKeys) {
this.streamKeys = streamKeys != null && !streamKeys.isEmpty() ? streamKeys : null;
for (int i = 0; i < mediaSourceFactories.size(); i++) {
mediaSourceFactories.valueAt(i).setStreamKeys(streamKeys);
}
return this;
}
@ -298,7 +318,6 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
return Arrays.copyOf(supportedTypes, supportedTypes.length);
}
@SuppressWarnings("deprecation")
@Override
public MediaSource createMediaSource(MediaItem mediaItem) {
Assertions.checkNotNull(mediaItem.playbackProperties);
@ -309,13 +328,6 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
@Nullable MediaSourceFactory mediaSourceFactory = mediaSourceFactories.get(type);
Assertions.checkNotNull(
mediaSourceFactory, "No suitable media source factory found for content type: " + type);
mediaSourceFactory.setDrmSessionManager(
drmSessionManager != null ? drmSessionManager : mediaSourceDrmHelper.create(mediaItem));
mediaSourceFactory.setStreamKeys(
!mediaItem.playbackProperties.streamKeys.isEmpty()
? mediaItem.playbackProperties.streamKeys
: streamKeys);
mediaSourceFactory.setLoadErrorHandlingPolicy(loadErrorHandlingPolicy);
// Make sure to retain the very same media item instance, if no value needs to be overridden.
if ((mediaItem.liveConfiguration.targetOffsetMs == C.TIME_UNSET

View File

@ -23,6 +23,7 @@ import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Player;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.DrmSessionManagerProvider;
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
@ -154,6 +155,18 @@ public final class ExtractorMediaSource extends CompositeMediaSource<Void> {
return this;
}
/**
* @deprecated Use {@link
* ProgressiveMediaSource.Factory#setDrmSessionManagerProvider(DrmSessionManagerProvider)}
* instead.
*/
@Deprecated
@Override
public Factory setDrmSessionManagerProvider(
@Nullable DrmSessionManagerProvider drmSessionManagerProvider) {
throw new UnsupportedOperationException();
}
/** @deprecated Use {@link ProgressiveMediaSource.Factory#setDrmSessionManager} instead. */
@Deprecated
@Override
@ -324,7 +337,7 @@ public final class ExtractorMediaSource extends CompositeMediaSource<Void> {
.build(),
dataSourceFactory,
extractorsFactory,
DrmSessionManager.getDummyDrmSessionManager(),
DrmSessionManager.DRM_UNSUPPORTED,
loadableLoadErrorHandlingPolicy,
continueLoadingCheckIntervalBytes);
}

View File

@ -23,7 +23,7 @@ import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.SeekParameters;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import com.google.android.exoplayer2.upstream.Allocator;
import java.io.IOException;
import org.checkerframework.checker.nullness.compatqual.NullableType;
@ -173,7 +173,7 @@ public final class MaskingMediaPeriod implements MediaPeriod, MediaPeriod.Callba
@Override
public long selectTracks(
@NullableType TrackSelection[] selections,
@NullableType ExoTrackSelection[] selections,
boolean[] mayRetainStreamFlags,
@NullableType SampleStream[] streams,
boolean[] streamResetFlags,

View File

@ -21,7 +21,7 @@ import com.google.android.exoplayer2.SeekParameters;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.offline.StreamKey;
import com.google.android.exoplayer2.source.MediaSource.MediaSourceCaller;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import java.io.IOException;
import java.util.Collections;
import java.util.List;
@ -29,21 +29,23 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
/**
* Loads media corresponding to a {@link Timeline.Period}, and allows that media to be read. All
* methods are called on the player's internal playback thread, as described in the
* {@link ExoPlayer} Javadoc.
* methods are called on the player's internal playback thread, as described in the {@link
* ExoPlayer} Javadoc.
*
* <p>A {@link MediaPeriod} may only able to provide one {@link SampleStream} corresponding to a
* group at any given time, however this {@link SampleStream} may adapt between multiple tracks
* within the group.
*/
public interface MediaPeriod extends SequenceableLoader {
/**
* A callback to be notified of {@link MediaPeriod} events.
*/
/** A callback to be notified of {@link MediaPeriod} events. */
interface Callback extends SequenceableLoader.Callback<MediaPeriod> {
/**
* Called when preparation completes.
*
* <p>Called on the playback thread. After invoking this method, the {@link MediaPeriod} can
* expect for {@link #selectTracks(TrackSelection[], boolean[], SampleStream[], boolean[],
* expect for {@link #selectTracks(ExoTrackSelection[], boolean[], SampleStream[], boolean[],
* long)} to be called with the initial track selection.
*
* @param mediaPeriod The prepared {@link MediaPeriod}.
@ -88,17 +90,17 @@ public interface MediaPeriod extends SequenceableLoader {
/**
* Returns a list of {@link StreamKey StreamKeys} which allow to filter the media in this period
* to load only the parts needed to play the provided {@link TrackSelection TrackSelections}.
* to load only the parts needed to play the provided {@link ExoTrackSelection TrackSelections}.
*
* <p>This method is only called after the period has been prepared.
*
* @param trackSelections The {@link TrackSelection TrackSelections} describing the tracks for
* @param trackSelections The {@link ExoTrackSelection TrackSelections} describing the tracks for
* which stream keys are requested.
* @return The corresponding {@link StreamKey StreamKeys} for the selected tracks, or an empty
* list if filtering is not possible and the entire media needs to be loaded to play the
* selected tracks.
*/
default List<StreamKey> getStreamKeys(List<TrackSelection> trackSelections) {
default List<StreamKey> getStreamKeys(List<ExoTrackSelection> trackSelections) {
return Collections.emptyList();
}
@ -113,8 +115,8 @@ public interface MediaPeriod extends SequenceableLoader {
* corresponding flag in {@code streamResetFlags} will be set to true. This flag will also be set
* if a new sample stream is created.
*
* <p>Note that previously passed {@link TrackSelection TrackSelections} are no longer valid, and
* any references to them must be updated to point to the new selections.
* <p>Note that previously passed {@link ExoTrackSelection TrackSelections} are no longer valid,
* and any references to them must be updated to point to the new selections.
*
* <p>This method is only called after the period has been prepared.
*
@ -133,7 +135,7 @@ public interface MediaPeriod extends SequenceableLoader {
* @return The actual position at which the tracks were enabled, in microseconds.
*/
long selectTracks(
@NullableType TrackSelection[] selections,
@NullableType ExoTrackSelection[] selections,
boolean[] mayRetainStreamFlags,
@NullableType SampleStream[] streams,
boolean[] streamResetFlags,

View File

@ -20,7 +20,9 @@ import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
import com.google.android.exoplayer2.drm.DefaultDrmSessionManagerProvider;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.DrmSessionManagerProvider;
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
import com.google.android.exoplayer2.offline.StreamKey;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
@ -56,41 +58,80 @@ public interface MediaSourceFactory {
return this;
}
/**
* Sets the {@link DrmSessionManagerProvider} used to obtain a {@link DrmSessionManager} for a
* {@link MediaItem}.
*
* <p>If not set, {@link DefaultDrmSessionManagerProvider} is used.
*
* <p>If set, calls to the following (deprecated) methods are ignored:
*
* <ul>
* <li>{@link #setDrmUserAgent(String)}
* <li>{@link #setDrmHttpDataSourceFactory(HttpDataSource.Factory)}
* </ul>
*
* @return This factory, for convenience.
*/
MediaSourceFactory setDrmSessionManagerProvider(
@Nullable DrmSessionManagerProvider drmSessionManagerProvider);
/**
* Sets the {@link DrmSessionManager} to use for all media items regardless of their {@link
* MediaItem.DrmConfiguration}.
*
* <p>Calling this with a non-null {@code drmSessionManager} is equivalent to calling {@code
* setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager)}.
*
* @param drmSessionManager The {@link DrmSessionManager}, or {@code null} to use the {@link
* DefaultDrmSessionManager}.
* @return This factory, for convenience.
* @deprecated Use {@link #setDrmSessionManagerProvider(DrmSessionManagerProvider)} and pass an
* implementation that always returns the same instance.
*/
@Deprecated
MediaSourceFactory setDrmSessionManager(@Nullable DrmSessionManager drmSessionManager);
/**
* Sets the {@link HttpDataSource.Factory} to be used for creating {@link HttpMediaDrmCallback
* HttpMediaDrmCallbacks} to execute key and provisioning requests over HTTP.
*
* <p>In case a {@link DrmSessionManager} has been set by {@link
* #setDrmSessionManager(DrmSessionManager)}, this data source factory is ignored.
* <p>Calls to this method are ignored if either a {@link
* #setDrmSessionManagerProvider(DrmSessionManagerProvider) DrmSessionManager provider} or {@link
* #setDrmSessionManager(DrmSessionManager) concrete DrmSessionManager} are provided.
*
* @param drmHttpDataSourceFactory The HTTP data source factory, or {@code null} to use {@link
* DefaultHttpDataSourceFactory}.
* @return This factory, for convenience.
* @deprecated Use {@link #setDrmSessionManagerProvider(DrmSessionManagerProvider)} and pass an
* implementation that configures the returned {@link DrmSessionManager} with the desired
* {@link HttpDataSource.Factory}.
*/
@Deprecated
MediaSourceFactory setDrmHttpDataSourceFactory(
@Nullable HttpDataSource.Factory drmHttpDataSourceFactory);
/**
* Sets the optional user agent to be used for DRM requests.
*
* <p>In case a factory has been set by {@link
* #setDrmHttpDataSourceFactory(HttpDataSource.Factory)} or a {@link DrmSessionManager} has been
* set by {@link #setDrmSessionManager(DrmSessionManager)}, this user agent is ignored.
* <p>Calls to this method are ignored if any of the following are provided:
*
* <ul>
* <li>A {@link #setDrmSessionManagerProvider(DrmSessionManagerProvider) DrmSessionManager
* provider}.
* <li>A {@link #setDrmSessionManager(DrmSessionManager) concrete DrmSessionManager}.
* <li>A {@link #setDrmHttpDataSourceFactory(HttpDataSource.Factory) DRM
* HttpDataSource.Factory}.
* </ul>
*
* @param userAgent The user agent to be used for DRM requests, or {@code null} to use the
* default.
* @return This factory, for convenience.
* @deprecated Use {@link #setDrmSessionManagerProvider(DrmSessionManagerProvider)} and pass an
* implementation that configures the returned {@link DrmSessionManager} with the desired
* {@code userAgent}.
*/
@Deprecated
MediaSourceFactory setDrmUserAgent(@Nullable String userAgent);
/**

View File

@ -23,7 +23,7 @@ import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.SeekParameters;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.offline.StreamKey;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import com.google.android.exoplayer2.util.Assertions;
import java.io.IOException;
import java.util.ArrayList;
@ -33,9 +33,7 @@ import java.util.List;
import org.checkerframework.checker.nullness.compatqual.NullableType;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* Merges multiple {@link MediaPeriod}s.
*/
/** Merges multiple {@link MediaPeriod}s. */
/* package */ final class MergingMediaPeriod implements MediaPeriod, MediaPeriod.Callback {
private final MediaPeriod[] periods;
@ -100,7 +98,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override
public long selectTracks(
@NullableType TrackSelection[] selections,
@NullableType ExoTrackSelection[] selections,
boolean[] mayRetainStreamFlags,
@NullableType SampleStream[] streams,
boolean[] streamResetFlags,
@ -126,15 +124,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
// Select tracks for each child, copying the resulting streams back into a new streams array.
@NullableType SampleStream[] newStreams = new SampleStream[selections.length];
@NullableType SampleStream[] childStreams = new SampleStream[selections.length];
@NullableType TrackSelection[] childSelections = new TrackSelection[selections.length];
@NullableType ExoTrackSelection[] childSelections = new ExoTrackSelection[selections.length];
ArrayList<MediaPeriod> enabledPeriodsList = new ArrayList<>(periods.length);
for (int i = 0; i < periods.length; i++) {
for (int j = 0; j < selections.length; j++) {
childStreams[j] = streamChildIndices[j] == i ? streams[j] : null;
childSelections[j] = selectionChildIndices[j] == i ? selections[j] : null;
}
long selectPositionUs = periods[i].selectTracks(childSelections, mayRetainStreamFlags,
childStreams, streamResetFlags, positionUs);
long selectPositionUs =
periods[i].selectTracks(
childSelections, mayRetainStreamFlags, childStreams, streamResetFlags, positionUs);
if (i == 0) {
positionUs = selectPositionUs;
} else if (selectPositionUs != positionUs) {
@ -314,13 +313,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
}
@Override
public List<StreamKey> getStreamKeys(List<TrackSelection> trackSelections) {
public List<StreamKey> getStreamKeys(List<ExoTrackSelection> trackSelections) {
return mediaPeriod.getStreamKeys(trackSelections);
}
@Override
public long selectTracks(
@NullableType TrackSelection[] selections,
@NullableType ExoTrackSelection[] selections,
boolean[] mayRetainStreamFlags,
@NullableType SampleStream[] streams,
boolean[] streamResetFlags,

View File

@ -40,7 +40,7 @@ import com.google.android.exoplayer2.extractor.TrackOutput;
import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.metadata.icy.IcyHeaders;
import com.google.android.exoplayer2.source.SampleQueue.UpstreamFormatChangedListener;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
@ -252,7 +252,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override
public long selectTracks(
@NullableType TrackSelection[] selections,
@NullableType ExoTrackSelection[] selections,
boolean[] mayRetainStreamFlags,
@NullableType SampleStream[] streams,
boolean[] streamResetFlags,
@ -277,7 +277,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
// Select new tracks.
for (int i = 0; i < selections.length; i++) {
if (streams[i] == null && selections[i] != null) {
TrackSelection selection = selections[i];
ExoTrackSelection selection = selections[i];
Assertions.checkState(selection.length() == 1);
Assertions.checkState(selection.getIndexInTrackGroup(0) == 0);
int track = tracks.indexOf(selection.getTrackGroup());

View File

@ -22,7 +22,9 @@ import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.drm.DefaultDrmSessionManagerProvider;
import com.google.android.exoplayer2.drm.DrmSessionManager;
import com.google.android.exoplayer2.drm.DrmSessionManagerProvider;
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
@ -51,10 +53,10 @@ public final class ProgressiveMediaSource extends BaseMediaSource
public static final class Factory implements MediaSourceFactory {
private final DataSource.Factory dataSourceFactory;
private final MediaSourceDrmHelper mediaSourceDrmHelper;
private ExtractorsFactory extractorsFactory;
@Nullable private DrmSessionManager drmSessionManager;
private boolean usingCustomDrmSessionManagerProvider;
private DrmSessionManagerProvider drmSessionManagerProvider;
private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private int continueLoadingCheckIntervalBytes;
@Nullable private String customCacheKey;
@ -79,7 +81,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource
public Factory(DataSource.Factory dataSourceFactory, ExtractorsFactory extractorsFactory) {
this.dataSourceFactory = dataSourceFactory;
this.extractorsFactory = extractorsFactory;
mediaSourceDrmHelper = new MediaSourceDrmHelper();
drmSessionManagerProvider = new DefaultDrmSessionManagerProvider();
loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy();
continueLoadingCheckIntervalBytes = DEFAULT_LOADING_CHECK_INTERVAL_BYTES;
}
@ -148,21 +150,42 @@ public final class ProgressiveMediaSource extends BaseMediaSource
}
@Override
public Factory setDrmSessionManagerProvider(
@Nullable DrmSessionManagerProvider drmSessionManagerProvider) {
if (drmSessionManagerProvider != null) {
this.drmSessionManagerProvider = drmSessionManagerProvider;
this.usingCustomDrmSessionManagerProvider = true;
} else {
this.drmSessionManagerProvider = new DefaultDrmSessionManagerProvider();
this.usingCustomDrmSessionManagerProvider = false;
}
return this;
}
public Factory setDrmSessionManager(@Nullable DrmSessionManager drmSessionManager) {
this.drmSessionManager = drmSessionManager;
if (drmSessionManager == null) {
setDrmSessionManagerProvider(null);
} else {
setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager);
}
return this;
}
@Override
public Factory setDrmHttpDataSourceFactory(
@Nullable HttpDataSource.Factory drmHttpDataSourceFactory) {
mediaSourceDrmHelper.setDrmHttpDataSourceFactory(drmHttpDataSourceFactory);
if (!usingCustomDrmSessionManagerProvider) {
((DefaultDrmSessionManagerProvider) drmSessionManagerProvider)
.setDrmHttpDataSourceFactory(drmHttpDataSourceFactory);
}
return this;
}
@Override
public Factory setDrmUserAgent(@Nullable String userAgent) {
mediaSourceDrmHelper.setDrmUserAgent(userAgent);
if (!usingCustomDrmSessionManagerProvider) {
((DefaultDrmSessionManagerProvider) drmSessionManagerProvider).setDrmUserAgent(userAgent);
}
return this;
}
@ -198,7 +221,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource
mediaItem,
dataSourceFactory,
extractorsFactory,
drmSessionManager != null ? drmSessionManager : mediaSourceDrmHelper.create(mediaItem),
drmSessionManagerProvider.get(mediaItem),
loadErrorHandlingPolicy,
continueLoadingCheckIntervalBytes);
}

View File

@ -115,39 +115,25 @@ import java.util.Arrays;
}
/**
* Reads data from the rolling buffer to populate a decoder input buffer.
* Reads data from the rolling buffer to populate a decoder input buffer, and advances the read
* position.
*
* @param buffer The buffer to populate.
* @param extrasHolder The extras holder whose offset should be read and subsequently adjusted.
*/
public void readToBuffer(DecoderInputBuffer buffer, SampleExtrasHolder extrasHolder) {
// Read encryption data if the sample is encrypted.
if (buffer.isEncrypted()) {
readEncryptionData(buffer, extrasHolder);
}
// Read sample data, extracting supplemental data into a separate buffer if needed.
if (buffer.hasSupplementalData()) {
// If there is supplemental data, the sample data is prefixed by its size.
scratch.reset(4);
readData(extrasHolder.offset, scratch.getData(), 4);
int sampleSize = scratch.readUnsignedIntToInt();
extrasHolder.offset += 4;
extrasHolder.size -= 4;
readAllocationNode = readSampleData(readAllocationNode, buffer, extrasHolder, scratch);
}
// Write the sample data.
buffer.ensureSpaceForWrite(sampleSize);
readData(extrasHolder.offset, buffer.data, sampleSize);
extrasHolder.offset += sampleSize;
extrasHolder.size -= sampleSize;
// Write the remaining data as supplemental data.
buffer.resetSupplementalData(extrasHolder.size);
readData(extrasHolder.offset, buffer.supplementalData, extrasHolder.size);
} else {
// Write the sample data.
buffer.ensureSpaceForWrite(extrasHolder.size);
readData(extrasHolder.offset, buffer.data, extrasHolder.size);
}
/**
* Peeks data from the rolling buffer to populate a decoder input buffer, without advancing the
* read position.
*
* @param buffer The buffer to populate.
* @param extrasHolder The extras holder whose offset should be read and subsequently adjusted.
*/
public void peekToBuffer(DecoderInputBuffer buffer, SampleExtrasHolder extrasHolder) {
readSampleData(readAllocationNode, buffer, extrasHolder, scratch);
}
/**
@ -210,151 +196,6 @@ import java.util.Arrays;
// Private methods.
/**
* Reads encryption data for the current sample.
*
* <p>The encryption data is written into {@link DecoderInputBuffer#cryptoInfo}, and {@link
* SampleExtrasHolder#size} is adjusted to subtract the number of bytes that were read. The same
* value is added to {@link SampleExtrasHolder#offset}.
*
* @param buffer The buffer into which the encryption data should be written.
* @param extrasHolder The extras holder whose offset should be read and subsequently adjusted.
*/
private void readEncryptionData(DecoderInputBuffer buffer, SampleExtrasHolder extrasHolder) {
long offset = extrasHolder.offset;
// Read the signal byte.
scratch.reset(1);
readData(offset, scratch.getData(), 1);
offset++;
byte signalByte = scratch.getData()[0];
boolean subsampleEncryption = (signalByte & 0x80) != 0;
int ivSize = signalByte & 0x7F;
// Read the initialization vector.
CryptoInfo cryptoInfo = buffer.cryptoInfo;
if (cryptoInfo.iv == null) {
cryptoInfo.iv = new byte[16];
} else {
// Zero out cryptoInfo.iv so that if ivSize < 16, the remaining bytes are correctly set to 0.
Arrays.fill(cryptoInfo.iv, (byte) 0);
}
readData(offset, cryptoInfo.iv, ivSize);
offset += ivSize;
// Read the subsample count, if present.
int subsampleCount;
if (subsampleEncryption) {
scratch.reset(2);
readData(offset, scratch.getData(), 2);
offset += 2;
subsampleCount = scratch.readUnsignedShort();
} else {
subsampleCount = 1;
}
// Write the clear and encrypted subsample sizes.
@Nullable int[] clearDataSizes = cryptoInfo.numBytesOfClearData;
if (clearDataSizes == null || clearDataSizes.length < subsampleCount) {
clearDataSizes = new int[subsampleCount];
}
@Nullable int[] encryptedDataSizes = cryptoInfo.numBytesOfEncryptedData;
if (encryptedDataSizes == null || encryptedDataSizes.length < subsampleCount) {
encryptedDataSizes = new int[subsampleCount];
}
if (subsampleEncryption) {
int subsampleDataLength = 6 * subsampleCount;
scratch.reset(subsampleDataLength);
readData(offset, scratch.getData(), subsampleDataLength);
offset += subsampleDataLength;
scratch.setPosition(0);
for (int i = 0; i < subsampleCount; i++) {
clearDataSizes[i] = scratch.readUnsignedShort();
encryptedDataSizes[i] = scratch.readUnsignedIntToInt();
}
} else {
clearDataSizes[0] = 0;
encryptedDataSizes[0] = extrasHolder.size - (int) (offset - extrasHolder.offset);
}
// Populate the cryptoInfo.
CryptoData cryptoData = Util.castNonNull(extrasHolder.cryptoData);
cryptoInfo.set(
subsampleCount,
clearDataSizes,
encryptedDataSizes,
cryptoData.encryptionKey,
cryptoInfo.iv,
cryptoData.cryptoMode,
cryptoData.encryptedBlocks,
cryptoData.clearBlocks);
// Adjust the offset and size to take into account the bytes read.
int bytesRead = (int) (offset - extrasHolder.offset);
extrasHolder.offset += bytesRead;
extrasHolder.size -= bytesRead;
}
/**
* Reads data from the front of the rolling buffer.
*
* @param absolutePosition The absolute position from which data should be read.
* @param target The buffer into which data should be written.
* @param length The number of bytes to read.
*/
private void readData(long absolutePosition, ByteBuffer target, int length) {
advanceReadTo(absolutePosition);
int remaining = length;
while (remaining > 0) {
int toCopy = min(remaining, (int) (readAllocationNode.endPosition - absolutePosition));
Allocation allocation = readAllocationNode.allocation;
target.put(allocation.data, readAllocationNode.translateOffset(absolutePosition), toCopy);
remaining -= toCopy;
absolutePosition += toCopy;
if (absolutePosition == readAllocationNode.endPosition) {
readAllocationNode = readAllocationNode.next;
}
}
}
/**
* Reads data from the front of the rolling buffer.
*
* @param absolutePosition The absolute position from which data should be read.
* @param target The array into which data should be written.
* @param length The number of bytes to read.
*/
private void readData(long absolutePosition, byte[] target, int length) {
advanceReadTo(absolutePosition);
int remaining = length;
while (remaining > 0) {
int toCopy = min(remaining, (int) (readAllocationNode.endPosition - absolutePosition));
Allocation allocation = readAllocationNode.allocation;
System.arraycopy(
allocation.data,
readAllocationNode.translateOffset(absolutePosition),
target,
length - remaining,
toCopy);
remaining -= toCopy;
absolutePosition += toCopy;
if (absolutePosition == readAllocationNode.endPosition) {
readAllocationNode = readAllocationNode.next;
}
}
}
/**
* Advances the read position to the specified absolute position.
*
* @param absolutePosition The position to which {@link #readAllocationNode} should be advanced.
*/
private void advanceReadTo(long absolutePosition) {
while (absolutePosition >= readAllocationNode.endPosition) {
readAllocationNode = readAllocationNode.next;
}
}
/**
* Clears allocation nodes starting from {@code fromNode}.
*
@ -409,6 +250,214 @@ import java.util.Arrays;
}
}
/**
* Reads data from the rolling buffer to populate a decoder input buffer.
*
* @param allocationNode The first {@link AllocationNode} containing data yet to be read.
* @param buffer The buffer to populate.
* @param extrasHolder The extras holder whose offset should be read and subsequently adjusted.
* @param scratch A scratch {@link ParsableByteArray}.
* @return The first {@link AllocationNode} that contains unread bytes after the last byte that
* the invocation read.
*/
private static AllocationNode readSampleData(
AllocationNode allocationNode,
DecoderInputBuffer buffer,
SampleExtrasHolder extrasHolder,
ParsableByteArray scratch) {
if (buffer.isEncrypted()) {
allocationNode = readEncryptionData(allocationNode, buffer, extrasHolder, scratch);
}
// Read sample data, extracting supplemental data into a separate buffer if needed.
if (buffer.hasSupplementalData()) {
// If there is supplemental data, the sample data is prefixed by its size.
scratch.reset(4);
allocationNode = readData(allocationNode, extrasHolder.offset, scratch.getData(), 4);
int sampleSize = scratch.readUnsignedIntToInt();
extrasHolder.offset += 4;
extrasHolder.size -= 4;
// Write the sample data.
buffer.ensureSpaceForWrite(sampleSize);
allocationNode = readData(allocationNode, extrasHolder.offset, buffer.data, sampleSize);
extrasHolder.offset += sampleSize;
extrasHolder.size -= sampleSize;
// Write the remaining data as supplemental data.
buffer.resetSupplementalData(extrasHolder.size);
allocationNode =
readData(allocationNode, extrasHolder.offset, buffer.supplementalData, extrasHolder.size);
} else {
// Write the sample data.
buffer.ensureSpaceForWrite(extrasHolder.size);
allocationNode =
readData(allocationNode, extrasHolder.offset, buffer.data, extrasHolder.size);
}
return allocationNode;
}
/**
* Reads encryption data for the sample described by {@code extrasHolder}.
*
* <p>The encryption data is written into {@link DecoderInputBuffer#cryptoInfo}, and {@link
* SampleExtrasHolder#size} is adjusted to subtract the number of bytes that were read. The same
* value is added to {@link SampleExtrasHolder#offset}.
*
* @param allocationNode The first {@link AllocationNode} containing data yet to be read.
* @param buffer The buffer into which the encryption data should be written.
* @param extrasHolder The extras holder whose offset should be read and subsequently adjusted.
* @param scratch A scratch {@link ParsableByteArray}.
* @return The first {@link AllocationNode} that contains unread bytes after this method returns.
*/
private static AllocationNode readEncryptionData(
AllocationNode allocationNode,
DecoderInputBuffer buffer,
SampleExtrasHolder extrasHolder,
ParsableByteArray scratch) {
long offset = extrasHolder.offset;
// Read the signal byte.
scratch.reset(1);
allocationNode = readData(allocationNode, offset, scratch.getData(), 1);
offset++;
byte signalByte = scratch.getData()[0];
boolean subsampleEncryption = (signalByte & 0x80) != 0;
int ivSize = signalByte & 0x7F;
// Read the initialization vector.
CryptoInfo cryptoInfo = buffer.cryptoInfo;
if (cryptoInfo.iv == null) {
cryptoInfo.iv = new byte[16];
} else {
// Zero out cryptoInfo.iv so that if ivSize < 16, the remaining bytes are correctly set to 0.
Arrays.fill(cryptoInfo.iv, (byte) 0);
}
allocationNode = readData(allocationNode, offset, cryptoInfo.iv, ivSize);
offset += ivSize;
// Read the subsample count, if present.
int subsampleCount;
if (subsampleEncryption) {
scratch.reset(2);
allocationNode = readData(allocationNode, offset, scratch.getData(), 2);
offset += 2;
subsampleCount = scratch.readUnsignedShort();
} else {
subsampleCount = 1;
}
// Write the clear and encrypted subsample sizes.
@Nullable int[] clearDataSizes = cryptoInfo.numBytesOfClearData;
if (clearDataSizes == null || clearDataSizes.length < subsampleCount) {
clearDataSizes = new int[subsampleCount];
}
@Nullable int[] encryptedDataSizes = cryptoInfo.numBytesOfEncryptedData;
if (encryptedDataSizes == null || encryptedDataSizes.length < subsampleCount) {
encryptedDataSizes = new int[subsampleCount];
}
if (subsampleEncryption) {
int subsampleDataLength = 6 * subsampleCount;
scratch.reset(subsampleDataLength);
allocationNode = readData(allocationNode, offset, scratch.getData(), subsampleDataLength);
offset += subsampleDataLength;
scratch.setPosition(0);
for (int i = 0; i < subsampleCount; i++) {
clearDataSizes[i] = scratch.readUnsignedShort();
encryptedDataSizes[i] = scratch.readUnsignedIntToInt();
}
} else {
clearDataSizes[0] = 0;
encryptedDataSizes[0] = extrasHolder.size - (int) (offset - extrasHolder.offset);
}
// Populate the cryptoInfo.
CryptoData cryptoData = Util.castNonNull(extrasHolder.cryptoData);
cryptoInfo.set(
subsampleCount,
clearDataSizes,
encryptedDataSizes,
cryptoData.encryptionKey,
cryptoInfo.iv,
cryptoData.cryptoMode,
cryptoData.encryptedBlocks,
cryptoData.clearBlocks);
// Adjust the offset and size to take into account the bytes read.
int bytesRead = (int) (offset - extrasHolder.offset);
extrasHolder.offset += bytesRead;
extrasHolder.size -= bytesRead;
return allocationNode;
}
/**
* Reads data from {@code allocationNode} and its following nodes.
*
* @param allocationNode The first {@link AllocationNode} containing data yet to be read.
* @param absolutePosition The absolute position from which data should be read.
* @param target The buffer into which data should be written.
* @param length The number of bytes to read.
* @return The first {@link AllocationNode} that contains unread bytes after this method returns.
*/
private static AllocationNode readData(
AllocationNode allocationNode, long absolutePosition, ByteBuffer target, int length) {
allocationNode = getNodeContainingPosition(allocationNode, absolutePosition);
int remaining = length;
while (remaining > 0) {
int toCopy = min(remaining, (int) (allocationNode.endPosition - absolutePosition));
Allocation allocation = allocationNode.allocation;
target.put(allocation.data, allocationNode.translateOffset(absolutePosition), toCopy);
remaining -= toCopy;
absolutePosition += toCopy;
if (absolutePosition == allocationNode.endPosition) {
allocationNode = allocationNode.next;
}
}
return allocationNode;
}
/**
* Reads data from {@code allocationNode} and its following nodes.
*
* @param allocationNode The first {@link AllocationNode} containing data yet to be read.
* @param absolutePosition The absolute position from which data should be read.
* @param target The array into which data should be written.
* @param length The number of bytes to read.
* @return The first {@link AllocationNode} that contains unread bytes after this method returns.
*/
private static AllocationNode readData(
AllocationNode allocationNode, long absolutePosition, byte[] target, int length) {
allocationNode = getNodeContainingPosition(allocationNode, absolutePosition);
int remaining = length;
while (remaining > 0) {
int toCopy = min(remaining, (int) (allocationNode.endPosition - absolutePosition));
Allocation allocation = allocationNode.allocation;
System.arraycopy(
allocation.data,
allocationNode.translateOffset(absolutePosition),
target,
length - remaining,
toCopy);
remaining -= toCopy;
absolutePosition += toCopy;
if (absolutePosition == allocationNode.endPosition) {
allocationNode = allocationNode.next;
}
}
return allocationNode;
}
/**
* Returns the {@link AllocationNode} in {@code allocationNode}'s chain which contains the given
* {@code absolutePosition}.
*/
private static AllocationNode getNodeContainingPosition(
AllocationNode allocationNode, long absolutePosition) {
while (absolutePosition >= allocationNode.endPosition) {
allocationNode = allocationNode.next;
}
return allocationNode;
}
/** A node in a linked list of {@link Allocation}s held by the output. */
private static final class AllocationNode {

View File

@ -377,6 +377,20 @@ public class SampleQueue implements TrackOutput {
return mayReadSample(relativeReadIndex);
}
/** Equivalent to {@link #read}, except it never advances the read position. */
public final int peek(
FormatHolder formatHolder,
DecoderInputBuffer buffer,
boolean formatRequired,
boolean loadingFinished) {
int result =
peekSampleMetadata(formatHolder, buffer, formatRequired, loadingFinished, extrasHolder);
if (result == C.RESULT_BUFFER_READ && !buffer.isEndOfStream() && !buffer.isFlagsOnly()) {
sampleDataQueue.peekToBuffer(buffer, extrasHolder);
}
return result;
}
/**
* Attempts to read from the queue.
*

View File

@ -25,7 +25,7 @@ import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.SeekParameters;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Assertions;
@ -194,7 +194,7 @@ public final class SilenceMediaSource extends BaseMediaSource {
@Override
public long selectTracks(
@NullableType TrackSelection[] selections,
@NullableType ExoTrackSelection[] selections,
boolean[] mayRetainStreamFlags,
@NullableType SampleStream[] streams,
boolean[] streamResetFlags,

View File

@ -31,10 +31,7 @@ public final class SinglePeriodTimeline extends Timeline {
private static final Object UID = new Object();
private static final MediaItem MEDIA_ITEM =
new MediaItem.Builder()
.setMediaId("com.google.android.exoplayer2.source.SinglePeriodTimeline")
.setUri(Uri.EMPTY)
.build();
new MediaItem.Builder().setMediaId("SinglePeriodTimeline").setUri(Uri.EMPTY).build();
private final long presentationStartTimeMs;
private final long windowStartTimeMs;

View File

@ -22,7 +22,7 @@ import com.google.android.exoplayer2.FormatHolder;
import com.google.android.exoplayer2.SeekParameters;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
@ -33,6 +33,7 @@ import com.google.android.exoplayer2.upstream.Loader.Loadable;
import com.google.android.exoplayer2.upstream.StatsDataSource;
import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util;
import java.io.IOException;
@ -41,15 +42,13 @@ import java.util.Arrays;
import org.checkerframework.checker.nullness.compatqual.NullableType;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
* A {@link MediaPeriod} with a single sample.
*/
/* package */ final class SingleSampleMediaPeriod implements MediaPeriod,
Loader.Callback<SingleSampleMediaPeriod.SourceLoadable> {
/** A {@link MediaPeriod} with a single sample. */
/* package */ final class SingleSampleMediaPeriod
implements MediaPeriod, Loader.Callback<SingleSampleMediaPeriod.SourceLoadable> {
/**
* The initial size of the allocation used to hold the sample data.
*/
private static final String TAG = "SingleSampleMediaPeriod";
/** The initial size of the allocation used to hold the sample data. */
private static final int INITIAL_SAMPLE_SIZE = 1024;
private final DataSpec dataSpec;
@ -113,7 +112,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override
public long selectTracks(
@NullableType TrackSelection[] selections,
@NullableType ExoTrackSelection[] selections,
boolean[] mayRetainStreamFlags,
@NullableType SampleStream[] streams,
boolean[] streamResetFlags,
@ -294,6 +293,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
LoadErrorAction action;
if (treatLoadErrorsAsEndOfStream && errorCanBePropagated) {
Log.w(TAG, "Loading failed, treating as end-of-stream.", error);
loadingFinished = true;
action = Loader.DONT_RETRY;
} else {
@ -348,8 +348,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
}
@Override
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer,
boolean requireFormat) {
public int readData(
FormatHolder formatHolder, DecoderInputBuffer buffer, boolean requireFormat) {
maybeNotifyDownstreamFormat();
if (streamState == STREAM_STATE_END_OF_STREAM) {
buffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM);

View File

@ -145,6 +145,7 @@ public final class Cea708Decoder extends CeaDecoder {
private final ParsableByteArray ccData;
private final ParsableBitArray serviceBlockPacket;
private int previousSequenceNumber;
// TODO: Use isWideAspectRatio in decoding.
@SuppressWarnings({"unused", "FieldCanBeLocal"})
private final boolean isWideAspectRatio;
@ -162,6 +163,7 @@ public final class Cea708Decoder extends CeaDecoder {
public Cea708Decoder(int accessibilityChannel, @Nullable List<byte[]> initializationData) {
ccData = new ParsableByteArray();
serviceBlockPacket = new ParsableBitArray();
previousSequenceNumber = C.INDEX_UNSET;
selectedServiceNumber = accessibilityChannel == Format.NO_VALUE ? 1 : accessibilityChannel;
isWideAspectRatio =
initializationData != null
@ -231,6 +233,18 @@ public final class Cea708Decoder extends CeaDecoder {
finalizeCurrentPacket();
int sequenceNumber = (ccData1 & 0xC0) >> 6; // first 2 bits
if (previousSequenceNumber != C.INDEX_UNSET
&& sequenceNumber != (previousSequenceNumber + 1) % 4) {
resetCueBuilders();
Log.w(
TAG,
"Sequence number discontinuity. previous="
+ previousSequenceNumber
+ " current="
+ sequenceNumber);
}
previousSequenceNumber = sequenceNumber;
int packetSize = ccData1 & 0x3F; // last 6 bits
if (packetSize == 0) {
packetSize = 64;
@ -270,10 +284,18 @@ public final class Cea708Decoder extends CeaDecoder {
@RequiresNonNull("currentDtvCcPacket")
private void processCurrentPacket() {
if (currentDtvCcPacket.currentIndex != (currentDtvCcPacket.packetSize * 2 - 1)) {
Log.w(TAG, "DtvCcPacket ended prematurely; size is " + (currentDtvCcPacket.packetSize * 2 - 1)
+ ", but current index is " + currentDtvCcPacket.currentIndex + " (sequence number "
+ currentDtvCcPacket.sequenceNumber + "); ignoring packet");
return;
Log.d(
TAG,
"DtvCcPacket ended prematurely; size is "
+ (currentDtvCcPacket.packetSize * 2 - 1)
+ ", but current index is "
+ currentDtvCcPacket.currentIndex
+ " (sequence number "
+ currentDtvCcPacket.sequenceNumber
+ ");");
// We've received cc_type=0x03 (packet start) before receiving packetSize byte pairs of data.
// This might indicate a byte pair has been lost, but we'll still attempt to process the data
// we have received.
}
serviceBlockPacket.reset(currentDtvCcPacket.packetData, currentDtvCcPacket.currentIndex);

View File

@ -19,6 +19,8 @@ import static com.google.android.exoplayer2.text.Cue.LINE_TYPE_FRACTION;
import static com.google.android.exoplayer2.util.Util.castNonNull;
import android.text.Layout;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.text.Cue;
@ -301,7 +303,18 @@ public final class SsaDecoder extends SimpleSubtitleDecoder {
SsaStyle.Overrides styleOverrides,
float screenWidth,
float screenHeight) {
Cue.Builder cue = new Cue.Builder().setText(text);
SpannableString spannableText = new SpannableString(text);
Cue.Builder cue = new Cue.Builder().setText(spannableText);
if (style != null) {
if (style.primaryColor != null) {
spannableText.setSpan(
new ForegroundColorSpan(style.primaryColor),
/* start= */ 0,
/* end= */ spannableText.length(),
SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE);
}
}
@SsaStyle.SsaAlignment int alignment;
if (styleOverrides.alignment != SsaStyle.SSA_ALIGNMENT_UNKNOWN) {

View File

@ -17,16 +17,20 @@
package com.google.android.exoplayer2.text.ssa;
import static com.google.android.exoplayer2.text.ssa.SsaDecoder.STYLE_LINE_PREFIX;
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.graphics.Color;
import android.graphics.PointF;
import android.text.TextUtils;
import androidx.annotation.ColorInt;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util;
import com.google.common.primitives.Ints;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.util.regex.Matcher;
@ -85,15 +89,18 @@ import java.util.regex.Pattern;
public final String name;
@SsaAlignment public final int alignment;
@Nullable @ColorInt public final Integer primaryColor;
private SsaStyle(String name, @SsaAlignment int alignment) {
private SsaStyle(
String name, @SsaAlignment int alignment, @Nullable @ColorInt Integer primaryColor) {
this.name = name;
this.alignment = alignment;
this.primaryColor = primaryColor;
}
@Nullable
public static SsaStyle fromStyleLine(String styleLine, Format format) {
Assertions.checkArgument(styleLine.startsWith(STYLE_LINE_PREFIX));
checkArgument(styleLine.startsWith(STYLE_LINE_PREFIX));
String[] styleValues = TextUtils.split(styleLine.substring(STYLE_LINE_PREFIX.length()), ",");
if (styleValues.length != format.length) {
Log.w(
@ -105,7 +112,9 @@ import java.util.regex.Pattern;
}
try {
return new SsaStyle(
styleValues[format.nameIndex].trim(), parseAlignment(styleValues[format.alignmentIndex]));
styleValues[format.nameIndex].trim(),
parseAlignment(styleValues[format.alignmentIndex].trim()),
parseColor(styleValues[format.primaryColorIndex].trim()));
} catch (RuntimeException e) {
Log.w(TAG, "Skipping malformed 'Style:' line: '" + styleLine + "'", e);
return null;
@ -144,6 +153,44 @@ import java.util.regex.Pattern;
}
}
/**
* Parses a SSA V4+ color expression.
*
* <p>A SSA V4+ color can be represented in hex {@code ("&HAABBGGRR")} or in 64-bit decimal format
* (byte order AABBGGRR). In both cases the alpha channel's value needs to be inverted because in
* SSA the 0xFF alpha value means transparent and 0x00 means opaque which is the opposite from the
* Android {@link ColorInt} representation.
*
* @param ssaColorExpression A SSA V4+ color expression.
* @return The parsed color value, or null if parsing failed.
*/
@Nullable
@ColorInt
public static Integer parseColor(String ssaColorExpression) {
// We use a long because the value is an unsigned 32-bit number, so can be larger than
// Integer.MAX_VALUE.
long abgr;
try {
abgr =
ssaColorExpression.startsWith("&H")
// Parse color from hex format (&HAABBGGRR).
? Long.parseLong(ssaColorExpression.substring(2), /* radix= */ 16)
// Parse color from decimal format (bytes order AABBGGRR).
: Long.parseLong(ssaColorExpression);
// Ensure only the bottom 4 bytes of abgr are set.
checkArgument(abgr <= 0xFFFFFFFFL);
} catch (IllegalArgumentException e) {
Log.w(TAG, "Failed to parse color expression: '" + ssaColorExpression + "'", e);
return null;
}
// Convert ABGR to ARGB.
int a = Ints.checkedCast(((abgr >> 24) & 0xFF) ^ 0xFF); // Flip alpha.
int b = Ints.checkedCast((abgr >> 16) & 0xFF);
int g = Ints.checkedCast((abgr >> 8) & 0xFF);
int r = Ints.checkedCast(abgr & 0xFF);
return Color.argb(a, r, g, b);
}
/**
* Represents a {@code Format:} line from the {@code [V4+ Styles]} section
*
@ -154,11 +201,13 @@ import java.util.regex.Pattern;
public final int nameIndex;
public final int alignmentIndex;
public final int primaryColorIndex;
public final int length;
private Format(int nameIndex, int alignmentIndex, int length) {
private Format(int nameIndex, int alignmentIndex, int primaryColorIndex, int length) {
this.nameIndex = nameIndex;
this.alignmentIndex = alignmentIndex;
this.primaryColorIndex = primaryColorIndex;
this.length = length;
}
@ -171,6 +220,7 @@ import java.util.regex.Pattern;
public static Format fromFormatLine(String styleFormatLine) {
int nameIndex = C.INDEX_UNSET;
int alignmentIndex = C.INDEX_UNSET;
int primaryColorIndex = C.INDEX_UNSET;
String[] keys =
TextUtils.split(styleFormatLine.substring(SsaDecoder.FORMAT_LINE_PREFIX.length()), ",");
for (int i = 0; i < keys.length; i++) {
@ -181,9 +231,14 @@ import java.util.regex.Pattern;
case "alignment":
alignmentIndex = i;
break;
case "primarycolour":
primaryColorIndex = i;
break;
}
}
return nameIndex != C.INDEX_UNSET ? new Format(nameIndex, alignmentIndex, keys.length) : null;
return nameIndex != C.INDEX_UNSET
? new Format(nameIndex, alignmentIndex, primaryColorIndex, keys.length)
: null;
}
}
@ -237,8 +292,7 @@ import java.util.regex.Pattern;
// Ignore invalid \pos() or \move() function.
}
try {
@SsaAlignment
int parsedAlignment = parseAlignmentOverride(braceContents);
@SsaAlignment int parsedAlignment = parseAlignmentOverride(braceContents);
if (parsedAlignment != SSA_ALIGNMENT_UNKNOWN) {
alignment = parsedAlignment;
}

View File

@ -38,13 +38,13 @@ import java.util.List;
import org.checkerframework.checker.nullness.compatqual.NullableType;
/**
* A bandwidth based adaptive {@link TrackSelection}, whose selected track is updated to be the one
* of highest quality given the current network conditions and the state of the buffer.
* A bandwidth based adaptive {@link ExoTrackSelection}, whose selected track is updated to be the
* one of highest quality given the current network conditions and the state of the buffer.
*/
public class AdaptiveTrackSelection extends BaseTrackSelection {
/** Factory for {@link AdaptiveTrackSelection} instances. */
public static class Factory implements TrackSelection.Factory {
public static class Factory implements ExoTrackSelection.Factory {
private final int minDurationForQualityIncreaseMs;
private final int maxDurationForQualityDecreaseMs;
@ -132,14 +132,14 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
}
@Override
public final @NullableType TrackSelection[] createTrackSelections(
public final @NullableType ExoTrackSelection[] createTrackSelections(
@NullableType Definition[] definitions,
BandwidthMeter bandwidthMeter,
MediaPeriodId mediaPeriodId,
Timeline timeline) {
ImmutableList<ImmutableList<AdaptationCheckpoint>> adaptationCheckpoints =
getAdaptationCheckpoints(definitions);
TrackSelection[] selections = new TrackSelection[definitions.length];
ExoTrackSelection[] selections = new ExoTrackSelection[definitions.length];
for (int i = 0; i < definitions.length; i++) {
@Nullable Definition definition = definitions[i];
if (definition == null || definition.tracks.length == 0) {

View File

@ -28,27 +28,17 @@ import com.google.android.exoplayer2.util.Util;
import java.util.Arrays;
import java.util.List;
/**
* An abstract base class suitable for most {@link TrackSelection} implementations.
*/
public abstract class BaseTrackSelection implements TrackSelection {
/** An abstract base class suitable for most {@link ExoTrackSelection} implementations. */
public abstract class BaseTrackSelection implements ExoTrackSelection {
/**
* The selected {@link TrackGroup}.
*/
/** The selected {@link TrackGroup}. */
protected final TrackGroup group;
/**
* The number of selected tracks within the {@link TrackGroup}. Always greater than zero.
*/
/** The number of selected tracks within the {@link TrackGroup}. Always greater than zero. */
protected final int length;
/**
* The indices of the selected tracks in {@link #group}, in order of decreasing bandwidth.
*/
/** The indices of the selected tracks in {@link #group}, in order of decreasing bandwidth. */
protected final int[] tracks;
/**
* The {@link Format}s of the selected tracks, in order of decreasing bandwidth.
*/
/** The {@link Format}s of the selected tracks, in order of decreasing bandwidth. */
private final Format[] formats;
/** Selected track exclusion timestamps, in order of decreasing bandwidth. */
private final long[] excludeUntilTimes;

View File

@ -222,7 +222,6 @@ public class DefaultTrackSelector extends MappingTrackSelector {
*
* @param context Any context.
*/
public ParametersBuilder(Context context) {
super(context);
setInitialValuesWithoutContext();
@ -826,9 +825,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
return this;
}
/**
* Builds a {@link Parameters} instance with the selected values.
*/
/** Builds a {@link Parameters} instance with the selected values. */
public Parameters build() {
return new Parameters(
// Video
@ -1614,6 +1611,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
* dimension).
*/
private static final float FRACTION_TO_CONSIDER_FULLSCREEN = 0.98f;
private static final int[] NO_TRACKS = new int[0];
/** Ordering of two format values. A known value is considered greater than Format#NO_VALUE. */
private static final Ordering<Integer> FORMAT_VALUE_ORDERING =
@ -1625,7 +1623,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
/** Ordering where all elements are equal. */
private static final Ordering<Integer> NO_ORDER = Ordering.from((first, second) -> 0);
private final TrackSelection.Factory trackSelectionFactory;
private final ExoTrackSelection.Factory trackSelectionFactory;
private final AtomicReference<Parameters> parametersReference;
/** @deprecated Use {@link #DefaultTrackSelector(Context)} instead. */
@ -1634,9 +1632,9 @@ public class DefaultTrackSelector extends MappingTrackSelector {
this(Parameters.DEFAULT_WITHOUT_CONTEXT, new AdaptiveTrackSelection.Factory());
}
/** @deprecated Use {@link #DefaultTrackSelector(Context, TrackSelection.Factory)}. */
/** @deprecated Use {@link #DefaultTrackSelector(Context, ExoTrackSelection.Factory)}. */
@Deprecated
public DefaultTrackSelector(TrackSelection.Factory trackSelectionFactory) {
public DefaultTrackSelector(ExoTrackSelection.Factory trackSelectionFactory) {
this(Parameters.DEFAULT_WITHOUT_CONTEXT, trackSelectionFactory);
}
@ -1647,17 +1645,18 @@ public class DefaultTrackSelector extends MappingTrackSelector {
/**
* @param context Any {@link Context}.
* @param trackSelectionFactory A factory for {@link TrackSelection}s.
* @param trackSelectionFactory A factory for {@link ExoTrackSelection}s.
*/
public DefaultTrackSelector(Context context, TrackSelection.Factory trackSelectionFactory) {
public DefaultTrackSelector(Context context, ExoTrackSelection.Factory trackSelectionFactory) {
this(Parameters.getDefaults(context), trackSelectionFactory);
}
/**
* @param parameters Initial {@link Parameters}.
* @param trackSelectionFactory A factory for {@link TrackSelection}s.
* @param trackSelectionFactory A factory for {@link ExoTrackSelection}s.
*/
public DefaultTrackSelector(Parameters parameters, TrackSelection.Factory trackSelectionFactory) {
public DefaultTrackSelector(
Parameters parameters, ExoTrackSelection.Factory trackSelectionFactory) {
this.trackSelectionFactory = trackSelectionFactory;
parametersReference = new AtomicReference<>(parameters);
}
@ -1700,7 +1699,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
// MappingTrackSelector implementation.
@Override
protected final Pair<@NullableType RendererConfiguration[], @NullableType TrackSelection[]>
protected final Pair<@NullableType RendererConfiguration[], @NullableType ExoTrackSelection[]>
selectTracks(
MappedTrackInfo mappedTrackInfo,
@Capabilities int[][][] rendererFormatSupports,
@ -1710,7 +1709,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
throws ExoPlaybackException {
Parameters params = parametersReference.get();
int rendererCount = mappedTrackInfo.getRendererCount();
TrackSelection.@NullableType Definition[] definitions =
ExoTrackSelection.@NullableType Definition[] definitions =
selectAllTracks(
mappedTrackInfo,
rendererFormatSupports,
@ -1729,7 +1728,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
definitions[i] =
override == null
? null
: new TrackSelection.Definition(
: new ExoTrackSelection.Definition(
rendererTrackGroups.get(override.groupIndex),
override.tracks,
override.reason,
@ -1738,14 +1737,14 @@ public class DefaultTrackSelector extends MappingTrackSelector {
}
@NullableType
TrackSelection[] rendererTrackSelections =
ExoTrackSelection[] rendererTrackSelections =
trackSelectionFactory.createTrackSelections(
definitions, getBandwidthMeter(), mediaPeriodId, timeline);
// Initialize the renderer configurations to the default configuration for all renderers with
// selections, and null otherwise.
@NullableType RendererConfiguration[] rendererConfigurations =
new RendererConfiguration[rendererCount];
@NullableType
RendererConfiguration[] rendererConfigurations = new RendererConfiguration[rendererCount];
for (int i = 0; i < rendererCount; i++) {
boolean forceRendererDisabled = params.getRendererDisabled(i);
boolean rendererEnabled =
@ -1779,19 +1778,19 @@ public class DefaultTrackSelector extends MappingTrackSelector {
* renderer, track group and track (in that order).
* @param rendererMixedMimeTypeAdaptationSupports The {@link AdaptiveSupport} for mixed MIME type
* adaptation for the renderer.
* @return The {@link TrackSelection.Definition}s for the renderers. A null entry indicates no
* @return The {@link ExoTrackSelection.Definition}s for the renderers. A null entry indicates no
* selection was made.
* @throws ExoPlaybackException If an error occurs while selecting the tracks.
*/
protected TrackSelection.@NullableType Definition[] selectAllTracks(
protected ExoTrackSelection.@NullableType Definition[] selectAllTracks(
MappedTrackInfo mappedTrackInfo,
@Capabilities int[][][] rendererFormatSupports,
@AdaptiveSupport int[] rendererMixedMimeTypeAdaptationSupports,
Parameters params)
throws ExoPlaybackException {
int rendererCount = mappedTrackInfo.getRendererCount();
TrackSelection.@NullableType Definition[] definitions =
new TrackSelection.Definition[rendererCount];
ExoTrackSelection.@NullableType Definition[] definitions =
new ExoTrackSelection.Definition[rendererCount];
boolean seenVideoRendererWithMappedTracks = false;
boolean selectedVideoTracks = false;
@ -1819,7 +1818,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
boolean enableAdaptiveTrackSelection =
params.allowMultipleAdaptiveSelections || !seenVideoRendererWithMappedTracks;
@Nullable
Pair<TrackSelection.Definition, AudioTrackScore> audioSelection =
Pair<ExoTrackSelection.Definition, AudioTrackScore> audioSelection =
selectAudioTrack(
mappedTrackInfo.getTrackGroups(i),
rendererFormatSupports[i],
@ -1834,7 +1833,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
// score. Clear the selection for that renderer.
definitions[selectedAudioRendererIndex] = null;
}
TrackSelection.Definition definition = audioSelection.first;
ExoTrackSelection.Definition definition = audioSelection.first;
definitions[i] = definition;
// We assume that audio tracks in the same group have matching language.
selectedAudioLanguage = definition.group.getFormat(definition.tracks[0]).language;
@ -1855,7 +1854,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
break;
case C.TRACK_TYPE_TEXT:
@Nullable
Pair<TrackSelection.Definition, TextTrackScore> textSelection =
Pair<ExoTrackSelection.Definition, TextTrackScore> textSelection =
selectTextTrack(
mappedTrackInfo.getTrackGroups(i),
rendererFormatSupports[i],
@ -1889,7 +1888,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
/**
* Called by {@link #selectAllTracks(MappedTrackInfo, int[][][], int[], Parameters)} to create a
* {@link TrackSelection} for a video renderer.
* {@link ExoTrackSelection} for a video renderer.
*
* @param groups The {@link TrackGroupArray} mapped to the renderer.
* @param formatSupport The {@link Capabilities} for each mapped track, indexed by track group and
@ -1898,19 +1897,19 @@ public class DefaultTrackSelector extends MappingTrackSelector {
* adaptation for the renderer.
* @param params The selector's current constraint parameters.
* @param enableAdaptiveTrackSelection Whether adaptive track selection is allowed.
* @return The {@link TrackSelection.Definition} for the renderer, or null if no selection was
* @return The {@link ExoTrackSelection.Definition} for the renderer, or null if no selection was
* made.
* @throws ExoPlaybackException If an error occurs while selecting the tracks.
*/
@Nullable
protected TrackSelection.Definition selectVideoTrack(
protected ExoTrackSelection.Definition selectVideoTrack(
TrackGroupArray groups,
@Capabilities int[][] formatSupport,
@AdaptiveSupport int mixedMimeTypeAdaptationSupports,
Parameters params,
boolean enableAdaptiveTrackSelection)
throws ExoPlaybackException {
TrackSelection.Definition definition = null;
ExoTrackSelection.Definition definition = null;
if (!params.forceHighestSupportedBitrate
&& !params.forceLowestBitrate
&& enableAdaptiveTrackSelection) {
@ -1924,7 +1923,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
}
@Nullable
private static TrackSelection.Definition selectAdaptiveVideoTrack(
private static ExoTrackSelection.Definition selectAdaptiveVideoTrack(
TrackGroupArray groups,
@Capabilities int[][] formatSupport,
@AdaptiveSupport int mixedMimeTypeAdaptationSupports,
@ -1956,7 +1955,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
params.viewportHeight,
params.viewportOrientationMayChange);
if (adaptiveTracks.length > 0) {
return new TrackSelection.Definition(group, adaptiveTracks);
return new ExoTrackSelection.Definition(group, adaptiveTracks);
}
}
return null;
@ -1982,8 +1981,9 @@ public class DefaultTrackSelector extends MappingTrackSelector {
return NO_TRACKS;
}
List<Integer> selectedTrackIndices = getViewportFilteredTrackIndices(group, viewportWidth,
viewportHeight, viewportOrientationMayChange);
List<Integer> selectedTrackIndices =
getViewportFilteredTrackIndices(
group, viewportWidth, viewportHeight, viewportOrientationMayChange);
if (selectedTrackIndices.size() < 2) {
return NO_TRACKS;
}
@ -2140,7 +2140,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
}
@Nullable
private static TrackSelection.Definition selectFixedVideoTrack(
private static ExoTrackSelection.Definition selectFixedVideoTrack(
TrackGroupArray groups, @Capabilities int[][] formatSupport, Parameters params) {
int selectedTrackIndex = C.INDEX_UNSET;
@Nullable TrackGroup selectedGroup = null;
@ -2160,8 +2160,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
// Ignore trick-play tracks for now.
continue;
}
if (isSupported(trackFormatSupport[trackIndex],
params.exceedRendererCapabilitiesIfNecessary)) {
if (isSupported(
trackFormatSupport[trackIndex], params.exceedRendererCapabilitiesIfNecessary)) {
VideoTrackScore trackScore =
new VideoTrackScore(
format,
@ -2183,14 +2183,14 @@ public class DefaultTrackSelector extends MappingTrackSelector {
return selectedGroup == null
? null
: new TrackSelection.Definition(selectedGroup, selectedTrackIndex);
: new ExoTrackSelection.Definition(selectedGroup, selectedTrackIndex);
}
// Audio track selection implementation.
/**
* Called by {@link #selectAllTracks(MappedTrackInfo, int[][][], int[], Parameters)} to create a
* {@link TrackSelection} for an audio renderer.
* {@link ExoTrackSelection} for an audio renderer.
*
* @param groups The {@link TrackGroupArray} mapped to the renderer.
* @param formatSupport The {@link Capabilities} for each mapped track, indexed by track group and
@ -2199,13 +2199,13 @@ public class DefaultTrackSelector extends MappingTrackSelector {
* adaptation for the renderer.
* @param params The selector's current constraint parameters.
* @param enableAdaptiveTrackSelection Whether adaptive track selection is allowed.
* @return The {@link TrackSelection.Definition} and corresponding {@link AudioTrackScore}, or
* @return The {@link ExoTrackSelection.Definition} and corresponding {@link AudioTrackScore}, or
* null if no selection was made.
* @throws ExoPlaybackException If an error occurs while selecting the tracks.
*/
@SuppressWarnings("unused")
@Nullable
protected Pair<TrackSelection.Definition, AudioTrackScore> selectAudioTrack(
protected Pair<ExoTrackSelection.Definition, AudioTrackScore> selectAudioTrack(
TrackGroupArray groups,
@Capabilities int[][] formatSupport,
@AdaptiveSupport int mixedMimeTypeAdaptationSupports,
@ -2219,8 +2219,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
TrackGroup trackGroup = groups.get(groupIndex);
@Capabilities int[] trackFormatSupport = formatSupport[groupIndex];
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
if (isSupported(trackFormatSupport[trackIndex],
params.exceedRendererCapabilitiesIfNecessary)) {
if (isSupported(
trackFormatSupport[trackIndex], params.exceedRendererCapabilitiesIfNecessary)) {
Format format = trackGroup.getFormat(trackIndex);
AudioTrackScore trackScore =
new AudioTrackScore(format, params, trackFormatSupport[trackIndex]);
@ -2243,7 +2243,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
TrackGroup selectedGroup = groups.get(selectedGroupIndex);
TrackSelection.Definition definition = null;
ExoTrackSelection.Definition definition = null;
if (!params.forceHighestSupportedBitrate
&& !params.forceLowestBitrate
&& enableAdaptiveTrackSelection) {
@ -2258,12 +2258,12 @@ public class DefaultTrackSelector extends MappingTrackSelector {
params.allowAudioMixedSampleRateAdaptiveness,
params.allowAudioMixedChannelCountAdaptiveness);
if (adaptiveTracks.length > 1) {
definition = new TrackSelection.Definition(selectedGroup, adaptiveTracks);
definition = new ExoTrackSelection.Definition(selectedGroup, adaptiveTracks);
}
}
if (definition == null) {
// We didn't make an adaptive selection, so make a fixed one instead.
definition = new TrackSelection.Definition(selectedGroup, selectedTrackIndex);
definition = new ExoTrackSelection.Definition(selectedGroup, selectedTrackIndex);
}
return Pair.create(definition, Assertions.checkNotNull(selectedTrackScore));
@ -2322,7 +2322,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
/**
* Called by {@link #selectAllTracks(MappedTrackInfo, int[][][], int[], Parameters)} to create a
* {@link TrackSelection} for a text renderer.
* {@link ExoTrackSelection} for a text renderer.
*
* @param groups The {@link TrackGroupArray} mapped to the renderer.
* @param formatSupport The {@link Capabilities} for each mapped track, indexed by track group and
@ -2330,12 +2330,12 @@ public class DefaultTrackSelector extends MappingTrackSelector {
* @param params The selector's current constraint parameters.
* @param selectedAudioLanguage The language of the selected audio track. May be null if the
* selected text track declares no language or no text track was selected.
* @return The {@link TrackSelection.Definition} and corresponding {@link TextTrackScore}, or null
* if no selection was made.
* @return The {@link ExoTrackSelection.Definition} and corresponding {@link TextTrackScore}, or
* null if no selection was made.
* @throws ExoPlaybackException If an error occurs while selecting the tracks.
*/
@Nullable
protected Pair<TrackSelection.Definition, TextTrackScore> selectTextTrack(
protected Pair<ExoTrackSelection.Definition, TextTrackScore> selectTextTrack(
TrackGroupArray groups,
@Capabilities int[][] formatSupport,
Parameters params,
@ -2348,8 +2348,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
TrackGroup trackGroup = groups.get(groupIndex);
@Capabilities int[] trackFormatSupport = formatSupport[groupIndex];
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
if (isSupported(trackFormatSupport[trackIndex],
params.exceedRendererCapabilitiesIfNecessary)) {
if (isSupported(
trackFormatSupport[trackIndex], params.exceedRendererCapabilitiesIfNecessary)) {
Format format = trackGroup.getFormat(trackIndex);
TextTrackScore trackScore =
new TextTrackScore(
@ -2366,7 +2366,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
return selectedGroup == null
? null
: Pair.create(
new TrackSelection.Definition(selectedGroup, selectedTrackIndex),
new ExoTrackSelection.Definition(selectedGroup, selectedTrackIndex),
Assertions.checkNotNull(selectedTrackScore));
}
@ -2374,18 +2374,18 @@ public class DefaultTrackSelector extends MappingTrackSelector {
/**
* Called by {@link #selectAllTracks(MappedTrackInfo, int[][][], int[], Parameters)} to create a
* {@link TrackSelection} for a renderer whose type is neither video, audio or text.
* {@link ExoTrackSelection} for a renderer whose type is neither video, audio or text.
*
* @param trackType The type of the renderer.
* @param groups The {@link TrackGroupArray} mapped to the renderer.
* @param formatSupport The {@link Capabilities} for each mapped track, indexed by track group and
* track (in that order).
* @param params The selector's current constraint parameters.
* @return The {@link TrackSelection} for the renderer, or null if no selection was made.
* @return The {@link ExoTrackSelection} for the renderer, or null if no selection was made.
* @throws ExoPlaybackException If an error occurs while selecting the tracks.
*/
@Nullable
protected TrackSelection.Definition selectOtherTrack(
protected ExoTrackSelection.Definition selectOtherTrack(
int trackType, TrackGroupArray groups, @Capabilities int[][] formatSupport, Parameters params)
throws ExoPlaybackException {
@Nullable TrackGroup selectedGroup = null;
@ -2395,8 +2395,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
TrackGroup trackGroup = groups.get(groupIndex);
@Capabilities int[] trackFormatSupport = formatSupport[groupIndex];
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
if (isSupported(trackFormatSupport[trackIndex],
params.exceedRendererCapabilitiesIfNecessary)) {
if (isSupported(
trackFormatSupport[trackIndex], params.exceedRendererCapabilitiesIfNecessary)) {
Format format = trackGroup.getFormat(trackIndex);
OtherTrackScore trackScore = new OtherTrackScore(format, trackFormatSupport[trackIndex]);
if (selectedTrackScore == null || trackScore.compareTo(selectedTrackScore) > 0) {
@ -2409,7 +2409,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
}
return selectedGroup == null
? null
: new TrackSelection.Definition(selectedGroup, selectedTrackIndex);
: new ExoTrackSelection.Definition(selectedGroup, selectedTrackIndex);
}
// Utility methods.
@ -2430,7 +2430,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
MappedTrackInfo mappedTrackInfo,
@Capabilities int[][][] renderererFormatSupports,
@NullableType RendererConfiguration[] rendererConfigurations,
@NullableType TrackSelection[] trackSelections) {
@NullableType ExoTrackSelection[] trackSelections) {
// Check whether we can enable tunneling. To enable tunneling we require exactly one audio and
// one video renderer to support tunneling and have a selection.
int tunnelingAudioRendererIndex = -1;
@ -2438,7 +2438,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
boolean enableTunneling = true;
for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) {
int rendererType = mappedTrackInfo.getRendererType(i);
TrackSelection trackSelection = trackSelections[i];
ExoTrackSelection trackSelection = trackSelections[i];
if ((rendererType == C.TRACK_TYPE_AUDIO || rendererType == C.TRACK_TYPE_VIDEO)
&& trackSelection != null) {
if (rendererSupportsTunneling(
@ -2471,16 +2471,18 @@ public class DefaultTrackSelector extends MappingTrackSelector {
}
/**
* Returns whether a renderer supports tunneling for a {@link TrackSelection}.
* Returns whether a renderer supports tunneling for a {@link ExoTrackSelection}.
*
* @param formatSupport The {@link Capabilities} for each track, indexed by group index and track
* index (in that order).
* @param trackGroups The {@link TrackGroupArray}s for the renderer.
* @param selection The track selection.
* @return Whether the renderer supports tunneling for the {@link TrackSelection}.
* @return Whether the renderer supports tunneling for the {@link ExoTrackSelection}.
*/
private static boolean rendererSupportsTunneling(
@Capabilities int[][] formatSupport, TrackGroupArray trackGroups, TrackSelection selection) {
@Capabilities int[][] formatSupport,
TrackGroupArray trackGroups,
ExoTrackSelection selection) {
if (selection == null) {
return false;
}
@ -2565,8 +2567,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
return 0;
}
private static List<Integer> getViewportFilteredTrackIndices(TrackGroup group, int viewportWidth,
int viewportHeight, boolean orientationMayChange) {
private static List<Integer> getViewportFilteredTrackIndices(
TrackGroup group, int viewportWidth, int viewportHeight, boolean orientationMayChange) {
// Initially include all indices.
ArrayList<Integer> selectedTrackIndices = new ArrayList<>(group.length);
for (int i = 0; i < group.length; i++) {
@ -2585,8 +2587,9 @@ public class DefaultTrackSelector extends MappingTrackSelector {
// smallest to exceed the maximum size at which it can be displayed within the viewport.
// We'll discard formats of higher resolution.
if (format.width > 0 && format.height > 0) {
Point maxVideoSizeInViewport = getMaxVideoSizeInViewport(orientationMayChange,
viewportWidth, viewportHeight, format.width, format.height);
Point maxVideoSizeInViewport =
getMaxVideoSizeInViewport(
orientationMayChange, viewportWidth, viewportHeight, format.width, format.height);
int videoPixels = format.width * format.height;
if (format.width >= (int) (maxVideoSizeInViewport.x * FRACTION_TO_CONSIDER_FULLSCREEN)
&& format.height >= (int) (maxVideoSizeInViewport.y * FRACTION_TO_CONSIDER_FULLSCREEN)
@ -2616,8 +2619,12 @@ public class DefaultTrackSelector extends MappingTrackSelector {
* Given viewport dimensions and video dimensions, computes the maximum size of the video as it
* will be rendered to fit inside of the viewport.
*/
private static Point getMaxVideoSizeInViewport(boolean orientationMayChange, int viewportWidth,
int viewportHeight, int videoWidth, int videoHeight) {
private static Point getMaxVideoSizeInViewport(
boolean orientationMayChange,
int viewportWidth,
int viewportHeight,
int videoWidth,
int videoHeight) {
if (orientationMayChange && (videoWidth > videoHeight) != (viewportWidth > viewportHeight)) {
// Rotation is allowed, and the video will be larger in the rotated viewport.
int tempViewportWidth = viewportWidth;

View File

@ -29,15 +29,12 @@ import java.util.List;
import org.checkerframework.checker.nullness.compatqual.NullableType;
/**
* A track selection consisting of a static subset of selected tracks belonging to a {@link
* TrackGroup}, and a possibly varying individual selected track from the subset.
*
* <p>Tracks belonging to the subset are exposed in decreasing bandwidth order. The individual
* selected track may change dynamically as a result of calling {@link #updateSelectedTrack(long,
* long, long, List, MediaChunkIterator[])} or {@link #evaluateQueueSize(long, List)}. This only
* happens between calls to {@link #enable()} and {@link #disable()}.
* A {@link TrackSelection} that can change the individually selected track as a result of calling
* {@link #updateSelectedTrack(long, long, long, List, MediaChunkIterator[])} or {@link
* #evaluateQueueSize(long, List)}. This only happens between calls to {@link #enable()} and {@link
* #disable()}.
*/
public interface TrackSelection {
public interface ExoTrackSelection extends TrackSelection {
/** Contains of a subset of selected tracks belonging to a {@link TrackGroup}. */
final class Definition {
@ -73,7 +70,7 @@ public interface TrackSelection {
}
}
/** Factory for {@link TrackSelection} instances. */
/** Factory for {@link ExoTrackSelection} instances. */
interface Factory {
/**
@ -91,7 +88,7 @@ public interface TrackSelection {
* include null values.
*/
@NullableType
TrackSelection[] createTrackSelections(
ExoTrackSelection[] createTrackSelections(
@NullableType Definition[] definitions,
BandwidthMeter bandwidthMeter,
MediaPeriodId mediaPeriodId,
@ -116,50 +113,6 @@ public interface TrackSelection {
*/
void disable();
/** Returns the {@link TrackGroup} to which the selected tracks belong. */
TrackGroup getTrackGroup();
// Static subset of selected tracks.
/** Returns the number of tracks in the selection. */
int length();
/**
* Returns the format of the track at a given index in the selection.
*
* @param index The index in the selection.
* @return The format of the selected track.
*/
Format getFormat(int index);
/**
* Returns the index in the track group of the track at a given index in the selection.
*
* @param index The index in the selection.
* @return The index of the selected track.
*/
int getIndexInTrackGroup(int index);
/**
* Returns the index in the selection of the track with the specified format. The format is
* located by identity so, for example, {@code selection.indexOf(selection.getFormat(index)) ==
* index} even if multiple selected tracks have formats that contain the same values.
*
* @param format The format.
* @return The index in the selection, or {@link C#INDEX_UNSET} if the track with the specified
* format is not part of the selection.
*/
int indexOf(Format format);
/**
* Returns the index in the selection of the track with the specified index in the track group.
*
* @param indexInTrackGroup The index in the track group.
* @return The index in the selection, or {@link C#INDEX_UNSET} if the track with the specified
* index is not part of the selection.
*/
int indexOf(int indexInTrackGroup);
// Individual selected track.
/** Returns the {@link Format} of the individual selected track. */

View File

@ -43,14 +43,12 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
/**
* Base class for {@link TrackSelector}s that first establish a mapping between {@link TrackGroup}s
* and {@link Renderer}s, and then from that mapping create a {@link TrackSelection} for each
* and {@link Renderer}s, and then from that mapping create a {@link ExoTrackSelection} for each
* renderer.
*/
public abstract class MappingTrackSelector extends TrackSelector {
/**
* Provides mapped track information for each renderer.
*/
/** Provides mapped track information for each renderer. */
public static final class MappedTrackInfo {
/**
@ -401,7 +399,7 @@ public abstract class MappingTrackSelector extends TrackSelector {
rendererFormatSupports,
unmappedTrackGroupArray);
Pair<@NullableType RendererConfiguration[], @NullableType TrackSelection[]> result =
Pair<@NullableType RendererConfiguration[], @NullableType ExoTrackSelection[]> result =
selectTracks(
mappedTrackInfo,
rendererFormatSupports,
@ -428,7 +426,7 @@ public abstract class MappingTrackSelector extends TrackSelector {
* RendererCapabilities#getTrackType()} is {@link C#TRACK_TYPE_NONE}.
* @throws ExoPlaybackException If an error occurs while selecting the tracks.
*/
protected abstract Pair<@NullableType RendererConfiguration[], @NullableType TrackSelection[]>
protected abstract Pair<@NullableType RendererConfiguration[], @NullableType ExoTrackSelection[]>
selectTracks(
MappedTrackInfo mappedTrackInfo,
@Capabilities int[][][] rendererFormatSupports,
@ -538,5 +536,4 @@ public abstract class MappingTrackSelector extends TrackSelector {
}
return mixedMimeTypeAdaptationSupport;
}
}

View File

@ -28,15 +28,11 @@ import java.util.List;
import java.util.Random;
import org.checkerframework.checker.nullness.compatqual.NullableType;
/**
* A {@link TrackSelection} whose selected track is updated randomly.
*/
/** An {@link ExoTrackSelection} whose selected track is updated randomly. */
public final class RandomTrackSelection extends BaseTrackSelection {
/**
* Factory for {@link RandomTrackSelection} instances.
*/
public static final class Factory implements TrackSelection.Factory {
/** Factory for {@link RandomTrackSelection} instances. */
public static final class Factory implements ExoTrackSelection.Factory {
private final Random random;
@ -44,15 +40,13 @@ public final class RandomTrackSelection extends BaseTrackSelection {
random = new Random();
}
/**
* @param seed A seed for the {@link Random} instance used by the factory.
*/
/** @param seed A seed for the {@link Random} instance used by the factory. */
public Factory(int seed) {
random = new Random(seed);
}
@Override
public @NullableType TrackSelection[] createTrackSelections(
public @NullableType ExoTrackSelection[] createTrackSelections(
@NullableType Definition[] definitions,
BandwidthMeter bandwidthMeter,
MediaPeriodId mediaPeriodId,
@ -144,5 +138,4 @@ public final class RandomTrackSelection extends BaseTrackSelection {
public Object getSelectionData() {
return null;
}
}

View File

@ -18,7 +18,7 @@ package com.google.android.exoplayer2.trackselection;
import androidx.annotation.Nullable;
import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride;
import com.google.android.exoplayer2.trackselection.TrackSelection.Definition;
import com.google.android.exoplayer2.trackselection.ExoTrackSelection.Definition;
import org.checkerframework.checker.nullness.compatqual.NullableType;
/** Track selection related utility methods. */
@ -35,7 +35,7 @@ public final class TrackSelectionUtil {
* @param trackSelectionDefinition A {@link Definition} for the track selection.
* @return The created track selection.
*/
TrackSelection createAdaptiveTrackSelection(Definition trackSelectionDefinition);
ExoTrackSelection createAdaptiveTrackSelection(Definition trackSelectionDefinition);
}
/**
@ -48,10 +48,10 @@ public final class TrackSelectionUtil {
* @return The array of created track selection. For null entries in {@code definitions} returns
* null values.
*/
public static @NullableType TrackSelection[] createTrackSelectionsForDefinitions(
public static @NullableType ExoTrackSelection[] createTrackSelectionsForDefinitions(
@NullableType Definition[] definitions,
AdaptiveTrackSelectionFactory adaptiveTrackSelectionFactory) {
TrackSelection[] selections = new TrackSelection[definitions.length];
ExoTrackSelection[] selections = new ExoTrackSelection[definitions.length];
boolean createdAdaptiveTrackSelection = false;
for (int i = 0; i < definitions.length; i++) {
Definition definition = definitions[i];

View File

@ -83,7 +83,7 @@ import com.google.android.exoplayer2.util.Assertions;
* thread. The track selector may call {@link InvalidationListener#onTrackSelectionsInvalidated()}
* from any thread.
*/
public abstract class TrackSelector implements TrackSelectorInterface {
public abstract class TrackSelector {
/**
* Notified when selections previously made by a {@link TrackSelector} are no longer valid.

View File

@ -20,9 +20,7 @@ import com.google.android.exoplayer2.RendererConfiguration;
import com.google.android.exoplayer2.util.Util;
import org.checkerframework.checker.nullness.compatqual.NullableType;
/**
* The result of a {@link TrackSelector} operation.
*/
/** The result of a {@link TrackSelector} operation. */
public final class TrackSelectorResult {
/** The number of selections in the result. Greater than or equal to zero. */
@ -32,10 +30,8 @@ public final class TrackSelectorResult {
* renderer should be disabled.
*/
public final @NullableType RendererConfiguration[] rendererConfigurations;
/**
* A {@link TrackSelectionArray} containing the track selection for each renderer.
*/
public final TrackSelectionArray selections;
/** A {@link ExoTrackSelection} array containing the track selection for each renderer. */
public final @NullableType ExoTrackSelection[] selections;
/**
* An opaque object that will be returned to {@link TrackSelector#onSelectionActivated(Object)}
* should the selections be activated.
@ -45,17 +41,17 @@ public final class TrackSelectorResult {
/**
* @param rendererConfigurations A {@link RendererConfiguration} for each renderer. A null entry
* indicates the corresponding renderer should be disabled.
* @param selections A {@link TrackSelectionArray} containing the selection for each renderer.
* @param selections A {@link ExoTrackSelection} array containing the selection for each renderer.
* @param info An opaque object that will be returned to {@link
* TrackSelector#onSelectionActivated(Object)} should the selection be activated. May be
* {@code null}.
*/
public TrackSelectorResult(
@NullableType RendererConfiguration[] rendererConfigurations,
@NullableType TrackSelection[] selections,
@NullableType ExoTrackSelection[] selections,
@Nullable Object info) {
this.rendererConfigurations = rendererConfigurations;
this.selections = new TrackSelectionArray(selections);
this.selections = selections.clone();
this.info = info;
length = rendererConfigurations.length;
}
@ -100,7 +96,6 @@ public final class TrackSelectorResult {
return false;
}
return Util.areEqual(rendererConfigurations[index], other.rendererConfigurations[index])
&& Util.areEqual(selections.get(index), other.selections.get(index));
&& Util.areEqual(selections[index], other.selections[index]);
}
}

View File

@ -35,7 +35,7 @@ public interface BandwidthMeter {
* changed.
*
* <p>Note: The estimated bitrate is typically derived from more information than just {@code
* bytes} and {@code elapsedMs}.
* bytesTransferred} and {@code elapsedMs}.
*
* @param elapsedMs The time taken to transfer {@code bytesTransferred}, in milliseconds. This
* is at most the elapsed time since the last callback, but may be less if there were

Some files were not shown because too many files have changed in this diff Show More