mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
commit
f17e846d3d
1
.github/ISSUE_TEMPLATE/bug.yml
vendored
1
.github/ISSUE_TEMPLATE/bug.yml
vendored
@ -17,6 +17,7 @@ body:
|
|||||||
label: Media3 Version
|
label: Media3 Version
|
||||||
description: What version of Media3 are you using?
|
description: What version of Media3 are you using?
|
||||||
options:
|
options:
|
||||||
|
- 1.0.0-rc01
|
||||||
- 1.0.0-beta03
|
- 1.0.0-beta03
|
||||||
- 1.0.0-beta02
|
- 1.0.0-beta02
|
||||||
- 1.0.0-beta01
|
- 1.0.0-beta01
|
||||||
|
12
README.md
12
README.md
@ -5,15 +5,13 @@ Android, including local playback (via ExoPlayer) and media sessions.
|
|||||||
|
|
||||||
## Current status
|
## Current status
|
||||||
|
|
||||||
AndroidX Media is currently in beta and we welcome your feedback via the
|
AndroidX Media is currently in release candidate and we welcome your feedback
|
||||||
[issue tracker][]. Please consult the [release notes][] for more details about
|
via the [issue tracker][]. Please consult the [release notes][] for more details
|
||||||
the beta release.
|
about the current release.
|
||||||
|
|
||||||
ExoPlayer's new home will be in AndroidX Media, but for now we are publishing it
|
ExoPlayer's new home will be in AndroidX Media, but for now we are publishing it
|
||||||
both in AndroidX Media and via the existing [ExoPlayer project][]. While
|
both in AndroidX Media and via the existing [ExoPlayer project][] and we are
|
||||||
AndroidX Media is in beta we recommend that production apps using ExoPlayer
|
still handling ExoPlayer issues on the [ExoPlayer issue tracker][].
|
||||||
continue to depend on the existing ExoPlayer project. We are still handling
|
|
||||||
ExoPlayer issues on the [ExoPlayer issue tracker][].
|
|
||||||
|
|
||||||
You'll find some [Media3 documentation on developer.android.com][], including a
|
You'll find some [Media3 documentation on developer.android.com][], including a
|
||||||
[migration guide for existing ExoPlayer and MediaSession users][].
|
[migration guide for existing ExoPlayer and MediaSession users][].
|
||||||
|
105
RELEASENOTES.md
105
RELEASENOTES.md
@ -1,4 +1,101 @@
|
|||||||
Release notes
|
# Release notes
|
||||||
|
|
||||||
|
### 1.0.0-rc01 (2023-02-16)
|
||||||
|
|
||||||
|
This release corresponds to the
|
||||||
|
[ExoPlayer 2.18.3 release](https://github.com/google/ExoPlayer/releases/tag/r2.18.3).
|
||||||
|
|
||||||
|
* Core library:
|
||||||
|
* Tweak the renderer's decoder ordering logic to uphold the
|
||||||
|
`MediaCodecSelector`'s preferences, even if a decoder reports it may not
|
||||||
|
be able to play the media performantly. For example with default
|
||||||
|
selector, hardware decoder with only functional support will be
|
||||||
|
preferred over software decoder that fully supports the format
|
||||||
|
([#10604](https://github.com/google/ExoPlayer/issues/10604)).
|
||||||
|
* Add `ExoPlayer.Builder.setPlaybackLooper` that sets a pre-existing
|
||||||
|
playback thread for a new ExoPlayer instance.
|
||||||
|
* Allow download manager helpers to be cleared
|
||||||
|
([#10776](https://github.com/google/ExoPlayer/issues/10776)).
|
||||||
|
* Add parameter to `BasePlayer.seekTo` to also indicate the command used
|
||||||
|
for seeking.
|
||||||
|
* Use theme when loading drawables on API 21+
|
||||||
|
([#220](https://github.com/androidx/media/issues/220)).
|
||||||
|
* Add `ConcatenatingMediaSource2` that allows combining multiple media
|
||||||
|
items into a single window
|
||||||
|
([#247](https://github.com/androidx/media/issues/247)).
|
||||||
|
* Extractors:
|
||||||
|
* Throw a `ParserException` instead of a `NullPointerException` if the
|
||||||
|
sample table (stbl) is missing a required sample description (stsd) when
|
||||||
|
parsing trak atoms.
|
||||||
|
* Correctly skip samples when seeking directly to a sync frame in fMP4
|
||||||
|
([#10941](https://github.com/google/ExoPlayer/issues/10941)).
|
||||||
|
* Audio:
|
||||||
|
* Use the compressed audio format bitrate to calculate the min buffer size
|
||||||
|
for `AudioTrack` in direct playbacks (passthrough).
|
||||||
|
* Text:
|
||||||
|
* Fix `TextRenderer` passing an invalid (negative) index to
|
||||||
|
`Subtitle.getEventTime` if a subtitle file contains no cues.
|
||||||
|
* SubRip: Add support for UTF-16 files if they start with a byte order
|
||||||
|
mark.
|
||||||
|
* Metadata:
|
||||||
|
* Parse multiple null-separated values from ID3 frames, as permitted by
|
||||||
|
ID3 v2.4.
|
||||||
|
* Add `MediaMetadata.mediaType` to denote the type of content or the type
|
||||||
|
of folder described by the metadata.
|
||||||
|
* Add `MediaMetadata.isBrowsable` as a replacement for
|
||||||
|
`MediaMetadata.folderType`. The folder type will be deprecated in the
|
||||||
|
next release.
|
||||||
|
* DASH:
|
||||||
|
* Add full parsing for image adaptation sets, including tile counts
|
||||||
|
([#3752](https://github.com/google/ExoPlayer/issues/3752)).
|
||||||
|
* UI:
|
||||||
|
* Fix the deprecated
|
||||||
|
`PlayerView.setControllerVisibilityListener(PlayerControlView.VisibilityListener)`
|
||||||
|
to ensure visibility changes are passed to the registered listener
|
||||||
|
([#229](https://github.com/androidx/media/issues/229)).
|
||||||
|
* Fix the ordering of the center player controls in `PlayerView` when
|
||||||
|
using a right-to-left (RTL) layout
|
||||||
|
([#227](https://github.com/androidx/media/issues/227)).
|
||||||
|
* Session:
|
||||||
|
* Add abstract `SimpleBasePlayer` to help implement the `Player` interface
|
||||||
|
for custom players.
|
||||||
|
* Add helper method to convert platform session token to Media3
|
||||||
|
`SessionToken` ([#171](https://github.com/androidx/media/issues/171)).
|
||||||
|
* Use `onMediaMetadataChanged` to trigger updates of the platform media
|
||||||
|
session ([#219](https://github.com/androidx/media/issues/219)).
|
||||||
|
* Add the media session as an argument of `getMediaButtons()` of the
|
||||||
|
`DefaultMediaNotificationProvider` and use immutable lists for clarity
|
||||||
|
([#216](https://github.com/androidx/media/issues/216)).
|
||||||
|
* Add `onSetMediaItems` callback listener to provide means to modify/set
|
||||||
|
`MediaItem` list, starting index and position by session before setting
|
||||||
|
onto Player ([#156](https://github.com/androidx/media/issues/156)).
|
||||||
|
* Avoid double tap detection for non-Bluetooth media button events
|
||||||
|
([#233](https://github.com/androidx/media/issues/233)).
|
||||||
|
* Make `QueueTimeline` more robust in case of a shady legacy session state
|
||||||
|
([#241](https://github.com/androidx/media/issues/241)).
|
||||||
|
* Metadata:
|
||||||
|
* Parse multiple null-separated values from ID3 frames, as permitted by
|
||||||
|
ID3 v2.4.
|
||||||
|
* Add `MediaMetadata.mediaType` to denote the type of content or the type
|
||||||
|
of folder described by the metadata.
|
||||||
|
* Add `MediaMetadata.isBrowsable` as a replacement for
|
||||||
|
`MediaMetadata.folderType`. The folder type will be deprecated in the
|
||||||
|
next release.
|
||||||
|
* Cast extension:
|
||||||
|
* Bump Cast SDK version to 21.2.0.
|
||||||
|
* IMA extension:
|
||||||
|
* Remove player listener of the `ImaServerSideAdInsertionMediaSource` on
|
||||||
|
the application thread to avoid threading issues.
|
||||||
|
* Add a property `focusSkipButtonWhenAvailable` to the
|
||||||
|
`ImaServerSideAdInsertionMediaSource.AdsLoader.Builder` to request
|
||||||
|
focusing the skip button on TV devices and set it to true by default.
|
||||||
|
* Add a method `focusSkipButton()` to the
|
||||||
|
`ImaServerSideAdInsertionMediaSource.AdsLoader` to programmatically
|
||||||
|
request to focus the skip button.
|
||||||
|
* Bump IMA SDK version to 3.29.0.
|
||||||
|
* Demo app:
|
||||||
|
* Request notification permission for download notifications at runtime
|
||||||
|
([#10884](https://github.com/google/ExoPlayer/issues/10884)).
|
||||||
|
|
||||||
### 1.0.0-beta03 (2022-11-22)
|
### 1.0.0-beta03 (2022-11-22)
|
||||||
|
|
||||||
@ -259,15 +356,15 @@ This release corresponds to the
|
|||||||
* Query the platform (API 29+) or assume the audio encoding channel count
|
* Query the platform (API 29+) or assume the audio encoding channel count
|
||||||
for audio passthrough when the format audio channel count is unset,
|
for audio passthrough when the format audio channel count is unset,
|
||||||
which occurs with HLS chunkless preparation
|
which occurs with HLS chunkless preparation
|
||||||
([10204](https://github.com/google/ExoPlayer/issues/10204)).
|
([#10204](https://github.com/google/ExoPlayer/issues/10204)).
|
||||||
* Configure `AudioTrack` with channel mask
|
* Configure `AudioTrack` with channel mask
|
||||||
`AudioFormat.CHANNEL_OUT_7POINT1POINT4` if the decoder outputs 12
|
`AudioFormat.CHANNEL_OUT_7POINT1POINT4` if the decoder outputs 12
|
||||||
channel PCM audio
|
channel PCM audio
|
||||||
([#10322](#https://github.com/google/ExoPlayer/pull/10322).
|
([#10322](#https://github.com/google/ExoPlayer/pull/10322)).
|
||||||
* DRM
|
* DRM
|
||||||
* Ensure the DRM session is always correctly updated when seeking
|
* Ensure the DRM session is always correctly updated when seeking
|
||||||
immediately after a format change
|
immediately after a format change
|
||||||
([10274](https://github.com/google/ExoPlayer/issues/10274)).
|
([#10274](https://github.com/google/ExoPlayer/issues/10274)).
|
||||||
* Text:
|
* Text:
|
||||||
* Change `Player.getCurrentCues()` to return `CueGroup` instead of
|
* Change `Player.getCurrentCues()` to return `CueGroup` instead of
|
||||||
`List<Cue>`.
|
`List<Cue>`.
|
||||||
|
@ -12,8 +12,8 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
project.ext {
|
project.ext {
|
||||||
releaseVersion = '1.0.0-beta03'
|
releaseVersion = '1.0.0-rc01'
|
||||||
releaseVersionCode = 1_000_000_1_03
|
releaseVersionCode = 1_000_000_2_01
|
||||||
minSdkVersion = 16
|
minSdkVersion = 16
|
||||||
appTargetSdkVersion = 33
|
appTargetSdkVersion = 33
|
||||||
// API version before restricting local file access.
|
// API version before restricting local file access.
|
||||||
|
@ -23,6 +23,7 @@
|
|||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
<uses-permission android:name="android.permission.RECEIVE_BOOT_COMPLETED"/>
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
|
||||||
|
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
|
||||||
|
|
||||||
<uses-feature android:name="android.software.leanback" android:required="false"/>
|
<uses-feature android:name="android.software.leanback" android:required="false"/>
|
||||||
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
|
<uses-feature android:name="android.hardware.touchscreen" android:required="false"/>
|
||||||
@ -35,6 +36,7 @@
|
|||||||
android:largeHeap="true"
|
android:largeHeap="true"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
android:requestLegacyExternalStorage="true"
|
android:requestLegacyExternalStorage="true"
|
||||||
|
android:supportsRtl="true"
|
||||||
android:name="androidx.multidex.MultiDexApplication"
|
android:name="androidx.multidex.MultiDexApplication"
|
||||||
tools:targetApi="29">
|
tools:targetApi="29">
|
||||||
|
|
||||||
|
@ -19,6 +19,7 @@ import static com.google.common.base.Preconditions.checkArgument;
|
|||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
|
||||||
|
import android.Manifest;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.SharedPreferences;
|
import android.content.SharedPreferences;
|
||||||
@ -41,8 +42,10 @@ import android.widget.ExpandableListView.OnChildClickListener;
|
|||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
import androidx.annotation.DoNotInline;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.OptIn;
|
import androidx.annotation.OptIn;
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
import androidx.media3.common.MediaItem.ClippingConfiguration;
|
import androidx.media3.common.MediaItem.ClippingConfiguration;
|
||||||
@ -76,6 +79,7 @@ public class SampleChooserActivity extends AppCompatActivity
|
|||||||
private static final String TAG = "SampleChooserActivity";
|
private static final String TAG = "SampleChooserActivity";
|
||||||
private static final String GROUP_POSITION_PREFERENCE_KEY = "sample_chooser_group_position";
|
private static final String GROUP_POSITION_PREFERENCE_KEY = "sample_chooser_group_position";
|
||||||
private static final String CHILD_POSITION_PREFERENCE_KEY = "sample_chooser_child_position";
|
private static final String CHILD_POSITION_PREFERENCE_KEY = "sample_chooser_child_position";
|
||||||
|
private static final int POST_NOTIFICATION_PERMISSION_REQUEST_CODE = 100;
|
||||||
|
|
||||||
private String[] uris;
|
private String[] uris;
|
||||||
private boolean useExtensionRenderers;
|
private boolean useExtensionRenderers;
|
||||||
@ -83,6 +87,8 @@ public class SampleChooserActivity extends AppCompatActivity
|
|||||||
private SampleAdapter sampleAdapter;
|
private SampleAdapter sampleAdapter;
|
||||||
private MenuItem preferExtensionDecodersMenuItem;
|
private MenuItem preferExtensionDecodersMenuItem;
|
||||||
private ExpandableListView sampleListView;
|
private ExpandableListView sampleListView;
|
||||||
|
@Nullable private MediaItem downloadMediaItemWaitingForNotificationPermission;
|
||||||
|
private boolean notificationPermissionToastShown;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate(Bundle savedInstanceState) {
|
public void onCreate(Bundle savedInstanceState) {
|
||||||
@ -172,12 +178,34 @@ public class SampleChooserActivity extends AppCompatActivity
|
|||||||
public void onRequestPermissionsResult(
|
public void onRequestPermissionsResult(
|
||||||
int requestCode, String[] permissions, int[] grantResults) {
|
int requestCode, String[] permissions, int[] grantResults) {
|
||||||
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
super.onRequestPermissionsResult(requestCode, permissions, grantResults);
|
||||||
|
if (requestCode == POST_NOTIFICATION_PERMISSION_REQUEST_CODE) {
|
||||||
|
handlePostNotificationPermissionGrantResults(grantResults);
|
||||||
|
} else {
|
||||||
|
handleExternalStoragePermissionGrantResults(grantResults);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handlePostNotificationPermissionGrantResults(int[] grantResults) {
|
||||||
|
if (!notificationPermissionToastShown
|
||||||
|
&& (grantResults.length == 0 || grantResults[0] != PackageManager.PERMISSION_GRANTED)) {
|
||||||
|
Toast.makeText(
|
||||||
|
getApplicationContext(), R.string.post_notification_not_granted, Toast.LENGTH_LONG)
|
||||||
|
.show();
|
||||||
|
notificationPermissionToastShown = true;
|
||||||
|
}
|
||||||
|
if (downloadMediaItemWaitingForNotificationPermission != null) {
|
||||||
|
// Download with or without permission to post notifications.
|
||||||
|
toggleDownload(downloadMediaItemWaitingForNotificationPermission);
|
||||||
|
downloadMediaItemWaitingForNotificationPermission = null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void handleExternalStoragePermissionGrantResults(int[] grantResults) {
|
||||||
if (grantResults.length == 0) {
|
if (grantResults.length == 0) {
|
||||||
// Empty results are triggered if a permission is requested while another request was already
|
// Empty results are triggered if a permission is requested while another request was already
|
||||||
// pending and can be safely ignored in this case.
|
// pending and can be safely ignored in this case.
|
||||||
return;
|
return;
|
||||||
}
|
} else if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
|
||||||
loadSample();
|
loadSample();
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(getApplicationContext(), R.string.sample_list_load_error, Toast.LENGTH_LONG)
|
Toast.makeText(getApplicationContext(), R.string.sample_list_load_error, Toast.LENGTH_LONG)
|
||||||
@ -244,13 +272,24 @@ public class SampleChooserActivity extends AppCompatActivity
|
|||||||
if (downloadUnsupportedStringId != 0) {
|
if (downloadUnsupportedStringId != 0) {
|
||||||
Toast.makeText(getApplicationContext(), downloadUnsupportedStringId, Toast.LENGTH_LONG)
|
Toast.makeText(getApplicationContext(), downloadUnsupportedStringId, Toast.LENGTH_LONG)
|
||||||
.show();
|
.show();
|
||||||
|
} else if (!notificationPermissionToastShown
|
||||||
|
&& Util.SDK_INT >= 33
|
||||||
|
&& checkSelfPermission(Api33.getPostNotificationPermissionString())
|
||||||
|
!= PackageManager.PERMISSION_GRANTED) {
|
||||||
|
downloadMediaItemWaitingForNotificationPermission = playlistHolder.mediaItems.get(0);
|
||||||
|
requestPermissions(
|
||||||
|
new String[] {Api33.getPostNotificationPermissionString()},
|
||||||
|
/* requestCode= */ POST_NOTIFICATION_PERMISSION_REQUEST_CODE);
|
||||||
} else {
|
} else {
|
||||||
|
toggleDownload(playlistHolder.mediaItems.get(0));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void toggleDownload(MediaItem mediaItem) {
|
||||||
RenderersFactory renderersFactory =
|
RenderersFactory renderersFactory =
|
||||||
DemoUtil.buildRenderersFactory(
|
DemoUtil.buildRenderersFactory(
|
||||||
/* context= */ this, isNonNullAndChecked(preferExtensionDecodersMenuItem));
|
/* context= */ this, isNonNullAndChecked(preferExtensionDecodersMenuItem));
|
||||||
downloadTracker.toggleDownload(
|
downloadTracker.toggleDownload(getSupportFragmentManager(), mediaItem, renderersFactory);
|
||||||
getSupportFragmentManager(), playlistHolder.mediaItems.get(0), renderersFactory);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private int getDownloadUnsupportedStringId(PlaylistHolder playlistHolder) {
|
private int getDownloadUnsupportedStringId(PlaylistHolder playlistHolder) {
|
||||||
@ -630,4 +669,13 @@ public class SampleChooserActivity extends AppCompatActivity
|
|||||||
this.playlists = new ArrayList<>();
|
this.playlists = new ArrayList<>();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@RequiresApi(33)
|
||||||
|
private static class Api33 {
|
||||||
|
|
||||||
|
@DoNotInline
|
||||||
|
public static String getPostNotificationPermissionString() {
|
||||||
|
return Manifest.permission.POST_NOTIFICATIONS;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,8 @@
|
|||||||
|
|
||||||
<string name="sample_list_load_error">One or more sample lists failed to load</string>
|
<string name="sample_list_load_error">One or more sample lists failed to load</string>
|
||||||
|
|
||||||
|
<string name="post_notification_not_granted">Notifications suppressed. Grant permission to see download notifications.</string>
|
||||||
|
|
||||||
<string name="download_start_error">Failed to start download</string>
|
<string name="download_start_error">Failed to start download</string>
|
||||||
|
|
||||||
<string name="download_start_error_offline_license">Failed to obtain offline license</string>
|
<string name="download_start_error_offline_license">Failed to obtain offline license</string>
|
||||||
|
@ -31,7 +31,7 @@ android {
|
|||||||
versionName project.ext.releaseVersion
|
versionName project.ext.releaseVersion
|
||||||
versionCode project.ext.releaseVersionCode
|
versionCode project.ext.releaseVersionCode
|
||||||
minSdkVersion project.ext.minSdkVersion
|
minSdkVersion project.ext.minSdkVersion
|
||||||
targetSdkVersion project.ext.targetSdkVersion
|
targetSdkVersion project.ext.appTargetSdkVersion
|
||||||
multiDexEnabled true
|
multiDexEnabled true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -38,7 +38,7 @@ import com.google.common.util.concurrent.ListenableFuture
|
|||||||
class MainActivity : AppCompatActivity() {
|
class MainActivity : AppCompatActivity() {
|
||||||
private lateinit var browserFuture: ListenableFuture<MediaBrowser>
|
private lateinit var browserFuture: ListenableFuture<MediaBrowser>
|
||||||
private val browser: MediaBrowser?
|
private val browser: MediaBrowser?
|
||||||
get() = if (browserFuture.isDone) browserFuture.get() else null
|
get() = if (browserFuture.isDone && !browserFuture.isCancelled) browserFuture.get() else null
|
||||||
|
|
||||||
private lateinit var mediaListAdapter: FolderMediaItemArrayAdapter
|
private lateinit var mediaListAdapter: FolderMediaItemArrayAdapter
|
||||||
private lateinit var mediaListView: ListView
|
private lateinit var mediaListView: ListView
|
||||||
|
@ -20,11 +20,6 @@ import android.net.Uri
|
|||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
import androidx.media3.common.MediaItem.SubtitleConfiguration
|
import androidx.media3.common.MediaItem.SubtitleConfiguration
|
||||||
import androidx.media3.common.MediaMetadata
|
import androidx.media3.common.MediaMetadata
|
||||||
import androidx.media3.common.MediaMetadata.FOLDER_TYPE_ALBUMS
|
|
||||||
import androidx.media3.common.MediaMetadata.FOLDER_TYPE_ARTISTS
|
|
||||||
import androidx.media3.common.MediaMetadata.FOLDER_TYPE_GENRES
|
|
||||||
import androidx.media3.common.MediaMetadata.FOLDER_TYPE_MIXED
|
|
||||||
import androidx.media3.common.MediaMetadata.FOLDER_TYPE_NONE
|
|
||||||
import androidx.media3.common.util.Util
|
import androidx.media3.common.util.Util
|
||||||
import com.google.common.collect.ImmutableList
|
import com.google.common.collect.ImmutableList
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
@ -67,7 +62,8 @@ object MediaItemTree {
|
|||||||
title: String,
|
title: String,
|
||||||
mediaId: String,
|
mediaId: String,
|
||||||
isPlayable: Boolean,
|
isPlayable: Boolean,
|
||||||
@MediaMetadata.FolderType folderType: Int,
|
isBrowsable: Boolean,
|
||||||
|
mediaType: @MediaMetadata.MediaType Int,
|
||||||
subtitleConfigurations: List<SubtitleConfiguration> = mutableListOf(),
|
subtitleConfigurations: List<SubtitleConfiguration> = mutableListOf(),
|
||||||
album: String? = null,
|
album: String? = null,
|
||||||
artist: String? = null,
|
artist: String? = null,
|
||||||
@ -81,9 +77,10 @@ object MediaItemTree {
|
|||||||
.setTitle(title)
|
.setTitle(title)
|
||||||
.setArtist(artist)
|
.setArtist(artist)
|
||||||
.setGenre(genre)
|
.setGenre(genre)
|
||||||
.setFolderType(folderType)
|
.setIsBrowsable(isBrowsable)
|
||||||
.setIsPlayable(isPlayable)
|
.setIsPlayable(isPlayable)
|
||||||
.setArtworkUri(imageUri)
|
.setArtworkUri(imageUri)
|
||||||
|
.setMediaType(mediaType)
|
||||||
.build()
|
.build()
|
||||||
|
|
||||||
return MediaItem.Builder()
|
return MediaItem.Builder()
|
||||||
@ -109,7 +106,8 @@ object MediaItemTree {
|
|||||||
title = "Root Folder",
|
title = "Root Folder",
|
||||||
mediaId = ROOT_ID,
|
mediaId = ROOT_ID,
|
||||||
isPlayable = false,
|
isPlayable = false,
|
||||||
folderType = FOLDER_TYPE_MIXED
|
isBrowsable = true,
|
||||||
|
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_MIXED
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
treeNodes[ALBUM_ID] =
|
treeNodes[ALBUM_ID] =
|
||||||
@ -118,7 +116,8 @@ object MediaItemTree {
|
|||||||
title = "Album Folder",
|
title = "Album Folder",
|
||||||
mediaId = ALBUM_ID,
|
mediaId = ALBUM_ID,
|
||||||
isPlayable = false,
|
isPlayable = false,
|
||||||
folderType = FOLDER_TYPE_MIXED
|
isBrowsable = true,
|
||||||
|
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_ALBUMS
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
treeNodes[ARTIST_ID] =
|
treeNodes[ARTIST_ID] =
|
||||||
@ -127,7 +126,8 @@ object MediaItemTree {
|
|||||||
title = "Artist Folder",
|
title = "Artist Folder",
|
||||||
mediaId = ARTIST_ID,
|
mediaId = ARTIST_ID,
|
||||||
isPlayable = false,
|
isPlayable = false,
|
||||||
folderType = FOLDER_TYPE_MIXED
|
isBrowsable = true,
|
||||||
|
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_ARTISTS
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
treeNodes[GENRE_ID] =
|
treeNodes[GENRE_ID] =
|
||||||
@ -136,7 +136,8 @@ object MediaItemTree {
|
|||||||
title = "Genre Folder",
|
title = "Genre Folder",
|
||||||
mediaId = GENRE_ID,
|
mediaId = GENRE_ID,
|
||||||
isPlayable = false,
|
isPlayable = false,
|
||||||
folderType = FOLDER_TYPE_MIXED
|
isBrowsable = true,
|
||||||
|
mediaType = MediaMetadata.MEDIA_TYPE_FOLDER_GENRES
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
treeNodes[ROOT_ID]!!.addChild(ALBUM_ID)
|
treeNodes[ROOT_ID]!!.addChild(ALBUM_ID)
|
||||||
@ -188,7 +189,8 @@ object MediaItemTree {
|
|||||||
title = title,
|
title = title,
|
||||||
mediaId = idInTree,
|
mediaId = idInTree,
|
||||||
isPlayable = true,
|
isPlayable = true,
|
||||||
folderType = FOLDER_TYPE_NONE,
|
isBrowsable = false,
|
||||||
|
mediaType = MediaMetadata.MEDIA_TYPE_MUSIC,
|
||||||
subtitleConfigurations,
|
subtitleConfigurations,
|
||||||
album = album,
|
album = album,
|
||||||
artist = artist,
|
artist = artist,
|
||||||
@ -207,7 +209,8 @@ object MediaItemTree {
|
|||||||
title = album,
|
title = album,
|
||||||
mediaId = albumFolderIdInTree,
|
mediaId = albumFolderIdInTree,
|
||||||
isPlayable = true,
|
isPlayable = true,
|
||||||
folderType = FOLDER_TYPE_ALBUMS,
|
isBrowsable = true,
|
||||||
|
mediaType = MediaMetadata.MEDIA_TYPE_ALBUM,
|
||||||
subtitleConfigurations
|
subtitleConfigurations
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -223,7 +226,8 @@ object MediaItemTree {
|
|||||||
title = artist,
|
title = artist,
|
||||||
mediaId = artistFolderIdInTree,
|
mediaId = artistFolderIdInTree,
|
||||||
isPlayable = true,
|
isPlayable = true,
|
||||||
folderType = FOLDER_TYPE_ARTISTS,
|
isBrowsable = true,
|
||||||
|
mediaType = MediaMetadata.MEDIA_TYPE_ARTIST,
|
||||||
subtitleConfigurations
|
subtitleConfigurations
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -239,7 +243,8 @@ object MediaItemTree {
|
|||||||
title = genre,
|
title = genre,
|
||||||
mediaId = genreFolderIdInTree,
|
mediaId = genreFolderIdInTree,
|
||||||
isPlayable = true,
|
isPlayable = true,
|
||||||
folderType = FOLDER_TYPE_GENRES,
|
isBrowsable = true,
|
||||||
|
mediaType = MediaMetadata.MEDIA_TYPE_GENRE,
|
||||||
subtitleConfigurations
|
subtitleConfigurations
|
||||||
)
|
)
|
||||||
)
|
)
|
||||||
@ -262,7 +267,7 @@ object MediaItemTree {
|
|||||||
|
|
||||||
fun getRandomItem(): MediaItem {
|
fun getRandomItem(): MediaItem {
|
||||||
var curRoot = getRootItem()
|
var curRoot = getRootItem()
|
||||||
while (curRoot.mediaMetadata.folderType != FOLDER_TYPE_NONE) {
|
while (curRoot.mediaMetadata.isBrowsable == true) {
|
||||||
val children = getChildren(curRoot.mediaId)!!
|
val children = getChildren(curRoot.mediaId)!!
|
||||||
curRoot = children.random()
|
curRoot = children.random()
|
||||||
}
|
}
|
||||||
|
@ -15,22 +15,21 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.demo.session
|
package androidx.media3.demo.session
|
||||||
|
|
||||||
import android.app.PendingIntent.FLAG_IMMUTABLE
|
import android.app.NotificationChannel
|
||||||
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
import android.app.NotificationManager
|
||||||
|
import android.app.PendingIntent.*
|
||||||
import android.app.TaskStackBuilder
|
import android.app.TaskStackBuilder
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
|
import androidx.core.app.NotificationCompat
|
||||||
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.media3.common.AudioAttributes
|
import androidx.media3.common.AudioAttributes
|
||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
|
import androidx.media3.common.util.Util
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
import androidx.media3.session.CommandButton
|
import androidx.media3.session.*
|
||||||
import androidx.media3.session.LibraryResult
|
|
||||||
import androidx.media3.session.MediaLibraryService
|
|
||||||
import androidx.media3.session.MediaSession
|
|
||||||
import androidx.media3.session.MediaSession.ControllerInfo
|
import androidx.media3.session.MediaSession.ControllerInfo
|
||||||
import androidx.media3.session.SessionCommand
|
|
||||||
import androidx.media3.session.SessionResult
|
|
||||||
import com.google.common.collect.ImmutableList
|
import com.google.common.collect.ImmutableList
|
||||||
import com.google.common.util.concurrent.Futures
|
import com.google.common.util.concurrent.Futures
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
@ -51,6 +50,8 @@ class PlaybackService : MediaLibraryService() {
|
|||||||
"android.media3.session.demo.SHUFFLE_ON"
|
"android.media3.session.demo.SHUFFLE_ON"
|
||||||
private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF =
|
private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF =
|
||||||
"android.media3.session.demo.SHUFFLE_OFF"
|
"android.media3.session.demo.SHUFFLE_OFF"
|
||||||
|
private const val NOTIFICATION_ID = 123
|
||||||
|
private const val CHANNEL_ID = "demo_session_notification_channel_id"
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
@ -66,15 +67,23 @@ class PlaybackService : MediaLibraryService() {
|
|||||||
)
|
)
|
||||||
customLayout = ImmutableList.of(customCommands[0])
|
customLayout = ImmutableList.of(customCommands[0])
|
||||||
initializeSessionAndPlayer()
|
initializeSessionAndPlayer()
|
||||||
|
setListener(MediaSessionServiceListener())
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onGetSession(controllerInfo: ControllerInfo): MediaLibrarySession {
|
override fun onGetSession(controllerInfo: ControllerInfo): MediaLibrarySession {
|
||||||
return mediaLibrarySession
|
return mediaLibrarySession
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onTaskRemoved(rootIntent: Intent?) {
|
||||||
|
if (!player.playWhenReady) {
|
||||||
|
stopSelf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
player.release()
|
player.release()
|
||||||
mediaLibrarySession.release()
|
mediaLibrarySession.release()
|
||||||
|
clearListener()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,4 +262,49 @@ class PlaybackService : MediaLibraryService() {
|
|||||||
private fun ignoreFuture(customLayout: ListenableFuture<SessionResult>) {
|
private fun ignoreFuture(customLayout: ListenableFuture<SessionResult>) {
|
||||||
/* Do nothing. */
|
/* Do nothing. */
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private inner class MediaSessionServiceListener : Listener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This method is only required to be implemented on Android 12 or above when an attempt is made
|
||||||
|
* by a media controller to resume playback when the {@link MediaSessionService} is in the
|
||||||
|
* background.
|
||||||
|
*/
|
||||||
|
override fun onForegroundServiceStartNotAllowedException() {
|
||||||
|
val notificationManagerCompat = NotificationManagerCompat.from(this@PlaybackService)
|
||||||
|
ensureNotificationChannel(notificationManagerCompat)
|
||||||
|
val pendingIntent =
|
||||||
|
TaskStackBuilder.create(this@PlaybackService).run {
|
||||||
|
addNextIntent(Intent(this@PlaybackService, MainActivity::class.java))
|
||||||
|
|
||||||
|
val immutableFlag = if (Build.VERSION.SDK_INT >= 23) FLAG_IMMUTABLE else 0
|
||||||
|
getPendingIntent(0, immutableFlag or FLAG_UPDATE_CURRENT)
|
||||||
|
}
|
||||||
|
val builder =
|
||||||
|
NotificationCompat.Builder(this@PlaybackService, CHANNEL_ID)
|
||||||
|
.setContentIntent(pendingIntent)
|
||||||
|
.setSmallIcon(R.drawable.media3_notification_small_icon)
|
||||||
|
.setContentTitle(getString(R.string.notification_content_title))
|
||||||
|
.setStyle(
|
||||||
|
NotificationCompat.BigTextStyle().bigText(getString(R.string.notification_content_text))
|
||||||
|
)
|
||||||
|
.setPriority(NotificationCompat.PRIORITY_DEFAULT)
|
||||||
|
.setAutoCancel(true)
|
||||||
|
notificationManagerCompat.notify(NOTIFICATION_ID, builder.build())
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun ensureNotificationChannel(notificationManagerCompat: NotificationManagerCompat) {
|
||||||
|
if (Util.SDK_INT < 26 || notificationManagerCompat.getNotificationChannel(CHANNEL_ID) != null) {
|
||||||
|
return
|
||||||
|
}
|
||||||
|
|
||||||
|
val channel =
|
||||||
|
NotificationChannel(
|
||||||
|
CHANNEL_ID,
|
||||||
|
getString(R.string.notification_channel_name),
|
||||||
|
NotificationManager.IMPORTANCE_DEFAULT
|
||||||
|
)
|
||||||
|
notificationManagerCompat.createNotificationChannel(channel)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,4 +24,9 @@
|
|||||||
<string name="no_item_prompt">
|
<string name="no_item_prompt">
|
||||||
"! No media in the play list !\nPlease try to add more from browser"
|
"! No media in the play list !\nPlease try to add more from browser"
|
||||||
</string>
|
</string>
|
||||||
|
<string name="notification_content_title">Playback cannot be resumed</string>
|
||||||
|
<string name="notification_content_text">Press on the play button on the media notification if it
|
||||||
|
is still present, otherwise please open the app to start the playback and re-connect the session
|
||||||
|
to the controller</string>
|
||||||
|
<string name="notification_channel_name">Playback cannot be resumed</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -1,386 +0,0 @@
|
|||||||
#!/bin/bash
|
|
||||||
# Copyright (C) 2022 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.
|
|
||||||
##
|
|
||||||
shopt -s extglob
|
|
||||||
|
|
||||||
PACKAGE_MAPPINGS='com.google.android.exoplayer2 androidx.media3.exoplayer
|
|
||||||
com.google.android.exoplayer2.analytics androidx.media3.exoplayer.analytics
|
|
||||||
com.google.android.exoplayer2.audio androidx.media3.exoplayer.audio
|
|
||||||
com.google.android.exoplayer2.castdemo androidx.media3.demo.cast
|
|
||||||
com.google.android.exoplayer2.database androidx.media3.database
|
|
||||||
com.google.android.exoplayer2.decoder androidx.media3.decoder
|
|
||||||
com.google.android.exoplayer2.demo androidx.media3.demo.main
|
|
||||||
com.google.android.exoplayer2.drm androidx.media3.exoplayer.drm
|
|
||||||
com.google.android.exoplayer2.ext.av1 androidx.media3.decoder.av1
|
|
||||||
com.google.android.exoplayer2.ext.cast androidx.media3.cast
|
|
||||||
com.google.android.exoplayer2.ext.cronet androidx.media3.datasource.cronet
|
|
||||||
com.google.android.exoplayer2.ext.ffmpeg androidx.media3.decoder.ffmpeg
|
|
||||||
com.google.android.exoplayer2.ext.flac androidx.media3.decoder.flac
|
|
||||||
com.google.android.exoplayer2.ext.ima androidx.media3.exoplayer.ima
|
|
||||||
com.google.android.exoplayer2.ext.leanback androidx.media3.ui.leanback
|
|
||||||
com.google.android.exoplayer2.ext.okhttp androidx.media3.datasource.okhttp
|
|
||||||
com.google.android.exoplayer2.ext.opus androidx.media3.decoder.opus
|
|
||||||
com.google.android.exoplayer2.ext.rtmp androidx.media3.datasource.rtmp
|
|
||||||
com.google.android.exoplayer2.ext.vp9 androidx.media3.decoder.vp9
|
|
||||||
com.google.android.exoplayer2.ext.workmanager androidx.media3.exoplayer.workmanager
|
|
||||||
com.google.android.exoplayer2.extractor androidx.media3.extractor
|
|
||||||
com.google.android.exoplayer2.gldemo androidx.media3.demo.gl
|
|
||||||
com.google.android.exoplayer2.mediacodec androidx.media3.exoplayer.mediacodec
|
|
||||||
com.google.android.exoplayer2.metadata androidx.media3.extractor.metadata
|
|
||||||
com.google.android.exoplayer2.offline androidx.media3.exoplayer.offline
|
|
||||||
com.google.android.exoplayer2.playbacktests androidx.media3.test.exoplayer.playback
|
|
||||||
com.google.android.exoplayer2.robolectric androidx.media3.test.utils.robolectric
|
|
||||||
com.google.android.exoplayer2.scheduler androidx.media3.exoplayer.scheduler
|
|
||||||
com.google.android.exoplayer2.source androidx.media3.exoplayer.source
|
|
||||||
com.google.android.exoplayer2.source.dash androidx.media3.exoplayer.dash
|
|
||||||
com.google.android.exoplayer2.source.hls androidx.media3.exoplayer.hls
|
|
||||||
com.google.android.exoplayer2.source.rtsp androidx.media3.exoplayer.rtsp
|
|
||||||
com.google.android.exoplayer2.source.smoothstreaming androidx.media3.exoplayer.smoothstreaming
|
|
||||||
com.google.android.exoplayer2.surfacedemo androidx.media3.demo.surface
|
|
||||||
com.google.android.exoplayer2.testdata androidx.media3.test.data
|
|
||||||
com.google.android.exoplayer2.testutil androidx.media3.test.utils
|
|
||||||
com.google.android.exoplayer2.text androidx.media3.extractor.text
|
|
||||||
com.google.android.exoplayer2.trackselection androidx.media3.exoplayer.trackselection
|
|
||||||
com.google.android.exoplayer2.transformer androidx.media3.transformer
|
|
||||||
com.google.android.exoplayer2.transformerdemo androidx.media3.demo.transformer
|
|
||||||
com.google.android.exoplayer2.ui androidx.media3.ui
|
|
||||||
com.google.android.exoplayer2.upstream androidx.media3.datasource
|
|
||||||
com.google.android.exoplayer2.upstream.cache androidx.media3.datasource.cache
|
|
||||||
com.google.android.exoplayer2.upstream.crypto androidx.media3.exoplayer.upstream.crypto
|
|
||||||
com.google.android.exoplayer2.util androidx.media3.common.util
|
|
||||||
com.google.android.exoplayer2.util androidx.media3.exoplayer.util
|
|
||||||
com.google.android.exoplayer2.video androidx.media3.exoplayer.video'
|
|
||||||
|
|
||||||
|
|
||||||
CLASS_RENAMINGS='com.google.android.exoplayer2.ui.StyledPlayerView androidx.media3.ui.PlayerView
|
|
||||||
StyledPlayerView PlayerView
|
|
||||||
com.google.android.exoplayer2.ui.StyledPlayerControlView androidx.media3.ui.PlayerControlView
|
|
||||||
StyledPlayerControlView PlayerControlView
|
|
||||||
com.google.android.exoplayer2.ExoPlayerLibraryInfo androidx.media3.common.MediaLibraryInfo
|
|
||||||
ExoPlayerLibraryInfo MediaLibraryInfo
|
|
||||||
com.google.android.exoplayer2.SimpleExoPlayer androidx.media3.exoplayer.ExoPlayer
|
|
||||||
SimpleExoPlayer ExoPlayer'
|
|
||||||
|
|
||||||
CLASS_MAPPINGS='com.google.android.exoplayer2.text.span androidx.media3.common.text HorizontalTextInVerticalContextSpan LanguageFeatureSpan RubySpan SpanUtil TextAnnotation TextEmphasisSpan
|
|
||||||
com.google.android.exoplayer2.text androidx.media3.common.text CueGroup Cue
|
|
||||||
com.google.android.exoplayer2.text androidx.media3.exoplayer.text ExoplayerCuesDecoder SubtitleDecoderFactory TextOutput TextRenderer
|
|
||||||
com.google.android.exoplayer2.upstream.crypto androidx.media3.datasource AesCipherDataSource AesCipherDataSink AesFlushingCipher
|
|
||||||
com.google.android.exoplayer2.util androidx.media3.common.util AtomicFile Assertions BundleableUtil BundleUtil Clock ClosedSource CodecSpecificDataUtil ColorParser ConditionVariable Consumer CopyOnWriteMultiset EGLSurfaceTexture GlProgram GlUtil HandlerWrapper LibraryLoader ListenerSet Log LongArray MediaFormatUtil NetworkTypeObserver NonNullApi NotificationUtil ParsableBitArray ParsableByteArray RepeatModeUtil RunnableFutureTask SystemClock SystemHandlerWrapper TimedValueQueue TimestampAdjuster TraceUtil UnknownNull UnstableApi UriUtil Util XmlPullParserUtil
|
|
||||||
com.google.android.exoplayer2.util androidx.media3.common ErrorMessageProvider FlagSet FileTypes MimeTypes PriorityTaskManager
|
|
||||||
com.google.android.exoplayer2.metadata androidx.media3.common Metadata
|
|
||||||
com.google.android.exoplayer2.metadata androidx.media3.exoplayer.metadata MetadataDecoderFactory MetadataOutput MetadataRenderer
|
|
||||||
com.google.android.exoplayer2.audio androidx.media3.common AudioAttributes AuxEffectInfo
|
|
||||||
com.google.android.exoplayer2.ui androidx.media3.common AdOverlayInfo AdViewProvider
|
|
||||||
com.google.android.exoplayer2.source.ads androidx.media3.common AdPlaybackState
|
|
||||||
com.google.android.exoplayer2.source androidx.media3.common MediaPeriodId TrackGroup
|
|
||||||
com.google.android.exoplayer2.offline androidx.media3.common StreamKey
|
|
||||||
com.google.android.exoplayer2.ui androidx.media3.exoplayer.offline DownloadNotificationHelper
|
|
||||||
com.google.android.exoplayer2.trackselection androidx.media3.common TrackSelectionParameters TrackSelectionOverride
|
|
||||||
com.google.android.exoplayer2.video androidx.media3.common ColorInfo VideoSize
|
|
||||||
com.google.android.exoplayer2.upstream androidx.media3.common DataReader
|
|
||||||
com.google.android.exoplayer2.upstream androidx.media3.exoplayer.upstream Allocation Allocator BandwidthMeter CachedRegionTracker DefaultAllocator DefaultBandwidthMeter DefaultLoadErrorHandlingPolicy Loader LoaderErrorThrower ParsingLoadable SlidingPercentile TimeToFirstByteEstimator
|
|
||||||
com.google.android.exoplayer2.audio androidx.media3.extractor AacUtil Ac3Util Ac4Util DtsUtil MpegAudioUtil OpusUtil WavUtil
|
|
||||||
com.google.android.exoplayer2.util androidx.media3.extractor NalUnitUtil ParsableNalUnitBitArray
|
|
||||||
com.google.android.exoplayer2.video androidx.media3.extractor AvcConfig DolbyVisionConfig HevcConfig
|
|
||||||
com.google.android.exoplayer2.decoder androidx.media3.exoplayer DecoderCounters DecoderReuseEvaluation
|
|
||||||
com.google.android.exoplayer2.util androidx.media3.exoplayer MediaClock StandaloneMediaClock
|
|
||||||
com.google.android.exoplayer2 androidx.media3.exoplayer FormatHolder PlayerMessage
|
|
||||||
com.google.android.exoplayer2 androidx.media3.common BasePlayer BundleListRetriever Bundleable ControlDispatcher C DefaultControlDispatcher DeviceInfo ErrorMessageProvider ExoPlayerLibraryInfo Format ForwardingPlayer HeartRating IllegalSeekPositionException MediaItem MediaMetadata ParserException PercentageRating PlaybackException PlaybackParameters Player PositionInfo Rating StarRating ThumbRating Timeline Tracks
|
|
||||||
com.google.android.exoplayer2.drm androidx.media3.common DrmInitData'
|
|
||||||
|
|
||||||
DEPENDENCY_MAPPINGS='exoplayer media3-exoplayer
|
|
||||||
exoplayer-common media3-common
|
|
||||||
exoplayer-core media3-exoplayer
|
|
||||||
exoplayer-dash media3-exoplayer-dash
|
|
||||||
exoplayer-database media3-database
|
|
||||||
exoplayer-datasource media-datasource
|
|
||||||
exoplayer-decoder media3-decoder
|
|
||||||
exoplayer-extractor media3-extractor
|
|
||||||
exoplayer-hls media3-exoplayer-hls
|
|
||||||
exoplayer-robolectricutils media3-test-utils-robolectric
|
|
||||||
exoplayer-rtsp media3-exoplayer-rtsp
|
|
||||||
exoplayer-smoothstreaming media3-exoplayer-smoothstreaming
|
|
||||||
exoplayer-testutils media3-test-utils
|
|
||||||
exoplayer-transformer media3-transformer
|
|
||||||
exoplayer-ui media3-ui
|
|
||||||
extension-cast media3-cast
|
|
||||||
extension-cronet media3-datasource-cronet
|
|
||||||
extension-ima media3-exoplayer-ima
|
|
||||||
extension-leanback media3-ui-leanback
|
|
||||||
extension-okhttp media3-datasource-okhttp
|
|
||||||
extension-rtmp media3-datasource-rtmp
|
|
||||||
extension-workmanager media3-exoplayer-workmanager'
|
|
||||||
|
|
||||||
# Rewrites classes, packages and dependencies from the legacy ExoPlayer package structure
|
|
||||||
# to androidx.media3 structure.
|
|
||||||
|
|
||||||
MEDIA3_VERSION="1.0.0-beta02"
|
|
||||||
LEGACY_PEER_VERSION="2.18.1"
|
|
||||||
|
|
||||||
function usage() {
|
|
||||||
echo "usage: $0 [-p|-c|-d|-v]|[-m|-l [-x <path>] [-f] PROJECT_ROOT]"
|
|
||||||
echo " PROJECT_ROOT: path to your project root (location of 'gradlew')"
|
|
||||||
echo " -p: list package mappings and then exit"
|
|
||||||
echo " -c: list class mappings (precedence over package mappings) and then exit"
|
|
||||||
echo " -d: list dependency mappings and then exit"
|
|
||||||
echo " -m: migrate packages, classes and dependencies to AndroidX Media3"
|
|
||||||
echo " -l: list files that will be considered for rewrite and then exit"
|
|
||||||
echo " -x: exclude the path from the list of file to be changed: 'app/src/test'"
|
|
||||||
echo " -f: force the action even when validation fails"
|
|
||||||
echo " -v: print the exoplayer2/media3 version strings of this script and exit"
|
|
||||||
echo " --noclean : Do not call './gradlew clean' in project directory."
|
|
||||||
echo " -h, --help: show this help text"
|
|
||||||
}
|
|
||||||
|
|
||||||
function print_pairs {
|
|
||||||
while read -r line;
|
|
||||||
do
|
|
||||||
IFS=' ' read -ra PAIR <<< "$line"
|
|
||||||
printf "%-55s %-30s\n" "${PAIR[0]}" "${PAIR[1]}"
|
|
||||||
done <<< "$(echo "$@")"
|
|
||||||
}
|
|
||||||
|
|
||||||
function print_class_mappings {
|
|
||||||
while read -r mapping;
|
|
||||||
do
|
|
||||||
old=$(echo "$mapping" | cut -d ' ' -f1)
|
|
||||||
new=$(echo "$mapping" | cut -d ' ' -f2)
|
|
||||||
classes=$(echo "$mapping" | cut -d ' ' -f3-)
|
|
||||||
for clazz in $classes;
|
|
||||||
do
|
|
||||||
printf "%-80s %-30s\n" "$old.$clazz" "$new.$clazz"
|
|
||||||
done
|
|
||||||
done <<< "$(echo "$CLASS_MAPPINGS" | sort)"
|
|
||||||
}
|
|
||||||
|
|
||||||
ERROR_COUNTER=0
|
|
||||||
VALIDATION_ERRORS=''
|
|
||||||
|
|
||||||
function add_validation_error {
|
|
||||||
let ERROR_COUNTER++
|
|
||||||
VALIDATION_ERRORS+="\033[31m[$ERROR_COUNTER] ->\033[0m ${1}"
|
|
||||||
}
|
|
||||||
|
|
||||||
function validate_exoplayer_version() {
|
|
||||||
has_exoplayer_dependency=''
|
|
||||||
while read -r file;
|
|
||||||
do
|
|
||||||
local version
|
|
||||||
version=$(grep -m 1 "com\.google\.android\.exoplayer:" "$file" | cut -d ":" -f3 | tr -d \" | tr -d \')
|
|
||||||
if [[ ! -z $version ]] && [[ ! "$version" =~ $LEGACY_PEER_VERSION ]];
|
|
||||||
then
|
|
||||||
add_validation_error "The version does not match '$LEGACY_PEER_VERSION'. \
|
|
||||||
Update to '$LEGACY_PEER_VERSION' or use the migration script matching your \
|
|
||||||
current version. Current version '$version' found in\n $file\n"
|
|
||||||
fi
|
|
||||||
done <<< "$(find . -type f -name "build.gradle")"
|
|
||||||
}
|
|
||||||
|
|
||||||
function validate_string_not_contained {
|
|
||||||
local pattern=$1 # regex
|
|
||||||
local failure_message=$2
|
|
||||||
while read -r file;
|
|
||||||
do
|
|
||||||
if grep -q -e "$pattern" "$file";
|
|
||||||
then
|
|
||||||
add_validation_error "$failure_message:\n $file\n"
|
|
||||||
fi
|
|
||||||
done <<< "$files"
|
|
||||||
}
|
|
||||||
|
|
||||||
function validate_string_patterns {
|
|
||||||
validate_string_not_contained \
|
|
||||||
'com\.google\.android\.exoplayer2\..*\*' \
|
|
||||||
'Replace wildcard import statements with fully qualified import statements';
|
|
||||||
validate_string_not_contained \
|
|
||||||
'com\.google\.android\.exoplayer2\.ui\.PlayerView' \
|
|
||||||
'Migrate PlayerView to StyledPlayerView before migrating';
|
|
||||||
validate_string_not_contained \
|
|
||||||
'LegacyPlayerView' \
|
|
||||||
'Migrate LegacyPlayerView to StyledPlayerView before migrating';
|
|
||||||
validate_string_not_contained \
|
|
||||||
'com\.google\.android\.exoplayer2\.ext\.mediasession' \
|
|
||||||
'The MediaSessionConnector is integrated in androidx.media3.session.MediaSession'
|
|
||||||
}
|
|
||||||
|
|
||||||
SED_CMD_INPLACE='sed -i '
|
|
||||||
if [[ "$OSTYPE" == "darwin"* ]]; then
|
|
||||||
SED_CMD_INPLACE="sed -i '' "
|
|
||||||
fi
|
|
||||||
|
|
||||||
MIGRATE_FILES='1'
|
|
||||||
LIST_FILES_ONLY='1'
|
|
||||||
PRINT_CLASS_MAPPINGS='1'
|
|
||||||
PRINT_PACKAGE_MAPPINGS='1'
|
|
||||||
PRINT_DEPENDENCY_MAPPINGS='1'
|
|
||||||
PRINT_VERSION='1'
|
|
||||||
NO_CLEAN='1'
|
|
||||||
FORCE='1'
|
|
||||||
IGNORE_VERSION='1'
|
|
||||||
EXCLUDED_PATHS=''
|
|
||||||
|
|
||||||
while [[ $1 =~ ^-.* ]];
|
|
||||||
do
|
|
||||||
case "$1" in
|
|
||||||
-m ) MIGRATE_FILES='';;
|
|
||||||
-l ) LIST_FILES_ONLY='';;
|
|
||||||
-c ) PRINT_CLASS_MAPPINGS='';;
|
|
||||||
-p ) PRINT_PACKAGE_MAPPINGS='';;
|
|
||||||
-d ) PRINT_DEPENDENCY_MAPPINGS='';;
|
|
||||||
-v ) PRINT_VERSION='';;
|
|
||||||
-f ) FORCE='';;
|
|
||||||
-x ) shift; EXCLUDED_PATHS="$(printf "%s\n%s" $EXCLUDED_PATHS $1)";;
|
|
||||||
--noclean ) NO_CLEAN='';;
|
|
||||||
* ) usage && exit 1;;
|
|
||||||
esac
|
|
||||||
shift
|
|
||||||
done
|
|
||||||
|
|
||||||
if [[ -z $PRINT_DEPENDENCY_MAPPINGS ]];
|
|
||||||
then
|
|
||||||
print_pairs "$DEPENDENCY_MAPPINGS"
|
|
||||||
exit 0
|
|
||||||
elif [[ -z $PRINT_PACKAGE_MAPPINGS ]];
|
|
||||||
then
|
|
||||||
print_pairs "$PACKAGE_MAPPINGS"
|
|
||||||
exit 0
|
|
||||||
elif [[ -z $PRINT_CLASS_MAPPINGS ]];
|
|
||||||
then
|
|
||||||
print_class_mappings
|
|
||||||
exit 0
|
|
||||||
elif [[ -z $PRINT_VERSION ]];
|
|
||||||
then
|
|
||||||
echo "$LEGACY_PEER_VERSION -> $MEDIA3_VERSION. This script is written to migrate from ExoPlayer $LEGACY_PEER_VERSION to AndroidX Media3 $MEDIA3_VERSION"
|
|
||||||
exit 0
|
|
||||||
elif [[ -z $1 ]];
|
|
||||||
then
|
|
||||||
usage
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ ! -f $1/gradlew ]];
|
|
||||||
then
|
|
||||||
echo "directory seems not to exist or is not a gradle project (missing 'gradlew')"
|
|
||||||
usage
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
PROJECT_ROOT=$1
|
|
||||||
cd "$PROJECT_ROOT"
|
|
||||||
|
|
||||||
# Create the set of files to transform
|
|
||||||
exclusion="/build/|/.idea/|/res/drawable|/res/color|/res/mipmap|/res/values|"
|
|
||||||
if [[ ! -z $EXCLUDED_PATHS ]];
|
|
||||||
then
|
|
||||||
while read -r path;
|
|
||||||
do
|
|
||||||
exclusion="$exclusion./$path|"
|
|
||||||
done <<< "$EXCLUDED_PATHS"
|
|
||||||
fi
|
|
||||||
files=$(find . -name '*\.java' -o -name '*\.kt' -o -name '*\.xml' | grep -Ev "'$exclusion'")
|
|
||||||
|
|
||||||
# Validate project and exit in case of validation errors
|
|
||||||
validate_string_patterns
|
|
||||||
validate_exoplayer_version "$PROJECT_ROOT"
|
|
||||||
if [[ ! -z $FORCE && ! -z "$VALIDATION_ERRORS" ]];
|
|
||||||
then
|
|
||||||
echo "============================================="
|
|
||||||
echo "Validation errors (use -f to force execution)"
|
|
||||||
echo "---------------------------------------------"
|
|
||||||
echo -e "$VALIDATION_ERRORS"
|
|
||||||
exit 1
|
|
||||||
fi
|
|
||||||
|
|
||||||
if [[ -z $LIST_FILES_ONLY ]];
|
|
||||||
then
|
|
||||||
echo "$files" | cut -c 3-
|
|
||||||
find . -type f -name 'build\.gradle' | cut -c 3-
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
# start migration after successful validation or when forced to disregard validation
|
|
||||||
# errors
|
|
||||||
|
|
||||||
if [[ ! -z "$MIGRATE_FILES" ]];
|
|
||||||
then
|
|
||||||
echo "nothing to do"
|
|
||||||
usage
|
|
||||||
exit 0
|
|
||||||
fi
|
|
||||||
|
|
||||||
PWD=$(pwd)
|
|
||||||
if [[ ! -z $NO_CLEAN ]];
|
|
||||||
then
|
|
||||||
cd "$PROJECT_ROOT"
|
|
||||||
./gradlew clean
|
|
||||||
cd "$PWD"
|
|
||||||
fi
|
|
||||||
|
|
||||||
# create expressions for class renamings
|
|
||||||
renaming_expressions=''
|
|
||||||
while read -r renaming;
|
|
||||||
do
|
|
||||||
src=$(echo "$renaming" | cut -d ' ' -f1 | sed -e 's/\./\\\./g')
|
|
||||||
dest=$(echo "$renaming" | cut -d ' ' -f2)
|
|
||||||
renaming_expressions+="-e s/$src/$dest/g "
|
|
||||||
done <<< "$CLASS_RENAMINGS"
|
|
||||||
|
|
||||||
# create expressions for class mappings
|
|
||||||
classes_expressions=''
|
|
||||||
while read -r mapping;
|
|
||||||
do
|
|
||||||
src=$(echo "$mapping" | cut -d ' ' -f1 | sed -e 's/\./\\\./g')
|
|
||||||
dest=$(echo "$mapping" | cut -d ' ' -f2)
|
|
||||||
classes=$(echo "$mapping" | cut -d ' ' -f3-)
|
|
||||||
for clazz in $classes;
|
|
||||||
do
|
|
||||||
classes_expressions+="-e s/$src\.$clazz/$dest.$clazz/g "
|
|
||||||
done
|
|
||||||
done <<< "$CLASS_MAPPINGS"
|
|
||||||
|
|
||||||
# create expressions for package mappings
|
|
||||||
packages_expressions=''
|
|
||||||
while read -r mapping;
|
|
||||||
do
|
|
||||||
src=$(echo "$mapping" | cut -d ' ' -f1 | sed -e 's/\./\\\./g')
|
|
||||||
dest=$(echo "$mapping" | cut -d ' ' -f2)
|
|
||||||
packages_expressions+="-e s/$src/$dest/g "
|
|
||||||
done <<< "$PACKAGE_MAPPINGS"
|
|
||||||
|
|
||||||
# do search and replace with expressions in each selected file
|
|
||||||
while read -r file;
|
|
||||||
do
|
|
||||||
echo "migrating $file"
|
|
||||||
expr="$renaming_expressions $classes_expressions $packages_expressions"
|
|
||||||
$SED_CMD_INPLACE $expr $file
|
|
||||||
done <<< "$files"
|
|
||||||
|
|
||||||
# create expressions for dependencies in gradle files
|
|
||||||
EXOPLAYER_GROUP="com\.google\.android\.exoplayer"
|
|
||||||
MEDIA3_GROUP="androidx.media3"
|
|
||||||
dependency_expressions=""
|
|
||||||
while read -r mapping
|
|
||||||
do
|
|
||||||
OLD=$(echo "$mapping" | cut -d ' ' -f1 | sed -e 's/\./\\\./g')
|
|
||||||
NEW=$(echo "$mapping" | cut -d ' ' -f2)
|
|
||||||
dependency_expressions="$dependency_expressions -e s/$EXOPLAYER_GROUP:$OLD:.*\"/$MEDIA3_GROUP:$NEW:$MEDIA3_VERSION\"/g -e s/$EXOPLAYER_GROUP:$OLD:.*'/$MEDIA3_GROUP:$NEW:$MEDIA3_VERSION'/"
|
|
||||||
done <<< "$DEPENDENCY_MAPPINGS"
|
|
||||||
|
|
||||||
## do search and replace for dependencies in gradle files
|
|
||||||
while read -r build_file;
|
|
||||||
do
|
|
||||||
echo "migrating build file $build_file"
|
|
||||||
$SED_CMD_INPLACE $dependency_expressions $build_file
|
|
||||||
done <<< "$(find . -type f -name 'build\.gradle')"
|
|
@ -27,3 +27,9 @@ Create a `CastPlayer` and use it to control a Cast receiver app. Since
|
|||||||
`CastPlayer` implements the `Player` interface, it can be passed to all media
|
`CastPlayer` implements the `Player` interface, it can be passed to all media
|
||||||
components that accept a `Player`, including the UI components provided by the
|
components that accept a `Player`, including the UI components provided by the
|
||||||
UI module.
|
UI module.
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
* [Javadoc][]
|
||||||
|
|
||||||
|
[Javadoc]: https://developer.android.com/reference/androidx/media3/packages
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
apply from: "$gradle.ext.androidxMediaSettingsDir/common_library_config.gradle"
|
apply from: "$gradle.ext.androidxMediaSettingsDir/common_library_config.gradle"
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api 'com.google.android.gms:play-services-cast-framework:21.0.1'
|
api 'com.google.android.gms:play-services-cast-framework:21.2.0'
|
||||||
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
|
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
|
||||||
implementation project(modulePrefix + 'lib-common')
|
implementation project(modulePrefix + 'lib-common')
|
||||||
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
|
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.cast;
|
package androidx.media3.cast;
|
||||||
|
|
||||||
|
import static androidx.annotation.VisibleForTesting.PROTECTED;
|
||||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||||
import static androidx.media3.common.util.Util.castNonNull;
|
import static androidx.media3.common.util.Util.castNonNull;
|
||||||
import static java.lang.Math.min;
|
import static java.lang.Math.min;
|
||||||
@ -43,7 +44,6 @@ import androidx.media3.common.TrackSelectionParameters;
|
|||||||
import androidx.media3.common.Tracks;
|
import androidx.media3.common.Tracks;
|
||||||
import androidx.media3.common.VideoSize;
|
import androidx.media3.common.VideoSize;
|
||||||
import androidx.media3.common.text.CueGroup;
|
import androidx.media3.common.text.CueGroup;
|
||||||
import androidx.media3.common.util.Assertions;
|
|
||||||
import androidx.media3.common.util.Clock;
|
import androidx.media3.common.util.Clock;
|
||||||
import androidx.media3.common.util.ListenerSet;
|
import androidx.media3.common.util.ListenerSet;
|
||||||
import androidx.media3.common.util.Log;
|
import androidx.media3.common.util.Log;
|
||||||
@ -295,7 +295,7 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void addMediaItems(int index, List<MediaItem> mediaItems) {
|
public void addMediaItems(int index, List<MediaItem> mediaItems) {
|
||||||
Assertions.checkArgument(index >= 0);
|
checkArgument(index >= 0);
|
||||||
int uid = MediaQueueItem.INVALID_ITEM_ID;
|
int uid = MediaQueueItem.INVALID_ITEM_ID;
|
||||||
if (index < currentTimeline.getWindowCount()) {
|
if (index < currentTimeline.getWindowCount()) {
|
||||||
uid = (int) currentTimeline.getWindow(/* windowIndex= */ index, window).uid;
|
uid = (int) currentTimeline.getWindow(/* windowIndex= */ index, window).uid;
|
||||||
@ -305,14 +305,11 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void moveMediaItems(int fromIndex, int toIndex, int newIndex) {
|
public void moveMediaItems(int fromIndex, int toIndex, int newIndex) {
|
||||||
Assertions.checkArgument(
|
checkArgument(fromIndex >= 0 && fromIndex <= toIndex && newIndex >= 0);
|
||||||
fromIndex >= 0
|
int playlistSize = currentTimeline.getWindowCount();
|
||||||
&& fromIndex <= toIndex
|
toIndex = min(toIndex, playlistSize);
|
||||||
&& toIndex <= currentTimeline.getWindowCount()
|
newIndex = min(newIndex, playlistSize - (toIndex - fromIndex));
|
||||||
&& newIndex >= 0
|
if (fromIndex >= playlistSize || fromIndex == toIndex || fromIndex == newIndex) {
|
||||||
&& newIndex < currentTimeline.getWindowCount());
|
|
||||||
newIndex = min(newIndex, currentTimeline.getWindowCount() - (toIndex - fromIndex));
|
|
||||||
if (fromIndex == toIndex || fromIndex == newIndex) {
|
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -325,9 +322,10 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeMediaItems(int fromIndex, int toIndex) {
|
public void removeMediaItems(int fromIndex, int toIndex) {
|
||||||
Assertions.checkArgument(fromIndex >= 0 && toIndex >= fromIndex);
|
checkArgument(fromIndex >= 0 && toIndex >= fromIndex);
|
||||||
toIndex = min(toIndex, currentTimeline.getWindowCount());
|
int playlistSize = currentTimeline.getWindowCount();
|
||||||
if (fromIndex == toIndex) {
|
toIndex = min(toIndex, playlistSize);
|
||||||
|
if (fromIndex >= playlistSize || fromIndex == toIndex) {
|
||||||
// Do nothing.
|
// Do nothing.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -399,7 +397,16 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
// don't implement onPositionDiscontinuity().
|
// don't implement onPositionDiscontinuity().
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
@Override
|
@Override
|
||||||
public void seekTo(int mediaItemIndex, long positionMs) {
|
@VisibleForTesting(otherwise = PROTECTED)
|
||||||
|
public void seekTo(
|
||||||
|
int mediaItemIndex,
|
||||||
|
long positionMs,
|
||||||
|
@Player.Command int seekCommand,
|
||||||
|
boolean isRepeatingCurrentItem) {
|
||||||
|
checkArgument(mediaItemIndex >= 0);
|
||||||
|
if (!currentTimeline.isEmpty() && mediaItemIndex >= currentTimeline.getWindowCount()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
MediaStatus mediaStatus = getMediaStatus();
|
MediaStatus mediaStatus = getMediaStatus();
|
||||||
// We assume the default position is 0. There is no support for seeking to the default position
|
// We assume the default position is 0. There is no support for seeking to the default position
|
||||||
// in RemoteMediaClient.
|
// in RemoteMediaClient.
|
||||||
|
@ -26,10 +26,6 @@ import com.google.android.gms.cast.MediaTrack;
|
|||||||
/** Utility methods for Cast integration. */
|
/** Utility methods for Cast integration. */
|
||||||
/* package */ final class CastUtils {
|
/* package */ final class CastUtils {
|
||||||
|
|
||||||
/** The duration returned by {@link MediaInfo#getStreamDuration()} for live streams. */
|
|
||||||
// TODO: Remove once [Internal ref: b/171657375] is fixed.
|
|
||||||
private static final long LIVE_STREAM_DURATION = -1000;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the duration in microseconds advertised by a media info, or {@link C#TIME_UNSET} if
|
* Returns the duration in microseconds advertised by a media info, or {@link C#TIME_UNSET} if
|
||||||
* unknown or not applicable.
|
* unknown or not applicable.
|
||||||
@ -42,9 +38,7 @@ import com.google.android.gms.cast.MediaTrack;
|
|||||||
return C.TIME_UNSET;
|
return C.TIME_UNSET;
|
||||||
}
|
}
|
||||||
long durationMs = mediaInfo.getStreamDuration();
|
long durationMs = mediaInfo.getStreamDuration();
|
||||||
return durationMs != MediaInfo.UNKNOWN_DURATION && durationMs != LIVE_STREAM_DURATION
|
return durationMs != MediaInfo.UNKNOWN_DURATION ? Util.msToUs(durationMs) : C.TIME_UNSET;
|
||||||
? Util.msToUs(durationMs)
|
|
||||||
: C.TIME_UNSET;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -2,3 +2,9 @@
|
|||||||
|
|
||||||
Provides common code and utilities used by other media modules. Application code
|
Provides common code and utilities used by other media modules. Application code
|
||||||
will not normally need to depend on this module directly.
|
will not normally need to depend on this module directly.
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
* [Javadoc][]
|
||||||
|
|
||||||
|
[Javadoc]: https://developer.android.com/reference/androidx/media3/packages
|
||||||
|
@ -459,44 +459,29 @@ public final class AdPlaybackState implements Bundleable {
|
|||||||
|
|
||||||
// Bundleable implementation.
|
// Bundleable implementation.
|
||||||
|
|
||||||
@Documented
|
private static final String FIELD_TIME_US = Util.intToStringMaxRadix(0);
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
private static final String FIELD_COUNT = Util.intToStringMaxRadix(1);
|
||||||
@Target(TYPE_USE)
|
private static final String FIELD_URIS = Util.intToStringMaxRadix(2);
|
||||||
@IntDef({
|
private static final String FIELD_STATES = Util.intToStringMaxRadix(3);
|
||||||
FIELD_TIME_US,
|
private static final String FIELD_DURATIONS_US = Util.intToStringMaxRadix(4);
|
||||||
FIELD_COUNT,
|
private static final String FIELD_CONTENT_RESUME_OFFSET_US = Util.intToStringMaxRadix(5);
|
||||||
FIELD_URIS,
|
private static final String FIELD_IS_SERVER_SIDE_INSERTED = Util.intToStringMaxRadix(6);
|
||||||
FIELD_STATES,
|
private static final String FIELD_ORIGINAL_COUNT = Util.intToStringMaxRadix(7);
|
||||||
FIELD_DURATIONS_US,
|
|
||||||
FIELD_CONTENT_RESUME_OFFSET_US,
|
|
||||||
FIELD_IS_SERVER_SIDE_INSERTED,
|
|
||||||
FIELD_ORIGINAL_COUNT
|
|
||||||
})
|
|
||||||
private @interface FieldNumber {}
|
|
||||||
|
|
||||||
private static final int FIELD_TIME_US = 0;
|
|
||||||
private static final int FIELD_COUNT = 1;
|
|
||||||
private static final int FIELD_URIS = 2;
|
|
||||||
private static final int FIELD_STATES = 3;
|
|
||||||
private static final int FIELD_DURATIONS_US = 4;
|
|
||||||
private static final int FIELD_CONTENT_RESUME_OFFSET_US = 5;
|
|
||||||
private static final int FIELD_IS_SERVER_SIDE_INSERTED = 6;
|
|
||||||
private static final int FIELD_ORIGINAL_COUNT = 7;
|
|
||||||
|
|
||||||
// putParcelableArrayList actually supports null elements.
|
// putParcelableArrayList actually supports null elements.
|
||||||
@SuppressWarnings("nullness:argument")
|
@SuppressWarnings("nullness:argument")
|
||||||
@Override
|
@Override
|
||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putLong(keyForField(FIELD_TIME_US), timeUs);
|
bundle.putLong(FIELD_TIME_US, timeUs);
|
||||||
bundle.putInt(keyForField(FIELD_COUNT), count);
|
bundle.putInt(FIELD_COUNT, count);
|
||||||
bundle.putInt(keyForField(FIELD_ORIGINAL_COUNT), originalCount);
|
bundle.putInt(FIELD_ORIGINAL_COUNT, originalCount);
|
||||||
bundle.putParcelableArrayList(
|
bundle.putParcelableArrayList(
|
||||||
keyForField(FIELD_URIS), new ArrayList<@NullableType Uri>(Arrays.asList(uris)));
|
FIELD_URIS, new ArrayList<@NullableType Uri>(Arrays.asList(uris)));
|
||||||
bundle.putIntArray(keyForField(FIELD_STATES), states);
|
bundle.putIntArray(FIELD_STATES, states);
|
||||||
bundle.putLongArray(keyForField(FIELD_DURATIONS_US), durationsUs);
|
bundle.putLongArray(FIELD_DURATIONS_US, durationsUs);
|
||||||
bundle.putLong(keyForField(FIELD_CONTENT_RESUME_OFFSET_US), contentResumeOffsetUs);
|
bundle.putLong(FIELD_CONTENT_RESUME_OFFSET_US, contentResumeOffsetUs);
|
||||||
bundle.putBoolean(keyForField(FIELD_IS_SERVER_SIDE_INSERTED), isServerSideInserted);
|
bundle.putBoolean(FIELD_IS_SERVER_SIDE_INSERTED, isServerSideInserted);
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -506,18 +491,16 @@ public final class AdPlaybackState implements Bundleable {
|
|||||||
// getParcelableArrayList may have null elements.
|
// getParcelableArrayList may have null elements.
|
||||||
@SuppressWarnings("nullness:type.argument")
|
@SuppressWarnings("nullness:type.argument")
|
||||||
private static AdGroup fromBundle(Bundle bundle) {
|
private static AdGroup fromBundle(Bundle bundle) {
|
||||||
long timeUs = bundle.getLong(keyForField(FIELD_TIME_US));
|
long timeUs = bundle.getLong(FIELD_TIME_US);
|
||||||
int count = bundle.getInt(keyForField(FIELD_COUNT), /* defaultValue= */ C.LENGTH_UNSET);
|
int count = bundle.getInt(FIELD_COUNT);
|
||||||
int originalCount =
|
int originalCount = bundle.getInt(FIELD_ORIGINAL_COUNT);
|
||||||
bundle.getInt(keyForField(FIELD_ORIGINAL_COUNT), /* defaultValue= */ C.LENGTH_UNSET);
|
@Nullable ArrayList<@NullableType Uri> uriList = bundle.getParcelableArrayList(FIELD_URIS);
|
||||||
@Nullable
|
|
||||||
ArrayList<@NullableType Uri> uriList = bundle.getParcelableArrayList(keyForField(FIELD_URIS));
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@AdState
|
@AdState
|
||||||
int[] states = bundle.getIntArray(keyForField(FIELD_STATES));
|
int[] states = bundle.getIntArray(FIELD_STATES);
|
||||||
@Nullable long[] durationsUs = bundle.getLongArray(keyForField(FIELD_DURATIONS_US));
|
@Nullable long[] durationsUs = bundle.getLongArray(FIELD_DURATIONS_US);
|
||||||
long contentResumeOffsetUs = bundle.getLong(keyForField(FIELD_CONTENT_RESUME_OFFSET_US));
|
long contentResumeOffsetUs = bundle.getLong(FIELD_CONTENT_RESUME_OFFSET_US);
|
||||||
boolean isServerSideInserted = bundle.getBoolean(keyForField(FIELD_IS_SERVER_SIDE_INSERTED));
|
boolean isServerSideInserted = bundle.getBoolean(FIELD_IS_SERVER_SIDE_INSERTED);
|
||||||
return new AdGroup(
|
return new AdGroup(
|
||||||
timeUs,
|
timeUs,
|
||||||
count,
|
count,
|
||||||
@ -528,10 +511,6 @@ public final class AdPlaybackState implements Bundleable {
|
|||||||
contentResumeOffsetUs,
|
contentResumeOffsetUs,
|
||||||
isServerSideInserted);
|
isServerSideInserted);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String keyForField(@AdGroup.FieldNumber int field) {
|
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1122,21 +1101,10 @@ public final class AdPlaybackState implements Bundleable {
|
|||||||
|
|
||||||
// Bundleable implementation.
|
// Bundleable implementation.
|
||||||
|
|
||||||
@Documented
|
private static final String FIELD_AD_GROUPS = Util.intToStringMaxRadix(1);
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
private static final String FIELD_AD_RESUME_POSITION_US = Util.intToStringMaxRadix(2);
|
||||||
@Target(TYPE_USE)
|
private static final String FIELD_CONTENT_DURATION_US = Util.intToStringMaxRadix(3);
|
||||||
@IntDef({
|
private static final String FIELD_REMOVED_AD_GROUP_COUNT = Util.intToStringMaxRadix(4);
|
||||||
FIELD_AD_GROUPS,
|
|
||||||
FIELD_AD_RESUME_POSITION_US,
|
|
||||||
FIELD_CONTENT_DURATION_US,
|
|
||||||
FIELD_REMOVED_AD_GROUP_COUNT
|
|
||||||
})
|
|
||||||
private @interface FieldNumber {}
|
|
||||||
|
|
||||||
private static final int FIELD_AD_GROUPS = 1;
|
|
||||||
private static final int FIELD_AD_RESUME_POSITION_US = 2;
|
|
||||||
private static final int FIELD_CONTENT_DURATION_US = 3;
|
|
||||||
private static final int FIELD_REMOVED_AD_GROUP_COUNT = 4;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
@ -1152,10 +1120,18 @@ public final class AdPlaybackState implements Bundleable {
|
|||||||
for (AdGroup adGroup : adGroups) {
|
for (AdGroup adGroup : adGroups) {
|
||||||
adGroupBundleList.add(adGroup.toBundle());
|
adGroupBundleList.add(adGroup.toBundle());
|
||||||
}
|
}
|
||||||
bundle.putParcelableArrayList(keyForField(FIELD_AD_GROUPS), adGroupBundleList);
|
if (!adGroupBundleList.isEmpty()) {
|
||||||
bundle.putLong(keyForField(FIELD_AD_RESUME_POSITION_US), adResumePositionUs);
|
bundle.putParcelableArrayList(FIELD_AD_GROUPS, adGroupBundleList);
|
||||||
bundle.putLong(keyForField(FIELD_CONTENT_DURATION_US), contentDurationUs);
|
}
|
||||||
bundle.putInt(keyForField(FIELD_REMOVED_AD_GROUP_COUNT), removedAdGroupCount);
|
if (adResumePositionUs != NONE.adResumePositionUs) {
|
||||||
|
bundle.putLong(FIELD_AD_RESUME_POSITION_US, adResumePositionUs);
|
||||||
|
}
|
||||||
|
if (contentDurationUs != NONE.contentDurationUs) {
|
||||||
|
bundle.putLong(FIELD_CONTENT_DURATION_US, contentDurationUs);
|
||||||
|
}
|
||||||
|
if (removedAdGroupCount != NONE.removedAdGroupCount) {
|
||||||
|
bundle.putInt(FIELD_REMOVED_AD_GROUP_COUNT, removedAdGroupCount);
|
||||||
|
}
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1167,9 +1143,7 @@ public final class AdPlaybackState implements Bundleable {
|
|||||||
public static final Bundleable.Creator<AdPlaybackState> CREATOR = AdPlaybackState::fromBundle;
|
public static final Bundleable.Creator<AdPlaybackState> CREATOR = AdPlaybackState::fromBundle;
|
||||||
|
|
||||||
private static AdPlaybackState fromBundle(Bundle bundle) {
|
private static AdPlaybackState fromBundle(Bundle bundle) {
|
||||||
@Nullable
|
@Nullable ArrayList<Bundle> adGroupBundleList = bundle.getParcelableArrayList(FIELD_AD_GROUPS);
|
||||||
ArrayList<Bundle> adGroupBundleList =
|
|
||||||
bundle.getParcelableArrayList(keyForField(FIELD_AD_GROUPS));
|
|
||||||
@Nullable AdGroup[] adGroups;
|
@Nullable AdGroup[] adGroups;
|
||||||
if (adGroupBundleList == null) {
|
if (adGroupBundleList == null) {
|
||||||
adGroups = new AdGroup[0];
|
adGroups = new AdGroup[0];
|
||||||
@ -1180,18 +1154,15 @@ public final class AdPlaybackState implements Bundleable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
long adResumePositionUs =
|
long adResumePositionUs =
|
||||||
bundle.getLong(keyForField(FIELD_AD_RESUME_POSITION_US), /* defaultValue= */ 0);
|
bundle.getLong(FIELD_AD_RESUME_POSITION_US, /* defaultValue= */ NONE.adResumePositionUs);
|
||||||
long contentDurationUs =
|
long contentDurationUs =
|
||||||
bundle.getLong(keyForField(FIELD_CONTENT_DURATION_US), /* defaultValue= */ C.TIME_UNSET);
|
bundle.getLong(FIELD_CONTENT_DURATION_US, /* defaultValue= */ NONE.contentDurationUs);
|
||||||
int removedAdGroupCount = bundle.getInt(keyForField(FIELD_REMOVED_AD_GROUP_COUNT));
|
int removedAdGroupCount =
|
||||||
|
bundle.getInt(FIELD_REMOVED_AD_GROUP_COUNT, /* defaultValue= */ NONE.removedAdGroupCount);
|
||||||
return new AdPlaybackState(
|
return new AdPlaybackState(
|
||||||
/* adsId= */ null, adGroups, adResumePositionUs, contentDurationUs, removedAdGroupCount);
|
/* adsId= */ null, adGroups, adResumePositionUs, contentDurationUs, removedAdGroupCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String keyForField(@FieldNumber int field) {
|
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static AdGroup[] createEmptyAdGroups(long[] adGroupTimesUs) {
|
private static AdGroup[] createEmptyAdGroups(long[] adGroupTimesUs) {
|
||||||
AdGroup[] adGroups = new AdGroup[adGroupTimesUs.length];
|
AdGroup[] adGroups = new AdGroup[adGroupTimesUs.length];
|
||||||
for (int i = 0; i < adGroups.length; i++) {
|
for (int i = 0; i < adGroups.length; i++) {
|
||||||
|
@ -15,20 +15,13 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.common;
|
package androidx.media3.common;
|
||||||
|
|
||||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.DoNotInline;
|
import androidx.annotation.DoNotInline;
|
||||||
import androidx.annotation.IntDef;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
import java.lang.annotation.Documented;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attributes for audio playback, which configure the underlying platform {@link
|
* Attributes for audio playback, which configure the underlying platform {@link
|
||||||
@ -205,33 +198,21 @@ public final class AudioAttributes implements Bundleable {
|
|||||||
|
|
||||||
// Bundleable implementation.
|
// Bundleable implementation.
|
||||||
|
|
||||||
@Documented
|
private static final String FIELD_CONTENT_TYPE = Util.intToStringMaxRadix(0);
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
private static final String FIELD_FLAGS = Util.intToStringMaxRadix(1);
|
||||||
@Target(TYPE_USE)
|
private static final String FIELD_USAGE = Util.intToStringMaxRadix(2);
|
||||||
@IntDef({
|
private static final String FIELD_ALLOWED_CAPTURE_POLICY = Util.intToStringMaxRadix(3);
|
||||||
FIELD_CONTENT_TYPE,
|
private static final String FIELD_SPATIALIZATION_BEHAVIOR = Util.intToStringMaxRadix(4);
|
||||||
FIELD_FLAGS,
|
|
||||||
FIELD_USAGE,
|
|
||||||
FIELD_ALLOWED_CAPTURE_POLICY,
|
|
||||||
FIELD_SPATIALIZATION_BEHAVIOR
|
|
||||||
})
|
|
||||||
private @interface FieldNumber {}
|
|
||||||
|
|
||||||
private static final int FIELD_CONTENT_TYPE = 0;
|
|
||||||
private static final int FIELD_FLAGS = 1;
|
|
||||||
private static final int FIELD_USAGE = 2;
|
|
||||||
private static final int FIELD_ALLOWED_CAPTURE_POLICY = 3;
|
|
||||||
private static final int FIELD_SPATIALIZATION_BEHAVIOR = 4;
|
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Override
|
@Override
|
||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putInt(keyForField(FIELD_CONTENT_TYPE), contentType);
|
bundle.putInt(FIELD_CONTENT_TYPE, contentType);
|
||||||
bundle.putInt(keyForField(FIELD_FLAGS), flags);
|
bundle.putInt(FIELD_FLAGS, flags);
|
||||||
bundle.putInt(keyForField(FIELD_USAGE), usage);
|
bundle.putInt(FIELD_USAGE, usage);
|
||||||
bundle.putInt(keyForField(FIELD_ALLOWED_CAPTURE_POLICY), allowedCapturePolicy);
|
bundle.putInt(FIELD_ALLOWED_CAPTURE_POLICY, allowedCapturePolicy);
|
||||||
bundle.putInt(keyForField(FIELD_SPATIALIZATION_BEHAVIOR), spatializationBehavior);
|
bundle.putInt(FIELD_SPATIALIZATION_BEHAVIOR, spatializationBehavior);
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -240,29 +221,24 @@ public final class AudioAttributes implements Bundleable {
|
|||||||
public static final Creator<AudioAttributes> CREATOR =
|
public static final Creator<AudioAttributes> CREATOR =
|
||||||
bundle -> {
|
bundle -> {
|
||||||
Builder builder = new Builder();
|
Builder builder = new Builder();
|
||||||
if (bundle.containsKey(keyForField(FIELD_CONTENT_TYPE))) {
|
if (bundle.containsKey(FIELD_CONTENT_TYPE)) {
|
||||||
builder.setContentType(bundle.getInt(keyForField(FIELD_CONTENT_TYPE)));
|
builder.setContentType(bundle.getInt(FIELD_CONTENT_TYPE));
|
||||||
}
|
}
|
||||||
if (bundle.containsKey(keyForField(FIELD_FLAGS))) {
|
if (bundle.containsKey(FIELD_FLAGS)) {
|
||||||
builder.setFlags(bundle.getInt(keyForField(FIELD_FLAGS)));
|
builder.setFlags(bundle.getInt(FIELD_FLAGS));
|
||||||
}
|
}
|
||||||
if (bundle.containsKey(keyForField(FIELD_USAGE))) {
|
if (bundle.containsKey(FIELD_USAGE)) {
|
||||||
builder.setUsage(bundle.getInt(keyForField(FIELD_USAGE)));
|
builder.setUsage(bundle.getInt(FIELD_USAGE));
|
||||||
}
|
}
|
||||||
if (bundle.containsKey(keyForField(FIELD_ALLOWED_CAPTURE_POLICY))) {
|
if (bundle.containsKey(FIELD_ALLOWED_CAPTURE_POLICY)) {
|
||||||
builder.setAllowedCapturePolicy(bundle.getInt(keyForField(FIELD_ALLOWED_CAPTURE_POLICY)));
|
builder.setAllowedCapturePolicy(bundle.getInt(FIELD_ALLOWED_CAPTURE_POLICY));
|
||||||
}
|
}
|
||||||
if (bundle.containsKey(keyForField(FIELD_SPATIALIZATION_BEHAVIOR))) {
|
if (bundle.containsKey(FIELD_SPATIALIZATION_BEHAVIOR)) {
|
||||||
builder.setSpatializationBehavior(
|
builder.setSpatializationBehavior(bundle.getInt(FIELD_SPATIALIZATION_BEHAVIOR));
|
||||||
bundle.getInt(keyForField(FIELD_SPATIALIZATION_BEHAVIOR)));
|
|
||||||
}
|
}
|
||||||
return builder.build();
|
return builder.build();
|
||||||
};
|
};
|
||||||
|
|
||||||
private static String keyForField(@FieldNumber int field) {
|
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
|
||||||
}
|
|
||||||
|
|
||||||
@RequiresApi(29)
|
@RequiresApi(29)
|
||||||
private static final class Api29 {
|
private static final class Api29 {
|
||||||
@DoNotInline
|
@DoNotInline
|
||||||
|
@ -15,14 +15,15 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.common;
|
package androidx.media3.common;
|
||||||
|
|
||||||
|
import static androidx.annotation.VisibleForTesting.PROTECTED;
|
||||||
import static java.lang.Math.max;
|
import static java.lang.Math.max;
|
||||||
import static java.lang.Math.min;
|
import static java.lang.Math.min;
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.VisibleForTesting;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.errorprone.annotations.ForOverride;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/** Abstract base {@link Player} which implements common implementation independent methods. */
|
/** Abstract base {@link Player} which implements common implementation independent methods. */
|
||||||
@ -121,27 +122,23 @@ public abstract class BasePlayer implements Player {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void seekToDefaultPosition() {
|
public final void seekToDefaultPosition() {
|
||||||
seekToDefaultPosition(getCurrentMediaItemIndex());
|
seekToDefaultPositionInternal(
|
||||||
|
getCurrentMediaItemIndex(), Player.COMMAND_SEEK_TO_DEFAULT_POSITION);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void seekToDefaultPosition(int mediaItemIndex) {
|
public final void seekToDefaultPosition(int mediaItemIndex) {
|
||||||
seekTo(mediaItemIndex, /* positionMs= */ C.TIME_UNSET);
|
seekToDefaultPositionInternal(mediaItemIndex, Player.COMMAND_SEEK_TO_MEDIA_ITEM);
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public final void seekTo(long positionMs) {
|
|
||||||
seekTo(getCurrentMediaItemIndex(), positionMs);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void seekBack() {
|
public final void seekBack() {
|
||||||
seekToOffset(-getSeekBackIncrement());
|
seekToOffset(-getSeekBackIncrement(), Player.COMMAND_SEEK_BACK);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void seekForward() {
|
public final void seekForward() {
|
||||||
seekToOffset(getSeekForwardIncrement());
|
seekToOffset(getSeekForwardIncrement(), Player.COMMAND_SEEK_FORWARD);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -187,15 +184,7 @@ public abstract class BasePlayer implements Player {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void seekToPreviousMediaItem() {
|
public final void seekToPreviousMediaItem() {
|
||||||
int previousMediaItemIndex = getPreviousMediaItemIndex();
|
seekToPreviousMediaItemInternal(Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM);
|
||||||
if (previousMediaItemIndex == C.INDEX_UNSET) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (previousMediaItemIndex == getCurrentMediaItemIndex()) {
|
|
||||||
repeatCurrentMediaItem();
|
|
||||||
} else {
|
|
||||||
seekToDefaultPosition(previousMediaItemIndex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -207,12 +196,12 @@ public abstract class BasePlayer implements Player {
|
|||||||
boolean hasPreviousMediaItem = hasPreviousMediaItem();
|
boolean hasPreviousMediaItem = hasPreviousMediaItem();
|
||||||
if (isCurrentMediaItemLive() && !isCurrentMediaItemSeekable()) {
|
if (isCurrentMediaItemLive() && !isCurrentMediaItemSeekable()) {
|
||||||
if (hasPreviousMediaItem) {
|
if (hasPreviousMediaItem) {
|
||||||
seekToPreviousMediaItem();
|
seekToPreviousMediaItemInternal(Player.COMMAND_SEEK_TO_PREVIOUS);
|
||||||
}
|
}
|
||||||
} else if (hasPreviousMediaItem && getCurrentPosition() <= getMaxSeekToPreviousPosition()) {
|
} else if (hasPreviousMediaItem && getCurrentPosition() <= getMaxSeekToPreviousPosition()) {
|
||||||
seekToPreviousMediaItem();
|
seekToPreviousMediaItemInternal(Player.COMMAND_SEEK_TO_PREVIOUS);
|
||||||
} else {
|
} else {
|
||||||
seekTo(/* positionMs= */ 0);
|
seekToCurrentItem(/* positionMs= */ 0, Player.COMMAND_SEEK_TO_PREVIOUS);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -259,15 +248,7 @@ public abstract class BasePlayer implements Player {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void seekToNextMediaItem() {
|
public final void seekToNextMediaItem() {
|
||||||
int nextMediaItemIndex = getNextMediaItemIndex();
|
seekToNextMediaItemInternal(Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM);
|
||||||
if (nextMediaItemIndex == C.INDEX_UNSET) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
if (nextMediaItemIndex == getCurrentMediaItemIndex()) {
|
|
||||||
repeatCurrentMediaItem();
|
|
||||||
} else {
|
|
||||||
seekToDefaultPosition(nextMediaItemIndex);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -277,12 +258,42 @@ public abstract class BasePlayer implements Player {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (hasNextMediaItem()) {
|
if (hasNextMediaItem()) {
|
||||||
seekToNextMediaItem();
|
seekToNextMediaItemInternal(Player.COMMAND_SEEK_TO_NEXT);
|
||||||
} else if (isCurrentMediaItemLive() && isCurrentMediaItemDynamic()) {
|
} else if (isCurrentMediaItemLive() && isCurrentMediaItemDynamic()) {
|
||||||
seekToDefaultPosition();
|
seekToDefaultPositionInternal(getCurrentMediaItemIndex(), Player.COMMAND_SEEK_TO_NEXT);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void seekTo(long positionMs) {
|
||||||
|
seekToCurrentItem(positionMs, Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void seekTo(int mediaItemIndex, long positionMs) {
|
||||||
|
seekTo(
|
||||||
|
mediaItemIndex,
|
||||||
|
positionMs,
|
||||||
|
Player.COMMAND_SEEK_TO_MEDIA_ITEM,
|
||||||
|
/* isRepeatingCurrentItem= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Seeks to a position in the specified {@link MediaItem}.
|
||||||
|
*
|
||||||
|
* @param mediaItemIndex The index of the {@link MediaItem}.
|
||||||
|
* @param positionMs The seek position in the specified {@link MediaItem} in milliseconds, or
|
||||||
|
* {@link C#TIME_UNSET} to seek to the media item's default position.
|
||||||
|
* @param seekCommand The {@link Player.Command} used to trigger the seek.
|
||||||
|
* @param isRepeatingCurrentItem Whether this seeks repeats the current item.
|
||||||
|
*/
|
||||||
|
@VisibleForTesting(otherwise = PROTECTED)
|
||||||
|
public abstract void seekTo(
|
||||||
|
int mediaItemIndex,
|
||||||
|
long positionMs,
|
||||||
|
@Player.Command int seekCommand,
|
||||||
|
boolean isRepeatingCurrentItem);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void setPlaybackSpeed(float speed) {
|
public final void setPlaybackSpeed(float speed) {
|
||||||
setPlaybackParameters(getPlaybackParameters().withSpeed(speed));
|
setPlaybackParameters(getPlaybackParameters().withSpeed(speed));
|
||||||
@ -437,29 +448,63 @@ public abstract class BasePlayer implements Player {
|
|||||||
: timeline.getWindow(getCurrentMediaItemIndex(), window).getDurationMs();
|
: timeline.getWindow(getCurrentMediaItemIndex(), window).getDurationMs();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Repeat the current media item.
|
|
||||||
*
|
|
||||||
* <p>The default implementation seeks to the default position in the current item, which can be
|
|
||||||
* overridden for additional handling.
|
|
||||||
*/
|
|
||||||
@ForOverride
|
|
||||||
protected void repeatCurrentMediaItem() {
|
|
||||||
seekToDefaultPosition();
|
|
||||||
}
|
|
||||||
|
|
||||||
private @RepeatMode int getRepeatModeForNavigation() {
|
private @RepeatMode int getRepeatModeForNavigation() {
|
||||||
@RepeatMode int repeatMode = getRepeatMode();
|
@RepeatMode int repeatMode = getRepeatMode();
|
||||||
return repeatMode == REPEAT_MODE_ONE ? REPEAT_MODE_OFF : repeatMode;
|
return repeatMode == REPEAT_MODE_ONE ? REPEAT_MODE_OFF : repeatMode;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void seekToOffset(long offsetMs) {
|
private void seekToCurrentItem(long positionMs, @Player.Command int seekCommand) {
|
||||||
|
seekTo(
|
||||||
|
getCurrentMediaItemIndex(), positionMs, seekCommand, /* isRepeatingCurrentItem= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void seekToOffset(long offsetMs, @Player.Command int seekCommand) {
|
||||||
long positionMs = getCurrentPosition() + offsetMs;
|
long positionMs = getCurrentPosition() + offsetMs;
|
||||||
long durationMs = getDuration();
|
long durationMs = getDuration();
|
||||||
if (durationMs != C.TIME_UNSET) {
|
if (durationMs != C.TIME_UNSET) {
|
||||||
positionMs = min(positionMs, durationMs);
|
positionMs = min(positionMs, durationMs);
|
||||||
}
|
}
|
||||||
positionMs = max(positionMs, 0);
|
positionMs = max(positionMs, 0);
|
||||||
seekTo(positionMs);
|
seekToCurrentItem(positionMs, seekCommand);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void seekToDefaultPositionInternal(int mediaItemIndex, @Player.Command int seekCommand) {
|
||||||
|
seekTo(
|
||||||
|
mediaItemIndex,
|
||||||
|
/* positionMs= */ C.TIME_UNSET,
|
||||||
|
seekCommand,
|
||||||
|
/* isRepeatingCurrentItem= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void seekToNextMediaItemInternal(@Player.Command int seekCommand) {
|
||||||
|
int nextMediaItemIndex = getNextMediaItemIndex();
|
||||||
|
if (nextMediaItemIndex == C.INDEX_UNSET) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (nextMediaItemIndex == getCurrentMediaItemIndex()) {
|
||||||
|
repeatCurrentMediaItem(seekCommand);
|
||||||
|
} else {
|
||||||
|
seekToDefaultPositionInternal(nextMediaItemIndex, seekCommand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void seekToPreviousMediaItemInternal(@Player.Command int seekCommand) {
|
||||||
|
int previousMediaItemIndex = getPreviousMediaItemIndex();
|
||||||
|
if (previousMediaItemIndex == C.INDEX_UNSET) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
if (previousMediaItemIndex == getCurrentMediaItemIndex()) {
|
||||||
|
repeatCurrentMediaItem(seekCommand);
|
||||||
|
} else {
|
||||||
|
seekToDefaultPositionInternal(previousMediaItemIndex, seekCommand);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void repeatCurrentMediaItem(@Player.Command int seekCommand) {
|
||||||
|
seekTo(
|
||||||
|
getCurrentMediaItemIndex(),
|
||||||
|
/* positionMs= */ C.TIME_UNSET,
|
||||||
|
seekCommand,
|
||||||
|
/* isRepeatingCurrentItem= */ true);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -196,7 +196,7 @@ public final class C {
|
|||||||
* #ENCODING_PCM_16BIT_BIG_ENDIAN}, {@link #ENCODING_PCM_24BIT}, {@link #ENCODING_PCM_32BIT},
|
* #ENCODING_PCM_16BIT_BIG_ENDIAN}, {@link #ENCODING_PCM_24BIT}, {@link #ENCODING_PCM_32BIT},
|
||||||
* {@link #ENCODING_PCM_FLOAT}, {@link #ENCODING_MP3}, {@link #ENCODING_AC3}, {@link
|
* {@link #ENCODING_PCM_FLOAT}, {@link #ENCODING_MP3}, {@link #ENCODING_AC3}, {@link
|
||||||
* #ENCODING_E_AC3}, {@link #ENCODING_E_AC3_JOC}, {@link #ENCODING_AC4}, {@link #ENCODING_DTS},
|
* #ENCODING_E_AC3}, {@link #ENCODING_E_AC3_JOC}, {@link #ENCODING_AC4}, {@link #ENCODING_DTS},
|
||||||
* {@link #ENCODING_DTS_HD} or {@link #ENCODING_DOLBY_TRUEHD}.
|
* {@link #ENCODING_DTS_HD}, {@link #ENCODING_DOLBY_TRUEHD} or {@link #ENCODING_OPUS}.
|
||||||
*/
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Documented
|
@Documented
|
||||||
@ -224,7 +224,8 @@ public final class C {
|
|||||||
ENCODING_AC4,
|
ENCODING_AC4,
|
||||||
ENCODING_DTS,
|
ENCODING_DTS,
|
||||||
ENCODING_DTS_HD,
|
ENCODING_DTS_HD,
|
||||||
ENCODING_DOLBY_TRUEHD
|
ENCODING_DOLBY_TRUEHD,
|
||||||
|
ENCODING_OPUS,
|
||||||
})
|
})
|
||||||
public @interface Encoding {}
|
public @interface Encoding {}
|
||||||
|
|
||||||
@ -325,6 +326,10 @@ public final class C {
|
|||||||
* @see AudioFormat#ENCODING_DOLBY_TRUEHD
|
* @see AudioFormat#ENCODING_DOLBY_TRUEHD
|
||||||
*/
|
*/
|
||||||
@UnstableApi public static final int ENCODING_DOLBY_TRUEHD = AudioFormat.ENCODING_DOLBY_TRUEHD;
|
@UnstableApi public static final int ENCODING_DOLBY_TRUEHD = AudioFormat.ENCODING_DOLBY_TRUEHD;
|
||||||
|
/**
|
||||||
|
* @see AudioFormat#ENCODING_OPUS
|
||||||
|
*/
|
||||||
|
@UnstableApi public static final int ENCODING_OPUS = AudioFormat.ENCODING_OPUS;
|
||||||
|
|
||||||
/** Represents the behavior affecting whether spatialization will be used. */
|
/** Represents the behavior affecting whether spatialization will be used. */
|
||||||
@Documented
|
@Documented
|
||||||
|
@ -15,16 +15,10 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.common;
|
package androidx.media3.common;
|
||||||
|
|
||||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.IntDef;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import java.lang.annotation.Documented;
|
import androidx.media3.common.util.Util;
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import org.checkerframework.dataflow.qual.Pure;
|
import org.checkerframework.dataflow.qual.Pure;
|
||||||
|
|
||||||
@ -183,41 +177,26 @@ public final class ColorInfo implements Bundleable {
|
|||||||
|
|
||||||
// Bundleable implementation
|
// Bundleable implementation
|
||||||
|
|
||||||
@Documented
|
private static final String FIELD_COLOR_SPACE = Util.intToStringMaxRadix(0);
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
private static final String FIELD_COLOR_RANGE = Util.intToStringMaxRadix(1);
|
||||||
@Target(TYPE_USE)
|
private static final String FIELD_COLOR_TRANSFER = Util.intToStringMaxRadix(2);
|
||||||
@IntDef({
|
private static final String FIELD_HDR_STATIC_INFO = Util.intToStringMaxRadix(3);
|
||||||
FIELD_COLOR_SPACE,
|
|
||||||
FIELD_COLOR_RANGE,
|
|
||||||
FIELD_COLOR_TRANSFER,
|
|
||||||
FIELD_HDR_STATIC_INFO,
|
|
||||||
})
|
|
||||||
private @interface FieldNumber {}
|
|
||||||
|
|
||||||
private static final int FIELD_COLOR_SPACE = 0;
|
|
||||||
private static final int FIELD_COLOR_RANGE = 1;
|
|
||||||
private static final int FIELD_COLOR_TRANSFER = 2;
|
|
||||||
private static final int FIELD_HDR_STATIC_INFO = 3;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putInt(keyForField(FIELD_COLOR_SPACE), colorSpace);
|
bundle.putInt(FIELD_COLOR_SPACE, colorSpace);
|
||||||
bundle.putInt(keyForField(FIELD_COLOR_RANGE), colorRange);
|
bundle.putInt(FIELD_COLOR_RANGE, colorRange);
|
||||||
bundle.putInt(keyForField(FIELD_COLOR_TRANSFER), colorTransfer);
|
bundle.putInt(FIELD_COLOR_TRANSFER, colorTransfer);
|
||||||
bundle.putByteArray(keyForField(FIELD_HDR_STATIC_INFO), hdrStaticInfo);
|
bundle.putByteArray(FIELD_HDR_STATIC_INFO, hdrStaticInfo);
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final Creator<ColorInfo> CREATOR =
|
public static final Creator<ColorInfo> CREATOR =
|
||||||
bundle ->
|
bundle ->
|
||||||
new ColorInfo(
|
new ColorInfo(
|
||||||
bundle.getInt(keyForField(FIELD_COLOR_SPACE), Format.NO_VALUE),
|
bundle.getInt(FIELD_COLOR_SPACE, Format.NO_VALUE),
|
||||||
bundle.getInt(keyForField(FIELD_COLOR_RANGE), Format.NO_VALUE),
|
bundle.getInt(FIELD_COLOR_RANGE, Format.NO_VALUE),
|
||||||
bundle.getInt(keyForField(FIELD_COLOR_TRANSFER), Format.NO_VALUE),
|
bundle.getInt(FIELD_COLOR_TRANSFER, Format.NO_VALUE),
|
||||||
bundle.getByteArray(keyForField(FIELD_HDR_STATIC_INFO)));
|
bundle.getByteArray(FIELD_HDR_STATIC_INFO));
|
||||||
|
|
||||||
private static String keyForField(@FieldNumber int field) {
|
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -21,6 +21,7 @@ import android.os.Bundle;
|
|||||||
import androidx.annotation.IntDef;
|
import androidx.annotation.IntDef;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import androidx.media3.common.util.Util;
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.Documented;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
@ -87,23 +88,17 @@ public final class DeviceInfo implements Bundleable {
|
|||||||
|
|
||||||
// Bundleable implementation.
|
// Bundleable implementation.
|
||||||
|
|
||||||
@Documented
|
private static final String FIELD_PLAYBACK_TYPE = Util.intToStringMaxRadix(0);
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
private static final String FIELD_MIN_VOLUME = Util.intToStringMaxRadix(1);
|
||||||
@Target(TYPE_USE)
|
private static final String FIELD_MAX_VOLUME = Util.intToStringMaxRadix(2);
|
||||||
@IntDef({FIELD_PLAYBACK_TYPE, FIELD_MIN_VOLUME, FIELD_MAX_VOLUME})
|
|
||||||
private @interface FieldNumber {}
|
|
||||||
|
|
||||||
private static final int FIELD_PLAYBACK_TYPE = 0;
|
|
||||||
private static final int FIELD_MIN_VOLUME = 1;
|
|
||||||
private static final int FIELD_MAX_VOLUME = 2;
|
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Override
|
@Override
|
||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putInt(keyForField(FIELD_PLAYBACK_TYPE), playbackType);
|
bundle.putInt(FIELD_PLAYBACK_TYPE, playbackType);
|
||||||
bundle.putInt(keyForField(FIELD_MIN_VOLUME), minVolume);
|
bundle.putInt(FIELD_MIN_VOLUME, minVolume);
|
||||||
bundle.putInt(keyForField(FIELD_MAX_VOLUME), maxVolume);
|
bundle.putInt(FIELD_MAX_VOLUME, maxVolume);
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -112,14 +107,9 @@ public final class DeviceInfo implements Bundleable {
|
|||||||
public static final Creator<DeviceInfo> CREATOR =
|
public static final Creator<DeviceInfo> CREATOR =
|
||||||
bundle -> {
|
bundle -> {
|
||||||
int playbackType =
|
int playbackType =
|
||||||
bundle.getInt(
|
bundle.getInt(FIELD_PLAYBACK_TYPE, /* defaultValue= */ PLAYBACK_TYPE_LOCAL);
|
||||||
keyForField(FIELD_PLAYBACK_TYPE), /* defaultValue= */ PLAYBACK_TYPE_LOCAL);
|
int minVolume = bundle.getInt(FIELD_MIN_VOLUME, /* defaultValue= */ 0);
|
||||||
int minVolume = bundle.getInt(keyForField(FIELD_MIN_VOLUME), /* defaultValue= */ 0);
|
int maxVolume = bundle.getInt(FIELD_MAX_VOLUME, /* defaultValue= */ 0);
|
||||||
int maxVolume = bundle.getInt(keyForField(FIELD_MAX_VOLUME), /* defaultValue= */ 0);
|
|
||||||
return new DeviceInfo(playbackType, minVolume, maxVolume);
|
return new DeviceInfo(playbackType, minVolume, maxVolume);
|
||||||
};
|
};
|
||||||
|
|
||||||
private static String keyForField(@FieldNumber int field) {
|
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -15,20 +15,13 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.common;
|
package androidx.media3.common;
|
||||||
|
|
||||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.IntDef;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.util.BundleableUtil;
|
import androidx.media3.common.util.BundleableUtil;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import com.google.common.base.Joiner;
|
import com.google.common.base.Joiner;
|
||||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
import java.lang.annotation.Documented;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -112,6 +105,13 @@ import java.util.UUID;
|
|||||||
* <ul>
|
* <ul>
|
||||||
* <li>{@link #accessibilityChannel}
|
* <li>{@link #accessibilityChannel}
|
||||||
* </ul>
|
* </ul>
|
||||||
|
*
|
||||||
|
* <h2 id="image-formats">Fields relevant to image formats</h2>
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link #tileCountHorizontal}
|
||||||
|
* <li>{@link #tileCountVertical}
|
||||||
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public final class Format implements Bundleable {
|
public final class Format implements Bundleable {
|
||||||
|
|
||||||
@ -172,6 +172,11 @@ public final class Format implements Bundleable {
|
|||||||
|
|
||||||
private int accessibilityChannel;
|
private int accessibilityChannel;
|
||||||
|
|
||||||
|
// Image specific
|
||||||
|
|
||||||
|
private int tileCountHorizontal;
|
||||||
|
private int tileCountVertical;
|
||||||
|
|
||||||
// Provided by the source.
|
// Provided by the source.
|
||||||
|
|
||||||
private @C.CryptoType int cryptoType;
|
private @C.CryptoType int cryptoType;
|
||||||
@ -195,6 +200,9 @@ public final class Format implements Bundleable {
|
|||||||
pcmEncoding = NO_VALUE;
|
pcmEncoding = NO_VALUE;
|
||||||
// Text specific.
|
// Text specific.
|
||||||
accessibilityChannel = NO_VALUE;
|
accessibilityChannel = NO_VALUE;
|
||||||
|
// Image specific.
|
||||||
|
tileCountHorizontal = NO_VALUE;
|
||||||
|
tileCountVertical = NO_VALUE;
|
||||||
// Provided by the source.
|
// Provided by the source.
|
||||||
cryptoType = C.CRYPTO_TYPE_NONE;
|
cryptoType = C.CRYPTO_TYPE_NONE;
|
||||||
}
|
}
|
||||||
@ -239,6 +247,9 @@ public final class Format implements Bundleable {
|
|||||||
this.encoderPadding = format.encoderPadding;
|
this.encoderPadding = format.encoderPadding;
|
||||||
// Text specific.
|
// Text specific.
|
||||||
this.accessibilityChannel = format.accessibilityChannel;
|
this.accessibilityChannel = format.accessibilityChannel;
|
||||||
|
// Image specific.
|
||||||
|
this.tileCountHorizontal = format.tileCountHorizontal;
|
||||||
|
this.tileCountVertical = format.tileCountVertical;
|
||||||
// Provided by the source.
|
// Provided by the source.
|
||||||
this.cryptoType = format.cryptoType;
|
this.cryptoType = format.cryptoType;
|
||||||
}
|
}
|
||||||
@ -614,6 +625,32 @@ public final class Format implements Bundleable {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Image specific.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets {@link Format#tileCountHorizontal}. The default value is {@link #NO_VALUE}.
|
||||||
|
*
|
||||||
|
* @param tileCountHorizontal The {@link Format#accessibilityChannel}.
|
||||||
|
* @return The builder.
|
||||||
|
*/
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public Builder setTileCountHorizontal(int tileCountHorizontal) {
|
||||||
|
this.tileCountHorizontal = tileCountHorizontal;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets {@link Format#tileCountVertical}. The default value is {@link #NO_VALUE}.
|
||||||
|
*
|
||||||
|
* @param tileCountVertical The {@link Format#accessibilityChannel}.
|
||||||
|
* @return The builder.
|
||||||
|
*/
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public Builder setTileCountVertical(int tileCountVertical) {
|
||||||
|
this.tileCountVertical = tileCountVertical;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
// Provided by source.
|
// Provided by source.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -786,6 +823,15 @@ public final class Format implements Bundleable {
|
|||||||
/** The Accessibility channel, or {@link #NO_VALUE} if not known or applicable. */
|
/** The Accessibility channel, or {@link #NO_VALUE} if not known or applicable. */
|
||||||
@UnstableApi public final int accessibilityChannel;
|
@UnstableApi public final int accessibilityChannel;
|
||||||
|
|
||||||
|
// Image specific.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The number of horizontal tiles in an image, or {@link #NO_VALUE} if not known or applicable.
|
||||||
|
*/
|
||||||
|
@UnstableApi public final int tileCountHorizontal;
|
||||||
|
/** The number of vertical tiles in an image, or {@link #NO_VALUE} if not known or applicable. */
|
||||||
|
@UnstableApi public final int tileCountVertical;
|
||||||
|
|
||||||
// Provided by source.
|
// Provided by source.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1015,6 +1061,9 @@ public final class Format implements Bundleable {
|
|||||||
encoderPadding = builder.encoderPadding == NO_VALUE ? 0 : builder.encoderPadding;
|
encoderPadding = builder.encoderPadding == NO_VALUE ? 0 : builder.encoderPadding;
|
||||||
// Text specific.
|
// Text specific.
|
||||||
accessibilityChannel = builder.accessibilityChannel;
|
accessibilityChannel = builder.accessibilityChannel;
|
||||||
|
// Image specific.
|
||||||
|
tileCountHorizontal = builder.tileCountHorizontal;
|
||||||
|
tileCountVertical = builder.tileCountVertical;
|
||||||
// Provided by source.
|
// Provided by source.
|
||||||
if (builder.cryptoType == C.CRYPTO_TYPE_NONE && drmInitData != null) {
|
if (builder.cryptoType == C.CRYPTO_TYPE_NONE && drmInitData != null) {
|
||||||
// Encrypted content cannot use CRYPTO_TYPE_NONE.
|
// Encrypted content cannot use CRYPTO_TYPE_NONE.
|
||||||
@ -1275,6 +1324,9 @@ public final class Format implements Bundleable {
|
|||||||
result = 31 * result + encoderPadding;
|
result = 31 * result + encoderPadding;
|
||||||
// Text specific.
|
// Text specific.
|
||||||
result = 31 * result + accessibilityChannel;
|
result = 31 * result + accessibilityChannel;
|
||||||
|
// Image specific.
|
||||||
|
result = 31 * result + tileCountHorizontal;
|
||||||
|
result = 31 * result + tileCountVertical;
|
||||||
// Provided by the source.
|
// Provided by the source.
|
||||||
result = 31 * result + cryptoType;
|
result = 31 * result + cryptoType;
|
||||||
hashCode = result;
|
hashCode = result;
|
||||||
@ -1311,6 +1363,8 @@ public final class Format implements Bundleable {
|
|||||||
&& encoderDelay == other.encoderDelay
|
&& encoderDelay == other.encoderDelay
|
||||||
&& encoderPadding == other.encoderPadding
|
&& encoderPadding == other.encoderPadding
|
||||||
&& accessibilityChannel == other.accessibilityChannel
|
&& accessibilityChannel == other.accessibilityChannel
|
||||||
|
&& tileCountHorizontal == other.tileCountHorizontal
|
||||||
|
&& tileCountVertical == other.tileCountVertical
|
||||||
&& cryptoType == other.cryptoType
|
&& cryptoType == other.cryptoType
|
||||||
&& Float.compare(frameRate, other.frameRate) == 0
|
&& Float.compare(frameRate, other.frameRate) == 0
|
||||||
&& Float.compare(pixelWidthHeightRatio, other.pixelWidthHeightRatio) == 0
|
&& Float.compare(pixelWidthHeightRatio, other.pixelWidthHeightRatio) == 0
|
||||||
@ -1476,73 +1530,39 @@ public final class Format implements Bundleable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Bundleable implementation.
|
// Bundleable implementation.
|
||||||
@Documented
|
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
|
||||||
@Target(TYPE_USE)
|
|
||||||
@IntDef({
|
|
||||||
FIELD_ID,
|
|
||||||
FIELD_LABEL,
|
|
||||||
FIELD_LANGUAGE,
|
|
||||||
FIELD_SELECTION_FLAGS,
|
|
||||||
FIELD_ROLE_FLAGS,
|
|
||||||
FIELD_AVERAGE_BITRATE,
|
|
||||||
FIELD_PEAK_BITRATE,
|
|
||||||
FIELD_CODECS,
|
|
||||||
FIELD_METADATA,
|
|
||||||
FIELD_CONTAINER_MIME_TYPE,
|
|
||||||
FIELD_SAMPLE_MIME_TYPE,
|
|
||||||
FIELD_MAX_INPUT_SIZE,
|
|
||||||
FIELD_INITIALIZATION_DATA,
|
|
||||||
FIELD_DRM_INIT_DATA,
|
|
||||||
FIELD_SUBSAMPLE_OFFSET_US,
|
|
||||||
FIELD_WIDTH,
|
|
||||||
FIELD_HEIGHT,
|
|
||||||
FIELD_FRAME_RATE,
|
|
||||||
FIELD_ROTATION_DEGREES,
|
|
||||||
FIELD_PIXEL_WIDTH_HEIGHT_RATIO,
|
|
||||||
FIELD_PROJECTION_DATA,
|
|
||||||
FIELD_STEREO_MODE,
|
|
||||||
FIELD_COLOR_INFO,
|
|
||||||
FIELD_CHANNEL_COUNT,
|
|
||||||
FIELD_SAMPLE_RATE,
|
|
||||||
FIELD_PCM_ENCODING,
|
|
||||||
FIELD_ENCODER_DELAY,
|
|
||||||
FIELD_ENCODER_PADDING,
|
|
||||||
FIELD_ACCESSIBILITY_CHANNEL,
|
|
||||||
FIELD_CRYPTO_TYPE,
|
|
||||||
})
|
|
||||||
private @interface FieldNumber {}
|
|
||||||
|
|
||||||
private static final int FIELD_ID = 0;
|
private static final String FIELD_ID = Util.intToStringMaxRadix(0);
|
||||||
private static final int FIELD_LABEL = 1;
|
private static final String FIELD_LABEL = Util.intToStringMaxRadix(1);
|
||||||
private static final int FIELD_LANGUAGE = 2;
|
private static final String FIELD_LANGUAGE = Util.intToStringMaxRadix(2);
|
||||||
private static final int FIELD_SELECTION_FLAGS = 3;
|
private static final String FIELD_SELECTION_FLAGS = Util.intToStringMaxRadix(3);
|
||||||
private static final int FIELD_ROLE_FLAGS = 4;
|
private static final String FIELD_ROLE_FLAGS = Util.intToStringMaxRadix(4);
|
||||||
private static final int FIELD_AVERAGE_BITRATE = 5;
|
private static final String FIELD_AVERAGE_BITRATE = Util.intToStringMaxRadix(5);
|
||||||
private static final int FIELD_PEAK_BITRATE = 6;
|
private static final String FIELD_PEAK_BITRATE = Util.intToStringMaxRadix(6);
|
||||||
private static final int FIELD_CODECS = 7;
|
private static final String FIELD_CODECS = Util.intToStringMaxRadix(7);
|
||||||
private static final int FIELD_METADATA = 8;
|
private static final String FIELD_METADATA = Util.intToStringMaxRadix(8);
|
||||||
private static final int FIELD_CONTAINER_MIME_TYPE = 9;
|
private static final String FIELD_CONTAINER_MIME_TYPE = Util.intToStringMaxRadix(9);
|
||||||
private static final int FIELD_SAMPLE_MIME_TYPE = 10;
|
private static final String FIELD_SAMPLE_MIME_TYPE = Util.intToStringMaxRadix(10);
|
||||||
private static final int FIELD_MAX_INPUT_SIZE = 11;
|
private static final String FIELD_MAX_INPUT_SIZE = Util.intToStringMaxRadix(11);
|
||||||
private static final int FIELD_INITIALIZATION_DATA = 12;
|
private static final String FIELD_INITIALIZATION_DATA = Util.intToStringMaxRadix(12);
|
||||||
private static final int FIELD_DRM_INIT_DATA = 13;
|
private static final String FIELD_DRM_INIT_DATA = Util.intToStringMaxRadix(13);
|
||||||
private static final int FIELD_SUBSAMPLE_OFFSET_US = 14;
|
private static final String FIELD_SUBSAMPLE_OFFSET_US = Util.intToStringMaxRadix(14);
|
||||||
private static final int FIELD_WIDTH = 15;
|
private static final String FIELD_WIDTH = Util.intToStringMaxRadix(15);
|
||||||
private static final int FIELD_HEIGHT = 16;
|
private static final String FIELD_HEIGHT = Util.intToStringMaxRadix(16);
|
||||||
private static final int FIELD_FRAME_RATE = 17;
|
private static final String FIELD_FRAME_RATE = Util.intToStringMaxRadix(17);
|
||||||
private static final int FIELD_ROTATION_DEGREES = 18;
|
private static final String FIELD_ROTATION_DEGREES = Util.intToStringMaxRadix(18);
|
||||||
private static final int FIELD_PIXEL_WIDTH_HEIGHT_RATIO = 19;
|
private static final String FIELD_PIXEL_WIDTH_HEIGHT_RATIO = Util.intToStringMaxRadix(19);
|
||||||
private static final int FIELD_PROJECTION_DATA = 20;
|
private static final String FIELD_PROJECTION_DATA = Util.intToStringMaxRadix(20);
|
||||||
private static final int FIELD_STEREO_MODE = 21;
|
private static final String FIELD_STEREO_MODE = Util.intToStringMaxRadix(21);
|
||||||
private static final int FIELD_COLOR_INFO = 22;
|
private static final String FIELD_COLOR_INFO = Util.intToStringMaxRadix(22);
|
||||||
private static final int FIELD_CHANNEL_COUNT = 23;
|
private static final String FIELD_CHANNEL_COUNT = Util.intToStringMaxRadix(23);
|
||||||
private static final int FIELD_SAMPLE_RATE = 24;
|
private static final String FIELD_SAMPLE_RATE = Util.intToStringMaxRadix(24);
|
||||||
private static final int FIELD_PCM_ENCODING = 25;
|
private static final String FIELD_PCM_ENCODING = Util.intToStringMaxRadix(25);
|
||||||
private static final int FIELD_ENCODER_DELAY = 26;
|
private static final String FIELD_ENCODER_DELAY = Util.intToStringMaxRadix(26);
|
||||||
private static final int FIELD_ENCODER_PADDING = 27;
|
private static final String FIELD_ENCODER_PADDING = Util.intToStringMaxRadix(27);
|
||||||
private static final int FIELD_ACCESSIBILITY_CHANNEL = 28;
|
private static final String FIELD_ACCESSIBILITY_CHANNEL = Util.intToStringMaxRadix(28);
|
||||||
private static final int FIELD_CRYPTO_TYPE = 29;
|
private static final String FIELD_CRYPTO_TYPE = Util.intToStringMaxRadix(29);
|
||||||
|
private static final String FIELD_TILE_COUNT_HORIZONTAL = Util.intToStringMaxRadix(30);
|
||||||
|
private static final String FIELD_TILE_COUNT_VERTICAL = Util.intToStringMaxRadix(31);
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Override
|
@Override
|
||||||
@ -1557,51 +1577,54 @@ public final class Format implements Bundleable {
|
|||||||
@UnstableApi
|
@UnstableApi
|
||||||
public Bundle toBundle(boolean excludeMetadata) {
|
public Bundle toBundle(boolean excludeMetadata) {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putString(keyForField(FIELD_ID), id);
|
bundle.putString(FIELD_ID, id);
|
||||||
bundle.putString(keyForField(FIELD_LABEL), label);
|
bundle.putString(FIELD_LABEL, label);
|
||||||
bundle.putString(keyForField(FIELD_LANGUAGE), language);
|
bundle.putString(FIELD_LANGUAGE, language);
|
||||||
bundle.putInt(keyForField(FIELD_SELECTION_FLAGS), selectionFlags);
|
bundle.putInt(FIELD_SELECTION_FLAGS, selectionFlags);
|
||||||
bundle.putInt(keyForField(FIELD_ROLE_FLAGS), roleFlags);
|
bundle.putInt(FIELD_ROLE_FLAGS, roleFlags);
|
||||||
bundle.putInt(keyForField(FIELD_AVERAGE_BITRATE), averageBitrate);
|
bundle.putInt(FIELD_AVERAGE_BITRATE, averageBitrate);
|
||||||
bundle.putInt(keyForField(FIELD_PEAK_BITRATE), peakBitrate);
|
bundle.putInt(FIELD_PEAK_BITRATE, peakBitrate);
|
||||||
bundle.putString(keyForField(FIELD_CODECS), codecs);
|
bundle.putString(FIELD_CODECS, codecs);
|
||||||
if (!excludeMetadata) {
|
if (!excludeMetadata) {
|
||||||
// TODO (internal ref: b/239701618)
|
// TODO (internal ref: b/239701618)
|
||||||
bundle.putParcelable(keyForField(FIELD_METADATA), metadata);
|
bundle.putParcelable(FIELD_METADATA, metadata);
|
||||||
}
|
}
|
||||||
// Container specific.
|
// Container specific.
|
||||||
bundle.putString(keyForField(FIELD_CONTAINER_MIME_TYPE), containerMimeType);
|
bundle.putString(FIELD_CONTAINER_MIME_TYPE, containerMimeType);
|
||||||
// Sample specific.
|
// Sample specific.
|
||||||
bundle.putString(keyForField(FIELD_SAMPLE_MIME_TYPE), sampleMimeType);
|
bundle.putString(FIELD_SAMPLE_MIME_TYPE, sampleMimeType);
|
||||||
bundle.putInt(keyForField(FIELD_MAX_INPUT_SIZE), maxInputSize);
|
bundle.putInt(FIELD_MAX_INPUT_SIZE, maxInputSize);
|
||||||
for (int i = 0; i < initializationData.size(); i++) {
|
for (int i = 0; i < initializationData.size(); i++) {
|
||||||
bundle.putByteArray(keyForInitializationData(i), initializationData.get(i));
|
bundle.putByteArray(keyForInitializationData(i), initializationData.get(i));
|
||||||
}
|
}
|
||||||
// DrmInitData doesn't need to be Bundleable as it's only used in the playing process to
|
// DrmInitData doesn't need to be Bundleable as it's only used in the playing process to
|
||||||
// initialize the decoder.
|
// initialize the decoder.
|
||||||
bundle.putParcelable(keyForField(FIELD_DRM_INIT_DATA), drmInitData);
|
bundle.putParcelable(FIELD_DRM_INIT_DATA, drmInitData);
|
||||||
bundle.putLong(keyForField(FIELD_SUBSAMPLE_OFFSET_US), subsampleOffsetUs);
|
bundle.putLong(FIELD_SUBSAMPLE_OFFSET_US, subsampleOffsetUs);
|
||||||
// Video specific.
|
// Video specific.
|
||||||
bundle.putInt(keyForField(FIELD_WIDTH), width);
|
bundle.putInt(FIELD_WIDTH, width);
|
||||||
bundle.putInt(keyForField(FIELD_HEIGHT), height);
|
bundle.putInt(FIELD_HEIGHT, height);
|
||||||
bundle.putFloat(keyForField(FIELD_FRAME_RATE), frameRate);
|
bundle.putFloat(FIELD_FRAME_RATE, frameRate);
|
||||||
bundle.putInt(keyForField(FIELD_ROTATION_DEGREES), rotationDegrees);
|
bundle.putInt(FIELD_ROTATION_DEGREES, rotationDegrees);
|
||||||
bundle.putFloat(keyForField(FIELD_PIXEL_WIDTH_HEIGHT_RATIO), pixelWidthHeightRatio);
|
bundle.putFloat(FIELD_PIXEL_WIDTH_HEIGHT_RATIO, pixelWidthHeightRatio);
|
||||||
bundle.putByteArray(keyForField(FIELD_PROJECTION_DATA), projectionData);
|
bundle.putByteArray(FIELD_PROJECTION_DATA, projectionData);
|
||||||
bundle.putInt(keyForField(FIELD_STEREO_MODE), stereoMode);
|
bundle.putInt(FIELD_STEREO_MODE, stereoMode);
|
||||||
if (colorInfo != null) {
|
if (colorInfo != null) {
|
||||||
bundle.putBundle(keyForField(FIELD_COLOR_INFO), colorInfo.toBundle());
|
bundle.putBundle(FIELD_COLOR_INFO, colorInfo.toBundle());
|
||||||
}
|
}
|
||||||
// Audio specific.
|
// Audio specific.
|
||||||
bundle.putInt(keyForField(FIELD_CHANNEL_COUNT), channelCount);
|
bundle.putInt(FIELD_CHANNEL_COUNT, channelCount);
|
||||||
bundle.putInt(keyForField(FIELD_SAMPLE_RATE), sampleRate);
|
bundle.putInt(FIELD_SAMPLE_RATE, sampleRate);
|
||||||
bundle.putInt(keyForField(FIELD_PCM_ENCODING), pcmEncoding);
|
bundle.putInt(FIELD_PCM_ENCODING, pcmEncoding);
|
||||||
bundle.putInt(keyForField(FIELD_ENCODER_DELAY), encoderDelay);
|
bundle.putInt(FIELD_ENCODER_DELAY, encoderDelay);
|
||||||
bundle.putInt(keyForField(FIELD_ENCODER_PADDING), encoderPadding);
|
bundle.putInt(FIELD_ENCODER_PADDING, encoderPadding);
|
||||||
// Text specific.
|
// Text specific.
|
||||||
bundle.putInt(keyForField(FIELD_ACCESSIBILITY_CHANNEL), accessibilityChannel);
|
bundle.putInt(FIELD_ACCESSIBILITY_CHANNEL, accessibilityChannel);
|
||||||
|
// Image specific.
|
||||||
|
bundle.putInt(FIELD_TILE_COUNT_HORIZONTAL, tileCountHorizontal);
|
||||||
|
bundle.putInt(FIELD_TILE_COUNT_VERTICAL, tileCountVertical);
|
||||||
// Source specific.
|
// Source specific.
|
||||||
bundle.putInt(keyForField(FIELD_CRYPTO_TYPE), cryptoType);
|
bundle.putInt(FIELD_CRYPTO_TYPE, cryptoType);
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1612,28 +1635,22 @@ public final class Format implements Bundleable {
|
|||||||
Builder builder = new Builder();
|
Builder builder = new Builder();
|
||||||
BundleableUtil.ensureClassLoader(bundle);
|
BundleableUtil.ensureClassLoader(bundle);
|
||||||
builder
|
builder
|
||||||
.setId(defaultIfNull(bundle.getString(keyForField(FIELD_ID)), DEFAULT.id))
|
.setId(defaultIfNull(bundle.getString(FIELD_ID), DEFAULT.id))
|
||||||
.setLabel(defaultIfNull(bundle.getString(keyForField(FIELD_LABEL)), DEFAULT.label))
|
.setLabel(defaultIfNull(bundle.getString(FIELD_LABEL), DEFAULT.label))
|
||||||
.setLanguage(defaultIfNull(bundle.getString(keyForField(FIELD_LANGUAGE)), DEFAULT.language))
|
.setLanguage(defaultIfNull(bundle.getString(FIELD_LANGUAGE), DEFAULT.language))
|
||||||
.setSelectionFlags(
|
.setSelectionFlags(bundle.getInt(FIELD_SELECTION_FLAGS, DEFAULT.selectionFlags))
|
||||||
bundle.getInt(keyForField(FIELD_SELECTION_FLAGS), DEFAULT.selectionFlags))
|
.setRoleFlags(bundle.getInt(FIELD_ROLE_FLAGS, DEFAULT.roleFlags))
|
||||||
.setRoleFlags(bundle.getInt(keyForField(FIELD_ROLE_FLAGS), DEFAULT.roleFlags))
|
.setAverageBitrate(bundle.getInt(FIELD_AVERAGE_BITRATE, DEFAULT.averageBitrate))
|
||||||
.setAverageBitrate(
|
.setPeakBitrate(bundle.getInt(FIELD_PEAK_BITRATE, DEFAULT.peakBitrate))
|
||||||
bundle.getInt(keyForField(FIELD_AVERAGE_BITRATE), DEFAULT.averageBitrate))
|
.setCodecs(defaultIfNull(bundle.getString(FIELD_CODECS), DEFAULT.codecs))
|
||||||
.setPeakBitrate(bundle.getInt(keyForField(FIELD_PEAK_BITRATE), DEFAULT.peakBitrate))
|
.setMetadata(defaultIfNull(bundle.getParcelable(FIELD_METADATA), DEFAULT.metadata))
|
||||||
.setCodecs(defaultIfNull(bundle.getString(keyForField(FIELD_CODECS)), DEFAULT.codecs))
|
|
||||||
.setMetadata(
|
|
||||||
defaultIfNull(bundle.getParcelable(keyForField(FIELD_METADATA)), DEFAULT.metadata))
|
|
||||||
// Container specific.
|
// Container specific.
|
||||||
.setContainerMimeType(
|
.setContainerMimeType(
|
||||||
defaultIfNull(
|
defaultIfNull(bundle.getString(FIELD_CONTAINER_MIME_TYPE), DEFAULT.containerMimeType))
|
||||||
bundle.getString(keyForField(FIELD_CONTAINER_MIME_TYPE)),
|
|
||||||
DEFAULT.containerMimeType))
|
|
||||||
// Sample specific.
|
// Sample specific.
|
||||||
.setSampleMimeType(
|
.setSampleMimeType(
|
||||||
defaultIfNull(
|
defaultIfNull(bundle.getString(FIELD_SAMPLE_MIME_TYPE), DEFAULT.sampleMimeType))
|
||||||
bundle.getString(keyForField(FIELD_SAMPLE_MIME_TYPE)), DEFAULT.sampleMimeType))
|
.setMaxInputSize(bundle.getInt(FIELD_MAX_INPUT_SIZE, DEFAULT.maxInputSize));
|
||||||
.setMaxInputSize(bundle.getInt(keyForField(FIELD_MAX_INPUT_SIZE), DEFAULT.maxInputSize));
|
|
||||||
|
|
||||||
List<byte[]> initializationData = new ArrayList<>();
|
List<byte[]> initializationData = new ArrayList<>();
|
||||||
for (int i = 0; ; i++) {
|
for (int i = 0; ; i++) {
|
||||||
@ -1645,51 +1662,55 @@ public final class Format implements Bundleable {
|
|||||||
}
|
}
|
||||||
builder
|
builder
|
||||||
.setInitializationData(initializationData)
|
.setInitializationData(initializationData)
|
||||||
.setDrmInitData(bundle.getParcelable(keyForField(FIELD_DRM_INIT_DATA)))
|
.setDrmInitData(bundle.getParcelable(FIELD_DRM_INIT_DATA))
|
||||||
.setSubsampleOffsetUs(
|
.setSubsampleOffsetUs(bundle.getLong(FIELD_SUBSAMPLE_OFFSET_US, DEFAULT.subsampleOffsetUs))
|
||||||
bundle.getLong(keyForField(FIELD_SUBSAMPLE_OFFSET_US), DEFAULT.subsampleOffsetUs))
|
|
||||||
// Video specific.
|
// Video specific.
|
||||||
.setWidth(bundle.getInt(keyForField(FIELD_WIDTH), DEFAULT.width))
|
.setWidth(bundle.getInt(FIELD_WIDTH, DEFAULT.width))
|
||||||
.setHeight(bundle.getInt(keyForField(FIELD_HEIGHT), DEFAULT.height))
|
.setHeight(bundle.getInt(FIELD_HEIGHT, DEFAULT.height))
|
||||||
.setFrameRate(bundle.getFloat(keyForField(FIELD_FRAME_RATE), DEFAULT.frameRate))
|
.setFrameRate(bundle.getFloat(FIELD_FRAME_RATE, DEFAULT.frameRate))
|
||||||
.setRotationDegrees(
|
.setRotationDegrees(bundle.getInt(FIELD_ROTATION_DEGREES, DEFAULT.rotationDegrees))
|
||||||
bundle.getInt(keyForField(FIELD_ROTATION_DEGREES), DEFAULT.rotationDegrees))
|
|
||||||
.setPixelWidthHeightRatio(
|
.setPixelWidthHeightRatio(
|
||||||
bundle.getFloat(
|
bundle.getFloat(FIELD_PIXEL_WIDTH_HEIGHT_RATIO, DEFAULT.pixelWidthHeightRatio))
|
||||||
keyForField(FIELD_PIXEL_WIDTH_HEIGHT_RATIO), DEFAULT.pixelWidthHeightRatio))
|
.setProjectionData(bundle.getByteArray(FIELD_PROJECTION_DATA))
|
||||||
.setProjectionData(bundle.getByteArray(keyForField(FIELD_PROJECTION_DATA)))
|
.setStereoMode(bundle.getInt(FIELD_STEREO_MODE, DEFAULT.stereoMode));
|
||||||
.setStereoMode(bundle.getInt(keyForField(FIELD_STEREO_MODE), DEFAULT.stereoMode));
|
Bundle colorInfoBundle = bundle.getBundle(FIELD_COLOR_INFO);
|
||||||
Bundle colorInfoBundle = bundle.getBundle(keyForField(FIELD_COLOR_INFO));
|
|
||||||
if (colorInfoBundle != null) {
|
if (colorInfoBundle != null) {
|
||||||
builder.setColorInfo(ColorInfo.CREATOR.fromBundle(colorInfoBundle));
|
builder.setColorInfo(ColorInfo.CREATOR.fromBundle(colorInfoBundle));
|
||||||
}
|
}
|
||||||
// Audio specific.
|
// Audio specific.
|
||||||
builder
|
builder
|
||||||
.setChannelCount(bundle.getInt(keyForField(FIELD_CHANNEL_COUNT), DEFAULT.channelCount))
|
.setChannelCount(bundle.getInt(FIELD_CHANNEL_COUNT, DEFAULT.channelCount))
|
||||||
.setSampleRate(bundle.getInt(keyForField(FIELD_SAMPLE_RATE), DEFAULT.sampleRate))
|
.setSampleRate(bundle.getInt(FIELD_SAMPLE_RATE, DEFAULT.sampleRate))
|
||||||
.setPcmEncoding(bundle.getInt(keyForField(FIELD_PCM_ENCODING), DEFAULT.pcmEncoding))
|
.setPcmEncoding(bundle.getInt(FIELD_PCM_ENCODING, DEFAULT.pcmEncoding))
|
||||||
.setEncoderDelay(bundle.getInt(keyForField(FIELD_ENCODER_DELAY), DEFAULT.encoderDelay))
|
.setEncoderDelay(bundle.getInt(FIELD_ENCODER_DELAY, DEFAULT.encoderDelay))
|
||||||
.setEncoderPadding(
|
.setEncoderPadding(bundle.getInt(FIELD_ENCODER_PADDING, DEFAULT.encoderPadding))
|
||||||
bundle.getInt(keyForField(FIELD_ENCODER_PADDING), DEFAULT.encoderPadding))
|
|
||||||
// Text specific.
|
// Text specific.
|
||||||
.setAccessibilityChannel(
|
.setAccessibilityChannel(
|
||||||
bundle.getInt(keyForField(FIELD_ACCESSIBILITY_CHANNEL), DEFAULT.accessibilityChannel))
|
bundle.getInt(FIELD_ACCESSIBILITY_CHANNEL, DEFAULT.accessibilityChannel))
|
||||||
|
// Image specific.
|
||||||
|
.setTileCountHorizontal(
|
||||||
|
bundle.getInt(FIELD_TILE_COUNT_HORIZONTAL, DEFAULT.tileCountHorizontal))
|
||||||
|
.setTileCountVertical(bundle.getInt(FIELD_TILE_COUNT_VERTICAL, DEFAULT.tileCountVertical))
|
||||||
// Source specific.
|
// Source specific.
|
||||||
.setCryptoType(bundle.getInt(keyForField(FIELD_CRYPTO_TYPE), DEFAULT.cryptoType));
|
.setCryptoType(bundle.getInt(FIELD_CRYPTO_TYPE, DEFAULT.cryptoType));
|
||||||
|
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String keyForField(@FieldNumber int field) {
|
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static String keyForInitializationData(int initialisationDataIndex) {
|
private static String keyForInitializationData(int initialisationDataIndex) {
|
||||||
return keyForField(FIELD_INITIALIZATION_DATA)
|
return FIELD_INITIALIZATION_DATA
|
||||||
+ "_"
|
+ "_"
|
||||||
+ Integer.toString(initialisationDataIndex, Character.MAX_RADIX);
|
+ Integer.toString(initialisationDataIndex, Character.MAX_RADIX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Utility method to get {@code defaultValue} if {@code value} is {@code null}. {@code
|
||||||
|
* defaultValue} can be {@code null}.
|
||||||
|
*
|
||||||
|
* <p>Note: Current implementations of getters in {@link Bundle}, for example {@link
|
||||||
|
* Bundle#getString(String, String)} does not allow the defaultValue to be {@code null}, hence the
|
||||||
|
* need for this method.
|
||||||
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
private static <T> T defaultIfNull(@Nullable T value, @Nullable T defaultValue) {
|
private static <T> T defaultIfNull(@Nullable T value, @Nullable T defaultValue) {
|
||||||
return value != null ? value : defaultValue;
|
return value != null ? value : defaultValue;
|
||||||
|
@ -16,17 +16,12 @@
|
|||||||
package androidx.media3.common;
|
package androidx.media3.common;
|
||||||
|
|
||||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.IntDef;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import androidx.media3.common.util.Util;
|
||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
import java.lang.annotation.Documented;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A rating expressed as "heart" or "no heart". It can be used to indicate whether the content is a
|
* A rating expressed as "heart" or "no heart". It can be used to indicate whether the content is a
|
||||||
@ -81,22 +76,16 @@ public final class HeartRating extends Rating {
|
|||||||
|
|
||||||
private static final @RatingType int TYPE = RATING_TYPE_HEART;
|
private static final @RatingType int TYPE = RATING_TYPE_HEART;
|
||||||
|
|
||||||
@Documented
|
private static final String FIELD_RATED = Util.intToStringMaxRadix(1);
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
private static final String FIELD_IS_HEART = Util.intToStringMaxRadix(2);
|
||||||
@Target(TYPE_USE)
|
|
||||||
@IntDef({FIELD_RATING_TYPE, FIELD_RATED, FIELD_IS_HEART})
|
|
||||||
private @interface FieldNumber {}
|
|
||||||
|
|
||||||
private static final int FIELD_RATED = 1;
|
|
||||||
private static final int FIELD_IS_HEART = 2;
|
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Override
|
@Override
|
||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putInt(keyForField(FIELD_RATING_TYPE), TYPE);
|
bundle.putInt(FIELD_RATING_TYPE, TYPE);
|
||||||
bundle.putBoolean(keyForField(FIELD_RATED), rated);
|
bundle.putBoolean(FIELD_RATED, rated);
|
||||||
bundle.putBoolean(keyForField(FIELD_IS_HEART), isHeart);
|
bundle.putBoolean(FIELD_IS_HEART, isHeart);
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,16 +93,10 @@ public final class HeartRating extends Rating {
|
|||||||
@UnstableApi public static final Creator<HeartRating> CREATOR = HeartRating::fromBundle;
|
@UnstableApi public static final Creator<HeartRating> CREATOR = HeartRating::fromBundle;
|
||||||
|
|
||||||
private static HeartRating fromBundle(Bundle bundle) {
|
private static HeartRating fromBundle(Bundle bundle) {
|
||||||
checkArgument(
|
checkArgument(bundle.getInt(FIELD_RATING_TYPE, /* defaultValue= */ RATING_TYPE_UNSET) == TYPE);
|
||||||
bundle.getInt(keyForField(FIELD_RATING_TYPE), /* defaultValue= */ RATING_TYPE_UNSET)
|
boolean isRated = bundle.getBoolean(FIELD_RATED, /* defaultValue= */ false);
|
||||||
== TYPE);
|
|
||||||
boolean isRated = bundle.getBoolean(keyForField(FIELD_RATED), /* defaultValue= */ false);
|
|
||||||
return isRated
|
return isRated
|
||||||
? new HeartRating(bundle.getBoolean(keyForField(FIELD_IS_HEART), /* defaultValue= */ false))
|
? new HeartRating(bundle.getBoolean(FIELD_IS_HEART, /* defaultValue= */ false))
|
||||||
: new HeartRating();
|
: new HeartRating();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String keyForField(@FieldNumber int field) {
|
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -17,11 +17,9 @@ package androidx.media3.common;
|
|||||||
|
|
||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
import static androidx.media3.common.util.Assertions.checkState;
|
import static androidx.media3.common.util.Assertions.checkState;
|
||||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.IntDef;
|
|
||||||
import androidx.annotation.IntRange;
|
import androidx.annotation.IntRange;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.util.Assertions;
|
import androidx.media3.common.util.Assertions;
|
||||||
@ -31,10 +29,6 @@ import com.google.common.collect.ImmutableList;
|
|||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
import com.google.errorprone.annotations.InlineMe;
|
import com.google.errorprone.annotations.InlineMe;
|
||||||
import java.lang.annotation.Documented;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -1122,7 +1116,7 @@ public final class MediaItem implements Bundleable {
|
|||||||
private float minPlaybackSpeed;
|
private float minPlaybackSpeed;
|
||||||
private float maxPlaybackSpeed;
|
private float maxPlaybackSpeed;
|
||||||
|
|
||||||
/** Constructs an instance. */
|
/** Creates a new instance with default values. */
|
||||||
public Builder() {
|
public Builder() {
|
||||||
this.targetOffsetMs = C.TIME_UNSET;
|
this.targetOffsetMs = C.TIME_UNSET;
|
||||||
this.minOffsetMs = C.TIME_UNSET;
|
this.minOffsetMs = C.TIME_UNSET;
|
||||||
@ -1304,33 +1298,31 @@ public final class MediaItem implements Bundleable {
|
|||||||
|
|
||||||
// Bundleable implementation.
|
// Bundleable implementation.
|
||||||
|
|
||||||
@Documented
|
private static final String FIELD_TARGET_OFFSET_MS = Util.intToStringMaxRadix(0);
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
private static final String FIELD_MIN_OFFSET_MS = Util.intToStringMaxRadix(1);
|
||||||
@Target(TYPE_USE)
|
private static final String FIELD_MAX_OFFSET_MS = Util.intToStringMaxRadix(2);
|
||||||
@IntDef({
|
private static final String FIELD_MIN_PLAYBACK_SPEED = Util.intToStringMaxRadix(3);
|
||||||
FIELD_TARGET_OFFSET_MS,
|
private static final String FIELD_MAX_PLAYBACK_SPEED = Util.intToStringMaxRadix(4);
|
||||||
FIELD_MIN_OFFSET_MS,
|
|
||||||
FIELD_MAX_OFFSET_MS,
|
|
||||||
FIELD_MIN_PLAYBACK_SPEED,
|
|
||||||
FIELD_MAX_PLAYBACK_SPEED
|
|
||||||
})
|
|
||||||
private @interface FieldNumber {}
|
|
||||||
|
|
||||||
private static final int FIELD_TARGET_OFFSET_MS = 0;
|
|
||||||
private static final int FIELD_MIN_OFFSET_MS = 1;
|
|
||||||
private static final int FIELD_MAX_OFFSET_MS = 2;
|
|
||||||
private static final int FIELD_MIN_PLAYBACK_SPEED = 3;
|
|
||||||
private static final int FIELD_MAX_PLAYBACK_SPEED = 4;
|
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Override
|
@Override
|
||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putLong(keyForField(FIELD_TARGET_OFFSET_MS), targetOffsetMs);
|
if (targetOffsetMs != UNSET.targetOffsetMs) {
|
||||||
bundle.putLong(keyForField(FIELD_MIN_OFFSET_MS), minOffsetMs);
|
bundle.putLong(FIELD_TARGET_OFFSET_MS, targetOffsetMs);
|
||||||
bundle.putLong(keyForField(FIELD_MAX_OFFSET_MS), maxOffsetMs);
|
}
|
||||||
bundle.putFloat(keyForField(FIELD_MIN_PLAYBACK_SPEED), minPlaybackSpeed);
|
if (minOffsetMs != UNSET.minOffsetMs) {
|
||||||
bundle.putFloat(keyForField(FIELD_MAX_PLAYBACK_SPEED), maxPlaybackSpeed);
|
bundle.putLong(FIELD_MIN_OFFSET_MS, minOffsetMs);
|
||||||
|
}
|
||||||
|
if (maxOffsetMs != UNSET.maxOffsetMs) {
|
||||||
|
bundle.putLong(FIELD_MAX_OFFSET_MS, maxOffsetMs);
|
||||||
|
}
|
||||||
|
if (minPlaybackSpeed != UNSET.minPlaybackSpeed) {
|
||||||
|
bundle.putFloat(FIELD_MIN_PLAYBACK_SPEED, minPlaybackSpeed);
|
||||||
|
}
|
||||||
|
if (maxPlaybackSpeed != UNSET.maxPlaybackSpeed) {
|
||||||
|
bundle.putFloat(FIELD_MAX_PLAYBACK_SPEED, maxPlaybackSpeed);
|
||||||
|
}
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1339,18 +1331,13 @@ public final class MediaItem implements Bundleable {
|
|||||||
public static final Creator<LiveConfiguration> CREATOR =
|
public static final Creator<LiveConfiguration> CREATOR =
|
||||||
bundle ->
|
bundle ->
|
||||||
new LiveConfiguration(
|
new LiveConfiguration(
|
||||||
bundle.getLong(
|
bundle.getLong(FIELD_TARGET_OFFSET_MS, /* defaultValue= */ UNSET.targetOffsetMs),
|
||||||
keyForField(FIELD_TARGET_OFFSET_MS), /* defaultValue= */ C.TIME_UNSET),
|
bundle.getLong(FIELD_MIN_OFFSET_MS, /* defaultValue= */ UNSET.minOffsetMs),
|
||||||
bundle.getLong(keyForField(FIELD_MIN_OFFSET_MS), /* defaultValue= */ C.TIME_UNSET),
|
bundle.getLong(FIELD_MAX_OFFSET_MS, /* defaultValue= */ UNSET.maxOffsetMs),
|
||||||
bundle.getLong(keyForField(FIELD_MAX_OFFSET_MS), /* defaultValue= */ C.TIME_UNSET),
|
|
||||||
bundle.getFloat(
|
bundle.getFloat(
|
||||||
keyForField(FIELD_MIN_PLAYBACK_SPEED), /* defaultValue= */ C.RATE_UNSET),
|
FIELD_MIN_PLAYBACK_SPEED, /* defaultValue= */ UNSET.minPlaybackSpeed),
|
||||||
bundle.getFloat(
|
bundle.getFloat(
|
||||||
keyForField(FIELD_MAX_PLAYBACK_SPEED), /* defaultValue= */ C.RATE_UNSET));
|
FIELD_MAX_PLAYBACK_SPEED, /* defaultValue= */ UNSET.maxPlaybackSpeed));
|
||||||
|
|
||||||
private static String keyForField(@LiveConfiguration.FieldNumber int field) {
|
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Properties for a text track. */
|
/** Properties for a text track. */
|
||||||
@ -1589,7 +1576,7 @@ public final class MediaItem implements Bundleable {
|
|||||||
private boolean relativeToDefaultPosition;
|
private boolean relativeToDefaultPosition;
|
||||||
private boolean startsAtKeyFrame;
|
private boolean startsAtKeyFrame;
|
||||||
|
|
||||||
/** Constructs an instance. */
|
/** Creates a new instance with default values. */
|
||||||
public Builder() {
|
public Builder() {
|
||||||
endPositionMs = C.TIME_END_OF_SOURCE;
|
endPositionMs = C.TIME_END_OF_SOURCE;
|
||||||
}
|
}
|
||||||
@ -1742,33 +1729,31 @@ public final class MediaItem implements Bundleable {
|
|||||||
|
|
||||||
// Bundleable implementation.
|
// Bundleable implementation.
|
||||||
|
|
||||||
@Documented
|
private static final String FIELD_START_POSITION_MS = Util.intToStringMaxRadix(0);
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
private static final String FIELD_END_POSITION_MS = Util.intToStringMaxRadix(1);
|
||||||
@Target(TYPE_USE)
|
private static final String FIELD_RELATIVE_TO_LIVE_WINDOW = Util.intToStringMaxRadix(2);
|
||||||
@IntDef({
|
private static final String FIELD_RELATIVE_TO_DEFAULT_POSITION = Util.intToStringMaxRadix(3);
|
||||||
FIELD_START_POSITION_MS,
|
private static final String FIELD_STARTS_AT_KEY_FRAME = Util.intToStringMaxRadix(4);
|
||||||
FIELD_END_POSITION_MS,
|
|
||||||
FIELD_RELATIVE_TO_LIVE_WINDOW,
|
|
||||||
FIELD_RELATIVE_TO_DEFAULT_POSITION,
|
|
||||||
FIELD_STARTS_AT_KEY_FRAME
|
|
||||||
})
|
|
||||||
private @interface FieldNumber {}
|
|
||||||
|
|
||||||
private static final int FIELD_START_POSITION_MS = 0;
|
|
||||||
private static final int FIELD_END_POSITION_MS = 1;
|
|
||||||
private static final int FIELD_RELATIVE_TO_LIVE_WINDOW = 2;
|
|
||||||
private static final int FIELD_RELATIVE_TO_DEFAULT_POSITION = 3;
|
|
||||||
private static final int FIELD_STARTS_AT_KEY_FRAME = 4;
|
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Override
|
@Override
|
||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putLong(keyForField(FIELD_START_POSITION_MS), startPositionMs);
|
if (startPositionMs != UNSET.startPositionMs) {
|
||||||
bundle.putLong(keyForField(FIELD_END_POSITION_MS), endPositionMs);
|
bundle.putLong(FIELD_START_POSITION_MS, startPositionMs);
|
||||||
bundle.putBoolean(keyForField(FIELD_RELATIVE_TO_LIVE_WINDOW), relativeToLiveWindow);
|
}
|
||||||
bundle.putBoolean(keyForField(FIELD_RELATIVE_TO_DEFAULT_POSITION), relativeToDefaultPosition);
|
if (endPositionMs != UNSET.endPositionMs) {
|
||||||
bundle.putBoolean(keyForField(FIELD_STARTS_AT_KEY_FRAME), startsAtKeyFrame);
|
bundle.putLong(FIELD_END_POSITION_MS, endPositionMs);
|
||||||
|
}
|
||||||
|
if (relativeToLiveWindow != UNSET.relativeToLiveWindow) {
|
||||||
|
bundle.putBoolean(FIELD_RELATIVE_TO_LIVE_WINDOW, relativeToLiveWindow);
|
||||||
|
}
|
||||||
|
if (relativeToDefaultPosition != UNSET.relativeToDefaultPosition) {
|
||||||
|
bundle.putBoolean(FIELD_RELATIVE_TO_DEFAULT_POSITION, relativeToDefaultPosition);
|
||||||
|
}
|
||||||
|
if (startsAtKeyFrame != UNSET.startsAtKeyFrame) {
|
||||||
|
bundle.putBoolean(FIELD_STARTS_AT_KEY_FRAME, startsAtKeyFrame);
|
||||||
|
}
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1778,22 +1763,22 @@ public final class MediaItem implements Bundleable {
|
|||||||
bundle ->
|
bundle ->
|
||||||
new ClippingConfiguration.Builder()
|
new ClippingConfiguration.Builder()
|
||||||
.setStartPositionMs(
|
.setStartPositionMs(
|
||||||
bundle.getLong(keyForField(FIELD_START_POSITION_MS), /* defaultValue= */ 0))
|
|
||||||
.setEndPositionMs(
|
|
||||||
bundle.getLong(
|
bundle.getLong(
|
||||||
keyForField(FIELD_END_POSITION_MS),
|
FIELD_START_POSITION_MS, /* defaultValue= */ UNSET.startPositionMs))
|
||||||
/* defaultValue= */ C.TIME_END_OF_SOURCE))
|
.setEndPositionMs(
|
||||||
|
bundle.getLong(FIELD_END_POSITION_MS, /* defaultValue= */ UNSET.endPositionMs))
|
||||||
.setRelativeToLiveWindow(
|
.setRelativeToLiveWindow(
|
||||||
bundle.getBoolean(keyForField(FIELD_RELATIVE_TO_LIVE_WINDOW), false))
|
bundle.getBoolean(
|
||||||
|
FIELD_RELATIVE_TO_LIVE_WINDOW,
|
||||||
|
/* defaultValue= */ UNSET.relativeToLiveWindow))
|
||||||
.setRelativeToDefaultPosition(
|
.setRelativeToDefaultPosition(
|
||||||
bundle.getBoolean(keyForField(FIELD_RELATIVE_TO_DEFAULT_POSITION), false))
|
bundle.getBoolean(
|
||||||
|
FIELD_RELATIVE_TO_DEFAULT_POSITION,
|
||||||
|
/* defaultValue= */ UNSET.relativeToDefaultPosition))
|
||||||
.setStartsAtKeyFrame(
|
.setStartsAtKeyFrame(
|
||||||
bundle.getBoolean(keyForField(FIELD_STARTS_AT_KEY_FRAME), false))
|
bundle.getBoolean(
|
||||||
|
FIELD_STARTS_AT_KEY_FRAME, /* defaultValue= */ UNSET.startsAtKeyFrame))
|
||||||
.buildClippingProperties();
|
.buildClippingProperties();
|
||||||
|
|
||||||
private static String keyForField(@ClippingConfiguration.FieldNumber int field) {
|
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1912,28 +1897,22 @@ public final class MediaItem implements Bundleable {
|
|||||||
|
|
||||||
// Bundleable implementation.
|
// Bundleable implementation.
|
||||||
|
|
||||||
@Documented
|
private static final String FIELD_MEDIA_URI = Util.intToStringMaxRadix(0);
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
private static final String FIELD_SEARCH_QUERY = Util.intToStringMaxRadix(1);
|
||||||
@Target(TYPE_USE)
|
private static final String FIELD_EXTRAS = Util.intToStringMaxRadix(2);
|
||||||
@IntDef({FIELD_MEDIA_URI, FIELD_SEARCH_QUERY, FIELD_EXTRAS})
|
|
||||||
private @interface FieldNumber {}
|
|
||||||
|
|
||||||
private static final int FIELD_MEDIA_URI = 0;
|
|
||||||
private static final int FIELD_SEARCH_QUERY = 1;
|
|
||||||
private static final int FIELD_EXTRAS = 2;
|
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Override
|
@Override
|
||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
if (mediaUri != null) {
|
if (mediaUri != null) {
|
||||||
bundle.putParcelable(keyForField(FIELD_MEDIA_URI), mediaUri);
|
bundle.putParcelable(FIELD_MEDIA_URI, mediaUri);
|
||||||
}
|
}
|
||||||
if (searchQuery != null) {
|
if (searchQuery != null) {
|
||||||
bundle.putString(keyForField(FIELD_SEARCH_QUERY), searchQuery);
|
bundle.putString(FIELD_SEARCH_QUERY, searchQuery);
|
||||||
}
|
}
|
||||||
if (extras != null) {
|
if (extras != null) {
|
||||||
bundle.putBundle(keyForField(FIELD_EXTRAS), extras);
|
bundle.putBundle(FIELD_EXTRAS, extras);
|
||||||
}
|
}
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
@ -1943,14 +1922,10 @@ public final class MediaItem implements Bundleable {
|
|||||||
public static final Creator<RequestMetadata> CREATOR =
|
public static final Creator<RequestMetadata> CREATOR =
|
||||||
bundle ->
|
bundle ->
|
||||||
new RequestMetadata.Builder()
|
new RequestMetadata.Builder()
|
||||||
.setMediaUri(bundle.getParcelable(keyForField(FIELD_MEDIA_URI)))
|
.setMediaUri(bundle.getParcelable(FIELD_MEDIA_URI))
|
||||||
.setSearchQuery(bundle.getString(keyForField(FIELD_SEARCH_QUERY)))
|
.setSearchQuery(bundle.getString(FIELD_SEARCH_QUERY))
|
||||||
.setExtras(bundle.getBundle(keyForField(FIELD_EXTRAS)))
|
.setExtras(bundle.getBundle(FIELD_EXTRAS))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
private static String keyForField(@RequestMetadata.FieldNumber int field) {
|
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2046,24 +2021,11 @@ public final class MediaItem implements Bundleable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Bundleable implementation.
|
// Bundleable implementation.
|
||||||
|
private static final String FIELD_MEDIA_ID = Util.intToStringMaxRadix(0);
|
||||||
@Documented
|
private static final String FIELD_LIVE_CONFIGURATION = Util.intToStringMaxRadix(1);
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
private static final String FIELD_MEDIA_METADATA = Util.intToStringMaxRadix(2);
|
||||||
@Target(TYPE_USE)
|
private static final String FIELD_CLIPPING_PROPERTIES = Util.intToStringMaxRadix(3);
|
||||||
@IntDef({
|
private static final String FIELD_REQUEST_METADATA = Util.intToStringMaxRadix(4);
|
||||||
FIELD_MEDIA_ID,
|
|
||||||
FIELD_LIVE_CONFIGURATION,
|
|
||||||
FIELD_MEDIA_METADATA,
|
|
||||||
FIELD_CLIPPING_PROPERTIES,
|
|
||||||
FIELD_REQUEST_METADATA
|
|
||||||
})
|
|
||||||
private @interface FieldNumber {}
|
|
||||||
|
|
||||||
private static final int FIELD_MEDIA_ID = 0;
|
|
||||||
private static final int FIELD_LIVE_CONFIGURATION = 1;
|
|
||||||
private static final int FIELD_MEDIA_METADATA = 2;
|
|
||||||
private static final int FIELD_CLIPPING_PROPERTIES = 3;
|
|
||||||
private static final int FIELD_REQUEST_METADATA = 4;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
@ -2075,11 +2037,21 @@ public final class MediaItem implements Bundleable {
|
|||||||
@Override
|
@Override
|
||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putString(keyForField(FIELD_MEDIA_ID), mediaId);
|
if (!mediaId.equals(DEFAULT_MEDIA_ID)) {
|
||||||
bundle.putBundle(keyForField(FIELD_LIVE_CONFIGURATION), liveConfiguration.toBundle());
|
bundle.putString(FIELD_MEDIA_ID, mediaId);
|
||||||
bundle.putBundle(keyForField(FIELD_MEDIA_METADATA), mediaMetadata.toBundle());
|
}
|
||||||
bundle.putBundle(keyForField(FIELD_CLIPPING_PROPERTIES), clippingConfiguration.toBundle());
|
if (!liveConfiguration.equals(LiveConfiguration.UNSET)) {
|
||||||
bundle.putBundle(keyForField(FIELD_REQUEST_METADATA), requestMetadata.toBundle());
|
bundle.putBundle(FIELD_LIVE_CONFIGURATION, liveConfiguration.toBundle());
|
||||||
|
}
|
||||||
|
if (!mediaMetadata.equals(MediaMetadata.EMPTY)) {
|
||||||
|
bundle.putBundle(FIELD_MEDIA_METADATA, mediaMetadata.toBundle());
|
||||||
|
}
|
||||||
|
if (!clippingConfiguration.equals(ClippingConfiguration.UNSET)) {
|
||||||
|
bundle.putBundle(FIELD_CLIPPING_PROPERTIES, clippingConfiguration.toBundle());
|
||||||
|
}
|
||||||
|
if (!requestMetadata.equals(RequestMetadata.EMPTY)) {
|
||||||
|
bundle.putBundle(FIELD_REQUEST_METADATA, requestMetadata.toBundle());
|
||||||
|
}
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2092,31 +2064,29 @@ public final class MediaItem implements Bundleable {
|
|||||||
|
|
||||||
@SuppressWarnings("deprecation") // Unbundling to ClippingProperties while it still exists.
|
@SuppressWarnings("deprecation") // Unbundling to ClippingProperties while it still exists.
|
||||||
private static MediaItem fromBundle(Bundle bundle) {
|
private static MediaItem fromBundle(Bundle bundle) {
|
||||||
String mediaId = checkNotNull(bundle.getString(keyForField(FIELD_MEDIA_ID), DEFAULT_MEDIA_ID));
|
String mediaId = checkNotNull(bundle.getString(FIELD_MEDIA_ID, DEFAULT_MEDIA_ID));
|
||||||
@Nullable
|
@Nullable Bundle liveConfigurationBundle = bundle.getBundle(FIELD_LIVE_CONFIGURATION);
|
||||||
Bundle liveConfigurationBundle = bundle.getBundle(keyForField(FIELD_LIVE_CONFIGURATION));
|
|
||||||
LiveConfiguration liveConfiguration;
|
LiveConfiguration liveConfiguration;
|
||||||
if (liveConfigurationBundle == null) {
|
if (liveConfigurationBundle == null) {
|
||||||
liveConfiguration = LiveConfiguration.UNSET;
|
liveConfiguration = LiveConfiguration.UNSET;
|
||||||
} else {
|
} else {
|
||||||
liveConfiguration = LiveConfiguration.CREATOR.fromBundle(liveConfigurationBundle);
|
liveConfiguration = LiveConfiguration.CREATOR.fromBundle(liveConfigurationBundle);
|
||||||
}
|
}
|
||||||
@Nullable Bundle mediaMetadataBundle = bundle.getBundle(keyForField(FIELD_MEDIA_METADATA));
|
@Nullable Bundle mediaMetadataBundle = bundle.getBundle(FIELD_MEDIA_METADATA);
|
||||||
MediaMetadata mediaMetadata;
|
MediaMetadata mediaMetadata;
|
||||||
if (mediaMetadataBundle == null) {
|
if (mediaMetadataBundle == null) {
|
||||||
mediaMetadata = MediaMetadata.EMPTY;
|
mediaMetadata = MediaMetadata.EMPTY;
|
||||||
} else {
|
} else {
|
||||||
mediaMetadata = MediaMetadata.CREATOR.fromBundle(mediaMetadataBundle);
|
mediaMetadata = MediaMetadata.CREATOR.fromBundle(mediaMetadataBundle);
|
||||||
}
|
}
|
||||||
@Nullable
|
@Nullable Bundle clippingConfigurationBundle = bundle.getBundle(FIELD_CLIPPING_PROPERTIES);
|
||||||
Bundle clippingConfigurationBundle = bundle.getBundle(keyForField(FIELD_CLIPPING_PROPERTIES));
|
|
||||||
ClippingProperties clippingConfiguration;
|
ClippingProperties clippingConfiguration;
|
||||||
if (clippingConfigurationBundle == null) {
|
if (clippingConfigurationBundle == null) {
|
||||||
clippingConfiguration = ClippingProperties.UNSET;
|
clippingConfiguration = ClippingProperties.UNSET;
|
||||||
} else {
|
} else {
|
||||||
clippingConfiguration = ClippingConfiguration.CREATOR.fromBundle(clippingConfigurationBundle);
|
clippingConfiguration = ClippingConfiguration.CREATOR.fromBundle(clippingConfigurationBundle);
|
||||||
}
|
}
|
||||||
@Nullable Bundle requestMetadataBundle = bundle.getBundle(keyForField(FIELD_REQUEST_METADATA));
|
@Nullable Bundle requestMetadataBundle = bundle.getBundle(FIELD_REQUEST_METADATA);
|
||||||
RequestMetadata requestMetadata;
|
RequestMetadata requestMetadata;
|
||||||
if (requestMetadataBundle == null) {
|
if (requestMetadataBundle == null) {
|
||||||
requestMetadata = RequestMetadata.EMPTY;
|
requestMetadata = RequestMetadata.EMPTY;
|
||||||
@ -2131,8 +2101,4 @@ public final class MediaItem implements Bundleable {
|
|||||||
mediaMetadata,
|
mediaMetadata,
|
||||||
requestMetadata);
|
requestMetadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String keyForField(@FieldNumber int field) {
|
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -29,11 +29,11 @@ public final class MediaLibraryInfo {
|
|||||||
|
|
||||||
/** The version of the library expressed as a string, for example "1.2.3" or "1.2.3-beta01". */
|
/** The version of the library expressed as a string, for example "1.2.3" or "1.2.3-beta01". */
|
||||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
|
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
|
||||||
public static final String VERSION = "1.0.0-beta03";
|
public static final String VERSION = "1.0.0-rc01";
|
||||||
|
|
||||||
/** The version of the library expressed as {@code TAG + "/" + VERSION}. */
|
/** The version of the library expressed as {@code TAG + "/" + VERSION}. */
|
||||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
||||||
public static final String VERSION_SLASHY = "AndroidXMedia3/1.0.0-beta03";
|
public static final String VERSION_SLASHY = "AndroidXMedia3/1.0.0-rc01";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The version of the library expressed as an integer, for example 1002003300.
|
* The version of the library expressed as an integer, for example 1002003300.
|
||||||
@ -47,7 +47,7 @@ public final class MediaLibraryInfo {
|
|||||||
* (123-045-006-3-00).
|
* (123-045-006-3-00).
|
||||||
*/
|
*/
|
||||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
||||||
public static final int VERSION_INT = 1_000_000_1_03;
|
public static final int VERSION_INT = 1_000_000_2_01;
|
||||||
|
|
||||||
/** Whether the library was compiled with {@link Assertions} checks enabled. */
|
/** Whether the library was compiled with {@link Assertions} checks enabled. */
|
||||||
public static final boolean ASSERTIONS_ENABLED = true;
|
public static final boolean ASSERTIONS_ENABLED = true;
|
||||||
|
@ -61,6 +61,7 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
@Nullable private Integer trackNumber;
|
@Nullable private Integer trackNumber;
|
||||||
@Nullable private Integer totalTrackCount;
|
@Nullable private Integer totalTrackCount;
|
||||||
@Nullable private @FolderType Integer folderType;
|
@Nullable private @FolderType Integer folderType;
|
||||||
|
@Nullable private Boolean isBrowsable;
|
||||||
@Nullable private Boolean isPlayable;
|
@Nullable private Boolean isPlayable;
|
||||||
@Nullable private Integer recordingYear;
|
@Nullable private Integer recordingYear;
|
||||||
@Nullable private Integer recordingMonth;
|
@Nullable private Integer recordingMonth;
|
||||||
@ -76,6 +77,7 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
@Nullable private CharSequence genre;
|
@Nullable private CharSequence genre;
|
||||||
@Nullable private CharSequence compilation;
|
@Nullable private CharSequence compilation;
|
||||||
@Nullable private CharSequence station;
|
@Nullable private CharSequence station;
|
||||||
|
@Nullable private @MediaType Integer mediaType;
|
||||||
@Nullable private Bundle extras;
|
@Nullable private Bundle extras;
|
||||||
|
|
||||||
public Builder() {}
|
public Builder() {}
|
||||||
@ -96,6 +98,7 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
this.trackNumber = mediaMetadata.trackNumber;
|
this.trackNumber = mediaMetadata.trackNumber;
|
||||||
this.totalTrackCount = mediaMetadata.totalTrackCount;
|
this.totalTrackCount = mediaMetadata.totalTrackCount;
|
||||||
this.folderType = mediaMetadata.folderType;
|
this.folderType = mediaMetadata.folderType;
|
||||||
|
this.isBrowsable = mediaMetadata.isBrowsable;
|
||||||
this.isPlayable = mediaMetadata.isPlayable;
|
this.isPlayable = mediaMetadata.isPlayable;
|
||||||
this.recordingYear = mediaMetadata.recordingYear;
|
this.recordingYear = mediaMetadata.recordingYear;
|
||||||
this.recordingMonth = mediaMetadata.recordingMonth;
|
this.recordingMonth = mediaMetadata.recordingMonth;
|
||||||
@ -111,6 +114,7 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
this.genre = mediaMetadata.genre;
|
this.genre = mediaMetadata.genre;
|
||||||
this.compilation = mediaMetadata.compilation;
|
this.compilation = mediaMetadata.compilation;
|
||||||
this.station = mediaMetadata.station;
|
this.station = mediaMetadata.station;
|
||||||
|
this.mediaType = mediaMetadata.mediaType;
|
||||||
this.extras = mediaMetadata.extras;
|
this.extras = mediaMetadata.extras;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -244,13 +248,26 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sets the {@link FolderType}. */
|
/**
|
||||||
|
* Sets the {@link FolderType}.
|
||||||
|
*
|
||||||
|
* <p>This method will be deprecated. Use {@link #setIsBrowsable} to indicate if an item is a
|
||||||
|
* browsable folder and use {@link #setMediaType} to indicate the type of the folder.
|
||||||
|
*/
|
||||||
@CanIgnoreReturnValue
|
@CanIgnoreReturnValue
|
||||||
public Builder setFolderType(@Nullable @FolderType Integer folderType) {
|
public Builder setFolderType(@Nullable @FolderType Integer folderType) {
|
||||||
this.folderType = folderType;
|
this.folderType = folderType;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Sets whether the media is a browsable folder. */
|
||||||
|
@UnstableApi
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public Builder setIsBrowsable(@Nullable Boolean isBrowsable) {
|
||||||
|
this.isBrowsable = isBrowsable;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/** Sets whether the media is playable. */
|
/** Sets whether the media is playable. */
|
||||||
@CanIgnoreReturnValue
|
@CanIgnoreReturnValue
|
||||||
public Builder setIsPlayable(@Nullable Boolean isPlayable) {
|
public Builder setIsPlayable(@Nullable Boolean isPlayable) {
|
||||||
@ -383,6 +400,14 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Sets the {@link MediaType}. */
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
@UnstableApi
|
||||||
|
public Builder setMediaType(@Nullable @MediaType Integer mediaType) {
|
||||||
|
this.mediaType = mediaType;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/** Sets the extras {@link Bundle}. */
|
/** Sets the extras {@link Bundle}. */
|
||||||
@CanIgnoreReturnValue
|
@CanIgnoreReturnValue
|
||||||
public Builder setExtras(@Nullable Bundle extras) {
|
public Builder setExtras(@Nullable Bundle extras) {
|
||||||
@ -481,6 +506,9 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
if (mediaMetadata.folderType != null) {
|
if (mediaMetadata.folderType != null) {
|
||||||
setFolderType(mediaMetadata.folderType);
|
setFolderType(mediaMetadata.folderType);
|
||||||
}
|
}
|
||||||
|
if (mediaMetadata.isBrowsable != null) {
|
||||||
|
setIsBrowsable(mediaMetadata.isBrowsable);
|
||||||
|
}
|
||||||
if (mediaMetadata.isPlayable != null) {
|
if (mediaMetadata.isPlayable != null) {
|
||||||
setIsPlayable(mediaMetadata.isPlayable);
|
setIsPlayable(mediaMetadata.isPlayable);
|
||||||
}
|
}
|
||||||
@ -529,6 +557,9 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
if (mediaMetadata.station != null) {
|
if (mediaMetadata.station != null) {
|
||||||
setStation(mediaMetadata.station);
|
setStation(mediaMetadata.station);
|
||||||
}
|
}
|
||||||
|
if (mediaMetadata.mediaType != null) {
|
||||||
|
setMediaType(mediaMetadata.mediaType);
|
||||||
|
}
|
||||||
if (mediaMetadata.extras != null) {
|
if (mediaMetadata.extras != null) {
|
||||||
setExtras(mediaMetadata.extras);
|
setExtras(mediaMetadata.extras);
|
||||||
}
|
}
|
||||||
@ -542,12 +573,186 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The type of content described by the media item.
|
||||||
|
*
|
||||||
|
* <p>One of {@link #MEDIA_TYPE_MIXED}, {@link #MEDIA_TYPE_MUSIC}, {@link
|
||||||
|
* #MEDIA_TYPE_AUDIO_BOOK_CHAPTER}, {@link #MEDIA_TYPE_PODCAST_EPISODE}, {@link
|
||||||
|
* #MEDIA_TYPE_RADIO_STATION}, {@link #MEDIA_TYPE_NEWS}, {@link #MEDIA_TYPE_VIDEO}, {@link
|
||||||
|
* #MEDIA_TYPE_TRAILER}, {@link #MEDIA_TYPE_MOVIE}, {@link #MEDIA_TYPE_TV_SHOW}, {@link
|
||||||
|
* #MEDIA_TYPE_ALBUM}, {@link #MEDIA_TYPE_ARTIST}, {@link #MEDIA_TYPE_GENRE}, {@link
|
||||||
|
* #MEDIA_TYPE_PLAYLIST}, {@link #MEDIA_TYPE_YEAR}, {@link #MEDIA_TYPE_AUDIO_BOOK}, {@link
|
||||||
|
* #MEDIA_TYPE_PODCAST}, {@link #MEDIA_TYPE_TV_CHANNEL}, {@link #MEDIA_TYPE_TV_SERIES}, {@link
|
||||||
|
* #MEDIA_TYPE_TV_SEASON}, {@link #MEDIA_TYPE_FOLDER_MIXED}, {@link #MEDIA_TYPE_FOLDER_ALBUMS},
|
||||||
|
* {@link #MEDIA_TYPE_FOLDER_ARTISTS}, {@link #MEDIA_TYPE_FOLDER_GENRES}, {@link
|
||||||
|
* #MEDIA_TYPE_FOLDER_PLAYLISTS}, {@link #MEDIA_TYPE_FOLDER_YEARS}, {@link
|
||||||
|
* #MEDIA_TYPE_FOLDER_AUDIO_BOOKS}, {@link #MEDIA_TYPE_FOLDER_PODCASTS}, {@link
|
||||||
|
* #MEDIA_TYPE_FOLDER_TV_CHANNELS}, {@link #MEDIA_TYPE_FOLDER_TV_SERIES}, {@link
|
||||||
|
* #MEDIA_TYPE_FOLDER_TV_SHOWS}, {@link #MEDIA_TYPE_FOLDER_RADIO_STATIONS}, {@link
|
||||||
|
* #MEDIA_TYPE_FOLDER_NEWS}, {@link #MEDIA_TYPE_FOLDER_VIDEOS}, {@link
|
||||||
|
* #MEDIA_TYPE_FOLDER_TRAILERS} or {@link #MEDIA_TYPE_FOLDER_MOVIES}.
|
||||||
|
*/
|
||||||
|
@Documented
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@Target(TYPE_USE)
|
||||||
|
@UnstableApi
|
||||||
|
@IntDef({
|
||||||
|
MEDIA_TYPE_MIXED,
|
||||||
|
MEDIA_TYPE_MUSIC,
|
||||||
|
MEDIA_TYPE_AUDIO_BOOK_CHAPTER,
|
||||||
|
MEDIA_TYPE_PODCAST_EPISODE,
|
||||||
|
MEDIA_TYPE_RADIO_STATION,
|
||||||
|
MEDIA_TYPE_NEWS,
|
||||||
|
MEDIA_TYPE_VIDEO,
|
||||||
|
MEDIA_TYPE_TRAILER,
|
||||||
|
MEDIA_TYPE_MOVIE,
|
||||||
|
MEDIA_TYPE_TV_SHOW,
|
||||||
|
MEDIA_TYPE_ALBUM,
|
||||||
|
MEDIA_TYPE_ARTIST,
|
||||||
|
MEDIA_TYPE_GENRE,
|
||||||
|
MEDIA_TYPE_PLAYLIST,
|
||||||
|
MEDIA_TYPE_YEAR,
|
||||||
|
MEDIA_TYPE_AUDIO_BOOK,
|
||||||
|
MEDIA_TYPE_PODCAST,
|
||||||
|
MEDIA_TYPE_TV_CHANNEL,
|
||||||
|
MEDIA_TYPE_TV_SERIES,
|
||||||
|
MEDIA_TYPE_TV_SEASON,
|
||||||
|
MEDIA_TYPE_FOLDER_MIXED,
|
||||||
|
MEDIA_TYPE_FOLDER_ALBUMS,
|
||||||
|
MEDIA_TYPE_FOLDER_ARTISTS,
|
||||||
|
MEDIA_TYPE_FOLDER_GENRES,
|
||||||
|
MEDIA_TYPE_FOLDER_PLAYLISTS,
|
||||||
|
MEDIA_TYPE_FOLDER_YEARS,
|
||||||
|
MEDIA_TYPE_FOLDER_AUDIO_BOOKS,
|
||||||
|
MEDIA_TYPE_FOLDER_PODCASTS,
|
||||||
|
MEDIA_TYPE_FOLDER_TV_CHANNELS,
|
||||||
|
MEDIA_TYPE_FOLDER_TV_SERIES,
|
||||||
|
MEDIA_TYPE_FOLDER_TV_SHOWS,
|
||||||
|
MEDIA_TYPE_FOLDER_RADIO_STATIONS,
|
||||||
|
MEDIA_TYPE_FOLDER_NEWS,
|
||||||
|
MEDIA_TYPE_FOLDER_VIDEOS,
|
||||||
|
MEDIA_TYPE_FOLDER_TRAILERS,
|
||||||
|
MEDIA_TYPE_FOLDER_MOVIES,
|
||||||
|
})
|
||||||
|
public @interface MediaType {}
|
||||||
|
|
||||||
|
/** Media of undetermined type or a mix of multiple {@linkplain MediaType media types}. */
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_MIXED = 0;
|
||||||
|
/** {@link MediaType} for music. */
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_MUSIC = 1;
|
||||||
|
/** {@link MediaType} for an audio book chapter. */
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_AUDIO_BOOK_CHAPTER = 2;
|
||||||
|
/** {@link MediaType} for a podcast episode. */
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_PODCAST_EPISODE = 3;
|
||||||
|
/** {@link MediaType} for a radio station. */
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_RADIO_STATION = 4;
|
||||||
|
/** {@link MediaType} for news. */
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_NEWS = 5;
|
||||||
|
/** {@link MediaType} for a video. */
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_VIDEO = 6;
|
||||||
|
/** {@link MediaType} for a movie trailer. */
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_TRAILER = 7;
|
||||||
|
/** {@link MediaType} for a movie. */
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_MOVIE = 8;
|
||||||
|
/** {@link MediaType} for a TV show. */
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_TV_SHOW = 9;
|
||||||
|
/**
|
||||||
|
* {@link MediaType} for a group of items (e.g., {@link #MEDIA_TYPE_MUSIC music}) belonging to an
|
||||||
|
* album.
|
||||||
|
*/
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_ALBUM = 10;
|
||||||
|
/**
|
||||||
|
* {@link MediaType} for a group of items (e.g., {@link #MEDIA_TYPE_MUSIC music}) from the same
|
||||||
|
* artist.
|
||||||
|
*/
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_ARTIST = 11;
|
||||||
|
/**
|
||||||
|
* {@link MediaType} for a group of items (e.g., {@link #MEDIA_TYPE_MUSIC music}) of the same
|
||||||
|
* genre.
|
||||||
|
*/
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_GENRE = 12;
|
||||||
|
/**
|
||||||
|
* {@link MediaType} for a group of items (e.g., {@link #MEDIA_TYPE_MUSIC music}) forming a
|
||||||
|
* playlist.
|
||||||
|
*/
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_PLAYLIST = 13;
|
||||||
|
/**
|
||||||
|
* {@link MediaType} for a group of items (e.g., {@link #MEDIA_TYPE_MUSIC music}) from the same
|
||||||
|
* year.
|
||||||
|
*/
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_YEAR = 14;
|
||||||
|
/**
|
||||||
|
* {@link MediaType} for a group of items forming an audio book. Items in this group are typically
|
||||||
|
* of type {@link #MEDIA_TYPE_AUDIO_BOOK_CHAPTER}.
|
||||||
|
*/
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_AUDIO_BOOK = 15;
|
||||||
|
/**
|
||||||
|
* {@link MediaType} for a group of items belonging to a podcast. Items in this group are
|
||||||
|
* typically of type {@link #MEDIA_TYPE_PODCAST_EPISODE}.
|
||||||
|
*/
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_PODCAST = 16;
|
||||||
|
/**
|
||||||
|
* {@link MediaType} for a group of items that are part of a TV channel. Items in this group are
|
||||||
|
* typically of type {@link #MEDIA_TYPE_TV_SHOW}, {@link #MEDIA_TYPE_TV_SERIES} or {@link
|
||||||
|
* #MEDIA_TYPE_MOVIE}.
|
||||||
|
*/
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_TV_CHANNEL = 17;
|
||||||
|
/**
|
||||||
|
* {@link MediaType} for a group of items that are part of a TV series. Items in this group are
|
||||||
|
* typically of type {@link #MEDIA_TYPE_TV_SHOW} or {@link #MEDIA_TYPE_TV_SEASON}.
|
||||||
|
*/
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_TV_SERIES = 18;
|
||||||
|
/**
|
||||||
|
* {@link MediaType} for a group of items that are part of a TV series. Items in this group are
|
||||||
|
* typically of type {@link #MEDIA_TYPE_TV_SHOW}.
|
||||||
|
*/
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_TV_SEASON = 19;
|
||||||
|
/** {@link MediaType} for a folder with mixed or undetermined content. */
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_FOLDER_MIXED = 20;
|
||||||
|
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_ALBUM albums}. */
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_FOLDER_ALBUMS = 21;
|
||||||
|
/** {@link MediaType} for a folder containing {@linkplain #FIELD_ARTIST artists}. */
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_FOLDER_ARTISTS = 22;
|
||||||
|
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_GENRE genres}. */
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_FOLDER_GENRES = 23;
|
||||||
|
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_PLAYLIST playlists}. */
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_FOLDER_PLAYLISTS = 24;
|
||||||
|
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_YEAR years}. */
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_FOLDER_YEARS = 25;
|
||||||
|
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_AUDIO_BOOK audio books}. */
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_FOLDER_AUDIO_BOOKS = 26;
|
||||||
|
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_PODCAST podcasts}. */
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_FOLDER_PODCASTS = 27;
|
||||||
|
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_TV_CHANNEL TV channels}. */
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_FOLDER_TV_CHANNELS = 28;
|
||||||
|
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_TV_SERIES TV series}. */
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_FOLDER_TV_SERIES = 29;
|
||||||
|
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_TV_SHOW TV shows}. */
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_FOLDER_TV_SHOWS = 30;
|
||||||
|
/**
|
||||||
|
* {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_RADIO_STATION radio
|
||||||
|
* stations}.
|
||||||
|
*/
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_FOLDER_RADIO_STATIONS = 31;
|
||||||
|
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_NEWS news}. */
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_FOLDER_NEWS = 32;
|
||||||
|
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_VIDEO videos}. */
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_FOLDER_VIDEOS = 33;
|
||||||
|
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_TRAILER movie trailers}. */
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_FOLDER_TRAILERS = 34;
|
||||||
|
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_MOVIE movies}. */
|
||||||
|
@UnstableApi public static final int MEDIA_TYPE_FOLDER_MOVIES = 35;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The folder type of the media item.
|
* The folder type of the media item.
|
||||||
*
|
*
|
||||||
* <p>This can be used as the type of a browsable bluetooth folder (see section 6.10.2.2 of the <a
|
* <p>This can be used as the type of a browsable bluetooth folder (see section 6.10.2.2 of the <a
|
||||||
* href="https://www.bluetooth.com/specifications/specs/a-v-remote-control-profile-1-6-2/">Bluetooth
|
* href="https://www.bluetooth.com/specifications/specs/a-v-remote-control-profile-1-6-2/">Bluetooth
|
||||||
* AVRCP 1.6.2</a>).
|
* AVRCP 1.6.2</a>).
|
||||||
|
*
|
||||||
|
* <p>One of {@link #FOLDER_TYPE_NONE}, {@link #FOLDER_TYPE_MIXED}, {@link #FOLDER_TYPE_TITLES},
|
||||||
|
* {@link #FOLDER_TYPE_ALBUMS}, {@link #FOLDER_TYPE_ARTISTS}, {@link #FOLDER_TYPE_GENRES}, {@link
|
||||||
|
* #FOLDER_TYPE_PLAYLISTS} or {@link #FOLDER_TYPE_YEARS}.
|
||||||
*/
|
*/
|
||||||
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
|
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
|
||||||
// with Kotlin usages from before TYPE_USE was added.
|
// with Kotlin usages from before TYPE_USE was added.
|
||||||
@ -588,6 +793,17 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
*
|
*
|
||||||
* <p>Values sourced from the ID3 v2.4 specification (See section 4.14 of
|
* <p>Values sourced from the ID3 v2.4 specification (See section 4.14 of
|
||||||
* https://id3.org/id3v2.4.0-frames).
|
* https://id3.org/id3v2.4.0-frames).
|
||||||
|
*
|
||||||
|
* <p>One of {@link #PICTURE_TYPE_OTHER}, {@link #PICTURE_TYPE_FILE_ICON}, {@link
|
||||||
|
* #PICTURE_TYPE_FILE_ICON_OTHER}, {@link #PICTURE_TYPE_FRONT_COVER}, {@link
|
||||||
|
* #PICTURE_TYPE_BACK_COVER}, {@link #PICTURE_TYPE_LEAFLET_PAGE}, {@link #PICTURE_TYPE_MEDIA},
|
||||||
|
* {@link #PICTURE_TYPE_LEAD_ARTIST_PERFORMER}, {@link #PICTURE_TYPE_ARTIST_PERFORMER}, {@link
|
||||||
|
* #PICTURE_TYPE_CONDUCTOR}, {@link #PICTURE_TYPE_BAND_ORCHESTRA}, {@link #PICTURE_TYPE_COMPOSER},
|
||||||
|
* {@link #PICTURE_TYPE_LYRICIST}, {@link #PICTURE_TYPE_RECORDING_LOCATION}, {@link
|
||||||
|
* #PICTURE_TYPE_DURING_RECORDING}, {@link #PICTURE_TYPE_DURING_PERFORMANCE}, {@link
|
||||||
|
* #PICTURE_TYPE_MOVIE_VIDEO_SCREEN_CAPTURE}, {@link #PICTURE_TYPE_A_BRIGHT_COLORED_FISH}, {@link
|
||||||
|
* #PICTURE_TYPE_ILLUSTRATION}, {@link #PICTURE_TYPE_BAND_ARTIST_LOGO} or {@link
|
||||||
|
* #PICTURE_TYPE_PUBLISHER_STUDIO_LOGO}.
|
||||||
*/
|
*/
|
||||||
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
|
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
|
||||||
// with Kotlin usages from before TYPE_USE was added.
|
// with Kotlin usages from before TYPE_USE was added.
|
||||||
@ -676,9 +892,16 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
@Nullable public final Integer trackNumber;
|
@Nullable public final Integer trackNumber;
|
||||||
/** Optional total number of tracks. */
|
/** Optional total number of tracks. */
|
||||||
@Nullable public final Integer totalTrackCount;
|
@Nullable public final Integer totalTrackCount;
|
||||||
/** Optional {@link FolderType}. */
|
/**
|
||||||
|
* Optional {@link FolderType}.
|
||||||
|
*
|
||||||
|
* <p>This field will be deprecated. Use {@link #isBrowsable} to indicate if an item is a
|
||||||
|
* browsable folder and use {@link #mediaType} to indicate the type of the folder.
|
||||||
|
*/
|
||||||
@Nullable public final @FolderType Integer folderType;
|
@Nullable public final @FolderType Integer folderType;
|
||||||
/** Optional boolean for media playability. */
|
/** Optional boolean to indicate that the media is a browsable folder. */
|
||||||
|
@UnstableApi @Nullable public final Boolean isBrowsable;
|
||||||
|
/** Optional boolean to indicate that the media is playable. */
|
||||||
@Nullable public final Boolean isPlayable;
|
@Nullable public final Boolean isPlayable;
|
||||||
/**
|
/**
|
||||||
* @deprecated Use {@link #recordingYear} instead.
|
* @deprecated Use {@link #recordingYear} instead.
|
||||||
@ -729,6 +952,8 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
@Nullable public final CharSequence compilation;
|
@Nullable public final CharSequence compilation;
|
||||||
/** Optional name of the station streaming the media. */
|
/** Optional name of the station streaming the media. */
|
||||||
@Nullable public final CharSequence station;
|
@Nullable public final CharSequence station;
|
||||||
|
/** Optional {@link MediaType}. */
|
||||||
|
@UnstableApi @Nullable public final @MediaType Integer mediaType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optional extras {@link Bundle}.
|
* Optional extras {@link Bundle}.
|
||||||
@ -739,6 +964,22 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
@Nullable public final Bundle extras;
|
@Nullable public final Bundle extras;
|
||||||
|
|
||||||
private MediaMetadata(Builder builder) {
|
private MediaMetadata(Builder builder) {
|
||||||
|
// Handle compatibility for deprecated fields.
|
||||||
|
@Nullable Boolean isBrowsable = builder.isBrowsable;
|
||||||
|
@Nullable Integer folderType = builder.folderType;
|
||||||
|
@Nullable Integer mediaType = builder.mediaType;
|
||||||
|
if (isBrowsable != null) {
|
||||||
|
if (!isBrowsable) {
|
||||||
|
folderType = FOLDER_TYPE_NONE;
|
||||||
|
} else if (folderType == null || folderType == FOLDER_TYPE_NONE) {
|
||||||
|
folderType = mediaType != null ? getFolderTypeFromMediaType(mediaType) : FOLDER_TYPE_MIXED;
|
||||||
|
}
|
||||||
|
} else if (folderType != null) {
|
||||||
|
isBrowsable = folderType != FOLDER_TYPE_NONE;
|
||||||
|
if (isBrowsable && mediaType == null) {
|
||||||
|
mediaType = getMediaTypeFromFolderType(folderType);
|
||||||
|
}
|
||||||
|
}
|
||||||
this.title = builder.title;
|
this.title = builder.title;
|
||||||
this.artist = builder.artist;
|
this.artist = builder.artist;
|
||||||
this.albumTitle = builder.albumTitle;
|
this.albumTitle = builder.albumTitle;
|
||||||
@ -753,7 +994,8 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
this.artworkUri = builder.artworkUri;
|
this.artworkUri = builder.artworkUri;
|
||||||
this.trackNumber = builder.trackNumber;
|
this.trackNumber = builder.trackNumber;
|
||||||
this.totalTrackCount = builder.totalTrackCount;
|
this.totalTrackCount = builder.totalTrackCount;
|
||||||
this.folderType = builder.folderType;
|
this.folderType = folderType;
|
||||||
|
this.isBrowsable = isBrowsable;
|
||||||
this.isPlayable = builder.isPlayable;
|
this.isPlayable = builder.isPlayable;
|
||||||
this.year = builder.recordingYear;
|
this.year = builder.recordingYear;
|
||||||
this.recordingYear = builder.recordingYear;
|
this.recordingYear = builder.recordingYear;
|
||||||
@ -770,6 +1012,7 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
this.genre = builder.genre;
|
this.genre = builder.genre;
|
||||||
this.compilation = builder.compilation;
|
this.compilation = builder.compilation;
|
||||||
this.station = builder.station;
|
this.station = builder.station;
|
||||||
|
this.mediaType = mediaType;
|
||||||
this.extras = builder.extras;
|
this.extras = builder.extras;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -802,6 +1045,7 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
&& Util.areEqual(trackNumber, that.trackNumber)
|
&& Util.areEqual(trackNumber, that.trackNumber)
|
||||||
&& Util.areEqual(totalTrackCount, that.totalTrackCount)
|
&& Util.areEqual(totalTrackCount, that.totalTrackCount)
|
||||||
&& Util.areEqual(folderType, that.folderType)
|
&& Util.areEqual(folderType, that.folderType)
|
||||||
|
&& Util.areEqual(isBrowsable, that.isBrowsable)
|
||||||
&& Util.areEqual(isPlayable, that.isPlayable)
|
&& Util.areEqual(isPlayable, that.isPlayable)
|
||||||
&& Util.areEqual(recordingYear, that.recordingYear)
|
&& Util.areEqual(recordingYear, that.recordingYear)
|
||||||
&& Util.areEqual(recordingMonth, that.recordingMonth)
|
&& Util.areEqual(recordingMonth, that.recordingMonth)
|
||||||
@ -816,7 +1060,8 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
&& Util.areEqual(totalDiscCount, that.totalDiscCount)
|
&& Util.areEqual(totalDiscCount, that.totalDiscCount)
|
||||||
&& Util.areEqual(genre, that.genre)
|
&& Util.areEqual(genre, that.genre)
|
||||||
&& Util.areEqual(compilation, that.compilation)
|
&& Util.areEqual(compilation, that.compilation)
|
||||||
&& Util.areEqual(station, that.station);
|
&& Util.areEqual(station, that.station)
|
||||||
|
&& Util.areEqual(mediaType, that.mediaType);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -837,6 +1082,7 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
trackNumber,
|
trackNumber,
|
||||||
totalTrackCount,
|
totalTrackCount,
|
||||||
folderType,
|
folderType,
|
||||||
|
isBrowsable,
|
||||||
isPlayable,
|
isPlayable,
|
||||||
recordingYear,
|
recordingYear,
|
||||||
recordingMonth,
|
recordingMonth,
|
||||||
@ -851,150 +1097,149 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
totalDiscCount,
|
totalDiscCount,
|
||||||
genre,
|
genre,
|
||||||
compilation,
|
compilation,
|
||||||
station);
|
station,
|
||||||
|
mediaType);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bundleable implementation.
|
// Bundleable implementation.
|
||||||
|
|
||||||
@Documented
|
private static final String FIELD_TITLE = Util.intToStringMaxRadix(0);
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
private static final String FIELD_ARTIST = Util.intToStringMaxRadix(1);
|
||||||
@Target(TYPE_USE)
|
private static final String FIELD_ALBUM_TITLE = Util.intToStringMaxRadix(2);
|
||||||
@IntDef({
|
private static final String FIELD_ALBUM_ARTIST = Util.intToStringMaxRadix(3);
|
||||||
FIELD_TITLE,
|
private static final String FIELD_DISPLAY_TITLE = Util.intToStringMaxRadix(4);
|
||||||
FIELD_ARTIST,
|
private static final String FIELD_SUBTITLE = Util.intToStringMaxRadix(5);
|
||||||
FIELD_ALBUM_TITLE,
|
private static final String FIELD_DESCRIPTION = Util.intToStringMaxRadix(6);
|
||||||
FIELD_ALBUM_ARTIST,
|
// 7 is reserved to maintain backward compatibility for a previously defined field.
|
||||||
FIELD_DISPLAY_TITLE,
|
private static final String FIELD_USER_RATING = Util.intToStringMaxRadix(8);
|
||||||
FIELD_SUBTITLE,
|
private static final String FIELD_OVERALL_RATING = Util.intToStringMaxRadix(9);
|
||||||
FIELD_DESCRIPTION,
|
private static final String FIELD_ARTWORK_DATA = Util.intToStringMaxRadix(10);
|
||||||
FIELD_MEDIA_URI,
|
private static final String FIELD_ARTWORK_URI = Util.intToStringMaxRadix(11);
|
||||||
FIELD_USER_RATING,
|
private static final String FIELD_TRACK_NUMBER = Util.intToStringMaxRadix(12);
|
||||||
FIELD_OVERALL_RATING,
|
private static final String FIELD_TOTAL_TRACK_COUNT = Util.intToStringMaxRadix(13);
|
||||||
FIELD_ARTWORK_DATA,
|
private static final String FIELD_FOLDER_TYPE = Util.intToStringMaxRadix(14);
|
||||||
FIELD_ARTWORK_DATA_TYPE,
|
private static final String FIELD_IS_PLAYABLE = Util.intToStringMaxRadix(15);
|
||||||
FIELD_ARTWORK_URI,
|
private static final String FIELD_RECORDING_YEAR = Util.intToStringMaxRadix(16);
|
||||||
FIELD_TRACK_NUMBER,
|
private static final String FIELD_RECORDING_MONTH = Util.intToStringMaxRadix(17);
|
||||||
FIELD_TOTAL_TRACK_COUNT,
|
private static final String FIELD_RECORDING_DAY = Util.intToStringMaxRadix(18);
|
||||||
FIELD_FOLDER_TYPE,
|
private static final String FIELD_RELEASE_YEAR = Util.intToStringMaxRadix(19);
|
||||||
FIELD_IS_PLAYABLE,
|
private static final String FIELD_RELEASE_MONTH = Util.intToStringMaxRadix(20);
|
||||||
FIELD_RECORDING_YEAR,
|
private static final String FIELD_RELEASE_DAY = Util.intToStringMaxRadix(21);
|
||||||
FIELD_RECORDING_MONTH,
|
private static final String FIELD_WRITER = Util.intToStringMaxRadix(22);
|
||||||
FIELD_RECORDING_DAY,
|
private static final String FIELD_COMPOSER = Util.intToStringMaxRadix(23);
|
||||||
FIELD_RELEASE_YEAR,
|
private static final String FIELD_CONDUCTOR = Util.intToStringMaxRadix(24);
|
||||||
FIELD_RELEASE_MONTH,
|
private static final String FIELD_DISC_NUMBER = Util.intToStringMaxRadix(25);
|
||||||
FIELD_RELEASE_DAY,
|
private static final String FIELD_TOTAL_DISC_COUNT = Util.intToStringMaxRadix(26);
|
||||||
FIELD_WRITER,
|
private static final String FIELD_GENRE = Util.intToStringMaxRadix(27);
|
||||||
FIELD_COMPOSER,
|
private static final String FIELD_COMPILATION = Util.intToStringMaxRadix(28);
|
||||||
FIELD_CONDUCTOR,
|
private static final String FIELD_ARTWORK_DATA_TYPE = Util.intToStringMaxRadix(29);
|
||||||
FIELD_DISC_NUMBER,
|
private static final String FIELD_STATION = Util.intToStringMaxRadix(30);
|
||||||
FIELD_TOTAL_DISC_COUNT,
|
private static final String FIELD_MEDIA_TYPE = Util.intToStringMaxRadix(31);
|
||||||
FIELD_GENRE,
|
private static final String FIELD_IS_BROWSABLE = Util.intToStringMaxRadix(32);
|
||||||
FIELD_COMPILATION,
|
private static final String FIELD_EXTRAS = Util.intToStringMaxRadix(1000);
|
||||||
FIELD_STATION,
|
|
||||||
FIELD_EXTRAS
|
|
||||||
})
|
|
||||||
private @interface FieldNumber {}
|
|
||||||
|
|
||||||
private static final int FIELD_TITLE = 0;
|
|
||||||
private static final int FIELD_ARTIST = 1;
|
|
||||||
private static final int FIELD_ALBUM_TITLE = 2;
|
|
||||||
private static final int FIELD_ALBUM_ARTIST = 3;
|
|
||||||
private static final int FIELD_DISPLAY_TITLE = 4;
|
|
||||||
private static final int FIELD_SUBTITLE = 5;
|
|
||||||
private static final int FIELD_DESCRIPTION = 6;
|
|
||||||
private static final int FIELD_MEDIA_URI = 7;
|
|
||||||
private static final int FIELD_USER_RATING = 8;
|
|
||||||
private static final int FIELD_OVERALL_RATING = 9;
|
|
||||||
private static final int FIELD_ARTWORK_DATA = 10;
|
|
||||||
private static final int FIELD_ARTWORK_URI = 11;
|
|
||||||
private static final int FIELD_TRACK_NUMBER = 12;
|
|
||||||
private static final int FIELD_TOTAL_TRACK_COUNT = 13;
|
|
||||||
private static final int FIELD_FOLDER_TYPE = 14;
|
|
||||||
private static final int FIELD_IS_PLAYABLE = 15;
|
|
||||||
private static final int FIELD_RECORDING_YEAR = 16;
|
|
||||||
private static final int FIELD_RECORDING_MONTH = 17;
|
|
||||||
private static final int FIELD_RECORDING_DAY = 18;
|
|
||||||
private static final int FIELD_RELEASE_YEAR = 19;
|
|
||||||
private static final int FIELD_RELEASE_MONTH = 20;
|
|
||||||
private static final int FIELD_RELEASE_DAY = 21;
|
|
||||||
private static final int FIELD_WRITER = 22;
|
|
||||||
private static final int FIELD_COMPOSER = 23;
|
|
||||||
private static final int FIELD_CONDUCTOR = 24;
|
|
||||||
private static final int FIELD_DISC_NUMBER = 25;
|
|
||||||
private static final int FIELD_TOTAL_DISC_COUNT = 26;
|
|
||||||
private static final int FIELD_GENRE = 27;
|
|
||||||
private static final int FIELD_COMPILATION = 28;
|
|
||||||
private static final int FIELD_ARTWORK_DATA_TYPE = 29;
|
|
||||||
private static final int FIELD_STATION = 30;
|
|
||||||
private static final int FIELD_EXTRAS = 1000;
|
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Override
|
@Override
|
||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putCharSequence(keyForField(FIELD_TITLE), title);
|
if (title != null) {
|
||||||
bundle.putCharSequence(keyForField(FIELD_ARTIST), artist);
|
bundle.putCharSequence(FIELD_TITLE, title);
|
||||||
bundle.putCharSequence(keyForField(FIELD_ALBUM_TITLE), albumTitle);
|
}
|
||||||
bundle.putCharSequence(keyForField(FIELD_ALBUM_ARTIST), albumArtist);
|
if (artist != null) {
|
||||||
bundle.putCharSequence(keyForField(FIELD_DISPLAY_TITLE), displayTitle);
|
bundle.putCharSequence(FIELD_ARTIST, artist);
|
||||||
bundle.putCharSequence(keyForField(FIELD_SUBTITLE), subtitle);
|
}
|
||||||
bundle.putCharSequence(keyForField(FIELD_DESCRIPTION), description);
|
if (albumTitle != null) {
|
||||||
bundle.putByteArray(keyForField(FIELD_ARTWORK_DATA), artworkData);
|
bundle.putCharSequence(FIELD_ALBUM_TITLE, albumTitle);
|
||||||
bundle.putParcelable(keyForField(FIELD_ARTWORK_URI), artworkUri);
|
}
|
||||||
bundle.putCharSequence(keyForField(FIELD_WRITER), writer);
|
if (albumArtist != null) {
|
||||||
bundle.putCharSequence(keyForField(FIELD_COMPOSER), composer);
|
bundle.putCharSequence(FIELD_ALBUM_ARTIST, albumArtist);
|
||||||
bundle.putCharSequence(keyForField(FIELD_CONDUCTOR), conductor);
|
}
|
||||||
bundle.putCharSequence(keyForField(FIELD_GENRE), genre);
|
if (displayTitle != null) {
|
||||||
bundle.putCharSequence(keyForField(FIELD_COMPILATION), compilation);
|
bundle.putCharSequence(FIELD_DISPLAY_TITLE, displayTitle);
|
||||||
bundle.putCharSequence(keyForField(FIELD_STATION), station);
|
}
|
||||||
|
if (subtitle != null) {
|
||||||
|
bundle.putCharSequence(FIELD_SUBTITLE, subtitle);
|
||||||
|
}
|
||||||
|
if (description != null) {
|
||||||
|
bundle.putCharSequence(FIELD_DESCRIPTION, description);
|
||||||
|
}
|
||||||
|
if (artworkData != null) {
|
||||||
|
bundle.putByteArray(FIELD_ARTWORK_DATA, artworkData);
|
||||||
|
}
|
||||||
|
if (artworkUri != null) {
|
||||||
|
bundle.putParcelable(FIELD_ARTWORK_URI, artworkUri);
|
||||||
|
}
|
||||||
|
if (writer != null) {
|
||||||
|
bundle.putCharSequence(FIELD_WRITER, writer);
|
||||||
|
}
|
||||||
|
if (composer != null) {
|
||||||
|
bundle.putCharSequence(FIELD_COMPOSER, composer);
|
||||||
|
}
|
||||||
|
if (conductor != null) {
|
||||||
|
bundle.putCharSequence(FIELD_CONDUCTOR, conductor);
|
||||||
|
}
|
||||||
|
if (genre != null) {
|
||||||
|
bundle.putCharSequence(FIELD_GENRE, genre);
|
||||||
|
}
|
||||||
|
if (compilation != null) {
|
||||||
|
bundle.putCharSequence(FIELD_COMPILATION, compilation);
|
||||||
|
}
|
||||||
|
if (station != null) {
|
||||||
|
bundle.putCharSequence(FIELD_STATION, station);
|
||||||
|
}
|
||||||
if (userRating != null) {
|
if (userRating != null) {
|
||||||
bundle.putBundle(keyForField(FIELD_USER_RATING), userRating.toBundle());
|
bundle.putBundle(FIELD_USER_RATING, userRating.toBundle());
|
||||||
}
|
}
|
||||||
if (overallRating != null) {
|
if (overallRating != null) {
|
||||||
bundle.putBundle(keyForField(FIELD_OVERALL_RATING), overallRating.toBundle());
|
bundle.putBundle(FIELD_OVERALL_RATING, overallRating.toBundle());
|
||||||
}
|
}
|
||||||
if (trackNumber != null) {
|
if (trackNumber != null) {
|
||||||
bundle.putInt(keyForField(FIELD_TRACK_NUMBER), trackNumber);
|
bundle.putInt(FIELD_TRACK_NUMBER, trackNumber);
|
||||||
}
|
}
|
||||||
if (totalTrackCount != null) {
|
if (totalTrackCount != null) {
|
||||||
bundle.putInt(keyForField(FIELD_TOTAL_TRACK_COUNT), totalTrackCount);
|
bundle.putInt(FIELD_TOTAL_TRACK_COUNT, totalTrackCount);
|
||||||
}
|
}
|
||||||
if (folderType != null) {
|
if (folderType != null) {
|
||||||
bundle.putInt(keyForField(FIELD_FOLDER_TYPE), folderType);
|
bundle.putInt(FIELD_FOLDER_TYPE, folderType);
|
||||||
|
}
|
||||||
|
if (isBrowsable != null) {
|
||||||
|
bundle.putBoolean(FIELD_IS_BROWSABLE, isBrowsable);
|
||||||
}
|
}
|
||||||
if (isPlayable != null) {
|
if (isPlayable != null) {
|
||||||
bundle.putBoolean(keyForField(FIELD_IS_PLAYABLE), isPlayable);
|
bundle.putBoolean(FIELD_IS_PLAYABLE, isPlayable);
|
||||||
}
|
}
|
||||||
if (recordingYear != null) {
|
if (recordingYear != null) {
|
||||||
bundle.putInt(keyForField(FIELD_RECORDING_YEAR), recordingYear);
|
bundle.putInt(FIELD_RECORDING_YEAR, recordingYear);
|
||||||
}
|
}
|
||||||
if (recordingMonth != null) {
|
if (recordingMonth != null) {
|
||||||
bundle.putInt(keyForField(FIELD_RECORDING_MONTH), recordingMonth);
|
bundle.putInt(FIELD_RECORDING_MONTH, recordingMonth);
|
||||||
}
|
}
|
||||||
if (recordingDay != null) {
|
if (recordingDay != null) {
|
||||||
bundle.putInt(keyForField(FIELD_RECORDING_DAY), recordingDay);
|
bundle.putInt(FIELD_RECORDING_DAY, recordingDay);
|
||||||
}
|
}
|
||||||
if (releaseYear != null) {
|
if (releaseYear != null) {
|
||||||
bundle.putInt(keyForField(FIELD_RELEASE_YEAR), releaseYear);
|
bundle.putInt(FIELD_RELEASE_YEAR, releaseYear);
|
||||||
}
|
}
|
||||||
if (releaseMonth != null) {
|
if (releaseMonth != null) {
|
||||||
bundle.putInt(keyForField(FIELD_RELEASE_MONTH), releaseMonth);
|
bundle.putInt(FIELD_RELEASE_MONTH, releaseMonth);
|
||||||
}
|
}
|
||||||
if (releaseDay != null) {
|
if (releaseDay != null) {
|
||||||
bundle.putInt(keyForField(FIELD_RELEASE_DAY), releaseDay);
|
bundle.putInt(FIELD_RELEASE_DAY, releaseDay);
|
||||||
}
|
}
|
||||||
if (discNumber != null) {
|
if (discNumber != null) {
|
||||||
bundle.putInt(keyForField(FIELD_DISC_NUMBER), discNumber);
|
bundle.putInt(FIELD_DISC_NUMBER, discNumber);
|
||||||
}
|
}
|
||||||
if (totalDiscCount != null) {
|
if (totalDiscCount != null) {
|
||||||
bundle.putInt(keyForField(FIELD_TOTAL_DISC_COUNT), totalDiscCount);
|
bundle.putInt(FIELD_TOTAL_DISC_COUNT, totalDiscCount);
|
||||||
}
|
}
|
||||||
if (artworkDataType != null) {
|
if (artworkDataType != null) {
|
||||||
bundle.putInt(keyForField(FIELD_ARTWORK_DATA_TYPE), artworkDataType);
|
bundle.putInt(FIELD_ARTWORK_DATA_TYPE, artworkDataType);
|
||||||
|
}
|
||||||
|
if (mediaType != null) {
|
||||||
|
bundle.putInt(FIELD_MEDIA_TYPE, mediaType);
|
||||||
}
|
}
|
||||||
if (extras != null) {
|
if (extras != null) {
|
||||||
bundle.putBundle(keyForField(FIELD_EXTRAS), extras);
|
bundle.putBundle(FIELD_EXTRAS, extras);
|
||||||
}
|
}
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
@ -1005,80 +1250,152 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
private static MediaMetadata fromBundle(Bundle bundle) {
|
private static MediaMetadata fromBundle(Bundle bundle) {
|
||||||
Builder builder = new Builder();
|
Builder builder = new Builder();
|
||||||
builder
|
builder
|
||||||
.setTitle(bundle.getCharSequence(keyForField(FIELD_TITLE)))
|
.setTitle(bundle.getCharSequence(FIELD_TITLE))
|
||||||
.setArtist(bundle.getCharSequence(keyForField(FIELD_ARTIST)))
|
.setArtist(bundle.getCharSequence(FIELD_ARTIST))
|
||||||
.setAlbumTitle(bundle.getCharSequence(keyForField(FIELD_ALBUM_TITLE)))
|
.setAlbumTitle(bundle.getCharSequence(FIELD_ALBUM_TITLE))
|
||||||
.setAlbumArtist(bundle.getCharSequence(keyForField(FIELD_ALBUM_ARTIST)))
|
.setAlbumArtist(bundle.getCharSequence(FIELD_ALBUM_ARTIST))
|
||||||
.setDisplayTitle(bundle.getCharSequence(keyForField(FIELD_DISPLAY_TITLE)))
|
.setDisplayTitle(bundle.getCharSequence(FIELD_DISPLAY_TITLE))
|
||||||
.setSubtitle(bundle.getCharSequence(keyForField(FIELD_SUBTITLE)))
|
.setSubtitle(bundle.getCharSequence(FIELD_SUBTITLE))
|
||||||
.setDescription(bundle.getCharSequence(keyForField(FIELD_DESCRIPTION)))
|
.setDescription(bundle.getCharSequence(FIELD_DESCRIPTION))
|
||||||
.setArtworkData(
|
.setArtworkData(
|
||||||
bundle.getByteArray(keyForField(FIELD_ARTWORK_DATA)),
|
bundle.getByteArray(FIELD_ARTWORK_DATA),
|
||||||
bundle.containsKey(keyForField(FIELD_ARTWORK_DATA_TYPE))
|
bundle.containsKey(FIELD_ARTWORK_DATA_TYPE)
|
||||||
? bundle.getInt(keyForField(FIELD_ARTWORK_DATA_TYPE))
|
? bundle.getInt(FIELD_ARTWORK_DATA_TYPE)
|
||||||
: null)
|
: null)
|
||||||
.setArtworkUri(bundle.getParcelable(keyForField(FIELD_ARTWORK_URI)))
|
.setArtworkUri(bundle.getParcelable(FIELD_ARTWORK_URI))
|
||||||
.setWriter(bundle.getCharSequence(keyForField(FIELD_WRITER)))
|
.setWriter(bundle.getCharSequence(FIELD_WRITER))
|
||||||
.setComposer(bundle.getCharSequence(keyForField(FIELD_COMPOSER)))
|
.setComposer(bundle.getCharSequence(FIELD_COMPOSER))
|
||||||
.setConductor(bundle.getCharSequence(keyForField(FIELD_CONDUCTOR)))
|
.setConductor(bundle.getCharSequence(FIELD_CONDUCTOR))
|
||||||
.setGenre(bundle.getCharSequence(keyForField(FIELD_GENRE)))
|
.setGenre(bundle.getCharSequence(FIELD_GENRE))
|
||||||
.setCompilation(bundle.getCharSequence(keyForField(FIELD_COMPILATION)))
|
.setCompilation(bundle.getCharSequence(FIELD_COMPILATION))
|
||||||
.setStation(bundle.getCharSequence(keyForField(FIELD_STATION)))
|
.setStation(bundle.getCharSequence(FIELD_STATION))
|
||||||
.setExtras(bundle.getBundle(keyForField(FIELD_EXTRAS)));
|
.setExtras(bundle.getBundle(FIELD_EXTRAS));
|
||||||
|
|
||||||
if (bundle.containsKey(keyForField(FIELD_USER_RATING))) {
|
if (bundle.containsKey(FIELD_USER_RATING)) {
|
||||||
@Nullable Bundle fieldBundle = bundle.getBundle(keyForField(FIELD_USER_RATING));
|
@Nullable Bundle fieldBundle = bundle.getBundle(FIELD_USER_RATING);
|
||||||
if (fieldBundle != null) {
|
if (fieldBundle != null) {
|
||||||
builder.setUserRating(Rating.CREATOR.fromBundle(fieldBundle));
|
builder.setUserRating(Rating.CREATOR.fromBundle(fieldBundle));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (bundle.containsKey(keyForField(FIELD_OVERALL_RATING))) {
|
if (bundle.containsKey(FIELD_OVERALL_RATING)) {
|
||||||
@Nullable Bundle fieldBundle = bundle.getBundle(keyForField(FIELD_OVERALL_RATING));
|
@Nullable Bundle fieldBundle = bundle.getBundle(FIELD_OVERALL_RATING);
|
||||||
if (fieldBundle != null) {
|
if (fieldBundle != null) {
|
||||||
builder.setOverallRating(Rating.CREATOR.fromBundle(fieldBundle));
|
builder.setOverallRating(Rating.CREATOR.fromBundle(fieldBundle));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (bundle.containsKey(keyForField(FIELD_TRACK_NUMBER))) {
|
if (bundle.containsKey(FIELD_TRACK_NUMBER)) {
|
||||||
builder.setTrackNumber(bundle.getInt(keyForField(FIELD_TRACK_NUMBER)));
|
builder.setTrackNumber(bundle.getInt(FIELD_TRACK_NUMBER));
|
||||||
}
|
}
|
||||||
if (bundle.containsKey(keyForField(FIELD_TOTAL_TRACK_COUNT))) {
|
if (bundle.containsKey(FIELD_TOTAL_TRACK_COUNT)) {
|
||||||
builder.setTotalTrackCount(bundle.getInt(keyForField(FIELD_TOTAL_TRACK_COUNT)));
|
builder.setTotalTrackCount(bundle.getInt(FIELD_TOTAL_TRACK_COUNT));
|
||||||
}
|
}
|
||||||
if (bundle.containsKey(keyForField(FIELD_FOLDER_TYPE))) {
|
if (bundle.containsKey(FIELD_FOLDER_TYPE)) {
|
||||||
builder.setFolderType(bundle.getInt(keyForField(FIELD_FOLDER_TYPE)));
|
builder.setFolderType(bundle.getInt(FIELD_FOLDER_TYPE));
|
||||||
}
|
}
|
||||||
if (bundle.containsKey(keyForField(FIELD_IS_PLAYABLE))) {
|
if (bundle.containsKey(FIELD_IS_BROWSABLE)) {
|
||||||
builder.setIsPlayable(bundle.getBoolean(keyForField(FIELD_IS_PLAYABLE)));
|
builder.setIsBrowsable(bundle.getBoolean(FIELD_IS_BROWSABLE));
|
||||||
}
|
}
|
||||||
if (bundle.containsKey(keyForField(FIELD_RECORDING_YEAR))) {
|
if (bundle.containsKey(FIELD_IS_PLAYABLE)) {
|
||||||
builder.setRecordingYear(bundle.getInt(keyForField(FIELD_RECORDING_YEAR)));
|
builder.setIsPlayable(bundle.getBoolean(FIELD_IS_PLAYABLE));
|
||||||
}
|
}
|
||||||
if (bundle.containsKey(keyForField(FIELD_RECORDING_MONTH))) {
|
if (bundle.containsKey(FIELD_RECORDING_YEAR)) {
|
||||||
builder.setRecordingMonth(bundle.getInt(keyForField(FIELD_RECORDING_MONTH)));
|
builder.setRecordingYear(bundle.getInt(FIELD_RECORDING_YEAR));
|
||||||
}
|
}
|
||||||
if (bundle.containsKey(keyForField(FIELD_RECORDING_DAY))) {
|
if (bundle.containsKey(FIELD_RECORDING_MONTH)) {
|
||||||
builder.setRecordingDay(bundle.getInt(keyForField(FIELD_RECORDING_DAY)));
|
builder.setRecordingMonth(bundle.getInt(FIELD_RECORDING_MONTH));
|
||||||
}
|
}
|
||||||
if (bundle.containsKey(keyForField(FIELD_RELEASE_YEAR))) {
|
if (bundle.containsKey(FIELD_RECORDING_DAY)) {
|
||||||
builder.setReleaseYear(bundle.getInt(keyForField(FIELD_RELEASE_YEAR)));
|
builder.setRecordingDay(bundle.getInt(FIELD_RECORDING_DAY));
|
||||||
}
|
}
|
||||||
if (bundle.containsKey(keyForField(FIELD_RELEASE_MONTH))) {
|
if (bundle.containsKey(FIELD_RELEASE_YEAR)) {
|
||||||
builder.setReleaseMonth(bundle.getInt(keyForField(FIELD_RELEASE_MONTH)));
|
builder.setReleaseYear(bundle.getInt(FIELD_RELEASE_YEAR));
|
||||||
}
|
}
|
||||||
if (bundle.containsKey(keyForField(FIELD_RELEASE_DAY))) {
|
if (bundle.containsKey(FIELD_RELEASE_MONTH)) {
|
||||||
builder.setReleaseDay(bundle.getInt(keyForField(FIELD_RELEASE_DAY)));
|
builder.setReleaseMonth(bundle.getInt(FIELD_RELEASE_MONTH));
|
||||||
}
|
}
|
||||||
if (bundle.containsKey(keyForField(FIELD_DISC_NUMBER))) {
|
if (bundle.containsKey(FIELD_RELEASE_DAY)) {
|
||||||
builder.setDiscNumber(bundle.getInt(keyForField(FIELD_DISC_NUMBER)));
|
builder.setReleaseDay(bundle.getInt(FIELD_RELEASE_DAY));
|
||||||
}
|
}
|
||||||
if (bundle.containsKey(keyForField(FIELD_TOTAL_DISC_COUNT))) {
|
if (bundle.containsKey(FIELD_DISC_NUMBER)) {
|
||||||
builder.setTotalDiscCount(bundle.getInt(keyForField(FIELD_TOTAL_DISC_COUNT)));
|
builder.setDiscNumber(bundle.getInt(FIELD_DISC_NUMBER));
|
||||||
|
}
|
||||||
|
if (bundle.containsKey(FIELD_TOTAL_DISC_COUNT)) {
|
||||||
|
builder.setTotalDiscCount(bundle.getInt(FIELD_TOTAL_DISC_COUNT));
|
||||||
|
}
|
||||||
|
if (bundle.containsKey(FIELD_MEDIA_TYPE)) {
|
||||||
|
builder.setMediaType(bundle.getInt(FIELD_MEDIA_TYPE));
|
||||||
}
|
}
|
||||||
|
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String keyForField(@FieldNumber int field) {
|
private static @FolderType int getFolderTypeFromMediaType(@MediaType int mediaType) {
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
switch (mediaType) {
|
||||||
|
case MEDIA_TYPE_ALBUM:
|
||||||
|
case MEDIA_TYPE_ARTIST:
|
||||||
|
case MEDIA_TYPE_AUDIO_BOOK:
|
||||||
|
case MEDIA_TYPE_AUDIO_BOOK_CHAPTER:
|
||||||
|
case MEDIA_TYPE_FOLDER_MOVIES:
|
||||||
|
case MEDIA_TYPE_FOLDER_NEWS:
|
||||||
|
case MEDIA_TYPE_FOLDER_RADIO_STATIONS:
|
||||||
|
case MEDIA_TYPE_FOLDER_TRAILERS:
|
||||||
|
case MEDIA_TYPE_FOLDER_VIDEOS:
|
||||||
|
case MEDIA_TYPE_GENRE:
|
||||||
|
case MEDIA_TYPE_MOVIE:
|
||||||
|
case MEDIA_TYPE_MUSIC:
|
||||||
|
case MEDIA_TYPE_NEWS:
|
||||||
|
case MEDIA_TYPE_PLAYLIST:
|
||||||
|
case MEDIA_TYPE_PODCAST:
|
||||||
|
case MEDIA_TYPE_PODCAST_EPISODE:
|
||||||
|
case MEDIA_TYPE_RADIO_STATION:
|
||||||
|
case MEDIA_TYPE_TRAILER:
|
||||||
|
case MEDIA_TYPE_TV_CHANNEL:
|
||||||
|
case MEDIA_TYPE_TV_SEASON:
|
||||||
|
case MEDIA_TYPE_TV_SERIES:
|
||||||
|
case MEDIA_TYPE_TV_SHOW:
|
||||||
|
case MEDIA_TYPE_VIDEO:
|
||||||
|
case MEDIA_TYPE_YEAR:
|
||||||
|
return FOLDER_TYPE_TITLES;
|
||||||
|
case MEDIA_TYPE_FOLDER_ALBUMS:
|
||||||
|
return FOLDER_TYPE_ALBUMS;
|
||||||
|
case MEDIA_TYPE_FOLDER_ARTISTS:
|
||||||
|
return FOLDER_TYPE_ARTISTS;
|
||||||
|
case MEDIA_TYPE_FOLDER_GENRES:
|
||||||
|
return FOLDER_TYPE_GENRES;
|
||||||
|
case MEDIA_TYPE_FOLDER_PLAYLISTS:
|
||||||
|
return FOLDER_TYPE_PLAYLISTS;
|
||||||
|
case MEDIA_TYPE_FOLDER_YEARS:
|
||||||
|
return FOLDER_TYPE_YEARS;
|
||||||
|
case MEDIA_TYPE_FOLDER_AUDIO_BOOKS:
|
||||||
|
case MEDIA_TYPE_FOLDER_MIXED:
|
||||||
|
case MEDIA_TYPE_FOLDER_TV_CHANNELS:
|
||||||
|
case MEDIA_TYPE_FOLDER_TV_SERIES:
|
||||||
|
case MEDIA_TYPE_FOLDER_TV_SHOWS:
|
||||||
|
case MEDIA_TYPE_FOLDER_PODCASTS:
|
||||||
|
case MEDIA_TYPE_MIXED:
|
||||||
|
default:
|
||||||
|
return FOLDER_TYPE_MIXED;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static @MediaType int getMediaTypeFromFolderType(@FolderType int folderType) {
|
||||||
|
switch (folderType) {
|
||||||
|
case FOLDER_TYPE_ALBUMS:
|
||||||
|
return MEDIA_TYPE_FOLDER_ALBUMS;
|
||||||
|
case FOLDER_TYPE_ARTISTS:
|
||||||
|
return MEDIA_TYPE_FOLDER_ARTISTS;
|
||||||
|
case FOLDER_TYPE_GENRES:
|
||||||
|
return MEDIA_TYPE_FOLDER_GENRES;
|
||||||
|
case FOLDER_TYPE_PLAYLISTS:
|
||||||
|
return MEDIA_TYPE_FOLDER_PLAYLISTS;
|
||||||
|
case FOLDER_TYPE_TITLES:
|
||||||
|
return MEDIA_TYPE_MIXED;
|
||||||
|
case FOLDER_TYPE_YEARS:
|
||||||
|
return MEDIA_TYPE_FOLDER_YEARS;
|
||||||
|
case FOLDER_TYPE_MIXED:
|
||||||
|
case FOLDER_TYPE_NONE:
|
||||||
|
default:
|
||||||
|
return MEDIA_TYPE_FOLDER_MIXED;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -50,11 +50,8 @@ public final class Metadata implements Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Updates the {@link MediaMetadata.Builder} with the type specific values stored in this Entry.
|
* Updates the {@link MediaMetadata.Builder} with the type-specific values stored in this {@code
|
||||||
*
|
* Entry}.
|
||||||
* <p>The order of the {@link Entry} objects in the {@link Metadata} matters. If two {@link
|
|
||||||
* Entry} entries attempt to populate the same {@link MediaMetadata} field, then the last one in
|
|
||||||
* the list is used.
|
|
||||||
*
|
*
|
||||||
* @param builder The builder to be updated.
|
* @param builder The builder to be updated.
|
||||||
*/
|
*/
|
||||||
|
@ -587,6 +587,8 @@ public final class MimeTypes {
|
|||||||
return C.ENCODING_DTS_HD;
|
return C.ENCODING_DTS_HD;
|
||||||
case MimeTypes.AUDIO_TRUEHD:
|
case MimeTypes.AUDIO_TRUEHD:
|
||||||
return C.ENCODING_DOLBY_TRUEHD;
|
return C.ENCODING_DOLBY_TRUEHD;
|
||||||
|
case MimeTypes.AUDIO_OPUS:
|
||||||
|
return C.ENCODING_OPUS;
|
||||||
default:
|
default:
|
||||||
return C.ENCODING_INVALID;
|
return C.ENCODING_INVALID;
|
||||||
}
|
}
|
||||||
|
@ -16,18 +16,13 @@
|
|||||||
package androidx.media3.common;
|
package androidx.media3.common;
|
||||||
|
|
||||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.FloatRange;
|
import androidx.annotation.FloatRange;
|
||||||
import androidx.annotation.IntDef;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import androidx.media3.common.util.Util;
|
||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
import java.lang.annotation.Documented;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
/** A rating expressed as a percentage. */
|
/** A rating expressed as a percentage. */
|
||||||
public final class PercentageRating extends Rating {
|
public final class PercentageRating extends Rating {
|
||||||
@ -79,20 +74,14 @@ public final class PercentageRating extends Rating {
|
|||||||
|
|
||||||
private static final @RatingType int TYPE = RATING_TYPE_PERCENTAGE;
|
private static final @RatingType int TYPE = RATING_TYPE_PERCENTAGE;
|
||||||
|
|
||||||
@Documented
|
private static final String FIELD_PERCENT = Util.intToStringMaxRadix(1);
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
|
||||||
@Target(TYPE_USE)
|
|
||||||
@IntDef({FIELD_RATING_TYPE, FIELD_PERCENT})
|
|
||||||
private @interface FieldNumber {}
|
|
||||||
|
|
||||||
private static final int FIELD_PERCENT = 1;
|
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Override
|
@Override
|
||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putInt(keyForField(FIELD_RATING_TYPE), TYPE);
|
bundle.putInt(FIELD_RATING_TYPE, TYPE);
|
||||||
bundle.putFloat(keyForField(FIELD_PERCENT), percent);
|
bundle.putFloat(FIELD_PERCENT, percent);
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -100,14 +89,8 @@ public final class PercentageRating extends Rating {
|
|||||||
@UnstableApi public static final Creator<PercentageRating> CREATOR = PercentageRating::fromBundle;
|
@UnstableApi public static final Creator<PercentageRating> CREATOR = PercentageRating::fromBundle;
|
||||||
|
|
||||||
private static PercentageRating fromBundle(Bundle bundle) {
|
private static PercentageRating fromBundle(Bundle bundle) {
|
||||||
checkArgument(
|
checkArgument(bundle.getInt(FIELD_RATING_TYPE, /* defaultValue= */ RATING_TYPE_UNSET) == TYPE);
|
||||||
bundle.getInt(keyForField(FIELD_RATING_TYPE), /* defaultValue= */ RATING_TYPE_UNSET)
|
float percent = bundle.getFloat(FIELD_PERCENT, /* defaultValue= */ RATING_UNSET);
|
||||||
== TYPE);
|
|
||||||
float percent = bundle.getFloat(keyForField(FIELD_PERCENT), /* defaultValue= */ RATING_UNSET);
|
|
||||||
return percent == RATING_UNSET ? new PercentageRating() : new PercentageRating(percent);
|
return percent == RATING_UNSET ? new PercentageRating() : new PercentageRating(percent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String keyForField(@FieldNumber int field) {
|
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -346,13 +346,12 @@ public class PlaybackException extends Exception implements Bundleable {
|
|||||||
@UnstableApi
|
@UnstableApi
|
||||||
protected PlaybackException(Bundle bundle) {
|
protected PlaybackException(Bundle bundle) {
|
||||||
this(
|
this(
|
||||||
/* message= */ bundle.getString(keyForField(FIELD_STRING_MESSAGE)),
|
/* message= */ bundle.getString(FIELD_STRING_MESSAGE),
|
||||||
/* cause= */ getCauseFromBundle(bundle),
|
/* cause= */ getCauseFromBundle(bundle),
|
||||||
/* errorCode= */ bundle.getInt(
|
/* errorCode= */ bundle.getInt(
|
||||||
keyForField(FIELD_INT_ERROR_CODE), /* defaultValue= */ ERROR_CODE_UNSPECIFIED),
|
FIELD_INT_ERROR_CODE, /* defaultValue= */ ERROR_CODE_UNSPECIFIED),
|
||||||
/* timestampMs= */ bundle.getLong(
|
/* timestampMs= */ bundle.getLong(
|
||||||
keyForField(FIELD_LONG_TIMESTAMP_MS),
|
FIELD_LONG_TIMESTAMP_MS, /* defaultValue= */ SystemClock.elapsedRealtime()));
|
||||||
/* defaultValue= */ SystemClock.elapsedRealtime()));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Creates a new instance using the given values. */
|
/** Creates a new instance using the given values. */
|
||||||
@ -401,18 +400,18 @@ public class PlaybackException extends Exception implements Bundleable {
|
|||||||
|
|
||||||
// Bundleable implementation.
|
// Bundleable implementation.
|
||||||
|
|
||||||
private static final int FIELD_INT_ERROR_CODE = 0;
|
private static final String FIELD_INT_ERROR_CODE = Util.intToStringMaxRadix(0);
|
||||||
private static final int FIELD_LONG_TIMESTAMP_MS = 1;
|
private static final String FIELD_LONG_TIMESTAMP_MS = Util.intToStringMaxRadix(1);
|
||||||
private static final int FIELD_STRING_MESSAGE = 2;
|
private static final String FIELD_STRING_MESSAGE = Util.intToStringMaxRadix(2);
|
||||||
private static final int FIELD_STRING_CAUSE_CLASS_NAME = 3;
|
private static final String FIELD_STRING_CAUSE_CLASS_NAME = Util.intToStringMaxRadix(3);
|
||||||
private static final int FIELD_STRING_CAUSE_MESSAGE = 4;
|
private static final String FIELD_STRING_CAUSE_MESSAGE = Util.intToStringMaxRadix(4);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines a minimum field ID value for subclasses to use when implementing {@link #toBundle()}
|
* Defines a minimum field ID value for subclasses to use when implementing {@link #toBundle()}
|
||||||
* and {@link Bundleable.Creator}.
|
* and {@link Bundleable.Creator}.
|
||||||
*
|
*
|
||||||
* <p>Subclasses should obtain their {@link Bundle Bundle's} field keys by applying a non-negative
|
* <p>Subclasses should obtain their {@link Bundle Bundle's} field keys by applying a non-negative
|
||||||
* offset on this constant and passing the result to {@link #keyForField(int)}.
|
* offset on this constant and passing the result to {@link Util#intToStringMaxRadix(int)}.
|
||||||
*/
|
*/
|
||||||
@UnstableApi protected static final int FIELD_CUSTOM_ID_BASE = 1000;
|
@UnstableApi protected static final int FIELD_CUSTOM_ID_BASE = 1000;
|
||||||
|
|
||||||
@ -424,29 +423,17 @@ public class PlaybackException extends Exception implements Bundleable {
|
|||||||
@Override
|
@Override
|
||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putInt(keyForField(FIELD_INT_ERROR_CODE), errorCode);
|
bundle.putInt(FIELD_INT_ERROR_CODE, errorCode);
|
||||||
bundle.putLong(keyForField(FIELD_LONG_TIMESTAMP_MS), timestampMs);
|
bundle.putLong(FIELD_LONG_TIMESTAMP_MS, timestampMs);
|
||||||
bundle.putString(keyForField(FIELD_STRING_MESSAGE), getMessage());
|
bundle.putString(FIELD_STRING_MESSAGE, getMessage());
|
||||||
@Nullable Throwable cause = getCause();
|
@Nullable Throwable cause = getCause();
|
||||||
if (cause != null) {
|
if (cause != null) {
|
||||||
bundle.putString(keyForField(FIELD_STRING_CAUSE_CLASS_NAME), cause.getClass().getName());
|
bundle.putString(FIELD_STRING_CAUSE_CLASS_NAME, cause.getClass().getName());
|
||||||
bundle.putString(keyForField(FIELD_STRING_CAUSE_MESSAGE), cause.getMessage());
|
bundle.putString(FIELD_STRING_CAUSE_MESSAGE, cause.getMessage());
|
||||||
}
|
}
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts the given field number to a string which can be used as a field key when implementing
|
|
||||||
* {@link #toBundle()} and {@link Bundleable.Creator}.
|
|
||||||
*
|
|
||||||
* <p>Subclasses should use {@code field} values greater than or equal to {@link
|
|
||||||
* #FIELD_CUSTOM_ID_BASE}.
|
|
||||||
*/
|
|
||||||
@UnstableApi
|
|
||||||
protected static String keyForField(int field) {
|
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Creates a new {@link Throwable} with possibly {@code null} message.
|
// Creates a new {@link Throwable} with possibly {@code null} message.
|
||||||
@SuppressWarnings("nullness:argument")
|
@SuppressWarnings("nullness:argument")
|
||||||
private static Throwable createThrowable(Class<?> clazz, @Nullable String message)
|
private static Throwable createThrowable(Class<?> clazz, @Nullable String message)
|
||||||
@ -462,8 +449,8 @@ public class PlaybackException extends Exception implements Bundleable {
|
|||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static Throwable getCauseFromBundle(Bundle bundle) {
|
private static Throwable getCauseFromBundle(Bundle bundle) {
|
||||||
@Nullable String causeClassName = bundle.getString(keyForField(FIELD_STRING_CAUSE_CLASS_NAME));
|
@Nullable String causeClassName = bundle.getString(FIELD_STRING_CAUSE_CLASS_NAME);
|
||||||
@Nullable String causeMessage = bundle.getString(keyForField(FIELD_STRING_CAUSE_MESSAGE));
|
@Nullable String causeMessage = bundle.getString(FIELD_STRING_CAUSE_MESSAGE);
|
||||||
@Nullable Throwable cause = null;
|
@Nullable Throwable cause = null;
|
||||||
if (!TextUtils.isEmpty(causeClassName)) {
|
if (!TextUtils.isEmpty(causeClassName)) {
|
||||||
try {
|
try {
|
||||||
|
@ -15,20 +15,13 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.common;
|
package androidx.media3.common;
|
||||||
|
|
||||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.CheckResult;
|
import androidx.annotation.CheckResult;
|
||||||
import androidx.annotation.FloatRange;
|
import androidx.annotation.FloatRange;
|
||||||
import androidx.annotation.IntDef;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.util.Assertions;
|
import androidx.media3.common.util.Assertions;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import java.lang.annotation.Documented;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
/** Parameters that apply to playback, including speed setting. */
|
/** Parameters that apply to playback, including speed setting. */
|
||||||
public final class PlaybackParameters implements Bundleable {
|
public final class PlaybackParameters implements Bundleable {
|
||||||
@ -122,21 +115,15 @@ public final class PlaybackParameters implements Bundleable {
|
|||||||
|
|
||||||
// Bundleable implementation.
|
// Bundleable implementation.
|
||||||
|
|
||||||
@Documented
|
private static final String FIELD_SPEED = Util.intToStringMaxRadix(0);
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
private static final String FIELD_PITCH = Util.intToStringMaxRadix(1);
|
||||||
@Target(TYPE_USE)
|
|
||||||
@IntDef({FIELD_SPEED, FIELD_PITCH})
|
|
||||||
private @interface FieldNumber {}
|
|
||||||
|
|
||||||
private static final int FIELD_SPEED = 0;
|
|
||||||
private static final int FIELD_PITCH = 1;
|
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Override
|
@Override
|
||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putFloat(keyForField(FIELD_SPEED), speed);
|
bundle.putFloat(FIELD_SPEED, speed);
|
||||||
bundle.putFloat(keyForField(FIELD_PITCH), pitch);
|
bundle.putFloat(FIELD_PITCH, pitch);
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,12 +131,8 @@ public final class PlaybackParameters implements Bundleable {
|
|||||||
@UnstableApi
|
@UnstableApi
|
||||||
public static final Creator<PlaybackParameters> CREATOR =
|
public static final Creator<PlaybackParameters> CREATOR =
|
||||||
bundle -> {
|
bundle -> {
|
||||||
float speed = bundle.getFloat(keyForField(FIELD_SPEED), /* defaultValue= */ 1f);
|
float speed = bundle.getFloat(FIELD_SPEED, /* defaultValue= */ 1f);
|
||||||
float pitch = bundle.getFloat(keyForField(FIELD_PITCH), /* defaultValue= */ 1f);
|
float pitch = bundle.getFloat(FIELD_PITCH, /* defaultValue= */ 1f);
|
||||||
return new PlaybackParameters(speed, pitch);
|
return new PlaybackParameters(speed, pitch);
|
||||||
};
|
};
|
||||||
|
|
||||||
private static String keyForField(@FieldNumber int field) {
|
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -20,6 +20,7 @@ import static java.lang.annotation.ElementType.TYPE_USE;
|
|||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.IntDef;
|
import androidx.annotation.IntDef;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import androidx.media3.common.util.Util;
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.Documented;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
@ -60,21 +61,14 @@ public abstract class Rating implements Bundleable {
|
|||||||
/* package */ static final int RATING_TYPE_STAR = 2;
|
/* package */ static final int RATING_TYPE_STAR = 2;
|
||||||
/* package */ static final int RATING_TYPE_THUMB = 3;
|
/* package */ static final int RATING_TYPE_THUMB = 3;
|
||||||
|
|
||||||
@Documented
|
/* package */ static final String FIELD_RATING_TYPE = Util.intToStringMaxRadix(0);
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
|
||||||
@Target(TYPE_USE)
|
|
||||||
@IntDef({FIELD_RATING_TYPE})
|
|
||||||
private @interface FieldNumber {}
|
|
||||||
|
|
||||||
/* package */ static final int FIELD_RATING_TYPE = 0;
|
|
||||||
|
|
||||||
/** Object that can restore a {@link Rating} from a {@link Bundle}. */
|
/** Object that can restore a {@link Rating} from a {@link Bundle}. */
|
||||||
@UnstableApi public static final Creator<Rating> CREATOR = Rating::fromBundle;
|
@UnstableApi public static final Creator<Rating> CREATOR = Rating::fromBundle;
|
||||||
|
|
||||||
private static Rating fromBundle(Bundle bundle) {
|
private static Rating fromBundle(Bundle bundle) {
|
||||||
@RatingType
|
@RatingType
|
||||||
int ratingType =
|
int ratingType = bundle.getInt(FIELD_RATING_TYPE, /* defaultValue= */ RATING_TYPE_UNSET);
|
||||||
bundle.getInt(keyForField(FIELD_RATING_TYPE), /* defaultValue= */ RATING_TYPE_UNSET);
|
|
||||||
switch (ratingType) {
|
switch (ratingType) {
|
||||||
case RATING_TYPE_HEART:
|
case RATING_TYPE_HEART:
|
||||||
return HeartRating.CREATOR.fromBundle(bundle);
|
return HeartRating.CREATOR.fromBundle(bundle);
|
||||||
@ -89,8 +83,4 @@ public abstract class Rating implements Bundleable {
|
|||||||
throw new IllegalArgumentException("Unknown RatingType: " + ratingType);
|
throw new IllegalArgumentException("Unknown RatingType: " + ratingType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String keyForField(@FieldNumber int field) {
|
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -16,19 +16,14 @@
|
|||||||
package androidx.media3.common;
|
package androidx.media3.common;
|
||||||
|
|
||||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.FloatRange;
|
import androidx.annotation.FloatRange;
|
||||||
import androidx.annotation.IntDef;
|
|
||||||
import androidx.annotation.IntRange;
|
import androidx.annotation.IntRange;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import androidx.media3.common.util.Util;
|
||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
import java.lang.annotation.Documented;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
/** A rating expressed as a fractional number of stars. */
|
/** A rating expressed as a fractional number of stars. */
|
||||||
public final class StarRating extends Rating {
|
public final class StarRating extends Rating {
|
||||||
@ -106,22 +101,16 @@ public final class StarRating extends Rating {
|
|||||||
private static final @RatingType int TYPE = RATING_TYPE_STAR;
|
private static final @RatingType int TYPE = RATING_TYPE_STAR;
|
||||||
private static final int MAX_STARS_DEFAULT = 5;
|
private static final int MAX_STARS_DEFAULT = 5;
|
||||||
|
|
||||||
@Documented
|
private static final String FIELD_MAX_STARS = Util.intToStringMaxRadix(1);
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
private static final String FIELD_STAR_RATING = Util.intToStringMaxRadix(2);
|
||||||
@Target(TYPE_USE)
|
|
||||||
@IntDef({FIELD_RATING_TYPE, FIELD_MAX_STARS, FIELD_STAR_RATING})
|
|
||||||
private @interface FieldNumber {}
|
|
||||||
|
|
||||||
private static final int FIELD_MAX_STARS = 1;
|
|
||||||
private static final int FIELD_STAR_RATING = 2;
|
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Override
|
@Override
|
||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putInt(keyForField(FIELD_RATING_TYPE), TYPE);
|
bundle.putInt(FIELD_RATING_TYPE, TYPE);
|
||||||
bundle.putInt(keyForField(FIELD_MAX_STARS), maxStars);
|
bundle.putInt(FIELD_MAX_STARS, maxStars);
|
||||||
bundle.putFloat(keyForField(FIELD_STAR_RATING), starRating);
|
bundle.putFloat(FIELD_STAR_RATING, starRating);
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -129,19 +118,11 @@ public final class StarRating extends Rating {
|
|||||||
@UnstableApi public static final Creator<StarRating> CREATOR = StarRating::fromBundle;
|
@UnstableApi public static final Creator<StarRating> CREATOR = StarRating::fromBundle;
|
||||||
|
|
||||||
private static StarRating fromBundle(Bundle bundle) {
|
private static StarRating fromBundle(Bundle bundle) {
|
||||||
checkArgument(
|
checkArgument(bundle.getInt(FIELD_RATING_TYPE, /* defaultValue= */ RATING_TYPE_UNSET) == TYPE);
|
||||||
bundle.getInt(keyForField(FIELD_RATING_TYPE), /* defaultValue= */ RATING_TYPE_UNSET)
|
int maxStars = bundle.getInt(FIELD_MAX_STARS, /* defaultValue= */ MAX_STARS_DEFAULT);
|
||||||
== TYPE);
|
float starRating = bundle.getFloat(FIELD_STAR_RATING, /* defaultValue= */ RATING_UNSET);
|
||||||
int maxStars =
|
|
||||||
bundle.getInt(keyForField(FIELD_MAX_STARS), /* defaultValue= */ MAX_STARS_DEFAULT);
|
|
||||||
float starRating =
|
|
||||||
bundle.getFloat(keyForField(FIELD_STAR_RATING), /* defaultValue= */ RATING_UNSET);
|
|
||||||
return starRating == RATING_UNSET
|
return starRating == RATING_UNSET
|
||||||
? new StarRating(maxStars)
|
? new StarRating(maxStars)
|
||||||
: new StarRating(maxStars, starRating);
|
: new StarRating(maxStars, starRating);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String keyForField(@FieldNumber int field) {
|
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -16,17 +16,12 @@
|
|||||||
package androidx.media3.common;
|
package androidx.media3.common;
|
||||||
|
|
||||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.IntDef;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import androidx.media3.common.util.Util;
|
||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
import java.lang.annotation.Documented;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
/** A rating expressed as "thumbs up" or "thumbs down". */
|
/** A rating expressed as "thumbs up" or "thumbs down". */
|
||||||
public final class ThumbRating extends Rating {
|
public final class ThumbRating extends Rating {
|
||||||
@ -78,22 +73,16 @@ public final class ThumbRating extends Rating {
|
|||||||
|
|
||||||
private static final @RatingType int TYPE = RATING_TYPE_THUMB;
|
private static final @RatingType int TYPE = RATING_TYPE_THUMB;
|
||||||
|
|
||||||
@Documented
|
private static final String FIELD_RATED = Util.intToStringMaxRadix(1);
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
private static final String FIELD_IS_THUMBS_UP = Util.intToStringMaxRadix(2);
|
||||||
@Target(TYPE_USE)
|
|
||||||
@IntDef({FIELD_RATING_TYPE, FIELD_RATED, FIELD_IS_THUMBS_UP})
|
|
||||||
private @interface FieldNumber {}
|
|
||||||
|
|
||||||
private static final int FIELD_RATED = 1;
|
|
||||||
private static final int FIELD_IS_THUMBS_UP = 2;
|
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Override
|
@Override
|
||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putInt(keyForField(FIELD_RATING_TYPE), TYPE);
|
bundle.putInt(FIELD_RATING_TYPE, TYPE);
|
||||||
bundle.putBoolean(keyForField(FIELD_RATED), rated);
|
bundle.putBoolean(FIELD_RATED, rated);
|
||||||
bundle.putBoolean(keyForField(FIELD_IS_THUMBS_UP), isThumbsUp);
|
bundle.putBoolean(FIELD_IS_THUMBS_UP, isThumbsUp);
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -101,17 +90,10 @@ public final class ThumbRating extends Rating {
|
|||||||
@UnstableApi public static final Creator<ThumbRating> CREATOR = ThumbRating::fromBundle;
|
@UnstableApi public static final Creator<ThumbRating> CREATOR = ThumbRating::fromBundle;
|
||||||
|
|
||||||
private static ThumbRating fromBundle(Bundle bundle) {
|
private static ThumbRating fromBundle(Bundle bundle) {
|
||||||
checkArgument(
|
checkArgument(bundle.getInt(FIELD_RATING_TYPE, /* defaultValue= */ RATING_TYPE_UNSET) == TYPE);
|
||||||
bundle.getInt(keyForField(FIELD_RATING_TYPE), /* defaultValue= */ RATING_TYPE_UNSET)
|
boolean rated = bundle.getBoolean(FIELD_RATED, /* defaultValue= */ false);
|
||||||
== TYPE);
|
|
||||||
boolean rated = bundle.getBoolean(keyForField(FIELD_RATED), /* defaultValue= */ false);
|
|
||||||
return rated
|
return rated
|
||||||
? new ThumbRating(
|
? new ThumbRating(bundle.getBoolean(FIELD_IS_THUMBS_UP, /* defaultValue= */ false))
|
||||||
bundle.getBoolean(keyForField(FIELD_IS_THUMBS_UP), /* defaultValue= */ false))
|
|
||||||
: new ThumbRating();
|
: new ThumbRating();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String keyForField(@FieldNumber int field) {
|
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -20,14 +20,12 @@ import static androidx.media3.common.util.Assertions.checkArgument;
|
|||||||
import static androidx.media3.common.util.Assertions.checkState;
|
import static androidx.media3.common.util.Assertions.checkState;
|
||||||
import static java.lang.Math.max;
|
import static java.lang.Math.max;
|
||||||
import static java.lang.Math.min;
|
import static java.lang.Math.min;
|
||||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.IBinder;
|
import android.os.IBinder;
|
||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import androidx.annotation.IntDef;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.util.Assertions;
|
import androidx.media3.common.util.Assertions;
|
||||||
import androidx.media3.common.util.BundleUtil;
|
import androidx.media3.common.util.BundleUtil;
|
||||||
@ -36,10 +34,6 @@ import androidx.media3.common.util.Util;
|
|||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
import com.google.errorprone.annotations.InlineMe;
|
import com.google.errorprone.annotations.InlineMe;
|
||||||
import java.lang.annotation.Documented;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -158,7 +152,7 @@ public abstract class Timeline implements Bundleable {
|
|||||||
|
|
||||||
private static final Object FAKE_WINDOW_UID = new Object();
|
private static final Object FAKE_WINDOW_UID = new Object();
|
||||||
|
|
||||||
private static final MediaItem EMPTY_MEDIA_ITEM =
|
private static final MediaItem PLACEHOLDER_MEDIA_ITEM =
|
||||||
new MediaItem.Builder()
|
new MediaItem.Builder()
|
||||||
.setMediaId("androidx.media3.common.Timeline")
|
.setMediaId("androidx.media3.common.Timeline")
|
||||||
.setUri(Uri.EMPTY)
|
.setUri(Uri.EMPTY)
|
||||||
@ -258,7 +252,7 @@ public abstract class Timeline implements Bundleable {
|
|||||||
/** Creates window. */
|
/** Creates window. */
|
||||||
public Window() {
|
public Window() {
|
||||||
uid = SINGLE_WINDOW_UID;
|
uid = SINGLE_WINDOW_UID;
|
||||||
mediaItem = EMPTY_MEDIA_ITEM;
|
mediaItem = PLACEHOLDER_MEDIA_ITEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sets the data held by this window. */
|
/** Sets the data held by this window. */
|
||||||
@ -281,7 +275,7 @@ public abstract class Timeline implements Bundleable {
|
|||||||
int lastPeriodIndex,
|
int lastPeriodIndex,
|
||||||
long positionInFirstPeriodUs) {
|
long positionInFirstPeriodUs) {
|
||||||
this.uid = uid;
|
this.uid = uid;
|
||||||
this.mediaItem = mediaItem != null ? mediaItem : EMPTY_MEDIA_ITEM;
|
this.mediaItem = mediaItem != null ? mediaItem : PLACEHOLDER_MEDIA_ITEM;
|
||||||
this.tag =
|
this.tag =
|
||||||
mediaItem != null && mediaItem.localConfiguration != null
|
mediaItem != null && mediaItem.localConfiguration != null
|
||||||
? mediaItem.localConfiguration.tag
|
? mediaItem.localConfiguration.tag
|
||||||
@ -420,63 +414,20 @@ public abstract class Timeline implements Bundleable {
|
|||||||
|
|
||||||
// Bundleable implementation.
|
// Bundleable implementation.
|
||||||
|
|
||||||
@Documented
|
private static final String FIELD_MEDIA_ITEM = Util.intToStringMaxRadix(1);
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
private static final String FIELD_PRESENTATION_START_TIME_MS = Util.intToStringMaxRadix(2);
|
||||||
@Target(TYPE_USE)
|
private static final String FIELD_WINDOW_START_TIME_MS = Util.intToStringMaxRadix(3);
|
||||||
@IntDef({
|
private static final String FIELD_ELAPSED_REALTIME_EPOCH_OFFSET_MS =
|
||||||
FIELD_MEDIA_ITEM,
|
Util.intToStringMaxRadix(4);
|
||||||
FIELD_PRESENTATION_START_TIME_MS,
|
private static final String FIELD_IS_SEEKABLE = Util.intToStringMaxRadix(5);
|
||||||
FIELD_WINDOW_START_TIME_MS,
|
private static final String FIELD_IS_DYNAMIC = Util.intToStringMaxRadix(6);
|
||||||
FIELD_ELAPSED_REALTIME_EPOCH_OFFSET_MS,
|
private static final String FIELD_LIVE_CONFIGURATION = Util.intToStringMaxRadix(7);
|
||||||
FIELD_IS_SEEKABLE,
|
private static final String FIELD_IS_PLACEHOLDER = Util.intToStringMaxRadix(8);
|
||||||
FIELD_IS_DYNAMIC,
|
private static final String FIELD_DEFAULT_POSITION_US = Util.intToStringMaxRadix(9);
|
||||||
FIELD_LIVE_CONFIGURATION,
|
private static final String FIELD_DURATION_US = Util.intToStringMaxRadix(10);
|
||||||
FIELD_IS_PLACEHOLDER,
|
private static final String FIELD_FIRST_PERIOD_INDEX = Util.intToStringMaxRadix(11);
|
||||||
FIELD_DEFAULT_POSITION_US,
|
private static final String FIELD_LAST_PERIOD_INDEX = Util.intToStringMaxRadix(12);
|
||||||
FIELD_DURATION_US,
|
private static final String FIELD_POSITION_IN_FIRST_PERIOD_US = Util.intToStringMaxRadix(13);
|
||||||
FIELD_FIRST_PERIOD_INDEX,
|
|
||||||
FIELD_LAST_PERIOD_INDEX,
|
|
||||||
FIELD_POSITION_IN_FIRST_PERIOD_US,
|
|
||||||
})
|
|
||||||
private @interface FieldNumber {}
|
|
||||||
|
|
||||||
private static final int FIELD_MEDIA_ITEM = 1;
|
|
||||||
private static final int FIELD_PRESENTATION_START_TIME_MS = 2;
|
|
||||||
private static final int FIELD_WINDOW_START_TIME_MS = 3;
|
|
||||||
private static final int FIELD_ELAPSED_REALTIME_EPOCH_OFFSET_MS = 4;
|
|
||||||
private static final int FIELD_IS_SEEKABLE = 5;
|
|
||||||
private static final int FIELD_IS_DYNAMIC = 6;
|
|
||||||
private static final int FIELD_LIVE_CONFIGURATION = 7;
|
|
||||||
private static final int FIELD_IS_PLACEHOLDER = 8;
|
|
||||||
private static final int FIELD_DEFAULT_POSITION_US = 9;
|
|
||||||
private static final int FIELD_DURATION_US = 10;
|
|
||||||
private static final int FIELD_FIRST_PERIOD_INDEX = 11;
|
|
||||||
private static final int FIELD_LAST_PERIOD_INDEX = 12;
|
|
||||||
private static final int FIELD_POSITION_IN_FIRST_PERIOD_US = 13;
|
|
||||||
|
|
||||||
private final Bundle toBundle(boolean excludeMediaItem) {
|
|
||||||
Bundle bundle = new Bundle();
|
|
||||||
bundle.putBundle(
|
|
||||||
keyForField(FIELD_MEDIA_ITEM),
|
|
||||||
excludeMediaItem ? MediaItem.EMPTY.toBundle() : mediaItem.toBundle());
|
|
||||||
bundle.putLong(keyForField(FIELD_PRESENTATION_START_TIME_MS), presentationStartTimeMs);
|
|
||||||
bundle.putLong(keyForField(FIELD_WINDOW_START_TIME_MS), windowStartTimeMs);
|
|
||||||
bundle.putLong(
|
|
||||||
keyForField(FIELD_ELAPSED_REALTIME_EPOCH_OFFSET_MS), elapsedRealtimeEpochOffsetMs);
|
|
||||||
bundle.putBoolean(keyForField(FIELD_IS_SEEKABLE), isSeekable);
|
|
||||||
bundle.putBoolean(keyForField(FIELD_IS_DYNAMIC), isDynamic);
|
|
||||||
@Nullable MediaItem.LiveConfiguration liveConfiguration = this.liveConfiguration;
|
|
||||||
if (liveConfiguration != null) {
|
|
||||||
bundle.putBundle(keyForField(FIELD_LIVE_CONFIGURATION), liveConfiguration.toBundle());
|
|
||||||
}
|
|
||||||
bundle.putBoolean(keyForField(FIELD_IS_PLACEHOLDER), isPlaceholder);
|
|
||||||
bundle.putLong(keyForField(FIELD_DEFAULT_POSITION_US), defaultPositionUs);
|
|
||||||
bundle.putLong(keyForField(FIELD_DURATION_US), durationUs);
|
|
||||||
bundle.putInt(keyForField(FIELD_FIRST_PERIOD_INDEX), firstPeriodIndex);
|
|
||||||
bundle.putInt(keyForField(FIELD_LAST_PERIOD_INDEX), lastPeriodIndex);
|
|
||||||
bundle.putLong(keyForField(FIELD_POSITION_IN_FIRST_PERIOD_US), positionInFirstPeriodUs);
|
|
||||||
return bundle;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
@ -485,11 +436,52 @@ public abstract class Timeline implements Bundleable {
|
|||||||
* restored by {@link #CREATOR} will be a fake {@link Object} and the {@link #manifest} of the
|
* restored by {@link #CREATOR} will be a fake {@link Object} and the {@link #manifest} of the
|
||||||
* instance will be {@code null}.
|
* instance will be {@code null}.
|
||||||
*/
|
*/
|
||||||
// TODO(b/166765820): See if missing fields would be okay and add them to the Bundle otherwise.
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Override
|
@Override
|
||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
return toBundle(/* excludeMediaItem= */ false);
|
Bundle bundle = new Bundle();
|
||||||
|
if (!MediaItem.EMPTY.equals(mediaItem)) {
|
||||||
|
bundle.putBundle(FIELD_MEDIA_ITEM, mediaItem.toBundle());
|
||||||
|
}
|
||||||
|
if (presentationStartTimeMs != C.TIME_UNSET) {
|
||||||
|
bundle.putLong(FIELD_PRESENTATION_START_TIME_MS, presentationStartTimeMs);
|
||||||
|
}
|
||||||
|
if (windowStartTimeMs != C.TIME_UNSET) {
|
||||||
|
bundle.putLong(FIELD_WINDOW_START_TIME_MS, windowStartTimeMs);
|
||||||
|
}
|
||||||
|
if (elapsedRealtimeEpochOffsetMs != C.TIME_UNSET) {
|
||||||
|
bundle.putLong(FIELD_ELAPSED_REALTIME_EPOCH_OFFSET_MS, elapsedRealtimeEpochOffsetMs);
|
||||||
|
}
|
||||||
|
if (isSeekable) {
|
||||||
|
bundle.putBoolean(FIELD_IS_SEEKABLE, isSeekable);
|
||||||
|
}
|
||||||
|
if (isDynamic) {
|
||||||
|
bundle.putBoolean(FIELD_IS_DYNAMIC, isDynamic);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable MediaItem.LiveConfiguration liveConfiguration = this.liveConfiguration;
|
||||||
|
if (liveConfiguration != null) {
|
||||||
|
bundle.putBundle(FIELD_LIVE_CONFIGURATION, liveConfiguration.toBundle());
|
||||||
|
}
|
||||||
|
if (isPlaceholder) {
|
||||||
|
bundle.putBoolean(FIELD_IS_PLACEHOLDER, isPlaceholder);
|
||||||
|
}
|
||||||
|
if (defaultPositionUs != 0) {
|
||||||
|
bundle.putLong(FIELD_DEFAULT_POSITION_US, defaultPositionUs);
|
||||||
|
}
|
||||||
|
if (durationUs != C.TIME_UNSET) {
|
||||||
|
bundle.putLong(FIELD_DURATION_US, durationUs);
|
||||||
|
}
|
||||||
|
if (firstPeriodIndex != 0) {
|
||||||
|
bundle.putInt(FIELD_FIRST_PERIOD_INDEX, firstPeriodIndex);
|
||||||
|
}
|
||||||
|
if (lastPeriodIndex != 0) {
|
||||||
|
bundle.putInt(FIELD_LAST_PERIOD_INDEX, lastPeriodIndex);
|
||||||
|
}
|
||||||
|
if (positionInFirstPeriodUs != 0) {
|
||||||
|
bundle.putLong(FIELD_POSITION_IN_FIRST_PERIOD_US, positionInFirstPeriodUs);
|
||||||
|
}
|
||||||
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -501,42 +493,31 @@ public abstract class Timeline implements Bundleable {
|
|||||||
@UnstableApi public static final Creator<Window> CREATOR = Window::fromBundle;
|
@UnstableApi public static final Creator<Window> CREATOR = Window::fromBundle;
|
||||||
|
|
||||||
private static Window fromBundle(Bundle bundle) {
|
private static Window fromBundle(Bundle bundle) {
|
||||||
@Nullable Bundle mediaItemBundle = bundle.getBundle(keyForField(FIELD_MEDIA_ITEM));
|
@Nullable Bundle mediaItemBundle = bundle.getBundle(FIELD_MEDIA_ITEM);
|
||||||
@Nullable
|
@Nullable
|
||||||
MediaItem mediaItem =
|
MediaItem mediaItem =
|
||||||
mediaItemBundle != null ? MediaItem.CREATOR.fromBundle(mediaItemBundle) : null;
|
mediaItemBundle != null ? MediaItem.CREATOR.fromBundle(mediaItemBundle) : MediaItem.EMPTY;
|
||||||
long presentationStartTimeMs =
|
long presentationStartTimeMs =
|
||||||
bundle.getLong(
|
bundle.getLong(FIELD_PRESENTATION_START_TIME_MS, /* defaultValue= */ C.TIME_UNSET);
|
||||||
keyForField(FIELD_PRESENTATION_START_TIME_MS), /* defaultValue= */ C.TIME_UNSET);
|
|
||||||
long windowStartTimeMs =
|
long windowStartTimeMs =
|
||||||
bundle.getLong(keyForField(FIELD_WINDOW_START_TIME_MS), /* defaultValue= */ C.TIME_UNSET);
|
bundle.getLong(FIELD_WINDOW_START_TIME_MS, /* defaultValue= */ C.TIME_UNSET);
|
||||||
long elapsedRealtimeEpochOffsetMs =
|
long elapsedRealtimeEpochOffsetMs =
|
||||||
bundle.getLong(
|
bundle.getLong(FIELD_ELAPSED_REALTIME_EPOCH_OFFSET_MS, /* defaultValue= */ C.TIME_UNSET);
|
||||||
keyForField(FIELD_ELAPSED_REALTIME_EPOCH_OFFSET_MS),
|
boolean isSeekable = bundle.getBoolean(FIELD_IS_SEEKABLE, /* defaultValue= */ false);
|
||||||
/* defaultValue= */ C.TIME_UNSET);
|
boolean isDynamic = bundle.getBoolean(FIELD_IS_DYNAMIC, /* defaultValue= */ false);
|
||||||
boolean isSeekable =
|
@Nullable Bundle liveConfigurationBundle = bundle.getBundle(FIELD_LIVE_CONFIGURATION);
|
||||||
bundle.getBoolean(keyForField(FIELD_IS_SEEKABLE), /* defaultValue= */ false);
|
|
||||||
boolean isDynamic =
|
|
||||||
bundle.getBoolean(keyForField(FIELD_IS_DYNAMIC), /* defaultValue= */ false);
|
|
||||||
@Nullable
|
|
||||||
Bundle liveConfigurationBundle = bundle.getBundle(keyForField(FIELD_LIVE_CONFIGURATION));
|
|
||||||
@Nullable
|
@Nullable
|
||||||
MediaItem.LiveConfiguration liveConfiguration =
|
MediaItem.LiveConfiguration liveConfiguration =
|
||||||
liveConfigurationBundle != null
|
liveConfigurationBundle != null
|
||||||
? MediaItem.LiveConfiguration.CREATOR.fromBundle(liveConfigurationBundle)
|
? MediaItem.LiveConfiguration.CREATOR.fromBundle(liveConfigurationBundle)
|
||||||
: null;
|
: null;
|
||||||
boolean isPlaceHolder =
|
boolean isPlaceHolder = bundle.getBoolean(FIELD_IS_PLACEHOLDER, /* defaultValue= */ false);
|
||||||
bundle.getBoolean(keyForField(FIELD_IS_PLACEHOLDER), /* defaultValue= */ false);
|
long defaultPositionUs = bundle.getLong(FIELD_DEFAULT_POSITION_US, /* defaultValue= */ 0);
|
||||||
long defaultPositionUs =
|
long durationUs = bundle.getLong(FIELD_DURATION_US, /* defaultValue= */ C.TIME_UNSET);
|
||||||
bundle.getLong(keyForField(FIELD_DEFAULT_POSITION_US), /* defaultValue= */ 0);
|
int firstPeriodIndex = bundle.getInt(FIELD_FIRST_PERIOD_INDEX, /* defaultValue= */ 0);
|
||||||
long durationUs =
|
int lastPeriodIndex = bundle.getInt(FIELD_LAST_PERIOD_INDEX, /* defaultValue= */ 0);
|
||||||
bundle.getLong(keyForField(FIELD_DURATION_US), /* defaultValue= */ C.TIME_UNSET);
|
|
||||||
int firstPeriodIndex =
|
|
||||||
bundle.getInt(keyForField(FIELD_FIRST_PERIOD_INDEX), /* defaultValue= */ 0);
|
|
||||||
int lastPeriodIndex =
|
|
||||||
bundle.getInt(keyForField(FIELD_LAST_PERIOD_INDEX), /* defaultValue= */ 0);
|
|
||||||
long positionInFirstPeriodUs =
|
long positionInFirstPeriodUs =
|
||||||
bundle.getLong(keyForField(FIELD_POSITION_IN_FIRST_PERIOD_US), /* defaultValue= */ 0);
|
bundle.getLong(FIELD_POSITION_IN_FIRST_PERIOD_US, /* defaultValue= */ 0);
|
||||||
|
|
||||||
Window window = new Window();
|
Window window = new Window();
|
||||||
window.set(
|
window.set(
|
||||||
@ -557,10 +538,6 @@ public abstract class Timeline implements Bundleable {
|
|||||||
window.isPlaceholder = isPlaceHolder;
|
window.isPlaceholder = isPlaceHolder;
|
||||||
return window;
|
return window;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String keyForField(@Window.FieldNumber int field) {
|
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -912,23 +889,11 @@ public abstract class Timeline implements Bundleable {
|
|||||||
|
|
||||||
// Bundleable implementation.
|
// Bundleable implementation.
|
||||||
|
|
||||||
@Documented
|
private static final String FIELD_WINDOW_INDEX = Util.intToStringMaxRadix(0);
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
private static final String FIELD_DURATION_US = Util.intToStringMaxRadix(1);
|
||||||
@Target(TYPE_USE)
|
private static final String FIELD_POSITION_IN_WINDOW_US = Util.intToStringMaxRadix(2);
|
||||||
@IntDef({
|
private static final String FIELD_PLACEHOLDER = Util.intToStringMaxRadix(3);
|
||||||
FIELD_WINDOW_INDEX,
|
private static final String FIELD_AD_PLAYBACK_STATE = Util.intToStringMaxRadix(4);
|
||||||
FIELD_DURATION_US,
|
|
||||||
FIELD_POSITION_IN_WINDOW_US,
|
|
||||||
FIELD_PLACEHOLDER,
|
|
||||||
FIELD_AD_PLAYBACK_STATE
|
|
||||||
})
|
|
||||||
private @interface FieldNumber {}
|
|
||||||
|
|
||||||
private static final int FIELD_WINDOW_INDEX = 0;
|
|
||||||
private static final int FIELD_DURATION_US = 1;
|
|
||||||
private static final int FIELD_POSITION_IN_WINDOW_US = 2;
|
|
||||||
private static final int FIELD_PLACEHOLDER = 3;
|
|
||||||
private static final int FIELD_AD_PLAYBACK_STATE = 4;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
@ -936,16 +901,25 @@ public abstract class Timeline implements Bundleable {
|
|||||||
* <p>It omits the {@link #id} and {@link #uid} fields so these fields of an instance restored
|
* <p>It omits the {@link #id} and {@link #uid} fields so these fields of an instance restored
|
||||||
* by {@link #CREATOR} will always be {@code null}.
|
* by {@link #CREATOR} will always be {@code null}.
|
||||||
*/
|
*/
|
||||||
// TODO(b/166765820): See if missing fields would be okay and add them to the Bundle otherwise.
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Override
|
@Override
|
||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putInt(keyForField(FIELD_WINDOW_INDEX), windowIndex);
|
if (windowIndex != 0) {
|
||||||
bundle.putLong(keyForField(FIELD_DURATION_US), durationUs);
|
bundle.putInt(FIELD_WINDOW_INDEX, windowIndex);
|
||||||
bundle.putLong(keyForField(FIELD_POSITION_IN_WINDOW_US), positionInWindowUs);
|
}
|
||||||
bundle.putBoolean(keyForField(FIELD_PLACEHOLDER), isPlaceholder);
|
if (durationUs != C.TIME_UNSET) {
|
||||||
bundle.putBundle(keyForField(FIELD_AD_PLAYBACK_STATE), adPlaybackState.toBundle());
|
bundle.putLong(FIELD_DURATION_US, durationUs);
|
||||||
|
}
|
||||||
|
if (positionInWindowUs != 0) {
|
||||||
|
bundle.putLong(FIELD_POSITION_IN_WINDOW_US, positionInWindowUs);
|
||||||
|
}
|
||||||
|
if (isPlaceholder) {
|
||||||
|
bundle.putBoolean(FIELD_PLACEHOLDER, isPlaceholder);
|
||||||
|
}
|
||||||
|
if (!adPlaybackState.equals(AdPlaybackState.NONE)) {
|
||||||
|
bundle.putBundle(FIELD_AD_PLAYBACK_STATE, adPlaybackState.toBundle());
|
||||||
|
}
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -957,14 +931,11 @@ public abstract class Timeline implements Bundleable {
|
|||||||
@UnstableApi public static final Creator<Period> CREATOR = Period::fromBundle;
|
@UnstableApi public static final Creator<Period> CREATOR = Period::fromBundle;
|
||||||
|
|
||||||
private static Period fromBundle(Bundle bundle) {
|
private static Period fromBundle(Bundle bundle) {
|
||||||
int windowIndex = bundle.getInt(keyForField(FIELD_WINDOW_INDEX), /* defaultValue= */ 0);
|
int windowIndex = bundle.getInt(FIELD_WINDOW_INDEX, /* defaultValue= */ 0);
|
||||||
long durationUs =
|
long durationUs = bundle.getLong(FIELD_DURATION_US, /* defaultValue= */ C.TIME_UNSET);
|
||||||
bundle.getLong(keyForField(FIELD_DURATION_US), /* defaultValue= */ C.TIME_UNSET);
|
long positionInWindowUs = bundle.getLong(FIELD_POSITION_IN_WINDOW_US, /* defaultValue= */ 0);
|
||||||
long positionInWindowUs =
|
boolean isPlaceholder = bundle.getBoolean(FIELD_PLACEHOLDER, /* defaultValue= */ false);
|
||||||
bundle.getLong(keyForField(FIELD_POSITION_IN_WINDOW_US), /* defaultValue= */ 0);
|
@Nullable Bundle adPlaybackStateBundle = bundle.getBundle(FIELD_AD_PLAYBACK_STATE);
|
||||||
boolean isPlaceholder = bundle.getBoolean(keyForField(FIELD_PLACEHOLDER));
|
|
||||||
@Nullable
|
|
||||||
Bundle adPlaybackStateBundle = bundle.getBundle(keyForField(FIELD_AD_PLAYBACK_STATE));
|
|
||||||
AdPlaybackState adPlaybackState =
|
AdPlaybackState adPlaybackState =
|
||||||
adPlaybackStateBundle != null
|
adPlaybackStateBundle != null
|
||||||
? AdPlaybackState.CREATOR.fromBundle(adPlaybackStateBundle)
|
? AdPlaybackState.CREATOR.fromBundle(adPlaybackStateBundle)
|
||||||
@ -981,10 +952,6 @@ public abstract class Timeline implements Bundleable {
|
|||||||
isPlaceholder);
|
isPlaceholder);
|
||||||
return period;
|
return period;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String keyForField(@Period.FieldNumber int field) {
|
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** An empty timeline. */
|
/** An empty timeline. */
|
||||||
@ -1404,19 +1371,9 @@ public abstract class Timeline implements Bundleable {
|
|||||||
|
|
||||||
// Bundleable implementation.
|
// Bundleable implementation.
|
||||||
|
|
||||||
@Documented
|
private static final String FIELD_WINDOWS = Util.intToStringMaxRadix(0);
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
private static final String FIELD_PERIODS = Util.intToStringMaxRadix(1);
|
||||||
@Target(TYPE_USE)
|
private static final String FIELD_SHUFFLED_WINDOW_INDICES = Util.intToStringMaxRadix(2);
|
||||||
@IntDef({
|
|
||||||
FIELD_WINDOWS,
|
|
||||||
FIELD_PERIODS,
|
|
||||||
FIELD_SHUFFLED_WINDOW_INDICES,
|
|
||||||
})
|
|
||||||
private @interface FieldNumber {}
|
|
||||||
|
|
||||||
private static final int FIELD_WINDOWS = 0;
|
|
||||||
private static final int FIELD_PERIODS = 1;
|
|
||||||
private static final int FIELD_SHUFFLED_WINDOW_INDICES = 2;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
@ -1424,18 +1381,15 @@ public abstract class Timeline implements Bundleable {
|
|||||||
* <p>The {@link #getWindow(int, Window)} windows} and {@link #getPeriod(int, Period) periods} of
|
* <p>The {@link #getWindow(int, Window)} windows} and {@link #getPeriod(int, Period) periods} of
|
||||||
* an instance restored by {@link #CREATOR} may have missing fields as described in {@link
|
* an instance restored by {@link #CREATOR} may have missing fields as described in {@link
|
||||||
* Window#toBundle()} and {@link Period#toBundle()}.
|
* Window#toBundle()} and {@link Period#toBundle()}.
|
||||||
*
|
|
||||||
* @param excludeMediaItems Whether to exclude all {@link Window#mediaItem media items} of windows
|
|
||||||
* in the timeline.
|
|
||||||
*/
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public final Bundle toBundle(boolean excludeMediaItems) {
|
@Override
|
||||||
|
public final Bundle toBundle() {
|
||||||
List<Bundle> windowBundles = new ArrayList<>();
|
List<Bundle> windowBundles = new ArrayList<>();
|
||||||
int windowCount = getWindowCount();
|
int windowCount = getWindowCount();
|
||||||
Window window = new Window();
|
Window window = new Window();
|
||||||
for (int i = 0; i < windowCount; i++) {
|
for (int i = 0; i < windowCount; i++) {
|
||||||
windowBundles.add(
|
windowBundles.add(getWindow(i, window, /* defaultPositionProjectionUs= */ 0).toBundle());
|
||||||
getWindow(i, window, /* defaultPositionProjectionUs= */ 0).toBundle(excludeMediaItems));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
List<Bundle> periodBundles = new ArrayList<>();
|
List<Bundle> periodBundles = new ArrayList<>();
|
||||||
@ -1456,25 +1410,43 @@ public abstract class Timeline implements Bundleable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
BundleUtil.putBinder(
|
BundleUtil.putBinder(bundle, FIELD_WINDOWS, new BundleListRetriever(windowBundles));
|
||||||
bundle, keyForField(FIELD_WINDOWS), new BundleListRetriever(windowBundles));
|
BundleUtil.putBinder(bundle, FIELD_PERIODS, new BundleListRetriever(periodBundles));
|
||||||
BundleUtil.putBinder(
|
bundle.putIntArray(FIELD_SHUFFLED_WINDOW_INDICES, shuffledWindowIndices);
|
||||||
bundle, keyForField(FIELD_PERIODS), new BundleListRetriever(periodBundles));
|
|
||||||
bundle.putIntArray(keyForField(FIELD_SHUFFLED_WINDOW_INDICES), shuffledWindowIndices);
|
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* Returns a {@link Bundle} containing just the specified {@link Window}.
|
||||||
*
|
*
|
||||||
* <p>The {@link #getWindow(int, Window)} windows} and {@link #getPeriod(int, Period) periods} of
|
* <p>The {@link #getWindow(int, Window)} windows} and {@link #getPeriod(int, Period) periods} of
|
||||||
* an instance restored by {@link #CREATOR} may have missing fields as described in {@link
|
* an instance restored by {@link #CREATOR} may have missing fields as described in {@link
|
||||||
* Window#toBundle()} and {@link Period#toBundle()}.
|
* Window#toBundle()} and {@link Period#toBundle()}.
|
||||||
|
*
|
||||||
|
* @param windowIndex The index of the {@link Window} to include in the {@link Bundle}.
|
||||||
*/
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Override
|
public final Bundle toBundleWithOneWindowOnly(int windowIndex) {
|
||||||
public final Bundle toBundle() {
|
Window window = getWindow(windowIndex, new Window(), /* defaultPositionProjectionUs= */ 0);
|
||||||
return toBundle(/* excludeMediaItems= */ false);
|
|
||||||
|
List<Bundle> periodBundles = new ArrayList<>();
|
||||||
|
Period period = new Period();
|
||||||
|
for (int i = window.firstPeriodIndex; i <= window.lastPeriodIndex; i++) {
|
||||||
|
getPeriod(i, period, /* setIds= */ false);
|
||||||
|
period.windowIndex = 0;
|
||||||
|
periodBundles.add(period.toBundle());
|
||||||
|
}
|
||||||
|
|
||||||
|
window.lastPeriodIndex = window.lastPeriodIndex - window.firstPeriodIndex;
|
||||||
|
window.firstPeriodIndex = 0;
|
||||||
|
Bundle windowBundle = window.toBundle();
|
||||||
|
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
BundleUtil.putBinder(
|
||||||
|
bundle, FIELD_WINDOWS, new BundleListRetriever(ImmutableList.of(windowBundle)));
|
||||||
|
BundleUtil.putBinder(bundle, FIELD_PERIODS, new BundleListRetriever(periodBundles));
|
||||||
|
bundle.putIntArray(FIELD_SHUFFLED_WINDOW_INDICES, new int[] {0});
|
||||||
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1488,13 +1460,10 @@ public abstract class Timeline implements Bundleable {
|
|||||||
|
|
||||||
private static Timeline fromBundle(Bundle bundle) {
|
private static Timeline fromBundle(Bundle bundle) {
|
||||||
ImmutableList<Window> windows =
|
ImmutableList<Window> windows =
|
||||||
fromBundleListRetriever(
|
fromBundleListRetriever(Window.CREATOR, BundleUtil.getBinder(bundle, FIELD_WINDOWS));
|
||||||
Window.CREATOR, BundleUtil.getBinder(bundle, keyForField(FIELD_WINDOWS)));
|
|
||||||
ImmutableList<Period> periods =
|
ImmutableList<Period> periods =
|
||||||
fromBundleListRetriever(
|
fromBundleListRetriever(Period.CREATOR, BundleUtil.getBinder(bundle, FIELD_PERIODS));
|
||||||
Period.CREATOR, BundleUtil.getBinder(bundle, keyForField(FIELD_PERIODS)));
|
@Nullable int[] shuffledWindowIndices = bundle.getIntArray(FIELD_SHUFFLED_WINDOW_INDICES);
|
||||||
@Nullable
|
|
||||||
int[] shuffledWindowIndices = bundle.getIntArray(keyForField(FIELD_SHUFFLED_WINDOW_INDICES));
|
|
||||||
return new RemotableTimeline(
|
return new RemotableTimeline(
|
||||||
windows,
|
windows,
|
||||||
periods,
|
periods,
|
||||||
@ -1516,10 +1485,6 @@ public abstract class Timeline implements Bundleable {
|
|||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String keyForField(@FieldNumber int field) {
|
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int[] generateUnshuffledIndices(int n) {
|
private static int[] generateUnshuffledIndices(int n) {
|
||||||
int[] indices = new int[n];
|
int[] indices = new int[n];
|
||||||
for (int i = 0; i < n; i++) {
|
for (int i = 0; i < n; i++) {
|
||||||
|
@ -16,20 +16,15 @@
|
|||||||
package androidx.media3.common;
|
package androidx.media3.common;
|
||||||
|
|
||||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.CheckResult;
|
import androidx.annotation.CheckResult;
|
||||||
import androidx.annotation.IntDef;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.util.BundleableUtil;
|
import androidx.media3.common.util.BundleableUtil;
|
||||||
import androidx.media3.common.util.Log;
|
import androidx.media3.common.util.Log;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import androidx.media3.common.util.Util;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import java.lang.annotation.Documented;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
@ -165,15 +160,8 @@ public final class TrackGroup implements Bundleable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Bundleable implementation.
|
// Bundleable implementation.
|
||||||
|
private static final String FIELD_FORMATS = Util.intToStringMaxRadix(0);
|
||||||
@Documented
|
private static final String FIELD_ID = Util.intToStringMaxRadix(1);
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
|
||||||
@Target(TYPE_USE)
|
|
||||||
@IntDef({FIELD_FORMATS, FIELD_ID})
|
|
||||||
private @interface FieldNumber {}
|
|
||||||
|
|
||||||
private static final int FIELD_FORMATS = 0;
|
|
||||||
private static final int FIELD_ID = 1;
|
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Override
|
@Override
|
||||||
@ -183,8 +171,8 @@ public final class TrackGroup implements Bundleable {
|
|||||||
for (Format format : formats) {
|
for (Format format : formats) {
|
||||||
arrayList.add(format.toBundle(/* excludeMetadata= */ true));
|
arrayList.add(format.toBundle(/* excludeMetadata= */ true));
|
||||||
}
|
}
|
||||||
bundle.putParcelableArrayList(keyForField(FIELD_FORMATS), arrayList);
|
bundle.putParcelableArrayList(FIELD_FORMATS, arrayList);
|
||||||
bundle.putString(keyForField(FIELD_ID), id);
|
bundle.putString(FIELD_ID, id);
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -192,20 +180,15 @@ public final class TrackGroup implements Bundleable {
|
|||||||
@UnstableApi
|
@UnstableApi
|
||||||
public static final Creator<TrackGroup> CREATOR =
|
public static final Creator<TrackGroup> CREATOR =
|
||||||
bundle -> {
|
bundle -> {
|
||||||
@Nullable
|
@Nullable List<Bundle> formatBundles = bundle.getParcelableArrayList(FIELD_FORMATS);
|
||||||
List<Bundle> formatBundles = bundle.getParcelableArrayList(keyForField(FIELD_FORMATS));
|
|
||||||
List<Format> formats =
|
List<Format> formats =
|
||||||
formatBundles == null
|
formatBundles == null
|
||||||
? ImmutableList.of()
|
? ImmutableList.of()
|
||||||
: BundleableUtil.fromBundleList(Format.CREATOR, formatBundles);
|
: BundleableUtil.fromBundleList(Format.CREATOR, formatBundles);
|
||||||
String id = bundle.getString(keyForField(FIELD_ID), /* defaultValue= */ "");
|
String id = bundle.getString(FIELD_ID, /* defaultValue= */ "");
|
||||||
return new TrackGroup(id, formats.toArray(new Format[0]));
|
return new TrackGroup(id, formats.toArray(new Format[0]));
|
||||||
};
|
};
|
||||||
|
|
||||||
private static String keyForField(@FieldNumber int field) {
|
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
|
||||||
}
|
|
||||||
|
|
||||||
private void verifyCorrectness() {
|
private void verifyCorrectness() {
|
||||||
// TrackGroups should only contain tracks with exactly the same content (but in different
|
// TrackGroups should only contain tracks with exactly the same content (but in different
|
||||||
// qualities). We only log an error instead of throwing to not break backwards-compatibility for
|
// qualities). We only log an error instead of throwing to not break backwards-compatibility for
|
||||||
|
@ -20,14 +20,11 @@ import static java.util.Collections.max;
|
|||||||
import static java.util.Collections.min;
|
import static java.util.Collections.min;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.IntDef;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import androidx.media3.common.util.Util;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.primitives.Ints;
|
import com.google.common.primitives.Ints;
|
||||||
import java.lang.annotation.Documented;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -54,16 +51,8 @@ public final class TrackSelectionOverride implements Bundleable {
|
|||||||
/** The indices of tracks in a {@link TrackGroup} to be selected. */
|
/** The indices of tracks in a {@link TrackGroup} to be selected. */
|
||||||
public final ImmutableList<Integer> trackIndices;
|
public final ImmutableList<Integer> trackIndices;
|
||||||
|
|
||||||
@Documented
|
private static final String FIELD_TRACK_GROUP = Util.intToStringMaxRadix(0);
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
private static final String FIELD_TRACKS = Util.intToStringMaxRadix(1);
|
||||||
@IntDef({
|
|
||||||
FIELD_TRACK_GROUP,
|
|
||||||
FIELD_TRACKS,
|
|
||||||
})
|
|
||||||
private @interface FieldNumber {}
|
|
||||||
|
|
||||||
private static final int FIELD_TRACK_GROUP = 0;
|
|
||||||
private static final int FIELD_TRACKS = 1;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance to force {@code trackIndex} in {@code trackGroup} to be selected.
|
* Constructs an instance to force {@code trackIndex} in {@code trackGroup} to be selected.
|
||||||
@ -119,8 +108,8 @@ public final class TrackSelectionOverride implements Bundleable {
|
|||||||
@Override
|
@Override
|
||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putBundle(keyForField(FIELD_TRACK_GROUP), mediaTrackGroup.toBundle());
|
bundle.putBundle(FIELD_TRACK_GROUP, mediaTrackGroup.toBundle());
|
||||||
bundle.putIntArray(keyForField(FIELD_TRACKS), Ints.toArray(trackIndices));
|
bundle.putIntArray(FIELD_TRACKS, Ints.toArray(trackIndices));
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -128,13 +117,9 @@ public final class TrackSelectionOverride implements Bundleable {
|
|||||||
@UnstableApi
|
@UnstableApi
|
||||||
public static final Creator<TrackSelectionOverride> CREATOR =
|
public static final Creator<TrackSelectionOverride> CREATOR =
|
||||||
bundle -> {
|
bundle -> {
|
||||||
Bundle trackGroupBundle = checkNotNull(bundle.getBundle(keyForField(FIELD_TRACK_GROUP)));
|
Bundle trackGroupBundle = checkNotNull(bundle.getBundle(FIELD_TRACK_GROUP));
|
||||||
TrackGroup mediaTrackGroup = TrackGroup.CREATOR.fromBundle(trackGroupBundle);
|
TrackGroup mediaTrackGroup = TrackGroup.CREATOR.fromBundle(trackGroupBundle);
|
||||||
int[] tracks = checkNotNull(bundle.getIntArray(keyForField(FIELD_TRACKS)));
|
int[] tracks = checkNotNull(bundle.getIntArray(FIELD_TRACKS));
|
||||||
return new TrackSelectionOverride(mediaTrackGroup, Ints.asList(tracks));
|
return new TrackSelectionOverride(mediaTrackGroup, Ints.asList(tracks));
|
||||||
};
|
};
|
||||||
|
|
||||||
private static String keyForField(@FieldNumber int field) {
|
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -158,95 +158,71 @@ public class TrackSelectionParameters implements Bundleable {
|
|||||||
@UnstableApi
|
@UnstableApi
|
||||||
protected Builder(Bundle bundle) {
|
protected Builder(Bundle bundle) {
|
||||||
// Video
|
// Video
|
||||||
maxVideoWidth =
|
maxVideoWidth = bundle.getInt(FIELD_MAX_VIDEO_WIDTH, DEFAULT_WITHOUT_CONTEXT.maxVideoWidth);
|
||||||
bundle.getInt(keyForField(FIELD_MAX_VIDEO_WIDTH), DEFAULT_WITHOUT_CONTEXT.maxVideoWidth);
|
|
||||||
maxVideoHeight =
|
maxVideoHeight =
|
||||||
bundle.getInt(
|
bundle.getInt(FIELD_MAX_VIDEO_HEIGHT, DEFAULT_WITHOUT_CONTEXT.maxVideoHeight);
|
||||||
keyForField(FIELD_MAX_VIDEO_HEIGHT), DEFAULT_WITHOUT_CONTEXT.maxVideoHeight);
|
|
||||||
maxVideoFrameRate =
|
maxVideoFrameRate =
|
||||||
bundle.getInt(
|
bundle.getInt(FIELD_MAX_VIDEO_FRAMERATE, DEFAULT_WITHOUT_CONTEXT.maxVideoFrameRate);
|
||||||
keyForField(FIELD_MAX_VIDEO_FRAMERATE), DEFAULT_WITHOUT_CONTEXT.maxVideoFrameRate);
|
|
||||||
maxVideoBitrate =
|
maxVideoBitrate =
|
||||||
bundle.getInt(
|
bundle.getInt(FIELD_MAX_VIDEO_BITRATE, DEFAULT_WITHOUT_CONTEXT.maxVideoBitrate);
|
||||||
keyForField(FIELD_MAX_VIDEO_BITRATE), DEFAULT_WITHOUT_CONTEXT.maxVideoBitrate);
|
minVideoWidth = bundle.getInt(FIELD_MIN_VIDEO_WIDTH, DEFAULT_WITHOUT_CONTEXT.minVideoWidth);
|
||||||
minVideoWidth =
|
|
||||||
bundle.getInt(keyForField(FIELD_MIN_VIDEO_WIDTH), DEFAULT_WITHOUT_CONTEXT.minVideoWidth);
|
|
||||||
minVideoHeight =
|
minVideoHeight =
|
||||||
bundle.getInt(
|
bundle.getInt(FIELD_MIN_VIDEO_HEIGHT, DEFAULT_WITHOUT_CONTEXT.minVideoHeight);
|
||||||
keyForField(FIELD_MIN_VIDEO_HEIGHT), DEFAULT_WITHOUT_CONTEXT.minVideoHeight);
|
|
||||||
minVideoFrameRate =
|
minVideoFrameRate =
|
||||||
bundle.getInt(
|
bundle.getInt(FIELD_MIN_VIDEO_FRAMERATE, DEFAULT_WITHOUT_CONTEXT.minVideoFrameRate);
|
||||||
keyForField(FIELD_MIN_VIDEO_FRAMERATE), DEFAULT_WITHOUT_CONTEXT.minVideoFrameRate);
|
|
||||||
minVideoBitrate =
|
minVideoBitrate =
|
||||||
bundle.getInt(
|
bundle.getInt(FIELD_MIN_VIDEO_BITRATE, DEFAULT_WITHOUT_CONTEXT.minVideoBitrate);
|
||||||
keyForField(FIELD_MIN_VIDEO_BITRATE), DEFAULT_WITHOUT_CONTEXT.minVideoBitrate);
|
viewportWidth = bundle.getInt(FIELD_VIEWPORT_WIDTH, DEFAULT_WITHOUT_CONTEXT.viewportWidth);
|
||||||
viewportWidth =
|
viewportHeight = bundle.getInt(FIELD_VIEWPORT_HEIGHT, DEFAULT_WITHOUT_CONTEXT.viewportHeight);
|
||||||
bundle.getInt(keyForField(FIELD_VIEWPORT_WIDTH), DEFAULT_WITHOUT_CONTEXT.viewportWidth);
|
|
||||||
viewportHeight =
|
|
||||||
bundle.getInt(keyForField(FIELD_VIEWPORT_HEIGHT), DEFAULT_WITHOUT_CONTEXT.viewportHeight);
|
|
||||||
viewportOrientationMayChange =
|
viewportOrientationMayChange =
|
||||||
bundle.getBoolean(
|
bundle.getBoolean(
|
||||||
keyForField(FIELD_VIEWPORT_ORIENTATION_MAY_CHANGE),
|
FIELD_VIEWPORT_ORIENTATION_MAY_CHANGE,
|
||||||
DEFAULT_WITHOUT_CONTEXT.viewportOrientationMayChange);
|
DEFAULT_WITHOUT_CONTEXT.viewportOrientationMayChange);
|
||||||
preferredVideoMimeTypes =
|
preferredVideoMimeTypes =
|
||||||
ImmutableList.copyOf(
|
ImmutableList.copyOf(
|
||||||
firstNonNull(
|
firstNonNull(bundle.getStringArray(FIELD_PREFERRED_VIDEO_MIMETYPES), new String[0]));
|
||||||
bundle.getStringArray(keyForField(FIELD_PREFERRED_VIDEO_MIMETYPES)),
|
|
||||||
new String[0]));
|
|
||||||
preferredVideoRoleFlags =
|
preferredVideoRoleFlags =
|
||||||
bundle.getInt(
|
bundle.getInt(
|
||||||
keyForField(FIELD_PREFERRED_VIDEO_ROLE_FLAGS),
|
FIELD_PREFERRED_VIDEO_ROLE_FLAGS, DEFAULT_WITHOUT_CONTEXT.preferredVideoRoleFlags);
|
||||||
DEFAULT_WITHOUT_CONTEXT.preferredVideoRoleFlags);
|
|
||||||
// Audio
|
// Audio
|
||||||
String[] preferredAudioLanguages1 =
|
String[] preferredAudioLanguages1 =
|
||||||
firstNonNull(
|
firstNonNull(bundle.getStringArray(FIELD_PREFERRED_AUDIO_LANGUAGES), new String[0]);
|
||||||
bundle.getStringArray(keyForField(FIELD_PREFERRED_AUDIO_LANGUAGES)), new String[0]);
|
|
||||||
preferredAudioLanguages = normalizeLanguageCodes(preferredAudioLanguages1);
|
preferredAudioLanguages = normalizeLanguageCodes(preferredAudioLanguages1);
|
||||||
preferredAudioRoleFlags =
|
preferredAudioRoleFlags =
|
||||||
bundle.getInt(
|
bundle.getInt(
|
||||||
keyForField(FIELD_PREFERRED_AUDIO_ROLE_FLAGS),
|
FIELD_PREFERRED_AUDIO_ROLE_FLAGS, DEFAULT_WITHOUT_CONTEXT.preferredAudioRoleFlags);
|
||||||
DEFAULT_WITHOUT_CONTEXT.preferredAudioRoleFlags);
|
|
||||||
maxAudioChannelCount =
|
maxAudioChannelCount =
|
||||||
bundle.getInt(
|
bundle.getInt(
|
||||||
keyForField(FIELD_MAX_AUDIO_CHANNEL_COUNT),
|
FIELD_MAX_AUDIO_CHANNEL_COUNT, DEFAULT_WITHOUT_CONTEXT.maxAudioChannelCount);
|
||||||
DEFAULT_WITHOUT_CONTEXT.maxAudioChannelCount);
|
|
||||||
maxAudioBitrate =
|
maxAudioBitrate =
|
||||||
bundle.getInt(
|
bundle.getInt(FIELD_MAX_AUDIO_BITRATE, DEFAULT_WITHOUT_CONTEXT.maxAudioBitrate);
|
||||||
keyForField(FIELD_MAX_AUDIO_BITRATE), DEFAULT_WITHOUT_CONTEXT.maxAudioBitrate);
|
|
||||||
preferredAudioMimeTypes =
|
preferredAudioMimeTypes =
|
||||||
ImmutableList.copyOf(
|
ImmutableList.copyOf(
|
||||||
firstNonNull(
|
firstNonNull(bundle.getStringArray(FIELD_PREFERRED_AUDIO_MIME_TYPES), new String[0]));
|
||||||
bundle.getStringArray(keyForField(FIELD_PREFERRED_AUDIO_MIME_TYPES)),
|
|
||||||
new String[0]));
|
|
||||||
// Text
|
// Text
|
||||||
preferredTextLanguages =
|
preferredTextLanguages =
|
||||||
normalizeLanguageCodes(
|
normalizeLanguageCodes(
|
||||||
firstNonNull(
|
firstNonNull(bundle.getStringArray(FIELD_PREFERRED_TEXT_LANGUAGES), new String[0]));
|
||||||
bundle.getStringArray(keyForField(FIELD_PREFERRED_TEXT_LANGUAGES)),
|
|
||||||
new String[0]));
|
|
||||||
preferredTextRoleFlags =
|
preferredTextRoleFlags =
|
||||||
bundle.getInt(
|
bundle.getInt(
|
||||||
keyForField(FIELD_PREFERRED_TEXT_ROLE_FLAGS),
|
FIELD_PREFERRED_TEXT_ROLE_FLAGS, DEFAULT_WITHOUT_CONTEXT.preferredTextRoleFlags);
|
||||||
DEFAULT_WITHOUT_CONTEXT.preferredTextRoleFlags);
|
|
||||||
ignoredTextSelectionFlags =
|
ignoredTextSelectionFlags =
|
||||||
bundle.getInt(
|
bundle.getInt(
|
||||||
keyForField(FIELD_IGNORED_TEXT_SELECTION_FLAGS),
|
FIELD_IGNORED_TEXT_SELECTION_FLAGS,
|
||||||
DEFAULT_WITHOUT_CONTEXT.ignoredTextSelectionFlags);
|
DEFAULT_WITHOUT_CONTEXT.ignoredTextSelectionFlags);
|
||||||
selectUndeterminedTextLanguage =
|
selectUndeterminedTextLanguage =
|
||||||
bundle.getBoolean(
|
bundle.getBoolean(
|
||||||
keyForField(FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE),
|
FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE,
|
||||||
DEFAULT_WITHOUT_CONTEXT.selectUndeterminedTextLanguage);
|
DEFAULT_WITHOUT_CONTEXT.selectUndeterminedTextLanguage);
|
||||||
// General
|
// General
|
||||||
forceLowestBitrate =
|
forceLowestBitrate =
|
||||||
bundle.getBoolean(
|
bundle.getBoolean(FIELD_FORCE_LOWEST_BITRATE, DEFAULT_WITHOUT_CONTEXT.forceLowestBitrate);
|
||||||
keyForField(FIELD_FORCE_LOWEST_BITRATE), DEFAULT_WITHOUT_CONTEXT.forceLowestBitrate);
|
|
||||||
forceHighestSupportedBitrate =
|
forceHighestSupportedBitrate =
|
||||||
bundle.getBoolean(
|
bundle.getBoolean(
|
||||||
keyForField(FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE),
|
FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE,
|
||||||
DEFAULT_WITHOUT_CONTEXT.forceHighestSupportedBitrate);
|
DEFAULT_WITHOUT_CONTEXT.forceHighestSupportedBitrate);
|
||||||
@Nullable
|
@Nullable
|
||||||
List<Bundle> overrideBundleList =
|
List<Bundle> overrideBundleList = bundle.getParcelableArrayList(FIELD_SELECTION_OVERRIDES);
|
||||||
bundle.getParcelableArrayList(keyForField(FIELD_SELECTION_OVERRIDES));
|
|
||||||
List<TrackSelectionOverride> overrideList =
|
List<TrackSelectionOverride> overrideList =
|
||||||
overrideBundleList == null
|
overrideBundleList == null
|
||||||
? ImmutableList.of()
|
? ImmutableList.of()
|
||||||
@ -257,7 +233,7 @@ public class TrackSelectionParameters implements Bundleable {
|
|||||||
overrides.put(override.mediaTrackGroup, override);
|
overrides.put(override.mediaTrackGroup, override);
|
||||||
}
|
}
|
||||||
int[] disabledTrackTypeArray =
|
int[] disabledTrackTypeArray =
|
||||||
firstNonNull(bundle.getIntArray(keyForField(FIELD_DISABLED_TRACK_TYPE)), new int[0]);
|
firstNonNull(bundle.getIntArray(FIELD_DISABLED_TRACK_TYPE), new int[0]);
|
||||||
disabledTrackTypes = new HashSet<>();
|
disabledTrackTypes = new HashSet<>();
|
||||||
for (@C.TrackType int disabledTrackType : disabledTrackTypeArray) {
|
for (@C.TrackType int disabledTrackType : disabledTrackTypeArray) {
|
||||||
disabledTrackTypes.add(disabledTrackType);
|
disabledTrackTypes.add(disabledTrackType);
|
||||||
@ -1103,39 +1079,40 @@ public class TrackSelectionParameters implements Bundleable {
|
|||||||
|
|
||||||
// Bundleable implementation
|
// Bundleable implementation
|
||||||
|
|
||||||
private static final int FIELD_PREFERRED_AUDIO_LANGUAGES = 1;
|
private static final String FIELD_PREFERRED_AUDIO_LANGUAGES = Util.intToStringMaxRadix(1);
|
||||||
private static final int FIELD_PREFERRED_AUDIO_ROLE_FLAGS = 2;
|
private static final String FIELD_PREFERRED_AUDIO_ROLE_FLAGS = Util.intToStringMaxRadix(2);
|
||||||
private static final int FIELD_PREFERRED_TEXT_LANGUAGES = 3;
|
private static final String FIELD_PREFERRED_TEXT_LANGUAGES = Util.intToStringMaxRadix(3);
|
||||||
private static final int FIELD_PREFERRED_TEXT_ROLE_FLAGS = 4;
|
private static final String FIELD_PREFERRED_TEXT_ROLE_FLAGS = Util.intToStringMaxRadix(4);
|
||||||
private static final int FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE = 5;
|
private static final String FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE = Util.intToStringMaxRadix(5);
|
||||||
private static final int FIELD_MAX_VIDEO_WIDTH = 6;
|
private static final String FIELD_MAX_VIDEO_WIDTH = Util.intToStringMaxRadix(6);
|
||||||
private static final int FIELD_MAX_VIDEO_HEIGHT = 7;
|
private static final String FIELD_MAX_VIDEO_HEIGHT = Util.intToStringMaxRadix(7);
|
||||||
private static final int FIELD_MAX_VIDEO_FRAMERATE = 8;
|
private static final String FIELD_MAX_VIDEO_FRAMERATE = Util.intToStringMaxRadix(8);
|
||||||
private static final int FIELD_MAX_VIDEO_BITRATE = 9;
|
private static final String FIELD_MAX_VIDEO_BITRATE = Util.intToStringMaxRadix(9);
|
||||||
private static final int FIELD_MIN_VIDEO_WIDTH = 10;
|
private static final String FIELD_MIN_VIDEO_WIDTH = Util.intToStringMaxRadix(10);
|
||||||
private static final int FIELD_MIN_VIDEO_HEIGHT = 11;
|
private static final String FIELD_MIN_VIDEO_HEIGHT = Util.intToStringMaxRadix(11);
|
||||||
private static final int FIELD_MIN_VIDEO_FRAMERATE = 12;
|
private static final String FIELD_MIN_VIDEO_FRAMERATE = Util.intToStringMaxRadix(12);
|
||||||
private static final int FIELD_MIN_VIDEO_BITRATE = 13;
|
private static final String FIELD_MIN_VIDEO_BITRATE = Util.intToStringMaxRadix(13);
|
||||||
private static final int FIELD_VIEWPORT_WIDTH = 14;
|
private static final String FIELD_VIEWPORT_WIDTH = Util.intToStringMaxRadix(14);
|
||||||
private static final int FIELD_VIEWPORT_HEIGHT = 15;
|
private static final String FIELD_VIEWPORT_HEIGHT = Util.intToStringMaxRadix(15);
|
||||||
private static final int FIELD_VIEWPORT_ORIENTATION_MAY_CHANGE = 16;
|
private static final String FIELD_VIEWPORT_ORIENTATION_MAY_CHANGE = Util.intToStringMaxRadix(16);
|
||||||
private static final int FIELD_PREFERRED_VIDEO_MIMETYPES = 17;
|
private static final String FIELD_PREFERRED_VIDEO_MIMETYPES = Util.intToStringMaxRadix(17);
|
||||||
private static final int FIELD_MAX_AUDIO_CHANNEL_COUNT = 18;
|
private static final String FIELD_MAX_AUDIO_CHANNEL_COUNT = Util.intToStringMaxRadix(18);
|
||||||
private static final int FIELD_MAX_AUDIO_BITRATE = 19;
|
private static final String FIELD_MAX_AUDIO_BITRATE = Util.intToStringMaxRadix(19);
|
||||||
private static final int FIELD_PREFERRED_AUDIO_MIME_TYPES = 20;
|
private static final String FIELD_PREFERRED_AUDIO_MIME_TYPES = Util.intToStringMaxRadix(20);
|
||||||
private static final int FIELD_FORCE_LOWEST_BITRATE = 21;
|
private static final String FIELD_FORCE_LOWEST_BITRATE = Util.intToStringMaxRadix(21);
|
||||||
private static final int FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE = 22;
|
private static final String FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE = Util.intToStringMaxRadix(22);
|
||||||
private static final int FIELD_SELECTION_OVERRIDES = 23;
|
private static final String FIELD_SELECTION_OVERRIDES = Util.intToStringMaxRadix(23);
|
||||||
private static final int FIELD_DISABLED_TRACK_TYPE = 24;
|
private static final String FIELD_DISABLED_TRACK_TYPE = Util.intToStringMaxRadix(24);
|
||||||
private static final int FIELD_PREFERRED_VIDEO_ROLE_FLAGS = 25;
|
private static final String FIELD_PREFERRED_VIDEO_ROLE_FLAGS = Util.intToStringMaxRadix(25);
|
||||||
private static final int FIELD_IGNORED_TEXT_SELECTION_FLAGS = 26;
|
private static final String FIELD_IGNORED_TEXT_SELECTION_FLAGS = Util.intToStringMaxRadix(26);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines a minimum field ID value for subclasses to use when implementing {@link #toBundle()}
|
* Defines a minimum field ID value for subclasses to use when implementing {@link #toBundle()}
|
||||||
* and {@link Bundleable.Creator}.
|
* and {@link Bundleable.Creator}.
|
||||||
*
|
*
|
||||||
* <p>Subclasses should obtain keys for their {@link Bundle} representation by applying a
|
* <p>Subclasses should obtain keys for their {@link Bundle} representation by applying a
|
||||||
* non-negative offset on this constant and passing the result to {@link #keyForField(int)}.
|
* non-negative offset on this constant and passing the result to {@link
|
||||||
|
* Util#intToStringMaxRadix(int)}.
|
||||||
*/
|
*/
|
||||||
@UnstableApi protected static final int FIELD_CUSTOM_ID_BASE = 1000;
|
@UnstableApi protected static final int FIELD_CUSTOM_ID_BASE = 1000;
|
||||||
|
|
||||||
@ -1144,46 +1121,39 @@ public class TrackSelectionParameters implements Bundleable {
|
|||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
|
|
||||||
// Video
|
// Video
|
||||||
bundle.putInt(keyForField(FIELD_MAX_VIDEO_WIDTH), maxVideoWidth);
|
bundle.putInt(FIELD_MAX_VIDEO_WIDTH, maxVideoWidth);
|
||||||
bundle.putInt(keyForField(FIELD_MAX_VIDEO_HEIGHT), maxVideoHeight);
|
bundle.putInt(FIELD_MAX_VIDEO_HEIGHT, maxVideoHeight);
|
||||||
bundle.putInt(keyForField(FIELD_MAX_VIDEO_FRAMERATE), maxVideoFrameRate);
|
bundle.putInt(FIELD_MAX_VIDEO_FRAMERATE, maxVideoFrameRate);
|
||||||
bundle.putInt(keyForField(FIELD_MAX_VIDEO_BITRATE), maxVideoBitrate);
|
bundle.putInt(FIELD_MAX_VIDEO_BITRATE, maxVideoBitrate);
|
||||||
bundle.putInt(keyForField(FIELD_MIN_VIDEO_WIDTH), minVideoWidth);
|
bundle.putInt(FIELD_MIN_VIDEO_WIDTH, minVideoWidth);
|
||||||
bundle.putInt(keyForField(FIELD_MIN_VIDEO_HEIGHT), minVideoHeight);
|
bundle.putInt(FIELD_MIN_VIDEO_HEIGHT, minVideoHeight);
|
||||||
bundle.putInt(keyForField(FIELD_MIN_VIDEO_FRAMERATE), minVideoFrameRate);
|
bundle.putInt(FIELD_MIN_VIDEO_FRAMERATE, minVideoFrameRate);
|
||||||
bundle.putInt(keyForField(FIELD_MIN_VIDEO_BITRATE), minVideoBitrate);
|
bundle.putInt(FIELD_MIN_VIDEO_BITRATE, minVideoBitrate);
|
||||||
bundle.putInt(keyForField(FIELD_VIEWPORT_WIDTH), viewportWidth);
|
bundle.putInt(FIELD_VIEWPORT_WIDTH, viewportWidth);
|
||||||
bundle.putInt(keyForField(FIELD_VIEWPORT_HEIGHT), viewportHeight);
|
bundle.putInt(FIELD_VIEWPORT_HEIGHT, viewportHeight);
|
||||||
bundle.putBoolean(
|
bundle.putBoolean(FIELD_VIEWPORT_ORIENTATION_MAY_CHANGE, viewportOrientationMayChange);
|
||||||
keyForField(FIELD_VIEWPORT_ORIENTATION_MAY_CHANGE), viewportOrientationMayChange);
|
|
||||||
bundle.putStringArray(
|
bundle.putStringArray(
|
||||||
keyForField(FIELD_PREFERRED_VIDEO_MIMETYPES),
|
FIELD_PREFERRED_VIDEO_MIMETYPES, preferredVideoMimeTypes.toArray(new String[0]));
|
||||||
preferredVideoMimeTypes.toArray(new String[0]));
|
bundle.putInt(FIELD_PREFERRED_VIDEO_ROLE_FLAGS, preferredVideoRoleFlags);
|
||||||
bundle.putInt(keyForField(FIELD_PREFERRED_VIDEO_ROLE_FLAGS), preferredVideoRoleFlags);
|
|
||||||
// Audio
|
// Audio
|
||||||
bundle.putStringArray(
|
bundle.putStringArray(
|
||||||
keyForField(FIELD_PREFERRED_AUDIO_LANGUAGES),
|
FIELD_PREFERRED_AUDIO_LANGUAGES, preferredAudioLanguages.toArray(new String[0]));
|
||||||
preferredAudioLanguages.toArray(new String[0]));
|
bundle.putInt(FIELD_PREFERRED_AUDIO_ROLE_FLAGS, preferredAudioRoleFlags);
|
||||||
bundle.putInt(keyForField(FIELD_PREFERRED_AUDIO_ROLE_FLAGS), preferredAudioRoleFlags);
|
bundle.putInt(FIELD_MAX_AUDIO_CHANNEL_COUNT, maxAudioChannelCount);
|
||||||
bundle.putInt(keyForField(FIELD_MAX_AUDIO_CHANNEL_COUNT), maxAudioChannelCount);
|
bundle.putInt(FIELD_MAX_AUDIO_BITRATE, maxAudioBitrate);
|
||||||
bundle.putInt(keyForField(FIELD_MAX_AUDIO_BITRATE), maxAudioBitrate);
|
|
||||||
bundle.putStringArray(
|
bundle.putStringArray(
|
||||||
keyForField(FIELD_PREFERRED_AUDIO_MIME_TYPES),
|
FIELD_PREFERRED_AUDIO_MIME_TYPES, preferredAudioMimeTypes.toArray(new String[0]));
|
||||||
preferredAudioMimeTypes.toArray(new String[0]));
|
|
||||||
// Text
|
// Text
|
||||||
bundle.putStringArray(
|
bundle.putStringArray(
|
||||||
keyForField(FIELD_PREFERRED_TEXT_LANGUAGES), preferredTextLanguages.toArray(new String[0]));
|
FIELD_PREFERRED_TEXT_LANGUAGES, preferredTextLanguages.toArray(new String[0]));
|
||||||
bundle.putInt(keyForField(FIELD_PREFERRED_TEXT_ROLE_FLAGS), preferredTextRoleFlags);
|
bundle.putInt(FIELD_PREFERRED_TEXT_ROLE_FLAGS, preferredTextRoleFlags);
|
||||||
bundle.putInt(keyForField(FIELD_IGNORED_TEXT_SELECTION_FLAGS), ignoredTextSelectionFlags);
|
bundle.putInt(FIELD_IGNORED_TEXT_SELECTION_FLAGS, ignoredTextSelectionFlags);
|
||||||
bundle.putBoolean(
|
bundle.putBoolean(FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE, selectUndeterminedTextLanguage);
|
||||||
keyForField(FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE), selectUndeterminedTextLanguage);
|
|
||||||
// General
|
// General
|
||||||
bundle.putBoolean(keyForField(FIELD_FORCE_LOWEST_BITRATE), forceLowestBitrate);
|
bundle.putBoolean(FIELD_FORCE_LOWEST_BITRATE, forceLowestBitrate);
|
||||||
bundle.putBoolean(
|
bundle.putBoolean(FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE, forceHighestSupportedBitrate);
|
||||||
keyForField(FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE), forceHighestSupportedBitrate);
|
bundle.putParcelableArrayList(FIELD_SELECTION_OVERRIDES, toBundleArrayList(overrides.values()));
|
||||||
bundle.putParcelableArrayList(
|
bundle.putIntArray(FIELD_DISABLED_TRACK_TYPE, Ints.toArray(disabledTrackTypes));
|
||||||
keyForField(FIELD_SELECTION_OVERRIDES), toBundleArrayList(overrides.values()));
|
|
||||||
bundle.putIntArray(keyForField(FIELD_DISABLED_TRACK_TYPE), Ints.toArray(disabledTrackTypes));
|
|
||||||
|
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
@ -1199,16 +1169,4 @@ public class TrackSelectionParameters implements Bundleable {
|
|||||||
@UnstableApi @Deprecated
|
@UnstableApi @Deprecated
|
||||||
public static final Creator<TrackSelectionParameters> CREATOR =
|
public static final Creator<TrackSelectionParameters> CREATOR =
|
||||||
TrackSelectionParameters::fromBundle;
|
TrackSelectionParameters::fromBundle;
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts the given field number to a string which can be used as a field key when implementing
|
|
||||||
* {@link #toBundle()} and {@link Bundleable.Creator}.
|
|
||||||
*
|
|
||||||
* <p>Subclasses should use {@code field} values greater than or equal to {@link
|
|
||||||
* #FIELD_CUSTOM_ID_BASE}.
|
|
||||||
*/
|
|
||||||
@UnstableApi
|
|
||||||
protected static String keyForField(int field) {
|
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -18,20 +18,15 @@ package androidx.media3.common;
|
|||||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
import static androidx.media3.common.util.BundleableUtil.toBundleArrayList;
|
import static androidx.media3.common.util.BundleableUtil.toBundleArrayList;
|
||||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.IntDef;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.util.BundleableUtil;
|
import androidx.media3.common.util.BundleableUtil;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import androidx.media3.common.util.Util;
|
||||||
import com.google.common.base.MoreObjects;
|
import com.google.common.base.MoreObjects;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.primitives.Booleans;
|
import com.google.common.primitives.Booleans;
|
||||||
import java.lang.annotation.Documented;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -221,29 +216,19 @@ public final class Tracks implements Bundleable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Bundleable implementation.
|
// Bundleable implementation.
|
||||||
@Documented
|
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
|
||||||
@Target(TYPE_USE)
|
|
||||||
@IntDef({
|
|
||||||
FIELD_TRACK_GROUP,
|
|
||||||
FIELD_TRACK_SUPPORT,
|
|
||||||
FIELD_TRACK_SELECTED,
|
|
||||||
FIELD_ADAPTIVE_SUPPORTED,
|
|
||||||
})
|
|
||||||
private @interface FieldNumber {}
|
|
||||||
|
|
||||||
private static final int FIELD_TRACK_GROUP = 0;
|
private static final String FIELD_TRACK_GROUP = Util.intToStringMaxRadix(0);
|
||||||
private static final int FIELD_TRACK_SUPPORT = 1;
|
private static final String FIELD_TRACK_SUPPORT = Util.intToStringMaxRadix(1);
|
||||||
private static final int FIELD_TRACK_SELECTED = 3;
|
private static final String FIELD_TRACK_SELECTED = Util.intToStringMaxRadix(3);
|
||||||
private static final int FIELD_ADAPTIVE_SUPPORTED = 4;
|
private static final String FIELD_ADAPTIVE_SUPPORTED = Util.intToStringMaxRadix(4);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putBundle(keyForField(FIELD_TRACK_GROUP), mediaTrackGroup.toBundle());
|
bundle.putBundle(FIELD_TRACK_GROUP, mediaTrackGroup.toBundle());
|
||||||
bundle.putIntArray(keyForField(FIELD_TRACK_SUPPORT), trackSupport);
|
bundle.putIntArray(FIELD_TRACK_SUPPORT, trackSupport);
|
||||||
bundle.putBooleanArray(keyForField(FIELD_TRACK_SELECTED), trackSelected);
|
bundle.putBooleanArray(FIELD_TRACK_SELECTED, trackSelected);
|
||||||
bundle.putBoolean(keyForField(FIELD_ADAPTIVE_SUPPORTED), adaptiveSupported);
|
bundle.putBoolean(FIELD_ADAPTIVE_SUPPORTED, adaptiveSupported);
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -253,23 +238,16 @@ public final class Tracks implements Bundleable {
|
|||||||
bundle -> {
|
bundle -> {
|
||||||
// Can't create a Tracks.Group without a TrackGroup
|
// Can't create a Tracks.Group without a TrackGroup
|
||||||
TrackGroup trackGroup =
|
TrackGroup trackGroup =
|
||||||
TrackGroup.CREATOR.fromBundle(
|
TrackGroup.CREATOR.fromBundle(checkNotNull(bundle.getBundle(FIELD_TRACK_GROUP)));
|
||||||
checkNotNull(bundle.getBundle(keyForField(FIELD_TRACK_GROUP))));
|
|
||||||
final @C.FormatSupport int[] trackSupport =
|
final @C.FormatSupport int[] trackSupport =
|
||||||
MoreObjects.firstNonNull(
|
MoreObjects.firstNonNull(
|
||||||
bundle.getIntArray(keyForField(FIELD_TRACK_SUPPORT)), new int[trackGroup.length]);
|
bundle.getIntArray(FIELD_TRACK_SUPPORT), new int[trackGroup.length]);
|
||||||
boolean[] selected =
|
boolean[] selected =
|
||||||
MoreObjects.firstNonNull(
|
MoreObjects.firstNonNull(
|
||||||
bundle.getBooleanArray(keyForField(FIELD_TRACK_SELECTED)),
|
bundle.getBooleanArray(FIELD_TRACK_SELECTED), new boolean[trackGroup.length]);
|
||||||
new boolean[trackGroup.length]);
|
boolean adaptiveSupported = bundle.getBoolean(FIELD_ADAPTIVE_SUPPORTED, false);
|
||||||
boolean adaptiveSupported =
|
|
||||||
bundle.getBoolean(keyForField(FIELD_ADAPTIVE_SUPPORTED), false);
|
|
||||||
return new Group(trackGroup, adaptiveSupported, trackSupport, selected);
|
return new Group(trackGroup, adaptiveSupported, trackSupport, selected);
|
||||||
};
|
};
|
||||||
|
|
||||||
private static String keyForField(@FieldNumber int field) {
|
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Empty tracks. */
|
/** Empty tracks. */
|
||||||
@ -385,21 +363,13 @@ public final class Tracks implements Bundleable {
|
|||||||
}
|
}
|
||||||
// Bundleable implementation.
|
// Bundleable implementation.
|
||||||
|
|
||||||
@Documented
|
private static final String FIELD_TRACK_GROUPS = Util.intToStringMaxRadix(0);
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
|
||||||
@Target(TYPE_USE)
|
|
||||||
@IntDef({
|
|
||||||
FIELD_TRACK_GROUPS,
|
|
||||||
})
|
|
||||||
private @interface FieldNumber {}
|
|
||||||
|
|
||||||
private static final int FIELD_TRACK_GROUPS = 0;
|
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Override
|
@Override
|
||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putParcelableArrayList(keyForField(FIELD_TRACK_GROUPS), toBundleArrayList(groups));
|
bundle.putParcelableArrayList(FIELD_TRACK_GROUPS, toBundleArrayList(groups));
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -407,16 +377,11 @@ public final class Tracks implements Bundleable {
|
|||||||
@UnstableApi
|
@UnstableApi
|
||||||
public static final Creator<Tracks> CREATOR =
|
public static final Creator<Tracks> CREATOR =
|
||||||
bundle -> {
|
bundle -> {
|
||||||
@Nullable
|
@Nullable List<Bundle> groupBundles = bundle.getParcelableArrayList(FIELD_TRACK_GROUPS);
|
||||||
List<Bundle> groupBundles = bundle.getParcelableArrayList(keyForField(FIELD_TRACK_GROUPS));
|
|
||||||
List<Group> groups =
|
List<Group> groups =
|
||||||
groupBundles == null
|
groupBundles == null
|
||||||
? ImmutableList.of()
|
? ImmutableList.of()
|
||||||
: BundleableUtil.fromBundleList(Group.CREATOR, groupBundles);
|
: BundleableUtil.fromBundleList(Group.CREATOR, groupBundles);
|
||||||
return new Tracks(groups);
|
return new Tracks(groups);
|
||||||
};
|
};
|
||||||
|
|
||||||
private static String keyForField(@FieldNumber int field) {
|
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -15,18 +15,12 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.common;
|
package androidx.media3.common;
|
||||||
|
|
||||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.FloatRange;
|
import androidx.annotation.FloatRange;
|
||||||
import androidx.annotation.IntDef;
|
|
||||||
import androidx.annotation.IntRange;
|
import androidx.annotation.IntRange;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import java.lang.annotation.Documented;
|
import androidx.media3.common.util.Util;
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
|
|
||||||
/** Represents the video size. */
|
/** Represents the video size. */
|
||||||
public final class VideoSize implements Bundleable {
|
public final class VideoSize implements Bundleable {
|
||||||
@ -132,48 +126,32 @@ public final class VideoSize implements Bundleable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
// Bundleable implementation.
|
// Bundleable implementation.
|
||||||
@Documented
|
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
|
||||||
@Target(TYPE_USE)
|
|
||||||
@IntDef({
|
|
||||||
FIELD_WIDTH,
|
|
||||||
FIELD_HEIGHT,
|
|
||||||
FIELD_UNAPPLIED_ROTATION_DEGREES,
|
|
||||||
FIELD_PIXEL_WIDTH_HEIGHT_RATIO,
|
|
||||||
})
|
|
||||||
private @interface FieldNumber {}
|
|
||||||
|
|
||||||
private static final int FIELD_WIDTH = 0;
|
private static final String FIELD_WIDTH = Util.intToStringMaxRadix(0);
|
||||||
private static final int FIELD_HEIGHT = 1;
|
private static final String FIELD_HEIGHT = Util.intToStringMaxRadix(1);
|
||||||
private static final int FIELD_UNAPPLIED_ROTATION_DEGREES = 2;
|
private static final String FIELD_UNAPPLIED_ROTATION_DEGREES = Util.intToStringMaxRadix(2);
|
||||||
private static final int FIELD_PIXEL_WIDTH_HEIGHT_RATIO = 3;
|
private static final String FIELD_PIXEL_WIDTH_HEIGHT_RATIO = Util.intToStringMaxRadix(3);
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Override
|
@Override
|
||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putInt(keyForField(FIELD_WIDTH), width);
|
bundle.putInt(FIELD_WIDTH, width);
|
||||||
bundle.putInt(keyForField(FIELD_HEIGHT), height);
|
bundle.putInt(FIELD_HEIGHT, height);
|
||||||
bundle.putInt(keyForField(FIELD_UNAPPLIED_ROTATION_DEGREES), unappliedRotationDegrees);
|
bundle.putInt(FIELD_UNAPPLIED_ROTATION_DEGREES, unappliedRotationDegrees);
|
||||||
bundle.putFloat(keyForField(FIELD_PIXEL_WIDTH_HEIGHT_RATIO), pixelWidthHeightRatio);
|
bundle.putFloat(FIELD_PIXEL_WIDTH_HEIGHT_RATIO, pixelWidthHeightRatio);
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public static final Creator<VideoSize> CREATOR =
|
public static final Creator<VideoSize> CREATOR =
|
||||||
bundle -> {
|
bundle -> {
|
||||||
int width = bundle.getInt(keyForField(FIELD_WIDTH), DEFAULT_WIDTH);
|
int width = bundle.getInt(FIELD_WIDTH, DEFAULT_WIDTH);
|
||||||
int height = bundle.getInt(keyForField(FIELD_HEIGHT), DEFAULT_HEIGHT);
|
int height = bundle.getInt(FIELD_HEIGHT, DEFAULT_HEIGHT);
|
||||||
int unappliedRotationDegrees =
|
int unappliedRotationDegrees =
|
||||||
bundle.getInt(
|
bundle.getInt(FIELD_UNAPPLIED_ROTATION_DEGREES, DEFAULT_UNAPPLIED_ROTATION_DEGREES);
|
||||||
keyForField(FIELD_UNAPPLIED_ROTATION_DEGREES), DEFAULT_UNAPPLIED_ROTATION_DEGREES);
|
|
||||||
float pixelWidthHeightRatio =
|
float pixelWidthHeightRatio =
|
||||||
bundle.getFloat(
|
bundle.getFloat(FIELD_PIXEL_WIDTH_HEIGHT_RATIO, DEFAULT_PIXEL_WIDTH_HEIGHT_RATIO);
|
||||||
keyForField(FIELD_PIXEL_WIDTH_HEIGHT_RATIO), DEFAULT_PIXEL_WIDTH_HEIGHT_RATIO);
|
|
||||||
return new VideoSize(width, height, unappliedRotationDegrees, pixelWidthHeightRatio);
|
return new VideoSize(width, height, unappliedRotationDegrees, pixelWidthHeightRatio);
|
||||||
};
|
};
|
||||||
|
|
||||||
private static String keyForField(@FieldNumber int field) {
|
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -35,6 +35,7 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.media3.common.Bundleable;
|
import androidx.media3.common.Bundleable;
|
||||||
import androidx.media3.common.util.Assertions;
|
import androidx.media3.common.util.Assertions;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import androidx.media3.common.util.Util;
|
||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.Documented;
|
||||||
@ -977,69 +978,45 @@ public final class Cue implements Bundleable {
|
|||||||
|
|
||||||
// Bundleable implementation.
|
// Bundleable implementation.
|
||||||
|
|
||||||
@Documented
|
private static final String FIELD_TEXT = Util.intToStringMaxRadix(0);
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
private static final String FIELD_TEXT_ALIGNMENT = Util.intToStringMaxRadix(1);
|
||||||
@Target(TYPE_USE)
|
private static final String FIELD_MULTI_ROW_ALIGNMENT = Util.intToStringMaxRadix(2);
|
||||||
@IntDef({
|
private static final String FIELD_BITMAP = Util.intToStringMaxRadix(3);
|
||||||
FIELD_TEXT,
|
private static final String FIELD_LINE = Util.intToStringMaxRadix(4);
|
||||||
FIELD_TEXT_ALIGNMENT,
|
private static final String FIELD_LINE_TYPE = Util.intToStringMaxRadix(5);
|
||||||
FIELD_MULTI_ROW_ALIGNMENT,
|
private static final String FIELD_LINE_ANCHOR = Util.intToStringMaxRadix(6);
|
||||||
FIELD_BITMAP,
|
private static final String FIELD_POSITION = Util.intToStringMaxRadix(7);
|
||||||
FIELD_LINE,
|
private static final String FIELD_POSITION_ANCHOR = Util.intToStringMaxRadix(8);
|
||||||
FIELD_LINE_TYPE,
|
private static final String FIELD_TEXT_SIZE_TYPE = Util.intToStringMaxRadix(9);
|
||||||
FIELD_LINE_ANCHOR,
|
private static final String FIELD_TEXT_SIZE = Util.intToStringMaxRadix(10);
|
||||||
FIELD_POSITION,
|
private static final String FIELD_SIZE = Util.intToStringMaxRadix(11);
|
||||||
FIELD_POSITION_ANCHOR,
|
private static final String FIELD_BITMAP_HEIGHT = Util.intToStringMaxRadix(12);
|
||||||
FIELD_TEXT_SIZE_TYPE,
|
private static final String FIELD_WINDOW_COLOR = Util.intToStringMaxRadix(13);
|
||||||
FIELD_TEXT_SIZE,
|
private static final String FIELD_WINDOW_COLOR_SET = Util.intToStringMaxRadix(14);
|
||||||
FIELD_SIZE,
|
private static final String FIELD_VERTICAL_TYPE = Util.intToStringMaxRadix(15);
|
||||||
FIELD_BITMAP_HEIGHT,
|
private static final String FIELD_SHEAR_DEGREES = Util.intToStringMaxRadix(16);
|
||||||
FIELD_WINDOW_COLOR,
|
|
||||||
FIELD_WINDOW_COLOR_SET,
|
|
||||||
FIELD_VERTICAL_TYPE,
|
|
||||||
FIELD_SHEAR_DEGREES
|
|
||||||
})
|
|
||||||
private @interface FieldNumber {}
|
|
||||||
|
|
||||||
private static final int FIELD_TEXT = 0;
|
|
||||||
private static final int FIELD_TEXT_ALIGNMENT = 1;
|
|
||||||
private static final int FIELD_MULTI_ROW_ALIGNMENT = 2;
|
|
||||||
private static final int FIELD_BITMAP = 3;
|
|
||||||
private static final int FIELD_LINE = 4;
|
|
||||||
private static final int FIELD_LINE_TYPE = 5;
|
|
||||||
private static final int FIELD_LINE_ANCHOR = 6;
|
|
||||||
private static final int FIELD_POSITION = 7;
|
|
||||||
private static final int FIELD_POSITION_ANCHOR = 8;
|
|
||||||
private static final int FIELD_TEXT_SIZE_TYPE = 9;
|
|
||||||
private static final int FIELD_TEXT_SIZE = 10;
|
|
||||||
private static final int FIELD_SIZE = 11;
|
|
||||||
private static final int FIELD_BITMAP_HEIGHT = 12;
|
|
||||||
private static final int FIELD_WINDOW_COLOR = 13;
|
|
||||||
private static final int FIELD_WINDOW_COLOR_SET = 14;
|
|
||||||
private static final int FIELD_VERTICAL_TYPE = 15;
|
|
||||||
private static final int FIELD_SHEAR_DEGREES = 16;
|
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Override
|
@Override
|
||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putCharSequence(keyForField(FIELD_TEXT), text);
|
bundle.putCharSequence(FIELD_TEXT, text);
|
||||||
bundle.putSerializable(keyForField(FIELD_TEXT_ALIGNMENT), textAlignment);
|
bundle.putSerializable(FIELD_TEXT_ALIGNMENT, textAlignment);
|
||||||
bundle.putSerializable(keyForField(FIELD_MULTI_ROW_ALIGNMENT), multiRowAlignment);
|
bundle.putSerializable(FIELD_MULTI_ROW_ALIGNMENT, multiRowAlignment);
|
||||||
bundle.putParcelable(keyForField(FIELD_BITMAP), bitmap);
|
bundle.putParcelable(FIELD_BITMAP, bitmap);
|
||||||
bundle.putFloat(keyForField(FIELD_LINE), line);
|
bundle.putFloat(FIELD_LINE, line);
|
||||||
bundle.putInt(keyForField(FIELD_LINE_TYPE), lineType);
|
bundle.putInt(FIELD_LINE_TYPE, lineType);
|
||||||
bundle.putInt(keyForField(FIELD_LINE_ANCHOR), lineAnchor);
|
bundle.putInt(FIELD_LINE_ANCHOR, lineAnchor);
|
||||||
bundle.putFloat(keyForField(FIELD_POSITION), position);
|
bundle.putFloat(FIELD_POSITION, position);
|
||||||
bundle.putInt(keyForField(FIELD_POSITION_ANCHOR), positionAnchor);
|
bundle.putInt(FIELD_POSITION_ANCHOR, positionAnchor);
|
||||||
bundle.putInt(keyForField(FIELD_TEXT_SIZE_TYPE), textSizeType);
|
bundle.putInt(FIELD_TEXT_SIZE_TYPE, textSizeType);
|
||||||
bundle.putFloat(keyForField(FIELD_TEXT_SIZE), textSize);
|
bundle.putFloat(FIELD_TEXT_SIZE, textSize);
|
||||||
bundle.putFloat(keyForField(FIELD_SIZE), size);
|
bundle.putFloat(FIELD_SIZE, size);
|
||||||
bundle.putFloat(keyForField(FIELD_BITMAP_HEIGHT), bitmapHeight);
|
bundle.putFloat(FIELD_BITMAP_HEIGHT, bitmapHeight);
|
||||||
bundle.putBoolean(keyForField(FIELD_WINDOW_COLOR_SET), windowColorSet);
|
bundle.putBoolean(FIELD_WINDOW_COLOR_SET, windowColorSet);
|
||||||
bundle.putInt(keyForField(FIELD_WINDOW_COLOR), windowColor);
|
bundle.putInt(FIELD_WINDOW_COLOR, windowColor);
|
||||||
bundle.putInt(keyForField(FIELD_VERTICAL_TYPE), verticalType);
|
bundle.putInt(FIELD_VERTICAL_TYPE, verticalType);
|
||||||
bundle.putFloat(keyForField(FIELD_SHEAR_DEGREES), shearDegrees);
|
bundle.putFloat(FIELD_SHEAR_DEGREES, shearDegrees);
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1047,67 +1024,56 @@ public final class Cue implements Bundleable {
|
|||||||
|
|
||||||
private static final Cue fromBundle(Bundle bundle) {
|
private static final Cue fromBundle(Bundle bundle) {
|
||||||
Builder builder = new Builder();
|
Builder builder = new Builder();
|
||||||
@Nullable CharSequence text = bundle.getCharSequence(keyForField(FIELD_TEXT));
|
@Nullable CharSequence text = bundle.getCharSequence(FIELD_TEXT);
|
||||||
if (text != null) {
|
if (text != null) {
|
||||||
builder.setText(text);
|
builder.setText(text);
|
||||||
}
|
}
|
||||||
@Nullable
|
@Nullable Alignment textAlignment = (Alignment) bundle.getSerializable(FIELD_TEXT_ALIGNMENT);
|
||||||
Alignment textAlignment = (Alignment) bundle.getSerializable(keyForField(FIELD_TEXT_ALIGNMENT));
|
|
||||||
if (textAlignment != null) {
|
if (textAlignment != null) {
|
||||||
builder.setTextAlignment(textAlignment);
|
builder.setTextAlignment(textAlignment);
|
||||||
}
|
}
|
||||||
@Nullable
|
@Nullable
|
||||||
Alignment multiRowAlignment =
|
Alignment multiRowAlignment = (Alignment) bundle.getSerializable(FIELD_MULTI_ROW_ALIGNMENT);
|
||||||
(Alignment) bundle.getSerializable(keyForField(FIELD_MULTI_ROW_ALIGNMENT));
|
|
||||||
if (multiRowAlignment != null) {
|
if (multiRowAlignment != null) {
|
||||||
builder.setMultiRowAlignment(multiRowAlignment);
|
builder.setMultiRowAlignment(multiRowAlignment);
|
||||||
}
|
}
|
||||||
@Nullable Bitmap bitmap = bundle.getParcelable(keyForField(FIELD_BITMAP));
|
@Nullable Bitmap bitmap = bundle.getParcelable(FIELD_BITMAP);
|
||||||
if (bitmap != null) {
|
if (bitmap != null) {
|
||||||
builder.setBitmap(bitmap);
|
builder.setBitmap(bitmap);
|
||||||
}
|
}
|
||||||
if (bundle.containsKey(keyForField(FIELD_LINE))
|
if (bundle.containsKey(FIELD_LINE) && bundle.containsKey(FIELD_LINE_TYPE)) {
|
||||||
&& bundle.containsKey(keyForField(FIELD_LINE_TYPE))) {
|
builder.setLine(bundle.getFloat(FIELD_LINE), bundle.getInt(FIELD_LINE_TYPE));
|
||||||
builder.setLine(
|
|
||||||
bundle.getFloat(keyForField(FIELD_LINE)), bundle.getInt(keyForField(FIELD_LINE_TYPE)));
|
|
||||||
}
|
}
|
||||||
if (bundle.containsKey(keyForField(FIELD_LINE_ANCHOR))) {
|
if (bundle.containsKey(FIELD_LINE_ANCHOR)) {
|
||||||
builder.setLineAnchor(bundle.getInt(keyForField(FIELD_LINE_ANCHOR)));
|
builder.setLineAnchor(bundle.getInt(FIELD_LINE_ANCHOR));
|
||||||
}
|
}
|
||||||
if (bundle.containsKey(keyForField(FIELD_POSITION))) {
|
if (bundle.containsKey(FIELD_POSITION)) {
|
||||||
builder.setPosition(bundle.getFloat(keyForField(FIELD_POSITION)));
|
builder.setPosition(bundle.getFloat(FIELD_POSITION));
|
||||||
}
|
}
|
||||||
if (bundle.containsKey(keyForField(FIELD_POSITION_ANCHOR))) {
|
if (bundle.containsKey(FIELD_POSITION_ANCHOR)) {
|
||||||
builder.setPositionAnchor(bundle.getInt(keyForField(FIELD_POSITION_ANCHOR)));
|
builder.setPositionAnchor(bundle.getInt(FIELD_POSITION_ANCHOR));
|
||||||
}
|
}
|
||||||
if (bundle.containsKey(keyForField(FIELD_TEXT_SIZE))
|
if (bundle.containsKey(FIELD_TEXT_SIZE) && bundle.containsKey(FIELD_TEXT_SIZE_TYPE)) {
|
||||||
&& bundle.containsKey(keyForField(FIELD_TEXT_SIZE_TYPE))) {
|
builder.setTextSize(bundle.getFloat(FIELD_TEXT_SIZE), bundle.getInt(FIELD_TEXT_SIZE_TYPE));
|
||||||
builder.setTextSize(
|
|
||||||
bundle.getFloat(keyForField(FIELD_TEXT_SIZE)),
|
|
||||||
bundle.getInt(keyForField(FIELD_TEXT_SIZE_TYPE)));
|
|
||||||
}
|
}
|
||||||
if (bundle.containsKey(keyForField(FIELD_SIZE))) {
|
if (bundle.containsKey(FIELD_SIZE)) {
|
||||||
builder.setSize(bundle.getFloat(keyForField(FIELD_SIZE)));
|
builder.setSize(bundle.getFloat(FIELD_SIZE));
|
||||||
}
|
}
|
||||||
if (bundle.containsKey(keyForField(FIELD_BITMAP_HEIGHT))) {
|
if (bundle.containsKey(FIELD_BITMAP_HEIGHT)) {
|
||||||
builder.setBitmapHeight(bundle.getFloat(keyForField(FIELD_BITMAP_HEIGHT)));
|
builder.setBitmapHeight(bundle.getFloat(FIELD_BITMAP_HEIGHT));
|
||||||
}
|
}
|
||||||
if (bundle.containsKey(keyForField(FIELD_WINDOW_COLOR))) {
|
if (bundle.containsKey(FIELD_WINDOW_COLOR)) {
|
||||||
builder.setWindowColor(bundle.getInt(keyForField(FIELD_WINDOW_COLOR)));
|
builder.setWindowColor(bundle.getInt(FIELD_WINDOW_COLOR));
|
||||||
}
|
}
|
||||||
if (!bundle.getBoolean(keyForField(FIELD_WINDOW_COLOR_SET), /* defaultValue= */ false)) {
|
if (!bundle.getBoolean(FIELD_WINDOW_COLOR_SET, /* defaultValue= */ false)) {
|
||||||
builder.clearWindowColor();
|
builder.clearWindowColor();
|
||||||
}
|
}
|
||||||
if (bundle.containsKey(keyForField(FIELD_VERTICAL_TYPE))) {
|
if (bundle.containsKey(FIELD_VERTICAL_TYPE)) {
|
||||||
builder.setVerticalType(bundle.getInt(keyForField(FIELD_VERTICAL_TYPE)));
|
builder.setVerticalType(bundle.getInt(FIELD_VERTICAL_TYPE));
|
||||||
}
|
}
|
||||||
if (bundle.containsKey(keyForField(FIELD_SHEAR_DEGREES))) {
|
if (bundle.containsKey(FIELD_SHEAR_DEGREES)) {
|
||||||
builder.setShearDegrees(bundle.getFloat(keyForField(FIELD_SHEAR_DEGREES)));
|
builder.setShearDegrees(bundle.getFloat(FIELD_SHEAR_DEGREES));
|
||||||
}
|
}
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String keyForField(@FieldNumber int field) {
|
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -15,21 +15,15 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.common.text;
|
package androidx.media3.common.text;
|
||||||
|
|
||||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
|
||||||
|
|
||||||
import android.graphics.Bitmap;
|
import android.graphics.Bitmap;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.IntDef;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.Bundleable;
|
import androidx.media3.common.Bundleable;
|
||||||
import androidx.media3.common.Timeline;
|
import androidx.media3.common.Timeline;
|
||||||
import androidx.media3.common.util.BundleableUtil;
|
import androidx.media3.common.util.BundleableUtil;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import androidx.media3.common.util.Util;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import java.lang.annotation.Documented;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -66,41 +60,31 @@ public final class CueGroup implements Bundleable {
|
|||||||
|
|
||||||
// Bundleable implementation.
|
// Bundleable implementation.
|
||||||
|
|
||||||
@Documented
|
private static final String FIELD_CUES = Util.intToStringMaxRadix(0);
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
private static final String FIELD_PRESENTATION_TIME_US = Util.intToStringMaxRadix(1);
|
||||||
@Target(TYPE_USE)
|
|
||||||
@IntDef({FIELD_CUES, FIELD_PRESENTATION_TIME_US})
|
|
||||||
private @interface FieldNumber {}
|
|
||||||
|
|
||||||
private static final int FIELD_CUES = 0;
|
|
||||||
private static final int FIELD_PRESENTATION_TIME_US = 1;
|
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Override
|
@Override
|
||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putParcelableArrayList(
|
bundle.putParcelableArrayList(
|
||||||
keyForField(FIELD_CUES), BundleableUtil.toBundleArrayList(filterOutBitmapCues(cues)));
|
FIELD_CUES, BundleableUtil.toBundleArrayList(filterOutBitmapCues(cues)));
|
||||||
bundle.putLong(keyForField(FIELD_PRESENTATION_TIME_US), presentationTimeUs);
|
bundle.putLong(FIELD_PRESENTATION_TIME_US, presentationTimeUs);
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@UnstableApi public static final Creator<CueGroup> CREATOR = CueGroup::fromBundle;
|
@UnstableApi public static final Creator<CueGroup> CREATOR = CueGroup::fromBundle;
|
||||||
|
|
||||||
private static final CueGroup fromBundle(Bundle bundle) {
|
private static final CueGroup fromBundle(Bundle bundle) {
|
||||||
@Nullable ArrayList<Bundle> cueBundles = bundle.getParcelableArrayList(keyForField(FIELD_CUES));
|
@Nullable ArrayList<Bundle> cueBundles = bundle.getParcelableArrayList(FIELD_CUES);
|
||||||
List<Cue> cues =
|
List<Cue> cues =
|
||||||
cueBundles == null
|
cueBundles == null
|
||||||
? ImmutableList.of()
|
? ImmutableList.of()
|
||||||
: BundleableUtil.fromBundleList(Cue.CREATOR, cueBundles);
|
: BundleableUtil.fromBundleList(Cue.CREATOR, cueBundles);
|
||||||
long presentationTimeUs = bundle.getLong(keyForField(FIELD_PRESENTATION_TIME_US));
|
long presentationTimeUs = bundle.getLong(FIELD_PRESENTATION_TIME_US);
|
||||||
return new CueGroup(cues, presentationTimeUs);
|
return new CueGroup(cues, presentationTimeUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String keyForField(@FieldNumber int field) {
|
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Filters out {@link Cue} objects containing {@link Bitmap}. It is used when transferring cues
|
* Filters out {@link Cue} objects containing {@link Bitmap}. It is used when transferring cues
|
||||||
* between processes to prevent transferring too much data.
|
* between processes to prevent transferring too much data.
|
||||||
|
@ -15,9 +15,12 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.common.util;
|
package androidx.media3.common.util;
|
||||||
|
|
||||||
|
import static androidx.media3.common.util.Assertions.checkState;
|
||||||
|
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.os.Message;
|
import android.os.Message;
|
||||||
import androidx.annotation.CheckResult;
|
import androidx.annotation.CheckResult;
|
||||||
|
import androidx.annotation.GuardedBy;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.FlagSet;
|
import androidx.media3.common.FlagSet;
|
||||||
@ -34,6 +37,9 @@ import org.checkerframework.checker.nullness.qual.NonNull;
|
|||||||
* <p>Events are also guaranteed to be only sent to the listeners registered at the time the event
|
* <p>Events are also guaranteed to be only sent to the listeners registered at the time the event
|
||||||
* was enqueued and haven't been removed since.
|
* was enqueued and haven't been removed since.
|
||||||
*
|
*
|
||||||
|
* <p>All methods must be called on the {@link Looper} passed to the constructor unless indicated
|
||||||
|
* otherwise.
|
||||||
|
*
|
||||||
* @param <T> The listener type.
|
* @param <T> The listener type.
|
||||||
*/
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@ -76,14 +82,18 @@ public final class ListenerSet<T extends @NonNull Object> {
|
|||||||
private final CopyOnWriteArraySet<ListenerHolder<T>> listeners;
|
private final CopyOnWriteArraySet<ListenerHolder<T>> listeners;
|
||||||
private final ArrayDeque<Runnable> flushingEvents;
|
private final ArrayDeque<Runnable> flushingEvents;
|
||||||
private final ArrayDeque<Runnable> queuedEvents;
|
private final ArrayDeque<Runnable> queuedEvents;
|
||||||
|
private final Object releasedLock;
|
||||||
|
|
||||||
|
@GuardedBy("releasedLock")
|
||||||
private boolean released;
|
private boolean released;
|
||||||
|
|
||||||
|
private boolean throwsWhenUsingWrongThread;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new listener set.
|
* Creates a new listener set.
|
||||||
*
|
*
|
||||||
* @param looper A {@link Looper} used to call listeners on. The same {@link Looper} must be used
|
* @param looper A {@link Looper} used to call listeners on. The same {@link Looper} must be used
|
||||||
* to call all other methods of this class.
|
* to call all other methods of this class unless indicated otherwise.
|
||||||
* @param clock A {@link Clock}.
|
* @param clock A {@link Clock}.
|
||||||
* @param iterationFinishedEvent An {@link IterationFinishedEvent} sent when all other events sent
|
* @param iterationFinishedEvent An {@link IterationFinishedEvent} sent when all other events sent
|
||||||
* during one {@link Looper} message queue iteration were handled by the listeners.
|
* during one {@link Looper} message queue iteration were handled by the listeners.
|
||||||
@ -100,17 +110,21 @@ public final class ListenerSet<T extends @NonNull Object> {
|
|||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
this.listeners = listeners;
|
this.listeners = listeners;
|
||||||
this.iterationFinishedEvent = iterationFinishedEvent;
|
this.iterationFinishedEvent = iterationFinishedEvent;
|
||||||
|
releasedLock = new Object();
|
||||||
flushingEvents = new ArrayDeque<>();
|
flushingEvents = new ArrayDeque<>();
|
||||||
queuedEvents = new ArrayDeque<>();
|
queuedEvents = new ArrayDeque<>();
|
||||||
// It's safe to use "this" because we don't send a message before exiting the constructor.
|
// It's safe to use "this" because we don't send a message before exiting the constructor.
|
||||||
@SuppressWarnings("nullness:methodref.receiver.bound")
|
@SuppressWarnings("nullness:methodref.receiver.bound")
|
||||||
HandlerWrapper handler = clock.createHandler(looper, this::handleMessage);
|
HandlerWrapper handler = clock.createHandler(looper, this::handleMessage);
|
||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
|
throwsWhenUsingWrongThread = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copies the listener set.
|
* Copies the listener set.
|
||||||
*
|
*
|
||||||
|
* <p>This method can be called from any thread.
|
||||||
|
*
|
||||||
* @param looper The new {@link Looper} for the copied listener set.
|
* @param looper The new {@link Looper} for the copied listener set.
|
||||||
* @param iterationFinishedEvent The new {@link IterationFinishedEvent} sent when all other events
|
* @param iterationFinishedEvent The new {@link IterationFinishedEvent} sent when all other events
|
||||||
* sent during one {@link Looper} message queue iteration were handled by the listeners.
|
* sent during one {@link Looper} message queue iteration were handled by the listeners.
|
||||||
@ -124,6 +138,8 @@ public final class ListenerSet<T extends @NonNull Object> {
|
|||||||
/**
|
/**
|
||||||
* Copies the listener set.
|
* Copies the listener set.
|
||||||
*
|
*
|
||||||
|
* <p>This method can be called from any thread.
|
||||||
|
*
|
||||||
* @param looper The new {@link Looper} for the copied listener set.
|
* @param looper The new {@link Looper} for the copied listener set.
|
||||||
* @param clock The new {@link Clock} for the copied listener set.
|
* @param clock The new {@link Clock} for the copied listener set.
|
||||||
* @param iterationFinishedEvent The new {@link IterationFinishedEvent} sent when all other events
|
* @param iterationFinishedEvent The new {@link IterationFinishedEvent} sent when all other events
|
||||||
@ -141,15 +157,19 @@ public final class ListenerSet<T extends @NonNull Object> {
|
|||||||
*
|
*
|
||||||
* <p>If a listener is already present, it will not be added again.
|
* <p>If a listener is already present, it will not be added again.
|
||||||
*
|
*
|
||||||
|
* <p>This method can be called from any thread.
|
||||||
|
*
|
||||||
* @param listener The listener to be added.
|
* @param listener The listener to be added.
|
||||||
*/
|
*/
|
||||||
public void add(T listener) {
|
public void add(T listener) {
|
||||||
|
Assertions.checkNotNull(listener);
|
||||||
|
synchronized (releasedLock) {
|
||||||
if (released) {
|
if (released) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
Assertions.checkNotNull(listener);
|
|
||||||
listeners.add(new ListenerHolder<>(listener));
|
listeners.add(new ListenerHolder<>(listener));
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes a listener from the set.
|
* Removes a listener from the set.
|
||||||
@ -159,6 +179,7 @@ public final class ListenerSet<T extends @NonNull Object> {
|
|||||||
* @param listener The listener to be removed.
|
* @param listener The listener to be removed.
|
||||||
*/
|
*/
|
||||||
public void remove(T listener) {
|
public void remove(T listener) {
|
||||||
|
verifyCurrentThread();
|
||||||
for (ListenerHolder<T> listenerHolder : listeners) {
|
for (ListenerHolder<T> listenerHolder : listeners) {
|
||||||
if (listenerHolder.listener.equals(listener)) {
|
if (listenerHolder.listener.equals(listener)) {
|
||||||
listenerHolder.release(iterationFinishedEvent);
|
listenerHolder.release(iterationFinishedEvent);
|
||||||
@ -169,11 +190,13 @@ public final class ListenerSet<T extends @NonNull Object> {
|
|||||||
|
|
||||||
/** Removes all listeners from the set. */
|
/** Removes all listeners from the set. */
|
||||||
public void clear() {
|
public void clear() {
|
||||||
|
verifyCurrentThread();
|
||||||
listeners.clear();
|
listeners.clear();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the number of added listeners. */
|
/** Returns the number of added listeners. */
|
||||||
public int size() {
|
public int size() {
|
||||||
|
verifyCurrentThread();
|
||||||
return listeners.size();
|
return listeners.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -185,6 +208,7 @@ public final class ListenerSet<T extends @NonNull Object> {
|
|||||||
* @param event The event.
|
* @param event The event.
|
||||||
*/
|
*/
|
||||||
public void queueEvent(int eventFlag, Event<T> event) {
|
public void queueEvent(int eventFlag, Event<T> event) {
|
||||||
|
verifyCurrentThread();
|
||||||
CopyOnWriteArraySet<ListenerHolder<T>> listenerSnapshot = new CopyOnWriteArraySet<>(listeners);
|
CopyOnWriteArraySet<ListenerHolder<T>> listenerSnapshot = new CopyOnWriteArraySet<>(listeners);
|
||||||
queuedEvents.add(
|
queuedEvents.add(
|
||||||
() -> {
|
() -> {
|
||||||
@ -196,6 +220,7 @@ public final class ListenerSet<T extends @NonNull Object> {
|
|||||||
|
|
||||||
/** Notifies listeners of events previously enqueued with {@link #queueEvent(int, Event)}. */
|
/** Notifies listeners of events previously enqueued with {@link #queueEvent(int, Event)}. */
|
||||||
public void flushEvents() {
|
public void flushEvents() {
|
||||||
|
verifyCurrentThread();
|
||||||
if (queuedEvents.isEmpty()) {
|
if (queuedEvents.isEmpty()) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -234,11 +259,27 @@ public final class ListenerSet<T extends @NonNull Object> {
|
|||||||
* <p>This will ensure no events are sent to any listener after this method has been called.
|
* <p>This will ensure no events are sent to any listener after this method has been called.
|
||||||
*/
|
*/
|
||||||
public void release() {
|
public void release() {
|
||||||
|
verifyCurrentThread();
|
||||||
|
synchronized (releasedLock) {
|
||||||
|
released = true;
|
||||||
|
}
|
||||||
for (ListenerHolder<T> listenerHolder : listeners) {
|
for (ListenerHolder<T> listenerHolder : listeners) {
|
||||||
listenerHolder.release(iterationFinishedEvent);
|
listenerHolder.release(iterationFinishedEvent);
|
||||||
}
|
}
|
||||||
listeners.clear();
|
listeners.clear();
|
||||||
released = true;
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether methods throw when using the wrong thread.
|
||||||
|
*
|
||||||
|
* <p>Do not use this method unless to support legacy use cases.
|
||||||
|
*
|
||||||
|
* @param throwsWhenUsingWrongThread Whether to throw when using the wrong thread.
|
||||||
|
* @deprecated Do not use this method and ensure all calls are made from the correct thread.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
public void setThrowsWhenUsingWrongThread(boolean throwsWhenUsingWrongThread) {
|
||||||
|
this.throwsWhenUsingWrongThread = throwsWhenUsingWrongThread;
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean handleMessage(Message message) {
|
private boolean handleMessage(Message message) {
|
||||||
@ -254,6 +295,13 @@ public final class ListenerSet<T extends @NonNull Object> {
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void verifyCurrentThread() {
|
||||||
|
if (!throwsWhenUsingWrongThread) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
checkState(Thread.currentThread() == handler.getLooper().getThread());
|
||||||
|
}
|
||||||
|
|
||||||
private static final class ListenerHolder<T extends @NonNull Object> {
|
private static final class ListenerHolder<T extends @NonNull Object> {
|
||||||
|
|
||||||
public final T listener;
|
public final T listener;
|
||||||
|
@ -17,6 +17,9 @@ package androidx.media3.common.util;
|
|||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.common.base.Charsets;
|
import com.google.common.base.Charsets;
|
||||||
|
import com.google.common.collect.ImmutableSet;
|
||||||
|
import com.google.common.primitives.Chars;
|
||||||
|
import com.google.common.primitives.UnsignedBytes;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.charset.Charset;
|
import java.nio.charset.Charset;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@ -28,6 +31,12 @@ import java.util.Arrays;
|
|||||||
@UnstableApi
|
@UnstableApi
|
||||||
public final class ParsableByteArray {
|
public final class ParsableByteArray {
|
||||||
|
|
||||||
|
private static final char[] CR_AND_LF = {'\r', '\n'};
|
||||||
|
private static final char[] LF = {'\n'};
|
||||||
|
private static final ImmutableSet<Charset> SUPPORTED_CHARSETS_FOR_READLINE =
|
||||||
|
ImmutableSet.of(
|
||||||
|
Charsets.US_ASCII, Charsets.UTF_8, Charsets.UTF_16, Charsets.UTF_16BE, Charsets.UTF_16LE);
|
||||||
|
|
||||||
private byte[] data;
|
private byte[] data;
|
||||||
private int position;
|
private int position;
|
||||||
// TODO(internal b/147657250): Enforce this limit on all read methods.
|
// TODO(internal b/147657250): Enforce this limit on all read methods.
|
||||||
@ -490,45 +499,47 @@ public final class ParsableByteArray {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reads a line of text.
|
* Reads a line of text in UTF-8.
|
||||||
*
|
*
|
||||||
* <p>A line is considered to be terminated by any one of a carriage return ('\r'), a line feed
|
* <p>Equivalent to passing {@link Charsets#UTF_8} to {@link #readLine(Charset)}.
|
||||||
* ('\n'), or a carriage return followed immediately by a line feed ('\r\n'). The UTF-8 charset is
|
|
||||||
* used. This method discards leading UTF-8 byte order marks, if present.
|
|
||||||
*
|
|
||||||
* @return The line not including any line-termination characters, or null if the end of the data
|
|
||||||
* has already been reached.
|
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public String readLine() {
|
public String readLine() {
|
||||||
|
return readLine(Charsets.UTF_8);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a line of text in {@code charset}.
|
||||||
|
*
|
||||||
|
* <p>A line is considered to be terminated by any one of a carriage return ('\r'), a line feed
|
||||||
|
* ('\n'), or a carriage return followed immediately by a line feed ('\r\n'). This method discards
|
||||||
|
* leading UTF byte order marks (BOM), if present.
|
||||||
|
*
|
||||||
|
* <p>The {@linkplain #getPosition() position} is advanced to start of the next line (i.e. any
|
||||||
|
* line terminators are skipped).
|
||||||
|
*
|
||||||
|
* @param charset The charset used to interpret the bytes as a {@link String}.
|
||||||
|
* @return The line not including any line-termination characters, or null if the end of the data
|
||||||
|
* has already been reached.
|
||||||
|
* @throws IllegalArgumentException if charset is not supported. Only US_ASCII, UTF-8, UTF-16,
|
||||||
|
* UTF-16BE, and UTF-16LE are supported.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public String readLine(Charset charset) {
|
||||||
|
Assertions.checkArgument(
|
||||||
|
SUPPORTED_CHARSETS_FOR_READLINE.contains(charset), "Unsupported charset: " + charset);
|
||||||
if (bytesLeft() == 0) {
|
if (bytesLeft() == 0) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
int lineLimit = position;
|
if (!charset.equals(Charsets.US_ASCII)) {
|
||||||
while (lineLimit < limit && !Util.isLinebreak(data[lineLimit])) {
|
readUtfCharsetFromBom(); // Skip BOM if present
|
||||||
lineLimit++;
|
|
||||||
}
|
}
|
||||||
if (lineLimit - position >= 3
|
int lineLimit = findNextLineTerminator(charset);
|
||||||
&& data[position] == (byte) 0xEF
|
String line = readString(lineLimit - position, charset);
|
||||||
&& data[position + 1] == (byte) 0xBB
|
|
||||||
&& data[position + 2] == (byte) 0xBF) {
|
|
||||||
// There's a UTF-8 byte order mark at the start of the line. Discard it.
|
|
||||||
position += 3;
|
|
||||||
}
|
|
||||||
String line = Util.fromUtf8Bytes(data, position, lineLimit - position);
|
|
||||||
position = lineLimit;
|
|
||||||
if (position == limit) {
|
if (position == limit) {
|
||||||
return line;
|
return line;
|
||||||
}
|
}
|
||||||
if (data[position] == '\r') {
|
skipLineTerminator(charset);
|
||||||
position++;
|
|
||||||
if (position == limit) {
|
|
||||||
return line;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
if (data[position] == '\n') {
|
|
||||||
position++;
|
|
||||||
}
|
|
||||||
return line;
|
return line;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -566,4 +577,99 @@ public final class ParsableByteArray {
|
|||||||
position += length;
|
position += length;
|
||||||
return value;
|
return value;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Reads a UTF byte order mark (BOM) and returns the UTF {@link Charset} it represents. Returns
|
||||||
|
* {@code null} without advancing {@link #getPosition() position} if no BOM is found.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
public Charset readUtfCharsetFromBom() {
|
||||||
|
if (bytesLeft() >= 3
|
||||||
|
&& data[position] == (byte) 0xEF
|
||||||
|
&& data[position + 1] == (byte) 0xBB
|
||||||
|
&& data[position + 2] == (byte) 0xBF) {
|
||||||
|
position += 3;
|
||||||
|
return Charsets.UTF_8;
|
||||||
|
} else if (bytesLeft() >= 2) {
|
||||||
|
if (data[position] == (byte) 0xFE && data[position + 1] == (byte) 0xFF) {
|
||||||
|
position += 2;
|
||||||
|
return Charsets.UTF_16BE;
|
||||||
|
} else if (data[position] == (byte) 0xFF && data[position + 1] == (byte) 0xFE) {
|
||||||
|
position += 2;
|
||||||
|
return Charsets.UTF_16LE;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the index of the next occurrence of '\n' or '\r', or {@link #limit} if none is found.
|
||||||
|
*/
|
||||||
|
private int findNextLineTerminator(Charset charset) {
|
||||||
|
int stride;
|
||||||
|
if (charset.equals(Charsets.UTF_8) || charset.equals(Charsets.US_ASCII)) {
|
||||||
|
stride = 1;
|
||||||
|
} else if (charset.equals(Charsets.UTF_16)
|
||||||
|
|| charset.equals(Charsets.UTF_16LE)
|
||||||
|
|| charset.equals(Charsets.UTF_16BE)) {
|
||||||
|
stride = 2;
|
||||||
|
} else {
|
||||||
|
throw new IllegalArgumentException("Unsupported charset: " + charset);
|
||||||
|
}
|
||||||
|
for (int i = position; i < limit - (stride - 1); i += stride) {
|
||||||
|
if ((charset.equals(Charsets.UTF_8) || charset.equals(Charsets.US_ASCII))
|
||||||
|
&& Util.isLinebreak(data[i])) {
|
||||||
|
return i;
|
||||||
|
} else if ((charset.equals(Charsets.UTF_16) || charset.equals(Charsets.UTF_16BE))
|
||||||
|
&& data[i] == 0x00
|
||||||
|
&& Util.isLinebreak(data[i + 1])) {
|
||||||
|
return i;
|
||||||
|
} else if (charset.equals(Charsets.UTF_16LE)
|
||||||
|
&& data[i + 1] == 0x00
|
||||||
|
&& Util.isLinebreak(data[i])) {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return limit;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void skipLineTerminator(Charset charset) {
|
||||||
|
if (readCharacterIfInList(charset, CR_AND_LF) == '\r') {
|
||||||
|
readCharacterIfInList(charset, LF);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Peeks at the character at {@link #position} (as decoded by {@code charset}), returns it and
|
||||||
|
* advances {@link #position} past it if it's in {@code chars}, otherwise returns {@code 0}
|
||||||
|
* without advancing {@link #position}. Returns {@code 0} if {@link #bytesLeft()} doesn't allow
|
||||||
|
* reading a whole character in {@code charset}.
|
||||||
|
*
|
||||||
|
* <p>Only supports characters in {@code chars} that occupy a single code unit (i.e. one byte for
|
||||||
|
* UTF-8 and two bytes for UTF-16).
|
||||||
|
*/
|
||||||
|
private char readCharacterIfInList(Charset charset, char[] chars) {
|
||||||
|
char character;
|
||||||
|
int characterSize;
|
||||||
|
if ((charset.equals(Charsets.UTF_8) || charset.equals(Charsets.US_ASCII)) && bytesLeft() >= 1) {
|
||||||
|
character = Chars.checkedCast(UnsignedBytes.toInt(data[position]));
|
||||||
|
characterSize = 1;
|
||||||
|
} else if ((charset.equals(Charsets.UTF_16) || charset.equals(Charsets.UTF_16BE))
|
||||||
|
&& bytesLeft() >= 2) {
|
||||||
|
character = Chars.fromBytes(data[position], data[position + 1]);
|
||||||
|
characterSize = 2;
|
||||||
|
} else if (charset.equals(Charsets.UTF_16LE) && bytesLeft() >= 2) {
|
||||||
|
character = Chars.fromBytes(data[position + 1], data[position]);
|
||||||
|
characterSize = 2;
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (Chars.contains(chars, character)) {
|
||||||
|
position += characterSize;
|
||||||
|
return Chars.checkedCast(character);
|
||||||
|
} else {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -29,6 +29,9 @@ public final class Size {
|
|||||||
public static final Size UNKNOWN =
|
public static final Size UNKNOWN =
|
||||||
new Size(/* width= */ C.LENGTH_UNSET, /* height= */ C.LENGTH_UNSET);
|
new Size(/* width= */ C.LENGTH_UNSET, /* height= */ C.LENGTH_UNSET);
|
||||||
|
|
||||||
|
/* A static instance to represent a size of zero height and width. */
|
||||||
|
public static final Size ZERO = new Size(/* width= */ 0, /* height= */ 0);
|
||||||
|
|
||||||
private final int width;
|
private final int width;
|
||||||
private final int height;
|
private final int height;
|
||||||
|
|
||||||
|
@ -47,6 +47,7 @@ import android.content.res.Resources;
|
|||||||
import android.database.DatabaseUtils;
|
import android.database.DatabaseUtils;
|
||||||
import android.database.sqlite.SQLiteDatabase;
|
import android.database.sqlite.SQLiteDatabase;
|
||||||
import android.graphics.Point;
|
import android.graphics.Point;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
import android.hardware.display.DisplayManager;
|
import android.hardware.display.DisplayManager;
|
||||||
import android.media.AudioFormat;
|
import android.media.AudioFormat;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
@ -66,6 +67,8 @@ import android.util.SparseLongArray;
|
|||||||
import android.view.Display;
|
import android.view.Display;
|
||||||
import android.view.SurfaceView;
|
import android.view.SurfaceView;
|
||||||
import android.view.WindowManager;
|
import android.view.WindowManager;
|
||||||
|
import androidx.annotation.DoNotInline;
|
||||||
|
import androidx.annotation.DrawableRes;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
@ -2864,6 +2867,33 @@ public final class Util {
|
|||||||
return sum;
|
return sum;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link Drawable} for the given resource or throws a {@link
|
||||||
|
* Resources.NotFoundException} if not found.
|
||||||
|
*
|
||||||
|
* @param context The context to get the theme from starting with API 21.
|
||||||
|
* @param resources The resources to load the drawable from.
|
||||||
|
* @param drawableRes The drawable resource int.
|
||||||
|
* @return The loaded {@link Drawable}.
|
||||||
|
*/
|
||||||
|
@UnstableApi
|
||||||
|
public static Drawable getDrawable(
|
||||||
|
Context context, Resources resources, @DrawableRes int drawableRes) {
|
||||||
|
return SDK_INT >= 21
|
||||||
|
? Api21.getDrawable(context, resources, drawableRes)
|
||||||
|
: resources.getDrawable(drawableRes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a string representation of the integer using radix value {@link Character#MAX_RADIX}.
|
||||||
|
*
|
||||||
|
* @param i An integer to be converted to String.
|
||||||
|
*/
|
||||||
|
@UnstableApi
|
||||||
|
public static String intToStringMaxRadix(int i) {
|
||||||
|
return Integer.toString(i, Character.MAX_RADIX);
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static String getSystemProperty(String name) {
|
private static String getSystemProperty(String name) {
|
||||||
try {
|
try {
|
||||||
@ -3100,4 +3130,12 @@ public final class Util {
|
|||||||
0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4,
|
0xDE, 0xD9, 0xD0, 0xD7, 0xC2, 0xC5, 0xCC, 0xCB, 0xE6, 0xE1, 0xE8, 0xEF, 0xFA, 0xFD, 0xF4,
|
||||||
0xF3
|
0xF3
|
||||||
};
|
};
|
||||||
|
|
||||||
|
@RequiresApi(21)
|
||||||
|
private static final class Api21 {
|
||||||
|
@DoNotInline
|
||||||
|
public static Drawable getDrawable(Context context, Resources resources, @DrawableRes int res) {
|
||||||
|
return resources.getDrawable(res, context.getTheme());
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ import static com.google.common.truth.Truth.assertThat;
|
|||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import org.junit.Assert;
|
import org.junit.Assert;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@ -402,7 +403,43 @@ public class AdPlaybackStateTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void roundTripViaBundle_yieldsEqualFieldsExceptAdsId() {
|
public void adPlaybackStateWithNoAds_checkValues() {
|
||||||
|
AdPlaybackState adPlaybackStateWithNoAds = AdPlaybackState.NONE;
|
||||||
|
|
||||||
|
// Please refrain from altering these values since doing so would cause issues with backwards
|
||||||
|
// compatibility.
|
||||||
|
assertThat(adPlaybackStateWithNoAds.adsId).isNull();
|
||||||
|
assertThat(adPlaybackStateWithNoAds.adGroupCount).isEqualTo(0);
|
||||||
|
assertThat(adPlaybackStateWithNoAds.adResumePositionUs).isEqualTo(0);
|
||||||
|
assertThat(adPlaybackStateWithNoAds.contentDurationUs).isEqualTo(C.TIME_UNSET);
|
||||||
|
assertThat(adPlaybackStateWithNoAds.removedAdGroupCount).isEqualTo(0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void adPlaybackStateWithNoAds_toBundleSkipsDefaultValues_fromBundleRestoresThem() {
|
||||||
|
AdPlaybackState adPlaybackStateWithNoAds = AdPlaybackState.NONE;
|
||||||
|
|
||||||
|
Bundle adPlaybackStateWithNoAdsBundle = adPlaybackStateWithNoAds.toBundle();
|
||||||
|
|
||||||
|
// Check that default values are skipped when bundling.
|
||||||
|
assertThat(adPlaybackStateWithNoAdsBundle.keySet()).isEmpty();
|
||||||
|
|
||||||
|
AdPlaybackState adPlaybackStateWithNoAdsFromBundle =
|
||||||
|
AdPlaybackState.CREATOR.fromBundle(adPlaybackStateWithNoAdsBundle);
|
||||||
|
|
||||||
|
assertThat(adPlaybackStateWithNoAdsFromBundle.adsId).isEqualTo(adPlaybackStateWithNoAds.adsId);
|
||||||
|
assertThat(adPlaybackStateWithNoAdsFromBundle.adGroupCount)
|
||||||
|
.isEqualTo(adPlaybackStateWithNoAds.adGroupCount);
|
||||||
|
assertThat(adPlaybackStateWithNoAdsFromBundle.adResumePositionUs)
|
||||||
|
.isEqualTo(adPlaybackStateWithNoAds.adResumePositionUs);
|
||||||
|
assertThat(adPlaybackStateWithNoAdsFromBundle.contentDurationUs)
|
||||||
|
.isEqualTo(adPlaybackStateWithNoAds.contentDurationUs);
|
||||||
|
assertThat(adPlaybackStateWithNoAdsFromBundle.removedAdGroupCount)
|
||||||
|
.isEqualTo(adPlaybackStateWithNoAds.removedAdGroupCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createAdPlaybackState_roundTripViaBundle_yieldsEqualFieldsExceptAdsId() {
|
||||||
AdPlaybackState originalState =
|
AdPlaybackState originalState =
|
||||||
new AdPlaybackState(TEST_ADS_ID, TEST_AD_GROUP_TIMES_US)
|
new AdPlaybackState(TEST_ADS_ID, TEST_AD_GROUP_TIMES_US)
|
||||||
.withRemovedAdGroupCount(1)
|
.withRemovedAdGroupCount(1)
|
||||||
|
@ -0,0 +1,318 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2022 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 androidx.media3.common;
|
||||||
|
|
||||||
|
import static org.mockito.Mockito.spy;
|
||||||
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
|
import androidx.media3.test.utils.FakeTimeline;
|
||||||
|
import androidx.media3.test.utils.StubPlayer;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
/** Tests for {@link BasePlayer}. */
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class BasePlayerTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void seekTo_withIndexAndPosition_usesCommandSeekToMediaItem() {
|
||||||
|
BasePlayer player = spy(new TestBasePlayer());
|
||||||
|
|
||||||
|
player.seekTo(/* mediaItemIndex= */ 2, /* positionMs= */ 4000);
|
||||||
|
|
||||||
|
verify(player)
|
||||||
|
.seekTo(
|
||||||
|
/* mediaItemIndex= */ 2,
|
||||||
|
/* positionMs= */ 4000,
|
||||||
|
Player.COMMAND_SEEK_TO_MEDIA_ITEM,
|
||||||
|
/* isRepeatingCurrentItem= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void seekTo_withPosition_usesCommandSeekInCurrentMediaItem() {
|
||||||
|
BasePlayer player =
|
||||||
|
spy(
|
||||||
|
new TestBasePlayer() {
|
||||||
|
@Override
|
||||||
|
public int getCurrentMediaItemIndex() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
player.seekTo(/* positionMs= */ 4000);
|
||||||
|
|
||||||
|
verify(player)
|
||||||
|
.seekTo(
|
||||||
|
/* mediaItemIndex= */ 1,
|
||||||
|
/* positionMs= */ 4000,
|
||||||
|
Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM,
|
||||||
|
/* isRepeatingCurrentItem= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void seekToDefaultPosition_withIndex_usesCommandSeekToMediaItem() {
|
||||||
|
BasePlayer player = spy(new TestBasePlayer());
|
||||||
|
|
||||||
|
player.seekToDefaultPosition(/* mediaItemIndex= */ 2);
|
||||||
|
|
||||||
|
verify(player)
|
||||||
|
.seekTo(
|
||||||
|
/* mediaItemIndex= */ 2,
|
||||||
|
/* positionMs= */ C.TIME_UNSET,
|
||||||
|
Player.COMMAND_SEEK_TO_MEDIA_ITEM,
|
||||||
|
/* isRepeatingCurrentItem= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void seekToDefaultPosition_withoutIndex_usesCommandSeekToDefaultPosition() {
|
||||||
|
BasePlayer player =
|
||||||
|
spy(
|
||||||
|
new TestBasePlayer() {
|
||||||
|
@Override
|
||||||
|
public int getCurrentMediaItemIndex() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
player.seekToDefaultPosition();
|
||||||
|
|
||||||
|
verify(player)
|
||||||
|
.seekTo(
|
||||||
|
/* mediaItemIndex= */ 1,
|
||||||
|
/* positionMs= */ C.TIME_UNSET,
|
||||||
|
Player.COMMAND_SEEK_TO_DEFAULT_POSITION,
|
||||||
|
/* isRepeatingCurrentItem= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void seekToNext_usesCommandSeekToNext() {
|
||||||
|
BasePlayer player =
|
||||||
|
spy(
|
||||||
|
new TestBasePlayer() {
|
||||||
|
@Override
|
||||||
|
public int getCurrentMediaItemIndex() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
player.seekToNext();
|
||||||
|
|
||||||
|
verify(player)
|
||||||
|
.seekTo(
|
||||||
|
/* mediaItemIndex= */ 2,
|
||||||
|
/* positionMs= */ C.TIME_UNSET,
|
||||||
|
Player.COMMAND_SEEK_TO_NEXT,
|
||||||
|
/* isRepeatingCurrentItem= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void seekToNextMediaItem_usesCommandSeekToNextMediaItem() {
|
||||||
|
BasePlayer player =
|
||||||
|
spy(
|
||||||
|
new TestBasePlayer() {
|
||||||
|
@Override
|
||||||
|
public int getCurrentMediaItemIndex() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
player.seekToNextMediaItem();
|
||||||
|
|
||||||
|
verify(player)
|
||||||
|
.seekTo(
|
||||||
|
/* mediaItemIndex= */ 2,
|
||||||
|
/* positionMs= */ C.TIME_UNSET,
|
||||||
|
Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM,
|
||||||
|
/* isRepeatingCurrentItem= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void seekForward_usesCommandSeekForward() {
|
||||||
|
BasePlayer player =
|
||||||
|
spy(
|
||||||
|
new TestBasePlayer() {
|
||||||
|
@Override
|
||||||
|
public long getSeekForwardIncrement() {
|
||||||
|
return 2000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCurrentMediaItemIndex() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getCurrentPosition() {
|
||||||
|
return 5000;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
player.seekForward();
|
||||||
|
|
||||||
|
verify(player)
|
||||||
|
.seekTo(
|
||||||
|
/* mediaItemIndex= */ 1,
|
||||||
|
/* positionMs= */ 7000,
|
||||||
|
Player.COMMAND_SEEK_FORWARD,
|
||||||
|
/* isRepeatingCurrentItem= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void seekToPrevious_usesCommandSeekToPrevious() {
|
||||||
|
BasePlayer player =
|
||||||
|
spy(
|
||||||
|
new TestBasePlayer() {
|
||||||
|
@Override
|
||||||
|
public int getCurrentMediaItemIndex() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getMaxSeekToPreviousPosition() {
|
||||||
|
return 4000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getCurrentPosition() {
|
||||||
|
return 2000;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
player.seekToPrevious();
|
||||||
|
|
||||||
|
verify(player)
|
||||||
|
.seekTo(
|
||||||
|
/* mediaItemIndex= */ 0,
|
||||||
|
/* positionMs= */ C.TIME_UNSET,
|
||||||
|
Player.COMMAND_SEEK_TO_PREVIOUS,
|
||||||
|
/* isRepeatingCurrentItem= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void seekToPreviousMediaItem_usesCommandSeekToPreviousMediaItem() {
|
||||||
|
BasePlayer player =
|
||||||
|
spy(
|
||||||
|
new TestBasePlayer() {
|
||||||
|
@Override
|
||||||
|
public int getCurrentMediaItemIndex() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
player.seekToPreviousMediaItem();
|
||||||
|
|
||||||
|
verify(player)
|
||||||
|
.seekTo(
|
||||||
|
/* mediaItemIndex= */ 0,
|
||||||
|
/* positionMs= */ C.TIME_UNSET,
|
||||||
|
Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM,
|
||||||
|
/* isRepeatingCurrentItem= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void seekBack_usesCommandSeekBack() {
|
||||||
|
BasePlayer player =
|
||||||
|
spy(
|
||||||
|
new TestBasePlayer() {
|
||||||
|
@Override
|
||||||
|
public long getSeekBackIncrement() {
|
||||||
|
return 2000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCurrentMediaItemIndex() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getCurrentPosition() {
|
||||||
|
return 5000;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
|
||||||
|
player.seekBack();
|
||||||
|
|
||||||
|
verify(player)
|
||||||
|
.seekTo(
|
||||||
|
/* mediaItemIndex= */ 1,
|
||||||
|
/* positionMs= */ 3000,
|
||||||
|
Player.COMMAND_SEEK_BACK,
|
||||||
|
/* isRepeatingCurrentItem= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static class TestBasePlayer extends StubPlayer {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seekTo(
|
||||||
|
int mediaItemIndex,
|
||||||
|
long positionMs,
|
||||||
|
@Player.Command int seekCommand,
|
||||||
|
boolean isRepeatingCurrentItem) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getSeekBackIncrement() {
|
||||||
|
return 2000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getSeekForwardIncrement() {
|
||||||
|
return 2000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getMaxSeekToPreviousPosition() {
|
||||||
|
return 2000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Timeline getCurrentTimeline() {
|
||||||
|
return new FakeTimeline(/* windowCount= */ 3);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getCurrentMediaItemIndex() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getCurrentPosition() {
|
||||||
|
return 5000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public long getDuration() {
|
||||||
|
return 20000;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isPlayingAd() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getRepeatMode() {
|
||||||
|
return Player.REPEAT_MODE_OFF;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean getShuffleModeEnabled() {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -111,6 +111,8 @@ public final class FormatTest {
|
|||||||
.setEncoderPadding(1002)
|
.setEncoderPadding(1002)
|
||||||
.setAccessibilityChannel(2)
|
.setAccessibilityChannel(2)
|
||||||
.setCryptoType(C.CRYPTO_TYPE_CUSTOM_BASE)
|
.setCryptoType(C.CRYPTO_TYPE_CUSTOM_BASE)
|
||||||
|
.setTileCountHorizontal(20)
|
||||||
|
.setTileCountVertical(40)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -360,10 +360,12 @@ public class MediaItemTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void clippingConfigurationDefaults() {
|
public void createDefaultClippingConfigurationInstance_checksDefaultValues() {
|
||||||
MediaItem.ClippingConfiguration clippingConfiguration =
|
MediaItem.ClippingConfiguration clippingConfiguration =
|
||||||
new MediaItem.ClippingConfiguration.Builder().build();
|
new MediaItem.ClippingConfiguration.Builder().build();
|
||||||
|
|
||||||
|
// Please refrain from altering default values since doing so would cause issues with backwards
|
||||||
|
// compatibility.
|
||||||
assertThat(clippingConfiguration.startPositionMs).isEqualTo(0L);
|
assertThat(clippingConfiguration.startPositionMs).isEqualTo(0L);
|
||||||
assertThat(clippingConfiguration.endPositionMs).isEqualTo(C.TIME_END_OF_SOURCE);
|
assertThat(clippingConfiguration.endPositionMs).isEqualTo(C.TIME_END_OF_SOURCE);
|
||||||
assertThat(clippingConfiguration.relativeToLiveWindow).isFalse();
|
assertThat(clippingConfiguration.relativeToLiveWindow).isFalse();
|
||||||
@ -372,6 +374,38 @@ public class MediaItemTest {
|
|||||||
assertThat(clippingConfiguration).isEqualTo(MediaItem.ClippingConfiguration.UNSET);
|
assertThat(clippingConfiguration).isEqualTo(MediaItem.ClippingConfiguration.UNSET);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
createDefaultClippingConfigurationInstance_toBundleSkipsDefaultValues_fromBundleRestoresThem() {
|
||||||
|
MediaItem.ClippingConfiguration clippingConfiguration =
|
||||||
|
new MediaItem.ClippingConfiguration.Builder().build();
|
||||||
|
|
||||||
|
Bundle clippingConfigurationBundle = clippingConfiguration.toBundle();
|
||||||
|
|
||||||
|
// Check that default values are skipped when bundling.
|
||||||
|
assertThat(clippingConfigurationBundle.keySet()).isEmpty();
|
||||||
|
|
||||||
|
MediaItem.ClippingConfiguration clippingConfigurationFromBundle =
|
||||||
|
MediaItem.ClippingConfiguration.CREATOR.fromBundle(clippingConfigurationBundle);
|
||||||
|
|
||||||
|
assertThat(clippingConfigurationFromBundle).isEqualTo(clippingConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createClippingConfigurationInstance_roundTripViaBundle_yieldsEqualInstance() {
|
||||||
|
// Creates instance by setting some non-default values
|
||||||
|
MediaItem.ClippingConfiguration clippingConfiguration =
|
||||||
|
new MediaItem.ClippingConfiguration.Builder()
|
||||||
|
.setStartPositionMs(1000L)
|
||||||
|
.setStartsAtKeyFrame(true)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
MediaItem.ClippingConfiguration clippingConfigurationFromBundle =
|
||||||
|
MediaItem.ClippingConfiguration.CREATOR.fromBundle(clippingConfiguration.toBundle());
|
||||||
|
|
||||||
|
assertThat(clippingConfigurationFromBundle).isEqualTo(clippingConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void clippingConfigurationBuilder_throwsOnInvalidValues() {
|
public void clippingConfigurationBuilder_throwsOnInvalidValues() {
|
||||||
MediaItem.ClippingConfiguration.Builder clippingConfigurationBuilder =
|
MediaItem.ClippingConfiguration.Builder clippingConfigurationBuilder =
|
||||||
@ -514,6 +548,53 @@ public class MediaItemTest {
|
|||||||
assertThat(mediaItem.mediaMetadata).isEqualTo(mediaMetadata);
|
assertThat(mediaItem.mediaMetadata).isEqualTo(mediaMetadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createDefaultLiveConfigurationInstance_checksDefaultValues() {
|
||||||
|
MediaItem.LiveConfiguration liveConfiguration =
|
||||||
|
new MediaItem.LiveConfiguration.Builder().build();
|
||||||
|
|
||||||
|
// Please refrain from altering default values since doing so would cause issues with backwards
|
||||||
|
// compatibility.
|
||||||
|
assertThat(liveConfiguration.targetOffsetMs).isEqualTo(C.TIME_UNSET);
|
||||||
|
assertThat(liveConfiguration.minOffsetMs).isEqualTo(C.TIME_UNSET);
|
||||||
|
assertThat(liveConfiguration.maxOffsetMs).isEqualTo(C.TIME_UNSET);
|
||||||
|
assertThat(liveConfiguration.minPlaybackSpeed).isEqualTo(C.RATE_UNSET);
|
||||||
|
assertThat(liveConfiguration.maxPlaybackSpeed).isEqualTo(C.RATE_UNSET);
|
||||||
|
assertThat(liveConfiguration).isEqualTo(MediaItem.LiveConfiguration.UNSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
createDefaultLiveConfigurationInstance_toBundleSkipsDefaultValues_fromBundleRestoresThem() {
|
||||||
|
MediaItem.LiveConfiguration liveConfiguration =
|
||||||
|
new MediaItem.LiveConfiguration.Builder().build();
|
||||||
|
|
||||||
|
Bundle liveConfigurationBundle = liveConfiguration.toBundle();
|
||||||
|
|
||||||
|
// Check that default values are skipped when bundling.
|
||||||
|
assertThat(liveConfigurationBundle.keySet()).isEmpty();
|
||||||
|
|
||||||
|
MediaItem.LiveConfiguration liveConfigurationFromBundle =
|
||||||
|
MediaItem.LiveConfiguration.CREATOR.fromBundle(liveConfigurationBundle);
|
||||||
|
|
||||||
|
assertThat(liveConfigurationFromBundle).isEqualTo(liveConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createLiveConfigurationInstance_roundTripViaBundle_yieldsEqualInstance() {
|
||||||
|
// Creates instance by setting some non-default values
|
||||||
|
MediaItem.LiveConfiguration liveConfiguration =
|
||||||
|
new MediaItem.LiveConfiguration.Builder()
|
||||||
|
.setTargetOffsetMs(10_000)
|
||||||
|
.setMaxPlaybackSpeed(2f)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
MediaItem.LiveConfiguration liveConfigurationFromBundle =
|
||||||
|
MediaItem.LiveConfiguration.CREATOR.fromBundle(liveConfiguration.toBundle());
|
||||||
|
|
||||||
|
assertThat(liveConfigurationFromBundle).isEqualTo(liveConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void builderSetLiveConfiguration() {
|
public void builderSetLiveConfiguration() {
|
||||||
MediaItem mediaItem =
|
MediaItem mediaItem =
|
||||||
@ -747,4 +828,62 @@ public class MediaItemTest {
|
|||||||
assertThat(mediaItem.localConfiguration).isNotNull();
|
assertThat(mediaItem.localConfiguration).isNotNull();
|
||||||
assertThat(MediaItem.CREATOR.fromBundle(mediaItem.toBundle()).localConfiguration).isNull();
|
assertThat(MediaItem.CREATOR.fromBundle(mediaItem.toBundle()).localConfiguration).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createDefaultMediaItemInstance_checksDefaultValues() {
|
||||||
|
MediaItem mediaItem = new MediaItem.Builder().build();
|
||||||
|
|
||||||
|
// Please refrain from altering default values since doing so would cause issues with backwards
|
||||||
|
// compatibility.
|
||||||
|
assertThat(mediaItem.mediaId).isEqualTo(MediaItem.DEFAULT_MEDIA_ID);
|
||||||
|
assertThat(mediaItem.liveConfiguration).isEqualTo(MediaItem.LiveConfiguration.UNSET);
|
||||||
|
assertThat(mediaItem.mediaMetadata).isEqualTo(MediaMetadata.EMPTY);
|
||||||
|
assertThat(mediaItem.clippingConfiguration).isEqualTo(MediaItem.ClippingConfiguration.UNSET);
|
||||||
|
assertThat(mediaItem.requestMetadata).isEqualTo(RequestMetadata.EMPTY);
|
||||||
|
assertThat(mediaItem).isEqualTo(MediaItem.EMPTY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createDefaultMediaItemInstance_toBundleSkipsDefaultValues_fromBundleRestoresThem() {
|
||||||
|
MediaItem mediaItem = new MediaItem.Builder().build();
|
||||||
|
|
||||||
|
Bundle mediaItemBundle = mediaItem.toBundle();
|
||||||
|
|
||||||
|
// Check that default values are skipped when bundling.
|
||||||
|
assertThat(mediaItemBundle.keySet()).isEmpty();
|
||||||
|
|
||||||
|
MediaItem mediaItemFromBundle = MediaItem.CREATOR.fromBundle(mediaItem.toBundle());
|
||||||
|
|
||||||
|
assertThat(mediaItemFromBundle).isEqualTo(mediaItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createMediaItemInstance_roundTripViaBundle_yieldsEqualInstance() {
|
||||||
|
Bundle extras = new Bundle();
|
||||||
|
extras.putString("key", "value");
|
||||||
|
// Creates instance by setting some non-default values
|
||||||
|
MediaItem mediaItem =
|
||||||
|
new MediaItem.Builder()
|
||||||
|
.setLiveConfiguration(
|
||||||
|
new MediaItem.LiveConfiguration.Builder()
|
||||||
|
.setTargetOffsetMs(20_000)
|
||||||
|
.setMinOffsetMs(2_222)
|
||||||
|
.setMaxOffsetMs(4_444)
|
||||||
|
.setMinPlaybackSpeed(.9f)
|
||||||
|
.setMaxPlaybackSpeed(1.1f)
|
||||||
|
.build())
|
||||||
|
.setRequestMetadata(
|
||||||
|
new RequestMetadata.Builder()
|
||||||
|
.setMediaUri(Uri.parse("http://test.test"))
|
||||||
|
.setSearchQuery("search")
|
||||||
|
.setExtras(extras)
|
||||||
|
.build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
MediaItem mediaItemFromBundle = MediaItem.CREATOR.fromBundle(mediaItem.toBundle());
|
||||||
|
|
||||||
|
assertThat(mediaItemFromBundle).isEqualTo(mediaItem);
|
||||||
|
assertThat(mediaItemFromBundle.requestMetadata.extras)
|
||||||
|
.isEqualTo(mediaItem.requestMetadata.extras);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -49,6 +49,7 @@ public class MediaMetadataTest {
|
|||||||
assertThat(mediaMetadata.trackNumber).isNull();
|
assertThat(mediaMetadata.trackNumber).isNull();
|
||||||
assertThat(mediaMetadata.totalTrackCount).isNull();
|
assertThat(mediaMetadata.totalTrackCount).isNull();
|
||||||
assertThat(mediaMetadata.folderType).isNull();
|
assertThat(mediaMetadata.folderType).isNull();
|
||||||
|
assertThat(mediaMetadata.isBrowsable).isNull();
|
||||||
assertThat(mediaMetadata.isPlayable).isNull();
|
assertThat(mediaMetadata.isPlayable).isNull();
|
||||||
assertThat(mediaMetadata.recordingYear).isNull();
|
assertThat(mediaMetadata.recordingYear).isNull();
|
||||||
assertThat(mediaMetadata.recordingMonth).isNull();
|
assertThat(mediaMetadata.recordingMonth).isNull();
|
||||||
@ -64,6 +65,7 @@ public class MediaMetadataTest {
|
|||||||
assertThat(mediaMetadata.genre).isNull();
|
assertThat(mediaMetadata.genre).isNull();
|
||||||
assertThat(mediaMetadata.compilation).isNull();
|
assertThat(mediaMetadata.compilation).isNull();
|
||||||
assertThat(mediaMetadata.station).isNull();
|
assertThat(mediaMetadata.station).isNull();
|
||||||
|
assertThat(mediaMetadata.mediaType).isNull();
|
||||||
assertThat(mediaMetadata.extras).isNull();
|
assertThat(mediaMetadata.extras).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -105,13 +107,86 @@ public class MediaMetadataTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void roundTripViaBundle_yieldsEqualInstance() {
|
public void toBundleSkipsDefaultValues_fromBundleRestoresThem() {
|
||||||
|
MediaMetadata mediaMetadata = new MediaMetadata.Builder().build();
|
||||||
|
|
||||||
|
Bundle mediaMetadataBundle = mediaMetadata.toBundle();
|
||||||
|
|
||||||
|
// Check that default values are skipped when bundling.
|
||||||
|
assertThat(mediaMetadataBundle.keySet()).isEmpty();
|
||||||
|
|
||||||
|
MediaMetadata mediaMetadataFromBundle = MediaMetadata.CREATOR.fromBundle(mediaMetadataBundle);
|
||||||
|
|
||||||
|
assertThat(mediaMetadataFromBundle).isEqualTo(mediaMetadata);
|
||||||
|
// Extras is not implemented in MediaMetadata.equals(Object o).
|
||||||
|
assertThat(mediaMetadataFromBundle.extras).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createFullyPopulatedMediaMetadata_roundTripViaBundle_yieldsEqualInstance() {
|
||||||
MediaMetadata mediaMetadata = getFullyPopulatedMediaMetadata();
|
MediaMetadata mediaMetadata = getFullyPopulatedMediaMetadata();
|
||||||
|
|
||||||
MediaMetadata fromBundle = MediaMetadata.CREATOR.fromBundle(mediaMetadata.toBundle());
|
MediaMetadata mediaMetadataFromBundle =
|
||||||
assertThat(fromBundle).isEqualTo(mediaMetadata);
|
MediaMetadata.CREATOR.fromBundle(mediaMetadata.toBundle());
|
||||||
|
|
||||||
|
assertThat(mediaMetadataFromBundle).isEqualTo(mediaMetadata);
|
||||||
// Extras is not implemented in MediaMetadata.equals(Object o).
|
// Extras is not implemented in MediaMetadata.equals(Object o).
|
||||||
assertThat(fromBundle.extras.getString(EXTRAS_KEY)).isEqualTo(EXTRAS_VALUE);
|
assertThat(mediaMetadataFromBundle.extras.getString(EXTRAS_KEY)).isEqualTo(EXTRAS_VALUE);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void builderSetFolderType_toNone_setsIsBrowsableToFalse() {
|
||||||
|
MediaMetadata mediaMetadata =
|
||||||
|
new MediaMetadata.Builder().setFolderType(MediaMetadata.FOLDER_TYPE_NONE).build();
|
||||||
|
|
||||||
|
assertThat(mediaMetadata.isBrowsable).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void builderSetFolderType_toNotNone_setsIsBrowsableToTrueAndMatchingMediaType() {
|
||||||
|
MediaMetadata mediaMetadata =
|
||||||
|
new MediaMetadata.Builder().setFolderType(MediaMetadata.FOLDER_TYPE_PLAYLISTS).build();
|
||||||
|
|
||||||
|
assertThat(mediaMetadata.isBrowsable).isTrue();
|
||||||
|
assertThat(mediaMetadata.mediaType).isEqualTo(MediaMetadata.MEDIA_TYPE_FOLDER_PLAYLISTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
builderSetFolderType_toNotNoneWithManualMediaType_setsIsBrowsableToTrueAndDoesNotOverrideMediaType() {
|
||||||
|
MediaMetadata mediaMetadata =
|
||||||
|
new MediaMetadata.Builder()
|
||||||
|
.setMediaType(MediaMetadata.MEDIA_TYPE_FOLDER_PODCASTS)
|
||||||
|
.setFolderType(MediaMetadata.FOLDER_TYPE_PLAYLISTS)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertThat(mediaMetadata.isBrowsable).isTrue();
|
||||||
|
assertThat(mediaMetadata.mediaType).isEqualTo(MediaMetadata.MEDIA_TYPE_FOLDER_PODCASTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void builderSetIsBrowsable_toTrueWithoutMediaType_setsFolderTypeToMixed() {
|
||||||
|
MediaMetadata mediaMetadata = new MediaMetadata.Builder().setIsBrowsable(true).build();
|
||||||
|
|
||||||
|
assertThat(mediaMetadata.folderType).isEqualTo(MediaMetadata.FOLDER_TYPE_MIXED);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void builderSetIsBrowsable_toTrueWithMediaType_setsFolderTypeToMatchMediaType() {
|
||||||
|
MediaMetadata mediaMetadata =
|
||||||
|
new MediaMetadata.Builder()
|
||||||
|
.setIsBrowsable(true)
|
||||||
|
.setMediaType(MediaMetadata.MEDIA_TYPE_FOLDER_ARTISTS)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertThat(mediaMetadata.folderType).isEqualTo(MediaMetadata.FOLDER_TYPE_ARTISTS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void builderSetFolderType_toFalse_setsFolderTypeToNone() {
|
||||||
|
MediaMetadata mediaMetadata = new MediaMetadata.Builder().setIsBrowsable(false).build();
|
||||||
|
|
||||||
|
assertThat(mediaMetadata.folderType).isEqualTo(MediaMetadata.FOLDER_TYPE_NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static MediaMetadata getFullyPopulatedMediaMetadata() {
|
private static MediaMetadata getFullyPopulatedMediaMetadata() {
|
||||||
@ -134,6 +209,7 @@ public class MediaMetadataTest {
|
|||||||
.setTrackNumber(4)
|
.setTrackNumber(4)
|
||||||
.setTotalTrackCount(12)
|
.setTotalTrackCount(12)
|
||||||
.setFolderType(MediaMetadata.FOLDER_TYPE_PLAYLISTS)
|
.setFolderType(MediaMetadata.FOLDER_TYPE_PLAYLISTS)
|
||||||
|
.setIsBrowsable(true)
|
||||||
.setIsPlayable(true)
|
.setIsPlayable(true)
|
||||||
.setRecordingYear(2000)
|
.setRecordingYear(2000)
|
||||||
.setRecordingMonth(11)
|
.setRecordingMonth(11)
|
||||||
@ -149,6 +225,7 @@ public class MediaMetadataTest {
|
|||||||
.setGenre("Pop")
|
.setGenre("Pop")
|
||||||
.setCompilation("Amazing songs.")
|
.setCompilation("Amazing songs.")
|
||||||
.setStation("radio station")
|
.setStation("radio station")
|
||||||
|
.setMediaType(MediaMetadata.MEDIA_TYPE_MIXED)
|
||||||
.setExtras(extras)
|
.setExtras(extras)
|
||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -17,6 +17,7 @@ package androidx.media3.common;
|
|||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.MediaItem.LiveConfiguration;
|
import androidx.media3.common.MediaItem.LiveConfiguration;
|
||||||
import androidx.media3.exoplayer.source.ShuffleOrder.DefaultShuffleOrder;
|
import androidx.media3.exoplayer.source.ShuffleOrder.DefaultShuffleOrder;
|
||||||
@ -267,7 +268,9 @@ public class TimelineTest {
|
|||||||
/* durationUs= */ 2,
|
/* durationUs= */ 2,
|
||||||
/* defaultPositionUs= */ 22,
|
/* defaultPositionUs= */ 22,
|
||||||
/* windowOffsetInFirstPeriodUs= */ 222,
|
/* windowOffsetInFirstPeriodUs= */ 222,
|
||||||
ImmutableList.of(AdPlaybackState.NONE),
|
ImmutableList.of(
|
||||||
|
new AdPlaybackState(
|
||||||
|
/* adsId= */ null, /* adGroupTimesUs...= */ 10_000, 20_000)),
|
||||||
new MediaItem.Builder().setMediaId("mediaId2").build()),
|
new MediaItem.Builder().setMediaId("mediaId2").build()),
|
||||||
new TimelineWindowDefinition(
|
new TimelineWindowDefinition(
|
||||||
/* periodCount= */ 3,
|
/* periodCount= */ 3,
|
||||||
@ -334,6 +337,29 @@ public class TimelineTest {
|
|||||||
TimelineAsserts.assertEmpty(Timeline.CREATOR.fromBundle(Timeline.EMPTY.toBundle()));
|
TimelineAsserts.assertEmpty(Timeline.CREATOR.fromBundle(Timeline.EMPTY.toBundle()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void window_toBundleSkipsDefaultValues_fromBundleRestoresThem() {
|
||||||
|
Timeline.Window window = new Timeline.Window();
|
||||||
|
// Please refrain from altering these default values since doing so would cause issues with
|
||||||
|
// backwards compatibility.
|
||||||
|
window.presentationStartTimeMs = C.TIME_UNSET;
|
||||||
|
window.windowStartTimeMs = C.TIME_UNSET;
|
||||||
|
window.elapsedRealtimeEpochOffsetMs = C.TIME_UNSET;
|
||||||
|
window.durationUs = C.TIME_UNSET;
|
||||||
|
window.mediaItem = new MediaItem.Builder().build();
|
||||||
|
|
||||||
|
Bundle windowBundle = window.toBundle();
|
||||||
|
|
||||||
|
// Check that default values are skipped when bundling.
|
||||||
|
assertThat(windowBundle.keySet()).isEmpty();
|
||||||
|
|
||||||
|
Timeline.Window restoredWindow = Timeline.Window.CREATOR.fromBundle(windowBundle);
|
||||||
|
|
||||||
|
assertThat(restoredWindow.manifest).isNull();
|
||||||
|
TimelineAsserts.assertWindowEqualsExceptUidAndManifest(
|
||||||
|
/* expectedWindow= */ window, /* actualWindow= */ restoredWindow);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void roundTripViaBundle_ofWindow_yieldsEqualInstanceExceptUidAndManifest() {
|
public void roundTripViaBundle_ofWindow_yieldsEqualInstanceExceptUidAndManifest() {
|
||||||
Timeline.Window window = new Timeline.Window();
|
Timeline.Window window = new Timeline.Window();
|
||||||
@ -367,6 +393,26 @@ public class TimelineTest {
|
|||||||
/* expectedWindow= */ window, /* actualWindow= */ restoredWindow);
|
/* expectedWindow= */ window, /* actualWindow= */ restoredWindow);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void period_toBundleSkipsDefaultValues_fromBundleRestoresThem() {
|
||||||
|
Timeline.Period period = new Timeline.Period();
|
||||||
|
// Please refrain from altering these default values since doing so would cause issues with
|
||||||
|
// backwards compatibility.
|
||||||
|
period.durationUs = C.TIME_UNSET;
|
||||||
|
|
||||||
|
Bundle periodBundle = period.toBundle();
|
||||||
|
|
||||||
|
// Check that default values are skipped when bundling.
|
||||||
|
assertThat(periodBundle.keySet()).isEmpty();
|
||||||
|
|
||||||
|
Timeline.Period restoredPeriod = Timeline.Period.CREATOR.fromBundle(periodBundle);
|
||||||
|
|
||||||
|
assertThat(restoredPeriod.id).isNull();
|
||||||
|
assertThat(restoredPeriod.uid).isNull();
|
||||||
|
TimelineAsserts.assertPeriodEqualsExceptIds(
|
||||||
|
/* expectedPeriod= */ period, /* actualPeriod= */ restoredPeriod);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void roundTripViaBundle_ofPeriod_yieldsEqualInstanceExceptIds() {
|
public void roundTripViaBundle_ofPeriod_yieldsEqualInstanceExceptIds() {
|
||||||
Timeline.Period period = new Timeline.Period();
|
Timeline.Period period = new Timeline.Period();
|
||||||
|
@ -15,11 +15,13 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.common.util;
|
package androidx.media3.common.util;
|
||||||
|
|
||||||
|
import static androidx.media3.test.utils.TestUtil.createByteArray;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static java.nio.charset.Charset.forName;
|
import static java.nio.charset.Charset.forName;
|
||||||
import static org.junit.Assert.fail;
|
import static org.junit.Assert.fail;
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import com.google.common.base.Charsets;
|
||||||
import com.google.common.primitives.Bytes;
|
import com.google.common.primitives.Bytes;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
@ -330,6 +332,7 @@ public final class ParsableByteArrayTest {
|
|||||||
public void readLittleEndianLong() {
|
public void readLittleEndianLong() {
|
||||||
ParsableByteArray byteArray =
|
ParsableByteArray byteArray =
|
||||||
new ParsableByteArray(new byte[] {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xFF});
|
new ParsableByteArray(new byte[] {0x01, 0x00, 0x00, 0x00, 0x00, 0x00, 0x00, (byte) 0xFF});
|
||||||
|
|
||||||
assertThat(byteArray.readLittleEndianLong()).isEqualTo(0xFF00000000000001L);
|
assertThat(byteArray.readLittleEndianLong()).isEqualTo(0xFF00000000000001L);
|
||||||
assertThat(byteArray.getPosition()).isEqualTo(8);
|
assertThat(byteArray.getPosition()).isEqualTo(8);
|
||||||
}
|
}
|
||||||
@ -337,6 +340,7 @@ public final class ParsableByteArrayTest {
|
|||||||
@Test
|
@Test
|
||||||
public void readLittleEndianUnsignedInt() {
|
public void readLittleEndianUnsignedInt() {
|
||||||
ParsableByteArray byteArray = new ParsableByteArray(new byte[] {0x10, 0x00, 0x00, (byte) 0xFF});
|
ParsableByteArray byteArray = new ParsableByteArray(new byte[] {0x10, 0x00, 0x00, (byte) 0xFF});
|
||||||
|
|
||||||
assertThat(byteArray.readLittleEndianUnsignedInt()).isEqualTo(0xFF000010L);
|
assertThat(byteArray.readLittleEndianUnsignedInt()).isEqualTo(0xFF000010L);
|
||||||
assertThat(byteArray.getPosition()).isEqualTo(4);
|
assertThat(byteArray.getPosition()).isEqualTo(4);
|
||||||
}
|
}
|
||||||
@ -344,6 +348,7 @@ public final class ParsableByteArrayTest {
|
|||||||
@Test
|
@Test
|
||||||
public void readLittleEndianInt() {
|
public void readLittleEndianInt() {
|
||||||
ParsableByteArray byteArray = new ParsableByteArray(new byte[] {0x01, 0x00, 0x00, (byte) 0xFF});
|
ParsableByteArray byteArray = new ParsableByteArray(new byte[] {0x01, 0x00, 0x00, (byte) 0xFF});
|
||||||
|
|
||||||
assertThat(byteArray.readLittleEndianInt()).isEqualTo(0xFF000001);
|
assertThat(byteArray.readLittleEndianInt()).isEqualTo(0xFF000001);
|
||||||
assertThat(byteArray.getPosition()).isEqualTo(4);
|
assertThat(byteArray.getPosition()).isEqualTo(4);
|
||||||
}
|
}
|
||||||
@ -352,6 +357,7 @@ public final class ParsableByteArrayTest {
|
|||||||
public void readLittleEndianUnsignedInt24() {
|
public void readLittleEndianUnsignedInt24() {
|
||||||
byte[] data = {0x01, 0x02, (byte) 0xFF};
|
byte[] data = {0x01, 0x02, (byte) 0xFF};
|
||||||
ParsableByteArray byteArray = new ParsableByteArray(data);
|
ParsableByteArray byteArray = new ParsableByteArray(data);
|
||||||
|
|
||||||
assertThat(byteArray.readLittleEndianUnsignedInt24()).isEqualTo(0xFF0201);
|
assertThat(byteArray.readLittleEndianUnsignedInt24()).isEqualTo(0xFF0201);
|
||||||
assertThat(byteArray.getPosition()).isEqualTo(3);
|
assertThat(byteArray.getPosition()).isEqualTo(3);
|
||||||
}
|
}
|
||||||
@ -360,6 +366,7 @@ public final class ParsableByteArrayTest {
|
|||||||
public void readInt24Positive() {
|
public void readInt24Positive() {
|
||||||
byte[] data = {0x01, 0x02, (byte) 0xFF};
|
byte[] data = {0x01, 0x02, (byte) 0xFF};
|
||||||
ParsableByteArray byteArray = new ParsableByteArray(data);
|
ParsableByteArray byteArray = new ParsableByteArray(data);
|
||||||
|
|
||||||
assertThat(byteArray.readInt24()).isEqualTo(0x0102FF);
|
assertThat(byteArray.readInt24()).isEqualTo(0x0102FF);
|
||||||
assertThat(byteArray.getPosition()).isEqualTo(3);
|
assertThat(byteArray.getPosition()).isEqualTo(3);
|
||||||
}
|
}
|
||||||
@ -368,6 +375,7 @@ public final class ParsableByteArrayTest {
|
|||||||
public void readInt24Negative() {
|
public void readInt24Negative() {
|
||||||
byte[] data = {(byte) 0xFF, 0x02, (byte) 0x01};
|
byte[] data = {(byte) 0xFF, 0x02, (byte) 0x01};
|
||||||
ParsableByteArray byteArray = new ParsableByteArray(data);
|
ParsableByteArray byteArray = new ParsableByteArray(data);
|
||||||
|
|
||||||
assertThat(byteArray.readInt24()).isEqualTo(0xFFFF0201);
|
assertThat(byteArray.readInt24()).isEqualTo(0xFFFF0201);
|
||||||
assertThat(byteArray.getPosition()).isEqualTo(3);
|
assertThat(byteArray.getPosition()).isEqualTo(3);
|
||||||
}
|
}
|
||||||
@ -376,6 +384,7 @@ public final class ParsableByteArrayTest {
|
|||||||
public void readLittleEndianUnsignedShort() {
|
public void readLittleEndianUnsignedShort() {
|
||||||
ParsableByteArray byteArray =
|
ParsableByteArray byteArray =
|
||||||
new ParsableByteArray(new byte[] {0x01, (byte) 0xFF, 0x02, (byte) 0xFF});
|
new ParsableByteArray(new byte[] {0x01, (byte) 0xFF, 0x02, (byte) 0xFF});
|
||||||
|
|
||||||
assertThat(byteArray.readLittleEndianUnsignedShort()).isEqualTo(0xFF01);
|
assertThat(byteArray.readLittleEndianUnsignedShort()).isEqualTo(0xFF01);
|
||||||
assertThat(byteArray.getPosition()).isEqualTo(2);
|
assertThat(byteArray.getPosition()).isEqualTo(2);
|
||||||
assertThat(byteArray.readLittleEndianUnsignedShort()).isEqualTo(0xFF02);
|
assertThat(byteArray.readLittleEndianUnsignedShort()).isEqualTo(0xFF02);
|
||||||
@ -386,6 +395,7 @@ public final class ParsableByteArrayTest {
|
|||||||
public void readLittleEndianShort() {
|
public void readLittleEndianShort() {
|
||||||
ParsableByteArray byteArray =
|
ParsableByteArray byteArray =
|
||||||
new ParsableByteArray(new byte[] {0x01, (byte) 0xFF, 0x02, (byte) 0xFF});
|
new ParsableByteArray(new byte[] {0x01, (byte) 0xFF, 0x02, (byte) 0xFF});
|
||||||
|
|
||||||
assertThat(byteArray.readLittleEndianShort()).isEqualTo((short) 0xFF01);
|
assertThat(byteArray.readLittleEndianShort()).isEqualTo((short) 0xFF01);
|
||||||
assertThat(byteArray.getPosition()).isEqualTo(2);
|
assertThat(byteArray.getPosition()).isEqualTo(2);
|
||||||
assertThat(byteArray.readLittleEndianShort()).isEqualTo((short) 0xFF02);
|
assertThat(byteArray.readLittleEndianShort()).isEqualTo((short) 0xFF02);
|
||||||
@ -420,6 +430,7 @@ public final class ParsableByteArrayTest {
|
|||||||
(byte) 0x20,
|
(byte) 0x20,
|
||||||
};
|
};
|
||||||
ParsableByteArray byteArray = new ParsableByteArray(data);
|
ParsableByteArray byteArray = new ParsableByteArray(data);
|
||||||
|
|
||||||
assertThat(byteArray.readString(data.length)).isEqualTo("ä ö ® π √ ± 谢 ");
|
assertThat(byteArray.readString(data.length)).isEqualTo("ä ö ® π √ ± 谢 ");
|
||||||
assertThat(byteArray.getPosition()).isEqualTo(data.length);
|
assertThat(byteArray.getPosition()).isEqualTo(data.length);
|
||||||
}
|
}
|
||||||
@ -428,6 +439,7 @@ public final class ParsableByteArrayTest {
|
|||||||
public void readAsciiString() {
|
public void readAsciiString() {
|
||||||
byte[] data = new byte[] {'t', 'e', 's', 't'};
|
byte[] data = new byte[] {'t', 'e', 's', 't'};
|
||||||
ParsableByteArray testArray = new ParsableByteArray(data);
|
ParsableByteArray testArray = new ParsableByteArray(data);
|
||||||
|
|
||||||
assertThat(testArray.readString(data.length, forName("US-ASCII"))).isEqualTo("test");
|
assertThat(testArray.readString(data.length, forName("US-ASCII"))).isEqualTo("test");
|
||||||
assertThat(testArray.getPosition()).isEqualTo(data.length);
|
assertThat(testArray.getPosition()).isEqualTo(data.length);
|
||||||
}
|
}
|
||||||
@ -436,6 +448,7 @@ public final class ParsableByteArrayTest {
|
|||||||
public void readStringOutOfBoundsDoesNotMovePosition() {
|
public void readStringOutOfBoundsDoesNotMovePosition() {
|
||||||
byte[] data = {(byte) 0xC3, (byte) 0xA4, (byte) 0x20};
|
byte[] data = {(byte) 0xC3, (byte) 0xA4, (byte) 0x20};
|
||||||
ParsableByteArray byteArray = new ParsableByteArray(data);
|
ParsableByteArray byteArray = new ParsableByteArray(data);
|
||||||
|
|
||||||
try {
|
try {
|
||||||
byteArray.readString(data.length + 1);
|
byteArray.readString(data.length + 1);
|
||||||
fail();
|
fail();
|
||||||
@ -452,17 +465,22 @@ public final class ParsableByteArrayTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void readNullTerminatedStringWithLengths() {
|
public void readNullTerminatedStringWithLengths_readLengthsMatchNullPositions() {
|
||||||
byte[] bytes = new byte[] {'f', 'o', 'o', 0, 'b', 'a', 'r', 0};
|
byte[] bytes = new byte[] {'f', 'o', 'o', 0, 'b', 'a', 'r', 0};
|
||||||
// Test with lengths that match NUL byte positions.
|
|
||||||
ParsableByteArray parser = new ParsableByteArray(bytes);
|
ParsableByteArray parser = new ParsableByteArray(bytes);
|
||||||
assertThat(parser.readNullTerminatedString(4)).isEqualTo("foo");
|
assertThat(parser.readNullTerminatedString(4)).isEqualTo("foo");
|
||||||
assertThat(parser.getPosition()).isEqualTo(4);
|
assertThat(parser.getPosition()).isEqualTo(4);
|
||||||
assertThat(parser.readNullTerminatedString(4)).isEqualTo("bar");
|
assertThat(parser.readNullTerminatedString(4)).isEqualTo("bar");
|
||||||
assertThat(parser.getPosition()).isEqualTo(8);
|
assertThat(parser.getPosition()).isEqualTo(8);
|
||||||
assertThat(parser.readNullTerminatedString()).isNull();
|
assertThat(parser.readNullTerminatedString()).isNull();
|
||||||
// Test with lengths that do not match NUL byte positions.
|
}
|
||||||
parser = new ParsableByteArray(bytes);
|
|
||||||
|
@Test
|
||||||
|
public void readNullTerminatedStringWithLengths_readLengthsDontMatchNullPositions() {
|
||||||
|
byte[] bytes = new byte[] {'f', 'o', 'o', 0, 'b', 'a', 'r', 0};
|
||||||
|
ParsableByteArray parser = new ParsableByteArray(bytes);
|
||||||
|
|
||||||
assertThat(parser.readNullTerminatedString(2)).isEqualTo("fo");
|
assertThat(parser.readNullTerminatedString(2)).isEqualTo("fo");
|
||||||
assertThat(parser.getPosition()).isEqualTo(2);
|
assertThat(parser.getPosition()).isEqualTo(2);
|
||||||
assertThat(parser.readNullTerminatedString(2)).isEqualTo("o");
|
assertThat(parser.readNullTerminatedString(2)).isEqualTo("o");
|
||||||
@ -472,13 +490,23 @@ public final class ParsableByteArrayTest {
|
|||||||
assertThat(parser.readNullTerminatedString(1)).isEqualTo("");
|
assertThat(parser.readNullTerminatedString(1)).isEqualTo("");
|
||||||
assertThat(parser.getPosition()).isEqualTo(8);
|
assertThat(parser.getPosition()).isEqualTo(8);
|
||||||
assertThat(parser.readNullTerminatedString()).isNull();
|
assertThat(parser.readNullTerminatedString()).isNull();
|
||||||
// Test with limit at NUL
|
}
|
||||||
parser = new ParsableByteArray(bytes, 4);
|
|
||||||
|
@Test
|
||||||
|
public void readNullTerminatedStringWithLengths_limitAtNull() {
|
||||||
|
byte[] bytes = new byte[] {'f', 'o', 'o', 0, 'b', 'a', 'r', 0};
|
||||||
|
ParsableByteArray parser = new ParsableByteArray(bytes, /* limit= */ 4);
|
||||||
|
|
||||||
assertThat(parser.readNullTerminatedString(4)).isEqualTo("foo");
|
assertThat(parser.readNullTerminatedString(4)).isEqualTo("foo");
|
||||||
assertThat(parser.getPosition()).isEqualTo(4);
|
assertThat(parser.getPosition()).isEqualTo(4);
|
||||||
assertThat(parser.readNullTerminatedString()).isNull();
|
assertThat(parser.readNullTerminatedString()).isNull();
|
||||||
// Test with limit before NUL
|
}
|
||||||
parser = new ParsableByteArray(bytes, 3);
|
|
||||||
|
@Test
|
||||||
|
public void readNullTerminatedStringWithLengths_limitBeforeNull() {
|
||||||
|
byte[] bytes = new byte[] {'f', 'o', 'o', 0, 'b', 'a', 'r', 0};
|
||||||
|
ParsableByteArray parser = new ParsableByteArray(bytes, /* limit= */ 3);
|
||||||
|
|
||||||
assertThat(parser.readNullTerminatedString(3)).isEqualTo("foo");
|
assertThat(parser.readNullTerminatedString(3)).isEqualTo("foo");
|
||||||
assertThat(parser.getPosition()).isEqualTo(3);
|
assertThat(parser.getPosition()).isEqualTo(3);
|
||||||
assertThat(parser.readNullTerminatedString()).isNull();
|
assertThat(parser.readNullTerminatedString()).isNull();
|
||||||
@ -487,20 +515,30 @@ public final class ParsableByteArrayTest {
|
|||||||
@Test
|
@Test
|
||||||
public void readNullTerminatedString() {
|
public void readNullTerminatedString() {
|
||||||
byte[] bytes = new byte[] {'f', 'o', 'o', 0, 'b', 'a', 'r', 0};
|
byte[] bytes = new byte[] {'f', 'o', 'o', 0, 'b', 'a', 'r', 0};
|
||||||
// Test normal case.
|
|
||||||
ParsableByteArray parser = new ParsableByteArray(bytes);
|
ParsableByteArray parser = new ParsableByteArray(bytes);
|
||||||
|
|
||||||
assertThat(parser.readNullTerminatedString()).isEqualTo("foo");
|
assertThat(parser.readNullTerminatedString()).isEqualTo("foo");
|
||||||
assertThat(parser.getPosition()).isEqualTo(4);
|
assertThat(parser.getPosition()).isEqualTo(4);
|
||||||
assertThat(parser.readNullTerminatedString()).isEqualTo("bar");
|
assertThat(parser.readNullTerminatedString()).isEqualTo("bar");
|
||||||
assertThat(parser.getPosition()).isEqualTo(8);
|
assertThat(parser.getPosition()).isEqualTo(8);
|
||||||
assertThat(parser.readNullTerminatedString()).isNull();
|
assertThat(parser.readNullTerminatedString()).isNull();
|
||||||
// Test with limit at NUL.
|
}
|
||||||
parser = new ParsableByteArray(bytes, 4);
|
|
||||||
|
@Test
|
||||||
|
public void readNullTerminatedString_withLimitAtNull() {
|
||||||
|
byte[] bytes = new byte[] {'f', 'o', 'o', 0, 'b', 'a', 'r', 0};
|
||||||
|
ParsableByteArray parser = new ParsableByteArray(bytes, /* limit= */ 4);
|
||||||
|
|
||||||
assertThat(parser.readNullTerminatedString()).isEqualTo("foo");
|
assertThat(parser.readNullTerminatedString()).isEqualTo("foo");
|
||||||
assertThat(parser.getPosition()).isEqualTo(4);
|
assertThat(parser.getPosition()).isEqualTo(4);
|
||||||
assertThat(parser.readNullTerminatedString()).isNull();
|
assertThat(parser.readNullTerminatedString()).isNull();
|
||||||
// Test with limit before NUL.
|
}
|
||||||
parser = new ParsableByteArray(bytes, 3);
|
|
||||||
|
@Test
|
||||||
|
public void readNullTerminatedString_withLimitBeforeNull() {
|
||||||
|
byte[] bytes = new byte[] {'f', 'o', 'o', 0, 'b', 'a', 'r', 0};
|
||||||
|
ParsableByteArray parser = new ParsableByteArray(bytes, /* limit= */ 3);
|
||||||
|
|
||||||
assertThat(parser.readNullTerminatedString()).isEqualTo("foo");
|
assertThat(parser.readNullTerminatedString()).isEqualTo("foo");
|
||||||
assertThat(parser.getPosition()).isEqualTo(3);
|
assertThat(parser.getPosition()).isEqualTo(3);
|
||||||
assertThat(parser.readNullTerminatedString()).isNull();
|
assertThat(parser.readNullTerminatedString()).isNull();
|
||||||
@ -510,6 +548,7 @@ public final class ParsableByteArrayTest {
|
|||||||
public void readNullTerminatedStringWithoutEndingNull() {
|
public void readNullTerminatedStringWithoutEndingNull() {
|
||||||
byte[] bytes = new byte[] {'f', 'o', 'o', 0, 'b', 'a', 'r'};
|
byte[] bytes = new byte[] {'f', 'o', 'o', 0, 'b', 'a', 'r'};
|
||||||
ParsableByteArray parser = new ParsableByteArray(bytes);
|
ParsableByteArray parser = new ParsableByteArray(bytes);
|
||||||
|
|
||||||
assertThat(parser.readNullTerminatedString()).isEqualTo("foo");
|
assertThat(parser.readNullTerminatedString()).isEqualTo("foo");
|
||||||
assertThat(parser.readNullTerminatedString()).isEqualTo("bar");
|
assertThat(parser.readNullTerminatedString()).isEqualTo("bar");
|
||||||
assertThat(parser.readNullTerminatedString()).isNull();
|
assertThat(parser.readNullTerminatedString()).isNull();
|
||||||
@ -518,78 +557,364 @@ public final class ParsableByteArrayTest {
|
|||||||
@Test
|
@Test
|
||||||
public void readDelimiterTerminatedString() {
|
public void readDelimiterTerminatedString() {
|
||||||
byte[] bytes = new byte[] {'f', 'o', 'o', '*', 'b', 'a', 'r', '*'};
|
byte[] bytes = new byte[] {'f', 'o', 'o', '*', 'b', 'a', 'r', '*'};
|
||||||
// Test normal case.
|
|
||||||
ParsableByteArray parser = new ParsableByteArray(bytes);
|
ParsableByteArray parser = new ParsableByteArray(bytes);
|
||||||
|
|
||||||
assertThat(parser.readDelimiterTerminatedString('*')).isEqualTo("foo");
|
assertThat(parser.readDelimiterTerminatedString('*')).isEqualTo("foo");
|
||||||
assertThat(parser.getPosition()).isEqualTo(4);
|
assertThat(parser.getPosition()).isEqualTo(4);
|
||||||
assertThat(parser.readDelimiterTerminatedString('*')).isEqualTo("bar");
|
assertThat(parser.readDelimiterTerminatedString('*')).isEqualTo("bar");
|
||||||
assertThat(parser.getPosition()).isEqualTo(8);
|
assertThat(parser.getPosition()).isEqualTo(8);
|
||||||
assertThat(parser.readDelimiterTerminatedString('*')).isNull();
|
assertThat(parser.readDelimiterTerminatedString('*')).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readDelimiterTerminatedString_limitAtDelimiter() {
|
||||||
|
byte[] bytes = new byte[] {'f', 'o', 'o', '*', 'b', 'a', 'r', '*'};
|
||||||
|
ParsableByteArray parser = new ParsableByteArray(bytes, /* limit= */ 4);
|
||||||
|
|
||||||
// Test with limit at delimiter.
|
|
||||||
parser = new ParsableByteArray(bytes, 4);
|
|
||||||
assertThat(parser.readDelimiterTerminatedString('*')).isEqualTo("foo");
|
assertThat(parser.readDelimiterTerminatedString('*')).isEqualTo("foo");
|
||||||
assertThat(parser.getPosition()).isEqualTo(4);
|
assertThat(parser.getPosition()).isEqualTo(4);
|
||||||
assertThat(parser.readDelimiterTerminatedString('*')).isNull();
|
assertThat(parser.readDelimiterTerminatedString('*')).isNull();
|
||||||
// Test with limit before delimiter.
|
}
|
||||||
parser = new ParsableByteArray(bytes, 3);
|
|
||||||
|
@Test
|
||||||
|
public void readDelimiterTerminatedString_limitBeforeDelimiter() {
|
||||||
|
byte[] bytes = new byte[] {'f', 'o', 'o', '*', 'b', 'a', 'r', '*'};
|
||||||
|
ParsableByteArray parser = new ParsableByteArray(bytes, /* limit= */ 3);
|
||||||
|
|
||||||
assertThat(parser.readDelimiterTerminatedString('*')).isEqualTo("foo");
|
assertThat(parser.readDelimiterTerminatedString('*')).isEqualTo("foo");
|
||||||
assertThat(parser.getPosition()).isEqualTo(3);
|
assertThat(parser.getPosition()).isEqualTo(3);
|
||||||
assertThat(parser.readDelimiterTerminatedString('*')).isNull();
|
assertThat(parser.readDelimiterTerminatedString('*')).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void readDelimiterTerminatedStringWithoutEndingDelimiter() {
|
public void readDelimiterTerminatedStringW_noDelimiter() {
|
||||||
byte[] bytes = new byte[] {'f', 'o', 'o', '*', 'b', 'a', 'r'};
|
byte[] bytes = new byte[] {'f', 'o', 'o', '*', 'b', 'a', 'r'};
|
||||||
ParsableByteArray parser = new ParsableByteArray(bytes);
|
ParsableByteArray parser = new ParsableByteArray(bytes);
|
||||||
|
|
||||||
assertThat(parser.readDelimiterTerminatedString('*')).isEqualTo("foo");
|
assertThat(parser.readDelimiterTerminatedString('*')).isEqualTo("foo");
|
||||||
assertThat(parser.readDelimiterTerminatedString('*')).isEqualTo("bar");
|
assertThat(parser.readDelimiterTerminatedString('*')).isEqualTo("bar");
|
||||||
assertThat(parser.readDelimiterTerminatedString('*')).isNull();
|
assertThat(parser.readDelimiterTerminatedString('*')).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void readSingleLineWithoutEndingTrail() {
|
public void readSingleLineWithoutEndingTrail_ascii() {
|
||||||
byte[] bytes = new byte[] {'f', 'o', 'o'};
|
byte[] bytes = "foo".getBytes(Charsets.US_ASCII);
|
||||||
ParsableByteArray parser = new ParsableByteArray(bytes);
|
ParsableByteArray parser = new ParsableByteArray(bytes);
|
||||||
|
|
||||||
|
assertThat(parser.readLine(Charsets.US_ASCII)).isEqualTo("foo");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(3);
|
||||||
|
assertThat(parser.readLine(Charsets.US_ASCII)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readSingleLineWithEndingLf_ascii() {
|
||||||
|
byte[] bytes = "foo\n".getBytes(Charsets.US_ASCII);
|
||||||
|
ParsableByteArray parser = new ParsableByteArray(bytes);
|
||||||
|
|
||||||
|
assertThat(parser.readLine(Charsets.US_ASCII)).isEqualTo("foo");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(4);
|
||||||
|
assertThat(parser.readLine(Charsets.US_ASCII)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readTwoLinesWithCrFollowedByLf_ascii() {
|
||||||
|
byte[] bytes = "foo\r\nbar".getBytes(Charsets.US_ASCII);
|
||||||
|
ParsableByteArray parser = new ParsableByteArray(bytes);
|
||||||
|
|
||||||
|
assertThat(parser.readLine(Charsets.US_ASCII)).isEqualTo("foo");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(5);
|
||||||
|
assertThat(parser.readLine(Charsets.US_ASCII)).isEqualTo("bar");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(8);
|
||||||
|
assertThat(parser.readLine(Charsets.US_ASCII)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readThreeLinesWithEmptyLine_ascii() {
|
||||||
|
byte[] bytes = "foo\r\n\rbar".getBytes(Charsets.US_ASCII);
|
||||||
|
ParsableByteArray parser = new ParsableByteArray(bytes);
|
||||||
|
|
||||||
|
assertThat(parser.readLine(Charsets.US_ASCII)).isEqualTo("foo");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(5);
|
||||||
|
assertThat(parser.readLine(Charsets.US_ASCII)).isEqualTo("");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(6);
|
||||||
|
assertThat(parser.readLine(Charsets.US_ASCII)).isEqualTo("bar");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(9);
|
||||||
|
assertThat(parser.readLine(Charsets.US_ASCII)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readFourLinesWithLfFollowedByCr_ascii() {
|
||||||
|
byte[] bytes = "foo\n\r\rbar\r\n".getBytes(Charsets.US_ASCII);
|
||||||
|
ParsableByteArray parser = new ParsableByteArray(bytes);
|
||||||
|
|
||||||
|
assertThat(parser.readLine(Charsets.US_ASCII)).isEqualTo("foo");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(4);
|
||||||
|
assertThat(parser.readLine(Charsets.US_ASCII)).isEqualTo("");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(5);
|
||||||
|
assertThat(parser.readLine(Charsets.US_ASCII)).isEqualTo("");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(6);
|
||||||
|
assertThat(parser.readLine(Charsets.US_ASCII)).isEqualTo("bar");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(11);
|
||||||
|
assertThat(parser.readLine(Charsets.US_ASCII)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readSingleLineWithoutEndingTrail_utf8() {
|
||||||
|
byte[] bytes = "foo".getBytes(Charsets.UTF_8);
|
||||||
|
ParsableByteArray parser = new ParsableByteArray(bytes);
|
||||||
|
|
||||||
assertThat(parser.readLine()).isEqualTo("foo");
|
assertThat(parser.readLine()).isEqualTo("foo");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(3);
|
||||||
assertThat(parser.readLine()).isNull();
|
assertThat(parser.readLine()).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void readSingleLineWithEndingLf() {
|
public void readSingleLineWithEndingLf_utf8() {
|
||||||
byte[] bytes = new byte[] {'f', 'o', 'o', '\n'};
|
byte[] bytes = "foo\n".getBytes(Charsets.UTF_8);
|
||||||
ParsableByteArray parser = new ParsableByteArray(bytes);
|
ParsableByteArray parser = new ParsableByteArray(bytes);
|
||||||
|
|
||||||
assertThat(parser.readLine()).isEqualTo("foo");
|
assertThat(parser.readLine()).isEqualTo("foo");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(4);
|
||||||
assertThat(parser.readLine()).isNull();
|
assertThat(parser.readLine()).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void readTwoLinesWithCrFollowedByLf() {
|
public void readTwoLinesWithCrFollowedByLf_utf8() {
|
||||||
byte[] bytes = new byte[] {'f', 'o', 'o', '\r', '\n', 'b', 'a', 'r'};
|
byte[] bytes = "foo\r\nbar".getBytes(Charsets.UTF_8);
|
||||||
ParsableByteArray parser = new ParsableByteArray(bytes);
|
ParsableByteArray parser = new ParsableByteArray(bytes);
|
||||||
|
|
||||||
assertThat(parser.readLine()).isEqualTo("foo");
|
assertThat(parser.readLine()).isEqualTo("foo");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(5);
|
||||||
assertThat(parser.readLine()).isEqualTo("bar");
|
assertThat(parser.readLine()).isEqualTo("bar");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(8);
|
||||||
assertThat(parser.readLine()).isNull();
|
assertThat(parser.readLine()).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void readThreeLinesWithEmptyLine() {
|
public void readThreeLinesWithEmptyLineAndLeadingBom_utf8() {
|
||||||
byte[] bytes = new byte[] {'f', 'o', 'o', '\r', '\n', '\r', 'b', 'a', 'r'};
|
byte[] bytes =
|
||||||
|
Bytes.concat(createByteArray(0xEF, 0xBB, 0xBF), "foo\r\n\rbar".getBytes(Charsets.UTF_8));
|
||||||
ParsableByteArray parser = new ParsableByteArray(bytes);
|
ParsableByteArray parser = new ParsableByteArray(bytes);
|
||||||
|
|
||||||
assertThat(parser.readLine()).isEqualTo("foo");
|
assertThat(parser.readLine()).isEqualTo("foo");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(8);
|
||||||
assertThat(parser.readLine()).isEqualTo("");
|
assertThat(parser.readLine()).isEqualTo("");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(9);
|
||||||
assertThat(parser.readLine()).isEqualTo("bar");
|
assertThat(parser.readLine()).isEqualTo("bar");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(12);
|
||||||
assertThat(parser.readLine()).isNull();
|
assertThat(parser.readLine()).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void readFourLinesWithLfFollowedByCr() {
|
public void readFourLinesWithLfFollowedByCr_utf8() {
|
||||||
byte[] bytes = new byte[] {'f', 'o', 'o', '\n', '\r', '\r', 'b', 'a', 'r', '\r', '\n'};
|
byte[] bytes = "foo\n\r\rbar\r\n".getBytes(Charsets.UTF_8);
|
||||||
ParsableByteArray parser = new ParsableByteArray(bytes);
|
ParsableByteArray parser = new ParsableByteArray(bytes);
|
||||||
|
|
||||||
assertThat(parser.readLine()).isEqualTo("foo");
|
assertThat(parser.readLine()).isEqualTo("foo");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(4);
|
||||||
assertThat(parser.readLine()).isEqualTo("");
|
assertThat(parser.readLine()).isEqualTo("");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(5);
|
||||||
assertThat(parser.readLine()).isEqualTo("");
|
assertThat(parser.readLine()).isEqualTo("");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(6);
|
||||||
assertThat(parser.readLine()).isEqualTo("bar");
|
assertThat(parser.readLine()).isEqualTo("bar");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(11);
|
||||||
assertThat(parser.readLine()).isNull();
|
assertThat(parser.readLine()).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readSingleLineWithoutEndingTrail_utf16() {
|
||||||
|
// Use UTF_16BE because we don't want the leading BOM that's added by getBytes(UTF_16). We
|
||||||
|
// explicitly test with a BOM elsewhere.
|
||||||
|
byte[] bytes = "foo".getBytes(Charsets.UTF_16BE);
|
||||||
|
ParsableByteArray parser = new ParsableByteArray(bytes);
|
||||||
|
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16)).isEqualTo("foo");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(6);
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readSingleLineWithEndingLf_utf16() {
|
||||||
|
// Use UTF_16BE because we don't want the leading BOM that's added by getBytes(UTF_16). We
|
||||||
|
// explicitly test with a BOM elsewhere.
|
||||||
|
byte[] bytes = "foo\n".getBytes(Charsets.UTF_16BE);
|
||||||
|
ParsableByteArray parser = new ParsableByteArray(bytes);
|
||||||
|
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16)).isEqualTo("foo");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(8);
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readTwoLinesWithCrFollowedByLf_utf16() {
|
||||||
|
// Use UTF_16BE because we don't want the leading BOM that's added by getBytes(UTF_16). We
|
||||||
|
// explicitly test with a BOM elsewhere.
|
||||||
|
byte[] bytes = "foo\r\nbar".getBytes(Charsets.UTF_16BE);
|
||||||
|
ParsableByteArray parser = new ParsableByteArray(bytes);
|
||||||
|
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16)).isEqualTo("foo");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(10);
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16)).isEqualTo("bar");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(16);
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readThreeLinesWithEmptyLineAndLeadingBom_utf16() {
|
||||||
|
// getBytes(UTF_16) always adds the leading BOM.
|
||||||
|
byte[] bytes = "foo\r\n\rbar".getBytes(Charsets.UTF_16);
|
||||||
|
ParsableByteArray parser = new ParsableByteArray(bytes);
|
||||||
|
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16)).isEqualTo("foo");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(12);
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16)).isEqualTo("");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(14);
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16)).isEqualTo("bar");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(20);
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readFourLinesWithLfFollowedByCr_utf16() {
|
||||||
|
// Use UTF_16BE because we don't want the leading BOM that's added by getBytes(UTF_16). We
|
||||||
|
// explicitly test with a BOM elsewhere.
|
||||||
|
byte[] bytes = "foo\n\r\rbar\r\n".getBytes(Charsets.UTF_16BE);
|
||||||
|
ParsableByteArray parser = new ParsableByteArray(bytes);
|
||||||
|
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16)).isEqualTo("foo");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(8);
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16)).isEqualTo("");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(10);
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16)).isEqualTo("");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(12);
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16)).isEqualTo("bar");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(22);
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readSingleLineWithoutEndingTrail_utf16be() {
|
||||||
|
byte[] bytes = "foo".getBytes(Charsets.UTF_16BE);
|
||||||
|
ParsableByteArray parser = new ParsableByteArray(bytes);
|
||||||
|
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16BE)).isEqualTo("foo");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(6);
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16BE)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readSingleLineWithEndingLf_utf16be() {
|
||||||
|
byte[] bytes = "foo\n".getBytes(Charsets.UTF_16BE);
|
||||||
|
ParsableByteArray parser = new ParsableByteArray(bytes);
|
||||||
|
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16BE)).isEqualTo("foo");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(8);
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16BE)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readTwoLinesWithCrFollowedByLf_utf16be() {
|
||||||
|
byte[] bytes = "foo\r\nbar".getBytes(Charsets.UTF_16BE);
|
||||||
|
ParsableByteArray parser = new ParsableByteArray(bytes);
|
||||||
|
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16BE)).isEqualTo("foo");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(10);
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16BE)).isEqualTo("bar");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(16);
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16BE)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readThreeLinesWithEmptyLineAndLeadingBom_utf16be() {
|
||||||
|
byte[] bytes =
|
||||||
|
Bytes.concat(createByteArray(0xFE, 0xFF), "foo\r\n\rbar".getBytes(Charsets.UTF_16BE));
|
||||||
|
ParsableByteArray parser = new ParsableByteArray(bytes);
|
||||||
|
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16BE)).isEqualTo("foo");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(12);
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16BE)).isEqualTo("");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(14);
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16BE)).isEqualTo("bar");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(20);
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16BE)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readFourLinesWithLfFollowedByCr_utf16be() {
|
||||||
|
byte[] bytes = "foo\n\r\rbar\r\n".getBytes(Charsets.UTF_16BE);
|
||||||
|
ParsableByteArray parser = new ParsableByteArray(bytes);
|
||||||
|
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16BE)).isEqualTo("foo");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(8);
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16BE)).isEqualTo("");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(10);
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16BE)).isEqualTo("");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(12);
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16BE)).isEqualTo("bar");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(22);
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16BE)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readSingleLineWithoutEndingTrail_utf16le() {
|
||||||
|
byte[] bytes = "foo".getBytes(Charsets.UTF_16LE);
|
||||||
|
ParsableByteArray parser = new ParsableByteArray(bytes);
|
||||||
|
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16LE)).isEqualTo("foo");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(6);
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16LE)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readSingleLineWithEndingLf_utf16le() {
|
||||||
|
byte[] bytes = "foo\n".getBytes(Charsets.UTF_16LE);
|
||||||
|
ParsableByteArray parser = new ParsableByteArray(bytes);
|
||||||
|
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16LE)).isEqualTo("foo");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(8);
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16LE)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readTwoLinesWithCrFollowedByLf_utf16le() {
|
||||||
|
byte[] bytes = "foo\r\nbar".getBytes(Charsets.UTF_16LE);
|
||||||
|
ParsableByteArray parser = new ParsableByteArray(bytes);
|
||||||
|
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16LE)).isEqualTo("foo");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(10);
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16LE)).isEqualTo("bar");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(16);
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16LE)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readThreeLinesWithEmptyLineAndLeadingBom_utf16le() {
|
||||||
|
byte[] bytes =
|
||||||
|
Bytes.concat(createByteArray(0xFF, 0xFE), "foo\r\n\rbar".getBytes(Charsets.UTF_16LE));
|
||||||
|
ParsableByteArray parser = new ParsableByteArray(bytes);
|
||||||
|
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16LE)).isEqualTo("foo");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(12);
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16LE)).isEqualTo("");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(14);
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16LE)).isEqualTo("bar");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(20);
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16LE)).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void readFourLinesWithLfFollowedByCr_utf16le() {
|
||||||
|
byte[] bytes = "foo\n\r\rbar\r\n".getBytes(Charsets.UTF_16LE);
|
||||||
|
ParsableByteArray parser = new ParsableByteArray(bytes);
|
||||||
|
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16LE)).isEqualTo("foo");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(8);
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16LE)).isEqualTo("");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(10);
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16LE)).isEqualTo("");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(12);
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16LE)).isEqualTo("bar");
|
||||||
|
assertThat(parser.getPosition()).isEqualTo(22);
|
||||||
|
assertThat(parser.readLine(Charsets.UTF_16LE)).isNull();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -5,7 +5,6 @@ will not normally need to depend on this module directly.
|
|||||||
|
|
||||||
## Links
|
## Links
|
||||||
|
|
||||||
* [Javadoc][]: Classes matching `androidx.media3.database.*` belong to this
|
* [Javadoc][]
|
||||||
module.
|
|
||||||
|
|
||||||
[Javadoc]: https://exoplayer.dev/doc/reference/index.html
|
[Javadoc]: https://developer.android.com/reference/androidx/media3/packages
|
||||||
|
@ -3,3 +3,9 @@
|
|||||||
Provides a `DataSource` abstraction and a number of concrete implementations for
|
Provides a `DataSource` abstraction and a number of concrete implementations for
|
||||||
reading data from different sources. Application code will not normally need to
|
reading data from different sources. Application code will not normally need to
|
||||||
depend on this module directly.
|
depend on this module directly.
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
* [Javadoc][]
|
||||||
|
|
||||||
|
[Javadoc]: https://developer.android.com/reference/androidx/media3/packages
|
||||||
|
@ -119,3 +119,9 @@ whilst still using Cronet Fallback for other networking performed by your
|
|||||||
application.
|
application.
|
||||||
|
|
||||||
[Send a simple request]: https://developer.android.com/guide/topics/connectivity/cronet/start
|
[Send a simple request]: https://developer.android.com/guide/topics/connectivity/cronet/start
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
* [Javadoc][]
|
||||||
|
|
||||||
|
[Javadoc]: https://developer.android.com/reference/androidx/media3/packages
|
||||||
|
@ -48,3 +48,9 @@ new DefaultDataSourceFactory(
|
|||||||
...
|
...
|
||||||
/* baseDataSourceFactory= */ new OkHttpDataSource.Factory(...));
|
/* baseDataSourceFactory= */ new OkHttpDataSource.Factory(...));
|
||||||
```
|
```
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
* [Javadoc][]
|
||||||
|
|
||||||
|
[Javadoc]: https://developer.android.com/reference/androidx/media3/packages
|
||||||
|
@ -45,3 +45,9 @@ application code are required. Alternatively, if you know that your application
|
|||||||
doesn't need to handle any other protocols, you can update any
|
doesn't need to handle any other protocols, you can update any
|
||||||
`DataSource.Factory` instantiations in your application code to use
|
`DataSource.Factory` instantiations in your application code to use
|
||||||
`RtmpDataSource.Factory` directly.
|
`RtmpDataSource.Factory` directly.
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
* [Javadoc][]
|
||||||
|
|
||||||
|
[Javadoc]: https://developer.android.com/reference/androidx/media3/packages
|
||||||
|
@ -2,3 +2,9 @@
|
|||||||
|
|
||||||
Provides a decoder abstraction. Application code will not normally need to
|
Provides a decoder abstraction. Application code will not normally need to
|
||||||
depend on this module directly.
|
depend on this module directly.
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
* [Javadoc][]
|
||||||
|
|
||||||
|
[Javadoc]: https://developer.android.com/reference/androidx/media3/packages
|
||||||
|
@ -123,3 +123,11 @@ gets from the libgav1 decoder:
|
|||||||
|
|
||||||
Note: Although the default option uses `ANativeWindow`, based on our testing the
|
Note: Although the default option uses `ANativeWindow`, based on our testing the
|
||||||
GL rendering mode has better performance, so should be preferred
|
GL rendering mode has better performance, so should be preferred
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
<!-- TODO(b/204738828): Add link to 'troubleshooting using decoding extensions' media3 guide entry when it's published on developer.android.com -->
|
||||||
|
|
||||||
|
* [Javadoc][]
|
||||||
|
|
||||||
|
[Javadoc]: https://developer.android.com/reference/androidx/media3/packages
|
||||||
|
@ -116,3 +116,11 @@ then implement your own logic to use the renderer for a given track.
|
|||||||
[Android NDK]: https://developer.android.com/tools/sdk/ndk/index.html
|
[Android NDK]: https://developer.android.com/tools/sdk/ndk/index.html
|
||||||
[ExoPlayer issue 2781]: https://github.com/google/ExoPlayer/issues/2781
|
[ExoPlayer issue 2781]: https://github.com/google/ExoPlayer/issues/2781
|
||||||
[Supported formats]: https://exoplayer.dev/supported-formats.html#ffmpeg-extension
|
[Supported formats]: https://exoplayer.dev/supported-formats.html#ffmpeg-extension
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
<!-- TODO(b/204738828): Add link to 'troubleshooting using extensions' media3 guide entry when it's published on developer.android.com -->
|
||||||
|
|
||||||
|
* [Javadoc][]
|
||||||
|
|
||||||
|
[Javadoc]: https://developer.android.com/reference/androidx/media3/packages
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
# See the License for the specific language governing permissions and
|
# See the License for the specific language governing permissions and
|
||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
set -eu
|
||||||
|
|
||||||
FFMPEG_MODULE_PATH=$1
|
FFMPEG_MODULE_PATH=$1
|
||||||
NDK_PATH=$2
|
NDK_PATH=$2
|
||||||
|
@ -95,3 +95,11 @@ Note: These instructions assume you're using `DefaultTrackSelector`. If you have
|
|||||||
a custom track selector the choice of `Renderer` is up to your implementation,
|
a custom track selector the choice of `Renderer` is up to your implementation,
|
||||||
so you need to make sure you are passing an `LibflacAudioRenderer` to the
|
so you need to make sure you are passing an `LibflacAudioRenderer` to the
|
||||||
player, then implement your own logic to use the renderer for a given track.
|
player, then implement your own logic to use the renderer for a given track.
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
<!-- TODO(b/204738828): Add link to 'troubleshooting decoding using extensions' media3 guide entry when it's published on developer.android.com -->
|
||||||
|
|
||||||
|
* [Javadoc][]
|
||||||
|
|
||||||
|
[Javadoc]: https://developer.android.com/reference/androidx/media3/packages
|
||||||
|
@ -99,3 +99,11 @@ Note: These instructions assume you're using `DefaultTrackSelector`. If you have
|
|||||||
a custom track selector the choice of `Renderer` is up to your implementation,
|
a custom track selector the choice of `Renderer` is up to your implementation,
|
||||||
so you need to make sure you are passing an `LibopusAudioRenderer` to the
|
so you need to make sure you are passing an `LibopusAudioRenderer` to the
|
||||||
player, then implement your own logic to use the renderer for a given track.
|
player, then implement your own logic to use the renderer for a given track.
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
<!-- TODO(b/204738828): Add link to 'troubleshooting using decoding extensions' media3 guide entry when it's published on developer.android.com -->
|
||||||
|
|
||||||
|
* [Javadoc][]
|
||||||
|
|
||||||
|
[Javadoc]: https://developer.android.com/reference/androidx/media3/packages
|
||||||
|
@ -15,7 +15,7 @@
|
|||||||
# limitations under the License.
|
# limitations under the License.
|
||||||
#
|
#
|
||||||
|
|
||||||
set -e
|
set -eu
|
||||||
ASM_CONVERTER="./libopus/celt/arm/arm2gnu.pl"
|
ASM_CONVERTER="./libopus/celt/arm/arm2gnu.pl"
|
||||||
|
|
||||||
if [[ ! -x "${ASM_CONVERTER}" ]]; then
|
if [[ ! -x "${ASM_CONVERTER}" ]]; then
|
||||||
|
@ -136,3 +136,11 @@ gets from the libvpx decoder:
|
|||||||
|
|
||||||
Note: Although the default option uses `ANativeWindow`, based on our testing the
|
Note: Although the default option uses `ANativeWindow`, based on our testing the
|
||||||
GL rendering mode has better performance, so should be preferred.
|
GL rendering mode has better performance, so should be preferred.
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
<!-- TODO(b/204738828): Add link to 'troubleshooting using decoding extensions' media3 guide entry when it's published on developer.android.com -->
|
||||||
|
|
||||||
|
* [Javadoc][]
|
||||||
|
|
||||||
|
[Javadoc]: https://developer.android.com/reference/androidx/media3/packages
|
||||||
|
@ -18,7 +18,7 @@
|
|||||||
# a bash script that generates the necessary config files for libvpx android ndk
|
# a bash script that generates the necessary config files for libvpx android ndk
|
||||||
# builds.
|
# builds.
|
||||||
|
|
||||||
set -e
|
set -eu
|
||||||
|
|
||||||
if [ $# -ne 0 ]; then
|
if [ $# -ne 0 ]; then
|
||||||
echo "Usage: ${0}"
|
echo "Usage: ${0}"
|
||||||
|
@ -17,3 +17,9 @@ Alternatively, you can clone this GitHub project and depend on the module
|
|||||||
locally. Instructions for doing this can be found in the [top level README][].
|
locally. Instructions for doing this can be found in the [top level README][].
|
||||||
|
|
||||||
[top level README]: ../../README.md
|
[top level README]: ../../README.md
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
* [Javadoc][]
|
||||||
|
|
||||||
|
[Javadoc]: https://developer.android.com/reference/androidx/media3/packages
|
||||||
|
@ -18,3 +18,9 @@ Alternatively, you can clone this GitHub project and depend on the module
|
|||||||
locally. Instructions for doing this can be found in the [top level README][].
|
locally. Instructions for doing this can be found in the [top level README][].
|
||||||
|
|
||||||
[top level README]: ../../README.md
|
[top level README]: ../../README.md
|
||||||
|
|
||||||
|
## Links
|
||||||
|
|
||||||
|
* [Javadoc][]
|
||||||
|
|
||||||
|
[Javadoc]: https://developer.android.com/reference/androidx/media3/packages
|
||||||
|
@ -250,17 +250,15 @@ public final class ExoPlaybackException extends PlaybackException {
|
|||||||
|
|
||||||
private ExoPlaybackException(Bundle bundle) {
|
private ExoPlaybackException(Bundle bundle) {
|
||||||
super(bundle);
|
super(bundle);
|
||||||
type = bundle.getInt(keyForField(FIELD_TYPE), /* defaultValue= */ TYPE_UNEXPECTED);
|
type = bundle.getInt(FIELD_TYPE, /* defaultValue= */ TYPE_UNEXPECTED);
|
||||||
rendererName = bundle.getString(keyForField(FIELD_RENDERER_NAME));
|
rendererName = bundle.getString(FIELD_RENDERER_NAME);
|
||||||
rendererIndex =
|
rendererIndex = bundle.getInt(FIELD_RENDERER_INDEX, /* defaultValue= */ C.INDEX_UNSET);
|
||||||
bundle.getInt(keyForField(FIELD_RENDERER_INDEX), /* defaultValue= */ C.INDEX_UNSET);
|
@Nullable Bundle rendererFormatBundle = bundle.getBundle(FIELD_RENDERER_FORMAT);
|
||||||
@Nullable Bundle rendererFormatBundle = bundle.getBundle(keyForField(FIELD_RENDERER_FORMAT));
|
|
||||||
rendererFormat =
|
rendererFormat =
|
||||||
rendererFormatBundle == null ? null : Format.CREATOR.fromBundle(rendererFormatBundle);
|
rendererFormatBundle == null ? null : Format.CREATOR.fromBundle(rendererFormatBundle);
|
||||||
rendererFormatSupport =
|
rendererFormatSupport =
|
||||||
bundle.getInt(
|
bundle.getInt(FIELD_RENDERER_FORMAT_SUPPORT, /* defaultValue= */ C.FORMAT_HANDLED);
|
||||||
keyForField(FIELD_RENDERER_FORMAT_SUPPORT), /* defaultValue= */ C.FORMAT_HANDLED);
|
isRecoverable = bundle.getBoolean(FIELD_IS_RECOVERABLE, /* defaultValue= */ false);
|
||||||
isRecoverable = bundle.getBoolean(keyForField(FIELD_IS_RECOVERABLE), /* defaultValue= */ false);
|
|
||||||
mediaPeriodId = null;
|
mediaPeriodId = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -403,12 +401,17 @@ public final class ExoPlaybackException extends PlaybackException {
|
|||||||
@UnstableApi
|
@UnstableApi
|
||||||
public static final Creator<ExoPlaybackException> CREATOR = ExoPlaybackException::new;
|
public static final Creator<ExoPlaybackException> CREATOR = ExoPlaybackException::new;
|
||||||
|
|
||||||
private static final int FIELD_TYPE = FIELD_CUSTOM_ID_BASE + 1;
|
private static final String FIELD_TYPE = Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 1);
|
||||||
private static final int FIELD_RENDERER_NAME = FIELD_CUSTOM_ID_BASE + 2;
|
private static final String FIELD_RENDERER_NAME =
|
||||||
private static final int FIELD_RENDERER_INDEX = FIELD_CUSTOM_ID_BASE + 3;
|
Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 2);
|
||||||
private static final int FIELD_RENDERER_FORMAT = FIELD_CUSTOM_ID_BASE + 4;
|
private static final String FIELD_RENDERER_INDEX =
|
||||||
private static final int FIELD_RENDERER_FORMAT_SUPPORT = FIELD_CUSTOM_ID_BASE + 5;
|
Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 3);
|
||||||
private static final int FIELD_IS_RECOVERABLE = FIELD_CUSTOM_ID_BASE + 6;
|
private static final String FIELD_RENDERER_FORMAT =
|
||||||
|
Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 4);
|
||||||
|
private static final String FIELD_RENDERER_FORMAT_SUPPORT =
|
||||||
|
Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 5);
|
||||||
|
private static final String FIELD_IS_RECOVERABLE =
|
||||||
|
Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 6);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
@ -420,14 +423,14 @@ public final class ExoPlaybackException extends PlaybackException {
|
|||||||
@Override
|
@Override
|
||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
Bundle bundle = super.toBundle();
|
Bundle bundle = super.toBundle();
|
||||||
bundle.putInt(keyForField(FIELD_TYPE), type);
|
bundle.putInt(FIELD_TYPE, type);
|
||||||
bundle.putString(keyForField(FIELD_RENDERER_NAME), rendererName);
|
bundle.putString(FIELD_RENDERER_NAME, rendererName);
|
||||||
bundle.putInt(keyForField(FIELD_RENDERER_INDEX), rendererIndex);
|
bundle.putInt(FIELD_RENDERER_INDEX, rendererIndex);
|
||||||
if (rendererFormat != null) {
|
if (rendererFormat != null) {
|
||||||
bundle.putBundle(keyForField(FIELD_RENDERER_FORMAT), rendererFormat.toBundle());
|
bundle.putBundle(FIELD_RENDERER_FORMAT, rendererFormat.toBundle());
|
||||||
}
|
}
|
||||||
bundle.putInt(keyForField(FIELD_RENDERER_FORMAT_SUPPORT), rendererFormatSupport);
|
bundle.putInt(FIELD_RENDERER_FORMAT_SUPPORT, rendererFormatSupport);
|
||||||
bundle.putBoolean(keyForField(FIELD_IS_RECOVERABLE), isRecoverable);
|
bundle.putBoolean(FIELD_IS_RECOVERABLE, isRecoverable);
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -24,6 +24,7 @@ import android.media.AudioDeviceInfo;
|
|||||||
import android.media.AudioTrack;
|
import android.media.AudioTrack;
|
||||||
import android.media.MediaCodec;
|
import android.media.MediaCodec;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
|
import android.os.Process;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import android.view.SurfaceHolder;
|
import android.view.SurfaceHolder;
|
||||||
import android.view.SurfaceView;
|
import android.view.SurfaceView;
|
||||||
@ -131,15 +132,15 @@ import java.util.List;
|
|||||||
* threading model">
|
* threading model">
|
||||||
*
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>ExoPlayer instances must be accessed from a single application thread. For the vast
|
* <li>ExoPlayer instances must be accessed from a single application thread unless indicated
|
||||||
* majority of cases this should be the application's main thread. Using the application's
|
* otherwise. For the vast majority of cases this should be the application's main thread.
|
||||||
* main thread is also a requirement when using ExoPlayer's UI components or the IMA
|
* Using the application's main thread is also a requirement when using ExoPlayer's UI
|
||||||
* extension. The thread on which an ExoPlayer instance must be accessed can be explicitly
|
* components or the IMA extension. The thread on which an ExoPlayer instance must be accessed
|
||||||
* specified by passing a `Looper` when creating the player. If no `Looper` is specified, then
|
* can be explicitly specified by passing a `Looper` when creating the player. If no `Looper`
|
||||||
* the `Looper` of the thread that the player is created on is used, or if that thread does
|
* is specified, then the `Looper` of the thread that the player is created on is used, or if
|
||||||
* not have a `Looper`, the `Looper` of the application's main thread is used. In all cases
|
* that thread does not have a `Looper`, the `Looper` of the application's main thread is
|
||||||
* the `Looper` of the thread from which the player must be accessed can be queried using
|
* used. In all cases the `Looper` of the thread from which the player must be accessed can be
|
||||||
* {@link #getApplicationLooper()}.
|
* queried using {@link #getApplicationLooper()}.
|
||||||
* <li>Registered listeners are called on the thread associated with {@link
|
* <li>Registered listeners are called on the thread associated with {@link
|
||||||
* #getApplicationLooper()}. Note that this means registered listeners are called on the same
|
* #getApplicationLooper()}. Note that this means registered listeners are called on the same
|
||||||
* thread which must be used to access the player.
|
* thread which must be used to access the player.
|
||||||
@ -485,6 +486,7 @@ public interface ExoPlayer extends Player {
|
|||||||
/* package */ long detachSurfaceTimeoutMs;
|
/* package */ long detachSurfaceTimeoutMs;
|
||||||
/* package */ boolean pauseAtEndOfMediaItems;
|
/* package */ boolean pauseAtEndOfMediaItems;
|
||||||
/* package */ boolean usePlatformDiagnostics;
|
/* package */ boolean usePlatformDiagnostics;
|
||||||
|
@Nullable /* package */ Looper playbackLooper;
|
||||||
/* package */ boolean buildCalled;
|
/* package */ boolean buildCalled;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -527,6 +529,7 @@ public interface ExoPlayer extends Player {
|
|||||||
* <li>{@code pauseAtEndOfMediaItems}: {@code false}
|
* <li>{@code pauseAtEndOfMediaItems}: {@code false}
|
||||||
* <li>{@code usePlatformDiagnostics}: {@code true}
|
* <li>{@code usePlatformDiagnostics}: {@code true}
|
||||||
* <li>{@link Clock}: {@link Clock#DEFAULT}
|
* <li>{@link Clock}: {@link Clock#DEFAULT}
|
||||||
|
* <li>{@code playbackLooper}: {@code null} (create new thread)
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* @param context A {@link Context}.
|
* @param context A {@link Context}.
|
||||||
@ -1134,6 +1137,24 @@ public interface ExoPlayer extends Player {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link Looper} that will be used for playback.
|
||||||
|
*
|
||||||
|
* <p>The backing thread should run with priority {@link Process#THREAD_PRIORITY_AUDIO} and
|
||||||
|
* should handle messages within 10ms.
|
||||||
|
*
|
||||||
|
* @param playbackLooper A {@link looper}.
|
||||||
|
* @return This builder.
|
||||||
|
* @throws IllegalStateException If {@link #build()} has already been called.
|
||||||
|
*/
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
@UnstableApi
|
||||||
|
public Builder setPlaybackLooper(Looper playbackLooper) {
|
||||||
|
checkState(!buildCalled);
|
||||||
|
this.playbackLooper = playbackLooper;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds an {@link ExoPlayer} instance.
|
* Builds an {@link ExoPlayer} instance.
|
||||||
*
|
*
|
||||||
@ -1208,6 +1229,8 @@ public interface ExoPlayer extends Player {
|
|||||||
/**
|
/**
|
||||||
* Adds a listener to receive audio offload events.
|
* Adds a listener to receive audio offload events.
|
||||||
*
|
*
|
||||||
|
* <p>This method can be called from any thread.
|
||||||
|
*
|
||||||
* @param listener The listener to register.
|
* @param listener The listener to register.
|
||||||
*/
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@ -1228,6 +1251,8 @@ public interface ExoPlayer extends Player {
|
|||||||
/**
|
/**
|
||||||
* Adds an {@link AnalyticsListener} to receive analytics events.
|
* Adds an {@link AnalyticsListener} to receive analytics events.
|
||||||
*
|
*
|
||||||
|
* <p>This method can be called from any thread.
|
||||||
|
*
|
||||||
* @param listener The listener to be added.
|
* @param listener The listener to be added.
|
||||||
*/
|
*/
|
||||||
void addAnalyticsListener(AnalyticsListener listener);
|
void addAnalyticsListener(AnalyticsListener listener);
|
||||||
@ -1293,11 +1318,19 @@ public interface ExoPlayer extends Player {
|
|||||||
@Deprecated
|
@Deprecated
|
||||||
TrackSelectionArray getCurrentTrackSelections();
|
TrackSelectionArray getCurrentTrackSelections();
|
||||||
|
|
||||||
/** Returns the {@link Looper} associated with the playback thread. */
|
/**
|
||||||
|
* Returns the {@link Looper} associated with the playback thread.
|
||||||
|
*
|
||||||
|
* <p>This method may be called from any thread.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
Looper getPlaybackLooper();
|
Looper getPlaybackLooper();
|
||||||
|
|
||||||
/** Returns the {@link Clock} used for playback. */
|
/**
|
||||||
|
* Returns the {@link Clock} used for playback.
|
||||||
|
*
|
||||||
|
* <p>This method can be called from any thread.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
Clock getClock();
|
Clock getClock();
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ package androidx.media3.exoplayer;
|
|||||||
import static androidx.media3.common.C.TRACK_TYPE_AUDIO;
|
import static androidx.media3.common.C.TRACK_TYPE_AUDIO;
|
||||||
import static androidx.media3.common.C.TRACK_TYPE_CAMERA_MOTION;
|
import static androidx.media3.common.C.TRACK_TYPE_CAMERA_MOTION;
|
||||||
import static androidx.media3.common.C.TRACK_TYPE_VIDEO;
|
import static androidx.media3.common.C.TRACK_TYPE_VIDEO;
|
||||||
|
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
import static androidx.media3.common.util.Assertions.checkState;
|
import static androidx.media3.common.util.Assertions.checkState;
|
||||||
import static androidx.media3.common.util.Util.castNonNull;
|
import static androidx.media3.common.util.Util.castNonNull;
|
||||||
@ -76,7 +77,6 @@ import androidx.media3.common.Tracks;
|
|||||||
import androidx.media3.common.VideoSize;
|
import androidx.media3.common.VideoSize;
|
||||||
import androidx.media3.common.text.Cue;
|
import androidx.media3.common.text.Cue;
|
||||||
import androidx.media3.common.text.CueGroup;
|
import androidx.media3.common.text.CueGroup;
|
||||||
import androidx.media3.common.util.Assertions;
|
|
||||||
import androidx.media3.common.util.Clock;
|
import androidx.media3.common.util.Clock;
|
||||||
import androidx.media3.common.util.ConditionVariable;
|
import androidx.media3.common.util.ConditionVariable;
|
||||||
import androidx.media3.common.util.HandlerWrapper;
|
import androidx.media3.common.util.HandlerWrapper;
|
||||||
@ -88,6 +88,7 @@ import androidx.media3.exoplayer.PlayerMessage.Target;
|
|||||||
import androidx.media3.exoplayer.Renderer.MessageType;
|
import androidx.media3.exoplayer.Renderer.MessageType;
|
||||||
import androidx.media3.exoplayer.analytics.AnalyticsCollector;
|
import androidx.media3.exoplayer.analytics.AnalyticsCollector;
|
||||||
import androidx.media3.exoplayer.analytics.AnalyticsListener;
|
import androidx.media3.exoplayer.analytics.AnalyticsListener;
|
||||||
|
import androidx.media3.exoplayer.analytics.DefaultAnalyticsCollector;
|
||||||
import androidx.media3.exoplayer.analytics.MediaMetricsListener;
|
import androidx.media3.exoplayer.analytics.MediaMetricsListener;
|
||||||
import androidx.media3.exoplayer.analytics.PlayerId;
|
import androidx.media3.exoplayer.analytics.PlayerId;
|
||||||
import androidx.media3.exoplayer.audio.AudioRendererEventListener;
|
import androidx.media3.exoplayer.audio.AudioRendererEventListener;
|
||||||
@ -345,7 +346,8 @@ import java.util.concurrent.TimeoutException;
|
|||||||
applicationLooper,
|
applicationLooper,
|
||||||
clock,
|
clock,
|
||||||
playbackInfoUpdateListener,
|
playbackInfoUpdateListener,
|
||||||
playerId);
|
playerId,
|
||||||
|
builder.playbackLooper);
|
||||||
|
|
||||||
volume = 1;
|
volume = 1;
|
||||||
repeatMode = Player.REPEAT_MODE_OFF;
|
repeatMode = Player.REPEAT_MODE_OFF;
|
||||||
@ -478,7 +480,7 @@ import java.util.concurrent.TimeoutException;
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeAudioOffloadListener(AudioOffloadListener listener) {
|
public void removeAudioOffloadListener(AudioOffloadListener listener) {
|
||||||
// Don't verify application thread. We allow calls to this method from any thread.
|
verifyApplicationThread();
|
||||||
audioOffloadListeners.remove(listener);
|
audioOffloadListeners.remove(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -621,7 +623,6 @@ import java.util.concurrent.TimeoutException;
|
|||||||
@Override
|
@Override
|
||||||
public void addMediaItems(int index, List<MediaItem> mediaItems) {
|
public void addMediaItems(int index, List<MediaItem> mediaItems) {
|
||||||
verifyApplicationThread();
|
verifyApplicationThread();
|
||||||
index = min(index, mediaSourceHolderSnapshots.size());
|
|
||||||
addMediaSources(index, createMediaSources(mediaItems));
|
addMediaSources(index, createMediaSources(mediaItems));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -646,7 +647,8 @@ import java.util.concurrent.TimeoutException;
|
|||||||
@Override
|
@Override
|
||||||
public void addMediaSources(int index, List<MediaSource> mediaSources) {
|
public void addMediaSources(int index, List<MediaSource> mediaSources) {
|
||||||
verifyApplicationThread();
|
verifyApplicationThread();
|
||||||
Assertions.checkArgument(index >= 0);
|
checkArgument(index >= 0);
|
||||||
|
index = min(index, mediaSourceHolderSnapshots.size());
|
||||||
Timeline oldTimeline = getCurrentTimeline();
|
Timeline oldTimeline = getCurrentTimeline();
|
||||||
pendingOperationAcks++;
|
pendingOperationAcks++;
|
||||||
List<MediaSourceList.MediaSourceHolder> holders = addMediaSourceHolders(index, mediaSources);
|
List<MediaSourceList.MediaSourceHolder> holders = addMediaSourceHolders(index, mediaSources);
|
||||||
@ -672,7 +674,13 @@ import java.util.concurrent.TimeoutException;
|
|||||||
@Override
|
@Override
|
||||||
public void removeMediaItems(int fromIndex, int toIndex) {
|
public void removeMediaItems(int fromIndex, int toIndex) {
|
||||||
verifyApplicationThread();
|
verifyApplicationThread();
|
||||||
toIndex = min(toIndex, mediaSourceHolderSnapshots.size());
|
checkArgument(fromIndex >= 0 && toIndex >= fromIndex);
|
||||||
|
int playlistSize = mediaSourceHolderSnapshots.size();
|
||||||
|
toIndex = min(toIndex, playlistSize);
|
||||||
|
if (fromIndex >= playlistSize || fromIndex == toIndex) {
|
||||||
|
// Do nothing.
|
||||||
|
return;
|
||||||
|
}
|
||||||
PlaybackInfo newPlaybackInfo = removeMediaItemsInternal(fromIndex, toIndex);
|
PlaybackInfo newPlaybackInfo = removeMediaItemsInternal(fromIndex, toIndex);
|
||||||
boolean positionDiscontinuity =
|
boolean positionDiscontinuity =
|
||||||
!newPlaybackInfo.periodId.periodUid.equals(playbackInfo.periodId.periodUid);
|
!newPlaybackInfo.periodId.periodUid.equals(playbackInfo.periodId.periodUid);
|
||||||
@ -691,14 +699,16 @@ import java.util.concurrent.TimeoutException;
|
|||||||
@Override
|
@Override
|
||||||
public void moveMediaItems(int fromIndex, int toIndex, int newFromIndex) {
|
public void moveMediaItems(int fromIndex, int toIndex, int newFromIndex) {
|
||||||
verifyApplicationThread();
|
verifyApplicationThread();
|
||||||
Assertions.checkArgument(
|
checkArgument(fromIndex >= 0 && fromIndex <= toIndex && newFromIndex >= 0);
|
||||||
fromIndex >= 0
|
int playlistSize = mediaSourceHolderSnapshots.size();
|
||||||
&& fromIndex <= toIndex
|
toIndex = min(toIndex, playlistSize);
|
||||||
&& toIndex <= mediaSourceHolderSnapshots.size()
|
newFromIndex = min(newFromIndex, playlistSize - (toIndex - fromIndex));
|
||||||
&& newFromIndex >= 0);
|
if (fromIndex >= playlistSize || fromIndex == toIndex || fromIndex == newFromIndex) {
|
||||||
|
// Do nothing.
|
||||||
|
return;
|
||||||
|
}
|
||||||
Timeline oldTimeline = getCurrentTimeline();
|
Timeline oldTimeline = getCurrentTimeline();
|
||||||
pendingOperationAcks++;
|
pendingOperationAcks++;
|
||||||
newFromIndex = min(newFromIndex, mediaSourceHolderSnapshots.size() - (toIndex - fromIndex));
|
|
||||||
Util.moveItems(mediaSourceHolderSnapshots, fromIndex, toIndex, newFromIndex);
|
Util.moveItems(mediaSourceHolderSnapshots, fromIndex, toIndex, newFromIndex);
|
||||||
Timeline newTimeline = createMaskingTimeline();
|
Timeline newTimeline = createMaskingTimeline();
|
||||||
PlaybackInfo newPlaybackInfo =
|
PlaybackInfo newPlaybackInfo =
|
||||||
@ -821,16 +831,51 @@ import java.util.concurrent.TimeoutException;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void repeatCurrentMediaItem() {
|
public void seekTo(
|
||||||
|
int mediaItemIndex,
|
||||||
|
long positionMs,
|
||||||
|
@Player.Command int seekCommand,
|
||||||
|
boolean isRepeatingCurrentItem) {
|
||||||
verifyApplicationThread();
|
verifyApplicationThread();
|
||||||
seekToInternal(
|
checkArgument(mediaItemIndex >= 0);
|
||||||
getCurrentMediaItemIndex(), /* positionMs= */ C.TIME_UNSET, /* repeatMediaItem= */ true);
|
analyticsCollector.notifySeekStarted();
|
||||||
|
Timeline timeline = playbackInfo.timeline;
|
||||||
|
if (!timeline.isEmpty() && mediaItemIndex >= timeline.getWindowCount()) {
|
||||||
|
return;
|
||||||
}
|
}
|
||||||
|
pendingOperationAcks++;
|
||||||
@Override
|
if (isPlayingAd()) {
|
||||||
public void seekTo(int mediaItemIndex, long positionMs) {
|
// TODO: Investigate adding support for seeking during ads. This is complicated to do in
|
||||||
verifyApplicationThread();
|
// general because the midroll ad preceding the seek destination must be played before the
|
||||||
seekToInternal(mediaItemIndex, positionMs, /* repeatMediaItem= */ false);
|
// content position can be played, if a different ad is playing at the moment.
|
||||||
|
Log.w(TAG, "seekTo ignored because an ad is playing");
|
||||||
|
ExoPlayerImplInternal.PlaybackInfoUpdate playbackInfoUpdate =
|
||||||
|
new ExoPlayerImplInternal.PlaybackInfoUpdate(this.playbackInfo);
|
||||||
|
playbackInfoUpdate.incrementPendingOperationAcks(1);
|
||||||
|
playbackInfoUpdateListener.onPlaybackInfoUpdate(playbackInfoUpdate);
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
@Player.State
|
||||||
|
int newPlaybackState =
|
||||||
|
getPlaybackState() == Player.STATE_IDLE ? Player.STATE_IDLE : STATE_BUFFERING;
|
||||||
|
int oldMaskingMediaItemIndex = getCurrentMediaItemIndex();
|
||||||
|
PlaybackInfo newPlaybackInfo = playbackInfo.copyWithPlaybackState(newPlaybackState);
|
||||||
|
newPlaybackInfo =
|
||||||
|
maskTimelineAndPosition(
|
||||||
|
newPlaybackInfo,
|
||||||
|
timeline,
|
||||||
|
maskWindowPositionMsOrGetPeriodPositionUs(timeline, mediaItemIndex, positionMs));
|
||||||
|
internalPlayer.seekTo(timeline, mediaItemIndex, Util.msToUs(positionMs));
|
||||||
|
updatePlaybackInfo(
|
||||||
|
newPlaybackInfo,
|
||||||
|
/* ignored */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
|
||||||
|
/* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
|
||||||
|
/* seekProcessed= */ true,
|
||||||
|
/* positionDiscontinuity= */ true,
|
||||||
|
/* positionDiscontinuityReason= */ DISCONTINUITY_REASON_SEEK,
|
||||||
|
/* discontinuityWindowStartPositionUs= */ getCurrentPositionUsInternal(newPlaybackInfo),
|
||||||
|
oldMaskingMediaItemIndex,
|
||||||
|
isRepeatingCurrentItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -1486,7 +1531,7 @@ import java.util.concurrent.TimeoutException;
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeAnalyticsListener(AnalyticsListener listener) {
|
public void removeAnalyticsListener(AnalyticsListener listener) {
|
||||||
// Don't verify application thread. We allow calls to this method from any thread.
|
verifyApplicationThread();
|
||||||
analyticsCollector.removeListener(checkNotNull(listener));
|
analyticsCollector.removeListener(checkNotNull(listener));
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1603,9 +1648,8 @@ import java.util.concurrent.TimeoutException;
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeListener(Listener listener) {
|
public void removeListener(Listener listener) {
|
||||||
// Don't verify application thread. We allow calls to this method from any thread.
|
verifyApplicationThread();
|
||||||
checkNotNull(listener);
|
listeners.remove(checkNotNull(listener));
|
||||||
listeners.remove(listener);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -1688,8 +1732,14 @@ import java.util.concurrent.TimeoutException;
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation") // Calling deprecated methods.
|
||||||
/* package */ void setThrowsWhenUsingWrongThread(boolean throwsWhenUsingWrongThread) {
|
/* package */ void setThrowsWhenUsingWrongThread(boolean throwsWhenUsingWrongThread) {
|
||||||
this.throwsWhenUsingWrongThread = throwsWhenUsingWrongThread;
|
this.throwsWhenUsingWrongThread = throwsWhenUsingWrongThread;
|
||||||
|
listeners.setThrowsWhenUsingWrongThread(throwsWhenUsingWrongThread);
|
||||||
|
if (analyticsCollector instanceof DefaultAnalyticsCollector) {
|
||||||
|
((DefaultAnalyticsCollector) analyticsCollector)
|
||||||
|
.setThrowsWhenUsingWrongThread(throwsWhenUsingWrongThread);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2219,8 +2269,6 @@ import java.util.concurrent.TimeoutException;
|
|||||||
}
|
}
|
||||||
|
|
||||||
private PlaybackInfo removeMediaItemsInternal(int fromIndex, int toIndex) {
|
private PlaybackInfo removeMediaItemsInternal(int fromIndex, int toIndex) {
|
||||||
Assertions.checkArgument(
|
|
||||||
fromIndex >= 0 && toIndex >= fromIndex && toIndex <= mediaSourceHolderSnapshots.size());
|
|
||||||
int currentIndex = getCurrentMediaItemIndex();
|
int currentIndex = getCurrentMediaItemIndex();
|
||||||
Timeline oldTimeline = getCurrentTimeline();
|
Timeline oldTimeline = getCurrentTimeline();
|
||||||
int currentMediaSourceCount = mediaSourceHolderSnapshots.size();
|
int currentMediaSourceCount = mediaSourceHolderSnapshots.size();
|
||||||
@ -2259,7 +2307,7 @@ import java.util.concurrent.TimeoutException;
|
|||||||
|
|
||||||
private PlaybackInfo maskTimelineAndPosition(
|
private PlaybackInfo maskTimelineAndPosition(
|
||||||
PlaybackInfo playbackInfo, Timeline timeline, @Nullable Pair<Object, Long> periodPositionUs) {
|
PlaybackInfo playbackInfo, Timeline timeline, @Nullable Pair<Object, Long> periodPositionUs) {
|
||||||
Assertions.checkArgument(timeline.isEmpty() || periodPositionUs != null);
|
checkArgument(timeline.isEmpty() || periodPositionUs != null);
|
||||||
Timeline oldTimeline = playbackInfo.timeline;
|
Timeline oldTimeline = playbackInfo.timeline;
|
||||||
// Mask the timeline.
|
// Mask the timeline.
|
||||||
playbackInfo = playbackInfo.copyWithTimeline(timeline);
|
playbackInfo = playbackInfo.copyWithTimeline(timeline);
|
||||||
@ -2689,48 +2737,6 @@ import java.util.concurrent.TimeoutException;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void seekToInternal(int mediaItemIndex, long positionMs, boolean repeatMediaItem) {
|
|
||||||
analyticsCollector.notifySeekStarted();
|
|
||||||
Timeline timeline = playbackInfo.timeline;
|
|
||||||
if (mediaItemIndex < 0
|
|
||||||
|| (!timeline.isEmpty() && mediaItemIndex >= timeline.getWindowCount())) {
|
|
||||||
throw new IllegalSeekPositionException(timeline, mediaItemIndex, positionMs);
|
|
||||||
}
|
|
||||||
pendingOperationAcks++;
|
|
||||||
if (isPlayingAd()) {
|
|
||||||
// TODO: Investigate adding support for seeking during ads. This is complicated to do in
|
|
||||||
// general because the midroll ad preceding the seek destination must be played before the
|
|
||||||
// content position can be played, if a different ad is playing at the moment.
|
|
||||||
Log.w(TAG, "seekTo ignored because an ad is playing");
|
|
||||||
ExoPlayerImplInternal.PlaybackInfoUpdate playbackInfoUpdate =
|
|
||||||
new ExoPlayerImplInternal.PlaybackInfoUpdate(this.playbackInfo);
|
|
||||||
playbackInfoUpdate.incrementPendingOperationAcks(1);
|
|
||||||
playbackInfoUpdateListener.onPlaybackInfoUpdate(playbackInfoUpdate);
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
@Player.State
|
|
||||||
int newPlaybackState =
|
|
||||||
getPlaybackState() == Player.STATE_IDLE ? Player.STATE_IDLE : STATE_BUFFERING;
|
|
||||||
int oldMaskingMediaItemIndex = getCurrentMediaItemIndex();
|
|
||||||
PlaybackInfo newPlaybackInfo = playbackInfo.copyWithPlaybackState(newPlaybackState);
|
|
||||||
newPlaybackInfo =
|
|
||||||
maskTimelineAndPosition(
|
|
||||||
newPlaybackInfo,
|
|
||||||
timeline,
|
|
||||||
maskWindowPositionMsOrGetPeriodPositionUs(timeline, mediaItemIndex, positionMs));
|
|
||||||
internalPlayer.seekTo(timeline, mediaItemIndex, Util.msToUs(positionMs));
|
|
||||||
updatePlaybackInfo(
|
|
||||||
newPlaybackInfo,
|
|
||||||
/* ignored */ TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED,
|
|
||||||
/* ignored */ PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
|
|
||||||
/* seekProcessed= */ true,
|
|
||||||
/* positionDiscontinuity= */ true,
|
|
||||||
/* positionDiscontinuityReason= */ DISCONTINUITY_REASON_SEEK,
|
|
||||||
/* discontinuityWindowStartPositionUs= */ getCurrentPositionUsInternal(newPlaybackInfo),
|
|
||||||
oldMaskingMediaItemIndex,
|
|
||||||
repeatMediaItem);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static DeviceInfo createDeviceInfo(StreamVolumeManager streamVolumeManager) {
|
private static DeviceInfo createDeviceInfo(StreamVolumeManager streamVolumeManager) {
|
||||||
return new DeviceInfo(
|
return new DeviceInfo(
|
||||||
DeviceInfo.PLAYBACK_TYPE_LOCAL,
|
DeviceInfo.PLAYBACK_TYPE_LOCAL,
|
||||||
|
@ -189,7 +189,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
private final LoadControl loadControl;
|
private final LoadControl loadControl;
|
||||||
private final BandwidthMeter bandwidthMeter;
|
private final BandwidthMeter bandwidthMeter;
|
||||||
private final HandlerWrapper handler;
|
private final HandlerWrapper handler;
|
||||||
private final HandlerThread internalPlaybackThread;
|
@Nullable private final HandlerThread internalPlaybackThread;
|
||||||
private final Looper playbackLooper;
|
private final Looper playbackLooper;
|
||||||
private final Timeline.Window window;
|
private final Timeline.Window window;
|
||||||
private final Timeline.Period period;
|
private final Timeline.Period period;
|
||||||
@ -244,7 +244,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
Looper applicationLooper,
|
Looper applicationLooper,
|
||||||
Clock clock,
|
Clock clock,
|
||||||
PlaybackInfoUpdateListener playbackInfoUpdateListener,
|
PlaybackInfoUpdateListener playbackInfoUpdateListener,
|
||||||
PlayerId playerId) {
|
PlayerId playerId,
|
||||||
|
Looper playbackLooper) {
|
||||||
this.playbackInfoUpdateListener = playbackInfoUpdateListener;
|
this.playbackInfoUpdateListener = playbackInfoUpdateListener;
|
||||||
this.renderers = renderers;
|
this.renderers = renderers;
|
||||||
this.trackSelector = trackSelector;
|
this.trackSelector = trackSelector;
|
||||||
@ -280,17 +281,23 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
|
|
||||||
deliverPendingMessageAtStartPositionRequired = true;
|
deliverPendingMessageAtStartPositionRequired = true;
|
||||||
|
|
||||||
Handler eventHandler = new Handler(applicationLooper);
|
HandlerWrapper eventHandler = clock.createHandler(applicationLooper, /* callback= */ null);
|
||||||
queue = new MediaPeriodQueue(analyticsCollector, eventHandler);
|
queue = new MediaPeriodQueue(analyticsCollector, eventHandler);
|
||||||
mediaSourceList =
|
mediaSourceList =
|
||||||
new MediaSourceList(/* listener= */ this, analyticsCollector, eventHandler, playerId);
|
new MediaSourceList(/* listener= */ this, analyticsCollector, eventHandler, playerId);
|
||||||
|
|
||||||
|
if (playbackLooper != null) {
|
||||||
|
internalPlaybackThread = null;
|
||||||
|
this.playbackLooper = playbackLooper;
|
||||||
|
} else {
|
||||||
// Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can
|
// Note: The documentation for Process.THREAD_PRIORITY_AUDIO that states "Applications can
|
||||||
// not normally change to this priority" is incorrect.
|
// not normally change to this priority" is incorrect.
|
||||||
internalPlaybackThread = new HandlerThread("ExoPlayer:Playback", Process.THREAD_PRIORITY_AUDIO);
|
internalPlaybackThread =
|
||||||
|
new HandlerThread("ExoPlayer:Playback", Process.THREAD_PRIORITY_AUDIO);
|
||||||
internalPlaybackThread.start();
|
internalPlaybackThread.start();
|
||||||
playbackLooper = internalPlaybackThread.getLooper();
|
this.playbackLooper = internalPlaybackThread.getLooper();
|
||||||
handler = clock.createHandler(playbackLooper, this);
|
}
|
||||||
|
handler = clock.createHandler(this.playbackLooper, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void experimentalSetForegroundModeTimeoutMs(long setForegroundModeTimeoutMs) {
|
public void experimentalSetForegroundModeTimeoutMs(long setForegroundModeTimeoutMs) {
|
||||||
@ -393,7 +400,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public synchronized void sendMessage(PlayerMessage message) {
|
public synchronized void sendMessage(PlayerMessage message) {
|
||||||
if (released || !internalPlaybackThread.isAlive()) {
|
if (released || !playbackLooper.getThread().isAlive()) {
|
||||||
Log.w(TAG, "Ignoring messages sent after release.");
|
Log.w(TAG, "Ignoring messages sent after release.");
|
||||||
message.markAsProcessed(/* isDelivered= */ false);
|
message.markAsProcessed(/* isDelivered= */ false);
|
||||||
return;
|
return;
|
||||||
@ -408,7 +415,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
* @return Whether the operations succeeded. If false, the operation timed out.
|
* @return Whether the operations succeeded. If false, the operation timed out.
|
||||||
*/
|
*/
|
||||||
public synchronized boolean setForegroundMode(boolean foregroundMode) {
|
public synchronized boolean setForegroundMode(boolean foregroundMode) {
|
||||||
if (released || !internalPlaybackThread.isAlive()) {
|
if (released || !playbackLooper.getThread().isAlive()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
if (foregroundMode) {
|
if (foregroundMode) {
|
||||||
@ -430,7 +437,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
* @return Whether the release succeeded. If false, the release timed out.
|
* @return Whether the release succeeded. If false, the release timed out.
|
||||||
*/
|
*/
|
||||||
public synchronized boolean release() {
|
public synchronized boolean release() {
|
||||||
if (released || !internalPlaybackThread.isAlive()) {
|
if (released || !playbackLooper.getThread().isAlive()) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
handler.sendEmptyMessage(MSG_RELEASE);
|
handler.sendEmptyMessage(MSG_RELEASE);
|
||||||
@ -1382,7 +1389,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
/* resetError= */ false);
|
/* resetError= */ false);
|
||||||
loadControl.onReleased();
|
loadControl.onReleased();
|
||||||
setState(Player.STATE_IDLE);
|
setState(Player.STATE_IDLE);
|
||||||
|
if (internalPlaybackThread != null) {
|
||||||
internalPlaybackThread.quit();
|
internalPlaybackThread.quit();
|
||||||
|
}
|
||||||
synchronized (this) {
|
synchronized (this) {
|
||||||
released = true;
|
released = true;
|
||||||
notifyAll();
|
notifyAll();
|
||||||
|
@ -26,6 +26,7 @@ import androidx.media3.common.C;
|
|||||||
import androidx.media3.common.Player.RepeatMode;
|
import androidx.media3.common.Player.RepeatMode;
|
||||||
import androidx.media3.common.Timeline;
|
import androidx.media3.common.Timeline;
|
||||||
import androidx.media3.common.util.Assertions;
|
import androidx.media3.common.util.Assertions;
|
||||||
|
import androidx.media3.common.util.HandlerWrapper;
|
||||||
import androidx.media3.exoplayer.analytics.AnalyticsCollector;
|
import androidx.media3.exoplayer.analytics.AnalyticsCollector;
|
||||||
import androidx.media3.exoplayer.source.MediaPeriod;
|
import androidx.media3.exoplayer.source.MediaPeriod;
|
||||||
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
|
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
|
||||||
@ -71,7 +72,7 @@ import com.google.common.collect.ImmutableList;
|
|||||||
private final Timeline.Period period;
|
private final Timeline.Period period;
|
||||||
private final Timeline.Window window;
|
private final Timeline.Window window;
|
||||||
private final AnalyticsCollector analyticsCollector;
|
private final AnalyticsCollector analyticsCollector;
|
||||||
private final Handler analyticsCollectorHandler;
|
private final HandlerWrapper analyticsCollectorHandler;
|
||||||
|
|
||||||
private long nextWindowSequenceNumber;
|
private long nextWindowSequenceNumber;
|
||||||
private @RepeatMode int repeatMode;
|
private @RepeatMode int repeatMode;
|
||||||
@ -91,7 +92,7 @@ import com.google.common.collect.ImmutableList;
|
|||||||
* on.
|
* on.
|
||||||
*/
|
*/
|
||||||
public MediaPeriodQueue(
|
public MediaPeriodQueue(
|
||||||
AnalyticsCollector analyticsCollector, Handler analyticsCollectorHandler) {
|
AnalyticsCollector analyticsCollector, HandlerWrapper analyticsCollectorHandler) {
|
||||||
this.analyticsCollector = analyticsCollector;
|
this.analyticsCollector = analyticsCollector;
|
||||||
this.analyticsCollectorHandler = analyticsCollectorHandler;
|
this.analyticsCollectorHandler = analyticsCollectorHandler;
|
||||||
period = new Timeline.Period();
|
period = new Timeline.Period();
|
||||||
|
@ -15,13 +15,16 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.exoplayer;
|
package androidx.media3.exoplayer;
|
||||||
|
|
||||||
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
import static java.lang.Math.max;
|
import static java.lang.Math.max;
|
||||||
import static java.lang.Math.min;
|
import static java.lang.Math.min;
|
||||||
|
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.util.Pair;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.Timeline;
|
import androidx.media3.common.Timeline;
|
||||||
import androidx.media3.common.util.Assertions;
|
import androidx.media3.common.util.Assertions;
|
||||||
|
import androidx.media3.common.util.HandlerWrapper;
|
||||||
import androidx.media3.common.util.Log;
|
import androidx.media3.common.util.Log;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import androidx.media3.datasource.TransferListener;
|
import androidx.media3.datasource.TransferListener;
|
||||||
@ -48,6 +51,7 @@ import java.util.Iterator;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
|
import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Concatenates multiple {@link MediaSource}s. The list of {@link MediaSource}s can be modified
|
* Concatenates multiple {@link MediaSource}s. The list of {@link MediaSource}s can be modified
|
||||||
@ -77,11 +81,10 @@ import java.util.Set;
|
|||||||
private final IdentityHashMap<MediaPeriod, MediaSourceHolder> mediaSourceByMediaPeriod;
|
private final IdentityHashMap<MediaPeriod, MediaSourceHolder> mediaSourceByMediaPeriod;
|
||||||
private final Map<Object, MediaSourceHolder> mediaSourceByUid;
|
private final Map<Object, MediaSourceHolder> mediaSourceByUid;
|
||||||
private final MediaSourceListInfoRefreshListener mediaSourceListInfoListener;
|
private final MediaSourceListInfoRefreshListener mediaSourceListInfoListener;
|
||||||
private final MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher;
|
|
||||||
private final DrmSessionEventListener.EventDispatcher drmEventDispatcher;
|
|
||||||
private final HashMap<MediaSourceList.MediaSourceHolder, MediaSourceAndListener> childSources;
|
private final HashMap<MediaSourceList.MediaSourceHolder, MediaSourceAndListener> childSources;
|
||||||
private final Set<MediaSourceHolder> enabledMediaSourceHolders;
|
private final Set<MediaSourceHolder> enabledMediaSourceHolders;
|
||||||
|
private final AnalyticsCollector eventListener;
|
||||||
|
private final HandlerWrapper eventHandler;
|
||||||
private ShuffleOrder shuffleOrder;
|
private ShuffleOrder shuffleOrder;
|
||||||
private boolean isPrepared;
|
private boolean isPrepared;
|
||||||
|
|
||||||
@ -101,7 +104,7 @@ import java.util.Set;
|
|||||||
public MediaSourceList(
|
public MediaSourceList(
|
||||||
MediaSourceListInfoRefreshListener listener,
|
MediaSourceListInfoRefreshListener listener,
|
||||||
AnalyticsCollector analyticsCollector,
|
AnalyticsCollector analyticsCollector,
|
||||||
Handler analyticsCollectorHandler,
|
HandlerWrapper analyticsCollectorHandler,
|
||||||
PlayerId playerId) {
|
PlayerId playerId) {
|
||||||
this.playerId = playerId;
|
this.playerId = playerId;
|
||||||
mediaSourceListInfoListener = listener;
|
mediaSourceListInfoListener = listener;
|
||||||
@ -109,12 +112,10 @@ import java.util.Set;
|
|||||||
mediaSourceByMediaPeriod = new IdentityHashMap<>();
|
mediaSourceByMediaPeriod = new IdentityHashMap<>();
|
||||||
mediaSourceByUid = new HashMap<>();
|
mediaSourceByUid = new HashMap<>();
|
||||||
mediaSourceHolders = new ArrayList<>();
|
mediaSourceHolders = new ArrayList<>();
|
||||||
mediaSourceEventDispatcher = new MediaSourceEventListener.EventDispatcher();
|
eventListener = analyticsCollector;
|
||||||
drmEventDispatcher = new DrmSessionEventListener.EventDispatcher();
|
eventHandler = analyticsCollectorHandler;
|
||||||
childSources = new HashMap<>();
|
childSources = new HashMap<>();
|
||||||
enabledMediaSourceHolders = new HashSet<>();
|
enabledMediaSourceHolders = new HashSet<>();
|
||||||
mediaSourceEventDispatcher.addEventListener(analyticsCollectorHandler, analyticsCollector);
|
|
||||||
drmEventDispatcher.addEventListener(analyticsCollectorHandler, analyticsCollector);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -308,7 +309,7 @@ import java.util.Set;
|
|||||||
Object mediaSourceHolderUid = getMediaSourceHolderUid(id.periodUid);
|
Object mediaSourceHolderUid = getMediaSourceHolderUid(id.periodUid);
|
||||||
MediaSource.MediaPeriodId childMediaPeriodId =
|
MediaSource.MediaPeriodId childMediaPeriodId =
|
||||||
id.copyWithPeriodUid(getChildPeriodUid(id.periodUid));
|
id.copyWithPeriodUid(getChildPeriodUid(id.periodUid));
|
||||||
MediaSourceHolder holder = Assertions.checkNotNull(mediaSourceByUid.get(mediaSourceHolderUid));
|
MediaSourceHolder holder = checkNotNull(mediaSourceByUid.get(mediaSourceHolderUid));
|
||||||
enableMediaSource(holder);
|
enableMediaSource(holder);
|
||||||
holder.activeMediaPeriodIds.add(childMediaPeriodId);
|
holder.activeMediaPeriodIds.add(childMediaPeriodId);
|
||||||
MediaPeriod mediaPeriod =
|
MediaPeriod mediaPeriod =
|
||||||
@ -324,8 +325,7 @@ import java.util.Set;
|
|||||||
* @param mediaPeriod The period to release.
|
* @param mediaPeriod The period to release.
|
||||||
*/
|
*/
|
||||||
public void releasePeriod(MediaPeriod mediaPeriod) {
|
public void releasePeriod(MediaPeriod mediaPeriod) {
|
||||||
MediaSourceHolder holder =
|
MediaSourceHolder holder = checkNotNull(mediaSourceByMediaPeriod.remove(mediaPeriod));
|
||||||
Assertions.checkNotNull(mediaSourceByMediaPeriod.remove(mediaPeriod));
|
|
||||||
holder.mediaSource.releasePeriod(mediaPeriod);
|
holder.mediaSource.releasePeriod(mediaPeriod);
|
||||||
holder.activeMediaPeriodIds.remove(((MaskingMediaPeriod) mediaPeriod).id);
|
holder.activeMediaPeriodIds.remove(((MaskingMediaPeriod) mediaPeriod).id);
|
||||||
if (!mediaSourceByMediaPeriod.isEmpty()) {
|
if (!mediaSourceByMediaPeriod.isEmpty()) {
|
||||||
@ -450,8 +450,7 @@ import java.util.Set;
|
|||||||
private void maybeReleaseChildSource(MediaSourceHolder mediaSourceHolder) {
|
private void maybeReleaseChildSource(MediaSourceHolder mediaSourceHolder) {
|
||||||
// Release if the source has been removed from the playlist and no periods are still active.
|
// Release if the source has been removed from the playlist and no periods are still active.
|
||||||
if (mediaSourceHolder.isRemoved && mediaSourceHolder.activeMediaPeriodIds.isEmpty()) {
|
if (mediaSourceHolder.isRemoved && mediaSourceHolder.activeMediaPeriodIds.isEmpty()) {
|
||||||
MediaSourceAndListener removedChild =
|
MediaSourceAndListener removedChild = checkNotNull(childSources.remove(mediaSourceHolder));
|
||||||
Assertions.checkNotNull(childSources.remove(mediaSourceHolder));
|
|
||||||
removedChild.mediaSource.releaseSource(removedChild.caller);
|
removedChild.mediaSource.releaseSource(removedChild.caller);
|
||||||
removedChild.mediaSource.removeEventListener(removedChild.eventListener);
|
removedChild.mediaSource.removeEventListener(removedChild.eventListener);
|
||||||
removedChild.mediaSource.removeDrmEventListener(removedChild.eventListener);
|
removedChild.mediaSource.removeDrmEventListener(removedChild.eventListener);
|
||||||
@ -526,12 +525,8 @@ import java.util.Set;
|
|||||||
implements MediaSourceEventListener, DrmSessionEventListener {
|
implements MediaSourceEventListener, DrmSessionEventListener {
|
||||||
|
|
||||||
private final MediaSourceList.MediaSourceHolder id;
|
private final MediaSourceList.MediaSourceHolder id;
|
||||||
private MediaSourceEventListener.EventDispatcher mediaSourceEventDispatcher;
|
|
||||||
private DrmSessionEventListener.EventDispatcher drmEventDispatcher;
|
|
||||||
|
|
||||||
public ForwardingEventListener(MediaSourceList.MediaSourceHolder id) {
|
public ForwardingEventListener(MediaSourceList.MediaSourceHolder id) {
|
||||||
mediaSourceEventDispatcher = MediaSourceList.this.mediaSourceEventDispatcher;
|
|
||||||
drmEventDispatcher = MediaSourceList.this.drmEventDispatcher;
|
|
||||||
this.id = id;
|
this.id = id;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -543,8 +538,14 @@ import java.util.Set;
|
|||||||
@Nullable MediaSource.MediaPeriodId mediaPeriodId,
|
@Nullable MediaSource.MediaPeriodId mediaPeriodId,
|
||||||
LoadEventInfo loadEventData,
|
LoadEventInfo loadEventData,
|
||||||
MediaLoadData mediaLoadData) {
|
MediaLoadData mediaLoadData) {
|
||||||
if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) {
|
@Nullable
|
||||||
mediaSourceEventDispatcher.loadStarted(loadEventData, mediaLoadData);
|
Pair<Integer, MediaSource.@NullableType MediaPeriodId> eventParameters =
|
||||||
|
getEventParameters(windowIndex, mediaPeriodId);
|
||||||
|
if (eventParameters != null) {
|
||||||
|
eventHandler.post(
|
||||||
|
() ->
|
||||||
|
eventListener.onLoadStarted(
|
||||||
|
eventParameters.first, eventParameters.second, loadEventData, mediaLoadData));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -554,8 +555,14 @@ import java.util.Set;
|
|||||||
@Nullable MediaSource.MediaPeriodId mediaPeriodId,
|
@Nullable MediaSource.MediaPeriodId mediaPeriodId,
|
||||||
LoadEventInfo loadEventData,
|
LoadEventInfo loadEventData,
|
||||||
MediaLoadData mediaLoadData) {
|
MediaLoadData mediaLoadData) {
|
||||||
if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) {
|
@Nullable
|
||||||
mediaSourceEventDispatcher.loadCompleted(loadEventData, mediaLoadData);
|
Pair<Integer, MediaSource.@NullableType MediaPeriodId> eventParameters =
|
||||||
|
getEventParameters(windowIndex, mediaPeriodId);
|
||||||
|
if (eventParameters != null) {
|
||||||
|
eventHandler.post(
|
||||||
|
() ->
|
||||||
|
eventListener.onLoadCompleted(
|
||||||
|
eventParameters.first, eventParameters.second, loadEventData, mediaLoadData));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -565,8 +572,14 @@ import java.util.Set;
|
|||||||
@Nullable MediaSource.MediaPeriodId mediaPeriodId,
|
@Nullable MediaSource.MediaPeriodId mediaPeriodId,
|
||||||
LoadEventInfo loadEventData,
|
LoadEventInfo loadEventData,
|
||||||
MediaLoadData mediaLoadData) {
|
MediaLoadData mediaLoadData) {
|
||||||
if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) {
|
@Nullable
|
||||||
mediaSourceEventDispatcher.loadCanceled(loadEventData, mediaLoadData);
|
Pair<Integer, MediaSource.@NullableType MediaPeriodId> eventParameters =
|
||||||
|
getEventParameters(windowIndex, mediaPeriodId);
|
||||||
|
if (eventParameters != null) {
|
||||||
|
eventHandler.post(
|
||||||
|
() ->
|
||||||
|
eventListener.onLoadCanceled(
|
||||||
|
eventParameters.first, eventParameters.second, loadEventData, mediaLoadData));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -578,8 +591,19 @@ import java.util.Set;
|
|||||||
MediaLoadData mediaLoadData,
|
MediaLoadData mediaLoadData,
|
||||||
IOException error,
|
IOException error,
|
||||||
boolean wasCanceled) {
|
boolean wasCanceled) {
|
||||||
if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) {
|
@Nullable
|
||||||
mediaSourceEventDispatcher.loadError(loadEventData, mediaLoadData, error, wasCanceled);
|
Pair<Integer, MediaSource.@NullableType MediaPeriodId> eventParameters =
|
||||||
|
getEventParameters(windowIndex, mediaPeriodId);
|
||||||
|
if (eventParameters != null) {
|
||||||
|
eventHandler.post(
|
||||||
|
() ->
|
||||||
|
eventListener.onLoadError(
|
||||||
|
eventParameters.first,
|
||||||
|
eventParameters.second,
|
||||||
|
loadEventData,
|
||||||
|
mediaLoadData,
|
||||||
|
error,
|
||||||
|
wasCanceled));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -588,8 +612,14 @@ import java.util.Set;
|
|||||||
int windowIndex,
|
int windowIndex,
|
||||||
@Nullable MediaSource.MediaPeriodId mediaPeriodId,
|
@Nullable MediaSource.MediaPeriodId mediaPeriodId,
|
||||||
MediaLoadData mediaLoadData) {
|
MediaLoadData mediaLoadData) {
|
||||||
if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) {
|
@Nullable
|
||||||
mediaSourceEventDispatcher.upstreamDiscarded(mediaLoadData);
|
Pair<Integer, MediaSource.@NullableType MediaPeriodId> eventParameters =
|
||||||
|
getEventParameters(windowIndex, mediaPeriodId);
|
||||||
|
if (eventParameters != null) {
|
||||||
|
eventHandler.post(
|
||||||
|
() ->
|
||||||
|
eventListener.onUpstreamDiscarded(
|
||||||
|
eventParameters.first, checkNotNull(eventParameters.second), mediaLoadData));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -598,8 +628,14 @@ import java.util.Set;
|
|||||||
int windowIndex,
|
int windowIndex,
|
||||||
@Nullable MediaSource.MediaPeriodId mediaPeriodId,
|
@Nullable MediaSource.MediaPeriodId mediaPeriodId,
|
||||||
MediaLoadData mediaLoadData) {
|
MediaLoadData mediaLoadData) {
|
||||||
if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) {
|
@Nullable
|
||||||
mediaSourceEventDispatcher.downstreamFormatChanged(mediaLoadData);
|
Pair<Integer, MediaSource.@NullableType MediaPeriodId> eventParameters =
|
||||||
|
getEventParameters(windowIndex, mediaPeriodId);
|
||||||
|
if (eventParameters != null) {
|
||||||
|
eventHandler.post(
|
||||||
|
() ->
|
||||||
|
eventListener.onDownstreamFormatChanged(
|
||||||
|
eventParameters.first, eventParameters.second, mediaLoadData));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -610,75 +646,94 @@ import java.util.Set;
|
|||||||
int windowIndex,
|
int windowIndex,
|
||||||
@Nullable MediaSource.MediaPeriodId mediaPeriodId,
|
@Nullable MediaSource.MediaPeriodId mediaPeriodId,
|
||||||
@DrmSession.State int state) {
|
@DrmSession.State int state) {
|
||||||
if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) {
|
@Nullable
|
||||||
drmEventDispatcher.drmSessionAcquired(state);
|
Pair<Integer, MediaSource.@NullableType MediaPeriodId> eventParameters =
|
||||||
|
getEventParameters(windowIndex, mediaPeriodId);
|
||||||
|
if (eventParameters != null) {
|
||||||
|
eventHandler.post(
|
||||||
|
() ->
|
||||||
|
eventListener.onDrmSessionAcquired(
|
||||||
|
eventParameters.first, eventParameters.second, state));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDrmKeysLoaded(
|
public void onDrmKeysLoaded(
|
||||||
int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId) {
|
int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId) {
|
||||||
if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) {
|
@Nullable
|
||||||
drmEventDispatcher.drmKeysLoaded();
|
Pair<Integer, MediaSource.@NullableType MediaPeriodId> eventParameters =
|
||||||
|
getEventParameters(windowIndex, mediaPeriodId);
|
||||||
|
if (eventParameters != null) {
|
||||||
|
eventHandler.post(
|
||||||
|
() -> eventListener.onDrmKeysLoaded(eventParameters.first, eventParameters.second));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDrmSessionManagerError(
|
public void onDrmSessionManagerError(
|
||||||
int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId, Exception error) {
|
int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId, Exception error) {
|
||||||
if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) {
|
@Nullable
|
||||||
drmEventDispatcher.drmSessionManagerError(error);
|
Pair<Integer, MediaSource.@NullableType MediaPeriodId> eventParameters =
|
||||||
|
getEventParameters(windowIndex, mediaPeriodId);
|
||||||
|
if (eventParameters != null) {
|
||||||
|
eventHandler.post(
|
||||||
|
() ->
|
||||||
|
eventListener.onDrmSessionManagerError(
|
||||||
|
eventParameters.first, eventParameters.second, error));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDrmKeysRestored(
|
public void onDrmKeysRestored(
|
||||||
int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId) {
|
int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId) {
|
||||||
if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) {
|
@Nullable
|
||||||
drmEventDispatcher.drmKeysRestored();
|
Pair<Integer, MediaSource.@NullableType MediaPeriodId> eventParameters =
|
||||||
|
getEventParameters(windowIndex, mediaPeriodId);
|
||||||
|
if (eventParameters != null) {
|
||||||
|
eventHandler.post(
|
||||||
|
() -> eventListener.onDrmKeysRestored(eventParameters.first, eventParameters.second));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDrmKeysRemoved(
|
public void onDrmKeysRemoved(
|
||||||
int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId) {
|
int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId) {
|
||||||
if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) {
|
@Nullable
|
||||||
drmEventDispatcher.drmKeysRemoved();
|
Pair<Integer, MediaSource.@NullableType MediaPeriodId> eventParameters =
|
||||||
|
getEventParameters(windowIndex, mediaPeriodId);
|
||||||
|
if (eventParameters != null) {
|
||||||
|
eventHandler.post(
|
||||||
|
() -> eventListener.onDrmKeysRemoved(eventParameters.first, eventParameters.second));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDrmSessionReleased(
|
public void onDrmSessionReleased(
|
||||||
int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId) {
|
int windowIndex, @Nullable MediaSource.MediaPeriodId mediaPeriodId) {
|
||||||
if (maybeUpdateEventDispatcher(windowIndex, mediaPeriodId)) {
|
@Nullable
|
||||||
drmEventDispatcher.drmSessionReleased();
|
Pair<Integer, MediaSource.@NullableType MediaPeriodId> eventParameters =
|
||||||
|
getEventParameters(windowIndex, mediaPeriodId);
|
||||||
|
if (eventParameters != null) {
|
||||||
|
eventHandler.post(
|
||||||
|
() ->
|
||||||
|
eventListener.onDrmSessionReleased(eventParameters.first, eventParameters.second));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Updates the event dispatcher and returns whether the event should be dispatched. */
|
/** Updates the event parameters and returns whether the event should be dispatched. */
|
||||||
private boolean maybeUpdateEventDispatcher(
|
@Nullable
|
||||||
|
private Pair<Integer, MediaSource.@NullableType MediaPeriodId> getEventParameters(
|
||||||
int childWindowIndex, @Nullable MediaSource.MediaPeriodId childMediaPeriodId) {
|
int childWindowIndex, @Nullable MediaSource.MediaPeriodId childMediaPeriodId) {
|
||||||
@Nullable MediaSource.MediaPeriodId mediaPeriodId = null;
|
@Nullable MediaSource.MediaPeriodId mediaPeriodId = null;
|
||||||
if (childMediaPeriodId != null) {
|
if (childMediaPeriodId != null) {
|
||||||
mediaPeriodId = getMediaPeriodIdForChildMediaPeriodId(id, childMediaPeriodId);
|
mediaPeriodId = getMediaPeriodIdForChildMediaPeriodId(id, childMediaPeriodId);
|
||||||
if (mediaPeriodId == null) {
|
if (mediaPeriodId == null) {
|
||||||
// Media period not found. Ignore event.
|
// Media period not found. Ignore event.
|
||||||
return false;
|
return null;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
int windowIndex = getWindowIndexForChildWindowIndex(id, childWindowIndex);
|
int windowIndex = getWindowIndexForChildWindowIndex(id, childWindowIndex);
|
||||||
if (mediaSourceEventDispatcher.windowIndex != windowIndex
|
return Pair.create(windowIndex, mediaPeriodId);
|
||||||
|| !Util.areEqual(mediaSourceEventDispatcher.mediaPeriodId, mediaPeriodId)) {
|
|
||||||
mediaSourceEventDispatcher =
|
|
||||||
MediaSourceList.this.mediaSourceEventDispatcher.withParameters(
|
|
||||||
windowIndex, mediaPeriodId, /* mediaTimeOffsetMs= */ 0L);
|
|
||||||
}
|
|
||||||
if (drmEventDispatcher.windowIndex != windowIndex
|
|
||||||
|| !Util.areEqual(drmEventDispatcher.mediaPeriodId, mediaPeriodId)) {
|
|
||||||
drmEventDispatcher =
|
|
||||||
MediaSourceList.this.drmEventDispatcher.withParameters(windowIndex, mediaPeriodId);
|
|
||||||
}
|
|
||||||
return true;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -144,13 +144,13 @@ public interface RendererCapabilities {
|
|||||||
/** A mask to apply to {@link Capabilities} to obtain {@link DecoderSupport} only. */
|
/** A mask to apply to {@link Capabilities} to obtain {@link DecoderSupport} only. */
|
||||||
int MODE_SUPPORT_MASK = 0b11 << 7;
|
int MODE_SUPPORT_MASK = 0b11 << 7;
|
||||||
/**
|
/**
|
||||||
* The renderer will use a decoder for fallback mimetype if possible as format's MIME type is
|
* The format's MIME type is unsupported and the renderer may use a decoder for a fallback MIME
|
||||||
* unsupported
|
* type.
|
||||||
*/
|
*/
|
||||||
int DECODER_SUPPORT_FALLBACK_MIMETYPE = 0b10 << 7;
|
int DECODER_SUPPORT_FALLBACK_MIMETYPE = 0b10 << 7;
|
||||||
/** The renderer is able to use the primary decoder for the format's MIME type. */
|
/** The renderer is able to use the primary decoder for the format's MIME type. */
|
||||||
int DECODER_SUPPORT_PRIMARY = 0b1 << 7;
|
int DECODER_SUPPORT_PRIMARY = 0b1 << 7;
|
||||||
/** The renderer will use a fallback decoder. */
|
/** The format exceeds the primary decoder's capabilities but is supported by fallback decoder */
|
||||||
int DECODER_SUPPORT_FALLBACK = 0;
|
int DECODER_SUPPORT_FALLBACK = 0;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.exoplayer;
|
package androidx.media3.exoplayer;
|
||||||
|
|
||||||
|
import static androidx.annotation.VisibleForTesting.PROTECTED;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.media.AudioDeviceInfo;
|
import android.media.AudioDeviceInfo;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
@ -1004,10 +1006,16 @@ public class SimpleExoPlayer extends BasePlayer
|
|||||||
return player.isLoading();
|
return player.isLoading();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("ForOverride") // Forwarding to ForOverride method in ExoPlayerImpl.
|
||||||
@Override
|
@Override
|
||||||
public void seekTo(int mediaItemIndex, long positionMs) {
|
@VisibleForTesting(otherwise = PROTECTED)
|
||||||
|
public void seekTo(
|
||||||
|
int mediaItemIndex,
|
||||||
|
long positionMs,
|
||||||
|
@Player.Command int seekCommand,
|
||||||
|
boolean isRepeatingCurrentItem) {
|
||||||
blockUntilConstructorFinished();
|
blockUntilConstructorFinished();
|
||||||
player.seekTo(mediaItemIndex, positionMs);
|
player.seekTo(mediaItemIndex, positionMs, seekCommand, isRepeatingCurrentItem);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -96,6 +96,20 @@ public class DefaultAnalyticsCollector implements AnalyticsCollector {
|
|||||||
eventTimes = new SparseArray<>();
|
eventTimes = new SparseArray<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether methods throw when using the wrong thread.
|
||||||
|
*
|
||||||
|
* <p>Do not use this method unless to support legacy use cases.
|
||||||
|
*
|
||||||
|
* @param throwsWhenUsingWrongThread Whether to throw when using the wrong thread.
|
||||||
|
* @deprecated Do not use this method and ensure all calls are made from the correct thread.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("deprecation") // Calling deprecated method.
|
||||||
|
@Deprecated
|
||||||
|
public void setThrowsWhenUsingWrongThread(boolean throwsWhenUsingWrongThread) {
|
||||||
|
listeners.setThrowsWhenUsingWrongThread(throwsWhenUsingWrongThread);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@CallSuper
|
@CallSuper
|
||||||
public void addListener(AnalyticsListener listener) {
|
public void addListener(AnalyticsListener listener) {
|
||||||
|
@ -60,6 +60,7 @@ import androidx.media3.extractor.Ac3Util;
|
|||||||
import androidx.media3.extractor.Ac4Util;
|
import androidx.media3.extractor.Ac4Util;
|
||||||
import androidx.media3.extractor.DtsUtil;
|
import androidx.media3.extractor.DtsUtil;
|
||||||
import androidx.media3.extractor.MpegAudioUtil;
|
import androidx.media3.extractor.MpegAudioUtil;
|
||||||
|
import androidx.media3.extractor.OpusUtil;
|
||||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
import com.google.errorprone.annotations.InlineMe;
|
import com.google.errorprone.annotations.InlineMe;
|
||||||
import com.google.errorprone.annotations.InlineMeValidationDisabled;
|
import com.google.errorprone.annotations.InlineMeValidationDisabled;
|
||||||
@ -203,6 +204,8 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
* @param pcmFrameSize The size of the PCM frames if the {@code encoding} is PCM, 1 otherwise,
|
* @param pcmFrameSize The size of the PCM frames if the {@code encoding} is PCM, 1 otherwise,
|
||||||
* in bytes.
|
* in bytes.
|
||||||
* @param sampleRate The sample rate of the format, in Hz.
|
* @param sampleRate The sample rate of the format, in Hz.
|
||||||
|
* @param bitrate The bitrate of the audio stream if the stream is compressed, or {@link
|
||||||
|
* Format#NO_VALUE} if {@code encoding} is PCM or the bitrate is not known.
|
||||||
* @param maxAudioTrackPlaybackSpeed The maximum speed the content will be played using {@link
|
* @param maxAudioTrackPlaybackSpeed The maximum speed the content will be played using {@link
|
||||||
* AudioTrack#setPlaybackParams}. 0.5 is 2x slow motion, 1 is real time, 2 is 2x fast
|
* AudioTrack#setPlaybackParams}. 0.5 is 2x slow motion, 1 is real time, 2 is 2x fast
|
||||||
* forward, etc. This will be {@code 1} unless {@link
|
* forward, etc. This will be {@code 1} unless {@link
|
||||||
@ -217,6 +220,7 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
@OutputMode int outputMode,
|
@OutputMode int outputMode,
|
||||||
int pcmFrameSize,
|
int pcmFrameSize,
|
||||||
int sampleRate,
|
int sampleRate,
|
||||||
|
int bitrate,
|
||||||
double maxAudioTrackPlaybackSpeed);
|
double maxAudioTrackPlaybackSpeed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -788,8 +792,9 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
getAudioTrackMinBufferSize(outputSampleRate, outputChannelConfig, outputEncoding),
|
getAudioTrackMinBufferSize(outputSampleRate, outputChannelConfig, outputEncoding),
|
||||||
outputEncoding,
|
outputEncoding,
|
||||||
outputMode,
|
outputMode,
|
||||||
outputPcmFrameSize,
|
outputPcmFrameSize != C.LENGTH_UNSET ? outputPcmFrameSize : 1,
|
||||||
outputSampleRate,
|
outputSampleRate,
|
||||||
|
inputFormat.bitrate,
|
||||||
enableAudioTrackPlaybackParams ? MAX_PLAYBACK_SPEED : DEFAULT_PLAYBACK_SPEED);
|
enableAudioTrackPlaybackParams ? MAX_PLAYBACK_SPEED : DEFAULT_PLAYBACK_SPEED);
|
||||||
|
|
||||||
offloadDisabledUntilNextConfiguration = false;
|
offloadDisabledUntilNextConfiguration = false;
|
||||||
@ -1000,9 +1005,11 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
getSubmittedFrames() - trimmingAudioProcessor.getTrimmedFrameCount());
|
getSubmittedFrames() - trimmingAudioProcessor.getTrimmedFrameCount());
|
||||||
if (!startMediaTimeUsNeedsSync
|
if (!startMediaTimeUsNeedsSync
|
||||||
&& Math.abs(expectedPresentationTimeUs - presentationTimeUs) > 200000) {
|
&& Math.abs(expectedPresentationTimeUs - presentationTimeUs) > 200000) {
|
||||||
|
if (listener != null) {
|
||||||
listener.onAudioSinkError(
|
listener.onAudioSinkError(
|
||||||
new AudioSink.UnexpectedDiscontinuityException(
|
new AudioSink.UnexpectedDiscontinuityException(
|
||||||
presentationTimeUs, expectedPresentationTimeUs));
|
presentationTimeUs, expectedPresentationTimeUs));
|
||||||
|
}
|
||||||
startMediaTimeUsNeedsSync = true;
|
startMediaTimeUsNeedsSync = true;
|
||||||
}
|
}
|
||||||
if (startMediaTimeUsNeedsSync) {
|
if (startMediaTimeUsNeedsSync) {
|
||||||
@ -1785,6 +1792,8 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
? 0
|
? 0
|
||||||
: (Ac3Util.parseTrueHdSyncframeAudioSampleCount(buffer, syncframeOffset)
|
: (Ac3Util.parseTrueHdSyncframeAudioSampleCount(buffer, syncframeOffset)
|
||||||
* Ac3Util.TRUEHD_RECHUNK_SAMPLE_COUNT);
|
* Ac3Util.TRUEHD_RECHUNK_SAMPLE_COUNT);
|
||||||
|
case C.ENCODING_OPUS:
|
||||||
|
return OpusUtil.parsePacketAudioSampleCount(buffer);
|
||||||
case C.ENCODING_PCM_16BIT:
|
case C.ENCODING_PCM_16BIT:
|
||||||
case C.ENCODING_PCM_16BIT_BIG_ENDIAN:
|
case C.ENCODING_PCM_16BIT_BIG_ENDIAN:
|
||||||
case C.ENCODING_PCM_24BIT:
|
case C.ENCODING_PCM_24BIT:
|
||||||
|
@ -19,6 +19,7 @@ import static androidx.media3.common.util.Util.constrainValue;
|
|||||||
import static androidx.media3.exoplayer.audio.DefaultAudioSink.OUTPUT_MODE_OFFLOAD;
|
import static androidx.media3.exoplayer.audio.DefaultAudioSink.OUTPUT_MODE_OFFLOAD;
|
||||||
import static androidx.media3.exoplayer.audio.DefaultAudioSink.OUTPUT_MODE_PASSTHROUGH;
|
import static androidx.media3.exoplayer.audio.DefaultAudioSink.OUTPUT_MODE_PASSTHROUGH;
|
||||||
import static androidx.media3.exoplayer.audio.DefaultAudioSink.OUTPUT_MODE_PCM;
|
import static androidx.media3.exoplayer.audio.DefaultAudioSink.OUTPUT_MODE_PCM;
|
||||||
|
import static com.google.common.math.IntMath.divide;
|
||||||
import static com.google.common.primitives.Ints.checkedCast;
|
import static com.google.common.primitives.Ints.checkedCast;
|
||||||
import static java.lang.Math.max;
|
import static java.lang.Math.max;
|
||||||
|
|
||||||
@ -32,7 +33,9 @@ import androidx.media3.extractor.Ac3Util;
|
|||||||
import androidx.media3.extractor.Ac4Util;
|
import androidx.media3.extractor.Ac4Util;
|
||||||
import androidx.media3.extractor.DtsUtil;
|
import androidx.media3.extractor.DtsUtil;
|
||||||
import androidx.media3.extractor.MpegAudioUtil;
|
import androidx.media3.extractor.MpegAudioUtil;
|
||||||
|
import androidx.media3.extractor.OpusUtil;
|
||||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
|
import java.math.RoundingMode;
|
||||||
|
|
||||||
/** Provide the buffer size to use when creating an {@link AudioTrack}. */
|
/** Provide the buffer size to use when creating an {@link AudioTrack}. */
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@ -173,10 +176,11 @@ public class DefaultAudioTrackBufferSizeProvider
|
|||||||
@OutputMode int outputMode,
|
@OutputMode int outputMode,
|
||||||
int pcmFrameSize,
|
int pcmFrameSize,
|
||||||
int sampleRate,
|
int sampleRate,
|
||||||
|
int bitrate,
|
||||||
double maxAudioTrackPlaybackSpeed) {
|
double maxAudioTrackPlaybackSpeed) {
|
||||||
int bufferSize =
|
int bufferSize =
|
||||||
get1xBufferSizeInBytes(
|
get1xBufferSizeInBytes(
|
||||||
minBufferSizeInBytes, encoding, outputMode, pcmFrameSize, sampleRate);
|
minBufferSizeInBytes, encoding, outputMode, pcmFrameSize, sampleRate, bitrate);
|
||||||
// Maintain the buffer duration by scaling the size accordingly.
|
// Maintain the buffer duration by scaling the size accordingly.
|
||||||
bufferSize = (int) (bufferSize * maxAudioTrackPlaybackSpeed);
|
bufferSize = (int) (bufferSize * maxAudioTrackPlaybackSpeed);
|
||||||
// Buffer size must not be lower than the AudioTrack min buffer size for this format.
|
// Buffer size must not be lower than the AudioTrack min buffer size for this format.
|
||||||
@ -187,12 +191,17 @@ public class DefaultAudioTrackBufferSizeProvider
|
|||||||
|
|
||||||
/** Returns the buffer size for playback at 1x speed. */
|
/** Returns the buffer size for playback at 1x speed. */
|
||||||
protected int get1xBufferSizeInBytes(
|
protected int get1xBufferSizeInBytes(
|
||||||
int minBufferSizeInBytes, int encoding, int outputMode, int pcmFrameSize, int sampleRate) {
|
int minBufferSizeInBytes,
|
||||||
|
int encoding,
|
||||||
|
int outputMode,
|
||||||
|
int pcmFrameSize,
|
||||||
|
int sampleRate,
|
||||||
|
int bitrate) {
|
||||||
switch (outputMode) {
|
switch (outputMode) {
|
||||||
case OUTPUT_MODE_PCM:
|
case OUTPUT_MODE_PCM:
|
||||||
return getPcmBufferSizeInBytes(minBufferSizeInBytes, sampleRate, pcmFrameSize);
|
return getPcmBufferSizeInBytes(minBufferSizeInBytes, sampleRate, pcmFrameSize);
|
||||||
case OUTPUT_MODE_PASSTHROUGH:
|
case OUTPUT_MODE_PASSTHROUGH:
|
||||||
return getPassthroughBufferSizeInBytes(encoding);
|
return getPassthroughBufferSizeInBytes(encoding, bitrate);
|
||||||
case OUTPUT_MODE_OFFLOAD:
|
case OUTPUT_MODE_OFFLOAD:
|
||||||
return getOffloadBufferSizeInBytes(encoding);
|
return getOffloadBufferSizeInBytes(encoding);
|
||||||
default:
|
default:
|
||||||
@ -209,13 +218,16 @@ public class DefaultAudioTrackBufferSizeProvider
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the buffer size for passthrough playback. */
|
/** Returns the buffer size for passthrough playback. */
|
||||||
protected int getPassthroughBufferSizeInBytes(@C.Encoding int encoding) {
|
protected int getPassthroughBufferSizeInBytes(@C.Encoding int encoding, int bitrate) {
|
||||||
int bufferSizeUs = passthroughBufferDurationUs;
|
int bufferSizeUs = passthroughBufferDurationUs;
|
||||||
if (encoding == C.ENCODING_AC3) {
|
if (encoding == C.ENCODING_AC3) {
|
||||||
bufferSizeUs *= ac3BufferMultiplicationFactor;
|
bufferSizeUs *= ac3BufferMultiplicationFactor;
|
||||||
}
|
}
|
||||||
int maxByteRate = getMaximumEncodedRateBytesPerSecond(encoding);
|
int byteRate =
|
||||||
return checkedCast((long) bufferSizeUs * maxByteRate / C.MICROS_PER_SECOND);
|
bitrate != Format.NO_VALUE
|
||||||
|
? divide(bitrate, 8, RoundingMode.CEILING)
|
||||||
|
: getMaximumEncodedRateBytesPerSecond(encoding);
|
||||||
|
return checkedCast((long) bufferSizeUs * byteRate / C.MICROS_PER_SECOND);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the buffer size for offload playback. */
|
/** Returns the buffer size for offload playback. */
|
||||||
@ -255,6 +267,8 @@ public class DefaultAudioTrackBufferSizeProvider
|
|||||||
return DtsUtil.DTS_HD_MAX_RATE_BYTES_PER_SECOND;
|
return DtsUtil.DTS_HD_MAX_RATE_BYTES_PER_SECOND;
|
||||||
case C.ENCODING_DOLBY_TRUEHD:
|
case C.ENCODING_DOLBY_TRUEHD:
|
||||||
return Ac3Util.TRUEHD_MAX_RATE_BYTES_PER_SECOND;
|
return Ac3Util.TRUEHD_MAX_RATE_BYTES_PER_SECOND;
|
||||||
|
case C.ENCODING_OPUS:
|
||||||
|
return OpusUtil.MAX_BYTES_PER_SECOND;
|
||||||
case C.ENCODING_PCM_16BIT:
|
case C.ENCODING_PCM_16BIT:
|
||||||
case C.ENCODING_PCM_16BIT_BIG_ENDIAN:
|
case C.ENCODING_PCM_16BIT_BIG_ENDIAN:
|
||||||
case C.ENCODING_PCM_24BIT:
|
case C.ENCODING_PCM_24BIT:
|
||||||
|
@ -245,7 +245,8 @@ public final class MediaCodecInfo {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether the decoder may support decoding the given {@code format}.
|
* Returns whether the decoder may support decoding the given {@code format} both functionally and
|
||||||
|
* performantly.
|
||||||
*
|
*
|
||||||
* @param format The input media format.
|
* @param format The input media format.
|
||||||
* @return Whether the decoder may support decoding the given {@code format}.
|
* @return Whether the decoder may support decoding the given {@code format}.
|
||||||
@ -256,7 +257,7 @@ public final class MediaCodecInfo {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isCodecProfileAndLevelSupported(format)) {
|
if (!isCodecProfileAndLevelSupported(format, /* checkPerformanceCapabilities= */ true)) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -283,15 +284,24 @@ public final class MediaCodecInfo {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the decoder may functionally support decoding the given {@code format}.
|
||||||
|
*
|
||||||
|
* @param format The input media format.
|
||||||
|
* @return Whether the decoder may functionally support decoding the given {@code format}.
|
||||||
|
*/
|
||||||
|
public boolean isFormatFunctionallySupported(Format format) {
|
||||||
|
return isSampleMimeTypeSupported(format)
|
||||||
|
&& isCodecProfileAndLevelSupported(format, /* checkPerformanceCapabilities= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
private boolean isSampleMimeTypeSupported(Format format) {
|
private boolean isSampleMimeTypeSupported(Format format) {
|
||||||
return mimeType.equals(format.sampleMimeType)
|
return mimeType.equals(format.sampleMimeType)
|
||||||
|| mimeType.equals(MediaCodecUtil.getAlternativeCodecMimeType(format));
|
|| mimeType.equals(MediaCodecUtil.getAlternativeCodecMimeType(format));
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isCodecProfileAndLevelSupported(Format format) {
|
private boolean isCodecProfileAndLevelSupported(
|
||||||
if (format.codecs == null) {
|
Format format, boolean checkPerformanceCapabilities) {
|
||||||
return true;
|
|
||||||
}
|
|
||||||
Pair<Integer, Integer> codecProfileAndLevel = MediaCodecUtil.getCodecProfileAndLevel(format);
|
Pair<Integer, Integer> codecProfileAndLevel = MediaCodecUtil.getCodecProfileAndLevel(format);
|
||||||
if (codecProfileAndLevel == null) {
|
if (codecProfileAndLevel == null) {
|
||||||
// If we don't know any better, we assume that the profile and level are supported.
|
// If we don't know any better, we assume that the profile and level are supported.
|
||||||
@ -327,7 +337,7 @@ public final class MediaCodecInfo {
|
|||||||
|
|
||||||
for (CodecProfileLevel profileLevel : profileLevels) {
|
for (CodecProfileLevel profileLevel : profileLevels) {
|
||||||
if (profileLevel.profile == profile
|
if (profileLevel.profile == profile
|
||||||
&& profileLevel.level >= level
|
&& (profileLevel.level >= level || !checkPerformanceCapabilities)
|
||||||
&& !needsProfileExcludedWorkaround(mimeType, profile)) {
|
&& !needsProfileExcludedWorkaround(mimeType, profile)) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
@ -1113,6 +1113,14 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
}
|
}
|
||||||
codecInitializedTimestamp = SystemClock.elapsedRealtime();
|
codecInitializedTimestamp = SystemClock.elapsedRealtime();
|
||||||
|
|
||||||
|
if (!codecInfo.isFormatSupported(inputFormat)) {
|
||||||
|
Log.w(
|
||||||
|
TAG,
|
||||||
|
Util.formatInvariant(
|
||||||
|
"Format exceeds selected codec's capabilities [%s, %s]",
|
||||||
|
Format.toLogString(inputFormat), codecName));
|
||||||
|
}
|
||||||
|
|
||||||
this.codecInfo = codecInfo;
|
this.codecInfo = codecInfo;
|
||||||
this.codecOperatingRate = codecOperatingRate;
|
this.codecOperatingRate = codecOperatingRate;
|
||||||
codecInputFormat = inputFormat;
|
codecInputFormat = inputFormat;
|
||||||
@ -2425,7 +2433,11 @@ public abstract class MediaCodecRenderer extends BaseRenderer {
|
|||||||
|| (Util.SDK_INT <= 17 && "OMX.allwinner.video.decoder.avc".equals(name))
|
|| (Util.SDK_INT <= 17 && "OMX.allwinner.video.decoder.avc".equals(name))
|
||||||
|| (Util.SDK_INT <= 29
|
|| (Util.SDK_INT <= 29
|
||||||
&& ("OMX.broadcom.video_decoder.tunnel".equals(name)
|
&& ("OMX.broadcom.video_decoder.tunnel".equals(name)
|
||||||
|| "OMX.broadcom.video_decoder.tunnel.secure".equals(name)))
|
|| "OMX.broadcom.video_decoder.tunnel.secure".equals(name)
|
||||||
|
|| "OMX.bcm.vdec.avc.tunnel".equals(name)
|
||||||
|
|| "OMX.bcm.vdec.avc.tunnel.secure".equals(name)
|
||||||
|
|| "OMX.bcm.vdec.hevc.tunnel".equals(name)
|
||||||
|
|| "OMX.bcm.vdec.hevc.tunnel.secure".equals(name)))
|
||||||
|| ("Amazon".equals(Util.MANUFACTURER) && "AFTS".equals(Util.MODEL) && codecInfo.secure);
|
|| ("Amazon".equals(Util.MANUFACTURER) && "AFTS".equals(Util.MODEL) && codecInfo.secure);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -190,22 +190,15 @@ public final class MediaCodecUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a copy of the provided decoder list sorted such that decoders with format support are
|
* Returns a copy of the provided decoder list sorted such that decoders with functional format
|
||||||
* listed first. The returned list is modifiable for convenience.
|
* support are listed first. The returned list is modifiable for convenience.
|
||||||
*/
|
*/
|
||||||
@CheckResult
|
@CheckResult
|
||||||
public static List<MediaCodecInfo> getDecoderInfosSortedByFormatSupport(
|
public static List<MediaCodecInfo> getDecoderInfosSortedByFormatSupport(
|
||||||
List<MediaCodecInfo> decoderInfos, Format format) {
|
List<MediaCodecInfo> decoderInfos, Format format) {
|
||||||
decoderInfos = new ArrayList<>(decoderInfos);
|
decoderInfos = new ArrayList<>(decoderInfos);
|
||||||
sortByScore(
|
sortByScore(
|
||||||
decoderInfos,
|
decoderInfos, decoderInfo -> decoderInfo.isFormatFunctionallySupported(format) ? 1 : 0);
|
||||||
decoderInfo -> {
|
|
||||||
try {
|
|
||||||
return decoderInfo.isFormatSupported(format) ? 1 : 0;
|
|
||||||
} catch (DecoderQueryException e) {
|
|
||||||
return -1;
|
|
||||||
}
|
|
||||||
});
|
|
||||||
return decoderInfos;
|
return decoderInfos;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -40,7 +40,16 @@ import java.util.List;
|
|||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
|
|
||||||
/** A {@link Service} for downloading media. */
|
/**
|
||||||
|
* A {@link Service} for downloading media.
|
||||||
|
*
|
||||||
|
* <p>Apps with target SDK 33 and greater need to add the {@code
|
||||||
|
* android.permission.POST_NOTIFICATIONS} permission to the manifest and request the permission at
|
||||||
|
* runtime before starting downloads. Without that permission granted by the user, notifications
|
||||||
|
* posted by this service are not displayed. See <a
|
||||||
|
* href="https://developer.android.com/develop/ui/views/notifications/notification-permission">the
|
||||||
|
* official UI guide</a> for more detailed information.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public abstract class DownloadService extends Service {
|
public abstract class DownloadService extends Service {
|
||||||
|
|
||||||
@ -574,6 +583,17 @@ public abstract class DownloadService extends Service {
|
|||||||
Util.startForegroundService(context, intent);
|
Util.startForegroundService(context, intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clear all {@linkplain DownloadManagerHelper download manager helpers} before restarting the
|
||||||
|
* service.
|
||||||
|
*
|
||||||
|
* <p>Calling this method is normally only required if an app supports downloading content for
|
||||||
|
* multiple users for which different download directories should be used.
|
||||||
|
*/
|
||||||
|
public static void clearDownloadManagerHelpers() {
|
||||||
|
downloadManagerHelpers.clear();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onCreate() {
|
public void onCreate() {
|
||||||
if (channelId != null) {
|
if (channelId != null) {
|
||||||
|
@ -61,7 +61,7 @@ public final class ConcatenatingMediaSource extends CompositeMediaSource<MediaSo
|
|||||||
private static final int MSG_UPDATE_TIMELINE = 4;
|
private static final int MSG_UPDATE_TIMELINE = 4;
|
||||||
private static final int MSG_ON_COMPLETION = 5;
|
private static final int MSG_ON_COMPLETION = 5;
|
||||||
|
|
||||||
private static final MediaItem EMPTY_MEDIA_ITEM =
|
private static final MediaItem PLACEHOLDER_MEDIA_ITEM =
|
||||||
new MediaItem.Builder().setUri(Uri.EMPTY).build();
|
new MediaItem.Builder().setUri(Uri.EMPTY).build();
|
||||||
|
|
||||||
// Accessed on any thread.
|
// Accessed on any thread.
|
||||||
@ -451,7 +451,7 @@ public final class ConcatenatingMediaSource extends CompositeMediaSource<MediaSo
|
|||||||
public MediaItem getMediaItem() {
|
public MediaItem getMediaItem() {
|
||||||
// This method is actually never called because getInitialTimeline is implemented and hence the
|
// This method is actually never called because getInitialTimeline is implemented and hence the
|
||||||
// MaskingMediaSource does not need to create a placeholder timeline for this media source.
|
// MaskingMediaSource does not need to create a placeholder timeline for this media source.
|
||||||
return EMPTY_MEDIA_ITEM;
|
return PLACEHOLDER_MEDIA_ITEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -1018,7 +1018,7 @@ public final class ConcatenatingMediaSource extends CompositeMediaSource<MediaSo
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MediaItem getMediaItem() {
|
public MediaItem getMediaItem() {
|
||||||
return EMPTY_MEDIA_ITEM;
|
return PLACEHOLDER_MEDIA_ITEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -0,0 +1,610 @@
|
|||||||
|
/*
|
||||||
|
* 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 androidx.media3.exoplayer.source;
|
||||||
|
|
||||||
|
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||||
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
|
import static androidx.media3.common.util.Assertions.checkState;
|
||||||
|
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.net.Uri;
|
||||||
|
import android.os.Handler;
|
||||||
|
import android.os.Message;
|
||||||
|
import android.util.Pair;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.media3.common.C;
|
||||||
|
import androidx.media3.common.MediaItem;
|
||||||
|
import androidx.media3.common.Player;
|
||||||
|
import androidx.media3.common.Timeline;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import androidx.media3.common.util.Util;
|
||||||
|
import androidx.media3.datasource.TransferListener;
|
||||||
|
import androidx.media3.exoplayer.upstream.Allocator;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
|
import java.util.IdentityHashMap;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Concatenates multiple {@link MediaSource MediaSources}, combining everything in one single {@link
|
||||||
|
* Timeline.Window}.
|
||||||
|
*
|
||||||
|
* <p>This class can only be used under the following conditions:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>All sources must be non-empty.
|
||||||
|
* <li>All {@link Timeline.Window Windows} defined by the sources, except the first, must have an
|
||||||
|
* {@link Timeline.Window#getPositionInFirstPeriodUs() period offset} of zero. This excludes,
|
||||||
|
* for example, live streams or {@link ClippingMediaSource} with a non-zero start position.
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
@UnstableApi
|
||||||
|
public final class ConcatenatingMediaSource2 extends CompositeMediaSource<Integer> {
|
||||||
|
|
||||||
|
/** A builder for {@link ConcatenatingMediaSource2} instances. */
|
||||||
|
public static final class Builder {
|
||||||
|
|
||||||
|
private final ImmutableList.Builder<MediaSourceHolder> mediaSourceHoldersBuilder;
|
||||||
|
|
||||||
|
private int index;
|
||||||
|
@Nullable private MediaItem mediaItem;
|
||||||
|
@Nullable private MediaSource.Factory mediaSourceFactory;
|
||||||
|
|
||||||
|
/** Creates the builder. */
|
||||||
|
public Builder() {
|
||||||
|
mediaSourceHoldersBuilder = ImmutableList.builder();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instructs the builder to use a {@link DefaultMediaSourceFactory} to convert {@link MediaItem
|
||||||
|
* MediaItems} to {@link MediaSource MediaSources} for all future calls to {@link
|
||||||
|
* #add(MediaItem)} or {@link #add(MediaItem, long)}.
|
||||||
|
*
|
||||||
|
* @param context A {@link Context}.
|
||||||
|
* @return This builder.
|
||||||
|
*/
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public Builder useDefaultMediaSourceFactory(Context context) {
|
||||||
|
return setMediaSourceFactory(new DefaultMediaSourceFactory(context));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a {@link MediaSource.Factory} that is used to convert {@link MediaItem MediaItems} to
|
||||||
|
* {@link MediaSource MediaSources} for all future calls to {@link #add(MediaItem)} or {@link
|
||||||
|
* #add(MediaItem, long)}.
|
||||||
|
*
|
||||||
|
* @param mediaSourceFactory A {@link MediaSource.Factory}.
|
||||||
|
* @return This builder.
|
||||||
|
*/
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public Builder setMediaSourceFactory(MediaSource.Factory mediaSourceFactory) {
|
||||||
|
this.mediaSourceFactory = checkNotNull(mediaSourceFactory);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link MediaItem} to be used for the concatenated media source.
|
||||||
|
*
|
||||||
|
* <p>This {@link MediaItem} will be used as {@link Timeline.Window#mediaItem} for the
|
||||||
|
* concatenated source and will be returned by {@link Player#getCurrentMediaItem()}.
|
||||||
|
*
|
||||||
|
* <p>The default is {@code MediaItem.fromUri(Uri.EMPTY)}.
|
||||||
|
*
|
||||||
|
* @param mediaItem The {@link MediaItem}.
|
||||||
|
* @return This builder.
|
||||||
|
*/
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public Builder setMediaItem(MediaItem mediaItem) {
|
||||||
|
this.mediaItem = mediaItem;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a {@link MediaItem} to the concatenation.
|
||||||
|
*
|
||||||
|
* <p>{@link #useDefaultMediaSourceFactory(Context)} or {@link
|
||||||
|
* #setMediaSourceFactory(MediaSource.Factory)} must be called before this method.
|
||||||
|
*
|
||||||
|
* <p>This method must not be used with media items for progressive media that can't provide
|
||||||
|
* their duration with their first {@link Timeline} update. Use {@link #add(MediaItem, long)}
|
||||||
|
* instead.
|
||||||
|
*
|
||||||
|
* @param mediaItem The {@link MediaItem}.
|
||||||
|
* @return This builder.
|
||||||
|
*/
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public Builder add(MediaItem mediaItem) {
|
||||||
|
return add(mediaItem, /* initialPlaceholderDurationMs= */ C.TIME_UNSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a {@link MediaItem} to the concatenation and specifies its initial placeholder duration
|
||||||
|
* used while the actual duration is still unknown.
|
||||||
|
*
|
||||||
|
* <p>{@link #useDefaultMediaSourceFactory(Context)} or {@link
|
||||||
|
* #setMediaSourceFactory(MediaSource.Factory)} must be called before this method.
|
||||||
|
*
|
||||||
|
* <p>Setting a placeholder duration is required for media items for progressive media that
|
||||||
|
* can't provide their duration with their first {@link Timeline} update. It may also be used
|
||||||
|
* for other items to make the duration known immediately.
|
||||||
|
*
|
||||||
|
* @param mediaItem The {@link MediaItem}.
|
||||||
|
* @param initialPlaceholderDurationMs The initial placeholder duration in milliseconds used
|
||||||
|
* while the actual duration is still unknown, or {@link C#TIME_UNSET} to not define one.
|
||||||
|
* The placeholder duration is used for every {@link Timeline.Window} defined by {@link
|
||||||
|
* Timeline} of the {@link MediaItem}.
|
||||||
|
* @return This builder.
|
||||||
|
*/
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public Builder add(MediaItem mediaItem, long initialPlaceholderDurationMs) {
|
||||||
|
checkNotNull(mediaItem);
|
||||||
|
checkStateNotNull(
|
||||||
|
mediaSourceFactory,
|
||||||
|
"Must use useDefaultMediaSourceFactory or setMediaSourceFactory first.");
|
||||||
|
return add(mediaSourceFactory.createMediaSource(mediaItem), initialPlaceholderDurationMs);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a {@link MediaSource} to the concatenation.
|
||||||
|
*
|
||||||
|
* <p>This method must not be used for sources like {@link ProgressiveMediaSource} that can't
|
||||||
|
* provide their duration with their first {@link Timeline} update. Use {@link #add(MediaSource,
|
||||||
|
* long)} instead.
|
||||||
|
*
|
||||||
|
* @param mediaSource The {@link MediaSource}.
|
||||||
|
* @return This builder.
|
||||||
|
*/
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public Builder add(MediaSource mediaSource) {
|
||||||
|
return add(mediaSource, /* initialPlaceholderDurationMs= */ C.TIME_UNSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Adds a {@link MediaSource} to the concatenation and specifies its initial placeholder
|
||||||
|
* duration used while the actual duration is still unknown.
|
||||||
|
*
|
||||||
|
* <p>Setting a placeholder duration is required for sources like {@link ProgressiveMediaSource}
|
||||||
|
* that can't provide their duration with their first {@link Timeline} update. It may also be
|
||||||
|
* used for other sources to make the duration known immediately.
|
||||||
|
*
|
||||||
|
* @param mediaSource The {@link MediaSource}.
|
||||||
|
* @param initialPlaceholderDurationMs The initial placeholder duration in milliseconds used
|
||||||
|
* while the actual duration is still unknown, or {@link C#TIME_UNSET} to not define one.
|
||||||
|
* The placeholder duration is used for every {@link Timeline.Window} defined by {@link
|
||||||
|
* Timeline} of the {@link MediaSource}.
|
||||||
|
* @return This builder.
|
||||||
|
*/
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public Builder add(MediaSource mediaSource, long initialPlaceholderDurationMs) {
|
||||||
|
checkNotNull(mediaSource);
|
||||||
|
checkState(
|
||||||
|
!(mediaSource instanceof ProgressiveMediaSource)
|
||||||
|
|| initialPlaceholderDurationMs != C.TIME_UNSET,
|
||||||
|
"Progressive media source must define an initial placeholder duration.");
|
||||||
|
mediaSourceHoldersBuilder.add(
|
||||||
|
new MediaSourceHolder(mediaSource, index++, Util.msToUs(initialPlaceholderDurationMs)));
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Builds the concatenating media source. */
|
||||||
|
public ConcatenatingMediaSource2 build() {
|
||||||
|
checkArgument(index > 0, "Must add at least one source to the concatenation.");
|
||||||
|
if (mediaItem == null) {
|
||||||
|
mediaItem = MediaItem.fromUri(Uri.EMPTY);
|
||||||
|
}
|
||||||
|
return new ConcatenatingMediaSource2(mediaItem, mediaSourceHoldersBuilder.build());
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final int MSG_UPDATE_TIMELINE = 0;
|
||||||
|
|
||||||
|
private final MediaItem mediaItem;
|
||||||
|
private final ImmutableList<MediaSourceHolder> mediaSourceHolders;
|
||||||
|
private final IdentityHashMap<MediaPeriod, MediaSourceHolder> mediaSourceByMediaPeriod;
|
||||||
|
|
||||||
|
@Nullable private Handler playbackThreadHandler;
|
||||||
|
private boolean timelineUpdateScheduled;
|
||||||
|
|
||||||
|
private ConcatenatingMediaSource2(
|
||||||
|
MediaItem mediaItem, ImmutableList<MediaSourceHolder> mediaSourceHolders) {
|
||||||
|
this.mediaItem = mediaItem;
|
||||||
|
this.mediaSourceHolders = mediaSourceHolders;
|
||||||
|
mediaSourceByMediaPeriod = new IdentityHashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Timeline getInitialTimeline() {
|
||||||
|
return maybeCreateConcatenatedTimeline();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MediaItem getMediaItem() {
|
||||||
|
return mediaItem;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void prepareSourceInternal(@Nullable TransferListener mediaTransferListener) {
|
||||||
|
super.prepareSourceInternal(mediaTransferListener);
|
||||||
|
playbackThreadHandler = new Handler(/* callback= */ this::handleMessage);
|
||||||
|
for (int i = 0; i < mediaSourceHolders.size(); i++) {
|
||||||
|
MediaSourceHolder holder = mediaSourceHolders.get(i);
|
||||||
|
prepareChildSource(/* id= */ i, holder.mediaSource);
|
||||||
|
}
|
||||||
|
scheduleTimelineUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("MissingSuperCall")
|
||||||
|
@Override
|
||||||
|
protected void enableInternal() {
|
||||||
|
// Suppress enabling all child sources here as they can be lazily enabled when creating periods.
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MediaPeriod createPeriod(MediaPeriodId id, Allocator allocator, long startPositionUs) {
|
||||||
|
int holderIndex = getChildIndex(id.periodUid);
|
||||||
|
MediaSourceHolder holder = mediaSourceHolders.get(holderIndex);
|
||||||
|
MediaPeriodId childMediaPeriodId =
|
||||||
|
id.copyWithPeriodUid(getChildPeriodUid(id.periodUid))
|
||||||
|
.copyWithWindowSequenceNumber(
|
||||||
|
getChildWindowSequenceNumber(
|
||||||
|
id.windowSequenceNumber, mediaSourceHolders.size(), holder.index));
|
||||||
|
enableChildSource(holder.index);
|
||||||
|
holder.activeMediaPeriods++;
|
||||||
|
MediaPeriod mediaPeriod =
|
||||||
|
holder.mediaSource.createPeriod(childMediaPeriodId, allocator, startPositionUs);
|
||||||
|
mediaSourceByMediaPeriod.put(mediaPeriod, holder);
|
||||||
|
disableUnusedMediaSources();
|
||||||
|
return mediaPeriod;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void releasePeriod(MediaPeriod mediaPeriod) {
|
||||||
|
MediaSourceHolder holder = checkNotNull(mediaSourceByMediaPeriod.remove(mediaPeriod));
|
||||||
|
holder.mediaSource.releasePeriod(mediaPeriod);
|
||||||
|
holder.activeMediaPeriods--;
|
||||||
|
if (!mediaSourceByMediaPeriod.isEmpty()) {
|
||||||
|
disableUnusedMediaSources();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void releaseSourceInternal() {
|
||||||
|
super.releaseSourceInternal();
|
||||||
|
if (playbackThreadHandler != null) {
|
||||||
|
playbackThreadHandler.removeCallbacksAndMessages(null);
|
||||||
|
playbackThreadHandler = null;
|
||||||
|
}
|
||||||
|
timelineUpdateScheduled = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void onChildSourceInfoRefreshed(
|
||||||
|
Integer childSourceId, MediaSource mediaSource, Timeline newTimeline) {
|
||||||
|
scheduleTimelineUpdate();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@Nullable
|
||||||
|
protected MediaPeriodId getMediaPeriodIdForChildMediaPeriodId(
|
||||||
|
Integer childSourceId, MediaPeriodId mediaPeriodId) {
|
||||||
|
int childIndex =
|
||||||
|
getChildIndexFromChildWindowSequenceNumber(
|
||||||
|
mediaPeriodId.windowSequenceNumber, mediaSourceHolders.size());
|
||||||
|
if (childSourceId != childIndex) {
|
||||||
|
// Ensure the reported media period id has the expected window sequence number. Otherwise it
|
||||||
|
// does not belong to this child source.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
long windowSequenceNumber =
|
||||||
|
getWindowSequenceNumberFromChildWindowSequenceNumber(
|
||||||
|
mediaPeriodId.windowSequenceNumber, mediaSourceHolders.size());
|
||||||
|
Object periodUid = getPeriodUid(childSourceId, mediaPeriodId.periodUid);
|
||||||
|
return mediaPeriodId
|
||||||
|
.copyWithPeriodUid(periodUid)
|
||||||
|
.copyWithWindowSequenceNumber(windowSequenceNumber);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected int getWindowIndexForChildWindowIndex(Integer childSourceId, int windowIndex) {
|
||||||
|
return 0;
|
||||||
|
}
|
||||||
|
|
||||||
|
private boolean handleMessage(Message msg) {
|
||||||
|
if (msg.what == MSG_UPDATE_TIMELINE) {
|
||||||
|
updateTimeline();
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
private void scheduleTimelineUpdate() {
|
||||||
|
if (!timelineUpdateScheduled) {
|
||||||
|
checkNotNull(playbackThreadHandler).obtainMessage(MSG_UPDATE_TIMELINE).sendToTarget();
|
||||||
|
timelineUpdateScheduled = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void updateTimeline() {
|
||||||
|
timelineUpdateScheduled = false;
|
||||||
|
@Nullable ConcatenatedTimeline timeline = maybeCreateConcatenatedTimeline();
|
||||||
|
if (timeline != null) {
|
||||||
|
refreshSourceInfo(timeline);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void disableUnusedMediaSources() {
|
||||||
|
for (int i = 0; i < mediaSourceHolders.size(); i++) {
|
||||||
|
MediaSourceHolder holder = mediaSourceHolders.get(i);
|
||||||
|
if (holder.activeMediaPeriods == 0) {
|
||||||
|
disableChildSource(holder.index);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private ConcatenatedTimeline maybeCreateConcatenatedTimeline() {
|
||||||
|
Timeline.Window window = new Timeline.Window();
|
||||||
|
Timeline.Period period = new Timeline.Period();
|
||||||
|
ImmutableList.Builder<Timeline> timelinesBuilder = ImmutableList.builder();
|
||||||
|
ImmutableList.Builder<Integer> firstPeriodIndicesBuilder = ImmutableList.builder();
|
||||||
|
ImmutableList.Builder<Long> periodOffsetsInWindowUsBuilder = ImmutableList.builder();
|
||||||
|
int periodCount = 0;
|
||||||
|
boolean isSeekable = true;
|
||||||
|
boolean isDynamic = false;
|
||||||
|
long durationUs = 0;
|
||||||
|
long defaultPositionUs = 0;
|
||||||
|
long nextPeriodOffsetInWindowUs = 0;
|
||||||
|
boolean manifestsAreIdentical = true;
|
||||||
|
boolean hasInitialManifest = false;
|
||||||
|
@Nullable Object initialManifest = null;
|
||||||
|
for (int i = 0; i < mediaSourceHolders.size(); i++) {
|
||||||
|
MediaSourceHolder holder = mediaSourceHolders.get(i);
|
||||||
|
Timeline timeline = holder.mediaSource.getTimeline();
|
||||||
|
checkArgument(!timeline.isEmpty(), "Can't concatenate empty child Timeline.");
|
||||||
|
timelinesBuilder.add(timeline);
|
||||||
|
firstPeriodIndicesBuilder.add(periodCount);
|
||||||
|
periodCount += timeline.getPeriodCount();
|
||||||
|
for (int j = 0; j < timeline.getWindowCount(); j++) {
|
||||||
|
timeline.getWindow(/* windowIndex= */ j, window);
|
||||||
|
if (!hasInitialManifest) {
|
||||||
|
initialManifest = window.manifest;
|
||||||
|
hasInitialManifest = true;
|
||||||
|
}
|
||||||
|
manifestsAreIdentical =
|
||||||
|
manifestsAreIdentical && Util.areEqual(initialManifest, window.manifest);
|
||||||
|
|
||||||
|
long windowDurationUs = window.durationUs;
|
||||||
|
if (windowDurationUs == C.TIME_UNSET) {
|
||||||
|
if (holder.initialPlaceholderDurationUs == C.TIME_UNSET) {
|
||||||
|
// Source duration isn't known yet and we have no placeholder duration.
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
windowDurationUs = holder.initialPlaceholderDurationUs;
|
||||||
|
}
|
||||||
|
durationUs += windowDurationUs;
|
||||||
|
if (holder.index == 0 && j == 0) {
|
||||||
|
defaultPositionUs = window.defaultPositionUs;
|
||||||
|
nextPeriodOffsetInWindowUs = -window.positionInFirstPeriodUs;
|
||||||
|
} else {
|
||||||
|
checkArgument(
|
||||||
|
window.positionInFirstPeriodUs == 0,
|
||||||
|
"Can't concatenate windows. A window has a non-zero offset in a period.");
|
||||||
|
}
|
||||||
|
// Assume placeholder windows are seekable to not prevent seeking in other periods.
|
||||||
|
isSeekable &= window.isSeekable || window.isPlaceholder;
|
||||||
|
isDynamic |= window.isDynamic;
|
||||||
|
}
|
||||||
|
int childPeriodCount = timeline.getPeriodCount();
|
||||||
|
for (int j = 0; j < childPeriodCount; j++) {
|
||||||
|
periodOffsetsInWindowUsBuilder.add(nextPeriodOffsetInWindowUs);
|
||||||
|
timeline.getPeriod(/* periodIndex= */ j, period);
|
||||||
|
long periodDurationUs = period.durationUs;
|
||||||
|
if (periodDurationUs == C.TIME_UNSET) {
|
||||||
|
checkArgument(
|
||||||
|
childPeriodCount == 1,
|
||||||
|
"Can't concatenate multiple periods with unknown duration in one window.");
|
||||||
|
long windowDurationUs =
|
||||||
|
window.durationUs != C.TIME_UNSET
|
||||||
|
? window.durationUs
|
||||||
|
: holder.initialPlaceholderDurationUs;
|
||||||
|
periodDurationUs = windowDurationUs + window.positionInFirstPeriodUs;
|
||||||
|
}
|
||||||
|
nextPeriodOffsetInWindowUs += periodDurationUs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return new ConcatenatedTimeline(
|
||||||
|
mediaItem,
|
||||||
|
timelinesBuilder.build(),
|
||||||
|
firstPeriodIndicesBuilder.build(),
|
||||||
|
periodOffsetsInWindowUsBuilder.build(),
|
||||||
|
isSeekable,
|
||||||
|
isDynamic,
|
||||||
|
durationUs,
|
||||||
|
defaultPositionUs,
|
||||||
|
manifestsAreIdentical ? initialManifest : null);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the period uid for the concatenated source from the child index and child period uid.
|
||||||
|
*/
|
||||||
|
private static Object getPeriodUid(int childIndex, Object childPeriodUid) {
|
||||||
|
return Pair.create(childIndex, childPeriodUid);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the child index from the period uid of the concatenated source. */
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static int getChildIndex(Object periodUid) {
|
||||||
|
return ((Pair<Integer, Object>) periodUid).first;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the uid of child period from the period uid of the concatenated source. */
|
||||||
|
@SuppressWarnings("unchecked")
|
||||||
|
private static Object getChildPeriodUid(Object periodUid) {
|
||||||
|
return ((Pair<Integer, Object>) periodUid).second;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the window sequence number used for the child source. */
|
||||||
|
private static long getChildWindowSequenceNumber(
|
||||||
|
long windowSequenceNumber, int childCount, int childIndex) {
|
||||||
|
return windowSequenceNumber * childCount + childIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the index of the child source from a child window sequence number. */
|
||||||
|
private static int getChildIndexFromChildWindowSequenceNumber(
|
||||||
|
long childWindowSequenceNumber, int childCount) {
|
||||||
|
return (int) (childWindowSequenceNumber % childCount);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the concatenated window sequence number from a child window sequence number. */
|
||||||
|
private static long getWindowSequenceNumberFromChildWindowSequenceNumber(
|
||||||
|
long childWindowSequenceNumber, int childCount) {
|
||||||
|
return childWindowSequenceNumber / childCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/* package */ static final class MediaSourceHolder {
|
||||||
|
|
||||||
|
public final MaskingMediaSource mediaSource;
|
||||||
|
public final int index;
|
||||||
|
public final long initialPlaceholderDurationUs;
|
||||||
|
|
||||||
|
public int activeMediaPeriods;
|
||||||
|
|
||||||
|
public MediaSourceHolder(
|
||||||
|
MediaSource mediaSource, int index, long initialPlaceholderDurationUs) {
|
||||||
|
this.mediaSource = new MaskingMediaSource(mediaSource, /* useLazyPreparation= */ false);
|
||||||
|
this.index = index;
|
||||||
|
this.initialPlaceholderDurationUs = initialPlaceholderDurationUs;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class ConcatenatedTimeline extends Timeline {
|
||||||
|
|
||||||
|
private final MediaItem mediaItem;
|
||||||
|
private final ImmutableList<Timeline> timelines;
|
||||||
|
private final ImmutableList<Integer> firstPeriodIndices;
|
||||||
|
private final ImmutableList<Long> periodOffsetsInWindowUs;
|
||||||
|
private final boolean isSeekable;
|
||||||
|
private final boolean isDynamic;
|
||||||
|
private final long durationUs;
|
||||||
|
private final long defaultPositionUs;
|
||||||
|
@Nullable private final Object manifest;
|
||||||
|
|
||||||
|
public ConcatenatedTimeline(
|
||||||
|
MediaItem mediaItem,
|
||||||
|
ImmutableList<Timeline> timelines,
|
||||||
|
ImmutableList<Integer> firstPeriodIndices,
|
||||||
|
ImmutableList<Long> periodOffsetsInWindowUs,
|
||||||
|
boolean isSeekable,
|
||||||
|
boolean isDynamic,
|
||||||
|
long durationUs,
|
||||||
|
long defaultPositionUs,
|
||||||
|
@Nullable Object manifest) {
|
||||||
|
this.mediaItem = mediaItem;
|
||||||
|
this.timelines = timelines;
|
||||||
|
this.firstPeriodIndices = firstPeriodIndices;
|
||||||
|
this.periodOffsetsInWindowUs = periodOffsetsInWindowUs;
|
||||||
|
this.isSeekable = isSeekable;
|
||||||
|
this.isDynamic = isDynamic;
|
||||||
|
this.durationUs = durationUs;
|
||||||
|
this.defaultPositionUs = defaultPositionUs;
|
||||||
|
this.manifest = manifest;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getWindowCount() {
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int getPeriodCount() {
|
||||||
|
return periodOffsetsInWindowUs.size();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final Window getWindow(
|
||||||
|
int windowIndex, Window window, long defaultPositionProjectionUs) {
|
||||||
|
return window.set(
|
||||||
|
Window.SINGLE_WINDOW_UID,
|
||||||
|
mediaItem,
|
||||||
|
manifest,
|
||||||
|
/* presentationStartTimeMs= */ C.TIME_UNSET,
|
||||||
|
/* windowStartTimeMs= */ C.TIME_UNSET,
|
||||||
|
/* elapsedRealtimeEpochOffsetMs= */ C.TIME_UNSET,
|
||||||
|
isSeekable,
|
||||||
|
isDynamic,
|
||||||
|
/* liveConfiguration= */ null,
|
||||||
|
defaultPositionUs,
|
||||||
|
durationUs,
|
||||||
|
/* firstPeriodIndex= */ 0,
|
||||||
|
/* lastPeriodIndex= */ getPeriodCount() - 1,
|
||||||
|
/* positionInFirstPeriodUs= */ -periodOffsetsInWindowUs.get(0));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final Period getPeriodByUid(Object periodUid, Period period) {
|
||||||
|
int childIndex = getChildIndex(periodUid);
|
||||||
|
Object childPeriodUid = getChildPeriodUid(periodUid);
|
||||||
|
Timeline timeline = timelines.get(childIndex);
|
||||||
|
int periodIndex =
|
||||||
|
firstPeriodIndices.get(childIndex) + timeline.getIndexOfPeriod(childPeriodUid);
|
||||||
|
timeline.getPeriodByUid(childPeriodUid, period);
|
||||||
|
period.windowIndex = 0;
|
||||||
|
period.positionInWindowUs = periodOffsetsInWindowUs.get(periodIndex);
|
||||||
|
period.uid = periodUid;
|
||||||
|
return period;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final Period getPeriod(int periodIndex, Period period, boolean setIds) {
|
||||||
|
int childIndex = getChildIndexByPeriodIndex(periodIndex);
|
||||||
|
int firstPeriodIndexInChild = firstPeriodIndices.get(childIndex);
|
||||||
|
timelines.get(childIndex).getPeriod(periodIndex - firstPeriodIndexInChild, period, setIds);
|
||||||
|
period.windowIndex = 0;
|
||||||
|
period.positionInWindowUs = periodOffsetsInWindowUs.get(periodIndex);
|
||||||
|
if (setIds) {
|
||||||
|
period.uid = getPeriodUid(childIndex, checkNotNull(period.uid));
|
||||||
|
}
|
||||||
|
return period;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final int getIndexOfPeriod(Object uid) {
|
||||||
|
if (!(uid instanceof Pair) || !(((Pair<?, ?>) uid).first instanceof Integer)) {
|
||||||
|
return C.INDEX_UNSET;
|
||||||
|
}
|
||||||
|
int childIndex = getChildIndex(uid);
|
||||||
|
Object periodUid = getChildPeriodUid(uid);
|
||||||
|
int periodIndexInChild = timelines.get(childIndex).getIndexOfPeriod(periodUid);
|
||||||
|
return periodIndexInChild == C.INDEX_UNSET
|
||||||
|
? C.INDEX_UNSET
|
||||||
|
: firstPeriodIndices.get(childIndex) + periodIndexInChild;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final Object getUidOfPeriod(int periodIndex) {
|
||||||
|
int childIndex = getChildIndexByPeriodIndex(periodIndex);
|
||||||
|
int firstPeriodIndexInChild = firstPeriodIndices.get(childIndex);
|
||||||
|
Object periodUidInChild =
|
||||||
|
timelines.get(childIndex).getUidOfPeriod(periodIndex - firstPeriodIndexInChild);
|
||||||
|
return getPeriodUid(childIndex, periodUidInChild);
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getChildIndexByPeriodIndex(int periodIndex) {
|
||||||
|
return Util.binarySearchFloor(
|
||||||
|
firstPeriodIndices, periodIndex + 1, /* inclusive= */ false, /* stayInBounds= */ false);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -118,17 +118,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
for (int i = 0; i < selections.length; i++) {
|
for (int i = 0; i < selections.length; i++) {
|
||||||
Integer streamChildIndex = streams[i] == null ? null : streamPeriodIndices.get(streams[i]);
|
Integer streamChildIndex = streams[i] == null ? null : streamPeriodIndices.get(streams[i]);
|
||||||
streamChildIndices[i] = streamChildIndex == null ? C.INDEX_UNSET : streamChildIndex;
|
streamChildIndices[i] = streamChildIndex == null ? C.INDEX_UNSET : streamChildIndex;
|
||||||
selectionChildIndices[i] = C.INDEX_UNSET;
|
|
||||||
if (selections[i] != null) {
|
if (selections[i] != null) {
|
||||||
TrackGroup mergedTrackGroup = selections[i].getTrackGroup();
|
TrackGroup mergedTrackGroup = selections[i].getTrackGroup();
|
||||||
TrackGroup childTrackGroup =
|
// mergedTrackGroup.id is 'periods array index' + ":" + childTrackGroup.id
|
||||||
checkNotNull(childTrackGroupByMergedTrackGroup.get(mergedTrackGroup));
|
selectionChildIndices[i] =
|
||||||
for (int j = 0; j < periods.length; j++) {
|
Integer.parseInt(mergedTrackGroup.id.substring(0, mergedTrackGroup.id.indexOf(":")));
|
||||||
if (periods[j].getTrackGroups().indexOf(childTrackGroup) != C.INDEX_UNSET) {
|
} else {
|
||||||
selectionChildIndices[i] = j;
|
selectionChildIndices[i] = C.INDEX_UNSET;
|
||||||
break;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
streamPeriodIndices.clear();
|
streamPeriodIndices.clear();
|
||||||
|
@ -72,7 +72,7 @@ public final class MergingMediaSource extends CompositeMediaSource<Integer> {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static final int PERIOD_COUNT_UNSET = -1;
|
private static final int PERIOD_COUNT_UNSET = -1;
|
||||||
private static final MediaItem EMPTY_MEDIA_ITEM =
|
private static final MediaItem PLACEHOLDER_MEDIA_ITEM =
|
||||||
new MediaItem.Builder().setMediaId("MergingMediaSource").build();
|
new MediaItem.Builder().setMediaId("MergingMediaSource").build();
|
||||||
|
|
||||||
private final boolean adjustPeriodTimeOffsets;
|
private final boolean adjustPeriodTimeOffsets;
|
||||||
@ -163,7 +163,7 @@ public final class MergingMediaSource extends CompositeMediaSource<Integer> {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MediaItem getMediaItem() {
|
public MediaItem getMediaItem() {
|
||||||
return mediaSources.length > 0 ? mediaSources[0].getMediaItem() : EMPTY_MEDIA_ITEM;
|
return mediaSources.length > 0 ? mediaSources[0].getMediaItem() : PLACEHOLDER_MEDIA_ITEM;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -15,10 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.exoplayer.source;
|
package androidx.media3.exoplayer.source;
|
||||||
|
|
||||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.IntDef;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.Bundleable;
|
import androidx.media3.common.Bundleable;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
@ -26,11 +23,8 @@ import androidx.media3.common.TrackGroup;
|
|||||||
import androidx.media3.common.util.BundleableUtil;
|
import androidx.media3.common.util.BundleableUtil;
|
||||||
import androidx.media3.common.util.Log;
|
import androidx.media3.common.util.Log;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import androidx.media3.common.util.Util;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import java.lang.annotation.Documented;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -118,21 +112,13 @@ public final class TrackGroupArray implements Bundleable {
|
|||||||
|
|
||||||
// Bundleable implementation.
|
// Bundleable implementation.
|
||||||
|
|
||||||
@Documented
|
private static final String FIELD_TRACK_GROUPS = Util.intToStringMaxRadix(0);
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
|
||||||
@Target(TYPE_USE)
|
|
||||||
@IntDef({
|
|
||||||
FIELD_TRACK_GROUPS,
|
|
||||||
})
|
|
||||||
private @interface FieldNumber {}
|
|
||||||
|
|
||||||
private static final int FIELD_TRACK_GROUPS = 0;
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putParcelableArrayList(
|
bundle.putParcelableArrayList(
|
||||||
keyForField(FIELD_TRACK_GROUPS), BundleableUtil.toBundleArrayList(trackGroups));
|
FIELD_TRACK_GROUPS, BundleableUtil.toBundleArrayList(trackGroups));
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,8 +126,7 @@ public final class TrackGroupArray implements Bundleable {
|
|||||||
public static final Creator<TrackGroupArray> CREATOR =
|
public static final Creator<TrackGroupArray> CREATOR =
|
||||||
bundle -> {
|
bundle -> {
|
||||||
@Nullable
|
@Nullable
|
||||||
List<Bundle> trackGroupBundles =
|
List<Bundle> trackGroupBundles = bundle.getParcelableArrayList(FIELD_TRACK_GROUPS);
|
||||||
bundle.getParcelableArrayList(keyForField(FIELD_TRACK_GROUPS));
|
|
||||||
if (trackGroupBundles == null) {
|
if (trackGroupBundles == null) {
|
||||||
return new TrackGroupArray();
|
return new TrackGroupArray();
|
||||||
}
|
}
|
||||||
@ -163,8 +148,4 @@ public final class TrackGroupArray implements Bundleable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String keyForField(@FieldNumber int field) {
|
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -427,7 +427,7 @@ public final class TextRenderer extends BaseRenderer implements Callback {
|
|||||||
@SideEffectFree
|
@SideEffectFree
|
||||||
private long getCurrentEventTimeUs(long positionUs) {
|
private long getCurrentEventTimeUs(long positionUs) {
|
||||||
int nextEventTimeIndex = subtitle.getNextEventTimeIndex(positionUs);
|
int nextEventTimeIndex = subtitle.getNextEventTimeIndex(positionUs);
|
||||||
if (nextEventTimeIndex == 0) {
|
if (nextEventTimeIndex == 0 || subtitle.getEventTimeCount() == 0) {
|
||||||
return subtitle.timeUs;
|
return subtitle.timeUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -827,69 +827,62 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
|||||||
// Video
|
// Video
|
||||||
setExceedVideoConstraintsIfNecessary(
|
setExceedVideoConstraintsIfNecessary(
|
||||||
bundle.getBoolean(
|
bundle.getBoolean(
|
||||||
Parameters.keyForField(Parameters.FIELD_EXCEED_VIDEO_CONSTRAINTS_IF_NECESSARY),
|
Parameters.FIELD_EXCEED_VIDEO_CONSTRAINTS_IF_NECESSARY,
|
||||||
defaultValue.exceedVideoConstraintsIfNecessary));
|
defaultValue.exceedVideoConstraintsIfNecessary));
|
||||||
setAllowVideoMixedMimeTypeAdaptiveness(
|
setAllowVideoMixedMimeTypeAdaptiveness(
|
||||||
bundle.getBoolean(
|
bundle.getBoolean(
|
||||||
Parameters.keyForField(Parameters.FIELD_ALLOW_VIDEO_MIXED_MIME_TYPE_ADAPTIVENESS),
|
Parameters.FIELD_ALLOW_VIDEO_MIXED_MIME_TYPE_ADAPTIVENESS,
|
||||||
defaultValue.allowVideoMixedMimeTypeAdaptiveness));
|
defaultValue.allowVideoMixedMimeTypeAdaptiveness));
|
||||||
setAllowVideoNonSeamlessAdaptiveness(
|
setAllowVideoNonSeamlessAdaptiveness(
|
||||||
bundle.getBoolean(
|
bundle.getBoolean(
|
||||||
Parameters.keyForField(Parameters.FIELD_ALLOW_VIDEO_NON_SEAMLESS_ADAPTIVENESS),
|
Parameters.FIELD_ALLOW_VIDEO_NON_SEAMLESS_ADAPTIVENESS,
|
||||||
defaultValue.allowVideoNonSeamlessAdaptiveness));
|
defaultValue.allowVideoNonSeamlessAdaptiveness));
|
||||||
setAllowVideoMixedDecoderSupportAdaptiveness(
|
setAllowVideoMixedDecoderSupportAdaptiveness(
|
||||||
bundle.getBoolean(
|
bundle.getBoolean(
|
||||||
Parameters.keyForField(
|
Parameters.FIELD_ALLOW_VIDEO_MIXED_DECODER_SUPPORT_ADAPTIVENESS,
|
||||||
Parameters.FIELD_ALLOW_VIDEO_MIXED_DECODER_SUPPORT_ADAPTIVENESS),
|
|
||||||
defaultValue.allowVideoMixedDecoderSupportAdaptiveness));
|
defaultValue.allowVideoMixedDecoderSupportAdaptiveness));
|
||||||
// Audio
|
// Audio
|
||||||
setExceedAudioConstraintsIfNecessary(
|
setExceedAudioConstraintsIfNecessary(
|
||||||
bundle.getBoolean(
|
bundle.getBoolean(
|
||||||
Parameters.keyForField(Parameters.FIELD_EXCEED_AUDIO_CONSTRAINTS_IF_NCESSARY),
|
Parameters.FIELD_EXCEED_AUDIO_CONSTRAINTS_IF_NECESSARY,
|
||||||
defaultValue.exceedAudioConstraintsIfNecessary));
|
defaultValue.exceedAudioConstraintsIfNecessary));
|
||||||
setAllowAudioMixedMimeTypeAdaptiveness(
|
setAllowAudioMixedMimeTypeAdaptiveness(
|
||||||
bundle.getBoolean(
|
bundle.getBoolean(
|
||||||
Parameters.keyForField(Parameters.FIELD_ALLOW_AUDIO_MIXED_MIME_TYPE_ADAPTIVENESS),
|
Parameters.FIELD_ALLOW_AUDIO_MIXED_MIME_TYPE_ADAPTIVENESS,
|
||||||
defaultValue.allowAudioMixedMimeTypeAdaptiveness));
|
defaultValue.allowAudioMixedMimeTypeAdaptiveness));
|
||||||
setAllowAudioMixedSampleRateAdaptiveness(
|
setAllowAudioMixedSampleRateAdaptiveness(
|
||||||
bundle.getBoolean(
|
bundle.getBoolean(
|
||||||
Parameters.keyForField(Parameters.FIELD_ALLOW_AUDIO_MIXED_SAMPLE_RATE_ADAPTIVENESS),
|
Parameters.FIELD_ALLOW_AUDIO_MIXED_SAMPLE_RATE_ADAPTIVENESS,
|
||||||
defaultValue.allowAudioMixedSampleRateAdaptiveness));
|
defaultValue.allowAudioMixedSampleRateAdaptiveness));
|
||||||
setAllowAudioMixedChannelCountAdaptiveness(
|
setAllowAudioMixedChannelCountAdaptiveness(
|
||||||
bundle.getBoolean(
|
bundle.getBoolean(
|
||||||
Parameters.keyForField(
|
Parameters.FIELD_ALLOW_AUDIO_MIXED_CHANNEL_COUNT_ADAPTIVENESS,
|
||||||
Parameters.FIELD_ALLOW_AUDIO_MIXED_CHANNEL_COUNT_ADAPTIVENESS),
|
|
||||||
defaultValue.allowAudioMixedChannelCountAdaptiveness));
|
defaultValue.allowAudioMixedChannelCountAdaptiveness));
|
||||||
setAllowAudioMixedDecoderSupportAdaptiveness(
|
setAllowAudioMixedDecoderSupportAdaptiveness(
|
||||||
bundle.getBoolean(
|
bundle.getBoolean(
|
||||||
Parameters.keyForField(
|
Parameters.FIELD_ALLOW_AUDIO_MIXED_DECODER_SUPPORT_ADAPTIVENESS,
|
||||||
Parameters.FIELD_ALLOW_AUDIO_MIXED_DECODER_SUPPORT_ADAPTIVENESS),
|
|
||||||
defaultValue.allowAudioMixedDecoderSupportAdaptiveness));
|
defaultValue.allowAudioMixedDecoderSupportAdaptiveness));
|
||||||
setConstrainAudioChannelCountToDeviceCapabilities(
|
setConstrainAudioChannelCountToDeviceCapabilities(
|
||||||
bundle.getBoolean(
|
bundle.getBoolean(
|
||||||
Parameters.keyForField(
|
Parameters.FIELD_CONSTRAIN_AUDIO_CHANNEL_COUNT_TO_DEVICE_CAPABILITIES,
|
||||||
Parameters.FIELD_CONSTRAIN_AUDIO_CHANNEL_COUNT_TO_DEVICE_CAPABILITIES),
|
|
||||||
defaultValue.constrainAudioChannelCountToDeviceCapabilities));
|
defaultValue.constrainAudioChannelCountToDeviceCapabilities));
|
||||||
// General
|
// General
|
||||||
setExceedRendererCapabilitiesIfNecessary(
|
setExceedRendererCapabilitiesIfNecessary(
|
||||||
bundle.getBoolean(
|
bundle.getBoolean(
|
||||||
Parameters.keyForField(Parameters.FIELD_EXCEED_RENDERER_CAPABILITIES_IF_NECESSARY),
|
Parameters.FIELD_EXCEED_RENDERER_CAPABILITIES_IF_NECESSARY,
|
||||||
defaultValue.exceedRendererCapabilitiesIfNecessary));
|
defaultValue.exceedRendererCapabilitiesIfNecessary));
|
||||||
setTunnelingEnabled(
|
setTunnelingEnabled(
|
||||||
bundle.getBoolean(
|
bundle.getBoolean(Parameters.FIELD_TUNNELING_ENABLED, defaultValue.tunnelingEnabled));
|
||||||
Parameters.keyForField(Parameters.FIELD_TUNNELING_ENABLED),
|
|
||||||
defaultValue.tunnelingEnabled));
|
|
||||||
setAllowMultipleAdaptiveSelections(
|
setAllowMultipleAdaptiveSelections(
|
||||||
bundle.getBoolean(
|
bundle.getBoolean(
|
||||||
Parameters.keyForField(Parameters.FIELD_ALLOW_MULTIPLE_ADAPTIVE_SELECTIONS),
|
Parameters.FIELD_ALLOW_MULTIPLE_ADAPTIVE_SELECTIONS,
|
||||||
defaultValue.allowMultipleAdaptiveSelections));
|
defaultValue.allowMultipleAdaptiveSelections));
|
||||||
// Overrides
|
// Overrides
|
||||||
selectionOverrides = new SparseArray<>();
|
selectionOverrides = new SparseArray<>();
|
||||||
setSelectionOverridesFromBundle(bundle);
|
setSelectionOverridesFromBundle(bundle);
|
||||||
rendererDisabledFlags =
|
rendererDisabledFlags =
|
||||||
makeSparseBooleanArrayFromTrueKeys(
|
makeSparseBooleanArrayFromTrueKeys(
|
||||||
bundle.getIntArray(
|
bundle.getIntArray(Parameters.FIELD_RENDERER_DISABLED_INDICES));
|
||||||
Parameters.keyForField(Parameters.FIELD_RENDERER_DISABLED_INDICES)));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@CanIgnoreReturnValue
|
@CanIgnoreReturnValue
|
||||||
@ -1571,20 +1564,17 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
|||||||
private void setSelectionOverridesFromBundle(Bundle bundle) {
|
private void setSelectionOverridesFromBundle(Bundle bundle) {
|
||||||
@Nullable
|
@Nullable
|
||||||
int[] rendererIndices =
|
int[] rendererIndices =
|
||||||
bundle.getIntArray(
|
bundle.getIntArray(Parameters.FIELD_SELECTION_OVERRIDES_RENDERER_INDICES);
|
||||||
Parameters.keyForField(Parameters.FIELD_SELECTION_OVERRIDES_RENDERER_INDICES));
|
|
||||||
@Nullable
|
@Nullable
|
||||||
ArrayList<Bundle> trackGroupArrayBundles =
|
ArrayList<Bundle> trackGroupArrayBundles =
|
||||||
bundle.getParcelableArrayList(
|
bundle.getParcelableArrayList(Parameters.FIELD_SELECTION_OVERRIDES_TRACK_GROUP_ARRAYS);
|
||||||
Parameters.keyForField(Parameters.FIELD_SELECTION_OVERRIDES_TRACK_GROUP_ARRAYS));
|
|
||||||
List<TrackGroupArray> trackGroupArrays =
|
List<TrackGroupArray> trackGroupArrays =
|
||||||
trackGroupArrayBundles == null
|
trackGroupArrayBundles == null
|
||||||
? ImmutableList.of()
|
? ImmutableList.of()
|
||||||
: BundleableUtil.fromBundleList(TrackGroupArray.CREATOR, trackGroupArrayBundles);
|
: BundleableUtil.fromBundleList(TrackGroupArray.CREATOR, trackGroupArrayBundles);
|
||||||
@Nullable
|
@Nullable
|
||||||
SparseArray<Bundle> selectionOverrideBundles =
|
SparseArray<Bundle> selectionOverrideBundles =
|
||||||
bundle.getSparseParcelableArray(
|
bundle.getSparseParcelableArray(Parameters.FIELD_SELECTION_OVERRIDES);
|
||||||
Parameters.keyForField(Parameters.FIELD_SELECTION_OVERRIDES));
|
|
||||||
SparseArray<SelectionOverride> selectionOverrides =
|
SparseArray<SelectionOverride> selectionOverrides =
|
||||||
selectionOverrideBundles == null
|
selectionOverrideBundles == null
|
||||||
? new SparseArray<>()
|
? new SparseArray<>()
|
||||||
@ -1874,32 +1864,40 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
|||||||
|
|
||||||
// Bundleable implementation.
|
// Bundleable implementation.
|
||||||
|
|
||||||
private static final int FIELD_EXCEED_VIDEO_CONSTRAINTS_IF_NECESSARY = FIELD_CUSTOM_ID_BASE;
|
private static final String FIELD_EXCEED_VIDEO_CONSTRAINTS_IF_NECESSARY =
|
||||||
private static final int FIELD_ALLOW_VIDEO_MIXED_MIME_TYPE_ADAPTIVENESS =
|
Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE);
|
||||||
FIELD_CUSTOM_ID_BASE + 1;
|
private static final String FIELD_ALLOW_VIDEO_MIXED_MIME_TYPE_ADAPTIVENESS =
|
||||||
private static final int FIELD_ALLOW_VIDEO_NON_SEAMLESS_ADAPTIVENESS = FIELD_CUSTOM_ID_BASE + 2;
|
Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 1);
|
||||||
private static final int FIELD_EXCEED_AUDIO_CONSTRAINTS_IF_NCESSARY = FIELD_CUSTOM_ID_BASE + 3;
|
private static final String FIELD_ALLOW_VIDEO_NON_SEAMLESS_ADAPTIVENESS =
|
||||||
private static final int FIELD_ALLOW_AUDIO_MIXED_MIME_TYPE_ADAPTIVENESS =
|
Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 2);
|
||||||
FIELD_CUSTOM_ID_BASE + 4;
|
private static final String FIELD_EXCEED_AUDIO_CONSTRAINTS_IF_NECESSARY =
|
||||||
private static final int FIELD_ALLOW_AUDIO_MIXED_SAMPLE_RATE_ADAPTIVENESS =
|
Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 3);
|
||||||
FIELD_CUSTOM_ID_BASE + 5;
|
private static final String FIELD_ALLOW_AUDIO_MIXED_MIME_TYPE_ADAPTIVENESS =
|
||||||
private static final int FIELD_ALLOW_AUDIO_MIXED_CHANNEL_COUNT_ADAPTIVENESS =
|
Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 4);
|
||||||
FIELD_CUSTOM_ID_BASE + 6;
|
private static final String FIELD_ALLOW_AUDIO_MIXED_SAMPLE_RATE_ADAPTIVENESS =
|
||||||
private static final int FIELD_EXCEED_RENDERER_CAPABILITIES_IF_NECESSARY =
|
Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 5);
|
||||||
FIELD_CUSTOM_ID_BASE + 7;
|
private static final String FIELD_ALLOW_AUDIO_MIXED_CHANNEL_COUNT_ADAPTIVENESS =
|
||||||
private static final int FIELD_TUNNELING_ENABLED = FIELD_CUSTOM_ID_BASE + 8;
|
Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 6);
|
||||||
private static final int FIELD_ALLOW_MULTIPLE_ADAPTIVE_SELECTIONS = FIELD_CUSTOM_ID_BASE + 9;
|
private static final String FIELD_EXCEED_RENDERER_CAPABILITIES_IF_NECESSARY =
|
||||||
private static final int FIELD_SELECTION_OVERRIDES_RENDERER_INDICES = FIELD_CUSTOM_ID_BASE + 10;
|
Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 7);
|
||||||
private static final int FIELD_SELECTION_OVERRIDES_TRACK_GROUP_ARRAYS =
|
private static final String FIELD_TUNNELING_ENABLED =
|
||||||
FIELD_CUSTOM_ID_BASE + 11;
|
Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 8);
|
||||||
private static final int FIELD_SELECTION_OVERRIDES = FIELD_CUSTOM_ID_BASE + 12;
|
private static final String FIELD_ALLOW_MULTIPLE_ADAPTIVE_SELECTIONS =
|
||||||
private static final int FIELD_RENDERER_DISABLED_INDICES = FIELD_CUSTOM_ID_BASE + 13;
|
Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 9);
|
||||||
private static final int FIELD_ALLOW_VIDEO_MIXED_DECODER_SUPPORT_ADAPTIVENESS =
|
private static final String FIELD_SELECTION_OVERRIDES_RENDERER_INDICES =
|
||||||
FIELD_CUSTOM_ID_BASE + 14;
|
Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 10);
|
||||||
private static final int FIELD_ALLOW_AUDIO_MIXED_DECODER_SUPPORT_ADAPTIVENESS =
|
private static final String FIELD_SELECTION_OVERRIDES_TRACK_GROUP_ARRAYS =
|
||||||
FIELD_CUSTOM_ID_BASE + 15;
|
Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 11);
|
||||||
private static final int FIELD_CONSTRAIN_AUDIO_CHANNEL_COUNT_TO_DEVICE_CAPABILITIES =
|
private static final String FIELD_SELECTION_OVERRIDES =
|
||||||
FIELD_CUSTOM_ID_BASE + 16;
|
Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 12);
|
||||||
|
private static final String FIELD_RENDERER_DISABLED_INDICES =
|
||||||
|
Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 13);
|
||||||
|
private static final String FIELD_ALLOW_VIDEO_MIXED_DECODER_SUPPORT_ADAPTIVENESS =
|
||||||
|
Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 14);
|
||||||
|
private static final String FIELD_ALLOW_AUDIO_MIXED_DECODER_SUPPORT_ADAPTIVENESS =
|
||||||
|
Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 15);
|
||||||
|
private static final String FIELD_CONSTRAIN_AUDIO_CHANNEL_COUNT_TO_DEVICE_CAPABILITIES =
|
||||||
|
Util.intToStringMaxRadix(FIELD_CUSTOM_ID_BASE + 16);
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
@ -1907,49 +1905,40 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
|||||||
|
|
||||||
// Video
|
// Video
|
||||||
bundle.putBoolean(
|
bundle.putBoolean(
|
||||||
keyForField(FIELD_EXCEED_VIDEO_CONSTRAINTS_IF_NECESSARY),
|
FIELD_EXCEED_VIDEO_CONSTRAINTS_IF_NECESSARY, exceedVideoConstraintsIfNecessary);
|
||||||
exceedVideoConstraintsIfNecessary);
|
|
||||||
bundle.putBoolean(
|
bundle.putBoolean(
|
||||||
keyForField(FIELD_ALLOW_VIDEO_MIXED_MIME_TYPE_ADAPTIVENESS),
|
FIELD_ALLOW_VIDEO_MIXED_MIME_TYPE_ADAPTIVENESS, allowVideoMixedMimeTypeAdaptiveness);
|
||||||
allowVideoMixedMimeTypeAdaptiveness);
|
|
||||||
bundle.putBoolean(
|
bundle.putBoolean(
|
||||||
keyForField(FIELD_ALLOW_VIDEO_NON_SEAMLESS_ADAPTIVENESS),
|
FIELD_ALLOW_VIDEO_NON_SEAMLESS_ADAPTIVENESS, allowVideoNonSeamlessAdaptiveness);
|
||||||
allowVideoNonSeamlessAdaptiveness);
|
|
||||||
bundle.putBoolean(
|
bundle.putBoolean(
|
||||||
keyForField(FIELD_ALLOW_VIDEO_MIXED_DECODER_SUPPORT_ADAPTIVENESS),
|
FIELD_ALLOW_VIDEO_MIXED_DECODER_SUPPORT_ADAPTIVENESS,
|
||||||
allowVideoMixedDecoderSupportAdaptiveness);
|
allowVideoMixedDecoderSupportAdaptiveness);
|
||||||
// Audio
|
// Audio
|
||||||
bundle.putBoolean(
|
bundle.putBoolean(
|
||||||
keyForField(FIELD_EXCEED_AUDIO_CONSTRAINTS_IF_NCESSARY),
|
FIELD_EXCEED_AUDIO_CONSTRAINTS_IF_NECESSARY, exceedAudioConstraintsIfNecessary);
|
||||||
exceedAudioConstraintsIfNecessary);
|
|
||||||
bundle.putBoolean(
|
bundle.putBoolean(
|
||||||
keyForField(FIELD_ALLOW_AUDIO_MIXED_MIME_TYPE_ADAPTIVENESS),
|
FIELD_ALLOW_AUDIO_MIXED_MIME_TYPE_ADAPTIVENESS, allowAudioMixedMimeTypeAdaptiveness);
|
||||||
allowAudioMixedMimeTypeAdaptiveness);
|
|
||||||
bundle.putBoolean(
|
bundle.putBoolean(
|
||||||
keyForField(FIELD_ALLOW_AUDIO_MIXED_SAMPLE_RATE_ADAPTIVENESS),
|
FIELD_ALLOW_AUDIO_MIXED_SAMPLE_RATE_ADAPTIVENESS, allowAudioMixedSampleRateAdaptiveness);
|
||||||
allowAudioMixedSampleRateAdaptiveness);
|
|
||||||
bundle.putBoolean(
|
bundle.putBoolean(
|
||||||
keyForField(FIELD_ALLOW_AUDIO_MIXED_CHANNEL_COUNT_ADAPTIVENESS),
|
FIELD_ALLOW_AUDIO_MIXED_CHANNEL_COUNT_ADAPTIVENESS,
|
||||||
allowAudioMixedChannelCountAdaptiveness);
|
allowAudioMixedChannelCountAdaptiveness);
|
||||||
bundle.putBoolean(
|
bundle.putBoolean(
|
||||||
keyForField(FIELD_ALLOW_AUDIO_MIXED_DECODER_SUPPORT_ADAPTIVENESS),
|
FIELD_ALLOW_AUDIO_MIXED_DECODER_SUPPORT_ADAPTIVENESS,
|
||||||
allowAudioMixedDecoderSupportAdaptiveness);
|
allowAudioMixedDecoderSupportAdaptiveness);
|
||||||
bundle.putBoolean(
|
bundle.putBoolean(
|
||||||
keyForField(FIELD_CONSTRAIN_AUDIO_CHANNEL_COUNT_TO_DEVICE_CAPABILITIES),
|
FIELD_CONSTRAIN_AUDIO_CHANNEL_COUNT_TO_DEVICE_CAPABILITIES,
|
||||||
constrainAudioChannelCountToDeviceCapabilities);
|
constrainAudioChannelCountToDeviceCapabilities);
|
||||||
// General
|
// General
|
||||||
bundle.putBoolean(
|
bundle.putBoolean(
|
||||||
keyForField(FIELD_EXCEED_RENDERER_CAPABILITIES_IF_NECESSARY),
|
FIELD_EXCEED_RENDERER_CAPABILITIES_IF_NECESSARY, exceedRendererCapabilitiesIfNecessary);
|
||||||
exceedRendererCapabilitiesIfNecessary);
|
bundle.putBoolean(FIELD_TUNNELING_ENABLED, tunnelingEnabled);
|
||||||
bundle.putBoolean(keyForField(FIELD_TUNNELING_ENABLED), tunnelingEnabled);
|
bundle.putBoolean(FIELD_ALLOW_MULTIPLE_ADAPTIVE_SELECTIONS, allowMultipleAdaptiveSelections);
|
||||||
bundle.putBoolean(
|
|
||||||
keyForField(FIELD_ALLOW_MULTIPLE_ADAPTIVE_SELECTIONS), allowMultipleAdaptiveSelections);
|
|
||||||
|
|
||||||
putSelectionOverridesToBundle(bundle, selectionOverrides);
|
putSelectionOverridesToBundle(bundle, selectionOverrides);
|
||||||
// Only true values are put into rendererDisabledFlags.
|
// Only true values are put into rendererDisabledFlags.
|
||||||
bundle.putIntArray(
|
bundle.putIntArray(
|
||||||
keyForField(FIELD_RENDERER_DISABLED_INDICES),
|
FIELD_RENDERER_DISABLED_INDICES, getKeysFromSparseBooleanArray(rendererDisabledFlags));
|
||||||
getKeysFromSparseBooleanArray(rendererDisabledFlags));
|
|
||||||
|
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
@ -1982,12 +1971,12 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
|||||||
rendererIndices.add(rendererIndex);
|
rendererIndices.add(rendererIndex);
|
||||||
}
|
}
|
||||||
bundle.putIntArray(
|
bundle.putIntArray(
|
||||||
keyForField(FIELD_SELECTION_OVERRIDES_RENDERER_INDICES), Ints.toArray(rendererIndices));
|
FIELD_SELECTION_OVERRIDES_RENDERER_INDICES, Ints.toArray(rendererIndices));
|
||||||
bundle.putParcelableArrayList(
|
bundle.putParcelableArrayList(
|
||||||
keyForField(FIELD_SELECTION_OVERRIDES_TRACK_GROUP_ARRAYS),
|
FIELD_SELECTION_OVERRIDES_TRACK_GROUP_ARRAYS,
|
||||||
BundleableUtil.toBundleArrayList(trackGroupArrays));
|
BundleableUtil.toBundleArrayList(trackGroupArrays));
|
||||||
bundle.putSparseParcelableArray(
|
bundle.putSparseParcelableArray(
|
||||||
keyForField(FIELD_SELECTION_OVERRIDES), BundleableUtil.toBundleSparseArray(selections));
|
FIELD_SELECTION_OVERRIDES, BundleableUtil.toBundleSparseArray(selections));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2116,27 +2105,17 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
|||||||
|
|
||||||
// Bundleable implementation.
|
// Bundleable implementation.
|
||||||
|
|
||||||
@Documented
|
private static final String FIELD_GROUP_INDEX = Util.intToStringMaxRadix(0);
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
private static final String FIELD_TRACKS = Util.intToStringMaxRadix(1);
|
||||||
@Target(TYPE_USE)
|
private static final String FIELD_TRACK_TYPE = Util.intToStringMaxRadix(2);
|
||||||
@IntDef({
|
|
||||||
FIELD_GROUP_INDEX,
|
|
||||||
FIELD_TRACKS,
|
|
||||||
FIELD_TRACK_TYPE,
|
|
||||||
})
|
|
||||||
private @interface FieldNumber {}
|
|
||||||
|
|
||||||
private static final int FIELD_GROUP_INDEX = 0;
|
|
||||||
private static final int FIELD_TRACKS = 1;
|
|
||||||
private static final int FIELD_TRACK_TYPE = 2;
|
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Override
|
@Override
|
||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putInt(keyForField(FIELD_GROUP_INDEX), groupIndex);
|
bundle.putInt(FIELD_GROUP_INDEX, groupIndex);
|
||||||
bundle.putIntArray(keyForField(FIELD_TRACKS), tracks);
|
bundle.putIntArray(FIELD_TRACKS, tracks);
|
||||||
bundle.putInt(keyForField(FIELD_TRACK_TYPE), type);
|
bundle.putInt(FIELD_TRACK_TYPE, type);
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2144,17 +2123,13 @@ public class DefaultTrackSelector extends MappingTrackSelector {
|
|||||||
@UnstableApi
|
@UnstableApi
|
||||||
public static final Creator<SelectionOverride> CREATOR =
|
public static final Creator<SelectionOverride> CREATOR =
|
||||||
bundle -> {
|
bundle -> {
|
||||||
int groupIndex = bundle.getInt(keyForField(FIELD_GROUP_INDEX), -1);
|
int groupIndex = bundle.getInt(FIELD_GROUP_INDEX, -1);
|
||||||
@Nullable int[] tracks = bundle.getIntArray(keyForField(FIELD_TRACKS));
|
@Nullable int[] tracks = bundle.getIntArray(FIELD_TRACKS);
|
||||||
int trackType = bundle.getInt(keyForField(FIELD_TRACK_TYPE), -1);
|
int trackType = bundle.getInt(FIELD_TRACK_TYPE, -1);
|
||||||
Assertions.checkArgument(groupIndex >= 0 && trackType >= 0);
|
Assertions.checkArgument(groupIndex >= 0 && trackType >= 0);
|
||||||
Assertions.checkNotNull(tracks);
|
Assertions.checkNotNull(tracks);
|
||||||
return new SelectionOverride(groupIndex, tracks, trackType);
|
return new SelectionOverride(groupIndex, tracks, trackType);
|
||||||
};
|
};
|
||||||
|
|
||||||
private static String keyForField(@FieldNumber int field) {
|
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -48,27 +48,27 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList
|
|||||||
|
|
||||||
/** Default initial Wifi bitrate estimate in bits per second. */
|
/** Default initial Wifi bitrate estimate in bits per second. */
|
||||||
public static final ImmutableList<Long> DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI =
|
public static final ImmutableList<Long> DEFAULT_INITIAL_BITRATE_ESTIMATES_WIFI =
|
||||||
ImmutableList.of(4_800_000L, 3_100_000L, 2_100_000L, 1_500_000L, 800_000L);
|
ImmutableList.of(4_400_000L, 3_200_000L, 2_300_000L, 1_600_000L, 810_000L);
|
||||||
|
|
||||||
/** Default initial 2G bitrate estimates in bits per second. */
|
/** Default initial 2G bitrate estimates in bits per second. */
|
||||||
public static final ImmutableList<Long> DEFAULT_INITIAL_BITRATE_ESTIMATES_2G =
|
public static final ImmutableList<Long> DEFAULT_INITIAL_BITRATE_ESTIMATES_2G =
|
||||||
ImmutableList.of(1_500_000L, 1_000_000L, 730_000L, 440_000L, 170_000L);
|
ImmutableList.of(1_400_000L, 990_000L, 730_000L, 510_000L, 230_000L);
|
||||||
|
|
||||||
/** Default initial 3G bitrate estimates in bits per second. */
|
/** Default initial 3G bitrate estimates in bits per second. */
|
||||||
public static final ImmutableList<Long> DEFAULT_INITIAL_BITRATE_ESTIMATES_3G =
|
public static final ImmutableList<Long> DEFAULT_INITIAL_BITRATE_ESTIMATES_3G =
|
||||||
ImmutableList.of(2_200_000L, 1_400_000L, 1_100_000L, 910_000L, 620_000L);
|
ImmutableList.of(2_100_000L, 1_400_000L, 1_000_000L, 890_000L, 640_000L);
|
||||||
|
|
||||||
/** Default initial 4G bitrate estimates in bits per second. */
|
/** Default initial 4G bitrate estimates in bits per second. */
|
||||||
public static final ImmutableList<Long> DEFAULT_INITIAL_BITRATE_ESTIMATES_4G =
|
public static final ImmutableList<Long> DEFAULT_INITIAL_BITRATE_ESTIMATES_4G =
|
||||||
ImmutableList.of(3_000_000L, 1_900_000L, 1_400_000L, 1_000_000L, 660_000L);
|
ImmutableList.of(2_600_000L, 1_700_000L, 1_300_000L, 1_000_000L, 700_000L);
|
||||||
|
|
||||||
/** Default initial 5G-NSA bitrate estimates in bits per second. */
|
/** Default initial 5G-NSA bitrate estimates in bits per second. */
|
||||||
public static final ImmutableList<Long> DEFAULT_INITIAL_BITRATE_ESTIMATES_5G_NSA =
|
public static final ImmutableList<Long> DEFAULT_INITIAL_BITRATE_ESTIMATES_5G_NSA =
|
||||||
ImmutableList.of(6_000_000L, 4_100_000L, 3_200_000L, 1_800_000L, 1_000_000L);
|
ImmutableList.of(5_700_000L, 3_700_000L, 2_300_000L, 1_700_000L, 990_000L);
|
||||||
|
|
||||||
/** Default initial 5G-SA bitrate estimates in bits per second. */
|
/** Default initial 5G-SA bitrate estimates in bits per second. */
|
||||||
public static final ImmutableList<Long> DEFAULT_INITIAL_BITRATE_ESTIMATES_5G_SA =
|
public static final ImmutableList<Long> DEFAULT_INITIAL_BITRATE_ESTIMATES_5G_SA =
|
||||||
ImmutableList.of(2_800_000L, 2_400_000L, 1_600_000L, 1_100_000L, 950_000L);
|
ImmutableList.of(2_800_000L, 1_800_000L, 1_400_000L, 1_100_000L, 870_000L);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Default initial bitrate estimate used when the device is offline or the network type cannot be
|
* Default initial bitrate estimate used when the device is offline or the network type cannot be
|
||||||
@ -483,394 +483,410 @@ public final class DefaultBandwidthMeter implements BandwidthMeter, TransferList
|
|||||||
*/
|
*/
|
||||||
private static int[] getInitialBitrateCountryGroupAssignment(String country) {
|
private static int[] getInitialBitrateCountryGroupAssignment(String country) {
|
||||||
switch (country) {
|
switch (country) {
|
||||||
|
case "AD":
|
||||||
|
case "CW":
|
||||||
|
return new int[] {2, 2, 0, 0, 2, 2};
|
||||||
case "AE":
|
case "AE":
|
||||||
return new int[] {1, 4, 4, 4, 4, 0};
|
return new int[] {1, 4, 3, 4, 4, 2};
|
||||||
case "AG":
|
case "AG":
|
||||||
return new int[] {2, 4, 1, 2, 2, 2};
|
return new int[] {2, 4, 3, 4, 2, 2};
|
||||||
case "AI":
|
case "AL":
|
||||||
return new int[] {0, 2, 0, 3, 2, 2};
|
return new int[] {1, 1, 1, 3, 2, 2};
|
||||||
case "AM":
|
case "AM":
|
||||||
return new int[] {2, 3, 2, 3, 2, 2};
|
return new int[] {2, 3, 2, 3, 2, 2};
|
||||||
case "AO":
|
case "AO":
|
||||||
return new int[] {4, 4, 3, 2, 2, 2};
|
return new int[] {4, 4, 4, 3, 2, 2};
|
||||||
case "AS":
|
case "AS":
|
||||||
return new int[] {2, 2, 3, 3, 2, 2};
|
return new int[] {2, 2, 3, 3, 2, 2};
|
||||||
case "AT":
|
case "AT":
|
||||||
return new int[] {1, 0, 1, 1, 0, 0};
|
return new int[] {1, 2, 1, 4, 1, 4};
|
||||||
case "AU":
|
case "AU":
|
||||||
return new int[] {0, 1, 1, 1, 2, 0};
|
return new int[] {0, 2, 1, 1, 3, 0};
|
||||||
case "AW":
|
|
||||||
return new int[] {1, 3, 4, 4, 2, 2};
|
|
||||||
case "BA":
|
|
||||||
return new int[] {1, 2, 1, 1, 2, 2};
|
|
||||||
case "BD":
|
|
||||||
return new int[] {2, 1, 3, 3, 2, 2};
|
|
||||||
case "BE":
|
case "BE":
|
||||||
return new int[] {0, 1, 4, 4, 3, 2};
|
return new int[] {0, 1, 4, 4, 3, 2};
|
||||||
case "BF":
|
|
||||||
return new int[] {4, 3, 4, 3, 2, 2};
|
|
||||||
case "BH":
|
case "BH":
|
||||||
return new int[] {1, 2, 1, 3, 4, 2};
|
return new int[] {1, 3, 1, 4, 4, 2};
|
||||||
case "BJ":
|
case "BJ":
|
||||||
return new int[] {4, 4, 3, 3, 2, 2};
|
return new int[] {4, 4, 2, 3, 2, 2};
|
||||||
|
case "BN":
|
||||||
|
return new int[] {3, 2, 0, 1, 2, 2};
|
||||||
case "BO":
|
case "BO":
|
||||||
return new int[] {1, 2, 3, 2, 2, 2};
|
return new int[] {1, 2, 3, 2, 2, 2};
|
||||||
case "BS":
|
case "BR":
|
||||||
return new int[] {4, 4, 2, 2, 2, 2};
|
return new int[] {1, 1, 2, 1, 1, 0};
|
||||||
case "BT":
|
|
||||||
return new int[] {3, 1, 3, 2, 2, 2};
|
|
||||||
case "BW":
|
case "BW":
|
||||||
return new int[] {3, 2, 1, 0, 2, 2};
|
return new int[] {3, 2, 1, 0, 2, 2};
|
||||||
case "BY":
|
case "BY":
|
||||||
return new int[] {0, 1, 2, 3, 2, 2};
|
return new int[] {1, 1, 2, 3, 2, 2};
|
||||||
case "BZ":
|
|
||||||
return new int[] {2, 4, 2, 1, 2, 2};
|
|
||||||
case "CA":
|
case "CA":
|
||||||
return new int[] {0, 2, 2, 2, 3, 2};
|
return new int[] {0, 2, 3, 3, 3, 3};
|
||||||
case "CD":
|
|
||||||
return new int[] {4, 2, 3, 2, 2, 2};
|
|
||||||
case "CH":
|
case "CH":
|
||||||
return new int[] {0, 0, 0, 1, 0, 2};
|
return new int[] {0, 0, 0, 0, 0, 3};
|
||||||
|
case "BZ":
|
||||||
|
case "CK":
|
||||||
|
return new int[] {2, 2, 2, 1, 2, 2};
|
||||||
|
case "CL":
|
||||||
|
return new int[] {1, 1, 2, 1, 3, 2};
|
||||||
case "CM":
|
case "CM":
|
||||||
return new int[] {3, 3, 3, 3, 2, 2};
|
return new int[] {4, 3, 3, 4, 2, 2};
|
||||||
case "CN":
|
case "CN":
|
||||||
return new int[] {2, 0, 1, 1, 3, 2};
|
return new int[] {2, 0, 4, 3, 3, 1};
|
||||||
case "CO":
|
case "CO":
|
||||||
return new int[] {2, 3, 4, 3, 2, 2};
|
return new int[] {2, 3, 4, 2, 2, 2};
|
||||||
case "CR":
|
case "CR":
|
||||||
return new int[] {2, 3, 4, 4, 2, 2};
|
return new int[] {2, 4, 4, 4, 2, 2};
|
||||||
case "CV":
|
case "CV":
|
||||||
return new int[] {2, 1, 0, 0, 2, 2};
|
return new int[] {2, 3, 0, 1, 2, 2};
|
||||||
case "BN":
|
case "CZ":
|
||||||
case "CW":
|
return new int[] {0, 0, 2, 0, 1, 2};
|
||||||
return new int[] {2, 2, 0, 0, 2, 2};
|
|
||||||
case "DE":
|
case "DE":
|
||||||
return new int[] {0, 1, 2, 2, 2, 3};
|
return new int[] {0, 1, 3, 2, 2, 2};
|
||||||
case "DK":
|
|
||||||
return new int[] {0, 0, 3, 2, 0, 2};
|
|
||||||
case "DO":
|
case "DO":
|
||||||
return new int[] {3, 4, 4, 4, 4, 2};
|
return new int[] {3, 4, 4, 4, 4, 2};
|
||||||
|
case "AZ":
|
||||||
|
case "BF":
|
||||||
|
case "DZ":
|
||||||
|
return new int[] {3, 3, 4, 4, 2, 2};
|
||||||
case "EC":
|
case "EC":
|
||||||
return new int[] {2, 3, 2, 1, 2, 2};
|
return new int[] {1, 3, 2, 1, 2, 2};
|
||||||
case "ET":
|
case "CI":
|
||||||
return new int[] {4, 3, 3, 1, 2, 2};
|
case "EG":
|
||||||
|
return new int[] {3, 4, 3, 3, 2, 2};
|
||||||
case "FI":
|
case "FI":
|
||||||
return new int[] {0, 0, 0, 3, 0, 2};
|
return new int[] {0, 0, 0, 2, 0, 2};
|
||||||
case "FJ":
|
case "FJ":
|
||||||
return new int[] {3, 1, 2, 2, 2, 2};
|
return new int[] {3, 1, 2, 3, 2, 2};
|
||||||
case "FM":
|
case "FM":
|
||||||
return new int[] {4, 2, 4, 1, 2, 2};
|
return new int[] {4, 2, 3, 0, 2, 2};
|
||||||
case "FR":
|
case "AI":
|
||||||
return new int[] {1, 2, 3, 1, 0, 2};
|
|
||||||
case "GB":
|
|
||||||
return new int[] {0, 0, 1, 1, 1, 1};
|
|
||||||
case "GE":
|
|
||||||
return new int[] {1, 1, 1, 2, 2, 2};
|
|
||||||
case "BB":
|
case "BB":
|
||||||
|
case "BM":
|
||||||
|
case "BQ":
|
||||||
case "DM":
|
case "DM":
|
||||||
case "FO":
|
case "FO":
|
||||||
case "GI":
|
|
||||||
return new int[] {0, 2, 0, 0, 2, 2};
|
return new int[] {0, 2, 0, 0, 2, 2};
|
||||||
case "AF":
|
case "FR":
|
||||||
case "GM":
|
return new int[] {1, 1, 2, 1, 1, 2};
|
||||||
return new int[] {4, 3, 3, 4, 2, 2};
|
case "GB":
|
||||||
case "GN":
|
return new int[] {0, 1, 1, 2, 1, 2};
|
||||||
return new int[] {4, 3, 4, 2, 2, 2};
|
case "GE":
|
||||||
case "GQ":
|
return new int[] {1, 0, 0, 2, 2, 2};
|
||||||
return new int[] {4, 2, 1, 4, 2, 2};
|
case "GG":
|
||||||
case "GT":
|
return new int[] {0, 2, 1, 0, 2, 2};
|
||||||
return new int[] {2, 3, 2, 2, 2, 2};
|
|
||||||
case "CG":
|
case "CG":
|
||||||
case "EG":
|
case "GH":
|
||||||
|
return new int[] {3, 3, 3, 3, 2, 2};
|
||||||
|
case "GM":
|
||||||
|
return new int[] {4, 3, 2, 4, 2, 2};
|
||||||
|
case "GN":
|
||||||
|
return new int[] {4, 4, 4, 2, 2, 2};
|
||||||
|
case "GP":
|
||||||
|
return new int[] {3, 1, 1, 3, 2, 2};
|
||||||
|
case "GQ":
|
||||||
|
return new int[] {4, 4, 3, 3, 2, 2};
|
||||||
|
case "GT":
|
||||||
|
return new int[] {2, 2, 2, 1, 1, 2};
|
||||||
|
case "AW":
|
||||||
|
case "GU":
|
||||||
|
return new int[] {1, 2, 4, 4, 2, 2};
|
||||||
case "GW":
|
case "GW":
|
||||||
return new int[] {3, 4, 3, 3, 2, 2};
|
return new int[] {4, 4, 2, 2, 2, 2};
|
||||||
case "GY":
|
case "GY":
|
||||||
return new int[] {3, 2, 2, 1, 2, 2};
|
return new int[] {3, 0, 1, 1, 2, 2};
|
||||||
case "HK":
|
case "HK":
|
||||||
return new int[] {0, 1, 2, 3, 2, 0};
|
return new int[] {0, 1, 1, 3, 2, 0};
|
||||||
case "HU":
|
case "HN":
|
||||||
return new int[] {0, 0, 0, 1, 3, 2};
|
return new int[] {3, 3, 2, 2, 2, 2};
|
||||||
case "ID":
|
case "ID":
|
||||||
return new int[] {3, 1, 2, 2, 3, 2};
|
return new int[] {3, 1, 1, 2, 3, 2};
|
||||||
case "ES":
|
case "BA":
|
||||||
case "IE":
|
case "IE":
|
||||||
return new int[] {0, 1, 1, 1, 2, 2};
|
return new int[] {1, 1, 1, 1, 2, 2};
|
||||||
case "CL":
|
|
||||||
case "IL":
|
case "IL":
|
||||||
return new int[] {1, 2, 2, 2, 3, 2};
|
return new int[] {1, 2, 2, 3, 4, 2};
|
||||||
|
case "IM":
|
||||||
|
return new int[] {0, 2, 0, 1, 2, 2};
|
||||||
case "IN":
|
case "IN":
|
||||||
return new int[] {1, 1, 3, 2, 3, 3};
|
return new int[] {1, 1, 2, 1, 2, 1};
|
||||||
case "IQ":
|
|
||||||
return new int[] {3, 2, 2, 3, 2, 2};
|
|
||||||
case "IR":
|
case "IR":
|
||||||
return new int[] {3, 0, 1, 1, 4, 1};
|
return new int[] {4, 2, 3, 3, 4, 2};
|
||||||
|
case "IS":
|
||||||
|
return new int[] {0, 0, 1, 0, 0, 2};
|
||||||
case "IT":
|
case "IT":
|
||||||
return new int[] {0, 0, 0, 1, 1, 2};
|
return new int[] {0, 0, 1, 1, 1, 2};
|
||||||
|
case "GI":
|
||||||
|
case "JE":
|
||||||
|
return new int[] {1, 2, 0, 1, 2, 2};
|
||||||
case "JM":
|
case "JM":
|
||||||
return new int[] {2, 4, 3, 2, 2, 2};
|
return new int[] {2, 4, 2, 1, 2, 2};
|
||||||
case "JO":
|
case "JO":
|
||||||
return new int[] {2, 1, 1, 2, 2, 2};
|
return new int[] {2, 0, 1, 1, 2, 2};
|
||||||
case "JP":
|
case "JP":
|
||||||
return new int[] {0, 1, 1, 2, 2, 4};
|
return new int[] {0, 3, 3, 3, 4, 4};
|
||||||
case "KH":
|
|
||||||
return new int[] {2, 1, 4, 2, 2, 2};
|
|
||||||
case "CF":
|
|
||||||
case "KI":
|
|
||||||
return new int[] {4, 2, 4, 2, 2, 2};
|
|
||||||
case "FK":
|
|
||||||
case "KE":
|
case "KE":
|
||||||
case "KP":
|
return new int[] {3, 2, 2, 1, 2, 2};
|
||||||
return new int[] {3, 2, 2, 2, 2, 2};
|
case "KH":
|
||||||
|
return new int[] {1, 0, 4, 2, 2, 2};
|
||||||
|
case "CU":
|
||||||
|
case "KI":
|
||||||
|
return new int[] {4, 2, 4, 3, 2, 2};
|
||||||
|
case "CD":
|
||||||
|
case "KM":
|
||||||
|
return new int[] {4, 3, 3, 2, 2, 2};
|
||||||
case "KR":
|
case "KR":
|
||||||
return new int[] {0, 1, 1, 3, 4, 4};
|
return new int[] {0, 2, 2, 4, 4, 4};
|
||||||
case "CY":
|
|
||||||
case "KW":
|
case "KW":
|
||||||
return new int[] {1, 0, 0, 0, 0, 2};
|
return new int[] {1, 0, 1, 0, 0, 2};
|
||||||
|
case "BD":
|
||||||
case "KZ":
|
case "KZ":
|
||||||
return new int[] {2, 1, 2, 2, 2, 2};
|
return new int[] {2, 1, 2, 2, 2, 2};
|
||||||
case "LA":
|
case "LA":
|
||||||
return new int[] {1, 2, 1, 3, 2, 2};
|
return new int[] {1, 2, 1, 3, 2, 2};
|
||||||
|
case "BS":
|
||||||
case "LB":
|
case "LB":
|
||||||
return new int[] {3, 3, 2, 4, 2, 2};
|
return new int[] {3, 2, 1, 2, 2, 2};
|
||||||
case "LK":
|
case "LK":
|
||||||
return new int[] {3, 1, 3, 3, 4, 2};
|
return new int[] {3, 2, 3, 4, 4, 2};
|
||||||
case "CI":
|
|
||||||
case "DZ":
|
|
||||||
case "LR":
|
case "LR":
|
||||||
return new int[] {3, 4, 4, 4, 2, 2};
|
return new int[] {3, 4, 3, 4, 2, 2};
|
||||||
case "LS":
|
|
||||||
return new int[] {3, 3, 2, 2, 2, 2};
|
|
||||||
case "LT":
|
|
||||||
return new int[] {0, 0, 0, 0, 2, 2};
|
|
||||||
case "LU":
|
case "LU":
|
||||||
return new int[] {1, 0, 3, 2, 1, 4};
|
return new int[] {1, 1, 4, 2, 0, 2};
|
||||||
|
case "CY":
|
||||||
|
case "HR":
|
||||||
|
case "LV":
|
||||||
|
return new int[] {1, 0, 0, 0, 0, 2};
|
||||||
case "MA":
|
case "MA":
|
||||||
return new int[] {3, 3, 1, 1, 2, 2};
|
return new int[] {3, 3, 2, 1, 2, 2};
|
||||||
case "MC":
|
case "MC":
|
||||||
return new int[] {0, 2, 2, 0, 2, 2};
|
return new int[] {0, 2, 2, 0, 2, 2};
|
||||||
|
case "MD":
|
||||||
|
return new int[] {1, 0, 0, 0, 2, 2};
|
||||||
case "ME":
|
case "ME":
|
||||||
return new int[] {2, 0, 0, 1, 2, 2};
|
return new int[] {2, 0, 0, 1, 1, 2};
|
||||||
|
case "MH":
|
||||||
|
return new int[] {4, 2, 1, 3, 2, 2};
|
||||||
case "MK":
|
case "MK":
|
||||||
return new int[] {1, 0, 0, 1, 3, 2};
|
return new int[] {2, 0, 0, 1, 3, 2};
|
||||||
case "MM":
|
case "MM":
|
||||||
return new int[] {2, 4, 2, 3, 2, 2};
|
return new int[] {2, 2, 2, 3, 4, 2};
|
||||||
case "MN":
|
case "MN":
|
||||||
return new int[] {2, 0, 1, 2, 2, 2};
|
return new int[] {2, 0, 1, 2, 2, 2};
|
||||||
case "MO":
|
case "MO":
|
||||||
case "MP":
|
return new int[] {0, 2, 4, 4, 4, 2};
|
||||||
return new int[] {0, 2, 4, 4, 2, 2};
|
case "KG":
|
||||||
case "GP":
|
|
||||||
case "MQ":
|
case "MQ":
|
||||||
return new int[] {2, 1, 2, 3, 2, 2};
|
return new int[] {2, 1, 1, 2, 2, 2};
|
||||||
case "MU":
|
case "MR":
|
||||||
return new int[] {3, 1, 1, 2, 2, 2};
|
return new int[] {4, 2, 3, 4, 2, 2};
|
||||||
|
case "DK":
|
||||||
|
case "EE":
|
||||||
|
case "HU":
|
||||||
|
case "LT":
|
||||||
|
case "MT":
|
||||||
|
return new int[] {0, 0, 0, 0, 0, 2};
|
||||||
case "MV":
|
case "MV":
|
||||||
return new int[] {3, 4, 1, 4, 2, 2};
|
return new int[] {3, 4, 1, 3, 3, 2};
|
||||||
case "MW":
|
case "MW":
|
||||||
return new int[] {4, 2, 3, 3, 2, 2};
|
return new int[] {4, 2, 3, 3, 2, 2};
|
||||||
case "MX":
|
case "MX":
|
||||||
return new int[] {2, 4, 3, 4, 2, 2};
|
return new int[] {3, 4, 4, 4, 2, 2};
|
||||||
case "MY":
|
case "MY":
|
||||||
return new int[] {1, 0, 3, 1, 3, 2};
|
return new int[] {1, 0, 4, 1, 2, 2};
|
||||||
case "MZ":
|
case "NA":
|
||||||
return new int[] {3, 1, 2, 1, 2, 2};
|
return new int[] {3, 4, 3, 2, 2, 2};
|
||||||
case "NC":
|
case "NC":
|
||||||
return new int[] {3, 3, 4, 4, 2, 2};
|
return new int[] {3, 2, 3, 4, 2, 2};
|
||||||
case "NG":
|
case "NG":
|
||||||
return new int[] {3, 4, 2, 1, 2, 2};
|
return new int[] {3, 4, 2, 1, 2, 2};
|
||||||
|
case "NI":
|
||||||
|
return new int[] {2, 3, 4, 3, 2, 2};
|
||||||
case "NL":
|
case "NL":
|
||||||
return new int[] {0, 2, 2, 3, 0, 3};
|
return new int[] {0, 2, 3, 3, 0, 4};
|
||||||
case "CZ":
|
|
||||||
case "NO":
|
case "NO":
|
||||||
return new int[] {0, 0, 2, 0, 1, 2};
|
return new int[] {0, 1, 2, 1, 1, 2};
|
||||||
case "NP":
|
case "NP":
|
||||||
return new int[] {2, 2, 4, 3, 2, 2};
|
return new int[] {2, 1, 4, 3, 2, 2};
|
||||||
case "NR":
|
case "NR":
|
||||||
|
return new int[] {4, 0, 3, 2, 2, 2};
|
||||||
case "NU":
|
case "NU":
|
||||||
return new int[] {4, 2, 2, 1, 2, 2};
|
return new int[] {4, 2, 2, 1, 2, 2};
|
||||||
|
case "NZ":
|
||||||
|
return new int[] {1, 0, 2, 2, 4, 2};
|
||||||
case "OM":
|
case "OM":
|
||||||
return new int[] {2, 3, 1, 3, 4, 2};
|
return new int[] {2, 3, 1, 3, 4, 2};
|
||||||
case "GU":
|
case "PA":
|
||||||
|
return new int[] {2, 3, 3, 3, 2, 2};
|
||||||
case "PE":
|
case "PE":
|
||||||
return new int[] {1, 2, 4, 4, 4, 2};
|
return new int[] {1, 2, 4, 4, 3, 2};
|
||||||
case "CK":
|
case "AF":
|
||||||
case "PF":
|
|
||||||
return new int[] {2, 2, 2, 1, 2, 2};
|
|
||||||
case "ML":
|
|
||||||
case "PG":
|
case "PG":
|
||||||
return new int[] {4, 3, 3, 2, 2, 2};
|
return new int[] {4, 3, 3, 3, 2, 2};
|
||||||
case "PH":
|
case "PH":
|
||||||
return new int[] {2, 1, 3, 3, 3, 0};
|
return new int[] {2, 1, 3, 2, 2, 0};
|
||||||
case "NZ":
|
|
||||||
case "PL":
|
case "PL":
|
||||||
return new int[] {1, 1, 2, 2, 4, 2};
|
return new int[] {2, 1, 2, 2, 4, 2};
|
||||||
case "PR":
|
case "PR":
|
||||||
return new int[] {2, 0, 2, 1, 2, 1};
|
return new int[] {2, 0, 2, 0, 2, 1};
|
||||||
case "PS":
|
case "PS":
|
||||||
return new int[] {3, 4, 1, 2, 2, 2};
|
return new int[] {3, 4, 1, 4, 2, 2};
|
||||||
|
case "PT":
|
||||||
|
return new int[] {1, 0, 0, 0, 1, 2};
|
||||||
case "PW":
|
case "PW":
|
||||||
return new int[] {2, 2, 4, 1, 2, 2};
|
return new int[] {2, 2, 4, 2, 2, 2};
|
||||||
case "QA":
|
case "BL":
|
||||||
return new int[] {2, 4, 4, 4, 4, 2};
|
|
||||||
case "MF":
|
case "MF":
|
||||||
|
case "PY":
|
||||||
|
return new int[] {1, 2, 2, 2, 2, 2};
|
||||||
|
case "QA":
|
||||||
|
return new int[] {1, 4, 4, 4, 4, 2};
|
||||||
case "RE":
|
case "RE":
|
||||||
return new int[] {1, 2, 1, 2, 2, 2};
|
return new int[] {1, 2, 2, 3, 1, 2};
|
||||||
case "RO":
|
case "RO":
|
||||||
return new int[] {0, 0, 1, 2, 1, 2};
|
return new int[] {0, 0, 1, 2, 1, 2};
|
||||||
case "MD":
|
|
||||||
case "RS":
|
case "RS":
|
||||||
return new int[] {1, 0, 0, 0, 2, 2};
|
return new int[] {2, 0, 0, 0, 2, 2};
|
||||||
case "RU":
|
case "RU":
|
||||||
return new int[] {1, 0, 0, 0, 4, 3};
|
return new int[] {1, 0, 0, 0, 3, 3};
|
||||||
case "RW":
|
case "RW":
|
||||||
return new int[] {3, 4, 2, 0, 2, 2};
|
return new int[] {3, 3, 1, 0, 2, 2};
|
||||||
|
case "MU":
|
||||||
case "SA":
|
case "SA":
|
||||||
return new int[] {3, 1, 1, 1, 2, 2};
|
return new int[] {3, 1, 1, 2, 2, 2};
|
||||||
|
case "CF":
|
||||||
case "SB":
|
case "SB":
|
||||||
return new int[] {4, 2, 4, 3, 2, 2};
|
return new int[] {4, 2, 4, 2, 2, 2};
|
||||||
|
case "SC":
|
||||||
|
return new int[] {4, 3, 1, 1, 2, 2};
|
||||||
|
case "SD":
|
||||||
|
return new int[] {4, 3, 4, 2, 2, 2};
|
||||||
|
case "SE":
|
||||||
|
return new int[] {0, 1, 1, 1, 0, 2};
|
||||||
case "SG":
|
case "SG":
|
||||||
return new int[] {1, 1, 2, 2, 2, 1};
|
return new int[] {2, 3, 3, 3, 3, 3};
|
||||||
case "AQ":
|
case "AQ":
|
||||||
case "ER":
|
case "ER":
|
||||||
case "SH":
|
case "SH":
|
||||||
return new int[] {4, 2, 2, 2, 2, 2};
|
return new int[] {4, 2, 2, 2, 2, 2};
|
||||||
case "GR":
|
|
||||||
case "HR":
|
|
||||||
case "SI":
|
|
||||||
return new int[] {1, 0, 0, 0, 1, 2};
|
|
||||||
case "BG":
|
case "BG":
|
||||||
case "MT":
|
case "ES":
|
||||||
case "SK":
|
case "GR":
|
||||||
|
case "SI":
|
||||||
return new int[] {0, 0, 0, 0, 1, 2};
|
return new int[] {0, 0, 0, 0, 1, 2};
|
||||||
case "AX":
|
case "IQ":
|
||||||
case "LI":
|
case "SJ":
|
||||||
case "MS":
|
return new int[] {3, 2, 2, 2, 2, 2};
|
||||||
case "PM":
|
case "SK":
|
||||||
case "SM":
|
return new int[] {1, 1, 1, 1, 3, 2};
|
||||||
return new int[] {0, 2, 2, 2, 2, 2};
|
case "GF":
|
||||||
|
case "PK":
|
||||||
|
case "SL":
|
||||||
|
return new int[] {3, 2, 3, 3, 2, 2};
|
||||||
|
case "ET":
|
||||||
case "SN":
|
case "SN":
|
||||||
return new int[] {4, 4, 4, 3, 2, 2};
|
return new int[] {4, 4, 3, 2, 2, 2};
|
||||||
|
case "SO":
|
||||||
|
return new int[] {3, 2, 2, 4, 4, 2};
|
||||||
case "SR":
|
case "SR":
|
||||||
return new int[] {2, 4, 3, 0, 2, 2};
|
return new int[] {2, 4, 3, 0, 2, 2};
|
||||||
case "SS":
|
|
||||||
return new int[] {4, 3, 2, 3, 2, 2};
|
|
||||||
case "ST":
|
case "ST":
|
||||||
return new int[] {2, 2, 1, 2, 2, 2};
|
return new int[] {2, 2, 1, 2, 2, 2};
|
||||||
case "NI":
|
case "PF":
|
||||||
case "PA":
|
|
||||||
case "SV":
|
case "SV":
|
||||||
return new int[] {2, 3, 3, 3, 2, 2};
|
return new int[] {2, 3, 3, 1, 2, 2};
|
||||||
case "SZ":
|
case "SZ":
|
||||||
return new int[] {3, 3, 3, 4, 2, 2};
|
return new int[] {4, 4, 3, 4, 2, 2};
|
||||||
case "SX":
|
|
||||||
case "TC":
|
case "TC":
|
||||||
return new int[] {1, 2, 1, 0, 2, 2};
|
return new int[] {2, 2, 1, 3, 2, 2};
|
||||||
case "GA":
|
case "GA":
|
||||||
case "TG":
|
case "TG":
|
||||||
return new int[] {3, 4, 1, 0, 2, 2};
|
return new int[] {3, 4, 1, 0, 2, 2};
|
||||||
case "TH":
|
case "TH":
|
||||||
return new int[] {0, 2, 2, 3, 3, 4};
|
return new int[] {0, 1, 2, 1, 2, 2};
|
||||||
case "TK":
|
|
||||||
return new int[] {2, 2, 2, 4, 2, 2};
|
|
||||||
case "CU":
|
|
||||||
case "DJ":
|
case "DJ":
|
||||||
case "SY":
|
case "SY":
|
||||||
case "TJ":
|
case "TJ":
|
||||||
case "TL":
|
|
||||||
return new int[] {4, 3, 4, 4, 2, 2};
|
return new int[] {4, 3, 4, 4, 2, 2};
|
||||||
case "SC":
|
case "GL":
|
||||||
|
case "TK":
|
||||||
|
return new int[] {2, 2, 2, 4, 2, 2};
|
||||||
|
case "TL":
|
||||||
|
return new int[] {4, 2, 4, 4, 2, 2};
|
||||||
|
case "SS":
|
||||||
case "TM":
|
case "TM":
|
||||||
return new int[] {4, 2, 1, 1, 2, 2};
|
return new int[] {4, 2, 2, 3, 2, 2};
|
||||||
case "AZ":
|
|
||||||
case "GF":
|
|
||||||
case "LY":
|
|
||||||
case "PK":
|
|
||||||
case "SO":
|
|
||||||
case "TO":
|
|
||||||
return new int[] {3, 2, 3, 3, 2, 2};
|
|
||||||
case "TR":
|
case "TR":
|
||||||
return new int[] {1, 1, 0, 0, 2, 2};
|
return new int[] {1, 0, 0, 1, 3, 2};
|
||||||
case "TT":
|
case "TT":
|
||||||
return new int[] {1, 4, 1, 3, 2, 2};
|
return new int[] {1, 4, 0, 0, 2, 2};
|
||||||
case "EE":
|
|
||||||
case "IS":
|
|
||||||
case "LV":
|
|
||||||
case "PT":
|
|
||||||
case "SE":
|
|
||||||
case "TW":
|
case "TW":
|
||||||
return new int[] {0, 0, 0, 0, 0, 2};
|
return new int[] {0, 2, 0, 0, 0, 0};
|
||||||
|
case "ML":
|
||||||
case "TZ":
|
case "TZ":
|
||||||
return new int[] {3, 4, 3, 2, 2, 2};
|
return new int[] {3, 4, 2, 2, 2, 2};
|
||||||
case "IM":
|
|
||||||
case "UA":
|
case "UA":
|
||||||
return new int[] {0, 2, 1, 1, 2, 2};
|
return new int[] {0, 1, 1, 2, 4, 2};
|
||||||
case "SL":
|
case "LS":
|
||||||
case "UG":
|
case "UG":
|
||||||
return new int[] {3, 3, 4, 3, 2, 2};
|
return new int[] {3, 3, 3, 2, 2, 2};
|
||||||
case "US":
|
case "US":
|
||||||
return new int[] {1, 0, 2, 2, 3, 1};
|
return new int[] {1, 1, 4, 1, 3, 1};
|
||||||
case "AR":
|
|
||||||
case "KG":
|
|
||||||
case "TN":
|
case "TN":
|
||||||
case "UY":
|
case "UY":
|
||||||
return new int[] {2, 1, 1, 1, 2, 2};
|
return new int[] {2, 1, 1, 1, 2, 2};
|
||||||
case "UZ":
|
case "UZ":
|
||||||
return new int[] {2, 2, 3, 4, 2, 2};
|
return new int[] {2, 2, 3, 4, 3, 2};
|
||||||
case "BL":
|
case "AX":
|
||||||
case "CX":
|
case "CX":
|
||||||
|
case "LI":
|
||||||
|
case "MP":
|
||||||
|
case "MS":
|
||||||
|
case "PM":
|
||||||
|
case "SM":
|
||||||
case "VA":
|
case "VA":
|
||||||
return new int[] {1, 2, 2, 2, 2, 2};
|
return new int[] {0, 2, 2, 2, 2, 2};
|
||||||
case "AD":
|
|
||||||
case "BM":
|
|
||||||
case "BQ":
|
|
||||||
case "GD":
|
case "GD":
|
||||||
case "GL":
|
|
||||||
case "KN":
|
case "KN":
|
||||||
case "KY":
|
case "KY":
|
||||||
case "LC":
|
case "LC":
|
||||||
|
case "SX":
|
||||||
case "VC":
|
case "VC":
|
||||||
return new int[] {1, 2, 0, 0, 2, 2};
|
return new int[] {1, 2, 0, 0, 2, 2};
|
||||||
case "VG":
|
case "VG":
|
||||||
return new int[] {2, 2, 1, 1, 2, 2};
|
return new int[] {2, 2, 0, 1, 2, 2};
|
||||||
case "GG":
|
|
||||||
case "VI":
|
case "VI":
|
||||||
return new int[] {0, 2, 0, 1, 2, 2};
|
return new int[] {0, 2, 1, 2, 2, 2};
|
||||||
case "VN":
|
case "VN":
|
||||||
return new int[] {0, 3, 3, 4, 2, 2};
|
return new int[] {0, 0, 1, 2, 2, 1};
|
||||||
case "GH":
|
|
||||||
case "NA":
|
|
||||||
case "VU":
|
case "VU":
|
||||||
return new int[] {3, 3, 3, 2, 2, 2};
|
return new int[] {4, 3, 3, 1, 2, 2};
|
||||||
case "IO":
|
case "IO":
|
||||||
case "MH":
|
|
||||||
case "TV":
|
case "TV":
|
||||||
case "WF":
|
case "WF":
|
||||||
return new int[] {4, 2, 2, 4, 2, 2};
|
return new int[] {4, 2, 2, 4, 2, 2};
|
||||||
|
case "BT":
|
||||||
|
case "MZ":
|
||||||
case "WS":
|
case "WS":
|
||||||
return new int[] {3, 1, 3, 1, 2, 2};
|
return new int[] {3, 1, 2, 1, 2, 2};
|
||||||
case "AL":
|
|
||||||
case "XK":
|
case "XK":
|
||||||
return new int[] {1, 1, 1, 1, 2, 2};
|
return new int[] {1, 2, 1, 1, 2, 2};
|
||||||
case "BI":
|
case "BI":
|
||||||
case "HT":
|
case "HT":
|
||||||
case "KM":
|
|
||||||
case "MG":
|
case "MG":
|
||||||
case "NE":
|
case "NE":
|
||||||
case "SD":
|
|
||||||
case "TD":
|
case "TD":
|
||||||
case "VE":
|
case "VE":
|
||||||
case "YE":
|
case "YE":
|
||||||
return new int[] {4, 4, 4, 4, 2, 2};
|
return new int[] {4, 4, 4, 4, 2, 2};
|
||||||
case "JE":
|
|
||||||
case "YT":
|
case "YT":
|
||||||
return new int[] {4, 2, 2, 3, 2, 2};
|
return new int[] {2, 3, 3, 4, 2, 2};
|
||||||
case "ZA":
|
case "ZA":
|
||||||
return new int[] {3, 2, 2, 1, 1, 2};
|
return new int[] {2, 3, 2, 1, 2, 2};
|
||||||
case "ZM":
|
case "ZM":
|
||||||
return new int[] {3, 3, 4, 2, 2, 2};
|
return new int[] {4, 4, 4, 3, 3, 2};
|
||||||
case "MR":
|
case "LY":
|
||||||
|
case "TO":
|
||||||
case "ZW":
|
case "ZW":
|
||||||
return new int[] {4, 2, 4, 4, 2, 2};
|
return new int[] {3, 2, 4, 3, 2, 2};
|
||||||
default:
|
default:
|
||||||
return new int[] {2, 2, 2, 2, 2, 2};
|
return new int[] {2, 2, 2, 2, 2, 2};
|
||||||
}
|
}
|
||||||
|
@ -2050,7 +2050,7 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void handleFrameRendered(long presentationTimeUs) {
|
private void handleFrameRendered(long presentationTimeUs) {
|
||||||
if (this != tunnelingOnFrameRenderedListener) {
|
if (this != tunnelingOnFrameRenderedListener || getCodec() == null) {
|
||||||
// Stale event.
|
// Stale event.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -95,7 +95,6 @@ import androidx.media3.common.AdPlaybackState;
|
|||||||
import androidx.media3.common.AudioAttributes;
|
import androidx.media3.common.AudioAttributes;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
import androidx.media3.common.IllegalSeekPositionException;
|
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
import androidx.media3.common.MediaMetadata;
|
import androidx.media3.common.MediaMetadata;
|
||||||
import androidx.media3.common.Metadata;
|
import androidx.media3.common.Metadata;
|
||||||
@ -112,6 +111,7 @@ import androidx.media3.common.TrackGroup;
|
|||||||
import androidx.media3.common.Tracks;
|
import androidx.media3.common.Tracks;
|
||||||
import androidx.media3.common.util.Assertions;
|
import androidx.media3.common.util.Assertions;
|
||||||
import androidx.media3.common.util.Clock;
|
import androidx.media3.common.util.Clock;
|
||||||
|
import androidx.media3.common.util.SystemClock;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import androidx.media3.datasource.TransferListener;
|
import androidx.media3.datasource.TransferListener;
|
||||||
import androidx.media3.exoplayer.analytics.AnalyticsListener;
|
import androidx.media3.exoplayer.analytics.AnalyticsListener;
|
||||||
@ -930,31 +930,100 @@ public final class ExoPlayerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void illegalSeekPositionDoesThrow() throws Exception {
|
public void seekTo_indexLargerThanPlaylist_isIgnored() throws Exception {
|
||||||
final IllegalSeekPositionException[] exception = new IllegalSeekPositionException[1];
|
ExoPlayer player = new TestExoPlayerBuilder(context).build();
|
||||||
ActionSchedule actionSchedule =
|
player.setMediaItem(MediaItem.fromUri("http://test"));
|
||||||
new ActionSchedule.Builder(TAG)
|
|
||||||
.waitForPlaybackState(Player.STATE_BUFFERING)
|
player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 1000);
|
||||||
.executeRunnable(
|
|
||||||
new PlayerRunnable() {
|
assertThat(player.getCurrentMediaItemIndex()).isEqualTo(0);
|
||||||
@Override
|
player.release();
|
||||||
public void run(ExoPlayer player) {
|
|
||||||
try {
|
|
||||||
player.seekTo(/* mediaItemIndex= */ 100, /* positionMs= */ 0);
|
|
||||||
} catch (IllegalSeekPositionException e) {
|
|
||||||
exception[0] = e;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addMediaItems_indexLargerThanPlaylist_addsToEndOfPlaylist() throws Exception {
|
||||||
|
ExoPlayer player = new TestExoPlayerBuilder(context).build();
|
||||||
|
player.setMediaItem(MediaItem.fromUri("http://test"));
|
||||||
|
ImmutableList<MediaItem> addedItems =
|
||||||
|
ImmutableList.of(MediaItem.fromUri("http://new1"), MediaItem.fromUri("http://new2"));
|
||||||
|
|
||||||
|
player.addMediaItems(/* index= */ 5000, addedItems);
|
||||||
|
|
||||||
|
assertThat(player.getMediaItemCount()).isEqualTo(3);
|
||||||
|
assertThat(player.getMediaItemAt(1)).isEqualTo(addedItems.get(0));
|
||||||
|
assertThat(player.getMediaItemAt(2)).isEqualTo(addedItems.get(1));
|
||||||
|
player.release();
|
||||||
}
|
}
|
||||||
})
|
|
||||||
.waitForPlaybackState(Player.STATE_ENDED)
|
@Test
|
||||||
.build();
|
public void removeMediaItems_fromIndexLargerThanPlaylist_isIgnored() throws Exception {
|
||||||
new ExoPlayerTestRunner.Builder(context)
|
ExoPlayer player = new TestExoPlayerBuilder(context).build();
|
||||||
.setActionSchedule(actionSchedule)
|
player.setMediaItems(
|
||||||
.build()
|
ImmutableList.of(MediaItem.fromUri("http://item1"), MediaItem.fromUri("http://item2")));
|
||||||
.start()
|
|
||||||
.blockUntilActionScheduleFinished(TIMEOUT_MS)
|
player.removeMediaItems(/* fromIndex= */ 5000, /* toIndex= */ 6000);
|
||||||
.blockUntilEnded(TIMEOUT_MS);
|
|
||||||
assertThat(exception[0]).isNotNull();
|
assertThat(player.getMediaItemCount()).isEqualTo(2);
|
||||||
|
player.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void removeMediaItems_toIndexLargerThanPlaylist_removesUpToEndOfPlaylist()
|
||||||
|
throws Exception {
|
||||||
|
ExoPlayer player = new TestExoPlayerBuilder(context).build();
|
||||||
|
player.setMediaItems(
|
||||||
|
ImmutableList.of(MediaItem.fromUri("http://item1"), MediaItem.fromUri("http://item2")));
|
||||||
|
|
||||||
|
player.removeMediaItems(/* fromIndex= */ 1, /* toIndex= */ 6000);
|
||||||
|
|
||||||
|
assertThat(player.getMediaItemCount()).isEqualTo(1);
|
||||||
|
assertThat(player.getMediaItemAt(0).localConfiguration.uri.toString())
|
||||||
|
.isEqualTo("http://item1");
|
||||||
|
player.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void moveMediaItems_fromIndexLargerThanPlaylist_isIgnored() throws Exception {
|
||||||
|
ExoPlayer player = new TestExoPlayerBuilder(context).build();
|
||||||
|
ImmutableList<MediaItem> items =
|
||||||
|
ImmutableList.of(MediaItem.fromUri("http://item1"), MediaItem.fromUri("http://item2"));
|
||||||
|
player.setMediaItems(items);
|
||||||
|
|
||||||
|
player.moveMediaItems(/* fromIndex= */ 5000, /* toIndex= */ 6000, /* newIndex= */ 0);
|
||||||
|
|
||||||
|
assertThat(player.getMediaItemAt(0)).isEqualTo(items.get(0));
|
||||||
|
assertThat(player.getMediaItemAt(1)).isEqualTo(items.get(1));
|
||||||
|
player.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void moveMediaItems_toIndexLargerThanPlaylist_movesItemsUpToEndOfPlaylist()
|
||||||
|
throws Exception {
|
||||||
|
ExoPlayer player = new TestExoPlayerBuilder(context).build();
|
||||||
|
ImmutableList<MediaItem> items =
|
||||||
|
ImmutableList.of(MediaItem.fromUri("http://item1"), MediaItem.fromUri("http://item2"));
|
||||||
|
player.setMediaItems(items);
|
||||||
|
|
||||||
|
player.moveMediaItems(/* fromIndex= */ 1, /* toIndex= */ 6000, /* newIndex= */ 0);
|
||||||
|
|
||||||
|
assertThat(player.getMediaItemAt(0)).isEqualTo(items.get(1));
|
||||||
|
assertThat(player.getMediaItemAt(1)).isEqualTo(items.get(0));
|
||||||
|
player.release();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void moveMediaItems_newIndexLargerThanPlaylist_movesItemsUpToEndOfPlaylist()
|
||||||
|
throws Exception {
|
||||||
|
ExoPlayer player = new TestExoPlayerBuilder(context).build();
|
||||||
|
ImmutableList<MediaItem> items =
|
||||||
|
ImmutableList.of(MediaItem.fromUri("http://item1"), MediaItem.fromUri("http://item2"));
|
||||||
|
player.setMediaItems(items);
|
||||||
|
|
||||||
|
player.moveMediaItems(/* fromIndex= */ 0, /* toIndex= */ 1, /* newIndex= */ 5000);
|
||||||
|
|
||||||
|
assertThat(player.getMediaItemAt(0)).isEqualTo(items.get(1));
|
||||||
|
assertThat(player.getMediaItemAt(1)).isEqualTo(items.get(0));
|
||||||
|
player.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -2523,7 +2592,7 @@ public final class ExoPlayerTest {
|
|||||||
.build()
|
.build()
|
||||||
.start()
|
.start()
|
||||||
.blockUntilEnded(TIMEOUT_MS);
|
.blockUntilEnded(TIMEOUT_MS);
|
||||||
assertThat(target.positionMs).isEqualTo(C.POSITION_UNSET);
|
assertThat(target.positionMs).isEqualTo(C.TIME_UNSET);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -2545,7 +2614,7 @@ public final class ExoPlayerTest {
|
|||||||
.build()
|
.build()
|
||||||
.start()
|
.start()
|
||||||
.blockUntilEnded(TIMEOUT_MS);
|
.blockUntilEnded(TIMEOUT_MS);
|
||||||
assertThat(target.positionMs).isEqualTo(C.POSITION_UNSET);
|
assertThat(target.positionMs).isEqualTo(C.TIME_UNSET);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -10407,7 +10476,9 @@ public final class ExoPlayerTest {
|
|||||||
new Metadata(
|
new Metadata(
|
||||||
new BinaryFrame(/* id= */ "", /* data= */ new byte[0]),
|
new BinaryFrame(/* id= */ "", /* data= */ new byte[0]),
|
||||||
new TextInformationFrame(
|
new TextInformationFrame(
|
||||||
/* id= */ "TT2", /* description= */ null, /* value= */ "title")))
|
/* id= */ "TT2",
|
||||||
|
/* description= */ null,
|
||||||
|
/* values= */ ImmutableList.of("title"))))
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// Set multiple values together.
|
// Set multiple values together.
|
||||||
@ -11897,7 +11968,11 @@ public final class ExoPlayerTest {
|
|||||||
new TestExoPlayerBuilder(context)
|
new TestExoPlayerBuilder(context)
|
||||||
.setRenderersFactory(
|
.setRenderersFactory(
|
||||||
(handler, videoListener, audioListener, textOutput, metadataOutput) -> {
|
(handler, videoListener, audioListener, textOutput, metadataOutput) -> {
|
||||||
videoRenderer.set(new FakeVideoRenderer(handler, videoListener));
|
videoRenderer.set(
|
||||||
|
new FakeVideoRenderer(
|
||||||
|
SystemClock.DEFAULT.createHandler(
|
||||||
|
handler.getLooper(), /* callback= */ null),
|
||||||
|
videoListener));
|
||||||
return new Renderer[] {videoRenderer.get()};
|
return new Renderer[] {videoRenderer.get()};
|
||||||
})
|
})
|
||||||
.build();
|
.build();
|
||||||
@ -11999,10 +12074,20 @@ public final class ExoPlayerTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
@Config(sdk = Config.ALL_SDKS)
|
@Config(sdk = Config.ALL_SDKS)
|
||||||
public void builder_inBackgroundThread_doesNotThrow() throws Exception {
|
public void builder_inBackgroundThreadWithAllowedAnyThreadMethods_doesNotThrow()
|
||||||
|
throws Exception {
|
||||||
Thread builderThread =
|
Thread builderThread =
|
||||||
new Thread(
|
new Thread(
|
||||||
() -> new ExoPlayer.Builder(ApplicationProvider.getApplicationContext()).build());
|
() -> {
|
||||||
|
ExoPlayer player =
|
||||||
|
new ExoPlayer.Builder(ApplicationProvider.getApplicationContext()).build();
|
||||||
|
player.addListener(new Listener() {});
|
||||||
|
player.addAnalyticsListener(new AnalyticsListener() {});
|
||||||
|
player.addAudioOffloadListener(new ExoPlayer.AudioOffloadListener() {});
|
||||||
|
player.getClock();
|
||||||
|
player.getApplicationLooper();
|
||||||
|
player.getPlaybackLooper();
|
||||||
|
});
|
||||||
AtomicReference<Throwable> builderThrow = new AtomicReference<>();
|
AtomicReference<Throwable> builderThrow = new AtomicReference<>();
|
||||||
builderThread.setUncaughtExceptionHandler((thread, throwable) -> builderThrow.set(throwable));
|
builderThread.setUncaughtExceptionHandler((thread, throwable) -> builderThrow.set(throwable));
|
||||||
|
|
||||||
@ -12034,7 +12119,12 @@ public final class ExoPlayerTest {
|
|||||||
new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext())
|
new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext())
|
||||||
.setRenderersFactory(
|
.setRenderersFactory(
|
||||||
(handler, videoListener, audioListener, textOutput, metadataOutput) ->
|
(handler, videoListener, audioListener, textOutput, metadataOutput) ->
|
||||||
new Renderer[] {new FakeVideoRenderer(handler, videoListener)})
|
new Renderer[] {
|
||||||
|
new FakeVideoRenderer(
|
||||||
|
SystemClock.DEFAULT.createHandler(
|
||||||
|
handler.getLooper(), /* callback= */ null),
|
||||||
|
videoListener)
|
||||||
|
})
|
||||||
.build();
|
.build();
|
||||||
AnalyticsListener listener = mock(AnalyticsListener.class);
|
AnalyticsListener listener = mock(AnalyticsListener.class);
|
||||||
player.addAnalyticsListener(listener);
|
player.addAnalyticsListener(listener);
|
||||||
@ -12059,7 +12149,12 @@ public final class ExoPlayerTest {
|
|||||||
new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext())
|
new TestExoPlayerBuilder(ApplicationProvider.getApplicationContext())
|
||||||
.setRenderersFactory(
|
.setRenderersFactory(
|
||||||
(handler, videoListener, audioListener, textOutput, metadataOutput) ->
|
(handler, videoListener, audioListener, textOutput, metadataOutput) ->
|
||||||
new Renderer[] {new FakeVideoRenderer(handler, videoListener)})
|
new Renderer[] {
|
||||||
|
new FakeVideoRenderer(
|
||||||
|
SystemClock.DEFAULT.createHandler(
|
||||||
|
handler.getLooper(), /* callback= */ null),
|
||||||
|
videoListener)
|
||||||
|
})
|
||||||
.build();
|
.build();
|
||||||
Player.Listener listener = mock(Player.Listener.class);
|
Player.Listener listener = mock(Player.Listener.class);
|
||||||
player.addListener(listener);
|
player.addListener(listener);
|
||||||
@ -12275,7 +12370,7 @@ public final class ExoPlayerTest {
|
|||||||
|
|
||||||
public PositionGrabbingMessageTarget() {
|
public PositionGrabbingMessageTarget() {
|
||||||
mediaItemIndex = C.INDEX_UNSET;
|
mediaItemIndex = C.INDEX_UNSET;
|
||||||
positionMs = C.POSITION_UNSET;
|
positionMs = C.TIME_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
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