Compare commits
No commits in common. "release" and "1.2.1" have entirely different histories.
10
.github/ISSUE_TEMPLATE/bug.yml
vendored
|
|
@ -17,16 +17,10 @@ body:
|
||||||
label: Version
|
label: Version
|
||||||
description: What version of Media3 (or ExoPlayer) are you using?
|
description: What version of Media3 (or ExoPlayer) are you using?
|
||||||
options:
|
options:
|
||||||
- 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.1
|
||||||
- Media3 1.2.0
|
- Media3 1.2.0
|
||||||
|
- Media3 main branch
|
||||||
|
- Media3 pre-release (alpha, beta or RC not in this list)
|
||||||
- Media3 1.1.1 / ExoPlayer 2.19.1
|
- Media3 1.1.1 / ExoPlayer 2.19.1
|
||||||
- Media3 1.1.0 / ExoPlayer 2.19.0
|
- Media3 1.1.0 / ExoPlayer 2.19.0
|
||||||
- Media3 1.0.2 / ExoPlayer 2.18.7
|
- Media3 1.0.2 / ExoPlayer 2.18.7
|
||||||
|
|
|
||||||
41
.gitignore
vendored
|
|
@ -52,31 +52,30 @@ tmp
|
||||||
|
|
||||||
# External native builds
|
# External native builds
|
||||||
.externalNativeBuild
|
.externalNativeBuild
|
||||||
.cxx
|
|
||||||
|
|
||||||
# VP9 decoder extension
|
# VP9 extension
|
||||||
libraries/decoder_vp9/src/main/jni/libvpx
|
extensions/vp9/src/main/jni/libvpx
|
||||||
libraries/decoder_vp9/src/main/jni/libvpx_android_configs
|
extensions/vp9/src/main/jni/libvpx_android_configs
|
||||||
libraries/decoder_vp9/src/main/jni/libyuv
|
extensions/vp9/src/main/jni/libyuv
|
||||||
|
|
||||||
# AV1 decoder extension
|
# AV1 extension
|
||||||
libraries/decoder_av1/src/main/jni/cpu_features
|
extensions/av1/src/main/jni/cpu_features
|
||||||
libraries/decoder_av1/src/main/jni/libgav1
|
extensions/av1/src/main/jni/libgav1
|
||||||
|
|
||||||
# Opus decoder extension
|
# Opus extension
|
||||||
libraries/decoder_opus/src/main/jni/libopus
|
extensions/opus/src/main/jni/libopus
|
||||||
|
|
||||||
# FLAC decoder extension
|
# FLAC extension
|
||||||
libraries/decoder_flac/src/main/jni/flac
|
extensions/flac/src/main/jni/flac
|
||||||
|
|
||||||
# FFmpeg decoder extension
|
# FFmpeg extension
|
||||||
libraries/decoder_ffmpeg/src/main/jni/ffmpeg
|
extensions/ffmpeg/src/main/jni/ffmpeg
|
||||||
|
|
||||||
# Cronet datasource extension
|
# Cronet extension
|
||||||
libraries/datasource_cronet/jniLibs/*
|
extensions/cronet/jniLibs/*
|
||||||
!libraries/datasource_cronet/jniLibs/README.md
|
!extensions/cronet/jniLibs/README.md
|
||||||
libraries/datasource_cronet/libs/*
|
extensions/cronet/libs/*
|
||||||
!libraries/datasource_cronet/libs/README.md
|
!extensions/cronet/libs/README.md
|
||||||
|
|
||||||
# MIDI decoder extension
|
# MIDI extension
|
||||||
libraries/decoder_midi/lib
|
extensions/midi/lib
|
||||||
|
|
|
||||||
45
README.md
|
|
@ -100,6 +100,12 @@ compileOptions {
|
||||||
}
|
}
|
||||||
```
|
```
|
||||||
|
|
||||||
|
#### 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
|
### Locally
|
||||||
|
|
||||||
Cloning the repository and depending on the modules locally is required when
|
Cloning the repository and depending on the modules locally is required when
|
||||||
|
|
@ -110,20 +116,23 @@ First, clone the repository into a local directory:
|
||||||
|
|
||||||
```sh
|
```sh
|
||||||
git clone https://github.com/androidx/media.git
|
git clone https://github.com/androidx/media.git
|
||||||
|
cd media
|
||||||
```
|
```
|
||||||
|
|
||||||
Next, add the following to your project's `settings.gradle.kts` 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:
|
`path/to/media` with the path to your local copy:
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
(gradle as ExtensionAware).extra["androidxMediaModulePrefix"] = "media3-"
|
gradle.extra.apply {
|
||||||
|
set("androidxMediaModulePrefix", "media-")
|
||||||
|
}
|
||||||
apply(from = file("path/to/media/core_settings.gradle"))
|
apply(from = file("path/to/media/core_settings.gradle"))
|
||||||
```
|
```
|
||||||
|
|
||||||
Or in Gradle Groovy DSL `settings.gradle`:
|
Or in Gradle Groovy DSL `settings.gradle`:
|
||||||
|
|
||||||
```groovy
|
```groovy
|
||||||
gradle.ext.androidxMediaModulePrefix = 'media3-'
|
gradle.ext.androidxMediaModulePrefix = 'media-'
|
||||||
apply from: file("path/to/media/core_settings.gradle")
|
apply from: file("path/to/media/core_settings.gradle")
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
@ -132,37 +141,17 @@ You can depend on them from `build.gradle.kts` as you would on any other local
|
||||||
module, for example:
|
module, for example:
|
||||||
|
|
||||||
```kotlin
|
```kotlin
|
||||||
implementation(project(":media3-lib-exoplayer"))
|
implementation(project(":media-lib-exoplayer"))
|
||||||
implementation(project(":media3-lib-exoplayer-dash"))
|
implementation(project(":media-lib-exoplayer-dash"))
|
||||||
implementation(project(":media3-lib-ui"))
|
implementation(project(":media-lib-ui"))
|
||||||
```
|
```
|
||||||
|
|
||||||
Or in Gradle Groovy DSL `build.gradle`:
|
Or in Gradle Groovy DSL `build.gradle`:
|
||||||
|
|
||||||
```groovy
|
```groovy
|
||||||
implementation project(':media3-lib-exoplayer')
|
implementation project(':media-lib-exoplayer')
|
||||||
implementation project(':media3-lib-exoplayer-dash')
|
implementation project(':media-lib-exoplayer-dash')
|
||||||
implementation project(':media3-lib-ui')
|
implementation project(':media-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
|
## Developing AndroidX Media
|
||||||
|
|
|
||||||
1119
RELEASENOTES.md
67
api.txt
|
|
@ -26,7 +26,7 @@ package androidx.media3.common {
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class AudioAttributes {
|
public final class AudioAttributes {
|
||||||
method public androidx.media3.common.AudioAttributes.AudioAttributesV21 getAudioAttributesV21();
|
method @RequiresApi(21) public androidx.media3.common.AudioAttributes.AudioAttributesV21 getAudioAttributesV21();
|
||||||
field public static final androidx.media3.common.AudioAttributes DEFAULT;
|
field public static final androidx.media3.common.AudioAttributes DEFAULT;
|
||||||
field @androidx.media3.common.C.AudioAllowedCapturePolicy public final int allowedCapturePolicy;
|
field @androidx.media3.common.C.AudioAllowedCapturePolicy public final int allowedCapturePolicy;
|
||||||
field @androidx.media3.common.C.AudioContentType public final int contentType;
|
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;
|
field @androidx.media3.common.C.AudioUsage public final int usage;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class AudioAttributes.AudioAttributesV21 {
|
@RequiresApi(21) public static final class AudioAttributes.AudioAttributesV21 {
|
||||||
field public final android.media.AudioAttributes audioAttributes;
|
field public final android.media.AudioAttributes audioAttributes;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -79,7 +79,6 @@ package androidx.media3.common {
|
||||||
field public static final java.util.UUID PLAYREADY_UUID;
|
field public static final java.util.UUID PLAYREADY_UUID;
|
||||||
field public static final float RATE_UNSET = -3.4028235E38f;
|
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_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_CAPTION = 64; // 0x40
|
||||||
field public static final int ROLE_FLAG_COMMENTARY = 8; // 0x8
|
field public static final int ROLE_FLAG_COMMENTARY = 8; // 0x8
|
||||||
field public static final int ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND = 1024; // 0x400
|
field public static final int ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND = 1024; // 0x400
|
||||||
|
|
@ -157,7 +156,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(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, 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.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.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 {
|
@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 {
|
||||||
|
|
@ -549,7 +548,6 @@ package androidx.media3.common {
|
||||||
field public static final String APPLICATION_PGS = "application/pgs";
|
field public static final String APPLICATION_PGS = "application/pgs";
|
||||||
field @Deprecated 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_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_SS = "application/vnd.ms-sstr+xml";
|
||||||
field public static final String APPLICATION_SUBRIP = "application/x-subrip";
|
field public static final String APPLICATION_SUBRIP = "application/x-subrip";
|
||||||
field public static final String APPLICATION_TTML = "application/ttml+xml";
|
field public static final String APPLICATION_TTML = "application/ttml+xml";
|
||||||
|
|
@ -623,20 +621,13 @@ package androidx.media3.common {
|
||||||
method public static String getErrorCodeName(@androidx.media3.common.PlaybackException.ErrorCode int);
|
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 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_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_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_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_INIT_FAILED = 4001; // 0xfa1
|
||||||
field public static final int ERROR_CODE_DECODER_QUERY_FAILED = 4002; // 0xfa2
|
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_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_EXCEEDS_CAPABILITIES = 4004; // 0xfa4
|
||||||
field public static final int ERROR_CODE_DECODING_FORMAT_UNSUPPORTED = 4005; // 0xfa5
|
field public static final int ERROR_CODE_DECODING_FORMAT_UNSUPPORTED = 4005; // 0xfa5
|
||||||
field public static final int ERROR_CODE_DISCONNECTED = -100; // 0xffffff9c
|
|
||||||
field public static final int ERROR_CODE_DRM_CONTENT_ERROR = 6003; // 0x1773
|
field public static final int ERROR_CODE_DRM_CONTENT_ERROR = 6003; // 0x1773
|
||||||
field public static final int ERROR_CODE_DRM_DEVICE_REVOKED = 6007; // 0x1777
|
field public static final int ERROR_CODE_DRM_DEVICE_REVOKED = 6007; // 0x1777
|
||||||
field public static final int ERROR_CODE_DRM_DISALLOWED_OPERATION = 6005; // 0x1775
|
field public static final int ERROR_CODE_DRM_DISALLOWED_OPERATION = 6005; // 0x1775
|
||||||
|
|
@ -646,9 +637,7 @@ package androidx.media3.common {
|
||||||
field public static final int ERROR_CODE_DRM_SCHEME_UNSUPPORTED = 6001; // 0x1771
|
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_SYSTEM_ERROR = 6006; // 0x1776
|
||||||
field public static final int ERROR_CODE_DRM_UNSPECIFIED = 6000; // 0x1770
|
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_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_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_CLEARTEXT_NOT_PERMITTED = 2007; // 0x7d7
|
||||||
field public static final int ERROR_CODE_IO_FILE_NOT_FOUND = 2005; // 0x7d5
|
field public static final int ERROR_CODE_IO_FILE_NOT_FOUND = 2005; // 0x7d5
|
||||||
|
|
@ -658,25 +647,18 @@ package androidx.media3.common {
|
||||||
field public static final int ERROR_CODE_IO_NO_PERMISSION = 2006; // 0x7d6
|
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_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_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_MALFORMED = 3001; // 0xbb9
|
||||||
field public static final int ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED = 3003; // 0xbbb
|
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_MALFORMED = 3002; // 0xbba
|
||||||
field public static final int ERROR_CODE_PARSING_MANIFEST_UNSUPPORTED = 3004; // 0xbbc
|
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_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_TIMEOUT = 1003; // 0x3eb
|
||||||
field public static final int ERROR_CODE_UNSPECIFIED = 1000; // 0x3e8
|
field public static final int ERROR_CODE_UNSPECIFIED = 1000; // 0x3e8
|
||||||
field @androidx.media3.common.PlaybackException.ErrorCode public final int errorCode;
|
field @androidx.media3.common.PlaybackException.ErrorCode public final int errorCode;
|
||||||
field public final long timestampMs;
|
field public final long timestampMs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@IntDef(open=true, value={androidx.media3.common.PlaybackException.ERROR_CODE_INVALID_STATE, androidx.media3.common.PlaybackException.ERROR_CODE_BAD_VALUE, androidx.media3.common.PlaybackException.ERROR_CODE_PERMISSION_DENIED, androidx.media3.common.PlaybackException.ERROR_CODE_NOT_SUPPORTED, androidx.media3.common.PlaybackException.ERROR_CODE_DISCONNECTED, androidx.media3.common.PlaybackException.ERROR_CODE_AUTHENTICATION_EXPIRED, androidx.media3.common.PlaybackException.ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED, androidx.media3.common.PlaybackException.ERROR_CODE_CONCURRENT_STREAM_LIMIT, androidx.media3.common.PlaybackException.ERROR_CODE_PARENTAL_CONTROL_RESTRICTED, androidx.media3.common.PlaybackException.ERROR_CODE_NOT_AVAILABLE_IN_REGION, androidx.media3.common.PlaybackException.ERROR_CODE_SKIP_LIMIT_REACHED, androidx.media3.common.PlaybackException.ERROR_CODE_SETUP_REQUIRED, androidx.media3.common.PlaybackException.ERROR_CODE_END_OF_PLAYLIST, androidx.media3.common.PlaybackException.ERROR_CODE_CONTENT_ALREADY_PLAYING, androidx.media3.common.PlaybackException.ERROR_CODE_UNSPECIFIED, androidx.media3.common.PlaybackException.ERROR_CODE_REMOTE_ERROR, androidx.media3.common.PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW, androidx.media3.common.PlaybackException.ERROR_CODE_TIMEOUT, androidx.media3.common.PlaybackException.ERROR_CODE_FAILED_RUNTIME_CHECK, androidx.media3.common.PlaybackException.ERROR_CODE_IO_UNSPECIFIED, androidx.media3.common.PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT, androidx.media3.common.PlaybackException.ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE, androidx.media3.common.PlaybackException.ERROR_CODE_IO_BAD_HTTP_STATUS, androidx.media3.common.PlaybackException.ERROR_CODE_IO_FILE_NOT_FOUND, androidx.media3.common.PlaybackException.ERROR_CODE_IO_NO_PERMISSION, androidx.media3.common.PlaybackException.ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED, androidx.media3.common.PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE, androidx.media3.common.PlaybackException.ERROR_CODE_PARSING_CONTAINER_MALFORMED, androidx.media3.common.PlaybackException.ERROR_CODE_PARSING_MANIFEST_MALFORMED, androidx.media3.common.PlaybackException.ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED, androidx.media3.common.PlaybackException.ERROR_CODE_PARSING_MANIFEST_UNSUPPORTED, androidx.media3.common.PlaybackException.ERROR_CODE_DECODER_INIT_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DECODER_QUERY_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DECODING_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DECODING_FORMAT_EXCEEDS_CAPABILITIES, androidx.media3.common.PlaybackException.ERROR_CODE_DECODING_FORMAT_UNSUPPORTED, androidx.media3.common.PlaybackException.ERROR_CODE_AUDIO_TRACK_INIT_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_AUDIO_TRACK_WRITE_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_AUDIO_TRACK_OFFLOAD_INIT_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_AUDIO_TRACK_OFFLOAD_WRITE_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_UNSPECIFIED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_SCHEME_UNSUPPORTED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_PROVISIONING_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_CONTENT_ERROR, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_LICENSE_ACQUISITION_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_DISALLOWED_OPERATION, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_SYSTEM_ERROR, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_DEVICE_REVOKED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_LICENSE_EXPIRED}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface PlaybackException.ErrorCode {
|
@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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class PlaybackParameters {
|
public final class PlaybackParameters {
|
||||||
|
|
@ -781,7 +763,7 @@ package androidx.media3.common {
|
||||||
method @Deprecated public void setDeviceMuted(boolean);
|
method @Deprecated public void setDeviceMuted(boolean);
|
||||||
method public void setDeviceMuted(boolean, @androidx.media3.common.C.VolumeFlags int);
|
method public void setDeviceMuted(boolean, @androidx.media3.common.C.VolumeFlags int);
|
||||||
method @Deprecated public void setDeviceVolume(@IntRange(from=0) 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 setDeviceVolume(@IntRange(from=0) int, int);
|
||||||
method public void setMediaItem(androidx.media3.common.MediaItem);
|
method public void setMediaItem(androidx.media3.common.MediaItem);
|
||||||
method public void setMediaItem(androidx.media3.common.MediaItem, boolean);
|
method public void setMediaItem(androidx.media3.common.MediaItem, boolean);
|
||||||
method public void setMediaItem(androidx.media3.common.MediaItem, long);
|
method public void setMediaItem(androidx.media3.common.MediaItem, long);
|
||||||
|
|
@ -844,7 +826,6 @@ package androidx.media3.common {
|
||||||
field public static final int DISCONTINUITY_REASON_REMOVE = 4; // 0x4
|
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 = 1; // 0x1
|
||||||
field public static final int DISCONTINUITY_REASON_SEEK_ADJUSTMENT = 2; // 0x2
|
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 DISCONTINUITY_REASON_SKIP = 3; // 0x3
|
||||||
field public static final int EVENT_AUDIO_ATTRIBUTES_CHANGED = 20; // 0x14
|
field public static final int EVENT_AUDIO_ATTRIBUTES_CHANGED = 20; // 0x14
|
||||||
field public static final int EVENT_AUDIO_SESSION_ID = 21; // 0x15
|
field public static final int EVENT_AUDIO_SESSION_ID = 21; // 0x15
|
||||||
|
|
@ -913,7 +894,7 @@ package androidx.media3.common {
|
||||||
field public static final androidx.media3.common.Player.Commands EMPTY;
|
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, 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.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.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 {
|
@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 {
|
||||||
|
|
@ -1094,7 +1075,7 @@ package androidx.media3.common {
|
||||||
method public androidx.media3.common.TrackSelectionParameters.Builder buildUpon();
|
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 fromBundle(android.os.Bundle);
|
||||||
method public static androidx.media3.common.TrackSelectionParameters getDefaults(android.content.Context);
|
method public static androidx.media3.common.TrackSelectionParameters getDefaults(android.content.Context);
|
||||||
method @CallSuper public android.os.Bundle toBundle();
|
method public android.os.Bundle toBundle();
|
||||||
field public final com.google.common.collect.ImmutableSet<java.lang.Integer> disabledTrackTypes;
|
field public final com.google.common.collect.ImmutableSet<java.lang.Integer> disabledTrackTypes;
|
||||||
field public final boolean forceHighestSupportedBitrate;
|
field public final boolean forceHighestSupportedBitrate;
|
||||||
field public final boolean forceLowestBitrate;
|
field public final boolean forceLowestBitrate;
|
||||||
|
|
@ -1192,7 +1173,7 @@ package androidx.media3.common {
|
||||||
field public static final androidx.media3.common.VideoSize UNKNOWN;
|
field public static final androidx.media3.common.VideoSize UNKNOWN;
|
||||||
field @IntRange(from=0) public final int height;
|
field @IntRange(from=0) public final int height;
|
||||||
field @FloatRange(from=0, fromInclusive=false) public final float pixelWidthHeightRatio;
|
field @FloatRange(from=0, fromInclusive=false) public final float pixelWidthHeightRatio;
|
||||||
field @Deprecated @IntRange(from=0, to=359) public final int unappliedRotationDegrees;
|
field @IntRange(from=0, to=359) public final int unappliedRotationDegrees;
|
||||||
field @IntRange(from=0) public final int width;
|
field @IntRange(from=0) public final int width;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1388,7 +1369,7 @@ package androidx.media3.exoplayer.analytics {
|
||||||
|
|
||||||
package androidx.media3.exoplayer.drm {
|
package androidx.media3.exoplayer.drm {
|
||||||
|
|
||||||
public final class FrameworkMediaDrm {
|
@RequiresApi(18) public final class FrameworkMediaDrm {
|
||||||
method public static boolean isCryptoSchemeSupported(java.util.UUID);
|
method public static boolean isCryptoSchemeSupported(java.util.UUID);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1406,29 +1387,6 @@ package androidx.media3.exoplayer.ima {
|
||||||
method public androidx.media3.exoplayer.ima.ImaAdsLoader build();
|
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 {
|
package androidx.media3.exoplayer.source {
|
||||||
|
|
@ -1438,7 +1396,6 @@ package androidx.media3.exoplayer.source {
|
||||||
method public androidx.media3.exoplayer.source.DefaultMediaSourceFactory clearLocalAdInsertionComponents();
|
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 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 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 {
|
public interface MediaSource {
|
||||||
|
|
@ -1527,7 +1484,7 @@ package androidx.media3.session {
|
||||||
field @Nullable public final V value;
|
field @Nullable public final V value;
|
||||||
}
|
}
|
||||||
|
|
||||||
@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 {
|
@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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class MediaBrowser extends androidx.media3.session.MediaController {
|
public final class MediaBrowser extends androidx.media3.session.MediaController {
|
||||||
|
|
@ -1803,7 +1760,6 @@ package androidx.media3.session {
|
||||||
method public int getControllerVersion();
|
method public int getControllerVersion();
|
||||||
method public String getPackageName();
|
method public String getPackageName();
|
||||||
method public int getUid();
|
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
|
field public static final int LEGACY_CONTROLLER_VERSION = 0; // 0x0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -1852,7 +1808,6 @@ package androidx.media3.session {
|
||||||
ctor public SessionCommands.Builder();
|
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);
|
||||||
method public androidx.media3.session.SessionCommands.Builder add(@androidx.media3.session.SessionCommand.CommandCode int);
|
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 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);
|
||||||
method public androidx.media3.session.SessionCommands.Builder remove(@androidx.media3.session.SessionCommand.CommandCode int);
|
method public androidx.media3.session.SessionCommands.Builder remove(@androidx.media3.session.SessionCommand.CommandCode int);
|
||||||
|
|
@ -1882,7 +1837,7 @@ package androidx.media3.session {
|
||||||
field @androidx.media3.session.SessionResult.Code public final int resultCode;
|
field @androidx.media3.session.SessionResult.Code public final int resultCode;
|
||||||
}
|
}
|
||||||
|
|
||||||
@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 {
|
@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 {
|
||||||
}
|
}
|
||||||
|
|
||||||
public final class SessionToken {
|
public final class SessionToken {
|
||||||
|
|
|
||||||
|
|
@ -17,9 +17,9 @@ buildscript {
|
||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:8.3.2'
|
classpath 'com.android.tools.build:gradle:8.0.1'
|
||||||
classpath 'com.google.android.gms:strict-version-matcher-plugin:1.2.4'
|
classpath 'com.google.android.gms:strict-version-matcher-plugin:1.2.4'
|
||||||
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.10'
|
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.20'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
allprojects {
|
allprojects {
|
||||||
|
|
|
||||||
|
|
@ -14,8 +14,6 @@
|
||||||
apply from: "$gradle.ext.androidxMediaSettingsDir/constants.gradle"
|
apply from: "$gradle.ext.androidxMediaSettingsDir/constants.gradle"
|
||||||
apply plugin: 'com.android.library'
|
apply plugin: 'com.android.library'
|
||||||
|
|
||||||
group = 'androidx.media3'
|
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion project.ext.compileSdkVersion
|
compileSdkVersion project.ext.compileSdkVersion
|
||||||
|
|
||||||
|
|
@ -27,9 +25,11 @@ android {
|
||||||
aarMetadata {
|
aarMetadata {
|
||||||
minCompileSdk = project.ext.compileSdkVersion
|
minCompileSdk = project.ext.compileSdkVersion
|
||||||
}
|
}
|
||||||
|
multiDexEnabled true
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
|
coreLibraryDesugaringEnabled true
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
|
|
@ -41,3 +41,8 @@ android {
|
||||||
unitTests.includeAndroidResources true
|
unitTests.includeAndroidResources true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
androidTestImplementation 'androidx.multidex:multidex:' + androidxMultidexVersion
|
||||||
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||||
|
}
|
||||||
|
|
|
||||||
|
|
@ -12,42 +12,41 @@
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
project.ext {
|
project.ext {
|
||||||
releaseVersion = '1.5.1'
|
releaseVersion = '1.2.1'
|
||||||
releaseVersionCode = 1_005_001_3_00
|
releaseVersionCode = 1_002_001_3_00
|
||||||
minSdkVersion = 21
|
minSdkVersion = 16
|
||||||
// See https://developer.android.com/training/cars/media/automotive-os#automotive-module
|
// See https://developer.android.com/training/cars/media/automotive-os#automotive-module
|
||||||
automotiveMinSdkVersion = 28
|
automotiveMinSdkVersion = 28
|
||||||
appTargetSdkVersion = 34
|
appTargetSdkVersion = 34
|
||||||
// Upgrading this requires [Internal ref: b/193254928] to be fixed, or some
|
// Upgrading this requires [Internal ref: b/193254928] to be fixed, or some
|
||||||
// additional robolectric config.
|
// additional robolectric config.
|
||||||
targetSdkVersion = 30
|
targetSdkVersion = 30
|
||||||
compileSdkVersion = 35
|
compileSdkVersion = 34
|
||||||
dexmakerVersion = '2.28.3'
|
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'
|
junitVersion = '4.13.2'
|
||||||
// Use the same Guava version as the Android repo:
|
// Use the same Guava version as the Android repo:
|
||||||
// https://cs.android.com/android/platform/superproject/main/+/main:external/guava/METADATA
|
// https://cs.android.com/android/platform/superproject/main/+/main:external/guava/METADATA
|
||||||
guavaVersion = '33.3.1-android'
|
guavaVersion = '31.1-android'
|
||||||
glideVersion = '4.14.2'
|
|
||||||
kotlinxCoroutinesVersion = '1.8.1'
|
|
||||||
leakCanaryVersion = '2.10'
|
|
||||||
mockitoVersion = '3.12.4'
|
mockitoVersion = '3.12.4'
|
||||||
robolectricVersion = '4.11'
|
robolectricVersion = '4.10.3'
|
||||||
// Keep this in sync with Google's internal Checker Framework version.
|
// Keep this in sync with Google's internal Checker Framework version.
|
||||||
checkerframeworkVersion = '3.13.0'
|
checkerframeworkVersion = '3.13.0'
|
||||||
errorProneVersion = '2.18.0'
|
errorProneVersion = '2.18.0'
|
||||||
jsr305Version = '3.0.2'
|
jsr305Version = '3.0.2'
|
||||||
kotlinAnnotationsVersion = '1.9.0'
|
kotlinAnnotationsVersion = '1.8.20'
|
||||||
androidxAnnotationVersion = '1.6.0'
|
// Updating this to 1.4.0+ will import Kotlin stdlib [internal ref: b/277891049].
|
||||||
|
androidxAnnotationVersion = '1.3.0'
|
||||||
androidxAnnotationExperimentalVersion = '1.3.1'
|
androidxAnnotationExperimentalVersion = '1.3.1'
|
||||||
androidxAppCompatVersion = '1.6.1'
|
androidxAppCompatVersion = '1.6.1'
|
||||||
androidxCollectionVersion = '1.2.0'
|
androidxCollectionVersion = '1.2.0'
|
||||||
androidxConstraintLayoutVersion = '2.1.4'
|
androidxConstraintLayoutVersion = '2.1.4'
|
||||||
|
// Updating this to 1.9.0+ will import Kotlin stdlib [internal ref: b/277891049].
|
||||||
androidxCoreVersion = '1.8.0'
|
androidxCoreVersion = '1.8.0'
|
||||||
androidxExifInterfaceVersion = '1.3.6'
|
androidxExifInterfaceVersion = '1.3.6'
|
||||||
androidxLifecycleVersion = '2.6.0'
|
androidxFuturesVersion = '1.1.0'
|
||||||
androidxMediaVersion = '1.7.0'
|
androidxMediaVersion = '1.6.0'
|
||||||
|
androidxMedia2Version = '1.2.1'
|
||||||
|
androidxMultidexVersion = '2.0.1'
|
||||||
androidxRecyclerViewVersion = '1.3.0'
|
androidxRecyclerViewVersion = '1.3.0'
|
||||||
androidxMaterialVersion = '1.8.0'
|
androidxMaterialVersion = '1.8.0'
|
||||||
androidxTestCoreVersion = '1.5.0'
|
androidxTestCoreVersion = '1.5.0'
|
||||||
|
|
@ -55,8 +54,9 @@ project.ext {
|
||||||
androidxTestJUnitVersion = '1.1.5'
|
androidxTestJUnitVersion = '1.1.5'
|
||||||
androidxTestRunnerVersion = '1.5.2'
|
androidxTestRunnerVersion = '1.5.2'
|
||||||
androidxTestRulesVersion = '1.5.0'
|
androidxTestRulesVersion = '1.5.0'
|
||||||
|
androidxTestServicesStorageVersion = '1.4.2'
|
||||||
androidxTestTruthVersion = '1.5.0'
|
androidxTestTruthVersion = '1.5.0'
|
||||||
truthVersion = '1.4.0'
|
truthVersion = '1.1.3'
|
||||||
okhttpVersion = '4.12.0'
|
okhttpVersion = '4.12.0'
|
||||||
modulePrefix = ':'
|
modulePrefix = ':'
|
||||||
if (gradle.ext.has('androidxMediaModulePrefix')) {
|
if (gradle.ext.has('androidxMediaModulePrefix')) {
|
||||||
|
|
|
||||||
|
|
@ -24,9 +24,6 @@ if (gradle.ext.has('androidxMediaModulePrefix')) {
|
||||||
include modulePrefix + 'lib-common'
|
include modulePrefix + 'lib-common'
|
||||||
project(modulePrefix + 'lib-common').projectDir = new File(rootDir, 'libraries/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'
|
include modulePrefix + 'lib-container'
|
||||||
project(modulePrefix + 'lib-container').projectDir = new File(rootDir, 'libraries/container')
|
project(modulePrefix + 'lib-container').projectDir = new File(rootDir, 'libraries/container')
|
||||||
|
|
||||||
|
|
@ -60,8 +57,6 @@ include modulePrefix + 'lib-datasource'
|
||||||
project(modulePrefix + 'lib-datasource').projectDir = new File(rootDir, 'libraries/datasource')
|
project(modulePrefix + 'lib-datasource').projectDir = new File(rootDir, 'libraries/datasource')
|
||||||
include modulePrefix + 'lib-datasource-cronet'
|
include modulePrefix + 'lib-datasource-cronet'
|
||||||
project(modulePrefix + 'lib-datasource-cronet').projectDir = new File(rootDir, 'libraries/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'
|
include modulePrefix + 'lib-datasource-rtmp'
|
||||||
project(modulePrefix + 'lib-datasource-rtmp').projectDir = new File(rootDir, 'libraries/datasource_rtmp')
|
project(modulePrefix + 'lib-datasource-rtmp').projectDir = new File(rootDir, 'libraries/datasource_rtmp')
|
||||||
include modulePrefix + 'lib-datasource-okhttp'
|
include modulePrefix + 'lib-datasource-okhttp'
|
||||||
|
|
@ -75,12 +70,8 @@ include modulePrefix + 'lib-decoder-ffmpeg'
|
||||||
project(modulePrefix + 'lib-decoder-ffmpeg').projectDir = new File(rootDir, 'libraries/decoder_ffmpeg')
|
project(modulePrefix + 'lib-decoder-ffmpeg').projectDir = new File(rootDir, 'libraries/decoder_ffmpeg')
|
||||||
include modulePrefix + 'lib-decoder-flac'
|
include modulePrefix + 'lib-decoder-flac'
|
||||||
project(modulePrefix + 'lib-decoder-flac').projectDir = new File(rootDir, 'libraries/decoder_flac')
|
project(modulePrefix + 'lib-decoder-flac').projectDir = new File(rootDir, 'libraries/decoder_flac')
|
||||||
include modulePrefix + 'lib-decoder-iamf'
|
include modulePrefix + 'lib-decoder-midi'
|
||||||
project(modulePrefix + 'lib-decoder-iamf').projectDir = new File(rootDir, 'libraries/decoder_iamf')
|
project(modulePrefix + 'lib-decoder-midi').projectDir = new File(rootDir, 'libraries/decoder_midi')
|
||||||
if (gradle.ext.has('androidxMediaEnableMidiModule') && gradle.ext.androidxMediaEnableMidiModule) {
|
|
||||||
include modulePrefix + 'lib-decoder-midi'
|
|
||||||
project(modulePrefix + 'lib-decoder-midi').projectDir = new File(rootDir, 'libraries/decoder_midi')
|
|
||||||
}
|
|
||||||
include modulePrefix + 'lib-decoder-opus'
|
include modulePrefix + 'lib-decoder-opus'
|
||||||
project(modulePrefix + 'lib-decoder-opus').projectDir = new File(rootDir, 'libraries/decoder_opus')
|
project(modulePrefix + 'lib-decoder-opus').projectDir = new File(rootDir, 'libraries/decoder_opus')
|
||||||
include modulePrefix + 'lib-decoder-vp9'
|
include modulePrefix + 'lib-decoder-vp9'
|
||||||
|
|
@ -107,3 +98,7 @@ include modulePrefix + 'test-data'
|
||||||
project(modulePrefix + 'test-data').projectDir = new File(rootDir, 'libraries/test_data')
|
project(modulePrefix + 'test-data').projectDir = new File(rootDir, 'libraries/test_data')
|
||||||
include modulePrefix + 'test-utils'
|
include modulePrefix + 'test-utils'
|
||||||
project(modulePrefix + 'test-utils').projectDir = new File(rootDir, 'libraries/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')
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ apply plugin: 'com.android.application'
|
||||||
android {
|
android {
|
||||||
namespace 'androidx.media3.demo.cast'
|
namespace 'androidx.media3.demo.cast'
|
||||||
|
|
||||||
compileSdk project.ext.compileSdkVersion
|
compileSdkVersion project.ext.compileSdkVersion
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
|
@ -29,6 +29,7 @@ android {
|
||||||
versionCode project.ext.releaseVersionCode
|
versionCode project.ext.releaseVersionCode
|
||||||
minSdkVersion project.ext.minSdkVersion
|
minSdkVersion project.ext.minSdkVersion
|
||||||
targetSdkVersion project.ext.appTargetSdkVersion
|
targetSdkVersion project.ext.appTargetSdkVersion
|
||||||
|
multiDexEnabled true
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|
@ -61,6 +62,7 @@ dependencies {
|
||||||
implementation project(modulePrefix + 'lib-ui')
|
implementation project(modulePrefix + 'lib-ui')
|
||||||
implementation project(modulePrefix + 'lib-cast')
|
implementation project(modulePrefix + 'lib-cast')
|
||||||
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
|
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
|
||||||
|
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
|
||||||
implementation 'androidx.recyclerview:recyclerview:' + androidxRecyclerViewVersion
|
implementation 'androidx.recyclerview:recyclerview:' + androidxRecyclerViewVersion
|
||||||
implementation 'com.google.android.material:material:' + androidxMaterialVersion
|
implementation 'com.google.android.material:material:' + androidxMaterialVersion
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -23,6 +23,7 @@
|
||||||
<uses-sdk/>
|
<uses-sdk/>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name="androidx.multidex.MultiDexApplication"
|
||||||
android:label="@string/application_name"
|
android:label="@string/application_name"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:largeHeap="true"
|
android:largeHeap="true"
|
||||||
|
|
|
||||||
|
|
@ -1,5 +1,5 @@
|
||||||
/*
|
/*
|
||||||
* Copyright (C) 2024 The Android Open Source Project
|
* Copyright 2020 The Android Open Source Project
|
||||||
*
|
*
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
* you may not use this file except in compliance with the License.
|
* you may not use this file except in compliance with the License.
|
||||||
|
|
@ -13,10 +13,11 @@
|
||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
@NonNullApi
|
package androidx.media3.demo.cast;
|
||||||
@OptIn(markerClass = UnstableApi.class)
|
|
||||||
package androidx.media3.demo.composition;
|
|
||||||
|
|
||||||
import androidx.annotation.OptIn;
|
import androidx.multidex.MultiDexApplication;
|
||||||
import androidx.media3.common.util.NonNullApi;
|
|
||||||
import androidx.media3.common.util.UnstableApi;
|
// Note: Multidex is enabled in code not AndroidManifest.xml because the internal build system
|
||||||
|
// doesn't dejetify MultiDexApplication in AndroidManifest.xml.
|
||||||
|
/** Application for multidex support. */
|
||||||
|
public final class DemoApplication extends MultiDexApplication {}
|
||||||
|
|
@ -44,7 +44,6 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder;
|
||||||
import com.google.android.gms.cast.framework.CastButtonFactory;
|
import com.google.android.gms.cast.framework.CastButtonFactory;
|
||||||
import com.google.android.gms.cast.framework.CastContext;
|
import com.google.android.gms.cast.framework.CastContext;
|
||||||
import com.google.android.gms.dynamite.DynamiteModule;
|
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
|
* An activity that plays video using {@link ExoPlayer} and supports casting using ExoPlayer's Cast
|
||||||
|
|
@ -66,7 +65,7 @@ public class MainActivity extends AppCompatActivity
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
// Getting the cast context later than onStart can cause device discovery not to take place.
|
// Getting the cast context later than onStart can cause device discovery not to take place.
|
||||||
try {
|
try {
|
||||||
castContext = CastContext.getSharedInstance(this, MoreExecutors.directExecutor()).getResult();
|
castContext = CastContext.getSharedInstance(this);
|
||||||
} catch (RuntimeException e) {
|
} catch (RuntimeException e) {
|
||||||
Throwable cause = e.getCause();
|
Throwable cause = e.getCause();
|
||||||
while (cause != null) {
|
while (cause != null) {
|
||||||
|
|
|
||||||
|
|
@ -1,14 +0,0 @@
|
||||||
# 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.
|
|
||||||
|
|
@ -1,87 +0,0 @@
|
||||||
// Copyright 2024 The Android Open Source Project
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
apply from: '../../constants.gradle'
|
|
||||||
apply plugin: 'com.android.application'
|
|
||||||
apply plugin: 'kotlin-android'
|
|
||||||
|
|
||||||
android {
|
|
||||||
namespace 'androidx.media3.demo.compose'
|
|
||||||
|
|
||||||
compileSdk project.ext.compileSdkVersion
|
|
||||||
|
|
||||||
compileOptions {
|
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
|
||||||
}
|
|
||||||
|
|
||||||
kotlinOptions {
|
|
||||||
jvmTarget = '1.8'
|
|
||||||
}
|
|
||||||
|
|
||||||
defaultConfig {
|
|
||||||
versionName project.ext.releaseVersion
|
|
||||||
versionCode project.ext.releaseVersionCode
|
|
||||||
minSdkVersion project.ext.minSdkVersion
|
|
||||||
targetSdkVersion project.ext.appTargetSdkVersion
|
|
||||||
}
|
|
||||||
|
|
||||||
buildTypes {
|
|
||||||
release {
|
|
||||||
shrinkResources true
|
|
||||||
minifyEnabled true
|
|
||||||
signingConfig signingConfigs.debug
|
|
||||||
}
|
|
||||||
debug {
|
|
||||||
jniDebuggable = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lintOptions {
|
|
||||||
// The demo app isn't indexed, and doesn't have translations.
|
|
||||||
disable 'GoogleAppIndexingWarning','MissingTranslation'
|
|
||||||
}
|
|
||||||
buildFeatures {
|
|
||||||
viewBinding true
|
|
||||||
compose true
|
|
||||||
}
|
|
||||||
composeOptions {
|
|
||||||
kotlinCompilerExtensionVersion = "1.5.3"
|
|
||||||
}
|
|
||||||
|
|
||||||
testOptions {
|
|
||||||
unitTests {
|
|
||||||
includeAndroidResources = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
def composeBom = platform('androidx.compose:compose-bom:2024.05.00')
|
|
||||||
implementation composeBom
|
|
||||||
|
|
||||||
implementation 'androidx.activity:activity-compose:1.9.0'
|
|
||||||
implementation 'androidx.compose.foundation:foundation-android:1.6.7'
|
|
||||||
implementation 'androidx.compose.material3:material3-android:1.2.1'
|
|
||||||
implementation 'com.google.android.material:material:' + androidxMaterialVersion
|
|
||||||
|
|
||||||
implementation project(modulePrefix + 'lib-exoplayer')
|
|
||||||
|
|
||||||
// For detecting and debugging leaks only. LeakCanary is not needed for demo app to work.
|
|
||||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:' + leakCanaryVersion
|
|
||||||
|
|
||||||
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:' + kotlinxCoroutinesVersion
|
|
||||||
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
|
|
||||||
testImplementation project(modulePrefix + 'test-utils')
|
|
||||||
|
|
||||||
}
|
|
||||||
|
|
@ -1,38 +0,0 @@
|
||||||
<?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>
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2024 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package androidx.media3.demo.compose
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import androidx.activity.ComponentActivity
|
|
||||||
import androidx.activity.compose.setContent
|
|
||||||
import androidx.activity.enableEdgeToEdge
|
|
||||||
import androidx.compose.foundation.layout.Arrangement
|
|
||||||
import androidx.compose.foundation.layout.Column
|
|
||||||
import androidx.compose.material3.Surface
|
|
||||||
import androidx.compose.runtime.remember
|
|
||||||
import androidx.compose.ui.Alignment
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.compose.ui.platform.LocalContext
|
|
||||||
import androidx.media3.common.MediaItem
|
|
||||||
import androidx.media3.common.Player
|
|
||||||
import androidx.media3.demo.compose.data.videos
|
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
|
||||||
|
|
||||||
class MainActivity : ComponentActivity() {
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
enableEdgeToEdge()
|
|
||||||
setContent {
|
|
||||||
Surface {
|
|
||||||
Column(
|
|
||||||
verticalArrangement = Arrangement.Center,
|
|
||||||
horizontalAlignment = Alignment.CenterHorizontally,
|
|
||||||
) {
|
|
||||||
val context = LocalContext.current
|
|
||||||
val exoPlayer = remember {
|
|
||||||
ExoPlayer.Builder(context).build().apply {
|
|
||||||
setMediaItem(MediaItem.fromUri(videos[0]))
|
|
||||||
prepare()
|
|
||||||
playWhenReady = true
|
|
||||||
repeatMode = Player.REPEAT_MODE_ONE
|
|
||||||
}
|
|
||||||
}
|
|
||||||
PlayerSurface(
|
|
||||||
player = exoPlayer,
|
|
||||||
surfaceType = SURFACE_TYPE_SURFACE_VIEW,
|
|
||||||
modifier = Modifier.align(Alignment.CenterHorizontally),
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,76 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2024 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
|
|
||||||
package androidx.media3.demo.compose
|
|
||||||
|
|
||||||
import android.view.Surface
|
|
||||||
import android.view.SurfaceView
|
|
||||||
import android.view.TextureView
|
|
||||||
import androidx.annotation.IntDef
|
|
||||||
import androidx.compose.foundation.AndroidEmbeddedExternalSurface
|
|
||||||
import androidx.compose.foundation.AndroidExternalSurface
|
|
||||||
import androidx.compose.foundation.AndroidExternalSurfaceScope
|
|
||||||
import androidx.compose.runtime.Composable
|
|
||||||
import androidx.compose.ui.Modifier
|
|
||||||
import androidx.media3.common.Player
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Provides a dedicated drawing [Surface] for media playbacks using a [Player].
|
|
||||||
*
|
|
||||||
* The player's video output is displayed with either a [SurfaceView]/[AndroidExternalSurface] or a
|
|
||||||
* [TextureView]/[AndroidEmbeddedExternalSurface].
|
|
||||||
*
|
|
||||||
* [Player] takes care of attaching the rendered output to the [Surface] and clearing it, when it is
|
|
||||||
* destroyed.
|
|
||||||
*
|
|
||||||
* See
|
|
||||||
* [Choosing a surface type](https://developer.android.com/media/media3/ui/playerview#surfacetype)
|
|
||||||
* for more information.
|
|
||||||
*/
|
|
||||||
@Composable
|
|
||||||
fun PlayerSurface(player: Player, surfaceType: @SurfaceType Int, modifier: Modifier = Modifier) {
|
|
||||||
val onSurfaceCreated: (Surface) -> Unit = { surface -> player.setVideoSurface(surface) }
|
|
||||||
val onSurfaceDestroyed: () -> Unit = { player.setVideoSurface(null) }
|
|
||||||
val onSurfaceInitialized: AndroidExternalSurfaceScope.() -> Unit = {
|
|
||||||
onSurface { surface, _, _ ->
|
|
||||||
onSurfaceCreated(surface)
|
|
||||||
surface.onDestroyed { onSurfaceDestroyed() }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
when (surfaceType) {
|
|
||||||
SURFACE_TYPE_SURFACE_VIEW ->
|
|
||||||
AndroidExternalSurface(modifier = modifier, onInit = onSurfaceInitialized)
|
|
||||||
SURFACE_TYPE_TEXTURE_VIEW ->
|
|
||||||
AndroidEmbeddedExternalSurface(modifier = modifier, onInit = onSurfaceInitialized)
|
|
||||||
else -> throw IllegalArgumentException("Unrecognized surface type: $surfaceType")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* The type of surface view used for media playbacks. One of [SURFACE_TYPE_SURFACE_VIEW] or
|
|
||||||
* [SURFACE_TYPE_TEXTURE_VIEW].
|
|
||||||
*/
|
|
||||||
@MustBeDocumented
|
|
||||||
@Retention(AnnotationRetention.SOURCE)
|
|
||||||
@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE, AnnotationTarget.TYPE_PARAMETER)
|
|
||||||
@IntDef(SURFACE_TYPE_SURFACE_VIEW, SURFACE_TYPE_TEXTURE_VIEW)
|
|
||||||
annotation class SurfaceType
|
|
||||||
|
|
||||||
/** Surface type equivalent to [SurfaceView] . */
|
|
||||||
const val SURFACE_TYPE_SURFACE_VIEW = 1
|
|
||||||
/** Surface type equivalent to [TextureView]. */
|
|
||||||
const val SURFACE_TYPE_TEXTURE_VIEW = 2
|
|
||||||
|
|
@ -1,23 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2024 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package androidx.media3.demo.compose.data
|
|
||||||
|
|
||||||
val videos =
|
|
||||||
listOf(
|
|
||||||
"https://html5demos.com/assets/dizzy.mp4",
|
|
||||||
"https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/gear0/fileSequence0.aac",
|
|
||||||
"https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/video-vp9-360.webm",
|
|
||||||
)
|
|
||||||
|
|
@ -1,20 +0,0 @@
|
||||||
<?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>
|
|
||||||
|
|
||||||
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
|
@ -1,31 +0,0 @@
|
||||||
<?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>
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
<?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>
|
|
||||||
|
|
@ -1,26 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Copyright 2024 The Android Open Source Project
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
https://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
-->
|
|
||||||
<resources>
|
|
||||||
<string name="app_name">Media3 Compose Demo</string>
|
|
||||||
<string name="current_playlist_name">Current playlist</string>
|
|
||||||
<string name="open_player_content_description">Click to view your play list</string>
|
|
||||||
<string name="added_media_item_format">Added %1$s to playlist</string>
|
|
||||||
<string name="shuffle">Shuffle</string>
|
|
||||||
<string name="play_button">Play</string>
|
|
||||||
<string name="waiting_for_metadata">Waiting for playlist to load…</string>
|
|
||||||
<string name="notification_permission_denied">
|
|
||||||
"Without notification access the app can't warn about failed background operations"</string>
|
|
||||||
</resources>
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
<?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>
|
|
||||||
|
|
@ -1,8 +0,0 @@
|
||||||
# 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.
|
|
||||||
|
|
@ -1,62 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 +0,0 @@
|
||||||
# Proguard rules specific to the composition demo app.
|
|
||||||
|
|
@ -1,51 +0,0 @@
|
||||||
<?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>
|
|
||||||
|
|
@ -1,69 +0,0 @@
|
||||||
/*
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,505 +0,0 @@
|
||||||
/*
|
|
||||||
* 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 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);
|
|
||||||
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();
|
|
||||||
}
|
|
||||||
|
|
||||||
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.setRepeatMode(Player.REPEAT_MODE_ALL);
|
|
||||||
player.setComposition(composition);
|
|
||||||
player.prepare();
|
|
||||||
player.play();
|
|
||||||
}
|
|
||||||
|
|
||||||
private void selectPreset() {
|
|
||||||
new AlertDialog.Builder(/* context= */ this)
|
|
||||||
.setTitle(R.string.select_preset_title)
|
|
||||||
.setMultiChoiceItems(presetDescriptions, selectedMediaItems, this::selectPresetInDialog)
|
|
||||||
.setPositiveButton(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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,93 +0,0 @@
|
||||||
/*
|
|
||||||
* 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;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,178 +0,0 @@
|
||||||
<?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>
|
|
||||||
|
|
@ -1,110 +0,0 @@
|
||||||
<?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>
|
|
||||||
|
|
@ -1,30 +0,0 @@
|
||||||
<?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>
|
|
||||||
|
|
@ -1,25 +0,0 @@
|
||||||
<?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" />
|
|
||||||
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
|
@ -1,31 +0,0 @@
|
||||||
<?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>
|
|
||||||
|
|
@ -1,77 +0,0 @@
|
||||||
<?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>
|
|
||||||
|
|
@ -1,24 +0,0 @@
|
||||||
<?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>
|
|
||||||
|
|
@ -1,39 +0,0 @@
|
||||||
<?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>
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
<?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>
|
|
||||||
|
|
@ -17,7 +17,7 @@ apply plugin: 'com.android.application'
|
||||||
android {
|
android {
|
||||||
namespace 'androidx.media3.demo.gl'
|
namespace 'androidx.media3.demo.gl'
|
||||||
|
|
||||||
compileSdk project.ext.compileSdkVersion
|
compileSdkVersion project.ext.compileSdkVersion
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
|
@ -29,6 +29,7 @@ android {
|
||||||
versionCode project.ext.releaseVersionCode
|
versionCode project.ext.releaseVersionCode
|
||||||
minSdkVersion project.ext.minSdkVersion
|
minSdkVersion project.ext.minSdkVersion
|
||||||
targetSdkVersion project.ext.appTargetSdkVersion
|
targetSdkVersion project.ext.appTargetSdkVersion
|
||||||
|
multiDexEnabled true
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|
@ -54,5 +55,6 @@ dependencies {
|
||||||
implementation project(modulePrefix + 'lib-exoplayer-smoothstreaming')
|
implementation project(modulePrefix + 'lib-exoplayer-smoothstreaming')
|
||||||
implementation project(modulePrefix + 'lib-ui')
|
implementation project(modulePrefix + 'lib-ui')
|
||||||
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
|
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
|
||||||
|
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
|
||||||
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
|
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,6 +22,7 @@
|
||||||
<uses-sdk/>
|
<uses-sdk/>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name="androidx.multidex.MultiDexApplication"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/application_name">
|
android:label="@string/application_name">
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
paint = new Paint();
|
paint = new Paint();
|
||||||
paint.setTextSize(64);
|
paint.setTextSize(64);
|
||||||
paint.setAntiAlias(true);
|
paint.setAntiAlias(true);
|
||||||
paint.setColor(Color.WHITE);
|
paint.setARGB(0xFF, 0xFF, 0xFF, 0xFF);
|
||||||
textures = new int[1];
|
textures = new int[1];
|
||||||
overlayBitmap = Bitmap.createBitmap(OVERLAY_WIDTH, OVERLAY_HEIGHT, Bitmap.Config.ARGB_8888);
|
overlayBitmap = Bitmap.createBitmap(OVERLAY_WIDTH, OVERLAY_HEIGHT, Bitmap.Config.ARGB_8888);
|
||||||
overlayCanvas = new Canvas(overlayBitmap);
|
overlayCanvas = new Canvas(overlayBitmap);
|
||||||
|
|
|
||||||
|
|
@ -143,7 +143,7 @@ public final class MainActivity extends Activity {
|
||||||
? Assertions.checkNotNull(intent.getData())
|
? Assertions.checkNotNull(intent.getData())
|
||||||
: Uri.parse(DEFAULT_MEDIA_URI);
|
: Uri.parse(DEFAULT_MEDIA_URI);
|
||||||
DrmSessionManager drmSessionManager;
|
DrmSessionManager drmSessionManager;
|
||||||
if (intent.hasExtra(DRM_SCHEME_EXTRA)) {
|
if (Util.SDK_INT >= 18 && intent.hasExtra(DRM_SCHEME_EXTRA)) {
|
||||||
String drmScheme = Assertions.checkNotNull(intent.getStringExtra(DRM_SCHEME_EXTRA));
|
String drmScheme = Assertions.checkNotNull(intent.getStringExtra(DRM_SCHEME_EXTRA));
|
||||||
String drmLicenseUrl = Assertions.checkNotNull(intent.getStringExtra(DRM_LICENSE_URL_EXTRA));
|
String drmLicenseUrl = Assertions.checkNotNull(intent.getStringExtra(DRM_LICENSE_URL_EXTRA));
|
||||||
UUID drmSchemeUuid = Assertions.checkNotNull(Util.getDrmUuid(drmScheme));
|
UUID drmSchemeUuid = Assertions.checkNotNull(Util.getDrmUuid(drmScheme));
|
||||||
|
|
|
||||||
|
|
@ -19,7 +19,7 @@ apply plugin: 'kotlin-android'
|
||||||
android {
|
android {
|
||||||
namespace 'androidx.media3.demo.main'
|
namespace 'androidx.media3.demo.main'
|
||||||
|
|
||||||
compileSdk project.ext.compileSdkVersion
|
compileSdkVersion project.ext.compileSdkVersion
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
|
@ -31,6 +31,7 @@ android {
|
||||||
versionCode project.ext.releaseVersionCode
|
versionCode project.ext.releaseVersionCode
|
||||||
minSdkVersion project.ext.minSdkVersion
|
minSdkVersion project.ext.minSdkVersion
|
||||||
targetSdkVersion project.ext.appTargetSdkVersion
|
targetSdkVersion project.ext.appTargetSdkVersion
|
||||||
|
multiDexEnabled true
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|
@ -54,9 +55,7 @@ android {
|
||||||
disable 'GoogleAppIndexingWarning','MissingTranslation','IconDensities'
|
disable 'GoogleAppIndexingWarning','MissingTranslation','IconDensities'
|
||||||
}
|
}
|
||||||
|
|
||||||
flavorDimensions = ["decoderExtensions"]
|
flavorDimensions "decoderExtensions"
|
||||||
|
|
||||||
buildFeatures.buildConfig true
|
|
||||||
|
|
||||||
productFlavors {
|
productFlavors {
|
||||||
noDecoderExtensions {
|
noDecoderExtensions {
|
||||||
|
|
@ -74,6 +73,7 @@ dependencies {
|
||||||
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
|
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
|
||||||
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
|
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
|
||||||
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
|
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
|
||||||
|
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
|
||||||
implementation 'com.google.android.material:material:' + androidxMaterialVersion
|
implementation 'com.google.android.material:material:' + androidxMaterialVersion
|
||||||
implementation project(modulePrefix + 'lib-exoplayer')
|
implementation project(modulePrefix + 'lib-exoplayer')
|
||||||
implementation project(modulePrefix + 'lib-exoplayer-dash')
|
implementation project(modulePrefix + 'lib-exoplayer-dash')
|
||||||
|
|
@ -87,7 +87,6 @@ dependencies {
|
||||||
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-ffmpeg')
|
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-ffmpeg')
|
||||||
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-flac')
|
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-flac')
|
||||||
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-opus')
|
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-opus')
|
||||||
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-iamf')
|
|
||||||
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-vp9')
|
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-vp9')
|
||||||
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-midi')
|
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-midi')
|
||||||
withDecoderExtensionsImplementation project(modulePrefix + 'lib-datasource-rtmp')
|
withDecoderExtensionsImplementation project(modulePrefix + 'lib-datasource-rtmp')
|
||||||
|
|
|
||||||
|
|
@ -40,6 +40,7 @@
|
||||||
android:largeHeap="true"
|
android:largeHeap="true"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
android:supportsRtl="true"
|
android:supportsRtl="true"
|
||||||
|
android:name="androidx.multidex.MultiDexApplication"
|
||||||
tools:targetApi="29">
|
tools:targetApi="29">
|
||||||
|
|
||||||
<activity android:name=".SampleChooserActivity"
|
<activity android:name=".SampleChooserActivity"
|
||||||
|
|
|
||||||
|
|
@ -593,27 +593,6 @@
|
||||||
"clip_start_position_ms": 10000
|
"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
|
|
||||||
}
|
|
||||||
]
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
|
|
@ -705,7 +684,7 @@
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Progressive",
|
"name": "Misc",
|
||||||
"samples": [
|
"samples": [
|
||||||
{
|
{
|
||||||
"name": "Dizzy (MP4)",
|
"name": "Dizzy (MP4)",
|
||||||
|
|
@ -758,44 +737,6 @@
|
||||||
{
|
{
|
||||||
"name": "One hour frame counter (MP4)",
|
"name": "One hour frame counter (MP4)",
|
||||||
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mp4/frame-counter-one-hour.mp4"
|
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mp4/frame-counter-one-hour.mp4"
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Immersive Audio Format Sample (MP4, IAMF)",
|
|
||||||
"uri": "https://github.com/AOMediaCodec/libiamf/raw/main/tests/test_000036_s.mp4"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Images",
|
|
||||||
"samples": [
|
|
||||||
{
|
|
||||||
"name": "JPEG (wide)",
|
|
||||||
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/jpg/london.jpg",
|
|
||||||
"image_duration_ms": 2000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "JPEG (tall)",
|
|
||||||
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/jpg/tokyo.jpg",
|
|
||||||
"image_duration_ms": 2000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "PNG",
|
|
||||||
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/png/media3test.png",
|
|
||||||
"image_duration_ms": 2000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "JPEG motion photo (still)",
|
|
||||||
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/jpg/london_motion_photo.jpg",
|
|
||||||
"image_duration_ms": 2000
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "JPEG motion photo (motion)",
|
|
||||||
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/jpg/london_motion_photo.jpg"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "JPEG (Ultra HDR)",
|
|
||||||
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/jpg/ultra_hdr.jpg",
|
|
||||||
"image_duration_ms": 2000
|
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -63,7 +63,7 @@ public class DemoDownloadService extends DownloadService {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected Scheduler getScheduler() {
|
protected Scheduler getScheduler() {
|
||||||
return new PlatformScheduler(this, JOB_ID);
|
return Util.SDK_INT >= 21 ? new PlatformScheduler(this, JOB_ID) : null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
|
|
||||||
|
|
@ -16,16 +16,12 @@
|
||||||
package androidx.media3.demo.main;
|
package androidx.media3.demo.main;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.http.HttpEngine;
|
|
||||||
import android.os.Build;
|
|
||||||
import android.os.ext.SdkExtensions;
|
|
||||||
import androidx.annotation.OptIn;
|
import androidx.annotation.OptIn;
|
||||||
import androidx.media3.database.DatabaseProvider;
|
import androidx.media3.database.DatabaseProvider;
|
||||||
import androidx.media3.database.StandaloneDatabaseProvider;
|
import androidx.media3.database.StandaloneDatabaseProvider;
|
||||||
import androidx.media3.datasource.DataSource;
|
import androidx.media3.datasource.DataSource;
|
||||||
import androidx.media3.datasource.DefaultDataSource;
|
import androidx.media3.datasource.DefaultDataSource;
|
||||||
import androidx.media3.datasource.DefaultHttpDataSource;
|
import androidx.media3.datasource.DefaultHttpDataSource;
|
||||||
import androidx.media3.datasource.HttpEngineDataSource;
|
|
||||||
import androidx.media3.datasource.cache.Cache;
|
import androidx.media3.datasource.cache.Cache;
|
||||||
import androidx.media3.datasource.cache.CacheDataSource;
|
import androidx.media3.datasource.cache.CacheDataSource;
|
||||||
import androidx.media3.datasource.cache.NoOpCacheEvictor;
|
import androidx.media3.datasource.cache.NoOpCacheEvictor;
|
||||||
|
|
@ -50,26 +46,25 @@ public final class DemoUtil {
|
||||||
|
|
||||||
public static final String DOWNLOAD_NOTIFICATION_CHANNEL_ID = "download_channel";
|
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 TAG = "DemoUtil";
|
||||||
private static final String DOWNLOAD_CONTENT_DIRECTORY = "downloads";
|
private static final String DOWNLOAD_CONTENT_DIRECTORY = "downloads";
|
||||||
|
|
||||||
private static DataSource.@MonotonicNonNull Factory dataSourceFactory;
|
private static DataSource.@MonotonicNonNull Factory dataSourceFactory;
|
||||||
private static DataSource.@MonotonicNonNull Factory httpDataSourceFactory;
|
private static DataSource.@MonotonicNonNull Factory httpDataSourceFactory;
|
||||||
|
|
||||||
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
|
||||||
private static @MonotonicNonNull DatabaseProvider databaseProvider;
|
private static @MonotonicNonNull DatabaseProvider databaseProvider;
|
||||||
|
|
||||||
private static @MonotonicNonNull File downloadDirectory;
|
private static @MonotonicNonNull File downloadDirectory;
|
||||||
|
|
||||||
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
|
||||||
private static @MonotonicNonNull Cache downloadCache;
|
private static @MonotonicNonNull Cache downloadCache;
|
||||||
|
|
||||||
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
|
||||||
private static @MonotonicNonNull DownloadManager downloadManager;
|
private static @MonotonicNonNull DownloadManager downloadManager;
|
||||||
|
|
||||||
private static @MonotonicNonNull DownloadTracker downloadTracker;
|
private static @MonotonicNonNull DownloadTracker downloadTracker;
|
||||||
|
|
||||||
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
|
||||||
private static @MonotonicNonNull DownloadNotificationHelper downloadNotificationHelper;
|
private static @MonotonicNonNull DownloadNotificationHelper downloadNotificationHelper;
|
||||||
|
|
||||||
/** Returns whether extension renderers should be used. */
|
/** Returns whether extension renderers should be used. */
|
||||||
|
|
@ -91,30 +86,24 @@ public final class DemoUtil {
|
||||||
.setExtensionRendererMode(extensionRendererMode);
|
.setExtensionRendererMode(extensionRendererMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
|
||||||
public static synchronized DataSource.Factory getHttpDataSourceFactory(Context context) {
|
public static synchronized DataSource.Factory getHttpDataSourceFactory(Context context) {
|
||||||
if (httpDataSourceFactory != null) {
|
if (httpDataSourceFactory == null) {
|
||||||
return httpDataSourceFactory;
|
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();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
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;
|
return httpDataSourceFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -139,7 +128,6 @@ public final class DemoUtil {
|
||||||
return downloadNotificationHelper;
|
return downloadNotificationHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
|
||||||
public static synchronized DownloadManager getDownloadManager(Context context) {
|
public static synchronized DownloadManager getDownloadManager(Context context) {
|
||||||
ensureDownloadManagerInitialized(context);
|
ensureDownloadManagerInitialized(context);
|
||||||
return downloadManager;
|
return downloadManager;
|
||||||
|
|
|
||||||
|
|
@ -16,16 +16,15 @@
|
||||||
package androidx.media3.demo.main;
|
package androidx.media3.demo.main;
|
||||||
|
|
||||||
import static com.google.common.base.Preconditions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static com.google.common.base.Preconditions.checkState;
|
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Handler;
|
import android.os.AsyncTask;
|
||||||
import android.os.Looper;
|
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.OptIn;
|
import androidx.annotation.OptIn;
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.DrmInitData;
|
import androidx.media3.common.DrmInitData;
|
||||||
|
|
@ -55,9 +54,6 @@ import java.io.IOException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.CopyOnWriteArraySet;
|
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. */
|
/** Tracks media that has been downloaded. */
|
||||||
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||||
|
|
@ -186,7 +182,7 @@ public class DownloadTracker {
|
||||||
trackSelectionDialog.dismiss();
|
trackSelectionDialog.dismiss();
|
||||||
}
|
}
|
||||||
if (widevineOfflineLicenseFetchTask != null) {
|
if (widevineOfflineLicenseFetchTask != null) {
|
||||||
widevineOfflineLicenseFetchTask.cancel();
|
widevineOfflineLicenseFetchTask.cancel(false);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -201,7 +197,12 @@ public class DownloadTracker {
|
||||||
}
|
}
|
||||||
|
|
||||||
// The content is DRM protected. We need to acquire an offline license.
|
// 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.
|
// TODO(internal b/163107948): Support cases where DrmInitData are not in the manifest.
|
||||||
if (!hasNonNullWidevineSchemaData(format.drmInitData)) {
|
if (!hasNonNullWidevineSchemaData(format.drmInitData)) {
|
||||||
Toast.makeText(context, R.string.download_start_error_offline_license, Toast.LENGTH_LONG)
|
Toast.makeText(context, R.string.download_start_error_offline_license, Toast.LENGTH_LONG)
|
||||||
|
|
@ -356,16 +357,15 @@ public class DownloadTracker {
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Downloads a Widevine offline license in a background thread. */
|
/** Downloads a Widevine offline license in a background thread. */
|
||||||
private static final class WidevineOfflineLicenseFetchTask {
|
@RequiresApi(18)
|
||||||
|
private static final class WidevineOfflineLicenseFetchTask extends AsyncTask<Void, Void, Void> {
|
||||||
|
|
||||||
private final Format format;
|
private final Format format;
|
||||||
private final MediaItem.DrmConfiguration drmConfiguration;
|
private final MediaItem.DrmConfiguration drmConfiguration;
|
||||||
private final DataSource.Factory dataSourceFactory;
|
private final DataSource.Factory dataSourceFactory;
|
||||||
private final StartDownloadDialogHelper dialogHelper;
|
private final StartDownloadDialogHelper dialogHelper;
|
||||||
private final DownloadHelper downloadHelper;
|
private final DownloadHelper downloadHelper;
|
||||||
private final ExecutorService executorService;
|
|
||||||
|
|
||||||
@Nullable Future<?> future;
|
|
||||||
@Nullable private byte[] keySetId;
|
@Nullable private byte[] keySetId;
|
||||||
@Nullable private DrmSession.DrmSessionException drmSessionException;
|
@Nullable private DrmSession.DrmSessionException drmSessionException;
|
||||||
|
|
||||||
|
|
@ -375,8 +375,6 @@ public class DownloadTracker {
|
||||||
DataSource.Factory dataSourceFactory,
|
DataSource.Factory dataSourceFactory,
|
||||||
StartDownloadDialogHelper dialogHelper,
|
StartDownloadDialogHelper dialogHelper,
|
||||||
DownloadHelper downloadHelper) {
|
DownloadHelper downloadHelper) {
|
||||||
checkState(drmConfiguration.scheme.equals(C.WIDEVINE_UUID));
|
|
||||||
this.executorService = Executors.newSingleThreadExecutor();
|
|
||||||
this.format = format;
|
this.format = format;
|
||||||
this.drmConfiguration = drmConfiguration;
|
this.drmConfiguration = drmConfiguration;
|
||||||
this.dataSourceFactory = dataSourceFactory;
|
this.dataSourceFactory = dataSourceFactory;
|
||||||
|
|
@ -384,41 +382,32 @@ public class DownloadTracker {
|
||||||
this.downloadHelper = downloadHelper;
|
this.downloadHelper = downloadHelper;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void cancel() {
|
@Override
|
||||||
if (future != null) {
|
protected Void doInBackground(Void... voids) {
|
||||||
future.cancel(/* mayInterruptIfRunning= */ false);
|
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();
|
||||||
}
|
}
|
||||||
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void execute() {
|
@Override
|
||||||
future =
|
protected void onPostExecute(Void aVoid) {
|
||||||
executorService.submit(
|
if (drmSessionException != null) {
|
||||||
() -> {
|
dialogHelper.onOfflineLicenseFetchedError(drmSessionException);
|
||||||
OfflineLicenseHelper offlineLicenseHelper =
|
} else {
|
||||||
OfflineLicenseHelper.newWidevineInstance(
|
dialogHelper.onOfflineLicenseFetched(downloadHelper, checkNotNull(keySetId));
|
||||||
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,14 +22,11 @@ import static com.google.common.base.Preconditions.checkState;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.OptIn;
|
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
import androidx.media3.common.MediaItem.ClippingConfiguration;
|
import androidx.media3.common.MediaItem.ClippingConfiguration;
|
||||||
import androidx.media3.common.MediaItem.SubtitleConfiguration;
|
import androidx.media3.common.MediaItem.SubtitleConfiguration;
|
||||||
import androidx.media3.common.MediaMetadata;
|
import androidx.media3.common.MediaMetadata;
|
||||||
import androidx.media3.common.Player;
|
|
||||||
import androidx.media3.common.util.UnstableApi;
|
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
|
|
@ -56,7 +53,6 @@ public class IntentUtil {
|
||||||
public static final String MIME_TYPE_EXTRA = "mime_type";
|
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_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 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";
|
public static final String AD_TAG_URI_EXTRA = "ad_tag_uri";
|
||||||
|
|
||||||
|
|
@ -70,21 +66,6 @@ public class IntentUtil {
|
||||||
public static final String SUBTITLE_URI_EXTRA = "subtitle_uri";
|
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_MIME_TYPE_EXTRA = "subtitle_mime_type";
|
||||||
public static final String SUBTITLE_LANGUAGE_EXTRA = "subtitle_language";
|
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}. */
|
/** Creates a list of {@link MediaItem media items} from an {@link Intent}. */
|
||||||
public static List<MediaItem> createMediaItemsFromIntent(Intent intent) {
|
public static List<MediaItem> createMediaItemsFromIntent(Intent intent) {
|
||||||
|
|
@ -133,7 +114,6 @@ public class IntentUtil {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(markerClass = UnstableApi.class) // Setting image duration.
|
|
||||||
private static MediaItem createMediaItemFromIntent(
|
private static MediaItem createMediaItemFromIntent(
|
||||||
Uri uri, Intent intent, String extrasKeySuffix) {
|
Uri uri, Intent intent, String extrasKeySuffix) {
|
||||||
@Nullable String mimeType = intent.getStringExtra(MIME_TYPE_EXTRA + extrasKeySuffix);
|
@Nullable String mimeType = intent.getStringExtra(MIME_TYPE_EXTRA + extrasKeySuffix);
|
||||||
|
|
@ -142,7 +122,6 @@ public class IntentUtil {
|
||||||
@Nullable
|
@Nullable
|
||||||
SubtitleConfiguration subtitleConfiguration =
|
SubtitleConfiguration subtitleConfiguration =
|
||||||
createSubtitleConfiguration(intent, extrasKeySuffix);
|
createSubtitleConfiguration(intent, extrasKeySuffix);
|
||||||
long imageDurationMs = intent.getLongExtra(IMAGE_DURATION_MS + extrasKeySuffix, C.TIME_UNSET);
|
|
||||||
MediaItem.Builder builder =
|
MediaItem.Builder builder =
|
||||||
new MediaItem.Builder()
|
new MediaItem.Builder()
|
||||||
.setUri(uri)
|
.setUri(uri)
|
||||||
|
|
@ -155,8 +134,7 @@ public class IntentUtil {
|
||||||
.setEndPositionMs(
|
.setEndPositionMs(
|
||||||
intent.getLongExtra(
|
intent.getLongExtra(
|
||||||
CLIP_END_POSITION_MS_EXTRA + extrasKeySuffix, C.TIME_END_OF_SOURCE))
|
CLIP_END_POSITION_MS_EXTRA + extrasKeySuffix, C.TIME_END_OF_SOURCE))
|
||||||
.build())
|
.build());
|
||||||
.setImageDurationMs(imageDurationMs);
|
|
||||||
if (adTagUri != null) {
|
if (adTagUri != null) {
|
||||||
builder.setAdsConfiguration(
|
builder.setAdsConfiguration(
|
||||||
new MediaItem.AdsConfiguration.Builder(Uri.parse(adTagUri)).build());
|
new MediaItem.AdsConfiguration.Builder(Uri.parse(adTagUri)).build());
|
||||||
|
|
@ -217,7 +195,6 @@ public class IntentUtil {
|
||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(markerClass = UnstableApi.class) // Accessing image duration.
|
|
||||||
private static void addLocalConfigurationToIntent(
|
private static void addLocalConfigurationToIntent(
|
||||||
MediaItem.LocalConfiguration localConfiguration, Intent intent, String extrasKeySuffix) {
|
MediaItem.LocalConfiguration localConfiguration, Intent intent, String extrasKeySuffix) {
|
||||||
intent
|
intent
|
||||||
|
|
@ -238,9 +215,6 @@ public class IntentUtil {
|
||||||
intent.putExtra(SUBTITLE_MIME_TYPE_EXTRA + extrasKeySuffix, subtitleConfiguration.mimeType);
|
intent.putExtra(SUBTITLE_MIME_TYPE_EXTRA + extrasKeySuffix, subtitleConfiguration.mimeType);
|
||||||
intent.putExtra(SUBTITLE_LANGUAGE_EXTRA + extrasKeySuffix, subtitleConfiguration.language);
|
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(
|
private static void addDrmConfigurationToIntent(
|
||||||
|
|
|
||||||
|
|
@ -93,8 +93,11 @@ public class PlayerActivity extends AppCompatActivity
|
||||||
|
|
||||||
@Nullable private AdsLoader clientSideAdsLoader;
|
@Nullable private AdsLoader clientSideAdsLoader;
|
||||||
|
|
||||||
@Nullable private ImaServerSideAdInsertionMediaSource.AdsLoader serverSideAdsLoader;
|
@OptIn(markerClass = UnstableApi.class)
|
||||||
|
@Nullable
|
||||||
|
private ImaServerSideAdInsertionMediaSource.AdsLoader serverSideAdsLoader;
|
||||||
|
|
||||||
|
@OptIn(markerClass = UnstableApi.class)
|
||||||
private ImaServerSideAdInsertionMediaSource.AdsLoader.@MonotonicNonNull State
|
private ImaServerSideAdInsertionMediaSource.AdsLoader.@MonotonicNonNull State
|
||||||
serverSideAdsLoaderState;
|
serverSideAdsLoaderState;
|
||||||
|
|
||||||
|
|
@ -259,8 +262,8 @@ public class PlayerActivity extends AppCompatActivity
|
||||||
* @return Whether initialization was successful.
|
* @return Whether initialization was successful.
|
||||||
*/
|
*/
|
||||||
protected boolean initializePlayer() {
|
protected boolean initializePlayer() {
|
||||||
Intent intent = getIntent();
|
|
||||||
if (player == null) {
|
if (player == null) {
|
||||||
|
Intent intent = getIntent();
|
||||||
|
|
||||||
mediaItems = createMediaItems(intent);
|
mediaItems = createMediaItems(intent);
|
||||||
if (mediaItems.isEmpty()) {
|
if (mediaItems.isEmpty()) {
|
||||||
|
|
@ -290,15 +293,11 @@ public class PlayerActivity extends AppCompatActivity
|
||||||
}
|
}
|
||||||
player.setMediaItems(mediaItems, /* resetPosition= */ !haveStartPosition);
|
player.setMediaItems(mediaItems, /* resetPosition= */ !haveStartPosition);
|
||||||
player.prepare();
|
player.prepare();
|
||||||
String repeatModeExtra = intent.getStringExtra(IntentUtil.REPEAT_MODE_EXTRA);
|
|
||||||
if (repeatModeExtra != null) {
|
|
||||||
player.setRepeatMode(IntentUtil.parseRepeatModeExtra(repeatModeExtra));
|
|
||||||
}
|
|
||||||
updateButtonVisibility();
|
updateButtonVisibility();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(markerClass = UnstableApi.class) // DRM configuration
|
@OptIn(markerClass = UnstableApi.class) // SSAI configuration
|
||||||
private MediaSource.Factory createMediaSourceFactory() {
|
private MediaSource.Factory createMediaSourceFactory() {
|
||||||
DefaultDrmSessionManagerProvider drmSessionManagerProvider =
|
DefaultDrmSessionManagerProvider drmSessionManagerProvider =
|
||||||
new DefaultDrmSessionManagerProvider();
|
new DefaultDrmSessionManagerProvider();
|
||||||
|
|
@ -331,6 +330,7 @@ public class PlayerActivity extends AppCompatActivity
|
||||||
playerBuilder.setRenderersFactory(renderersFactory);
|
playerBuilder.setRenderersFactory(renderersFactory);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(markerClass = UnstableApi.class)
|
||||||
private void configurePlayerWithServerSideAdsLoader() {
|
private void configurePlayerWithServerSideAdsLoader() {
|
||||||
serverSideAdsLoader.setPlayer(player);
|
serverSideAdsLoader.setPlayer(player);
|
||||||
}
|
}
|
||||||
|
|
@ -361,7 +361,11 @@ public class PlayerActivity extends AppCompatActivity
|
||||||
|
|
||||||
MediaItem.DrmConfiguration drmConfiguration = mediaItem.localConfiguration.drmConfiguration;
|
MediaItem.DrmConfiguration drmConfiguration = mediaItem.localConfiguration.drmConfiguration;
|
||||||
if (drmConfiguration != null) {
|
if (drmConfiguration != null) {
|
||||||
if (!FrameworkMediaDrm.isCryptoSchemeSupported(drmConfiguration.scheme)) {
|
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)) {
|
||||||
showToast(R.string.error_drm_unsupported_scheme);
|
showToast(R.string.error_drm_unsupported_scheme);
|
||||||
finish();
|
finish();
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
|
|
@ -399,6 +403,7 @@ public class PlayerActivity extends AppCompatActivity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(markerClass = UnstableApi.class)
|
||||||
private void releaseServerSideAdsLoader() {
|
private void releaseServerSideAdsLoader() {
|
||||||
serverSideAdsLoaderState = serverSideAdsLoader.release();
|
serverSideAdsLoaderState = serverSideAdsLoader.release();
|
||||||
serverSideAdsLoader = null;
|
serverSideAdsLoader = null;
|
||||||
|
|
@ -412,17 +417,20 @@ public class PlayerActivity extends AppCompatActivity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(markerClass = UnstableApi.class)
|
||||||
private void saveServerSideAdsLoaderState(Bundle outState) {
|
private void saveServerSideAdsLoaderState(Bundle outState) {
|
||||||
if (serverSideAdsLoaderState != null) {
|
if (serverSideAdsLoaderState != null) {
|
||||||
outState.putBundle(KEY_SERVER_SIDE_ADS_LOADER_STATE, serverSideAdsLoaderState.toBundle());
|
outState.putBundle(KEY_SERVER_SIDE_ADS_LOADER_STATE, serverSideAdsLoaderState.toBundle());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(markerClass = UnstableApi.class)
|
||||||
private void restoreServerSideAdsLoaderState(Bundle savedInstanceState) {
|
private void restoreServerSideAdsLoaderState(Bundle savedInstanceState) {
|
||||||
Bundle adsLoaderStateBundle = savedInstanceState.getBundle(KEY_SERVER_SIDE_ADS_LOADER_STATE);
|
Bundle adsLoaderStateBundle = savedInstanceState.getBundle(KEY_SERVER_SIDE_ADS_LOADER_STATE);
|
||||||
if (adsLoaderStateBundle != null) {
|
if (adsLoaderStateBundle != null) {
|
||||||
serverSideAdsLoaderState =
|
serverSideAdsLoaderState =
|
||||||
ImaServerSideAdInsertionMediaSource.AdsLoader.State.fromBundle(adsLoaderStateBundle);
|
ImaServerSideAdInsertionMediaSource.AdsLoader.State.CREATOR.fromBundle(
|
||||||
|
adsLoaderStateBundle);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -506,7 +514,7 @@ public class PlayerActivity extends AppCompatActivity
|
||||||
|
|
||||||
private class PlayerErrorMessageProvider implements ErrorMessageProvider<PlaybackException> {
|
private class PlayerErrorMessageProvider implements ErrorMessageProvider<PlaybackException> {
|
||||||
|
|
||||||
@OptIn(markerClass = UnstableApi.class) // Using decoder exceptions
|
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||||
@Override
|
@Override
|
||||||
public Pair<Integer, String> getErrorMessage(PlaybackException e) {
|
public Pair<Integer, String> getErrorMessage(PlaybackException e) {
|
||||||
String errorString = getString(R.string.error_generic);
|
String errorString = getString(R.string.error_generic);
|
||||||
|
|
@ -547,7 +555,7 @@ public class PlayerActivity extends AppCompatActivity
|
||||||
return mediaItems;
|
return mediaItems;
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(markerClass = UnstableApi.class) // Using Download API
|
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||||
private static MediaItem maybeSetDownloadProperties(
|
private static MediaItem maybeSetDownloadProperties(
|
||||||
MediaItem item, @Nullable DownloadRequest downloadRequest) {
|
MediaItem item, @Nullable DownloadRequest downloadRequest) {
|
||||||
if (downloadRequest == null) {
|
if (downloadRequest == null) {
|
||||||
|
|
|
||||||
|
|
@ -26,13 +26,11 @@ import android.content.SharedPreferences;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.content.res.AssetManager;
|
import android.content.res.AssetManager;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.AsyncTask;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
|
||||||
import android.os.Looper;
|
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
import android.util.JsonReader;
|
import android.util.JsonReader;
|
||||||
import android.util.Log;
|
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
import android.view.MenuItem;
|
import android.view.MenuItem;
|
||||||
|
|
@ -45,15 +43,15 @@ import android.widget.ExpandableListView.OnChildClickListener;
|
||||||
import android.widget.ImageButton;
|
import android.widget.ImageButton;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
|
import androidx.annotation.DoNotInline;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.OptIn;
|
import androidx.annotation.OptIn;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.media3.common.C;
|
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
import androidx.media3.common.MediaItem.ClippingConfiguration;
|
import androidx.media3.common.MediaItem.ClippingConfiguration;
|
||||||
import androidx.media3.common.MediaMetadata;
|
import androidx.media3.common.MediaMetadata;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.Log;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import androidx.media3.datasource.DataSource;
|
import androidx.media3.datasource.DataSource;
|
||||||
import androidx.media3.datasource.DataSourceInputStream;
|
import androidx.media3.datasource.DataSourceInputStream;
|
||||||
|
|
@ -67,7 +65,6 @@ import com.google.common.collect.ImmutableMap;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.io.InputStreamReader;
|
import java.io.InputStreamReader;
|
||||||
import java.nio.charset.StandardCharsets;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
|
@ -75,8 +72,6 @@ import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
import java.util.Map;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import java.util.concurrent.ExecutorService;
|
|
||||||
import java.util.concurrent.Executors;
|
|
||||||
|
|
||||||
/** An activity for selecting from a list of media samples. */
|
/** An activity for selecting from a list of media samples. */
|
||||||
public class SampleChooserActivity extends AppCompatActivity
|
public class SampleChooserActivity extends AppCompatActivity
|
||||||
|
|
@ -119,7 +114,6 @@ public class SampleChooserActivity extends AppCompatActivity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} 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)
|
Toast.makeText(getApplicationContext(), R.string.sample_list_load_error, Toast.LENGTH_LONG)
|
||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
@ -260,7 +254,6 @@ public class SampleChooserActivity extends AppCompatActivity
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
|
||||||
private void toggleDownload(MediaItem mediaItem) {
|
private void toggleDownload(MediaItem mediaItem) {
|
||||||
RenderersFactory renderersFactory =
|
RenderersFactory renderersFactory =
|
||||||
DemoUtil.buildRenderersFactory(
|
DemoUtil.buildRenderersFactory(
|
||||||
|
|
@ -277,10 +270,6 @@ public class SampleChooserActivity extends AppCompatActivity
|
||||||
if (localConfiguration.adsConfiguration != null) {
|
if (localConfiguration.adsConfiguration != null) {
|
||||||
return R.string.download_ads_unsupported;
|
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();
|
String scheme = localConfiguration.uri.getScheme();
|
||||||
if (!("http".equals(scheme) || "https".equals(scheme))) {
|
if (!("http".equals(scheme) || "https".equals(scheme))) {
|
||||||
return R.string.download_scheme_unsupported;
|
return R.string.download_scheme_unsupported;
|
||||||
|
|
@ -293,43 +282,34 @@ public class SampleChooserActivity extends AppCompatActivity
|
||||||
return menuItem != null && menuItem.isChecked();
|
return menuItem != null && menuItem.isChecked();
|
||||||
}
|
}
|
||||||
|
|
||||||
private final class SampleListLoader {
|
private final class SampleListLoader extends AsyncTask<String, Void, List<PlaylistGroup>> {
|
||||||
|
|
||||||
private final ExecutorService executorService;
|
|
||||||
|
|
||||||
private boolean sawError;
|
private boolean sawError;
|
||||||
|
|
||||||
public SampleListLoader() {
|
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||||
executorService = Executors.newSingleThreadExecutor();
|
@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;
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
@Override
|
||||||
public void execute(String... uris) {
|
protected void onPostExecute(List<PlaylistGroup> result) {
|
||||||
executorService.execute(
|
onPlaylistGroups(result, sawError);
|
||||||
() -> {
|
|
||||||
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)
|
private void readPlaylistGroups(JsonReader reader, List<PlaylistGroup> groups)
|
||||||
|
|
@ -373,7 +353,6 @@ public class SampleChooserActivity extends AppCompatActivity
|
||||||
group.playlists.addAll(playlistHolders);
|
group.playlists.addAll(playlistHolders);
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(markerClass = UnstableApi.class) // Setting image duration.
|
|
||||||
private PlaylistHolder readEntry(JsonReader reader, boolean insidePlaylist) throws IOException {
|
private PlaylistHolder readEntry(JsonReader reader, boolean insidePlaylist) throws IOException {
|
||||||
Uri uri = null;
|
Uri uri = null;
|
||||||
String extension = null;
|
String extension = null;
|
||||||
|
|
@ -411,9 +390,6 @@ public class SampleChooserActivity extends AppCompatActivity
|
||||||
case "clip_end_position_ms":
|
case "clip_end_position_ms":
|
||||||
clippingConfiguration.setEndPositionMs(reader.nextLong());
|
clippingConfiguration.setEndPositionMs(reader.nextLong());
|
||||||
break;
|
break;
|
||||||
case "image_duration_ms":
|
|
||||||
mediaItem.setImageDurationMs(reader.nextLong());
|
|
||||||
break;
|
|
||||||
case "ad_tag_uri":
|
case "ad_tag_uri":
|
||||||
mediaItem.setAdsConfiguration(
|
mediaItem.setAdsConfiguration(
|
||||||
new MediaItem.AdsConfiguration.Builder(Uri.parse(reader.nextString())).build());
|
new MediaItem.AdsConfiguration.Builder(Uri.parse(reader.nextString())).build());
|
||||||
|
|
@ -666,6 +642,7 @@ public class SampleChooserActivity extends AppCompatActivity
|
||||||
@RequiresApi(33)
|
@RequiresApi(33)
|
||||||
private static class Api33 {
|
private static class Api33 {
|
||||||
|
|
||||||
|
@DoNotInline
|
||||||
public static String getPostNotificationPermissionString() {
|
public static String getPostNotificationPermissionString() {
|
||||||
return Manifest.permission.POST_NOTIFICATIONS;
|
return Manifest.permission.POST_NOTIFICATIONS;
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -67,8 +67,7 @@ public final class TrackSelectionDialog extends DialogFragment {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final ImmutableList<Integer> SUPPORTED_TRACK_TYPES =
|
public static final ImmutableList<Integer> SUPPORTED_TRACK_TYPES =
|
||||||
ImmutableList.of(
|
ImmutableList.of(C.TRACK_TYPE_VIDEO, C.TRACK_TYPE_AUDIO, C.TRACK_TYPE_TEXT);
|
||||||
C.TRACK_TYPE_VIDEO, C.TRACK_TYPE_AUDIO, C.TRACK_TYPE_TEXT, C.TRACK_TYPE_IMAGE);
|
|
||||||
|
|
||||||
private final SparseArray<TrackSelectionViewFragment> tabFragments;
|
private final SparseArray<TrackSelectionViewFragment> tabFragments;
|
||||||
private final ArrayList<Integer> tabTrackTypes;
|
private final ArrayList<Integer> tabTrackTypes;
|
||||||
|
|
@ -267,13 +266,11 @@ public final class TrackSelectionDialog extends DialogFragment {
|
||||||
private static String getTrackTypeString(Resources resources, @C.TrackType int trackType) {
|
private static String getTrackTypeString(Resources resources, @C.TrackType int trackType) {
|
||||||
switch (trackType) {
|
switch (trackType) {
|
||||||
case C.TRACK_TYPE_VIDEO:
|
case C.TRACK_TYPE_VIDEO:
|
||||||
return resources.getString(R.string.track_selection_title_video);
|
return resources.getString(R.string.exo_track_selection_title_video);
|
||||||
case C.TRACK_TYPE_AUDIO:
|
case C.TRACK_TYPE_AUDIO:
|
||||||
return resources.getString(R.string.track_selection_title_audio);
|
return resources.getString(R.string.exo_track_selection_title_audio);
|
||||||
case C.TRACK_TYPE_TEXT:
|
case C.TRACK_TYPE_TEXT:
|
||||||
return resources.getString(R.string.track_selection_title_text);
|
return resources.getString(R.string.exo_track_selection_title_text);
|
||||||
case C.TRACK_TYPE_IMAGE:
|
|
||||||
return resources.getString(R.string.track_selection_title_image);
|
|
||||||
default:
|
default:
|
||||||
throw new IllegalArgumentException();
|
throw new IllegalArgumentException();
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -25,6 +25,8 @@
|
||||||
|
|
||||||
<string name="error_generic">Playback failed</string>
|
<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_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>
|
<string name="error_no_decoder">This device does not provide a decoder for <xliff:g id="mime_type">%1$s</xliff:g></string>
|
||||||
|
|
@ -57,16 +59,6 @@
|
||||||
|
|
||||||
<string name="download_ads_unsupported">IMA does not support offline ads</string>
|
<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="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>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ apply plugin: 'kotlin-android'
|
||||||
android {
|
android {
|
||||||
namespace 'androidx.media3.demo.session'
|
namespace 'androidx.media3.demo.session'
|
||||||
|
|
||||||
compileSdk project.ext.compileSdkVersion
|
compileSdkVersion project.ext.compileSdkVersion
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
|
@ -34,6 +34,7 @@ android {
|
||||||
versionCode project.ext.releaseVersionCode
|
versionCode project.ext.releaseVersionCode
|
||||||
minSdkVersion project.ext.minSdkVersion
|
minSdkVersion project.ext.minSdkVersion
|
||||||
targetSdkVersion project.ext.appTargetSdkVersion
|
targetSdkVersion project.ext.appTargetSdkVersion
|
||||||
|
multiDexEnabled true
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|
@ -59,13 +60,11 @@ android {
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
// For detecting and debugging leaks only. LeakCanary is not needed for demo app to work.
|
// For detecting and debugging leaks only. LeakCanary is not needed for demo app to work.
|
||||||
debugImplementation 'com.squareup.leakcanary:leakcanary-android:' + leakCanaryVersion
|
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10'
|
||||||
implementation 'androidx.core:core-ktx:' + androidxCoreVersion
|
implementation 'androidx.core:core-ktx:' + androidxCoreVersion
|
||||||
implementation 'androidx.lifecycle:lifecycle-common:' + androidxLifecycleVersion
|
|
||||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:' + androidxLifecycleVersion
|
|
||||||
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
|
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
|
||||||
|
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
|
||||||
implementation 'com.google.android.material:material:' + androidxMaterialVersion
|
implementation 'com.google.android.material:material:' + androidxMaterialVersion
|
||||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-guava:' + kotlinxCoroutinesVersion
|
|
||||||
implementation project(modulePrefix + 'lib-ui')
|
implementation project(modulePrefix + 'lib-ui')
|
||||||
implementation project(modulePrefix + 'lib-session')
|
implementation project(modulePrefix + 'lib-session')
|
||||||
implementation project(modulePrefix + 'demo-session-service')
|
implementation project(modulePrefix + 'demo-session-service')
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="androidx.media3.demo.session">
|
package="androidx.media3.demo.session">
|
||||||
|
|
||||||
<uses-sdk/>
|
<uses-sdk/>
|
||||||
|
|
@ -22,10 +23,12 @@
|
||||||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name="androidx.multidex.MultiDexApplication"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name"
|
android:label="@string/app_name"
|
||||||
android:theme="@style/Theme.Media3Demo">
|
android:theme="@style/Theme.Media3Demo"
|
||||||
|
tools:replace="android:name">
|
||||||
|
|
||||||
<!-- Declare that this session demo supports Android Auto. -->
|
<!-- Declare that this session demo supports Android Auto. -->
|
||||||
<meta-data
|
<meta-data
|
||||||
|
|
@ -56,7 +59,7 @@
|
||||||
android:foregroundServiceType="mediaPlayback"
|
android:foregroundServiceType="mediaPlayback"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="androidx.media3.session.MediaLibraryService"/>
|
<action android:name="androidx.media3.session.MediaSessionService"/>
|
||||||
<action android:name="android.media.browse.MediaBrowserService"/>
|
<action android:name="android.media.browse.MediaBrowserService"/>
|
||||||
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
|
<action android:name="android.media.action.MEDIA_PLAY_FROM_SEARCH"/>
|
||||||
</intent-filter>
|
</intent-filter>
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,6 @@ package androidx.media3.demo.session
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.util.Log
|
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
|
|
@ -29,9 +28,6 @@ import android.widget.TextView
|
||||||
import androidx.annotation.OptIn
|
import androidx.annotation.OptIn
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
import androidx.appcompat.app.AppCompatActivity
|
||||||
import androidx.core.content.ContextCompat
|
import androidx.core.content.ContextCompat
|
||||||
import androidx.lifecycle.Lifecycle
|
|
||||||
import androidx.lifecycle.lifecycleScope
|
|
||||||
import androidx.lifecycle.repeatOnLifecycle
|
|
||||||
import androidx.media3.common.C.TRACK_TYPE_TEXT
|
import androidx.media3.common.C.TRACK_TYPE_TEXT
|
||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
import androidx.media3.common.Player
|
import androidx.media3.common.Player
|
||||||
|
|
@ -44,15 +40,13 @@ import androidx.media3.session.MediaController
|
||||||
import androidx.media3.session.SessionToken
|
import androidx.media3.session.SessionToken
|
||||||
import androidx.media3.ui.PlayerView
|
import androidx.media3.ui.PlayerView
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
import kotlinx.coroutines.awaitCancellation
|
import com.google.common.util.concurrent.MoreExecutors
|
||||||
import kotlinx.coroutines.guava.await
|
|
||||||
import kotlinx.coroutines.launch
|
|
||||||
|
|
||||||
private const val TAG = "PlayerActivity"
|
|
||||||
|
|
||||||
class PlayerActivity : AppCompatActivity() {
|
class PlayerActivity : AppCompatActivity() {
|
||||||
private lateinit var controllerFuture: ListenableFuture<MediaController>
|
private lateinit var controllerFuture: ListenableFuture<MediaController>
|
||||||
private lateinit var controller: MediaController
|
private val controller: MediaController?
|
||||||
|
get() =
|
||||||
|
if (controllerFuture.isDone && !controllerFuture.isCancelled) controllerFuture.get() else null
|
||||||
|
|
||||||
private lateinit var playerView: PlayerView
|
private lateinit var playerView: PlayerView
|
||||||
private lateinit var mediaItemListView: ListView
|
private lateinit var mediaItemListView: ListView
|
||||||
|
|
@ -60,21 +54,8 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
private val mediaItemList: MutableList<MediaItem> = mutableListOf()
|
private val mediaItemList: MutableList<MediaItem> = mutableListOf()
|
||||||
private var lastMediaItemId: String? = null
|
private var lastMediaItemId: String? = null
|
||||||
|
|
||||||
@OptIn(UnstableApi::class) // PlayerView.hideController
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
lifecycleScope.launch {
|
|
||||||
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
|
||||||
try {
|
|
||||||
initializeController()
|
|
||||||
awaitCancellation()
|
|
||||||
} finally {
|
|
||||||
playerView.player = null
|
|
||||||
releaseController()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
setContentView(R.layout.activity_player)
|
setContentView(R.layout.activity_player)
|
||||||
playerView = findViewById(R.id.player_view)
|
playerView = findViewById(R.id.player_view)
|
||||||
|
|
||||||
|
|
@ -83,8 +64,10 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
mediaItemListView.adapter = mediaItemListAdapter
|
mediaItemListView.adapter = mediaItemListAdapter
|
||||||
mediaItemListView.setOnItemClickListener { _, _, position, _ ->
|
mediaItemListView.setOnItemClickListener { _, _, position, _ ->
|
||||||
run {
|
run {
|
||||||
|
val controller = this.controller ?: return@run
|
||||||
if (controller.currentMediaItemIndex == position) {
|
if (controller.currentMediaItemIndex == position) {
|
||||||
controller.playWhenReady = !controller.playWhenReady
|
controller.playWhenReady = !controller.playWhenReady
|
||||||
|
@OptIn(UnstableApi::class) // PlayerView.hideController
|
||||||
if (controller.playWhenReady) {
|
if (controller.playWhenReady) {
|
||||||
playerView.hideController()
|
playerView.hideController()
|
||||||
}
|
}
|
||||||
|
|
@ -96,15 +79,26 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private suspend fun initializeController() {
|
override fun onStart() {
|
||||||
|
super.onStart()
|
||||||
|
initializeController()
|
||||||
|
}
|
||||||
|
|
||||||
|
override fun onStop() {
|
||||||
|
super.onStop()
|
||||||
|
playerView.player = null
|
||||||
|
releaseController()
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun initializeController() {
|
||||||
controllerFuture =
|
controllerFuture =
|
||||||
MediaController.Builder(
|
MediaController.Builder(
|
||||||
this,
|
this,
|
||||||
SessionToken(this, ComponentName(this, PlaybackService::class.java)),
|
SessionToken(this, ComponentName(this, PlaybackService::class.java))
|
||||||
)
|
)
|
||||||
.buildAsync()
|
.buildAsync()
|
||||||
updateMediaMetadataUI()
|
updateMediaMetadataUI()
|
||||||
setController()
|
controllerFuture.addListener({ setController() }, MoreExecutors.directExecutor())
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun releaseController() {
|
private fun releaseController() {
|
||||||
|
|
@ -112,13 +106,9 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(UnstableApi::class) // PlayerView.setShowSubtitleButton
|
@OptIn(UnstableApi::class) // PlayerView.setShowSubtitleButton
|
||||||
private suspend fun setController() {
|
private fun setController() {
|
||||||
try {
|
val controller = this.controller ?: return
|
||||||
controller = controllerFuture.await()
|
|
||||||
} catch (t: Throwable) {
|
|
||||||
Log.w(TAG, "Failed to connect to MediaController", t)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
playerView.player = controller
|
playerView.player = controller
|
||||||
|
|
||||||
updateCurrentPlaylistUI()
|
updateCurrentPlaylistUI()
|
||||||
|
|
@ -147,7 +137,8 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateMediaMetadataUI() {
|
private fun updateMediaMetadataUI() {
|
||||||
if (!::controller.isInitialized || controller.mediaItemCount == 0) {
|
val controller = this.controller
|
||||||
|
if (controller == null || controller.mediaItemCount == 0) {
|
||||||
findViewById<TextView>(R.id.media_title).text = getString(R.string.waiting_for_metadata)
|
findViewById<TextView>(R.id.media_title).text = getString(R.string.waiting_for_metadata)
|
||||||
findViewById<TextView>(R.id.media_artist).text = ""
|
findViewById<TextView>(R.id.media_artist).text = ""
|
||||||
return
|
return
|
||||||
|
|
@ -161,9 +152,7 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateCurrentPlaylistUI() {
|
private fun updateCurrentPlaylistUI() {
|
||||||
if (!::controller.isInitialized) {
|
val controller = this.controller ?: return
|
||||||
return
|
|
||||||
}
|
|
||||||
mediaItemList.clear()
|
mediaItemList.clear()
|
||||||
for (i in 0 until controller.mediaItemCount) {
|
for (i in 0 until controller.mediaItemCount) {
|
||||||
mediaItemList.add(controller.getMediaItemAt(i))
|
mediaItemList.add(controller.getMediaItemAt(i))
|
||||||
|
|
@ -174,7 +163,7 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
private inner class MediaItemListAdapter(
|
private inner class MediaItemListAdapter(
|
||||||
context: Context,
|
context: Context,
|
||||||
viewID: Int,
|
viewID: Int,
|
||||||
mediaItemList: List<MediaItem>,
|
mediaItemList: List<MediaItem>
|
||||||
) : ArrayAdapter<MediaItem>(context, viewID, mediaItemList) {
|
) : ArrayAdapter<MediaItem>(context, viewID, mediaItemList) {
|
||||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||||
val mediaItem = getItem(position)!!
|
val mediaItem = getItem(position)!!
|
||||||
|
|
@ -184,7 +173,7 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
returnConvertView.findViewById<TextView>(R.id.media_item).text = mediaItem.mediaMetadata.title
|
returnConvertView.findViewById<TextView>(R.id.media_item).text = mediaItem.mediaMetadata.title
|
||||||
|
|
||||||
val deleteButton = returnConvertView.findViewById<Button>(R.id.delete_button)
|
val deleteButton = returnConvertView.findViewById<Button>(R.id.delete_button)
|
||||||
if (::controller.isInitialized && position == controller.currentMediaItemIndex) {
|
if (position == controller?.currentMediaItemIndex) {
|
||||||
// Styles for the current media item list item.
|
// Styles for the current media item list item.
|
||||||
returnConvertView.setBackgroundColor(
|
returnConvertView.setBackgroundColor(
|
||||||
ContextCompat.getColor(context, R.color.playlist_item_background)
|
ContextCompat.getColor(context, R.color.playlist_item_background)
|
||||||
|
|
@ -203,6 +192,7 @@ class PlayerActivity : AppCompatActivity() {
|
||||||
.setTextColor(ContextCompat.getColor(context, R.color.white))
|
.setTextColor(ContextCompat.getColor(context, R.color.white))
|
||||||
deleteButton.visibility = View.VISIBLE
|
deleteButton.visibility = View.VISIBLE
|
||||||
deleteButton.setOnClickListener {
|
deleteButton.setOnClickListener {
|
||||||
|
val controller = this@PlayerActivity.controller ?: return@setOnClickListener
|
||||||
controller.removeMediaItem(position)
|
controller.removeMediaItem(position)
|
||||||
updateCurrentPlaylistUI()
|
updateCurrentPlaylistUI()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -22,16 +22,21 @@
|
||||||
android:background="@color/player_background"
|
android:background="@color/player_background"
|
||||||
tools:context=".PlayerActivity">
|
tools:context=".PlayerActivity">
|
||||||
|
|
||||||
<androidx.media3.ui.PlayerView
|
<androidx.media3.ui.AspectRatioFrameLayout
|
||||||
android:id="@+id/player_view"
|
|
||||||
android:background="@color/player_background"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="300dp"
|
android:layout_height="300dp"
|
||||||
app:artwork_display_mode="fill"
|
android:layout_width="match_parent"
|
||||||
app:default_artwork="@drawable/artwork_placeholder"
|
>
|
||||||
app:repeat_toggle_modes="one|all"
|
<androidx.media3.ui.PlayerView
|
||||||
app:show_shuffle_button="true"
|
android:id="@+id/player_view"
|
||||||
app:shutter_background_color="@color/player_background" />
|
android:background="@color/player_background"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="match_parent"
|
||||||
|
app:artwork_display_mode="fill"
|
||||||
|
app:default_artwork="@drawable/artwork_placeholder"
|
||||||
|
app:repeat_toggle_modes="one|all"
|
||||||
|
app:show_shuffle_button="true"
|
||||||
|
app:shutter_background_color="@color/player_background" />
|
||||||
|
</androidx.media3.ui.AspectRatioFrameLayout>
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/media_artist"
|
android:id="@+id/media_artist"
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
<resources>
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
<!-- Base application theme. -->
|
<!-- Base application theme. -->
|
||||||
<style name="Theme.Media3Demo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
<style name="Theme.Media3Demo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||||
<!-- Primary brand color. -->
|
<!-- Primary brand color. -->
|
||||||
|
|
@ -25,7 +25,9 @@
|
||||||
<item name="colorSecondaryVariant">@color/teal_200</item>
|
<item name="colorSecondaryVariant">@color/teal_200</item>
|
||||||
<item name="colorOnSecondary">@color/black</item>
|
<item name="colorOnSecondary">@color/black</item>
|
||||||
<!-- Status bar color. -->
|
<!-- Status bar color. -->
|
||||||
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
|
<item name="android:statusBarColor" tools:targetApi="l">
|
||||||
|
?attr/colorPrimaryVariant
|
||||||
|
</item>
|
||||||
<!-- Customize your theme here. -->
|
<!-- Customize your theme here. -->
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -16,11 +16,11 @@
|
||||||
<resources>
|
<resources>
|
||||||
<string name="app_name">Media3 Session Demo</string>
|
<string name="app_name">Media3 Session Demo</string>
|
||||||
<string name="current_playlist_name">Current playlist</string>
|
<string name="current_playlist_name">Current playlist</string>
|
||||||
<string name="open_player_content_description">Click to view your playlist</string>
|
<string name="open_player_content_description">Click to view your play list</string>
|
||||||
<string name="added_media_item_format">Added %1$s to playlist</string>
|
<string name="added_media_item_format">Added %1$s to playlist</string>
|
||||||
<string name="shuffle">Shuffle</string>
|
<string name="shuffle">Shuffle</string>
|
||||||
<string name="play_button">Play</string>
|
<string name="play_button">Play</string>
|
||||||
<string name="waiting_for_metadata">Connecting…</string>
|
<string name="waiting_for_metadata">Waiting for playlist to load…</string>
|
||||||
<string name="notification_permission_denied">
|
<string name="notification_permission_denied">
|
||||||
"Without notification access the app can't warn about failed background operations"</string>
|
"Without notification access the app can't warn about failed background operations"</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -13,7 +13,7 @@
|
||||||
See the License for the specific language governing permissions and
|
See the License for the specific language governing permissions and
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
<resources>
|
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||||
<!-- Base application theme. -->
|
<!-- Base application theme. -->
|
||||||
<style name="Theme.Media3Demo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
<style name="Theme.Media3Demo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||||
<!-- Primary brand color. -->
|
<!-- Primary brand color. -->
|
||||||
|
|
@ -25,7 +25,9 @@
|
||||||
<item name="colorSecondaryVariant">@color/teal_700</item>
|
<item name="colorSecondaryVariant">@color/teal_700</item>
|
||||||
<item name="colorOnSecondary">@color/black</item>
|
<item name="colorOnSecondary">@color/black</item>
|
||||||
<!-- Status bar color. -->
|
<!-- Status bar color. -->
|
||||||
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
|
<item name="android:statusBarColor" tools:targetApi="l">
|
||||||
|
?attr/colorPrimaryVariant
|
||||||
|
</item>
|
||||||
<!-- Customize your theme here. -->
|
<!-- Customize your theme here. -->
|
||||||
</style>
|
</style>
|
||||||
</resources>
|
</resources>
|
||||||
|
|
|
||||||
|
|
@ -18,7 +18,7 @@ apply plugin: 'kotlin-android'
|
||||||
android {
|
android {
|
||||||
namespace 'androidx.media3.demo.session.automotive'
|
namespace 'androidx.media3.demo.session.automotive'
|
||||||
|
|
||||||
compileSdk project.ext.compileSdkVersion
|
compileSdkVersion project.ext.compileSdkVersion
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
|
@ -34,6 +34,7 @@ android {
|
||||||
versionCode project.ext.releaseVersionCode
|
versionCode project.ext.releaseVersionCode
|
||||||
minSdkVersion project.ext.automotiveMinSdkVersion
|
minSdkVersion project.ext.automotiveMinSdkVersion
|
||||||
targetSdkVersion project.ext.appTargetSdkVersion
|
targetSdkVersion project.ext.appTargetSdkVersion
|
||||||
|
multiDexEnabled true
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|
@ -59,6 +60,7 @@ android {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'androidx.core:core-ktx:' + androidxCoreVersion
|
implementation 'androidx.core:core-ktx:' + androidxCoreVersion
|
||||||
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
|
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
|
||||||
|
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
|
||||||
implementation 'com.google.android.material:material:' + androidxMaterialVersion
|
implementation 'com.google.android.material:material:' + androidxMaterialVersion
|
||||||
implementation project(modulePrefix + 'lib-session')
|
implementation project(modulePrefix + 'lib-session')
|
||||||
implementation project(modulePrefix + 'demo-session-service')
|
implementation project(modulePrefix + 'demo-session-service')
|
||||||
|
|
|
||||||
|
|
@ -14,6 +14,7 @@
|
||||||
limitations under the License.
|
limitations under the License.
|
||||||
-->
|
-->
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
package="androidx.media3.demo.session.automotive">
|
package="androidx.media3.demo.session.automotive">
|
||||||
|
|
||||||
<uses-sdk/>
|
<uses-sdk/>
|
||||||
|
|
@ -38,11 +39,13 @@
|
||||||
android:resource="@xml/automotive_app_desc"/>
|
android:resource="@xml/automotive_app_desc"/>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
|
android:name="androidx.multidex.MultiDexApplication"
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
android:taskAffinity=""
|
android:taskAffinity=""
|
||||||
android:appCategory="audio"
|
android:appCategory="audio"
|
||||||
android:icon="@mipmap/ic_launcher"
|
android:icon="@mipmap/ic_launcher"
|
||||||
android:label="@string/app_name">
|
android:label="@string/app_name"
|
||||||
|
tools:replace="android:name">
|
||||||
|
|
||||||
<meta-data
|
<meta-data
|
||||||
android:name="androidx.car.app.TintableAttributionIcon"
|
android:name="androidx.car.app.TintableAttributionIcon"
|
||||||
|
|
|
||||||
|
|
@ -17,7 +17,7 @@ apply plugin: 'kotlin-android'
|
||||||
android {
|
android {
|
||||||
namespace 'androidx.media3.demo.session.service'
|
namespace 'androidx.media3.demo.session.service'
|
||||||
|
|
||||||
compileSdk project.ext.compileSdkVersion
|
compileSdkVersion project.ext.compileSdkVersion
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
|
@ -33,6 +33,7 @@ android {
|
||||||
versionCode project.ext.releaseVersionCode
|
versionCode project.ext.releaseVersionCode
|
||||||
minSdkVersion project.ext.minSdkVersion
|
minSdkVersion project.ext.minSdkVersion
|
||||||
targetSdkVersion project.ext.appTargetSdkVersion
|
targetSdkVersion project.ext.appTargetSdkVersion
|
||||||
|
multiDexEnabled true
|
||||||
}
|
}
|
||||||
|
|
||||||
buildTypes {
|
buildTypes {
|
||||||
|
|
@ -53,6 +54,7 @@ android {
|
||||||
dependencies {
|
dependencies {
|
||||||
implementation 'androidx.core:core-ktx:' + androidxCoreVersion
|
implementation 'androidx.core:core-ktx:' + androidxCoreVersion
|
||||||
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
|
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
|
||||||
|
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
|
||||||
implementation project(modulePrefix + 'lib-exoplayer')
|
implementation project(modulePrefix + 'lib-exoplayer')
|
||||||
implementation project(modulePrefix + 'lib-exoplayer-dash')
|
implementation project(modulePrefix + 'lib-exoplayer-dash')
|
||||||
implementation project(modulePrefix + 'lib-exoplayer-hls')
|
implementation project(modulePrefix + 'lib-exoplayer-hls')
|
||||||
|
|
|
||||||
|
|
@ -27,7 +27,6 @@ import androidx.media3.session.MediaLibraryService
|
||||||
import androidx.media3.session.MediaSession
|
import androidx.media3.session.MediaSession
|
||||||
import androidx.media3.session.MediaSession.MediaItemsWithStartPosition
|
import androidx.media3.session.MediaSession.MediaItemsWithStartPosition
|
||||||
import androidx.media3.session.SessionCommand
|
import androidx.media3.session.SessionCommand
|
||||||
import androidx.media3.session.SessionError
|
|
||||||
import androidx.media3.session.SessionResult
|
import androidx.media3.session.SessionResult
|
||||||
import com.google.common.collect.ImmutableList
|
import com.google.common.collect.ImmutableList
|
||||||
import com.google.common.util.concurrent.Futures
|
import com.google.common.util.concurrent.Futures
|
||||||
|
|
@ -41,17 +40,18 @@ open class DemoMediaLibrarySessionCallback(context: Context) :
|
||||||
MediaItemTree.initialize(context.assets)
|
MediaItemTree.initialize(context.assets)
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(UnstableApi::class) // TODO: b/328238954 - Remove once new CommandButton icons are stable.
|
|
||||||
private val customLayoutCommandButtons: List<CommandButton> =
|
private val customLayoutCommandButtons: List<CommandButton> =
|
||||||
listOf(
|
listOf(
|
||||||
CommandButton.Builder(CommandButton.ICON_SHUFFLE_OFF)
|
CommandButton.Builder()
|
||||||
.setDisplayName(context.getString(R.string.exo_controls_shuffle_on_description))
|
.setDisplayName(context.getString(R.string.exo_controls_shuffle_on_description))
|
||||||
.setSessionCommand(SessionCommand(CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON, Bundle.EMPTY))
|
.setSessionCommand(SessionCommand(CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON, Bundle.EMPTY))
|
||||||
|
.setIconResId(R.drawable.exo_icon_shuffle_off)
|
||||||
.build(),
|
.build(),
|
||||||
CommandButton.Builder(CommandButton.ICON_SHUFFLE_ON)
|
CommandButton.Builder()
|
||||||
.setDisplayName(context.getString(R.string.exo_controls_shuffle_off_description))
|
.setDisplayName(context.getString(R.string.exo_controls_shuffle_off_description))
|
||||||
.setSessionCommand(SessionCommand(CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF, Bundle.EMPTY))
|
.setSessionCommand(SessionCommand(CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF, Bundle.EMPTY))
|
||||||
.build(),
|
.setIconResId(R.drawable.exo_icon_shuffle_on)
|
||||||
|
.build()
|
||||||
)
|
)
|
||||||
|
|
||||||
@OptIn(UnstableApi::class) // MediaSession.ConnectionResult.DEFAULT_SESSION_AND_LIBRARY_COMMANDS
|
@OptIn(UnstableApi::class) // MediaSession.ConnectionResult.DEFAULT_SESSION_AND_LIBRARY_COMMANDS
|
||||||
|
|
@ -70,7 +70,7 @@ open class DemoMediaLibrarySessionCallback(context: Context) :
|
||||||
@OptIn(UnstableApi::class)
|
@OptIn(UnstableApi::class)
|
||||||
override fun onConnect(
|
override fun onConnect(
|
||||||
session: MediaSession,
|
session: MediaSession,
|
||||||
controller: MediaSession.ControllerInfo,
|
controller: MediaSession.ControllerInfo
|
||||||
): MediaSession.ConnectionResult {
|
): MediaSession.ConnectionResult {
|
||||||
if (
|
if (
|
||||||
session.isMediaNotificationController(controller) ||
|
session.isMediaNotificationController(controller) ||
|
||||||
|
|
@ -93,7 +93,7 @@ open class DemoMediaLibrarySessionCallback(context: Context) :
|
||||||
session: MediaSession,
|
session: MediaSession,
|
||||||
controller: MediaSession.ControllerInfo,
|
controller: MediaSession.ControllerInfo,
|
||||||
customCommand: SessionCommand,
|
customCommand: SessionCommand,
|
||||||
args: Bundle,
|
args: Bundle
|
||||||
): ListenableFuture<SessionResult> {
|
): ListenableFuture<SessionResult> {
|
||||||
if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON == customCommand.customAction) {
|
if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON == customCommand.customAction) {
|
||||||
// Enable shuffling.
|
// Enable shuffling.
|
||||||
|
|
@ -101,7 +101,7 @@ open class DemoMediaLibrarySessionCallback(context: Context) :
|
||||||
// Change the custom layout to contain the `Disable shuffling` command.
|
// Change the custom layout to contain the `Disable shuffling` command.
|
||||||
session.setCustomLayout(
|
session.setCustomLayout(
|
||||||
session.mediaNotificationControllerInfo!!,
|
session.mediaNotificationControllerInfo!!,
|
||||||
ImmutableList.of(customLayoutCommandButtons[1]),
|
ImmutableList.of(customLayoutCommandButtons[1])
|
||||||
)
|
)
|
||||||
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
||||||
} else if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF == customCommand.customAction) {
|
} else if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF == customCommand.customAction) {
|
||||||
|
|
@ -110,53 +110,51 @@ open class DemoMediaLibrarySessionCallback(context: Context) :
|
||||||
// Change the custom layout to contain the `Enable shuffling` command.
|
// Change the custom layout to contain the `Enable shuffling` command.
|
||||||
session.setCustomLayout(
|
session.setCustomLayout(
|
||||||
session.mediaNotificationControllerInfo!!,
|
session.mediaNotificationControllerInfo!!,
|
||||||
ImmutableList.of(customLayoutCommandButtons[0]),
|
ImmutableList.of(customLayoutCommandButtons[0])
|
||||||
)
|
)
|
||||||
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
||||||
}
|
}
|
||||||
return Futures.immediateFuture(SessionResult(SessionError.ERROR_NOT_SUPPORTED))
|
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_ERROR_NOT_SUPPORTED))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onGetLibraryRoot(
|
override fun onGetLibraryRoot(
|
||||||
session: MediaLibraryService.MediaLibrarySession,
|
session: MediaLibraryService.MediaLibrarySession,
|
||||||
browser: MediaSession.ControllerInfo,
|
browser: MediaSession.ControllerInfo,
|
||||||
params: MediaLibraryService.LibraryParams?,
|
params: MediaLibraryService.LibraryParams?
|
||||||
): ListenableFuture<LibraryResult<MediaItem>> {
|
): ListenableFuture<LibraryResult<MediaItem>> {
|
||||||
return Futures.immediateFuture(LibraryResult.ofItem(MediaItemTree.getRootItem(), params))
|
return Futures.immediateFuture(LibraryResult.ofItem(MediaItemTree.getRootItem(), params))
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(UnstableApi::class) // SessionError.ERROR_BAD_VALUE
|
|
||||||
override fun onGetItem(
|
override fun onGetItem(
|
||||||
session: MediaLibraryService.MediaLibrarySession,
|
session: MediaLibraryService.MediaLibrarySession,
|
||||||
browser: MediaSession.ControllerInfo,
|
browser: MediaSession.ControllerInfo,
|
||||||
mediaId: String,
|
mediaId: String
|
||||||
): ListenableFuture<LibraryResult<MediaItem>> {
|
): ListenableFuture<LibraryResult<MediaItem>> {
|
||||||
MediaItemTree.getItem(mediaId)?.let {
|
MediaItemTree.getItem(mediaId)?.let {
|
||||||
return Futures.immediateFuture(LibraryResult.ofItem(it, /* params= */ null))
|
return Futures.immediateFuture(LibraryResult.ofItem(it, /* params= */ null))
|
||||||
}
|
}
|
||||||
return Futures.immediateFuture(LibraryResult.ofError(SessionError.ERROR_BAD_VALUE))
|
return Futures.immediateFuture(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE))
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(UnstableApi::class) // SessionError.ERROR_BAD_VALUE
|
|
||||||
override fun onGetChildren(
|
override fun onGetChildren(
|
||||||
session: MediaLibraryService.MediaLibrarySession,
|
session: MediaLibraryService.MediaLibrarySession,
|
||||||
browser: MediaSession.ControllerInfo,
|
browser: MediaSession.ControllerInfo,
|
||||||
parentId: String,
|
parentId: String,
|
||||||
page: Int,
|
page: Int,
|
||||||
pageSize: Int,
|
pageSize: Int,
|
||||||
params: MediaLibraryService.LibraryParams?,
|
params: MediaLibraryService.LibraryParams?
|
||||||
): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
|
): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
|
||||||
val children = MediaItemTree.getChildren(parentId)
|
val children = MediaItemTree.getChildren(parentId)
|
||||||
if (children.isNotEmpty()) {
|
if (children.isNotEmpty()) {
|
||||||
return Futures.immediateFuture(LibraryResult.ofItemList(children, params))
|
return Futures.immediateFuture(LibraryResult.ofItemList(children, params))
|
||||||
}
|
}
|
||||||
return Futures.immediateFuture(LibraryResult.ofError(SessionError.ERROR_BAD_VALUE))
|
return Futures.immediateFuture(LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE))
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onAddMediaItems(
|
override fun onAddMediaItems(
|
||||||
mediaSession: MediaSession,
|
mediaSession: MediaSession,
|
||||||
controller: MediaSession.ControllerInfo,
|
controller: MediaSession.ControllerInfo,
|
||||||
mediaItems: List<MediaItem>,
|
mediaItems: List<MediaItem>
|
||||||
): ListenableFuture<List<MediaItem>> {
|
): ListenableFuture<List<MediaItem>> {
|
||||||
return Futures.immediateFuture(resolveMediaItems(mediaItems))
|
return Futures.immediateFuture(resolveMediaItems(mediaItems))
|
||||||
}
|
}
|
||||||
|
|
@ -167,7 +165,7 @@ open class DemoMediaLibrarySessionCallback(context: Context) :
|
||||||
browser: MediaSession.ControllerInfo,
|
browser: MediaSession.ControllerInfo,
|
||||||
mediaItems: List<MediaItem>,
|
mediaItems: List<MediaItem>,
|
||||||
startIndex: Int,
|
startIndex: Int,
|
||||||
startPositionMs: Long,
|
startPositionMs: Long
|
||||||
): ListenableFuture<MediaItemsWithStartPosition> {
|
): ListenableFuture<MediaItemsWithStartPosition> {
|
||||||
if (mediaItems.size == 1) {
|
if (mediaItems.size == 1) {
|
||||||
// Try to expand a single item to a playlist.
|
// Try to expand a single item to a playlist.
|
||||||
|
|
@ -196,7 +194,7 @@ open class DemoMediaLibrarySessionCallback(context: Context) :
|
||||||
private fun maybeExpandSingleItemToPlaylist(
|
private fun maybeExpandSingleItemToPlaylist(
|
||||||
mediaItem: MediaItem,
|
mediaItem: MediaItem,
|
||||||
startIndex: Int,
|
startIndex: Int,
|
||||||
startPositionMs: Long,
|
startPositionMs: Long
|
||||||
): MediaItemsWithStartPosition? {
|
): MediaItemsWithStartPosition? {
|
||||||
var playlist = listOf<MediaItem>()
|
var playlist = listOf<MediaItem>()
|
||||||
var indexInPlaylist = startIndex
|
var indexInPlaylist = startIndex
|
||||||
|
|
@ -225,7 +223,7 @@ open class DemoMediaLibrarySessionCallback(context: Context) :
|
||||||
session: MediaLibraryService.MediaLibrarySession,
|
session: MediaLibraryService.MediaLibrarySession,
|
||||||
browser: MediaSession.ControllerInfo,
|
browser: MediaSession.ControllerInfo,
|
||||||
query: String,
|
query: String,
|
||||||
params: MediaLibraryService.LibraryParams?,
|
params: MediaLibraryService.LibraryParams?
|
||||||
): ListenableFuture<LibraryResult<Void>> {
|
): ListenableFuture<LibraryResult<Void>> {
|
||||||
session.notifySearchResultChanged(browser, query, MediaItemTree.search(query).size, params)
|
session.notifySearchResultChanged(browser, query, MediaItemTree.search(query).size, params)
|
||||||
return Futures.immediateFuture(LibraryResult.ofVoid())
|
return Futures.immediateFuture(LibraryResult.ofVoid())
|
||||||
|
|
@ -237,7 +235,7 @@ open class DemoMediaLibrarySessionCallback(context: Context) :
|
||||||
query: String,
|
query: String,
|
||||||
page: Int,
|
page: Int,
|
||||||
pageSize: Int,
|
pageSize: Int,
|
||||||
params: MediaLibraryService.LibraryParams?,
|
params: MediaLibraryService.LibraryParams?
|
||||||
): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
|
): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
|
||||||
return Futures.immediateFuture(LibraryResult.ofItemList(MediaItemTree.search(query), params))
|
return Futures.immediateFuture(LibraryResult.ofItemList(MediaItemTree.search(query), params))
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -19,18 +19,17 @@ import android.Manifest
|
||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
import android.app.PendingIntent
|
import android.app.PendingIntent
|
||||||
|
import android.content.Intent
|
||||||
import android.content.pm.PackageManager
|
import android.content.pm.PackageManager
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import androidx.annotation.OptIn
|
import androidx.annotation.OptIn
|
||||||
import androidx.core.app.NotificationCompat
|
import androidx.core.app.NotificationCompat
|
||||||
import androidx.core.app.NotificationManagerCompat
|
import androidx.core.app.NotificationManagerCompat
|
||||||
import androidx.core.os.bundleOf
|
|
||||||
import androidx.media3.common.AudioAttributes
|
import androidx.media3.common.AudioAttributes
|
||||||
import androidx.media3.common.util.UnstableApi
|
import androidx.media3.common.util.UnstableApi
|
||||||
import androidx.media3.demo.session.service.R
|
import androidx.media3.demo.session.service.R
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
import androidx.media3.exoplayer.util.EventLogger
|
import androidx.media3.exoplayer.util.EventLogger
|
||||||
import androidx.media3.session.MediaConstants
|
|
||||||
import androidx.media3.session.MediaLibraryService
|
import androidx.media3.session.MediaLibraryService
|
||||||
import androidx.media3.session.MediaSession
|
import androidx.media3.session.MediaSession
|
||||||
import androidx.media3.session.MediaSession.ControllerInfo
|
import androidx.media3.session.MediaSession.ControllerInfo
|
||||||
|
|
@ -91,6 +90,13 @@ open class DemoPlaybackService : MediaLibraryService() {
|
||||||
return mediaLibrarySession
|
return mediaLibrarySession
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onTaskRemoved(rootIntent: Intent?) {
|
||||||
|
val player = mediaLibrarySession.player
|
||||||
|
if (!player.playWhenReady || player.mediaItemCount == 0) {
|
||||||
|
stopSelf()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// MediaSession.setSessionActivity
|
// MediaSession.setSessionActivity
|
||||||
// MediaSessionService.clearListener
|
// MediaSessionService.clearListener
|
||||||
@OptIn(UnstableApi::class)
|
@OptIn(UnstableApi::class)
|
||||||
|
|
@ -113,17 +119,6 @@ open class DemoPlaybackService : MediaLibraryService() {
|
||||||
MediaLibrarySession.Builder(this, player, createLibrarySessionCallback())
|
MediaLibrarySession.Builder(this, player, createLibrarySessionCallback())
|
||||||
.also { builder -> getSingleTopActivity()?.let { builder.setSessionActivity(it) } }
|
.also { builder -> getSingleTopActivity()?.let { builder.setSessionActivity(it) } }
|
||||||
.build()
|
.build()
|
||||||
.also { mediaLibrarySession ->
|
|
||||||
// The media session always supports skip, except at the start and end of the playlist.
|
|
||||||
// Reserve the space for the skip action in these cases to avoid custom actions jumping
|
|
||||||
// around when the user skips.
|
|
||||||
mediaLibrarySession.setSessionExtras(
|
|
||||||
bundleOf(
|
|
||||||
MediaConstants.EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_PREV to true,
|
|
||||||
MediaConstants.EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_NEXT to true,
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@OptIn(UnstableApi::class) // MediaSessionService.Listener
|
@OptIn(UnstableApi::class) // MediaSessionService.Listener
|
||||||
|
|
@ -171,7 +166,7 @@ open class DemoPlaybackService : MediaLibraryService() {
|
||||||
NotificationChannel(
|
NotificationChannel(
|
||||||
CHANNEL_ID,
|
CHANNEL_ID,
|
||||||
getString(R.string.notification_channel_name),
|
getString(R.string.notification_channel_name),
|
||||||
NotificationManager.IMPORTANCE_DEFAULT,
|
NotificationManager.IMPORTANCE_DEFAULT
|
||||||
)
|
)
|
||||||
notificationManagerCompat.createNotificationChannel(channel)
|
notificationManagerCompat.createNotificationChannel(channel)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,6 +0,0 @@
|
||||||
# Short form content demo
|
|
||||||
|
|
||||||
This app demonstrates usage of ExoPlayer in common short form content UI setups.
|
|
||||||
|
|
||||||
See the [demos README](../README.md) for instructions on how to build and run
|
|
||||||
this demo.
|
|
||||||
|
|
@ -1,94 +0,0 @@
|
||||||
// Copyright 2023 The Android Open Source Project
|
|
||||||
//
|
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
// you may not use this file except in compliance with the License.
|
|
||||||
// You may obtain a copy of the License at
|
|
||||||
//
|
|
||||||
// http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
//
|
|
||||||
// Unless required by applicable law or agreed to in writing, software
|
|
||||||
// distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
// See the License for the specific language governing permissions and
|
|
||||||
// limitations under the License.
|
|
||||||
apply from: '../../constants.gradle'
|
|
||||||
apply plugin: 'com.android.application'
|
|
||||||
apply plugin: 'kotlin-android'
|
|
||||||
|
|
||||||
android {
|
|
||||||
namespace 'androidx.media3.demo.shortform'
|
|
||||||
|
|
||||||
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
|
|
||||||
proguardFiles = [
|
|
||||||
'proguard-rules.txt',
|
|
||||||
getDefaultProguardFile('proguard-android-optimize.txt')
|
|
||||||
]
|
|
||||||
signingConfig signingConfigs.debug
|
|
||||||
}
|
|
||||||
debug {
|
|
||||||
jniDebuggable = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
lintOptions {
|
|
||||||
// The demo app isn't indexed, and doesn't have translations.
|
|
||||||
disable 'GoogleAppIndexingWarning','MissingTranslation'
|
|
||||||
}
|
|
||||||
buildFeatures {
|
|
||||||
viewBinding true
|
|
||||||
}
|
|
||||||
sourceSets {
|
|
||||||
main {
|
|
||||||
java {
|
|
||||||
srcDirs 'src/main/java'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
test {
|
|
||||||
java {
|
|
||||||
srcDirs 'src/test/java'
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
dependencies {
|
|
||||||
implementation 'androidx.appcompat:appcompat:1.6.1'
|
|
||||||
implementation 'com.google.android.material:material:1.9.0'
|
|
||||||
implementation 'androidx.constraintlayout:constraintlayout:2.1.4'
|
|
||||||
|
|
||||||
implementation 'androidx.core:core-ktx:' + androidxCoreVersion
|
|
||||||
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
|
|
||||||
implementation 'com.google.android.material:material:' + androidxMaterialVersion
|
|
||||||
implementation project(modulePrefix + 'lib-exoplayer')
|
|
||||||
implementation project(modulePrefix + 'lib-exoplayer-dash')
|
|
||||||
implementation project(modulePrefix + 'lib-exoplayer-hls')
|
|
||||||
implementation project(modulePrefix + 'lib-ui')
|
|
||||||
|
|
||||||
testImplementation 'androidx.test:core:' + androidxTestCoreVersion
|
|
||||||
testImplementation 'androidx.test.ext:junit:' + androidxTestJUnitVersion
|
|
||||||
testImplementation 'junit:junit:' + junitVersion
|
|
||||||
testImplementation 'com.google.truth:truth:' + truthVersion
|
|
||||||
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
|
|
||||||
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
|
|
||||||
}
|
|
||||||
|
|
@ -1,2 +0,0 @@
|
||||||
# Proguard rules specific to the media3 short form content demo app.
|
|
||||||
|
|
||||||
|
|
@ -1,43 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Copyright 2023 The Android Open Source Project
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
-->
|
|
||||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
package="androidx.media3.demo.shortform">
|
|
||||||
|
|
||||||
<application
|
|
||||||
android:allowBackup="false"
|
|
||||||
android:icon="@mipmap/ic_launcher"
|
|
||||||
android:label="@string/app_name"
|
|
||||||
android:theme="@style/Theme.MaterialComponents.DayNight.NoActionBar"
|
|
||||||
android:taskAffinity="">
|
|
||||||
<activity
|
|
||||||
android:exported="true"
|
|
||||||
android:name=".MainActivity">
|
|
||||||
<intent-filter>
|
|
||||||
<action android:name="android.intent.action.MAIN" />
|
|
||||||
<category android:name="android.intent.category.LAUNCHER" />
|
|
||||||
</intent-filter>
|
|
||||||
</activity>
|
|
||||||
<activity
|
|
||||||
android:exported="false"
|
|
||||||
android:label="@string/title_activity_view_pager"
|
|
||||||
android:name=".viewpager.ViewPagerActivity"/>
|
|
||||||
</application>
|
|
||||||
|
|
||||||
<uses-permission android:name="android.permission.INTERNET" />
|
|
||||||
|
|
||||||
<uses-sdk />
|
|
||||||
|
|
||||||
</manifest>
|
|
||||||
|
|
@ -1,64 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package androidx.media3.demo.shortform
|
|
||||||
|
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.text.Editable
|
|
||||||
import android.text.TextWatcher
|
|
||||||
import android.view.View
|
|
||||||
import android.widget.EditText
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.media3.common.util.UnstableApi
|
|
||||||
import androidx.media3.demo.shortform.viewpager.ViewPagerActivity
|
|
||||||
import java.lang.Integer.max
|
|
||||||
import java.lang.Integer.min
|
|
||||||
|
|
||||||
class MainActivity : AppCompatActivity() {
|
|
||||||
|
|
||||||
@androidx.annotation.OptIn(UnstableApi::class)
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
setContentView(R.layout.activity_main)
|
|
||||||
|
|
||||||
var numberOfPlayers = 3
|
|
||||||
val numPlayersFieldView = findViewById<EditText>(R.id.num_players_field)
|
|
||||||
numPlayersFieldView.addTextChangedListener(
|
|
||||||
object : TextWatcher {
|
|
||||||
override fun beforeTextChanged(s: CharSequence, start: Int, count: Int, after: Int) = Unit
|
|
||||||
|
|
||||||
override fun onTextChanged(s: CharSequence, start: Int, before: Int, count: Int) = Unit
|
|
||||||
|
|
||||||
override fun afterTextChanged(s: Editable) {
|
|
||||||
val newText = numPlayersFieldView.text.toString()
|
|
||||||
if (newText != "") {
|
|
||||||
numberOfPlayers = max(1, min(newText.toInt(), 5))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
findViewById<View>(R.id.view_pager_button).setOnClickListener {
|
|
||||||
startActivity(
|
|
||||||
Intent(this, ViewPagerActivity::class.java).putExtra(NUM_PLAYERS_EXTRA, numberOfPlayers)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
const val NUM_PLAYERS_EXTRA = "number_of_players"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,35 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* 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.shortform
|
|
||||||
|
|
||||||
import androidx.media3.common.MediaItem
|
|
||||||
|
|
||||||
class MediaItemDatabase {
|
|
||||||
|
|
||||||
private val mediaUris =
|
|
||||||
mutableListOf(
|
|
||||||
"https://storage.googleapis.com/exoplayer-test-media-0/shortform_1.mp4",
|
|
||||||
"https://storage.googleapis.com/exoplayer-test-media-0/shortform_2.mp4",
|
|
||||||
"https://storage.googleapis.com/exoplayer-test-media-0/shortform_3.mp4",
|
|
||||||
"https://storage.googleapis.com/exoplayer-test-media-0/shortform_4.mp4",
|
|
||||||
"https://storage.googleapis.com/exoplayer-test-media-0/shortform_6.mp4",
|
|
||||||
)
|
|
||||||
|
|
||||||
fun get(index: Int): MediaItem {
|
|
||||||
val uri = mediaUris.get(index.mod(mediaUris.size))
|
|
||||||
return MediaItem.Builder().setUri(uri).setMediaId(index.toString()).build()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,129 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package androidx.media3.demo.shortform
|
|
||||||
|
|
||||||
import android.os.Handler
|
|
||||||
import android.os.Looper
|
|
||||||
import androidx.annotation.OptIn
|
|
||||||
import androidx.media3.common.Player
|
|
||||||
import androidx.media3.common.util.UnstableApi
|
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
|
||||||
import androidx.media3.exoplayer.source.preload.DefaultPreloadManager.Builder
|
|
||||||
import androidx.media3.exoplayer.util.EventLogger
|
|
||||||
import com.google.common.collect.BiMap
|
|
||||||
import com.google.common.collect.HashBiMap
|
|
||||||
import com.google.common.collect.Maps
|
|
||||||
import java.util.Collections
|
|
||||||
import java.util.LinkedList
|
|
||||||
import java.util.Queue
|
|
||||||
|
|
||||||
@OptIn(UnstableApi::class)
|
|
||||||
class PlayerPool(private val numberOfPlayers: Int, preloadManagerBuilder: Builder) {
|
|
||||||
|
|
||||||
/** Creates a player instance to be used by the pool. */
|
|
||||||
interface PlayerFactory {
|
|
||||||
/** Creates an [ExoPlayer] instance. */
|
|
||||||
fun createPlayer(): ExoPlayer
|
|
||||||
}
|
|
||||||
|
|
||||||
private val availablePlayerQueue: Queue<Int> = LinkedList()
|
|
||||||
private val playerMap: BiMap<Int, ExoPlayer> = Maps.synchronizedBiMap(HashBiMap.create())
|
|
||||||
private val playerRequestTokenSet: MutableSet<Int> = Collections.synchronizedSet(HashSet<Int>())
|
|
||||||
private val playerFactory: PlayerFactory = DefaultPlayerFactory(preloadManagerBuilder)
|
|
||||||
|
|
||||||
fun acquirePlayer(token: Int, callback: (ExoPlayer) -> Unit) {
|
|
||||||
synchronized(playerMap) {
|
|
||||||
// Add token to set of views requesting players
|
|
||||||
playerRequestTokenSet.add(token)
|
|
||||||
acquirePlayerInternal(token, callback)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun acquirePlayerInternal(token: Int, callback: (ExoPlayer) -> Unit) {
|
|
||||||
synchronized(playerMap) {
|
|
||||||
if (!availablePlayerQueue.isEmpty()) {
|
|
||||||
val playerNumber = availablePlayerQueue.remove()
|
|
||||||
playerMap[playerNumber]?.let { callback.invoke(it) }
|
|
||||||
playerRequestTokenSet.remove(token)
|
|
||||||
return
|
|
||||||
} else if (playerMap.size < numberOfPlayers) {
|
|
||||||
val player = playerFactory.createPlayer()
|
|
||||||
playerMap[playerMap.size] = player
|
|
||||||
callback.invoke(player)
|
|
||||||
playerRequestTokenSet.remove(token)
|
|
||||||
return
|
|
||||||
} else if (playerRequestTokenSet.contains(token)) {
|
|
||||||
Handler(Looper.getMainLooper()).postDelayed({ acquirePlayerInternal(token, callback) }, 500)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Calls [Player.play()] for the given player and pauses all other players. */
|
|
||||||
fun play(player: Player) {
|
|
||||||
pauseAllPlayers(player)
|
|
||||||
player.play()
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Pauses all players.
|
|
||||||
*
|
|
||||||
* @param keepOngoingPlayer The optional player that should keep playing if not paused.
|
|
||||||
*/
|
|
||||||
private fun pauseAllPlayers(keepOngoingPlayer: Player? = null) {
|
|
||||||
for (player in playerMap.values) {
|
|
||||||
if (player != keepOngoingPlayer) {
|
|
||||||
player.pause()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun releasePlayer(token: Int, player: ExoPlayer?) {
|
|
||||||
synchronized(playerMap) {
|
|
||||||
// Remove token from set of views requesting players & remove potential callbacks
|
|
||||||
// trying to grab the player
|
|
||||||
playerRequestTokenSet.remove(token)
|
|
||||||
// Stop the player and release into the pool for reusing, do not player.release()
|
|
||||||
player?.stop()
|
|
||||||
player?.clearMediaItems()
|
|
||||||
if (player != null) {
|
|
||||||
val playerNumber = playerMap.inverse()[player]
|
|
||||||
availablePlayerQueue.add(playerNumber)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
fun destroyPlayers() {
|
|
||||||
synchronized(playerMap) {
|
|
||||||
for (i in 0 until playerMap.size) {
|
|
||||||
playerMap[i]?.release()
|
|
||||||
playerMap.remove(i)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@OptIn(UnstableApi::class)
|
|
||||||
private class DefaultPlayerFactory(private val preloadManagerBuilder: Builder) : PlayerFactory {
|
|
||||||
private var playerCounter = 0
|
|
||||||
|
|
||||||
override fun createPlayer(): ExoPlayer {
|
|
||||||
val player = preloadManagerBuilder.buildExoPlayer()
|
|
||||||
player.addAnalyticsListener(EventLogger("player-$playerCounter"))
|
|
||||||
playerCounter++
|
|
||||||
player.repeatMode = ExoPlayer.REPEAT_MODE_ONE
|
|
||||||
return player
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,63 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package androidx.media3.demo.shortform.viewpager
|
|
||||||
|
|
||||||
import android.os.Bundle
|
|
||||||
import android.util.Log
|
|
||||||
import androidx.appcompat.app.AppCompatActivity
|
|
||||||
import androidx.media3.demo.shortform.MainActivity
|
|
||||||
import androidx.media3.demo.shortform.MediaItemDatabase
|
|
||||||
import androidx.media3.demo.shortform.R
|
|
||||||
import androidx.viewpager2.widget.ViewPager2
|
|
||||||
|
|
||||||
class ViewPagerActivity : AppCompatActivity() {
|
|
||||||
private lateinit var viewPagerView: ViewPager2
|
|
||||||
private lateinit var onPageChangeCallback: ViewPager2.OnPageChangeCallback
|
|
||||||
private var numberOfPlayers = 3
|
|
||||||
private var mediaItemDatabase = MediaItemDatabase()
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val TAG = "ViewPagerActivity"
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
|
||||||
super.onCreate(savedInstanceState)
|
|
||||||
setContentView(R.layout.activity_view_pager)
|
|
||||||
numberOfPlayers = intent.getIntExtra(MainActivity.NUM_PLAYERS_EXTRA, numberOfPlayers)
|
|
||||||
Log.d(TAG, "Using a pool of $numberOfPlayers players")
|
|
||||||
viewPagerView = findViewById(R.id.viewPager)
|
|
||||||
viewPagerView.offscreenPageLimit = 1
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStart() {
|
|
||||||
super.onStart()
|
|
||||||
val adapter = ViewPagerMediaAdapter(mediaItemDatabase, numberOfPlayers, applicationContext)
|
|
||||||
viewPagerView.adapter = adapter
|
|
||||||
onPageChangeCallback =
|
|
||||||
object : ViewPager2.OnPageChangeCallback() {
|
|
||||||
override fun onPageSelected(position: Int) {
|
|
||||||
adapter.onPageSelected(position)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
viewPagerView.registerOnPageChangeCallback(onPageChangeCallback)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onStop() {
|
|
||||||
viewPagerView.unregisterOnPageChangeCallback(onPageChangeCallback)
|
|
||||||
viewPagerView.adapter = null
|
|
||||||
super.onStop()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,187 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package androidx.media3.demo.shortform.viewpager
|
|
||||||
|
|
||||||
import android.content.Context
|
|
||||||
import android.view.LayoutInflater
|
|
||||||
import android.view.ViewGroup
|
|
||||||
import androidx.annotation.OptIn
|
|
||||||
import androidx.media3.common.C
|
|
||||||
import androidx.media3.common.MediaItem
|
|
||||||
import androidx.media3.common.util.Log
|
|
||||||
import androidx.media3.common.util.UnstableApi
|
|
||||||
import androidx.media3.demo.shortform.MediaItemDatabase
|
|
||||||
import androidx.media3.demo.shortform.PlayerPool
|
|
||||||
import androidx.media3.demo.shortform.R
|
|
||||||
import androidx.media3.exoplayer.DefaultLoadControl
|
|
||||||
import androidx.media3.exoplayer.source.preload.DefaultPreloadManager
|
|
||||||
import androidx.media3.exoplayer.source.preload.DefaultPreloadManager.Status.STAGE_LOADED_FOR_DURATION_MS
|
|
||||||
import androidx.media3.exoplayer.source.preload.TargetPreloadStatusControl
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
import kotlin.math.abs
|
|
||||||
|
|
||||||
@OptIn(UnstableApi::class)
|
|
||||||
class ViewPagerMediaAdapter(
|
|
||||||
private val mediaItemDatabase: MediaItemDatabase,
|
|
||||||
numberOfPlayers: Int,
|
|
||||||
context: Context,
|
|
||||||
) : RecyclerView.Adapter<ViewPagerMediaHolder>() {
|
|
||||||
private val preloadManager: DefaultPreloadManager
|
|
||||||
private val currentMediaItemsAndIndexes: ArrayDeque<Pair<MediaItem, Int>> = ArrayDeque()
|
|
||||||
private var playerPool: PlayerPool
|
|
||||||
private val holderMap: MutableMap<Int, ViewPagerMediaHolder>
|
|
||||||
private val preloadControl: DefaultPreloadControl
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val TAG = "ViewPagerMediaAdapter"
|
|
||||||
private const val LOAD_CONTROL_MIN_BUFFER_MS = 5_000
|
|
||||||
private const val LOAD_CONTROL_MAX_BUFFER_MS = 20_000
|
|
||||||
private const val LOAD_CONTROL_BUFFER_FOR_PLAYBACK_MS = 500
|
|
||||||
private const val MANAGED_ITEM_COUNT = 10
|
|
||||||
private const val ITEM_ADD_REMOVE_COUNT = 4
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
val loadControl =
|
|
||||||
DefaultLoadControl.Builder()
|
|
||||||
.setBufferDurationsMs(
|
|
||||||
LOAD_CONTROL_MIN_BUFFER_MS,
|
|
||||||
LOAD_CONTROL_MAX_BUFFER_MS,
|
|
||||||
LOAD_CONTROL_BUFFER_FOR_PLAYBACK_MS,
|
|
||||||
DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS,
|
|
||||||
)
|
|
||||||
.setPrioritizeTimeOverSizeThresholds(true)
|
|
||||||
.build()
|
|
||||||
preloadControl = DefaultPreloadControl()
|
|
||||||
val preloadManagerBuilder =
|
|
||||||
DefaultPreloadManager.Builder(context.applicationContext, preloadControl)
|
|
||||||
.setLoadControl(loadControl)
|
|
||||||
playerPool = PlayerPool(numberOfPlayers, preloadManagerBuilder)
|
|
||||||
holderMap = mutableMapOf()
|
|
||||||
preloadManager = preloadManagerBuilder.build()
|
|
||||||
for (i in 0 until MANAGED_ITEM_COUNT) {
|
|
||||||
addMediaItem(index = i, isAddingToRightEnd = true)
|
|
||||||
}
|
|
||||||
preloadManager.invalidate()
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
|
|
||||||
playerPool.destroyPlayers()
|
|
||||||
preloadManager.release()
|
|
||||||
holderMap.clear()
|
|
||||||
super.onDetachedFromRecyclerView(recyclerView)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewPagerMediaHolder {
|
|
||||||
val view =
|
|
||||||
LayoutInflater.from(parent.context).inflate(R.layout.media_item_view_pager, parent, false)
|
|
||||||
val holder = ViewPagerMediaHolder(view, playerPool)
|
|
||||||
view.addOnAttachStateChangeListener(holder)
|
|
||||||
return holder
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onBindViewHolder(holder: ViewPagerMediaHolder, position: Int) {
|
|
||||||
val mediaItem = mediaItemDatabase.get(position)
|
|
||||||
Log.d(TAG, "onBindViewHolder: Getting item at position $position")
|
|
||||||
var currentMediaSource = preloadManager.getMediaSource(mediaItem)
|
|
||||||
if (currentMediaSource == null) {
|
|
||||||
preloadManager.add(mediaItem, position)
|
|
||||||
currentMediaSource = preloadManager.getMediaSource(mediaItem)!!
|
|
||||||
}
|
|
||||||
holder.bindData(currentMediaSource)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewAttachedToWindow(holder: ViewPagerMediaHolder) {
|
|
||||||
val holderBindingAdapterPosition = holder.bindingAdapterPosition
|
|
||||||
holderMap[holderBindingAdapterPosition] = holder
|
|
||||||
|
|
||||||
if (!currentMediaItemsAndIndexes.isEmpty()) {
|
|
||||||
val leftMostIndex = currentMediaItemsAndIndexes.first().second
|
|
||||||
val rightMostIndex = currentMediaItemsAndIndexes.last().second
|
|
||||||
|
|
||||||
if (rightMostIndex - holderBindingAdapterPosition <= 2) {
|
|
||||||
Log.d(TAG, "onViewAttachedToWindow: Approaching to the rightmost item")
|
|
||||||
for (i in 1 until ITEM_ADD_REMOVE_COUNT + 1) {
|
|
||||||
addMediaItem(index = rightMostIndex + i, isAddingToRightEnd = true)
|
|
||||||
removeMediaItem(isRemovingFromRightEnd = false)
|
|
||||||
}
|
|
||||||
} else if (holderBindingAdapterPosition - leftMostIndex <= 2) {
|
|
||||||
Log.d(TAG, "onViewAttachedToWindow: Approaching to the leftmost item")
|
|
||||||
for (i in 1 until ITEM_ADD_REMOVE_COUNT + 1) {
|
|
||||||
addMediaItem(index = leftMostIndex - i, isAddingToRightEnd = false)
|
|
||||||
removeMediaItem(isRemovingFromRightEnd = true)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewDetachedFromWindow(holder: ViewPagerMediaHolder) {
|
|
||||||
holderMap.remove(holder.bindingAdapterPosition)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun getItemCount(): Int {
|
|
||||||
// Effectively infinite scroll
|
|
||||||
return Int.MAX_VALUE
|
|
||||||
}
|
|
||||||
|
|
||||||
fun onPageSelected(position: Int) {
|
|
||||||
holderMap[position]?.playIfPossible()
|
|
||||||
preloadControl.currentPlayingIndex = position
|
|
||||||
preloadManager.setCurrentPlayingIndex(position)
|
|
||||||
preloadManager.invalidate()
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun addMediaItem(index: Int, isAddingToRightEnd: Boolean) {
|
|
||||||
if (index < 0) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
Log.d(TAG, "addMediaItem: Adding item at index $index")
|
|
||||||
val mediaItem = mediaItemDatabase.get(index)
|
|
||||||
preloadManager.add(mediaItem, index)
|
|
||||||
if (isAddingToRightEnd) {
|
|
||||||
currentMediaItemsAndIndexes.addLast(Pair(mediaItem, index))
|
|
||||||
} else {
|
|
||||||
currentMediaItemsAndIndexes.addFirst(Pair(mediaItem, index))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun removeMediaItem(isRemovingFromRightEnd: Boolean) {
|
|
||||||
if (currentMediaItemsAndIndexes.size <= MANAGED_ITEM_COUNT) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
val itemAndIndex =
|
|
||||||
if (isRemovingFromRightEnd) {
|
|
||||||
currentMediaItemsAndIndexes.removeLast()
|
|
||||||
} else {
|
|
||||||
currentMediaItemsAndIndexes.removeFirst()
|
|
||||||
}
|
|
||||||
Log.d(TAG, "removeMediaItem: Removing item at index ${itemAndIndex.second}")
|
|
||||||
preloadManager.remove(itemAndIndex.first)
|
|
||||||
}
|
|
||||||
|
|
||||||
inner class DefaultPreloadControl(var currentPlayingIndex: Int = C.INDEX_UNSET) :
|
|
||||||
TargetPreloadStatusControl<Int> {
|
|
||||||
|
|
||||||
override fun getTargetPreloadStatus(rankingData: Int): DefaultPreloadManager.Status? {
|
|
||||||
if (abs(rankingData - currentPlayingIndex) == 2) {
|
|
||||||
return DefaultPreloadManager.Status(STAGE_LOADED_FOR_DURATION_MS, 500L)
|
|
||||||
} else if (abs(rankingData - currentPlayingIndex) == 1) {
|
|
||||||
return DefaultPreloadManager.Status(STAGE_LOADED_FOR_DURATION_MS, 1000L)
|
|
||||||
}
|
|
||||||
return null
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
@ -1,111 +0,0 @@
|
||||||
/*
|
|
||||||
* Copyright 2023 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* 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.shortform.viewpager
|
|
||||||
|
|
||||||
import android.view.View
|
|
||||||
import androidx.annotation.OptIn
|
|
||||||
import androidx.media3.common.util.Log
|
|
||||||
import androidx.media3.common.util.UnstableApi
|
|
||||||
import androidx.media3.demo.shortform.PlayerPool
|
|
||||||
import androidx.media3.demo.shortform.R
|
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
|
||||||
import androidx.media3.exoplayer.source.MediaSource
|
|
||||||
import androidx.media3.ui.PlayerView
|
|
||||||
import androidx.recyclerview.widget.RecyclerView
|
|
||||||
|
|
||||||
@OptIn(UnstableApi::class)
|
|
||||||
class ViewPagerMediaHolder(itemView: View, private val playerPool: PlayerPool) :
|
|
||||||
RecyclerView.ViewHolder(itemView), View.OnAttachStateChangeListener {
|
|
||||||
private val playerView: PlayerView = itemView.findViewById(R.id.player_view)
|
|
||||||
private var exoPlayer: ExoPlayer? = null
|
|
||||||
private var isInView: Boolean = false
|
|
||||||
private var pendingPlayRequestUponSetupPlayer: Boolean = false
|
|
||||||
|
|
||||||
private lateinit var mediaSource: MediaSource
|
|
||||||
|
|
||||||
companion object {
|
|
||||||
private const val TAG = "ViewPagerMediaHolder"
|
|
||||||
}
|
|
||||||
|
|
||||||
init {
|
|
||||||
// Define click listener for the ViewHolder's View
|
|
||||||
playerView.findViewById<PlayerView>(R.id.player_view).setOnClickListener {
|
|
||||||
if (it is PlayerView) {
|
|
||||||
it.player?.run { playWhenReady = !playWhenReady }
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private val player: ExoPlayer?
|
|
||||||
get() {
|
|
||||||
return exoPlayer
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewAttachedToWindow(view: View) {
|
|
||||||
Log.d(TAG, "onViewAttachedToWindow: $bindingAdapterPosition")
|
|
||||||
isInView = true
|
|
||||||
if (player == null) {
|
|
||||||
playerPool.acquirePlayer(bindingAdapterPosition, ::setupPlayer)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onViewDetachedFromWindow(view: View) {
|
|
||||||
Log.d(TAG, "onViewDetachedFromWindow: $bindingAdapterPosition")
|
|
||||||
isInView = false
|
|
||||||
releasePlayer(exoPlayer)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun bindData(mediaSource: MediaSource) {
|
|
||||||
this.mediaSource = mediaSource
|
|
||||||
}
|
|
||||||
|
|
||||||
fun playIfPossible() {
|
|
||||||
player?.let { playerPool.play(it) }
|
|
||||||
if (player == null) {
|
|
||||||
Log.d(TAG, "playIfPossible: The player hasn't been setup yet")
|
|
||||||
pendingPlayRequestUponSetupPlayer = true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun releasePlayer(player: ExoPlayer?) {
|
|
||||||
playerPool.releasePlayer(bindingAdapterPosition, player ?: exoPlayer)
|
|
||||||
this.exoPlayer = null
|
|
||||||
playerView.player = null
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun setupPlayer(player: ExoPlayer) {
|
|
||||||
if (!isInView) {
|
|
||||||
releasePlayer(player)
|
|
||||||
} else {
|
|
||||||
if (player != exoPlayer) {
|
|
||||||
releasePlayer(exoPlayer)
|
|
||||||
}
|
|
||||||
|
|
||||||
player.run {
|
|
||||||
repeatMode = ExoPlayer.REPEAT_MODE_ONE
|
|
||||||
setMediaSource(mediaSource)
|
|
||||||
seekTo(currentPosition)
|
|
||||||
this@ViewPagerMediaHolder.exoPlayer = player
|
|
||||||
player.prepare()
|
|
||||||
playerView.player = player
|
|
||||||
if (pendingPlayRequestUponSetupPlayer) {
|
|
||||||
playerPool.play(player)
|
|
||||||
pendingPlayRequestUponSetupPlayer = false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
Before Width: | Height: | Size: 36 KiB |
|
|
@ -1,48 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Copyright 2023 The Android Open Source Project
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
-->
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical"
|
|
||||||
tools:context=".MainActivity">
|
|
||||||
|
|
||||||
<Button android:id="@+id/view_pager_button"
|
|
||||||
android:text="@string/open_view_pager_activity"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:layout_marginLeft="12dp"
|
|
||||||
android:layout_marginStart="12dp"
|
|
||||||
android:layout_marginEnd="12dp"
|
|
||||||
android:layout_marginRight="12dp"/>
|
|
||||||
|
|
||||||
<com.google.android.material.textfield.TextInputEditText
|
|
||||||
android:id="@+id/num_players_field"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="40dp"
|
|
||||||
android:layout_marginTop="150dp"
|
|
||||||
android:layout_marginStart="12dp"
|
|
||||||
android:layout_marginEnd="12dp"
|
|
||||||
android:layout_marginLeft="12dp"
|
|
||||||
android:layout_marginRight="12dp"
|
|
||||||
android:layout_gravity="center_horizontal"
|
|
||||||
android:background="@color/purple_700"
|
|
||||||
android:gravity="center"
|
|
||||||
android:hint="@string/num_of_players"
|
|
||||||
android:inputType="number"
|
|
||||||
android:textColorHint="@color/grey" />
|
|
||||||
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
@ -1,29 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Copyright 2023 The Android Open Source Project
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
-->
|
|
||||||
<FrameLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical"
|
|
||||||
tools:context=".viewpager.ViewPagerActivity">
|
|
||||||
|
|
||||||
<androidx.viewpager2.widget.ViewPager2
|
|
||||||
android:id="@+id/viewPager"
|
|
||||||
android:orientation="vertical"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"/>
|
|
||||||
|
|
||||||
</FrameLayout>
|
|
||||||
|
|
@ -1,32 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Copyright 2023 The Android Open Source Project
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
-->
|
|
||||||
|
|
||||||
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
|
||||||
xmlns:tools="http://schemas.android.com/tools"
|
|
||||||
xmlns:app="http://schemas.android.com/apk/res-auto"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
android:orientation="vertical"
|
|
||||||
tools:context=".viewpager.ViewPagerActivity">
|
|
||||||
|
|
||||||
<androidx.media3.ui.PlayerView android:id="@+id/player_view"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="match_parent"
|
|
||||||
app:use_controller="false"
|
|
||||||
app:resize_mode="fill"
|
|
||||||
app:show_shuffle_button="true"
|
|
||||||
app:show_subtitle_button="true"/>
|
|
||||||
</LinearLayout>
|
|
||||||
|
Before Width: | Height: | Size: 3.3 KiB |
|
Before Width: | Height: | Size: 2.1 KiB |
|
Before Width: | Height: | Size: 4.8 KiB |
|
Before Width: | Height: | Size: 7.3 KiB |
|
Before Width: | Height: | Size: 10 KiB |
|
|
@ -1,30 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Copyright 2021 The Android Open Source Project
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
-->
|
|
||||||
<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>
|
|
||||||
|
|
@ -1,21 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?><!-- Copyright 2021 The Android Open Source Project
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
-->
|
|
||||||
<resources>
|
|
||||||
<string name="app_name">Media3 short-form content Demo</string>
|
|
||||||
<string name="open_view_pager_activity">Open view pager activity</string>
|
|
||||||
<string name="add_view_pager">Add view pager, please!</string>
|
|
||||||
<string name="title_activity_view_pager">ViewPager activity</string>
|
|
||||||
<string name="num_of_players">How Many Players?</string>
|
|
||||||
</resources>
|
|
||||||
|
|
@ -1,31 +0,0 @@
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<!-- Copyright 2021 The Android Open Source Project
|
|
||||||
|
|
||||||
Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
you may not use this file except in compliance with the License.
|
|
||||||
You may obtain a copy of the License at
|
|
||||||
|
|
||||||
http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
|
|
||||||
Unless required by applicable law or agreed to in writing, software
|
|
||||||
distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
See the License for the specific language governing permissions and
|
|
||||||
limitations under the License.
|
|
||||||
-->
|
|
||||||
<resources>
|
|
||||||
<!-- Base application theme. -->
|
|
||||||
<style name="Theme.Media3Demo" 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>
|
|
||||||
|
|
@ -17,7 +17,7 @@ apply plugin: 'com.android.application'
|
||||||
android {
|
android {
|
||||||
namespace 'androidx.media3.demo.surface'
|
namespace 'androidx.media3.demo.surface'
|
||||||
|
|
||||||
compileSdk project.ext.compileSdkVersion
|
compileSdkVersion project.ext.compileSdkVersion
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
|
|
|
||||||