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) ### 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: * Core library:
* Remove long deprecated symbols: * Remove long deprecated symbols:
* `AdaptiveMediaSourceEventListener`. Use `MediaSourceEventListener` * `AdaptiveMediaSourceEventListener`. Use `MediaSourceEventListener`
@ -80,8 +92,8 @@
`MediaSourceEventListener` and `SingleSampleMediaSource.Factory` `MediaSourceEventListener` and `SingleSampleMediaSource.Factory`
* `SimpleExoPlayer.addVideoDebugListener`, * `SimpleExoPlayer.addVideoDebugListener`,
`SimpleExoPlayer.removeVideoDebugListener`, `SimpleExoPlayer.removeVideoDebugListener`,
`SimpleExoPlayer.addAudioDebugListener` `SimpleExoPlayer.addAudioDebugListener` and
and `SimpleExoPlayer.removeAudioDebugListener`. Use `SimpleExoPlayer.removeAudioDebugListener`. Use
`SimpleExoPlayer.addAnalyticsListener` and `SimpleExoPlayer.addAnalyticsListener` and
`SimpleExoPlayer.removeAnalyticsListener` instead. `SimpleExoPlayer.removeAnalyticsListener` instead.
* `AdaptiveMediaSourceEventListener`. Use `MediaSourceEventListener` * `AdaptiveMediaSourceEventListener`. Use `MediaSourceEventListener`
@ -126,16 +138,18 @@
* Add option to `MergingMediaSource` to clip the durations of all sources * Add option to `MergingMediaSource` to clip the durations of all sources
to have the same length to have the same length
([#8422](https://github.com/google/ExoPlayer/issues/8422)). ([#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 `ExoPlaybackException.OutOfMemoryError`.
* Remove `setVideoDecoderOutputBufferRenderer` from Player API. Clients * Remove `setVideoDecoderOutputBufferRenderer` from Player API. Clients
should use `setOutputSurface` directly instead. should use `setOutputSurface` directly instead.
* Default `SingleSampleMediaSource.treatLoadErrorsAsEndOfStream` to `true` * Default `SingleSampleMediaSource.treatLoadErrorsAsEndOfStream` to `true`
([#8430](https://github.com/google/ExoPlayer/issues/8430)). ([#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: * Extractors:
* Populate codecs string for H.264/AVC in MP4, Matroska and FLV streams to * Populate codecs string for H.264/AVC in MP4, Matroska and FLV streams to
allow decoder capability checks based on codec profile/level allow decoder capability checks based on codec profile/level
@ -145,7 +159,10 @@
([#8393](https://github.com/google/ExoPlayer/issues/8393)). ([#8393](https://github.com/google/ExoPlayer/issues/8393)).
* Handle sample size mismatches between raw audio `stsd` information and * Handle sample size mismatches between raw audio `stsd` information and
`stsz` fixed sample size in MP4 extractors. `stsz` fixed sample size in MP4 extractors.
* Add support for playing JPEG motion photos
([#5405](https://github.com/google/ExoPlayer/issues/5405)).
* Track selection: * Track selection:
* Moved `Player.getTrackSelector` to the `ExoPlayer` interface.
* Allow parallel adaptation for video and audio * Allow parallel adaptation for video and audio
([#5111](https://github.com/google/ExoPlayer/issues/5111)). ([#5111](https://github.com/google/ExoPlayer/issues/5111)).
* Simplified enabling tunneling with `DefaultTrackSelector`. * Simplified enabling tunneling with `DefaultTrackSelector`.
@ -157,6 +174,10 @@
([#8320](https://github.com/google/ExoPlayer/issues/8320)). ([#8320](https://github.com/google/ExoPlayer/issues/8320)).
* Add option to specify preferred audio role flags. * Add option to specify preferred audio role flags.
* Forward `Timeline` and `MediaPeriodId` to `TrackSelection.Factory`. * 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: * DASH:
* Support low-latency DASH playback (`availabilityTimeOffset` and * Support low-latency DASH playback (`availabilityTimeOffset` and
`ServiceDescription` tags) `ServiceDescription` tags)
@ -181,12 +202,24 @@
Widevine or Clearkey protected content in a playlist. Widevine or Clearkey protected content in a playlist.
* Add `ExoMediaDrm.KeyRequest.getRequestType` * Add `ExoMediaDrm.KeyRequest.getRequestType`
([#7847](https://github.com/google/ExoPlayer/issues/7847)). ([#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: * Analytics:
* Pass a `DecoderReuseEvaluation` to `AnalyticsListener`'s * Pass a `DecoderReuseEvaluation` to `AnalyticsListener`'s
`onVideoInputFormatChanged` and `onAudioInputFormatChanged` methods. The `onVideoInputFormatChanged` and `onAudioInputFormatChanged` methods. The
`DecoderReuseEvaluation` indicates whether it was possible to re-use an `DecoderReuseEvaluation` indicates whether it was possible to re-use an
existing decoder instance for the new format, and if not then the existing decoder instance for the new format, and if not then the
reasons why. 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: * Audio:
* Fix handling of audio session IDs * Fix handling of audio session IDs
([#8190](https://github.com/google/ExoPlayer/issues/8190)). ([#8190](https://github.com/google/ExoPlayer/issues/8190)).
@ -199,10 +232,13 @@
`onAudioSessionIdChanged` is called in fewer cases than `onAudioSessionIdChanged` is called in fewer cases than
`onAudioSessionId` was called, due to the improved handling of audio `onAudioSessionId` was called, due to the improved handling of audio
session IDs as described above. 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: * Text:
* Gracefully handle null-terminated subtitle content in Matroska * Fix CEA-708 sequence number discontinuity handling
containers. ([#1807](https://github.com/google/ExoPlayer/issues/1807)).
* Fix CEA-708 anchor positioning * Fix CEA-708 handling of unexpectedly small packets
([#1807](https://github.com/google/ExoPlayer/issues/1807)). ([#1807](https://github.com/google/ExoPlayer/issues/1807)).
* Data sources: * Data sources:
* Use the user agent of the underlying network stack by default. * Use the user agent of the underlying network stack by default.
@ -217,9 +253,17 @@
ad view group ad view group
([#7344](https://github.com/google/ExoPlayer/issues/7344)), ([#7344](https://github.com/google/ExoPlayer/issues/7344)),
([#8339](https://github.com/google/ExoPlayer/issues/8339)). ([#8339](https://github.com/google/ExoPlayer/issues/8339)).
* Fix a bug that could cause the next content position played after a * Fix a bug that could cause the next content position played after a seek
seek to snap back to the cue point of the preceding ad, rather than to snap back to the cue point of the preceding ad, rather than the
the requested content position. 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: * FFmpeg extension:
* Link the FFmpeg library statically, saving 350KB in binary size on * Link the FFmpeg library statically, saving 350KB in binary size on
average. average.
@ -300,7 +344,6 @@
* Support enabling the previous and next actions individually in * Support enabling the previous and next actions individually in
`PlayerNotificationManager`. `PlayerNotificationManager`.
* Audio: * Audio:
* Retry playback after some types of `AudioTrack` error.
* Work around `AudioManager` crashes when calling `getStreamVolume` * Work around `AudioManager` crashes when calling `getStreamVolume`
([#8191](https://github.com/google/ExoPlayer/issues/8191)). ([#8191](https://github.com/google/ExoPlayer/issues/8191)).
* Extractors: * Extractors:

View file

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

View file

@ -28,6 +28,7 @@ include modulePrefix + 'library-dash'
include modulePrefix + 'library-extractor' include modulePrefix + 'library-extractor'
include modulePrefix + 'library-hls' include modulePrefix + 'library-hls'
include modulePrefix + 'library-smoothstreaming' include modulePrefix + 'library-smoothstreaming'
include modulePrefix + 'library-transformer'
include modulePrefix + 'library-ui' include modulePrefix + 'library-ui'
include modulePrefix + 'robolectricutils' include modulePrefix + 'robolectricutils'
include modulePrefix + 'testutils' 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-extractor').projectDir = new File(rootDir, 'library/extractor')
project(modulePrefix + 'library-hls').projectDir = new File(rootDir, 'library/hls') project(modulePrefix + 'library-hls').projectDir = new File(rootDir, 'library/hls')
project(modulePrefix + 'library-smoothstreaming').projectDir = new File(rootDir, 'library/smoothstreaming') 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 + 'library-ui').projectDir = new File(rootDir, 'library/ui')
project(modulePrefix + 'robolectricutils').projectDir = new File(rootDir, 'robolectricutils') project(modulePrefix + 'robolectricutils').projectDir = new File(rootDir, 'robolectricutils')
project(modulePrefix + 'testutils').projectDir = new File(rootDir, 'testutils') 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) .setUuidAndExoMediaDrmProvider(drmSchemeUuid, FrameworkMediaDrm.DEFAULT_PROVIDER)
.build(drmCallback); .build(drmCallback);
} else { } else {
drmSessionManager = DrmSessionManager.getDummyDrmSessionManager(); drmSessionManager = DrmSessionManager.DRM_UNSUPPORTED;
} }
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(this); DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(this);

View file

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

View file

@ -16,7 +16,7 @@ apply from: "$gradle.ext.exoplayerSettingsDir/common_library_config.gradle"
dependencies { dependencies {
api 'com.google.android.gms:play-services-cast-framework:18.1.0' api 'com.google.android.gms:play-services-cast-framework:18.1.0'
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'library-common')
implementation project(modulePrefix + 'library-ui') implementation project(modulePrefix + 'library-ui')
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkCompatVersion 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.metadata.Metadata;
import com.google.android.exoplayer2.source.TrackGroup; import com.google.android.exoplayer2.source.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray; 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.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray; 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.Assertions;
import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.ListenerSet; import com.google.android.exoplayer2.util.ListenerSet;
import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
@ -138,6 +137,7 @@ public final class CastPlayer extends BasePlayer {
listeners = listeners =
new ListenerSet<>( new ListenerSet<>(
Looper.getMainLooper(), Looper.getMainLooper(),
Clock.DEFAULT,
Player.Events::new, Player.Events::new,
(listener, eventFlags) -> listener.onEvents(/* player= */ this, eventFlags)); (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 @Override
public void setRepeatMode(@RepeatMode int repeatMode) { public void setRepeatMode(@RepeatMode int repeatMode) {
if (remoteMediaClient == null) { if (remoteMediaClient == null) {
@ -771,7 +765,7 @@ public final class CastPlayer extends BasePlayer {
int rendererIndex = getRendererIndexForTrackType(trackType); int rendererIndex = getRendererIndexForTrackType(trackType);
if (isTrackActive(id, activeTrackIds) && rendererIndex != C.INDEX_UNSET if (isTrackActive(id, activeTrackIds) && rendererIndex != C.INDEX_UNSET
&& trackSelections[rendererIndex] == null) { && trackSelections[rendererIndex] == null) {
trackSelections[rendererIndex] = new FixedTrackSelection(trackGroups[i], 0); trackSelections[rendererIndex] = new CastTrackSelection(trackGroups[i]);
} }
} }
TrackGroupArray newTrackGroups = new TrackGroupArray(trackGroups); 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. // limitations under the License.
apply from: "$gradle.ext.exoplayerSettingsDir/common_library_config.gradle" apply from: "$gradle.ext.exoplayerSettingsDir/common_library_config.gradle"
android {
defaultConfig {
multiDexEnabled true
}
}
dependencies { dependencies {
api "com.google.android.gms:play-services-cronet:17.0.0" 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 implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion 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 + 'library')
testImplementation project(modulePrefix + 'testutils') testImplementation project(modulePrefix + 'testutils')
testImplementation 'com.squareup.okhttp3:mockwebserver:' + mockWebServerVersion 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 com.google.android.exoplayer2.util.Util.castNonNull;
import static java.lang.Math.max; import static java.lang.Math.max;
import static java.lang.Math.min;
import android.net.Uri; import android.net.Uri;
import android.text.TextUtils; 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.Log;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.common.base.Predicate; import com.google.common.base.Predicate;
import com.google.common.primitives.Ints;
import java.io.IOException; import java.io.IOException;
import java.io.InterruptedIOException; import java.io.InterruptedIOException;
import java.net.SocketTimeoutException; import java.net.SocketTimeoutException;
@ -655,14 +655,21 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
readBuffer.flip(); readBuffer.flip();
Assertions.checkState(readBuffer.hasRemaining()); Assertions.checkState(readBuffer.hasRemaining());
if (bytesToSkip > 0) { if (bytesToSkip > 0) {
int bytesSkipped = (int) min(readBuffer.remaining(), bytesToSkip); int bytesSkipped = (int) Math.min(readBuffer.remaining(), bytesToSkip);
readBuffer.position(readBuffer.position() + bytesSkipped); readBuffer.position(readBuffer.position() + bytesSkipped);
bytesToSkip -= 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); readBuffer.get(buffer, offset, bytesRead);
if (bytesRemaining != C.LENGTH_UNSET) { 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. // Copy as much as possible from the src buffer into dst buffer.
// Returns the number of bytes copied. // Returns the number of bytes copied.
private static int copyByteBuffer(ByteBuffer src, ByteBuffer dst) { 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(); int limit = src.limit();
src.limit(src.position() + remaining); src.limit(src.position() + remaining);
dst.put(src); dst.put(src);

View file

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

View file

@ -451,25 +451,10 @@ import java.util.Map;
return; return;
} }
if (playbackState == Player.STATE_BUFFERING && !player.isPlayingAd()) { if (playbackState == Player.STATE_BUFFERING
// Check whether we are waiting for an ad to preload. && !player.isPlayingAd()
int adGroupIndex = getLoadingAdGroupIndex(); && isWaitingForAdToLoad()) {
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(); waitingForPreloadElapsedRealtimeMs = SystemClock.elapsedRealtime();
}
} else if (playbackState == Player.STATE_READY) { } else if (playbackState == Player.STATE_READY) {
waitingForPreloadElapsedRealtimeMs = C.TIME_UNSET; waitingForPreloadElapsedRealtimeMs = C.TIME_UNSET;
} }
@ -759,25 +744,33 @@ import java.util.Map;
if (imaAdInfo != null) { if (imaAdInfo != null) {
adPlaybackState = adPlaybackState.withSkippedAdGroup(imaAdInfo.adGroupIndex); adPlaybackState = adPlaybackState.withSkippedAdGroup(imaAdInfo.adGroupIndex);
updateAdPlaybackState(); 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) { private void handlePlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) {
@ -1305,6 +1298,12 @@ import java.util.Map;
handleAdGroupLoadError(new IOException("Ad preloading timed out")); handleAdGroupLoadError(new IOException("Ad preloading timed out"));
maybeNotifyPendingAdLoadError(); 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; return videoProgressUpdate;

View file

@ -60,7 +60,6 @@ import java.util.Collection;
import java.util.Collections; import java.util.Collections;
import java.util.HashMap; import java.util.HashMap;
import java.util.List; import java.util.List;
import java.util.Locale;
import java.util.Set; import java.util.Set;
/** /**
@ -700,7 +699,7 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
@Override @Override
public ImaSdkSettings createImaSdkSettings() { public ImaSdkSettings createImaSdkSettings() {
ImaSdkSettings settings = ImaSdkFactory.getInstance().createImaSdkSettings(); ImaSdkSettings settings = ImaSdkFactory.getInstance().createImaSdkSettings();
settings.setLanguage(getImaLanguageCodeForDefaultLocale()); settings.setLanguage(Util.getSystemLanguageCodes()[0]);
return settings; return settings;
} }
@ -742,17 +741,5 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
return ImaSdkFactory.getInstance() return ImaSdkFactory.getInstance()
.createAdsLoader(context, imaSdkSettings, adDisplayContainer); .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.Timeline;
import com.google.android.exoplayer2.testutil.StubExoPlayer; import com.google.android.exoplayer2.testutil.StubExoPlayer;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray; import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
import com.google.android.exoplayer2.util.Clock;
import com.google.android.exoplayer2.util.ListenerSet; import com.google.android.exoplayer2.util.ListenerSet;
/** A fake player for testing content/ad playback. */ /** A fake player for testing content/ad playback. */
@ -43,6 +44,7 @@ import com.google.android.exoplayer2.util.ListenerSet;
listeners = listeners =
new ListenerSet<>( new ListenerSet<>(
Looper.getMainLooper(), Looper.getMainLooper(),
Clock.DEFAULT,
Player.Events::new, Player.Events::new,
(listener, eventFlags) -> listener.onEvents(/* player= */ this, eventFlags)); (listener, eventFlags) -> listener.onEvents(/* player= */ this, eventFlags));
period = new Timeline.Period(); period = new Timeline.Period();

View file

@ -450,6 +450,62 @@ public final class ImaAdsLoaderTest {
.withAdLoadError(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0)); .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 @Test
public void bufferingDuringAd_callsOnBuffering() { public void bufferingDuringAd_callsOnBuffering() {
// Load the preroll ad. // Load the preroll ad.

View file

@ -14,7 +14,7 @@
apply from: "$gradle.ext.exoplayerSettingsDir/common_library_config.gradle" apply from: "$gradle.ext.exoplayerSettingsDir/common_library_config.gradle"
dependencies { dependencies {
implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'library-common')
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion 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" apply from: "$gradle.ext.exoplayerSettingsDir/common_library_config.gradle"
dependencies { dependencies {
implementation project(modulePrefix + 'library-core') implementation project(modulePrefix + 'library-common')
implementation 'net.butterflytv.utils:rtmp-client:3.1.0' implementation 'net.butterflytv.utils:rtmp-client:3.1.0'
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
testImplementation project(modulePrefix + 'library-core')
testImplementation project(modulePrefix + 'testutils') testImplementation project(modulePrefix + 'testutils')
testImplementation 'org.robolectric:robolectric:' + robolectricVersion testImplementation 'org.robolectric:robolectric:' + robolectricVersion
} }

View file

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

View file

@ -1080,23 +1080,6 @@ public final class C {
/** Indicates the track is intended for trick play. */ /** Indicates the track is intended for trick play. */
public static final int ROLE_FLAG_TRICK_PLAY = 1 << 14; 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 * Level of renderer support for a format. One of {@link #FORMAT_HANDLED}, {@link
* #FORMAT_EXCEEDS_CAPABILITIES}, {@link #FORMAT_UNSUPPORTED_DRM}, {@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". */ /** The version of the library expressed as a string, for example "1.2.3". */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa. // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
public static final String VERSION = "2.12.3"; public static final String VERSION = "2.13.0";
/** The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */ /** The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
public static final String VERSION_SLASHY = "ExoPlayerLib/2.12.3"; public static final String VERSION_SLASHY = "ExoPlayerLib/2.13.0";
/** /**
* The version of the library expressed as an integer, for example 1002003. * 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). * integer version 123045006 (123-045-006).
*/ */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa. // Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
public static final int VERSION_INT = 2012003; public static final int VERSION_INT = 2013000;
/** /**
* The default user agent for requests made by the library. * 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.Cue;
import com.google.android.exoplayer2.text.TextOutput; import com.google.android.exoplayer2.text.TextOutput;
import com.google.android.exoplayer2.trackselection.TrackSelectionArray; 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.MutableFlags;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.android.exoplayer2.video.VideoFrameMetadataListener; import com.google.android.exoplayer2.video.VideoFrameMetadataListener;
@ -618,11 +617,14 @@ public interface Player {
default void onSeekProcessed() {} 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)}. * ExoPlayer#experimentalSetOffloadSchedulingEnabled(boolean)}.
* *
* <p>This method is experimental, and will be renamed or removed in a future release. * <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) {} default void onExperimentalOffloadSchedulingEnabledChanged(boolean offloadSchedulingEnabled) {}
/** /**
@ -740,9 +742,7 @@ public interface Player {
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@IntDef({STATE_IDLE, STATE_BUFFERING, STATE_READY, STATE_ENDED}) @IntDef({STATE_IDLE, STATE_BUFFERING, STATE_READY, STATE_ENDED})
@interface State {} @interface State {}
/** /** The player does not have any media to play. */
* The player does not have any media to play.
*/
int STATE_IDLE = 1; int STATE_IDLE = 1;
/** /**
* The player is not able to immediately play from its current position. This state typically * 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. * {@link #getPlayWhenReady()} is true, and paused otherwise.
*/ */
int STATE_READY = 3; int STATE_READY = 3;
/** /** The player has finished playing the media. */
* The player has finished playing the media.
*/
int STATE_ENDED = 4; 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 * 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. * 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" * 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 * 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. * 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 * 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 * {@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 * first window will move to the last window, and "Next" when playing the last window will move to
* the first window. * 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}, * Reasons for position discontinuities. One of {@link #DISCONTINUITY_REASON_PERIOD_TRANSITION},
@ -1218,7 +1216,8 @@ public interface Player {
* *
* @return The current repeat mode. * @return The current repeat mode.
*/ */
@RepeatMode int getRepeatMode(); @RepeatMode
int getRepeatMode();
/** /**
* Sets whether shuffling of windows is enabled. * Sets whether shuffling of windows is enabled.
@ -1227,9 +1226,7 @@ public interface Player {
*/ */
void setShuffleModeEnabled(boolean shuffleModeEnabled); void setShuffleModeEnabled(boolean shuffleModeEnabled);
/** /** Returns whether shuffling of windows is enabled. */
* Returns whether shuffling of windows is enabled.
*/
boolean getShuffleModeEnabled(); boolean getShuffleModeEnabled();
/** /**
@ -1364,9 +1361,7 @@ public interface Player {
*/ */
void release(); void release();
/** /** Returns the number of renderers. */
* Returns the number of renderers.
*/
int getRendererCount(); int getRendererCount();
/** /**
@ -1380,12 +1375,6 @@ public interface Player {
*/ */
int getRendererType(int index); 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. */ /** Returns the available track groups. */
TrackGroupArray getCurrentTrackGroups(); TrackGroupArray getCurrentTrackGroups();
@ -1411,14 +1400,10 @@ public interface Player {
@Nullable @Nullable
Object getCurrentManifest(); 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(); Timeline getCurrentTimeline();
/** /** Returns the index of the period currently being played. */
* Returns the index of the period currently being played.
*/
int getCurrentPeriodIndex(); int getCurrentPeriodIndex();
/** /**
@ -1538,9 +1523,7 @@ public interface Player {
*/ */
boolean isCurrentWindowSeekable(); boolean isCurrentWindowSeekable();
/** /** Returns whether the player is currently playing an ad. */
* Returns whether the player is currently playing an ad.
*/
boolean isPlayingAd(); 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. * @return The index of the next window, or {@link C#INDEX_UNSET} if this is the last window.
*/ */
public int getNextWindowIndex( public int getNextWindowIndex(
int windowIndex, @C.RepeatMode int repeatMode, boolean shuffleModeEnabled) { int windowIndex, @Player.RepeatMode int repeatMode, boolean shuffleModeEnabled) {
switch (repeatMode) { switch (repeatMode) {
case C.REPEAT_MODE_OFF: case Player.REPEAT_MODE_OFF:
return windowIndex == getLastWindowIndex(shuffleModeEnabled) ? C.INDEX_UNSET return windowIndex == getLastWindowIndex(shuffleModeEnabled) ? C.INDEX_UNSET
: windowIndex + 1; : windowIndex + 1;
case C.REPEAT_MODE_ONE: case Player.REPEAT_MODE_ONE:
return windowIndex; return windowIndex;
case C.REPEAT_MODE_ALL: case Player.REPEAT_MODE_ALL:
return windowIndex == getLastWindowIndex(shuffleModeEnabled) return windowIndex == getLastWindowIndex(shuffleModeEnabled)
? getFirstWindowIndex(shuffleModeEnabled) : windowIndex + 1; ? getFirstWindowIndex(shuffleModeEnabled) : windowIndex + 1;
default: 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. * @return The index of the previous window, or {@link C#INDEX_UNSET} if this is the first window.
*/ */
public int getPreviousWindowIndex( public int getPreviousWindowIndex(
int windowIndex, @C.RepeatMode int repeatMode, boolean shuffleModeEnabled) { int windowIndex, @Player.RepeatMode int repeatMode, boolean shuffleModeEnabled) {
switch (repeatMode) { switch (repeatMode) {
case C.REPEAT_MODE_OFF: case Player.REPEAT_MODE_OFF:
return windowIndex == getFirstWindowIndex(shuffleModeEnabled) ? C.INDEX_UNSET return windowIndex == getFirstWindowIndex(shuffleModeEnabled) ? C.INDEX_UNSET
: windowIndex - 1; : windowIndex - 1;
case C.REPEAT_MODE_ONE: case Player.REPEAT_MODE_ONE:
return windowIndex; return windowIndex;
case C.REPEAT_MODE_ALL: case Player.REPEAT_MODE_ALL:
return windowIndex == getFirstWindowIndex(shuffleModeEnabled) return windowIndex == getFirstWindowIndex(shuffleModeEnabled)
? getLastWindowIndex(shuffleModeEnabled) : windowIndex - 1; ? getLastWindowIndex(shuffleModeEnabled) : windowIndex - 1;
default: default:
@ -847,7 +847,7 @@ public abstract class Timeline {
int periodIndex, int periodIndex,
Period period, Period period,
Window window, Window window,
@C.RepeatMode int repeatMode, @Player.RepeatMode int repeatMode,
boolean shuffleModeEnabled) { boolean shuffleModeEnabled) {
int windowIndex = getPeriod(periodIndex, period).windowIndex; int windowIndex = getPeriod(periodIndex, period).windowIndex;
if (getWindow(windowIndex, window).lastPeriodIndex == periodIndex) { if (getWindow(windowIndex, window).lastPeriodIndex == periodIndex) {
@ -875,7 +875,7 @@ public abstract class Timeline {
int periodIndex, int periodIndex,
Period period, Period period,
Window window, Window window,
@C.RepeatMode int repeatMode, @Player.RepeatMode int repeatMode,
boolean shuffleModeEnabled) { boolean shuffleModeEnabled) {
return getNextPeriodIndex(periodIndex, period, window, repeatMode, shuffleModeEnabled) return getNextPeriodIndex(periodIndex, period, window, repeatMode, shuffleModeEnabled)
== C.INDEX_UNSET; == C.INDEX_UNSET;

View file

@ -15,6 +15,7 @@
*/ */
package com.google.android.exoplayer2.audio; package com.google.android.exoplayer2.audio;
import android.os.Bundle;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi; import androidx.annotation.RequiresApi;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
@ -33,6 +34,11 @@ import com.google.android.exoplayer2.util.Util;
*/ */
public final class AudioAttributes { 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(); public static final AudioAttributes DEFAULT = new Builder().build();
/** /**
@ -159,4 +165,31 @@ public final class AudioAttributes {
return result; 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; package com.google.android.exoplayer2.device;
import android.os.Bundle;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
@ -26,6 +27,10 @@ import java.lang.annotation.Target;
/** Information about the playback device. */ /** Information about the playback device. */
public final class DeviceInfo { 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}. */ /** Types of playback. One of {@link #PLAYBACK_TYPE_LOCAL} or {@link #PLAYBACK_TYPE_REMOTE}. */
@Documented @Documented
@Retention(RetentionPolicy.SOURCE) @Retention(RetentionPolicy.SOURCE)
@ -80,4 +85,21 @@ public final class DeviceInfo {
result = 31 * result + maxVolume; result = 31 * result + maxVolume;
return result; 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; package com.google.android.exoplayer2.device;
// TODO(b/172315872) change back to @link after player migration to common. import com.google.android.exoplayer2.Player;
/** A listener for changes of {@code Player.DeviceComponent}. */
/** A listener for changes of {@link Player.DeviceComponent}. */
public interface DeviceListener { public interface DeviceListener {
/** Called when the device information changes. */ /** Called when the device information changes. */

View file

@ -15,13 +15,17 @@
*/ */
package com.google.android.exoplayer2.metadata.mp4; package com.google.android.exoplayer2.metadata.mp4;
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
import android.os.Parcel; import android.os.Parcel;
import android.os.Parcelable; import android.os.Parcelable;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.metadata.Metadata; import com.google.android.exoplayer2.metadata.Metadata;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.common.base.Objects; import com.google.common.base.Objects;
import com.google.common.collect.ComparisonChain;
import java.util.ArrayList; import java.util.ArrayList;
import java.util.Comparator;
import java.util.List; import java.util.List;
/** Holds information about the segments of slow motion playback within a track. */ /** 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. */ /** Holds information about a single segment of slow motion playback within a track. */
public static final class Segment implements Parcelable { 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. */ /** The start time, in milliseconds, of the track segment that is intended to be slow motion. */
public final long startTimeMs; public final long startTimeMs;
/** The end time, in milliseconds, of the track segment that is intended to be slow motion. */ /** 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. * Creates an instance.
* *
* @param startTimeMs See {@link #startTimeMs}. * @param startTimeMs See {@link #startTimeMs}. Must be less than endTimeMs.
* @param endTimeMs See {@link #endTimeMs}. * @param endTimeMs See {@link #endTimeMs}.
* @param speedDivisor See {@link #speedDivisor}. * @param speedDivisor See {@link #speedDivisor}.
*/ */
public Segment(long startTimeMs, long endTimeMs, int speedDivisor) { public Segment(long startTimeMs, long endTimeMs, int speedDivisor) {
checkArgument(startTimeMs < endTimeMs);
this.startTimeMs = startTimeMs; this.startTimeMs = startTimeMs;
this.endTimeMs = endTimeMs; this.endTimeMs = endTimeMs;
this.speedDivisor = speedDivisor; this.speedDivisor = speedDivisor;
@ -113,9 +126,15 @@ public final class SlowMotionData implements Metadata.Entry {
public final List<Segment> segments; 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) { public SlowMotionData(List<Segment> segments) {
this.segments = segments; this.segments = segments;
checkArgument(!doSegmentsOverlap(segments));
} }
@Override @Override
@ -164,4 +183,19 @@ public final class SlowMotionData implements Metadata.Entry {
return new SlowMotionData[size]; 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 com.google.android.exoplayer2.util.Assertions;
import java.util.Arrays; import java.util.Arrays;
// TODO: Add an allowMultipleStreams boolean to indicate where the one stream per group restriction /** Defines an immutable group of tracks identified by their format identity. */
// 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.
*/
public final class TrackGroup implements Parcelable { public final class TrackGroup implements Parcelable {
/** /** The number of tracks in the group. */
* The number of tracks in the group.
*/
public final int length; public final int length;
private final Format[] formats; private final Format[] formats;
@ -45,7 +35,7 @@ public final class TrackGroup implements Parcelable {
private int hashCode; 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) { public TrackGroup(Format... formats) {
Assertions.checkState(formats.length > 0); Assertions.checkState(formats.length > 0);

View file

@ -21,17 +21,13 @@ import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import java.util.Arrays; 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 { public final class TrackGroupArray implements Parcelable {
/** /** The empty array. */
* The empty array.
*/
public static final TrackGroupArray EMPTY = new TrackGroupArray(); 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; public final int length;
private final TrackGroup[] trackGroups; private final TrackGroup[] trackGroups;
@ -39,9 +35,7 @@ public final class TrackGroupArray implements Parcelable {
// Lazily initialized hashcode. // Lazily initialized hashcode.
private int hashCode; private int hashCode;
/** /** @param trackGroups The groups. May be empty. */
* @param trackGroups The groups. Must not be null or contain null elements, but may be empty.
*/
public TrackGroupArray(TrackGroup... trackGroups) { public TrackGroupArray(TrackGroup... trackGroups) {
this.trackGroups = trackGroups; this.trackGroups = trackGroups;
this.length = trackGroups.length; this.length = trackGroups.length;
@ -83,9 +77,7 @@ public final class TrackGroupArray implements Parcelable {
return C.INDEX_UNSET; return C.INDEX_UNSET;
} }
/** /** Returns whether this track group array is empty. */
* Returns whether this track group array is empty.
*/
public boolean isEmpty() { public boolean isEmpty() {
return length == 0; 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; TrackSelectionArray other = (TrackSelectionArray) obj;
return Arrays.equals(trackSelections, other.trackSelections); return Arrays.equals(trackSelections, other.trackSelections);
} }
} }

View file

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

View file

@ -43,9 +43,6 @@ public interface Clock {
/** @see android.os.SystemClock#uptimeMillis() */ /** @see android.os.SystemClock#uptimeMillis() */
long 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 * Creates a {@link HandlerWrapper} using a specified looper and a specified callback for handling
* messages. * messages.
@ -53,4 +50,12 @@ public interface Clock {
* @see Handler#Handler(Looper, Handler.Callback) * @see Handler#Handler(Looper, Handler.Callback)
*/ */
HandlerWrapper createHandler(Looper looper, @Nullable Handler.Callback 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.Handler;
import android.os.Looper; import android.os.Looper;
import android.os.Message;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
/** /**
@ -26,39 +25,55 @@ import androidx.annotation.Nullable;
*/ */
public interface HandlerWrapper { 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(); Looper getLooper();
/** @see Handler#obtainMessage(int) */ /** See {@link Handler#hasMessages(int)}. */
boolean hasMessages(int what);
/** See {@link Handler#obtainMessage(int)}. */
Message obtainMessage(int what); Message obtainMessage(int what);
/** @see Handler#obtainMessage(int, Object) */ /** See {@link Handler#obtainMessage(int, Object)}. */
Message obtainMessage(int what, @Nullable Object obj); 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); 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); 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); boolean sendEmptyMessage(int what);
/** @see Handler#sendEmptyMessageDelayed(int, long) */ /** See {@link Handler#sendEmptyMessageDelayed(int, long)}. */
boolean sendEmptyMessageDelayed(int what, int delayMs); boolean sendEmptyMessageDelayed(int what, int delayMs);
/** @see Handler#sendEmptyMessageAtTime(int, long) */ /** See {@link Handler#sendEmptyMessageAtTime(int, long)}. */
boolean sendEmptyMessageAtTime(int what, long uptimeMs); boolean sendEmptyMessageAtTime(int what, long uptimeMs);
/** @see Handler#removeMessages(int) */ /** See {@link Handler#removeMessages(int)}. */
void removeMessages(int what); void removeMessages(int what);
/** @see Handler#removeCallbacksAndMessages(Object) */ /** See {@link Handler#removeCallbacksAndMessages(Object)}. */
void removeCallbacksAndMessages(@Nullable Object token); void removeCallbacksAndMessages(@Nullable Object token);
/** @see Handler#post(Runnable) */ /** See {@link Handler#post(Runnable)}. */
boolean post(Runnable runnable); boolean post(Runnable runnable);
/** @see Handler#postDelayed(Runnable, long) */ /** See {@link Handler#postDelayed(Runnable, long)}. */
boolean postDelayed(Runnable runnable, long delayMs); boolean postDelayed(Runnable runnable, long delayMs);
} }

View file

@ -15,7 +15,6 @@
*/ */
package com.google.android.exoplayer2.util; package com.google.android.exoplayer2.util;
import android.os.Handler;
import android.os.Looper; import android.os.Looper;
import android.os.Message; import android.os.Message;
import androidx.annotation.CheckResult; 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_ITERATION_FINISHED = 0;
private static final int MSG_LAZY_RELEASE = 1; 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 Supplier<E> eventFlagsSupplier;
private final IterationFinishedEvent<T, E> iterationFinishedEvent; private final IterationFinishedEvent<T, E> iterationFinishedEvent;
private final CopyOnWriteArraySet<ListenerHolder<T, E>> listeners; 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 * @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. * 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 * @param eventFlagsSupplier A {@link Supplier} for new instances of {@link E the event flags
* type}. * type}.
* @param iterationFinishedEvent An {@link IterationFinishedEvent} sent when all other events sent * @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( public ListenerSet(
Looper looper, Looper looper,
Clock clock,
Supplier<E> eventFlagsSupplier, Supplier<E> eventFlagsSupplier,
IterationFinishedEvent<T, E> iterationFinishedEvent) { IterationFinishedEvent<T, E> iterationFinishedEvent) {
this( this(
/* listeners= */ new CopyOnWriteArraySet<>(), /* listeners= */ new CopyOnWriteArraySet<>(),
looper, looper,
clock,
eventFlagsSupplier, eventFlagsSupplier,
iterationFinishedEvent); iterationFinishedEvent);
} }
@ -105,8 +108,10 @@ public final class ListenerSet<T, E extends MutableFlags> {
private ListenerSet( private ListenerSet(
CopyOnWriteArraySet<ListenerHolder<T, E>> listeners, CopyOnWriteArraySet<ListenerHolder<T, E>> listeners,
Looper looper, Looper looper,
Clock clock,
Supplier<E> eventFlagsSupplier, Supplier<E> eventFlagsSupplier,
IterationFinishedEvent<T, E> iterationFinishedEvent) { IterationFinishedEvent<T, E> iterationFinishedEvent) {
this.clock = clock;
this.listeners = listeners; this.listeners = listeners;
this.eventFlagsSupplier = eventFlagsSupplier; this.eventFlagsSupplier = eventFlagsSupplier;
this.iterationFinishedEvent = iterationFinishedEvent; this.iterationFinishedEvent = iterationFinishedEvent;
@ -114,7 +119,7 @@ public final class ListenerSet<T, E extends MutableFlags> {
queuedEvents = new ArrayDeque<>(); queuedEvents = new ArrayDeque<>();
// It's safe to use "this" because we don't send a message before exiting the constructor. // It's safe to use "this" because we don't send a message before exiting the constructor.
@SuppressWarnings("methodref.receiver.bound.invalid") @SuppressWarnings("methodref.receiver.bound.invalid")
Handler handler = Util.createHandler(looper, this::handleMessage); HandlerWrapper handler = clock.createHandler(looper, this::handleMessage);
this.handler = handler; this.handler = handler;
} }
@ -129,7 +134,7 @@ public final class ListenerSet<T, E extends MutableFlags> {
@CheckResult @CheckResult
public ListenerSet<T, E> copy( public ListenerSet<T, E> copy(
Looper looper, IterationFinishedEvent<T, E> iterationFinishedEvent) { 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.Retention;
import java.lang.annotation.RetentionPolicy; import java.lang.annotation.RetentionPolicy;
import java.net.UnknownHostException; import java.net.UnknownHostException;
import org.checkerframework.dataflow.qual.Pure;
/** Wrapper around {@link android.util.Log} which allows to set the log level. */ /** Wrapper around {@link android.util.Log} which allows to set the log level. */
public final class Log { public final class Log {
@ -51,11 +52,13 @@ public final class Log {
private Log() {} private Log() {}
/** Returns current {@link LogLevel} for ExoPlayer logcat logging. */ /** Returns current {@link LogLevel} for ExoPlayer logcat logging. */
@Pure
public static @LogLevel int getLogLevel() { public static @LogLevel int getLogLevel() {
return logLevel; return logLevel;
} }
/** Returns whether stack traces of {@link Throwable}s will be logged to logcat. */ /** Returns whether stack traces of {@link Throwable}s will be logged to logcat. */
@Pure
public boolean getLogStackTraces() { public boolean getLogStackTraces() {
return logStackTraces; return logStackTraces;
} }
@ -80,6 +83,7 @@ public final class Log {
} }
/** @see android.util.Log#d(String, String) */ /** @see android.util.Log#d(String, String) */
@Pure
public static void d(String tag, String message) { public static void d(String tag, String message) {
if (logLevel == LOG_LEVEL_ALL) { if (logLevel == LOG_LEVEL_ALL) {
android.util.Log.d(tag, message); android.util.Log.d(tag, message);
@ -87,11 +91,13 @@ public final class Log {
} }
/** @see android.util.Log#d(String, String, Throwable) */ /** @see android.util.Log#d(String, String, Throwable) */
@Pure
public static void d(String tag, String message, @Nullable Throwable throwable) { public static void d(String tag, String message, @Nullable Throwable throwable) {
d(tag, appendThrowableString(message, throwable)); d(tag, appendThrowableString(message, throwable));
} }
/** @see android.util.Log#i(String, String) */ /** @see android.util.Log#i(String, String) */
@Pure
public static void i(String tag, String message) { public static void i(String tag, String message) {
if (logLevel <= LOG_LEVEL_INFO) { if (logLevel <= LOG_LEVEL_INFO) {
android.util.Log.i(tag, message); android.util.Log.i(tag, message);
@ -99,11 +105,13 @@ public final class Log {
} }
/** @see android.util.Log#i(String, String, Throwable) */ /** @see android.util.Log#i(String, String, Throwable) */
@Pure
public static void i(String tag, String message, @Nullable Throwable throwable) { public static void i(String tag, String message, @Nullable Throwable throwable) {
i(tag, appendThrowableString(message, throwable)); i(tag, appendThrowableString(message, throwable));
} }
/** @see android.util.Log#w(String, String) */ /** @see android.util.Log#w(String, String) */
@Pure
public static void w(String tag, String message) { public static void w(String tag, String message) {
if (logLevel <= LOG_LEVEL_WARNING) { if (logLevel <= LOG_LEVEL_WARNING) {
android.util.Log.w(tag, message); android.util.Log.w(tag, message);
@ -111,11 +119,13 @@ public final class Log {
} }
/** @see android.util.Log#w(String, String, Throwable) */ /** @see android.util.Log#w(String, String, Throwable) */
@Pure
public static void w(String tag, String message, @Nullable Throwable throwable) { public static void w(String tag, String message, @Nullable Throwable throwable) {
w(tag, appendThrowableString(message, throwable)); w(tag, appendThrowableString(message, throwable));
} }
/** @see android.util.Log#e(String, String) */ /** @see android.util.Log#e(String, String) */
@Pure
public static void e(String tag, String message) { public static void e(String tag, String message) {
if (logLevel <= LOG_LEVEL_ERROR) { if (logLevel <= LOG_LEVEL_ERROR) {
android.util.Log.e(tag, message); android.util.Log.e(tag, message);
@ -123,6 +133,7 @@ public final class Log {
} }
/** @see android.util.Log#e(String, String, Throwable) */ /** @see android.util.Log#e(String, String, Throwable) */
@Pure
public static void e(String tag, String message, @Nullable Throwable throwable) { public static void e(String tag, String message, @Nullable Throwable throwable) {
e(tag, appendThrowableString(message, throwable)); e(tag, appendThrowableString(message, throwable));
} }
@ -139,6 +150,7 @@ public final class Log {
* @return The string representation of the {@link Throwable}. * @return The string representation of the {@link Throwable}.
*/ */
@Nullable @Nullable
@Pure
public static String getThrowableString(@Nullable Throwable throwable) { public static String getThrowableString(@Nullable Throwable throwable) {
if (throwable == null) { if (throwable == null) {
return null; return null;
@ -157,6 +169,7 @@ public final class Log {
} }
} }
@Pure
private static String appendThrowableString(String message, @Nullable Throwable throwable) { private static String appendThrowableString(String message, @Nullable Throwable throwable) {
@Nullable String throwableString = getThrowableString(throwable); @Nullable String throwableString = getThrowableString(throwable);
if (!TextUtils.isEmpty(throwableString)) { if (!TextUtils.isEmpty(throwableString)) {
@ -165,6 +178,7 @@ public final class Log {
return message; return message;
} }
@Pure
private static boolean isCausedByUnknownHostException(@Nullable Throwable throwable) { private static boolean isCausedByUnknownHostException(@Nullable Throwable throwable) {
while (throwable != null) { while (throwable != null) {
if (throwable instanceof UnknownHostException) { if (throwable instanceof UnknownHostException) {

View file

@ -43,13 +43,13 @@ public class SystemClock implements Clock {
return android.os.SystemClock.uptimeMillis(); return android.os.SystemClock.uptimeMillis();
} }
@Override
public void sleep(long sleepTimeMs) {
android.os.SystemClock.sleep(sleepTimeMs);
}
@Override @Override
public HandlerWrapper createHandler(Looper looper, @Nullable Callback callback) { public HandlerWrapper createHandler(Looper looper, @Nullable Callback callback) {
return new SystemHandlerWrapper(new Handler(looper, 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.Matcher;
import java.util.regex.Pattern; import java.util.regex.Pattern;
import java.util.zip.DataFormatException; import java.util.zip.DataFormatException;
import java.util.zip.GZIPOutputStream;
import java.util.zip.Inflater; import java.util.zip.Inflater;
import org.checkerframework.checker.initialization.qual.UnknownInitialization; import org.checkerframework.checker.initialization.qual.UnknownInitialization;
import org.checkerframework.checker.nullness.compatqual.NullableType; 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. */ /** Returns a data URI with the specified MIME type and data. */
public static Uri getDataUriForString(String mimeType, String 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( return Uri.parse(
"data:" + mimeType + ";base64," + Base64.encodeToString(data.getBytes(), Base64.NO_WRAP)); "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()}. */ /** Creates a new empty file in the directory returned by {@link Context#getCacheDir()}. */
public static File createTempFile(Context context, String prefix) throws IOException { 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; 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 * 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 * 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); return getMobileNetworkType(networkInfo);
case ConnectivityManager.TYPE_ETHERNET: case ConnectivityManager.TYPE_ETHERNET:
return C.NETWORK_TYPE_ETHERNET; return C.NETWORK_TYPE_ETHERNET;
default: // VPN, Bluetooth, Dummy. default:
return C.NETWORK_TYPE_OTHER; return C.NETWORK_TYPE_OTHER;
} }
} }
@ -2610,7 +2620,7 @@ public final class Util {
"hsn", "zh-hsn" "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. // See https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry.
private static final String[] isoLegacyTagReplacements = private static final String[] isoLegacyTagReplacements =
new String[] { 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 @Test
public void queueEvent_withoutFlush_sendsNoEvents() { public void queueEvent_withoutFlush_sendsNoEvents() {
ListenerSet<TestListener, Flags> listenerSet = 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); TestListener listener = mock(TestListener.class);
listenerSet.add(listener); listenerSet.add(listener);
@ -57,7 +58,8 @@ public class ListenerSetTest {
@Test @Test
public void flushEvents_sendsPreviouslyQueuedEventsToAllListeners() { public void flushEvents_sendsPreviouslyQueuedEventsToAllListeners() {
ListenerSet<TestListener, Flags> listenerSet = 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 listener1 = mock(TestListener.class);
TestListener listener2 = mock(TestListener.class); TestListener listener2 = mock(TestListener.class);
listenerSet.add(listener1); listenerSet.add(listener1);
@ -81,7 +83,8 @@ public class ListenerSetTest {
@Test @Test
public void flushEvents_recursive_sendsEventsInCorrectOrder() { public void flushEvents_recursive_sendsEventsInCorrectOrder() {
ListenerSet<TestListener, Flags> listenerSet = 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. // Listener1 sends callback3 recursively when receiving callback1.
TestListener listener1 = TestListener listener1 =
spy( spy(
@ -114,7 +117,8 @@ public class ListenerSetTest {
public void public void
flushEvents_withMultipleMessageQueueIterations_sendsIterationFinishedEventPerIteration() { flushEvents_withMultipleMessageQueueIterations_sendsIterationFinishedEventPerIteration() {
ListenerSet<TestListener, Flags> listenerSet = 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. // Listener1 sends callback1 recursively when receiving callback3.
TestListener listener1 = TestListener listener1 =
spy( spy(
@ -170,7 +174,8 @@ public class ListenerSetTest {
@Test @Test
public void flushEvents_calledFromIterationFinishedCallback_restartsIterationFinishedEvents() { public void flushEvents_calledFromIterationFinishedCallback_restartsIterationFinishedEvents() {
ListenerSet<TestListener, Flags> listenerSet = 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. // Listener2 sends callback1 recursively when receiving the iteration finished event.
TestListener listener2 = TestListener listener2 =
spy( spy(
@ -212,7 +217,8 @@ public class ListenerSetTest {
@Test @Test
public void flushEvents_withUnsetEventFlag_doesNotThrow() { public void flushEvents_withUnsetEventFlag_doesNotThrow() {
ListenerSet<TestListener, Flags> listenerSet = 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.queueEvent(/* eventFlag= */ C.INDEX_UNSET, TestListener::callback1);
listenerSet.flushEvents(); listenerSet.flushEvents();
@ -224,7 +230,8 @@ public class ListenerSetTest {
@Test @Test
public void add_withRecursion_onlyReceivesUpdatesForFutureEvents() { public void add_withRecursion_onlyReceivesUpdatesForFutureEvents() {
ListenerSet<TestListener, Flags> listenerSet = 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); TestListener listener2 = mock(TestListener.class);
// Listener1 adds listener2 recursively. // Listener1 adds listener2 recursively.
TestListener listener1 = TestListener listener1 =
@ -256,7 +263,8 @@ public class ListenerSetTest {
@Test @Test
public void add_withQueueing_onlyReceivesUpdatesForFutureEvents() { public void add_withQueueing_onlyReceivesUpdatesForFutureEvents() {
ListenerSet<TestListener, Flags> listenerSet = 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 listener1 = mock(TestListener.class);
TestListener listener2 = mock(TestListener.class); TestListener listener2 = mock(TestListener.class);
@ -281,7 +289,8 @@ public class ListenerSetTest {
@Test @Test
public void remove_withRecursion_stopsReceivingEventsImmediately() { public void remove_withRecursion_stopsReceivingEventsImmediately() {
ListenerSet<TestListener, Flags> listenerSet = 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); TestListener listener2 = mock(TestListener.class);
// Listener1 removes listener2 recursively. // Listener1 removes listener2 recursively.
TestListener listener1 = TestListener listener1 =
@ -309,7 +318,8 @@ public class ListenerSetTest {
@Test @Test
public void remove_withQueueing_stopsReceivingEventsImmediately() { public void remove_withQueueing_stopsReceivingEventsImmediately() {
ListenerSet<TestListener, Flags> listenerSet = 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 listener1 = mock(TestListener.class);
TestListener listener2 = mock(TestListener.class); TestListener listener2 = mock(TestListener.class);
listenerSet.add(listener1); listenerSet.add(listener1);
@ -330,7 +340,8 @@ public class ListenerSetTest {
@Test @Test
public void release_stopsForwardingEventsImmediately() { public void release_stopsForwardingEventsImmediately() {
ListenerSet<TestListener, Flags> listenerSet = 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); TestListener listener2 = mock(TestListener.class);
// Listener1 releases the set from within the callback. // Listener1 releases the set from within the callback.
TestListener listener1 = TestListener listener1 =
@ -357,7 +368,8 @@ public class ListenerSetTest {
@Test @Test
public void release_preventsRegisteringNewListeners() { public void release_preventsRegisteringNewListeners() {
ListenerSet<TestListener, Flags> listenerSet = 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); TestListener listener = mock(TestListener.class);
listenerSet.release(); listenerSet.release();
@ -370,7 +382,8 @@ public class ListenerSetTest {
@Test @Test
public void lazyRelease_stopsForwardingEventsFromNewHandlerMessagesAndCallsReleaseCallback() { public void lazyRelease_stopsForwardingEventsFromNewHandlerMessagesAndCallsReleaseCallback() {
ListenerSet<TestListener, Flags> listenerSet = 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); TestListener listener = mock(TestListener.class);
listenerSet.add(listener); 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.escapeFileName;
import static com.google.android.exoplayer2.util.Util.getCodecsOfType; 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.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.minValue;
import static com.google.android.exoplayer2.util.Util.parseXsDateTime; import static com.google.android.exoplayer2.util.Util.parseXsDateTime;
import static com.google.android.exoplayer2.util.Util.parseXsDuration; import static com.google.android.exoplayer2.util.Util.parseXsDuration;
@ -37,6 +38,9 @@ import android.text.style.UnderlineSpan;
import android.util.SparseLongArray; import android.util.SparseLongArray;
import androidx.test.ext.junit.runners.AndroidJUnit4; import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.android.exoplayer2.C; 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.ByteBuffer;
import java.nio.ByteOrder; import java.nio.ByteOrder;
import java.util.ArrayList; import java.util.ArrayList;
@ -45,6 +49,7 @@ import java.util.Formatter;
import java.util.NoSuchElementException; import java.util.NoSuchElementException;
import java.util.Random; import java.util.Random;
import java.util.zip.Deflater; import java.util.zip.Deflater;
import java.util.zip.GZIPInputStream;
import org.junit.Test; import org.junit.Test;
import org.junit.runner.RunWith; import org.junit.runner.RunWith;
import org.robolectric.annotation.Config; import org.robolectric.annotation.Config;
@ -927,6 +932,17 @@ public class UtilTest {
assertThat(result).isEqualTo(0x4); 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 @Test
public void getBigEndianInt_fromBigEndian() { public void getBigEndianInt_fromBigEndian() {
byte[] bytes = {0x1F, 0x2E, 0x3D, 0x4C}; byte[] bytes = {0x1F, 0x2E, 0x3D, 0x4C};

View file

@ -21,16 +21,14 @@ import static java.lang.Math.min;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.source.TrackGroupArray; 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.Allocator;
import com.google.android.exoplayer2.upstream.DefaultAllocator; import com.google.android.exoplayer2.upstream.DefaultAllocator;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
/** /** The default {@link LoadControl} implementation. */
* The default {@link LoadControl} implementation.
*/
public class DefaultLoadControl implements LoadControl { public class DefaultLoadControl implements LoadControl {
/** /**
@ -318,8 +316,8 @@ public class DefaultLoadControl implements LoadControl {
} }
@Override @Override
public void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups, public void onTracksSelected(
TrackSelectionArray trackSelections) { Renderer[] renderers, TrackGroupArray trackGroups, ExoTrackSelection[] trackSelections) {
targetBufferBytes = targetBufferBytes =
targetBufferBytesOverwrite == C.LENGTH_UNSET targetBufferBytesOverwrite == C.LENGTH_UNSET
? calculateTargetBufferBytes(renderers, trackSelections) ? calculateTargetBufferBytes(renderers, trackSelections)
@ -402,10 +400,10 @@ public class DefaultLoadControl implements LoadControl {
* @return The target buffer size in bytes. * @return The target buffer size in bytes.
*/ */
protected int calculateTargetBufferBytes( protected int calculateTargetBufferBytes(
Renderer[] renderers, TrackSelectionArray trackSelectionArray) { Renderer[] renderers, ExoTrackSelection[] trackSelectionArray) {
int targetBufferSize = 0; int targetBufferSize = 0;
for (int i = 0; i < renderers.length; i++) { for (int i = 0; i < renderers.length; i++) {
if (trackSelectionArray.get(i) != null) { if (trackSelectionArray[i] != null) {
targetBufferSize += getDefaultBufferSize(renderers[i].getTrackType()); 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 @Nullable
TrackSelector getTrackSelector(); TrackSelector getTrackSelector();
/** Returns the {@link Looper} associated with the playback thread. */ /** Returns the {@link Looper} associated with the playback thread. */
Looper getPlaybackLooper(); Looper getPlaybackLooper();
/** Returns the {@link Clock} used for playback. */
Clock getClock();
/** @deprecated Use {@link #prepare()} instead. */ /** @deprecated Use {@link #prepare()} instead. */
@Deprecated @Deprecated
void retry(); 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.MediaSourceFactory;
import com.google.android.exoplayer2.source.ShuffleOrder; import com.google.android.exoplayer2.source.ShuffleOrder;
import com.google.android.exoplayer2.source.TrackGroupArray; 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.TrackSelectionArray;
import com.google.android.exoplayer2.trackselection.TrackSelector; import com.google.android.exoplayer2.trackselection.TrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelectorResult; import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
import com.google.android.exoplayer2.upstream.BandwidthMeter; import com.google.android.exoplayer2.upstream.BandwidthMeter;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Clock; 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.ListenerSet;
import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
@ -67,10 +68,9 @@ import java.util.List;
private final Renderer[] renderers; private final Renderer[] renderers;
private final TrackSelector trackSelector; private final TrackSelector trackSelector;
private final Handler playbackInfoUpdateHandler; private final HandlerWrapper playbackInfoUpdateHandler;
private final ExoPlayerImplInternal.PlaybackInfoUpdateListener playbackInfoUpdateListener; private final ExoPlayerImplInternal.PlaybackInfoUpdateListener playbackInfoUpdateListener;
private final ExoPlayerImplInternal internalPlayer; private final ExoPlayerImplInternal internalPlayer;
private final Handler internalPlayerHandler;
private final ListenerSet<Player.EventListener, Player.Events> listeners; private final ListenerSet<Player.EventListener, Player.Events> listeners;
private final Timeline.Period period; private final Timeline.Period period;
private final List<MediaSourceHolderSnapshot> mediaSourceHolderSnapshots; private final List<MediaSourceHolderSnapshot> mediaSourceHolderSnapshots;
@ -79,6 +79,7 @@ import java.util.List;
@Nullable private final AnalyticsCollector analyticsCollector; @Nullable private final AnalyticsCollector analyticsCollector;
private final Looper applicationLooper; private final Looper applicationLooper;
private final BandwidthMeter bandwidthMeter; private final BandwidthMeter bandwidthMeter;
private final Clock clock;
@RepeatMode private int repeatMode; @RepeatMode private int repeatMode;
private boolean shuffleModeEnabled; private boolean shuffleModeEnabled;
@ -137,8 +138,15 @@ import java.util.List;
Clock clock, Clock clock,
Looper applicationLooper, Looper applicationLooper,
@Nullable Player wrappingPlayer) { @Nullable Player wrappingPlayer) {
Log.i(TAG, "Init " + Integer.toHexString(System.identityHashCode(this)) + " [" Log.i(
+ ExoPlayerLibraryInfo.VERSION_SLASHY + "] [" + Util.DEVICE_DEBUG_INFO + "]"); TAG,
"Init "
+ Integer.toHexString(System.identityHashCode(this))
+ " ["
+ ExoPlayerLibraryInfo.VERSION_SLASHY
+ "] ["
+ Util.DEVICE_DEBUG_INFO
+ "]");
checkState(renderers.length > 0); checkState(renderers.length > 0);
this.renderers = checkNotNull(renderers); this.renderers = checkNotNull(renderers);
this.trackSelector = checkNotNull(trackSelector); this.trackSelector = checkNotNull(trackSelector);
@ -149,11 +157,13 @@ import java.util.List;
this.seekParameters = seekParameters; this.seekParameters = seekParameters;
this.pauseAtEndOfMediaItems = pauseAtEndOfMediaItems; this.pauseAtEndOfMediaItems = pauseAtEndOfMediaItems;
this.applicationLooper = applicationLooper; this.applicationLooper = applicationLooper;
this.clock = clock;
repeatMode = Player.REPEAT_MODE_OFF; repeatMode = Player.REPEAT_MODE_OFF;
Player playerForListeners = wrappingPlayer != null ? wrappingPlayer : this; Player playerForListeners = wrappingPlayer != null ? wrappingPlayer : this;
listeners = listeners =
new ListenerSet<>( new ListenerSet<>(
applicationLooper, applicationLooper,
clock,
Player.Events::new, Player.Events::new,
(listener, eventFlags) -> listener.onEvents(playerForListeners, eventFlags)); (listener, eventFlags) -> listener.onEvents(playerForListeners, eventFlags));
mediaSourceHolderSnapshots = new ArrayList<>(); mediaSourceHolderSnapshots = new ArrayList<>();
@ -161,11 +171,11 @@ import java.util.List;
emptyTrackSelectorResult = emptyTrackSelectorResult =
new TrackSelectorResult( new TrackSelectorResult(
new RendererConfiguration[renderers.length], new RendererConfiguration[renderers.length],
new TrackSelection[renderers.length], new ExoTrackSelection[renderers.length],
/* info= */ null); /* info= */ null);
period = new Timeline.Period(); period = new Timeline.Period();
maskingWindowIndex = C.INDEX_UNSET; maskingWindowIndex = C.INDEX_UNSET;
playbackInfoUpdateHandler = new Handler(applicationLooper); playbackInfoUpdateHandler = clock.createHandler(applicationLooper, /* callback= */ null);
playbackInfoUpdateListener = playbackInfoUpdateListener =
playbackInfoUpdate -> playbackInfoUpdate ->
playbackInfoUpdateHandler.post(() -> handlePlaybackInfo(playbackInfoUpdate)); playbackInfoUpdateHandler.post(() -> handlePlaybackInfo(playbackInfoUpdate));
@ -192,7 +202,6 @@ import java.util.List;
applicationLooper, applicationLooper,
clock, clock,
playbackInfoUpdateListener); playbackInfoUpdateListener);
internalPlayerHandler = new Handler(internalPlayer.getPlaybackLooper());
} }
/** /**
@ -259,6 +268,11 @@ import java.util.List;
return applicationLooper; return applicationLooper;
} }
@Override
public Clock getClock() {
return clock;
}
@Override @Override
public void addListener(Player.EventListener listener) { public void addListener(Player.EventListener listener) {
listeners.add(listener); listeners.add(listener);
@ -724,9 +738,17 @@ import java.util.List;
@Override @Override
public void release() { public void release() {
Log.i(TAG, "Release " + Integer.toHexString(System.identityHashCode(this)) + " [" Log.i(
+ ExoPlayerLibraryInfo.VERSION_SLASHY + "] [" + Util.DEVICE_DEBUG_INFO + "] [" TAG,
+ ExoPlayerLibraryInfo.registeredModules() + "]"); "Release "
+ Integer.toHexString(System.identityHashCode(this))
+ " ["
+ ExoPlayerLibraryInfo.VERSION_SLASHY
+ "] ["
+ Util.DEVICE_DEBUG_INFO
+ "] ["
+ ExoPlayerLibraryInfo.registeredModules()
+ "]");
if (!internalPlayer.release()) { if (!internalPlayer.release()) {
// One of the renderers timed out releasing its resources. // One of the renderers timed out releasing its resources.
listeners.sendEvent( listeners.sendEvent(
@ -754,7 +776,8 @@ import java.util.List;
target, target,
playbackInfo.timeline, playbackInfo.timeline,
getCurrentWindowIndex(), getCurrentWindowIndex(),
internalPlayerHandler); clock,
internalPlayer.getPlaybackLooper());
} }
@Override @Override
@ -882,7 +905,7 @@ import java.util.List;
@Override @Override
public TrackSelectionArray getCurrentTrackSelections() { public TrackSelectionArray getCurrentTrackSelections() {
return playbackInfo.trackSelectorResult.selections; return new TrackSelectionArray(playbackInfo.trackSelectorResult.selections);
} }
@Override @Override
@ -1005,11 +1028,11 @@ import java.util.List;
} }
if (previousPlaybackInfo.trackSelectorResult != newPlaybackInfo.trackSelectorResult) { if (previousPlaybackInfo.trackSelectorResult != newPlaybackInfo.trackSelectorResult) {
trackSelector.onSelectionActivated(newPlaybackInfo.trackSelectorResult.info); trackSelector.onSelectionActivated(newPlaybackInfo.trackSelectorResult.info);
TrackSelectionArray newSelection =
new TrackSelectionArray(newPlaybackInfo.trackSelectorResult.selections);
listeners.queueEvent( listeners.queueEvent(
Player.EVENT_TRACKS_CHANGED, Player.EVENT_TRACKS_CHANGED,
listener -> listener -> listener.onTracksChanged(newPlaybackInfo.trackGroups, newSelection));
listener.onTracksChanged(
newPlaybackInfo.trackGroups, newPlaybackInfo.trackSelectorResult.selections));
} }
if (!previousPlaybackInfo.staticMetadata.equals(newPlaybackInfo.staticMetadata)) { if (!previousPlaybackInfo.staticMetadata.equals(newPlaybackInfo.staticMetadata)) {
listeners.queueEvent( 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.SampleStream;
import com.google.android.exoplayer2.source.ShuffleOrder; import com.google.android.exoplayer2.source.ShuffleOrder;
import com.google.android.exoplayer2.source.TrackGroupArray; 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.TrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelectorResult; import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
import com.google.android.exoplayer2.upstream.BandwidthMeter; import com.google.android.exoplayer2.upstream.BandwidthMeter;
@ -558,7 +557,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
if (e.isRecoverable && pendingRecoverableError == null) { if (e.isRecoverable && pendingRecoverableError == null) {
Log.w(TAG, "Recoverable playback error", e); Log.w(TAG, "Recoverable playback error", e);
pendingRecoverableError = 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 // 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. // recovered or the player stopped before any other message is handled.
message.getTarget().sendMessageAtFrontOfQueue(message); message.getTarget().sendMessageAtFrontOfQueue(message);
@ -625,6 +624,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
boolean wasInterrupted = false; boolean wasInterrupted = false;
while (!condition.get() && remainingMs > 0) { while (!condition.get() && remainingMs > 0) {
try { try {
clock.onThreadBlocked();
wait(remainingMs); wait(remainingMs);
} catch (InterruptedException e) { } catch (InterruptedException e) {
wasInterrupted = true; wasInterrupted = true;
@ -726,8 +726,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
private void notifyTrackSelectionPlayWhenReadyChanged(boolean playWhenReady) { private void notifyTrackSelectionPlayWhenReadyChanged(boolean playWhenReady) {
MediaPeriodHolder periodHolder = queue.getPlayingPeriod(); MediaPeriodHolder periodHolder = queue.getPlayingPeriod();
while (periodHolder != null) { while (periodHolder != null) {
TrackSelection[] trackSelections = periodHolder.getTrackSelectorResult().selections.getAll(); for (ExoTrackSelection trackSelection : periodHolder.getTrackSelectorResult().selections) {
for (TrackSelection trackSelection : trackSelections) {
if (trackSelection != null) { if (trackSelection != null) {
trackSelection.onPlayWhenReadyChanged(playWhenReady); trackSelection.onPlayWhenReadyChanged(playWhenReady);
} }
@ -901,8 +900,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
private void notifyTrackSelectionRebuffer() { private void notifyTrackSelectionRebuffer() {
MediaPeriodHolder periodHolder = queue.getPlayingPeriod(); MediaPeriodHolder periodHolder = queue.getPlayingPeriod();
while (periodHolder != null) { while (periodHolder != null) {
TrackSelection[] trackSelections = periodHolder.getTrackSelectorResult().selections.getAll(); for (ExoTrackSelection trackSelection : periodHolder.getTrackSelectorResult().selections) {
for (TrackSelection trackSelection : trackSelections) {
if (trackSelection != null) { if (trackSelection != null) {
trackSelection.onRebuffer(); trackSelection.onRebuffer();
} }
@ -1468,7 +1466,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
} }
private void sendMessageToTarget(PlayerMessage message) throws ExoPlaybackException { private void sendMessageToTarget(PlayerMessage message) throws ExoPlaybackException {
if (message.getHandler().getLooper() == playbackLooper) { if (message.getLooper() == playbackLooper) {
deliverMessage(message); deliverMessage(message);
if (playbackInfo.playbackState == Player.STATE_READY if (playbackInfo.playbackState == Player.STATE_READY
|| playbackInfo.playbackState == Player.STATE_BUFFERING) { || playbackInfo.playbackState == Player.STATE_BUFFERING) {
@ -1481,13 +1479,15 @@ import java.util.concurrent.atomic.AtomicBoolean;
} }
private void sendMessageToTargetThread(final PlayerMessage message) { private void sendMessageToTargetThread(final PlayerMessage message) {
Handler handler = message.getHandler(); Looper looper = message.getLooper();
if (!handler.getLooper().getThread().isAlive()) { if (!looper.getThread().isAlive()) {
Log.w("TAG", "Trying to send message on a dead thread."); Log.w("TAG", "Trying to send message on a dead thread.");
message.markAsProcessed(/* isDelivered= */ false); message.markAsProcessed(/* isDelivered= */ false);
return; return;
} }
handler.post( clock
.createHandler(looper, /* callback= */ null)
.post(
() -> { () -> {
try { try {
deliverMessage(message); deliverMessage(message);
@ -1690,8 +1690,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
private void updateTrackSelectionPlaybackSpeed(float playbackSpeed) { private void updateTrackSelectionPlaybackSpeed(float playbackSpeed) {
MediaPeriodHolder periodHolder = queue.getPlayingPeriod(); MediaPeriodHolder periodHolder = queue.getPlayingPeriod();
while (periodHolder != null) { while (periodHolder != null) {
TrackSelection[] trackSelections = periodHolder.getTrackSelectorResult().selections.getAll(); for (ExoTrackSelection trackSelection : periodHolder.getTrackSelectorResult().selections) {
for (TrackSelection trackSelection : trackSelections) {
if (trackSelection != null) { if (trackSelection != null) {
trackSelection.onPlaybackSpeed(playbackSpeed); trackSelection.onPlaybackSpeed(playbackSpeed);
} }
@ -1703,8 +1702,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
private void notifyTrackSelectionDiscontinuity() { private void notifyTrackSelectionDiscontinuity() {
MediaPeriodHolder periodHolder = queue.getPlayingPeriod(); MediaPeriodHolder periodHolder = queue.getPlayingPeriod();
while (periodHolder != null) { while (periodHolder != null) {
TrackSelection[] trackSelections = periodHolder.getTrackSelectorResult().selections.getAll(); for (ExoTrackSelection trackSelection : periodHolder.getTrackSelectorResult().selections) {
for (TrackSelection trackSelection : trackSelections) {
if (trackSelection != null) { if (trackSelection != null) {
trackSelection.onDiscontinuity(); trackSelection.onDiscontinuity();
} }
@ -1732,8 +1730,13 @@ import java.util.concurrent.atomic.AtomicBoolean;
? livePlaybackSpeedControl.getTargetLiveOffsetUs() ? livePlaybackSpeedControl.getTargetLiveOffsetUs()
: C.TIME_UNSET; : C.TIME_UNSET;
MediaPeriodHolder loadingHolder = queue.getLoadingPeriod(); MediaPeriodHolder loadingHolder = queue.getLoadingPeriod();
boolean bufferedToEnd = loadingHolder.isFullyBuffered() && loadingHolder.info.isFinal; boolean isBufferedToEnd = loadingHolder.isFullyBuffered() && loadingHolder.info.isFinal;
return bufferedToEnd // 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( || loadControl.shouldStartPlayback(
getTotalBufferedDurationUs(), getTotalBufferedDurationUs(),
mediaClock.getPlaybackParameters().speed, mediaClock.getPlaybackParameters().speed,
@ -2016,7 +2019,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
} }
if (!renderer.isCurrentStreamFinal()) { if (!renderer.isCurrentStreamFinal()) {
// The renderer stream is not final, so we can replace the sample streams immediately. // 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( renderer.replaceStream(
formats, formats,
readingPeriodHolder.sampleStreams[i], readingPeriodHolder.sampleStreams[i],
@ -2266,11 +2269,10 @@ import java.util.concurrent.atomic.AtomicBoolean;
} }
private ImmutableList<Metadata> extractMetadataFromTrackSelectionArray( private ImmutableList<Metadata> extractMetadataFromTrackSelectionArray(
TrackSelectionArray trackSelectionArray) { ExoTrackSelection[] trackSelections) {
ImmutableList.Builder<Metadata> result = new ImmutableList.Builder<>(); ImmutableList.Builder<Metadata> result = new ImmutableList.Builder<>();
boolean seenNonEmptyMetadata = false; boolean seenNonEmptyMetadata = false;
for (int i = 0; i < trackSelectionArray.length; i++) { for (ExoTrackSelection trackSelection : trackSelections) {
@Nullable TrackSelection trackSelection = trackSelectionArray.get(i);
if (trackSelection != null) { if (trackSelection != null) {
Format format = trackSelection.getFormat(/* index= */ 0); Format format = trackSelection.getFormat(/* index= */ 0);
if (format.metadata == null) { if (format.metadata == null) {
@ -2318,7 +2320,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
TrackSelectorResult trackSelectorResult = periodHolder.getTrackSelectorResult(); TrackSelectorResult trackSelectorResult = periodHolder.getTrackSelectorResult();
RendererConfiguration rendererConfiguration = RendererConfiguration rendererConfiguration =
trackSelectorResult.rendererConfigurations[rendererIndex]; trackSelectorResult.rendererConfigurations[rendererIndex];
TrackSelection newSelection = trackSelectorResult.selections.get(rendererIndex); ExoTrackSelection newSelection = trackSelectorResult.selections[rendererIndex];
Format[] formats = getFormats(newSelection); Format[] formats = getFormats(newSelection);
// The renderer needs enabling with its new track selection. // The renderer needs enabling with its new track selection.
boolean playing = shouldPlayWhenReady() && playbackInfo.playbackState == Player.STATE_READY; 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); 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. // Build an array of formats contained by the selection.
int length = newSelection != null ? newSelection.length() : 0; int length = newSelection != null ? newSelection.length() : 0;
Format[] formats = new Format[length]; 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. */ /** The operation where this error occurred is not defined. */
public static final int TIMEOUT_OPERATION_UNDEFINED = 0; 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 {@link Player#release}. */
/** The error occurred in {@code Player#release}. */
public static final int TIMEOUT_OPERATION_RELEASE = 1; public static final int TIMEOUT_OPERATION_RELEASE = 1;
/** The error occurred in {@code ExoPlayer#setForegroundMode}. */ /** The error occurred in {@link ExoPlayer#setForegroundMode}. */
// TODO(b/172315872) Set foregroundMode is an ExoPlayer method, NOT a player one.
public static final int TIMEOUT_OPERATION_SET_FOREGROUND_MODE = 2; public static final int TIMEOUT_OPERATION_SET_FOREGROUND_MODE = 2;
/** The error occurred while detaching a surface from the player. */ /** The error occurred while detaching a surface from the player. */
public static final int TIMEOUT_OPERATION_DETACH_SURFACE = 3; 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.TrackGroup;
import com.google.android.exoplayer2.source.TrackGroupArray; 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.Allocator;
/** /** Controls buffering of media. */
* Controls buffering of media.
*/
public interface LoadControl { public interface LoadControl {
/** Called by the player when prepared with a new source. */ /** 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 trackGroups The {@link TrackGroup}s from which the selection was made.
* @param trackSelections The track selections that were made. * @param trackSelections The track selections that were made.
*/ */
void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups, void onTracksSelected(
TrackSelectionArray trackSelections); Renderer[] renderers, TrackGroupArray trackGroups, ExoTrackSelection[] trackSelections);
/** /** Called by the player when stopped. */
* Called by the player when stopped.
*/
void onStopped(); void onStopped();
/** /** Called by the player when released. */
* Called by the player when released.
*/
void onReleased(); 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(); Allocator getAllocator();
/** /**
* Returns the duration of media to retain in the buffer prior to the current playback position, * Returns the duration of media to retain in the buffer prior to the current playback position,
* for fast backward seeking. * for fast backward seeking.
* <p> *
* Note: If {@link #retainBackBufferFromKeyframe()} is false then seeking in the back-buffer will * <p>Note: If {@link #retainBackBufferFromKeyframe()} is false then seeking in the back-buffer
* only be fast if the back-buffer contains a keyframe prior to the seek position. * 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 * <p>Note: Implementations should return a single value. Dynamic changes to the back-buffer are
* currently supported. * not currently supported.
* *
* @return The duration of media to retain in the buffer prior to the current playback position, * @return The duration of media to retain in the buffer prior to the current playback position,
* in microseconds. * in microseconds.
@ -71,17 +63,19 @@ public interface LoadControl {
/** /**
* Returns whether media should be retained from the keyframe before the current playback position * 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. * 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 * <p>Warning: Returning true will cause the back-buffer size to depend on the spacing of
* in the media being played. Returning true is not recommended unless you control the media and * keyframes in the media being played. Returning true is not recommended unless you control the
* are comfortable with the back-buffer size exceeding {@link #getBackBufferDurationUs()} by as * media and are comfortable with the back-buffer size exceeding {@link
* much as the maximum duration between adjacent keyframes in the media. * #getBackBufferDurationUs()} by as much as the maximum duration between adjacent keyframes in
* <p> * the media.
* Note: Implementations should return a single value. Dynamic changes to the back-buffer are not *
* currently supported. * <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 * @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(); 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.MediaSource.MediaPeriodId;
import com.google.android.exoplayer2.source.SampleStream; import com.google.android.exoplayer2.source.SampleStream;
import com.google.android.exoplayer2.source.TrackGroupArray; 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.TrackSelector;
import com.google.android.exoplayer2.trackselection.TrackSelectorResult; import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
@ -233,7 +232,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
throws ExoPlaybackException { throws ExoPlaybackException {
TrackSelectorResult selectorResult = TrackSelectorResult selectorResult =
trackSelector.selectTracks(rendererCapabilities, getTrackGroups(), info.id, timeline); trackSelector.selectTracks(rendererCapabilities, getTrackGroups(), info.id, timeline);
for (TrackSelection trackSelection : selectorResult.selections.getAll()) { for (ExoTrackSelection trackSelection : selectorResult.selections) {
if (trackSelection != null) { if (trackSelection != null) {
trackSelection.onPlaybackSpeed(playbackSpeed); trackSelection.onPlaybackSpeed(playbackSpeed);
} }
@ -289,10 +288,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
trackSelectorResult = newTrackSelectorResult; trackSelectorResult = newTrackSelectorResult;
enableTrackSelectionsInResult(); enableTrackSelectionsInResult();
// Disable streams on the period and get new streams for updated/newly-enabled tracks. // Disable streams on the period and get new streams for updated/newly-enabled tracks.
TrackSelectionArray trackSelections = newTrackSelectorResult.selections;
positionUs = positionUs =
mediaPeriod.selectTracks( mediaPeriod.selectTracks(
trackSelections.getAll(), newTrackSelectorResult.selections,
mayRetainStreamFlags, mayRetainStreamFlags,
sampleStreams, sampleStreams,
streamResetFlags, streamResetFlags,
@ -309,7 +307,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
hasEnabledTracks = true; hasEnabledTracks = true;
} }
} else { } else {
Assertions.checkState(trackSelections.get(i) == null); Assertions.checkState(newTrackSelectorResult.selections[i] == null);
} }
} }
return positionUs; return positionUs;
@ -361,7 +359,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
} }
for (int i = 0; i < trackSelectorResult.length; i++) { for (int i = 0; i < trackSelectorResult.length; i++) {
boolean rendererEnabled = trackSelectorResult.isRendererEnabled(i); boolean rendererEnabled = trackSelectorResult.isRendererEnabled(i);
TrackSelection trackSelection = trackSelectorResult.selections.get(i); ExoTrackSelection trackSelection = trackSelectorResult.selections[i];
if (rendererEnabled && trackSelection != null) { if (rendererEnabled && trackSelection != null) {
trackSelection.enable(); trackSelection.enable();
} }
@ -374,7 +372,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
} }
for (int i = 0; i < trackSelectorResult.length; i++) { for (int i = 0; i < trackSelectorResult.length; i++) {
boolean rendererEnabled = trackSelectorResult.isRendererEnabled(i); boolean rendererEnabled = trackSelectorResult.isRendererEnabled(i);
TrackSelection trackSelection = trackSelectorResult.selections.get(i); ExoTrackSelection trackSelection = trackSelectorResult.selections[i];
if (rendererEnabled && trackSelection != null) { if (rendererEnabled && trackSelection != null) {
trackSelection.disable(); trackSelection.disable();
} }

View file

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

View file

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

View file

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

View file

@ -1457,28 +1457,69 @@ public final class DefaultAudioSink implements AudioSink {
if (!supportedEncoding) { if (!supportedEncoding) {
return null; return null;
} }
if (encoding == C.ENCODING_E_AC3_JOC
// E-AC3 JOC is object based, so any channel count specified in the format is arbitrary. Use 6, && !audioCapabilities.supportsEncoding(C.ENCODING_E_AC3_JOC)) {
// since the E-AC3 compatible part of the stream is 5.1. // E-AC3 receivers support E-AC3 JOC streams (but decode only the base layer).
int channelCount = encoding == C.ENCODING_E_AC3_JOC ? 6 : format.channelCount; encoding = C.ENCODING_E_AC3;
if (channelCount > audioCapabilities.getMaxChannelCount()) { }
if (!audioCapabilities.supportsEncoding(encoding)) {
return null; 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); int channelConfig = getChannelConfigForPassthrough(channelCount);
if (channelConfig == AudioFormat.CHANNEL_INVALID) { if (channelConfig == AudioFormat.CHANNEL_INVALID) {
return null; return null;
} }
if (audioCapabilities.supportsEncoding(encoding)) {
return Pair.create(encoding, channelConfig); 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 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) { private static int getChannelConfigForPassthrough(int channelCount) {

View file

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

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.MimeTypes;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Sets; import com.google.common.collect.Sets;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.Retention; 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 // ResourceBusyException is only available at API 19, so on earlier versions we always
// eagerly release regardless of the underlying error. // eagerly release regardless of the underlying error.
if (!keepaliveSessions.isEmpty()) { 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). // release (via callback).
ImmutableList<DefaultDrmSession> timingOutSessions = ImmutableSet<DefaultDrmSession> keepaliveSessions =
ImmutableList.copyOf(this.keepaliveSessions); ImmutableSet.copyOf(this.keepaliveSessions);
for (DrmSession timingOutSession : timingOutSessions) { for (DrmSession keepaliveSession : keepaliveSessions) {
timingOutSession.release(/* eventDispatcher= */ null); keepaliveSession.release(/* eventDispatcher= */ null);
} }
// Undo the acquisitions from createAndAcquireSession(). // Undo the acquisitions from createAndAcquireSession().
session.release(eventDispatcher); session.release(eventDispatcher);

View file

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

View file

@ -22,13 +22,8 @@ import com.google.android.exoplayer2.Format;
/** Manages a DRM session. */ /** Manages a DRM session. */
public interface DrmSessionManager { public interface DrmSessionManager {
/** Returns {@link #DUMMY}. */ /** An instance that supports no DRM schemes. */
static DrmSessionManager getDummyDrmSessionManager() { DrmSessionManager DRM_UNSUPPORTED =
return DUMMY;
}
/** {@link DrmSessionManager} that supports no DRM schemes. */
DrmSessionManager DUMMY =
new DrmSessionManager() { new DrmSessionManager() {
@Override @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. * 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"); * Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with 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 * See the License for the specific language governing permissions and
* limitations under the License. * 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. * A provider to obtain a {@link DrmSessionManager} suitable for playing the content described by a
* * {@link MediaItem}.
* <p>No Player agnostic track selection is currently supported. Clients should downcast to the
* implementation's track selection.
*/ */
// TODO(b/172315872) Define an interface for track selection. public interface DrmSessionManagerProvider {
public interface TrackSelectorInterface {}
/** 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 { /* package */ final class C2Mp3TimestampTracker {
// Mirroring the actual codec, as can be found at // 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 long DECODER_DELAY_SAMPLES = 529;
private static final String TAG = "C2Mp3TimestampTracker"; 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. // 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) { if (processedSamples == 0) {
anchorTimestampUs = buffer.timeUs; anchorTimestampUs = buffer.timeUs;
processedSamples = frameCount - DECODER_DELAY_SAMPLES; 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. // which may not be widely supported. See https://github.com/google/ExoPlayer/issues/5145.
return true; 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; return true;
} }
} }
@ -334,8 +342,8 @@ public final class MediaCodecInfo {
if (isVideo) { if (isVideo) {
return adaptive; return adaptive;
} else { } else {
Pair<Integer, Integer> codecProfileLevel = MediaCodecUtil.getCodecProfileAndLevel(format); Pair<Integer, Integer> profileLevel = MediaCodecUtil.getCodecProfileAndLevel(format);
return codecProfileLevel != null && codecProfileLevel.first == CodecProfileLevel.AACObjectXHE; return profileLevel != null && profileLevel.first == CodecProfileLevel.AACObjectXHE;
} }
} }
@ -678,6 +686,60 @@ public final class MediaCodecInfo {
return capabilities.getMaxSupportedInstances(); 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 * Returns whether the decoder is known to fail when adapting, despite advertising itself as an
* adaptive decoder. * adaptive decoder.

View file

@ -2206,8 +2206,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
if (bypassBatchBuffer.hasSamples()) { if (bypassBatchBuffer.hasSamples()) {
bypassBatchBuffer.flip(); 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 { private void bypassRead() throws ExoPlaybackException {
@ -2221,7 +2223,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
switch (result) { switch (result) {
case C.RESULT_FORMAT_READ: case C.RESULT_FORMAT_READ:
onInputFormatChanged(formatHolder); onInputFormatChanged(formatHolder);
break; return;
case C.RESULT_NOTHING_READ: case C.RESULT_NOTHING_READ:
return; return;
case C.RESULT_BUFFER_READ: 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;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.Parameters; import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.Parameters;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride; 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.MappingTrackSelector.MappedTrackInfo;
import com.google.android.exoplayer2.trackselection.TrackSelection;
import com.google.android.exoplayer2.trackselection.TrackSelectorResult; import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
import com.google.android.exoplayer2.upstream.Allocator; import com.google.android.exoplayer2.upstream.Allocator;
import com.google.android.exoplayer2.upstream.BandwidthMeter; import com.google.android.exoplayer2.upstream.BandwidthMeter;
@ -465,8 +465,9 @@ public final class DownloadHelper {
private @MonotonicNonNull MediaPreparer mediaPreparer; private @MonotonicNonNull MediaPreparer mediaPreparer;
private TrackGroupArray @MonotonicNonNull [] trackGroupArrays; private TrackGroupArray @MonotonicNonNull [] trackGroupArrays;
private MappedTrackInfo @MonotonicNonNull [] mappedTrackInfos; private MappedTrackInfo @MonotonicNonNull [] mappedTrackInfos;
private List<TrackSelection> @MonotonicNonNull [][] trackSelectionsByPeriodAndRenderer; private List<ExoTrackSelection> @MonotonicNonNull [][] trackSelectionsByPeriodAndRenderer;
private List<TrackSelection> @MonotonicNonNull [][] immutableTrackSelectionsByPeriodAndRenderer; private List<ExoTrackSelection> @MonotonicNonNull [][]
immutableTrackSelectionsByPeriodAndRenderer;
/** /**
* Creates download helper. * 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. * called until after preparation completes.
* *
* @param periodIndex The period index. * @param periodIndex The period index.
* @param rendererIndex The renderer 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(); assertPreparedWithMedia();
return immutableTrackSelectionsByPeriodAndRenderer[periodIndex][rendererIndex]; return immutableTrackSelectionsByPeriodAndRenderer[periodIndex][rendererIndex];
} }
@ -751,7 +752,7 @@ public final class DownloadHelper {
} }
assertPreparedWithMedia(); assertPreparedWithMedia();
List<StreamKey> streamKeys = new ArrayList<>(); List<StreamKey> streamKeys = new ArrayList<>();
List<TrackSelection> allSelections = new ArrayList<>(); List<ExoTrackSelection> allSelections = new ArrayList<>();
int periodCount = trackSelectionsByPeriodAndRenderer.length; int periodCount = trackSelectionsByPeriodAndRenderer.length;
for (int periodIndex = 0; periodIndex < periodCount; periodIndex++) { for (int periodIndex = 0; periodIndex < periodCount; periodIndex++) {
allSelections.clear(); allSelections.clear();
@ -773,9 +774,9 @@ public final class DownloadHelper {
int periodCount = mediaPreparer.mediaPeriods.length; int periodCount = mediaPreparer.mediaPeriods.length;
int rendererCount = rendererCapabilities.length; int rendererCount = rendererCapabilities.length;
trackSelectionsByPeriodAndRenderer = trackSelectionsByPeriodAndRenderer =
(List<TrackSelection>[][]) new List<?>[periodCount][rendererCount]; (List<ExoTrackSelection>[][]) new List<?>[periodCount][rendererCount];
immutableTrackSelectionsByPeriodAndRenderer = immutableTrackSelectionsByPeriodAndRenderer =
(List<TrackSelection>[][]) new List<?>[periodCount][rendererCount]; (List<ExoTrackSelection>[][]) new List<?>[periodCount][rendererCount];
for (int i = 0; i < periodCount; i++) { for (int i = 0; i < periodCount; i++) {
for (int j = 0; j < rendererCount; j++) { for (int j = 0; j < rendererCount; j++) {
trackSelectionsByPeriodAndRenderer[i][j] = new ArrayList<>(); trackSelectionsByPeriodAndRenderer[i][j] = new ArrayList<>();
@ -847,15 +848,15 @@ public final class DownloadHelper {
new MediaPeriodId(mediaPreparer.timeline.getUidOfPeriod(periodIndex)), new MediaPeriodId(mediaPreparer.timeline.getUidOfPeriod(periodIndex)),
mediaPreparer.timeline); mediaPreparer.timeline);
for (int i = 0; i < trackSelectorResult.length; i++) { for (int i = 0; i < trackSelectorResult.length; i++) {
@Nullable TrackSelection newSelection = trackSelectorResult.selections.get(i); @Nullable ExoTrackSelection newSelection = trackSelectorResult.selections[i];
if (newSelection == null) { if (newSelection == null) {
continue; continue;
} }
List<TrackSelection> existingSelectionList = List<ExoTrackSelection> existingSelectionList =
trackSelectionsByPeriodAndRenderer[periodIndex][i]; trackSelectionsByPeriodAndRenderer[periodIndex][i];
boolean mergedWithExistingSelection = false; boolean mergedWithExistingSelection = false;
for (int j = 0; j < existingSelectionList.size(); j++) { for (int j = 0; j < existingSelectionList.size(); j++) {
TrackSelection existingSelection = existingSelectionList.get(j); ExoTrackSelection existingSelection = existingSelectionList.get(j);
if (existingSelection.getTrackGroup() == newSelection.getTrackGroup()) { if (existingSelection.getTrackGroup() == newSelection.getTrackGroup()) {
// Merge with existing selection. // Merge with existing selection.
scratchSet.clear(); scratchSet.clear();
@ -1066,15 +1067,15 @@ public final class DownloadHelper {
private static final class DownloadTrackSelection extends BaseTrackSelection { private static final class DownloadTrackSelection extends BaseTrackSelection {
private static final class Factory implements TrackSelection.Factory { private static final class Factory implements ExoTrackSelection.Factory {
@Override @Override
public @NullableType TrackSelection[] createTrackSelections( public @NullableType ExoTrackSelection[] createTrackSelections(
@NullableType Definition[] definitions, @NullableType Definition[] definitions,
BandwidthMeter bandwidthMeter, BandwidthMeter bandwidthMeter,
MediaPeriodId mediaPeriodId, MediaPeriodId mediaPeriodId,
Timeline timeline) { Timeline timeline) {
@NullableType TrackSelection[] selections = new TrackSelection[definitions.length]; @NullableType ExoTrackSelection[] selections = new ExoTrackSelection[definitions.length];
for (int i = 0; i < definitions.length; i++) { for (int i = 0; i < definitions.length; i++) {
selections[i] = selections[i] =
definitions[i] == null 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.FormatHolder;
import com.google.android.exoplayer2.SeekParameters; import com.google.android.exoplayer2.SeekParameters;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer; 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.Assertions;
import com.google.android.exoplayer2.util.MimeTypes; import com.google.android.exoplayer2.util.MimeTypes;
import com.google.android.exoplayer2.util.Util; 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 { 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; public final MediaPeriod mediaPeriod;
@Nullable private MediaPeriod.Callback callback; @Nullable private MediaPeriod.Callback callback;
@ -98,7 +96,7 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
@Override @Override
public long selectTracks( public long selectTracks(
@NullableType TrackSelection[] selections, @NullableType ExoTrackSelection[] selections,
boolean[] mayRetainStreamFlags, boolean[] mayRetainStreamFlags,
@NullableType SampleStream[] streams, @NullableType SampleStream[] streams,
boolean[] streamResetFlags, boolean[] streamResetFlags,
@ -250,7 +248,7 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
} }
private static boolean shouldKeepInitialDiscontinuity( 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 // 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 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, // 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 // 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. // seek behaviour and do not need an initial discontinuity to reset the renderer.
if (startUs != 0) { if (startUs != 0) {
for (TrackSelection trackSelection : selections) { for (ExoTrackSelection trackSelection : selections) {
if (trackSelection != null) { if (trackSelection != null) {
Format selectedFormat = trackSelection.getSelectedFormat(); Format selectedFormat = trackSelection.getSelectedFormat();
if (!MimeTypes.allSamplesAreSyncSamples( if (!MimeTypes.allSamplesAreSyncSamples(
@ -274,9 +272,7 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
return false; return false;
} }
/** /** Wraps a {@link SampleStream} and clips its samples. */
* Wraps a {@link SampleStream} and clips its samples.
*/
private final class ClippingSampleStream implements SampleStream { private final class ClippingSampleStream implements SampleStream {
public final SampleStream childStream; public final SampleStream childStream;
@ -302,8 +298,8 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
} }
@Override @Override
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, public int readData(
boolean requireFormat) { FormatHolder formatHolder, DecoderInputBuffer buffer, boolean requireFormat) {
if (isPendingInitialDiscontinuity()) { if (isPendingInitialDiscontinuity()) {
return C.RESULT_NOTHING_READ; 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.C;
import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.drm.DrmSessionManager; 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.DefaultExtractorsFactory;
import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.extractor.ExtractorsFactory;
import com.google.android.exoplayer2.offline.StreamKey; import com.google.android.exoplayer2.offline.StreamKey;
@ -100,15 +101,12 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
private static final String TAG = "DefaultMediaSourceFactory"; private static final String TAG = "DefaultMediaSourceFactory";
private final MediaSourceDrmHelper mediaSourceDrmHelper;
private final DataSource.Factory dataSourceFactory; private final DataSource.Factory dataSourceFactory;
private final SparseArray<MediaSourceFactory> mediaSourceFactories; private final SparseArray<MediaSourceFactory> mediaSourceFactories;
@C.ContentType private final int[] supportedTypes; @C.ContentType private final int[] supportedTypes;
@Nullable private AdsLoaderProvider adsLoaderProvider; @Nullable private AdsLoaderProvider adsLoaderProvider;
@Nullable private AdViewProvider adViewProvider; @Nullable private AdViewProvider adViewProvider;
@Nullable private DrmSessionManager drmSessionManager;
@Nullable private List<StreamKey> streamKeys;
@Nullable private LoadErrorHandlingPolicy loadErrorHandlingPolicy; @Nullable private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private long liveTargetOffsetMs; private long liveTargetOffsetMs;
private long liveMinOffsetMs; private long liveMinOffsetMs;
@ -157,7 +155,6 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
public DefaultMediaSourceFactory( public DefaultMediaSourceFactory(
DataSource.Factory dataSourceFactory, ExtractorsFactory extractorsFactory) { DataSource.Factory dataSourceFactory, ExtractorsFactory extractorsFactory) {
this.dataSourceFactory = dataSourceFactory; this.dataSourceFactory = dataSourceFactory;
mediaSourceDrmHelper = new MediaSourceDrmHelper();
mediaSourceFactories = loadDelegates(dataSourceFactory, extractorsFactory); mediaSourceFactories = loadDelegates(dataSourceFactory, extractorsFactory);
supportedTypes = new int[mediaSourceFactories.size()]; supportedTypes = new int[mediaSourceFactories.size()];
for (int i = 0; i < mediaSourceFactories.size(); i++) { for (int i = 0; i < mediaSourceFactories.size(); i++) {
@ -254,23 +251,41 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
return this; return this;
} }
@SuppressWarnings("deprecation") // Calling through to the same deprecated method.
@Override @Override
public DefaultMediaSourceFactory setDrmHttpDataSourceFactory( public DefaultMediaSourceFactory setDrmHttpDataSourceFactory(
@Nullable HttpDataSource.Factory drmHttpDataSourceFactory) { @Nullable HttpDataSource.Factory drmHttpDataSourceFactory) {
mediaSourceDrmHelper.setDrmHttpDataSourceFactory(drmHttpDataSourceFactory); for (int i = 0; i < mediaSourceFactories.size(); i++) {
mediaSourceFactories.valueAt(i).setDrmHttpDataSourceFactory(drmHttpDataSourceFactory);
}
return this; return this;
} }
@SuppressWarnings("deprecation") // Calling through to the same deprecated method.
@Override @Override
public DefaultMediaSourceFactory setDrmUserAgent(@Nullable String userAgent) { public DefaultMediaSourceFactory setDrmUserAgent(@Nullable String userAgent) {
mediaSourceDrmHelper.setDrmUserAgent(userAgent); for (int i = 0; i < mediaSourceFactories.size(); i++) {
mediaSourceFactories.valueAt(i).setDrmUserAgent(userAgent);
}
return this; return this;
} }
@SuppressWarnings("deprecation") // Calling through to the same deprecated method.
@Override @Override
public DefaultMediaSourceFactory setDrmSessionManager( public DefaultMediaSourceFactory setDrmSessionManager(
@Nullable DrmSessionManager drmSessionManager) { @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; return this;
} }
@ -278,6 +293,9 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
public DefaultMediaSourceFactory setLoadErrorHandlingPolicy( public DefaultMediaSourceFactory setLoadErrorHandlingPolicy(
@Nullable LoadErrorHandlingPolicy loadErrorHandlingPolicy) { @Nullable LoadErrorHandlingPolicy loadErrorHandlingPolicy) {
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy; this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
for (int i = 0; i < mediaSourceFactories.size(); i++) {
mediaSourceFactories.valueAt(i).setLoadErrorHandlingPolicy(loadErrorHandlingPolicy);
}
return this; return this;
} }
@ -285,11 +303,13 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
* @deprecated Use {@link MediaItem.Builder#setStreamKeys(List)} and {@link * @deprecated Use {@link MediaItem.Builder#setStreamKeys(List)} and {@link
* #createMediaSource(MediaItem)} instead. * #createMediaSource(MediaItem)} instead.
*/ */
@SuppressWarnings("deprecation") @SuppressWarnings("deprecation") // Calling through to the same deprecated method.
@Deprecated @Deprecated
@Override @Override
public DefaultMediaSourceFactory setStreamKeys(@Nullable List<StreamKey> streamKeys) { 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; return this;
} }
@ -298,7 +318,6 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
return Arrays.copyOf(supportedTypes, supportedTypes.length); return Arrays.copyOf(supportedTypes, supportedTypes.length);
} }
@SuppressWarnings("deprecation")
@Override @Override
public MediaSource createMediaSource(MediaItem mediaItem) { public MediaSource createMediaSource(MediaItem mediaItem) {
Assertions.checkNotNull(mediaItem.playbackProperties); Assertions.checkNotNull(mediaItem.playbackProperties);
@ -309,13 +328,6 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
@Nullable MediaSourceFactory mediaSourceFactory = mediaSourceFactories.get(type); @Nullable MediaSourceFactory mediaSourceFactory = mediaSourceFactories.get(type);
Assertions.checkNotNull( Assertions.checkNotNull(
mediaSourceFactory, "No suitable media source factory found for content type: " + type); 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. // Make sure to retain the very same media item instance, if no value needs to be overridden.
if ((mediaItem.liveConfiguration.targetOffsetMs == C.TIME_UNSET 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.Player;
import com.google.android.exoplayer2.Timeline; import com.google.android.exoplayer2.Timeline;
import com.google.android.exoplayer2.drm.DrmSessionManager; 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.DefaultExtractorsFactory;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.extractor.ExtractorsFactory;
@ -154,6 +155,18 @@ public final class ExtractorMediaSource extends CompositeMediaSource<Void> {
return this; 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 Use {@link ProgressiveMediaSource.Factory#setDrmSessionManager} instead. */
@Deprecated @Deprecated
@Override @Override
@ -324,7 +337,7 @@ public final class ExtractorMediaSource extends CompositeMediaSource<Void> {
.build(), .build(),
dataSourceFactory, dataSourceFactory,
extractorsFactory, extractorsFactory,
DrmSessionManager.getDummyDrmSessionManager(), DrmSessionManager.DRM_UNSUPPORTED,
loadableLoadErrorHandlingPolicy, loadableLoadErrorHandlingPolicy,
continueLoadingCheckIntervalBytes); continueLoadingCheckIntervalBytes);
} }

View file

@ -23,7 +23,7 @@ import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.SeekParameters; import com.google.android.exoplayer2.SeekParameters;
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId; 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 com.google.android.exoplayer2.upstream.Allocator;
import java.io.IOException; import java.io.IOException;
import org.checkerframework.checker.nullness.compatqual.NullableType; import org.checkerframework.checker.nullness.compatqual.NullableType;
@ -173,7 +173,7 @@ public final class MaskingMediaPeriod implements MediaPeriod, MediaPeriod.Callba
@Override @Override
public long selectTracks( public long selectTracks(
@NullableType TrackSelection[] selections, @NullableType ExoTrackSelection[] selections,
boolean[] mayRetainStreamFlags, boolean[] mayRetainStreamFlags,
@NullableType SampleStream[] streams, @NullableType SampleStream[] streams,
boolean[] streamResetFlags, 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.Timeline;
import com.google.android.exoplayer2.offline.StreamKey; import com.google.android.exoplayer2.offline.StreamKey;
import com.google.android.exoplayer2.source.MediaSource.MediaSourceCaller; 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.io.IOException;
import java.util.Collections; import java.util.Collections;
import java.util.List; 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 * 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 * methods are called on the player's internal playback thread, as described in the {@link
* {@link ExoPlayer} Javadoc. * 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 { 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> { interface Callback extends SequenceableLoader.Callback<MediaPeriod> {
/** /**
* Called when preparation completes. * Called when preparation completes.
* *
* <p>Called on the playback thread. After invoking this method, the {@link MediaPeriod} can * <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. * long)} to be called with the initial track selection.
* *
* @param mediaPeriod The prepared {@link MediaPeriod}. * @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 * 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. * <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. * which stream keys are requested.
* @return The corresponding {@link StreamKey StreamKeys} for the selected tracks, or an empty * @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 * list if filtering is not possible and the entire media needs to be loaded to play the
* selected tracks. * selected tracks.
*/ */
default List<StreamKey> getStreamKeys(List<TrackSelection> trackSelections) { default List<StreamKey> getStreamKeys(List<ExoTrackSelection> trackSelections) {
return Collections.emptyList(); 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 * corresponding flag in {@code streamResetFlags} will be set to true. This flag will also be set
* if a new sample stream is created. * if a new sample stream is created.
* *
* <p>Note that previously passed {@link TrackSelection TrackSelections} are no longer valid, and * <p>Note that previously passed {@link ExoTrackSelection TrackSelections} are no longer valid,
* any references to them must be updated to point to the new selections. * 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. * <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. * @return The actual position at which the tracks were enabled, in microseconds.
*/ */
long selectTracks( long selectTracks(
@NullableType TrackSelection[] selections, @NullableType ExoTrackSelection[] selections,
boolean[] mayRetainStreamFlags, boolean[] mayRetainStreamFlags,
@NullableType SampleStream[] streams, @NullableType SampleStream[] streams,
boolean[] streamResetFlags, boolean[] streamResetFlags,

View file

@ -20,7 +20,9 @@ import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager; 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.DrmSessionManager;
import com.google.android.exoplayer2.drm.DrmSessionManagerProvider;
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback; import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
import com.google.android.exoplayer2.offline.StreamKey; import com.google.android.exoplayer2.offline.StreamKey;
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory; import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
@ -56,41 +58,80 @@ public interface MediaSourceFactory {
return this; 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 * Sets the {@link DrmSessionManager} to use for all media items regardless of their {@link
* MediaItem.DrmConfiguration}. * 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 * @param drmSessionManager The {@link DrmSessionManager}, or {@code null} to use the {@link
* DefaultDrmSessionManager}. * DefaultDrmSessionManager}.
* @return This factory, for convenience. * @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); MediaSourceFactory setDrmSessionManager(@Nullable DrmSessionManager drmSessionManager);
/** /**
* Sets the {@link HttpDataSource.Factory} to be used for creating {@link HttpMediaDrmCallback * Sets the {@link HttpDataSource.Factory} to be used for creating {@link HttpMediaDrmCallback
* HttpMediaDrmCallbacks} to execute key and provisioning requests over HTTP. * HttpMediaDrmCallbacks} to execute key and provisioning requests over HTTP.
* *
* <p>In case a {@link DrmSessionManager} has been set by {@link * <p>Calls to this method are ignored if either a {@link
* #setDrmSessionManager(DrmSessionManager)}, this data source factory is ignored. * #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 * @param drmHttpDataSourceFactory The HTTP data source factory, or {@code null} to use {@link
* DefaultHttpDataSourceFactory}. * DefaultHttpDataSourceFactory}.
* @return This factory, for convenience. * @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( MediaSourceFactory setDrmHttpDataSourceFactory(
@Nullable HttpDataSource.Factory drmHttpDataSourceFactory); @Nullable HttpDataSource.Factory drmHttpDataSourceFactory);
/** /**
* Sets the optional user agent to be used for DRM requests. * Sets the optional user agent to be used for DRM requests.
* *
* <p>In case a factory has been set by {@link * <p>Calls to this method are ignored if any of the following are provided:
* #setDrmHttpDataSourceFactory(HttpDataSource.Factory)} or a {@link DrmSessionManager} has been *
* set by {@link #setDrmSessionManager(DrmSessionManager)}, this user agent is ignored. * <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 * @param userAgent The user agent to be used for DRM requests, or {@code null} to use the
* default. * default.
* @return This factory, for convenience. * @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); 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.SeekParameters;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.offline.StreamKey; 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 com.google.android.exoplayer2.util.Assertions;
import java.io.IOException; import java.io.IOException;
import java.util.ArrayList; import java.util.ArrayList;
@ -33,9 +33,7 @@ import java.util.List;
import org.checkerframework.checker.nullness.compatqual.NullableType; import org.checkerframework.checker.nullness.compatqual.NullableType;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; 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 { /* package */ final class MergingMediaPeriod implements MediaPeriod, MediaPeriod.Callback {
private final MediaPeriod[] periods; private final MediaPeriod[] periods;
@ -100,7 +98,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override @Override
public long selectTracks( public long selectTracks(
@NullableType TrackSelection[] selections, @NullableType ExoTrackSelection[] selections,
boolean[] mayRetainStreamFlags, boolean[] mayRetainStreamFlags,
@NullableType SampleStream[] streams, @NullableType SampleStream[] streams,
boolean[] streamResetFlags, 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. // Select tracks for each child, copying the resulting streams back into a new streams array.
@NullableType SampleStream[] newStreams = new SampleStream[selections.length]; @NullableType SampleStream[] newStreams = new SampleStream[selections.length];
@NullableType SampleStream[] childStreams = 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); ArrayList<MediaPeriod> enabledPeriodsList = new ArrayList<>(periods.length);
for (int i = 0; i < periods.length; i++) { for (int i = 0; i < periods.length; i++) {
for (int j = 0; j < selections.length; j++) { for (int j = 0; j < selections.length; j++) {
childStreams[j] = streamChildIndices[j] == i ? streams[j] : null; childStreams[j] = streamChildIndices[j] == i ? streams[j] : null;
childSelections[j] = selectionChildIndices[j] == i ? selections[j] : null; childSelections[j] = selectionChildIndices[j] == i ? selections[j] : null;
} }
long selectPositionUs = periods[i].selectTracks(childSelections, mayRetainStreamFlags, long selectPositionUs =
childStreams, streamResetFlags, positionUs); periods[i].selectTracks(
childSelections, mayRetainStreamFlags, childStreams, streamResetFlags, positionUs);
if (i == 0) { if (i == 0) {
positionUs = selectPositionUs; positionUs = selectPositionUs;
} else if (selectPositionUs != positionUs) { } else if (selectPositionUs != positionUs) {
@ -314,13 +313,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
@Override @Override
public List<StreamKey> getStreamKeys(List<TrackSelection> trackSelections) { public List<StreamKey> getStreamKeys(List<ExoTrackSelection> trackSelections) {
return mediaPeriod.getStreamKeys(trackSelections); return mediaPeriod.getStreamKeys(trackSelections);
} }
@Override @Override
public long selectTracks( public long selectTracks(
@NullableType TrackSelection[] selections, @NullableType ExoTrackSelection[] selections,
boolean[] mayRetainStreamFlags, boolean[] mayRetainStreamFlags,
@NullableType SampleStream[] streams, @NullableType SampleStream[] streams,
boolean[] streamResetFlags, 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.Metadata;
import com.google.android.exoplayer2.metadata.icy.IcyHeaders; import com.google.android.exoplayer2.metadata.icy.IcyHeaders;
import com.google.android.exoplayer2.source.SampleQueue.UpstreamFormatChangedListener; 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.Allocator;
import com.google.android.exoplayer2.upstream.DataSource; import com.google.android.exoplayer2.upstream.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
@ -252,7 +252,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override @Override
public long selectTracks( public long selectTracks(
@NullableType TrackSelection[] selections, @NullableType ExoTrackSelection[] selections,
boolean[] mayRetainStreamFlags, boolean[] mayRetainStreamFlags,
@NullableType SampleStream[] streams, @NullableType SampleStream[] streams,
boolean[] streamResetFlags, boolean[] streamResetFlags,
@ -277,7 +277,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
// Select new tracks. // Select new tracks.
for (int i = 0; i < selections.length; i++) { for (int i = 0; i < selections.length; i++) {
if (streams[i] == null && selections[i] != null) { if (streams[i] == null && selections[i] != null) {
TrackSelection selection = selections[i]; ExoTrackSelection selection = selections[i];
Assertions.checkState(selection.length() == 1); Assertions.checkState(selection.length() == 1);
Assertions.checkState(selection.getIndexInTrackGroup(0) == 0); Assertions.checkState(selection.getIndexInTrackGroup(0) == 0);
int track = tracks.indexOf(selection.getTrackGroup()); 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.C;
import com.google.android.exoplayer2.MediaItem; import com.google.android.exoplayer2.MediaItem;
import com.google.android.exoplayer2.Timeline; 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.DrmSessionManager;
import com.google.android.exoplayer2.drm.DrmSessionManagerProvider;
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory; import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
import com.google.android.exoplayer2.extractor.Extractor; import com.google.android.exoplayer2.extractor.Extractor;
import com.google.android.exoplayer2.extractor.ExtractorsFactory; import com.google.android.exoplayer2.extractor.ExtractorsFactory;
@ -51,10 +53,10 @@ public final class ProgressiveMediaSource extends BaseMediaSource
public static final class Factory implements MediaSourceFactory { public static final class Factory implements MediaSourceFactory {
private final DataSource.Factory dataSourceFactory; private final DataSource.Factory dataSourceFactory;
private final MediaSourceDrmHelper mediaSourceDrmHelper;
private ExtractorsFactory extractorsFactory; private ExtractorsFactory extractorsFactory;
@Nullable private DrmSessionManager drmSessionManager; private boolean usingCustomDrmSessionManagerProvider;
private DrmSessionManagerProvider drmSessionManagerProvider;
private LoadErrorHandlingPolicy loadErrorHandlingPolicy; private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
private int continueLoadingCheckIntervalBytes; private int continueLoadingCheckIntervalBytes;
@Nullable private String customCacheKey; @Nullable private String customCacheKey;
@ -79,7 +81,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource
public Factory(DataSource.Factory dataSourceFactory, ExtractorsFactory extractorsFactory) { public Factory(DataSource.Factory dataSourceFactory, ExtractorsFactory extractorsFactory) {
this.dataSourceFactory = dataSourceFactory; this.dataSourceFactory = dataSourceFactory;
this.extractorsFactory = extractorsFactory; this.extractorsFactory = extractorsFactory;
mediaSourceDrmHelper = new MediaSourceDrmHelper(); drmSessionManagerProvider = new DefaultDrmSessionManagerProvider();
loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy(); loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy();
continueLoadingCheckIntervalBytes = DEFAULT_LOADING_CHECK_INTERVAL_BYTES; continueLoadingCheckIntervalBytes = DEFAULT_LOADING_CHECK_INTERVAL_BYTES;
} }
@ -148,21 +150,42 @@ public final class ProgressiveMediaSource extends BaseMediaSource
} }
@Override @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) { public Factory setDrmSessionManager(@Nullable DrmSessionManager drmSessionManager) {
this.drmSessionManager = drmSessionManager; if (drmSessionManager == null) {
setDrmSessionManagerProvider(null);
} else {
setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager);
}
return this; return this;
} }
@Override @Override
public Factory setDrmHttpDataSourceFactory( public Factory setDrmHttpDataSourceFactory(
@Nullable HttpDataSource.Factory drmHttpDataSourceFactory) { @Nullable HttpDataSource.Factory drmHttpDataSourceFactory) {
mediaSourceDrmHelper.setDrmHttpDataSourceFactory(drmHttpDataSourceFactory); if (!usingCustomDrmSessionManagerProvider) {
((DefaultDrmSessionManagerProvider) drmSessionManagerProvider)
.setDrmHttpDataSourceFactory(drmHttpDataSourceFactory);
}
return this; return this;
} }
@Override @Override
public Factory setDrmUserAgent(@Nullable String userAgent) { public Factory setDrmUserAgent(@Nullable String userAgent) {
mediaSourceDrmHelper.setDrmUserAgent(userAgent); if (!usingCustomDrmSessionManagerProvider) {
((DefaultDrmSessionManagerProvider) drmSessionManagerProvider).setDrmUserAgent(userAgent);
}
return this; return this;
} }
@ -198,7 +221,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource
mediaItem, mediaItem,
dataSourceFactory, dataSourceFactory,
extractorsFactory, extractorsFactory,
drmSessionManager != null ? drmSessionManager : mediaSourceDrmHelper.create(mediaItem), drmSessionManagerProvider.get(mediaItem),
loadErrorHandlingPolicy, loadErrorHandlingPolicy,
continueLoadingCheckIntervalBytes); 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 buffer The buffer to populate.
* @param extrasHolder The extras holder whose offset should be read and subsequently adjusted. * @param extrasHolder The extras holder whose offset should be read and subsequently adjusted.
*/ */
public void readToBuffer(DecoderInputBuffer buffer, SampleExtrasHolder extrasHolder) { public void readToBuffer(DecoderInputBuffer buffer, SampleExtrasHolder extrasHolder) {
// Read encryption data if the sample is encrypted. readAllocationNode = readSampleData(readAllocationNode, buffer, extrasHolder, scratch);
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;
// Write the sample data. /**
buffer.ensureSpaceForWrite(sampleSize); * Peeks data from the rolling buffer to populate a decoder input buffer, without advancing the
readData(extrasHolder.offset, buffer.data, sampleSize); * read position.
extrasHolder.offset += sampleSize; *
extrasHolder.size -= sampleSize; * @param buffer The buffer to populate.
* @param extrasHolder The extras holder whose offset should be read and subsequently adjusted.
// Write the remaining data as supplemental data. */
buffer.resetSupplementalData(extrasHolder.size); public void peekToBuffer(DecoderInputBuffer buffer, SampleExtrasHolder extrasHolder) {
readData(extrasHolder.offset, buffer.supplementalData, extrasHolder.size); readSampleData(readAllocationNode, buffer, extrasHolder, scratch);
} else {
// Write the sample data.
buffer.ensureSpaceForWrite(extrasHolder.size);
readData(extrasHolder.offset, buffer.data, extrasHolder.size);
}
} }
/** /**
@ -210,151 +196,6 @@ import java.util.Arrays;
// Private methods. // 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}. * 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. */ /** A node in a linked list of {@link Allocation}s held by the output. */
private static final class AllocationNode { private static final class AllocationNode {

View file

@ -377,6 +377,20 @@ public class SampleQueue implements TrackOutput {
return mayReadSample(relativeReadIndex); 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. * 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.MediaItem;
import com.google.android.exoplayer2.SeekParameters; import com.google.android.exoplayer2.SeekParameters;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer; 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.Allocator;
import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
@ -194,7 +194,7 @@ public final class SilenceMediaSource extends BaseMediaSource {
@Override @Override
public long selectTracks( public long selectTracks(
@NullableType TrackSelection[] selections, @NullableType ExoTrackSelection[] selections,
boolean[] mayRetainStreamFlags, boolean[] mayRetainStreamFlags,
@NullableType SampleStream[] streams, @NullableType SampleStream[] streams,
boolean[] streamResetFlags, boolean[] streamResetFlags,

View file

@ -31,10 +31,7 @@ public final class SinglePeriodTimeline extends Timeline {
private static final Object UID = new Object(); private static final Object UID = new Object();
private static final MediaItem MEDIA_ITEM = private static final MediaItem MEDIA_ITEM =
new MediaItem.Builder() new MediaItem.Builder().setMediaId("SinglePeriodTimeline").setUri(Uri.EMPTY).build();
.setMediaId("com.google.android.exoplayer2.source.SinglePeriodTimeline")
.setUri(Uri.EMPTY)
.build();
private final long presentationStartTimeMs; private final long presentationStartTimeMs;
private final long windowStartTimeMs; 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.SeekParameters;
import com.google.android.exoplayer2.decoder.DecoderInputBuffer; import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher; 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.DataSource;
import com.google.android.exoplayer2.upstream.DataSpec; import com.google.android.exoplayer2.upstream.DataSpec;
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy; 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.StatsDataSource;
import com.google.android.exoplayer2.upstream.TransferListener; import com.google.android.exoplayer2.upstream.TransferListener;
import com.google.android.exoplayer2.util.Assertions; 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.MimeTypes;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import java.io.IOException; import java.io.IOException;
@ -41,15 +42,13 @@ import java.util.Arrays;
import org.checkerframework.checker.nullness.compatqual.NullableType; import org.checkerframework.checker.nullness.compatqual.NullableType;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull; import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** /** A {@link MediaPeriod} with a single sample. */
* A {@link MediaPeriod} with a single sample. /* package */ final class SingleSampleMediaPeriod
*/ implements MediaPeriod, Loader.Callback<SingleSampleMediaPeriod.SourceLoadable> {
/* package */ final class SingleSampleMediaPeriod implements MediaPeriod,
Loader.Callback<SingleSampleMediaPeriod.SourceLoadable> {
/** private static final String TAG = "SingleSampleMediaPeriod";
* The initial size of the allocation used to hold the sample data.
*/ /** The initial size of the allocation used to hold the sample data. */
private static final int INITIAL_SAMPLE_SIZE = 1024; private static final int INITIAL_SAMPLE_SIZE = 1024;
private final DataSpec dataSpec; private final DataSpec dataSpec;
@ -113,7 +112,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@Override @Override
public long selectTracks( public long selectTracks(
@NullableType TrackSelection[] selections, @NullableType ExoTrackSelection[] selections,
boolean[] mayRetainStreamFlags, boolean[] mayRetainStreamFlags,
@NullableType SampleStream[] streams, @NullableType SampleStream[] streams,
boolean[] streamResetFlags, boolean[] streamResetFlags,
@ -294,6 +293,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
LoadErrorAction action; LoadErrorAction action;
if (treatLoadErrorsAsEndOfStream && errorCanBePropagated) { if (treatLoadErrorsAsEndOfStream && errorCanBePropagated) {
Log.w(TAG, "Loading failed, treating as end-of-stream.", error);
loadingFinished = true; loadingFinished = true;
action = Loader.DONT_RETRY; action = Loader.DONT_RETRY;
} else { } else {
@ -348,8 +348,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
} }
@Override @Override
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer, public int readData(
boolean requireFormat) { FormatHolder formatHolder, DecoderInputBuffer buffer, boolean requireFormat) {
maybeNotifyDownstreamFormat(); maybeNotifyDownstreamFormat();
if (streamState == STREAM_STATE_END_OF_STREAM) { if (streamState == STREAM_STATE_END_OF_STREAM) {
buffer.addFlag(C.BUFFER_FLAG_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 ParsableByteArray ccData;
private final ParsableBitArray serviceBlockPacket; private final ParsableBitArray serviceBlockPacket;
private int previousSequenceNumber;
// TODO: Use isWideAspectRatio in decoding. // TODO: Use isWideAspectRatio in decoding.
@SuppressWarnings({"unused", "FieldCanBeLocal"}) @SuppressWarnings({"unused", "FieldCanBeLocal"})
private final boolean isWideAspectRatio; private final boolean isWideAspectRatio;
@ -162,6 +163,7 @@ public final class Cea708Decoder extends CeaDecoder {
public Cea708Decoder(int accessibilityChannel, @Nullable List<byte[]> initializationData) { public Cea708Decoder(int accessibilityChannel, @Nullable List<byte[]> initializationData) {
ccData = new ParsableByteArray(); ccData = new ParsableByteArray();
serviceBlockPacket = new ParsableBitArray(); serviceBlockPacket = new ParsableBitArray();
previousSequenceNumber = C.INDEX_UNSET;
selectedServiceNumber = accessibilityChannel == Format.NO_VALUE ? 1 : accessibilityChannel; selectedServiceNumber = accessibilityChannel == Format.NO_VALUE ? 1 : accessibilityChannel;
isWideAspectRatio = isWideAspectRatio =
initializationData != null initializationData != null
@ -231,6 +233,18 @@ public final class Cea708Decoder extends CeaDecoder {
finalizeCurrentPacket(); finalizeCurrentPacket();
int sequenceNumber = (ccData1 & 0xC0) >> 6; // first 2 bits 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 int packetSize = ccData1 & 0x3F; // last 6 bits
if (packetSize == 0) { if (packetSize == 0) {
packetSize = 64; packetSize = 64;
@ -270,10 +284,18 @@ public final class Cea708Decoder extends CeaDecoder {
@RequiresNonNull("currentDtvCcPacket") @RequiresNonNull("currentDtvCcPacket")
private void processCurrentPacket() { private void processCurrentPacket() {
if (currentDtvCcPacket.currentIndex != (currentDtvCcPacket.packetSize * 2 - 1)) { if (currentDtvCcPacket.currentIndex != (currentDtvCcPacket.packetSize * 2 - 1)) {
Log.w(TAG, "DtvCcPacket ended prematurely; size is " + (currentDtvCcPacket.packetSize * 2 - 1) Log.d(
+ ", but current index is " + currentDtvCcPacket.currentIndex + " (sequence number " TAG,
+ currentDtvCcPacket.sequenceNumber + "); ignoring packet"); "DtvCcPacket ended prematurely; size is "
return; + (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); 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 static com.google.android.exoplayer2.util.Util.castNonNull;
import android.text.Layout; import android.text.Layout;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.text.Cue; import com.google.android.exoplayer2.text.Cue;
@ -301,7 +303,18 @@ public final class SsaDecoder extends SimpleSubtitleDecoder {
SsaStyle.Overrides styleOverrides, SsaStyle.Overrides styleOverrides,
float screenWidth, float screenWidth,
float screenHeight) { 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; @SsaStyle.SsaAlignment int alignment;
if (styleOverrides.alignment != SsaStyle.SSA_ALIGNMENT_UNKNOWN) { if (styleOverrides.alignment != SsaStyle.SSA_ALIGNMENT_UNKNOWN) {

View file

@ -17,16 +17,20 @@
package com.google.android.exoplayer2.text.ssa; 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.text.ssa.SsaDecoder.STYLE_LINE_PREFIX;
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
import static java.lang.annotation.RetentionPolicy.SOURCE; import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.graphics.Color;
import android.graphics.PointF; import android.graphics.PointF;
import android.text.TextUtils; import android.text.TextUtils;
import androidx.annotation.ColorInt;
import androidx.annotation.IntDef; import androidx.annotation.IntDef;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.C; import com.google.android.exoplayer2.C;
import com.google.android.exoplayer2.util.Assertions; import com.google.android.exoplayer2.util.Assertions;
import com.google.android.exoplayer2.util.Log; import com.google.android.exoplayer2.util.Log;
import com.google.android.exoplayer2.util.Util; import com.google.android.exoplayer2.util.Util;
import com.google.common.primitives.Ints;
import java.lang.annotation.Documented; import java.lang.annotation.Documented;
import java.lang.annotation.Retention; import java.lang.annotation.Retention;
import java.util.regex.Matcher; import java.util.regex.Matcher;
@ -85,15 +89,18 @@ import java.util.regex.Pattern;
public final String name; public final String name;
@SsaAlignment public final int alignment; @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.name = name;
this.alignment = alignment; this.alignment = alignment;
this.primaryColor = primaryColor;
} }
@Nullable @Nullable
public static SsaStyle fromStyleLine(String styleLine, Format format) { 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()), ","); String[] styleValues = TextUtils.split(styleLine.substring(STYLE_LINE_PREFIX.length()), ",");
if (styleValues.length != format.length) { if (styleValues.length != format.length) {
Log.w( Log.w(
@ -105,7 +112,9 @@ import java.util.regex.Pattern;
} }
try { try {
return new SsaStyle( 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) { } catch (RuntimeException e) {
Log.w(TAG, "Skipping malformed 'Style:' line: '" + styleLine + "'", e); Log.w(TAG, "Skipping malformed 'Style:' line: '" + styleLine + "'", e);
return null; 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 * 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 nameIndex;
public final int alignmentIndex; public final int alignmentIndex;
public final int primaryColorIndex;
public final int length; 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.nameIndex = nameIndex;
this.alignmentIndex = alignmentIndex; this.alignmentIndex = alignmentIndex;
this.primaryColorIndex = primaryColorIndex;
this.length = length; this.length = length;
} }
@ -171,6 +220,7 @@ import java.util.regex.Pattern;
public static Format fromFormatLine(String styleFormatLine) { public static Format fromFormatLine(String styleFormatLine) {
int nameIndex = C.INDEX_UNSET; int nameIndex = C.INDEX_UNSET;
int alignmentIndex = C.INDEX_UNSET; int alignmentIndex = C.INDEX_UNSET;
int primaryColorIndex = C.INDEX_UNSET;
String[] keys = String[] keys =
TextUtils.split(styleFormatLine.substring(SsaDecoder.FORMAT_LINE_PREFIX.length()), ","); TextUtils.split(styleFormatLine.substring(SsaDecoder.FORMAT_LINE_PREFIX.length()), ",");
for (int i = 0; i < keys.length; i++) { for (int i = 0; i < keys.length; i++) {
@ -181,9 +231,14 @@ import java.util.regex.Pattern;
case "alignment": case "alignment":
alignmentIndex = i; alignmentIndex = i;
break; 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. // Ignore invalid \pos() or \move() function.
} }
try { try {
@SsaAlignment @SsaAlignment int parsedAlignment = parseAlignmentOverride(braceContents);
int parsedAlignment = parseAlignmentOverride(braceContents);
if (parsedAlignment != SSA_ALIGNMENT_UNKNOWN) { if (parsedAlignment != SSA_ALIGNMENT_UNKNOWN) {
alignment = parsedAlignment; alignment = parsedAlignment;
} }

View file

@ -38,13 +38,13 @@ import java.util.List;
import org.checkerframework.checker.nullness.compatqual.NullableType; import org.checkerframework.checker.nullness.compatqual.NullableType;
/** /**
* A bandwidth based adaptive {@link TrackSelection}, whose selected track is updated to be the one * A bandwidth based adaptive {@link ExoTrackSelection}, whose selected track is updated to be the
* of highest quality given the current network conditions and the state of the buffer. * one of highest quality given the current network conditions and the state of the buffer.
*/ */
public class AdaptiveTrackSelection extends BaseTrackSelection { public class AdaptiveTrackSelection extends BaseTrackSelection {
/** Factory for {@link AdaptiveTrackSelection} instances. */ /** 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 minDurationForQualityIncreaseMs;
private final int maxDurationForQualityDecreaseMs; private final int maxDurationForQualityDecreaseMs;
@ -132,14 +132,14 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
} }
@Override @Override
public final @NullableType TrackSelection[] createTrackSelections( public final @NullableType ExoTrackSelection[] createTrackSelections(
@NullableType Definition[] definitions, @NullableType Definition[] definitions,
BandwidthMeter bandwidthMeter, BandwidthMeter bandwidthMeter,
MediaPeriodId mediaPeriodId, MediaPeriodId mediaPeriodId,
Timeline timeline) { Timeline timeline) {
ImmutableList<ImmutableList<AdaptationCheckpoint>> adaptationCheckpoints = ImmutableList<ImmutableList<AdaptationCheckpoint>> adaptationCheckpoints =
getAdaptationCheckpoints(definitions); getAdaptationCheckpoints(definitions);
TrackSelection[] selections = new TrackSelection[definitions.length]; ExoTrackSelection[] selections = new ExoTrackSelection[definitions.length];
for (int i = 0; i < definitions.length; i++) { for (int i = 0; i < definitions.length; i++) {
@Nullable Definition definition = definitions[i]; @Nullable Definition definition = definitions[i];
if (definition == null || definition.tracks.length == 0) { 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.Arrays;
import java.util.List; import java.util.List;
/** /** An abstract base class suitable for most {@link ExoTrackSelection} implementations. */
* An abstract base class suitable for most {@link TrackSelection} implementations. public abstract class BaseTrackSelection implements ExoTrackSelection {
*/
public abstract class BaseTrackSelection implements TrackSelection {
/** /** The selected {@link TrackGroup}. */
* The selected {@link TrackGroup}.
*/
protected final TrackGroup group; 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; 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; 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; private final Format[] formats;
/** Selected track exclusion timestamps, in order of decreasing bandwidth. */ /** Selected track exclusion timestamps, in order of decreasing bandwidth. */
private final long[] excludeUntilTimes; private final long[] excludeUntilTimes;

View file

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

View file

@ -29,15 +29,12 @@ import java.util.List;
import org.checkerframework.checker.nullness.compatqual.NullableType; import org.checkerframework.checker.nullness.compatqual.NullableType;
/** /**
* A track selection consisting of a static subset of selected tracks belonging to a {@link * A {@link TrackSelection} that can change the individually selected track as a result of calling
* TrackGroup}, and a possibly varying individual selected track from the subset. * {@link #updateSelectedTrack(long, long, long, List, MediaChunkIterator[])} or {@link
* * #evaluateQueueSize(long, List)}. This only happens between calls to {@link #enable()} and {@link
* <p>Tracks belonging to the subset are exposed in decreasing bandwidth order. The individual * #disable()}.
* 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()}.
*/ */
public interface TrackSelection { public interface ExoTrackSelection extends TrackSelection {
/** Contains of a subset of selected tracks belonging to a {@link TrackGroup}. */ /** Contains of a subset of selected tracks belonging to a {@link TrackGroup}. */
final class Definition { final class Definition {
@ -73,7 +70,7 @@ public interface TrackSelection {
} }
} }
/** Factory for {@link TrackSelection} instances. */ /** Factory for {@link ExoTrackSelection} instances. */
interface Factory { interface Factory {
/** /**
@ -91,7 +88,7 @@ public interface TrackSelection {
* include null values. * include null values.
*/ */
@NullableType @NullableType
TrackSelection[] createTrackSelections( ExoTrackSelection[] createTrackSelections(
@NullableType Definition[] definitions, @NullableType Definition[] definitions,
BandwidthMeter bandwidthMeter, BandwidthMeter bandwidthMeter,
MediaPeriodId mediaPeriodId, MediaPeriodId mediaPeriodId,
@ -116,50 +113,6 @@ public interface TrackSelection {
*/ */
void disable(); 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. // Individual selected track.
/** Returns the {@link Format} of the 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 * 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. * renderer.
*/ */
public abstract class MappingTrackSelector extends TrackSelector { 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 { public static final class MappedTrackInfo {
/** /**
@ -401,7 +399,7 @@ public abstract class MappingTrackSelector extends TrackSelector {
rendererFormatSupports, rendererFormatSupports,
unmappedTrackGroupArray); unmappedTrackGroupArray);
Pair<@NullableType RendererConfiguration[], @NullableType TrackSelection[]> result = Pair<@NullableType RendererConfiguration[], @NullableType ExoTrackSelection[]> result =
selectTracks( selectTracks(
mappedTrackInfo, mappedTrackInfo,
rendererFormatSupports, rendererFormatSupports,
@ -428,7 +426,7 @@ public abstract class MappingTrackSelector extends TrackSelector {
* RendererCapabilities#getTrackType()} is {@link C#TRACK_TYPE_NONE}. * RendererCapabilities#getTrackType()} is {@link C#TRACK_TYPE_NONE}.
* @throws ExoPlaybackException If an error occurs while selecting the tracks. * @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( selectTracks(
MappedTrackInfo mappedTrackInfo, MappedTrackInfo mappedTrackInfo,
@Capabilities int[][][] rendererFormatSupports, @Capabilities int[][][] rendererFormatSupports,
@ -538,5 +536,4 @@ public abstract class MappingTrackSelector extends TrackSelector {
} }
return mixedMimeTypeAdaptationSupport; return mixedMimeTypeAdaptationSupport;
} }
} }

View file

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

View file

@ -18,7 +18,7 @@ package com.google.android.exoplayer2.trackselection;
import androidx.annotation.Nullable; import androidx.annotation.Nullable;
import com.google.android.exoplayer2.source.TrackGroupArray; import com.google.android.exoplayer2.source.TrackGroupArray;
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride; 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; import org.checkerframework.checker.nullness.compatqual.NullableType;
/** Track selection related utility methods. */ /** Track selection related utility methods. */
@ -35,7 +35,7 @@ public final class TrackSelectionUtil {
* @param trackSelectionDefinition A {@link Definition} for the track selection. * @param trackSelectionDefinition A {@link Definition} for the track selection.
* @return The created 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 * @return The array of created track selection. For null entries in {@code definitions} returns
* null values. * null values.
*/ */
public static @NullableType TrackSelection[] createTrackSelectionsForDefinitions( public static @NullableType ExoTrackSelection[] createTrackSelectionsForDefinitions(
@NullableType Definition[] definitions, @NullableType Definition[] definitions,
AdaptiveTrackSelectionFactory adaptiveTrackSelectionFactory) { AdaptiveTrackSelectionFactory adaptiveTrackSelectionFactory) {
TrackSelection[] selections = new TrackSelection[definitions.length]; ExoTrackSelection[] selections = new ExoTrackSelection[definitions.length];
boolean createdAdaptiveTrackSelection = false; boolean createdAdaptiveTrackSelection = false;
for (int i = 0; i < definitions.length; i++) { for (int i = 0; i < definitions.length; i++) {
Definition definition = definitions[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()} * thread. The track selector may call {@link InvalidationListener#onTrackSelectionsInvalidated()}
* from any thread. * 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. * 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 com.google.android.exoplayer2.util.Util;
import org.checkerframework.checker.nullness.compatqual.NullableType; 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 { public final class TrackSelectorResult {
/** The number of selections in the result. Greater than or equal to zero. */ /** 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. * renderer should be disabled.
*/ */
public final @NullableType RendererConfiguration[] rendererConfigurations; public final @NullableType RendererConfiguration[] rendererConfigurations;
/** /** A {@link ExoTrackSelection} array containing the track selection for each renderer. */
* A {@link TrackSelectionArray} containing the track selection for each renderer. public final @NullableType ExoTrackSelection[] selections;
*/
public final TrackSelectionArray selections;
/** /**
* An opaque object that will be returned to {@link TrackSelector#onSelectionActivated(Object)} * An opaque object that will be returned to {@link TrackSelector#onSelectionActivated(Object)}
* should the selections be activated. * should the selections be activated.
@ -45,17 +41,17 @@ public final class TrackSelectorResult {
/** /**
* @param rendererConfigurations A {@link RendererConfiguration} for each renderer. A null entry * @param rendererConfigurations A {@link RendererConfiguration} for each renderer. A null entry
* indicates the corresponding renderer should be disabled. * 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 * @param info An opaque object that will be returned to {@link
* TrackSelector#onSelectionActivated(Object)} should the selection be activated. May be * TrackSelector#onSelectionActivated(Object)} should the selection be activated. May be
* {@code null}. * {@code null}.
*/ */
public TrackSelectorResult( public TrackSelectorResult(
@NullableType RendererConfiguration[] rendererConfigurations, @NullableType RendererConfiguration[] rendererConfigurations,
@NullableType TrackSelection[] selections, @NullableType ExoTrackSelection[] selections,
@Nullable Object info) { @Nullable Object info) {
this.rendererConfigurations = rendererConfigurations; this.rendererConfigurations = rendererConfigurations;
this.selections = new TrackSelectionArray(selections); this.selections = selections.clone();
this.info = info; this.info = info;
length = rendererConfigurations.length; length = rendererConfigurations.length;
} }
@ -100,7 +96,6 @@ public final class TrackSelectorResult {
return false; return false;
} }
return Util.areEqual(rendererConfigurations[index], other.rendererConfigurations[index]) 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. * changed.
* *
* <p>Note: The estimated bitrate is typically derived from more information than just {@code * <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 * @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 * 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