mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
Merge branch 'dev-v2' of https://github.com/TadejZupancic/ExoPlayer into dev-v2
This commit is contained in:
commit
d3b091b22a
@ -2,6 +2,18 @@
|
||||
|
||||
### dev-v2 (not yet released)
|
||||
|
||||
* Core library:
|
||||
* Log a warning when `SingleSampleMediaPeriod` transforms a load error
|
||||
into end-of-stream.
|
||||
* Extractors:
|
||||
* Fix Vorbis private codec data parsing in the Matroska extractor
|
||||
([#8496](https://github.com/google/ExoPlayer/issues/8496)).
|
||||
* Text:
|
||||
* Add support for the SSA `primaryColour` style attribute
|
||||
([#8435](https://github.com/google/ExoPlayer/issues/8435)).
|
||||
|
||||
### 2.13.0 (not yet released - targeted for 2021-02-TBD)
|
||||
|
||||
* Core library:
|
||||
* Remove long deprecated symbols:
|
||||
* `AdaptiveMediaSourceEventListener`. Use `MediaSourceEventListener`
|
||||
@ -80,8 +92,8 @@
|
||||
`MediaSourceEventListener` and `SingleSampleMediaSource.Factory`
|
||||
* `SimpleExoPlayer.addVideoDebugListener`,
|
||||
`SimpleExoPlayer.removeVideoDebugListener`,
|
||||
`SimpleExoPlayer.addAudioDebugListener`
|
||||
and `SimpleExoPlayer.removeAudioDebugListener`. Use
|
||||
`SimpleExoPlayer.addAudioDebugListener` and
|
||||
`SimpleExoPlayer.removeAudioDebugListener`. Use
|
||||
`SimpleExoPlayer.addAnalyticsListener` and
|
||||
`SimpleExoPlayer.removeAnalyticsListener` instead.
|
||||
* `AdaptiveMediaSourceEventListener`. Use `MediaSourceEventListener`
|
||||
@ -126,16 +138,18 @@
|
||||
* Add option to `MergingMediaSource` to clip the durations of all sources
|
||||
to have the same length
|
||||
([#8422](https://github.com/google/ExoPlayer/issues/8422)).
|
||||
* Fix propagation of `LoadErrorHandlingPolicy` from
|
||||
`DefaultMediaSourceFactory` into `SingleSampleMediaSource.Factory` when
|
||||
creating subtitle media sources from
|
||||
`MediaItem.playbackProperties.subtitles`
|
||||
([#8430](https://github.com/google/ExoPlayer/issues/8430)).
|
||||
* Remove `ExoPlaybackException.OutOfMemoryError`.
|
||||
* Remove `setVideoDecoderOutputBufferRenderer` from Player API. Clients
|
||||
should use `setOutputSurface` directly instead.
|
||||
* Default `SingleSampleMediaSource.treatLoadErrorsAsEndOfStream` to `true`
|
||||
([#8430](https://github.com/google/ExoPlayer/issues/8430)).
|
||||
* Remove `setVideoDecoderOutputBufferRenderer` from Player API. Use
|
||||
`setVideoSurfaceView` and `clearVideoSurfaceView` instead.
|
||||
* Replace `PlayerMessage.setHandler` with `PlayerMessage.setLooper`.
|
||||
* Transformer:
|
||||
* Add a library to transform media inputs. Available transformations are:
|
||||
configuration of output container format, removal of audio or video
|
||||
track and slow motion flattening.
|
||||
* Extractors:
|
||||
* Populate codecs string for H.264/AVC in MP4, Matroska and FLV streams to
|
||||
allow decoder capability checks based on codec profile/level
|
||||
@ -145,7 +159,10 @@
|
||||
([#8393](https://github.com/google/ExoPlayer/issues/8393)).
|
||||
* Handle sample size mismatches between raw audio `stsd` information and
|
||||
`stsz` fixed sample size in MP4 extractors.
|
||||
* Add support for playing JPEG motion photos
|
||||
([#5405](https://github.com/google/ExoPlayer/issues/5405)).
|
||||
* Track selection:
|
||||
* Moved `Player.getTrackSelector` to the `ExoPlayer` interface.
|
||||
* Allow parallel adaptation for video and audio
|
||||
([#5111](https://github.com/google/ExoPlayer/issues/5111)).
|
||||
* Simplified enabling tunneling with `DefaultTrackSelector`.
|
||||
@ -157,6 +174,10 @@
|
||||
([#8320](https://github.com/google/ExoPlayer/issues/8320)).
|
||||
* Add option to specify preferred audio role flags.
|
||||
* Forward `Timeline` and `MediaPeriodId` to `TrackSelection.Factory`.
|
||||
* In order to make it immutable, `TrackSelection` in the `Player` API now
|
||||
only contains methods related to static selection.
|
||||
The rest of the methods have been moved to the child
|
||||
class `ExoTrackSelection` which is used by all ExoPlayer components.
|
||||
* DASH:
|
||||
* Support low-latency DASH playback (`availabilityTimeOffset` and
|
||||
`ServiceDescription` tags)
|
||||
@ -181,12 +202,24 @@
|
||||
Widevine or Clearkey protected content in a playlist.
|
||||
* Add `ExoMediaDrm.KeyRequest.getRequestType`
|
||||
([#7847](https://github.com/google/ExoPlayer/issues/7847)).
|
||||
* Drop key & provision responses if `DefaultDrmSession` is released while
|
||||
waiting for the response. This fixes (harmless) `IllegalStateException:
|
||||
sending message to a Handler on a dead thread` log messages
|
||||
([#8328](https://github.com/google/ExoPlayer/issues/8328)).
|
||||
* Allow apps to fully customize DRM behaviour per-`MediaItem` by passing a
|
||||
`DrmSessionManagerProvider` to `MediaSourceFactory`
|
||||
([#8466](https://github.com/google/ExoPlayer/issues/8466)).
|
||||
* Analytics:
|
||||
* Pass a `DecoderReuseEvaluation` to `AnalyticsListener`'s
|
||||
`onVideoInputFormatChanged` and `onAudioInputFormatChanged` methods. The
|
||||
`DecoderReuseEvaluation` indicates whether it was possible to re-use an
|
||||
existing decoder instance for the new format, and if not then the
|
||||
reasons why.
|
||||
* Video:
|
||||
* Fix VP9 format capability checks on API level 23 and earlier. The
|
||||
platform does not correctly report the VP9 level supported by the
|
||||
decoder in this case, so we estimate it based on the decoder's maximum
|
||||
supported bitrate.
|
||||
* Audio:
|
||||
* Fix handling of audio session IDs
|
||||
([#8190](https://github.com/google/ExoPlayer/issues/8190)).
|
||||
@ -199,10 +232,13 @@
|
||||
`onAudioSessionIdChanged` is called in fewer cases than
|
||||
`onAudioSessionId` was called, due to the improved handling of audio
|
||||
session IDs as described above.
|
||||
* Retry playback after some types of `AudioTrack` error.
|
||||
* Create E-AC3 JOC passthrough `AudioTrack`s using the maximum supported
|
||||
channel count (instead of assuming 6 channels) from API 29.
|
||||
* Text:
|
||||
* Gracefully handle null-terminated subtitle content in Matroska
|
||||
containers.
|
||||
* Fix CEA-708 anchor positioning
|
||||
* Fix CEA-708 sequence number discontinuity handling
|
||||
([#1807](https://github.com/google/ExoPlayer/issues/1807)).
|
||||
* Fix CEA-708 handling of unexpectedly small packets
|
||||
([#1807](https://github.com/google/ExoPlayer/issues/1807)).
|
||||
* Data sources:
|
||||
* Use the user agent of the underlying network stack by default.
|
||||
@ -217,9 +253,17 @@
|
||||
ad view group
|
||||
([#7344](https://github.com/google/ExoPlayer/issues/7344)),
|
||||
([#8339](https://github.com/google/ExoPlayer/issues/8339)).
|
||||
* Fix a bug that could cause the next content position played after a
|
||||
seek to snap back to the cue point of the preceding ad, rather than
|
||||
the requested content position.
|
||||
* Fix a bug that could cause the next content position played after a seek
|
||||
to snap back to the cue point of the preceding ad, rather than the
|
||||
requested content position.
|
||||
* Fix a regression that caused an ad group to be skipped after an initial
|
||||
seek to a non-zero position. Unsupported VPAID ads will still be
|
||||
skipped but only after the preload timeout rather than instantly
|
||||
([#8428](https://github.com/google/ExoPlayer/issues/8428)),
|
||||
([#7832](https://github.com/google/ExoPlayer/issues/7832)).
|
||||
* Fix a regression that caused a short ad followed by another ad to be
|
||||
skipped due to playback being stuck buffering waiting for the second ad
|
||||
to load ([#8492](https://github.com/google/ExoPlayer/issues/8492)).
|
||||
* FFmpeg extension:
|
||||
* Link the FFmpeg library statically, saving 350KB in binary size on
|
||||
average.
|
||||
@ -300,7 +344,6 @@
|
||||
* Support enabling the previous and next actions individually in
|
||||
`PlayerNotificationManager`.
|
||||
* Audio:
|
||||
* Retry playback after some types of `AudioTrack` error.
|
||||
* Work around `AudioManager` crashes when calling `getStreamVolume`
|
||||
([#8191](https://github.com/google/ExoPlayer/issues/8191)).
|
||||
* Extractors:
|
||||
|
@ -13,8 +13,8 @@
|
||||
// limitations under the License.
|
||||
project.ext {
|
||||
// ExoPlayer version and version code.
|
||||
releaseVersion = '2.12.3'
|
||||
releaseVersionCode = 2012003
|
||||
releaseVersion = '2.13.0'
|
||||
releaseVersionCode = 2013000
|
||||
minSdkVersion = 16
|
||||
appTargetSdkVersion = 29
|
||||
targetSdkVersion = 28 // TODO: Bump once b/143232359 is resolved. Also fix TODOs in UtilTest.
|
||||
@ -24,7 +24,7 @@ project.ext {
|
||||
guavaVersion = '27.1-android'
|
||||
mockitoVersion = '2.28.2'
|
||||
mockWebServerVersion = '3.12.0'
|
||||
robolectricVersion = '4.5-alpha-3'
|
||||
robolectricVersion = '4.5'
|
||||
checkerframeworkVersion = '3.3.0'
|
||||
checkerframeworkCompatVersion = '2.5.0'
|
||||
jsr305Version = '3.0.2'
|
||||
|
@ -28,6 +28,7 @@ include modulePrefix + 'library-dash'
|
||||
include modulePrefix + 'library-extractor'
|
||||
include modulePrefix + 'library-hls'
|
||||
include modulePrefix + 'library-smoothstreaming'
|
||||
include modulePrefix + 'library-transformer'
|
||||
include modulePrefix + 'library-ui'
|
||||
include modulePrefix + 'robolectricutils'
|
||||
include modulePrefix + 'testutils'
|
||||
@ -56,6 +57,7 @@ project(modulePrefix + 'library-dash').projectDir = new File(rootDir, 'library/d
|
||||
project(modulePrefix + 'library-extractor').projectDir = new File(rootDir, 'library/extractor')
|
||||
project(modulePrefix + 'library-hls').projectDir = new File(rootDir, 'library/hls')
|
||||
project(modulePrefix + 'library-smoothstreaming').projectDir = new File(rootDir, 'library/smoothstreaming')
|
||||
project(modulePrefix + 'library-transformer').projectDir = new File(rootDir, 'library/transformer')
|
||||
project(modulePrefix + 'library-ui').projectDir = new File(rootDir, 'library/ui')
|
||||
project(modulePrefix + 'robolectricutils').projectDir = new File(rootDir, 'robolectricutils')
|
||||
project(modulePrefix + 'testutils').projectDir = new File(rootDir, 'testutils')
|
||||
|
@ -152,7 +152,7 @@ public final class MainActivity extends Activity {
|
||||
.setUuidAndExoMediaDrmProvider(drmSchemeUuid, FrameworkMediaDrm.DEFAULT_PROVIDER)
|
||||
.build(drmCallback);
|
||||
} else {
|
||||
drmSessionManager = DrmSessionManager.getDummyDrmSessionManager();
|
||||
drmSessionManager = DrmSessionManager.DRM_UNSUPPORTED;
|
||||
}
|
||||
|
||||
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(this);
|
||||
|
@ -197,7 +197,7 @@ public final class MainActivity extends Activity {
|
||||
.setUuidAndExoMediaDrmProvider(drmSchemeUuid, FrameworkMediaDrm.DEFAULT_PROVIDER)
|
||||
.build(drmCallback);
|
||||
} else {
|
||||
drmSessionManager = DrmSessionManager.getDummyDrmSessionManager();
|
||||
drmSessionManager = DrmSessionManager.DRM_UNSUPPORTED;
|
||||
}
|
||||
|
||||
DataSource.Factory dataSourceFactory = new DefaultDataSourceFactory(this);
|
||||
|
@ -16,7 +16,7 @@ apply from: "$gradle.ext.exoplayerSettingsDir/common_library_config.gradle"
|
||||
dependencies {
|
||||
api 'com.google.android.gms:play-services-cast-framework:18.1.0'
|
||||
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
|
||||
implementation project(modulePrefix + 'library-core')
|
||||
implementation project(modulePrefix + 'library-common')
|
||||
implementation project(modulePrefix + 'library-ui')
|
||||
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
|
||||
compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkCompatVersion
|
||||
|
@ -30,11 +30,10 @@ import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.metadata.Metadata;
|
||||
import com.google.android.exoplayer2.source.TrackGroup;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.trackselection.FixedTrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.Clock;
|
||||
import com.google.android.exoplayer2.util.ListenerSet;
|
||||
import com.google.android.exoplayer2.util.Log;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
@ -138,6 +137,7 @@ public final class CastPlayer extends BasePlayer {
|
||||
listeners =
|
||||
new ListenerSet<>(
|
||||
Looper.getMainLooper(),
|
||||
Clock.DEFAULT,
|
||||
Player.Events::new,
|
||||
(listener, eventFlags) -> listener.onEvents(/* player= */ this, eventFlags));
|
||||
|
||||
@ -504,12 +504,6 @@ public final class CastPlayer extends BasePlayer {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
@Nullable
|
||||
public TrackSelector getTrackSelector() {
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void setRepeatMode(@RepeatMode int repeatMode) {
|
||||
if (remoteMediaClient == null) {
|
||||
@ -771,7 +765,7 @@ public final class CastPlayer extends BasePlayer {
|
||||
int rendererIndex = getRendererIndexForTrackType(trackType);
|
||||
if (isTrackActive(id, activeTrackIds) && rendererIndex != C.INDEX_UNSET
|
||||
&& trackSelections[rendererIndex] == null) {
|
||||
trackSelections[rendererIndex] = new FixedTrackSelection(trackGroups[i], 0);
|
||||
trackSelections[rendererIndex] = new CastTrackSelection(trackGroups[i]);
|
||||
}
|
||||
}
|
||||
TrackGroupArray newTrackGroups = new TrackGroupArray(trackGroups);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -13,12 +13,25 @@
|
||||
// limitations under the License.
|
||||
apply from: "$gradle.ext.exoplayerSettingsDir/common_library_config.gradle"
|
||||
|
||||
android {
|
||||
defaultConfig {
|
||||
multiDexEnabled true
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
api "com.google.android.gms:play-services-cronet:17.0.0"
|
||||
implementation project(modulePrefix + 'library-core')
|
||||
implementation project(modulePrefix + 'library-common')
|
||||
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
|
||||
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
|
||||
compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
|
||||
androidTestImplementation 'androidx.test:rules:' + androidxTestRulesVersion
|
||||
androidTestImplementation 'androidx.test:runner:' + androidxTestRunnerVersion
|
||||
androidTestImplementation 'androidx.multidex:multidex:' + androidxMultidexVersion
|
||||
// Emulator tests assume that an app-packaged version of cronet is
|
||||
// available.
|
||||
androidTestImplementation 'org.chromium.net:cronet-embedded:76.3809.111'
|
||||
androidTestImplementation(project(modulePrefix + 'testutils'))
|
||||
testImplementation project(modulePrefix + 'library')
|
||||
testImplementation project(modulePrefix + 'testutils')
|
||||
testImplementation 'com.squareup.okhttp3:mockwebserver:' + mockWebServerVersion
|
||||
|
34
extensions/cronet/src/androidTest/AndroidManifest.xml
Normal file
34
extensions/cronet/src/androidTest/AndroidManifest.xml
Normal 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>
|
@ -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();
|
||||
}
|
||||
}
|
||||
}
|
@ -17,7 +17,6 @@ package com.google.android.exoplayer2.ext.cronet;
|
||||
|
||||
import static com.google.android.exoplayer2.util.Util.castNonNull;
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.Math.min;
|
||||
|
||||
import android.net.Uri;
|
||||
import android.text.TextUtils;
|
||||
@ -37,6 +36,7 @@ import com.google.android.exoplayer2.util.ConditionVariable;
|
||||
import com.google.android.exoplayer2.util.Log;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.common.base.Predicate;
|
||||
import com.google.common.primitives.Ints;
|
||||
import java.io.IOException;
|
||||
import java.io.InterruptedIOException;
|
||||
import java.net.SocketTimeoutException;
|
||||
@ -655,14 +655,21 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
|
||||
readBuffer.flip();
|
||||
Assertions.checkState(readBuffer.hasRemaining());
|
||||
if (bytesToSkip > 0) {
|
||||
int bytesSkipped = (int) min(readBuffer.remaining(), bytesToSkip);
|
||||
int bytesSkipped = (int) Math.min(readBuffer.remaining(), bytesToSkip);
|
||||
readBuffer.position(readBuffer.position() + bytesSkipped);
|
||||
bytesToSkip -= bytesSkipped;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
int bytesRead = min(readBuffer.remaining(), readLength);
|
||||
// Ensure we read up to bytesRemaining, in case this was a Range request with finite end, but
|
||||
// the server does not support Range requests and transmitted the entire resource.
|
||||
int bytesRead =
|
||||
Ints.min(
|
||||
bytesRemaining != C.LENGTH_UNSET ? (int) bytesRemaining : Integer.MAX_VALUE,
|
||||
readBuffer.remaining(),
|
||||
readLength);
|
||||
|
||||
readBuffer.get(buffer, offset, bytesRead);
|
||||
|
||||
if (bytesRemaining != C.LENGTH_UNSET) {
|
||||
@ -1039,7 +1046,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
|
||||
// Copy as much as possible from the src buffer into dst buffer.
|
||||
// Returns the number of bytes copied.
|
||||
private static int copyByteBuffer(ByteBuffer src, ByteBuffer dst) {
|
||||
int remaining = min(src.remaining(), dst.remaining());
|
||||
int remaining = Math.min(src.remaining(), dst.remaining());
|
||||
int limit = src.limit();
|
||||
src.limit(src.position() + remaining);
|
||||
dst.put(src);
|
||||
|
@ -17,7 +17,7 @@ package com.google.android.exoplayer2.ext.cronet;
|
||||
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
|
||||
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
|
||||
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
||||
import com.google.android.exoplayer2.upstream.HttpDataSource.BaseFactory;
|
||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||
@ -25,8 +25,6 @@ import java.util.concurrent.Executor;
|
||||
import org.chromium.net.CronetEngine;
|
||||
|
||||
/** @deprecated Use {@link CronetDataSource.Factory} instead. */
|
||||
// Uses deprecated DefaultHttpDataSourceFactory
|
||||
@SuppressWarnings("deprecation")
|
||||
@Deprecated
|
||||
public final class CronetDataSourceFactory extends BaseFactory {
|
||||
|
||||
@ -82,7 +80,7 @@ public final class CronetDataSourceFactory extends BaseFactory {
|
||||
* Creates an instance.
|
||||
*
|
||||
* <p>If the {@link CronetEngineWrapper} fails to provide a {@link CronetEngine}, a {@link
|
||||
* DefaultHttpDataSourceFactory} will be used instead.
|
||||
* DefaultHttpDataSource.Factory} will be used instead.
|
||||
*
|
||||
* <p>Sets {@link CronetDataSource#DEFAULT_CONNECT_TIMEOUT_MILLIS} as the connection timeout,
|
||||
* {@link CronetDataSource#DEFAULT_READ_TIMEOUT_MILLIS} as the read timeout.
|
||||
@ -98,7 +96,7 @@ public final class CronetDataSourceFactory extends BaseFactory {
|
||||
* Creates an instance.
|
||||
*
|
||||
* <p>If the {@link CronetEngineWrapper} fails to provide a {@link CronetEngine}, a {@link
|
||||
* DefaultHttpDataSourceFactory} will be used instead.
|
||||
* DefaultHttpDataSource.Factory} will be used instead.
|
||||
*
|
||||
* <p>Sets {@link CronetDataSource#DEFAULT_CONNECT_TIMEOUT_MILLIS} as the connection timeout,
|
||||
* {@link CronetDataSource#DEFAULT_READ_TIMEOUT_MILLIS} as the read timeout.
|
||||
@ -118,19 +116,14 @@ public final class CronetDataSourceFactory extends BaseFactory {
|
||||
DEFAULT_CONNECT_TIMEOUT_MILLIS,
|
||||
DEFAULT_READ_TIMEOUT_MILLIS,
|
||||
false,
|
||||
new DefaultHttpDataSourceFactory(
|
||||
userAgent,
|
||||
/* listener= */ null,
|
||||
DEFAULT_CONNECT_TIMEOUT_MILLIS,
|
||||
DEFAULT_READ_TIMEOUT_MILLIS,
|
||||
false));
|
||||
new DefaultHttpDataSource.Factory().setUserAgent(userAgent));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* <p>If the {@link CronetEngineWrapper} fails to provide a {@link CronetEngine}, a {@link
|
||||
* DefaultHttpDataSourceFactory} will be used instead.
|
||||
* DefaultHttpDataSource.Factory} will be used instead.
|
||||
*
|
||||
* @param cronetEngineWrapper A {@link CronetEngineWrapper}.
|
||||
* @param executor The {@link java.util.concurrent.Executor} that will perform the requests.
|
||||
@ -152,15 +145,13 @@ public final class CronetDataSourceFactory extends BaseFactory {
|
||||
cronetEngineWrapper,
|
||||
executor,
|
||||
/* transferListener= */ null,
|
||||
DEFAULT_CONNECT_TIMEOUT_MILLIS,
|
||||
DEFAULT_READ_TIMEOUT_MILLIS,
|
||||
connectTimeoutMs,
|
||||
readTimeoutMs,
|
||||
resetTimeoutOnRedirects,
|
||||
new DefaultHttpDataSourceFactory(
|
||||
userAgent,
|
||||
/* listener= */ null,
|
||||
connectTimeoutMs,
|
||||
readTimeoutMs,
|
||||
resetTimeoutOnRedirects));
|
||||
new DefaultHttpDataSource.Factory()
|
||||
.setUserAgent(userAgent)
|
||||
.setConnectTimeoutMs(connectTimeoutMs)
|
||||
.setReadTimeoutMs(readTimeoutMs));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -228,7 +219,7 @@ public final class CronetDataSourceFactory extends BaseFactory {
|
||||
* Creates an instance.
|
||||
*
|
||||
* <p>If the {@link CronetEngineWrapper} fails to provide a {@link CronetEngine}, a {@link
|
||||
* DefaultHttpDataSourceFactory} will be used instead.
|
||||
* DefaultHttpDataSource.Factory} will be used instead.
|
||||
*
|
||||
* <p>Sets {@link CronetDataSource#DEFAULT_CONNECT_TIMEOUT_MILLIS} as the connection timeout,
|
||||
* {@link CronetDataSource#DEFAULT_READ_TIMEOUT_MILLIS} as the read timeout.
|
||||
@ -248,7 +239,7 @@ public final class CronetDataSourceFactory extends BaseFactory {
|
||||
* Creates an instance.
|
||||
*
|
||||
* <p>If the {@link CronetEngineWrapper} fails to provide a {@link CronetEngine}, a {@link
|
||||
* DefaultHttpDataSourceFactory} will be used instead.
|
||||
* DefaultHttpDataSource.Factory} will be used instead.
|
||||
*
|
||||
* <p>Sets {@link CronetDataSource#DEFAULT_CONNECT_TIMEOUT_MILLIS} as the connection timeout,
|
||||
* {@link CronetDataSource#DEFAULT_READ_TIMEOUT_MILLIS} as the read timeout.
|
||||
@ -272,19 +263,16 @@ public final class CronetDataSourceFactory extends BaseFactory {
|
||||
DEFAULT_CONNECT_TIMEOUT_MILLIS,
|
||||
DEFAULT_READ_TIMEOUT_MILLIS,
|
||||
false,
|
||||
new DefaultHttpDataSourceFactory(
|
||||
userAgent,
|
||||
transferListener,
|
||||
DEFAULT_CONNECT_TIMEOUT_MILLIS,
|
||||
DEFAULT_READ_TIMEOUT_MILLIS,
|
||||
false));
|
||||
new DefaultHttpDataSource.Factory()
|
||||
.setUserAgent(userAgent)
|
||||
.setTransferListener(transferListener));
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* <p>If the {@link CronetEngineWrapper} fails to provide a {@link CronetEngine}, a {@link
|
||||
* DefaultHttpDataSourceFactory} will be used instead.
|
||||
* DefaultHttpDataSource.Factory} will be used instead.
|
||||
*
|
||||
* @param cronetEngineWrapper A {@link CronetEngineWrapper}.
|
||||
* @param executor The {@link java.util.concurrent.Executor} that will perform the requests.
|
||||
@ -308,11 +296,14 @@ public final class CronetDataSourceFactory extends BaseFactory {
|
||||
cronetEngineWrapper,
|
||||
executor,
|
||||
transferListener,
|
||||
DEFAULT_CONNECT_TIMEOUT_MILLIS,
|
||||
DEFAULT_READ_TIMEOUT_MILLIS,
|
||||
connectTimeoutMs,
|
||||
readTimeoutMs,
|
||||
resetTimeoutOnRedirects,
|
||||
new DefaultHttpDataSourceFactory(
|
||||
userAgent, transferListener, connectTimeoutMs, readTimeoutMs, resetTimeoutOnRedirects));
|
||||
new DefaultHttpDataSource.Factory()
|
||||
.setUserAgent(userAgent)
|
||||
.setTransferListener(transferListener)
|
||||
.setConnectTimeoutMs(connectTimeoutMs)
|
||||
.setReadTimeoutMs(readTimeoutMs));
|
||||
}
|
||||
|
||||
/**
|
||||
|
@ -451,25 +451,10 @@ import java.util.Map;
|
||||
return;
|
||||
}
|
||||
|
||||
if (playbackState == Player.STATE_BUFFERING && !player.isPlayingAd()) {
|
||||
// Check whether we are waiting for an ad to preload.
|
||||
int adGroupIndex = getLoadingAdGroupIndex();
|
||||
if (adGroupIndex == C.INDEX_UNSET) {
|
||||
return;
|
||||
}
|
||||
AdPlaybackState.AdGroup adGroup = adPlaybackState.adGroups[adGroupIndex];
|
||||
if (adGroup.count != C.LENGTH_UNSET
|
||||
&& adGroup.count != 0
|
||||
&& adGroup.states[0] != AdPlaybackState.AD_STATE_UNAVAILABLE) {
|
||||
// An ad is available already so we must be buffering for some other reason.
|
||||
return;
|
||||
}
|
||||
long adGroupTimeMs = C.usToMs(adPlaybackState.adGroupTimesUs[adGroupIndex]);
|
||||
long contentPositionMs = getContentPeriodPositionMs(player, timeline, period);
|
||||
long timeUntilAdMs = adGroupTimeMs - contentPositionMs;
|
||||
if (timeUntilAdMs < configuration.adPreloadTimeoutMs) {
|
||||
waitingForPreloadElapsedRealtimeMs = SystemClock.elapsedRealtime();
|
||||
}
|
||||
if (playbackState == Player.STATE_BUFFERING
|
||||
&& !player.isPlayingAd()
|
||||
&& isWaitingForAdToLoad()) {
|
||||
waitingForPreloadElapsedRealtimeMs = SystemClock.elapsedRealtime();
|
||||
} else if (playbackState == Player.STATE_READY) {
|
||||
waitingForPreloadElapsedRealtimeMs = C.TIME_UNSET;
|
||||
}
|
||||
@ -759,27 +744,35 @@ import java.util.Map;
|
||||
if (imaAdInfo != null) {
|
||||
adPlaybackState = adPlaybackState.withSkippedAdGroup(imaAdInfo.adGroupIndex);
|
||||
updateAdPlaybackState();
|
||||
} else {
|
||||
// Mark any ads for the current/reported player position that haven't loaded as being in the
|
||||
// error state, to force resuming content. This includes VPAID ads that never load.
|
||||
long playerPositionUs;
|
||||
if (player != null) {
|
||||
playerPositionUs = C.msToUs(getContentPeriodPositionMs(player, timeline, period));
|
||||
} else if (!VideoProgressUpdate.VIDEO_TIME_NOT_READY.equals(lastContentProgress)) {
|
||||
// Playback is backgrounded so use the last reported content position.
|
||||
playerPositionUs = C.msToUs(lastContentProgress.getCurrentTimeMs());
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
int adGroupIndex =
|
||||
adPlaybackState.getAdGroupIndexForPositionUs(
|
||||
playerPositionUs, C.msToUs(contentDurationMs));
|
||||
if (adGroupIndex != C.INDEX_UNSET) {
|
||||
markAdGroupInErrorStateAndClearPendingContentPosition(adGroupIndex);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this instance is expecting the first ad in an the upcoming ad group to load
|
||||
* within the {@link ImaUtil.Configuration#adPreloadTimeoutMs preload timeout}.
|
||||
*/
|
||||
private boolean isWaitingForAdToLoad() {
|
||||
@Nullable Player player = this.player;
|
||||
if (player == null) {
|
||||
return false;
|
||||
}
|
||||
int adGroupIndex = getLoadingAdGroupIndex();
|
||||
if (adGroupIndex == C.INDEX_UNSET) {
|
||||
return false;
|
||||
}
|
||||
AdPlaybackState.AdGroup adGroup = adPlaybackState.adGroups[adGroupIndex];
|
||||
if (adGroup.count != C.LENGTH_UNSET
|
||||
&& adGroup.count != 0
|
||||
&& adGroup.states[0] != AdPlaybackState.AD_STATE_UNAVAILABLE) {
|
||||
// An ad is available already.
|
||||
return false;
|
||||
}
|
||||
long adGroupTimeMs = C.usToMs(adPlaybackState.adGroupTimesUs[adGroupIndex]);
|
||||
long contentPositionMs = getContentPeriodPositionMs(player, timeline, period);
|
||||
long timeUntilAdMs = adGroupTimeMs - contentPositionMs;
|
||||
return timeUntilAdMs < configuration.adPreloadTimeoutMs;
|
||||
}
|
||||
|
||||
private void handlePlayerStateChanged(boolean playWhenReady, @Player.State int playbackState) {
|
||||
if (playingAd && imaAdState == IMA_AD_STATE_PLAYING) {
|
||||
if (!bufferingAd && playbackState == Player.STATE_BUFFERING) {
|
||||
@ -1305,6 +1298,12 @@ import java.util.Map;
|
||||
handleAdGroupLoadError(new IOException("Ad preloading timed out"));
|
||||
maybeNotifyPendingAdLoadError();
|
||||
}
|
||||
} else if (pendingContentPositionMs != C.TIME_UNSET
|
||||
&& player != null
|
||||
&& player.getPlaybackState() == Player.STATE_BUFFERING
|
||||
&& isWaitingForAdToLoad()) {
|
||||
// Prepare to timeout the load of an ad for the pending seek operation.
|
||||
waitingForPreloadElapsedRealtimeMs = SystemClock.elapsedRealtime();
|
||||
}
|
||||
|
||||
return videoProgressUpdate;
|
||||
|
@ -60,7 +60,6 @@ import java.util.Collection;
|
||||
import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Locale;
|
||||
import java.util.Set;
|
||||
|
||||
/**
|
||||
@ -700,7 +699,7 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
|
||||
@Override
|
||||
public ImaSdkSettings createImaSdkSettings() {
|
||||
ImaSdkSettings settings = ImaSdkFactory.getInstance().createImaSdkSettings();
|
||||
settings.setLanguage(getImaLanguageCodeForDefaultLocale());
|
||||
settings.setLanguage(Util.getSystemLanguageCodes()[0]);
|
||||
return settings;
|
||||
}
|
||||
|
||||
@ -742,17 +741,5 @@ public final class ImaAdsLoader implements Player.EventListener, AdsLoader {
|
||||
return ImaSdkFactory.getInstance()
|
||||
.createAdsLoader(context, imaSdkSettings, adDisplayContainer);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a language code that's suitable for passing to {@link ImaSdkSettings#setLanguage} and
|
||||
* corresponds to the device's {@link Locale#getDefault() default Locale}. IMA will fall back to
|
||||
* its default language code ("en") if the value returned is unsupported.
|
||||
*/
|
||||
// TODO: It may be possible to define a better mapping onto IMA's supported language codes. See:
|
||||
// https://developers.google.com/interactive-media-ads/docs/sdks/android/client-side/localization.
|
||||
// [Internal ref: b/174042000] will help if implemented.
|
||||
private static String getImaLanguageCodeForDefaultLocale() {
|
||||
return Util.splitAtFirst(Util.getSystemLanguageCodes()[0], "-")[0];
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -21,6 +21,7 @@ import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.testutil.StubExoPlayer;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.util.Clock;
|
||||
import com.google.android.exoplayer2.util.ListenerSet;
|
||||
|
||||
/** A fake player for testing content/ad playback. */
|
||||
@ -43,6 +44,7 @@ import com.google.android.exoplayer2.util.ListenerSet;
|
||||
listeners =
|
||||
new ListenerSet<>(
|
||||
Looper.getMainLooper(),
|
||||
Clock.DEFAULT,
|
||||
Player.Events::new,
|
||||
(listener, eventFlags) -> listener.onEvents(/* player= */ this, eventFlags));
|
||||
period = new Timeline.Period();
|
||||
|
@ -450,6 +450,62 @@ public final class ImaAdsLoaderTest {
|
||||
.withAdLoadError(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startPlaybackAfterMidroll_doesNotSkipMidroll() {
|
||||
// Simulate an ad at 2 seconds, and starting playback with an initial seek position at the ad.
|
||||
long adGroupPositionInWindowUs = 2 * C.MICROS_PER_SECOND;
|
||||
long adGroupTimeUs =
|
||||
adGroupPositionInWindowUs
|
||||
+ TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
|
||||
ImmutableList<Float> cuePoints = ImmutableList.of((float) adGroupTimeUs / C.MICROS_PER_SECOND);
|
||||
when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints);
|
||||
fakePlayer.setState(Player.STATE_BUFFERING, /* playWhenReady= */ true);
|
||||
fakePlayer.setPlayingContentPosition(/* periodIndex= */ 0, C.usToMs(adGroupPositionInWindowUs));
|
||||
|
||||
// Start ad loading while still buffering and simulate the calls from the IMA SDK to resume then
|
||||
// immediately pause content playback.
|
||||
imaAdsLoader.start(
|
||||
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
|
||||
contentProgressProvider.getContentProgress();
|
||||
adEventListener.onAdEvent(getAdEvent(AdEventType.CONTENT_RESUME_REQUESTED, /* ad= */ null));
|
||||
contentProgressProvider.getContentProgress();
|
||||
adEventListener.onAdEvent(getAdEvent(AdEventType.CONTENT_PAUSE_REQUESTED, /* ad= */ null));
|
||||
contentProgressProvider.getContentProgress();
|
||||
|
||||
assertThat(getAdPlaybackState(/* periodIndex= */ 0))
|
||||
.isEqualTo(
|
||||
new AdPlaybackState(TEST_ADS_ID, getAdGroupTimesUsForCuePoints(cuePoints))
|
||||
.withContentDurationUs(CONTENT_PERIOD_DURATION_US));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void startPlaybackAfterMidroll_withAdNotPreloadingAfterTimeout_hasErrorAdGroup() {
|
||||
// Simulate an ad at 2 seconds, and starting playback with an initial seek position at the ad.
|
||||
long adGroupPositionInWindowUs = 2 * C.MICROS_PER_SECOND;
|
||||
long adGroupTimeUs =
|
||||
adGroupPositionInWindowUs
|
||||
+ TimelineWindowDefinition.DEFAULT_WINDOW_OFFSET_IN_FIRST_PERIOD_US;
|
||||
ImmutableList<Float> cuePoints = ImmutableList.of((float) adGroupTimeUs / C.MICROS_PER_SECOND);
|
||||
when(mockAdsManager.getAdCuePoints()).thenReturn(cuePoints);
|
||||
fakePlayer.setState(Player.STATE_BUFFERING, /* playWhenReady= */ true);
|
||||
fakePlayer.setPlayingContentPosition(/* periodIndex= */ 0, C.usToMs(adGroupPositionInWindowUs));
|
||||
|
||||
// Start ad loading while still buffering and poll progress without the ad loading.
|
||||
imaAdsLoader.start(
|
||||
adsMediaSource, TEST_DATA_SPEC, TEST_ADS_ID, adViewProvider, adsLoaderListener);
|
||||
contentProgressProvider.getContentProgress();
|
||||
ShadowSystemClock.advanceBy(Duration.ofSeconds(5));
|
||||
contentProgressProvider.getContentProgress();
|
||||
|
||||
assertThat(getAdPlaybackState(/* periodIndex= */ 0))
|
||||
.isEqualTo(
|
||||
new AdPlaybackState(TEST_ADS_ID, getAdGroupTimesUsForCuePoints(cuePoints))
|
||||
.withContentDurationUs(CONTENT_PERIOD_DURATION_US)
|
||||
.withAdDurationsUs(new long[][] {{TEST_AD_DURATION_US}})
|
||||
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
|
||||
.withAdLoadError(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0));
|
||||
}
|
||||
|
||||
@Test
|
||||
public void bufferingDuringAd_callsOnBuffering() {
|
||||
// Load the preroll ad.
|
||||
|
@ -14,7 +14,7 @@
|
||||
apply from: "$gradle.ext.exoplayerSettingsDir/common_library_config.gradle"
|
||||
|
||||
dependencies {
|
||||
implementation project(modulePrefix + 'library-core')
|
||||
implementation project(modulePrefix + 'library-common')
|
||||
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
|
||||
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
|
||||
compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
|
||||
|
@ -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());
|
||||
}
|
||||
}
|
@ -14,10 +14,11 @@
|
||||
apply from: "$gradle.ext.exoplayerSettingsDir/common_library_config.gradle"
|
||||
|
||||
dependencies {
|
||||
implementation project(modulePrefix + 'library-core')
|
||||
implementation project(modulePrefix + 'library-common')
|
||||
implementation 'net.butterflytv.utils:rtmp-client:3.1.0'
|
||||
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
|
||||
compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
|
||||
testImplementation project(modulePrefix + 'library-core')
|
||||
testImplementation project(modulePrefix + 'testutils')
|
||||
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
|
||||
}
|
||||
|
@ -35,7 +35,9 @@ dependencies {
|
||||
testImplementation 'androidx.test.ext:junit:' + androidxTestJUnitVersion
|
||||
testImplementation 'junit:junit:' + junitVersion
|
||||
testImplementation 'com.google.truth:truth:' + truthVersion
|
||||
testImplementation 'com.squareup.okhttp3:mockwebserver:' + mockWebServerVersion
|
||||
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
|
||||
testImplementation project(modulePrefix + 'testutils')
|
||||
}
|
||||
|
||||
ext {
|
||||
|
@ -1080,23 +1080,6 @@ public final class C {
|
||||
/** Indicates the track is intended for trick play. */
|
||||
public static final int ROLE_FLAG_TRICK_PLAY = 1 << 14;
|
||||
|
||||
// TODO(b/172315872) Move usage back to Player.RepeatMode when Player is moved in common.
|
||||
/**
|
||||
* Repeat modes for playback. One of {@link #REPEAT_MODE_OFF}, {@link #REPEAT_MODE_ONE} or {@link
|
||||
* #REPEAT_MODE_ALL}.
|
||||
*/
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({REPEAT_MODE_OFF, REPEAT_MODE_ONE, REPEAT_MODE_ALL})
|
||||
static @interface RepeatMode {}
|
||||
|
||||
/** Normal playback without repetition. */
|
||||
/* package */ static final int REPEAT_MODE_OFF = 0;
|
||||
/** "Repeat One" mode to repeat the currently playing window infinitely. */
|
||||
/* package */ static final int REPEAT_MODE_ONE = 1;
|
||||
/** "Repeat All" mode to repeat the entire timeline infinitely. */
|
||||
/* package */ static final int REPEAT_MODE_ALL = 2;
|
||||
|
||||
/**
|
||||
* Level of renderer support for a format. One of {@link #FORMAT_HANDLED}, {@link
|
||||
* #FORMAT_EXCEEDS_CAPABILITIES}, {@link #FORMAT_UNSUPPORTED_DRM}, {@link
|
||||
|
@ -30,11 +30,11 @@ public final class ExoPlayerLibraryInfo {
|
||||
|
||||
/** The version of the library expressed as a string, for example "1.2.3". */
|
||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
|
||||
public static final String VERSION = "2.12.3";
|
||||
public static final String VERSION = "2.13.0";
|
||||
|
||||
/** The version of the library expressed as {@code "ExoPlayerLib/" + VERSION}. */
|
||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
||||
public static final String VERSION_SLASHY = "ExoPlayerLib/2.12.3";
|
||||
public static final String VERSION_SLASHY = "ExoPlayerLib/2.13.0";
|
||||
|
||||
/**
|
||||
* The version of the library expressed as an integer, for example 1002003.
|
||||
@ -44,7 +44,7 @@ public final class ExoPlayerLibraryInfo {
|
||||
* integer version 123045006 (123-045-006).
|
||||
*/
|
||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
||||
public static final int VERSION_INT = 2012003;
|
||||
public static final int VERSION_INT = 2013000;
|
||||
|
||||
/**
|
||||
* The default user agent for requests made by the library.
|
||||
|
@ -34,7 +34,6 @@ import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.text.Cue;
|
||||
import com.google.android.exoplayer2.text.TextOutput;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectorInterface;
|
||||
import com.google.android.exoplayer2.util.MutableFlags;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.android.exoplayer2.video.VideoFrameMetadataListener;
|
||||
@ -618,11 +617,14 @@ public interface Player {
|
||||
default void onSeekProcessed() {}
|
||||
|
||||
/**
|
||||
* Called when the player has started or stopped offload scheduling after a call to {@link
|
||||
* Called when the player has started or stopped offload scheduling.
|
||||
*
|
||||
* <p>If using ExoPlayer, this is done by calling {@code
|
||||
* ExoPlayer#experimentalSetOffloadSchedulingEnabled(boolean)}.
|
||||
*
|
||||
* <p>This method is experimental, and will be renamed or removed in a future release.
|
||||
*/
|
||||
// TODO(b/172315872) Move this method in a new ExoPlayer.EventListener.
|
||||
default void onExperimentalOffloadSchedulingEnabledChanged(boolean offloadSchedulingEnabled) {}
|
||||
|
||||
/**
|
||||
@ -740,9 +742,7 @@ public interface Player {
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@IntDef({STATE_IDLE, STATE_BUFFERING, STATE_READY, STATE_ENDED})
|
||||
@interface State {}
|
||||
/**
|
||||
* The player does not have any media to play.
|
||||
*/
|
||||
/** The player does not have any media to play. */
|
||||
int STATE_IDLE = 1;
|
||||
/**
|
||||
* The player is not able to immediately play from its current position. This state typically
|
||||
@ -754,9 +754,7 @@ public interface Player {
|
||||
* {@link #getPlayWhenReady()} is true, and paused otherwise.
|
||||
*/
|
||||
int STATE_READY = 3;
|
||||
/**
|
||||
* The player has finished playing the media.
|
||||
*/
|
||||
/** The player has finished playing the media. */
|
||||
int STATE_ENDED = 4;
|
||||
|
||||
/**
|
||||
@ -817,20 +815,20 @@ public interface Player {
|
||||
* Normal playback without repetition. "Previous" and "Next" actions move to the previous and next
|
||||
* windows respectively, and do nothing when there is no previous or next window to move to.
|
||||
*/
|
||||
int REPEAT_MODE_OFF = C.REPEAT_MODE_OFF;
|
||||
int REPEAT_MODE_OFF = 0;
|
||||
/**
|
||||
* Repeats the currently playing window infinitely during ongoing playback. "Previous" and "Next"
|
||||
* actions behave as they do in {@link #REPEAT_MODE_OFF}, moving to the previous and next windows
|
||||
* respectively, and doing nothing when there is no previous or next window to move to.
|
||||
*/
|
||||
int REPEAT_MODE_ONE = C.REPEAT_MODE_ONE;
|
||||
int REPEAT_MODE_ONE = 1;
|
||||
/**
|
||||
* Repeats the entire timeline infinitely. "Previous" and "Next" actions behave as they do in
|
||||
* {@link #REPEAT_MODE_OFF}, but with looping at the ends so that "Previous" when playing the
|
||||
* first window will move to the last window, and "Next" when playing the last window will move to
|
||||
* the first window.
|
||||
*/
|
||||
int REPEAT_MODE_ALL = C.REPEAT_MODE_ALL;
|
||||
int REPEAT_MODE_ALL = 2;
|
||||
|
||||
/**
|
||||
* Reasons for position discontinuities. One of {@link #DISCONTINUITY_REASON_PERIOD_TRANSITION},
|
||||
@ -1218,7 +1216,8 @@ public interface Player {
|
||||
*
|
||||
* @return The current repeat mode.
|
||||
*/
|
||||
@RepeatMode int getRepeatMode();
|
||||
@RepeatMode
|
||||
int getRepeatMode();
|
||||
|
||||
/**
|
||||
* Sets whether shuffling of windows is enabled.
|
||||
@ -1227,9 +1226,7 @@ public interface Player {
|
||||
*/
|
||||
void setShuffleModeEnabled(boolean shuffleModeEnabled);
|
||||
|
||||
/**
|
||||
* Returns whether shuffling of windows is enabled.
|
||||
*/
|
||||
/** Returns whether shuffling of windows is enabled. */
|
||||
boolean getShuffleModeEnabled();
|
||||
|
||||
/**
|
||||
@ -1364,9 +1361,7 @@ public interface Player {
|
||||
*/
|
||||
void release();
|
||||
|
||||
/**
|
||||
* Returns the number of renderers.
|
||||
*/
|
||||
/** Returns the number of renderers. */
|
||||
int getRendererCount();
|
||||
|
||||
/**
|
||||
@ -1380,12 +1375,6 @@ public interface Player {
|
||||
*/
|
||||
int getRendererType(int index);
|
||||
|
||||
/**
|
||||
* Returns the track selector that this player uses, or null if track selection is not supported.
|
||||
*/
|
||||
@Nullable
|
||||
TrackSelectorInterface getTrackSelector();
|
||||
|
||||
/** Returns the available track groups. */
|
||||
TrackGroupArray getCurrentTrackGroups();
|
||||
|
||||
@ -1411,14 +1400,10 @@ public interface Player {
|
||||
@Nullable
|
||||
Object getCurrentManifest();
|
||||
|
||||
/**
|
||||
* Returns the current {@link Timeline}. Never null, but may be empty.
|
||||
*/
|
||||
/** Returns the current {@link Timeline}. Never null, but may be empty. */
|
||||
Timeline getCurrentTimeline();
|
||||
|
||||
/**
|
||||
* Returns the index of the period currently being played.
|
||||
*/
|
||||
/** Returns the index of the period currently being played. */
|
||||
int getCurrentPeriodIndex();
|
||||
|
||||
/**
|
||||
@ -1538,9 +1523,7 @@ public interface Player {
|
||||
*/
|
||||
boolean isCurrentWindowSeekable();
|
||||
|
||||
/**
|
||||
* Returns whether the player is currently playing an ad.
|
||||
*/
|
||||
/** Returns whether the player is currently playing an ad. */
|
||||
boolean isPlayingAd();
|
||||
|
||||
/**
|
@ -734,14 +734,14 @@ public abstract class Timeline {
|
||||
* @return The index of the next window, or {@link C#INDEX_UNSET} if this is the last window.
|
||||
*/
|
||||
public int getNextWindowIndex(
|
||||
int windowIndex, @C.RepeatMode int repeatMode, boolean shuffleModeEnabled) {
|
||||
int windowIndex, @Player.RepeatMode int repeatMode, boolean shuffleModeEnabled) {
|
||||
switch (repeatMode) {
|
||||
case C.REPEAT_MODE_OFF:
|
||||
case Player.REPEAT_MODE_OFF:
|
||||
return windowIndex == getLastWindowIndex(shuffleModeEnabled) ? C.INDEX_UNSET
|
||||
: windowIndex + 1;
|
||||
case C.REPEAT_MODE_ONE:
|
||||
case Player.REPEAT_MODE_ONE:
|
||||
return windowIndex;
|
||||
case C.REPEAT_MODE_ALL:
|
||||
case Player.REPEAT_MODE_ALL:
|
||||
return windowIndex == getLastWindowIndex(shuffleModeEnabled)
|
||||
? getFirstWindowIndex(shuffleModeEnabled) : windowIndex + 1;
|
||||
default:
|
||||
@ -759,14 +759,14 @@ public abstract class Timeline {
|
||||
* @return The index of the previous window, or {@link C#INDEX_UNSET} if this is the first window.
|
||||
*/
|
||||
public int getPreviousWindowIndex(
|
||||
int windowIndex, @C.RepeatMode int repeatMode, boolean shuffleModeEnabled) {
|
||||
int windowIndex, @Player.RepeatMode int repeatMode, boolean shuffleModeEnabled) {
|
||||
switch (repeatMode) {
|
||||
case C.REPEAT_MODE_OFF:
|
||||
case Player.REPEAT_MODE_OFF:
|
||||
return windowIndex == getFirstWindowIndex(shuffleModeEnabled) ? C.INDEX_UNSET
|
||||
: windowIndex - 1;
|
||||
case C.REPEAT_MODE_ONE:
|
||||
case Player.REPEAT_MODE_ONE:
|
||||
return windowIndex;
|
||||
case C.REPEAT_MODE_ALL:
|
||||
case Player.REPEAT_MODE_ALL:
|
||||
return windowIndex == getFirstWindowIndex(shuffleModeEnabled)
|
||||
? getLastWindowIndex(shuffleModeEnabled) : windowIndex - 1;
|
||||
default:
|
||||
@ -847,7 +847,7 @@ public abstract class Timeline {
|
||||
int periodIndex,
|
||||
Period period,
|
||||
Window window,
|
||||
@C.RepeatMode int repeatMode,
|
||||
@Player.RepeatMode int repeatMode,
|
||||
boolean shuffleModeEnabled) {
|
||||
int windowIndex = getPeriod(periodIndex, period).windowIndex;
|
||||
if (getWindow(windowIndex, window).lastPeriodIndex == periodIndex) {
|
||||
@ -875,7 +875,7 @@ public abstract class Timeline {
|
||||
int periodIndex,
|
||||
Period period,
|
||||
Window window,
|
||||
@C.RepeatMode int repeatMode,
|
||||
@Player.RepeatMode int repeatMode,
|
||||
boolean shuffleModeEnabled) {
|
||||
return getNextPeriodIndex(periodIndex, period, window, repeatMode, shuffleModeEnabled)
|
||||
== C.INDEX_UNSET;
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.audio;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import com.google.android.exoplayer2.C;
|
||||
@ -33,6 +34,11 @@ import com.google.android.exoplayer2.util.Util;
|
||||
*/
|
||||
public final class AudioAttributes {
|
||||
|
||||
private static final String FIELD_CONTENT_TYPE = "contentType";
|
||||
private static final String FIELD_FLAGS = "flags";
|
||||
private static final String FIELD_USAGE = "usage";
|
||||
private static final String FIELD_ALLOWED_CAPTURE_POLICY = "allowedCapturePolicy";
|
||||
|
||||
public static final AudioAttributes DEFAULT = new Builder().build();
|
||||
|
||||
/**
|
||||
@ -159,4 +165,31 @@ public final class AudioAttributes {
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Converts this instance into a {@link Bundle}. */
|
||||
public Bundle toBundle() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putInt(FIELD_CONTENT_TYPE, contentType);
|
||||
bundle.putInt(FIELD_FLAGS, flags);
|
||||
bundle.putInt(FIELD_USAGE, usage);
|
||||
bundle.putInt(FIELD_ALLOWED_CAPTURE_POLICY, allowedCapturePolicy);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
/** Creates an {@link AudioAttributes} instance from a {@link Bundle}. */
|
||||
public static AudioAttributes fromBundle(Bundle bundle) {
|
||||
Builder builder = new Builder();
|
||||
if (bundle.containsKey(FIELD_CONTENT_TYPE)) {
|
||||
builder.setContentType(bundle.getInt(FIELD_CONTENT_TYPE));
|
||||
}
|
||||
if (bundle.containsKey(FIELD_FLAGS)) {
|
||||
builder.setFlags(bundle.getInt(FIELD_FLAGS));
|
||||
}
|
||||
if (bundle.containsKey(FIELD_USAGE)) {
|
||||
builder.setUsage(bundle.getInt(FIELD_USAGE));
|
||||
}
|
||||
if (bundle.containsKey(FIELD_ALLOWED_CAPTURE_POLICY)) {
|
||||
builder.setAllowedCapturePolicy(bundle.getInt(FIELD_ALLOWED_CAPTURE_POLICY));
|
||||
}
|
||||
return builder.build();
|
||||
}
|
||||
}
|
||||
|
@ -15,6 +15,7 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.device;
|
||||
|
||||
import android.os.Bundle;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.Nullable;
|
||||
import java.lang.annotation.Documented;
|
||||
@ -26,6 +27,10 @@ import java.lang.annotation.Target;
|
||||
/** Information about the playback device. */
|
||||
public final class DeviceInfo {
|
||||
|
||||
private static final String FIELD_PLAYBACK_TYPE = "playbackType";
|
||||
private static final String FIELD_MIN_VOLUME = "minVolume";
|
||||
private static final String FIELD_MAX_VOLUME = "maxVolume";
|
||||
|
||||
/** Types of playback. One of {@link #PLAYBACK_TYPE_LOCAL} or {@link #PLAYBACK_TYPE_REMOTE}. */
|
||||
@Documented
|
||||
@Retention(RetentionPolicy.SOURCE)
|
||||
@ -80,4 +85,21 @@ public final class DeviceInfo {
|
||||
result = 31 * result + maxVolume;
|
||||
return result;
|
||||
}
|
||||
|
||||
/** Converts this instance into a {@link Bundle}. */
|
||||
public Bundle toBundle() {
|
||||
Bundle bundle = new Bundle();
|
||||
bundle.putInt(FIELD_PLAYBACK_TYPE, playbackType);
|
||||
bundle.putInt(FIELD_MIN_VOLUME, minVolume);
|
||||
bundle.putInt(FIELD_MAX_VOLUME, maxVolume);
|
||||
return bundle;
|
||||
}
|
||||
|
||||
/** Creates an {@link DeviceInfo} instance from a {@link Bundle}. */
|
||||
public static DeviceInfo fromBundle(Bundle bundle) {
|
||||
int playbackType = bundle.getInt(FIELD_PLAYBACK_TYPE, /* defaultValue= */ PLAYBACK_TYPE_LOCAL);
|
||||
int minVolume = bundle.getInt(FIELD_MIN_VOLUME, /* defaultValue= */ 0);
|
||||
int maxVolume = bundle.getInt(FIELD_MAX_VOLUME, /* defaultValue= */ 0);
|
||||
return new DeviceInfo(playbackType, minVolume, maxVolume);
|
||||
}
|
||||
}
|
||||
|
@ -15,8 +15,9 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.device;
|
||||
|
||||
// TODO(b/172315872) change back to @link after player migration to common.
|
||||
/** A listener for changes of {@code Player.DeviceComponent}. */
|
||||
import com.google.android.exoplayer2.Player;
|
||||
|
||||
/** A listener for changes of {@link Player.DeviceComponent}. */
|
||||
public interface DeviceListener {
|
||||
|
||||
/** Called when the device information changes. */
|
||||
|
@ -15,13 +15,17 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.metadata.mp4;
|
||||
|
||||
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
|
||||
|
||||
import android.os.Parcel;
|
||||
import android.os.Parcelable;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.metadata.Metadata;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.common.base.Objects;
|
||||
import com.google.common.collect.ComparisonChain;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Comparator;
|
||||
import java.util.List;
|
||||
|
||||
/** Holds information about the segments of slow motion playback within a track. */
|
||||
@ -30,6 +34,14 @@ public final class SlowMotionData implements Metadata.Entry {
|
||||
/** Holds information about a single segment of slow motion playback within a track. */
|
||||
public static final class Segment implements Parcelable {
|
||||
|
||||
public static final Comparator<Segment> BY_START_THEN_END_THEN_DIVISOR =
|
||||
(s1, s2) ->
|
||||
ComparisonChain.start()
|
||||
.compare(s1.startTimeMs, s2.startTimeMs)
|
||||
.compare(s1.endTimeMs, s2.endTimeMs)
|
||||
.compare(s1.speedDivisor, s2.speedDivisor)
|
||||
.result();
|
||||
|
||||
/** The start time, in milliseconds, of the track segment that is intended to be slow motion. */
|
||||
public final long startTimeMs;
|
||||
/** The end time, in milliseconds, of the track segment that is intended to be slow motion. */
|
||||
@ -45,11 +57,12 @@ public final class SlowMotionData implements Metadata.Entry {
|
||||
/**
|
||||
* Creates an instance.
|
||||
*
|
||||
* @param startTimeMs See {@link #startTimeMs}.
|
||||
* @param startTimeMs See {@link #startTimeMs}. Must be less than endTimeMs.
|
||||
* @param endTimeMs See {@link #endTimeMs}.
|
||||
* @param speedDivisor See {@link #speedDivisor}.
|
||||
*/
|
||||
public Segment(long startTimeMs, long endTimeMs, int speedDivisor) {
|
||||
checkArgument(startTimeMs < endTimeMs);
|
||||
this.startTimeMs = startTimeMs;
|
||||
this.endTimeMs = endTimeMs;
|
||||
this.speedDivisor = speedDivisor;
|
||||
@ -113,9 +126,15 @@ public final class SlowMotionData implements Metadata.Entry {
|
||||
|
||||
public final List<Segment> segments;
|
||||
|
||||
/** Creates an instance with a list of {@link Segment}s. */
|
||||
/**
|
||||
* Creates an instance with a list of {@link Segment}s.
|
||||
*
|
||||
* <p>The segments must not overlap, that is that the start time of a segment can not be between
|
||||
* the start and end time of another segment.
|
||||
*/
|
||||
public SlowMotionData(List<Segment> segments) {
|
||||
this.segments = segments;
|
||||
checkArgument(!doSegmentsOverlap(segments));
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -164,4 +183,19 @@ public final class SlowMotionData implements Metadata.Entry {
|
||||
return new SlowMotionData[size];
|
||||
}
|
||||
};
|
||||
|
||||
private static boolean doSegmentsOverlap(List<Segment> segments) {
|
||||
if (segments.isEmpty()) {
|
||||
return false;
|
||||
}
|
||||
long previousEndTimeMs = segments.get(0).endTimeMs;
|
||||
for (int i = 1; i < segments.size(); i++) {
|
||||
if (segments.get(i).startTimeMs < previousEndTimeMs) {
|
||||
return true;
|
||||
}
|
||||
previousEndTimeMs = segments.get(i).endTimeMs;
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
@ -23,20 +23,10 @@ import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import java.util.Arrays;
|
||||
|
||||
// TODO: Add an allowMultipleStreams boolean to indicate where the one stream per group restriction
|
||||
// does not apply.
|
||||
/**
|
||||
* Defines a group of tracks exposed by a {@link MediaPeriod}.
|
||||
*
|
||||
* <p>A {@link MediaPeriod} is only able to provide one {@link SampleStream} corresponding to a
|
||||
* group at any given time, however this {@link SampleStream} may adapt between multiple tracks
|
||||
* within the group.
|
||||
*/
|
||||
/** Defines an immutable group of tracks identified by their format identity. */
|
||||
public final class TrackGroup implements Parcelable {
|
||||
|
||||
/**
|
||||
* The number of tracks in the group.
|
||||
*/
|
||||
/** The number of tracks in the group. */
|
||||
public final int length;
|
||||
|
||||
private final Format[] formats;
|
||||
@ -45,7 +35,7 @@ public final class TrackGroup implements Parcelable {
|
||||
private int hashCode;
|
||||
|
||||
/**
|
||||
* @param formats The track formats. Must not be null, contain null elements or be of length 0.
|
||||
* @param formats The track formats. At least one {@link Format} must be provided.
|
||||
*/
|
||||
public TrackGroup(Format... formats) {
|
||||
Assertions.checkState(formats.length > 0);
|
@ -21,17 +21,13 @@ import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import java.util.Arrays;
|
||||
|
||||
/** An array of {@link TrackGroup}s exposed by a {@link MediaPeriod}. */
|
||||
/** An immutable array of {@link TrackGroup}s. */
|
||||
public final class TrackGroupArray implements Parcelable {
|
||||
|
||||
/**
|
||||
* The empty array.
|
||||
*/
|
||||
/** The empty array. */
|
||||
public static final TrackGroupArray EMPTY = new TrackGroupArray();
|
||||
|
||||
/**
|
||||
* The number of groups in the array. Greater than or equal to zero.
|
||||
*/
|
||||
/** The number of groups in the array. Greater than or equal to zero. */
|
||||
public final int length;
|
||||
|
||||
private final TrackGroup[] trackGroups;
|
||||
@ -39,9 +35,7 @@ public final class TrackGroupArray implements Parcelable {
|
||||
// Lazily initialized hashcode.
|
||||
private int hashCode;
|
||||
|
||||
/**
|
||||
* @param trackGroups The groups. Must not be null or contain null elements, but may be empty.
|
||||
*/
|
||||
/** @param trackGroups The groups. May be empty. */
|
||||
public TrackGroupArray(TrackGroup... trackGroups) {
|
||||
this.trackGroups = trackGroups;
|
||||
this.length = trackGroups.length;
|
||||
@ -83,9 +77,7 @@ public final class TrackGroupArray implements Parcelable {
|
||||
return C.INDEX_UNSET;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether this track group array is empty.
|
||||
*/
|
||||
/** Returns whether this track group array is empty. */
|
||||
public boolean isEmpty() {
|
||||
return length == 0;
|
||||
}
|
@ -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);
|
||||
}
|
@ -73,5 +73,4 @@ public final class TrackSelectionArray {
|
||||
TrackSelectionArray other = (TrackSelectionArray) obj;
|
||||
return Arrays.equals(trackSelections, other.trackSelections);
|
||||
}
|
||||
|
||||
}
|
@ -15,6 +15,8 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.upstream;
|
||||
|
||||
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
|
||||
import static com.google.android.exoplayer2.util.Util.castNonNull;
|
||||
import static java.lang.Math.max;
|
||||
import static java.lang.Math.min;
|
||||
|
||||
@ -24,7 +26,6 @@ import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.upstream.DataSpec.HttpMethod;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.Log;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.common.base.Predicate;
|
||||
@ -42,10 +43,10 @@ import java.util.Collections;
|
||||
import java.util.HashMap;
|
||||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.concurrent.atomic.AtomicReference;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
/**
|
||||
* An {@link HttpDataSource} that uses Android's {@link HttpURLConnection}.
|
||||
@ -208,7 +209,6 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
|
||||
private static final long MAX_BYTES_TO_DRAIN = 2048;
|
||||
private static final Pattern CONTENT_RANGE_HEADER =
|
||||
Pattern.compile("^bytes (\\d+)-(\\d+)/(\\d+)$");
|
||||
private static final AtomicReference<byte[]> skipBufferReference = new AtomicReference<>();
|
||||
|
||||
private final boolean allowCrossProtocolRedirects;
|
||||
private final int connectTimeoutMillis;
|
||||
@ -221,6 +221,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
|
||||
@Nullable private DataSpec dataSpec;
|
||||
@Nullable private HttpURLConnection connection;
|
||||
@Nullable private InputStream inputStream;
|
||||
private byte @MonotonicNonNull [] skipBuffer;
|
||||
private boolean opened;
|
||||
private int responseCode;
|
||||
|
||||
@ -318,14 +319,14 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
|
||||
|
||||
@Override
|
||||
public void setRequestProperty(String name, String value) {
|
||||
Assertions.checkNotNull(name);
|
||||
Assertions.checkNotNull(value);
|
||||
checkNotNull(name);
|
||||
checkNotNull(value);
|
||||
requestProperties.set(name, value);
|
||||
}
|
||||
|
||||
@Override
|
||||
public void clearRequestProperty(String name) {
|
||||
Assertions.checkNotNull(name);
|
||||
checkNotNull(name);
|
||||
requestProperties.remove(name);
|
||||
}
|
||||
|
||||
@ -343,6 +344,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
|
||||
this.bytesRead = 0;
|
||||
this.bytesSkipped = 0;
|
||||
transferInitializing(dataSpec);
|
||||
|
||||
try {
|
||||
connection = makeConnection(dataSpec);
|
||||
} catch (IOException e) {
|
||||
@ -355,6 +357,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
|
||||
"Unable to connect", e, dataSpec, HttpDataSourceException.TYPE_OPEN);
|
||||
}
|
||||
|
||||
HttpURLConnection connection = this.connection;
|
||||
String responseMessage;
|
||||
try {
|
||||
responseCode = connection.getResponseCode();
|
||||
@ -438,19 +441,22 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
|
||||
skipInternal();
|
||||
return readInternal(buffer, offset, readLength);
|
||||
} catch (IOException e) {
|
||||
throw new HttpDataSourceException(e, dataSpec, HttpDataSourceException.TYPE_READ);
|
||||
throw new HttpDataSourceException(
|
||||
e, castNonNull(dataSpec), HttpDataSourceException.TYPE_READ);
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
public void close() throws HttpDataSourceException {
|
||||
try {
|
||||
@Nullable InputStream inputStream = this.inputStream;
|
||||
if (inputStream != null) {
|
||||
maybeTerminateInputStream(connection, bytesRemaining());
|
||||
try {
|
||||
inputStream.close();
|
||||
} catch (IOException e) {
|
||||
throw new HttpDataSourceException(e, dataSpec, HttpDataSourceException.TYPE_CLOSE);
|
||||
throw new HttpDataSourceException(
|
||||
e, castNonNull(dataSpec), HttpDataSourceException.TYPE_CLOSE);
|
||||
}
|
||||
}
|
||||
} finally {
|
||||
@ -694,7 +700,9 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
|
||||
if (matcher.find()) {
|
||||
try {
|
||||
long contentLengthFromRange =
|
||||
Long.parseLong(matcher.group(2)) - Long.parseLong(matcher.group(1)) + 1;
|
||||
Long.parseLong(checkNotNull(matcher.group(2)))
|
||||
- Long.parseLong(checkNotNull(matcher.group(1)))
|
||||
+ 1;
|
||||
if (contentLength < 0) {
|
||||
// Some proxy servers strip the Content-Length header. Fall back to the length
|
||||
// calculated here in this case.
|
||||
@ -729,15 +737,13 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
|
||||
return;
|
||||
}
|
||||
|
||||
// Acquire the shared skip buffer.
|
||||
byte[] skipBuffer = skipBufferReference.getAndSet(null);
|
||||
if (skipBuffer == null) {
|
||||
skipBuffer = new byte[4096];
|
||||
}
|
||||
|
||||
while (bytesSkipped != bytesToSkip) {
|
||||
int readLength = (int) min(bytesToSkip - bytesSkipped, skipBuffer.length);
|
||||
int read = inputStream.read(skipBuffer, 0, readLength);
|
||||
int read = castNonNull(inputStream).read(skipBuffer, 0, readLength);
|
||||
if (Thread.currentThread().isInterrupted()) {
|
||||
throw new InterruptedIOException();
|
||||
}
|
||||
@ -747,9 +753,6 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
|
||||
bytesSkipped += read;
|
||||
bytesTransferred(read);
|
||||
}
|
||||
|
||||
// Release the shared skip buffer.
|
||||
skipBufferReference.set(skipBuffer);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -778,7 +781,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
|
||||
readLength = (int) min(readLength, bytesRemaining);
|
||||
}
|
||||
|
||||
int read = inputStream.read(buffer, offset, readLength);
|
||||
int read = castNonNull(inputStream).read(buffer, offset, readLength);
|
||||
if (read == -1) {
|
||||
if (bytesToRead != C.LENGTH_UNSET) {
|
||||
// End of stream reached having not read sufficient data.
|
||||
@ -803,8 +806,9 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
|
||||
* @param bytesRemaining The number of bytes remaining to be read from the input stream if its
|
||||
* length is known. {@link C#LENGTH_UNSET} otherwise.
|
||||
*/
|
||||
private static void maybeTerminateInputStream(HttpURLConnection connection, long bytesRemaining) {
|
||||
if (Util.SDK_INT != 19 && Util.SDK_INT != 20) {
|
||||
private static void maybeTerminateInputStream(
|
||||
@Nullable HttpURLConnection connection, long bytesRemaining) {
|
||||
if (connection == null || Util.SDK_INT < 19 || Util.SDK_INT > 20) {
|
||||
return;
|
||||
}
|
||||
|
||||
@ -825,7 +829,8 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
|
||||
|| "com.android.okhttp.internal.http.HttpTransport$FixedLengthInputStream"
|
||||
.equals(className)) {
|
||||
Class<?> superclass = inputStream.getClass().getSuperclass();
|
||||
Method unexpectedEndOfInput = superclass.getDeclaredMethod("unexpectedEndOfInput");
|
||||
Method unexpectedEndOfInput =
|
||||
checkNotNull(superclass).getDeclaredMethod("unexpectedEndOfInput");
|
||||
unexpectedEndOfInput.setAccessible(true);
|
||||
unexpectedEndOfInput.invoke(inputStream);
|
||||
}
|
||||
@ -836,7 +841,6 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
/**
|
||||
* Closes the current connection quietly, if there is one.
|
||||
*/
|
@ -43,9 +43,6 @@ public interface Clock {
|
||||
/** @see android.os.SystemClock#uptimeMillis() */
|
||||
long uptimeMillis();
|
||||
|
||||
/** @see android.os.SystemClock#sleep(long) */
|
||||
void sleep(long sleepTimeMs);
|
||||
|
||||
/**
|
||||
* Creates a {@link HandlerWrapper} using a specified looper and a specified callback for handling
|
||||
* messages.
|
||||
@ -53,4 +50,12 @@ public interface Clock {
|
||||
* @see Handler#Handler(Looper, Handler.Callback)
|
||||
*/
|
||||
HandlerWrapper createHandler(Looper looper, @Nullable Handler.Callback callback);
|
||||
|
||||
/**
|
||||
* Notifies the clock that the current thread is about to be blocked and won't return until a
|
||||
* condition on another thread becomes true.
|
||||
*
|
||||
* <p>Should be a no-op for all non-test cases.
|
||||
*/
|
||||
void onThreadBlocked();
|
||||
}
|
@ -17,7 +17,6 @@ package com.google.android.exoplayer2.util;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import androidx.annotation.Nullable;
|
||||
|
||||
/**
|
||||
@ -26,39 +25,55 @@ import androidx.annotation.Nullable;
|
||||
*/
|
||||
public interface HandlerWrapper {
|
||||
|
||||
/** @see Handler#getLooper() */
|
||||
/** A message obtained from the handler. */
|
||||
interface Message {
|
||||
|
||||
/** See {@link android.os.Message#sendToTarget()}. */
|
||||
void sendToTarget();
|
||||
|
||||
/** See {@link android.os.Message#getTarget()}. */
|
||||
HandlerWrapper getTarget();
|
||||
}
|
||||
|
||||
/** See {@link Handler#getLooper()}. */
|
||||
Looper getLooper();
|
||||
|
||||
/** @see Handler#obtainMessage(int) */
|
||||
/** See {@link Handler#hasMessages(int)}. */
|
||||
boolean hasMessages(int what);
|
||||
|
||||
/** See {@link Handler#obtainMessage(int)}. */
|
||||
Message obtainMessage(int what);
|
||||
|
||||
/** @see Handler#obtainMessage(int, Object) */
|
||||
/** See {@link Handler#obtainMessage(int, Object)}. */
|
||||
Message obtainMessage(int what, @Nullable Object obj);
|
||||
|
||||
/** @see Handler#obtainMessage(int, int, int) */
|
||||
/** See {@link Handler#obtainMessage(int, int, int)}. */
|
||||
Message obtainMessage(int what, int arg1, int arg2);
|
||||
|
||||
/** @see Handler#obtainMessage(int, int, int, Object) */
|
||||
/** See {@link Handler#obtainMessage(int, int, int, Object)}. */
|
||||
Message obtainMessage(int what, int arg1, int arg2, @Nullable Object obj);
|
||||
|
||||
/** @see Handler#sendEmptyMessage(int) */
|
||||
/** See {@link Handler#sendMessageAtFrontOfQueue(android.os.Message)}. */
|
||||
boolean sendMessageAtFrontOfQueue(Message message);
|
||||
|
||||
/** See {@link Handler#sendEmptyMessage(int)}. */
|
||||
boolean sendEmptyMessage(int what);
|
||||
|
||||
/** @see Handler#sendEmptyMessageDelayed(int, long) */
|
||||
/** See {@link Handler#sendEmptyMessageDelayed(int, long)}. */
|
||||
boolean sendEmptyMessageDelayed(int what, int delayMs);
|
||||
|
||||
/** @see Handler#sendEmptyMessageAtTime(int, long) */
|
||||
/** See {@link Handler#sendEmptyMessageAtTime(int, long)}. */
|
||||
boolean sendEmptyMessageAtTime(int what, long uptimeMs);
|
||||
|
||||
/** @see Handler#removeMessages(int) */
|
||||
/** See {@link Handler#removeMessages(int)}. */
|
||||
void removeMessages(int what);
|
||||
|
||||
/** @see Handler#removeCallbacksAndMessages(Object) */
|
||||
/** See {@link Handler#removeCallbacksAndMessages(Object)}. */
|
||||
void removeCallbacksAndMessages(@Nullable Object token);
|
||||
|
||||
/** @see Handler#post(Runnable) */
|
||||
/** See {@link Handler#post(Runnable)}. */
|
||||
boolean post(Runnable runnable);
|
||||
|
||||
/** @see Handler#postDelayed(Runnable, long) */
|
||||
/** See {@link Handler#postDelayed(Runnable, long)}. */
|
||||
boolean postDelayed(Runnable runnable, long delayMs);
|
||||
}
|
@ -15,7 +15,6 @@
|
||||
*/
|
||||
package com.google.android.exoplayer2.util;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import androidx.annotation.CheckResult;
|
||||
@ -72,7 +71,8 @@ public final class ListenerSet<T, E extends MutableFlags> {
|
||||
private static final int MSG_ITERATION_FINISHED = 0;
|
||||
private static final int MSG_LAZY_RELEASE = 1;
|
||||
|
||||
private final Handler handler;
|
||||
private final Clock clock;
|
||||
private final HandlerWrapper handler;
|
||||
private final Supplier<E> eventFlagsSupplier;
|
||||
private final IterationFinishedEvent<T, E> iterationFinishedEvent;
|
||||
private final CopyOnWriteArraySet<ListenerHolder<T, E>> listeners;
|
||||
@ -86,6 +86,7 @@ public final class ListenerSet<T, E extends MutableFlags> {
|
||||
*
|
||||
* @param looper A {@link Looper} used to call listeners on. The same {@link Looper} must be used
|
||||
* to call all other methods of this class.
|
||||
* @param clock A {@link Clock}.
|
||||
* @param eventFlagsSupplier A {@link Supplier} for new instances of {@link E the event flags
|
||||
* type}.
|
||||
* @param iterationFinishedEvent An {@link IterationFinishedEvent} sent when all other events sent
|
||||
@ -93,11 +94,13 @@ public final class ListenerSet<T, E extends MutableFlags> {
|
||||
*/
|
||||
public ListenerSet(
|
||||
Looper looper,
|
||||
Clock clock,
|
||||
Supplier<E> eventFlagsSupplier,
|
||||
IterationFinishedEvent<T, E> iterationFinishedEvent) {
|
||||
this(
|
||||
/* listeners= */ new CopyOnWriteArraySet<>(),
|
||||
looper,
|
||||
clock,
|
||||
eventFlagsSupplier,
|
||||
iterationFinishedEvent);
|
||||
}
|
||||
@ -105,8 +108,10 @@ public final class ListenerSet<T, E extends MutableFlags> {
|
||||
private ListenerSet(
|
||||
CopyOnWriteArraySet<ListenerHolder<T, E>> listeners,
|
||||
Looper looper,
|
||||
Clock clock,
|
||||
Supplier<E> eventFlagsSupplier,
|
||||
IterationFinishedEvent<T, E> iterationFinishedEvent) {
|
||||
this.clock = clock;
|
||||
this.listeners = listeners;
|
||||
this.eventFlagsSupplier = eventFlagsSupplier;
|
||||
this.iterationFinishedEvent = iterationFinishedEvent;
|
||||
@ -114,7 +119,7 @@ public final class ListenerSet<T, E extends MutableFlags> {
|
||||
queuedEvents = new ArrayDeque<>();
|
||||
// It's safe to use "this" because we don't send a message before exiting the constructor.
|
||||
@SuppressWarnings("methodref.receiver.bound.invalid")
|
||||
Handler handler = Util.createHandler(looper, this::handleMessage);
|
||||
HandlerWrapper handler = clock.createHandler(looper, this::handleMessage);
|
||||
this.handler = handler;
|
||||
}
|
||||
|
||||
@ -129,7 +134,7 @@ public final class ListenerSet<T, E extends MutableFlags> {
|
||||
@CheckResult
|
||||
public ListenerSet<T, E> copy(
|
||||
Looper looper, IterationFinishedEvent<T, E> iterationFinishedEvent) {
|
||||
return new ListenerSet<>(listeners, looper, eventFlagsSupplier, iterationFinishedEvent);
|
||||
return new ListenerSet<>(listeners, looper, clock, eventFlagsSupplier, iterationFinishedEvent);
|
||||
}
|
||||
|
||||
/**
|
@ -22,6 +22,7 @@ import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.lang.annotation.RetentionPolicy;
|
||||
import java.net.UnknownHostException;
|
||||
import org.checkerframework.dataflow.qual.Pure;
|
||||
|
||||
/** Wrapper around {@link android.util.Log} which allows to set the log level. */
|
||||
public final class Log {
|
||||
@ -51,11 +52,13 @@ public final class Log {
|
||||
private Log() {}
|
||||
|
||||
/** Returns current {@link LogLevel} for ExoPlayer logcat logging. */
|
||||
@Pure
|
||||
public static @LogLevel int getLogLevel() {
|
||||
return logLevel;
|
||||
}
|
||||
|
||||
/** Returns whether stack traces of {@link Throwable}s will be logged to logcat. */
|
||||
@Pure
|
||||
public boolean getLogStackTraces() {
|
||||
return logStackTraces;
|
||||
}
|
||||
@ -80,6 +83,7 @@ public final class Log {
|
||||
}
|
||||
|
||||
/** @see android.util.Log#d(String, String) */
|
||||
@Pure
|
||||
public static void d(String tag, String message) {
|
||||
if (logLevel == LOG_LEVEL_ALL) {
|
||||
android.util.Log.d(tag, message);
|
||||
@ -87,11 +91,13 @@ public final class Log {
|
||||
}
|
||||
|
||||
/** @see android.util.Log#d(String, String, Throwable) */
|
||||
@Pure
|
||||
public static void d(String tag, String message, @Nullable Throwable throwable) {
|
||||
d(tag, appendThrowableString(message, throwable));
|
||||
}
|
||||
|
||||
/** @see android.util.Log#i(String, String) */
|
||||
@Pure
|
||||
public static void i(String tag, String message) {
|
||||
if (logLevel <= LOG_LEVEL_INFO) {
|
||||
android.util.Log.i(tag, message);
|
||||
@ -99,11 +105,13 @@ public final class Log {
|
||||
}
|
||||
|
||||
/** @see android.util.Log#i(String, String, Throwable) */
|
||||
@Pure
|
||||
public static void i(String tag, String message, @Nullable Throwable throwable) {
|
||||
i(tag, appendThrowableString(message, throwable));
|
||||
}
|
||||
|
||||
/** @see android.util.Log#w(String, String) */
|
||||
@Pure
|
||||
public static void w(String tag, String message) {
|
||||
if (logLevel <= LOG_LEVEL_WARNING) {
|
||||
android.util.Log.w(tag, message);
|
||||
@ -111,11 +119,13 @@ public final class Log {
|
||||
}
|
||||
|
||||
/** @see android.util.Log#w(String, String, Throwable) */
|
||||
@Pure
|
||||
public static void w(String tag, String message, @Nullable Throwable throwable) {
|
||||
w(tag, appendThrowableString(message, throwable));
|
||||
}
|
||||
|
||||
/** @see android.util.Log#e(String, String) */
|
||||
@Pure
|
||||
public static void e(String tag, String message) {
|
||||
if (logLevel <= LOG_LEVEL_ERROR) {
|
||||
android.util.Log.e(tag, message);
|
||||
@ -123,6 +133,7 @@ public final class Log {
|
||||
}
|
||||
|
||||
/** @see android.util.Log#e(String, String, Throwable) */
|
||||
@Pure
|
||||
public static void e(String tag, String message, @Nullable Throwable throwable) {
|
||||
e(tag, appendThrowableString(message, throwable));
|
||||
}
|
||||
@ -139,6 +150,7 @@ public final class Log {
|
||||
* @return The string representation of the {@link Throwable}.
|
||||
*/
|
||||
@Nullable
|
||||
@Pure
|
||||
public static String getThrowableString(@Nullable Throwable throwable) {
|
||||
if (throwable == null) {
|
||||
return null;
|
||||
@ -157,6 +169,7 @@ public final class Log {
|
||||
}
|
||||
}
|
||||
|
||||
@Pure
|
||||
private static String appendThrowableString(String message, @Nullable Throwable throwable) {
|
||||
@Nullable String throwableString = getThrowableString(throwable);
|
||||
if (!TextUtils.isEmpty(throwableString)) {
|
||||
@ -165,6 +178,7 @@ public final class Log {
|
||||
return message;
|
||||
}
|
||||
|
||||
@Pure
|
||||
private static boolean isCausedByUnknownHostException(@Nullable Throwable throwable) {
|
||||
while (throwable != null) {
|
||||
if (throwable instanceof UnknownHostException) {
|
||||
|
@ -43,13 +43,13 @@ public class SystemClock implements Clock {
|
||||
return android.os.SystemClock.uptimeMillis();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void sleep(long sleepTimeMs) {
|
||||
android.os.SystemClock.sleep(sleepTimeMs);
|
||||
}
|
||||
|
||||
@Override
|
||||
public HandlerWrapper createHandler(Looper looper, @Nullable Callback callback) {
|
||||
return new SystemHandlerWrapper(new Handler(looper, callback));
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onThreadBlocked() {
|
||||
// Do nothing.
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
||||
}
|
@ -91,6 +91,7 @@ import java.util.concurrent.Executors;
|
||||
import java.util.regex.Matcher;
|
||||
import java.util.regex.Pattern;
|
||||
import java.util.zip.DataFormatException;
|
||||
import java.util.zip.GZIPOutputStream;
|
||||
import java.util.zip.Inflater;
|
||||
import org.checkerframework.checker.initialization.qual.UnknownInitialization;
|
||||
import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||
@ -2045,8 +2046,6 @@ public final class Util {
|
||||
|
||||
/** Returns a data URI with the specified MIME type and data. */
|
||||
public static Uri getDataUriForString(String mimeType, String data) {
|
||||
// TODO(internal: b/169937045): For now we don't pass the URL_SAFE flag as DataSchemeDataSource
|
||||
// doesn't decode using it.
|
||||
return Uri.parse(
|
||||
"data:" + mimeType + ";base64," + Base64.encodeToString(data.getBytes(), Base64.NO_WRAP));
|
||||
}
|
||||
@ -2085,7 +2084,7 @@ public final class Util {
|
||||
|
||||
/** Creates a new empty file in the directory returned by {@link Context#getCacheDir()}. */
|
||||
public static File createTempFile(Context context, String prefix) throws IOException {
|
||||
return File.createTempFile(prefix, null, context.getCacheDir());
|
||||
return File.createTempFile(prefix, null, checkNotNull(context.getCacheDir()));
|
||||
}
|
||||
|
||||
/**
|
||||
@ -2123,6 +2122,17 @@ public final class Util {
|
||||
return initialValue;
|
||||
}
|
||||
|
||||
/** Compresses {@code input} using gzip and returns the result in a newly allocated byte array. */
|
||||
public static byte[] gzip(byte[] input) {
|
||||
ByteArrayOutputStream output = new ByteArrayOutputStream();
|
||||
try (GZIPOutputStream os = new GZIPOutputStream(output)) {
|
||||
os.write(input);
|
||||
} catch (IOException e) {
|
||||
throw new AssertionError(e);
|
||||
}
|
||||
return output.toByteArray();
|
||||
}
|
||||
|
||||
/**
|
||||
* Absolute <i>get</i> method for reading an int value in {@link ByteOrder#BIG_ENDIAN} in a {@link
|
||||
* ByteBuffer}. Same as {@link ByteBuffer#getInt(int)} except the buffer's order as returned by
|
||||
@ -2179,7 +2189,7 @@ public final class Util {
|
||||
return getMobileNetworkType(networkInfo);
|
||||
case ConnectivityManager.TYPE_ETHERNET:
|
||||
return C.NETWORK_TYPE_ETHERNET;
|
||||
default: // VPN, Bluetooth, Dummy.
|
||||
default:
|
||||
return C.NETWORK_TYPE_OTHER;
|
||||
}
|
||||
}
|
||||
@ -2610,7 +2620,7 @@ public final class Util {
|
||||
"hsn", "zh-hsn"
|
||||
};
|
||||
|
||||
// Legacy ("grandfathered") tags, replaced by modern equivalents (including macrolanguage)
|
||||
// Legacy tags that have been replaced by modern equivalents (including macrolanguage)
|
||||
// See https://www.iana.org/assignments/language-subtag-registry/language-subtag-registry.
|
||||
private static final String[] isoLegacyTagReplacements =
|
||||
new String[] {
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
@ -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);
|
||||
}
|
||||
}
|
@ -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());
|
||||
}
|
||||
}
|
@ -43,7 +43,8 @@ public class ListenerSetTest {
|
||||
@Test
|
||||
public void queueEvent_withoutFlush_sendsNoEvents() {
|
||||
ListenerSet<TestListener, Flags> listenerSet =
|
||||
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
|
||||
new ListenerSet<>(
|
||||
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
|
||||
TestListener listener = mock(TestListener.class);
|
||||
listenerSet.add(listener);
|
||||
|
||||
@ -57,7 +58,8 @@ public class ListenerSetTest {
|
||||
@Test
|
||||
public void flushEvents_sendsPreviouslyQueuedEventsToAllListeners() {
|
||||
ListenerSet<TestListener, Flags> listenerSet =
|
||||
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
|
||||
new ListenerSet<>(
|
||||
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
|
||||
TestListener listener1 = mock(TestListener.class);
|
||||
TestListener listener2 = mock(TestListener.class);
|
||||
listenerSet.add(listener1);
|
||||
@ -81,7 +83,8 @@ public class ListenerSetTest {
|
||||
@Test
|
||||
public void flushEvents_recursive_sendsEventsInCorrectOrder() {
|
||||
ListenerSet<TestListener, Flags> listenerSet =
|
||||
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
|
||||
new ListenerSet<>(
|
||||
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
|
||||
// Listener1 sends callback3 recursively when receiving callback1.
|
||||
TestListener listener1 =
|
||||
spy(
|
||||
@ -114,7 +117,8 @@ public class ListenerSetTest {
|
||||
public void
|
||||
flushEvents_withMultipleMessageQueueIterations_sendsIterationFinishedEventPerIteration() {
|
||||
ListenerSet<TestListener, Flags> listenerSet =
|
||||
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
|
||||
new ListenerSet<>(
|
||||
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
|
||||
// Listener1 sends callback1 recursively when receiving callback3.
|
||||
TestListener listener1 =
|
||||
spy(
|
||||
@ -170,7 +174,8 @@ public class ListenerSetTest {
|
||||
@Test
|
||||
public void flushEvents_calledFromIterationFinishedCallback_restartsIterationFinishedEvents() {
|
||||
ListenerSet<TestListener, Flags> listenerSet =
|
||||
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
|
||||
new ListenerSet<>(
|
||||
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
|
||||
// Listener2 sends callback1 recursively when receiving the iteration finished event.
|
||||
TestListener listener2 =
|
||||
spy(
|
||||
@ -212,7 +217,8 @@ public class ListenerSetTest {
|
||||
@Test
|
||||
public void flushEvents_withUnsetEventFlag_doesNotThrow() {
|
||||
ListenerSet<TestListener, Flags> listenerSet =
|
||||
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
|
||||
new ListenerSet<>(
|
||||
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
|
||||
|
||||
listenerSet.queueEvent(/* eventFlag= */ C.INDEX_UNSET, TestListener::callback1);
|
||||
listenerSet.flushEvents();
|
||||
@ -224,7 +230,8 @@ public class ListenerSetTest {
|
||||
@Test
|
||||
public void add_withRecursion_onlyReceivesUpdatesForFutureEvents() {
|
||||
ListenerSet<TestListener, Flags> listenerSet =
|
||||
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
|
||||
new ListenerSet<>(
|
||||
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
|
||||
TestListener listener2 = mock(TestListener.class);
|
||||
// Listener1 adds listener2 recursively.
|
||||
TestListener listener1 =
|
||||
@ -256,7 +263,8 @@ public class ListenerSetTest {
|
||||
@Test
|
||||
public void add_withQueueing_onlyReceivesUpdatesForFutureEvents() {
|
||||
ListenerSet<TestListener, Flags> listenerSet =
|
||||
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
|
||||
new ListenerSet<>(
|
||||
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
|
||||
TestListener listener1 = mock(TestListener.class);
|
||||
TestListener listener2 = mock(TestListener.class);
|
||||
|
||||
@ -281,7 +289,8 @@ public class ListenerSetTest {
|
||||
@Test
|
||||
public void remove_withRecursion_stopsReceivingEventsImmediately() {
|
||||
ListenerSet<TestListener, Flags> listenerSet =
|
||||
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
|
||||
new ListenerSet<>(
|
||||
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
|
||||
TestListener listener2 = mock(TestListener.class);
|
||||
// Listener1 removes listener2 recursively.
|
||||
TestListener listener1 =
|
||||
@ -309,7 +318,8 @@ public class ListenerSetTest {
|
||||
@Test
|
||||
public void remove_withQueueing_stopsReceivingEventsImmediately() {
|
||||
ListenerSet<TestListener, Flags> listenerSet =
|
||||
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
|
||||
new ListenerSet<>(
|
||||
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
|
||||
TestListener listener1 = mock(TestListener.class);
|
||||
TestListener listener2 = mock(TestListener.class);
|
||||
listenerSet.add(listener1);
|
||||
@ -330,7 +340,8 @@ public class ListenerSetTest {
|
||||
@Test
|
||||
public void release_stopsForwardingEventsImmediately() {
|
||||
ListenerSet<TestListener, Flags> listenerSet =
|
||||
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
|
||||
new ListenerSet<>(
|
||||
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
|
||||
TestListener listener2 = mock(TestListener.class);
|
||||
// Listener1 releases the set from within the callback.
|
||||
TestListener listener1 =
|
||||
@ -357,7 +368,8 @@ public class ListenerSetTest {
|
||||
@Test
|
||||
public void release_preventsRegisteringNewListeners() {
|
||||
ListenerSet<TestListener, Flags> listenerSet =
|
||||
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
|
||||
new ListenerSet<>(
|
||||
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
|
||||
TestListener listener = mock(TestListener.class);
|
||||
|
||||
listenerSet.release();
|
||||
@ -370,7 +382,8 @@ public class ListenerSetTest {
|
||||
@Test
|
||||
public void lazyRelease_stopsForwardingEventsFromNewHandlerMessagesAndCallsReleaseCallback() {
|
||||
ListenerSet<TestListener, Flags> listenerSet =
|
||||
new ListenerSet<>(Looper.myLooper(), Flags::new, TestListener::iterationFinished);
|
||||
new ListenerSet<>(
|
||||
Looper.myLooper(), Clock.DEFAULT, Flags::new, TestListener::iterationFinished);
|
||||
TestListener listener = mock(TestListener.class);
|
||||
listenerSet.add(listener);
|
||||
|
@ -20,6 +20,7 @@ import static com.google.android.exoplayer2.util.Util.binarySearchFloor;
|
||||
import static com.google.android.exoplayer2.util.Util.escapeFileName;
|
||||
import static com.google.android.exoplayer2.util.Util.getCodecsOfType;
|
||||
import static com.google.android.exoplayer2.util.Util.getStringForTime;
|
||||
import static com.google.android.exoplayer2.util.Util.gzip;
|
||||
import static com.google.android.exoplayer2.util.Util.minValue;
|
||||
import static com.google.android.exoplayer2.util.Util.parseXsDateTime;
|
||||
import static com.google.android.exoplayer2.util.Util.parseXsDuration;
|
||||
@ -37,6 +38,9 @@ import android.text.style.UnderlineSpan;
|
||||
import android.util.SparseLongArray;
|
||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.testutil.TestUtil;
|
||||
import com.google.common.io.ByteStreams;
|
||||
import java.io.ByteArrayInputStream;
|
||||
import java.nio.ByteBuffer;
|
||||
import java.nio.ByteOrder;
|
||||
import java.util.ArrayList;
|
||||
@ -45,6 +49,7 @@ import java.util.Formatter;
|
||||
import java.util.NoSuchElementException;
|
||||
import java.util.Random;
|
||||
import java.util.zip.Deflater;
|
||||
import java.util.zip.GZIPInputStream;
|
||||
import org.junit.Test;
|
||||
import org.junit.runner.RunWith;
|
||||
import org.robolectric.annotation.Config;
|
||||
@ -927,6 +932,17 @@ public class UtilTest {
|
||||
assertThat(result).isEqualTo(0x4);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void gzip_resultInflatesBackToOriginalValue() throws Exception {
|
||||
byte[] input = TestUtil.buildTestData(20);
|
||||
|
||||
byte[] deflated = gzip(input);
|
||||
|
||||
byte[] inflated =
|
||||
ByteStreams.toByteArray(new GZIPInputStream(new ByteArrayInputStream(deflated)));
|
||||
assertThat(inflated).isEqualTo(input);
|
||||
}
|
||||
|
||||
@Test
|
||||
public void getBigEndianInt_fromBigEndian() {
|
||||
byte[] bytes = {0x1F, 0x2E, 0x3D, 0x4C};
|
||||
|
@ -21,16 +21,14 @@ import static java.lang.Math.min;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
import com.google.android.exoplayer2.upstream.DefaultAllocator;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.Log;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
|
||||
/**
|
||||
* The default {@link LoadControl} implementation.
|
||||
*/
|
||||
/** The default {@link LoadControl} implementation. */
|
||||
public class DefaultLoadControl implements LoadControl {
|
||||
|
||||
/**
|
||||
@ -318,8 +316,8 @@ public class DefaultLoadControl implements LoadControl {
|
||||
}
|
||||
|
||||
@Override
|
||||
public void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups,
|
||||
TrackSelectionArray trackSelections) {
|
||||
public void onTracksSelected(
|
||||
Renderer[] renderers, TrackGroupArray trackGroups, ExoTrackSelection[] trackSelections) {
|
||||
targetBufferBytes =
|
||||
targetBufferBytesOverwrite == C.LENGTH_UNSET
|
||||
? calculateTargetBufferBytes(renderers, trackSelections)
|
||||
@ -402,10 +400,10 @@ public class DefaultLoadControl implements LoadControl {
|
||||
* @return The target buffer size in bytes.
|
||||
*/
|
||||
protected int calculateTargetBufferBytes(
|
||||
Renderer[] renderers, TrackSelectionArray trackSelectionArray) {
|
||||
Renderer[] renderers, ExoTrackSelection[] trackSelectionArray) {
|
||||
int targetBufferSize = 0;
|
||||
for (int i = 0; i < renderers.length; i++) {
|
||||
if (trackSelectionArray.get(i) != null) {
|
||||
if (trackSelectionArray[i] != null) {
|
||||
targetBufferSize += getDefaultBufferSize(renderers[i].getTrackType());
|
||||
}
|
||||
}
|
||||
|
@ -449,13 +449,18 @@ public interface ExoPlayer extends Player {
|
||||
}
|
||||
}
|
||||
|
||||
@Override
|
||||
/**
|
||||
* Returns the track selector that this player uses, or null if track selection is not supported.
|
||||
*/
|
||||
@Nullable
|
||||
TrackSelector getTrackSelector();
|
||||
|
||||
/** Returns the {@link Looper} associated with the playback thread. */
|
||||
Looper getPlaybackLooper();
|
||||
|
||||
/** Returns the {@link Clock} used for playback. */
|
||||
Clock getClock();
|
||||
|
||||
/** @deprecated Use {@link #prepare()} instead. */
|
||||
@Deprecated
|
||||
void retry();
|
||||
|
@ -34,13 +34,14 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
|
||||
import com.google.android.exoplayer2.source.MediaSourceFactory;
|
||||
import com.google.android.exoplayer2.source.ShuffleOrder;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
||||
import com.google.android.exoplayer2.upstream.BandwidthMeter;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.Clock;
|
||||
import com.google.android.exoplayer2.util.HandlerWrapper;
|
||||
import com.google.android.exoplayer2.util.ListenerSet;
|
||||
import com.google.android.exoplayer2.util.Log;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
@ -67,10 +68,9 @@ import java.util.List;
|
||||
|
||||
private final Renderer[] renderers;
|
||||
private final TrackSelector trackSelector;
|
||||
private final Handler playbackInfoUpdateHandler;
|
||||
private final HandlerWrapper playbackInfoUpdateHandler;
|
||||
private final ExoPlayerImplInternal.PlaybackInfoUpdateListener playbackInfoUpdateListener;
|
||||
private final ExoPlayerImplInternal internalPlayer;
|
||||
private final Handler internalPlayerHandler;
|
||||
private final ListenerSet<Player.EventListener, Player.Events> listeners;
|
||||
private final Timeline.Period period;
|
||||
private final List<MediaSourceHolderSnapshot> mediaSourceHolderSnapshots;
|
||||
@ -79,6 +79,7 @@ import java.util.List;
|
||||
@Nullable private final AnalyticsCollector analyticsCollector;
|
||||
private final Looper applicationLooper;
|
||||
private final BandwidthMeter bandwidthMeter;
|
||||
private final Clock clock;
|
||||
|
||||
@RepeatMode private int repeatMode;
|
||||
private boolean shuffleModeEnabled;
|
||||
@ -137,8 +138,15 @@ import java.util.List;
|
||||
Clock clock,
|
||||
Looper applicationLooper,
|
||||
@Nullable Player wrappingPlayer) {
|
||||
Log.i(TAG, "Init " + Integer.toHexString(System.identityHashCode(this)) + " ["
|
||||
+ ExoPlayerLibraryInfo.VERSION_SLASHY + "] [" + Util.DEVICE_DEBUG_INFO + "]");
|
||||
Log.i(
|
||||
TAG,
|
||||
"Init "
|
||||
+ Integer.toHexString(System.identityHashCode(this))
|
||||
+ " ["
|
||||
+ ExoPlayerLibraryInfo.VERSION_SLASHY
|
||||
+ "] ["
|
||||
+ Util.DEVICE_DEBUG_INFO
|
||||
+ "]");
|
||||
checkState(renderers.length > 0);
|
||||
this.renderers = checkNotNull(renderers);
|
||||
this.trackSelector = checkNotNull(trackSelector);
|
||||
@ -149,11 +157,13 @@ import java.util.List;
|
||||
this.seekParameters = seekParameters;
|
||||
this.pauseAtEndOfMediaItems = pauseAtEndOfMediaItems;
|
||||
this.applicationLooper = applicationLooper;
|
||||
this.clock = clock;
|
||||
repeatMode = Player.REPEAT_MODE_OFF;
|
||||
Player playerForListeners = wrappingPlayer != null ? wrappingPlayer : this;
|
||||
listeners =
|
||||
new ListenerSet<>(
|
||||
applicationLooper,
|
||||
clock,
|
||||
Player.Events::new,
|
||||
(listener, eventFlags) -> listener.onEvents(playerForListeners, eventFlags));
|
||||
mediaSourceHolderSnapshots = new ArrayList<>();
|
||||
@ -161,11 +171,11 @@ import java.util.List;
|
||||
emptyTrackSelectorResult =
|
||||
new TrackSelectorResult(
|
||||
new RendererConfiguration[renderers.length],
|
||||
new TrackSelection[renderers.length],
|
||||
new ExoTrackSelection[renderers.length],
|
||||
/* info= */ null);
|
||||
period = new Timeline.Period();
|
||||
maskingWindowIndex = C.INDEX_UNSET;
|
||||
playbackInfoUpdateHandler = new Handler(applicationLooper);
|
||||
playbackInfoUpdateHandler = clock.createHandler(applicationLooper, /* callback= */ null);
|
||||
playbackInfoUpdateListener =
|
||||
playbackInfoUpdate ->
|
||||
playbackInfoUpdateHandler.post(() -> handlePlaybackInfo(playbackInfoUpdate));
|
||||
@ -192,7 +202,6 @@ import java.util.List;
|
||||
applicationLooper,
|
||||
clock,
|
||||
playbackInfoUpdateListener);
|
||||
internalPlayerHandler = new Handler(internalPlayer.getPlaybackLooper());
|
||||
}
|
||||
|
||||
/**
|
||||
@ -259,6 +268,11 @@ import java.util.List;
|
||||
return applicationLooper;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Clock getClock() {
|
||||
return clock;
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(Player.EventListener listener) {
|
||||
listeners.add(listener);
|
||||
@ -724,9 +738,17 @@ import java.util.List;
|
||||
|
||||
@Override
|
||||
public void release() {
|
||||
Log.i(TAG, "Release " + Integer.toHexString(System.identityHashCode(this)) + " ["
|
||||
+ ExoPlayerLibraryInfo.VERSION_SLASHY + "] [" + Util.DEVICE_DEBUG_INFO + "] ["
|
||||
+ ExoPlayerLibraryInfo.registeredModules() + "]");
|
||||
Log.i(
|
||||
TAG,
|
||||
"Release "
|
||||
+ Integer.toHexString(System.identityHashCode(this))
|
||||
+ " ["
|
||||
+ ExoPlayerLibraryInfo.VERSION_SLASHY
|
||||
+ "] ["
|
||||
+ Util.DEVICE_DEBUG_INFO
|
||||
+ "] ["
|
||||
+ ExoPlayerLibraryInfo.registeredModules()
|
||||
+ "]");
|
||||
if (!internalPlayer.release()) {
|
||||
// One of the renderers timed out releasing its resources.
|
||||
listeners.sendEvent(
|
||||
@ -754,7 +776,8 @@ import java.util.List;
|
||||
target,
|
||||
playbackInfo.timeline,
|
||||
getCurrentWindowIndex(),
|
||||
internalPlayerHandler);
|
||||
clock,
|
||||
internalPlayer.getPlaybackLooper());
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -882,7 +905,7 @@ import java.util.List;
|
||||
|
||||
@Override
|
||||
public TrackSelectionArray getCurrentTrackSelections() {
|
||||
return playbackInfo.trackSelectorResult.selections;
|
||||
return new TrackSelectionArray(playbackInfo.trackSelectorResult.selections);
|
||||
}
|
||||
|
||||
@Override
|
||||
@ -1005,11 +1028,11 @@ import java.util.List;
|
||||
}
|
||||
if (previousPlaybackInfo.trackSelectorResult != newPlaybackInfo.trackSelectorResult) {
|
||||
trackSelector.onSelectionActivated(newPlaybackInfo.trackSelectorResult.info);
|
||||
TrackSelectionArray newSelection =
|
||||
new TrackSelectionArray(newPlaybackInfo.trackSelectorResult.selections);
|
||||
listeners.queueEvent(
|
||||
Player.EVENT_TRACKS_CHANGED,
|
||||
listener ->
|
||||
listener.onTracksChanged(
|
||||
newPlaybackInfo.trackGroups, newPlaybackInfo.trackSelectorResult.selections));
|
||||
listener -> listener.onTracksChanged(newPlaybackInfo.trackGroups, newSelection));
|
||||
}
|
||||
if (!previousPlaybackInfo.staticMetadata.equals(newPlaybackInfo.staticMetadata)) {
|
||||
listeners.queueEvent(
|
||||
|
@ -40,8 +40,7 @@ import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
|
||||
import com.google.android.exoplayer2.source.SampleStream;
|
||||
import com.google.android.exoplayer2.source.ShuffleOrder;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
||||
import com.google.android.exoplayer2.upstream.BandwidthMeter;
|
||||
@ -558,7 +557,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
if (e.isRecoverable && pendingRecoverableError == null) {
|
||||
Log.w(TAG, "Recoverable playback error", e);
|
||||
pendingRecoverableError = e;
|
||||
Message message = handler.obtainMessage(MSG_ATTEMPT_ERROR_RECOVERY, e);
|
||||
HandlerWrapper.Message message = handler.obtainMessage(MSG_ATTEMPT_ERROR_RECOVERY, e);
|
||||
// Given that the player is now in an unhandled exception state, the error needs to be
|
||||
// recovered or the player stopped before any other message is handled.
|
||||
message.getTarget().sendMessageAtFrontOfQueue(message);
|
||||
@ -625,6 +624,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
boolean wasInterrupted = false;
|
||||
while (!condition.get() && remainingMs > 0) {
|
||||
try {
|
||||
clock.onThreadBlocked();
|
||||
wait(remainingMs);
|
||||
} catch (InterruptedException e) {
|
||||
wasInterrupted = true;
|
||||
@ -726,8 +726,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
private void notifyTrackSelectionPlayWhenReadyChanged(boolean playWhenReady) {
|
||||
MediaPeriodHolder periodHolder = queue.getPlayingPeriod();
|
||||
while (periodHolder != null) {
|
||||
TrackSelection[] trackSelections = periodHolder.getTrackSelectorResult().selections.getAll();
|
||||
for (TrackSelection trackSelection : trackSelections) {
|
||||
for (ExoTrackSelection trackSelection : periodHolder.getTrackSelectorResult().selections) {
|
||||
if (trackSelection != null) {
|
||||
trackSelection.onPlayWhenReadyChanged(playWhenReady);
|
||||
}
|
||||
@ -901,8 +900,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
private void notifyTrackSelectionRebuffer() {
|
||||
MediaPeriodHolder periodHolder = queue.getPlayingPeriod();
|
||||
while (periodHolder != null) {
|
||||
TrackSelection[] trackSelections = periodHolder.getTrackSelectorResult().selections.getAll();
|
||||
for (TrackSelection trackSelection : trackSelections) {
|
||||
for (ExoTrackSelection trackSelection : periodHolder.getTrackSelectorResult().selections) {
|
||||
if (trackSelection != null) {
|
||||
trackSelection.onRebuffer();
|
||||
}
|
||||
@ -1468,7 +1466,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
}
|
||||
|
||||
private void sendMessageToTarget(PlayerMessage message) throws ExoPlaybackException {
|
||||
if (message.getHandler().getLooper() == playbackLooper) {
|
||||
if (message.getLooper() == playbackLooper) {
|
||||
deliverMessage(message);
|
||||
if (playbackInfo.playbackState == Player.STATE_READY
|
||||
|| playbackInfo.playbackState == Player.STATE_BUFFERING) {
|
||||
@ -1481,21 +1479,23 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
}
|
||||
|
||||
private void sendMessageToTargetThread(final PlayerMessage message) {
|
||||
Handler handler = message.getHandler();
|
||||
if (!handler.getLooper().getThread().isAlive()) {
|
||||
Looper looper = message.getLooper();
|
||||
if (!looper.getThread().isAlive()) {
|
||||
Log.w("TAG", "Trying to send message on a dead thread.");
|
||||
message.markAsProcessed(/* isDelivered= */ false);
|
||||
return;
|
||||
}
|
||||
handler.post(
|
||||
() -> {
|
||||
try {
|
||||
deliverMessage(message);
|
||||
} catch (ExoPlaybackException e) {
|
||||
Log.e(TAG, "Unexpected error delivering message on external thread.", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
clock
|
||||
.createHandler(looper, /* callback= */ null)
|
||||
.post(
|
||||
() -> {
|
||||
try {
|
||||
deliverMessage(message);
|
||||
} catch (ExoPlaybackException e) {
|
||||
Log.e(TAG, "Unexpected error delivering message on external thread.", e);
|
||||
throw new RuntimeException(e);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
private void deliverMessage(PlayerMessage message) throws ExoPlaybackException {
|
||||
@ -1690,8 +1690,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
private void updateTrackSelectionPlaybackSpeed(float playbackSpeed) {
|
||||
MediaPeriodHolder periodHolder = queue.getPlayingPeriod();
|
||||
while (periodHolder != null) {
|
||||
TrackSelection[] trackSelections = periodHolder.getTrackSelectorResult().selections.getAll();
|
||||
for (TrackSelection trackSelection : trackSelections) {
|
||||
for (ExoTrackSelection trackSelection : periodHolder.getTrackSelectorResult().selections) {
|
||||
if (trackSelection != null) {
|
||||
trackSelection.onPlaybackSpeed(playbackSpeed);
|
||||
}
|
||||
@ -1703,8 +1702,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
private void notifyTrackSelectionDiscontinuity() {
|
||||
MediaPeriodHolder periodHolder = queue.getPlayingPeriod();
|
||||
while (periodHolder != null) {
|
||||
TrackSelection[] trackSelections = periodHolder.getTrackSelectorResult().selections.getAll();
|
||||
for (TrackSelection trackSelection : trackSelections) {
|
||||
for (ExoTrackSelection trackSelection : periodHolder.getTrackSelectorResult().selections) {
|
||||
if (trackSelection != null) {
|
||||
trackSelection.onDiscontinuity();
|
||||
}
|
||||
@ -1732,8 +1730,13 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
? livePlaybackSpeedControl.getTargetLiveOffsetUs()
|
||||
: C.TIME_UNSET;
|
||||
MediaPeriodHolder loadingHolder = queue.getLoadingPeriod();
|
||||
boolean bufferedToEnd = loadingHolder.isFullyBuffered() && loadingHolder.info.isFinal;
|
||||
return bufferedToEnd
|
||||
boolean isBufferedToEnd = loadingHolder.isFullyBuffered() && loadingHolder.info.isFinal;
|
||||
// Ad loader implementations may only load ad media once playback has nearly reached the ad, but
|
||||
// it is possible for playback to be stuck buffering waiting for this. Therefore, we start
|
||||
// playback regardless of buffered duration if we are waiting for an ad media period to prepare.
|
||||
boolean isAdPendingPreparation = loadingHolder.info.id.isAd() && !loadingHolder.prepared;
|
||||
return isBufferedToEnd
|
||||
|| isAdPendingPreparation
|
||||
|| loadControl.shouldStartPlayback(
|
||||
getTotalBufferedDurationUs(),
|
||||
mediaClock.getPlaybackParameters().speed,
|
||||
@ -2016,7 +2019,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
}
|
||||
if (!renderer.isCurrentStreamFinal()) {
|
||||
// The renderer stream is not final, so we can replace the sample streams immediately.
|
||||
Format[] formats = getFormats(newTrackSelectorResult.selections.get(i));
|
||||
Format[] formats = getFormats(newTrackSelectorResult.selections[i]);
|
||||
renderer.replaceStream(
|
||||
formats,
|
||||
readingPeriodHolder.sampleStreams[i],
|
||||
@ -2266,11 +2269,10 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
}
|
||||
|
||||
private ImmutableList<Metadata> extractMetadataFromTrackSelectionArray(
|
||||
TrackSelectionArray trackSelectionArray) {
|
||||
ExoTrackSelection[] trackSelections) {
|
||||
ImmutableList.Builder<Metadata> result = new ImmutableList.Builder<>();
|
||||
boolean seenNonEmptyMetadata = false;
|
||||
for (int i = 0; i < trackSelectionArray.length; i++) {
|
||||
@Nullable TrackSelection trackSelection = trackSelectionArray.get(i);
|
||||
for (ExoTrackSelection trackSelection : trackSelections) {
|
||||
if (trackSelection != null) {
|
||||
Format format = trackSelection.getFormat(/* index= */ 0);
|
||||
if (format.metadata == null) {
|
||||
@ -2318,7 +2320,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
TrackSelectorResult trackSelectorResult = periodHolder.getTrackSelectorResult();
|
||||
RendererConfiguration rendererConfiguration =
|
||||
trackSelectorResult.rendererConfigurations[rendererIndex];
|
||||
TrackSelection newSelection = trackSelectorResult.selections.get(rendererIndex);
|
||||
ExoTrackSelection newSelection = trackSelectorResult.selections[rendererIndex];
|
||||
Format[] formats = getFormats(newSelection);
|
||||
// The renderer needs enabling with its new track selection.
|
||||
boolean playing = shouldPlayWhenReady() && playbackInfo.playbackState == Player.STATE_READY;
|
||||
@ -2792,7 +2794,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
||||
return newPeriodIndex == C.INDEX_UNSET ? null : newTimeline.getUidOfPeriod(newPeriodIndex);
|
||||
}
|
||||
|
||||
private static Format[] getFormats(TrackSelection newSelection) {
|
||||
private static Format[] getFormats(ExoTrackSelection newSelection) {
|
||||
// Build an array of formats contained by the selection.
|
||||
int length = newSelection != null ? newSelection.length() : 0;
|
||||
Format[] formats = new Format[length];
|
||||
|
@ -41,11 +41,9 @@ public final class ExoTimeoutException extends Exception {
|
||||
|
||||
/** The operation where this error occurred is not defined. */
|
||||
public static final int TIMEOUT_OPERATION_UNDEFINED = 0;
|
||||
// TODO(b/172315872) Change back @code to @link when the Player is in common.
|
||||
/** The error occurred in {@code Player#release}. */
|
||||
/** The error occurred in {@link Player#release}. */
|
||||
public static final int TIMEOUT_OPERATION_RELEASE = 1;
|
||||
/** The error occurred in {@code ExoPlayer#setForegroundMode}. */
|
||||
// TODO(b/172315872) Set foregroundMode is an ExoPlayer method, NOT a player one.
|
||||
/** The error occurred in {@link ExoPlayer#setForegroundMode}. */
|
||||
public static final int TIMEOUT_OPERATION_SET_FOREGROUND_MODE = 2;
|
||||
/** The error occurred while detaching a surface from the player. */
|
||||
public static final int TIMEOUT_OPERATION_DETACH_SURFACE = 3;
|
||||
|
@ -17,12 +17,10 @@ package com.google.android.exoplayer2;
|
||||
|
||||
import com.google.android.exoplayer2.source.TrackGroup;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
|
||||
/**
|
||||
* Controls buffering of media.
|
||||
*/
|
||||
/** Controls buffering of media. */
|
||||
public interface LoadControl {
|
||||
|
||||
/** Called by the player when prepared with a new source. */
|
||||
@ -35,33 +33,27 @@ public interface LoadControl {
|
||||
* @param trackGroups The {@link TrackGroup}s from which the selection was made.
|
||||
* @param trackSelections The track selections that were made.
|
||||
*/
|
||||
void onTracksSelected(Renderer[] renderers, TrackGroupArray trackGroups,
|
||||
TrackSelectionArray trackSelections);
|
||||
void onTracksSelected(
|
||||
Renderer[] renderers, TrackGroupArray trackGroups, ExoTrackSelection[] trackSelections);
|
||||
|
||||
/**
|
||||
* Called by the player when stopped.
|
||||
*/
|
||||
/** Called by the player when stopped. */
|
||||
void onStopped();
|
||||
|
||||
/**
|
||||
* Called by the player when released.
|
||||
*/
|
||||
/** Called by the player when released. */
|
||||
void onReleased();
|
||||
|
||||
/**
|
||||
* Returns the {@link Allocator} that should be used to obtain media buffer allocations.
|
||||
*/
|
||||
/** Returns the {@link Allocator} that should be used to obtain media buffer allocations. */
|
||||
Allocator getAllocator();
|
||||
|
||||
/**
|
||||
* Returns the duration of media to retain in the buffer prior to the current playback position,
|
||||
* for fast backward seeking.
|
||||
* <p>
|
||||
* Note: If {@link #retainBackBufferFromKeyframe()} is false then seeking in the back-buffer will
|
||||
* only be fast if the back-buffer contains a keyframe prior to the seek position.
|
||||
* <p>
|
||||
* Note: Implementations should return a single value. Dynamic changes to the back-buffer are not
|
||||
* currently supported.
|
||||
*
|
||||
* <p>Note: If {@link #retainBackBufferFromKeyframe()} is false then seeking in the back-buffer
|
||||
* will only be fast if the back-buffer contains a keyframe prior to the seek position.
|
||||
*
|
||||
* <p>Note: Implementations should return a single value. Dynamic changes to the back-buffer are
|
||||
* not currently supported.
|
||||
*
|
||||
* @return The duration of media to retain in the buffer prior to the current playback position,
|
||||
* in microseconds.
|
||||
@ -71,17 +63,19 @@ public interface LoadControl {
|
||||
/**
|
||||
* Returns whether media should be retained from the keyframe before the current playback position
|
||||
* minus {@link #getBackBufferDurationUs()}, rather than any sample before or at that position.
|
||||
* <p>
|
||||
* Warning: Returning true will cause the back-buffer size to depend on the spacing of keyframes
|
||||
* in the media being played. Returning true is not recommended unless you control the media and
|
||||
* are comfortable with the back-buffer size exceeding {@link #getBackBufferDurationUs()} by as
|
||||
* much as the maximum duration between adjacent keyframes in the media.
|
||||
* <p>
|
||||
* Note: Implementations should return a single value. Dynamic changes to the back-buffer are not
|
||||
* currently supported.
|
||||
*
|
||||
* <p>Warning: Returning true will cause the back-buffer size to depend on the spacing of
|
||||
* keyframes in the media being played. Returning true is not recommended unless you control the
|
||||
* media and are comfortable with the back-buffer size exceeding {@link
|
||||
* #getBackBufferDurationUs()} by as much as the maximum duration between adjacent keyframes in
|
||||
* the media.
|
||||
*
|
||||
* <p>Note: Implementations should return a single value. Dynamic changes to the back-buffer are
|
||||
* not currently supported.
|
||||
*
|
||||
* @return Whether media should be retained from the keyframe before the current playback position
|
||||
* minus {@link #getBackBufferDurationUs()}, rather than any sample before or at that position.
|
||||
* minus {@link #getBackBufferDurationUs()}, rather than any sample before or at that
|
||||
* position.
|
||||
*/
|
||||
boolean retainBackBufferFromKeyframe();
|
||||
|
||||
|
@ -24,8 +24,7 @@ import com.google.android.exoplayer2.source.MediaPeriod;
|
||||
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
|
||||
import com.google.android.exoplayer2.source.SampleStream;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectionArray;
|
||||
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelector;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
@ -233,7 +232,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||
throws ExoPlaybackException {
|
||||
TrackSelectorResult selectorResult =
|
||||
trackSelector.selectTracks(rendererCapabilities, getTrackGroups(), info.id, timeline);
|
||||
for (TrackSelection trackSelection : selectorResult.selections.getAll()) {
|
||||
for (ExoTrackSelection trackSelection : selectorResult.selections) {
|
||||
if (trackSelection != null) {
|
||||
trackSelection.onPlaybackSpeed(playbackSpeed);
|
||||
}
|
||||
@ -289,10 +288,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||
trackSelectorResult = newTrackSelectorResult;
|
||||
enableTrackSelectionsInResult();
|
||||
// Disable streams on the period and get new streams for updated/newly-enabled tracks.
|
||||
TrackSelectionArray trackSelections = newTrackSelectorResult.selections;
|
||||
positionUs =
|
||||
mediaPeriod.selectTracks(
|
||||
trackSelections.getAll(),
|
||||
newTrackSelectorResult.selections,
|
||||
mayRetainStreamFlags,
|
||||
sampleStreams,
|
||||
streamResetFlags,
|
||||
@ -309,7 +307,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||
hasEnabledTracks = true;
|
||||
}
|
||||
} else {
|
||||
Assertions.checkState(trackSelections.get(i) == null);
|
||||
Assertions.checkState(newTrackSelectorResult.selections[i] == null);
|
||||
}
|
||||
}
|
||||
return positionUs;
|
||||
@ -361,7 +359,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||
}
|
||||
for (int i = 0; i < trackSelectorResult.length; i++) {
|
||||
boolean rendererEnabled = trackSelectorResult.isRendererEnabled(i);
|
||||
TrackSelection trackSelection = trackSelectorResult.selections.get(i);
|
||||
ExoTrackSelection trackSelection = trackSelectorResult.selections[i];
|
||||
if (rendererEnabled && trackSelection != null) {
|
||||
trackSelection.enable();
|
||||
}
|
||||
@ -374,7 +372,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||
}
|
||||
for (int i = 0; i < trackSelectorResult.length; i++) {
|
||||
boolean rendererEnabled = trackSelectorResult.isRendererEnabled(i);
|
||||
TrackSelection trackSelection = trackSelectorResult.selections.get(i);
|
||||
ExoTrackSelection trackSelection = trackSelectorResult.selections[i];
|
||||
if (rendererEnabled && trackSelection != null) {
|
||||
trackSelection.disable();
|
||||
}
|
||||
|
@ -16,8 +16,8 @@
|
||||
package com.google.android.exoplayer2;
|
||||
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.VisibleForTesting;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.Clock;
|
||||
import java.util.concurrent.TimeoutException;
|
||||
@ -55,11 +55,12 @@ public final class PlayerMessage {
|
||||
|
||||
private final Target target;
|
||||
private final Sender sender;
|
||||
private final Clock clock;
|
||||
private final Timeline timeline;
|
||||
|
||||
private int type;
|
||||
@Nullable private Object payload;
|
||||
private Handler handler;
|
||||
private Looper looper;
|
||||
private int windowIndex;
|
||||
private long positionMs;
|
||||
private boolean deleteAfterDelivery;
|
||||
@ -77,7 +78,8 @@ public final class PlayerMessage {
|
||||
* set to {@link Timeline#EMPTY}, any position can be specified.
|
||||
* @param defaultWindowIndex The default window index in the {@code timeline} when no other window
|
||||
* index is specified.
|
||||
* @param defaultHandler The default handler to send the message on when no other handler is
|
||||
* @param clock The {@link Clock}.
|
||||
* @param defaultLooper The default {@link Looper} to send the message on when no other looper is
|
||||
* specified.
|
||||
*/
|
||||
public PlayerMessage(
|
||||
@ -85,11 +87,13 @@ public final class PlayerMessage {
|
||||
Target target,
|
||||
Timeline timeline,
|
||||
int defaultWindowIndex,
|
||||
Handler defaultHandler) {
|
||||
Clock clock,
|
||||
Looper defaultLooper) {
|
||||
this.sender = sender;
|
||||
this.target = target;
|
||||
this.timeline = timeline;
|
||||
this.handler = defaultHandler;
|
||||
this.looper = defaultLooper;
|
||||
this.clock = clock;
|
||||
this.windowIndex = defaultWindowIndex;
|
||||
this.positionMs = C.TIME_UNSET;
|
||||
this.deleteAfterDelivery = true;
|
||||
@ -142,22 +146,28 @@ public final class PlayerMessage {
|
||||
return payload;
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link #setLooper(Looper)} instead. */
|
||||
@Deprecated
|
||||
public PlayerMessage setHandler(Handler handler) {
|
||||
return setLooper(handler.getLooper());
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the handler the message is delivered on.
|
||||
* Sets the {@link Looper} the message is delivered on.
|
||||
*
|
||||
* @param handler A {@link Handler}.
|
||||
* @param looper A {@link Looper}.
|
||||
* @return This message.
|
||||
* @throws IllegalStateException If {@link #send()} has already been called.
|
||||
*/
|
||||
public PlayerMessage setHandler(Handler handler) {
|
||||
public PlayerMessage setLooper(Looper looper) {
|
||||
Assertions.checkState(!isSent);
|
||||
this.handler = handler;
|
||||
this.looper = looper;
|
||||
return this;
|
||||
}
|
||||
|
||||
/** Returns the handler the message is delivered on. */
|
||||
public Handler getHandler() {
|
||||
return handler;
|
||||
/** Returns the {@link Looper} the message is delivered on. */
|
||||
public Looper getLooper() {
|
||||
return looper;
|
||||
}
|
||||
|
||||
/**
|
||||
@ -287,19 +297,19 @@ public final class PlayerMessage {
|
||||
* Blocks until after the message has been delivered or the player is no longer able to deliver
|
||||
* the message.
|
||||
*
|
||||
* <p>Note that this method can't be called if the current thread is the same thread used by the
|
||||
* message handler set with {@link #setHandler(Handler)} as it would cause a deadlock.
|
||||
* <p>Note that this method must not be called if the current thread is the same thread used by
|
||||
* the message {@link #getLooper() looper} as it would cause a deadlock.
|
||||
*
|
||||
* @return Whether the message was delivered successfully.
|
||||
* @throws IllegalStateException If this method is called before {@link #send()}.
|
||||
* @throws IllegalStateException If this method is called on the same thread used by the message
|
||||
* handler set with {@link #setHandler(Handler)}.
|
||||
* {@link #getLooper() looper}.
|
||||
* @throws InterruptedException If the current thread is interrupted while waiting for the message
|
||||
* to be delivered.
|
||||
*/
|
||||
public synchronized boolean blockUntilDelivered() throws InterruptedException {
|
||||
Assertions.checkState(isSent);
|
||||
Assertions.checkState(handler.getLooper().getThread() != Thread.currentThread());
|
||||
Assertions.checkState(looper.getThread() != Thread.currentThread());
|
||||
while (!isProcessed) {
|
||||
wait();
|
||||
}
|
||||
@ -310,14 +320,14 @@ public final class PlayerMessage {
|
||||
* Blocks until after the message has been delivered or the player is no longer able to deliver
|
||||
* the message or the specified timeout elapsed.
|
||||
*
|
||||
* <p>Note that this method can't be called if the current thread is the same thread used by the
|
||||
* message handler set with {@link #setHandler(Handler)} as it would cause a deadlock.
|
||||
* <p>Note that this method must not be called if the current thread is the same thread used by
|
||||
* the message {@link #getLooper() looper} as it would cause a deadlock.
|
||||
*
|
||||
* @param timeoutMs The timeout in milliseconds.
|
||||
* @return Whether the message was delivered successfully.
|
||||
* @throws IllegalStateException If this method is called before {@link #send()}.
|
||||
* @throws IllegalStateException If this method is called on the same thread used by the message
|
||||
* handler set with {@link #setHandler(Handler)}.
|
||||
* {@link #getLooper() looper}.
|
||||
* @throws TimeoutException If the {@code timeoutMs} elapsed and this message has not been
|
||||
* delivered and the player is still able to deliver the message.
|
||||
* @throws InterruptedException If the current thread is interrupted while waiting for the message
|
||||
@ -325,26 +335,19 @@ public final class PlayerMessage {
|
||||
*/
|
||||
public synchronized boolean blockUntilDelivered(long timeoutMs)
|
||||
throws InterruptedException, TimeoutException {
|
||||
return blockUntilDelivered(timeoutMs, Clock.DEFAULT);
|
||||
}
|
||||
|
||||
@VisibleForTesting()
|
||||
/* package */ synchronized boolean blockUntilDelivered(long timeoutMs, Clock clock)
|
||||
throws InterruptedException, TimeoutException {
|
||||
Assertions.checkState(isSent);
|
||||
Assertions.checkState(handler.getLooper().getThread() != Thread.currentThread());
|
||||
Assertions.checkState(looper.getThread() != Thread.currentThread());
|
||||
|
||||
long deadlineMs = clock.elapsedRealtime() + timeoutMs;
|
||||
long remainingMs = timeoutMs;
|
||||
while (!isProcessed && remainingMs > 0) {
|
||||
clock.onThreadBlocked();
|
||||
wait(remainingMs);
|
||||
remainingMs = deadlineMs - clock.elapsedRealtime();
|
||||
}
|
||||
|
||||
if (!isProcessed) {
|
||||
throw new TimeoutException("Message delivery timed out.");
|
||||
}
|
||||
|
||||
return isDelivered;
|
||||
}
|
||||
}
|
||||
|
@ -1202,6 +1202,11 @@ public class SimpleExoPlayer extends BasePlayer
|
||||
return player.getApplicationLooper();
|
||||
}
|
||||
|
||||
@Override
|
||||
public Clock getClock() {
|
||||
return player.getClock();
|
||||
}
|
||||
|
||||
@Override
|
||||
public void addListener(Player.EventListener listener) {
|
||||
// Don't verify application thread. We allow calls to this method from any thread.
|
||||
|
@ -91,6 +91,7 @@ public class AnalyticsCollector
|
||||
listeners =
|
||||
new ListenerSet<>(
|
||||
Util.getCurrentOrMainLooper(),
|
||||
clock,
|
||||
AnalyticsListener.Events::new,
|
||||
(listener, eventFlags) -> {});
|
||||
period = new Period();
|
||||
|
@ -1457,28 +1457,69 @@ public final class DefaultAudioSink implements AudioSink {
|
||||
if (!supportedEncoding) {
|
||||
return null;
|
||||
}
|
||||
|
||||
// E-AC3 JOC is object based, so any channel count specified in the format is arbitrary. Use 6,
|
||||
// since the E-AC3 compatible part of the stream is 5.1.
|
||||
int channelCount = encoding == C.ENCODING_E_AC3_JOC ? 6 : format.channelCount;
|
||||
if (channelCount > audioCapabilities.getMaxChannelCount()) {
|
||||
if (encoding == C.ENCODING_E_AC3_JOC
|
||||
&& !audioCapabilities.supportsEncoding(C.ENCODING_E_AC3_JOC)) {
|
||||
// E-AC3 receivers support E-AC3 JOC streams (but decode only the base layer).
|
||||
encoding = C.ENCODING_E_AC3;
|
||||
}
|
||||
if (!audioCapabilities.supportsEncoding(encoding)) {
|
||||
return null;
|
||||
}
|
||||
|
||||
int channelCount;
|
||||
if (encoding == C.ENCODING_E_AC3_JOC) {
|
||||
// E-AC3 JOC is object based so the format channel count is arbitrary. From API 29 we can get
|
||||
// the channel count for this encoding, but before then there is no way to query it so we
|
||||
// assume 6 channel audio is supported.
|
||||
if (Util.SDK_INT >= 29) {
|
||||
channelCount =
|
||||
getMaxSupportedChannelCountForPassthroughV29(C.ENCODING_E_AC3_JOC, format.sampleRate);
|
||||
if (channelCount == 0) {
|
||||
Log.w(TAG, "E-AC3 JOC encoding supported but no channel count supported");
|
||||
return null;
|
||||
}
|
||||
} else {
|
||||
channelCount = 6;
|
||||
}
|
||||
} else {
|
||||
channelCount = format.channelCount;
|
||||
if (channelCount > audioCapabilities.getMaxChannelCount()) {
|
||||
return null;
|
||||
}
|
||||
}
|
||||
int channelConfig = getChannelConfigForPassthrough(channelCount);
|
||||
if (channelConfig == AudioFormat.CHANNEL_INVALID) {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (audioCapabilities.supportsEncoding(encoding)) {
|
||||
return Pair.create(encoding, channelConfig);
|
||||
} else if (encoding == C.ENCODING_E_AC3_JOC
|
||||
&& audioCapabilities.supportsEncoding(C.ENCODING_E_AC3)) {
|
||||
// E-AC3 receivers support E-AC3 JOC streams (but decode in 2-D rather than 3-D).
|
||||
return Pair.create(C.ENCODING_E_AC3, channelConfig);
|
||||
}
|
||||
return Pair.create(encoding, channelConfig);
|
||||
}
|
||||
|
||||
return null;
|
||||
/**
|
||||
* Returns the maximum number of channels supported for passthrough playback of audio in the given
|
||||
* format, or 0 if the format is unsupported.
|
||||
*/
|
||||
@RequiresApi(29)
|
||||
private static int getMaxSupportedChannelCountForPassthroughV29(
|
||||
@C.Encoding int encoding, int sampleRate) {
|
||||
android.media.AudioAttributes audioAttributes =
|
||||
new android.media.AudioAttributes.Builder()
|
||||
.setUsage(android.media.AudioAttributes.USAGE_MEDIA)
|
||||
.setContentType(android.media.AudioAttributes.CONTENT_TYPE_MOVIE)
|
||||
.build();
|
||||
// TODO(internal b/25994457): Query supported channel masks directly once it's supported.
|
||||
for (int channelCount = 8; channelCount > 0; channelCount--) {
|
||||
AudioFormat audioFormat =
|
||||
new AudioFormat.Builder()
|
||||
.setEncoding(encoding)
|
||||
.setSampleRate(sampleRate)
|
||||
.setChannelMask(Util.getAudioTrackChannelConfig(channelCount))
|
||||
.build();
|
||||
if (AudioTrack.isDirectPlaybackSupported(audioFormat, audioAttributes)) {
|
||||
return channelCount;
|
||||
}
|
||||
}
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static int getChannelConfigForPassthrough(int channelCount) {
|
||||
|
@ -26,6 +26,7 @@ import android.os.Looper;
|
||||
import android.os.Message;
|
||||
import android.os.SystemClock;
|
||||
import android.util.Pair;
|
||||
import androidx.annotation.GuardedBy;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import com.google.android.exoplayer2.C;
|
||||
@ -308,7 +309,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
// Assigning null to various non-null variables for clean-up.
|
||||
state = STATE_RELEASED;
|
||||
Util.castNonNull(responseHandler).removeCallbacksAndMessages(null);
|
||||
Util.castNonNull(requestHandler).removeCallbacksAndMessages(null);
|
||||
Util.castNonNull(requestHandler).release();
|
||||
requestHandler = null;
|
||||
Util.castNonNull(requestHandlerThread).quit();
|
||||
requestHandlerThread = null;
|
||||
@ -570,6 +571,9 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
@SuppressLint("HandlerLeak")
|
||||
private class RequestHandler extends Handler {
|
||||
|
||||
@GuardedBy("this")
|
||||
private boolean isReleased;
|
||||
|
||||
public RequestHandler(Looper backgroundLooper) {
|
||||
super(backgroundLooper);
|
||||
}
|
||||
@ -610,9 +614,13 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
response = e;
|
||||
}
|
||||
loadErrorHandlingPolicy.onLoadTaskConcluded(requestTask.taskId);
|
||||
responseHandler
|
||||
.obtainMessage(msg.what, Pair.create(requestTask.request, response))
|
||||
.sendToTarget();
|
||||
synchronized (this) {
|
||||
if (!isReleased) {
|
||||
responseHandler
|
||||
.obtainMessage(msg.what, Pair.create(requestTask.request, response))
|
||||
.sendToTarget();
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private boolean maybeRetryRequest(Message originalMsg, MediaDrmCallbackException exception) {
|
||||
@ -647,8 +655,18 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||
// The error is fatal.
|
||||
return false;
|
||||
}
|
||||
sendMessageDelayed(Message.obtain(originalMsg), retryDelayMs);
|
||||
return true;
|
||||
synchronized (this) {
|
||||
if (!isReleased) {
|
||||
sendMessageDelayed(Message.obtain(originalMsg), retryDelayMs);
|
||||
return true;
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
|
||||
public synchronized void release() {
|
||||
removeCallbacksAndMessages(/* token= */ null);
|
||||
isReleased = true;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -36,6 +36,7 @@ import com.google.android.exoplayer2.util.Log;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import com.google.common.collect.ImmutableSet;
|
||||
import com.google.common.collect.Sets;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
@ -632,12 +633,12 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
||||
// ResourceBusyException is only available at API 19, so on earlier versions we always
|
||||
// eagerly release regardless of the underlying error.
|
||||
if (!keepaliveSessions.isEmpty()) {
|
||||
// Make a local copy, because sessions are removed from this.timingOutSessions during
|
||||
// Make a local copy, because sessions are removed from this.keepaliveSessions during
|
||||
// release (via callback).
|
||||
ImmutableList<DefaultDrmSession> timingOutSessions =
|
||||
ImmutableList.copyOf(this.keepaliveSessions);
|
||||
for (DrmSession timingOutSession : timingOutSessions) {
|
||||
timingOutSession.release(/* eventDispatcher= */ null);
|
||||
ImmutableSet<DefaultDrmSession> keepaliveSessions =
|
||||
ImmutableSet.copyOf(this.keepaliveSessions);
|
||||
for (DrmSession keepaliveSession : keepaliveSessions) {
|
||||
keepaliveSession.release(/* eventDispatcher= */ null);
|
||||
}
|
||||
// Undo the acquisitions from createAndAcquireSession().
|
||||
session.release(eventDispatcher);
|
||||
|
@ -13,16 +13,12 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.source;
|
||||
package com.google.android.exoplayer2.drm;
|
||||
|
||||
import static com.google.android.exoplayer2.drm.DefaultDrmSessionManager.MODE_PLAYBACK;
|
||||
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.MediaItem;
|
||||
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
|
||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||
import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
|
||||
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
|
||||
import com.google.android.exoplayer2.upstream.DefaultHttpDataSource;
|
||||
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
|
||||
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
||||
@ -31,8 +27,8 @@ import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.common.primitives.Ints;
|
||||
import java.util.Map;
|
||||
|
||||
/** A helper to create a {@link DrmSessionManager} from a {@link MediaItem}. */
|
||||
public final class MediaSourceDrmHelper {
|
||||
/** Default implementation of {@link DrmSessionManagerProvider}. */
|
||||
public final class DefaultDrmSessionManagerProvider implements DrmSessionManagerProvider {
|
||||
|
||||
@Nullable private HttpDataSource.Factory drmHttpDataSourceFactory;
|
||||
@Nullable private String userAgent;
|
||||
@ -62,13 +58,13 @@ public final class MediaSourceDrmHelper {
|
||||
this.userAgent = userAgent;
|
||||
}
|
||||
|
||||
/** Creates a {@link DrmSessionManager} for the given media item. */
|
||||
public DrmSessionManager create(MediaItem mediaItem) {
|
||||
@Override
|
||||
public DrmSessionManager get(MediaItem mediaItem) {
|
||||
Assertions.checkNotNull(mediaItem.playbackProperties);
|
||||
@Nullable
|
||||
MediaItem.DrmConfiguration drmConfiguration = mediaItem.playbackProperties.drmConfiguration;
|
||||
if (drmConfiguration == null || Util.SDK_INT < 18) {
|
||||
return DrmSessionManager.getDummyDrmSessionManager();
|
||||
return DrmSessionManager.DRM_UNSUPPORTED;
|
||||
}
|
||||
HttpDataSource.Factory dataSourceFactory =
|
||||
drmHttpDataSourceFactory != null
|
@ -22,13 +22,8 @@ import com.google.android.exoplayer2.Format;
|
||||
/** Manages a DRM session. */
|
||||
public interface DrmSessionManager {
|
||||
|
||||
/** Returns {@link #DUMMY}. */
|
||||
static DrmSessionManager getDummyDrmSessionManager() {
|
||||
return DUMMY;
|
||||
}
|
||||
|
||||
/** {@link DrmSessionManager} that supports no DRM schemes. */
|
||||
DrmSessionManager DUMMY =
|
||||
/** An instance that supports no DRM schemes. */
|
||||
DrmSessionManager DRM_UNSUPPORTED =
|
||||
new DrmSessionManager() {
|
||||
|
||||
@Override
|
||||
@ -54,6 +49,23 @@ public interface DrmSessionManager {
|
||||
}
|
||||
};
|
||||
|
||||
/**
|
||||
* An instance that supports no DRM schemes.
|
||||
*
|
||||
* @deprecated Use {@link #DRM_UNSUPPORTED}.
|
||||
*/
|
||||
@Deprecated DrmSessionManager DUMMY = DRM_UNSUPPORTED;
|
||||
|
||||
/**
|
||||
* Returns {@link #DRM_UNSUPPORTED}.
|
||||
*
|
||||
* @deprecated Use {@link #DRM_UNSUPPORTED}.
|
||||
*/
|
||||
@Deprecated
|
||||
static DrmSessionManager getDummyDrmSessionManager() {
|
||||
return DRM_UNSUPPORTED;
|
||||
}
|
||||
|
||||
/**
|
||||
* Acquires any required resources.
|
||||
*
|
||||
|
@ -1,5 +1,5 @@
|
||||
/*
|
||||
* Copyright (C) 2020 The Android Open Source Project
|
||||
* Copyright 2021 The Android Open Source Project
|
||||
*
|
||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||
* you may not use this file except in compliance with the License.
|
||||
@ -13,14 +13,16 @@
|
||||
* See the License for the specific language governing permissions and
|
||||
* limitations under the License.
|
||||
*/
|
||||
package com.google.android.exoplayer2.trackselection;
|
||||
package com.google.android.exoplayer2.drm;
|
||||
|
||||
import com.google.android.exoplayer2.MediaItem;
|
||||
|
||||
// TODO(b/172315872) Replace @code by @link when Player has been migrated to common
|
||||
/**
|
||||
* The component of a {@code Player} responsible for selecting tracks to be played.
|
||||
*
|
||||
* <p>No Player agnostic track selection is currently supported. Clients should downcast to the
|
||||
* implementation's track selection.
|
||||
* A provider to obtain a {@link DrmSessionManager} suitable for playing the content described by a
|
||||
* {@link MediaItem}.
|
||||
*/
|
||||
// TODO(b/172315872) Define an interface for track selection.
|
||||
public interface TrackSelectorInterface {}
|
||||
public interface DrmSessionManagerProvider {
|
||||
|
||||
/** Returns a {@link DrmSessionManager} for the given media item. */
|
||||
DrmSessionManager get(MediaItem mediaItem);
|
||||
}
|
@ -30,7 +30,7 @@ import java.nio.ByteBuffer;
|
||||
/* package */ final class C2Mp3TimestampTracker {
|
||||
|
||||
// Mirroring the actual codec, as can be found at
|
||||
// https://cs.android.com/android/platform/superproject/+/master:frameworks/av/media/codec2/components/mp3/C2SoftMp3Dec.h;l=55;drc=3665390c9d32a917398b240c5a46ced07a3b65eb
|
||||
// https://cs.android.com/android/platform/superproject/+/main:frameworks/av/media/codec2/components/mp3/C2SoftMp3Dec.h;l=55;drc=3665390c9d32a917398b240c5a46ced07a3b65eb
|
||||
private static final long DECODER_DELAY_SAMPLES = 529;
|
||||
private static final String TAG = "C2Mp3TimestampTracker";
|
||||
|
||||
@ -76,7 +76,7 @@ import java.nio.ByteBuffer;
|
||||
}
|
||||
|
||||
// These calculations mirror the timestamp calculations in the Codec2 Mp3 Decoder.
|
||||
// https://cs.android.com/android/platform/superproject/+/master:frameworks/av/media/codec2/components/mp3/C2SoftMp3Dec.cpp;l=464;drc=ed134640332fea70ca4b05694289d91a5265bb46
|
||||
// https://cs.android.com/android/platform/superproject/+/main:frameworks/av/media/codec2/components/mp3/C2SoftMp3Dec.cpp;l=464;drc=ed134640332fea70ca4b05694289d91a5265bb46
|
||||
if (processedSamples == 0) {
|
||||
anchorTimestampUs = buffer.timeUs;
|
||||
processedSamples = frameCount - DECODER_DELAY_SAMPLES;
|
||||
|
@ -298,8 +298,16 @@ public final class MediaCodecInfo {
|
||||
// which may not be widely supported. See https://github.com/google/ExoPlayer/issues/5145.
|
||||
return true;
|
||||
}
|
||||
for (CodecProfileLevel capabilities : getProfileLevels()) {
|
||||
if (capabilities.profile == profile && capabilities.level >= level) {
|
||||
|
||||
CodecProfileLevel[] profileLevels = getProfileLevels();
|
||||
if (Util.SDK_INT <= 23 && MimeTypes.VIDEO_VP9.equals(mimeType) && profileLevels.length == 0) {
|
||||
// Some older devices don't report profile levels for VP9. Estimate them using other data in
|
||||
// the codec capabilities.
|
||||
profileLevels = estimateLegacyVp9ProfileLevels(capabilities);
|
||||
}
|
||||
|
||||
for (CodecProfileLevel profileLevel : profileLevels) {
|
||||
if (profileLevel.profile == profile && profileLevel.level >= level) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
@ -334,8 +342,8 @@ public final class MediaCodecInfo {
|
||||
if (isVideo) {
|
||||
return adaptive;
|
||||
} else {
|
||||
Pair<Integer, Integer> codecProfileLevel = MediaCodecUtil.getCodecProfileAndLevel(format);
|
||||
return codecProfileLevel != null && codecProfileLevel.first == CodecProfileLevel.AACObjectXHE;
|
||||
Pair<Integer, Integer> profileLevel = MediaCodecUtil.getCodecProfileAndLevel(format);
|
||||
return profileLevel != null && profileLevel.first == CodecProfileLevel.AACObjectXHE;
|
||||
}
|
||||
}
|
||||
|
||||
@ -678,6 +686,60 @@ public final class MediaCodecInfo {
|
||||
return capabilities.getMaxSupportedInstances();
|
||||
}
|
||||
|
||||
/**
|
||||
* Called on devices with {@link Util#SDK_INT} 23 and below, for VP9 decoders whose {@link
|
||||
* CodecCapabilities} do not correctly report profile levels. The returned {@link
|
||||
* CodecProfileLevel CodecProfileLevels} are estimated based on other data in the {@link
|
||||
* CodecCapabilities}.
|
||||
*
|
||||
* @param capabilities The {@link CodecCapabilities} for a VP9 decoder, or {@code null} if not
|
||||
* known.
|
||||
* @return The estimated {@link CodecProfileLevel CodecProfileLevels} for the decoder.
|
||||
*/
|
||||
private static CodecProfileLevel[] estimateLegacyVp9ProfileLevels(
|
||||
@Nullable CodecCapabilities capabilities) {
|
||||
int maxBitrate = 0;
|
||||
if (capabilities != null) {
|
||||
@Nullable VideoCapabilities videoCapabilities = capabilities.getVideoCapabilities();
|
||||
if (videoCapabilities != null) {
|
||||
maxBitrate = videoCapabilities.getBitrateRange().getUpper();
|
||||
}
|
||||
}
|
||||
|
||||
// Values taken from https://www.webmproject.org/vp9/levels.
|
||||
int level;
|
||||
if (maxBitrate >= 180_000_000) {
|
||||
level = CodecProfileLevel.VP9Level52;
|
||||
} else if (maxBitrate >= 120_000_000) {
|
||||
level = CodecProfileLevel.VP9Level51;
|
||||
} else if (maxBitrate >= 60_000_000) {
|
||||
level = CodecProfileLevel.VP9Level5;
|
||||
} else if (maxBitrate >= 30_000_000) {
|
||||
level = CodecProfileLevel.VP9Level41;
|
||||
} else if (maxBitrate >= 18_000_000) {
|
||||
level = CodecProfileLevel.VP9Level4;
|
||||
} else if (maxBitrate >= 12_000_000) {
|
||||
level = CodecProfileLevel.VP9Level31;
|
||||
} else if (maxBitrate >= 7_200_000) {
|
||||
level = CodecProfileLevel.VP9Level3;
|
||||
} else if (maxBitrate >= 3_600_000) {
|
||||
level = CodecProfileLevel.VP9Level21;
|
||||
} else if (maxBitrate >= 1_800_000) {
|
||||
level = CodecProfileLevel.VP9Level2;
|
||||
} else if (maxBitrate >= 800_000) {
|
||||
level = CodecProfileLevel.VP9Level11;
|
||||
} else { // Assume level 1 is always supported.
|
||||
level = CodecProfileLevel.VP9Level1;
|
||||
}
|
||||
|
||||
CodecProfileLevel profileLevel = new CodecProfileLevel();
|
||||
// Since this method is for legacy devices only, assume that only profile 0 is supported.
|
||||
profileLevel.profile = CodecProfileLevel.VP9Profile0;
|
||||
profileLevel.level = level;
|
||||
|
||||
return new CodecProfileLevel[] {profileLevel};
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether the decoder is known to fail when adapting, despite advertising itself as an
|
||||
* adaptive decoder.
|
||||
|
@ -2206,8 +2206,10 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||
if (bypassBatchBuffer.hasSamples()) {
|
||||
bypassBatchBuffer.flip();
|
||||
}
|
||||
// We can make more progress if we have batched data or the EOS to process.
|
||||
return bypassBatchBuffer.hasSamples() || inputStreamEnded;
|
||||
|
||||
// We can make more progress if we have batched data, an EOS, or a re-initialization to process
|
||||
// (note that one or more of the code blocks above will be executed during the next call).
|
||||
return bypassBatchBuffer.hasSamples() || inputStreamEnded || bypassDrainAndReinitialize;
|
||||
}
|
||||
|
||||
private void bypassRead() throws ExoPlaybackException {
|
||||
@ -2221,7 +2223,7 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
||||
switch (result) {
|
||||
case C.RESULT_FORMAT_READ:
|
||||
onInputFormatChanged(formatHolder);
|
||||
break;
|
||||
return;
|
||||
case C.RESULT_NOTHING_READ:
|
||||
return;
|
||||
case C.RESULT_BUFFER_READ:
|
||||
|
@ -48,8 +48,8 @@ import com.google.android.exoplayer2.trackselection.BaseTrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.Parameters;
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride;
|
||||
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.MappingTrackSelector.MappedTrackInfo;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
import com.google.android.exoplayer2.upstream.BandwidthMeter;
|
||||
@ -465,8 +465,9 @@ public final class DownloadHelper {
|
||||
private @MonotonicNonNull MediaPreparer mediaPreparer;
|
||||
private TrackGroupArray @MonotonicNonNull [] trackGroupArrays;
|
||||
private MappedTrackInfo @MonotonicNonNull [] mappedTrackInfos;
|
||||
private List<TrackSelection> @MonotonicNonNull [][] trackSelectionsByPeriodAndRenderer;
|
||||
private List<TrackSelection> @MonotonicNonNull [][] immutableTrackSelectionsByPeriodAndRenderer;
|
||||
private List<ExoTrackSelection> @MonotonicNonNull [][] trackSelectionsByPeriodAndRenderer;
|
||||
private List<ExoTrackSelection> @MonotonicNonNull [][]
|
||||
immutableTrackSelectionsByPeriodAndRenderer;
|
||||
|
||||
/**
|
||||
* Creates download helper.
|
||||
@ -573,14 +574,14 @@ public final class DownloadHelper {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns all {@link TrackSelection track selections} for a period and renderer. Must not be
|
||||
* Returns all {@link ExoTrackSelection track selections} for a period and renderer. Must not be
|
||||
* called until after preparation completes.
|
||||
*
|
||||
* @param periodIndex The period index.
|
||||
* @param rendererIndex The renderer index.
|
||||
* @return A list of selected {@link TrackSelection track selections}.
|
||||
* @return A list of selected {@link ExoTrackSelection track selections}.
|
||||
*/
|
||||
public List<TrackSelection> getTrackSelections(int periodIndex, int rendererIndex) {
|
||||
public List<ExoTrackSelection> getTrackSelections(int periodIndex, int rendererIndex) {
|
||||
assertPreparedWithMedia();
|
||||
return immutableTrackSelectionsByPeriodAndRenderer[periodIndex][rendererIndex];
|
||||
}
|
||||
@ -751,7 +752,7 @@ public final class DownloadHelper {
|
||||
}
|
||||
assertPreparedWithMedia();
|
||||
List<StreamKey> streamKeys = new ArrayList<>();
|
||||
List<TrackSelection> allSelections = new ArrayList<>();
|
||||
List<ExoTrackSelection> allSelections = new ArrayList<>();
|
||||
int periodCount = trackSelectionsByPeriodAndRenderer.length;
|
||||
for (int periodIndex = 0; periodIndex < periodCount; periodIndex++) {
|
||||
allSelections.clear();
|
||||
@ -773,9 +774,9 @@ public final class DownloadHelper {
|
||||
int periodCount = mediaPreparer.mediaPeriods.length;
|
||||
int rendererCount = rendererCapabilities.length;
|
||||
trackSelectionsByPeriodAndRenderer =
|
||||
(List<TrackSelection>[][]) new List<?>[periodCount][rendererCount];
|
||||
(List<ExoTrackSelection>[][]) new List<?>[periodCount][rendererCount];
|
||||
immutableTrackSelectionsByPeriodAndRenderer =
|
||||
(List<TrackSelection>[][]) new List<?>[periodCount][rendererCount];
|
||||
(List<ExoTrackSelection>[][]) new List<?>[periodCount][rendererCount];
|
||||
for (int i = 0; i < periodCount; i++) {
|
||||
for (int j = 0; j < rendererCount; j++) {
|
||||
trackSelectionsByPeriodAndRenderer[i][j] = new ArrayList<>();
|
||||
@ -847,15 +848,15 @@ public final class DownloadHelper {
|
||||
new MediaPeriodId(mediaPreparer.timeline.getUidOfPeriod(periodIndex)),
|
||||
mediaPreparer.timeline);
|
||||
for (int i = 0; i < trackSelectorResult.length; i++) {
|
||||
@Nullable TrackSelection newSelection = trackSelectorResult.selections.get(i);
|
||||
@Nullable ExoTrackSelection newSelection = trackSelectorResult.selections[i];
|
||||
if (newSelection == null) {
|
||||
continue;
|
||||
}
|
||||
List<TrackSelection> existingSelectionList =
|
||||
List<ExoTrackSelection> existingSelectionList =
|
||||
trackSelectionsByPeriodAndRenderer[periodIndex][i];
|
||||
boolean mergedWithExistingSelection = false;
|
||||
for (int j = 0; j < existingSelectionList.size(); j++) {
|
||||
TrackSelection existingSelection = existingSelectionList.get(j);
|
||||
ExoTrackSelection existingSelection = existingSelectionList.get(j);
|
||||
if (existingSelection.getTrackGroup() == newSelection.getTrackGroup()) {
|
||||
// Merge with existing selection.
|
||||
scratchSet.clear();
|
||||
@ -1066,15 +1067,15 @@ public final class DownloadHelper {
|
||||
|
||||
private static final class DownloadTrackSelection extends BaseTrackSelection {
|
||||
|
||||
private static final class Factory implements TrackSelection.Factory {
|
||||
private static final class Factory implements ExoTrackSelection.Factory {
|
||||
|
||||
@Override
|
||||
public @NullableType TrackSelection[] createTrackSelections(
|
||||
public @NullableType ExoTrackSelection[] createTrackSelections(
|
||||
@NullableType Definition[] definitions,
|
||||
BandwidthMeter bandwidthMeter,
|
||||
MediaPeriodId mediaPeriodId,
|
||||
Timeline timeline) {
|
||||
@NullableType TrackSelection[] selections = new TrackSelection[definitions.length];
|
||||
@NullableType ExoTrackSelection[] selections = new ExoTrackSelection[definitions.length];
|
||||
for (int i = 0; i < definitions.length; i++) {
|
||||
selections[i] =
|
||||
definitions[i] == null
|
||||
|
@ -21,7 +21,7 @@ import com.google.android.exoplayer2.Format;
|
||||
import com.google.android.exoplayer2.FormatHolder;
|
||||
import com.google.android.exoplayer2.SeekParameters;
|
||||
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
@ -34,9 +34,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||
*/
|
||||
public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callback {
|
||||
|
||||
/**
|
||||
* The {@link MediaPeriod} wrapped by this clipping media period.
|
||||
*/
|
||||
/** The {@link MediaPeriod} wrapped by this clipping media period. */
|
||||
public final MediaPeriod mediaPeriod;
|
||||
|
||||
@Nullable private MediaPeriod.Callback callback;
|
||||
@ -98,7 +96,7 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
|
||||
|
||||
@Override
|
||||
public long selectTracks(
|
||||
@NullableType TrackSelection[] selections,
|
||||
@NullableType ExoTrackSelection[] selections,
|
||||
boolean[] mayRetainStreamFlags,
|
||||
@NullableType SampleStream[] streams,
|
||||
boolean[] streamResetFlags,
|
||||
@ -250,7 +248,7 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
|
||||
}
|
||||
|
||||
private static boolean shouldKeepInitialDiscontinuity(
|
||||
long startUs, @NullableType TrackSelection[] selections) {
|
||||
long startUs, @NullableType ExoTrackSelection[] selections) {
|
||||
// If the clipping start position is non-zero, the clipping sample streams will adjust
|
||||
// timestamps on buffers they read from the unclipped sample streams. These adjusted buffer
|
||||
// timestamps can be negative, because sample streams provide buffers starting at a key-frame,
|
||||
@ -261,7 +259,7 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
|
||||
// However, for tracks where all samples are sync samples, we assume they have random access
|
||||
// seek behaviour and do not need an initial discontinuity to reset the renderer.
|
||||
if (startUs != 0) {
|
||||
for (TrackSelection trackSelection : selections) {
|
||||
for (ExoTrackSelection trackSelection : selections) {
|
||||
if (trackSelection != null) {
|
||||
Format selectedFormat = trackSelection.getSelectedFormat();
|
||||
if (!MimeTypes.allSamplesAreSyncSamples(
|
||||
@ -274,9 +272,7 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Wraps a {@link SampleStream} and clips its samples.
|
||||
*/
|
||||
/** Wraps a {@link SampleStream} and clips its samples. */
|
||||
private final class ClippingSampleStream implements SampleStream {
|
||||
|
||||
public final SampleStream childStream;
|
||||
@ -302,8 +298,8 @@ public final class ClippingMediaPeriod implements MediaPeriod, MediaPeriod.Callb
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer,
|
||||
boolean requireFormat) {
|
||||
public int readData(
|
||||
FormatHolder formatHolder, DecoderInputBuffer buffer, boolean requireFormat) {
|
||||
if (isPendingInitialDiscontinuity()) {
|
||||
return C.RESULT_NOTHING_READ;
|
||||
}
|
||||
|
@ -24,6 +24,7 @@ import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.MediaItem;
|
||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||
import com.google.android.exoplayer2.drm.DrmSessionManagerProvider;
|
||||
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
||||
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
|
||||
import com.google.android.exoplayer2.offline.StreamKey;
|
||||
@ -100,15 +101,12 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
||||
|
||||
private static final String TAG = "DefaultMediaSourceFactory";
|
||||
|
||||
private final MediaSourceDrmHelper mediaSourceDrmHelper;
|
||||
private final DataSource.Factory dataSourceFactory;
|
||||
private final SparseArray<MediaSourceFactory> mediaSourceFactories;
|
||||
@C.ContentType private final int[] supportedTypes;
|
||||
|
||||
@Nullable private AdsLoaderProvider adsLoaderProvider;
|
||||
@Nullable private AdViewProvider adViewProvider;
|
||||
@Nullable private DrmSessionManager drmSessionManager;
|
||||
@Nullable private List<StreamKey> streamKeys;
|
||||
@Nullable private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
|
||||
private long liveTargetOffsetMs;
|
||||
private long liveMinOffsetMs;
|
||||
@ -157,7 +155,6 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
||||
public DefaultMediaSourceFactory(
|
||||
DataSource.Factory dataSourceFactory, ExtractorsFactory extractorsFactory) {
|
||||
this.dataSourceFactory = dataSourceFactory;
|
||||
mediaSourceDrmHelper = new MediaSourceDrmHelper();
|
||||
mediaSourceFactories = loadDelegates(dataSourceFactory, extractorsFactory);
|
||||
supportedTypes = new int[mediaSourceFactories.size()];
|
||||
for (int i = 0; i < mediaSourceFactories.size(); i++) {
|
||||
@ -254,23 +251,41 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation") // Calling through to the same deprecated method.
|
||||
@Override
|
||||
public DefaultMediaSourceFactory setDrmHttpDataSourceFactory(
|
||||
@Nullable HttpDataSource.Factory drmHttpDataSourceFactory) {
|
||||
mediaSourceDrmHelper.setDrmHttpDataSourceFactory(drmHttpDataSourceFactory);
|
||||
for (int i = 0; i < mediaSourceFactories.size(); i++) {
|
||||
mediaSourceFactories.valueAt(i).setDrmHttpDataSourceFactory(drmHttpDataSourceFactory);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation") // Calling through to the same deprecated method.
|
||||
@Override
|
||||
public DefaultMediaSourceFactory setDrmUserAgent(@Nullable String userAgent) {
|
||||
mediaSourceDrmHelper.setDrmUserAgent(userAgent);
|
||||
for (int i = 0; i < mediaSourceFactories.size(); i++) {
|
||||
mediaSourceFactories.valueAt(i).setDrmUserAgent(userAgent);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation") // Calling through to the same deprecated method.
|
||||
@Override
|
||||
public DefaultMediaSourceFactory setDrmSessionManager(
|
||||
@Nullable DrmSessionManager drmSessionManager) {
|
||||
this.drmSessionManager = drmSessionManager;
|
||||
for (int i = 0; i < mediaSourceFactories.size(); i++) {
|
||||
mediaSourceFactories.valueAt(i).setDrmSessionManager(drmSessionManager);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public DefaultMediaSourceFactory setDrmSessionManagerProvider(
|
||||
@Nullable DrmSessionManagerProvider drmSessionManagerProvider) {
|
||||
for (int i = 0; i < mediaSourceFactories.size(); i++) {
|
||||
mediaSourceFactories.valueAt(i).setDrmSessionManagerProvider(drmSessionManagerProvider);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -278,6 +293,9 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
||||
public DefaultMediaSourceFactory setLoadErrorHandlingPolicy(
|
||||
@Nullable LoadErrorHandlingPolicy loadErrorHandlingPolicy) {
|
||||
this.loadErrorHandlingPolicy = loadErrorHandlingPolicy;
|
||||
for (int i = 0; i < mediaSourceFactories.size(); i++) {
|
||||
mediaSourceFactories.valueAt(i).setLoadErrorHandlingPolicy(loadErrorHandlingPolicy);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -285,11 +303,13 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
||||
* @deprecated Use {@link MediaItem.Builder#setStreamKeys(List)} and {@link
|
||||
* #createMediaSource(MediaItem)} instead.
|
||||
*/
|
||||
@SuppressWarnings("deprecation")
|
||||
@SuppressWarnings("deprecation") // Calling through to the same deprecated method.
|
||||
@Deprecated
|
||||
@Override
|
||||
public DefaultMediaSourceFactory setStreamKeys(@Nullable List<StreamKey> streamKeys) {
|
||||
this.streamKeys = streamKeys != null && !streamKeys.isEmpty() ? streamKeys : null;
|
||||
for (int i = 0; i < mediaSourceFactories.size(); i++) {
|
||||
mediaSourceFactories.valueAt(i).setStreamKeys(streamKeys);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -298,7 +318,6 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
||||
return Arrays.copyOf(supportedTypes, supportedTypes.length);
|
||||
}
|
||||
|
||||
@SuppressWarnings("deprecation")
|
||||
@Override
|
||||
public MediaSource createMediaSource(MediaItem mediaItem) {
|
||||
Assertions.checkNotNull(mediaItem.playbackProperties);
|
||||
@ -309,13 +328,6 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
||||
@Nullable MediaSourceFactory mediaSourceFactory = mediaSourceFactories.get(type);
|
||||
Assertions.checkNotNull(
|
||||
mediaSourceFactory, "No suitable media source factory found for content type: " + type);
|
||||
mediaSourceFactory.setDrmSessionManager(
|
||||
drmSessionManager != null ? drmSessionManager : mediaSourceDrmHelper.create(mediaItem));
|
||||
mediaSourceFactory.setStreamKeys(
|
||||
!mediaItem.playbackProperties.streamKeys.isEmpty()
|
||||
? mediaItem.playbackProperties.streamKeys
|
||||
: streamKeys);
|
||||
mediaSourceFactory.setLoadErrorHandlingPolicy(loadErrorHandlingPolicy);
|
||||
|
||||
// Make sure to retain the very same media item instance, if no value needs to be overridden.
|
||||
if ((mediaItem.liveConfiguration.targetOffsetMs == C.TIME_UNSET
|
||||
|
@ -23,6 +23,7 @@ import com.google.android.exoplayer2.MediaItem;
|
||||
import com.google.android.exoplayer2.Player;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||
import com.google.android.exoplayer2.drm.DrmSessionManagerProvider;
|
||||
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
||||
import com.google.android.exoplayer2.extractor.Extractor;
|
||||
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
|
||||
@ -154,6 +155,18 @@ public final class ExtractorMediaSource extends CompositeMediaSource<Void> {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* @deprecated Use {@link
|
||||
* ProgressiveMediaSource.Factory#setDrmSessionManagerProvider(DrmSessionManagerProvider)}
|
||||
* instead.
|
||||
*/
|
||||
@Deprecated
|
||||
@Override
|
||||
public Factory setDrmSessionManagerProvider(
|
||||
@Nullable DrmSessionManagerProvider drmSessionManagerProvider) {
|
||||
throw new UnsupportedOperationException();
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link ProgressiveMediaSource.Factory#setDrmSessionManager} instead. */
|
||||
@Deprecated
|
||||
@Override
|
||||
@ -324,7 +337,7 @@ public final class ExtractorMediaSource extends CompositeMediaSource<Void> {
|
||||
.build(),
|
||||
dataSourceFactory,
|
||||
extractorsFactory,
|
||||
DrmSessionManager.getDummyDrmSessionManager(),
|
||||
DrmSessionManager.DRM_UNSUPPORTED,
|
||||
loadableLoadErrorHandlingPolicy,
|
||||
continueLoadingCheckIntervalBytes);
|
||||
}
|
||||
|
@ -23,7 +23,7 @@ import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.SeekParameters;
|
||||
import com.google.android.exoplayer2.source.MediaSource.MediaPeriodId;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
import java.io.IOException;
|
||||
import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||
@ -173,7 +173,7 @@ public final class MaskingMediaPeriod implements MediaPeriod, MediaPeriod.Callba
|
||||
|
||||
@Override
|
||||
public long selectTracks(
|
||||
@NullableType TrackSelection[] selections,
|
||||
@NullableType ExoTrackSelection[] selections,
|
||||
boolean[] mayRetainStreamFlags,
|
||||
@NullableType SampleStream[] streams,
|
||||
boolean[] streamResetFlags,
|
||||
|
@ -21,7 +21,7 @@ import com.google.android.exoplayer2.SeekParameters;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.offline.StreamKey;
|
||||
import com.google.android.exoplayer2.source.MediaSource.MediaSourceCaller;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
|
||||
import java.io.IOException;
|
||||
import java.util.Collections;
|
||||
import java.util.List;
|
||||
@ -29,21 +29,23 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||
|
||||
/**
|
||||
* Loads media corresponding to a {@link Timeline.Period}, and allows that media to be read. All
|
||||
* methods are called on the player's internal playback thread, as described in the
|
||||
* {@link ExoPlayer} Javadoc.
|
||||
* methods are called on the player's internal playback thread, as described in the {@link
|
||||
* ExoPlayer} Javadoc.
|
||||
*
|
||||
* <p>A {@link MediaPeriod} may only able to provide one {@link SampleStream} corresponding to a
|
||||
* group at any given time, however this {@link SampleStream} may adapt between multiple tracks
|
||||
* within the group.
|
||||
*/
|
||||
public interface MediaPeriod extends SequenceableLoader {
|
||||
|
||||
/**
|
||||
* A callback to be notified of {@link MediaPeriod} events.
|
||||
*/
|
||||
/** A callback to be notified of {@link MediaPeriod} events. */
|
||||
interface Callback extends SequenceableLoader.Callback<MediaPeriod> {
|
||||
|
||||
/**
|
||||
* Called when preparation completes.
|
||||
*
|
||||
* <p>Called on the playback thread. After invoking this method, the {@link MediaPeriod} can
|
||||
* expect for {@link #selectTracks(TrackSelection[], boolean[], SampleStream[], boolean[],
|
||||
* expect for {@link #selectTracks(ExoTrackSelection[], boolean[], SampleStream[], boolean[],
|
||||
* long)} to be called with the initial track selection.
|
||||
*
|
||||
* @param mediaPeriod The prepared {@link MediaPeriod}.
|
||||
@ -88,17 +90,17 @@ public interface MediaPeriod extends SequenceableLoader {
|
||||
|
||||
/**
|
||||
* Returns a list of {@link StreamKey StreamKeys} which allow to filter the media in this period
|
||||
* to load only the parts needed to play the provided {@link TrackSelection TrackSelections}.
|
||||
* to load only the parts needed to play the provided {@link ExoTrackSelection TrackSelections}.
|
||||
*
|
||||
* <p>This method is only called after the period has been prepared.
|
||||
*
|
||||
* @param trackSelections The {@link TrackSelection TrackSelections} describing the tracks for
|
||||
* @param trackSelections The {@link ExoTrackSelection TrackSelections} describing the tracks for
|
||||
* which stream keys are requested.
|
||||
* @return The corresponding {@link StreamKey StreamKeys} for the selected tracks, or an empty
|
||||
* list if filtering is not possible and the entire media needs to be loaded to play the
|
||||
* selected tracks.
|
||||
*/
|
||||
default List<StreamKey> getStreamKeys(List<TrackSelection> trackSelections) {
|
||||
default List<StreamKey> getStreamKeys(List<ExoTrackSelection> trackSelections) {
|
||||
return Collections.emptyList();
|
||||
}
|
||||
|
||||
@ -113,8 +115,8 @@ public interface MediaPeriod extends SequenceableLoader {
|
||||
* corresponding flag in {@code streamResetFlags} will be set to true. This flag will also be set
|
||||
* if a new sample stream is created.
|
||||
*
|
||||
* <p>Note that previously passed {@link TrackSelection TrackSelections} are no longer valid, and
|
||||
* any references to them must be updated to point to the new selections.
|
||||
* <p>Note that previously passed {@link ExoTrackSelection TrackSelections} are no longer valid,
|
||||
* and any references to them must be updated to point to the new selections.
|
||||
*
|
||||
* <p>This method is only called after the period has been prepared.
|
||||
*
|
||||
@ -133,7 +135,7 @@ public interface MediaPeriod extends SequenceableLoader {
|
||||
* @return The actual position at which the tracks were enabled, in microseconds.
|
||||
*/
|
||||
long selectTracks(
|
||||
@NullableType TrackSelection[] selections,
|
||||
@NullableType ExoTrackSelection[] selections,
|
||||
boolean[] mayRetainStreamFlags,
|
||||
@NullableType SampleStream[] streams,
|
||||
boolean[] streamResetFlags,
|
||||
|
@ -20,7 +20,9 @@ import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.MediaItem;
|
||||
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
|
||||
import com.google.android.exoplayer2.drm.DefaultDrmSessionManagerProvider;
|
||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||
import com.google.android.exoplayer2.drm.DrmSessionManagerProvider;
|
||||
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
|
||||
import com.google.android.exoplayer2.offline.StreamKey;
|
||||
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
|
||||
@ -56,41 +58,80 @@ public interface MediaSourceFactory {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Sets the {@link DrmSessionManagerProvider} used to obtain a {@link DrmSessionManager} for a
|
||||
* {@link MediaItem}.
|
||||
*
|
||||
* <p>If not set, {@link DefaultDrmSessionManagerProvider} is used.
|
||||
*
|
||||
* <p>If set, calls to the following (deprecated) methods are ignored:
|
||||
*
|
||||
* <ul>
|
||||
* <li>{@link #setDrmUserAgent(String)}
|
||||
* <li>{@link #setDrmHttpDataSourceFactory(HttpDataSource.Factory)}
|
||||
* </ul>
|
||||
*
|
||||
* @return This factory, for convenience.
|
||||
*/
|
||||
MediaSourceFactory setDrmSessionManagerProvider(
|
||||
@Nullable DrmSessionManagerProvider drmSessionManagerProvider);
|
||||
|
||||
/**
|
||||
* Sets the {@link DrmSessionManager} to use for all media items regardless of their {@link
|
||||
* MediaItem.DrmConfiguration}.
|
||||
*
|
||||
* <p>Calling this with a non-null {@code drmSessionManager} is equivalent to calling {@code
|
||||
* setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager)}.
|
||||
*
|
||||
* @param drmSessionManager The {@link DrmSessionManager}, or {@code null} to use the {@link
|
||||
* DefaultDrmSessionManager}.
|
||||
* @return This factory, for convenience.
|
||||
* @deprecated Use {@link #setDrmSessionManagerProvider(DrmSessionManagerProvider)} and pass an
|
||||
* implementation that always returns the same instance.
|
||||
*/
|
||||
@Deprecated
|
||||
MediaSourceFactory setDrmSessionManager(@Nullable DrmSessionManager drmSessionManager);
|
||||
|
||||
/**
|
||||
* Sets the {@link HttpDataSource.Factory} to be used for creating {@link HttpMediaDrmCallback
|
||||
* HttpMediaDrmCallbacks} to execute key and provisioning requests over HTTP.
|
||||
*
|
||||
* <p>In case a {@link DrmSessionManager} has been set by {@link
|
||||
* #setDrmSessionManager(DrmSessionManager)}, this data source factory is ignored.
|
||||
* <p>Calls to this method are ignored if either a {@link
|
||||
* #setDrmSessionManagerProvider(DrmSessionManagerProvider) DrmSessionManager provider} or {@link
|
||||
* #setDrmSessionManager(DrmSessionManager) concrete DrmSessionManager} are provided.
|
||||
*
|
||||
* @param drmHttpDataSourceFactory The HTTP data source factory, or {@code null} to use {@link
|
||||
* DefaultHttpDataSourceFactory}.
|
||||
* @return This factory, for convenience.
|
||||
* @deprecated Use {@link #setDrmSessionManagerProvider(DrmSessionManagerProvider)} and pass an
|
||||
* implementation that configures the returned {@link DrmSessionManager} with the desired
|
||||
* {@link HttpDataSource.Factory}.
|
||||
*/
|
||||
@Deprecated
|
||||
MediaSourceFactory setDrmHttpDataSourceFactory(
|
||||
@Nullable HttpDataSource.Factory drmHttpDataSourceFactory);
|
||||
|
||||
/**
|
||||
* Sets the optional user agent to be used for DRM requests.
|
||||
*
|
||||
* <p>In case a factory has been set by {@link
|
||||
* #setDrmHttpDataSourceFactory(HttpDataSource.Factory)} or a {@link DrmSessionManager} has been
|
||||
* set by {@link #setDrmSessionManager(DrmSessionManager)}, this user agent is ignored.
|
||||
* <p>Calls to this method are ignored if any of the following are provided:
|
||||
*
|
||||
* <ul>
|
||||
* <li>A {@link #setDrmSessionManagerProvider(DrmSessionManagerProvider) DrmSessionManager
|
||||
* provider}.
|
||||
* <li>A {@link #setDrmSessionManager(DrmSessionManager) concrete DrmSessionManager}.
|
||||
* <li>A {@link #setDrmHttpDataSourceFactory(HttpDataSource.Factory) DRM
|
||||
* HttpDataSource.Factory}.
|
||||
* </ul>
|
||||
*
|
||||
* @param userAgent The user agent to be used for DRM requests, or {@code null} to use the
|
||||
* default.
|
||||
* @return This factory, for convenience.
|
||||
* @deprecated Use {@link #setDrmSessionManagerProvider(DrmSessionManagerProvider)} and pass an
|
||||
* implementation that configures the returned {@link DrmSessionManager} with the desired
|
||||
* {@code userAgent}.
|
||||
*/
|
||||
@Deprecated
|
||||
MediaSourceFactory setDrmUserAgent(@Nullable String userAgent);
|
||||
|
||||
/**
|
||||
|
@ -23,7 +23,7 @@ import com.google.android.exoplayer2.FormatHolder;
|
||||
import com.google.android.exoplayer2.SeekParameters;
|
||||
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
||||
import com.google.android.exoplayer2.offline.StreamKey;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import java.io.IOException;
|
||||
import java.util.ArrayList;
|
||||
@ -33,9 +33,7 @@ import java.util.List;
|
||||
import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
/**
|
||||
* Merges multiple {@link MediaPeriod}s.
|
||||
*/
|
||||
/** Merges multiple {@link MediaPeriod}s. */
|
||||
/* package */ final class MergingMediaPeriod implements MediaPeriod, MediaPeriod.Callback {
|
||||
|
||||
private final MediaPeriod[] periods;
|
||||
@ -100,7 +98,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
@Override
|
||||
public long selectTracks(
|
||||
@NullableType TrackSelection[] selections,
|
||||
@NullableType ExoTrackSelection[] selections,
|
||||
boolean[] mayRetainStreamFlags,
|
||||
@NullableType SampleStream[] streams,
|
||||
boolean[] streamResetFlags,
|
||||
@ -126,15 +124,16 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
// Select tracks for each child, copying the resulting streams back into a new streams array.
|
||||
@NullableType SampleStream[] newStreams = new SampleStream[selections.length];
|
||||
@NullableType SampleStream[] childStreams = new SampleStream[selections.length];
|
||||
@NullableType TrackSelection[] childSelections = new TrackSelection[selections.length];
|
||||
@NullableType ExoTrackSelection[] childSelections = new ExoTrackSelection[selections.length];
|
||||
ArrayList<MediaPeriod> enabledPeriodsList = new ArrayList<>(periods.length);
|
||||
for (int i = 0; i < periods.length; i++) {
|
||||
for (int j = 0; j < selections.length; j++) {
|
||||
childStreams[j] = streamChildIndices[j] == i ? streams[j] : null;
|
||||
childSelections[j] = selectionChildIndices[j] == i ? selections[j] : null;
|
||||
}
|
||||
long selectPositionUs = periods[i].selectTracks(childSelections, mayRetainStreamFlags,
|
||||
childStreams, streamResetFlags, positionUs);
|
||||
long selectPositionUs =
|
||||
periods[i].selectTracks(
|
||||
childSelections, mayRetainStreamFlags, childStreams, streamResetFlags, positionUs);
|
||||
if (i == 0) {
|
||||
positionUs = selectPositionUs;
|
||||
} else if (selectPositionUs != positionUs) {
|
||||
@ -314,13 +313,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
}
|
||||
|
||||
@Override
|
||||
public List<StreamKey> getStreamKeys(List<TrackSelection> trackSelections) {
|
||||
public List<StreamKey> getStreamKeys(List<ExoTrackSelection> trackSelections) {
|
||||
return mediaPeriod.getStreamKeys(trackSelections);
|
||||
}
|
||||
|
||||
@Override
|
||||
public long selectTracks(
|
||||
@NullableType TrackSelection[] selections,
|
||||
@NullableType ExoTrackSelection[] selections,
|
||||
boolean[] mayRetainStreamFlags,
|
||||
@NullableType SampleStream[] streams,
|
||||
boolean[] streamResetFlags,
|
||||
|
@ -40,7 +40,7 @@ import com.google.android.exoplayer2.extractor.TrackOutput;
|
||||
import com.google.android.exoplayer2.metadata.Metadata;
|
||||
import com.google.android.exoplayer2.metadata.icy.IcyHeaders;
|
||||
import com.google.android.exoplayer2.source.SampleQueue.UpstreamFormatChangedListener;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
||||
@ -252,7 +252,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
@Override
|
||||
public long selectTracks(
|
||||
@NullableType TrackSelection[] selections,
|
||||
@NullableType ExoTrackSelection[] selections,
|
||||
boolean[] mayRetainStreamFlags,
|
||||
@NullableType SampleStream[] streams,
|
||||
boolean[] streamResetFlags,
|
||||
@ -277,7 +277,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
// Select new tracks.
|
||||
for (int i = 0; i < selections.length; i++) {
|
||||
if (streams[i] == null && selections[i] != null) {
|
||||
TrackSelection selection = selections[i];
|
||||
ExoTrackSelection selection = selections[i];
|
||||
Assertions.checkState(selection.length() == 1);
|
||||
Assertions.checkState(selection.getIndexInTrackGroup(0) == 0);
|
||||
int track = tracks.indexOf(selection.getTrackGroup());
|
||||
|
@ -22,7 +22,9 @@ import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.MediaItem;
|
||||
import com.google.android.exoplayer2.Timeline;
|
||||
import com.google.android.exoplayer2.drm.DefaultDrmSessionManagerProvider;
|
||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||
import com.google.android.exoplayer2.drm.DrmSessionManagerProvider;
|
||||
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
||||
import com.google.android.exoplayer2.extractor.Extractor;
|
||||
import com.google.android.exoplayer2.extractor.ExtractorsFactory;
|
||||
@ -51,10 +53,10 @@ public final class ProgressiveMediaSource extends BaseMediaSource
|
||||
public static final class Factory implements MediaSourceFactory {
|
||||
|
||||
private final DataSource.Factory dataSourceFactory;
|
||||
private final MediaSourceDrmHelper mediaSourceDrmHelper;
|
||||
|
||||
private ExtractorsFactory extractorsFactory;
|
||||
@Nullable private DrmSessionManager drmSessionManager;
|
||||
private boolean usingCustomDrmSessionManagerProvider;
|
||||
private DrmSessionManagerProvider drmSessionManagerProvider;
|
||||
private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
|
||||
private int continueLoadingCheckIntervalBytes;
|
||||
@Nullable private String customCacheKey;
|
||||
@ -79,7 +81,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource
|
||||
public Factory(DataSource.Factory dataSourceFactory, ExtractorsFactory extractorsFactory) {
|
||||
this.dataSourceFactory = dataSourceFactory;
|
||||
this.extractorsFactory = extractorsFactory;
|
||||
mediaSourceDrmHelper = new MediaSourceDrmHelper();
|
||||
drmSessionManagerProvider = new DefaultDrmSessionManagerProvider();
|
||||
loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy();
|
||||
continueLoadingCheckIntervalBytes = DEFAULT_LOADING_CHECK_INTERVAL_BYTES;
|
||||
}
|
||||
@ -148,21 +150,42 @@ public final class ProgressiveMediaSource extends BaseMediaSource
|
||||
}
|
||||
|
||||
@Override
|
||||
public Factory setDrmSessionManagerProvider(
|
||||
@Nullable DrmSessionManagerProvider drmSessionManagerProvider) {
|
||||
if (drmSessionManagerProvider != null) {
|
||||
this.drmSessionManagerProvider = drmSessionManagerProvider;
|
||||
this.usingCustomDrmSessionManagerProvider = true;
|
||||
} else {
|
||||
this.drmSessionManagerProvider = new DefaultDrmSessionManagerProvider();
|
||||
this.usingCustomDrmSessionManagerProvider = false;
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
public Factory setDrmSessionManager(@Nullable DrmSessionManager drmSessionManager) {
|
||||
this.drmSessionManager = drmSessionManager;
|
||||
if (drmSessionManager == null) {
|
||||
setDrmSessionManagerProvider(null);
|
||||
} else {
|
||||
setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Factory setDrmHttpDataSourceFactory(
|
||||
@Nullable HttpDataSource.Factory drmHttpDataSourceFactory) {
|
||||
mediaSourceDrmHelper.setDrmHttpDataSourceFactory(drmHttpDataSourceFactory);
|
||||
if (!usingCustomDrmSessionManagerProvider) {
|
||||
((DefaultDrmSessionManagerProvider) drmSessionManagerProvider)
|
||||
.setDrmHttpDataSourceFactory(drmHttpDataSourceFactory);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@Override
|
||||
public Factory setDrmUserAgent(@Nullable String userAgent) {
|
||||
mediaSourceDrmHelper.setDrmUserAgent(userAgent);
|
||||
if (!usingCustomDrmSessionManagerProvider) {
|
||||
((DefaultDrmSessionManagerProvider) drmSessionManagerProvider).setDrmUserAgent(userAgent);
|
||||
}
|
||||
return this;
|
||||
}
|
||||
|
||||
@ -198,7 +221,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource
|
||||
mediaItem,
|
||||
dataSourceFactory,
|
||||
extractorsFactory,
|
||||
drmSessionManager != null ? drmSessionManager : mediaSourceDrmHelper.create(mediaItem),
|
||||
drmSessionManagerProvider.get(mediaItem),
|
||||
loadErrorHandlingPolicy,
|
||||
continueLoadingCheckIntervalBytes);
|
||||
}
|
||||
|
@ -115,39 +115,25 @@ import java.util.Arrays;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads data from the rolling buffer to populate a decoder input buffer.
|
||||
* Reads data from the rolling buffer to populate a decoder input buffer, and advances the read
|
||||
* position.
|
||||
*
|
||||
* @param buffer The buffer to populate.
|
||||
* @param extrasHolder The extras holder whose offset should be read and subsequently adjusted.
|
||||
*/
|
||||
public void readToBuffer(DecoderInputBuffer buffer, SampleExtrasHolder extrasHolder) {
|
||||
// Read encryption data if the sample is encrypted.
|
||||
if (buffer.isEncrypted()) {
|
||||
readEncryptionData(buffer, extrasHolder);
|
||||
}
|
||||
// Read sample data, extracting supplemental data into a separate buffer if needed.
|
||||
if (buffer.hasSupplementalData()) {
|
||||
// If there is supplemental data, the sample data is prefixed by its size.
|
||||
scratch.reset(4);
|
||||
readData(extrasHolder.offset, scratch.getData(), 4);
|
||||
int sampleSize = scratch.readUnsignedIntToInt();
|
||||
extrasHolder.offset += 4;
|
||||
extrasHolder.size -= 4;
|
||||
readAllocationNode = readSampleData(readAllocationNode, buffer, extrasHolder, scratch);
|
||||
}
|
||||
|
||||
// Write the sample data.
|
||||
buffer.ensureSpaceForWrite(sampleSize);
|
||||
readData(extrasHolder.offset, buffer.data, sampleSize);
|
||||
extrasHolder.offset += sampleSize;
|
||||
extrasHolder.size -= sampleSize;
|
||||
|
||||
// Write the remaining data as supplemental data.
|
||||
buffer.resetSupplementalData(extrasHolder.size);
|
||||
readData(extrasHolder.offset, buffer.supplementalData, extrasHolder.size);
|
||||
} else {
|
||||
// Write the sample data.
|
||||
buffer.ensureSpaceForWrite(extrasHolder.size);
|
||||
readData(extrasHolder.offset, buffer.data, extrasHolder.size);
|
||||
}
|
||||
/**
|
||||
* Peeks data from the rolling buffer to populate a decoder input buffer, without advancing the
|
||||
* read position.
|
||||
*
|
||||
* @param buffer The buffer to populate.
|
||||
* @param extrasHolder The extras holder whose offset should be read and subsequently adjusted.
|
||||
*/
|
||||
public void peekToBuffer(DecoderInputBuffer buffer, SampleExtrasHolder extrasHolder) {
|
||||
readSampleData(readAllocationNode, buffer, extrasHolder, scratch);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -210,151 +196,6 @@ import java.util.Arrays;
|
||||
|
||||
// Private methods.
|
||||
|
||||
/**
|
||||
* Reads encryption data for the current sample.
|
||||
*
|
||||
* <p>The encryption data is written into {@link DecoderInputBuffer#cryptoInfo}, and {@link
|
||||
* SampleExtrasHolder#size} is adjusted to subtract the number of bytes that were read. The same
|
||||
* value is added to {@link SampleExtrasHolder#offset}.
|
||||
*
|
||||
* @param buffer The buffer into which the encryption data should be written.
|
||||
* @param extrasHolder The extras holder whose offset should be read and subsequently adjusted.
|
||||
*/
|
||||
private void readEncryptionData(DecoderInputBuffer buffer, SampleExtrasHolder extrasHolder) {
|
||||
long offset = extrasHolder.offset;
|
||||
|
||||
// Read the signal byte.
|
||||
scratch.reset(1);
|
||||
readData(offset, scratch.getData(), 1);
|
||||
offset++;
|
||||
byte signalByte = scratch.getData()[0];
|
||||
boolean subsampleEncryption = (signalByte & 0x80) != 0;
|
||||
int ivSize = signalByte & 0x7F;
|
||||
|
||||
// Read the initialization vector.
|
||||
CryptoInfo cryptoInfo = buffer.cryptoInfo;
|
||||
if (cryptoInfo.iv == null) {
|
||||
cryptoInfo.iv = new byte[16];
|
||||
} else {
|
||||
// Zero out cryptoInfo.iv so that if ivSize < 16, the remaining bytes are correctly set to 0.
|
||||
Arrays.fill(cryptoInfo.iv, (byte) 0);
|
||||
}
|
||||
readData(offset, cryptoInfo.iv, ivSize);
|
||||
offset += ivSize;
|
||||
|
||||
// Read the subsample count, if present.
|
||||
int subsampleCount;
|
||||
if (subsampleEncryption) {
|
||||
scratch.reset(2);
|
||||
readData(offset, scratch.getData(), 2);
|
||||
offset += 2;
|
||||
subsampleCount = scratch.readUnsignedShort();
|
||||
} else {
|
||||
subsampleCount = 1;
|
||||
}
|
||||
|
||||
// Write the clear and encrypted subsample sizes.
|
||||
@Nullable int[] clearDataSizes = cryptoInfo.numBytesOfClearData;
|
||||
if (clearDataSizes == null || clearDataSizes.length < subsampleCount) {
|
||||
clearDataSizes = new int[subsampleCount];
|
||||
}
|
||||
@Nullable int[] encryptedDataSizes = cryptoInfo.numBytesOfEncryptedData;
|
||||
if (encryptedDataSizes == null || encryptedDataSizes.length < subsampleCount) {
|
||||
encryptedDataSizes = new int[subsampleCount];
|
||||
}
|
||||
if (subsampleEncryption) {
|
||||
int subsampleDataLength = 6 * subsampleCount;
|
||||
scratch.reset(subsampleDataLength);
|
||||
readData(offset, scratch.getData(), subsampleDataLength);
|
||||
offset += subsampleDataLength;
|
||||
scratch.setPosition(0);
|
||||
for (int i = 0; i < subsampleCount; i++) {
|
||||
clearDataSizes[i] = scratch.readUnsignedShort();
|
||||
encryptedDataSizes[i] = scratch.readUnsignedIntToInt();
|
||||
}
|
||||
} else {
|
||||
clearDataSizes[0] = 0;
|
||||
encryptedDataSizes[0] = extrasHolder.size - (int) (offset - extrasHolder.offset);
|
||||
}
|
||||
|
||||
// Populate the cryptoInfo.
|
||||
CryptoData cryptoData = Util.castNonNull(extrasHolder.cryptoData);
|
||||
cryptoInfo.set(
|
||||
subsampleCount,
|
||||
clearDataSizes,
|
||||
encryptedDataSizes,
|
||||
cryptoData.encryptionKey,
|
||||
cryptoInfo.iv,
|
||||
cryptoData.cryptoMode,
|
||||
cryptoData.encryptedBlocks,
|
||||
cryptoData.clearBlocks);
|
||||
|
||||
// Adjust the offset and size to take into account the bytes read.
|
||||
int bytesRead = (int) (offset - extrasHolder.offset);
|
||||
extrasHolder.offset += bytesRead;
|
||||
extrasHolder.size -= bytesRead;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads data from the front of the rolling buffer.
|
||||
*
|
||||
* @param absolutePosition The absolute position from which data should be read.
|
||||
* @param target The buffer into which data should be written.
|
||||
* @param length The number of bytes to read.
|
||||
*/
|
||||
private void readData(long absolutePosition, ByteBuffer target, int length) {
|
||||
advanceReadTo(absolutePosition);
|
||||
int remaining = length;
|
||||
while (remaining > 0) {
|
||||
int toCopy = min(remaining, (int) (readAllocationNode.endPosition - absolutePosition));
|
||||
Allocation allocation = readAllocationNode.allocation;
|
||||
target.put(allocation.data, readAllocationNode.translateOffset(absolutePosition), toCopy);
|
||||
remaining -= toCopy;
|
||||
absolutePosition += toCopy;
|
||||
if (absolutePosition == readAllocationNode.endPosition) {
|
||||
readAllocationNode = readAllocationNode.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads data from the front of the rolling buffer.
|
||||
*
|
||||
* @param absolutePosition The absolute position from which data should be read.
|
||||
* @param target The array into which data should be written.
|
||||
* @param length The number of bytes to read.
|
||||
*/
|
||||
private void readData(long absolutePosition, byte[] target, int length) {
|
||||
advanceReadTo(absolutePosition);
|
||||
int remaining = length;
|
||||
while (remaining > 0) {
|
||||
int toCopy = min(remaining, (int) (readAllocationNode.endPosition - absolutePosition));
|
||||
Allocation allocation = readAllocationNode.allocation;
|
||||
System.arraycopy(
|
||||
allocation.data,
|
||||
readAllocationNode.translateOffset(absolutePosition),
|
||||
target,
|
||||
length - remaining,
|
||||
toCopy);
|
||||
remaining -= toCopy;
|
||||
absolutePosition += toCopy;
|
||||
if (absolutePosition == readAllocationNode.endPosition) {
|
||||
readAllocationNode = readAllocationNode.next;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Advances the read position to the specified absolute position.
|
||||
*
|
||||
* @param absolutePosition The position to which {@link #readAllocationNode} should be advanced.
|
||||
*/
|
||||
private void advanceReadTo(long absolutePosition) {
|
||||
while (absolutePosition >= readAllocationNode.endPosition) {
|
||||
readAllocationNode = readAllocationNode.next;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Clears allocation nodes starting from {@code fromNode}.
|
||||
*
|
||||
@ -409,6 +250,214 @@ import java.util.Arrays;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads data from the rolling buffer to populate a decoder input buffer.
|
||||
*
|
||||
* @param allocationNode The first {@link AllocationNode} containing data yet to be read.
|
||||
* @param buffer The buffer to populate.
|
||||
* @param extrasHolder The extras holder whose offset should be read and subsequently adjusted.
|
||||
* @param scratch A scratch {@link ParsableByteArray}.
|
||||
* @return The first {@link AllocationNode} that contains unread bytes after the last byte that
|
||||
* the invocation read.
|
||||
*/
|
||||
private static AllocationNode readSampleData(
|
||||
AllocationNode allocationNode,
|
||||
DecoderInputBuffer buffer,
|
||||
SampleExtrasHolder extrasHolder,
|
||||
ParsableByteArray scratch) {
|
||||
if (buffer.isEncrypted()) {
|
||||
allocationNode = readEncryptionData(allocationNode, buffer, extrasHolder, scratch);
|
||||
}
|
||||
// Read sample data, extracting supplemental data into a separate buffer if needed.
|
||||
if (buffer.hasSupplementalData()) {
|
||||
// If there is supplemental data, the sample data is prefixed by its size.
|
||||
scratch.reset(4);
|
||||
allocationNode = readData(allocationNode, extrasHolder.offset, scratch.getData(), 4);
|
||||
int sampleSize = scratch.readUnsignedIntToInt();
|
||||
extrasHolder.offset += 4;
|
||||
extrasHolder.size -= 4;
|
||||
|
||||
// Write the sample data.
|
||||
buffer.ensureSpaceForWrite(sampleSize);
|
||||
allocationNode = readData(allocationNode, extrasHolder.offset, buffer.data, sampleSize);
|
||||
extrasHolder.offset += sampleSize;
|
||||
extrasHolder.size -= sampleSize;
|
||||
|
||||
// Write the remaining data as supplemental data.
|
||||
buffer.resetSupplementalData(extrasHolder.size);
|
||||
allocationNode =
|
||||
readData(allocationNode, extrasHolder.offset, buffer.supplementalData, extrasHolder.size);
|
||||
} else {
|
||||
// Write the sample data.
|
||||
buffer.ensureSpaceForWrite(extrasHolder.size);
|
||||
allocationNode =
|
||||
readData(allocationNode, extrasHolder.offset, buffer.data, extrasHolder.size);
|
||||
}
|
||||
return allocationNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads encryption data for the sample described by {@code extrasHolder}.
|
||||
*
|
||||
* <p>The encryption data is written into {@link DecoderInputBuffer#cryptoInfo}, and {@link
|
||||
* SampleExtrasHolder#size} is adjusted to subtract the number of bytes that were read. The same
|
||||
* value is added to {@link SampleExtrasHolder#offset}.
|
||||
*
|
||||
* @param allocationNode The first {@link AllocationNode} containing data yet to be read.
|
||||
* @param buffer The buffer into which the encryption data should be written.
|
||||
* @param extrasHolder The extras holder whose offset should be read and subsequently adjusted.
|
||||
* @param scratch A scratch {@link ParsableByteArray}.
|
||||
* @return The first {@link AllocationNode} that contains unread bytes after this method returns.
|
||||
*/
|
||||
private static AllocationNode readEncryptionData(
|
||||
AllocationNode allocationNode,
|
||||
DecoderInputBuffer buffer,
|
||||
SampleExtrasHolder extrasHolder,
|
||||
ParsableByteArray scratch) {
|
||||
long offset = extrasHolder.offset;
|
||||
|
||||
// Read the signal byte.
|
||||
scratch.reset(1);
|
||||
allocationNode = readData(allocationNode, offset, scratch.getData(), 1);
|
||||
offset++;
|
||||
byte signalByte = scratch.getData()[0];
|
||||
boolean subsampleEncryption = (signalByte & 0x80) != 0;
|
||||
int ivSize = signalByte & 0x7F;
|
||||
|
||||
// Read the initialization vector.
|
||||
CryptoInfo cryptoInfo = buffer.cryptoInfo;
|
||||
if (cryptoInfo.iv == null) {
|
||||
cryptoInfo.iv = new byte[16];
|
||||
} else {
|
||||
// Zero out cryptoInfo.iv so that if ivSize < 16, the remaining bytes are correctly set to 0.
|
||||
Arrays.fill(cryptoInfo.iv, (byte) 0);
|
||||
}
|
||||
allocationNode = readData(allocationNode, offset, cryptoInfo.iv, ivSize);
|
||||
offset += ivSize;
|
||||
|
||||
// Read the subsample count, if present.
|
||||
int subsampleCount;
|
||||
if (subsampleEncryption) {
|
||||
scratch.reset(2);
|
||||
allocationNode = readData(allocationNode, offset, scratch.getData(), 2);
|
||||
offset += 2;
|
||||
subsampleCount = scratch.readUnsignedShort();
|
||||
} else {
|
||||
subsampleCount = 1;
|
||||
}
|
||||
|
||||
// Write the clear and encrypted subsample sizes.
|
||||
@Nullable int[] clearDataSizes = cryptoInfo.numBytesOfClearData;
|
||||
if (clearDataSizes == null || clearDataSizes.length < subsampleCount) {
|
||||
clearDataSizes = new int[subsampleCount];
|
||||
}
|
||||
@Nullable int[] encryptedDataSizes = cryptoInfo.numBytesOfEncryptedData;
|
||||
if (encryptedDataSizes == null || encryptedDataSizes.length < subsampleCount) {
|
||||
encryptedDataSizes = new int[subsampleCount];
|
||||
}
|
||||
if (subsampleEncryption) {
|
||||
int subsampleDataLength = 6 * subsampleCount;
|
||||
scratch.reset(subsampleDataLength);
|
||||
allocationNode = readData(allocationNode, offset, scratch.getData(), subsampleDataLength);
|
||||
offset += subsampleDataLength;
|
||||
scratch.setPosition(0);
|
||||
for (int i = 0; i < subsampleCount; i++) {
|
||||
clearDataSizes[i] = scratch.readUnsignedShort();
|
||||
encryptedDataSizes[i] = scratch.readUnsignedIntToInt();
|
||||
}
|
||||
} else {
|
||||
clearDataSizes[0] = 0;
|
||||
encryptedDataSizes[0] = extrasHolder.size - (int) (offset - extrasHolder.offset);
|
||||
}
|
||||
|
||||
// Populate the cryptoInfo.
|
||||
CryptoData cryptoData = Util.castNonNull(extrasHolder.cryptoData);
|
||||
cryptoInfo.set(
|
||||
subsampleCount,
|
||||
clearDataSizes,
|
||||
encryptedDataSizes,
|
||||
cryptoData.encryptionKey,
|
||||
cryptoInfo.iv,
|
||||
cryptoData.cryptoMode,
|
||||
cryptoData.encryptedBlocks,
|
||||
cryptoData.clearBlocks);
|
||||
|
||||
// Adjust the offset and size to take into account the bytes read.
|
||||
int bytesRead = (int) (offset - extrasHolder.offset);
|
||||
extrasHolder.offset += bytesRead;
|
||||
extrasHolder.size -= bytesRead;
|
||||
return allocationNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads data from {@code allocationNode} and its following nodes.
|
||||
*
|
||||
* @param allocationNode The first {@link AllocationNode} containing data yet to be read.
|
||||
* @param absolutePosition The absolute position from which data should be read.
|
||||
* @param target The buffer into which data should be written.
|
||||
* @param length The number of bytes to read.
|
||||
* @return The first {@link AllocationNode} that contains unread bytes after this method returns.
|
||||
*/
|
||||
private static AllocationNode readData(
|
||||
AllocationNode allocationNode, long absolutePosition, ByteBuffer target, int length) {
|
||||
allocationNode = getNodeContainingPosition(allocationNode, absolutePosition);
|
||||
int remaining = length;
|
||||
while (remaining > 0) {
|
||||
int toCopy = min(remaining, (int) (allocationNode.endPosition - absolutePosition));
|
||||
Allocation allocation = allocationNode.allocation;
|
||||
target.put(allocation.data, allocationNode.translateOffset(absolutePosition), toCopy);
|
||||
remaining -= toCopy;
|
||||
absolutePosition += toCopy;
|
||||
if (absolutePosition == allocationNode.endPosition) {
|
||||
allocationNode = allocationNode.next;
|
||||
}
|
||||
}
|
||||
return allocationNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reads data from {@code allocationNode} and its following nodes.
|
||||
*
|
||||
* @param allocationNode The first {@link AllocationNode} containing data yet to be read.
|
||||
* @param absolutePosition The absolute position from which data should be read.
|
||||
* @param target The array into which data should be written.
|
||||
* @param length The number of bytes to read.
|
||||
* @return The first {@link AllocationNode} that contains unread bytes after this method returns.
|
||||
*/
|
||||
private static AllocationNode readData(
|
||||
AllocationNode allocationNode, long absolutePosition, byte[] target, int length) {
|
||||
allocationNode = getNodeContainingPosition(allocationNode, absolutePosition);
|
||||
int remaining = length;
|
||||
while (remaining > 0) {
|
||||
int toCopy = min(remaining, (int) (allocationNode.endPosition - absolutePosition));
|
||||
Allocation allocation = allocationNode.allocation;
|
||||
System.arraycopy(
|
||||
allocation.data,
|
||||
allocationNode.translateOffset(absolutePosition),
|
||||
target,
|
||||
length - remaining,
|
||||
toCopy);
|
||||
remaining -= toCopy;
|
||||
absolutePosition += toCopy;
|
||||
if (absolutePosition == allocationNode.endPosition) {
|
||||
allocationNode = allocationNode.next;
|
||||
}
|
||||
}
|
||||
return allocationNode;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the {@link AllocationNode} in {@code allocationNode}'s chain which contains the given
|
||||
* {@code absolutePosition}.
|
||||
*/
|
||||
private static AllocationNode getNodeContainingPosition(
|
||||
AllocationNode allocationNode, long absolutePosition) {
|
||||
while (absolutePosition >= allocationNode.endPosition) {
|
||||
allocationNode = allocationNode.next;
|
||||
}
|
||||
return allocationNode;
|
||||
}
|
||||
|
||||
/** A node in a linked list of {@link Allocation}s held by the output. */
|
||||
private static final class AllocationNode {
|
||||
|
||||
|
@ -377,6 +377,20 @@ public class SampleQueue implements TrackOutput {
|
||||
return mayReadSample(relativeReadIndex);
|
||||
}
|
||||
|
||||
/** Equivalent to {@link #read}, except it never advances the read position. */
|
||||
public final int peek(
|
||||
FormatHolder formatHolder,
|
||||
DecoderInputBuffer buffer,
|
||||
boolean formatRequired,
|
||||
boolean loadingFinished) {
|
||||
int result =
|
||||
peekSampleMetadata(formatHolder, buffer, formatRequired, loadingFinished, extrasHolder);
|
||||
if (result == C.RESULT_BUFFER_READ && !buffer.isEndOfStream() && !buffer.isFlagsOnly()) {
|
||||
sampleDataQueue.peekToBuffer(buffer, extrasHolder);
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
/**
|
||||
* Attempts to read from the queue.
|
||||
*
|
||||
|
@ -25,7 +25,7 @@ import com.google.android.exoplayer2.FormatHolder;
|
||||
import com.google.android.exoplayer2.MediaItem;
|
||||
import com.google.android.exoplayer2.SeekParameters;
|
||||
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
|
||||
import com.google.android.exoplayer2.upstream.Allocator;
|
||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
@ -194,7 +194,7 @@ public final class SilenceMediaSource extends BaseMediaSource {
|
||||
|
||||
@Override
|
||||
public long selectTracks(
|
||||
@NullableType TrackSelection[] selections,
|
||||
@NullableType ExoTrackSelection[] selections,
|
||||
boolean[] mayRetainStreamFlags,
|
||||
@NullableType SampleStream[] streams,
|
||||
boolean[] streamResetFlags,
|
||||
|
@ -31,10 +31,7 @@ public final class SinglePeriodTimeline extends Timeline {
|
||||
|
||||
private static final Object UID = new Object();
|
||||
private static final MediaItem MEDIA_ITEM =
|
||||
new MediaItem.Builder()
|
||||
.setMediaId("com.google.android.exoplayer2.source.SinglePeriodTimeline")
|
||||
.setUri(Uri.EMPTY)
|
||||
.build();
|
||||
new MediaItem.Builder().setMediaId("SinglePeriodTimeline").setUri(Uri.EMPTY).build();
|
||||
|
||||
private final long presentationStartTimeMs;
|
||||
private final long windowStartTimeMs;
|
||||
|
@ -22,7 +22,7 @@ import com.google.android.exoplayer2.FormatHolder;
|
||||
import com.google.android.exoplayer2.SeekParameters;
|
||||
import com.google.android.exoplayer2.decoder.DecoderInputBuffer;
|
||||
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection;
|
||||
import com.google.android.exoplayer2.trackselection.ExoTrackSelection;
|
||||
import com.google.android.exoplayer2.upstream.DataSource;
|
||||
import com.google.android.exoplayer2.upstream.DataSpec;
|
||||
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
|
||||
@ -33,6 +33,7 @@ import com.google.android.exoplayer2.upstream.Loader.Loadable;
|
||||
import com.google.android.exoplayer2.upstream.StatsDataSource;
|
||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.Log;
|
||||
import com.google.android.exoplayer2.util.MimeTypes;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import java.io.IOException;
|
||||
@ -41,15 +42,13 @@ import java.util.Arrays;
|
||||
import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
/**
|
||||
* A {@link MediaPeriod} with a single sample.
|
||||
*/
|
||||
/* package */ final class SingleSampleMediaPeriod implements MediaPeriod,
|
||||
Loader.Callback<SingleSampleMediaPeriod.SourceLoadable> {
|
||||
/** A {@link MediaPeriod} with a single sample. */
|
||||
/* package */ final class SingleSampleMediaPeriod
|
||||
implements MediaPeriod, Loader.Callback<SingleSampleMediaPeriod.SourceLoadable> {
|
||||
|
||||
/**
|
||||
* The initial size of the allocation used to hold the sample data.
|
||||
*/
|
||||
private static final String TAG = "SingleSampleMediaPeriod";
|
||||
|
||||
/** The initial size of the allocation used to hold the sample data. */
|
||||
private static final int INITIAL_SAMPLE_SIZE = 1024;
|
||||
|
||||
private final DataSpec dataSpec;
|
||||
@ -113,7 +112,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
@Override
|
||||
public long selectTracks(
|
||||
@NullableType TrackSelection[] selections,
|
||||
@NullableType ExoTrackSelection[] selections,
|
||||
boolean[] mayRetainStreamFlags,
|
||||
@NullableType SampleStream[] streams,
|
||||
boolean[] streamResetFlags,
|
||||
@ -294,6 +293,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
|
||||
LoadErrorAction action;
|
||||
if (treatLoadErrorsAsEndOfStream && errorCanBePropagated) {
|
||||
Log.w(TAG, "Loading failed, treating as end-of-stream.", error);
|
||||
loadingFinished = true;
|
||||
action = Loader.DONT_RETRY;
|
||||
} else {
|
||||
@ -348,8 +348,8 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||
}
|
||||
|
||||
@Override
|
||||
public int readData(FormatHolder formatHolder, DecoderInputBuffer buffer,
|
||||
boolean requireFormat) {
|
||||
public int readData(
|
||||
FormatHolder formatHolder, DecoderInputBuffer buffer, boolean requireFormat) {
|
||||
maybeNotifyDownstreamFormat();
|
||||
if (streamState == STREAM_STATE_END_OF_STREAM) {
|
||||
buffer.addFlag(C.BUFFER_FLAG_END_OF_STREAM);
|
||||
|
@ -145,6 +145,7 @@ public final class Cea708Decoder extends CeaDecoder {
|
||||
|
||||
private final ParsableByteArray ccData;
|
||||
private final ParsableBitArray serviceBlockPacket;
|
||||
private int previousSequenceNumber;
|
||||
// TODO: Use isWideAspectRatio in decoding.
|
||||
@SuppressWarnings({"unused", "FieldCanBeLocal"})
|
||||
private final boolean isWideAspectRatio;
|
||||
@ -162,6 +163,7 @@ public final class Cea708Decoder extends CeaDecoder {
|
||||
public Cea708Decoder(int accessibilityChannel, @Nullable List<byte[]> initializationData) {
|
||||
ccData = new ParsableByteArray();
|
||||
serviceBlockPacket = new ParsableBitArray();
|
||||
previousSequenceNumber = C.INDEX_UNSET;
|
||||
selectedServiceNumber = accessibilityChannel == Format.NO_VALUE ? 1 : accessibilityChannel;
|
||||
isWideAspectRatio =
|
||||
initializationData != null
|
||||
@ -231,6 +233,18 @@ public final class Cea708Decoder extends CeaDecoder {
|
||||
finalizeCurrentPacket();
|
||||
|
||||
int sequenceNumber = (ccData1 & 0xC0) >> 6; // first 2 bits
|
||||
if (previousSequenceNumber != C.INDEX_UNSET
|
||||
&& sequenceNumber != (previousSequenceNumber + 1) % 4) {
|
||||
resetCueBuilders();
|
||||
Log.w(
|
||||
TAG,
|
||||
"Sequence number discontinuity. previous="
|
||||
+ previousSequenceNumber
|
||||
+ " current="
|
||||
+ sequenceNumber);
|
||||
}
|
||||
previousSequenceNumber = sequenceNumber;
|
||||
|
||||
int packetSize = ccData1 & 0x3F; // last 6 bits
|
||||
if (packetSize == 0) {
|
||||
packetSize = 64;
|
||||
@ -270,10 +284,18 @@ public final class Cea708Decoder extends CeaDecoder {
|
||||
@RequiresNonNull("currentDtvCcPacket")
|
||||
private void processCurrentPacket() {
|
||||
if (currentDtvCcPacket.currentIndex != (currentDtvCcPacket.packetSize * 2 - 1)) {
|
||||
Log.w(TAG, "DtvCcPacket ended prematurely; size is " + (currentDtvCcPacket.packetSize * 2 - 1)
|
||||
+ ", but current index is " + currentDtvCcPacket.currentIndex + " (sequence number "
|
||||
+ currentDtvCcPacket.sequenceNumber + "); ignoring packet");
|
||||
return;
|
||||
Log.d(
|
||||
TAG,
|
||||
"DtvCcPacket ended prematurely; size is "
|
||||
+ (currentDtvCcPacket.packetSize * 2 - 1)
|
||||
+ ", but current index is "
|
||||
+ currentDtvCcPacket.currentIndex
|
||||
+ " (sequence number "
|
||||
+ currentDtvCcPacket.sequenceNumber
|
||||
+ ");");
|
||||
// We've received cc_type=0x03 (packet start) before receiving packetSize byte pairs of data.
|
||||
// This might indicate a byte pair has been lost, but we'll still attempt to process the data
|
||||
// we have received.
|
||||
}
|
||||
|
||||
serviceBlockPacket.reset(currentDtvCcPacket.packetData, currentDtvCcPacket.currentIndex);
|
||||
|
@ -19,6 +19,8 @@ import static com.google.android.exoplayer2.text.Cue.LINE_TYPE_FRACTION;
|
||||
import static com.google.android.exoplayer2.util.Util.castNonNull;
|
||||
|
||||
import android.text.Layout;
|
||||
import android.text.SpannableString;
|
||||
import android.text.style.ForegroundColorSpan;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.text.Cue;
|
||||
@ -301,7 +303,18 @@ public final class SsaDecoder extends SimpleSubtitleDecoder {
|
||||
SsaStyle.Overrides styleOverrides,
|
||||
float screenWidth,
|
||||
float screenHeight) {
|
||||
Cue.Builder cue = new Cue.Builder().setText(text);
|
||||
SpannableString spannableText = new SpannableString(text);
|
||||
Cue.Builder cue = new Cue.Builder().setText(spannableText);
|
||||
|
||||
if (style != null) {
|
||||
if (style.primaryColor != null) {
|
||||
spannableText.setSpan(
|
||||
new ForegroundColorSpan(style.primaryColor),
|
||||
/* start= */ 0,
|
||||
/* end= */ spannableText.length(),
|
||||
SpannableString.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||
}
|
||||
}
|
||||
|
||||
@SsaStyle.SsaAlignment int alignment;
|
||||
if (styleOverrides.alignment != SsaStyle.SSA_ALIGNMENT_UNKNOWN) {
|
||||
|
@ -17,16 +17,20 @@
|
||||
package com.google.android.exoplayer2.text.ssa;
|
||||
|
||||
import static com.google.android.exoplayer2.text.ssa.SsaDecoder.STYLE_LINE_PREFIX;
|
||||
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
|
||||
import static java.lang.annotation.RetentionPolicy.SOURCE;
|
||||
|
||||
import android.graphics.Color;
|
||||
import android.graphics.PointF;
|
||||
import android.text.TextUtils;
|
||||
import androidx.annotation.ColorInt;
|
||||
import androidx.annotation.IntDef;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.C;
|
||||
import com.google.android.exoplayer2.util.Assertions;
|
||||
import com.google.android.exoplayer2.util.Log;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import com.google.common.primitives.Ints;
|
||||
import java.lang.annotation.Documented;
|
||||
import java.lang.annotation.Retention;
|
||||
import java.util.regex.Matcher;
|
||||
@ -85,15 +89,18 @@ import java.util.regex.Pattern;
|
||||
|
||||
public final String name;
|
||||
@SsaAlignment public final int alignment;
|
||||
@Nullable @ColorInt public final Integer primaryColor;
|
||||
|
||||
private SsaStyle(String name, @SsaAlignment int alignment) {
|
||||
private SsaStyle(
|
||||
String name, @SsaAlignment int alignment, @Nullable @ColorInt Integer primaryColor) {
|
||||
this.name = name;
|
||||
this.alignment = alignment;
|
||||
this.primaryColor = primaryColor;
|
||||
}
|
||||
|
||||
@Nullable
|
||||
public static SsaStyle fromStyleLine(String styleLine, Format format) {
|
||||
Assertions.checkArgument(styleLine.startsWith(STYLE_LINE_PREFIX));
|
||||
checkArgument(styleLine.startsWith(STYLE_LINE_PREFIX));
|
||||
String[] styleValues = TextUtils.split(styleLine.substring(STYLE_LINE_PREFIX.length()), ",");
|
||||
if (styleValues.length != format.length) {
|
||||
Log.w(
|
||||
@ -105,7 +112,9 @@ import java.util.regex.Pattern;
|
||||
}
|
||||
try {
|
||||
return new SsaStyle(
|
||||
styleValues[format.nameIndex].trim(), parseAlignment(styleValues[format.alignmentIndex]));
|
||||
styleValues[format.nameIndex].trim(),
|
||||
parseAlignment(styleValues[format.alignmentIndex].trim()),
|
||||
parseColor(styleValues[format.primaryColorIndex].trim()));
|
||||
} catch (RuntimeException e) {
|
||||
Log.w(TAG, "Skipping malformed 'Style:' line: '" + styleLine + "'", e);
|
||||
return null;
|
||||
@ -144,6 +153,44 @@ import java.util.regex.Pattern;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Parses a SSA V4+ color expression.
|
||||
*
|
||||
* <p>A SSA V4+ color can be represented in hex {@code ("&HAABBGGRR")} or in 64-bit decimal format
|
||||
* (byte order AABBGGRR). In both cases the alpha channel's value needs to be inverted because in
|
||||
* SSA the 0xFF alpha value means transparent and 0x00 means opaque which is the opposite from the
|
||||
* Android {@link ColorInt} representation.
|
||||
*
|
||||
* @param ssaColorExpression A SSA V4+ color expression.
|
||||
* @return The parsed color value, or null if parsing failed.
|
||||
*/
|
||||
@Nullable
|
||||
@ColorInt
|
||||
public static Integer parseColor(String ssaColorExpression) {
|
||||
// We use a long because the value is an unsigned 32-bit number, so can be larger than
|
||||
// Integer.MAX_VALUE.
|
||||
long abgr;
|
||||
try {
|
||||
abgr =
|
||||
ssaColorExpression.startsWith("&H")
|
||||
// Parse color from hex format (&HAABBGGRR).
|
||||
? Long.parseLong(ssaColorExpression.substring(2), /* radix= */ 16)
|
||||
// Parse color from decimal format (bytes order AABBGGRR).
|
||||
: Long.parseLong(ssaColorExpression);
|
||||
// Ensure only the bottom 4 bytes of abgr are set.
|
||||
checkArgument(abgr <= 0xFFFFFFFFL);
|
||||
} catch (IllegalArgumentException e) {
|
||||
Log.w(TAG, "Failed to parse color expression: '" + ssaColorExpression + "'", e);
|
||||
return null;
|
||||
}
|
||||
// Convert ABGR to ARGB.
|
||||
int a = Ints.checkedCast(((abgr >> 24) & 0xFF) ^ 0xFF); // Flip alpha.
|
||||
int b = Ints.checkedCast((abgr >> 16) & 0xFF);
|
||||
int g = Ints.checkedCast((abgr >> 8) & 0xFF);
|
||||
int r = Ints.checkedCast(abgr & 0xFF);
|
||||
return Color.argb(a, r, g, b);
|
||||
}
|
||||
|
||||
/**
|
||||
* Represents a {@code Format:} line from the {@code [V4+ Styles]} section
|
||||
*
|
||||
@ -154,11 +201,13 @@ import java.util.regex.Pattern;
|
||||
|
||||
public final int nameIndex;
|
||||
public final int alignmentIndex;
|
||||
public final int primaryColorIndex;
|
||||
public final int length;
|
||||
|
||||
private Format(int nameIndex, int alignmentIndex, int length) {
|
||||
private Format(int nameIndex, int alignmentIndex, int primaryColorIndex, int length) {
|
||||
this.nameIndex = nameIndex;
|
||||
this.alignmentIndex = alignmentIndex;
|
||||
this.primaryColorIndex = primaryColorIndex;
|
||||
this.length = length;
|
||||
}
|
||||
|
||||
@ -171,6 +220,7 @@ import java.util.regex.Pattern;
|
||||
public static Format fromFormatLine(String styleFormatLine) {
|
||||
int nameIndex = C.INDEX_UNSET;
|
||||
int alignmentIndex = C.INDEX_UNSET;
|
||||
int primaryColorIndex = C.INDEX_UNSET;
|
||||
String[] keys =
|
||||
TextUtils.split(styleFormatLine.substring(SsaDecoder.FORMAT_LINE_PREFIX.length()), ",");
|
||||
for (int i = 0; i < keys.length; i++) {
|
||||
@ -181,9 +231,14 @@ import java.util.regex.Pattern;
|
||||
case "alignment":
|
||||
alignmentIndex = i;
|
||||
break;
|
||||
case "primarycolour":
|
||||
primaryColorIndex = i;
|
||||
break;
|
||||
}
|
||||
}
|
||||
return nameIndex != C.INDEX_UNSET ? new Format(nameIndex, alignmentIndex, keys.length) : null;
|
||||
return nameIndex != C.INDEX_UNSET
|
||||
? new Format(nameIndex, alignmentIndex, primaryColorIndex, keys.length)
|
||||
: null;
|
||||
}
|
||||
}
|
||||
|
||||
@ -237,8 +292,7 @@ import java.util.regex.Pattern;
|
||||
// Ignore invalid \pos() or \move() function.
|
||||
}
|
||||
try {
|
||||
@SsaAlignment
|
||||
int parsedAlignment = parseAlignmentOverride(braceContents);
|
||||
@SsaAlignment int parsedAlignment = parseAlignmentOverride(braceContents);
|
||||
if (parsedAlignment != SSA_ALIGNMENT_UNKNOWN) {
|
||||
alignment = parsedAlignment;
|
||||
}
|
||||
|
@ -38,13 +38,13 @@ import java.util.List;
|
||||
import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||
|
||||
/**
|
||||
* A bandwidth based adaptive {@link TrackSelection}, whose selected track is updated to be the one
|
||||
* of highest quality given the current network conditions and the state of the buffer.
|
||||
* A bandwidth based adaptive {@link ExoTrackSelection}, whose selected track is updated to be the
|
||||
* one of highest quality given the current network conditions and the state of the buffer.
|
||||
*/
|
||||
public class AdaptiveTrackSelection extends BaseTrackSelection {
|
||||
|
||||
/** Factory for {@link AdaptiveTrackSelection} instances. */
|
||||
public static class Factory implements TrackSelection.Factory {
|
||||
public static class Factory implements ExoTrackSelection.Factory {
|
||||
|
||||
private final int minDurationForQualityIncreaseMs;
|
||||
private final int maxDurationForQualityDecreaseMs;
|
||||
@ -132,14 +132,14 @@ public class AdaptiveTrackSelection extends BaseTrackSelection {
|
||||
}
|
||||
|
||||
@Override
|
||||
public final @NullableType TrackSelection[] createTrackSelections(
|
||||
public final @NullableType ExoTrackSelection[] createTrackSelections(
|
||||
@NullableType Definition[] definitions,
|
||||
BandwidthMeter bandwidthMeter,
|
||||
MediaPeriodId mediaPeriodId,
|
||||
Timeline timeline) {
|
||||
ImmutableList<ImmutableList<AdaptationCheckpoint>> adaptationCheckpoints =
|
||||
getAdaptationCheckpoints(definitions);
|
||||
TrackSelection[] selections = new TrackSelection[definitions.length];
|
||||
ExoTrackSelection[] selections = new ExoTrackSelection[definitions.length];
|
||||
for (int i = 0; i < definitions.length; i++) {
|
||||
@Nullable Definition definition = definitions[i];
|
||||
if (definition == null || definition.tracks.length == 0) {
|
||||
|
@ -28,27 +28,17 @@ import com.google.android.exoplayer2.util.Util;
|
||||
import java.util.Arrays;
|
||||
import java.util.List;
|
||||
|
||||
/**
|
||||
* An abstract base class suitable for most {@link TrackSelection} implementations.
|
||||
*/
|
||||
public abstract class BaseTrackSelection implements TrackSelection {
|
||||
/** An abstract base class suitable for most {@link ExoTrackSelection} implementations. */
|
||||
public abstract class BaseTrackSelection implements ExoTrackSelection {
|
||||
|
||||
/**
|
||||
* The selected {@link TrackGroup}.
|
||||
*/
|
||||
/** The selected {@link TrackGroup}. */
|
||||
protected final TrackGroup group;
|
||||
/**
|
||||
* The number of selected tracks within the {@link TrackGroup}. Always greater than zero.
|
||||
*/
|
||||
/** The number of selected tracks within the {@link TrackGroup}. Always greater than zero. */
|
||||
protected final int length;
|
||||
/**
|
||||
* The indices of the selected tracks in {@link #group}, in order of decreasing bandwidth.
|
||||
*/
|
||||
/** The indices of the selected tracks in {@link #group}, in order of decreasing bandwidth. */
|
||||
protected final int[] tracks;
|
||||
|
||||
/**
|
||||
* The {@link Format}s of the selected tracks, in order of decreasing bandwidth.
|
||||
*/
|
||||
/** The {@link Format}s of the selected tracks, in order of decreasing bandwidth. */
|
||||
private final Format[] formats;
|
||||
/** Selected track exclusion timestamps, in order of decreasing bandwidth. */
|
||||
private final long[] excludeUntilTimes;
|
||||
|
@ -222,7 +222,6 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
*
|
||||
* @param context Any context.
|
||||
*/
|
||||
|
||||
public ParametersBuilder(Context context) {
|
||||
super(context);
|
||||
setInitialValuesWithoutContext();
|
||||
@ -826,9 +825,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
return this;
|
||||
}
|
||||
|
||||
/**
|
||||
* Builds a {@link Parameters} instance with the selected values.
|
||||
*/
|
||||
/** Builds a {@link Parameters} instance with the selected values. */
|
||||
public Parameters build() {
|
||||
return new Parameters(
|
||||
// Video
|
||||
@ -1614,6 +1611,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
* dimension).
|
||||
*/
|
||||
private static final float FRACTION_TO_CONSIDER_FULLSCREEN = 0.98f;
|
||||
|
||||
private static final int[] NO_TRACKS = new int[0];
|
||||
/** Ordering of two format values. A known value is considered greater than Format#NO_VALUE. */
|
||||
private static final Ordering<Integer> FORMAT_VALUE_ORDERING =
|
||||
@ -1625,7 +1623,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
/** Ordering where all elements are equal. */
|
||||
private static final Ordering<Integer> NO_ORDER = Ordering.from((first, second) -> 0);
|
||||
|
||||
private final TrackSelection.Factory trackSelectionFactory;
|
||||
private final ExoTrackSelection.Factory trackSelectionFactory;
|
||||
private final AtomicReference<Parameters> parametersReference;
|
||||
|
||||
/** @deprecated Use {@link #DefaultTrackSelector(Context)} instead. */
|
||||
@ -1634,9 +1632,9 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
this(Parameters.DEFAULT_WITHOUT_CONTEXT, new AdaptiveTrackSelection.Factory());
|
||||
}
|
||||
|
||||
/** @deprecated Use {@link #DefaultTrackSelector(Context, TrackSelection.Factory)}. */
|
||||
/** @deprecated Use {@link #DefaultTrackSelector(Context, ExoTrackSelection.Factory)}. */
|
||||
@Deprecated
|
||||
public DefaultTrackSelector(TrackSelection.Factory trackSelectionFactory) {
|
||||
public DefaultTrackSelector(ExoTrackSelection.Factory trackSelectionFactory) {
|
||||
this(Parameters.DEFAULT_WITHOUT_CONTEXT, trackSelectionFactory);
|
||||
}
|
||||
|
||||
@ -1647,17 +1645,18 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
|
||||
/**
|
||||
* @param context Any {@link Context}.
|
||||
* @param trackSelectionFactory A factory for {@link TrackSelection}s.
|
||||
* @param trackSelectionFactory A factory for {@link ExoTrackSelection}s.
|
||||
*/
|
||||
public DefaultTrackSelector(Context context, TrackSelection.Factory trackSelectionFactory) {
|
||||
public DefaultTrackSelector(Context context, ExoTrackSelection.Factory trackSelectionFactory) {
|
||||
this(Parameters.getDefaults(context), trackSelectionFactory);
|
||||
}
|
||||
|
||||
/**
|
||||
* @param parameters Initial {@link Parameters}.
|
||||
* @param trackSelectionFactory A factory for {@link TrackSelection}s.
|
||||
* @param trackSelectionFactory A factory for {@link ExoTrackSelection}s.
|
||||
*/
|
||||
public DefaultTrackSelector(Parameters parameters, TrackSelection.Factory trackSelectionFactory) {
|
||||
public DefaultTrackSelector(
|
||||
Parameters parameters, ExoTrackSelection.Factory trackSelectionFactory) {
|
||||
this.trackSelectionFactory = trackSelectionFactory;
|
||||
parametersReference = new AtomicReference<>(parameters);
|
||||
}
|
||||
@ -1700,7 +1699,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
// MappingTrackSelector implementation.
|
||||
|
||||
@Override
|
||||
protected final Pair<@NullableType RendererConfiguration[], @NullableType TrackSelection[]>
|
||||
protected final Pair<@NullableType RendererConfiguration[], @NullableType ExoTrackSelection[]>
|
||||
selectTracks(
|
||||
MappedTrackInfo mappedTrackInfo,
|
||||
@Capabilities int[][][] rendererFormatSupports,
|
||||
@ -1710,7 +1709,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
throws ExoPlaybackException {
|
||||
Parameters params = parametersReference.get();
|
||||
int rendererCount = mappedTrackInfo.getRendererCount();
|
||||
TrackSelection.@NullableType Definition[] definitions =
|
||||
ExoTrackSelection.@NullableType Definition[] definitions =
|
||||
selectAllTracks(
|
||||
mappedTrackInfo,
|
||||
rendererFormatSupports,
|
||||
@ -1729,7 +1728,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
definitions[i] =
|
||||
override == null
|
||||
? null
|
||||
: new TrackSelection.Definition(
|
||||
: new ExoTrackSelection.Definition(
|
||||
rendererTrackGroups.get(override.groupIndex),
|
||||
override.tracks,
|
||||
override.reason,
|
||||
@ -1738,14 +1737,14 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
}
|
||||
|
||||
@NullableType
|
||||
TrackSelection[] rendererTrackSelections =
|
||||
ExoTrackSelection[] rendererTrackSelections =
|
||||
trackSelectionFactory.createTrackSelections(
|
||||
definitions, getBandwidthMeter(), mediaPeriodId, timeline);
|
||||
|
||||
// Initialize the renderer configurations to the default configuration for all renderers with
|
||||
// selections, and null otherwise.
|
||||
@NullableType RendererConfiguration[] rendererConfigurations =
|
||||
new RendererConfiguration[rendererCount];
|
||||
@NullableType
|
||||
RendererConfiguration[] rendererConfigurations = new RendererConfiguration[rendererCount];
|
||||
for (int i = 0; i < rendererCount; i++) {
|
||||
boolean forceRendererDisabled = params.getRendererDisabled(i);
|
||||
boolean rendererEnabled =
|
||||
@ -1779,19 +1778,19 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
* renderer, track group and track (in that order).
|
||||
* @param rendererMixedMimeTypeAdaptationSupports The {@link AdaptiveSupport} for mixed MIME type
|
||||
* adaptation for the renderer.
|
||||
* @return The {@link TrackSelection.Definition}s for the renderers. A null entry indicates no
|
||||
* @return The {@link ExoTrackSelection.Definition}s for the renderers. A null entry indicates no
|
||||
* selection was made.
|
||||
* @throws ExoPlaybackException If an error occurs while selecting the tracks.
|
||||
*/
|
||||
protected TrackSelection.@NullableType Definition[] selectAllTracks(
|
||||
protected ExoTrackSelection.@NullableType Definition[] selectAllTracks(
|
||||
MappedTrackInfo mappedTrackInfo,
|
||||
@Capabilities int[][][] rendererFormatSupports,
|
||||
@AdaptiveSupport int[] rendererMixedMimeTypeAdaptationSupports,
|
||||
Parameters params)
|
||||
throws ExoPlaybackException {
|
||||
int rendererCount = mappedTrackInfo.getRendererCount();
|
||||
TrackSelection.@NullableType Definition[] definitions =
|
||||
new TrackSelection.Definition[rendererCount];
|
||||
ExoTrackSelection.@NullableType Definition[] definitions =
|
||||
new ExoTrackSelection.Definition[rendererCount];
|
||||
|
||||
boolean seenVideoRendererWithMappedTracks = false;
|
||||
boolean selectedVideoTracks = false;
|
||||
@ -1819,7 +1818,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
boolean enableAdaptiveTrackSelection =
|
||||
params.allowMultipleAdaptiveSelections || !seenVideoRendererWithMappedTracks;
|
||||
@Nullable
|
||||
Pair<TrackSelection.Definition, AudioTrackScore> audioSelection =
|
||||
Pair<ExoTrackSelection.Definition, AudioTrackScore> audioSelection =
|
||||
selectAudioTrack(
|
||||
mappedTrackInfo.getTrackGroups(i),
|
||||
rendererFormatSupports[i],
|
||||
@ -1834,7 +1833,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
// score. Clear the selection for that renderer.
|
||||
definitions[selectedAudioRendererIndex] = null;
|
||||
}
|
||||
TrackSelection.Definition definition = audioSelection.first;
|
||||
ExoTrackSelection.Definition definition = audioSelection.first;
|
||||
definitions[i] = definition;
|
||||
// We assume that audio tracks in the same group have matching language.
|
||||
selectedAudioLanguage = definition.group.getFormat(definition.tracks[0]).language;
|
||||
@ -1855,7 +1854,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
break;
|
||||
case C.TRACK_TYPE_TEXT:
|
||||
@Nullable
|
||||
Pair<TrackSelection.Definition, TextTrackScore> textSelection =
|
||||
Pair<ExoTrackSelection.Definition, TextTrackScore> textSelection =
|
||||
selectTextTrack(
|
||||
mappedTrackInfo.getTrackGroups(i),
|
||||
rendererFormatSupports[i],
|
||||
@ -1889,7 +1888,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
|
||||
/**
|
||||
* Called by {@link #selectAllTracks(MappedTrackInfo, int[][][], int[], Parameters)} to create a
|
||||
* {@link TrackSelection} for a video renderer.
|
||||
* {@link ExoTrackSelection} for a video renderer.
|
||||
*
|
||||
* @param groups The {@link TrackGroupArray} mapped to the renderer.
|
||||
* @param formatSupport The {@link Capabilities} for each mapped track, indexed by track group and
|
||||
@ -1898,19 +1897,19 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
* adaptation for the renderer.
|
||||
* @param params The selector's current constraint parameters.
|
||||
* @param enableAdaptiveTrackSelection Whether adaptive track selection is allowed.
|
||||
* @return The {@link TrackSelection.Definition} for the renderer, or null if no selection was
|
||||
* @return The {@link ExoTrackSelection.Definition} for the renderer, or null if no selection was
|
||||
* made.
|
||||
* @throws ExoPlaybackException If an error occurs while selecting the tracks.
|
||||
*/
|
||||
@Nullable
|
||||
protected TrackSelection.Definition selectVideoTrack(
|
||||
protected ExoTrackSelection.Definition selectVideoTrack(
|
||||
TrackGroupArray groups,
|
||||
@Capabilities int[][] formatSupport,
|
||||
@AdaptiveSupport int mixedMimeTypeAdaptationSupports,
|
||||
Parameters params,
|
||||
boolean enableAdaptiveTrackSelection)
|
||||
throws ExoPlaybackException {
|
||||
TrackSelection.Definition definition = null;
|
||||
ExoTrackSelection.Definition definition = null;
|
||||
if (!params.forceHighestSupportedBitrate
|
||||
&& !params.forceLowestBitrate
|
||||
&& enableAdaptiveTrackSelection) {
|
||||
@ -1924,7 +1923,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static TrackSelection.Definition selectAdaptiveVideoTrack(
|
||||
private static ExoTrackSelection.Definition selectAdaptiveVideoTrack(
|
||||
TrackGroupArray groups,
|
||||
@Capabilities int[][] formatSupport,
|
||||
@AdaptiveSupport int mixedMimeTypeAdaptationSupports,
|
||||
@ -1956,7 +1955,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
params.viewportHeight,
|
||||
params.viewportOrientationMayChange);
|
||||
if (adaptiveTracks.length > 0) {
|
||||
return new TrackSelection.Definition(group, adaptiveTracks);
|
||||
return new ExoTrackSelection.Definition(group, adaptiveTracks);
|
||||
}
|
||||
}
|
||||
return null;
|
||||
@ -1982,8 +1981,9 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
return NO_TRACKS;
|
||||
}
|
||||
|
||||
List<Integer> selectedTrackIndices = getViewportFilteredTrackIndices(group, viewportWidth,
|
||||
viewportHeight, viewportOrientationMayChange);
|
||||
List<Integer> selectedTrackIndices =
|
||||
getViewportFilteredTrackIndices(
|
||||
group, viewportWidth, viewportHeight, viewportOrientationMayChange);
|
||||
if (selectedTrackIndices.size() < 2) {
|
||||
return NO_TRACKS;
|
||||
}
|
||||
@ -2140,7 +2140,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
}
|
||||
|
||||
@Nullable
|
||||
private static TrackSelection.Definition selectFixedVideoTrack(
|
||||
private static ExoTrackSelection.Definition selectFixedVideoTrack(
|
||||
TrackGroupArray groups, @Capabilities int[][] formatSupport, Parameters params) {
|
||||
int selectedTrackIndex = C.INDEX_UNSET;
|
||||
@Nullable TrackGroup selectedGroup = null;
|
||||
@ -2160,8 +2160,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
// Ignore trick-play tracks for now.
|
||||
continue;
|
||||
}
|
||||
if (isSupported(trackFormatSupport[trackIndex],
|
||||
params.exceedRendererCapabilitiesIfNecessary)) {
|
||||
if (isSupported(
|
||||
trackFormatSupport[trackIndex], params.exceedRendererCapabilitiesIfNecessary)) {
|
||||
VideoTrackScore trackScore =
|
||||
new VideoTrackScore(
|
||||
format,
|
||||
@ -2183,14 +2183,14 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
|
||||
return selectedGroup == null
|
||||
? null
|
||||
: new TrackSelection.Definition(selectedGroup, selectedTrackIndex);
|
||||
: new ExoTrackSelection.Definition(selectedGroup, selectedTrackIndex);
|
||||
}
|
||||
|
||||
// Audio track selection implementation.
|
||||
|
||||
/**
|
||||
* Called by {@link #selectAllTracks(MappedTrackInfo, int[][][], int[], Parameters)} to create a
|
||||
* {@link TrackSelection} for an audio renderer.
|
||||
* {@link ExoTrackSelection} for an audio renderer.
|
||||
*
|
||||
* @param groups The {@link TrackGroupArray} mapped to the renderer.
|
||||
* @param formatSupport The {@link Capabilities} for each mapped track, indexed by track group and
|
||||
@ -2199,13 +2199,13 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
* adaptation for the renderer.
|
||||
* @param params The selector's current constraint parameters.
|
||||
* @param enableAdaptiveTrackSelection Whether adaptive track selection is allowed.
|
||||
* @return The {@link TrackSelection.Definition} and corresponding {@link AudioTrackScore}, or
|
||||
* @return The {@link ExoTrackSelection.Definition} and corresponding {@link AudioTrackScore}, or
|
||||
* null if no selection was made.
|
||||
* @throws ExoPlaybackException If an error occurs while selecting the tracks.
|
||||
*/
|
||||
@SuppressWarnings("unused")
|
||||
@Nullable
|
||||
protected Pair<TrackSelection.Definition, AudioTrackScore> selectAudioTrack(
|
||||
protected Pair<ExoTrackSelection.Definition, AudioTrackScore> selectAudioTrack(
|
||||
TrackGroupArray groups,
|
||||
@Capabilities int[][] formatSupport,
|
||||
@AdaptiveSupport int mixedMimeTypeAdaptationSupports,
|
||||
@ -2219,8 +2219,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
TrackGroup trackGroup = groups.get(groupIndex);
|
||||
@Capabilities int[] trackFormatSupport = formatSupport[groupIndex];
|
||||
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
|
||||
if (isSupported(trackFormatSupport[trackIndex],
|
||||
params.exceedRendererCapabilitiesIfNecessary)) {
|
||||
if (isSupported(
|
||||
trackFormatSupport[trackIndex], params.exceedRendererCapabilitiesIfNecessary)) {
|
||||
Format format = trackGroup.getFormat(trackIndex);
|
||||
AudioTrackScore trackScore =
|
||||
new AudioTrackScore(format, params, trackFormatSupport[trackIndex]);
|
||||
@ -2243,7 +2243,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
|
||||
TrackGroup selectedGroup = groups.get(selectedGroupIndex);
|
||||
|
||||
TrackSelection.Definition definition = null;
|
||||
ExoTrackSelection.Definition definition = null;
|
||||
if (!params.forceHighestSupportedBitrate
|
||||
&& !params.forceLowestBitrate
|
||||
&& enableAdaptiveTrackSelection) {
|
||||
@ -2258,12 +2258,12 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
params.allowAudioMixedSampleRateAdaptiveness,
|
||||
params.allowAudioMixedChannelCountAdaptiveness);
|
||||
if (adaptiveTracks.length > 1) {
|
||||
definition = new TrackSelection.Definition(selectedGroup, adaptiveTracks);
|
||||
definition = new ExoTrackSelection.Definition(selectedGroup, adaptiveTracks);
|
||||
}
|
||||
}
|
||||
if (definition == null) {
|
||||
// We didn't make an adaptive selection, so make a fixed one instead.
|
||||
definition = new TrackSelection.Definition(selectedGroup, selectedTrackIndex);
|
||||
definition = new ExoTrackSelection.Definition(selectedGroup, selectedTrackIndex);
|
||||
}
|
||||
|
||||
return Pair.create(definition, Assertions.checkNotNull(selectedTrackScore));
|
||||
@ -2322,7 +2322,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
|
||||
/**
|
||||
* Called by {@link #selectAllTracks(MappedTrackInfo, int[][][], int[], Parameters)} to create a
|
||||
* {@link TrackSelection} for a text renderer.
|
||||
* {@link ExoTrackSelection} for a text renderer.
|
||||
*
|
||||
* @param groups The {@link TrackGroupArray} mapped to the renderer.
|
||||
* @param formatSupport The {@link Capabilities} for each mapped track, indexed by track group and
|
||||
@ -2330,12 +2330,12 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
* @param params The selector's current constraint parameters.
|
||||
* @param selectedAudioLanguage The language of the selected audio track. May be null if the
|
||||
* selected text track declares no language or no text track was selected.
|
||||
* @return The {@link TrackSelection.Definition} and corresponding {@link TextTrackScore}, or null
|
||||
* if no selection was made.
|
||||
* @return The {@link ExoTrackSelection.Definition} and corresponding {@link TextTrackScore}, or
|
||||
* null if no selection was made.
|
||||
* @throws ExoPlaybackException If an error occurs while selecting the tracks.
|
||||
*/
|
||||
@Nullable
|
||||
protected Pair<TrackSelection.Definition, TextTrackScore> selectTextTrack(
|
||||
protected Pair<ExoTrackSelection.Definition, TextTrackScore> selectTextTrack(
|
||||
TrackGroupArray groups,
|
||||
@Capabilities int[][] formatSupport,
|
||||
Parameters params,
|
||||
@ -2348,8 +2348,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
TrackGroup trackGroup = groups.get(groupIndex);
|
||||
@Capabilities int[] trackFormatSupport = formatSupport[groupIndex];
|
||||
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
|
||||
if (isSupported(trackFormatSupport[trackIndex],
|
||||
params.exceedRendererCapabilitiesIfNecessary)) {
|
||||
if (isSupported(
|
||||
trackFormatSupport[trackIndex], params.exceedRendererCapabilitiesIfNecessary)) {
|
||||
Format format = trackGroup.getFormat(trackIndex);
|
||||
TextTrackScore trackScore =
|
||||
new TextTrackScore(
|
||||
@ -2366,7 +2366,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
return selectedGroup == null
|
||||
? null
|
||||
: Pair.create(
|
||||
new TrackSelection.Definition(selectedGroup, selectedTrackIndex),
|
||||
new ExoTrackSelection.Definition(selectedGroup, selectedTrackIndex),
|
||||
Assertions.checkNotNull(selectedTrackScore));
|
||||
}
|
||||
|
||||
@ -2374,18 +2374,18 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
|
||||
/**
|
||||
* Called by {@link #selectAllTracks(MappedTrackInfo, int[][][], int[], Parameters)} to create a
|
||||
* {@link TrackSelection} for a renderer whose type is neither video, audio or text.
|
||||
* {@link ExoTrackSelection} for a renderer whose type is neither video, audio or text.
|
||||
*
|
||||
* @param trackType The type of the renderer.
|
||||
* @param groups The {@link TrackGroupArray} mapped to the renderer.
|
||||
* @param formatSupport The {@link Capabilities} for each mapped track, indexed by track group and
|
||||
* track (in that order).
|
||||
* @param params The selector's current constraint parameters.
|
||||
* @return The {@link TrackSelection} for the renderer, or null if no selection was made.
|
||||
* @return The {@link ExoTrackSelection} for the renderer, or null if no selection was made.
|
||||
* @throws ExoPlaybackException If an error occurs while selecting the tracks.
|
||||
*/
|
||||
@Nullable
|
||||
protected TrackSelection.Definition selectOtherTrack(
|
||||
protected ExoTrackSelection.Definition selectOtherTrack(
|
||||
int trackType, TrackGroupArray groups, @Capabilities int[][] formatSupport, Parameters params)
|
||||
throws ExoPlaybackException {
|
||||
@Nullable TrackGroup selectedGroup = null;
|
||||
@ -2395,8 +2395,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
TrackGroup trackGroup = groups.get(groupIndex);
|
||||
@Capabilities int[] trackFormatSupport = formatSupport[groupIndex];
|
||||
for (int trackIndex = 0; trackIndex < trackGroup.length; trackIndex++) {
|
||||
if (isSupported(trackFormatSupport[trackIndex],
|
||||
params.exceedRendererCapabilitiesIfNecessary)) {
|
||||
if (isSupported(
|
||||
trackFormatSupport[trackIndex], params.exceedRendererCapabilitiesIfNecessary)) {
|
||||
Format format = trackGroup.getFormat(trackIndex);
|
||||
OtherTrackScore trackScore = new OtherTrackScore(format, trackFormatSupport[trackIndex]);
|
||||
if (selectedTrackScore == null || trackScore.compareTo(selectedTrackScore) > 0) {
|
||||
@ -2409,7 +2409,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
}
|
||||
return selectedGroup == null
|
||||
? null
|
||||
: new TrackSelection.Definition(selectedGroup, selectedTrackIndex);
|
||||
: new ExoTrackSelection.Definition(selectedGroup, selectedTrackIndex);
|
||||
}
|
||||
|
||||
// Utility methods.
|
||||
@ -2430,7 +2430,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
MappedTrackInfo mappedTrackInfo,
|
||||
@Capabilities int[][][] renderererFormatSupports,
|
||||
@NullableType RendererConfiguration[] rendererConfigurations,
|
||||
@NullableType TrackSelection[] trackSelections) {
|
||||
@NullableType ExoTrackSelection[] trackSelections) {
|
||||
// Check whether we can enable tunneling. To enable tunneling we require exactly one audio and
|
||||
// one video renderer to support tunneling and have a selection.
|
||||
int tunnelingAudioRendererIndex = -1;
|
||||
@ -2438,7 +2438,7 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
boolean enableTunneling = true;
|
||||
for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) {
|
||||
int rendererType = mappedTrackInfo.getRendererType(i);
|
||||
TrackSelection trackSelection = trackSelections[i];
|
||||
ExoTrackSelection trackSelection = trackSelections[i];
|
||||
if ((rendererType == C.TRACK_TYPE_AUDIO || rendererType == C.TRACK_TYPE_VIDEO)
|
||||
&& trackSelection != null) {
|
||||
if (rendererSupportsTunneling(
|
||||
@ -2471,16 +2471,18 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether a renderer supports tunneling for a {@link TrackSelection}.
|
||||
* Returns whether a renderer supports tunneling for a {@link ExoTrackSelection}.
|
||||
*
|
||||
* @param formatSupport The {@link Capabilities} for each track, indexed by group index and track
|
||||
* index (in that order).
|
||||
* @param trackGroups The {@link TrackGroupArray}s for the renderer.
|
||||
* @param selection The track selection.
|
||||
* @return Whether the renderer supports tunneling for the {@link TrackSelection}.
|
||||
* @return Whether the renderer supports tunneling for the {@link ExoTrackSelection}.
|
||||
*/
|
||||
private static boolean rendererSupportsTunneling(
|
||||
@Capabilities int[][] formatSupport, TrackGroupArray trackGroups, TrackSelection selection) {
|
||||
@Capabilities int[][] formatSupport,
|
||||
TrackGroupArray trackGroups,
|
||||
ExoTrackSelection selection) {
|
||||
if (selection == null) {
|
||||
return false;
|
||||
}
|
||||
@ -2565,8 +2567,8 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
return 0;
|
||||
}
|
||||
|
||||
private static List<Integer> getViewportFilteredTrackIndices(TrackGroup group, int viewportWidth,
|
||||
int viewportHeight, boolean orientationMayChange) {
|
||||
private static List<Integer> getViewportFilteredTrackIndices(
|
||||
TrackGroup group, int viewportWidth, int viewportHeight, boolean orientationMayChange) {
|
||||
// Initially include all indices.
|
||||
ArrayList<Integer> selectedTrackIndices = new ArrayList<>(group.length);
|
||||
for (int i = 0; i < group.length; i++) {
|
||||
@ -2585,8 +2587,9 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
// smallest to exceed the maximum size at which it can be displayed within the viewport.
|
||||
// We'll discard formats of higher resolution.
|
||||
if (format.width > 0 && format.height > 0) {
|
||||
Point maxVideoSizeInViewport = getMaxVideoSizeInViewport(orientationMayChange,
|
||||
viewportWidth, viewportHeight, format.width, format.height);
|
||||
Point maxVideoSizeInViewport =
|
||||
getMaxVideoSizeInViewport(
|
||||
orientationMayChange, viewportWidth, viewportHeight, format.width, format.height);
|
||||
int videoPixels = format.width * format.height;
|
||||
if (format.width >= (int) (maxVideoSizeInViewport.x * FRACTION_TO_CONSIDER_FULLSCREEN)
|
||||
&& format.height >= (int) (maxVideoSizeInViewport.y * FRACTION_TO_CONSIDER_FULLSCREEN)
|
||||
@ -2616,8 +2619,12 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
||||
* Given viewport dimensions and video dimensions, computes the maximum size of the video as it
|
||||
* will be rendered to fit inside of the viewport.
|
||||
*/
|
||||
private static Point getMaxVideoSizeInViewport(boolean orientationMayChange, int viewportWidth,
|
||||
int viewportHeight, int videoWidth, int videoHeight) {
|
||||
private static Point getMaxVideoSizeInViewport(
|
||||
boolean orientationMayChange,
|
||||
int viewportWidth,
|
||||
int viewportHeight,
|
||||
int videoWidth,
|
||||
int videoHeight) {
|
||||
if (orientationMayChange && (videoWidth > videoHeight) != (viewportWidth > viewportHeight)) {
|
||||
// Rotation is allowed, and the video will be larger in the rotated viewport.
|
||||
int tempViewportWidth = viewportWidth;
|
||||
|
@ -29,15 +29,12 @@ import java.util.List;
|
||||
import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||
|
||||
/**
|
||||
* A track selection consisting of a static subset of selected tracks belonging to a {@link
|
||||
* TrackGroup}, and a possibly varying individual selected track from the subset.
|
||||
*
|
||||
* <p>Tracks belonging to the subset are exposed in decreasing bandwidth order. The individual
|
||||
* selected track may change dynamically as a result of calling {@link #updateSelectedTrack(long,
|
||||
* long, long, List, MediaChunkIterator[])} or {@link #evaluateQueueSize(long, List)}. This only
|
||||
* happens between calls to {@link #enable()} and {@link #disable()}.
|
||||
* A {@link TrackSelection} that can change the individually selected track as a result of calling
|
||||
* {@link #updateSelectedTrack(long, long, long, List, MediaChunkIterator[])} or {@link
|
||||
* #evaluateQueueSize(long, List)}. This only happens between calls to {@link #enable()} and {@link
|
||||
* #disable()}.
|
||||
*/
|
||||
public interface TrackSelection {
|
||||
public interface ExoTrackSelection extends TrackSelection {
|
||||
|
||||
/** Contains of a subset of selected tracks belonging to a {@link TrackGroup}. */
|
||||
final class Definition {
|
||||
@ -73,7 +70,7 @@ public interface TrackSelection {
|
||||
}
|
||||
}
|
||||
|
||||
/** Factory for {@link TrackSelection} instances. */
|
||||
/** Factory for {@link ExoTrackSelection} instances. */
|
||||
interface Factory {
|
||||
|
||||
/**
|
||||
@ -91,7 +88,7 @@ public interface TrackSelection {
|
||||
* include null values.
|
||||
*/
|
||||
@NullableType
|
||||
TrackSelection[] createTrackSelections(
|
||||
ExoTrackSelection[] createTrackSelections(
|
||||
@NullableType Definition[] definitions,
|
||||
BandwidthMeter bandwidthMeter,
|
||||
MediaPeriodId mediaPeriodId,
|
||||
@ -116,50 +113,6 @@ public interface TrackSelection {
|
||||
*/
|
||||
void disable();
|
||||
|
||||
/** Returns the {@link TrackGroup} to which the selected tracks belong. */
|
||||
TrackGroup getTrackGroup();
|
||||
|
||||
// Static subset of selected tracks.
|
||||
|
||||
/** Returns the number of tracks in the selection. */
|
||||
int length();
|
||||
|
||||
/**
|
||||
* Returns the format of the track at a given index in the selection.
|
||||
*
|
||||
* @param index The index in the selection.
|
||||
* @return The format of the selected track.
|
||||
*/
|
||||
Format getFormat(int index);
|
||||
|
||||
/**
|
||||
* Returns the index in the track group of the track at a given index in the selection.
|
||||
*
|
||||
* @param index The index in the selection.
|
||||
* @return The index of the selected track.
|
||||
*/
|
||||
int getIndexInTrackGroup(int index);
|
||||
|
||||
/**
|
||||
* Returns the index in the selection of the track with the specified format. The format is
|
||||
* located by identity so, for example, {@code selection.indexOf(selection.getFormat(index)) ==
|
||||
* index} even if multiple selected tracks have formats that contain the same values.
|
||||
*
|
||||
* @param format The format.
|
||||
* @return The index in the selection, or {@link C#INDEX_UNSET} if the track with the specified
|
||||
* format is not part of the selection.
|
||||
*/
|
||||
int indexOf(Format format);
|
||||
|
||||
/**
|
||||
* Returns the index in the selection of the track with the specified index in the track group.
|
||||
*
|
||||
* @param indexInTrackGroup The index in the track group.
|
||||
* @return The index in the selection, or {@link C#INDEX_UNSET} if the track with the specified
|
||||
* index is not part of the selection.
|
||||
*/
|
||||
int indexOf(int indexInTrackGroup);
|
||||
|
||||
// Individual selected track.
|
||||
|
||||
/** Returns the {@link Format} of the individual selected track. */
|
@ -43,14 +43,12 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||
|
||||
/**
|
||||
* Base class for {@link TrackSelector}s that first establish a mapping between {@link TrackGroup}s
|
||||
* and {@link Renderer}s, and then from that mapping create a {@link TrackSelection} for each
|
||||
* and {@link Renderer}s, and then from that mapping create a {@link ExoTrackSelection} for each
|
||||
* renderer.
|
||||
*/
|
||||
public abstract class MappingTrackSelector extends TrackSelector {
|
||||
|
||||
/**
|
||||
* Provides mapped track information for each renderer.
|
||||
*/
|
||||
/** Provides mapped track information for each renderer. */
|
||||
public static final class MappedTrackInfo {
|
||||
|
||||
/**
|
||||
@ -401,7 +399,7 @@ public abstract class MappingTrackSelector extends TrackSelector {
|
||||
rendererFormatSupports,
|
||||
unmappedTrackGroupArray);
|
||||
|
||||
Pair<@NullableType RendererConfiguration[], @NullableType TrackSelection[]> result =
|
||||
Pair<@NullableType RendererConfiguration[], @NullableType ExoTrackSelection[]> result =
|
||||
selectTracks(
|
||||
mappedTrackInfo,
|
||||
rendererFormatSupports,
|
||||
@ -428,7 +426,7 @@ public abstract class MappingTrackSelector extends TrackSelector {
|
||||
* RendererCapabilities#getTrackType()} is {@link C#TRACK_TYPE_NONE}.
|
||||
* @throws ExoPlaybackException If an error occurs while selecting the tracks.
|
||||
*/
|
||||
protected abstract Pair<@NullableType RendererConfiguration[], @NullableType TrackSelection[]>
|
||||
protected abstract Pair<@NullableType RendererConfiguration[], @NullableType ExoTrackSelection[]>
|
||||
selectTracks(
|
||||
MappedTrackInfo mappedTrackInfo,
|
||||
@Capabilities int[][][] rendererFormatSupports,
|
||||
@ -538,5 +536,4 @@ public abstract class MappingTrackSelector extends TrackSelector {
|
||||
}
|
||||
return mixedMimeTypeAdaptationSupport;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -28,15 +28,11 @@ import java.util.List;
|
||||
import java.util.Random;
|
||||
import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||
|
||||
/**
|
||||
* A {@link TrackSelection} whose selected track is updated randomly.
|
||||
*/
|
||||
/** An {@link ExoTrackSelection} whose selected track is updated randomly. */
|
||||
public final class RandomTrackSelection extends BaseTrackSelection {
|
||||
|
||||
/**
|
||||
* Factory for {@link RandomTrackSelection} instances.
|
||||
*/
|
||||
public static final class Factory implements TrackSelection.Factory {
|
||||
/** Factory for {@link RandomTrackSelection} instances. */
|
||||
public static final class Factory implements ExoTrackSelection.Factory {
|
||||
|
||||
private final Random random;
|
||||
|
||||
@ -44,15 +40,13 @@ public final class RandomTrackSelection extends BaseTrackSelection {
|
||||
random = new Random();
|
||||
}
|
||||
|
||||
/**
|
||||
* @param seed A seed for the {@link Random} instance used by the factory.
|
||||
*/
|
||||
/** @param seed A seed for the {@link Random} instance used by the factory. */
|
||||
public Factory(int seed) {
|
||||
random = new Random(seed);
|
||||
}
|
||||
|
||||
@Override
|
||||
public @NullableType TrackSelection[] createTrackSelections(
|
||||
public @NullableType ExoTrackSelection[] createTrackSelections(
|
||||
@NullableType Definition[] definitions,
|
||||
BandwidthMeter bandwidthMeter,
|
||||
MediaPeriodId mediaPeriodId,
|
||||
@ -144,5 +138,4 @@ public final class RandomTrackSelection extends BaseTrackSelection {
|
||||
public Object getSelectionData() {
|
||||
return null;
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -18,7 +18,7 @@ package com.google.android.exoplayer2.trackselection;
|
||||
import androidx.annotation.Nullable;
|
||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride;
|
||||
import com.google.android.exoplayer2.trackselection.TrackSelection.Definition;
|
||||
import com.google.android.exoplayer2.trackselection.ExoTrackSelection.Definition;
|
||||
import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||
|
||||
/** Track selection related utility methods. */
|
||||
@ -35,7 +35,7 @@ public final class TrackSelectionUtil {
|
||||
* @param trackSelectionDefinition A {@link Definition} for the track selection.
|
||||
* @return The created track selection.
|
||||
*/
|
||||
TrackSelection createAdaptiveTrackSelection(Definition trackSelectionDefinition);
|
||||
ExoTrackSelection createAdaptiveTrackSelection(Definition trackSelectionDefinition);
|
||||
}
|
||||
|
||||
/**
|
||||
@ -48,10 +48,10 @@ public final class TrackSelectionUtil {
|
||||
* @return The array of created track selection. For null entries in {@code definitions} returns
|
||||
* null values.
|
||||
*/
|
||||
public static @NullableType TrackSelection[] createTrackSelectionsForDefinitions(
|
||||
public static @NullableType ExoTrackSelection[] createTrackSelectionsForDefinitions(
|
||||
@NullableType Definition[] definitions,
|
||||
AdaptiveTrackSelectionFactory adaptiveTrackSelectionFactory) {
|
||||
TrackSelection[] selections = new TrackSelection[definitions.length];
|
||||
ExoTrackSelection[] selections = new ExoTrackSelection[definitions.length];
|
||||
boolean createdAdaptiveTrackSelection = false;
|
||||
for (int i = 0; i < definitions.length; i++) {
|
||||
Definition definition = definitions[i];
|
||||
|
@ -83,7 +83,7 @@ import com.google.android.exoplayer2.util.Assertions;
|
||||
* thread. The track selector may call {@link InvalidationListener#onTrackSelectionsInvalidated()}
|
||||
* from any thread.
|
||||
*/
|
||||
public abstract class TrackSelector implements TrackSelectorInterface {
|
||||
public abstract class TrackSelector {
|
||||
|
||||
/**
|
||||
* Notified when selections previously made by a {@link TrackSelector} are no longer valid.
|
||||
|
@ -20,9 +20,7 @@ import com.google.android.exoplayer2.RendererConfiguration;
|
||||
import com.google.android.exoplayer2.util.Util;
|
||||
import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||
|
||||
/**
|
||||
* The result of a {@link TrackSelector} operation.
|
||||
*/
|
||||
/** The result of a {@link TrackSelector} operation. */
|
||||
public final class TrackSelectorResult {
|
||||
|
||||
/** The number of selections in the result. Greater than or equal to zero. */
|
||||
@ -32,10 +30,8 @@ public final class TrackSelectorResult {
|
||||
* renderer should be disabled.
|
||||
*/
|
||||
public final @NullableType RendererConfiguration[] rendererConfigurations;
|
||||
/**
|
||||
* A {@link TrackSelectionArray} containing the track selection for each renderer.
|
||||
*/
|
||||
public final TrackSelectionArray selections;
|
||||
/** A {@link ExoTrackSelection} array containing the track selection for each renderer. */
|
||||
public final @NullableType ExoTrackSelection[] selections;
|
||||
/**
|
||||
* An opaque object that will be returned to {@link TrackSelector#onSelectionActivated(Object)}
|
||||
* should the selections be activated.
|
||||
@ -45,17 +41,17 @@ public final class TrackSelectorResult {
|
||||
/**
|
||||
* @param rendererConfigurations A {@link RendererConfiguration} for each renderer. A null entry
|
||||
* indicates the corresponding renderer should be disabled.
|
||||
* @param selections A {@link TrackSelectionArray} containing the selection for each renderer.
|
||||
* @param selections A {@link ExoTrackSelection} array containing the selection for each renderer.
|
||||
* @param info An opaque object that will be returned to {@link
|
||||
* TrackSelector#onSelectionActivated(Object)} should the selection be activated. May be
|
||||
* {@code null}.
|
||||
*/
|
||||
public TrackSelectorResult(
|
||||
@NullableType RendererConfiguration[] rendererConfigurations,
|
||||
@NullableType TrackSelection[] selections,
|
||||
@NullableType ExoTrackSelection[] selections,
|
||||
@Nullable Object info) {
|
||||
this.rendererConfigurations = rendererConfigurations;
|
||||
this.selections = new TrackSelectionArray(selections);
|
||||
this.selections = selections.clone();
|
||||
this.info = info;
|
||||
length = rendererConfigurations.length;
|
||||
}
|
||||
@ -100,7 +96,6 @@ public final class TrackSelectorResult {
|
||||
return false;
|
||||
}
|
||||
return Util.areEqual(rendererConfigurations[index], other.rendererConfigurations[index])
|
||||
&& Util.areEqual(selections.get(index), other.selections.get(index));
|
||||
&& Util.areEqual(selections[index], other.selections[index]);
|
||||
}
|
||||
|
||||
}
|
||||
|
@ -35,7 +35,7 @@ public interface BandwidthMeter {
|
||||
* changed.
|
||||
*
|
||||
* <p>Note: The estimated bitrate is typically derived from more information than just {@code
|
||||
* bytes} and {@code elapsedMs}.
|
||||
* bytesTransferred} and {@code elapsedMs}.
|
||||
*
|
||||
* @param elapsedMs The time taken to transfer {@code bytesTransferred}, in milliseconds. This
|
||||
* is at most the elapsed time since the last callback, but may be less if there were
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user