Merge pull request #11 from androidx/main

Merge from androidx/media
This commit is contained in:
ybai001 2024-09-25 09:11:27 +08:00 committed by GitHub
commit e7254b809a
No known key found for this signature in database
GPG Key ID: B5690EEEBB952194
3911 changed files with 754218 additions and 61240 deletions

View File

@ -5,37 +5,31 @@ body:
- type: markdown
attributes:
value: |
We can only process bug reports that are actionable. Unclear bug reports or reports with
insufficient information may not get attention.
We can only process bug reports that are actionable. Unclear bug reports or reports with insufficient information may not get attention.
Before filing a bug:
-------------------------
- Search existing issues, including issues that are closed:
https://github.com/androidx/media/issues?q=is%3Aissue
- For ExoPlayer-related bugs, please also check for existing issues on the ExoPlayer
tracker: https://github.com/google/ExoPlayer/issues?q=is%3Aissue
- Search existing issues, including issues that are closed: https://github.com/androidx/media/issues?q=is%3Aissue
- For ExoPlayer-related bugs, please also check for existing issues on the ExoPlayer tracker: https://github.com/google/ExoPlayer/issues?q=is%3Aissue
- type: dropdown
attributes:
label: Media3 Version
label: Version
description: What version of Media3 (or ExoPlayer) are you using?
options:
- Media3 1.1.0-alpha01
- Media3 1.0.2
- Media3 1.0.1
- Media3 1.0.0
- Media3 1.0.0-rc02
- Media3 1.0.0-rc01
- Media3 1.0.0-beta03
- Media3 1.0.0-beta02
- Media3 1.0.0-beta01
- Media3 1.0.0-alpha03
- Media3 1.0.0-alpha02
- Media3 1.0.0-alpha01
- Media3 `main` branch
- ExoPlayer 2.18.7
- ExoPlayer 2.18.6
- ExoPlayer 2.18.5
- Media3 main branch
- Media3 pre-release (alpha, beta or RC not in this list)
- Media3 1.4.1
- Media3 1.4.0
- Media3 1.3.1
- Media3 1.3.0
- Media3 1.2.1
- Media3 1.2.0
- Media3 1.1.1 / ExoPlayer 2.19.1
- Media3 1.1.0 / ExoPlayer 2.19.0
- Media3 1.0.2 / ExoPlayer 2.18.7
- Media3 1.0.1 / ExoPlayer 2.18.6
- Media3 1.0.0 / ExoPlayer 2.18.5
- ExoPlayer 2.18.4
- ExoPlayer 2.18.3
- ExoPlayer 2.18.2
@ -50,10 +44,16 @@ body:
- ExoPlayer 2.14.2
- ExoPlayer 2.14.1
- ExoPlayer 2.14.0
- ExoPlayer `dev-v2` branch
- ExoPlayer dev-v2 branch
- Older (unsupported)
validations:
required: true
- type: textarea
attributes:
label: More version details
description: >
Required if you selected `main` or `dev-v2` (please provide an exact commit SHA),
or 'pre-release' or 'older' (please provide the version).
- type: textarea
attributes:
label: Devices that reproduce the issue
@ -114,7 +114,7 @@ body:
* Attach a file here
* Include a media URL
* Refer to a piece of media from the demo app (e.g. `Misc > Dizzy (MP4)`)
* If you don't want to post media publicly please email the info to dev.exoplayer@gmail.com with subject 'Issue #\<issuenumber\>' after filing this issue, and note that you will do this here.
* If you don't want to post media publicly please email the info to android-media-github@google.com with subject 'Issue #\<issuenumber\>' after filing this issue, and note that you will do this here.
* If you are certain the issue does not depend on the media being played, enter "Not applicable" here.
For DRM-protected media please also include the scheme and license server URL.
@ -124,8 +124,8 @@ body:
attributes:
label: Bug Report
description: |
After filing this issue please run `adb bugreport` shortly after reproducing the problem (ideally in the [demo app](https://github.com/androidx/media/tree/release/demos/main)) to capture a zip file, and email this to dev.exoplayer@gmail.com with subject 'Issue #\<issuenumber\>'.
After filing this issue please run `adb bugreport` shortly after reproducing the problem (ideally in the [demo app](https://github.com/androidx/media/tree/release/demos/main)) to capture a zip file, and email this to android-media-github@google.com with subject 'Issue #\<issuenumber\>'.
**Note:** Logcat output is **not** the same as a full bug report, and is often missing information that's useful for diagnosing issues. Please ensure you're sending a full bug report zip file.
options:
- label: You will email the zip file produced by `adb bugreport` to dev.exoplayer@gmail.com after filing this issue.
- label: You will email the zip file produced by `adb bugreport` to android-media-github@google.com after filing this issue.

View File

@ -39,6 +39,6 @@ Don't forget to check ExoPlayer's supported formats and devices, if applicable
(https://developer.android.com/guide/topics/media/exoplayer/supported-formats).
If there's something you don't want to post publicly, please submit the issue,
then email the link/bug report to dev.exoplayer@gmail.com using a subject in the
format "Issue #1234", where #1234 is your issue number (we don't reply to
emails).
then email the link/bug report to android-media-github@google.com using a
subject in the format "Issue #1234", where #1234 is your issue number (we don't
reply to emails).

10
.idea/icon.svg generated Normal file
View File

@ -0,0 +1,10 @@
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="60 60 130 130">
<g>
<path transform="matrix(1,0,0,-1,91.2359,110.7836)" d="M0 0C-1.459 1.259-2.67 2.872-3.493 4.807-5.848 10.342-4.026 16.923 .843 20.454 7.261 25.107 16.099 23.076 19.927 16.386 20.683 15.065 21.18 13.667 21.437 12.251 21.753 10.51 23.603 9.59 25.201 10.181L42.333 20.072-3.502 46.535C-9.567 50.037-17.147 45.66-17.147 38.657V-14.113L-.508-4.594C1.175-3.631 1.468-1.267 0 0" fill="#fcb64e"/>
<path transform="matrix(1,0,0,-1,74.2803,124.942)" d="M0 0C-.064 0-.128-.002-.192-.004V-.005L-.101-.058Z" fill="#fcb64e"/>
<path transform="matrix(1,0,0,-1,112.6543,151.6317)" d="M0 0C-.354-1.895-1.137-3.753-2.395-5.438-5.992-10.259-12.595-11.998-18.097-9.568-25.348-6.366-28.043 2.293-24.19 8.968-23.429 10.287-22.471 11.42-21.377 12.355-19.908 13.611-20.189 15.969-21.862 16.935L-38.566 26.579V-26.242C-38.566-33.245-30.985-37.621-24.92-34.12L20.823-7.71 4.225 1.874C2.545 2.843 .355 1.906 0 0" fill="#56a0d7"/>
<path transform="matrix(1,0,0,-1,74.0884,124.9471)" d="M0 0V-.106L.091-.053Z" fill="#56a0d7"/>
<path transform="matrix(1,0,0,-1,129.8726,137.40271)" d="M0 0C-1.352-.476-2.805-.736-4.321-.736-12.028-.736-18.18 5.928-17.328 13.809-16.665 19.936-11.683 24.817-5.545 25.38-3.585 25.559-1.707 25.302 .007 24.697 1.712 24.096 3.467 25.291 3.696 27.027V46.485C3.711 46.51 3.728 46.535 3.742 46.56V46.561L3.696 46.535V46.691L-13.436 36.8C-15.034 36.209-16.884 37.129-17.2 38.87-17.457 40.286-17.954 41.684-18.71 43.005-22.538 49.696-31.376 51.726-37.793 47.073-42.663 43.542-44.484 36.961-42.13 31.426-41.307 29.491-40.096 27.878-38.637 26.619-37.169 25.352-37.461 22.988-39.145 22.026L-55.784 12.506-55.873 12.456C-55.843 12.456-55.814 12.457-55.784 12.457-55.721 12.459-55.657 12.46-55.592 12.461L-55.693 12.403-55.784 12.35-39.081 2.706C-37.407 1.74-37.126-.618-38.595-1.874-39.689-2.809-40.647-3.942-41.408-5.261-45.262-11.936-42.566-20.595-35.315-23.797-29.813-26.227-23.21-24.488-19.613-19.667-18.355-17.982-17.572-16.124-17.218-14.229-16.863-12.323-14.673-11.386-12.994-12.355L3.605-21.939 3.696-21.991V-2.331C3.467-.592 1.709 .602 0 0" fill="#ae1e59"/>
<path transform="matrix(1,0,0,-1,179.4517,117.17461)" d="M0 0-45.835 26.463V26.461C-45.835 26.461-45.836 26.462-45.837 26.463V26.333 26.332 7.172C-45.837 7.043-45.866 6.923-45.883 6.799-46.112 5.062-47.867 3.868-49.572 4.469-51.286 5.074-53.164 5.331-55.124 5.151-61.262 4.589-66.244-.292-66.907-6.42-67.759-14.3-61.607-20.964-53.9-20.964-52.384-20.964-50.931-20.704-49.579-20.228-47.87-19.626-46.112-20.82-45.883-22.559-45.866-22.683-45.837-22.803-45.837-22.932V-42.219C-45.836-42.219-45.836-42.218-45.836-42.218L-45.835-42.22 0-15.756C6.064-12.255 6.064-3.501 0 0" fill="#ef5451"/>
</g>
</svg>

After

Width:  |  Height:  |  Size: 2.7 KiB

View File

@ -38,6 +38,24 @@ you made on top of `main` using
$ git diff -U0 main... | google-java-format-diff.py -p1 -i
```
### Push access to PR branches
Please ensure maintainers of this repository have push access to your PR branch
by ticking the `Allow edits from maintainers` checkbox when creating the PR (or
after it's created). See the
[GitHub docs](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork)
for more info. This allows us to make changes and fixes to the PR while it goes
through internal review, and ensures we don't create an
['evil' merge](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefevilmergeaevilmerge)
when it gets merged.
This checkbox only appears on PRs from individual-owned forks
(https://github.com/orgs/community/discussions/5634). If you open a PR from an
organization-owned fork we will ask you to open a new one from an
individual-owned fork. If this isn't possible we can still merge the PR, but it
will result in an 'evil' merge because the changes and fixes we make during
internal review will be part of the merge commit.
## Contributor license agreement
Contributions to any Google project must be accompanied by a Contributor

View File

@ -1,19 +1,21 @@
# AndroidX Media
AndroidX Media is a collection of libraries for implementing media use cases on
Android, including local playback (via ExoPlayer) and media sessions.
Android, including local playback (via ExoPlayer), video editing (via Transformer) and media sessions.
## Documentation
* The [developer guide][] provides a wealth of information.
* The [class reference][] documents the classes and methods.
* The [release notes][] document the major changes in each release.
* The [media dev center][] provides samples and guidelines.
* Follow our [developer blog][] to keep up to date with the latest
developments!
[developer guide]: https://developer.android.com/guide/topics/media/media3
[class reference]: https://developer.android.com/reference/androidx/media3/common/package-summary
[release notes]: RELEASENOTES.md
[media dev center]: https://developer.android.com/media
[developer blog]: https://medium.com/google-exoplayer
## Migration for existing ExoPlayer and MediaSession projects
@ -45,13 +47,21 @@ also possible to clone this GitHub repository and depend on the modules locally.
#### 1. Add module dependencies
The easiest way to get started using AndroidX Media is to add gradle
dependencies on the libraries you need in the `build.gradle` file of your app
module.
dependencies on the libraries you need in the `build.gradle.kts` file of your
app module.
For example, to depend on ExoPlayer with DASH playback support and UI components
you can add dependencies on the modules like this:
```gradle
```kotlin
implementation("androidx.media3:media3-exoplayer:1.X.X")
implementation("androidx.media3:media3-exoplayer-dash:1.X.X")
implementation("androidx.media3:media3-ui:1.X.X")
```
Or in Gradle Groovy DSL `build.gradle`:
```groovy
implementation 'androidx.media3:media3-exoplayer:1.X.X'
implementation 'androidx.media3:media3-exoplayer-dash:1.X.X'
implementation 'androidx.media3:media3-ui:1.X.X'
@ -73,21 +83,23 @@ details.
#### 2. Turn on Java 8 support
If not enabled already, you also need to turn on Java 8 support in all
`build.gradle` files depending on AndroidX Media, by adding the following to the
`android` section:
`build.gradle.kts` files depending on AndroidX Media, by adding the following to
the `android` section:
```gradle
```kotlin
compileOptions {
targetCompatibility = JavaVersion.VERSION_1_8
}
```
Or in Gradle Groovy DSL `build.gradle`:
```groovy
compileOptions {
targetCompatibility JavaVersion.VERSION_1_8
}
```
#### 3. Enable multidex
If your Gradle `minSdkVersion` is 20 or lower, you should
[enable multidex](https://developer.android.com/studio/build/multidex) in order
to prevent build errors.
### Locally
Cloning the repository and depending on the modules locally is required when
@ -98,24 +110,59 @@ First, clone the repository into a local directory:
```sh
git clone https://github.com/androidx/media.git
cd media
```
Next, add the following to your project's `settings.gradle` file, replacing
Next, add the following to your project's `settings.gradle.kts` file, replacing
`path/to/media` with the path to your local copy:
```gradle
gradle.ext.androidxMediaModulePrefix = 'media-'
```kotlin
(gradle as ExtensionAware).extra["androidxMediaModulePrefix"] = "media3-"
apply(from = file("path/to/media/core_settings.gradle"))
```
Or in Gradle Groovy DSL `settings.gradle`:
```groovy
gradle.ext.androidxMediaModulePrefix = 'media3-'
apply from: file("path/to/media/core_settings.gradle")
```
You should now see the AndroidX Media modules appear as part of your project.
You can depend on them as you would on any other local module, for example:
You can depend on them from `build.gradle.kts` as you would on any other local
module, for example:
```gradle
implementation project(':media-lib-exoplayer')
implementation project(':media-lib-exoplayer-dash')
implementation project(':media-lib-ui')
```kotlin
implementation(project(":media3-lib-exoplayer"))
implementation(project(":media3-lib-exoplayer-dash"))
implementation(project(":media3-lib-ui"))
```
Or in Gradle Groovy DSL `build.gradle`:
```groovy
implementation project(':media3-lib-exoplayer')
implementation project(':media3-lib-exoplayer-dash')
implementation project(':media3-lib-ui')
```
#### MIDI module
By default the [MIDI module](libraries/decoder_midi) is disabled as a local
dependency, because it requires additional Maven repository config. If you want
to use it as a local dependency, please configure the JitPack repository as
[described in the module README](libraries/decoder_midi/README.md#getting-the-module),
and then enable building the module in your `settings.gradle.kts` file:
```kotlin
gradle.extra.apply {
set("androidxMediaEnableMidiModule", true)
}
```
Or in Gradle Groovy DSL `settings.gradle`:
```groovy
gradle.ext.androidxMediaEnableMidiModule = true
```
## Developing AndroidX Media

File diff suppressed because it is too large Load Diff

496
api.txt
View File

@ -1,4 +1,4 @@
// Signature format: 3.0
// Signature format: 2.0
package androidx.media3.common {
public final class AdOverlayInfo {
@ -26,7 +26,7 @@ package androidx.media3.common {
}
public final class AudioAttributes {
method @RequiresApi(21) public androidx.media3.common.AudioAttributes.AudioAttributesV21 getAudioAttributesV21();
method public androidx.media3.common.AudioAttributes.AudioAttributesV21 getAudioAttributesV21();
field public static final androidx.media3.common.AudioAttributes DEFAULT;
field @androidx.media3.common.C.AudioAllowedCapturePolicy public final int allowedCapturePolicy;
field @androidx.media3.common.C.AudioContentType public final int contentType;
@ -35,7 +35,7 @@ package androidx.media3.common {
field @androidx.media3.common.C.AudioUsage public final int usage;
}
@RequiresApi(21) public static final class AudioAttributes.AudioAttributesV21 {
public static final class AudioAttributes.AudioAttributesV21 {
field public final android.media.AudioAttributes audioAttributes;
}
@ -79,6 +79,7 @@ package androidx.media3.common {
field public static final java.util.UUID PLAYREADY_UUID;
field public static final float RATE_UNSET = -3.4028235E38f;
field public static final int ROLE_FLAG_ALTERNATE = 2; // 0x2
field public static final int ROLE_FLAG_AUXILIARY = 32768; // 0x8000
field public static final int ROLE_FLAG_CAPTION = 64; // 0x40
field public static final int ROLE_FLAG_COMMENTARY = 8; // 0x8
field public static final int ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND = 1024; // 0x400
@ -127,6 +128,11 @@ package androidx.media3.common {
field public static final int USAGE_VOICE_COMMUNICATION = 2; // 0x2
field public static final int USAGE_VOICE_COMMUNICATION_SIGNALLING = 3; // 0x3
field public static final java.util.UUID UUID_NIL;
field public static final int VOLUME_FLAG_ALLOW_RINGER_MODES = 2; // 0x2
field public static final int VOLUME_FLAG_PLAY_SOUND = 4; // 0x4
field public static final int VOLUME_FLAG_REMOVE_SOUND_AND_VIBRATE = 8; // 0x8
field public static final int VOLUME_FLAG_SHOW_UI = 1; // 0x1
field public static final int VOLUME_FLAG_VIBRATE = 16; // 0x10
field public static final int WAKE_MODE_LOCAL = 1; // 0x1
field public static final int WAKE_MODE_NETWORK = 2; // 0x2
field public static final int WAKE_MODE_NONE = 0; // 0x0
@ -151,7 +157,7 @@ package androidx.media3.common {
@IntDef(open=true, value={androidx.media3.common.C.CRYPTO_TYPE_UNSUPPORTED, androidx.media3.common.C.CRYPTO_TYPE_NONE, androidx.media3.common.C.CRYPTO_TYPE_FRAMEWORK}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE_USE) public static @interface C.CryptoType {
}
@IntDef(flag=true, value={androidx.media3.common.C.ROLE_FLAG_MAIN, androidx.media3.common.C.ROLE_FLAG_ALTERNATE, androidx.media3.common.C.ROLE_FLAG_SUPPLEMENTARY, androidx.media3.common.C.ROLE_FLAG_COMMENTARY, androidx.media3.common.C.ROLE_FLAG_DUB, androidx.media3.common.C.ROLE_FLAG_EMERGENCY, androidx.media3.common.C.ROLE_FLAG_CAPTION, androidx.media3.common.C.ROLE_FLAG_SUBTITLE, androidx.media3.common.C.ROLE_FLAG_SIGN, androidx.media3.common.C.ROLE_FLAG_DESCRIBES_VIDEO, androidx.media3.common.C.ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND, androidx.media3.common.C.ROLE_FLAG_ENHANCED_DIALOG_INTELLIGIBILITY, androidx.media3.common.C.ROLE_FLAG_TRANSCRIBES_DIALOG, androidx.media3.common.C.ROLE_FLAG_EASY_TO_READ, androidx.media3.common.C.ROLE_FLAG_TRICK_PLAY}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface C.RoleFlags {
@IntDef(flag=true, value={androidx.media3.common.C.ROLE_FLAG_MAIN, androidx.media3.common.C.ROLE_FLAG_ALTERNATE, androidx.media3.common.C.ROLE_FLAG_SUPPLEMENTARY, androidx.media3.common.C.ROLE_FLAG_COMMENTARY, androidx.media3.common.C.ROLE_FLAG_DUB, androidx.media3.common.C.ROLE_FLAG_EMERGENCY, androidx.media3.common.C.ROLE_FLAG_CAPTION, androidx.media3.common.C.ROLE_FLAG_SUBTITLE, androidx.media3.common.C.ROLE_FLAG_SIGN, androidx.media3.common.C.ROLE_FLAG_DESCRIBES_VIDEO, androidx.media3.common.C.ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND, androidx.media3.common.C.ROLE_FLAG_ENHANCED_DIALOG_INTELLIGIBILITY, androidx.media3.common.C.ROLE_FLAG_TRANSCRIBES_DIALOG, androidx.media3.common.C.ROLE_FLAG_EASY_TO_READ, androidx.media3.common.C.ROLE_FLAG_TRICK_PLAY, androidx.media3.common.C.ROLE_FLAG_AUXILIARY}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface C.RoleFlags {
}
@IntDef(flag=true, value={androidx.media3.common.C.SELECTION_FLAG_DEFAULT, androidx.media3.common.C.SELECTION_FLAG_FORCED, androidx.media3.common.C.SELECTION_FLAG_AUTOSELECT}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface C.SelectionFlags {
@ -163,6 +169,9 @@ package androidx.media3.common {
@IntDef(open=true, value={androidx.media3.common.C.TRACK_TYPE_UNKNOWN, androidx.media3.common.C.TRACK_TYPE_DEFAULT, androidx.media3.common.C.TRACK_TYPE_AUDIO, androidx.media3.common.C.TRACK_TYPE_VIDEO, androidx.media3.common.C.TRACK_TYPE_TEXT, androidx.media3.common.C.TRACK_TYPE_IMAGE, androidx.media3.common.C.TRACK_TYPE_METADATA, androidx.media3.common.C.TRACK_TYPE_CAMERA_MOTION, androidx.media3.common.C.TRACK_TYPE_NONE}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE_USE) public static @interface C.TrackType {
}
@IntDef(flag=true, value={androidx.media3.common.C.VOLUME_FLAG_SHOW_UI, androidx.media3.common.C.VOLUME_FLAG_ALLOW_RINGER_MODES, androidx.media3.common.C.VOLUME_FLAG_PLAY_SOUND, androidx.media3.common.C.VOLUME_FLAG_REMOVE_SOUND_AND_VIBRATE, androidx.media3.common.C.VOLUME_FLAG_VIBRATE}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE_USE}) public static @interface C.VolumeFlags {
}
@IntDef({androidx.media3.common.C.WAKE_MODE_NONE, androidx.media3.common.C.WAKE_MODE_LOCAL, androidx.media3.common.C.WAKE_MODE_NETWORK}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface C.WakeMode {
}
@ -170,9 +179,18 @@ package androidx.media3.common {
field public static final int PLAYBACK_TYPE_LOCAL = 0; // 0x0
field public static final int PLAYBACK_TYPE_REMOTE = 1; // 0x1
field public static final androidx.media3.common.DeviceInfo UNKNOWN;
field public final int maxVolume;
field public final int minVolume;
field @IntRange(from=0) public final int maxVolume;
field @IntRange(from=0) public final int minVolume;
field @androidx.media3.common.DeviceInfo.PlaybackType public final int playbackType;
field @Nullable public final String routingControllerId;
}
public static final class DeviceInfo.Builder {
ctor public DeviceInfo.Builder(@androidx.media3.common.DeviceInfo.PlaybackType int);
method public androidx.media3.common.DeviceInfo build();
method public androidx.media3.common.DeviceInfo.Builder setMaxVolume(@IntRange(from=0) int);
method public androidx.media3.common.DeviceInfo.Builder setMinVolume(@IntRange(from=0) int);
method public androidx.media3.common.DeviceInfo.Builder setRoutingControllerId(@Nullable String);
}
@IntDef({androidx.media3.common.DeviceInfo.PLAYBACK_TYPE_LOCAL, androidx.media3.common.DeviceInfo.PLAYBACK_TYPE_REMOTE}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE_USE) public static @interface DeviceInfo.PlaybackType {
@ -209,8 +227,8 @@ package androidx.media3.common {
public final class MediaItem {
method public androidx.media3.common.MediaItem.Builder buildUpon();
method public static androidx.media3.common.MediaItem fromUri(String);
method public static androidx.media3.common.MediaItem fromUri(android.net.Uri);
method public static androidx.media3.common.MediaItem fromUri(String);
field public static final String DEFAULT_MEDIA_ID = "";
field public static final androidx.media3.common.MediaItem EMPTY;
field public final androidx.media3.common.MediaItem.ClippingConfiguration clippingConfiguration;
@ -247,8 +265,8 @@ package androidx.media3.common {
method public androidx.media3.common.MediaItem.Builder setRequestMetadata(androidx.media3.common.MediaItem.RequestMetadata);
method public androidx.media3.common.MediaItem.Builder setSubtitleConfigurations(java.util.List<androidx.media3.common.MediaItem.SubtitleConfiguration>);
method public androidx.media3.common.MediaItem.Builder setTag(@Nullable Object);
method public androidx.media3.common.MediaItem.Builder setUri(@Nullable String);
method public androidx.media3.common.MediaItem.Builder setUri(@Nullable android.net.Uri);
method public androidx.media3.common.MediaItem.Builder setUri(@Nullable String);
}
public static class MediaItem.ClippingConfiguration {
@ -318,7 +336,7 @@ package androidx.media3.common {
method public androidx.media3.common.MediaItem.LiveConfiguration.Builder setTargetOffsetMs(long);
}
public static class MediaItem.LocalConfiguration {
public static final class MediaItem.LocalConfiguration {
field @Nullable public final androidx.media3.common.MediaItem.AdsConfiguration adsConfiguration;
field @Nullable public final androidx.media3.common.MediaItem.DrmConfiguration drmConfiguration;
field @Nullable public final String mimeType;
@ -369,14 +387,50 @@ package androidx.media3.common {
public final class MediaMetadata {
method public androidx.media3.common.MediaMetadata.Builder buildUpon();
field public static final androidx.media3.common.MediaMetadata EMPTY;
field public static final int FOLDER_TYPE_ALBUMS = 2; // 0x2
field public static final int FOLDER_TYPE_ARTISTS = 3; // 0x3
field public static final int FOLDER_TYPE_GENRES = 4; // 0x4
field public static final int FOLDER_TYPE_MIXED = 0; // 0x0
field public static final int FOLDER_TYPE_NONE = -1; // 0xffffffff
field public static final int FOLDER_TYPE_PLAYLISTS = 5; // 0x5
field public static final int FOLDER_TYPE_TITLES = 1; // 0x1
field public static final int FOLDER_TYPE_YEARS = 6; // 0x6
field @Deprecated public static final int FOLDER_TYPE_ALBUMS = 2; // 0x2
field @Deprecated public static final int FOLDER_TYPE_ARTISTS = 3; // 0x3
field @Deprecated public static final int FOLDER_TYPE_GENRES = 4; // 0x4
field @Deprecated public static final int FOLDER_TYPE_MIXED = 0; // 0x0
field @Deprecated public static final int FOLDER_TYPE_NONE = -1; // 0xffffffff
field @Deprecated public static final int FOLDER_TYPE_PLAYLISTS = 5; // 0x5
field @Deprecated public static final int FOLDER_TYPE_TITLES = 1; // 0x1
field @Deprecated public static final int FOLDER_TYPE_YEARS = 6; // 0x6
field public static final int MEDIA_TYPE_ALBUM = 10; // 0xa
field public static final int MEDIA_TYPE_ARTIST = 11; // 0xb
field public static final int MEDIA_TYPE_AUDIO_BOOK = 15; // 0xf
field public static final int MEDIA_TYPE_AUDIO_BOOK_CHAPTER = 2; // 0x2
field public static final int MEDIA_TYPE_FOLDER_ALBUMS = 21; // 0x15
field public static final int MEDIA_TYPE_FOLDER_ARTISTS = 22; // 0x16
field public static final int MEDIA_TYPE_FOLDER_AUDIO_BOOKS = 26; // 0x1a
field public static final int MEDIA_TYPE_FOLDER_GENRES = 23; // 0x17
field public static final int MEDIA_TYPE_FOLDER_MIXED = 20; // 0x14
field public static final int MEDIA_TYPE_FOLDER_MOVIES = 35; // 0x23
field public static final int MEDIA_TYPE_FOLDER_NEWS = 32; // 0x20
field public static final int MEDIA_TYPE_FOLDER_PLAYLISTS = 24; // 0x18
field public static final int MEDIA_TYPE_FOLDER_PODCASTS = 27; // 0x1b
field public static final int MEDIA_TYPE_FOLDER_RADIO_STATIONS = 31; // 0x1f
field public static final int MEDIA_TYPE_FOLDER_TRAILERS = 34; // 0x22
field public static final int MEDIA_TYPE_FOLDER_TV_CHANNELS = 28; // 0x1c
field public static final int MEDIA_TYPE_FOLDER_TV_SERIES = 29; // 0x1d
field public static final int MEDIA_TYPE_FOLDER_TV_SHOWS = 30; // 0x1e
field public static final int MEDIA_TYPE_FOLDER_VIDEOS = 33; // 0x21
field public static final int MEDIA_TYPE_FOLDER_YEARS = 25; // 0x19
field public static final int MEDIA_TYPE_GENRE = 12; // 0xc
field public static final int MEDIA_TYPE_MIXED = 0; // 0x0
field public static final int MEDIA_TYPE_MOVIE = 8; // 0x8
field public static final int MEDIA_TYPE_MUSIC = 1; // 0x1
field public static final int MEDIA_TYPE_NEWS = 5; // 0x5
field public static final int MEDIA_TYPE_PLAYLIST = 13; // 0xd
field public static final int MEDIA_TYPE_PODCAST = 16; // 0x10
field public static final int MEDIA_TYPE_PODCAST_EPISODE = 3; // 0x3
field public static final int MEDIA_TYPE_RADIO_STATION = 4; // 0x4
field public static final int MEDIA_TYPE_TRAILER = 7; // 0x7
field public static final int MEDIA_TYPE_TV_CHANNEL = 17; // 0x11
field public static final int MEDIA_TYPE_TV_SEASON = 19; // 0x13
field public static final int MEDIA_TYPE_TV_SERIES = 18; // 0x12
field public static final int MEDIA_TYPE_TV_SHOW = 9; // 0x9
field public static final int MEDIA_TYPE_VIDEO = 6; // 0x6
field public static final int MEDIA_TYPE_YEAR = 14; // 0xe
field public static final int PICTURE_TYPE_ARTIST_PERFORMER = 8; // 0x8
field public static final int PICTURE_TYPE_A_BRIGHT_COLORED_FISH = 17; // 0x11
field public static final int PICTURE_TYPE_BACK_COVER = 4; // 0x4
@ -411,9 +465,11 @@ package androidx.media3.common {
field @Nullable public final Integer discNumber;
field @Nullable public final CharSequence displayTitle;
field @Nullable public final android.os.Bundle extras;
field @Nullable @androidx.media3.common.MediaMetadata.FolderType public final Integer folderType;
field @Deprecated @Nullable @androidx.media3.common.MediaMetadata.FolderType public final Integer folderType;
field @Nullable public final CharSequence genre;
field @Nullable public final Boolean isBrowsable;
field @Nullable public final Boolean isPlayable;
field @Nullable @androidx.media3.common.MediaMetadata.MediaType public final Integer mediaType;
field @Nullable public final androidx.media3.common.Rating overallRating;
field @Nullable public final Integer recordingDay;
field @Nullable public final Integer recordingMonth;
@ -447,9 +503,11 @@ package androidx.media3.common {
method public androidx.media3.common.MediaMetadata.Builder setDiscNumber(@Nullable Integer);
method public androidx.media3.common.MediaMetadata.Builder setDisplayTitle(@Nullable CharSequence);
method public androidx.media3.common.MediaMetadata.Builder setExtras(@Nullable android.os.Bundle);
method public androidx.media3.common.MediaMetadata.Builder setFolderType(@Nullable @androidx.media3.common.MediaMetadata.FolderType Integer);
method @Deprecated public androidx.media3.common.MediaMetadata.Builder setFolderType(@Nullable @androidx.media3.common.MediaMetadata.FolderType Integer);
method public androidx.media3.common.MediaMetadata.Builder setGenre(@Nullable CharSequence);
method public androidx.media3.common.MediaMetadata.Builder setIsBrowsable(@Nullable Boolean);
method public androidx.media3.common.MediaMetadata.Builder setIsPlayable(@Nullable Boolean);
method public androidx.media3.common.MediaMetadata.Builder setMediaType(@Nullable @androidx.media3.common.MediaMetadata.MediaType Integer);
method public androidx.media3.common.MediaMetadata.Builder setOverallRating(@Nullable androidx.media3.common.Rating);
method public androidx.media3.common.MediaMetadata.Builder setRecordingDay(@IntRange(from=1, to=31) @Nullable Integer);
method public androidx.media3.common.MediaMetadata.Builder setRecordingMonth(@IntRange(from=1, to=12) @Nullable Integer);
@ -467,7 +525,10 @@ package androidx.media3.common {
method public androidx.media3.common.MediaMetadata.Builder setWriter(@Nullable CharSequence);
}
@IntDef({androidx.media3.common.MediaMetadata.FOLDER_TYPE_NONE, androidx.media3.common.MediaMetadata.FOLDER_TYPE_MIXED, androidx.media3.common.MediaMetadata.FOLDER_TYPE_TITLES, androidx.media3.common.MediaMetadata.FOLDER_TYPE_ALBUMS, androidx.media3.common.MediaMetadata.FOLDER_TYPE_ARTISTS, androidx.media3.common.MediaMetadata.FOLDER_TYPE_GENRES, androidx.media3.common.MediaMetadata.FOLDER_TYPE_PLAYLISTS, androidx.media3.common.MediaMetadata.FOLDER_TYPE_YEARS}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface MediaMetadata.FolderType {
@Deprecated @IntDef({androidx.media3.common.MediaMetadata.FOLDER_TYPE_NONE, androidx.media3.common.MediaMetadata.FOLDER_TYPE_MIXED, androidx.media3.common.MediaMetadata.FOLDER_TYPE_TITLES, androidx.media3.common.MediaMetadata.FOLDER_TYPE_ALBUMS, androidx.media3.common.MediaMetadata.FOLDER_TYPE_ARTISTS, androidx.media3.common.MediaMetadata.FOLDER_TYPE_GENRES, androidx.media3.common.MediaMetadata.FOLDER_TYPE_PLAYLISTS, androidx.media3.common.MediaMetadata.FOLDER_TYPE_YEARS}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface MediaMetadata.FolderType {
}
@IntDef({androidx.media3.common.MediaMetadata.MEDIA_TYPE_MIXED, androidx.media3.common.MediaMetadata.MEDIA_TYPE_MUSIC, androidx.media3.common.MediaMetadata.MEDIA_TYPE_AUDIO_BOOK_CHAPTER, androidx.media3.common.MediaMetadata.MEDIA_TYPE_PODCAST_EPISODE, androidx.media3.common.MediaMetadata.MEDIA_TYPE_RADIO_STATION, androidx.media3.common.MediaMetadata.MEDIA_TYPE_NEWS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_VIDEO, androidx.media3.common.MediaMetadata.MEDIA_TYPE_TRAILER, androidx.media3.common.MediaMetadata.MEDIA_TYPE_MOVIE, androidx.media3.common.MediaMetadata.MEDIA_TYPE_TV_SHOW, androidx.media3.common.MediaMetadata.MEDIA_TYPE_ALBUM, androidx.media3.common.MediaMetadata.MEDIA_TYPE_ARTIST, androidx.media3.common.MediaMetadata.MEDIA_TYPE_GENRE, androidx.media3.common.MediaMetadata.MEDIA_TYPE_PLAYLIST, androidx.media3.common.MediaMetadata.MEDIA_TYPE_YEAR, androidx.media3.common.MediaMetadata.MEDIA_TYPE_AUDIO_BOOK, androidx.media3.common.MediaMetadata.MEDIA_TYPE_PODCAST, androidx.media3.common.MediaMetadata.MEDIA_TYPE_TV_CHANNEL, androidx.media3.common.MediaMetadata.MEDIA_TYPE_TV_SERIES, androidx.media3.common.MediaMetadata.MEDIA_TYPE_TV_SEASON, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_MIXED, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_ALBUMS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_ARTISTS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_GENRES, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_PLAYLISTS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_YEARS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_AUDIO_BOOKS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_PODCASTS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_TV_CHANNELS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_TV_SERIES, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_TV_SHOWS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_RADIO_STATIONS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_NEWS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_VIDEOS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_TRAILERS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_MOVIES}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE_USE) public static @interface MediaMetadata.MediaType {
}
@IntDef({androidx.media3.common.MediaMetadata.PICTURE_TYPE_OTHER, androidx.media3.common.MediaMetadata.PICTURE_TYPE_FILE_ICON, androidx.media3.common.MediaMetadata.PICTURE_TYPE_FILE_ICON_OTHER, androidx.media3.common.MediaMetadata.PICTURE_TYPE_FRONT_COVER, androidx.media3.common.MediaMetadata.PICTURE_TYPE_BACK_COVER, androidx.media3.common.MediaMetadata.PICTURE_TYPE_LEAFLET_PAGE, androidx.media3.common.MediaMetadata.PICTURE_TYPE_MEDIA, androidx.media3.common.MediaMetadata.PICTURE_TYPE_LEAD_ARTIST_PERFORMER, androidx.media3.common.MediaMetadata.PICTURE_TYPE_ARTIST_PERFORMER, androidx.media3.common.MediaMetadata.PICTURE_TYPE_CONDUCTOR, androidx.media3.common.MediaMetadata.PICTURE_TYPE_BAND_ORCHESTRA, androidx.media3.common.MediaMetadata.PICTURE_TYPE_COMPOSER, androidx.media3.common.MediaMetadata.PICTURE_TYPE_LYRICIST, androidx.media3.common.MediaMetadata.PICTURE_TYPE_RECORDING_LOCATION, androidx.media3.common.MediaMetadata.PICTURE_TYPE_DURING_RECORDING, androidx.media3.common.MediaMetadata.PICTURE_TYPE_DURING_PERFORMANCE, androidx.media3.common.MediaMetadata.PICTURE_TYPE_MOVIE_VIDEO_SCREEN_CAPTURE, androidx.media3.common.MediaMetadata.PICTURE_TYPE_A_BRIGHT_COLORED_FISH, androidx.media3.common.MediaMetadata.PICTURE_TYPE_ILLUSTRATION, androidx.media3.common.MediaMetadata.PICTURE_TYPE_BAND_ARTIST_LOGO, androidx.media3.common.MediaMetadata.PICTURE_TYPE_PUBLISHER_STUDIO_LOGO}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface MediaMetadata.PictureType {
@ -486,8 +547,9 @@ package androidx.media3.common {
field public static final String APPLICATION_MP4VTT = "application/x-mp4-vtt";
field public static final String APPLICATION_MPD = "application/dash+xml";
field public static final String APPLICATION_PGS = "application/pgs";
field public static final String APPLICATION_RAWCC = "application/x-rawcc";
field @Deprecated public static final String APPLICATION_RAWCC = "application/x-rawcc";
field public static final String APPLICATION_RTSP = "application/x-rtsp";
field public static final String APPLICATION_SDP = "application/sdp";
field public static final String APPLICATION_SS = "application/vnd.ms-sstr+xml";
field public static final String APPLICATION_SUBRIP = "application/x-subrip";
field public static final String APPLICATION_TTML = "application/ttml+xml";
@ -557,17 +619,24 @@ package androidx.media3.common {
public class PlaybackException extends java.lang.Exception {
method @CallSuper public boolean errorInfoEquals(@Nullable androidx.media3.common.PlaybackException);
method public static String getErrorCodeName(@androidx.media3.common.PlaybackException.ErrorCode int);
method public final String getErrorCodeName();
method public static String getErrorCodeName(@androidx.media3.common.PlaybackException.ErrorCode int);
field public static final int CUSTOM_ERROR_CODE_BASE = 1000000; // 0xf4240
field public static final int ERROR_CODE_AUDIO_TRACK_INIT_FAILED = 5001; // 0x1389
field public static final int ERROR_CODE_AUDIO_TRACK_OFFLOAD_INIT_FAILED = 5004; // 0x138c
field public static final int ERROR_CODE_AUDIO_TRACK_OFFLOAD_WRITE_FAILED = 5003; // 0x138b
field public static final int ERROR_CODE_AUDIO_TRACK_WRITE_FAILED = 5002; // 0x138a
field public static final int ERROR_CODE_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
field public static final int ERROR_CODE_BAD_VALUE = -3; // 0xfffffffd
field public static final int ERROR_CODE_BEHIND_LIVE_WINDOW = 1002; // 0x3ea
field public static final int ERROR_CODE_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
field public static final int ERROR_CODE_CONTENT_ALREADY_PLAYING = -110; // 0xffffff92
field public static final int ERROR_CODE_DECODER_INIT_FAILED = 4001; // 0xfa1
field public static final int ERROR_CODE_DECODER_QUERY_FAILED = 4002; // 0xfa2
field public static final int ERROR_CODE_DECODING_FAILED = 4003; // 0xfa3
field public static final int ERROR_CODE_DECODING_FORMAT_EXCEEDS_CAPABILITIES = 4004; // 0xfa4
field public static final int ERROR_CODE_DECODING_FORMAT_UNSUPPORTED = 4005; // 0xfa5
field public static final int ERROR_CODE_DISCONNECTED = -100; // 0xffffff9c
field public static final int ERROR_CODE_DRM_CONTENT_ERROR = 6003; // 0x1773
field public static final int ERROR_CODE_DRM_DEVICE_REVOKED = 6007; // 0x1777
field public static final int ERROR_CODE_DRM_DISALLOWED_OPERATION = 6005; // 0x1775
@ -577,7 +646,9 @@ package androidx.media3.common {
field public static final int ERROR_CODE_DRM_SCHEME_UNSUPPORTED = 6001; // 0x1771
field public static final int ERROR_CODE_DRM_SYSTEM_ERROR = 6006; // 0x1776
field public static final int ERROR_CODE_DRM_UNSPECIFIED = 6000; // 0x1770
field public static final int ERROR_CODE_END_OF_PLAYLIST = -109; // 0xffffff93
field public static final int ERROR_CODE_FAILED_RUNTIME_CHECK = 1004; // 0x3ec
field public static final int ERROR_CODE_INVALID_STATE = -2; // 0xfffffffe
field public static final int ERROR_CODE_IO_BAD_HTTP_STATUS = 2004; // 0x7d4
field public static final int ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED = 2007; // 0x7d7
field public static final int ERROR_CODE_IO_FILE_NOT_FOUND = 2005; // 0x7d5
@ -587,22 +658,29 @@ package androidx.media3.common {
field public static final int ERROR_CODE_IO_NO_PERMISSION = 2006; // 0x7d6
field public static final int ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE = 2008; // 0x7d8
field public static final int ERROR_CODE_IO_UNSPECIFIED = 2000; // 0x7d0
field public static final int ERROR_CODE_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
field public static final int ERROR_CODE_NOT_SUPPORTED = -6; // 0xfffffffa
field public static final int ERROR_CODE_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
field public static final int ERROR_CODE_PARSING_CONTAINER_MALFORMED = 3001; // 0xbb9
field public static final int ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED = 3003; // 0xbbb
field public static final int ERROR_CODE_PARSING_MANIFEST_MALFORMED = 3002; // 0xbba
field public static final int ERROR_CODE_PARSING_MANIFEST_UNSUPPORTED = 3004; // 0xbbc
field public static final int ERROR_CODE_PERMISSION_DENIED = -4; // 0xfffffffc
field public static final int ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
field public static final int ERROR_CODE_REMOTE_ERROR = 1001; // 0x3e9
field public static final int ERROR_CODE_SETUP_REQUIRED = -108; // 0xffffff94
field public static final int ERROR_CODE_SKIP_LIMIT_REACHED = -107; // 0xffffff95
field public static final int ERROR_CODE_TIMEOUT = 1003; // 0x3eb
field public static final int ERROR_CODE_UNSPECIFIED = 1000; // 0x3e8
field @androidx.media3.common.PlaybackException.ErrorCode public final int errorCode;
field public final long timestampMs;
}
@IntDef(open=true, value={androidx.media3.common.PlaybackException.ERROR_CODE_UNSPECIFIED, androidx.media3.common.PlaybackException.ERROR_CODE_REMOTE_ERROR, androidx.media3.common.PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW, androidx.media3.common.PlaybackException.ERROR_CODE_TIMEOUT, androidx.media3.common.PlaybackException.ERROR_CODE_FAILED_RUNTIME_CHECK, androidx.media3.common.PlaybackException.ERROR_CODE_IO_UNSPECIFIED, androidx.media3.common.PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT, androidx.media3.common.PlaybackException.ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE, androidx.media3.common.PlaybackException.ERROR_CODE_IO_BAD_HTTP_STATUS, androidx.media3.common.PlaybackException.ERROR_CODE_IO_FILE_NOT_FOUND, androidx.media3.common.PlaybackException.ERROR_CODE_IO_NO_PERMISSION, androidx.media3.common.PlaybackException.ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED, androidx.media3.common.PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE, androidx.media3.common.PlaybackException.ERROR_CODE_PARSING_CONTAINER_MALFORMED, androidx.media3.common.PlaybackException.ERROR_CODE_PARSING_MANIFEST_MALFORMED, androidx.media3.common.PlaybackException.ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED, androidx.media3.common.PlaybackException.ERROR_CODE_PARSING_MANIFEST_UNSUPPORTED, androidx.media3.common.PlaybackException.ERROR_CODE_DECODER_INIT_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DECODER_QUERY_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DECODING_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DECODING_FORMAT_EXCEEDS_CAPABILITIES, androidx.media3.common.PlaybackException.ERROR_CODE_DECODING_FORMAT_UNSUPPORTED, androidx.media3.common.PlaybackException.ERROR_CODE_AUDIO_TRACK_INIT_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_AUDIO_TRACK_WRITE_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_UNSPECIFIED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_SCHEME_UNSUPPORTED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_PROVISIONING_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_CONTENT_ERROR, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_LICENSE_ACQUISITION_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_DISALLOWED_OPERATION, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_SYSTEM_ERROR, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_DEVICE_REVOKED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_LICENSE_EXPIRED}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface PlaybackException.ErrorCode {
@IntDef(open=true, value={androidx.media3.common.PlaybackException.ERROR_CODE_INVALID_STATE, androidx.media3.common.PlaybackException.ERROR_CODE_BAD_VALUE, androidx.media3.common.PlaybackException.ERROR_CODE_PERMISSION_DENIED, androidx.media3.common.PlaybackException.ERROR_CODE_NOT_SUPPORTED, androidx.media3.common.PlaybackException.ERROR_CODE_DISCONNECTED, androidx.media3.common.PlaybackException.ERROR_CODE_AUTHENTICATION_EXPIRED, androidx.media3.common.PlaybackException.ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED, androidx.media3.common.PlaybackException.ERROR_CODE_CONCURRENT_STREAM_LIMIT, androidx.media3.common.PlaybackException.ERROR_CODE_PARENTAL_CONTROL_RESTRICTED, androidx.media3.common.PlaybackException.ERROR_CODE_NOT_AVAILABLE_IN_REGION, androidx.media3.common.PlaybackException.ERROR_CODE_SKIP_LIMIT_REACHED, androidx.media3.common.PlaybackException.ERROR_CODE_SETUP_REQUIRED, androidx.media3.common.PlaybackException.ERROR_CODE_END_OF_PLAYLIST, androidx.media3.common.PlaybackException.ERROR_CODE_CONTENT_ALREADY_PLAYING, androidx.media3.common.PlaybackException.ERROR_CODE_UNSPECIFIED, androidx.media3.common.PlaybackException.ERROR_CODE_REMOTE_ERROR, androidx.media3.common.PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW, androidx.media3.common.PlaybackException.ERROR_CODE_TIMEOUT, androidx.media3.common.PlaybackException.ERROR_CODE_FAILED_RUNTIME_CHECK, androidx.media3.common.PlaybackException.ERROR_CODE_IO_UNSPECIFIED, androidx.media3.common.PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT, androidx.media3.common.PlaybackException.ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE, androidx.media3.common.PlaybackException.ERROR_CODE_IO_BAD_HTTP_STATUS, androidx.media3.common.PlaybackException.ERROR_CODE_IO_FILE_NOT_FOUND, androidx.media3.common.PlaybackException.ERROR_CODE_IO_NO_PERMISSION, androidx.media3.common.PlaybackException.ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED, androidx.media3.common.PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE, androidx.media3.common.PlaybackException.ERROR_CODE_PARSING_CONTAINER_MALFORMED, androidx.media3.common.PlaybackException.ERROR_CODE_PARSING_MANIFEST_MALFORMED, androidx.media3.common.PlaybackException.ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED, androidx.media3.common.PlaybackException.ERROR_CODE_PARSING_MANIFEST_UNSUPPORTED, androidx.media3.common.PlaybackException.ERROR_CODE_DECODER_INIT_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DECODER_QUERY_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DECODING_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DECODING_FORMAT_EXCEEDS_CAPABILITIES, androidx.media3.common.PlaybackException.ERROR_CODE_DECODING_FORMAT_UNSUPPORTED, androidx.media3.common.PlaybackException.ERROR_CODE_AUDIO_TRACK_INIT_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_AUDIO_TRACK_WRITE_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_AUDIO_TRACK_OFFLOAD_INIT_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_AUDIO_TRACK_OFFLOAD_WRITE_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_UNSPECIFIED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_SCHEME_UNSUPPORTED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_PROVISIONING_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_CONTENT_ERROR, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_LICENSE_ACQUISITION_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_DISALLOWED_OPERATION, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_SYSTEM_ERROR, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_DEVICE_REVOKED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_LICENSE_EXPIRED}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface PlaybackException.ErrorCode {
}
public final class PlaybackParameters {
ctor public PlaybackParameters(float);
ctor public PlaybackParameters(@FloatRange(from=0, fromInclusive=false) float);
ctor public PlaybackParameters(@FloatRange(from=0, fromInclusive=false) float, @FloatRange(from=0, fromInclusive=false) float);
method @CheckResult public androidx.media3.common.PlaybackParameters withSpeed(@FloatRange(from=0, fromInclusive=false) float);
field public static final androidx.media3.common.PlaybackParameters DEFAULT;
@ -614,8 +692,8 @@ package androidx.media3.common {
method public void addListener(androidx.media3.common.Player.Listener);
method public void addMediaItem(androidx.media3.common.MediaItem);
method public void addMediaItem(int, androidx.media3.common.MediaItem);
method public void addMediaItems(java.util.List<androidx.media3.common.MediaItem>);
method public void addMediaItems(int, java.util.List<androidx.media3.common.MediaItem>);
method public void addMediaItems(java.util.List<androidx.media3.common.MediaItem>);
method public boolean canAdvertiseSession();
method public void clearMediaItems();
method public void clearVideoSurface();
@ -623,7 +701,8 @@ package androidx.media3.common {
method public void clearVideoSurfaceHolder(@Nullable android.view.SurfaceHolder);
method public void clearVideoSurfaceView(@Nullable android.view.SurfaceView);
method public void clearVideoTextureView(@Nullable android.view.TextureView);
method public void decreaseDeviceVolume();
method @Deprecated public void decreaseDeviceVolume();
method public void decreaseDeviceVolume(@androidx.media3.common.C.VolumeFlags int);
method public android.os.Looper getApplicationLooper();
method public androidx.media3.common.AudioAttributes getAudioAttributes();
method public androidx.media3.common.Player.Commands getAvailableCommands();
@ -667,7 +746,8 @@ package androidx.media3.common {
method @FloatRange(from=0, to=1.0) public float getVolume();
method public boolean hasNextMediaItem();
method public boolean hasPreviousMediaItem();
method public void increaseDeviceVolume();
method @Deprecated public void increaseDeviceVolume();
method public void increaseDeviceVolume(@androidx.media3.common.C.VolumeFlags int);
method public boolean isCommandAvailable(@androidx.media3.common.Player.Command int);
method public boolean isCurrentMediaItemDynamic();
method public boolean isCurrentMediaItemLive();
@ -685,21 +765,26 @@ package androidx.media3.common {
method public void removeListener(androidx.media3.common.Player.Listener);
method public void removeMediaItem(int);
method public void removeMediaItems(int, int);
method public void replaceMediaItem(int, androidx.media3.common.MediaItem);
method public void replaceMediaItems(int, int, java.util.List<androidx.media3.common.MediaItem>);
method public void seekBack();
method public void seekForward();
method public void seekTo(long);
method public void seekTo(int, long);
method public void seekTo(long);
method public void seekToDefaultPosition();
method public void seekToDefaultPosition(int);
method public void seekToNext();
method public void seekToNextMediaItem();
method public void seekToPrevious();
method public void seekToPreviousMediaItem();
method public void setDeviceMuted(boolean);
method public void setDeviceVolume(@IntRange(from=0) int);
method public void setAudioAttributes(androidx.media3.common.AudioAttributes, boolean);
method @Deprecated public void setDeviceMuted(boolean);
method public void setDeviceMuted(boolean, @androidx.media3.common.C.VolumeFlags int);
method @Deprecated public void setDeviceVolume(@IntRange(from=0) int);
method public void setDeviceVolume(@IntRange(from=0) int, @androidx.media3.common.C.VolumeFlags int);
method public void setMediaItem(androidx.media3.common.MediaItem);
method public void setMediaItem(androidx.media3.common.MediaItem, long);
method public void setMediaItem(androidx.media3.common.MediaItem, boolean);
method public void setMediaItem(androidx.media3.common.MediaItem, long);
method public void setMediaItems(java.util.List<androidx.media3.common.MediaItem>);
method public void setMediaItems(java.util.List<androidx.media3.common.MediaItem>, boolean);
method public void setMediaItems(java.util.List<androidx.media3.common.MediaItem>, int, long);
@ -716,12 +801,14 @@ package androidx.media3.common {
method public void setVideoTextureView(@Nullable android.view.TextureView);
method public void setVolume(@FloatRange(from=0, to=1.0) float);
method public void stop();
field public static final int COMMAND_ADJUST_DEVICE_VOLUME = 26; // 0x1a
field @Deprecated public static final int COMMAND_ADJUST_DEVICE_VOLUME = 26; // 0x1a
field public static final int COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS = 34; // 0x22
field public static final int COMMAND_CHANGE_MEDIA_ITEMS = 20; // 0x14
field public static final int COMMAND_GET_AUDIO_ATTRIBUTES = 21; // 0x15
field public static final int COMMAND_GET_CURRENT_MEDIA_ITEM = 16; // 0x10
field public static final int COMMAND_GET_DEVICE_VOLUME = 23; // 0x17
field public static final int COMMAND_GET_MEDIA_ITEMS_METADATA = 18; // 0x12
field @Deprecated public static final int COMMAND_GET_MEDIA_ITEMS_METADATA = 18; // 0x12
field public static final int COMMAND_GET_METADATA = 18; // 0x12
field public static final int COMMAND_GET_TEXT = 28; // 0x1c
field public static final int COMMAND_GET_TIMELINE = 17; // 0x11
field public static final int COMMAND_GET_TRACKS = 30; // 0x1e
@ -729,6 +816,7 @@ package androidx.media3.common {
field public static final int COMMAND_INVALID = -1; // 0xffffffff
field public static final int COMMAND_PLAY_PAUSE = 1; // 0x1
field public static final int COMMAND_PREPARE = 2; // 0x2
field public static final int COMMAND_RELEASE = 32; // 0x20
field public static final int COMMAND_SEEK_BACK = 11; // 0xb
field public static final int COMMAND_SEEK_FORWARD = 12; // 0xc
field public static final int COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM = 5; // 0x5
@ -738,9 +826,12 @@ package androidx.media3.common {
field public static final int COMMAND_SEEK_TO_NEXT_MEDIA_ITEM = 8; // 0x8
field public static final int COMMAND_SEEK_TO_PREVIOUS = 7; // 0x7
field public static final int COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM = 6; // 0x6
field public static final int COMMAND_SET_DEVICE_VOLUME = 25; // 0x19
field public static final int COMMAND_SET_AUDIO_ATTRIBUTES = 35; // 0x23
field @Deprecated public static final int COMMAND_SET_DEVICE_VOLUME = 25; // 0x19
field public static final int COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS = 33; // 0x21
field public static final int COMMAND_SET_MEDIA_ITEM = 31; // 0x1f
field public static final int COMMAND_SET_MEDIA_ITEMS_METADATA = 19; // 0x13
field @Deprecated public static final int COMMAND_SET_MEDIA_ITEMS_METADATA = 19; // 0x13
field public static final int COMMAND_SET_PLAYLIST_METADATA = 19; // 0x13
field public static final int COMMAND_SET_REPEAT_MODE = 15; // 0xf
field public static final int COMMAND_SET_SHUFFLE_MODE = 14; // 0xe
field public static final int COMMAND_SET_SPEED_AND_PITCH = 13; // 0xd
@ -753,6 +844,7 @@ package androidx.media3.common {
field public static final int DISCONTINUITY_REASON_REMOVE = 4; // 0x4
field public static final int DISCONTINUITY_REASON_SEEK = 1; // 0x1
field public static final int DISCONTINUITY_REASON_SEEK_ADJUSTMENT = 2; // 0x2
field public static final int DISCONTINUITY_REASON_SILENCE_SKIP = 6; // 0x6
field public static final int DISCONTINUITY_REASON_SKIP = 3; // 0x3
field public static final int EVENT_AUDIO_ATTRIBUTES_CHANGED = 20; // 0x14
field public static final int EVENT_AUDIO_SESSION_ID = 21; // 0x15
@ -791,10 +883,13 @@ package androidx.media3.common {
field public static final int MEDIA_ITEM_TRANSITION_REASON_SEEK = 2; // 0x2
field public static final int PLAYBACK_SUPPRESSION_REASON_NONE = 0; // 0x0
field public static final int PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS = 1; // 0x1
field public static final int PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT = 3; // 0x3
field @Deprecated public static final int PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_ROUTE = 2; // 0x2
field public static final int PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY = 3; // 0x3
field public static final int PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS = 2; // 0x2
field public static final int PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM = 5; // 0x5
field public static final int PLAY_WHEN_READY_CHANGE_REASON_REMOTE = 4; // 0x4
field public static final int PLAY_WHEN_READY_CHANGE_REASON_SUPPRESSED_TOO_LONG = 6; // 0x6
field public static final int PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST = 1; // 0x1
field public static final int REPEAT_MODE_ALL = 2; // 0x2
field public static final int REPEAT_MODE_OFF = 0; // 0x0
@ -807,7 +902,7 @@ package androidx.media3.common {
field public static final int TIMELINE_CHANGE_REASON_SOURCE_UPDATE = 1; // 0x1
}
@IntDef({androidx.media3.common.Player.COMMAND_INVALID, androidx.media3.common.Player.COMMAND_PLAY_PAUSE, androidx.media3.common.Player.COMMAND_PREPARE, androidx.media3.common.Player.COMMAND_STOP, androidx.media3.common.Player.COMMAND_SEEK_TO_DEFAULT_POSITION, androidx.media3.common.Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS, androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT, androidx.media3.common.Player.COMMAND_SEEK_TO_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_SEEK_BACK, androidx.media3.common.Player.COMMAND_SEEK_FORWARD, androidx.media3.common.Player.COMMAND_SET_SPEED_AND_PITCH, androidx.media3.common.Player.COMMAND_SET_SHUFFLE_MODE, androidx.media3.common.Player.COMMAND_SET_REPEAT_MODE, androidx.media3.common.Player.COMMAND_GET_CURRENT_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_GET_TIMELINE, androidx.media3.common.Player.COMMAND_GET_MEDIA_ITEMS_METADATA, androidx.media3.common.Player.COMMAND_SET_MEDIA_ITEMS_METADATA, androidx.media3.common.Player.COMMAND_SET_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_CHANGE_MEDIA_ITEMS, androidx.media3.common.Player.COMMAND_GET_AUDIO_ATTRIBUTES, androidx.media3.common.Player.COMMAND_GET_VOLUME, androidx.media3.common.Player.COMMAND_GET_DEVICE_VOLUME, androidx.media3.common.Player.COMMAND_SET_VOLUME, androidx.media3.common.Player.COMMAND_SET_DEVICE_VOLUME, androidx.media3.common.Player.COMMAND_ADJUST_DEVICE_VOLUME, androidx.media3.common.Player.COMMAND_SET_VIDEO_SURFACE, androidx.media3.common.Player.COMMAND_GET_TEXT, androidx.media3.common.Player.COMMAND_SET_TRACK_SELECTION_PARAMETERS, androidx.media3.common.Player.COMMAND_GET_TRACKS}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface Player.Command {
@IntDef({androidx.media3.common.Player.COMMAND_INVALID, androidx.media3.common.Player.COMMAND_PLAY_PAUSE, androidx.media3.common.Player.COMMAND_PREPARE, androidx.media3.common.Player.COMMAND_STOP, androidx.media3.common.Player.COMMAND_SEEK_TO_DEFAULT_POSITION, androidx.media3.common.Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS, androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT, androidx.media3.common.Player.COMMAND_SEEK_TO_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_SEEK_BACK, androidx.media3.common.Player.COMMAND_SEEK_FORWARD, androidx.media3.common.Player.COMMAND_SET_SPEED_AND_PITCH, androidx.media3.common.Player.COMMAND_SET_SHUFFLE_MODE, androidx.media3.common.Player.COMMAND_SET_REPEAT_MODE, androidx.media3.common.Player.COMMAND_GET_CURRENT_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_GET_TIMELINE, androidx.media3.common.Player.COMMAND_GET_MEDIA_ITEMS_METADATA, androidx.media3.common.Player.COMMAND_GET_METADATA, androidx.media3.common.Player.COMMAND_SET_MEDIA_ITEMS_METADATA, androidx.media3.common.Player.COMMAND_SET_PLAYLIST_METADATA, androidx.media3.common.Player.COMMAND_SET_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_CHANGE_MEDIA_ITEMS, androidx.media3.common.Player.COMMAND_GET_AUDIO_ATTRIBUTES, androidx.media3.common.Player.COMMAND_GET_VOLUME, androidx.media3.common.Player.COMMAND_GET_DEVICE_VOLUME, androidx.media3.common.Player.COMMAND_SET_VOLUME, androidx.media3.common.Player.COMMAND_SET_DEVICE_VOLUME, androidx.media3.common.Player.COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS, androidx.media3.common.Player.COMMAND_ADJUST_DEVICE_VOLUME, androidx.media3.common.Player.COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS, androidx.media3.common.Player.COMMAND_SET_AUDIO_ATTRIBUTES, androidx.media3.common.Player.COMMAND_SET_VIDEO_SURFACE, androidx.media3.common.Player.COMMAND_GET_TEXT, androidx.media3.common.Player.COMMAND_SET_TRACK_SELECTION_PARAMETERS, androidx.media3.common.Player.COMMAND_GET_TRACKS, androidx.media3.common.Player.COMMAND_RELEASE}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface Player.Command {
}
public static final class Player.Commands {
@ -818,7 +913,7 @@ package androidx.media3.common {
field public static final androidx.media3.common.Player.Commands EMPTY;
}
@IntDef({androidx.media3.common.Player.DISCONTINUITY_REASON_AUTO_TRANSITION, androidx.media3.common.Player.DISCONTINUITY_REASON_SEEK, androidx.media3.common.Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT, androidx.media3.common.Player.DISCONTINUITY_REASON_SKIP, androidx.media3.common.Player.DISCONTINUITY_REASON_REMOVE, androidx.media3.common.Player.DISCONTINUITY_REASON_INTERNAL}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface Player.DiscontinuityReason {
@IntDef({androidx.media3.common.Player.DISCONTINUITY_REASON_AUTO_TRANSITION, androidx.media3.common.Player.DISCONTINUITY_REASON_SEEK, androidx.media3.common.Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT, androidx.media3.common.Player.DISCONTINUITY_REASON_SKIP, androidx.media3.common.Player.DISCONTINUITY_REASON_REMOVE, androidx.media3.common.Player.DISCONTINUITY_REASON_INTERNAL, androidx.media3.common.Player.DISCONTINUITY_REASON_SILENCE_SKIP}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface Player.DiscontinuityReason {
}
@IntDef({androidx.media3.common.Player.EVENT_TIMELINE_CHANGED, androidx.media3.common.Player.EVENT_MEDIA_ITEM_TRANSITION, androidx.media3.common.Player.EVENT_TRACKS_CHANGED, androidx.media3.common.Player.EVENT_IS_LOADING_CHANGED, androidx.media3.common.Player.EVENT_PLAYBACK_STATE_CHANGED, androidx.media3.common.Player.EVENT_PLAY_WHEN_READY_CHANGED, androidx.media3.common.Player.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED, androidx.media3.common.Player.EVENT_IS_PLAYING_CHANGED, androidx.media3.common.Player.EVENT_REPEAT_MODE_CHANGED, androidx.media3.common.Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED, androidx.media3.common.Player.EVENT_PLAYER_ERROR, androidx.media3.common.Player.EVENT_POSITION_DISCONTINUITY, androidx.media3.common.Player.EVENT_PLAYBACK_PARAMETERS_CHANGED, androidx.media3.common.Player.EVENT_AVAILABLE_COMMANDS_CHANGED, androidx.media3.common.Player.EVENT_MEDIA_METADATA_CHANGED, androidx.media3.common.Player.EVENT_PLAYLIST_METADATA_CHANGED, androidx.media3.common.Player.EVENT_SEEK_BACK_INCREMENT_CHANGED, androidx.media3.common.Player.EVENT_SEEK_FORWARD_INCREMENT_CHANGED, androidx.media3.common.Player.EVENT_MAX_SEEK_TO_PREVIOUS_POSITION_CHANGED, androidx.media3.common.Player.EVENT_TRACK_SELECTION_PARAMETERS_CHANGED, androidx.media3.common.Player.EVENT_AUDIO_ATTRIBUTES_CHANGED, androidx.media3.common.Player.EVENT_AUDIO_SESSION_ID, androidx.media3.common.Player.EVENT_VOLUME_CHANGED, androidx.media3.common.Player.EVENT_SKIP_SILENCE_ENABLED_CHANGED, androidx.media3.common.Player.EVENT_SURFACE_SIZE_CHANGED, androidx.media3.common.Player.EVENT_VIDEO_SIZE_CHANGED, androidx.media3.common.Player.EVENT_RENDERED_FIRST_FRAME, androidx.media3.common.Player.EVENT_CUES, androidx.media3.common.Player.EVENT_METADATA, androidx.media3.common.Player.EVENT_DEVICE_INFO_CHANGED, androidx.media3.common.Player.EVENT_DEVICE_VOLUME_CHANGED}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface Player.Event {
@ -868,10 +963,10 @@ package androidx.media3.common {
@IntDef({androidx.media3.common.Player.MEDIA_ITEM_TRANSITION_REASON_REPEAT, androidx.media3.common.Player.MEDIA_ITEM_TRANSITION_REASON_AUTO, androidx.media3.common.Player.MEDIA_ITEM_TRANSITION_REASON_SEEK, androidx.media3.common.Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface Player.MediaItemTransitionReason {
}
@IntDef({androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS, androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY, androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_REMOTE, androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface Player.PlayWhenReadyChangeReason {
@IntDef({androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS, androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY, androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_REMOTE, androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM, androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_SUPPRESSED_TOO_LONG}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface Player.PlayWhenReadyChangeReason {
}
@IntDef({androidx.media3.common.Player.PLAYBACK_SUPPRESSION_REASON_NONE, androidx.media3.common.Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface Player.PlaybackSuppressionReason {
@IntDef({androidx.media3.common.Player.PLAYBACK_SUPPRESSION_REASON_NONE, androidx.media3.common.Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS, androidx.media3.common.Player.PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_ROUTE, androidx.media3.common.Player.PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface Player.PlaybackSuppressionReason {
}
public static final class Player.PositionInfo {
@ -999,7 +1094,7 @@ package androidx.media3.common {
method public androidx.media3.common.TrackSelectionParameters.Builder buildUpon();
method public static androidx.media3.common.TrackSelectionParameters fromBundle(android.os.Bundle);
method public static androidx.media3.common.TrackSelectionParameters getDefaults(android.content.Context);
method public android.os.Bundle toBundle();
method @CallSuper public android.os.Bundle toBundle();
field public final com.google.common.collect.ImmutableSet<java.lang.Integer> disabledTrackTypes;
field public final boolean forceHighestSupportedBitrate;
field public final boolean forceLowestBitrate;
@ -1097,7 +1192,7 @@ package androidx.media3.common {
field public static final androidx.media3.common.VideoSize UNKNOWN;
field @IntRange(from=0) public final int height;
field @FloatRange(from=0, fromInclusive=false) public final float pixelWidthHeightRatio;
field @IntRange(from=0, to=359) public final int unappliedRotationDegrees;
field @Deprecated @IntRange(from=0, to=359) public final int unappliedRotationDegrees;
field @IntRange(from=0) public final int width;
}
@ -1110,7 +1205,7 @@ package androidx.media3.common.text {
field public static final int ANCHOR_TYPE_MIDDLE = 1; // 0x1
field public static final int ANCHOR_TYPE_START = 0; // 0x0
field public static final float DIMEN_UNSET = -3.4028235E38f;
field public static final androidx.media3.common.text.Cue EMPTY;
field @Deprecated public static final androidx.media3.common.text.Cue EMPTY;
field public static final int LINE_TYPE_FRACTION = 0; // 0x0
field public static final int LINE_TYPE_NUMBER = 1; // 0x1
field public static final int TEXT_SIZE_TYPE_ABSOLUTE = 2; // 0x2
@ -1162,11 +1257,16 @@ package androidx.media3.common.util {
method public static boolean checkCleartextTrafficPermitted(androidx.media3.common.MediaItem...);
method @Nullable public static String getAdaptiveMimeTypeForContentType(@androidx.media3.common.C.ContentType int);
method @Nullable public static java.util.UUID getDrmUuid(String);
method public static boolean handlePauseButtonAction(@Nullable androidx.media3.common.Player);
method public static boolean handlePlayButtonAction(@Nullable androidx.media3.common.Player);
method public static boolean handlePlayPauseButtonAction(@Nullable androidx.media3.common.Player);
method @androidx.media3.common.C.ContentType public static int inferContentType(android.net.Uri);
method @androidx.media3.common.C.ContentType public static int inferContentTypeForExtension(String);
method @androidx.media3.common.C.ContentType public static int inferContentTypeForUriAndMimeType(android.net.Uri, @Nullable String);
method public static boolean maybeRequestReadExternalStoragePermission(android.app.Activity, android.net.Uri...);
method public static boolean maybeRequestReadExternalStoragePermission(android.app.Activity, androidx.media3.common.MediaItem...);
method @Deprecated public static boolean maybeRequestReadExternalStoragePermission(android.app.Activity, android.net.Uri...);
method @Deprecated public static boolean maybeRequestReadExternalStoragePermission(android.app.Activity, androidx.media3.common.MediaItem...);
method public static boolean maybeRequestReadStoragePermission(android.app.Activity, androidx.media3.common.MediaItem...);
method @org.checkerframework.checker.nullness.qual.EnsuresNonNullIf(result=false, expression="#1") public static boolean shouldShowPlayButton(@Nullable androidx.media3.common.Player);
}
}
@ -1264,7 +1364,6 @@ package androidx.media3.exoplayer {
method public void addAnalyticsListener(androidx.media3.exoplayer.analytics.AnalyticsListener);
method @Nullable public androidx.media3.exoplayer.ExoPlaybackException getPlayerError();
method public void removeAnalyticsListener(androidx.media3.exoplayer.analytics.AnalyticsListener);
method public void setAudioAttributes(androidx.media3.common.AudioAttributes, boolean);
method public void setHandleAudioBecomingNoisy(boolean);
method public void setWakeMode(@androidx.media3.common.C.WakeMode int);
}
@ -1289,7 +1388,7 @@ package androidx.media3.exoplayer.analytics {
package androidx.media3.exoplayer.drm {
@RequiresApi(18) public final class FrameworkMediaDrm {
public final class FrameworkMediaDrm {
method public static boolean isCryptoSchemeSupported(java.util.UUID);
}
@ -1307,6 +1406,29 @@ package androidx.media3.exoplayer.ima {
method public androidx.media3.exoplayer.ima.ImaAdsLoader build();
}
public final class ImaServerSideAdInsertionMediaSource implements androidx.media3.exoplayer.source.MediaSource {
}
public static final class ImaServerSideAdInsertionMediaSource.AdsLoader {
method public androidx.media3.exoplayer.ima.ImaServerSideAdInsertionMediaSource.AdsLoader.State release();
method public void setPlayer(androidx.media3.common.Player);
}
public static final class ImaServerSideAdInsertionMediaSource.AdsLoader.Builder {
ctor public ImaServerSideAdInsertionMediaSource.AdsLoader.Builder(android.content.Context, androidx.media3.common.AdViewProvider);
method public androidx.media3.exoplayer.ima.ImaServerSideAdInsertionMediaSource.AdsLoader build();
method public androidx.media3.exoplayer.ima.ImaServerSideAdInsertionMediaSource.AdsLoader.Builder setAdsLoaderState(androidx.media3.exoplayer.ima.ImaServerSideAdInsertionMediaSource.AdsLoader.State);
}
public static class ImaServerSideAdInsertionMediaSource.AdsLoader.State {
method public static androidx.media3.exoplayer.ima.ImaServerSideAdInsertionMediaSource.AdsLoader.State fromBundle(android.os.Bundle);
method public android.os.Bundle toBundle();
}
public static final class ImaServerSideAdInsertionMediaSource.Factory implements androidx.media3.exoplayer.source.MediaSource.Factory {
ctor public ImaServerSideAdInsertionMediaSource.Factory(androidx.media3.exoplayer.ima.ImaServerSideAdInsertionMediaSource.AdsLoader, androidx.media3.exoplayer.source.MediaSource.Factory);
}
}
package androidx.media3.exoplayer.source {
@ -1316,6 +1438,7 @@ package androidx.media3.exoplayer.source {
method public androidx.media3.exoplayer.source.DefaultMediaSourceFactory clearLocalAdInsertionComponents();
method public androidx.media3.exoplayer.source.DefaultMediaSourceFactory setDataSourceFactory(androidx.media3.datasource.DataSource.Factory);
method public androidx.media3.exoplayer.source.DefaultMediaSourceFactory setLocalAdInsertionComponents(androidx.media3.exoplayer.source.ads.AdsLoader.Provider, androidx.media3.common.AdViewProvider);
method public androidx.media3.exoplayer.source.DefaultMediaSourceFactory setServerSideAdInsertionMediaSourceFactory(@Nullable androidx.media3.exoplayer.source.MediaSource.Factory);
}
public interface MediaSource {
@ -1404,7 +1527,7 @@ package androidx.media3.session {
field @Nullable public final V value;
}
@IntDef({androidx.media3.session.LibraryResult.RESULT_SUCCESS, androidx.media3.session.LibraryResult.RESULT_ERROR_UNKNOWN, androidx.media3.session.LibraryResult.RESULT_ERROR_INVALID_STATE, androidx.media3.session.LibraryResult.RESULT_ERROR_BAD_VALUE, androidx.media3.session.LibraryResult.RESULT_ERROR_PERMISSION_DENIED, androidx.media3.session.LibraryResult.RESULT_ERROR_IO, androidx.media3.session.LibraryResult.RESULT_INFO_SKIPPED, androidx.media3.session.LibraryResult.RESULT_ERROR_SESSION_DISCONNECTED, androidx.media3.session.LibraryResult.RESULT_ERROR_NOT_SUPPORTED, androidx.media3.session.LibraryResult.RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED, androidx.media3.session.LibraryResult.RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED, androidx.media3.session.LibraryResult.RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT, androidx.media3.session.LibraryResult.RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED, androidx.media3.session.LibraryResult.RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION, androidx.media3.session.LibraryResult.RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED, androidx.media3.session.LibraryResult.RESULT_ERROR_SESSION_SETUP_REQUIRED}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE_USE) public static @interface LibraryResult.Code {
@IntDef({androidx.media3.session.LibraryResult.RESULT_SUCCESS, androidx.media3.session.SessionError.INFO_CANCELLED, androidx.media3.session.SessionError.ERROR_UNKNOWN, androidx.media3.session.SessionError.ERROR_INVALID_STATE, androidx.media3.session.SessionError.ERROR_BAD_VALUE, androidx.media3.session.SessionError.ERROR_PERMISSION_DENIED, androidx.media3.session.SessionError.ERROR_IO, androidx.media3.session.SessionError.ERROR_SESSION_DISCONNECTED, androidx.media3.session.SessionError.ERROR_NOT_SUPPORTED, androidx.media3.session.SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED, androidx.media3.session.SessionError.ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED, androidx.media3.session.SessionError.ERROR_SESSION_CONCURRENT_STREAM_LIMIT, androidx.media3.session.SessionError.ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED, androidx.media3.session.SessionError.ERROR_SESSION_NOT_AVAILABLE_IN_REGION, androidx.media3.session.SessionError.ERROR_SESSION_SKIP_LIMIT_REACHED, androidx.media3.session.SessionError.ERROR_SESSION_SETUP_REQUIRED}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE_USE) public static @interface LibraryResult.Code {
}
public final class MediaBrowser extends androidx.media3.session.MediaController {
@ -1438,122 +1561,129 @@ package androidx.media3.session {
field public static final String EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_PREV = "android.media.playback.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_PREVIOUS";
}
public class MediaController implements androidx.media3.common.Player {
method public void addListener(androidx.media3.common.Player.Listener);
method public void addMediaItem(androidx.media3.common.MediaItem);
method public void addMediaItem(int, androidx.media3.common.MediaItem);
method public void addMediaItems(java.util.List<androidx.media3.common.MediaItem>);
method public void addMediaItems(int, java.util.List<androidx.media3.common.MediaItem>);
method public boolean canAdvertiseSession();
method public void clearMediaItems();
method public void clearVideoSurface();
method public void clearVideoSurface(@Nullable android.view.Surface);
method public void clearVideoSurfaceHolder(@Nullable android.view.SurfaceHolder);
method public void clearVideoSurfaceView(@Nullable android.view.SurfaceView);
method public void clearVideoTextureView(@Nullable android.view.TextureView);
method public void decreaseDeviceVolume();
method public android.os.Looper getApplicationLooper();
method public androidx.media3.common.AudioAttributes getAudioAttributes();
method public androidx.media3.common.Player.Commands getAvailableCommands();
method public androidx.media3.session.SessionCommands getAvailableSessionCommands();
method @IntRange(from=0, to=100) public int getBufferedPercentage();
method public long getBufferedPosition();
method @Nullable public androidx.media3.session.SessionToken getConnectedToken();
method public long getContentBufferedPosition();
method public long getContentDuration();
method public long getContentPosition();
method public int getCurrentAdGroupIndex();
method public int getCurrentAdIndexInAdGroup();
method public androidx.media3.common.text.CueGroup getCurrentCues();
method public long getCurrentLiveOffset();
method @Nullable public androidx.media3.common.MediaItem getCurrentMediaItem();
method public int getCurrentMediaItemIndex();
method public int getCurrentPeriodIndex();
method public long getCurrentPosition();
method public androidx.media3.common.Timeline getCurrentTimeline();
method public androidx.media3.common.Tracks getCurrentTracks();
method public androidx.media3.common.DeviceInfo getDeviceInfo();
method @IntRange(from=0) public int getDeviceVolume();
method public long getDuration();
method public long getMaxSeekToPreviousPosition();
method public androidx.media3.common.MediaItem getMediaItemAt(int);
method public int getMediaItemCount();
method public androidx.media3.common.MediaMetadata getMediaMetadata();
method public int getNextMediaItemIndex();
method public boolean getPlayWhenReady();
method public androidx.media3.common.PlaybackParameters getPlaybackParameters();
method @androidx.media3.common.Player.State public int getPlaybackState();
method @androidx.media3.common.Player.PlaybackSuppressionReason public int getPlaybackSuppressionReason();
method @Nullable public androidx.media3.common.PlaybackException getPlayerError();
method public androidx.media3.common.MediaMetadata getPlaylistMetadata();
method public int getPreviousMediaItemIndex();
method @androidx.media3.common.Player.RepeatMode public int getRepeatMode();
method public long getSeekBackIncrement();
method public long getSeekForwardIncrement();
method @Nullable public android.app.PendingIntent getSessionActivity();
method public boolean getShuffleModeEnabled();
method public long getTotalBufferedDuration();
method public androidx.media3.common.TrackSelectionParameters getTrackSelectionParameters();
method public androidx.media3.common.VideoSize getVideoSize();
method @FloatRange(from=0, to=1) public float getVolume();
method public boolean hasNextMediaItem();
method public boolean hasPreviousMediaItem();
method public void increaseDeviceVolume();
method public boolean isCommandAvailable(@androidx.media3.common.Player.Command int);
method public boolean isConnected();
method public boolean isCurrentMediaItemDynamic();
method public boolean isCurrentMediaItemLive();
method public boolean isCurrentMediaItemSeekable();
method public boolean isDeviceMuted();
method public boolean isLoading();
method public boolean isPlaying();
method public boolean isPlayingAd();
method public boolean isSessionCommandAvailable(@androidx.media3.session.SessionCommand.CommandCode int);
method public boolean isSessionCommandAvailable(androidx.media3.session.SessionCommand);
method public void moveMediaItem(int, int);
method public void moveMediaItems(int, int, int);
method public void pause();
method public void play();
method public void prepare();
method public void release();
@com.google.errorprone.annotations.DoNotMock public class MediaController implements androidx.media3.common.Player {
method public final void addListener(androidx.media3.common.Player.Listener);
method public final void addMediaItem(androidx.media3.common.MediaItem);
method public final void addMediaItem(int, androidx.media3.common.MediaItem);
method public final void addMediaItems(int, java.util.List<androidx.media3.common.MediaItem>);
method public final void addMediaItems(java.util.List<androidx.media3.common.MediaItem>);
method public final boolean canAdvertiseSession();
method public final void clearMediaItems();
method public final void clearVideoSurface();
method public final void clearVideoSurface(@Nullable android.view.Surface);
method public final void clearVideoSurfaceHolder(@Nullable android.view.SurfaceHolder);
method public final void clearVideoSurfaceView(@Nullable android.view.SurfaceView);
method public final void clearVideoTextureView(@Nullable android.view.TextureView);
method @Deprecated public final void decreaseDeviceVolume();
method public final void decreaseDeviceVolume(@androidx.media3.common.C.VolumeFlags int);
method public final android.os.Looper getApplicationLooper();
method public final androidx.media3.common.AudioAttributes getAudioAttributes();
method public final androidx.media3.common.Player.Commands getAvailableCommands();
method public final androidx.media3.session.SessionCommands getAvailableSessionCommands();
method @IntRange(from=0, to=100) public final int getBufferedPercentage();
method public final long getBufferedPosition();
method @Nullable public final androidx.media3.session.SessionToken getConnectedToken();
method public final long getContentBufferedPosition();
method public final long getContentDuration();
method public final long getContentPosition();
method public final int getCurrentAdGroupIndex();
method public final int getCurrentAdIndexInAdGroup();
method public final androidx.media3.common.text.CueGroup getCurrentCues();
method public final long getCurrentLiveOffset();
method @Nullable public final androidx.media3.common.MediaItem getCurrentMediaItem();
method public final int getCurrentMediaItemIndex();
method public final int getCurrentPeriodIndex();
method public final long getCurrentPosition();
method public final androidx.media3.common.Timeline getCurrentTimeline();
method public final androidx.media3.common.Tracks getCurrentTracks();
method public final androidx.media3.common.DeviceInfo getDeviceInfo();
method @IntRange(from=0) public final int getDeviceVolume();
method public final long getDuration();
method public final long getMaxSeekToPreviousPosition();
method public final androidx.media3.common.MediaItem getMediaItemAt(int);
method public final int getMediaItemCount();
method public final androidx.media3.common.MediaMetadata getMediaMetadata();
method public final int getNextMediaItemIndex();
method public final boolean getPlayWhenReady();
method public final androidx.media3.common.PlaybackParameters getPlaybackParameters();
method @androidx.media3.common.Player.State public final int getPlaybackState();
method @androidx.media3.common.Player.PlaybackSuppressionReason public final int getPlaybackSuppressionReason();
method @Nullable public final androidx.media3.common.PlaybackException getPlayerError();
method public final androidx.media3.common.MediaMetadata getPlaylistMetadata();
method public final int getPreviousMediaItemIndex();
method @androidx.media3.common.Player.RepeatMode public final int getRepeatMode();
method public final long getSeekBackIncrement();
method public final long getSeekForwardIncrement();
method @Nullable public final android.app.PendingIntent getSessionActivity();
method public final boolean getShuffleModeEnabled();
method public final long getTotalBufferedDuration();
method public final androidx.media3.common.TrackSelectionParameters getTrackSelectionParameters();
method public final androidx.media3.common.VideoSize getVideoSize();
method @FloatRange(from=0, to=1) public final float getVolume();
method public final boolean hasNextMediaItem();
method public final boolean hasPreviousMediaItem();
method @Deprecated public final void increaseDeviceVolume();
method public final void increaseDeviceVolume(@androidx.media3.common.C.VolumeFlags int);
method public final boolean isCommandAvailable(@androidx.media3.common.Player.Command int);
method public final boolean isConnected();
method public final boolean isCurrentMediaItemDynamic();
method public final boolean isCurrentMediaItemLive();
method public final boolean isCurrentMediaItemSeekable();
method public final boolean isDeviceMuted();
method public final boolean isLoading();
method public final boolean isPlaying();
method public final boolean isPlayingAd();
method public final boolean isSessionCommandAvailable(androidx.media3.session.SessionCommand);
method public final boolean isSessionCommandAvailable(@androidx.media3.session.SessionCommand.CommandCode int);
method public final void moveMediaItem(int, int);
method public final void moveMediaItems(int, int, int);
method public final void pause();
method public final void play();
method public final void prepare();
method public final void release();
method public static void releaseFuture(java.util.concurrent.Future<? extends androidx.media3.session.MediaController>);
method public void removeListener(androidx.media3.common.Player.Listener);
method public void removeMediaItem(int);
method public void removeMediaItems(int, int);
method public void seekBack();
method public void seekForward();
method public void seekTo(long);
method public void seekTo(int, long);
method public void seekToDefaultPosition();
method public void seekToDefaultPosition(int);
method public void seekToNext();
method public void seekToNextMediaItem();
method public void seekToPrevious();
method public void seekToPreviousMediaItem();
method public com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionResult> sendCustomCommand(androidx.media3.session.SessionCommand, android.os.Bundle);
method public void setDeviceMuted(boolean);
method public void setDeviceVolume(@IntRange(from=0) int);
method public void setMediaItem(androidx.media3.common.MediaItem);
method public void setMediaItem(androidx.media3.common.MediaItem, long);
method public void setMediaItem(androidx.media3.common.MediaItem, boolean);
method public void setMediaItems(java.util.List<androidx.media3.common.MediaItem>);
method public void setMediaItems(java.util.List<androidx.media3.common.MediaItem>, boolean);
method public void setMediaItems(java.util.List<androidx.media3.common.MediaItem>, int, long);
method public void setPlayWhenReady(boolean);
method public void setPlaybackParameters(androidx.media3.common.PlaybackParameters);
method public void setPlaybackSpeed(float);
method public void setPlaylistMetadata(androidx.media3.common.MediaMetadata);
method public com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionResult> setRating(String, androidx.media3.common.Rating);
method public com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionResult> setRating(androidx.media3.common.Rating);
method public void setRepeatMode(@androidx.media3.common.Player.RepeatMode int);
method public void setShuffleModeEnabled(boolean);
method public void setTrackSelectionParameters(androidx.media3.common.TrackSelectionParameters);
method public void setVideoSurface(@Nullable android.view.Surface);
method public void setVideoSurfaceHolder(@Nullable android.view.SurfaceHolder);
method public void setVideoSurfaceView(@Nullable android.view.SurfaceView);
method public void setVideoTextureView(@Nullable android.view.TextureView);
method public void setVolume(@FloatRange(from=0, to=1) float);
method public void stop();
method public final void removeListener(androidx.media3.common.Player.Listener);
method public final void removeMediaItem(int);
method public final void removeMediaItems(int, int);
method public final void replaceMediaItem(int, androidx.media3.common.MediaItem);
method public final void replaceMediaItems(int, int, java.util.List<androidx.media3.common.MediaItem>);
method public final void seekBack();
method public final void seekForward();
method public final void seekTo(int, long);
method public final void seekTo(long);
method public final void seekToDefaultPosition();
method public final void seekToDefaultPosition(int);
method public final void seekToNext();
method public final void seekToNextMediaItem();
method public final void seekToPrevious();
method public final void seekToPreviousMediaItem();
method public final com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionResult> sendCustomCommand(androidx.media3.session.SessionCommand, android.os.Bundle);
method public final void setAudioAttributes(androidx.media3.common.AudioAttributes, boolean);
method @Deprecated public final void setDeviceMuted(boolean);
method public final void setDeviceMuted(boolean, @androidx.media3.common.C.VolumeFlags int);
method @Deprecated public final void setDeviceVolume(@IntRange(from=0) int);
method public final void setDeviceVolume(@IntRange(from=0) int, @androidx.media3.common.C.VolumeFlags int);
method public final void setMediaItem(androidx.media3.common.MediaItem);
method public final void setMediaItem(androidx.media3.common.MediaItem, boolean);
method public final void setMediaItem(androidx.media3.common.MediaItem, long);
method public final void setMediaItems(java.util.List<androidx.media3.common.MediaItem>);
method public final void setMediaItems(java.util.List<androidx.media3.common.MediaItem>, boolean);
method public final void setMediaItems(java.util.List<androidx.media3.common.MediaItem>, int, long);
method public final void setPlayWhenReady(boolean);
method public final void setPlaybackParameters(androidx.media3.common.PlaybackParameters);
method public final void setPlaybackSpeed(float);
method public final void setPlaylistMetadata(androidx.media3.common.MediaMetadata);
method public final com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionResult> setRating(androidx.media3.common.Rating);
method public final com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionResult> setRating(String, androidx.media3.common.Rating);
method public final void setRepeatMode(@androidx.media3.common.Player.RepeatMode int);
method public final void setShuffleModeEnabled(boolean);
method public final void setTrackSelectionParameters(androidx.media3.common.TrackSelectionParameters);
method public final void setVideoSurface(@Nullable android.view.Surface);
method public final void setVideoSurfaceHolder(@Nullable android.view.SurfaceHolder);
method public final void setVideoSurfaceView(@Nullable android.view.SurfaceView);
method public final void setVideoTextureView(@Nullable android.view.TextureView);
method public final void setVolume(@FloatRange(from=0, to=1) float);
method public final void stop();
}
public static final class MediaController.Builder {
@ -1622,21 +1752,22 @@ package androidx.media3.session {
field @IntRange(from=1) public final int notificationId;
}
public class MediaSession {
method public void broadcastCustomCommand(androidx.media3.session.SessionCommand, android.os.Bundle);
method public java.util.List<androidx.media3.session.MediaSession.ControllerInfo> getConnectedControllers();
method public String getId();
method public androidx.media3.common.Player getPlayer();
method @Nullable public android.app.PendingIntent getSessionActivity();
method public androidx.media3.session.SessionToken getToken();
method public void release();
method public com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionResult> sendCustomCommand(androidx.media3.session.MediaSession.ControllerInfo, androidx.media3.session.SessionCommand, android.os.Bundle);
method public void setAvailableCommands(androidx.media3.session.MediaSession.ControllerInfo, androidx.media3.session.SessionCommands, androidx.media3.common.Player.Commands);
method public com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionResult> setCustomLayout(androidx.media3.session.MediaSession.ControllerInfo, java.util.List<androidx.media3.session.CommandButton>);
method public void setCustomLayout(java.util.List<androidx.media3.session.CommandButton>);
method public void setPlayer(androidx.media3.common.Player);
method public void setSessionExtras(android.os.Bundle);
method public void setSessionExtras(androidx.media3.session.MediaSession.ControllerInfo, android.os.Bundle);
@com.google.errorprone.annotations.DoNotMock public class MediaSession {
method public final void broadcastCustomCommand(androidx.media3.session.SessionCommand, android.os.Bundle);
method public final java.util.List<androidx.media3.session.MediaSession.ControllerInfo> getConnectedControllers();
method @Nullable public final androidx.media3.session.MediaSession.ControllerInfo getControllerForCurrentRequest();
method public final String getId();
method public final androidx.media3.common.Player getPlayer();
method @Nullable public final android.app.PendingIntent getSessionActivity();
method public final androidx.media3.session.SessionToken getToken();
method public final void release();
method public final com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionResult> sendCustomCommand(androidx.media3.session.MediaSession.ControllerInfo, androidx.media3.session.SessionCommand, android.os.Bundle);
method public final void setAvailableCommands(androidx.media3.session.MediaSession.ControllerInfo, androidx.media3.session.SessionCommands, androidx.media3.common.Player.Commands);
method public final com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionResult> setCustomLayout(androidx.media3.session.MediaSession.ControllerInfo, java.util.List<androidx.media3.session.CommandButton>);
method public final void setCustomLayout(java.util.List<androidx.media3.session.CommandButton>);
method public final void setPlayer(androidx.media3.common.Player);
method public final void setSessionExtras(android.os.Bundle);
method public final void setSessionExtras(androidx.media3.session.MediaSession.ControllerInfo, android.os.Bundle);
}
public static final class MediaSession.Builder {
@ -1653,10 +1784,10 @@ package androidx.media3.session {
method public default androidx.media3.session.MediaSession.ConnectionResult onConnect(androidx.media3.session.MediaSession, androidx.media3.session.MediaSession.ControllerInfo);
method public default com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionResult> onCustomCommand(androidx.media3.session.MediaSession, androidx.media3.session.MediaSession.ControllerInfo, androidx.media3.session.SessionCommand, android.os.Bundle);
method public default void onDisconnected(androidx.media3.session.MediaSession, androidx.media3.session.MediaSession.ControllerInfo);
method @androidx.media3.session.SessionResult.Code public default int onPlayerCommandRequest(androidx.media3.session.MediaSession, androidx.media3.session.MediaSession.ControllerInfo, @androidx.media3.common.Player.Command int);
method @Deprecated @androidx.media3.session.SessionResult.Code public default int onPlayerCommandRequest(androidx.media3.session.MediaSession, androidx.media3.session.MediaSession.ControllerInfo, @androidx.media3.common.Player.Command int);
method public default void onPostConnect(androidx.media3.session.MediaSession, androidx.media3.session.MediaSession.ControllerInfo);
method public default com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionResult> onSetRating(androidx.media3.session.MediaSession, androidx.media3.session.MediaSession.ControllerInfo, String, androidx.media3.common.Rating);
method public default com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionResult> onSetRating(androidx.media3.session.MediaSession, androidx.media3.session.MediaSession.ControllerInfo, androidx.media3.common.Rating);
method public default com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionResult> onSetRating(androidx.media3.session.MediaSession, androidx.media3.session.MediaSession.ControllerInfo, String, androidx.media3.common.Rating);
}
public static final class MediaSession.ConnectionResult {
@ -1672,6 +1803,7 @@ package androidx.media3.session {
method public int getControllerVersion();
method public String getPackageName();
method public int getUid();
field public static final String LEGACY_CONTROLLER_PACKAGE_NAME = "android.media.session.MediaController";
field public static final int LEGACY_CONTROLLER_VERSION = 0; // 0x0
}
@ -1682,7 +1814,8 @@ package androidx.media3.session {
method public final boolean isSessionAdded(androidx.media3.session.MediaSession);
method @CallSuper @Nullable public android.os.IBinder onBind(@Nullable android.content.Intent);
method @Nullable public abstract androidx.media3.session.MediaSession onGetSession(androidx.media3.session.MediaSession.ControllerInfo);
method public void onUpdateNotification(androidx.media3.session.MediaSession);
method @Deprecated public void onUpdateNotification(androidx.media3.session.MediaSession);
method public void onUpdateNotification(androidx.media3.session.MediaSession, boolean);
method public final void removeSession(androidx.media3.session.MediaSession);
field public static final String SERVICE_INTERFACE = "androidx.media3.session.MediaSessionService";
}
@ -1719,6 +1852,7 @@ package androidx.media3.session {
ctor public SessionCommands.Builder();
method public androidx.media3.session.SessionCommands.Builder add(androidx.media3.session.SessionCommand);
method public androidx.media3.session.SessionCommands.Builder add(@androidx.media3.session.SessionCommand.CommandCode int);
method public androidx.media3.session.SessionCommands.Builder addSessionCommands(java.util.Collection<androidx.media3.session.SessionCommand>);
method public androidx.media3.session.SessionCommands build();
method public androidx.media3.session.SessionCommands.Builder remove(androidx.media3.session.SessionCommand);
method public androidx.media3.session.SessionCommands.Builder remove(@androidx.media3.session.SessionCommand.CommandCode int);
@ -1748,7 +1882,7 @@ package androidx.media3.session {
field @androidx.media3.session.SessionResult.Code public final int resultCode;
}
@IntDef({androidx.media3.session.SessionResult.RESULT_SUCCESS, androidx.media3.session.SessionResult.RESULT_ERROR_UNKNOWN, androidx.media3.session.SessionResult.RESULT_ERROR_INVALID_STATE, androidx.media3.session.SessionResult.RESULT_ERROR_BAD_VALUE, androidx.media3.session.SessionResult.RESULT_ERROR_PERMISSION_DENIED, androidx.media3.session.SessionResult.RESULT_ERROR_IO, androidx.media3.session.SessionResult.RESULT_INFO_SKIPPED, androidx.media3.session.SessionResult.RESULT_ERROR_SESSION_DISCONNECTED, androidx.media3.session.SessionResult.RESULT_ERROR_NOT_SUPPORTED, androidx.media3.session.SessionResult.RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED, androidx.media3.session.SessionResult.RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED, androidx.media3.session.SessionResult.RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT, androidx.media3.session.SessionResult.RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED, androidx.media3.session.SessionResult.RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION, androidx.media3.session.SessionResult.RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED, androidx.media3.session.SessionResult.RESULT_ERROR_SESSION_SETUP_REQUIRED}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE_USE) public static @interface SessionResult.Code {
@IntDef({androidx.media3.session.SessionResult.RESULT_SUCCESS, androidx.media3.session.SessionError.INFO_CANCELLED, androidx.media3.session.SessionError.ERROR_UNKNOWN, androidx.media3.session.SessionError.ERROR_INVALID_STATE, androidx.media3.session.SessionError.ERROR_BAD_VALUE, androidx.media3.session.SessionError.ERROR_PERMISSION_DENIED, androidx.media3.session.SessionError.ERROR_IO, androidx.media3.session.SessionError.ERROR_SESSION_DISCONNECTED, androidx.media3.session.SessionError.ERROR_NOT_SUPPORTED, androidx.media3.session.SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED, androidx.media3.session.SessionError.ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED, androidx.media3.session.SessionError.ERROR_SESSION_CONCURRENT_STREAM_LIMIT, androidx.media3.session.SessionError.ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED, androidx.media3.session.SessionError.ERROR_SESSION_NOT_AVAILABLE_IN_REGION, androidx.media3.session.SessionError.ERROR_SESSION_SKIP_LIMIT_REACHED, androidx.media3.session.SessionError.ERROR_SESSION_SETUP_REQUIRED}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE_USE) public static @interface SessionResult.Code {
}
public final class SessionToken {

View File

@ -17,15 +17,21 @@ buildscript {
mavenCentral()
}
dependencies {
classpath 'com.android.tools.build:gradle:7.2.1'
classpath 'com.google.android.gms:strict-version-matcher-plugin:1.2.2'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.21'
classpath 'com.android.tools.build:gradle:8.3.2'
classpath 'com.google.android.gms:strict-version-matcher-plugin:1.2.4'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.10'
}
}
allprojects {
repositories {
google()
mavenCentral()
maven {
url 'https://jitpack.io'
content {
includeGroup "com.github.philburk"
}
}
}
if (it.hasProperty('externalBuildDir')) {
if (!new File(externalBuildDir).isAbsolute()) {
@ -35,5 +41,3 @@ allprojects {
}
group = 'androidx.media3'
}
apply from: 'javadoc_combined.gradle'

View File

@ -14,6 +14,8 @@
apply from: "$gradle.ext.androidxMediaSettingsDir/constants.gradle"
apply plugin: 'com.android.library'
group = 'androidx.media3'
android {
compileSdkVersion project.ext.compileSdkVersion

View File

@ -12,50 +12,52 @@
// See the License for the specific language governing permissions and
// limitations under the License.
project.ext {
releaseVersion = '1.0.2'
releaseVersionCode = 1_000_002_3_00
minSdkVersion = 16
appTargetSdkVersion = 33
// API version before restricting local file access.
// https://developer.android.com/training/data-storage/app-specific
mainDemoAppTargetSdkVersion = 29
releaseVersion = '1.5.0-alpha01'
releaseVersionCode = 1_005_000_0_01
minSdkVersion = 21
// See https://developer.android.com/training/cars/media/automotive-os#automotive-module
automotiveMinSdkVersion = 28
appTargetSdkVersion = 34
// Upgrading this requires [Internal ref: b/193254928] to be fixed, or some
// additional robolectric config.
targetSdkVersion = 30
compileSdkVersion = 33
compileSdkVersion = 35
dexmakerVersion = '2.28.3'
// Use the same JUnit version as the Android repo:
// https://cs.android.com/android/platform/superproject/main/+/main:external/junit/METADATA
junitVersion = '4.13.2'
// Use the same Guava version as the Android repo:
// https://cs.android.com/android/platform/superproject/+/master:external/guava/METADATA
guavaVersion = '31.0.1-android'
// https://cs.android.com/android/platform/superproject/main/+/main:external/guava/METADATA
guavaVersion = '33.0.0-android'
glideVersion = '4.14.2'
kotlinxCoroutinesVersion = '1.8.1'
leakCanaryVersion = '2.10'
mockitoVersion = '3.12.4'
robolectricVersion = '4.8.1'
robolectricVersion = '4.11'
// Keep this in sync with Google's internal Checker Framework version.
checkerframeworkVersion = '3.13.0'
checkerframeworkCompatVersion = '2.5.5'
errorProneVersion = '2.10.0'
errorProneVersion = '2.18.0'
jsr305Version = '3.0.2'
kotlinAnnotationsVersion = '1.5.31'
androidxAnnotationVersion = '1.3.0'
androidxAnnotationExperimentalVersion = '1.2.0'
androidxAppCompatVersion = '1.3.1'
androidxCollectionVersion = '1.1.0'
androidxConstraintLayoutVersion = '2.0.4'
androidxCoreVersion = '1.7.0'
androidxFuturesVersion = '1.1.0'
androidxMediaVersion = '1.6.0'
androidxMedia2Version = '1.2.0'
androidxMultidexVersion = '2.0.1'
androidxRecyclerViewVersion = '1.2.1'
androidxMaterialVersion = '1.4.0'
androidxTestCoreVersion = '1.4.0'
androidxTestJUnitVersion = '1.1.3'
androidxTestRunnerVersion = '1.4.0'
androidxTestRulesVersion = '1.4.0'
androidxTestServicesStorageVersion = '1.4.0'
androidxTestTruthVersion = '1.4.0'
truthVersion = '1.1.3'
okhttpVersion = '4.9.2'
kotlinAnnotationsVersion = '1.9.0'
androidxAnnotationVersion = '1.6.0'
androidxAnnotationExperimentalVersion = '1.3.1'
androidxAppCompatVersion = '1.6.1'
androidxCollectionVersion = '1.2.0'
androidxConstraintLayoutVersion = '2.1.4'
androidxCoreVersion = '1.8.0'
androidxExifInterfaceVersion = '1.3.6'
androidxLifecycleVersion = '2.6.0'
androidxMediaVersion = '1.7.0'
androidxRecyclerViewVersion = '1.3.0'
androidxMaterialVersion = '1.8.0'
androidxTestCoreVersion = '1.5.0'
androidxTestEspressoVersion = '3.5.1'
androidxTestJUnitVersion = '1.1.5'
androidxTestRunnerVersion = '1.5.2'
androidxTestRulesVersion = '1.5.0'
androidxTestTruthVersion = '1.5.0'
truthVersion = '1.4.0'
okhttpVersion = '4.12.0'
modulePrefix = ':'
if (gradle.ext.has('androidxMediaModulePrefix')) {
modulePrefix += gradle.ext.androidxMediaModulePrefix

View File

@ -21,11 +21,15 @@ if (gradle.ext.has('androidxMediaModulePrefix')) {
modulePrefix += gradle.ext.androidxMediaModulePrefix
}
rootProject.name = gradle.ext.androidxMediaProjectName
include modulePrefix + 'lib-common'
project(modulePrefix + 'lib-common').projectDir = new File(rootDir, 'libraries/common')
include modulePrefix + 'lib-common-ktx'
project(modulePrefix + 'lib-common-ktx').projectDir = new File(rootDir, 'libraries/common_ktx')
include modulePrefix + 'lib-container'
project(modulePrefix + 'lib-container').projectDir = new File(rootDir, 'libraries/container')
include modulePrefix + 'lib-session'
project(modulePrefix + 'lib-session').projectDir = new File(rootDir, 'libraries/session')
@ -56,6 +60,8 @@ include modulePrefix + 'lib-datasource'
project(modulePrefix + 'lib-datasource').projectDir = new File(rootDir, 'libraries/datasource')
include modulePrefix + 'lib-datasource-cronet'
project(modulePrefix + 'lib-datasource-cronet').projectDir = new File(rootDir, 'libraries/datasource_cronet')
include modulePrefix + 'lib-datasource-httpengine'
project(modulePrefix + 'lib-datasource-httpengine').projectDir = new File(rootDir, 'libraries/datasource_httpengine')
include modulePrefix + 'lib-datasource-rtmp'
project(modulePrefix + 'lib-datasource-rtmp').projectDir = new File(rootDir, 'libraries/datasource_rtmp')
include modulePrefix + 'lib-datasource-okhttp'
@ -69,6 +75,12 @@ include modulePrefix + 'lib-decoder-ffmpeg'
project(modulePrefix + 'lib-decoder-ffmpeg').projectDir = new File(rootDir, 'libraries/decoder_ffmpeg')
include modulePrefix + 'lib-decoder-flac'
project(modulePrefix + 'lib-decoder-flac').projectDir = new File(rootDir, 'libraries/decoder_flac')
include modulePrefix + 'lib-decoder-iamf'
project(modulePrefix + 'lib-decoder-iamf').projectDir = new File(rootDir, 'libraries/decoder_iamf')
if (gradle.ext.has('androidxMediaEnableMidiModule') && gradle.ext.androidxMediaEnableMidiModule) {
include modulePrefix + 'lib-decoder-midi'
project(modulePrefix + 'lib-decoder-midi').projectDir = new File(rootDir, 'libraries/decoder_midi')
}
include modulePrefix + 'lib-decoder-opus'
project(modulePrefix + 'lib-decoder-opus').projectDir = new File(rootDir, 'libraries/decoder_opus')
include modulePrefix + 'lib-decoder-vp9'
@ -83,6 +95,9 @@ project(modulePrefix + 'lib-cast').projectDir = new File(rootDir, 'libraries/cas
include modulePrefix + 'lib-effect'
project(modulePrefix + 'lib-effect').projectDir = new File(rootDir, 'libraries/effect')
include modulePrefix + 'lib-muxer'
project(modulePrefix + 'lib-muxer').projectDir = new File(rootDir, 'libraries/muxer')
include modulePrefix + 'lib-transformer'
project(modulePrefix + 'lib-transformer').projectDir = new File(rootDir, 'libraries/transformer')
@ -92,7 +107,3 @@ include modulePrefix + 'test-data'
project(modulePrefix + 'test-data').projectDir = new File(rootDir, 'libraries/test_data')
include modulePrefix + 'test-utils'
project(modulePrefix + 'test-utils').projectDir = new File(rootDir, 'libraries/test_utils')
include modulePrefix + 'test-session-common'
project(modulePrefix + 'test-session-common').projectDir = new File(rootDir, 'libraries/test_session_common')
include modulePrefix + 'test-session-current'
project(modulePrefix + 'test-session-current').projectDir = new File(rootDir, 'libraries/test_session_current')

View File

@ -1,7 +1,116 @@
# Cast demo
This app demonstrates integration with Google Cast, as well as switching between
Google Cast and local playback using ExoPlayer.
This app demonstrates switching between Google Cast and local playback by using
`CastPlayer` and `ExoPlayer`.
## Building the demo app
See the [demos README](../README.md) for instructions on how to build and run
this demo.
Test your streams by adding a `MediaItem` with URI and mime type to the
`DemoUtil` and deploy the app on a real device for casting.
## Customization with `OptionsProvider`
The Cast SDK behaviour in the demo app or your own app can be customized by
providing a custom `OptionsProvider` (see
[`DefaultCastOptionsProvider`](https://github.com/androidx/media/blob/release/libraries/cast/src/main/java/androidx/media3/cast/DefaultCastOptionsProvider.java)
also).
Replace the default options provider in the `AndroidManifest.xml` with your own:
```xml
<meta-data
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
android:value="com.example.cast.MyOptionsProvider"/>
```
### Using a different Cast receiver app with the Media3 cast demo sender app
The Media3 cast demo app is an implementation of an
[Android Cast *sender app*](https://developers.google.com/cast/docs/android_sender)
that uses a *default Cast receiver app* (running on the Cast device) that is
customized to support DRM protected streams
[by passing DRM configuration via `MediaInfo`](https://developers.google.com/cast/docs/android_sender/exoplayer).
Hence Widevine DRM credentials can also be populated with a
`MediaItem.DrmConfiguration.Builder` (see the samples in `DemoUtil` marked with
`Widevine`).
If you test your own streams with this demo app, keep in mind that for your
production app you need to
[choose your own receiver app](https://developers.google.com/cast/docs/web_receiver#choose_a_web_receiver)
and have your own receiver app ID.
If you have a receiver app already and want to quickly test whether it works
well together with the `CastPlayer`, then you can configure the demo app to use
your receiver:
```java
public class MyOptionsProvider implements OptionsProvider {
@NonNull
@Override
public CastOptions getCastOptions(Context context) {
return new CastOptions.Builder()
.setReceiverApplicationId(YOUR_RECEIVER_APP_ID)
// other options
.build();
}
}
```
You can also use the plain
[default Cast receiver app](https://developers.google.com/cast/docs/web_receiver#default_media_web_receiver)
by using `CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID`.
#### Converting a Media3 `MediaItem` to a Cast `MediaQueueItem`
This demo app uses the
[`DefaultMediaItemConverter`](https://github.com/androidx/media/blob/release/libraries/cast/src/main/java/androidx/media3/cast/DefaultMediaItemConverter.java)
to convert a Media3 `MediaItem` to a `MediaQueueItem` of the Cast API. Apps that
use a custom receiver app, can use a custom `MediaItemConverter` instance by
passing it into the constructor of `CastPlayer`.
### Media session and notification
This Media3 cast demo app uses the media session and notification support
provided by the Cast SDK. If your app already integrates with a `MediaSession`,
the Cast session can be disabled to avoid duplicate notifications or sessions:
```java
public class MyOptionsProvider implements OptionsProvider {
@NonNull
@Override
public CastOptions getCastOptions(Context context) {
return new CastOptions.Builder()
.setCastMediaOptions(
new CastMediaOptions.Builder()
.setMediaSessionEnabled(false)
.setNotificationOptions(null)
.build())
// other options
.build();
}
}
```
## Supported media formats
Whether a specific stream is supported on a Cast device largely depends on the
receiver app, the media player used by the receiver and the Cast device, rather
then the implementation of the sender that basically only provides media URI and
metadata.
Generally, Google Cast and all Cast Web Receiver applications support the media
facilities and types listed on
[this page](https://developers.google.com/cast/docs/media). If you build a
custom receiver that uses a media player different to the media player of the
Cast receiver SDK, your app may support
[other formats or features](https://github.com/shaka-project/shaka-player) than
listed in the reference above.
The Media3 team can't give support for building a receiver app or investigations
regarding support for certain media formats on a cast devices. Please consult
the Cast documentation around
[building a receiver application](https://developers.google.com/cast/docs/web_receiver)
for further details.

View File

@ -15,7 +15,9 @@ apply from: '../../constants.gradle'
apply plugin: 'com.android.application'
android {
compileSdkVersion project.ext.compileSdkVersion
namespace 'androidx.media3.demo.cast'
compileSdk project.ext.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
@ -27,7 +29,6 @@ android {
versionCode project.ext.releaseVersionCode
minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.appTargetSdkVersion
multiDexEnabled true
}
buildTypes {
@ -60,7 +61,6 @@ dependencies {
implementation project(modulePrefix + 'lib-ui')
implementation project(modulePrefix + 'lib-cast')
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
implementation 'androidx.recyclerview:recyclerview:' + androidxRecyclerViewVersion
implementation 'com.google.android.material:material:' + androidxMaterialVersion
}

View File

@ -23,7 +23,6 @@
<uses-sdk/>
<application
android:name="androidx.multidex.MultiDexApplication"
android:label="@string/application_name"
android:icon="@mipmap/ic_launcher"
android:largeHeap="true"

View File

@ -37,27 +37,90 @@ import java.util.List;
static {
ArrayList<MediaItem> samples = new ArrayList<>();
// Clear content.
// HLS streams.
samples.add(
new MediaItem.Builder()
.setUri("https://storage.googleapis.com/wvmedia/clear/h264/tears/tears.mpd")
.setMediaMetadata(new MediaMetadata.Builder().setTitle("Clear DASH: Tears").build())
.setMimeType(MIME_TYPE_DASH)
.build());
samples.add(
new MediaItem.Builder()
.setUri("https://storage.googleapis.com/shaka-demo-assets/angel-one-hls/hls.m3u8")
.setMediaMetadata(new MediaMetadata.Builder().setTitle("Clear HLS: Angel one").build())
.setUri(
"https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/bipbop_4x3_variant.m3u8")
.setMediaMetadata(
new MediaMetadata.Builder()
.setTitle("HLS (adaptive): Apple 4x3 basic stream (TS/h264/aac)")
.build())
.setMimeType(MIME_TYPE_HLS)
.build());
samples.add(
new MediaItem.Builder()
.setUri(
"https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8")
.setMediaMetadata(
new MediaMetadata.Builder()
.setTitle("HLS (adaptive): Apple 16x9 basic stream (TS/h264/aac)")
.build())
.setMimeType(MIME_TYPE_HLS)
.build());
samples.add(
new MediaItem.Builder()
.setUri(
"https://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/hls/DesigningForGoogleCast.m3u8")
.setMediaMetadata(
new MediaMetadata.Builder()
.setTitle("HLS (1280x720): Designing For Google Cast (TS/h264/aac)")
.build())
.setMimeType(MIME_TYPE_HLS)
.build());
// DASH streams
samples.add(
new MediaItem.Builder()
.setUri("https://storage.googleapis.com/wvmedia/clear/h264/tears/tears.mpd")
.setMediaMetadata(
new MediaMetadata.Builder()
.setTitle("DASH (adaptive): Tears of steal (HD, MP4, H264/aac)")
.build())
.setMimeType(MIME_TYPE_DASH)
.build());
samples.add(
new MediaItem.Builder()
.setUri("https://storage.googleapis.com/wvmedia/clear/h264/tears/tears_uhd.mpd")
.setMediaMetadata(
new MediaMetadata.Builder()
.setTitle("DASH (3840x1714): Tears of steal (MP4, H264/aac)")
.build())
.setMimeType(MIME_TYPE_DASH)
.build());
// Progressive video streams
samples.add(
new MediaItem.Builder()
.setUri("https://html5demos.com/assets/dizzy.mp4")
.setMediaMetadata(new MediaMetadata.Builder().setTitle("Clear MP4: Dizzy").build())
.setMediaMetadata(
new MediaMetadata.Builder().setTitle("MP4 (480x360): Dizzy (H264/aac)").build())
.setMimeType(MIME_TYPE_VIDEO_MP4)
.build());
samples.add(
new MediaItem.Builder()
.setUri(
"https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv")
.setMediaMetadata(
new MediaMetadata.Builder().setTitle("MKV (1280x720): Screens (h264/aac)").build())
.setMimeType(MIME_TYPE_VIDEO_MP4)
.build());
// Progressive audio streams with artwork
samples.add(
new MediaItem.Builder()
.setUri("https://storage.googleapis.com/automotive-media/Keys_To_The_Kingdom.mp3")
.setMediaMetadata(
new MediaMetadata.Builder()
.setTitle("MP3: Keys To The Kingdom (44100/stereo/320kb/s)")
.setArtist("The 126ers")
.setAlbumTitle("Youtube Audio Library Rock 2")
.setGenre("Rock")
.setTrackNumber(1)
.setTotalTrackCount(4)
.setArtworkUri(
Uri.parse(
"https://storage.googleapis.com/automotive-media/album_art_3.jpg"))
.build())
.setMimeType(MimeTypes.AUDIO_MPEG)
.build());
// DRM content.
samples.add(
new MediaItem.Builder()

View File

@ -44,6 +44,7 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder;
import com.google.android.gms.cast.framework.CastButtonFactory;
import com.google.android.gms.cast.framework.CastContext;
import com.google.android.gms.dynamite.DynamiteModule;
import com.google.common.util.concurrent.MoreExecutors;
/**
* An activity that plays video using {@link ExoPlayer} and supports casting using ExoPlayer's Cast
@ -65,7 +66,7 @@ public class MainActivity extends AppCompatActivity
super.onCreate(savedInstanceState);
// Getting the cast context later than onStart can cause device discovery not to take place.
try {
castContext = CastContext.getSharedInstance(this);
castContext = CastContext.getSharedInstance(this, MoreExecutors.directExecutor()).getResult();
} catch (RuntimeException e) {
Throwable cause = e.getCause();
while (cause != null) {

View File

@ -14,6 +14,9 @@
* limitations under the License.
*/
@NonNullApi
@OptIn(markerClass = UnstableApi.class)
package androidx.media3.demo.cast;
import androidx.annotation.OptIn;
import androidx.media3.common.util.NonNullApi;
import androidx.media3.common.util.UnstableApi;

View File

@ -13,8 +13,11 @@
~ See the License for the specific language governing permissions and
~ limitations under the License.
-->
<!-- The NewApi lint can be safely ignored because vector support is available on pre-21 devices
through Android Gradle Plugin 1.4.0+. -->
<vector android:height="400dp" android:tint="#FFFFFF"
android:viewportHeight="24" android:viewportWidth="24"
android:width="400dp" xmlns:android="http://schemas.android.com/apk/res/android">
android:width="400dp" xmlns:android="http://schemas.android.com/apk/res/android"
tools:ignore="NewApi" xmlns:tools="http://schemas.android.com/tools">
<path android:fillColor="@android:color/white" android:pathData="M1,18v3h3c0,-1.66 -1.34,-3 -3,-3zM1,14v2c2.76,0 5,2.24 5,5h2c0,-3.87 -3.13,-7 -7,-7zM19,7L5,7v1.63c3.96,1.28 7.09,4.41 8.37,8.37L19,17L19,7zM1,10v2c4.97,0 9,4.03 9,9h2c0,-6.08 -4.93,-11 -11,-11zM21,3L3,3c-1.1,0 -2,0.9 -2,2v3h2L3,5h18v14h-7v2h7c1.1,0 2,-0.9 2,-2L23,5c0,-1.1 -0.9,-2 -2,-2z"/>
</vector>

View File

@ -13,11 +13,15 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<!-- The NewApi lint can be safely ignored because vector support is available on pre-21 devices
through Android Gradle Plugin 1.4.0+. -->
<vector xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
android:height="24.0dp"
android:viewportHeight="24.0"
android:viewportWidth="24.0"
android:width="24.0dp" >
android:width="24.0dp"
tools:ignore="NewApi" >
<path
android:fillColor="#FFFFFFFF"
android:pathData="M18,13h-5v5c0,0.55 -0.45,1 -1,1h0c-0.55,0 -1,-0.45 -1,-1v-5H6c-0.55,0 -1,-0.45 -1,-1v0c0,-0.55 0.45,-1 1,-1h5V6c0,-0.55 0.45,-1 1,-1h0c0.55,0 1,0.45 1,1v5h5c0.55,0 1,0.45 1,1v0C19,12.55 18.55,13 18,13z"/>

14
demos/compose/README.md Normal file
View File

@ -0,0 +1,14 @@
# ExoPlayer demo with Compose integration
This is an experimental ExoPlayer demo app that is built fully using Compose
features. This should be taken as Work-In-Progress, rather than experimental API
for testing out application development with the media3 and Jetpack Compose
libraries. Please await further announcement via Release Notes for when the
implementation is fully integrated into the library.
For an intermediate solution, use Jetpack Compose Interop with AndroidView and
PlayerView. However, note that it provides limited functionality and some
features may not be supported.
See the [demos README](../README.md) for instructions on how to build and run
this demo.

View File

@ -0,0 +1,87 @@
// Copyright 2024 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
//
// https://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.
apply from: '../../constants.gradle'
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
android {
namespace 'androidx.media3.demo.compose'
compileSdk project.ext.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
defaultConfig {
versionName project.ext.releaseVersion
versionCode project.ext.releaseVersionCode
minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.appTargetSdkVersion
}
buildTypes {
release {
shrinkResources true
minifyEnabled true
signingConfig signingConfigs.debug
}
debug {
jniDebuggable = true
}
}
lintOptions {
// The demo app isn't indexed, and doesn't have translations.
disable 'GoogleAppIndexingWarning','MissingTranslation'
}
buildFeatures {
viewBinding true
compose true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.3"
}
testOptions {
unitTests {
includeAndroidResources = true
}
}
}
dependencies {
def composeBom = platform('androidx.compose:compose-bom:2024.05.00')
implementation composeBom
implementation 'androidx.activity:activity-compose:1.9.0'
implementation 'androidx.compose.foundation:foundation-android:1.6.7'
implementation 'androidx.compose.material3:material3-android:1.2.1'
implementation 'com.google.android.material:material:' + androidxMaterialVersion
implementation project(modulePrefix + 'lib-exoplayer')
// For detecting and debugging leaks only. LeakCanary is not needed for demo app to work.
debugImplementation 'com.squareup.leakcanary:leakcanary-android:' + leakCanaryVersion
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:' + kotlinxCoroutinesVersion
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
testImplementation project(modulePrefix + 'test-utils')
}

View File

@ -0,0 +1,38 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2024 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
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="androidx.media3.demo.compose">
<uses-sdk/>
<uses-permission android:name="android.permission.INTERNET" />
<application
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.Media3ComposeDemo">
<activity
android:name="androidx.media3.demo.compose.MainActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -0,0 +1,63 @@
/*
* Copyright 2024 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
*
* https://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.demo.compose
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Surface
import androidx.compose.runtime.remember
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.platform.LocalContext
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.demo.compose.data.videos
import androidx.media3.exoplayer.ExoPlayer
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
Surface {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
val context = LocalContext.current
val exoPlayer = remember {
ExoPlayer.Builder(context).build().apply {
setMediaItem(MediaItem.fromUri(videos[0]))
prepare()
playWhenReady = true
repeatMode = Player.REPEAT_MODE_ONE
}
}
PlayerSurface(
player = exoPlayer,
surfaceType = SURFACE_TYPE_SURFACE_VIEW,
modifier = Modifier.align(Alignment.CenterHorizontally),
)
}
}
}
}
}

View File

@ -0,0 +1,76 @@
/*
* Copyright 2024 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
*
* https://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.demo.compose
import android.view.Surface
import android.view.SurfaceView
import android.view.TextureView
import androidx.annotation.IntDef
import androidx.compose.foundation.AndroidEmbeddedExternalSurface
import androidx.compose.foundation.AndroidExternalSurface
import androidx.compose.foundation.AndroidExternalSurfaceScope
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.media3.common.Player
/**
* Provides a dedicated drawing [Surface] for media playbacks using a [Player].
*
* The player's video output is displayed with either a [SurfaceView]/[AndroidExternalSurface] or a
* [TextureView]/[AndroidEmbeddedExternalSurface].
*
* [Player] takes care of attaching the rendered output to the [Surface] and clearing it, when it is
* destroyed.
*
* See
* [Choosing a surface type](https://developer.android.com/media/media3/ui/playerview#surfacetype)
* for more information.
*/
@Composable
fun PlayerSurface(player: Player, surfaceType: @SurfaceType Int, modifier: Modifier = Modifier) {
val onSurfaceCreated: (Surface) -> Unit = { surface -> player.setVideoSurface(surface) }
val onSurfaceDestroyed: () -> Unit = { player.setVideoSurface(null) }
val onSurfaceInitialized: AndroidExternalSurfaceScope.() -> Unit = {
onSurface { surface, _, _ ->
onSurfaceCreated(surface)
surface.onDestroyed { onSurfaceDestroyed() }
}
}
when (surfaceType) {
SURFACE_TYPE_SURFACE_VIEW ->
AndroidExternalSurface(modifier = modifier, onInit = onSurfaceInitialized)
SURFACE_TYPE_TEXTURE_VIEW ->
AndroidEmbeddedExternalSurface(modifier = modifier, onInit = onSurfaceInitialized)
else -> throw IllegalArgumentException("Unrecognized surface type: $surfaceType")
}
}
/**
* The type of surface view used for media playbacks. One of [SURFACE_TYPE_SURFACE_VIEW] or
* [SURFACE_TYPE_TEXTURE_VIEW].
*/
@MustBeDocumented
@Retention(AnnotationRetention.SOURCE)
@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE, AnnotationTarget.TYPE_PARAMETER)
@IntDef(SURFACE_TYPE_SURFACE_VIEW, SURFACE_TYPE_TEXTURE_VIEW)
annotation class SurfaceType
/** Surface type equivalent to [SurfaceView] . */
const val SURFACE_TYPE_SURFACE_VIEW = 1
/** Surface type equivalent to [TextureView]. */
const val SURFACE_TYPE_TEXTURE_VIEW = 2

View File

@ -0,0 +1,23 @@
/*
* Copyright 2024 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
*
* https://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.demo.compose.data
val videos =
listOf(
"https://html5demos.com/assets/dizzy.mp4",
"https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/gear0/fileSequence0.aac",
"https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/video-vp9-360.webm",
)

View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2024 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.
-->
<shape
xmlns:android="http://schemas.android.com/apk/res/android">
<gradient android:startColor="@color/grey" android:endColor="@color/grey"/>
</shape>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2024 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
https://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.
-->
<resources>
<!-- Base application theme. -->
<style name="Theme.Media3ComposeDemo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2024 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
https://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.
-->
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="grey">#FF999999</color>
<color name="background">#292929</color>
<color name="player_background">#1c1c1c</color>
<color name="playlist_item_background">#363434</color>
<color name="playlist_item_foreground">#635E5E</color>
<color name="divider">#646464</color>
</resources>

View File

@ -0,0 +1,26 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2024 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
https://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.
-->
<resources>
<string name="app_name">Media3 Compose Demo</string>
<string name="current_playlist_name">Current playlist</string>
<string name="open_player_content_description">Click to view your play list</string>
<string name="added_media_item_format">Added %1$s to playlist</string>
<string name="shuffle">Shuffle</string>
<string name="play_button">Play</string>
<string name="waiting_for_metadata">Waiting for playlist to load…</string>
<string name="notification_permission_denied">
"Without notification access the app can't warn about failed background operations"</string>
</resources>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2024 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
https://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.
-->
<resources>
<!-- Base application theme. -->
<style name="Theme.Media3ComposeDemo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@ -0,0 +1,8 @@
# Composition demo
This app is an **EXPERIMENTAL** demo app created to explore the potential of `Composition` and `CompositionPlayer` APIs. It may exhibit limited features, occasional bugs, or unexpected behaviors.
**Attention**: `CompositionPlayer` APIs should be taken as work in progress, rather than experimental API. Please await further announcement via [Release Notes](https://github.com/androidx/media/releases) for when the APIs are fully integrated.
See the [demos README](../README.md) for instructions on how to build and run
this demo.

View File

@ -0,0 +1,61 @@
/*
* Copyright 2024 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.
*/
apply from: '../../constants.gradle'
apply plugin: 'com.android.application'
android {
namespace 'androidx.media3.demo.composition'
compileSdk project.ext.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
defaultConfig {
versionName project.ext.releaseVersion
versionCode project.ext.releaseVersionCode
minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.appTargetSdkVersion
}
buildTypes {
release {
shrinkResources true
minifyEnabled true
proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.txt'
signingConfig signingConfigs.debug
}
}
lintOptions {
// This demo app isn't indexed and doesn't have translations.
disable 'GoogleAppIndexingWarning','MissingTranslation'
}
}
dependencies {
implementation 'androidx.appcompat:appcompat:1.6.1'
implementation 'com.google.android.material:material:' + androidxMaterialVersion
implementation project(modulePrefix + 'lib-effect')
implementation project(modulePrefix + 'lib-exoplayer')
implementation project(modulePrefix + 'lib-exoplayer-dash')
implementation project(modulePrefix + 'lib-transformer')
implementation project(modulePrefix + 'lib-ui')
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
}

View File

@ -0,0 +1 @@
# Proguard rules specific to the composition demo app.

View File

@ -0,0 +1,51 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="androidx.media3.demo.composition">
<uses-sdk />
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
<application
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:supportsRtl="true"
android:requestLegacyExternalStorage="true"
tools:targetApi="29"
android:taskAffinity=""
android:theme="@style/Theme.AppCompat" >
<activity android:name=".CompositionPreviewActivity"
android:configChanges="keyboard|keyboardHidden|orientation|screenSize|screenLayout|smallestScreenSize|uiMode"
android:launchMode="singleTop"
android:label="@string/app_name"
android:exported="true"
android:theme="@style/Theme.MaterialComponents.DayNight.NoActionBar">
<intent-filter>
<action android:name="android.intent.action.MAIN"/>
<category android:name="android.intent.category.LAUNCHER"/>
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -0,0 +1,69 @@
/*
* Copyright 2024 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.demo.composition;
import android.view.LayoutInflater;
import android.view.View;
import android.view.ViewGroup;
import android.widget.TextView;
import androidx.recyclerview.widget.RecyclerView;
import java.util.List;
/** A {@link RecyclerView.Adapter} that displays assets in a sequence in a {@link RecyclerView}. */
public final class AssetItemAdapter extends RecyclerView.Adapter<AssetItemAdapter.ViewHolder> {
private static final String TAG = "AssetItemAdapter";
private final List<String> data;
/**
* Creates a new instance
*
* @param data A list of items to populate RecyclerView with.
*/
public AssetItemAdapter(List<String> data) {
this.data = data;
}
@Override
public ViewHolder onCreateViewHolder(ViewGroup parent, int viewType) {
View v = LayoutInflater.from(parent.getContext()).inflate(R.layout.preset_item, parent, false);
return new ViewHolder(v);
}
@Override
public void onBindViewHolder(ViewHolder holder, int position) {
holder.getTextView().setText(data.get(position));
}
@Override
public int getItemCount() {
return data.size();
}
/** A {@link RecyclerView.ViewHolder} used to build {@link AssetItemAdapter}. */
public static final class ViewHolder extends RecyclerView.ViewHolder {
private final TextView textView;
private ViewHolder(View view) {
super(view);
textView = view.findViewById(R.id.preset_name_text);
}
private TextView getTextView() {
return textView;
}
}
}

View File

@ -0,0 +1,367 @@
/*
* Copyright 2024 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.demo.composition;
import android.app.Activity;
import android.content.DialogInterface;
import android.os.Bundle;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatButton;
import androidx.appcompat.widget.AppCompatCheckBox;
import androidx.appcompat.widget.AppCompatTextView;
import androidx.media3.common.Effect;
import androidx.media3.common.MediaItem;
import androidx.media3.common.PlaybackException;
import androidx.media3.common.Player;
import androidx.media3.common.audio.SonicAudioProcessor;
import androidx.media3.common.util.Clock;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.Util;
import androidx.media3.effect.RgbFilter;
import androidx.media3.transformer.Composition;
import androidx.media3.transformer.CompositionPlayer;
import androidx.media3.transformer.EditedMediaItem;
import androidx.media3.transformer.EditedMediaItemSequence;
import androidx.media3.transformer.Effects;
import androidx.media3.transformer.ExportException;
import androidx.media3.transformer.ExportResult;
import androidx.media3.transformer.JsonUtil;
import androidx.media3.transformer.Transformer;
import androidx.media3.ui.PlayerView;
import androidx.recyclerview.widget.DividerItemDecoration;
import androidx.recyclerview.widget.LinearLayoutManager;
import androidx.recyclerview.widget.RecyclerView;
import com.google.common.base.Stopwatch;
import com.google.common.base.Ticker;
import com.google.common.collect.ImmutableList;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
import java.util.List;
import java.util.concurrent.TimeUnit;
import org.json.JSONException;
import org.json.JSONObject;
/**
* An {@link Activity} that previews compositions, using {@link
* androidx.media3.transformer.CompositionPlayer}.
*/
public final class CompositionPreviewActivity extends AppCompatActivity {
private static final String TAG = "CompPreviewActivity";
private static final String AUDIO_URI =
"https://storage.googleapis.com/exoplayer-test-media-0/play.mp3";
private ArrayList<String> sequenceAssetTitles;
private boolean[] selectedMediaItems;
private String[] presetDescriptions;
private AssetItemAdapter assetItemAdapter;
@Nullable private CompositionPlayer compositionPlayer;
@Nullable private Transformer transformer;
@Nullable private File outputFile;
private PlayerView playerView;
private AppCompatButton exportButton;
private AppCompatTextView exportInformationTextView;
private Stopwatch exportStopwatch;
private boolean includeBackgroundAudioTrack;
private boolean appliesVideoEffects;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
setContentView(R.layout.composition_preview_activity);
playerView = findViewById(R.id.composition_player_view);
findViewById(R.id.preview_button).setOnClickListener(view -> previewComposition());
findViewById(R.id.edit_sequence_button).setOnClickListener(view -> selectPreset());
RecyclerView presetList = findViewById(R.id.composition_preset_list);
presetList.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL));
LinearLayoutManager layoutManager =
new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, /* reverseLayout= */ false);
presetList.setLayoutManager(layoutManager);
exportInformationTextView = findViewById(R.id.export_information_text);
exportButton = findViewById(R.id.composition_export_button);
exportButton.setOnClickListener(view -> exportComposition());
AppCompatCheckBox backgroundAudioCheckBox = findViewById(R.id.background_audio_checkbox);
backgroundAudioCheckBox.setOnCheckedChangeListener(
(compoundButton, checked) -> includeBackgroundAudioTrack = checked);
AppCompatCheckBox applyVideoEffectsCheckBox = findViewById(R.id.apply_video_effects_checkbox);
applyVideoEffectsCheckBox.setOnCheckedChangeListener(
((compoundButton, checked) -> appliesVideoEffects = checked));
presetDescriptions = getResources().getStringArray(R.array.preset_descriptions);
// Select two media items by default.
selectedMediaItems = new boolean[presetDescriptions.length];
selectedMediaItems[0] = true;
selectedMediaItems[2] = true;
sequenceAssetTitles = new ArrayList<>();
for (int i = 0; i < selectedMediaItems.length; i++) {
if (selectedMediaItems[i]) {
sequenceAssetTitles.add(presetDescriptions[i]);
}
}
assetItemAdapter = new AssetItemAdapter(sequenceAssetTitles);
presetList.setAdapter(assetItemAdapter);
exportStopwatch =
Stopwatch.createUnstarted(
new Ticker() {
@Override
public long read() {
return android.os.SystemClock.elapsedRealtimeNanos();
}
});
}
@Override
protected void onStart() {
super.onStart();
playerView.onResume();
}
@Override
protected void onStop() {
super.onStop();
playerView.onPause();
releasePlayer();
cancelExport();
exportStopwatch.reset();
}
private Composition prepareComposition() {
String[] presetUris = getResources().getStringArray(/* id= */ R.array.preset_uris);
int[] presetDurationsUs = getResources().getIntArray(/* id= */ R.array.preset_durations);
List<EditedMediaItem> mediaItems = new ArrayList<>();
ImmutableList<Effect> videoEffects =
appliesVideoEffects
? ImmutableList.of(
MatrixTransformationFactory.createDizzyCropEffect(),
RgbFilter.createGrayscaleFilter())
: ImmutableList.of();
// Preview requires all sequences to be the same duration, so calculate main sequence duration
// and limit background sequence duration to match.
long videoSequenceDurationUs = 0;
for (int i = 0; i < selectedMediaItems.length; i++) {
if (selectedMediaItems[i]) {
SonicAudioProcessor pitchChanger = new SonicAudioProcessor();
pitchChanger.setPitch(mediaItems.size() % 2 == 0 ? 2f : 0.2f);
MediaItem mediaItem =
new MediaItem.Builder()
.setUri(presetUris[i])
.setImageDurationMs(Util.usToMs(presetDurationsUs[i])) // Ignored for audio/video
.build();
EditedMediaItem.Builder itemBuilder =
new EditedMediaItem.Builder(mediaItem)
.setEffects(
new Effects(
/* audioProcessors= */ ImmutableList.of(pitchChanger),
/* videoEffects= */ videoEffects))
.setDurationUs(presetDurationsUs[i]);
videoSequenceDurationUs += presetDurationsUs[i];
mediaItems.add(itemBuilder.build());
}
}
EditedMediaItemSequence videoSequence = new EditedMediaItemSequence.Builder(mediaItems).build();
List<EditedMediaItemSequence> compositionSequences = new ArrayList<>();
compositionSequences.add(videoSequence);
if (includeBackgroundAudioTrack) {
compositionSequences.add(getAudioBackgroundSequence(Util.usToMs(videoSequenceDurationUs)));
}
SonicAudioProcessor sampleRateChanger = new SonicAudioProcessor();
sampleRateChanger.setOutputSampleRateHz(8_000);
return new Composition.Builder(/* sequences= */ compositionSequences)
.setEffects(
new Effects(
/* audioProcessors= */ ImmutableList.of(sampleRateChanger),
/* videoEffects= */ ImmutableList.of()))
.build();
}
private EditedMediaItemSequence getAudioBackgroundSequence(long durationMs) {
MediaItem audioMediaItem =
new MediaItem.Builder()
.setUri(AUDIO_URI)
.setClippingConfiguration(
new MediaItem.ClippingConfiguration.Builder()
.setStartPositionMs(0)
.setEndPositionMs(durationMs)
.build())
.build();
EditedMediaItem audioItem =
new EditedMediaItem.Builder(audioMediaItem).setDurationUs(59_000_000).build();
return new EditedMediaItemSequence.Builder(audioItem).build();
}
private void previewComposition() {
releasePlayer();
Composition composition = prepareComposition();
playerView.setPlayer(null);
CompositionPlayer player = new CompositionPlayer.Builder(getApplicationContext()).build();
this.compositionPlayer = player;
playerView.setPlayer(compositionPlayer);
playerView.setControllerAutoShow(false);
player.addListener(
new Player.Listener() {
@Override
public void onPlayerError(PlaybackException error) {
Toast.makeText(getApplicationContext(), "Preview error: " + error, Toast.LENGTH_LONG)
.show();
Log.e(TAG, "Preview error", error);
}
});
player.setRepeatMode(Player.REPEAT_MODE_ALL);
player.setComposition(composition);
player.prepare();
player.play();
}
private void selectPreset() {
new AlertDialog.Builder(/* context= */ this)
.setTitle(R.string.select_preset_title)
.setMultiChoiceItems(presetDescriptions, selectedMediaItems, this::selectPresetInDialog)
.setPositiveButton(android.R.string.ok, /* listener= */ null)
.setCancelable(false)
.create()
.show();
}
private void selectPresetInDialog(DialogInterface dialog, int which, boolean isChecked) {
selectedMediaItems[which] = isChecked;
// The items will be added to a the sequence in the order they were selected.
if (isChecked) {
sequenceAssetTitles.add(presetDescriptions[which]);
assetItemAdapter.notifyItemInserted(sequenceAssetTitles.size() - 1);
} else {
int index = sequenceAssetTitles.indexOf(presetDescriptions[which]);
sequenceAssetTitles.remove(presetDescriptions[which]);
assetItemAdapter.notifyItemRemoved(index);
}
}
private void exportComposition() {
// Cancel and clean up files from any ongoing export.
cancelExport();
Composition composition = prepareComposition();
try {
outputFile =
createExternalCacheFile(
"composition-preview-" + Clock.DEFAULT.elapsedRealtime() + ".mp4");
} catch (IOException e) {
Toast.makeText(
getApplicationContext(),
"Aborting export! Unable to create output file: " + e,
Toast.LENGTH_LONG)
.show();
Log.e(TAG, "Aborting export! Unable to create output file: ", e);
return;
}
String filePath = outputFile.getAbsolutePath();
transformer =
new Transformer.Builder(/* context= */ this)
.addListener(
new Transformer.Listener() {
@Override
public void onCompleted(Composition composition, ExportResult exportResult) {
exportStopwatch.stop();
long elapsedTimeMs = exportStopwatch.elapsed(TimeUnit.MILLISECONDS);
String details =
getString(R.string.export_completed, elapsedTimeMs / 1000.f, filePath);
Log.i(TAG, details);
exportInformationTextView.setText(details);
try {
JSONObject resultJson =
JsonUtil.exportResultAsJsonObject(exportResult)
.put("elapsedTimeMs", elapsedTimeMs)
.put("device", JsonUtil.getDeviceDetailsAsJsonObject());
for (String line : Util.split(resultJson.toString(2), "\n")) {
Log.i(TAG, line);
}
} catch (JSONException e) {
Log.w(TAG, "Unable to convert exportResult to JSON", e);
}
}
@Override
public void onError(
Composition composition,
ExportResult exportResult,
ExportException exportException) {
exportStopwatch.stop();
Toast.makeText(
getApplicationContext(),
"Export error: " + exportException,
Toast.LENGTH_LONG)
.show();
Log.e(TAG, "Export error", exportException);
exportInformationTextView.setText(R.string.export_error);
}
})
.build();
exportInformationTextView.setText(R.string.export_started);
exportStopwatch.reset();
exportStopwatch.start();
transformer.start(composition, filePath);
Log.i(TAG, "Export started");
}
private void releasePlayer() {
if (compositionPlayer != null) {
compositionPlayer.release();
compositionPlayer = null;
}
}
/** Cancels any ongoing export operation, and deletes output file contents. */
private void cancelExport() {
if (transformer != null) {
transformer.cancel();
transformer = null;
}
if (outputFile != null) {
outputFile.delete();
outputFile = null;
}
exportInformationTextView.setText("");
}
/**
* Creates a {@link File} of the {@code fileName} in the application cache directory.
*
* <p>If a file of that name already exists, it is overwritten.
*/
// TODO: b/320636291 - Refactor duplicate createExternalCacheFile functions.
private File createExternalCacheFile(String fileName) throws IOException {
File file = new File(getExternalCacheDir(), fileName);
if (file.exists() && !file.delete()) {
throw new IOException("Could not delete file: " + file.getAbsolutePath());
}
if (!file.createNewFile()) {
throw new IOException("Could not create file: " + file.getAbsolutePath());
}
return file;
}
}

View File

@ -0,0 +1,93 @@
/*
* Copyright 2024 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.demo.composition;
import android.graphics.Matrix;
import androidx.media3.common.C;
import androidx.media3.common.util.Util;
import androidx.media3.effect.GlMatrixTransformation;
import androidx.media3.effect.MatrixTransformation;
/**
* Factory for {@link GlMatrixTransformation GlMatrixTransformations} and {@link
* MatrixTransformation MatrixTransformations} that create video effects by applying transformation
* matrices to the individual video frames.
*/
/* package */ final class MatrixTransformationFactory {
/**
* Returns a {@link MatrixTransformation} that rescales the frames over the first {@link
* #ZOOM_DURATION_SECONDS} seconds, such that the rectangle filled with the input frame increases
* linearly in size from a single point to filling the full output frame.
*/
public static MatrixTransformation createZoomInTransition() {
return MatrixTransformationFactory::calculateZoomInTransitionMatrix;
}
/**
* Returns a {@link MatrixTransformation} that crops frames to a rectangle that moves on an
* ellipse.
*/
public static MatrixTransformation createDizzyCropEffect() {
return MatrixTransformationFactory::calculateDizzyCropMatrix;
}
/**
* Returns a {@link GlMatrixTransformation} that rotates a frame in 3D around the y-axis and
* applies perspective projection to 2D.
*/
public static GlMatrixTransformation createSpin3dEffect() {
return MatrixTransformationFactory::calculate3dSpinMatrix;
}
private static final float ZOOM_DURATION_SECONDS = 2f;
private static final float DIZZY_CROP_ROTATION_PERIOD_US = 5_000_000f;
private static Matrix calculateZoomInTransitionMatrix(long presentationTimeUs) {
Matrix transformationMatrix = new Matrix();
float scale = Math.min(1, presentationTimeUs / (C.MICROS_PER_SECOND * ZOOM_DURATION_SECONDS));
transformationMatrix.postScale(/* sx= */ scale, /* sy= */ scale);
return transformationMatrix;
}
private static android.graphics.Matrix calculateDizzyCropMatrix(long presentationTimeUs) {
double theta = presentationTimeUs * 2 * Math.PI / DIZZY_CROP_ROTATION_PERIOD_US;
float centerX = 0.5f * (float) Math.cos(theta);
float centerY = 0.5f * (float) Math.sin(theta);
android.graphics.Matrix transformationMatrix = new android.graphics.Matrix();
transformationMatrix.postTranslate(/* dx= */ centerX, /* dy= */ centerY);
transformationMatrix.postScale(/* sx= */ 2f, /* sy= */ 2f);
return transformationMatrix;
}
private static float[] calculate3dSpinMatrix(long presentationTimeUs) {
float[] transformationMatrix = new float[16];
android.opengl.Matrix.frustumM(
transformationMatrix,
/* offset= */ 0,
/* left= */ -1f,
/* right= */ 1f,
/* bottom= */ -1f,
/* top= */ 1f,
/* near= */ 3f,
/* far= */ 5f);
android.opengl.Matrix.translateM(
transformationMatrix, /* mOffset= */ 0, /* x= */ 0f, /* y= */ 0f, /* z= */ -4f);
float theta = Util.usToMs(presentationTimeUs) / 10f;
android.opengl.Matrix.rotateM(
transformationMatrix, /* mOffset= */ 0, theta, /* x= */ 0f, /* y= */ 1f, /* z= */ 0f);
return transformationMatrix;
}
}

View File

@ -0,0 +1,22 @@
/*
* Copyright (C) 2024 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.
*/
@NonNullApi
@OptIn(markerClass = UnstableApi.class)
package androidx.media3.demo.composition;
import androidx.annotation.OptIn;
import androidx.media3.common.util.NonNullApi;
import androidx.media3.common.util.UnstableApi;

View File

@ -0,0 +1,136 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2024 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.
-->
<androidx.constraintlayout.widget.ConstraintLayout
xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:padding="16dp">
<com.google.android.material.card.MaterialCardView
android:id="@+id/composition_preview_card_view"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_weight="1"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toTopOf="parent"
app:cardCornerRadius="4dp"
app:cardElevation="2dp">
<LinearLayout
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/input_text_view"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:layout_marginBottom="8dp"
android:padding="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:text="@string/preview_composition" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="200dp" >
<androidx.media3.ui.PlayerView
android:id="@+id/composition_player_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</FrameLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/sequence_header_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/video_sequence_items"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/composition_preview_card_view"
app:layout_constraintBottom_toTopOf="@id/composition_preset_list"/>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/edit_sequence_button"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:text="@string/edit"
app:layout_constraintStart_toEndOf="@id/sequence_header_text"
app:layout_constraintTop_toTopOf="@id/sequence_header_text"
app:layout_constraintBottom_toBottomOf="@id/sequence_header_text"/>
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/apply_video_effects_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/add_effects"
app:layout_constraintStart_toEndOf="@id/edit_sequence_button"
app:layout_constraintTop_toTopOf="@id/sequence_header_text"
app:layout_constraintBottom_toBottomOf="@id/sequence_header_text" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/composition_preset_list"
android:layout_width="match_parent"
android:layout_height="0dp"
android:layout_marginTop="16dp"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/edit_sequence_button"
app:layout_constraintBottom_toTopOf="@id/export_information_text"/>
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/export_information_text"
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@id/background_audio_checkbox"/>
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/background_audio_checkbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/add_background_audio"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/preview_button" />
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/composition_export_button"
android:text="@string/export"
android:layout_marginTop="16dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toEndOf="@id/preview_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toBottomOf="parent"/>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/preview_button"
android:text="@string/preview"
android:layout_marginTop="16dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toStartOf="@id/composition_export_button"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View File

@ -0,0 +1,30 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2024 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.
-->
<androidx.appcompat.widget.LinearLayoutCompat
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:padding="8dp">
<androidx.appcompat.widget.AppCompatTextView
android:id="@+id/preset_name_text"
android:layout_width="0dp"
android:layout_height="wrap_content"
android:layout_gravity="center"
android:layout_weight="1"/>
</androidx.appcompat.widget.LinearLayoutCompat>

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2024 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.
-->
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.Media3internal" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@ -0,0 +1,77 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2024 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.
-->
<resources>
<string-array name="preset_descriptions">
<item>720p H264 video and AAC audio</item>
<item>1080p H265 video and AAC audio</item>
<item>360p H264 video and AAC audio</item>
<item>360p VP8 video and Vorbis audio</item>
<item>4K H264 video and AAC audio (portrait, no B-frames)</item>
<item>8k H265 video and AAC audio</item>
<item>Short 1080p H265 video and AAC audio</item>
<item>Long 180p H264 video and AAC audio</item>
<item>H264 video and AAC audio (portrait, H &gt; W, 0°)</item>
<item>H264 video and AAC audio (portrait, H &lt; W, 90°)</item>
<item>SEF slow motion with 240 fps</item>
<item>480p DASH (non-square pixels)</item>
<item>HDR (HDR10+) H265 limited range video (encoding may fail)</item>
<item>HDR (HLG) H265 limited range video (encoding may fail)</item>
<item>720p H264 video with no audio</item>
<item>London JPG image (plays for 5 secs at 30 fps)</item>
<item>Tokyo JPG image (portrait, plays for 5 secs at 30 fps)</item>
<item>Pixel 7 shorter audio track</item>
</string-array>
<string-array name="preset_uris">
<item>https://storage.googleapis.com/exoplayer-test-media-1/mp4/android-screens-10s.mp4</item>
<item>https://storage.googleapis.com/exoplayer-test-media-0/android-block-1080-hevc.mp4</item>
<item>https://html5demos.com/assets/dizzy.mp4</item>
<item>https://html5demos.com/assets/dizzy.webm</item>
<item>https://storage.googleapis.com/exoplayer-test-media-1/mp4/portrait_4k60.mp4</item>
<item>https://storage.googleapis.com/exoplayer-test-media-1/mp4/8k24fps_4s.mp4</item>
<item>https://storage.googleapis.com/exoplayer-test-media-1/mp4/1920w_1080h_4s.mp4</item>
<item>https://storage.googleapis.com/exoplayer-test-media-0/BigBuckBunny_320x180.mp4</item>
<item>https://storage.googleapis.com/exoplayer-test-media-1/mp4/portrait_avc_aac.mp4</item>
<item>https://storage.googleapis.com/exoplayer-test-media-1/mp4/portrait_rotated_avc_aac.mp4</item>
<item>https://storage.googleapis.com/exoplayer-test-media-1/mp4/slow-motion/slowMotion_stopwatch_240fps_long.mp4</item>
<item>https://storage.googleapis.com/exoplayer-test-media-1/gen/screens/dash-vod-single-segment/manifest-baseline.mpd</item>
<item>https://storage.googleapis.com/exoplayer-test-media-1/mp4/samsung-s21-hdr-hdr10.mp4</item>
<item>https://storage.googleapis.com/exoplayer-test-media-1/mp4/Pixel7Pro_HLG_1080P.mp4</item>
<item>https://storage.googleapis.com/exoplayer-test-media-1/mp4/sample_video_track_only.mp4</item>
<item>https://storage.googleapis.com/exoplayer-test-media-1/jpg/london.jpg</item>
<item>https://storage.googleapis.com/exoplayer-test-media-1/jpg/tokyo.jpg</item>
<item>https://storage.googleapis.com/exoplayer-temp/audio-blip/metronome_selfie_pixel.mp4</item>
</string-array>
<integer-array name="preset_durations">
<item>10024000</item>
<item>23823000</item>
<item>25000000</item>
<item>25000000</item>
<item>3745000</item>
<item>4421000</item>
<item>3923000</item>
<item>596459000</item>
<item>3687000</item>
<item>2235000</item>
<item>47987000</item>
<item>128270000</item>
<item>4236000</item>
<item>5167000</item>
<item>1001000</item>
<item>5000000</item>
<item>5000000</item>
<item>2170000</item>
</integer-array>
</resources>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2024 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.
-->
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

View File

@ -0,0 +1,29 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2024 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.
-->
<resources>
<string name="app_name">Composition Demo</string>
<string name="edit">Edit</string>
<string name="add_effects">Add effects</string>
<string name="preview" translatable="false">Preview</string>
<string name="preview_composition" translatable="false">Composition preview</string>
<string name="video_sequence_items" translatable="false">Video sequence items:</string>
<string name="select_preset_title" translatable="false">Choose preset input</string>
<string name="export" translatable="false">Export</string>
<string name="export_completed" translatable="false">Export completed in %.3f seconds.\nOutput: %s</string>
<string name="export_error" translatable="false">Export error</string>
<string name="export_started" translatable="false">Export started</string>
<string name="add_background_audio" translatable="false">Add background audio</string>
</resources>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2024 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.
-->
<resources xmlns:tools="http://schemas.android.com/tools">
<!-- Base application theme. -->
<style name="Theme.Media3internal" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@ -15,7 +15,9 @@ apply from: '../../constants.gradle'
apply plugin: 'com.android.application'
android {
compileSdkVersion project.ext.compileSdkVersion
namespace 'androidx.media3.demo.gl'
compileSdk project.ext.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
@ -27,7 +29,6 @@ android {
versionCode project.ext.releaseVersionCode
minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.appTargetSdkVersion
multiDexEnabled true
}
buildTypes {
@ -53,7 +54,5 @@ dependencies {
implementation project(modulePrefix + 'lib-exoplayer-smoothstreaming')
implementation project(modulePrefix + 'lib-ui')
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkCompatVersion
}

View File

@ -22,7 +22,6 @@
<uses-sdk/>
<application
android:name="androidx.multidex.MultiDexApplication"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/application_name">

View File

@ -63,7 +63,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
paint = new Paint();
paint.setTextSize(64);
paint.setAntiAlias(true);
paint.setARGB(0xFF, 0xFF, 0xFF, 0xFF);
paint.setColor(Color.WHITE);
textures = new int[1];
overlayBitmap = Bitmap.createBitmap(OVERLAY_WIDTH, OVERLAY_HEIGHT, Bitmap.Config.ARGB_8888);
overlayCanvas = new Canvas(overlayBitmap);

View File

@ -143,7 +143,7 @@ public final class MainActivity extends Activity {
? Assertions.checkNotNull(intent.getData())
: Uri.parse(DEFAULT_MEDIA_URI);
DrmSessionManager drmSessionManager;
if (Util.SDK_INT >= 18 && intent.hasExtra(DRM_SCHEME_EXTRA)) {
if (intent.hasExtra(DRM_SCHEME_EXTRA)) {
String drmScheme = Assertions.checkNotNull(intent.getStringExtra(DRM_SCHEME_EXTRA));
String drmLicenseUrl = Assertions.checkNotNull(intent.getStringExtra(DRM_LICENSE_URL_EXTRA));
UUID drmSchemeUuid = Assertions.checkNotNull(Util.getDrmUuid(drmScheme));

View File

@ -14,6 +14,9 @@
* limitations under the License.
*/
@NonNullApi
@OptIn(markerClass = UnstableApi.class)
package androidx.media3.demo.gl;
import androidx.annotation.OptIn;
import androidx.media3.common.util.NonNullApi;
import androidx.media3.common.util.UnstableApi;

View File

@ -14,9 +14,12 @@
apply from: '../../constants.gradle'
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
android {
compileSdkVersion project.ext.compileSdkVersion
namespace 'androidx.media3.demo.main'
compileSdk project.ext.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
@ -27,10 +30,7 @@ android {
versionName project.ext.releaseVersion
versionCode project.ext.releaseVersionCode
minSdkVersion project.ext.minSdkVersion
// Not using appTargetSDKVersion to allow local file access on API 29
// and higher [Internal ref: b/191644662]
targetSdkVersion project.ext.mainDemoAppTargetSdkVersion
multiDexEnabled true
targetSdkVersion project.ext.appTargetSdkVersion
}
buildTypes {
@ -54,7 +54,9 @@ android {
disable 'GoogleAppIndexingWarning','MissingTranslation','IconDensities'
}
flavorDimensions "decoderExtensions"
flavorDimensions = ["decoderExtensions"]
buildFeatures.buildConfig true
productFlavors {
noDecoderExtensions {
@ -72,7 +74,6 @@ dependencies {
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
implementation 'com.google.android.material:material:' + androidxMaterialVersion
implementation project(modulePrefix + 'lib-exoplayer')
implementation project(modulePrefix + 'lib-exoplayer-dash')
@ -86,7 +87,9 @@ dependencies {
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-ffmpeg')
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-flac')
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-opus')
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-iamf')
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-vp9')
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-midi')
withDecoderExtensionsImplementation project(modulePrefix + 'lib-datasource-rtmp')
}

View File

@ -21,8 +21,12 @@
<uses-permission android:name="android.permission.INTERNET"/>
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_MEDIA_AUDIO"/>
<uses-permission android:name="android.permission.READ_MEDIA_IMAGES"/>
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
<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_DATA_SYNC"/>
<uses-permission android:name="android.permission.POST_NOTIFICATIONS"/>
<uses-feature android:name="android.software.leanback" android:required="false"/>
@ -35,9 +39,7 @@
android:banner="@drawable/ic_banner"
android:largeHeap="true"
android:allowBackup="false"
android:requestLegacyExternalStorage="true"
android:supportsRtl="true"
android:name="androidx.multidex.MultiDexApplication"
tools:targetApi="29">
<activity android:name=".SampleChooserActivity"
@ -52,6 +54,7 @@
</intent-filter>
<intent-filter>
<action android:name="android.intent.action.VIEW"/>
<action android:name="androidx.media3.demo.main.action.BROWSE"/>
<category android:name="android.intent.category.DEFAULT"/>
<category android:name="android.intent.category.BROWSABLE"/>
<data android:scheme="http"/>
@ -93,7 +96,8 @@
</activity>
<service android:name=".DemoDownloadService"
android:exported="false">
android:exported="false"
android:foregroundServiceType="dataSync">
<intent-filter>
<action android:name="androidx.media3.exoplayer.downloadService.action.RESTART"/>
<category android:name="android.intent.category.DEFAULT"/>

View File

@ -143,6 +143,12 @@
"drm_scheme": "widevine",
"drm_license_uri": "https://proxy.uat.widevine.com/proxy?video_id=GTS_HW_SECURE_ALL&provider=widevine_test"
},
{
"name": "20s license with renewal",
"uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd",
"drm_scheme": "widevine",
"drm_license_uri": "https://proxy.uat.widevine.com/proxy?video_id=GTS_CAN_RENEW&provider=widevine_test"
},
{
"name": "30s license (fails at ~30s)",
"uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd",
@ -406,6 +412,18 @@
"name": "DASH VOD: Tears of Steel (11 periods, pre/mid/post), 2/5/2 ads [5/10s]",
"uri": "ssai://dai.google.com/?contentSourceId=2559737&videoId=tos-dash&format=0&adsId=1"
},
{
"name": "DASH live: Tears of Steel (mid), 3 ads each [10 s]",
"uri": "ssai://dai.google.com/?assetKey=jNVjPZwzSkyeGiaNQTPqiQ&format=0&adsId=1"
},
{
"name": "DASH live: New Tears of Steel (mid), 3 ads each [10 s]",
"uri": "ssai://dai.google.com/?assetKey=PSzZMzAkSXCmlJOWDmRj8Q&format=0&adsId=12"
},
{
"name": "DASH live: Unencrypted stream with 30s ad breaks every minute",
"uri": "ssai://dai.google.com/?assetKey=0ndl1dJcRmKDUPxTRjvdog&format=0&adsId=21"
},
{
"name": "Playlist: No ads - HLS VOD: Demo (skippable pre/post) - No ads",
"playlist": [
@ -434,20 +452,6 @@
}
]
},
{
"name": "Playlist: No ads - HLS Live: Big Buck Bunny (mid) - No ads",
"playlist": [
{
"uri": "https://html5demos.com/assets/dizzy.mp4"
},
{
"uri": "ssai://dai.google.com/?assetKey=sN_IYUG8STe1ZzhIIE_ksA&format=2&adsId=3"
},
{
"uri": "https://html5demos.com/assets/dizzy.mp4"
}
]
},
{
"name": "Playlist: No ads - DASH VOD: Tears of Steel (11 periods, pre/mid/post) - No ads",
"playlist": [
@ -476,6 +480,34 @@
"uri": "https://html5demos.com/assets/dizzy.mp4"
}
]
},
{
"name": "Playlist: No ads - DASH live: Tears of Steel (mid) - No ads",
"playlist": [
{
"uri": "https://html5demos.com/assets/dizzy.mp4"
},
{
"uri": "ssai://dai.google.com/?assetKey=PSzZMzAkSXCmlJOWDmRj8Q&format=0&adsId=1"
},
{
"uri": "https://html5demos.com/assets/dizzy.mp4"
}
]
},
{
"name": "Playlist: No ads - HLS live: Big Buck Bunny - No ads",
"playlist": [
{
"uri": "https://html5demos.com/assets/dizzy.mp4"
},
{
"uri": "ssai://dai.google.com/?assetKey=sN_IYUG8STe1ZzhIIE_ksA&format=2&adsId=3"
},
{
"uri": "https://html5demos.com/assets/dizzy.mp4"
}
]
}
]
},
@ -494,7 +526,7 @@
]
},
{
"name": "Audio -> Video -> Audio",
"name": "Audio -> Video (MKV) -> Video (MKV) -> Audio -> Video (MKV) -> Video (DASH) -> Audio",
"playlist": [
{
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/audio-141.mp4"
@ -502,6 +534,18 @@
{
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv"
},
{
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv"
},
{
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/audio-141.mp4"
},
{
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv"
},
{
"uri": "https://storage.googleapis.com/wvmedia/clear/h264/tears/tears.mpd"
},
{
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/audio-141.mp4"
}
@ -549,6 +593,27 @@
"clip_start_position_ms": 10000
}
]
},
{
"name": "Image -> Video -> Image -> Image",
"playlist": [
{
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/jpg/tokyo.jpg",
"image_duration_ms": 2000
},
{
"uri": "https://html5demos.com/assets/dizzy.mp4",
"clip_end_position_ms": 2000
},
{
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/jpg/london.jpg",
"image_duration_ms": 2000
},
{
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/jpg/tokyo.jpg",
"image_duration_ms": 2000
}
]
}
]
},
@ -628,11 +693,19 @@
{
"name": "MPEG-4 Timed Text",
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mp4/dizzy-with-tx3g.mp4"
},
{
"name": "SubRip muxed into MKV",
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-with-subrip.mkv"
},
{
"name": "Overlapping SSA muxed into MKV",
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-with-overlapping-ssa.mkv"
}
]
},
{
"name": "Misc",
"name": "Progressive",
"samples": [
{
"name": "Dizzy (MP4)",
@ -685,6 +758,44 @@
{
"name": "One hour frame counter (MP4)",
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mp4/frame-counter-one-hour.mp4"
},
{
"name": "Immersive Audio Format Sample (MP4, IAMF)",
"uri": "https://github.com/AOMediaCodec/libiamf/raw/main/tests/test_000036_s.mp4"
}
]
},
{
"name": "Images",
"samples": [
{
"name": "JPEG (wide)",
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/jpg/london.jpg",
"image_duration_ms": 2000
},
{
"name": "JPEG (tall)",
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/jpg/tokyo.jpg",
"image_duration_ms": 2000
},
{
"name": "PNG",
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/png/media3test.png",
"image_duration_ms": 2000
},
{
"name": "JPEG motion photo (still)",
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/jpg/london_motion_photo.jpg",
"image_duration_ms": 2000
},
{
"name": "JPEG motion photo (motion)",
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/jpg/london_motion_photo.jpg"
},
{
"name": "JPEG (Ultra HDR)",
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/jpg/ultra_hdr.jpg",
"image_duration_ms": 2000
}
]
}

View File

@ -63,7 +63,7 @@ public class DemoDownloadService extends DownloadService {
@Override
protected Scheduler getScheduler() {
return Util.SDK_INT >= 21 ? new PlatformScheduler(this, JOB_ID) : null;
return new PlatformScheduler(this, JOB_ID);
}
@Override

View File

@ -16,12 +16,16 @@
package androidx.media3.demo.main;
import android.content.Context;
import android.net.http.HttpEngine;
import android.os.Build;
import android.os.ext.SdkExtensions;
import androidx.annotation.OptIn;
import androidx.media3.database.DatabaseProvider;
import androidx.media3.database.StandaloneDatabaseProvider;
import androidx.media3.datasource.DataSource;
import androidx.media3.datasource.DefaultDataSource;
import androidx.media3.datasource.DefaultHttpDataSource;
import androidx.media3.datasource.HttpEngineDataSource;
import androidx.media3.datasource.cache.Cache;
import androidx.media3.datasource.cache.CacheDataSource;
import androidx.media3.datasource.cache.NoOpCacheEvictor;
@ -46,25 +50,26 @@ public final class DemoUtil {
public static final String DOWNLOAD_NOTIFICATION_CHANNEL_ID = "download_channel";
/**
* Whether the demo application uses Cronet for networking. Note that Cronet does not provide
* automatic support for cookies (https://github.com/google/ExoPlayer/issues/5975).
*
* <p>If set to false, the platform's default network stack is used with a {@link CookieManager}
* configured in {@link #getHttpDataSourceFactory}.
*/
private static final boolean USE_CRONET_FOR_NETWORKING = true;
private static final String TAG = "DemoUtil";
private static final String DOWNLOAD_CONTENT_DIRECTORY = "downloads";
private static DataSource.@MonotonicNonNull Factory dataSourceFactory;
private static DataSource.@MonotonicNonNull Factory httpDataSourceFactory;
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
private static @MonotonicNonNull DatabaseProvider databaseProvider;
private static @MonotonicNonNull File downloadDirectory;
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
private static @MonotonicNonNull Cache downloadCache;
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
private static @MonotonicNonNull DownloadManager downloadManager;
private static @MonotonicNonNull DownloadTracker downloadTracker;
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
private static @MonotonicNonNull DownloadNotificationHelper downloadNotificationHelper;
/** Returns whether extension renderers should be used. */
@ -86,24 +91,30 @@ public final class DemoUtil {
.setExtensionRendererMode(extensionRendererMode);
}
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
public static synchronized DataSource.Factory getHttpDataSourceFactory(Context context) {
if (httpDataSourceFactory == null) {
if (USE_CRONET_FOR_NETWORKING) {
if (httpDataSourceFactory != null) {
return httpDataSourceFactory;
}
context = context.getApplicationContext();
if (Build.VERSION.SDK_INT >= 30
&& SdkExtensions.getExtensionVersion(Build.VERSION_CODES.S) >= 7) {
HttpEngine httpEngine = new HttpEngine.Builder(context).build();
httpDataSourceFactory =
new HttpEngineDataSource.Factory(httpEngine, Executors.newSingleThreadExecutor());
return httpDataSourceFactory;
}
@Nullable CronetEngine cronetEngine = CronetUtil.buildCronetEngine(context);
if (cronetEngine != null) {
httpDataSourceFactory =
new CronetDataSource.Factory(cronetEngine, Executors.newSingleThreadExecutor());
return httpDataSourceFactory;
}
}
if (httpDataSourceFactory == null) {
// We don't want to use Cronet, or we failed to instantiate a CronetEngine.
// The device doesn't support HttpEngine and we failed to instantiate a CronetEngine.
CookieManager cookieManager = new CookieManager();
cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER);
CookieHandler.setDefault(cookieManager);
httpDataSourceFactory = new DefaultHttpDataSource.Factory();
}
}
return httpDataSourceFactory;
}
@ -128,6 +139,7 @@ public final class DemoUtil {
return downloadNotificationHelper;
}
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
public static synchronized DownloadManager getDownloadManager(Context context) {
ensureDownloadManagerInitialized(context);
return downloadManager;

View File

@ -16,16 +16,18 @@
package androidx.media3.demo.main;
import static com.google.common.base.Preconditions.checkNotNull;
import static com.google.common.base.Preconditions.checkState;
import android.content.Context;
import android.content.DialogInterface;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Handler;
import android.os.Looper;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.annotation.RequiresApi;
import androidx.fragment.app.FragmentManager;
import androidx.media3.common.C;
import androidx.media3.common.DrmInitData;
import androidx.media3.common.Format;
import androidx.media3.common.MediaItem;
@ -51,7 +53,11 @@ import androidx.media3.exoplayer.source.TrackGroupArray;
import androidx.media3.exoplayer.trackselection.MappingTrackSelector.MappedTrackInfo;
import java.io.IOException;
import java.util.HashMap;
import java.util.UUID;
import java.util.concurrent.CopyOnWriteArraySet;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import java.util.concurrent.Future;
/** Tracks media that has been downloaded. */
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
@ -180,7 +186,7 @@ public class DownloadTracker {
trackSelectionDialog.dismiss();
}
if (widevineOfflineLicenseFetchTask != null) {
widevineOfflineLicenseFetchTask.cancel(false);
widevineOfflineLicenseFetchTask.cancel();
}
}
@ -195,14 +201,9 @@ public class DownloadTracker {
}
// The content is DRM protected. We need to acquire an offline license.
if (Util.SDK_INT < 18) {
Toast.makeText(context, R.string.error_drm_unsupported_before_api_18, Toast.LENGTH_LONG)
.show();
Log.e(TAG, "Downloading DRM protected content is not supported on API versions below 18");
return;
}
// TODO(internal b/163107948): Support cases where DrmInitData are not in the manifest.
if (!hasSchemaData(format.drmInitData)) {
if (!hasNonNullWidevineSchemaData(format.drmInitData)) {
Toast.makeText(context, R.string.download_start_error_offline_license, Toast.LENGTH_LONG)
.show();
Log.e(
@ -323,12 +324,14 @@ public class DownloadTracker {
}
/**
* Returns whether any the {@link DrmInitData.SchemeData} contained in {@code drmInitData} has
* non-null {@link DrmInitData.SchemeData#data}.
* Returns whether any {@link DrmInitData.SchemeData} that {@linkplain
* DrmInitData.SchemeData#matches(UUID) matches} {@link C#WIDEVINE_UUID} has non-null {@link
* DrmInitData.SchemeData#data}.
*/
private boolean hasSchemaData(DrmInitData drmInitData) {
private boolean hasNonNullWidevineSchemaData(DrmInitData drmInitData) {
for (int i = 0; i < drmInitData.schemeDataCount; i++) {
if (drmInitData.get(i).hasData()) {
DrmInitData.SchemeData schemeData = drmInitData.get(i);
if (schemeData.matches(C.WIDEVINE_UUID) && schemeData.hasData()) {
return true;
}
}
@ -353,15 +356,16 @@ public class DownloadTracker {
}
/** Downloads a Widevine offline license in a background thread. */
@RequiresApi(18)
private static final class WidevineOfflineLicenseFetchTask extends AsyncTask<Void, Void, Void> {
private static final class WidevineOfflineLicenseFetchTask {
private final Format format;
private final MediaItem.DrmConfiguration drmConfiguration;
private final DataSource.Factory dataSourceFactory;
private final StartDownloadDialogHelper dialogHelper;
private final DownloadHelper downloadHelper;
private final ExecutorService executorService;
@Nullable Future<?> future;
@Nullable private byte[] keySetId;
@Nullable private DrmSession.DrmSessionException drmSessionException;
@ -371,6 +375,8 @@ public class DownloadTracker {
DataSource.Factory dataSourceFactory,
StartDownloadDialogHelper dialogHelper,
DownloadHelper downloadHelper) {
checkState(drmConfiguration.scheme.equals(C.WIDEVINE_UUID));
this.executorService = Executors.newSingleThreadExecutor();
this.format = format;
this.drmConfiguration = drmConfiguration;
this.dataSourceFactory = dataSourceFactory;
@ -378,8 +384,16 @@ public class DownloadTracker {
this.downloadHelper = downloadHelper;
}
@Override
protected Void doInBackground(Void... voids) {
public void cancel() {
if (future != null) {
future.cancel(/* mayInterruptIfRunning= */ false);
}
}
public void execute() {
future =
executorService.submit(
() -> {
OfflineLicenseHelper offlineLicenseHelper =
OfflineLicenseHelper.newWidevineInstance(
drmConfiguration.licenseUri.toString(),
@ -393,17 +407,18 @@ public class DownloadTracker {
drmSessionException = e;
} finally {
offlineLicenseHelper.release();
}
return null;
}
@Override
protected void onPostExecute(Void aVoid) {
new Handler(Looper.getMainLooper())
.post(
() -> {
if (drmSessionException != null) {
dialogHelper.onOfflineLicenseFetchedError(drmSessionException);
} else {
dialogHelper.onOfflineLicenseFetched(downloadHelper, checkNotNull(keySetId));
}
dialogHelper.onOfflineLicenseFetched(
downloadHelper, checkNotNull(keySetId));
}
});
}
});
}
}
}

View File

@ -22,11 +22,14 @@ import static com.google.common.base.Preconditions.checkState;
import android.content.Intent;
import android.net.Uri;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.media3.common.C;
import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaItem.ClippingConfiguration;
import androidx.media3.common.MediaItem.SubtitleConfiguration;
import androidx.media3.common.MediaMetadata;
import androidx.media3.common.Player;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
@ -53,6 +56,7 @@ public class IntentUtil {
public static final String MIME_TYPE_EXTRA = "mime_type";
public static final String CLIP_START_POSITION_MS_EXTRA = "clip_start_position_ms";
public static final String CLIP_END_POSITION_MS_EXTRA = "clip_end_position_ms";
public static final String IMAGE_DURATION_MS = "image_duration_ms";
public static final String AD_TAG_URI_EXTRA = "ad_tag_uri";
@ -66,6 +70,21 @@ public class IntentUtil {
public static final String SUBTITLE_URI_EXTRA = "subtitle_uri";
public static final String SUBTITLE_MIME_TYPE_EXTRA = "subtitle_mime_type";
public static final String SUBTITLE_LANGUAGE_EXTRA = "subtitle_language";
public static final String REPEAT_MODE_EXTRA = "repeat_mode";
public static @Player.RepeatMode int parseRepeatModeExtra(String repeatMode) {
switch (repeatMode) {
case "OFF":
return Player.REPEAT_MODE_OFF;
case "ONE":
return Player.REPEAT_MODE_ONE;
case "ALL":
return Player.REPEAT_MODE_ALL;
default:
throw new IllegalArgumentException(
"Argument " + repeatMode + " does not match any of the repeat modes: OFF|ONE|ALL");
}
}
/** Creates a list of {@link MediaItem media items} from an {@link Intent}. */
public static List<MediaItem> createMediaItemsFromIntent(Intent intent) {
@ -94,7 +113,7 @@ public class IntentUtil {
if (mediaItem.mediaMetadata.title != null) {
intent.putExtra(TITLE_EXTRA, mediaItem.mediaMetadata.title);
}
addPlaybackPropertiesToIntent(localConfiguration, intent, /* extrasKeySuffix= */ "");
addLocalConfigurationToIntent(localConfiguration, intent, /* extrasKeySuffix= */ "");
addClippingConfigurationToIntent(
mediaItem.clippingConfiguration, intent, /* extrasKeySuffix= */ "");
} else {
@ -104,7 +123,7 @@ public class IntentUtil {
MediaItem.LocalConfiguration localConfiguration =
checkNotNull(mediaItem.localConfiguration);
intent.putExtra(URI_EXTRA + ("_" + i), localConfiguration.uri.toString());
addPlaybackPropertiesToIntent(localConfiguration, intent, /* extrasKeySuffix= */ "_" + i);
addLocalConfigurationToIntent(localConfiguration, intent, /* extrasKeySuffix= */ "_" + i);
addClippingConfigurationToIntent(
mediaItem.clippingConfiguration, intent, /* extrasKeySuffix= */ "_" + i);
if (mediaItem.mediaMetadata.title != null) {
@ -114,6 +133,7 @@ public class IntentUtil {
}
}
@OptIn(markerClass = UnstableApi.class) // Setting image duration.
private static MediaItem createMediaItemFromIntent(
Uri uri, Intent intent, String extrasKeySuffix) {
@Nullable String mimeType = intent.getStringExtra(MIME_TYPE_EXTRA + extrasKeySuffix);
@ -122,6 +142,7 @@ public class IntentUtil {
@Nullable
SubtitleConfiguration subtitleConfiguration =
createSubtitleConfiguration(intent, extrasKeySuffix);
long imageDurationMs = intent.getLongExtra(IMAGE_DURATION_MS + extrasKeySuffix, C.TIME_UNSET);
MediaItem.Builder builder =
new MediaItem.Builder()
.setUri(uri)
@ -134,7 +155,8 @@ public class IntentUtil {
.setEndPositionMs(
intent.getLongExtra(
CLIP_END_POSITION_MS_EXTRA + extrasKeySuffix, C.TIME_END_OF_SOURCE))
.build());
.build())
.setImageDurationMs(imageDurationMs);
if (adTagUri != null) {
builder.setAdsConfiguration(
new MediaItem.AdsConfiguration.Builder(Uri.parse(adTagUri)).build());
@ -195,7 +217,8 @@ public class IntentUtil {
return builder;
}
private static void addPlaybackPropertiesToIntent(
@OptIn(markerClass = UnstableApi.class) // Accessing image duration.
private static void addLocalConfigurationToIntent(
MediaItem.LocalConfiguration localConfiguration, Intent intent, String extrasKeySuffix) {
intent
.putExtra(MIME_TYPE_EXTRA + extrasKeySuffix, localConfiguration.mimeType)
@ -215,6 +238,9 @@ public class IntentUtil {
intent.putExtra(SUBTITLE_MIME_TYPE_EXTRA + extrasKeySuffix, subtitleConfiguration.mimeType);
intent.putExtra(SUBTITLE_LANGUAGE_EXTRA + extrasKeySuffix, subtitleConfiguration.language);
}
if (localConfiguration.imageDurationMs != C.TIME_UNSET) {
intent.putExtra(IMAGE_DURATION_MS + extrasKeySuffix, localConfiguration.imageDurationMs);
}
}
private static void addDrmConfigurationToIntent(

View File

@ -93,9 +93,6 @@ public class PlayerActivity extends AppCompatActivity
@Nullable private AdsLoader clientSideAdsLoader;
// TODO: Annotate this and serverSideAdsLoaderState below with @OptIn when it can be applied to
// fields (needs http://r.android.com/2004032 to be released into a version of
// androidx.annotation:annotation-experimental).
@Nullable private ImaServerSideAdInsertionMediaSource.AdsLoader serverSideAdsLoader;
private ImaServerSideAdInsertionMediaSource.AdsLoader.@MonotonicNonNull State
@ -262,8 +259,8 @@ public class PlayerActivity extends AppCompatActivity
* @return Whether initialization was successful.
*/
protected boolean initializePlayer() {
if (player == null) {
Intent intent = getIntent();
if (player == null) {
mediaItems = createMediaItems(intent);
if (mediaItems.isEmpty()) {
@ -293,11 +290,15 @@ public class PlayerActivity extends AppCompatActivity
}
player.setMediaItems(mediaItems, /* resetPosition= */ !haveStartPosition);
player.prepare();
String repeatModeExtra = intent.getStringExtra(IntentUtil.REPEAT_MODE_EXTRA);
if (repeatModeExtra != null) {
player.setRepeatMode(IntentUtil.parseRepeatModeExtra(repeatModeExtra));
}
updateButtonVisibility();
return true;
}
@OptIn(markerClass = UnstableApi.class) // SSAI configuration
@OptIn(markerClass = UnstableApi.class) // DRM configuration
private MediaSource.Factory createMediaSourceFactory() {
DefaultDrmSessionManagerProvider drmSessionManagerProvider =
new DefaultDrmSessionManagerProvider();
@ -330,7 +331,6 @@ public class PlayerActivity extends AppCompatActivity
playerBuilder.setRenderersFactory(renderersFactory);
}
@OptIn(markerClass = UnstableApi.class)
private void configurePlayerWithServerSideAdsLoader() {
serverSideAdsLoader.setPlayer(player);
}
@ -354,18 +354,14 @@ public class PlayerActivity extends AppCompatActivity
finish();
return Collections.emptyList();
}
if (Util.maybeRequestReadExternalStoragePermission(/* activity= */ this, mediaItem)) {
if (Util.maybeRequestReadStoragePermission(/* activity= */ this, mediaItem)) {
// The player will be reinitialized if the permission is granted.
return Collections.emptyList();
}
MediaItem.DrmConfiguration drmConfiguration = mediaItem.localConfiguration.drmConfiguration;
if (drmConfiguration != null) {
if (Build.VERSION.SDK_INT < 18) {
showToast(R.string.error_drm_unsupported_before_api_18);
finish();
return Collections.emptyList();
} else if (!FrameworkMediaDrm.isCryptoSchemeSupported(drmConfiguration.scheme)) {
if (!FrameworkMediaDrm.isCryptoSchemeSupported(drmConfiguration.scheme)) {
showToast(R.string.error_drm_unsupported_scheme);
finish();
return Collections.emptyList();
@ -403,7 +399,6 @@ public class PlayerActivity extends AppCompatActivity
}
}
@OptIn(markerClass = UnstableApi.class)
private void releaseServerSideAdsLoader() {
serverSideAdsLoaderState = serverSideAdsLoader.release();
serverSideAdsLoader = null;
@ -417,20 +412,17 @@ public class PlayerActivity extends AppCompatActivity
}
}
@OptIn(markerClass = UnstableApi.class)
private void saveServerSideAdsLoaderState(Bundle outState) {
if (serverSideAdsLoaderState != null) {
outState.putBundle(KEY_SERVER_SIDE_ADS_LOADER_STATE, serverSideAdsLoaderState.toBundle());
}
}
@OptIn(markerClass = UnstableApi.class)
private void restoreServerSideAdsLoaderState(Bundle savedInstanceState) {
Bundle adsLoaderStateBundle = savedInstanceState.getBundle(KEY_SERVER_SIDE_ADS_LOADER_STATE);
if (adsLoaderStateBundle != null) {
serverSideAdsLoaderState =
ImaServerSideAdInsertionMediaSource.AdsLoader.State.CREATOR.fromBundle(
adsLoaderStateBundle);
ImaServerSideAdInsertionMediaSource.AdsLoader.State.fromBundle(adsLoaderStateBundle);
}
}
@ -514,7 +506,7 @@ public class PlayerActivity extends AppCompatActivity
private class PlayerErrorMessageProvider implements ErrorMessageProvider<PlaybackException> {
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
@OptIn(markerClass = UnstableApi.class) // Using decoder exceptions
@Override
public Pair<Integer, String> getErrorMessage(PlaybackException e) {
String errorString = getString(R.string.error_generic);
@ -555,7 +547,7 @@ public class PlayerActivity extends AppCompatActivity
return mediaItems;
}
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
@OptIn(markerClass = UnstableApi.class) // Using Download API
private static MediaItem maybeSetDownloadProperties(
MediaItem item, @Nullable DownloadRequest downloadRequest) {
if (downloadRequest == null) {

View File

@ -26,11 +26,13 @@ import android.content.SharedPreferences;
import android.content.pm.PackageManager;
import android.content.res.AssetManager;
import android.net.Uri;
import android.os.AsyncTask;
import android.os.Build;
import android.os.Bundle;
import android.os.Handler;
import android.os.Looper;
import android.text.TextUtils;
import android.util.JsonReader;
import android.util.Log;
import android.view.Menu;
import android.view.MenuInflater;
import android.view.MenuItem;
@ -43,15 +45,15 @@ import android.widget.ExpandableListView.OnChildClickListener;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.DoNotInline;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.annotation.RequiresApi;
import androidx.appcompat.app.AppCompatActivity;
import androidx.media3.common.C;
import androidx.media3.common.MediaItem;
import androidx.media3.common.MediaItem.ClippingConfiguration;
import androidx.media3.common.MediaMetadata;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.datasource.DataSource;
import androidx.media3.datasource.DataSourceInputStream;
@ -65,6 +67,7 @@ import com.google.common.collect.ImmutableMap;
import java.io.IOException;
import java.io.InputStream;
import java.io.InputStreamReader;
import java.nio.charset.StandardCharsets;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Collections;
@ -72,6 +75,8 @@ import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
/** An activity for selecting from a list of media samples. */
public class SampleChooserActivity extends AppCompatActivity
@ -80,7 +85,6 @@ public class SampleChooserActivity extends AppCompatActivity
private static final String TAG = "SampleChooserActivity";
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 int POST_NOTIFICATION_PERMISSION_REQUEST_CODE = 100;
private String[] uris;
private boolean useExtensionRenderers;
@ -115,6 +119,7 @@ public class SampleChooserActivity extends AppCompatActivity
}
}
} catch (IOException e) {
Log.e(TAG, "One or more sample lists failed to load", e);
Toast.makeText(getApplicationContext(), R.string.sample_list_load_error, Toast.LENGTH_LONG)
.show();
}
@ -179,14 +184,6 @@ public class SampleChooserActivity extends AppCompatActivity
public void onRequestPermissionsResult(
int requestCode, String[] permissions, int[] 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(
@ -201,30 +198,8 @@ public class SampleChooserActivity extends AppCompatActivity
}
}
private void handleExternalStoragePermissionGrantResults(int[] grantResults) {
if (grantResults.length == 0) {
// Empty results are triggered if a permission is requested while another request was already
// pending and can be safely ignored in this case.
return;
} else if (grantResults[0] == PackageManager.PERMISSION_GRANTED) {
loadSample();
} else {
Toast.makeText(getApplicationContext(), R.string.sample_list_load_error, Toast.LENGTH_LONG)
.show();
finish();
}
}
private void loadSample() {
checkNotNull(uris);
for (int i = 0; i < uris.length; i++) {
Uri uri = Uri.parse(uris[i]);
if (Util.maybeRequestReadExternalStoragePermission(this, uri)) {
return;
}
}
SampleListLoader loaderTask = new SampleListLoader();
loaderTask.execute(uris);
}
@ -279,13 +254,13 @@ public class SampleChooserActivity extends AppCompatActivity
!= PackageManager.PERMISSION_GRANTED) {
downloadMediaItemWaitingForNotificationPermission = playlistHolder.mediaItems.get(0);
requestPermissions(
new String[] {Api33.getPostNotificationPermissionString()},
/* requestCode= */ POST_NOTIFICATION_PERMISSION_REQUEST_CODE);
new String[] {Api33.getPostNotificationPermissionString()}, /* requestCode= */ 0);
} else {
toggleDownload(playlistHolder.mediaItems.get(0));
}
}
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
private void toggleDownload(MediaItem mediaItem) {
RenderersFactory renderersFactory =
DemoUtil.buildRenderersFactory(
@ -302,6 +277,10 @@ public class SampleChooserActivity extends AppCompatActivity
if (localConfiguration.adsConfiguration != null) {
return R.string.download_ads_unsupported;
}
@Nullable MediaItem.DrmConfiguration drmConfiguration = localConfiguration.drmConfiguration;
if (drmConfiguration != null && !drmConfiguration.scheme.equals(C.WIDEVINE_UUID)) {
return R.string.download_only_widevine_drm_supported;
}
String scheme = localConfiguration.uri.getScheme();
if (!("http".equals(scheme) || "https".equals(scheme))) {
return R.string.download_scheme_unsupported;
@ -314,13 +293,20 @@ public class SampleChooserActivity extends AppCompatActivity
return menuItem != null && menuItem.isChecked();
}
private final class SampleListLoader extends AsyncTask<String, Void, List<PlaylistGroup>> {
private final class SampleListLoader {
private final ExecutorService executorService;
private boolean sawError;
public SampleListLoader() {
executorService = Executors.newSingleThreadExecutor();
}
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
@Override
protected List<PlaylistGroup> doInBackground(String... uris) {
public void execute(String... uris) {
executorService.execute(
() -> {
List<PlaylistGroup> result = new ArrayList<>();
Context context = getApplicationContext();
DataSource dataSource = DemoUtil.getDataSourceFactory(context).createDataSource();
@ -328,7 +314,9 @@ public class SampleChooserActivity extends AppCompatActivity
DataSpec dataSpec = new DataSpec(Uri.parse(uri));
InputStream inputStream = new DataSourceInputStream(dataSource, dataSpec);
try {
readPlaylistGroups(new JsonReader(new InputStreamReader(inputStream, "UTF-8")), result);
readPlaylistGroups(
new JsonReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)),
result);
} catch (Exception e) {
Log.e(TAG, "Error loading sample list: " + uri, e);
sawError = true;
@ -336,12 +324,12 @@ public class SampleChooserActivity extends AppCompatActivity
DataSourceUtil.closeQuietly(dataSource);
}
}
return result;
}
@Override
protected void onPostExecute(List<PlaylistGroup> result) {
new Handler(Looper.getMainLooper())
.post(
() -> {
onPlaylistGroups(result, sawError);
});
});
}
private void readPlaylistGroups(JsonReader reader, List<PlaylistGroup> groups)
@ -385,6 +373,7 @@ public class SampleChooserActivity extends AppCompatActivity
group.playlists.addAll(playlistHolders);
}
@OptIn(markerClass = UnstableApi.class) // Setting image duration.
private PlaylistHolder readEntry(JsonReader reader, boolean insidePlaylist) throws IOException {
Uri uri = null;
String extension = null;
@ -422,6 +411,9 @@ public class SampleChooserActivity extends AppCompatActivity
case "clip_end_position_ms":
clippingConfiguration.setEndPositionMs(reader.nextLong());
break;
case "image_duration_ms":
mediaItem.setImageDurationMs(reader.nextLong());
break;
case "ad_tag_uri":
mediaItem.setAdsConfiguration(
new MediaItem.AdsConfiguration.Builder(Uri.parse(reader.nextString())).build());
@ -674,7 +666,6 @@ public class SampleChooserActivity extends AppCompatActivity
@RequiresApi(33)
private static class Api33 {
@DoNotInline
public static String getPostNotificationPermissionString() {
return Manifest.permission.POST_NOTIFICATIONS;
}

View File

@ -67,7 +67,8 @@ public final class TrackSelectionDialog extends DialogFragment {
}
public static final ImmutableList<Integer> SUPPORTED_TRACK_TYPES =
ImmutableList.of(C.TRACK_TYPE_VIDEO, C.TRACK_TYPE_AUDIO, C.TRACK_TYPE_TEXT);
ImmutableList.of(
C.TRACK_TYPE_VIDEO, C.TRACK_TYPE_AUDIO, C.TRACK_TYPE_TEXT, C.TRACK_TYPE_IMAGE);
private final SparseArray<TrackSelectionViewFragment> tabFragments;
private final ArrayList<Integer> tabTrackTypes;
@ -266,11 +267,13 @@ public final class TrackSelectionDialog extends DialogFragment {
private static String getTrackTypeString(Resources resources, @C.TrackType int trackType) {
switch (trackType) {
case C.TRACK_TYPE_VIDEO:
return resources.getString(R.string.exo_track_selection_title_video);
return resources.getString(R.string.track_selection_title_video);
case C.TRACK_TYPE_AUDIO:
return resources.getString(R.string.exo_track_selection_title_audio);
return resources.getString(R.string.track_selection_title_audio);
case C.TRACK_TYPE_TEXT:
return resources.getString(R.string.exo_track_selection_title_text);
return resources.getString(R.string.track_selection_title_text);
case C.TRACK_TYPE_IMAGE:
return resources.getString(R.string.track_selection_title_image);
default:
throw new IllegalArgumentException();
}

View File

@ -25,8 +25,6 @@
<string name="error_generic">Playback failed</string>
<string name="error_drm_unsupported_before_api_18">DRM content not supported on API levels below 18</string>
<string name="error_drm_unsupported_scheme">This device does not support the required DRM scheme</string>
<string name="error_no_decoder">This device does not provide a decoder for <xliff:g id="mime_type">%1$s</xliff:g></string>
@ -59,6 +57,16 @@
<string name="download_ads_unsupported">IMA does not support offline ads</string>
<string name="download_only_widevine_drm_supported">This demo app only supports downloading unencrypted or Widevine DRM content</string>
<string name="prefer_extension_decoders">Prefer extension decoders</string>
<string name="track_selection_title_video">Video</string>
<string name="track_selection_title_audio">Audio</string>
<string name="track_selection_title_text">Text</string>
<string name="track_selection_title_image">Image</string>
</resources>

View File

@ -16,7 +16,9 @@ apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
android {
compileSdkVersion project.ext.compileSdkVersion
namespace 'androidx.media3.demo.session'
compileSdk project.ext.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
@ -32,7 +34,6 @@ android {
versionCode project.ext.releaseVersionCode
minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.appTargetSdkVersion
multiDexEnabled true
}
buildTypes {
@ -57,13 +58,15 @@ android {
}
dependencies {
// For detecting and debugging leaks only. LeakCanary is not needed for demo app to work.
debugImplementation 'com.squareup.leakcanary:leakcanary-android:' + leakCanaryVersion
implementation 'androidx.core:core-ktx:' + androidxCoreVersion
implementation 'androidx.lifecycle:lifecycle-common:' + androidxLifecycleVersion
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:' + androidxLifecycleVersion
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
implementation 'com.google.android.material:material:' + androidxMaterialVersion
implementation project(modulePrefix + 'lib-exoplayer')
implementation project(modulePrefix + 'lib-exoplayer-dash')
implementation project(modulePrefix + 'lib-exoplayer-hls')
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-guava:' + kotlinxCoroutinesVersion
implementation project(modulePrefix + 'lib-ui')
implementation project(modulePrefix + 'lib-session')
implementation project(modulePrefix + 'demo-session-service')
}

View File

@ -14,20 +14,23 @@
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="androidx.media3.demo.session">
<uses-sdk/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<application
android:name="androidx.multidex.MultiDexApplication"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.Media3Demo"
tools:replace="android:name">
android:theme="@style/Theme.Media3Demo">
<!-- Declare that this session demo supports Android Auto. -->
<meta-data
android:name="com.google.android.gms.car.application"
android:resource="@xml/auto_app_desc" />
<activity
android:name=".MainActivity"
@ -40,7 +43,9 @@
<activity
android:name=".PlayerActivity"
android:exported="true"/>
android:exported="true"
android:launchMode="singleTop"
android:theme="@style/Theme.AppCompat.NoActionBar"/>
<activity
android:name=".PlayableFolderActivity"
@ -51,8 +56,9 @@
android:foregroundServiceType="mediaPlayback"
android:exported="true">
<intent-filter>
<action android:name="androidx.media3.session.MediaSessionService"/>
<action android:name="androidx.media3.session.MediaLibraryService"/>
<action android:name="android.media.browse.MediaBrowserService"/>
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
</intent-filter>
</service>

View File

@ -15,9 +15,11 @@
*/
package androidx.media3.demo.session
import android.Manifest
import android.content.ComponentName
import android.content.Context
import android.content.Intent
import android.content.pm.PackageManager
import android.os.Build
import android.os.Bundle
import android.view.LayoutInflater
import android.view.MenuItem
@ -26,6 +28,7 @@ import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.ListView
import android.widget.TextView
import android.widget.Toast
import androidx.activity.OnBackPressedCallback
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
@ -70,18 +73,26 @@ class MainActivity : AppCompatActivity() {
findViewById<ExtendedFloatingActionButton>(R.id.open_player_floating_button)
.setOnClickListener {
// display the playing media items
val intent = Intent(this, PlayerActivity::class.java)
startActivity(intent)
// Start the session activity that shows the playback activity. The System UI uses the same
// intent in the same way to start the activity from the notification.
browser?.sessionActivity?.send()
}
onBackPressedDispatcher.addCallback(
object : OnBackPressedCallback(/* enabled= */ true) {
object : OnBackPressedCallback(true) {
override fun handleOnBackPressed() {
popPathStack()
}
}
)
if (
Build.VERSION.SDK_INT >= 33 &&
checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) !=
PackageManager.PERMISSION_GRANTED
) {
requestPermissions(arrayOf(Manifest.permission.POST_NOTIFICATIONS), /* requestCode= */ 0)
}
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
@ -102,6 +113,23 @@ class MainActivity : AppCompatActivity() {
super.onStop()
}
override fun onRequestPermissionsResult(
requestCode: Int,
permissions: Array<out String>,
grantResults: IntArray
) {
super.onRequestPermissionsResult(requestCode, permissions, grantResults)
if (grantResults.isEmpty()) {
// Empty results are triggered if a permission is requested while another request was already
// pending and can be safely ignored in this case.
return
}
if (grantResults[0] != PackageManager.PERMISSION_GRANTED) {
Toast.makeText(applicationContext, R.string.notification_permission_denied, Toast.LENGTH_LONG)
.show()
}
}
private fun initializeBrowser() {
browserFuture =
MediaBrowser.Builder(

View File

@ -43,7 +43,7 @@ import com.google.common.util.concurrent.ListenableFuture
class PlayableFolderActivity : AppCompatActivity() {
private lateinit var browserFuture: ListenableFuture<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 mediaList: ListView
private lateinit var mediaListAdapter: PlayableMediaItemArrayAdapter
@ -51,6 +51,7 @@ class PlayableFolderActivity : AppCompatActivity() {
companion object {
private const val MEDIA_ITEM_ID_KEY = "MEDIA_ITEM_ID_KEY"
fun createIntent(context: Context, mediaItemID: String): Intent {
val intent = Intent(context, PlayableFolderActivity::class.java)
intent.putExtra(MEDIA_ITEM_ID_KEY, mediaItemID)
@ -77,8 +78,7 @@ class PlayableFolderActivity : AppCompatActivity() {
browser.shuffleModeEnabled = false
browser.prepare()
browser.play()
val intent = Intent(this, PlayerActivity::class.java)
startActivity(intent)
browser.sessionActivity?.send()
}
}
@ -88,8 +88,7 @@ class PlayableFolderActivity : AppCompatActivity() {
browser.shuffleModeEnabled = true
browser.prepare()
browser.play()
val intent = Intent(this, PlayerActivity::class.java)
startActivity(intent)
browser.sessionActivity?.send()
}
findViewById<Button>(R.id.play_button).setOnClickListener {
@ -104,9 +103,9 @@ class PlayableFolderActivity : AppCompatActivity() {
findViewById<ExtendedFloatingActionButton>(R.id.open_player_floating_button)
.setOnClickListener {
// display the playing media items
val intent = Intent(this, PlayerActivity::class.java)
startActivity(intent)
// Start the session activity that shows the playback activity. The System UI uses the same
// intent in the same way to start the activity from the notification.
browser?.sessionActivity?.send()
}
}

View File

@ -1,319 +1,32 @@
/*
* 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.demo.session
import android.annotation.SuppressLint
import android.app.NotificationChannel
import android.app.NotificationManager
import android.app.PendingIntent.*
import android.app.TaskStackBuilder
import android.app.PendingIntent
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
import android.app.PendingIntent.getActivity
import android.content.Intent
import android.os.Build
import android.os.Bundle
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.media3.common.AudioAttributes
import androidx.media3.common.MediaItem
import androidx.media3.common.util.Util
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.session.*
import androidx.media3.session.LibraryResult.RESULT_ERROR_NOT_SUPPORTED
import androidx.media3.session.MediaSession.ControllerInfo
import com.google.common.collect.ImmutableList
import com.google.common.util.concurrent.Futures
import com.google.common.util.concurrent.ListenableFuture
import androidx.core.app.TaskStackBuilder
class PlaybackService : MediaLibraryService() {
private val librarySessionCallback = CustomMediaLibrarySessionCallback()
private lateinit var player: ExoPlayer
private lateinit var mediaLibrarySession: MediaLibrarySession
private lateinit var customCommands: List<CommandButton>
private var customLayout = ImmutableList.of<CommandButton>()
class PlaybackService : DemoPlaybackService() {
companion object {
private const val SEARCH_QUERY_PREFIX_COMPAT = "androidx://media3-session/playFromSearch"
private const val SEARCH_QUERY_PREFIX = "androidx://media3-session/setMediaUri"
private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON =
"android.media3.session.demo.SHUFFLE_ON"
private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF =
"android.media3.session.demo.SHUFFLE_OFF"
private const val NOTIFICATION_ID = 123
private const val CHANNEL_ID = "demo_session_notification_channel_id"
private val immutableFlag = if (Build.VERSION.SDK_INT >= 23) PendingIntent.FLAG_IMMUTABLE else 0
}
override fun onCreate() {
super.onCreate()
customCommands =
listOf(
getShuffleCommandButton(
SessionCommand(CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON, Bundle.EMPTY)
),
getShuffleCommandButton(
SessionCommand(CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF, Bundle.EMPTY)
)
)
customLayout = ImmutableList.of(customCommands[0])
initializeSessionAndPlayer()
setListener(MediaSessionServiceListener())
}
override fun onGetSession(controllerInfo: ControllerInfo): MediaLibrarySession {
return mediaLibrarySession
}
override fun onTaskRemoved(rootIntent: Intent?) {
if (!player.playWhenReady) {
stopSelf()
}
}
override fun onDestroy() {
player.release()
mediaLibrarySession.release()
clearListener()
super.onDestroy()
}
private inner class CustomMediaLibrarySessionCallback : MediaLibrarySession.Callback {
override fun onConnect(
session: MediaSession,
controller: ControllerInfo
): MediaSession.ConnectionResult {
val connectionResult = super.onConnect(session, controller)
val availableSessionCommands = connectionResult.availableSessionCommands.buildUpon()
for (commandButton in customCommands) {
// Add custom command to available session commands.
commandButton.sessionCommand?.let { availableSessionCommands.add(it) }
}
return MediaSession.ConnectionResult.accept(
availableSessionCommands.build(),
connectionResult.availablePlayerCommands
override fun getSingleTopActivity(): PendingIntent? {
return getActivity(
this,
0,
Intent(this, PlayerActivity::class.java),
immutableFlag or FLAG_UPDATE_CURRENT
)
}
override fun onPostConnect(session: MediaSession, controller: ControllerInfo) {
if (!customLayout.isEmpty() && controller.controllerVersion != 0) {
// Let Media3 controller (for instance the MediaNotificationProvider) know about the custom
// layout right after it connected.
ignoreFuture(mediaLibrarySession.setCustomLayout(controller, customLayout))
}
}
override fun onCustomCommand(
session: MediaSession,
controller: ControllerInfo,
customCommand: SessionCommand,
args: Bundle
): ListenableFuture<SessionResult> {
if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON == customCommand.customAction) {
// Enable shuffling.
player.shuffleModeEnabled = true
// Change the custom layout to contain the `Disable shuffling` command.
customLayout = ImmutableList.of(customCommands[1])
// Send the updated custom layout to controllers.
session.setCustomLayout(customLayout)
} else if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF == customCommand.customAction) {
// Disable shuffling.
player.shuffleModeEnabled = false
// Change the custom layout to contain the `Enable shuffling` command.
customLayout = ImmutableList.of(customCommands[0])
// Send the updated custom layout to controllers.
session.setCustomLayout(customLayout)
}
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
}
override fun onGetLibraryRoot(
session: MediaLibrarySession,
browser: ControllerInfo,
params: LibraryParams?
): ListenableFuture<LibraryResult<MediaItem>> {
if (params != null && params.isRecent) {
// The service currently does not support playback resumption. Tell System UI by returning
// an error of type 'RESULT_ERROR_NOT_SUPPORTED' for a `params.isRecent` request. See
// https://github.com/androidx/media/issues/355
return Futures.immediateFuture(LibraryResult.ofError(RESULT_ERROR_NOT_SUPPORTED))
}
return Futures.immediateFuture(LibraryResult.ofItem(MediaItemTree.getRootItem(), params))
}
override fun onGetItem(
session: MediaLibrarySession,
browser: ControllerInfo,
mediaId: String
): ListenableFuture<LibraryResult<MediaItem>> {
val item =
MediaItemTree.getItem(mediaId)
?: return Futures.immediateFuture(
LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE)
)
return Futures.immediateFuture(LibraryResult.ofItem(item, /* params= */ null))
}
override fun onSubscribe(
session: MediaLibrarySession,
browser: ControllerInfo,
parentId: String,
params: LibraryParams?
): ListenableFuture<LibraryResult<Void>> {
val children =
MediaItemTree.getChildren(parentId)
?: return Futures.immediateFuture(
LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE)
)
session.notifyChildrenChanged(browser, parentId, children.size, params)
return Futures.immediateFuture(LibraryResult.ofVoid())
}
override fun onGetChildren(
session: MediaLibrarySession,
browser: ControllerInfo,
parentId: String,
page: Int,
pageSize: Int,
params: LibraryParams?
): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
val children =
MediaItemTree.getChildren(parentId)
?: return Futures.immediateFuture(
LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE)
)
return Futures.immediateFuture(LibraryResult.ofItemList(children, params))
}
override fun onAddMediaItems(
mediaSession: MediaSession,
controller: MediaSession.ControllerInfo,
mediaItems: List<MediaItem>
): ListenableFuture<List<MediaItem>> {
val updatedMediaItems: List<MediaItem> =
mediaItems.map { mediaItem ->
if (mediaItem.requestMetadata.searchQuery != null)
getMediaItemFromSearchQuery(mediaItem.requestMetadata.searchQuery!!)
else MediaItemTree.getItem(mediaItem.mediaId) ?: mediaItem
}
return Futures.immediateFuture(updatedMediaItems)
}
private fun getMediaItemFromSearchQuery(query: String): MediaItem {
// Only accept query with pattern "play [Title]" or "[Title]"
// Where [Title]: must be exactly matched
// If no media with exact name found, play a random media instead
val mediaTitle =
if (query.startsWith("play ", ignoreCase = true)) {
query.drop(5)
} else {
query
}
return MediaItemTree.getItemFromTitle(mediaTitle) ?: MediaItemTree.getRandomItem()
}
}
private fun initializeSessionAndPlayer() {
player =
ExoPlayer.Builder(this)
.setAudioAttributes(AudioAttributes.DEFAULT, /* handleAudioFocus= */ true)
.build()
MediaItemTree.initialize(assets)
val sessionActivityPendingIntent =
TaskStackBuilder.create(this).run {
override fun getBackStackedActivity(): PendingIntent? {
return TaskStackBuilder.create(this).run {
addNextIntent(Intent(this@PlaybackService, MainActivity::class.java))
addNextIntent(Intent(this@PlaybackService, PlayerActivity::class.java))
val immutableFlag = if (Build.VERSION.SDK_INT >= 23) FLAG_IMMUTABLE else 0
getPendingIntent(0, immutableFlag or FLAG_UPDATE_CURRENT)
}
mediaLibrarySession =
MediaLibrarySession.Builder(this, player, librarySessionCallback)
.setSessionActivity(sessionActivityPendingIntent)
.build()
if (!customLayout.isEmpty()) {
// Send custom layout to legacy session.
mediaLibrarySession.setCustomLayout(customLayout)
}
}
private fun getShuffleCommandButton(sessionCommand: SessionCommand): CommandButton {
val isOn = sessionCommand.customAction == CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON
return CommandButton.Builder()
.setDisplayName(
getString(
if (isOn) R.string.exo_controls_shuffle_on_description
else R.string.exo_controls_shuffle_off_description
)
)
.setSessionCommand(sessionCommand)
.setIconResId(if (isOn) R.drawable.exo_icon_shuffle_off else R.drawable.exo_icon_shuffle_on)
.build()
}
private fun ignoreFuture(customLayout: ListenableFuture<SessionResult>) {
/* 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.
*/
@SuppressLint("MissingPermission") // TODO: b/280766358 - Request this permission at runtime.
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)
}
}

View File

@ -18,181 +18,163 @@ package androidx.media3.demo.session
import android.content.ComponentName
import android.content.Context
import android.os.Bundle
import android.util.Log
import android.view.LayoutInflater
import android.view.MenuItem
import android.view.View
import android.view.ViewGroup
import android.widget.ArrayAdapter
import android.widget.Button
import android.widget.ImageView
import android.widget.ListView
import android.widget.TextView
import androidx.annotation.OptIn
import androidx.appcompat.app.AppCompatActivity
import androidx.core.content.ContextCompat
import androidx.lifecycle.Lifecycle
import androidx.lifecycle.lifecycleScope
import androidx.lifecycle.repeatOnLifecycle
import androidx.media3.common.C.TRACK_TYPE_TEXT
import androidx.media3.common.MediaItem
import androidx.media3.common.MediaMetadata
import androidx.media3.common.Player
import androidx.media3.common.Tracks
import androidx.media3.common.Player.EVENT_MEDIA_ITEM_TRANSITION
import androidx.media3.common.Player.EVENT_MEDIA_METADATA_CHANGED
import androidx.media3.common.Player.EVENT_TIMELINE_CHANGED
import androidx.media3.common.Player.EVENT_TRACKS_CHANGED
import androidx.media3.common.util.UnstableApi
import androidx.media3.session.MediaController
import androidx.media3.session.SessionToken
import androidx.media3.ui.PlayerView
import com.google.common.util.concurrent.ListenableFuture
import com.google.common.util.concurrent.MoreExecutors
import kotlinx.coroutines.awaitCancellation
import kotlinx.coroutines.guava.await
import kotlinx.coroutines.launch
private const val TAG = "PlayerActivity"
class PlayerActivity : AppCompatActivity() {
private lateinit var controllerFuture: ListenableFuture<MediaController>
private val controller: MediaController?
get() = if (controllerFuture.isDone) controllerFuture.get() else null
private lateinit var controller: MediaController
private lateinit var playerView: PlayerView
private lateinit var mediaList: ListView
private lateinit var mediaListAdapter: PlayingMediaItemArrayAdapter
private val subItemMediaList: MutableList<MediaItem> = mutableListOf()
private lateinit var mediaItemListView: ListView
private lateinit var mediaItemListAdapter: MediaItemListAdapter
private val mediaItemList: MutableList<MediaItem> = mutableListOf()
private var lastMediaItemId: String? = null
@OptIn(UnstableApi::class) // PlayerView.hideController
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
setContentView(R.layout.activity_player)
playerView = findViewById(R.id.player_view)
mediaList = findViewById(R.id.current_playing_list)
mediaListAdapter = PlayingMediaItemArrayAdapter(this, R.layout.folder_items, subItemMediaList)
mediaList.adapter = mediaListAdapter
mediaList.setOnItemClickListener { _, _, position, _ ->
run {
val controller = this.controller ?: return@run
controller.seekToDefaultPosition(/* windowIndex= */ position)
mediaListAdapter.notifyDataSetChanged()
}
}
findViewById<ImageView>(R.id.shuffle_switch).setOnClickListener {
val controller = this.controller ?: return@setOnClickListener
controller.shuffleModeEnabled = !controller.shuffleModeEnabled
}
findViewById<ImageView>(R.id.repeat_switch).setOnClickListener {
val controller = this.controller ?: return@setOnClickListener
when (controller.repeatMode) {
Player.REPEAT_MODE_ALL -> controller.repeatMode = Player.REPEAT_MODE_OFF
Player.REPEAT_MODE_OFF -> controller.repeatMode = Player.REPEAT_MODE_ONE
Player.REPEAT_MODE_ONE -> controller.repeatMode = Player.REPEAT_MODE_ALL
}
}
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
}
override fun onStart() {
super.onStart()
lifecycleScope.launch {
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
try {
initializeController()
}
override fun onStop() {
super.onStop()
awaitCancellation()
} finally {
playerView.player = null
releaseController()
}
override fun onOptionsItemSelected(item: MenuItem): Boolean {
if (item.itemId == android.R.id.home) {
onBackPressed()
return true
}
return super.onOptionsItemSelected(item)
}
private fun initializeController() {
setContentView(R.layout.activity_player)
playerView = findViewById(R.id.player_view)
mediaItemListView = findViewById(R.id.current_playing_list)
mediaItemListAdapter = MediaItemListAdapter(this, R.layout.folder_items, mediaItemList)
mediaItemListView.adapter = mediaItemListAdapter
mediaItemListView.setOnItemClickListener { _, _, position, _ ->
run {
if (controller.currentMediaItemIndex == position) {
controller.playWhenReady = !controller.playWhenReady
if (controller.playWhenReady) {
playerView.hideController()
}
} else {
controller.seekToDefaultPosition(/* mediaItemIndex= */ position)
mediaItemListAdapter.notifyDataSetChanged()
}
}
}
}
private suspend fun initializeController() {
controllerFuture =
MediaController.Builder(
this,
SessionToken(this, ComponentName(this, PlaybackService::class.java))
SessionToken(this, ComponentName(this, PlaybackService::class.java)),
)
.buildAsync()
controllerFuture.addListener({ setController() }, MoreExecutors.directExecutor())
updateMediaMetadataUI()
setController()
}
private fun releaseController() {
MediaController.releaseFuture(controllerFuture)
}
private fun setController() {
val controller = this.controller ?: return
@OptIn(UnstableApi::class) // PlayerView.setShowSubtitleButton
private suspend fun setController() {
try {
controller = controllerFuture.await()
} catch (t: Throwable) {
Log.w(TAG, "Failed to connect to MediaController", t)
return
}
playerView.player = controller
updateCurrentPlaylistUI()
updateMediaMetadataUI(controller.mediaMetadata)
updateShuffleSwitchUI(controller.shuffleModeEnabled)
updateRepeatSwitchUI(controller.repeatMode)
updateMediaMetadataUI()
playerView.setShowSubtitleButton(controller.currentTracks.isTypeSupported(TRACK_TYPE_TEXT))
controller.addListener(
object : Player.Listener {
override fun onMediaItemTransition(mediaItem: MediaItem?, reason: Int) {
updateMediaMetadataUI(mediaItem?.mediaMetadata ?: MediaMetadata.EMPTY)
override fun onEvents(player: Player, events: Player.Events) {
if (events.contains(EVENT_TRACKS_CHANGED)) {
playerView.setShowSubtitleButton(player.currentTracks.isTypeSupported(TRACK_TYPE_TEXT))
}
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
updateShuffleSwitchUI(shuffleModeEnabled)
if (events.contains(EVENT_TIMELINE_CHANGED)) {
updateCurrentPlaylistUI()
}
override fun onRepeatModeChanged(repeatMode: Int) {
updateRepeatSwitchUI(repeatMode)
if (events.contains(EVENT_MEDIA_METADATA_CHANGED)) {
updateMediaMetadataUI()
}
if (events.contains(EVENT_MEDIA_ITEM_TRANSITION)) {
// Trigger adapter update to change highlight of current item.
mediaItemListAdapter.notifyDataSetChanged()
}
override fun onTracksChanged(tracks: Tracks) {
playerView.setShowSubtitleButton(tracks.isTypeSupported(TRACK_TYPE_TEXT))
}
}
)
}
private fun updateShuffleSwitchUI(shuffleModeEnabled: Boolean) {
val resId =
if (shuffleModeEnabled) R.drawable.exo_styled_controls_shuffle_on
else R.drawable.exo_styled_controls_shuffle_off
findViewById<ImageView>(R.id.shuffle_switch)
.setImageDrawable(ContextCompat.getDrawable(this, resId))
private fun updateMediaMetadataUI() {
if (!::controller.isInitialized || controller.mediaItemCount == 0) {
findViewById<TextView>(R.id.media_title).text = getString(R.string.waiting_for_metadata)
findViewById<TextView>(R.id.media_artist).text = ""
return
}
private fun updateRepeatSwitchUI(repeatMode: Int) {
val resId: Int =
when (repeatMode) {
Player.REPEAT_MODE_OFF -> R.drawable.exo_styled_controls_repeat_off
Player.REPEAT_MODE_ONE -> R.drawable.exo_styled_controls_repeat_one
Player.REPEAT_MODE_ALL -> R.drawable.exo_styled_controls_repeat_all
else -> R.drawable.exo_styled_controls_repeat_off
}
findViewById<ImageView>(R.id.repeat_switch)
.setImageDrawable(ContextCompat.getDrawable(this, resId))
}
val mediaMetadata = controller.mediaMetadata
val title: CharSequence = mediaMetadata.title ?: ""
private fun updateMediaMetadataUI(mediaMetadata: MediaMetadata) {
val title: CharSequence = mediaMetadata.title ?: getString(R.string.no_item_prompt)
findViewById<TextView>(R.id.video_title).text = title
findViewById<TextView>(R.id.video_album).text = mediaMetadata.albumTitle
findViewById<TextView>(R.id.video_artist).text = mediaMetadata.artist
findViewById<TextView>(R.id.video_genre).text = mediaMetadata.genre
// Trick to update playlist UI
mediaListAdapter.notifyDataSetChanged()
findViewById<TextView>(R.id.media_title).text = title
findViewById<TextView>(R.id.media_artist).text = mediaMetadata.artist
}
private fun updateCurrentPlaylistUI() {
val controller = this.controller ?: return
subItemMediaList.clear()
for (i in 0 until controller.mediaItemCount) {
subItemMediaList.add(controller.getMediaItemAt(i))
if (!::controller.isInitialized) {
return
}
mediaListAdapter.notifyDataSetChanged()
mediaItemList.clear()
for (i in 0 until controller.mediaItemCount) {
mediaItemList.add(controller.getMediaItemAt(i))
}
mediaItemListAdapter.notifyDataSetChanged()
}
private inner class PlayingMediaItemArrayAdapter(
private inner class MediaItemListAdapter(
context: Context,
viewID: Int,
mediaItemList: List<MediaItem>
mediaItemList: List<MediaItem>,
) : ArrayAdapter<MediaItem>(context, viewID, mediaItemList) {
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
val mediaItem = getItem(position)!!
@ -201,23 +183,30 @@ class PlayerActivity : AppCompatActivity() {
returnConvertView.findViewById<TextView>(R.id.media_item).text = mediaItem.mediaMetadata.title
if (position == controller?.currentMediaItemIndex) {
returnConvertView.setBackgroundColor(ContextCompat.getColor(context, R.color.white))
returnConvertView
.findViewById<TextView>(R.id.media_item)
.setTextColor(ContextCompat.getColor(context, R.color.black))
} else {
returnConvertView.setBackgroundColor(ContextCompat.getColor(context, R.color.black))
val deleteButton = returnConvertView.findViewById<Button>(R.id.delete_button)
if (::controller.isInitialized && position == controller.currentMediaItemIndex) {
// Styles for the current media item list item.
returnConvertView.setBackgroundColor(
ContextCompat.getColor(context, R.color.playlist_item_background)
)
returnConvertView
.findViewById<TextView>(R.id.media_item)
.setTextColor(ContextCompat.getColor(context, R.color.white))
}
returnConvertView.findViewById<Button>(R.id.delete_button).setOnClickListener {
val controller = this@PlayerActivity.controller ?: return@setOnClickListener
deleteButton.visibility = View.GONE
} else {
// Styles for any other media item list item.
returnConvertView.setBackgroundColor(
ContextCompat.getColor(context, R.color.player_background)
)
returnConvertView
.findViewById<TextView>(R.id.media_item)
.setTextColor(ContextCompat.getColor(context, R.color.white))
deleteButton.visibility = View.VISIBLE
deleteButton.setOnClickListener {
controller.removeMediaItem(position)
updateCurrentPlaylistUI()
}
}
return returnConvertView
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 40 KiB

View File

@ -19,86 +19,52 @@
android:orientation="vertical"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:background="@color/black"
android:background="@color/player_background"
tools:context=".PlayerActivity">
<androidx.media3.ui.AspectRatioFrameLayout
android:layout_height="300dp"
android:layout_width="match_parent"
>
<androidx.media3.ui.PlayerView
android:id="@+id/player_view"
android:background="@color/player_background"
android:layout_width="match_parent"
android:layout_height="match_parent"
app:use_artwork="true" />
</androidx.media3.ui.AspectRatioFrameLayout>
android:layout_height="300dp"
app:artwork_display_mode="fill"
app:default_artwork="@drawable/artwork_placeholder"
app:repeat_toggle_modes="one|all"
app:show_shuffle_button="true"
app:shutter_background_color="@color/player_background" />
<TextView
android:id="@+id/video_title"
android:id="@+id/media_artist"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="10dp"
android:paddingStart="10dp"
android:paddingTop="10dp"
android:textColor="@color/white"
android:textSize="14sp"/>
<TextView
android:id="@+id/media_title"
android:ellipsize="end"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:maxLines="1"
android:paddingLeft="10dp"
android:paddingBottom="10dp"
android:paddingStart="10dp"
android:textColor="@color/white"
android:textSize="20sp" />
<TextView
android:id="@+id/video_album"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:paddingLeft="10dp"
android:paddingBottom="10dp"
android:textColor="@color/white"
android:textSize="20sp" />
<TextView
android:id="@+id/video_artist"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:paddingLeft="10dp"
android:paddingBottom="10dp"/>
<TextView
android:id="@+id/video_genre"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:textColor="@color/white"
android:paddingLeft="10dp"
android:paddingBottom="10dp"
android:textSize="12sp" />
<LinearLayout
android:gravity="center_vertical"
android:layout_width="match_parent"
android:layout_height="wrap_content">
<ImageView
android:id="@+id/shuffle_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:contentDescription="@string/shuffle"
android:src="@drawable/exo_styled_controls_shuffle_off"
android:textColor="@color/white" />
<ImageView
android:layout_margin="@dimen/exo_icon_horizontal_margin"
android:id="@+id/repeat_switch"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:src="@drawable/exo_styled_controls_repeat_off"
android:textColor="@color/white"
android:contentDescription="@string/repeat"
/>
</LinearLayout>
<View
android:background="@color/divider"
android:layout_height="1dp"
android:layout_width="match_parent" />
<ListView
android:id="@+id/current_playing_list"
android:layout_width="match_parent"
android:divider="@drawable/divider"
android:dividerHeight="1px"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</LinearLayout>

View File

@ -25,6 +25,8 @@
android:layout_height="wrap_content"
android:gravity="center_vertical"
android:paddingStart="10dp"
android:maxLines="1"
android:ellipsize="end"
android:textColor="@color/white"
android:paddingEnd="10dp"
android:minHeight="50dp" />
@ -35,6 +37,7 @@
android:layout_width="50dp"
android:layout_height="match_parent"
android:id="@+id/delete_button"
android:backgroundTint="@color/playlist_item_foreground"
android:background="@drawable/baseline_playlist_remove_white_48"
/>

View File

@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources xmlns:tools="http://schemas.android.com/tools">
<resources>
<!-- Base application theme. -->
<style name="Theme.Media3Demo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
@ -25,9 +25,7 @@
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">
?attr/colorPrimaryVariant
</item>
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@ -22,4 +22,9 @@
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
<color name="grey">#FF999999</color>
<color name="background">#292929</color>
<color name="player_background">#1c1c1c</color>
<color name="playlist_item_background">#363434</color>
<color name="playlist_item_foreground">#635E5E</color>
<color name="divider">#646464</color>
</resources>

View File

@ -19,14 +19,8 @@
<string name="open_player_content_description">Click to view your playlist</string>
<string name="added_media_item_format">Added %1$s to playlist</string>
<string name="shuffle">Shuffle</string>
<string name="repeat">Repeat</string>
<string name="play_button">Play</string>
<string name="no_item_prompt">
"! No media in the play list !\nPlease try to add more from browser"
</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>
<string name="waiting_for_metadata">Connecting…</string>
<string name="notification_permission_denied">
"Without notification access the app can't warn about failed background operations"</string>
</resources>

View File

@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources xmlns:tools="http://schemas.android.com/tools">
<resources>
<!-- Base application theme. -->
<style name="Theme.Media3Demo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
@ -25,9 +25,7 @@
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">
?attr/colorPrimaryVariant
</item>
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@ -0,0 +1,18 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2023 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.
-->
<automotiveApp>
<uses name="media" />
</automotiveApp>

View File

@ -0,0 +1,7 @@
# Media3 Automotive session demo
This app demonstrates use of the `MediaLibraryService` for
[Android Automotive](https://developer.android.com/training/cars/media/automotive-os).
See the [demos README](../README.md) for instructions on how to build and run
this demo.

View File

@ -0,0 +1,65 @@
// Copyright 2023 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.
apply from: '../../constants.gradle'
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
android {
namespace 'androidx.media3.demo.session.automotive'
compileSdk project.ext.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
defaultConfig {
versionName project.ext.releaseVersion
versionCode project.ext.releaseVersionCode
minSdkVersion project.ext.automotiveMinSdkVersion
targetSdkVersion project.ext.appTargetSdkVersion
}
buildTypes {
release {
shrinkResources true
minifyEnabled true
proguardFiles = [
getDefaultProguardFile('proguard-android-optimize.txt')
]
signingConfig signingConfigs.debug
}
debug {
jniDebuggable = true
}
}
lintOptions {
// The demo app isn't indexed, and doesn't have translations.
disable 'GoogleAppIndexingWarning','MissingTranslation'
}
}
dependencies {
implementation 'androidx.core:core-ktx:' + androidxCoreVersion
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
implementation 'com.google.android.material:material:' + androidxMaterialVersion
implementation project(modulePrefix + 'lib-session')
implementation project(modulePrefix + 'demo-session-service')
}

View File

@ -0,0 +1,69 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2023 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
http://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
package="androidx.media3.demo.session.automotive">
<uses-sdk/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE" />
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<uses-feature
android:name="android.hardware.type.automotive"
android:required="true" />
<uses-feature
android:name="android.hardware.wifi"
android:required="false" />
<uses-feature
android:name="android.hardware.screen.portrait"
android:required="false" />
<uses-feature
android:name="android.hardware.screen.landscape"
android:required="false" />
<meta-data android:name="com.android.automotive"
android:resource="@xml/automotive_app_desc"/>
<application
android:allowBackup="false"
android:taskAffinity=""
android:appCategory="audio"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name">
<meta-data
android:name="androidx.car.app.TintableAttributionIcon"
android:resource="@mipmap/ic_launcher" />
<service
android:name=".AutomotiveService"
android:foregroundServiceType="mediaPlayback"
android:exported="true">
<intent-filter>
<action android:name="androidx.media3.session.MediaLibraryService"/>
<action android:name="android.media.browse.MediaBrowserService"/>
</intent-filter>
</service>
<!-- Artwork provider for content:// URIs -->
<provider
android:name="BitmapContentProvider"
android:authorities="androidx.media3"
android:exported="true" />
</application>
</manifest>

Binary file not shown.

After

Width:  |  Height:  |  Size: 353 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 215 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 41 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 278 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 240 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 425 KiB

View File

@ -0,0 +1,472 @@
{
"media": [
{
"id": "wake_up_01",
"title": "Intro - The Way Of Waking Up (feat. Alan Watts)",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/01_-_Intro_-_The_Way_Of_Waking_Up_feat_Alan_Watts.mp3",
"image": "content://androidx.media3/artwork/album_kyoto_connection.png",
"trackNumber": 1,
"totalTrackCount": 13,
"duration": 90,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_02",
"title": "Geisha",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/02_-_Geisha.mp3",
"image": "content://androidx.media3/artwork/album_kyoto_connection.png",
"trackNumber": 2,
"totalTrackCount": 13,
"duration": 267,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_03",
"title": "Voyage I - Waterfall",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/03_-_Voyage_I_-_Waterfall.mp3",
"image": "content://androidx.media3/artwork/album_kyoto_connection.png",
"trackNumber": 3,
"totalTrackCount": 13,
"duration": 264,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_04",
"title": "The Music In You",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/04_-_The_Music_In_You.mp3",
"image": "content://androidx.media3/artwork/album_kyoto_connection.png",
"trackNumber": 4,
"totalTrackCount": 13,
"duration": 223,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_05",
"title": "The Calm Before The Storm",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/05_-_The_Calm_Before_The_Storm.mp3",
"image": "content://androidx.media3/artwork/album_kyoto_connection.png",
"trackNumber": 5,
"totalTrackCount": 13,
"duration": 229,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_06",
"title": "No Pain, No Gain",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/06_-_No_Pain_No_Gain.mp3",
"image": "content://androidx.media3/artwork/album_kyoto_connection.png",
"trackNumber": 6,
"totalTrackCount": 13,
"duration": 304,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_07",
"title": "Voyage II - Satori",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/07_-_Voyage_II_-_Satori.mp3",
"image": "content://androidx.media3/artwork/album_kyoto_connection.png",
"trackNumber": 7,
"totalTrackCount": 13,
"duration": 256,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_08",
"title": "Reveal the Magic",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/08_-_Reveal_the_Magic.mp3",
"image": "content://androidx.media3/artwork/album_kyoto_connection.png",
"trackNumber": 8,
"totalTrackCount": 13,
"duration": 293,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_09",
"title": "Hachiko (The Faithtful Dog)",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/09_-_Hachiko_The_Faithtful_Dog.mp3",
"image": "content://androidx.media3/artwork/album_kyoto_connection.png",
"trackNumber": 9,
"totalTrackCount": 13,
"duration": 185,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_10",
"title": "Wake Up",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/10_-_Wake_Up.mp3",
"image": "content://androidx.media3/artwork/album_kyoto_connection.png",
"trackNumber": 10,
"totalTrackCount": 13,
"duration": 251,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_11",
"title": "Voyage III - The Space Between Us",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/11_-_Voyage_III_-_The_Space_Between_Us.mp3",
"image": "content://androidx.media3/artwork/album_kyoto_connection.png",
"trackNumber": 11,
"totalTrackCount": 13,
"duration": 290,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_12",
"title": "Ume No Kaori (feat. Sunawai)",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/12_-_Ume_No_Kaori_feat_Sunawai.mp3",
"image": "content://androidx.media3/artwork/album_kyoto_connection.png",
"trackNumber": 12,
"totalTrackCount": 13,
"duration": 334,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "wake_up_13",
"title": "Outro - Totally Here and Now (feat. Alan Watts)",
"album": "Wake Up",
"artist": "The Kyoto Connection",
"genre": "Electronic",
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/13_-_Outro_-_Totally_Here_and_Now_feat_Alan_Watts.mp3",
"image": "content://androidx.media3/artwork/album_kyoto_connection.png",
"trackNumber": 13,
"totalTrackCount": 13,
"duration": 242,
"site": "http://freemusicarchive.org/music/The_Kyoto_Connection/Wake_Up_1957/"
},
{
"id": "irsens_tale_01",
"title": "Intro (.udonthear)",
"album": "Irsen's Tale",
"artist": "Kai Engel",
"genre": "Ambient",
"source": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/01_-_Intro_udonthear.mp3",
"image": "content://androidx.media3/artwork/album_kai_engel.png",
"trackNumber": 1,
"totalTrackCount": 9,
"duration": 63,
"site": "http://freemusicarchive.org/music/Kai_Engel/Irsens_Tale/"
},
{
"id": "irsens_tale_02",
"title": "Leaving",
"album": "Irsen's Tale",
"artist": "Kai Engel",
"genre": "Ambient",
"source": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/02_-_Leaving.mp3",
"image": "content://androidx.media3/artwork/album_kai_engel.png",
"trackNumber": 2,
"totalTrackCount": 9,
"duration": 170,
"site": "http://freemusicarchive.org/music/Kai_Engel/Irsens_Tale/"
},
{
"id": "irsens_tale_03",
"title": "Irsen's Tale",
"album": "Irsen's Tale",
"artist": "Kai Engel",
"genre": "Ambient",
"source": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/03_-_Irsens_Tale.mp3",
"image": "content://androidx.media3/artwork/album_kai_engel.png",
"trackNumber": 3,
"totalTrackCount": 9,
"duration": 164,
"site": "http://freemusicarchive.org/music/Kai_Engel/Irsens_Tale/"
},
{
"id": "irsens_tale_04",
"title": "Moonlight Reprise",
"album": "Irsen's Tale",
"artist": "Kai Engel",
"genre": "Ambient",
"source": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/04_-_Moonlight_Reprise.mp3",
"image": "content://androidx.media3/artwork/album_kai_engel.png",
"trackNumber": 4,
"totalTrackCount": 9,
"duration": 181,
"site": "http://freemusicarchive.org/music/Kai_Engel/Irsens_Tale/"
},
{
"id": "irsens_tale_05",
"title": "Nothing Lasts Forever",
"album": "Irsen's Tale",
"artist": "Kai Engel",
"genre": "Ambient",
"source": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/05_-_Nothing_Lasts_Forever.mp3",
"image": "content://androidx.media3/artwork/album_kai_engel.png",
"trackNumber": 5,
"totalTrackCount": 9,
"duration": 132,
"site": "http://freemusicarchive.org/music/Kai_Engel/Irsens_Tale/"
},
{
"id": "irsens_tale_06",
"title": "The Moments of Our Mornings",
"album": "Irsen's Tale",
"artist": "Kai Engel",
"genre": "Ambient",
"source": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/06_-_The_Moments_of_Our_Mornings.mp3",
"image": "content://androidx.media3/artwork/album_kai_engel.png",
"trackNumber": 6,
"totalTrackCount": 9,
"duration": 104,
"site": "http://freemusicarchive.org/music/Kai_Engel/Irsens_Tale/"
},
{
"id": "irsens_tale_07",
"title": "Laceration",
"album": "Irsen's Tale",
"artist": "Kai Engel",
"genre": "Ambient",
"source": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/07_-_Laceration.mp3",
"image": "content://androidx.media3/artwork/album_kai_engel.png",
"trackNumber": 7,
"totalTrackCount": 9,
"duration": 173,
"site": "http://freemusicarchive.org/music/Kai_Engel/Irsens_Tale/"
},
{
"id": "irsens_tale_08",
"title": "Memories",
"album": "Irsen's Tale",
"artist": "Kai Engel",
"genre": "Ambient",
"source": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/08_-_Memories.mp3",
"image": "content://androidx.media3/artwork/album_kai_engel.png",
"trackNumber": 8,
"totalTrackCount": 9,
"duration": 213,
"site": "http://freemusicarchive.org/music/Kai_Engel/Irsens_Tale/"
},
{
"id": "irsens_tale_09",
"title": "Outro",
"album": "Irsen's Tale",
"artist": "Kai Engel",
"genre": "Ambient",
"source": "https://storage.googleapis.com/uamp/Kai_Engel_-_Irsens_Tale/09_-_Outro.mp3",
"image": "content://androidx.media3/artwork/album_kai_engel.png",
"trackNumber": 9,
"totalTrackCount": 9,
"duration": 65,
"site": "http://freemusicarchive.org/music/Kai_Engel/Irsens_Tale/"
},
{
"id": "jazz_in_paris",
"title": "Jazz in Paris",
"album": "Jazz & Blues",
"artist": "Media Right Productions",
"genre": "Jazz & Blues",
"source": "https://storage.googleapis.com/automotive-media/Jazz_In_Paris.mp3",
"image": "content://androidx.media3/artwork/album_sea.png",
"trackNumber": 1,
"totalTrackCount": 6,
"duration": 103,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "the_messenger",
"title": "The Messenger",
"album": "Jazz & Blues",
"artist": "Silent Partner",
"genre": "Jazz & Blues",
"source": "https://storage.googleapis.com/automotive-media/The_Messenger.mp3",
"image": "content://androidx.media3/artwork/album_sea.png",
"trackNumber": 2,
"totalTrackCount": 6,
"duration": 132,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "talkies",
"title": "Talkies",
"album": "Jazz & Blues",
"artist": "Huma-Huma",
"genre": "Jazz & Blues",
"source": "https://storage.googleapis.com/automotive-media/Talkies.mp3",
"image": "content://androidx.media3/artwork/album_sea.png",
"trackNumber": 3,
"totalTrackCount": 6,
"duration": 162,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "on_the_bach",
"title": "On the Bach",
"album": "Cinematic",
"artist": "Jingle Punks",
"genre": "Cinematic",
"source": "https://storage.googleapis.com/automotive-media/On_the_Bach.mp3",
"image": "content://androidx.media3/artwork/album_drinks.png",
"trackNumber": 4,
"totalTrackCount": 6,
"duration": 66,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "the_story_unfolds",
"title": "The Story Unfolds",
"album": "Cinematic",
"artist": "Jingle Punks",
"genre": "Cinematic",
"source": "https://storage.googleapis.com/automotive-media/The_Story_Unfolds.mp3",
"image": "content://androidx.media3/artwork/album_drinks.png",
"trackNumber": 5,
"totalTrackCount": 6,
"duration": 91,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "drop_and_roll",
"title": "Drop and Roll",
"album": "Youtube Audio Library Rock",
"artist": "Silent Partner",
"genre": "Rock",
"source": "https://storage.googleapis.com/automotive-media/Drop_and_Roll.mp3",
"image": "content://androidx.media3/artwork/album_road.png",
"trackNumber": 1,
"totalTrackCount": 7,
"duration": 121,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "motocross",
"title": "Motocross",
"album": "Youtube Audio Library Rock",
"artist": "Topher Mohr and Alex Elena",
"genre": "Rock",
"source": "https://storage.googleapis.com/automotive-media/Motocross.mp3",
"image": "content://androidx.media3/artwork/album_road.png",
"trackNumber": 2,
"totalTrackCount": 7,
"duration": 182,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "wish_youd_come_true",
"title": "Wish You'd Come True",
"album": "Youtube Audio Library Rock",
"artist": "The 126ers",
"genre": "Rock",
"source": "https://storage.googleapis.com/automotive-media/Wish_You_d_Come_True.mp3",
"image": "content://androidx.media3/artwork/album_road.png",
"trackNumber": 3,
"totalTrackCount": 7,
"duration": 169,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "awakening",
"title": "Awakening",
"album": "Youtube Audio Library Rock",
"artist": "Silent Partner",
"genre": "Rock",
"source": "https://storage.googleapis.com/automotive-media/Awakening.mp3",
"image": "content://androidx.media3/artwork/album_road.png",
"trackNumber": 4,
"totalTrackCount": 7,
"duration": 220,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "home",
"title": "Home",
"album": "Youtube Audio Library Rock",
"artist": "Letter Box",
"genre": "Rock",
"source": "https://storage.googleapis.com/automotive-media/Home.mp3",
"image": "content://androidx.media3/artwork/album_road.png",
"trackNumber": 5,
"totalTrackCount": 7,
"duration": 213,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "tell_the_angels",
"title": "Tell The Angels",
"album": "Youtube Audio Library Rock 2",
"artist": "Letter Box",
"genre": "Rock",
"source": "https://storage.googleapis.com/automotive-media/Tell_The_Angels.mp3",
"image": "content://androidx.media3/artwork/album_skyline.png",
"trackNumber": 6,
"totalTrackCount": 7,
"duration": 208,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "hey_sailor",
"title": "Hey Sailor",
"album": "Youtube Audio Library Rock 2",
"artist": "Letter Box",
"genre": "Rock",
"source": "https://storage.googleapis.com/automotive-media/Hey_Sailor.mp3",
"image": "content://androidx.media3/artwork/album_skyline.png",
"trackNumber": 7,
"totalTrackCount": 7,
"duration": 193,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "keys_to_the_kingdom",
"title": "Keys To The Kingdom",
"album": "Youtube Audio Library Rock 2",
"artist": "The 126ers",
"genre": "Rock",
"source": "https://storage.googleapis.com/automotive-media/Keys_To_The_Kingdom.mp3",
"image": "content://androidx.media3/artwork/album_skyline.png",
"trackNumber": 1,
"totalTrackCount": 2,
"duration": 221,
"site": "https://www.youtube.com/audiolibrary/music"
},
{
"id": "the_coldest_shoulder",
"title": "The Coldest Shoulder",
"album": "Youtube Audio Library Rock 2",
"artist": "The 126ers",
"genre": "Rock",
"source": "https://storage.googleapis.com/automotive-media/The_Coldest_Shoulder.mp3",
"image": "content://androidx.media3/artwork/album_skyline.png",
"trackNumber": 2,
"totalTrackCount": 2,
"duration": 160,
"site": "https://www.youtube.com/audiolibrary/music"
}
]
}

View File

@ -0,0 +1,59 @@
/*
* Copyright 2023 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.demo.session.automotive
import androidx.annotation.OptIn
import androidx.media3.common.MediaItem
import androidx.media3.common.util.UnstableApi
import androidx.media3.demo.session.DemoMediaLibrarySessionCallback
import androidx.media3.demo.session.DemoPlaybackService
import androidx.media3.session.LibraryResult
import androidx.media3.session.MediaConstants
import androidx.media3.session.MediaSession.ControllerInfo
import com.google.common.util.concurrent.ListenableFuture
class AutomotiveService : DemoPlaybackService() {
override fun createLibrarySessionCallback(): MediaLibrarySession.Callback {
return object : DemoMediaLibrarySessionCallback(this@AutomotiveService) {
@OptIn(UnstableApi::class)
override fun onGetLibraryRoot(
session: MediaLibrarySession,
browser: ControllerInfo,
params: LibraryParams?
): ListenableFuture<LibraryResult<MediaItem>> {
var responseParams = params
if (session.isAutomotiveController(browser)) {
// See https://developer.android.com/training/cars/media#apply_content_style
val rootHintParams = params ?: LibraryParams.Builder().build()
rootHintParams.extras.putInt(
MediaConstants.EXTRAS_KEY_CONTENT_STYLE_BROWSABLE,
MediaConstants.EXTRAS_VALUE_CONTENT_STYLE_GRID_ITEM
)
rootHintParams.extras.putInt(
MediaConstants.EXTRAS_KEY_CONTENT_STYLE_PLAYABLE,
MediaConstants.EXTRAS_VALUE_CONTENT_STYLE_LIST_ITEM
)
// Tweaked params are propagated to Automotive browsers as root hints.
responseParams = rootHintParams
}
// Use super to return the common library root with the tweaked params sent to the browser.
return super.onGetLibraryRoot(session, browser, responseParams)
}
}
}
}

View File

@ -0,0 +1,89 @@
/*
* Copyright 2023 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.demo.session.automotive
import android.content.ContentProvider
import android.content.ContentValues
import android.content.Context
import android.database.Cursor
import android.net.Uri
import android.os.ParcelFileDescriptor
import java.io.File
/**
* Provides artwork for content URIs.
*
* <p>A bitmap file in the asset folder with path 'artwork/album1.png' can be referenced as artwork
* URI with 'content://androidx.media3/artwork/album1.png'. 'androidx.media3' is the authority
* declared for the content provider in 'AndroidManifest.xml'.
*
* <p>For demo use only.
*/
class BitmapContentProvider : ContentProvider() {
override fun onCreate() = true
override fun openFile(uri: Uri, mode: String): ParcelFileDescriptor? {
context?.let { ctx ->
getAssetPath(uri)?.let {
return ParcelFileDescriptor.open(
copyAssetFileToCacheDirectory(ctx, it),
ParcelFileDescriptor.MODE_READ_ONLY
)
}
}
return super.openFile(uri, mode)
}
private fun getAssetPath(contentUri: Uri): String? {
contentUri.path?.let {
return it.substring(1)
}
return null
}
private fun copyAssetFileToCacheDirectory(context: Context, assetPath: String): File {
val publicFile = File(context.cacheDir, assetPath.replace("/", "_"))
if (!publicFile.exists()) {
context.assets.open(assetPath).copyTo(publicFile.outputStream())
}
return publicFile
}
// No-op implementations of abstract ContentProvider methods.
override fun insert(uri: Uri, values: ContentValues?): Uri? = null
override fun query(
uri: Uri,
projection: Array<String>?,
selection: String?,
selectionArgs: Array<String>?,
sortOrder: String?
): Cursor? = null
override fun update(
uri: Uri,
values: ContentValues?,
selection: String?,
selectionArgs: Array<String>?
) = 0
override fun delete(uri: Uri, selection: String?, selectionArgs: Array<String>?) = 0
override fun getType(uri: Uri): String? = null
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

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