Merge pull request #16 from androidx/main
Merge from androidx/media main branch
56
.github/ISSUE_TEMPLATE/bug.yml
vendored
@ -5,37 +5,33 @@ 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.5.1
|
||||
- Media3 1.5.0
|
||||
- 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 +46,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 +116,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 +126,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.
|
||||
|
6
.github/ISSUE_TEMPLATE/question.md
vendored
@ -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).
|
||||
|
47
.gitignore
vendored
@ -52,30 +52,37 @@ tmp
|
||||
|
||||
# External native builds
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
|
||||
# VP9 extension
|
||||
extensions/vp9/src/main/jni/libvpx
|
||||
extensions/vp9/src/main/jni/libvpx_android_configs
|
||||
extensions/vp9/src/main/jni/libyuv
|
||||
# VP9 decoder extension
|
||||
libraries/decoder_vp9/src/main/jni/libvpx
|
||||
libraries/decoder_vp9/src/main/jni/libvpx_android_configs
|
||||
libraries/decoder_vp9/src/main/jni/libyuv
|
||||
|
||||
# AV1 extension
|
||||
extensions/av1/src/main/jni/cpu_features
|
||||
extensions/av1/src/main/jni/libgav1
|
||||
# AV1 decoder extension
|
||||
libraries/decoder_av1/src/main/jni/cpu_features
|
||||
libraries/decoder_av1/src/main/jni/libgav1
|
||||
|
||||
# Opus extension
|
||||
extensions/opus/src/main/jni/libopus
|
||||
# Opus decoder extension
|
||||
libraries/decoder_opus/src/main/jni/libopus
|
||||
|
||||
# FLAC extension
|
||||
extensions/flac/src/main/jni/flac
|
||||
# FLAC decoder extension
|
||||
libraries/decoder_flac/src/main/jni/flac
|
||||
|
||||
# FFmpeg extension
|
||||
extensions/ffmpeg/src/main/jni/ffmpeg
|
||||
# FFmpeg decoder extension
|
||||
libraries/decoder_ffmpeg/src/main/jni/ffmpeg
|
||||
|
||||
# Cronet extension
|
||||
extensions/cronet/jniLibs/*
|
||||
!extensions/cronet/jniLibs/README.md
|
||||
extensions/cronet/libs/*
|
||||
!extensions/cronet/libs/README.md
|
||||
# Cronet datasource extension
|
||||
libraries/datasource_cronet/jniLibs/*
|
||||
!libraries/datasource_cronet/jniLibs/README.md
|
||||
libraries/datasource_cronet/libs/*
|
||||
!libraries/datasource_cronet/libs/README.md
|
||||
|
||||
# MIDI extension
|
||||
extensions/midi/lib
|
||||
# MIDI decoder extension
|
||||
libraries/decoder_midi/lib
|
||||
|
||||
# IAMF decoder extension
|
||||
libraries/decoder_iamf/src/main/jni/libiamf
|
||||
|
||||
# MPEG-H decoder extension
|
||||
libraries/decoder_mpegh/src/main/jni/libmpegh
|
||||
|
10
.idea/icon.svg
generated
Normal 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 |
@ -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
|
||||
|
91
README.md
@ -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
|
||||
|
2086
RELEASENOTES.md
505
api.txt
@ -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
|
||||
@ -410,10 +464,13 @@ package androidx.media3.common {
|
||||
field @Nullable public final CharSequence description;
|
||||
field @Nullable public final Integer discNumber;
|
||||
field @Nullable public final CharSequence displayTitle;
|
||||
field @Nullable public final Long durationMs;
|
||||
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;
|
||||
@ -446,10 +503,13 @@ package androidx.media3.common {
|
||||
method public androidx.media3.common.MediaMetadata.Builder setDescription(@Nullable CharSequence);
|
||||
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 setDurationMs(@Nullable Long);
|
||||
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 +527,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 +549,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 +621,25 @@ 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_DECODING_RESOURCES_RECLAIMED = 4006; // 0xfa6
|
||||
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 +649,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 +661,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_DECODING_RESOURCES_RECLAIMED, 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 +695,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 +704,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 +749,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 +768,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 +804,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 +819,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 +829,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 +847,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 +886,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 +905,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 +916,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 +966,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 +1097,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 +1195,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 +1208,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 +1260,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 +1367,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 +1391,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 +1409,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 +1441,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 +1530,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 +1564,131 @@ 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 com.google.common.collect.ImmutableList<androidx.media3.session.CommandButton> getCustomLayout();
|
||||
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 android.os.Bundle getSessionExtras();
|
||||
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 {
|
||||
@ -1567,6 +1702,7 @@ package androidx.media3.session {
|
||||
public static interface MediaController.Listener {
|
||||
method public default void onAvailableSessionCommandsChanged(androidx.media3.session.MediaController, androidx.media3.session.SessionCommands);
|
||||
method public default com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionResult> onCustomCommand(androidx.media3.session.MediaController, androidx.media3.session.SessionCommand, android.os.Bundle);
|
||||
method public default void onCustomLayoutChanged(androidx.media3.session.MediaController, java.util.List<androidx.media3.session.CommandButton>);
|
||||
method public default void onDisconnected(androidx.media3.session.MediaController);
|
||||
method public default void onExtrasChanged(androidx.media3.session.MediaController, android.os.Bundle);
|
||||
method public default com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionResult> onSetCustomLayout(androidx.media3.session.MediaController, java.util.List<androidx.media3.session.CommandButton>);
|
||||
@ -1622,21 +1758,23 @@ 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 android.os.Bundle getSessionExtras();
|
||||
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 {
|
||||
@ -1646,6 +1784,7 @@ package androidx.media3.session {
|
||||
method public androidx.media3.session.MediaSession.Builder setExtras(android.os.Bundle);
|
||||
method public androidx.media3.session.MediaSession.Builder setId(String);
|
||||
method public androidx.media3.session.MediaSession.Builder setSessionActivity(android.app.PendingIntent);
|
||||
method public androidx.media3.session.MediaSession.Builder setSessionExtras(android.os.Bundle);
|
||||
}
|
||||
|
||||
public static interface MediaSession.Callback {
|
||||
@ -1653,10 +1792,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 +1811,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 +1822,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 +1860,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,11 +1890,12 @@ 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 {
|
||||
ctor public SessionToken(android.content.Context, android.content.ComponentName);
|
||||
method public static com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionToken> createSessionToken(android.content.Context, android.media.session.MediaSession.Token);
|
||||
method public static com.google.common.collect.ImmutableSet<androidx.media3.session.SessionToken> getAllServiceTokens(android.content.Context);
|
||||
method public android.os.Bundle getExtras();
|
||||
method public String getPackageName();
|
||||
|
14
build.gradle
@ -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'
|
||||
|
@ -14,6 +14,8 @@
|
||||
apply from: "$gradle.ext.androidxMediaSettingsDir/constants.gradle"
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
group = 'androidx.media3'
|
||||
|
||||
android {
|
||||
compileSdkVersion project.ext.compileSdkVersion
|
||||
|
||||
@ -27,6 +29,10 @@ android {
|
||||
}
|
||||
}
|
||||
|
||||
lintOptions {
|
||||
checkTestSources true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
|
@ -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.6.0-alpha01'
|
||||
releaseVersionCode = 1_006_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
|
||||
targetSdkVersion = 31
|
||||
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.3.1-android'
|
||||
glideVersion = '4.14.2'
|
||||
kotlinxCoroutinesVersion = '1.8.1'
|
||||
leakCanaryVersion = '2.10'
|
||||
mockitoVersion = '3.12.4'
|
||||
robolectricVersion = '4.8.1'
|
||||
robolectricVersion = '4.14.1'
|
||||
// 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.8.7'
|
||||
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
|
||||
|
@ -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')
|
||||
|
||||
@ -48,6 +52,8 @@ include modulePrefix + 'lib-ui'
|
||||
project(modulePrefix + 'lib-ui').projectDir = new File(rootDir, 'libraries/ui')
|
||||
include modulePrefix + 'lib-ui-leanback'
|
||||
project(modulePrefix + 'lib-ui-leanback').projectDir = new File(rootDir, 'libraries/ui_leanback')
|
||||
include modulePrefix + 'lib-ui-compose'
|
||||
project(modulePrefix + 'lib-ui-compose').projectDir = new File(rootDir, 'libraries/ui_compose')
|
||||
|
||||
include modulePrefix + 'lib-database'
|
||||
project(modulePrefix + 'lib-database').projectDir = new File(rootDir, 'libraries/database')
|
||||
@ -56,6 +62,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 +77,14 @@ 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-mpegh'
|
||||
project(modulePrefix + 'lib-decoder-mpegh').projectDir = new File(rootDir, 'libraries/decoder_mpegh')
|
||||
include modulePrefix + 'lib-decoder-opus'
|
||||
project(modulePrefix + 'lib-decoder-opus').projectDir = new File(rootDir, 'libraries/decoder_opus')
|
||||
include modulePrefix + 'lib-decoder-vp9'
|
||||
@ -83,6 +99,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 +111,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')
|
||||
|
@ -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.
|
||||
|
@ -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
|
||||
}
|
||||
|
@ -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"
|
||||
|
@ -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()
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
@ -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>
|
||||
|
@ -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
@ -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.
|
86
demos/compose/build.gradle
Normal file
@ -0,0 +1,86 @@
|
||||
// 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 {
|
||||
compose true
|
||||
}
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.5.3"
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests {
|
||||
includeAndroidResources = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
def composeBom = platform('androidx.compose:compose-bom:2024.12.01')
|
||||
implementation composeBom
|
||||
|
||||
implementation 'androidx.activity:activity-compose:1.9.0'
|
||||
implementation 'androidx.compose.foundation:foundation'
|
||||
implementation 'androidx.compose.material3:material3'
|
||||
implementation 'androidx.compose.material:material-icons-extended'
|
||||
implementation 'androidx.compose.ui:ui-tooling-preview'
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-compose:' + androidxLifecycleVersion
|
||||
implementation 'com.google.android.material:material:' + androidxMaterialVersion
|
||||
|
||||
implementation project(modulePrefix + 'lib-exoplayer')
|
||||
implementation project(modulePrefix + 'lib-ui-compose')
|
||||
debugImplementation 'androidx.compose.ui:ui-tooling'
|
||||
|
||||
// For detecting and debugging leaks only. LeakCanary is not needed for demo app to work.
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:' + leakCanaryVersion
|
||||
}
|
20
demos/compose/lint.xml
Normal 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
|
||||
|
||||
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.
|
||||
-->
|
||||
<lint>
|
||||
<issue id="UnsafeOptInUsageError">
|
||||
<option name="opt-in" value="androidx.media3.common.util.UnstableApi" />
|
||||
</issue>
|
||||
</lint>
|
38
demos/compose/src/main/AndroidManifest.xml
Normal 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>
|
@ -0,0 +1,130 @@
|
||||
/*
|
||||
* 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.content.Context
|
||||
import android.os.Build
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.enableEdgeToEdge
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Box
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.navigationBarsPadding
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.lifecycle.compose.LifecycleResumeEffect
|
||||
import androidx.lifecycle.compose.LifecycleStartEffect
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.demo.compose.buttons.ExtraControls
|
||||
import androidx.media3.demo.compose.buttons.MinimalControls
|
||||
import androidx.media3.demo.compose.data.videos
|
||||
import androidx.media3.demo.compose.layout.noRippleClickable
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.ui.compose.PlayerSurface
|
||||
import androidx.media3.ui.compose.SURFACE_TYPE_SURFACE_VIEW
|
||||
import androidx.media3.ui.compose.state.rememberPresentationState
|
||||
|
||||
class MainActivity : ComponentActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
enableEdgeToEdge()
|
||||
setContent { ComposeDemoApp() }
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun ComposeDemoApp(modifier: Modifier = Modifier) {
|
||||
val context = LocalContext.current
|
||||
var player by remember { mutableStateOf<Player?>(null) }
|
||||
|
||||
// See the following resources
|
||||
// https://developer.android.com/topic/libraries/architecture/lifecycle#onStop-and-savedState
|
||||
// https://developer.android.com/develop/ui/views/layout/support-multi-window-mode#multi-window_mode_configuration
|
||||
// https://developer.android.com/develop/ui/compose/layouts/adaptive/support-multi-window-mode#android_9
|
||||
|
||||
if (Build.VERSION.SDK_INT > 23) {
|
||||
// Initialize/release in onStart()/onStop() only because in a multi-window environment multiple
|
||||
// apps can be visible at the same time. The apps that are out-of-focus are paused, but video
|
||||
// playback should continue.
|
||||
LifecycleStartEffect(Unit) {
|
||||
player = initializePlayer(context)
|
||||
onStopOrDispose {
|
||||
player?.apply { release() }
|
||||
player = null
|
||||
}
|
||||
}
|
||||
} else {
|
||||
// Call to onStop() is not guaranteed, hence we release the Player in onPause() instead
|
||||
LifecycleResumeEffect(Unit) {
|
||||
player = initializePlayer(context)
|
||||
onPauseOrDispose {
|
||||
player?.apply { release() }
|
||||
player = null
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
player?.let { MediaPlayerScreen(player = it, modifier = modifier.fillMaxSize()) }
|
||||
}
|
||||
|
||||
private fun initializePlayer(context: Context): Player =
|
||||
ExoPlayer.Builder(context).build().apply {
|
||||
setMediaItems(videos.map(MediaItem::fromUri))
|
||||
prepare()
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun MediaPlayerScreen(player: Player, modifier: Modifier = Modifier) {
|
||||
var showControls by remember { mutableStateOf(true) }
|
||||
val presentationState = rememberPresentationState(player)
|
||||
|
||||
Box(modifier) {
|
||||
PlayerSurface(
|
||||
player = player,
|
||||
surfaceType = SURFACE_TYPE_SURFACE_VIEW,
|
||||
modifier = modifier.noRippleClickable { showControls = !showControls },
|
||||
)
|
||||
|
||||
if (!presentationState.showSurface) {
|
||||
// hide the surface that is being prepared behind a shutter
|
||||
Box(modifier.background(Color.Black))
|
||||
}
|
||||
|
||||
if (showControls) {
|
||||
// drawn on top of a potential shutter
|
||||
MinimalControls(player, Modifier.align(Alignment.Center))
|
||||
ExtraControls(
|
||||
player,
|
||||
Modifier.fillMaxWidth()
|
||||
.align(Alignment.BottomCenter)
|
||||
.background(Color.Gray.copy(alpha = 0.4f))
|
||||
.navigationBarsPadding(),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,36 @@
|
||||
/*
|
||||
* 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.buttons
|
||||
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.media3.common.Player
|
||||
|
||||
@Composable
|
||||
internal fun ExtraControls(player: Player, modifier: Modifier = Modifier) {
|
||||
Row(
|
||||
modifier = modifier,
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
ShuffleButton(player)
|
||||
RepeatButton(player)
|
||||
}
|
||||
}
|
@ -0,0 +1,51 @@
|
||||
/*
|
||||
* 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.buttons
|
||||
|
||||
import androidx.compose.foundation.background
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.size
|
||||
import androidx.compose.foundation.shape.CircleShape
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.Color
|
||||
import androidx.compose.ui.unit.dp
|
||||
import androidx.media3.common.Player
|
||||
|
||||
/**
|
||||
* Minimal playback controls for a [Player].
|
||||
*
|
||||
* Includes buttons for seeking to a previous/next items or playing/pausing the playback.
|
||||
*/
|
||||
@Composable
|
||||
internal fun MinimalControls(player: Player, modifier: Modifier = Modifier) {
|
||||
val graySemiTransparentBackground = Color.Gray.copy(alpha = 0.1f)
|
||||
val modifierForIconButton =
|
||||
modifier.size(80.dp).background(graySemiTransparentBackground, CircleShape)
|
||||
Row(
|
||||
modifier = modifier.fillMaxWidth(),
|
||||
horizontalArrangement = Arrangement.SpaceEvenly,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
PreviousButton(player, modifierForIconButton)
|
||||
PlayPauseButton(player, modifierForIconButton)
|
||||
NextButton(player, modifierForIconButton)
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.buttons
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.SkipNext
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.demo.compose.R
|
||||
import androidx.media3.ui.compose.state.rememberNextButtonState
|
||||
|
||||
@Composable
|
||||
internal fun NextButton(player: Player, modifier: Modifier = Modifier) {
|
||||
val state = rememberNextButtonState(player)
|
||||
IconButton(onClick = state::onClick, modifier = modifier, enabled = state.isEnabled) {
|
||||
Icon(
|
||||
Icons.Default.SkipNext,
|
||||
contentDescription = stringResource(R.string.next_button),
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,41 @@
|
||||
/*
|
||||
* 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.buttons
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Pause
|
||||
import androidx.compose.material.icons.filled.PlayArrow
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.demo.compose.R
|
||||
import androidx.media3.ui.compose.state.rememberPlayPauseButtonState
|
||||
|
||||
@Composable
|
||||
internal fun PlayPauseButton(player: Player, modifier: Modifier = Modifier) {
|
||||
val state = rememberPlayPauseButtonState(player)
|
||||
val icon = if (state.showPlay) Icons.Default.PlayArrow else Icons.Default.Pause
|
||||
val contentDescription =
|
||||
if (state.showPlay) stringResource(R.string.playpause_button_play)
|
||||
else stringResource(R.string.playpause_button_pause)
|
||||
IconButton(onClick = state::onClick, modifier = modifier, enabled = state.isEnabled) {
|
||||
Icon(icon, contentDescription = contentDescription, modifier = modifier)
|
||||
}
|
||||
}
|
@ -0,0 +1,40 @@
|
||||
/*
|
||||
* 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.buttons
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.SkipPrevious
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.demo.compose.R
|
||||
import androidx.media3.ui.compose.state.rememberPreviousButtonState
|
||||
|
||||
@Composable
|
||||
internal fun PreviousButton(player: Player, modifier: Modifier = Modifier) {
|
||||
val state = rememberPreviousButtonState(player)
|
||||
IconButton(onClick = state::onClick, modifier = modifier, enabled = state.isEnabled) {
|
||||
Icon(
|
||||
Icons.Default.SkipPrevious,
|
||||
contentDescription = stringResource(R.string.previous_button),
|
||||
modifier = modifier,
|
||||
)
|
||||
}
|
||||
}
|
@ -0,0 +1,58 @@
|
||||
/*
|
||||
* 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.buttons
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Repeat
|
||||
import androidx.compose.material.icons.filled.RepeatOn
|
||||
import androidx.compose.material.icons.filled.RepeatOneOn
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.graphics.vector.ImageVector
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.demo.compose.R
|
||||
import androidx.media3.ui.compose.state.rememberRepeatButtonState
|
||||
|
||||
@Composable
|
||||
internal fun RepeatButton(player: Player, modifier: Modifier = Modifier) {
|
||||
val state = rememberRepeatButtonState(player)
|
||||
val icon = repeatModeIcon(state.repeatModeState)
|
||||
val contentDescription = repeatModeContentDescription(state.repeatModeState)
|
||||
IconButton(onClick = state::onClick, modifier = modifier, enabled = state.isEnabled) {
|
||||
Icon(icon, contentDescription = contentDescription, modifier = modifier)
|
||||
}
|
||||
}
|
||||
|
||||
private fun repeatModeIcon(repeatMode: @Player.RepeatMode Int): ImageVector {
|
||||
return when (repeatMode) {
|
||||
Player.REPEAT_MODE_OFF -> Icons.Default.Repeat
|
||||
Player.REPEAT_MODE_ONE -> Icons.Default.RepeatOneOn
|
||||
else -> Icons.Default.RepeatOn
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun repeatModeContentDescription(repeatMode: @Player.RepeatMode Int): String {
|
||||
return when (repeatMode) {
|
||||
Player.REPEAT_MODE_OFF -> stringResource(R.string.repeat_button_repeat_off_description)
|
||||
Player.REPEAT_MODE_ONE -> stringResource(R.string.repeat_button_repeat_one_description)
|
||||
else -> stringResource(R.string.repeat_button_repeat_all_description)
|
||||
}
|
||||
}
|
@ -0,0 +1,44 @@
|
||||
/*
|
||||
* 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.buttons
|
||||
|
||||
import androidx.compose.material.icons.Icons
|
||||
import androidx.compose.material.icons.filled.Shuffle
|
||||
import androidx.compose.material.icons.filled.ShuffleOn
|
||||
import androidx.compose.material3.Icon
|
||||
import androidx.compose.material3.IconButton
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.media3.common.Player
|
||||
import androidx.media3.demo.compose.R
|
||||
import androidx.media3.ui.compose.state.rememberShuffleButtonState
|
||||
|
||||
@Composable
|
||||
internal fun ShuffleButton(player: Player, modifier: Modifier = Modifier) {
|
||||
val state = rememberShuffleButtonState(player)
|
||||
val icon = if (state.shuffleOn) Icons.Default.ShuffleOn else Icons.Default.Shuffle
|
||||
val contentDescription =
|
||||
if (state.shuffleOn) {
|
||||
stringResource(R.string.shuffle_button_shuffle_on_description)
|
||||
} else {
|
||||
stringResource(R.string.shuffle_button_shuffle_off_description)
|
||||
}
|
||||
IconButton(onClick = state::onClick, modifier = modifier, enabled = state.isEnabled) {
|
||||
Icon(icon, contentDescription = contentDescription, modifier = modifier)
|
||||
}
|
||||
}
|
@ -0,0 +1,24 @@
|
||||
/*
|
||||
* 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://storage.googleapis.com/exoplayer-test-media-0/shortform_2.mp4",
|
||||
"https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/video-vp9-360.webm",
|
||||
"https://storage.googleapis.com/exoplayer-test-media-0/shortform_3.mp4",
|
||||
)
|
@ -0,0 +1,35 @@
|
||||
/*
|
||||
* 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.layout
|
||||
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.interaction.MutableInteractionSource
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.ui.Modifier
|
||||
|
||||
@Composable
|
||||
internal fun Modifier.noRippleClickable(onClick: () -> Unit): Modifier {
|
||||
return then(
|
||||
clickable(
|
||||
interactionSource = remember { MutableInteractionSource() },
|
||||
indication = null, // to prevent the ripple from the tap
|
||||
) {
|
||||
onClick()
|
||||
}
|
||||
)
|
||||
}
|
20
demos/compose/src/main/res/drawable/divider.xml
Normal 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>
|
||||
|
BIN
demos/compose/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
demos/compose/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
demos/compose/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
demos/compose/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
demos/compose/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 10 KiB |
31
demos/compose/src/main/res/values-night/themes.xml
Normal 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>
|
30
demos/compose/src/main/res/values/colors.xml
Normal 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>
|
27
demos/compose/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,27 @@
|
||||
<?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="playpause_button_play">Play</string>
|
||||
<string name="playpause_button_pause">Pause</string>
|
||||
<string name="next_button">Next</string>
|
||||
<string name="previous_button">Previous</string>
|
||||
<string name="repeat_button_repeat_off_description">Current mode: Repeat none. Toggle repeat mode.</string>
|
||||
<string name="repeat_button_repeat_one_description">Current mode: Repeat one. Toggle repeat mode.</string>
|
||||
<string name="repeat_button_repeat_all_description">Current mode: Repeat all. Toggle repeat mode.</string>
|
||||
<string name="shuffle_button_shuffle_on_description">Disable shuffle mode.</string>
|
||||
<string name="shuffle_button_shuffle_off_description">Enable shuffle mode</string>
|
||||
</resources>
|
31
demos/compose/src/main/res/values/themes.xml
Normal 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>
|
8
demos/composition/README.md
Normal 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.
|
62
demos/composition/build.gradle
Normal file
@ -0,0 +1,62 @@
|
||||
/*
|
||||
* 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-muxer')
|
||||
implementation project(modulePrefix + 'lib-transformer')
|
||||
implementation project(modulePrefix + 'lib-ui')
|
||||
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
|
||||
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
|
||||
}
|
1
demos/composition/proguard-rules.txt
Normal file
@ -0,0 +1 @@
|
||||
# Proguard rules specific to the composition demo app.
|
51
demos/composition/src/main/AndroidManifest.xml
Normal 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>
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
@ -0,0 +1,521 @@
|
||||
/*
|
||||
* 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 static android.content.pm.ActivityInfo.COLOR_MODE_HDR;
|
||||
import static androidx.media3.common.util.Util.SDK_INT;
|
||||
import static androidx.media3.transformer.Composition.HDR_MODE_EXPERIMENTAL_FORCE_INTERPRET_HDR_AS_SDR;
|
||||
import static androidx.media3.transformer.Composition.HDR_MODE_KEEP_HDR;
|
||||
import static androidx.media3.transformer.Composition.HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_MEDIACODEC;
|
||||
import static androidx.media3.transformer.Composition.HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL;
|
||||
|
||||
import android.app.Activity;
|
||||
import android.content.DialogInterface;
|
||||
import android.os.Bundle;
|
||||
import android.view.LayoutInflater;
|
||||
import android.view.View;
|
||||
import android.widget.ArrayAdapter;
|
||||
import android.widget.CheckBox;
|
||||
import android.widget.Spinner;
|
||||
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.MimeTypes;
|
||||
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.DebugTraceUtil;
|
||||
import androidx.media3.effect.LanczosResample;
|
||||
import androidx.media3.effect.Presentation;
|
||||
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.InAppMuxer;
|
||||
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 com.google.common.collect.ImmutableMap;
|
||||
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 static final String SAME_AS_INPUT_OPTION = "same as input";
|
||||
private static final ImmutableMap<String, @Composition.HdrMode Integer> HDR_MODE_DESCRIPTIONS =
|
||||
new ImmutableMap.Builder<String, @Composition.HdrMode Integer>()
|
||||
.put("Keep HDR", HDR_MODE_KEEP_HDR)
|
||||
.put("MediaCodec tone-map HDR to SDR", HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_MEDIACODEC)
|
||||
.put("OpenGL tone-map HDR to SDR", HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL)
|
||||
.put("Force Interpret HDR as SDR", HDR_MODE_EXPERIMENTAL_FORCE_INTERPRET_HDR_AS_SDR)
|
||||
.build();
|
||||
private static final ImmutableList<String> RESOLUTION_HEIGHTS =
|
||||
ImmutableList.of(
|
||||
SAME_AS_INPUT_OPTION, "144", "240", "360", "480", "720", "1080", "1440", "2160");
|
||||
|
||||
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);
|
||||
if (SDK_INT >= 26) {
|
||||
getWindow().setColorMode(COLOR_MODE_HDR);
|
||||
}
|
||||
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 -> showExportSettings());
|
||||
|
||||
AppCompatCheckBox backgroundAudioCheckBox = findViewById(R.id.background_audio_checkbox);
|
||||
backgroundAudioCheckBox.setOnCheckedChangeListener(
|
||||
(compoundButton, checked) -> includeBackgroundAudioTrack = checked);
|
||||
|
||||
ArrayAdapter<String> resolutionHeightAdapter =
|
||||
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
|
||||
resolutionHeightAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
Spinner resolutionHeightSpinner = findViewById(R.id.resolution_height_spinner);
|
||||
resolutionHeightSpinner.setAdapter(resolutionHeightAdapter);
|
||||
resolutionHeightAdapter.addAll(RESOLUTION_HEIGHTS);
|
||||
|
||||
ArrayAdapter<String> hdrModeAdapter = new ArrayAdapter<>(this, R.layout.spinner_item);
|
||||
hdrModeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
Spinner hdrModeSpinner = findViewById(R.id.hdr_mode_spinner);
|
||||
hdrModeSpinner.setAdapter(hdrModeAdapter);
|
||||
hdrModeAdapter.addAll(HDR_MODE_DESCRIPTIONS.keySet());
|
||||
|
||||
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();
|
||||
}
|
||||
|
||||
@SuppressWarnings("MissingSuperCall")
|
||||
@Override
|
||||
public void onBackPressed() {
|
||||
if (compositionPlayer != null) {
|
||||
compositionPlayer.pause();
|
||||
}
|
||||
if (exportStopwatch.isRunning()) {
|
||||
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.Builder<Effect> videoEffectsBuilder = new ImmutableList.Builder<>();
|
||||
if (appliesVideoEffects) {
|
||||
videoEffectsBuilder.add(MatrixTransformationFactory.createDizzyCropEffect());
|
||||
videoEffectsBuilder.add(RgbFilter.createGrayscaleFilter());
|
||||
}
|
||||
Spinner resolutionHeightSpinner = findViewById(R.id.resolution_height_spinner);
|
||||
String selectedResolutionHeight = String.valueOf(resolutionHeightSpinner.getSelectedItem());
|
||||
if (!SAME_AS_INPUT_OPTION.equals(selectedResolutionHeight)) {
|
||||
int resolutionHeight = Integer.parseInt(selectedResolutionHeight);
|
||||
videoEffectsBuilder.add(LanczosResample.scaleToFit(10000, resolutionHeight));
|
||||
videoEffectsBuilder.add(Presentation.createForHeight(resolutionHeight));
|
||||
}
|
||||
ImmutableList<Effect> videoEffects = videoEffectsBuilder.build();
|
||||
// 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);
|
||||
Spinner hdrModeSpinner = findViewById(R.id.hdr_mode_spinner);
|
||||
int selectedHdrMode =
|
||||
HDR_MODE_DESCRIPTIONS.get(String.valueOf(hdrModeSpinner.getSelectedItem()));
|
||||
return new Composition.Builder(compositionSequences)
|
||||
.setEffects(
|
||||
new Effects(
|
||||
/* audioProcessors= */ ImmutableList.of(sampleRateChanger),
|
||||
/* videoEffects= */ ImmutableList.of()))
|
||||
.setHdrMode(selectedHdrMode)
|
||||
.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.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(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 showExportSettings() {
|
||||
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
|
||||
LayoutInflater inflater = this.getLayoutInflater();
|
||||
View exportSettingsDialogView = inflater.inflate(R.layout.export_settings, null);
|
||||
|
||||
alertDialogBuilder
|
||||
.setView(exportSettingsDialogView)
|
||||
.setTitle(R.string.export_settings)
|
||||
.setPositiveButton(
|
||||
R.string.export, (dialog, id) -> exportComposition(exportSettingsDialogView))
|
||||
.setNegativeButton(R.string.cancel, (dialog, id) -> dialog.dismiss());
|
||||
|
||||
ArrayAdapter<String> audioMimeAdapter =
|
||||
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
|
||||
audioMimeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
Spinner audioMimeSpinner = exportSettingsDialogView.findViewById(R.id.audio_mime_spinner);
|
||||
audioMimeSpinner.setAdapter(audioMimeAdapter);
|
||||
audioMimeAdapter.addAll(
|
||||
SAME_AS_INPUT_OPTION, MimeTypes.AUDIO_AAC, MimeTypes.AUDIO_AMR_NB, MimeTypes.AUDIO_AMR_WB);
|
||||
|
||||
ArrayAdapter<String> videoMimeAdapter =
|
||||
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
|
||||
videoMimeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||
Spinner videoMimeSpinner = exportSettingsDialogView.findViewById(R.id.video_mime_spinner);
|
||||
videoMimeSpinner.setAdapter(videoMimeAdapter);
|
||||
videoMimeAdapter.addAll(
|
||||
SAME_AS_INPUT_OPTION,
|
||||
MimeTypes.VIDEO_H263,
|
||||
MimeTypes.VIDEO_H264,
|
||||
MimeTypes.VIDEO_H265,
|
||||
MimeTypes.VIDEO_MP4V,
|
||||
MimeTypes.VIDEO_AV1);
|
||||
|
||||
CheckBox enableDebugTracingCheckBox =
|
||||
exportSettingsDialogView.findViewById(R.id.enable_debug_tracing_checkbox);
|
||||
enableDebugTracingCheckBox.setOnCheckedChangeListener(
|
||||
(buttonView, isChecked) -> DebugTraceUtil.enableTracing = isChecked);
|
||||
|
||||
// Connect producing fragmented MP4 to using Media3 Muxer
|
||||
CheckBox useMedia3MuxerCheckBox =
|
||||
exportSettingsDialogView.findViewById(R.id.use_media3_muxer_checkbox);
|
||||
CheckBox produceFragmentedMp4CheckBox =
|
||||
exportSettingsDialogView.findViewById(R.id.produce_fragmented_mp4_checkbox);
|
||||
useMedia3MuxerCheckBox.setOnCheckedChangeListener(
|
||||
(buttonView, isChecked) -> {
|
||||
if (!isChecked) {
|
||||
produceFragmentedMp4CheckBox.setChecked(false);
|
||||
}
|
||||
});
|
||||
produceFragmentedMp4CheckBox.setOnCheckedChangeListener(
|
||||
(buttonView, isChecked) -> {
|
||||
if (isChecked) {
|
||||
useMedia3MuxerCheckBox.setChecked(true);
|
||||
}
|
||||
});
|
||||
|
||||
AlertDialog dialog = alertDialogBuilder.create();
|
||||
dialog.show();
|
||||
}
|
||||
|
||||
private void exportComposition(View exportSettingsDialogView) {
|
||||
// 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.Builder transformerBuilder = new Transformer.Builder(/* context= */ this);
|
||||
|
||||
Spinner audioMimeTypeSpinner = exportSettingsDialogView.findViewById(R.id.audio_mime_spinner);
|
||||
String selectedAudioMimeType = String.valueOf(audioMimeTypeSpinner.getSelectedItem());
|
||||
if (!SAME_AS_INPUT_OPTION.equals(selectedAudioMimeType)) {
|
||||
transformerBuilder.setAudioMimeType(selectedAudioMimeType);
|
||||
}
|
||||
|
||||
Spinner videoMimeTypeSpinner = exportSettingsDialogView.findViewById(R.id.video_mime_spinner);
|
||||
String selectedVideoMimeType = String.valueOf(videoMimeTypeSpinner.getSelectedItem());
|
||||
if (!SAME_AS_INPUT_OPTION.equals(selectedVideoMimeType)) {
|
||||
transformerBuilder.setVideoMimeType(selectedVideoMimeType);
|
||||
}
|
||||
|
||||
CheckBox useMedia3MuxerCheckBox =
|
||||
exportSettingsDialogView.findViewById(R.id.use_media3_muxer_checkbox);
|
||||
CheckBox produceFragmentedMp4CheckBox =
|
||||
exportSettingsDialogView.findViewById(R.id.produce_fragmented_mp4_checkbox);
|
||||
if (useMedia3MuxerCheckBox.isChecked()) {
|
||||
transformerBuilder.setMuxerFactory(
|
||||
new InAppMuxer.Factory.Builder()
|
||||
.setOutputFragmentedMp4(produceFragmentedMp4CheckBox.isChecked())
|
||||
.build());
|
||||
}
|
||||
|
||||
transformer =
|
||||
transformerBuilder
|
||||
.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.d(TAG, DebugTraceUtil.generateTraceSummary());
|
||||
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);
|
||||
Log.d(TAG, DebugTraceUtil.generateTraceSummary());
|
||||
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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
@ -0,0 +1,178 @@
|
||||
<?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/resolution_height_setting" />
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/resolution_height_setting"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginBottom="8dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/hdr_mode_setting">
|
||||
<TextView
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/output_video_resolution"/>
|
||||
<Spinner
|
||||
android:id="@+id/resolution_height_spinner"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:gravity="end"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"/>
|
||||
</LinearLayout>
|
||||
|
||||
<LinearLayout
|
||||
android:id="@+id/hdr_mode_setting"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginBottom="12dp"
|
||||
app:layout_constraintBottom_toTopOf="@id/preview_button">
|
||||
<TextView
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/hdr_mode" />
|
||||
<Spinner
|
||||
android:id="@+id/hdr_mode_spinner"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:gravity="end"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"/>
|
||||
</LinearLayout>
|
||||
|
||||
<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>
|
110
demos/composition/src/main/res/layout/export_settings.xml
Normal file
@ -0,0 +1,110 @@
|
||||
<?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.
|
||||
-->
|
||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:id="@+id/export_settings_list"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="vertical"
|
||||
android:padding="8dp">
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginBottom="12dp"
|
||||
android:layout_marginTop="12dp">
|
||||
<TextView
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/output_audio_mime_type"/>
|
||||
<Spinner
|
||||
android:id="@+id/audio_mime_spinner"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:gravity="end"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"/>
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical"
|
||||
android:layout_marginBottom="12dp">
|
||||
<TextView
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/output_video_mime_type"/>
|
||||
<Spinner
|
||||
android:id="@+id/video_mime_spinner"
|
||||
android:layout_gravity="end|center_vertical"
|
||||
android:gravity="end"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"/>
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
<TextView
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1"
|
||||
android:text="@string/enable_debug_tracing"/>
|
||||
<CheckBox
|
||||
android:id="@+id/enable_debug_tracing_checkbox"
|
||||
android:layout_gravity="end"
|
||||
android:checked="false"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"/>
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
<TextView
|
||||
android:text="@string/use_media3_muxer"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1" />
|
||||
<CheckBox
|
||||
android:id="@+id/use_media3_muxer_checkbox"
|
||||
android:layout_gravity="end"
|
||||
android:checked="false"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"/>
|
||||
</LinearLayout>
|
||||
<LinearLayout
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="wrap_content"
|
||||
android:orientation="horizontal"
|
||||
android:gravity="center_vertical">
|
||||
<TextView
|
||||
android:text="@string/produce_fragmented_mp4"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="0dp"
|
||||
android:layout_weight="1" />
|
||||
<CheckBox
|
||||
android:id="@+id/produce_fragmented_mp4_checkbox"
|
||||
android:layout_gravity="end"
|
||||
android:checked="false"
|
||||
android:layout_height="wrap_content"
|
||||
android:layout_width="wrap_content"/>
|
||||
</LinearLayout>
|
||||
</LinearLayout>
|
30
demos/composition/src/main/res/layout/preset_item.xml
Normal 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>
|
25
demos/composition/src/main/res/layout/spinner_item.xml
Normal file
@ -0,0 +1,25 @@
|
||||
<?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.
|
||||
-->
|
||||
<TextView
|
||||
xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
android:layout_width="wrap_content"
|
||||
android:layout_height="32dp"
|
||||
android:gravity="start|center_vertical"
|
||||
android:paddingLeft="4dp"
|
||||
android:paddingRight="4dp"
|
||||
android:layout_marginLeft="4dp"
|
||||
android:layout_marginRight="4dp"
|
||||
android:textIsSelectable="false" />
|
BIN
demos/composition/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
demos/composition/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
demos/composition/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
demos/composition/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
demos/composition/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 10 KiB |
31
demos/composition/src/main/res/values-night/themes.xml
Normal 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>
|
77
demos/composition/src/main/res/values/arrays.xml
Normal 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 > W, 0°)</item>
|
||||
<item>H264 video and AAC audio (portrait, H < 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>
|
24
demos/composition/src/main/res/values/colors.xml
Normal 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>
|
39
demos/composition/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,39 @@
|
||||
<?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>
|
||||
<string name="output_video_resolution" translatable="false">Output video resolution</string>
|
||||
<string name="hdr_mode" translatable="false">HDR mode</string>
|
||||
<string name="ok" translatable="false">OK</string>
|
||||
<string name="cancel" translatable="false">Cancel</string>
|
||||
<string name="export_settings" translatable="false">Export Settings</string>
|
||||
<string name="output_audio_mime_type" translatable="false">Output audio MIME type</string>
|
||||
<string name="output_video_mime_type" translatable="false">Output video MIME type</string>
|
||||
<string name="enable_debug_tracing" translatable="false">Enable debug tracing</string>
|
||||
<string name="use_media3_muxer" translatable="false">Use Media3 muxer</string>
|
||||
<string name="produce_fragmented_mp4" translatable="false">Produce fragmented MP4</string>
|
||||
</resources>
|
31
demos/composition/src/main/res/values/themes.xml
Normal 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>
|
7
demos/effect/README.md
Normal file
@ -0,0 +1,7 @@
|
||||
# Effect demo
|
||||
|
||||
This app demonstrates how to use the [Effect][] API to modify videos. It uses
|
||||
[setVideoEffects] method to add different effects to [ExoPlayer].
|
||||
|
||||
See the [demos README](../README.md) for instructions on how to build and run
|
||||
this demo.
|
83
demos/effect/build.gradle
Normal file
@ -0,0 +1,83 @@
|
||||
// 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.effect'
|
||||
|
||||
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 {
|
||||
compose true
|
||||
}
|
||||
composeOptions {
|
||||
kotlinCompilerExtensionVersion = "1.5.3"
|
||||
}
|
||||
|
||||
testOptions {
|
||||
unitTests {
|
||||
includeAndroidResources = true
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
dependencies {
|
||||
def composeBom = platform('androidx.compose:compose-bom:2024.10.00')
|
||||
implementation composeBom
|
||||
|
||||
implementation 'androidx.activity:activity-compose:1.9.0'
|
||||
implementation 'androidx.compose.foundation:foundation'
|
||||
implementation 'androidx.compose.material3:material3'
|
||||
implementation 'com.google.android.material:material:' + androidxMaterialVersion
|
||||
|
||||
implementation project(modulePrefix + 'lib-exoplayer')
|
||||
implementation project(modulePrefix + 'lib-ui')
|
||||
implementation project(modulePrefix + 'lib-effect')
|
||||
|
||||
// For detecting and debugging leaks only. LeakCanary is not needed for demo app to work.
|
||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:' + leakCanaryVersion
|
||||
}
|
20
demos/effect/lint.xml
Normal 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
|
||||
|
||||
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.
|
||||
-->
|
||||
<lint>
|
||||
<issue id="UnsafeOptInUsageError">
|
||||
<option name="opt-in" value="androidx.media3.common.util.UnstableApi" />
|
||||
</issue>
|
||||
</lint>
|
40
demos/effect/src/main/AndroidManifest.xml
Normal file
@ -0,0 +1,40 @@
|
||||
<?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.effect">
|
||||
|
||||
<uses-sdk/>
|
||||
<uses-permission android:name="android.permission.INTERNET" />
|
||||
<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:theme="@style/Theme.Media3EffectDemo">
|
||||
|
||||
<activity
|
||||
android:name="androidx.media3.demo.effect.EffectActivity"
|
||||
android:exported="true">
|
||||
<intent-filter>
|
||||
<action android:name="android.intent.action.MAIN" />
|
||||
<category android:name="android.intent.category.LAUNCHER" />
|
||||
</intent-filter>
|
||||
</activity>
|
||||
</application>
|
||||
</manifest>
|
27
demos/effect/src/main/assets/media.playlist.json
Normal file
@ -0,0 +1,27 @@
|
||||
[
|
||||
{
|
||||
"name": "Cats -> Dogs",
|
||||
"playlist": [
|
||||
{
|
||||
"uri": "https://html5demos.com/assets/dizzy.mp4"
|
||||
},
|
||||
{
|
||||
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Android Block -> Dogs -> BigBuckBunny",
|
||||
"playlist": [
|
||||
{
|
||||
"uri": "https://storage.googleapis.com/exoplayer-test-media-0/android-block-1080-hevc.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/exoplayer-test-media-0/BigBuckBunny_320x180.mp4"
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
@ -0,0 +1,140 @@
|
||||
/*
|
||||
* 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.effect
|
||||
|
||||
import android.graphics.Canvas
|
||||
import android.graphics.Color
|
||||
import android.graphics.Paint
|
||||
import android.graphics.PorterDuff
|
||||
import android.os.Handler
|
||||
import androidx.media3.common.VideoFrameProcessingException
|
||||
import androidx.media3.common.util.Size
|
||||
import androidx.media3.common.util.Util
|
||||
import androidx.media3.effect.CanvasOverlay
|
||||
import kotlin.math.abs
|
||||
import kotlin.random.Random
|
||||
|
||||
/** Mimics an emitter of confetti, dropping from the center of the frame. */
|
||||
internal class ConfettiOverlay : CanvasOverlay(/* useInputFrameSize= */ true) {
|
||||
|
||||
private val confettiList = mutableListOf<Confetti>()
|
||||
private val paint = Paint()
|
||||
private val handler = Handler(Util.getCurrentOrMainLooper())
|
||||
|
||||
private var addConfettiTask: (() -> Unit)? = null
|
||||
private var width = 0f
|
||||
private var height = 0f
|
||||
private var started = false
|
||||
|
||||
override fun configure(videoSize: Size) {
|
||||
super.configure(videoSize)
|
||||
this.width = videoSize.width.toFloat()
|
||||
this.height = videoSize.height.toFloat()
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
override fun onDraw(canvas: Canvas, presentationTimeUs: Long) {
|
||||
if (!started) {
|
||||
start()
|
||||
}
|
||||
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
|
||||
|
||||
confettiList.removeAll { confetti ->
|
||||
confetti.y > height / 2 || confetti.x <= 0 || confetti.x > width
|
||||
}
|
||||
for (confetti in confettiList) {
|
||||
confetti.draw(canvas, paint)
|
||||
confetti.update()
|
||||
}
|
||||
}
|
||||
|
||||
@Throws(VideoFrameProcessingException::class)
|
||||
override fun release() {
|
||||
super.release()
|
||||
handler.post(this::stop)
|
||||
}
|
||||
|
||||
/** Starts the confetti. */
|
||||
fun start() {
|
||||
addConfettiTask = this::addConfetti
|
||||
handler.post(checkNotNull(addConfettiTask))
|
||||
started = true
|
||||
}
|
||||
|
||||
/** Stops the confetti. */
|
||||
fun stop() {
|
||||
handler.removeCallbacks(checkNotNull(addConfettiTask))
|
||||
confettiList.clear()
|
||||
started = false
|
||||
addConfettiTask = null
|
||||
}
|
||||
|
||||
@Synchronized
|
||||
fun addConfetti() {
|
||||
repeat(5) {
|
||||
confettiList.add(
|
||||
Confetti(
|
||||
text = CONFETTI_TEXTS[abs(Random.nextInt()) % CONFETTI_TEXTS.size],
|
||||
x = width / 2f,
|
||||
y = EMITTER_POSITION_Y.toFloat(),
|
||||
size = CONFETTI_BASE_SIZE + Random.nextInt(CONFETTI_SIZE_VARIATION),
|
||||
color = Color.HSVToColor(floatArrayOf(Random.nextInt(360).toFloat(), 0.6f, 0.8f)),
|
||||
)
|
||||
)
|
||||
}
|
||||
handler.postDelayed(this::addConfetti, /* delayMillis= */ 100)
|
||||
}
|
||||
|
||||
private class Confetti(
|
||||
private val text: String,
|
||||
private val size: Int,
|
||||
private val color: Int,
|
||||
var x: Float,
|
||||
var y: Float,
|
||||
) {
|
||||
private val speedX = 4 * (Random.nextFloat() * 2 - 1) // Random speed in x direction
|
||||
private val speedY = 4 * Random.nextFloat() // Random speed in y direction
|
||||
private val rotationSpeed = (Random.nextFloat() - 0.5f) * 4f // Random rotation speed
|
||||
|
||||
private var rotation = Random.nextFloat() * 360f
|
||||
|
||||
/** Draws the [Confetti] on the [Canvas]. */
|
||||
fun draw(canvas: Canvas, paint: Paint) {
|
||||
canvas.save()
|
||||
paint.color = color
|
||||
canvas.translate(x, y)
|
||||
canvas.rotate(rotation)
|
||||
paint.textSize = size.toFloat()
|
||||
canvas.drawText(text, /* x= */ 0f, /* y= */ 0f, paint) // Only draw text
|
||||
canvas.restore()
|
||||
}
|
||||
|
||||
/** Updates the [Confetti]. */
|
||||
fun update() {
|
||||
x += speedX
|
||||
y += speedY
|
||||
rotation += rotationSpeed
|
||||
}
|
||||
}
|
||||
|
||||
private companion object {
|
||||
val CONFETTI_TEXTS = listOf("❊", "✿", "❊", "✦︎", "♥︎", "☕︎")
|
||||
const val EMITTER_POSITION_Y = -50
|
||||
const val CONFETTI_BASE_SIZE = 30
|
||||
const val CONFETTI_SIZE_VARIATION = 10
|
||||
}
|
||||
}
|
@ -0,0 +1,413 @@
|
||||
/*
|
||||
* 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.effect
|
||||
|
||||
import android.Manifest
|
||||
import android.net.Uri
|
||||
import android.os.Bundle
|
||||
import androidx.activity.ComponentActivity
|
||||
import androidx.activity.compose.rememberLauncherForActivityResult
|
||||
import androidx.activity.compose.setContent
|
||||
import androidx.activity.result.contract.ActivityResultContracts
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.compose.animation.animateContentSize
|
||||
import androidx.compose.animation.core.LinearEasing
|
||||
import androidx.compose.animation.core.tween
|
||||
import androidx.compose.foundation.clickable
|
||||
import androidx.compose.foundation.layout.Arrangement
|
||||
import androidx.compose.foundation.layout.Column
|
||||
import androidx.compose.foundation.layout.Row
|
||||
import androidx.compose.foundation.layout.fillMaxSize
|
||||
import androidx.compose.foundation.layout.fillMaxWidth
|
||||
import androidx.compose.foundation.layout.height
|
||||
import androidx.compose.foundation.layout.padding
|
||||
import androidx.compose.foundation.lazy.LazyColumn
|
||||
import androidx.compose.foundation.selection.selectable
|
||||
import androidx.compose.material3.AlertDialog
|
||||
import androidx.compose.material3.Button
|
||||
import androidx.compose.material3.Card
|
||||
import androidx.compose.material3.Checkbox
|
||||
import androidx.compose.material3.MaterialTheme
|
||||
import androidx.compose.material3.RadioButton
|
||||
import androidx.compose.material3.Scaffold
|
||||
import androidx.compose.material3.Slider
|
||||
import androidx.compose.material3.SnackbarHost
|
||||
import androidx.compose.material3.SnackbarHostState
|
||||
import androidx.compose.material3.Text
|
||||
import androidx.compose.runtime.Composable
|
||||
import androidx.compose.runtime.LaunchedEffect
|
||||
import androidx.compose.runtime.getValue
|
||||
import androidx.compose.runtime.mutableStateOf
|
||||
import androidx.compose.runtime.remember
|
||||
import androidx.compose.runtime.rememberCoroutineScope
|
||||
import androidx.compose.runtime.saveable.rememberSaveable
|
||||
import androidx.compose.runtime.setValue
|
||||
import androidx.compose.ui.Alignment
|
||||
import androidx.compose.ui.Modifier
|
||||
import androidx.compose.ui.platform.LocalContext
|
||||
import androidx.compose.ui.res.dimensionResource
|
||||
import androidx.compose.ui.res.stringResource
|
||||
import androidx.compose.ui.viewinterop.AndroidView
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.media3.common.Effect
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.common.util.Util.SDK_INT
|
||||
import androidx.media3.effect.Contrast
|
||||
import androidx.media3.effect.OverlayEffect
|
||||
import androidx.media3.effect.TextureOverlay
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.ui.PlayerView
|
||||
import com.google.common.collect.ImmutableList
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
class EffectActivity : ComponentActivity() {
|
||||
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
val playlistHolderList = mutableStateOf<List<PlaylistHolder>>(emptyList())
|
||||
lifecycleScope.launch {
|
||||
playlistHolderList.value =
|
||||
loadPlaylistsFromJson(JSON_FILENAME, this@EffectActivity, "EffectActivity")
|
||||
}
|
||||
setContent { EffectDemo(playlistHolderList.value) }
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
@Composable
|
||||
private fun EffectDemo(playlistHolderList: List<PlaylistHolder>) {
|
||||
val snackbarHostState = remember { SnackbarHostState() }
|
||||
val coroutineScope = rememberCoroutineScope()
|
||||
val context = LocalContext.current
|
||||
val exoPlayer by remember {
|
||||
mutableStateOf(ExoPlayer.Builder(context).build().apply { playWhenReady = true })
|
||||
}
|
||||
var effectsEnabled by remember { mutableStateOf(false) }
|
||||
|
||||
Scaffold(
|
||||
modifier = Modifier.fillMaxSize(),
|
||||
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
|
||||
) { paddingValues ->
|
||||
Column(
|
||||
modifier = Modifier.fillMaxWidth().padding(paddingValues),
|
||||
verticalArrangement = Arrangement.Top,
|
||||
horizontalAlignment = Alignment.CenterHorizontally,
|
||||
) {
|
||||
InputChooser(
|
||||
playlistHolderList,
|
||||
onException = { message ->
|
||||
coroutineScope.launch { snackbarHostState.showSnackbar(message) }
|
||||
},
|
||||
) { mediaItems ->
|
||||
effectsEnabled = true
|
||||
exoPlayer.apply {
|
||||
setMediaItems(mediaItems)
|
||||
setVideoEffects(emptyList())
|
||||
prepare()
|
||||
}
|
||||
}
|
||||
PlayerScreen(exoPlayer)
|
||||
EffectControls(
|
||||
effectsEnabled,
|
||||
onApplyEffectsClicked = { videoEffects ->
|
||||
exoPlayer.apply {
|
||||
setVideoEffects(videoEffects)
|
||||
prepare()
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun InputChooser(
|
||||
playlistHolderList: List<PlaylistHolder>,
|
||||
onException: (String) -> Unit,
|
||||
onNewMediaItems: (List<MediaItem>) -> Unit,
|
||||
) {
|
||||
var showPresetInputChooser by remember { mutableStateOf(false) }
|
||||
var showLocalFileChooser by remember { mutableStateOf(false) }
|
||||
Row(
|
||||
Modifier.padding(vertical = dimensionResource(id = R.dimen.regular_padding)),
|
||||
horizontalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.regular_padding)),
|
||||
) {
|
||||
Button(onClick = { showPresetInputChooser = true }) {
|
||||
Text(text = stringResource(id = R.string.choose_preset_input))
|
||||
}
|
||||
Button(onClick = { showLocalFileChooser = true }) {
|
||||
Text(text = stringResource(id = R.string.choose_local_file))
|
||||
}
|
||||
}
|
||||
if (showPresetInputChooser) {
|
||||
if (playlistHolderList.isNotEmpty()) {
|
||||
PresetInputChooser(
|
||||
playlistHolderList,
|
||||
onDismissRequest = { showPresetInputChooser = false },
|
||||
) { mediaItems ->
|
||||
onNewMediaItems(mediaItems)
|
||||
showPresetInputChooser = false
|
||||
}
|
||||
} else {
|
||||
onException(stringResource(id = R.string.no_loaded_playlists_error))
|
||||
showPresetInputChooser = false
|
||||
}
|
||||
}
|
||||
if (showLocalFileChooser) {
|
||||
LocalFileChooser(
|
||||
onException = { message ->
|
||||
onException(message)
|
||||
showLocalFileChooser = false
|
||||
}
|
||||
) { mediaItems ->
|
||||
onNewMediaItems(mediaItems)
|
||||
showLocalFileChooser = false
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PresetInputChooser(
|
||||
playlistHolderList: List<PlaylistHolder>,
|
||||
onDismissRequest: () -> Unit,
|
||||
onInputSelected: (List<MediaItem>) -> Unit,
|
||||
) {
|
||||
var selectedOption by remember { mutableStateOf(playlistHolderList.first()) }
|
||||
|
||||
AlertDialog(
|
||||
onDismissRequest = onDismissRequest,
|
||||
title = { Text(stringResource(id = R.string.choose_preset_input)) },
|
||||
confirmButton = {
|
||||
Button(onClick = { onInputSelected(selectedOption.mediaItems) }) {
|
||||
Text(text = stringResource(id = R.string.ok))
|
||||
}
|
||||
},
|
||||
text = {
|
||||
Column {
|
||||
playlistHolderList.forEach { playlistHolder ->
|
||||
Row(
|
||||
Modifier.fillMaxWidth()
|
||||
.selectable(
|
||||
(playlistHolder == selectedOption),
|
||||
onClick = { selectedOption = playlistHolder },
|
||||
),
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
RadioButton(
|
||||
selected = (playlistHolder == selectedOption),
|
||||
onClick = { selectedOption = playlistHolder },
|
||||
)
|
||||
Text(playlistHolder.title)
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
@Composable
|
||||
private fun LocalFileChooser(
|
||||
onException: (String) -> Unit,
|
||||
onFileSelected: (List<MediaItem>) -> Unit,
|
||||
) {
|
||||
val context = LocalContext.current
|
||||
val localFileChooserLauncher =
|
||||
rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.OpenDocument(),
|
||||
onResult = { uri: Uri? ->
|
||||
if (uri != null) {
|
||||
onFileSelected(listOf(MediaItem.fromUri(uri)))
|
||||
} else {
|
||||
onException(getString(R.string.can_not_open_file_error))
|
||||
}
|
||||
},
|
||||
)
|
||||
val permissionLauncher =
|
||||
rememberLauncherForActivityResult(
|
||||
contract = ActivityResultContracts.RequestPermission(),
|
||||
onResult = { isGranted: Boolean ->
|
||||
if (isGranted) {
|
||||
localFileChooserLauncher.launch(arrayOf("video/*"))
|
||||
} else {
|
||||
onException(getString(R.string.permission_not_granted_error))
|
||||
}
|
||||
},
|
||||
)
|
||||
LaunchedEffect(Unit) {
|
||||
val permission =
|
||||
if (SDK_INT >= 33) Manifest.permission.READ_MEDIA_VIDEO
|
||||
else Manifest.permission.READ_EXTERNAL_STORAGE
|
||||
val permissionCheck = ContextCompat.checkSelfPermission(context, permission)
|
||||
if (permissionCheck == android.content.pm.PackageManager.PERMISSION_GRANTED) {
|
||||
localFileChooserLauncher.launch(arrayOf("video/*"))
|
||||
} else {
|
||||
permissionLauncher.launch(permission)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun PlayerScreen(exoPlayer: ExoPlayer) {
|
||||
val context = LocalContext.current
|
||||
AndroidView(
|
||||
factory = { PlayerView(context).apply { player = exoPlayer } },
|
||||
modifier =
|
||||
Modifier.height(dimensionResource(id = R.dimen.android_view_height))
|
||||
.padding(all = dimensionResource(id = R.dimen.regular_padding)),
|
||||
)
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class)
|
||||
@Composable
|
||||
private fun EffectControls(enabled: Boolean, onApplyEffectsClicked: (List<Effect>) -> Unit) {
|
||||
var effectControlsState by remember { mutableStateOf(EffectControlsState()) }
|
||||
|
||||
Button(
|
||||
enabled = enabled && effectControlsState.effectsChanged,
|
||||
onClick = {
|
||||
val effectsList = mutableListOf<Effect>()
|
||||
|
||||
if (effectControlsState.contrastValue != 0f) {
|
||||
effectsList += Contrast(effectControlsState.contrastValue)
|
||||
}
|
||||
|
||||
val overlaysBuilder = ImmutableList.builder<TextureOverlay>()
|
||||
if (effectControlsState.confettiOverlayChecked) {
|
||||
overlaysBuilder.add(ConfettiOverlay())
|
||||
}
|
||||
effectsList += OverlayEffect(overlaysBuilder.build())
|
||||
|
||||
onApplyEffectsClicked(effectsList)
|
||||
effectControlsState = effectControlsState.copy(effectsChanged = false)
|
||||
},
|
||||
) {
|
||||
Text(text = stringResource(id = R.string.apply_effects))
|
||||
}
|
||||
|
||||
EffectControlsList(enabled, effectControlsState) { newEffectControlsState ->
|
||||
effectControlsState = newEffectControlsState
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
private fun EffectControlsList(
|
||||
enabled: Boolean,
|
||||
effectControlsState: EffectControlsState,
|
||||
onEffectControlsStateChange: (EffectControlsState) -> Unit,
|
||||
) {
|
||||
LazyColumn(Modifier.padding(vertical = dimensionResource(id = R.dimen.small_padding))) {
|
||||
item {
|
||||
EffectItem(
|
||||
name = stringResource(id = R.string.contrast),
|
||||
enabled = enabled,
|
||||
onCheckedChange = {
|
||||
onEffectControlsStateChange(
|
||||
effectControlsState.copy(effectsChanged = true, contrastValue = 0f)
|
||||
)
|
||||
},
|
||||
) {
|
||||
Row {
|
||||
Text(
|
||||
text = "%.2f".format(effectControlsState.contrastValue),
|
||||
style = MaterialTheme.typography.bodyLarge,
|
||||
modifier = Modifier.padding(dimensionResource(id = R.dimen.large_padding)).weight(1f),
|
||||
)
|
||||
Slider(
|
||||
value = effectControlsState.contrastValue,
|
||||
onValueChange = { newContrastValue ->
|
||||
val newRoundedContrastValue = "%.2f".format(newContrastValue).toFloat()
|
||||
onEffectControlsStateChange(
|
||||
effectControlsState.copy(
|
||||
effectsChanged = true,
|
||||
contrastValue = newRoundedContrastValue,
|
||||
)
|
||||
)
|
||||
},
|
||||
valueRange = -1f..1f,
|
||||
modifier = Modifier.weight(4f),
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
item {
|
||||
EffectItem(
|
||||
name = stringResource(R.string.confetti_overlay),
|
||||
enabled = enabled,
|
||||
onCheckedChange = { checked ->
|
||||
onEffectControlsStateChange(
|
||||
effectControlsState.copy(effectsChanged = true, confettiOverlayChecked = checked)
|
||||
)
|
||||
},
|
||||
)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
@Composable
|
||||
fun EffectItem(
|
||||
name: String,
|
||||
enabled: Boolean,
|
||||
onCheckedChange: (Boolean) -> Unit = {},
|
||||
content: @Composable () -> Unit = {},
|
||||
) {
|
||||
var checked by rememberSaveable { mutableStateOf(false) }
|
||||
Card(
|
||||
modifier =
|
||||
Modifier.padding(
|
||||
vertical = dimensionResource(id = R.dimen.small_padding),
|
||||
horizontal = dimensionResource(id = R.dimen.regular_padding),
|
||||
)
|
||||
.clickable(enabled = enabled && !checked) {
|
||||
checked = !checked
|
||||
onCheckedChange(checked)
|
||||
}
|
||||
) {
|
||||
Column(
|
||||
Modifier.padding(dimensionResource(id = R.dimen.large_padding))
|
||||
.animateContentSize(animationSpec = tween(durationMillis = 200, easing = LinearEasing))
|
||||
) {
|
||||
Row {
|
||||
Column(Modifier.weight(1f).padding(dimensionResource(id = R.dimen.large_padding))) {
|
||||
Text(text = name, style = MaterialTheme.typography.bodyLarge)
|
||||
}
|
||||
Checkbox(
|
||||
enabled = enabled,
|
||||
checked = checked,
|
||||
onCheckedChange = {
|
||||
checked = !checked
|
||||
onCheckedChange(checked)
|
||||
},
|
||||
)
|
||||
}
|
||||
if (checked) {
|
||||
content()
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
data class EffectControlsState(
|
||||
val effectsChanged: Boolean = false,
|
||||
val contrastValue: Float = 0f,
|
||||
val confettiOverlayChecked: Boolean = false,
|
||||
)
|
||||
|
||||
companion object {
|
||||
const val JSON_FILENAME = "media.playlist.json"
|
||||
}
|
||||
}
|
@ -0,0 +1,78 @@
|
||||
/*
|
||||
* 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.effect
|
||||
|
||||
import android.content.Context
|
||||
import android.net.Uri
|
||||
import android.util.JsonReader
|
||||
import android.util.Log
|
||||
import androidx.media3.common.MediaItem
|
||||
import java.io.IOException
|
||||
import java.io.InputStreamReader
|
||||
import java.nio.charset.StandardCharsets
|
||||
import kotlinx.coroutines.Dispatchers
|
||||
import kotlinx.coroutines.withContext
|
||||
|
||||
internal suspend fun loadPlaylistsFromJson(
|
||||
jsonFilename: String,
|
||||
context: Context,
|
||||
tag: String,
|
||||
): List<PlaylistHolder> =
|
||||
withContext(Dispatchers.IO) {
|
||||
try {
|
||||
context.assets.open(jsonFilename).use { inputStream ->
|
||||
val reader = JsonReader(InputStreamReader(inputStream, StandardCharsets.UTF_8))
|
||||
val playlistHolders = buildList {
|
||||
reader.beginArray()
|
||||
while (reader.hasNext()) {
|
||||
readPlaylist(reader)?.let { add(it) }
|
||||
}
|
||||
reader.endArray()
|
||||
}
|
||||
playlistHolders
|
||||
}
|
||||
} catch (e: IOException) {
|
||||
Log.e(tag, context.getString(R.string.playlist_loading_error, jsonFilename, e))
|
||||
emptyList()
|
||||
}
|
||||
}
|
||||
|
||||
private fun readPlaylist(reader: JsonReader): PlaylistHolder? {
|
||||
val playlistHolder = PlaylistHolder("", emptyList())
|
||||
reader.beginObject()
|
||||
while (reader.hasNext()) {
|
||||
val name = reader.nextName()
|
||||
if (name.equals("name")) {
|
||||
playlistHolder.title = reader.nextString()
|
||||
} else if (name.equals("playlist")) {
|
||||
playlistHolder.mediaItems = buildList {
|
||||
reader.beginArray()
|
||||
while (reader.hasNext()) {
|
||||
reader.beginObject()
|
||||
reader.nextName()
|
||||
add(MediaItem.fromUri(Uri.parse(reader.nextString())))
|
||||
reader.endObject()
|
||||
}
|
||||
reader.endArray()
|
||||
}
|
||||
}
|
||||
}
|
||||
reader.endObject()
|
||||
// Only return the playlistHolder object if it has media items
|
||||
return if (playlistHolder.mediaItems.isNotEmpty()) playlistHolder else null
|
||||
}
|
||||
|
||||
internal data class PlaylistHolder(var title: String, var mediaItems: List<MediaItem>)
|
BIN
demos/effect/src/main/res/mipmap-hdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 3.3 KiB |
BIN
demos/effect/src/main/res/mipmap-mdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 2.1 KiB |
BIN
demos/effect/src/main/res/mipmap-xhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 4.8 KiB |
BIN
demos/effect/src/main/res/mipmap-xxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 7.3 KiB |
BIN
demos/effect/src/main/res/mipmap-xxxhdpi/ic_launcher.png
Normal file
After Width: | Height: | Size: 10 KiB |
31
demos/effect/src/main/res/values-night/themes.xml
Normal 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.Media3EffectDemo" 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>
|
24
demos/effect/src/main/res/values/colors.xml
Normal 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
|
||||
|
||||
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>
|
||||
</resources>
|
21
demos/effect/src/main/res/values/dimens.xml
Normal file
@ -0,0 +1,21 @@
|
||||
<?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>
|
||||
<dimen name="small_padding">4dp</dimen>
|
||||
<dimen name="regular_padding">8dp</dimen>
|
||||
<dimen name="large_padding">12dp</dimen>
|
||||
<dimen name="android_view_height">256dp</dimen>
|
||||
</resources>
|
28
demos/effect/src/main/res/values/strings.xml
Normal file
@ -0,0 +1,28 @@
|
||||
<?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">Effect Demo</string>
|
||||
<string name="choose_preset_input">Choose preset input</string>
|
||||
<string name="choose_local_file">Choose local file</string>
|
||||
<string name="apply_effects">Apply effects</string>
|
||||
<string name="ok">OK</string>
|
||||
<string name="playlist_loading_error">Error loading playlist from %1$s: %2$s</string>
|
||||
<string name="no_loaded_playlists_error">There are no loaded preset inputs.</string>
|
||||
<string name="can_not_open_file_error">"File couldn't be opened. Please try again."</string>
|
||||
<string name="permission_not_granted_error">"Permission was not granted."</string>
|
||||
<string name="contrast">Contrast</string>
|
||||
<string name="confetti_overlay">Confetti Overlay</string>
|
||||
</resources>
|
31
demos/effect/src/main/res/values/themes.xml
Normal 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.Media3EffectDemo" 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>
|
@ -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
|
||||
}
|
||||
|
@ -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">
|
||||
|
@ -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);
|
||||
|
@ -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));
|
||||
|
@ -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;
|
||||
|
@ -16,7 +16,9 @@ apply from: '../../constants.gradle'
|
||||
apply plugin: 'com.android.application'
|
||||
|
||||
android {
|
||||
compileSdkVersion project.ext.compileSdkVersion
|
||||
namespace 'androidx.media3.demo.main'
|
||||
|
||||
compileSdk project.ext.compileSdkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
@ -27,10 +29,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 +53,9 @@ android {
|
||||
disable 'GoogleAppIndexingWarning','MissingTranslation','IconDensities'
|
||||
}
|
||||
|
||||
flavorDimensions "decoderExtensions"
|
||||
flavorDimensions = ["decoderExtensions"]
|
||||
|
||||
buildFeatures.buildConfig true
|
||||
|
||||
productFlavors {
|
||||
noDecoderExtensions {
|
||||
@ -72,7 +73,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 +86,10 @@ 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-decoder-mpegh')
|
||||
withDecoderExtensionsImplementation project(modulePrefix + 'lib-datasource-rtmp')
|
||||
}
|
||||
|
||||
|
@ -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"/>
|
||||
|
@ -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",
|
||||
@ -274,15 +280,25 @@
|
||||
"name": "IMA sample ad tags",
|
||||
"samples": [
|
||||
{
|
||||
"name": "Single inline linear",
|
||||
"name": "VMAP pre-roll single ad, mid-roll standard pods with 5 ads every 10 seconds for 1:40, post-roll single ad",
|
||||
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
|
||||
"ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dlinear&correlator="
|
||||
"ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpostlongpod&cmsid=496&vid=short_tencue&correlator="
|
||||
},
|
||||
{
|
||||
"name": "VMAP empty midroll",
|
||||
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
|
||||
"ad_tag_uri": "https://vastsynthesizer.appspot.com/empty-midroll"
|
||||
},
|
||||
{
|
||||
"name": "Single skippable inline",
|
||||
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
|
||||
"ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dskippablelinear&correlator="
|
||||
},
|
||||
{
|
||||
"name": "Single inline linear",
|
||||
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
|
||||
"ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dlinear&correlator="
|
||||
},
|
||||
{
|
||||
"name": "Single redirect linear",
|
||||
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
|
||||
@ -343,16 +359,6 @@
|
||||
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
|
||||
"ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpostoptimizedpodbumper&cmsid=496&vid=short_onecue&correlator="
|
||||
},
|
||||
{
|
||||
"name": "VMAP pre-roll single ad, mid-roll standard pods with 5 ads every 10 seconds for 1:40, post-roll single ad",
|
||||
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
|
||||
"ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpostlongpod&cmsid=496&vid=short_tencue&correlator="
|
||||
},
|
||||
{
|
||||
"name": "VMAP empty midroll",
|
||||
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
|
||||
"ad_tag_uri": "https://vastsynthesizer.appspot.com/empty-midroll"
|
||||
},
|
||||
{
|
||||
"name": "VMAP full, empty, full midrolls",
|
||||
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
|
||||
@ -390,6 +396,10 @@
|
||||
{
|
||||
"name": "IMA DAI streams",
|
||||
"samples": [
|
||||
{
|
||||
"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": "HLS VOD: Demo (skippable pre/post), single ads [30 s]",
|
||||
"uri": "ssai://dai.google.com/?contentSourceId=2483977&videoId=ima-vod-skippable-test&format=2&adsId=1"
|
||||
@ -403,8 +413,44 @@
|
||||
"uri": "ssai://dai.google.com/?assetKey=sN_IYUG8STe1ZzhIIE_ksA&format=2&adsId=3"
|
||||
},
|
||||
{
|
||||
"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": "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"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"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": "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",
|
||||
@ -434,20 +480,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": [
|
||||
@ -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,48 @@
|
||||
{
|
||||
"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": "MPEG-H HD (MP4, H265)",
|
||||
"uri": "https://media.githubusercontent.com/media/Fraunhofer-IIS/mpegh-test-content/main/TRI_Fileset_17_514H_D1_D2_D3_O1_24bit1080p50.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
|
||||
}
|
||||
]
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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) {
|
||||
context = context.getApplicationContext();
|
||||
@Nullable CronetEngine cronetEngine = CronetUtil.buildCronetEngine(context);
|
||||
if (cronetEngine != null) {
|
||||
httpDataSourceFactory =
|
||||
new CronetDataSource.Factory(cronetEngine, Executors.newSingleThreadExecutor());
|
||||
}
|
||||
}
|
||||
if (httpDataSourceFactory == null) {
|
||||
// We don't want to use Cronet, or we failed to instantiate a CronetEngine.
|
||||
CookieManager cookieManager = new CookieManager();
|
||||
cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER);
|
||||
CookieHandler.setDefault(cookieManager);
|
||||
httpDataSourceFactory = new DefaultHttpDataSource.Factory();
|
||||
}
|
||||
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;
|
||||
}
|
||||
// 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;
|
||||
|
@ -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,32 +384,41 @@ public class DownloadTracker {
|
||||
this.downloadHelper = downloadHelper;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
OfflineLicenseHelper offlineLicenseHelper =
|
||||
OfflineLicenseHelper.newWidevineInstance(
|
||||
drmConfiguration.licenseUri.toString(),
|
||||
drmConfiguration.forceDefaultLicenseUri,
|
||||
dataSourceFactory,
|
||||
drmConfiguration.licenseRequestHeaders,
|
||||
new DrmSessionEventListener.EventDispatcher());
|
||||
try {
|
||||
keySetId = offlineLicenseHelper.downloadLicense(format);
|
||||
} catch (DrmSession.DrmSessionException e) {
|
||||
drmSessionException = e;
|
||||
} finally {
|
||||
offlineLicenseHelper.release();
|
||||
public void cancel() {
|
||||
if (future != null) {
|
||||
future.cancel(/* mayInterruptIfRunning= */ false);
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid) {
|
||||
if (drmSessionException != null) {
|
||||
dialogHelper.onOfflineLicenseFetchedError(drmSessionException);
|
||||
} else {
|
||||
dialogHelper.onOfflineLicenseFetched(downloadHelper, checkNotNull(keySetId));
|
||||
}
|
||||
public void execute() {
|
||||
future =
|
||||
executorService.submit(
|
||||
() -> {
|
||||
OfflineLicenseHelper offlineLicenseHelper =
|
||||
OfflineLicenseHelper.newWidevineInstance(
|
||||
drmConfiguration.licenseUri.toString(),
|
||||
drmConfiguration.forceDefaultLicenseUri,
|
||||
dataSourceFactory,
|
||||
drmConfiguration.licenseRequestHeaders,
|
||||
new DrmSessionEventListener.EventDispatcher());
|
||||
try {
|
||||
keySetId = offlineLicenseHelper.downloadLicense(format);
|
||||
} catch (DrmSession.DrmSessionException e) {
|
||||
drmSessionException = e;
|
||||
} finally {
|
||||
offlineLicenseHelper.release();
|
||||
new Handler(Looper.getMainLooper())
|
||||
.post(
|
||||
() -> {
|
||||
if (drmSessionException != null) {
|
||||
dialogHelper.onOfflineLicenseFetchedError(drmSessionException);
|
||||
} else {
|
||||
dialogHelper.onOfflineLicenseFetched(
|
||||
downloadHelper, checkNotNull(keySetId));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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(
|
||||
|
@ -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() {
|
||||
Intent intent = getIntent();
|
||||
if (player == null) {
|
||||
Intent intent = getIntent();
|
||||
|
||||
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) {
|
||||
|
@ -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,34 +293,43 @@ 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;
|
||||
|
||||
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||
@Override
|
||||
protected List<PlaylistGroup> doInBackground(String... uris) {
|
||||
List<PlaylistGroup> result = new ArrayList<>();
|
||||
Context context = getApplicationContext();
|
||||
DataSource dataSource = DemoUtil.getDataSourceFactory(context).createDataSource();
|
||||
for (String uri : uris) {
|
||||
DataSpec dataSpec = new DataSpec(Uri.parse(uri));
|
||||
InputStream inputStream = new DataSourceInputStream(dataSource, dataSpec);
|
||||
try {
|
||||
readPlaylistGroups(new JsonReader(new InputStreamReader(inputStream, "UTF-8")), result);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error loading sample list: " + uri, e);
|
||||
sawError = true;
|
||||
} finally {
|
||||
DataSourceUtil.closeQuietly(dataSource);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
public SampleListLoader() {
|
||||
executorService = Executors.newSingleThreadExecutor();
|
||||
}
|
||||
|
||||
@Override
|
||||
protected void onPostExecute(List<PlaylistGroup> result) {
|
||||
onPlaylistGroups(result, sawError);
|
||||
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||
public void execute(String... uris) {
|
||||
executorService.execute(
|
||||
() -> {
|
||||
List<PlaylistGroup> result = new ArrayList<>();
|
||||
Context context = getApplicationContext();
|
||||
DataSource dataSource = DemoUtil.getDataSourceFactory(context).createDataSource();
|
||||
for (String uri : uris) {
|
||||
DataSpec dataSpec = new DataSpec(Uri.parse(uri));
|
||||
InputStream inputStream = new DataSourceInputStream(dataSource, dataSpec);
|
||||
try {
|
||||
readPlaylistGroups(
|
||||
new JsonReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)),
|
||||
result);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error loading sample list: " + uri, e);
|
||||
sawError = true;
|
||||
} finally {
|
||||
DataSourceUtil.closeQuietly(dataSource);
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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>
|
||||
|