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
|
||||
description: What version of Media3 (or ExoPlayer) are you using?
|
||||
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.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.0 / ExoPlayer 2.19.0
|
||||
- Media3 1.0.2 / ExoPlayer 2.18.7
|
||||
|
|
|
|||
41
.gitignore
vendored
|
|
@ -52,31 +52,30 @@ tmp
|
|||
|
||||
# External native builds
|
||||
.externalNativeBuild
|
||||
.cxx
|
||||
|
||||
# VP9 decoder extension
|
||||
libraries/decoder_vp9/src/main/jni/libvpx
|
||||
libraries/decoder_vp9/src/main/jni/libvpx_android_configs
|
||||
libraries/decoder_vp9/src/main/jni/libyuv
|
||||
# VP9 extension
|
||||
extensions/vp9/src/main/jni/libvpx
|
||||
extensions/vp9/src/main/jni/libvpx_android_configs
|
||||
extensions/vp9/src/main/jni/libyuv
|
||||
|
||||
# AV1 decoder extension
|
||||
libraries/decoder_av1/src/main/jni/cpu_features
|
||||
libraries/decoder_av1/src/main/jni/libgav1
|
||||
# AV1 extension
|
||||
extensions/av1/src/main/jni/cpu_features
|
||||
extensions/av1/src/main/jni/libgav1
|
||||
|
||||
# Opus decoder extension
|
||||
libraries/decoder_opus/src/main/jni/libopus
|
||||
# Opus extension
|
||||
extensions/opus/src/main/jni/libopus
|
||||
|
||||
# FLAC decoder extension
|
||||
libraries/decoder_flac/src/main/jni/flac
|
||||
# FLAC extension
|
||||
extensions/flac/src/main/jni/flac
|
||||
|
||||
# FFmpeg decoder extension
|
||||
libraries/decoder_ffmpeg/src/main/jni/ffmpeg
|
||||
# FFmpeg extension
|
||||
extensions/ffmpeg/src/main/jni/ffmpeg
|
||||
|
||||
# Cronet datasource extension
|
||||
libraries/datasource_cronet/jniLibs/*
|
||||
!libraries/datasource_cronet/jniLibs/README.md
|
||||
libraries/datasource_cronet/libs/*
|
||||
!libraries/datasource_cronet/libs/README.md
|
||||
# Cronet extension
|
||||
extensions/cronet/jniLibs/*
|
||||
!extensions/cronet/jniLibs/README.md
|
||||
extensions/cronet/libs/*
|
||||
!extensions/cronet/libs/README.md
|
||||
|
||||
# MIDI decoder extension
|
||||
libraries/decoder_midi/lib
|
||||
# MIDI extension
|
||||
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
|
||||
|
||||
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
|
||||
git clone https://github.com/androidx/media.git
|
||||
cd media
|
||||
```
|
||||
|
||||
Next, add the following to your project's `settings.gradle.kts` file, replacing
|
||||
`path/to/media` with the path to your local copy:
|
||||
|
||||
```kotlin
|
||||
(gradle as ExtensionAware).extra["androidxMediaModulePrefix"] = "media3-"
|
||||
gradle.extra.apply {
|
||||
set("androidxMediaModulePrefix", "media-")
|
||||
}
|
||||
apply(from = file("path/to/media/core_settings.gradle"))
|
||||
```
|
||||
|
||||
Or in Gradle Groovy DSL `settings.gradle`:
|
||||
|
||||
```groovy
|
||||
gradle.ext.androidxMediaModulePrefix = 'media3-'
|
||||
gradle.ext.androidxMediaModulePrefix = 'media-'
|
||||
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:
|
||||
|
||||
```kotlin
|
||||
implementation(project(":media3-lib-exoplayer"))
|
||||
implementation(project(":media3-lib-exoplayer-dash"))
|
||||
implementation(project(":media3-lib-ui"))
|
||||
implementation(project(":media-lib-exoplayer"))
|
||||
implementation(project(":media-lib-exoplayer-dash"))
|
||||
implementation(project(":media-lib-ui"))
|
||||
```
|
||||
|
||||
Or in Gradle Groovy DSL `build.gradle`:
|
||||
|
||||
```groovy
|
||||
implementation project(':media3-lib-exoplayer')
|
||||
implementation project(':media3-lib-exoplayer-dash')
|
||||
implementation project(':media3-lib-ui')
|
||||
```
|
||||
|
||||
#### MIDI module
|
||||
|
||||
By default the [MIDI module](libraries/decoder_midi) is disabled as a local
|
||||
dependency, because it requires additional Maven repository config. If you want
|
||||
to use it as a local dependency, please configure the JitPack repository as
|
||||
[described in the module README](libraries/decoder_midi/README.md#getting-the-module),
|
||||
and then enable building the module in your `settings.gradle.kts` file:
|
||||
|
||||
```kotlin
|
||||
gradle.extra.apply {
|
||||
set("androidxMediaEnableMidiModule", true)
|
||||
}
|
||||
```
|
||||
|
||||
Or in Gradle Groovy DSL `settings.gradle`:
|
||||
|
||||
```groovy
|
||||
gradle.ext.androidxMediaEnableMidiModule = true
|
||||
implementation project(':media-lib-exoplayer')
|
||||
implementation project(':media-lib-exoplayer-dash')
|
||||
implementation project(':media-lib-ui')
|
||||
```
|
||||
|
||||
## Developing AndroidX Media
|
||||
|
|
|
|||
1119
RELEASENOTES.md
67
api.txt
|
|
@ -26,7 +26,7 @@ package androidx.media3.common {
|
|||
}
|
||||
|
||||
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 @androidx.media3.common.C.AudioAllowedCapturePolicy public final int allowedCapturePolicy;
|
||||
field @androidx.media3.common.C.AudioContentType public final int contentType;
|
||||
|
|
@ -35,7 +35,7 @@ package androidx.media3.common {
|
|||
field @androidx.media3.common.C.AudioUsage public final int usage;
|
||||
}
|
||||
|
||||
public static final class AudioAttributes.AudioAttributesV21 {
|
||||
@RequiresApi(21) public static final class AudioAttributes.AudioAttributesV21 {
|
||||
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 float RATE_UNSET = -3.4028235E38f;
|
||||
field public static final int ROLE_FLAG_ALTERNATE = 2; // 0x2
|
||||
field public static final int ROLE_FLAG_AUXILIARY = 32768; // 0x8000
|
||||
field public static final int ROLE_FLAG_CAPTION = 64; // 0x40
|
||||
field public static final int ROLE_FLAG_COMMENTARY = 8; // 0x8
|
||||
field public static final int ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND = 1024; // 0x400
|
||||
|
|
@ -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(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 {
|
||||
|
|
@ -549,7 +548,6 @@ package androidx.media3.common {
|
|||
field public static final String APPLICATION_PGS = "application/pgs";
|
||||
field @Deprecated public static final String APPLICATION_RAWCC = "application/x-rawcc";
|
||||
field public static final String APPLICATION_RTSP = "application/x-rtsp";
|
||||
field public static final String APPLICATION_SDP = "application/sdp";
|
||||
field public static final String APPLICATION_SS = "application/vnd.ms-sstr+xml";
|
||||
field public static final String APPLICATION_SUBRIP = "application/x-subrip";
|
||||
field public static final String APPLICATION_TTML = "application/ttml+xml";
|
||||
|
|
@ -623,20 +621,13 @@ package androidx.media3.common {
|
|||
method public static String getErrorCodeName(@androidx.media3.common.PlaybackException.ErrorCode int);
|
||||
field public static final int CUSTOM_ERROR_CODE_BASE = 1000000; // 0xf4240
|
||||
field public static final int ERROR_CODE_AUDIO_TRACK_INIT_FAILED = 5001; // 0x1389
|
||||
field public static final int ERROR_CODE_AUDIO_TRACK_OFFLOAD_INIT_FAILED = 5004; // 0x138c
|
||||
field public static final int ERROR_CODE_AUDIO_TRACK_OFFLOAD_WRITE_FAILED = 5003; // 0x138b
|
||||
field public static final int ERROR_CODE_AUDIO_TRACK_WRITE_FAILED = 5002; // 0x138a
|
||||
field public static final int ERROR_CODE_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a
|
||||
field public static final int ERROR_CODE_BAD_VALUE = -3; // 0xfffffffd
|
||||
field public static final int ERROR_CODE_BEHIND_LIVE_WINDOW = 1002; // 0x3ea
|
||||
field public static final int ERROR_CODE_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98
|
||||
field public static final int ERROR_CODE_CONTENT_ALREADY_PLAYING = -110; // 0xffffff92
|
||||
field public static final int ERROR_CODE_DECODER_INIT_FAILED = 4001; // 0xfa1
|
||||
field public static final int ERROR_CODE_DECODER_QUERY_FAILED = 4002; // 0xfa2
|
||||
field public static final int ERROR_CODE_DECODING_FAILED = 4003; // 0xfa3
|
||||
field public static final int ERROR_CODE_DECODING_FORMAT_EXCEEDS_CAPABILITIES = 4004; // 0xfa4
|
||||
field public static final int ERROR_CODE_DECODING_FORMAT_UNSUPPORTED = 4005; // 0xfa5
|
||||
field public static final int ERROR_CODE_DISCONNECTED = -100; // 0xffffff9c
|
||||
field public static final int ERROR_CODE_DRM_CONTENT_ERROR = 6003; // 0x1773
|
||||
field public static final int ERROR_CODE_DRM_DEVICE_REVOKED = 6007; // 0x1777
|
||||
field public static final int ERROR_CODE_DRM_DISALLOWED_OPERATION = 6005; // 0x1775
|
||||
|
|
@ -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_SYSTEM_ERROR = 6006; // 0x1776
|
||||
field public static final int ERROR_CODE_DRM_UNSPECIFIED = 6000; // 0x1770
|
||||
field public static final int ERROR_CODE_END_OF_PLAYLIST = -109; // 0xffffff93
|
||||
field public static final int ERROR_CODE_FAILED_RUNTIME_CHECK = 1004; // 0x3ec
|
||||
field public static final int ERROR_CODE_INVALID_STATE = -2; // 0xfffffffe
|
||||
field public static final int ERROR_CODE_IO_BAD_HTTP_STATUS = 2004; // 0x7d4
|
||||
field public static final int ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED = 2007; // 0x7d7
|
||||
field public static final int ERROR_CODE_IO_FILE_NOT_FOUND = 2005; // 0x7d5
|
||||
|
|
@ -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_READ_POSITION_OUT_OF_RANGE = 2008; // 0x7d8
|
||||
field public static final int ERROR_CODE_IO_UNSPECIFIED = 2000; // 0x7d0
|
||||
field public static final int ERROR_CODE_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96
|
||||
field public static final int ERROR_CODE_NOT_SUPPORTED = -6; // 0xfffffffa
|
||||
field public static final int ERROR_CODE_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97
|
||||
field public static final int ERROR_CODE_PARSING_CONTAINER_MALFORMED = 3001; // 0xbb9
|
||||
field public static final int ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED = 3003; // 0xbbb
|
||||
field public static final int ERROR_CODE_PARSING_MANIFEST_MALFORMED = 3002; // 0xbba
|
||||
field public static final int ERROR_CODE_PARSING_MANIFEST_UNSUPPORTED = 3004; // 0xbbc
|
||||
field public static final int ERROR_CODE_PERMISSION_DENIED = -4; // 0xfffffffc
|
||||
field public static final int ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99
|
||||
field public static final int ERROR_CODE_REMOTE_ERROR = 1001; // 0x3e9
|
||||
field public static final int ERROR_CODE_SETUP_REQUIRED = -108; // 0xffffff94
|
||||
field public static final int ERROR_CODE_SKIP_LIMIT_REACHED = -107; // 0xffffff95
|
||||
field public static final int ERROR_CODE_TIMEOUT = 1003; // 0x3eb
|
||||
field public static final int ERROR_CODE_UNSPECIFIED = 1000; // 0x3e8
|
||||
field @androidx.media3.common.PlaybackException.ErrorCode public final int errorCode;
|
||||
field public final long timestampMs;
|
||||
}
|
||||
|
||||
@IntDef(open=true, value={androidx.media3.common.PlaybackException.ERROR_CODE_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 {
|
||||
|
|
@ -781,7 +763,7 @@ package androidx.media3.common {
|
|||
method @Deprecated public void setDeviceMuted(boolean);
|
||||
method public void setDeviceMuted(boolean, @androidx.media3.common.C.VolumeFlags int);
|
||||
method @Deprecated public void setDeviceVolume(@IntRange(from=0) int);
|
||||
method public void setDeviceVolume(@IntRange(from=0) int, @androidx.media3.common.C.VolumeFlags int);
|
||||
method public void setDeviceVolume(@IntRange(from=0) int, int);
|
||||
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, 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_SEEK = 1; // 0x1
|
||||
field public static final int DISCONTINUITY_REASON_SEEK_ADJUSTMENT = 2; // 0x2
|
||||
field public static final int DISCONTINUITY_REASON_SILENCE_SKIP = 6; // 0x6
|
||||
field public static final int DISCONTINUITY_REASON_SKIP = 3; // 0x3
|
||||
field public static final int EVENT_AUDIO_ATTRIBUTES_CHANGED = 20; // 0x14
|
||||
field public static final int EVENT_AUDIO_SESSION_ID = 21; // 0x15
|
||||
|
|
@ -913,7 +894,7 @@ package androidx.media3.common {
|
|||
field public static final androidx.media3.common.Player.Commands EMPTY;
|
||||
}
|
||||
|
||||
@IntDef({androidx.media3.common.Player.DISCONTINUITY_REASON_AUTO_TRANSITION, androidx.media3.common.Player.DISCONTINUITY_REASON_SEEK, androidx.media3.common.Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT, androidx.media3.common.Player.DISCONTINUITY_REASON_SKIP, androidx.media3.common.Player.DISCONTINUITY_REASON_REMOVE, androidx.media3.common.Player.DISCONTINUITY_REASON_INTERNAL, 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 {
|
||||
|
|
@ -1094,7 +1075,7 @@ package androidx.media3.common {
|
|||
method public androidx.media3.common.TrackSelectionParameters.Builder buildUpon();
|
||||
method public static androidx.media3.common.TrackSelectionParameters fromBundle(android.os.Bundle);
|
||||
method public static androidx.media3.common.TrackSelectionParameters getDefaults(android.content.Context);
|
||||
method @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 boolean forceHighestSupportedBitrate;
|
||||
field public final boolean forceLowestBitrate;
|
||||
|
|
@ -1192,7 +1173,7 @@ package androidx.media3.common {
|
|||
field public static final androidx.media3.common.VideoSize UNKNOWN;
|
||||
field @IntRange(from=0) public final int height;
|
||||
field @FloatRange(from=0, fromInclusive=false) public final float pixelWidthHeightRatio;
|
||||
field @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;
|
||||
}
|
||||
|
||||
|
|
@ -1388,7 +1369,7 @@ package androidx.media3.exoplayer.analytics {
|
|||
|
||||
package androidx.media3.exoplayer.drm {
|
||||
|
||||
public final class FrameworkMediaDrm {
|
||||
@RequiresApi(18) public final class FrameworkMediaDrm {
|
||||
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();
|
||||
}
|
||||
|
||||
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 {
|
||||
|
|
@ -1438,7 +1396,6 @@ package androidx.media3.exoplayer.source {
|
|||
method public androidx.media3.exoplayer.source.DefaultMediaSourceFactory clearLocalAdInsertionComponents();
|
||||
method public androidx.media3.exoplayer.source.DefaultMediaSourceFactory setDataSourceFactory(androidx.media3.datasource.DataSource.Factory);
|
||||
method public androidx.media3.exoplayer.source.DefaultMediaSourceFactory setLocalAdInsertionComponents(androidx.media3.exoplayer.source.ads.AdsLoader.Provider, androidx.media3.common.AdViewProvider);
|
||||
method public androidx.media3.exoplayer.source.DefaultMediaSourceFactory setServerSideAdInsertionMediaSourceFactory(@Nullable androidx.media3.exoplayer.source.MediaSource.Factory);
|
||||
}
|
||||
|
||||
public interface MediaSource {
|
||||
|
|
@ -1527,7 +1484,7 @@ package androidx.media3.session {
|
|||
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 {
|
||||
|
|
@ -1803,7 +1760,6 @@ package androidx.media3.session {
|
|||
method public int getControllerVersion();
|
||||
method public String getPackageName();
|
||||
method public int getUid();
|
||||
field public static final String LEGACY_CONTROLLER_PACKAGE_NAME = "android.media.session.MediaController";
|
||||
field public static final int LEGACY_CONTROLLER_VERSION = 0; // 0x0
|
||||
}
|
||||
|
||||
|
|
@ -1852,7 +1808,6 @@ package androidx.media3.session {
|
|||
ctor public SessionCommands.Builder();
|
||||
method public androidx.media3.session.SessionCommands.Builder add(androidx.media3.session.SessionCommand);
|
||||
method public androidx.media3.session.SessionCommands.Builder add(@androidx.media3.session.SessionCommand.CommandCode int);
|
||||
method public androidx.media3.session.SessionCommands.Builder addSessionCommands(java.util.Collection<androidx.media3.session.SessionCommand>);
|
||||
method public androidx.media3.session.SessionCommands build();
|
||||
method public androidx.media3.session.SessionCommands.Builder remove(androidx.media3.session.SessionCommand);
|
||||
method public androidx.media3.session.SessionCommands.Builder remove(@androidx.media3.session.SessionCommand.CommandCode int);
|
||||
|
|
@ -1882,7 +1837,7 @@ package androidx.media3.session {
|
|||
field @androidx.media3.session.SessionResult.Code public final int resultCode;
|
||||
}
|
||||
|
||||
@IntDef({androidx.media3.session.SessionResult.RESULT_SUCCESS, androidx.media3.session.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 {
|
||||
|
|
|
|||
|
|
@ -17,9 +17,9 @@ buildscript {
|
|||
mavenCentral()
|
||||
}
|
||||
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 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.10'
|
||||
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.20'
|
||||
}
|
||||
}
|
||||
allprojects {
|
||||
|
|
|
|||
|
|
@ -14,8 +14,6 @@
|
|||
apply from: "$gradle.ext.androidxMediaSettingsDir/constants.gradle"
|
||||
apply plugin: 'com.android.library'
|
||||
|
||||
group = 'androidx.media3'
|
||||
|
||||
android {
|
||||
compileSdkVersion project.ext.compileSdkVersion
|
||||
|
||||
|
|
@ -27,9 +25,11 @@ android {
|
|||
aarMetadata {
|
||||
minCompileSdk = project.ext.compileSdkVersion
|
||||
}
|
||||
multiDexEnabled true
|
||||
}
|
||||
|
||||
compileOptions {
|
||||
coreLibraryDesugaringEnabled true
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
targetCompatibility JavaVersion.VERSION_1_8
|
||||
}
|
||||
|
|
@ -41,3 +41,8 @@ android {
|
|||
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
|
||||
// limitations under the License.
|
||||
project.ext {
|
||||
releaseVersion = '1.5.1'
|
||||
releaseVersionCode = 1_005_001_3_00
|
||||
minSdkVersion = 21
|
||||
releaseVersion = '1.2.1'
|
||||
releaseVersionCode = 1_002_001_3_00
|
||||
minSdkVersion = 16
|
||||
// See https://developer.android.com/training/cars/media/automotive-os#automotive-module
|
||||
automotiveMinSdkVersion = 28
|
||||
appTargetSdkVersion = 34
|
||||
// Upgrading this requires [Internal ref: b/193254928] to be fixed, or some
|
||||
// additional robolectric config.
|
||||
targetSdkVersion = 30
|
||||
compileSdkVersion = 35
|
||||
compileSdkVersion = 34
|
||||
dexmakerVersion = '2.28.3'
|
||||
// Use the same JUnit version as the Android repo:
|
||||
// https://cs.android.com/android/platform/superproject/main/+/main:external/junit/METADATA
|
||||
junitVersion = '4.13.2'
|
||||
// Use the same Guava version as the Android repo:
|
||||
// https://cs.android.com/android/platform/superproject/main/+/main:external/guava/METADATA
|
||||
guavaVersion = '33.3.1-android'
|
||||
glideVersion = '4.14.2'
|
||||
kotlinxCoroutinesVersion = '1.8.1'
|
||||
leakCanaryVersion = '2.10'
|
||||
guavaVersion = '31.1-android'
|
||||
mockitoVersion = '3.12.4'
|
||||
robolectricVersion = '4.11'
|
||||
robolectricVersion = '4.10.3'
|
||||
// Keep this in sync with Google's internal Checker Framework version.
|
||||
checkerframeworkVersion = '3.13.0'
|
||||
errorProneVersion = '2.18.0'
|
||||
jsr305Version = '3.0.2'
|
||||
kotlinAnnotationsVersion = '1.9.0'
|
||||
androidxAnnotationVersion = '1.6.0'
|
||||
kotlinAnnotationsVersion = '1.8.20'
|
||||
// Updating this to 1.4.0+ will import Kotlin stdlib [internal ref: b/277891049].
|
||||
androidxAnnotationVersion = '1.3.0'
|
||||
androidxAnnotationExperimentalVersion = '1.3.1'
|
||||
androidxAppCompatVersion = '1.6.1'
|
||||
androidxCollectionVersion = '1.2.0'
|
||||
androidxConstraintLayoutVersion = '2.1.4'
|
||||
// Updating this to 1.9.0+ will import Kotlin stdlib [internal ref: b/277891049].
|
||||
androidxCoreVersion = '1.8.0'
|
||||
androidxExifInterfaceVersion = '1.3.6'
|
||||
androidxLifecycleVersion = '2.6.0'
|
||||
androidxMediaVersion = '1.7.0'
|
||||
androidxFuturesVersion = '1.1.0'
|
||||
androidxMediaVersion = '1.6.0'
|
||||
androidxMedia2Version = '1.2.1'
|
||||
androidxMultidexVersion = '2.0.1'
|
||||
androidxRecyclerViewVersion = '1.3.0'
|
||||
androidxMaterialVersion = '1.8.0'
|
||||
androidxTestCoreVersion = '1.5.0'
|
||||
|
|
@ -55,8 +54,9 @@ project.ext {
|
|||
androidxTestJUnitVersion = '1.1.5'
|
||||
androidxTestRunnerVersion = '1.5.2'
|
||||
androidxTestRulesVersion = '1.5.0'
|
||||
androidxTestServicesStorageVersion = '1.4.2'
|
||||
androidxTestTruthVersion = '1.5.0'
|
||||
truthVersion = '1.4.0'
|
||||
truthVersion = '1.1.3'
|
||||
okhttpVersion = '4.12.0'
|
||||
modulePrefix = ':'
|
||||
if (gradle.ext.has('androidxMediaModulePrefix')) {
|
||||
|
|
|
|||
|
|
@ -24,9 +24,6 @@ if (gradle.ext.has('androidxMediaModulePrefix')) {
|
|||
include modulePrefix + 'lib-common'
|
||||
project(modulePrefix + 'lib-common').projectDir = new File(rootDir, 'libraries/common')
|
||||
|
||||
include modulePrefix + 'lib-common-ktx'
|
||||
project(modulePrefix + 'lib-common-ktx').projectDir = new File(rootDir, 'libraries/common_ktx')
|
||||
|
||||
include modulePrefix + 'lib-container'
|
||||
project(modulePrefix + 'lib-container').projectDir = new File(rootDir, 'libraries/container')
|
||||
|
||||
|
|
@ -60,8 +57,6 @@ include modulePrefix + 'lib-datasource'
|
|||
project(modulePrefix + 'lib-datasource').projectDir = new File(rootDir, 'libraries/datasource')
|
||||
include modulePrefix + 'lib-datasource-cronet'
|
||||
project(modulePrefix + 'lib-datasource-cronet').projectDir = new File(rootDir, 'libraries/datasource_cronet')
|
||||
include modulePrefix + 'lib-datasource-httpengine'
|
||||
project(modulePrefix + 'lib-datasource-httpengine').projectDir = new File(rootDir, 'libraries/datasource_httpengine')
|
||||
include modulePrefix + 'lib-datasource-rtmp'
|
||||
project(modulePrefix + 'lib-datasource-rtmp').projectDir = new File(rootDir, 'libraries/datasource_rtmp')
|
||||
include modulePrefix + 'lib-datasource-okhttp'
|
||||
|
|
@ -75,12 +70,8 @@ include modulePrefix + 'lib-decoder-ffmpeg'
|
|||
project(modulePrefix + 'lib-decoder-ffmpeg').projectDir = new File(rootDir, 'libraries/decoder_ffmpeg')
|
||||
include modulePrefix + 'lib-decoder-flac'
|
||||
project(modulePrefix + 'lib-decoder-flac').projectDir = new File(rootDir, 'libraries/decoder_flac')
|
||||
include modulePrefix + 'lib-decoder-iamf'
|
||||
project(modulePrefix + 'lib-decoder-iamf').projectDir = new File(rootDir, 'libraries/decoder_iamf')
|
||||
if (gradle.ext.has('androidxMediaEnableMidiModule') && gradle.ext.androidxMediaEnableMidiModule) {
|
||||
include modulePrefix + 'lib-decoder-midi'
|
||||
project(modulePrefix + 'lib-decoder-midi').projectDir = new File(rootDir, 'libraries/decoder_midi')
|
||||
}
|
||||
include modulePrefix + 'lib-decoder-midi'
|
||||
project(modulePrefix + 'lib-decoder-midi').projectDir = new File(rootDir, 'libraries/decoder_midi')
|
||||
include modulePrefix + 'lib-decoder-opus'
|
||||
project(modulePrefix + 'lib-decoder-opus').projectDir = new File(rootDir, 'libraries/decoder_opus')
|
||||
include modulePrefix + 'lib-decoder-vp9'
|
||||
|
|
@ -107,3 +98,7 @@ include modulePrefix + 'test-data'
|
|||
project(modulePrefix + 'test-data').projectDir = new File(rootDir, 'libraries/test_data')
|
||||
include modulePrefix + 'test-utils'
|
||||
project(modulePrefix + 'test-utils').projectDir = new File(rootDir, 'libraries/test_utils')
|
||||
include modulePrefix + 'test-session-common'
|
||||
project(modulePrefix + 'test-session-common').projectDir = new File(rootDir, 'libraries/test_session_common')
|
||||
include modulePrefix + 'test-session-current'
|
||||
project(modulePrefix + 'test-session-current').projectDir = new File(rootDir, 'libraries/test_session_current')
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ apply plugin: 'com.android.application'
|
|||
android {
|
||||
namespace 'androidx.media3.demo.cast'
|
||||
|
||||
compileSdk project.ext.compileSdkVersion
|
||||
compileSdkVersion project.ext.compileSdkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
|
|
@ -29,6 +29,7 @@ android {
|
|||
versionCode project.ext.releaseVersionCode
|
||||
minSdkVersion project.ext.minSdkVersion
|
||||
targetSdkVersion project.ext.appTargetSdkVersion
|
||||
multiDexEnabled true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
|
@ -61,6 +62,7 @@ dependencies {
|
|||
implementation project(modulePrefix + 'lib-ui')
|
||||
implementation project(modulePrefix + 'lib-cast')
|
||||
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
|
||||
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
|
||||
implementation 'androidx.recyclerview:recyclerview:' + androidxRecyclerViewVersion
|
||||
implementation 'com.google.android.material:material:' + androidxMaterialVersion
|
||||
}
|
||||
|
|
|
|||
|
|
@ -23,6 +23,7 @@
|
|||
<uses-sdk/>
|
||||
|
||||
<application
|
||||
android:name="androidx.multidex.MultiDexApplication"
|
||||
android:label="@string/application_name"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
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");
|
||||
* 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
|
||||
* limitations under the License.
|
||||
*/
|
||||
@NonNullApi
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
package androidx.media3.demo.composition;
|
||||
package androidx.media3.demo.cast;
|
||||
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.media3.common.util.NonNullApi;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.multidex.MultiDexApplication;
|
||||
|
||||
// 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.CastContext;
|
||||
import com.google.android.gms.dynamite.DynamiteModule;
|
||||
import com.google.common.util.concurrent.MoreExecutors;
|
||||
|
||||
/**
|
||||
* An activity that plays video using {@link ExoPlayer} and supports casting using ExoPlayer's Cast
|
||||
|
|
@ -66,7 +65,7 @@ public class MainActivity extends AppCompatActivity
|
|||
super.onCreate(savedInstanceState);
|
||||
// Getting the cast context later than onStart can cause device discovery not to take place.
|
||||
try {
|
||||
castContext = CastContext.getSharedInstance(this, MoreExecutors.directExecutor()).getResult();
|
||||
castContext = CastContext.getSharedInstance(this);
|
||||
} catch (RuntimeException e) {
|
||||
Throwable cause = e.getCause();
|
||||
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 {
|
||||
namespace 'androidx.media3.demo.gl'
|
||||
|
||||
compileSdk project.ext.compileSdkVersion
|
||||
compileSdkVersion project.ext.compileSdkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
|
|
@ -29,6 +29,7 @@ android {
|
|||
versionCode project.ext.releaseVersionCode
|
||||
minSdkVersion project.ext.minSdkVersion
|
||||
targetSdkVersion project.ext.appTargetSdkVersion
|
||||
multiDexEnabled true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
|
@ -54,5 +55,6 @@ dependencies {
|
|||
implementation project(modulePrefix + 'lib-exoplayer-smoothstreaming')
|
||||
implementation project(modulePrefix + 'lib-ui')
|
||||
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
|
||||
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
|
||||
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,6 +22,7 @@
|
|||
<uses-sdk/>
|
||||
|
||||
<application
|
||||
android:name="androidx.multidex.MultiDexApplication"
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/application_name">
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||
paint = new Paint();
|
||||
paint.setTextSize(64);
|
||||
paint.setAntiAlias(true);
|
||||
paint.setColor(Color.WHITE);
|
||||
paint.setARGB(0xFF, 0xFF, 0xFF, 0xFF);
|
||||
textures = new int[1];
|
||||
overlayBitmap = Bitmap.createBitmap(OVERLAY_WIDTH, OVERLAY_HEIGHT, Bitmap.Config.ARGB_8888);
|
||||
overlayCanvas = new Canvas(overlayBitmap);
|
||||
|
|
|
|||
|
|
@ -143,7 +143,7 @@ public final class MainActivity extends Activity {
|
|||
? Assertions.checkNotNull(intent.getData())
|
||||
: Uri.parse(DEFAULT_MEDIA_URI);
|
||||
DrmSessionManager drmSessionManager;
|
||||
if (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 drmLicenseUrl = Assertions.checkNotNull(intent.getStringExtra(DRM_LICENSE_URL_EXTRA));
|
||||
UUID drmSchemeUuid = Assertions.checkNotNull(Util.getDrmUuid(drmScheme));
|
||||
|
|
|
|||
|
|
@ -19,7 +19,7 @@ apply plugin: 'kotlin-android'
|
|||
android {
|
||||
namespace 'androidx.media3.demo.main'
|
||||
|
||||
compileSdk project.ext.compileSdkVersion
|
||||
compileSdkVersion project.ext.compileSdkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
|
|
@ -31,6 +31,7 @@ android {
|
|||
versionCode project.ext.releaseVersionCode
|
||||
minSdkVersion project.ext.minSdkVersion
|
||||
targetSdkVersion project.ext.appTargetSdkVersion
|
||||
multiDexEnabled true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
|
@ -54,9 +55,7 @@ android {
|
|||
disable 'GoogleAppIndexingWarning','MissingTranslation','IconDensities'
|
||||
}
|
||||
|
||||
flavorDimensions = ["decoderExtensions"]
|
||||
|
||||
buildFeatures.buildConfig true
|
||||
flavorDimensions "decoderExtensions"
|
||||
|
||||
productFlavors {
|
||||
noDecoderExtensions {
|
||||
|
|
@ -74,6 +73,7 @@ dependencies {
|
|||
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
|
||||
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
|
||||
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
|
||||
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
|
||||
implementation 'com.google.android.material:material:' + androidxMaterialVersion
|
||||
implementation project(modulePrefix + 'lib-exoplayer')
|
||||
implementation project(modulePrefix + 'lib-exoplayer-dash')
|
||||
|
|
@ -87,7 +87,6 @@ dependencies {
|
|||
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-ffmpeg')
|
||||
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-flac')
|
||||
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-opus')
|
||||
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-iamf')
|
||||
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-vp9')
|
||||
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-midi')
|
||||
withDecoderExtensionsImplementation project(modulePrefix + 'lib-datasource-rtmp')
|
||||
|
|
|
|||
|
|
@ -40,6 +40,7 @@
|
|||
android:largeHeap="true"
|
||||
android:allowBackup="false"
|
||||
android:supportsRtl="true"
|
||||
android:name="androidx.multidex.MultiDexApplication"
|
||||
tools:targetApi="29">
|
||||
|
||||
<activity android:name=".SampleChooserActivity"
|
||||
|
|
|
|||
|
|
@ -593,27 +593,6 @@
|
|||
"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": [
|
||||
{
|
||||
"name": "Dizzy (MP4)",
|
||||
|
|
@ -758,44 +737,6 @@
|
|||
{
|
||||
"name": "One hour frame counter (MP4)",
|
||||
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mp4/frame-counter-one-hour.mp4"
|
||||
},
|
||||
{
|
||||
"name": "Immersive Audio Format Sample (MP4, IAMF)",
|
||||
"uri": "https://github.com/AOMediaCodec/libiamf/raw/main/tests/test_000036_s.mp4"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"name": "Images",
|
||||
"samples": [
|
||||
{
|
||||
"name": "JPEG (wide)",
|
||||
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/jpg/london.jpg",
|
||||
"image_duration_ms": 2000
|
||||
},
|
||||
{
|
||||
"name": "JPEG (tall)",
|
||||
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/jpg/tokyo.jpg",
|
||||
"image_duration_ms": 2000
|
||||
},
|
||||
{
|
||||
"name": "PNG",
|
||||
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/png/media3test.png",
|
||||
"image_duration_ms": 2000
|
||||
},
|
||||
{
|
||||
"name": "JPEG motion photo (still)",
|
||||
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/jpg/london_motion_photo.jpg",
|
||||
"image_duration_ms": 2000
|
||||
},
|
||||
{
|
||||
"name": "JPEG motion photo (motion)",
|
||||
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/jpg/london_motion_photo.jpg"
|
||||
},
|
||||
{
|
||||
"name": "JPEG (Ultra HDR)",
|
||||
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/jpg/ultra_hdr.jpg",
|
||||
"image_duration_ms": 2000
|
||||
}
|
||||
]
|
||||
}
|
||||
|
|
|
|||
|
|
@ -63,7 +63,7 @@ public class DemoDownloadService extends DownloadService {
|
|||
|
||||
@Override
|
||||
protected Scheduler getScheduler() {
|
||||
return new PlatformScheduler(this, JOB_ID);
|
||||
return Util.SDK_INT >= 21 ? new PlatformScheduler(this, JOB_ID) : null;
|
||||
}
|
||||
|
||||
@Override
|
||||
|
|
|
|||
|
|
@ -16,16 +16,12 @@
|
|||
package androidx.media3.demo.main;
|
||||
|
||||
import android.content.Context;
|
||||
import android.net.http.HttpEngine;
|
||||
import android.os.Build;
|
||||
import android.os.ext.SdkExtensions;
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.media3.database.DatabaseProvider;
|
||||
import androidx.media3.database.StandaloneDatabaseProvider;
|
||||
import androidx.media3.datasource.DataSource;
|
||||
import androidx.media3.datasource.DefaultDataSource;
|
||||
import androidx.media3.datasource.DefaultHttpDataSource;
|
||||
import androidx.media3.datasource.HttpEngineDataSource;
|
||||
import androidx.media3.datasource.cache.Cache;
|
||||
import androidx.media3.datasource.cache.CacheDataSource;
|
||||
import androidx.media3.datasource.cache.NoOpCacheEvictor;
|
||||
|
|
@ -50,26 +46,25 @@ public final class DemoUtil {
|
|||
|
||||
public static final String DOWNLOAD_NOTIFICATION_CHANNEL_ID = "download_channel";
|
||||
|
||||
/**
|
||||
* Whether the demo application uses Cronet for networking. Note that Cronet does not provide
|
||||
* automatic support for cookies (https://github.com/google/ExoPlayer/issues/5975).
|
||||
*
|
||||
* <p>If set to false, the platform's default network stack is used with a {@link CookieManager}
|
||||
* configured in {@link #getHttpDataSourceFactory}.
|
||||
*/
|
||||
private static final boolean USE_CRONET_FOR_NETWORKING = true;
|
||||
|
||||
private static final String TAG = "DemoUtil";
|
||||
private static final String DOWNLOAD_CONTENT_DIRECTORY = "downloads";
|
||||
|
||||
private static DataSource.@MonotonicNonNull Factory dataSourceFactory;
|
||||
private static DataSource.@MonotonicNonNull Factory httpDataSourceFactory;
|
||||
|
||||
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||
private static @MonotonicNonNull DatabaseProvider databaseProvider;
|
||||
|
||||
private static @MonotonicNonNull File downloadDirectory;
|
||||
|
||||
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||
private static @MonotonicNonNull Cache downloadCache;
|
||||
|
||||
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||
private static @MonotonicNonNull DownloadManager downloadManager;
|
||||
|
||||
private static @MonotonicNonNull DownloadTracker downloadTracker;
|
||||
|
||||
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||
private static @MonotonicNonNull DownloadNotificationHelper downloadNotificationHelper;
|
||||
|
||||
/** Returns whether extension renderers should be used. */
|
||||
|
|
@ -91,30 +86,24 @@ public final class DemoUtil {
|
|||
.setExtensionRendererMode(extensionRendererMode);
|
||||
}
|
||||
|
||||
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||
public static synchronized DataSource.Factory getHttpDataSourceFactory(Context context) {
|
||||
if (httpDataSourceFactory != null) {
|
||||
return httpDataSourceFactory;
|
||||
if (httpDataSourceFactory == null) {
|
||||
if (USE_CRONET_FOR_NETWORKING) {
|
||||
context = context.getApplicationContext();
|
||||
@Nullable CronetEngine cronetEngine = CronetUtil.buildCronetEngine(context);
|
||||
if (cronetEngine != null) {
|
||||
httpDataSourceFactory =
|
||||
new CronetDataSource.Factory(cronetEngine, Executors.newSingleThreadExecutor());
|
||||
}
|
||||
}
|
||||
if (httpDataSourceFactory == null) {
|
||||
// We don't want to use Cronet, or we failed to instantiate a CronetEngine.
|
||||
CookieManager cookieManager = new CookieManager();
|
||||
cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER);
|
||||
CookieHandler.setDefault(cookieManager);
|
||||
httpDataSourceFactory = new DefaultHttpDataSource.Factory();
|
||||
}
|
||||
}
|
||||
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;
|
||||
}
|
||||
|
||||
|
|
@ -139,7 +128,6 @@ public final class DemoUtil {
|
|||
return downloadNotificationHelper;
|
||||
}
|
||||
|
||||
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||
public static synchronized DownloadManager getDownloadManager(Context context) {
|
||||
ensureDownloadManagerInitialized(context);
|
||||
return downloadManager;
|
||||
|
|
|
|||
|
|
@ -16,16 +16,15 @@
|
|||
package androidx.media3.demo.main;
|
||||
|
||||
import static com.google.common.base.Preconditions.checkNotNull;
|
||||
import static com.google.common.base.Preconditions.checkState;
|
||||
|
||||
import android.content.Context;
|
||||
import android.content.DialogInterface;
|
||||
import android.net.Uri;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.os.AsyncTask;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.fragment.app.FragmentManager;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.DrmInitData;
|
||||
|
|
@ -55,9 +54,6 @@ import java.io.IOException;
|
|||
import java.util.HashMap;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.CopyOnWriteArraySet;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
import java.util.concurrent.Future;
|
||||
|
||||
/** Tracks media that has been downloaded. */
|
||||
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||
|
|
@ -186,7 +182,7 @@ public class DownloadTracker {
|
|||
trackSelectionDialog.dismiss();
|
||||
}
|
||||
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.
|
||||
|
||||
if (Util.SDK_INT < 18) {
|
||||
Toast.makeText(context, R.string.error_drm_unsupported_before_api_18, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
Log.e(TAG, "Downloading DRM protected content is not supported on API versions below 18");
|
||||
return;
|
||||
}
|
||||
// TODO(internal b/163107948): Support cases where DrmInitData are not in the manifest.
|
||||
if (!hasNonNullWidevineSchemaData(format.drmInitData)) {
|
||||
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. */
|
||||
private static final class WidevineOfflineLicenseFetchTask {
|
||||
@RequiresApi(18)
|
||||
private static final class WidevineOfflineLicenseFetchTask extends AsyncTask<Void, Void, Void> {
|
||||
|
||||
private final Format format;
|
||||
private final MediaItem.DrmConfiguration drmConfiguration;
|
||||
private final DataSource.Factory dataSourceFactory;
|
||||
private final StartDownloadDialogHelper dialogHelper;
|
||||
private final DownloadHelper downloadHelper;
|
||||
private final ExecutorService executorService;
|
||||
|
||||
@Nullable Future<?> future;
|
||||
@Nullable private byte[] keySetId;
|
||||
@Nullable private DrmSession.DrmSessionException drmSessionException;
|
||||
|
||||
|
|
@ -375,8 +375,6 @@ public class DownloadTracker {
|
|||
DataSource.Factory dataSourceFactory,
|
||||
StartDownloadDialogHelper dialogHelper,
|
||||
DownloadHelper downloadHelper) {
|
||||
checkState(drmConfiguration.scheme.equals(C.WIDEVINE_UUID));
|
||||
this.executorService = Executors.newSingleThreadExecutor();
|
||||
this.format = format;
|
||||
this.drmConfiguration = drmConfiguration;
|
||||
this.dataSourceFactory = dataSourceFactory;
|
||||
|
|
@ -384,41 +382,32 @@ public class DownloadTracker {
|
|||
this.downloadHelper = downloadHelper;
|
||||
}
|
||||
|
||||
public void cancel() {
|
||||
if (future != null) {
|
||||
future.cancel(/* mayInterruptIfRunning= */ false);
|
||||
@Override
|
||||
protected Void doInBackground(Void... voids) {
|
||||
OfflineLicenseHelper offlineLicenseHelper =
|
||||
OfflineLicenseHelper.newWidevineInstance(
|
||||
drmConfiguration.licenseUri.toString(),
|
||||
drmConfiguration.forceDefaultLicenseUri,
|
||||
dataSourceFactory,
|
||||
drmConfiguration.licenseRequestHeaders,
|
||||
new DrmSessionEventListener.EventDispatcher());
|
||||
try {
|
||||
keySetId = offlineLicenseHelper.downloadLicense(format);
|
||||
} catch (DrmSession.DrmSessionException e) {
|
||||
drmSessionException = e;
|
||||
} finally {
|
||||
offlineLicenseHelper.release();
|
||||
}
|
||||
return null;
|
||||
}
|
||||
|
||||
public void execute() {
|
||||
future =
|
||||
executorService.submit(
|
||||
() -> {
|
||||
OfflineLicenseHelper offlineLicenseHelper =
|
||||
OfflineLicenseHelper.newWidevineInstance(
|
||||
drmConfiguration.licenseUri.toString(),
|
||||
drmConfiguration.forceDefaultLicenseUri,
|
||||
dataSourceFactory,
|
||||
drmConfiguration.licenseRequestHeaders,
|
||||
new DrmSessionEventListener.EventDispatcher());
|
||||
try {
|
||||
keySetId = offlineLicenseHelper.downloadLicense(format);
|
||||
} catch (DrmSession.DrmSessionException e) {
|
||||
drmSessionException = e;
|
||||
} finally {
|
||||
offlineLicenseHelper.release();
|
||||
new Handler(Looper.getMainLooper())
|
||||
.post(
|
||||
() -> {
|
||||
if (drmSessionException != null) {
|
||||
dialogHelper.onOfflineLicenseFetchedError(drmSessionException);
|
||||
} else {
|
||||
dialogHelper.onOfflineLicenseFetched(
|
||||
downloadHelper, checkNotNull(keySetId));
|
||||
}
|
||||
});
|
||||
}
|
||||
});
|
||||
@Override
|
||||
protected void onPostExecute(Void aVoid) {
|
||||
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.net.Uri;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.MediaItem.ClippingConfiguration;
|
||||
import androidx.media3.common.MediaItem.SubtitleConfiguration;
|
||||
import androidx.media3.common.MediaMetadata;
|
||||
import androidx.media3.common.Player;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Util;
|
||||
import com.google.common.collect.ImmutableList;
|
||||
import java.util.ArrayList;
|
||||
|
|
@ -56,7 +53,6 @@ public class IntentUtil {
|
|||
public static final String MIME_TYPE_EXTRA = "mime_type";
|
||||
public static final String CLIP_START_POSITION_MS_EXTRA = "clip_start_position_ms";
|
||||
public static final String CLIP_END_POSITION_MS_EXTRA = "clip_end_position_ms";
|
||||
public static final String IMAGE_DURATION_MS = "image_duration_ms";
|
||||
|
||||
public static final String AD_TAG_URI_EXTRA = "ad_tag_uri";
|
||||
|
||||
|
|
@ -70,21 +66,6 @@ public class IntentUtil {
|
|||
public static final String SUBTITLE_URI_EXTRA = "subtitle_uri";
|
||||
public static final String SUBTITLE_MIME_TYPE_EXTRA = "subtitle_mime_type";
|
||||
public static final String SUBTITLE_LANGUAGE_EXTRA = "subtitle_language";
|
||||
public static final String REPEAT_MODE_EXTRA = "repeat_mode";
|
||||
|
||||
public static @Player.RepeatMode int parseRepeatModeExtra(String repeatMode) {
|
||||
switch (repeatMode) {
|
||||
case "OFF":
|
||||
return Player.REPEAT_MODE_OFF;
|
||||
case "ONE":
|
||||
return Player.REPEAT_MODE_ONE;
|
||||
case "ALL":
|
||||
return Player.REPEAT_MODE_ALL;
|
||||
default:
|
||||
throw new IllegalArgumentException(
|
||||
"Argument " + repeatMode + " does not match any of the repeat modes: OFF|ONE|ALL");
|
||||
}
|
||||
}
|
||||
|
||||
/** Creates a list of {@link MediaItem media items} from an {@link Intent}. */
|
||||
public static List<MediaItem> createMediaItemsFromIntent(Intent intent) {
|
||||
|
|
@ -133,7 +114,6 @@ public class IntentUtil {
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class) // Setting image duration.
|
||||
private static MediaItem createMediaItemFromIntent(
|
||||
Uri uri, Intent intent, String extrasKeySuffix) {
|
||||
@Nullable String mimeType = intent.getStringExtra(MIME_TYPE_EXTRA + extrasKeySuffix);
|
||||
|
|
@ -142,7 +122,6 @@ public class IntentUtil {
|
|||
@Nullable
|
||||
SubtitleConfiguration subtitleConfiguration =
|
||||
createSubtitleConfiguration(intent, extrasKeySuffix);
|
||||
long imageDurationMs = intent.getLongExtra(IMAGE_DURATION_MS + extrasKeySuffix, C.TIME_UNSET);
|
||||
MediaItem.Builder builder =
|
||||
new MediaItem.Builder()
|
||||
.setUri(uri)
|
||||
|
|
@ -155,8 +134,7 @@ public class IntentUtil {
|
|||
.setEndPositionMs(
|
||||
intent.getLongExtra(
|
||||
CLIP_END_POSITION_MS_EXTRA + extrasKeySuffix, C.TIME_END_OF_SOURCE))
|
||||
.build())
|
||||
.setImageDurationMs(imageDurationMs);
|
||||
.build());
|
||||
if (adTagUri != null) {
|
||||
builder.setAdsConfiguration(
|
||||
new MediaItem.AdsConfiguration.Builder(Uri.parse(adTagUri)).build());
|
||||
|
|
@ -217,7 +195,6 @@ public class IntentUtil {
|
|||
return builder;
|
||||
}
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class) // Accessing image duration.
|
||||
private static void addLocalConfigurationToIntent(
|
||||
MediaItem.LocalConfiguration localConfiguration, Intent intent, String extrasKeySuffix) {
|
||||
intent
|
||||
|
|
@ -238,9 +215,6 @@ public class IntentUtil {
|
|||
intent.putExtra(SUBTITLE_MIME_TYPE_EXTRA + extrasKeySuffix, subtitleConfiguration.mimeType);
|
||||
intent.putExtra(SUBTITLE_LANGUAGE_EXTRA + extrasKeySuffix, subtitleConfiguration.language);
|
||||
}
|
||||
if (localConfiguration.imageDurationMs != C.TIME_UNSET) {
|
||||
intent.putExtra(IMAGE_DURATION_MS + extrasKeySuffix, localConfiguration.imageDurationMs);
|
||||
}
|
||||
}
|
||||
|
||||
private static void addDrmConfigurationToIntent(
|
||||
|
|
|
|||
|
|
@ -93,8 +93,11 @@ public class PlayerActivity extends AppCompatActivity
|
|||
|
||||
@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
|
||||
serverSideAdsLoaderState;
|
||||
|
||||
|
|
@ -259,8 +262,8 @@ public class PlayerActivity extends AppCompatActivity
|
|||
* @return Whether initialization was successful.
|
||||
*/
|
||||
protected boolean initializePlayer() {
|
||||
Intent intent = getIntent();
|
||||
if (player == null) {
|
||||
Intent intent = getIntent();
|
||||
|
||||
mediaItems = createMediaItems(intent);
|
||||
if (mediaItems.isEmpty()) {
|
||||
|
|
@ -290,15 +293,11 @@ public class PlayerActivity extends AppCompatActivity
|
|||
}
|
||||
player.setMediaItems(mediaItems, /* resetPosition= */ !haveStartPosition);
|
||||
player.prepare();
|
||||
String repeatModeExtra = intent.getStringExtra(IntentUtil.REPEAT_MODE_EXTRA);
|
||||
if (repeatModeExtra != null) {
|
||||
player.setRepeatMode(IntentUtil.parseRepeatModeExtra(repeatModeExtra));
|
||||
}
|
||||
updateButtonVisibility();
|
||||
return true;
|
||||
}
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class) // DRM configuration
|
||||
@OptIn(markerClass = UnstableApi.class) // SSAI configuration
|
||||
private MediaSource.Factory createMediaSourceFactory() {
|
||||
DefaultDrmSessionManagerProvider drmSessionManagerProvider =
|
||||
new DefaultDrmSessionManagerProvider();
|
||||
|
|
@ -331,6 +330,7 @@ public class PlayerActivity extends AppCompatActivity
|
|||
playerBuilder.setRenderersFactory(renderersFactory);
|
||||
}
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
private void configurePlayerWithServerSideAdsLoader() {
|
||||
serverSideAdsLoader.setPlayer(player);
|
||||
}
|
||||
|
|
@ -361,7 +361,11 @@ public class PlayerActivity extends AppCompatActivity
|
|||
|
||||
MediaItem.DrmConfiguration drmConfiguration = mediaItem.localConfiguration.drmConfiguration;
|
||||
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);
|
||||
finish();
|
||||
return Collections.emptyList();
|
||||
|
|
@ -399,6 +403,7 @@ public class PlayerActivity extends AppCompatActivity
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
private void releaseServerSideAdsLoader() {
|
||||
serverSideAdsLoaderState = serverSideAdsLoader.release();
|
||||
serverSideAdsLoader = null;
|
||||
|
|
@ -412,17 +417,20 @@ public class PlayerActivity extends AppCompatActivity
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
private void saveServerSideAdsLoaderState(Bundle outState) {
|
||||
if (serverSideAdsLoaderState != null) {
|
||||
outState.putBundle(KEY_SERVER_SIDE_ADS_LOADER_STATE, serverSideAdsLoaderState.toBundle());
|
||||
}
|
||||
}
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class)
|
||||
private void restoreServerSideAdsLoaderState(Bundle savedInstanceState) {
|
||||
Bundle adsLoaderStateBundle = savedInstanceState.getBundle(KEY_SERVER_SIDE_ADS_LOADER_STATE);
|
||||
if (adsLoaderStateBundle != null) {
|
||||
serverSideAdsLoaderState =
|
||||
ImaServerSideAdInsertionMediaSource.AdsLoader.State.fromBundle(adsLoaderStateBundle);
|
||||
ImaServerSideAdInsertionMediaSource.AdsLoader.State.CREATOR.fromBundle(
|
||||
adsLoaderStateBundle);
|
||||
}
|
||||
}
|
||||
|
||||
|
|
@ -506,7 +514,7 @@ public class PlayerActivity extends AppCompatActivity
|
|||
|
||||
private class PlayerErrorMessageProvider implements ErrorMessageProvider<PlaybackException> {
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class) // Using decoder exceptions
|
||||
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||
@Override
|
||||
public Pair<Integer, String> getErrorMessage(PlaybackException e) {
|
||||
String errorString = getString(R.string.error_generic);
|
||||
|
|
@ -547,7 +555,7 @@ public class PlayerActivity extends AppCompatActivity
|
|||
return mediaItems;
|
||||
}
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class) // Using Download API
|
||||
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||
private static MediaItem maybeSetDownloadProperties(
|
||||
MediaItem item, @Nullable DownloadRequest downloadRequest) {
|
||||
if (downloadRequest == null) {
|
||||
|
|
|
|||
|
|
@ -26,13 +26,11 @@ import android.content.SharedPreferences;
|
|||
import android.content.pm.PackageManager;
|
||||
import android.content.res.AssetManager;
|
||||
import android.net.Uri;
|
||||
import android.os.AsyncTask;
|
||||
import android.os.Build;
|
||||
import android.os.Bundle;
|
||||
import android.os.Handler;
|
||||
import android.os.Looper;
|
||||
import android.text.TextUtils;
|
||||
import android.util.JsonReader;
|
||||
import android.util.Log;
|
||||
import android.view.Menu;
|
||||
import android.view.MenuInflater;
|
||||
import android.view.MenuItem;
|
||||
|
|
@ -45,15 +43,15 @@ import android.widget.ExpandableListView.OnChildClickListener;
|
|||
import android.widget.ImageButton;
|
||||
import android.widget.TextView;
|
||||
import android.widget.Toast;
|
||||
import androidx.annotation.DoNotInline;
|
||||
import androidx.annotation.Nullable;
|
||||
import androidx.annotation.OptIn;
|
||||
import androidx.annotation.RequiresApi;
|
||||
import androidx.appcompat.app.AppCompatActivity;
|
||||
import androidx.media3.common.C;
|
||||
import androidx.media3.common.MediaItem;
|
||||
import androidx.media3.common.MediaItem.ClippingConfiguration;
|
||||
import androidx.media3.common.MediaMetadata;
|
||||
import androidx.media3.common.util.UnstableApi;
|
||||
import androidx.media3.common.util.Log;
|
||||
import androidx.media3.common.util.Util;
|
||||
import androidx.media3.datasource.DataSource;
|
||||
import androidx.media3.datasource.DataSourceInputStream;
|
||||
|
|
@ -67,7 +65,6 @@ import com.google.common.collect.ImmutableMap;
|
|||
import java.io.IOException;
|
||||
import java.io.InputStream;
|
||||
import java.io.InputStreamReader;
|
||||
import java.nio.charset.StandardCharsets;
|
||||
import java.util.ArrayList;
|
||||
import java.util.Arrays;
|
||||
import java.util.Collections;
|
||||
|
|
@ -75,8 +72,6 @@ import java.util.HashMap;
|
|||
import java.util.List;
|
||||
import java.util.Map;
|
||||
import java.util.UUID;
|
||||
import java.util.concurrent.ExecutorService;
|
||||
import java.util.concurrent.Executors;
|
||||
|
||||
/** An activity for selecting from a list of media samples. */
|
||||
public class SampleChooserActivity extends AppCompatActivity
|
||||
|
|
@ -119,7 +114,6 @@ public class SampleChooserActivity extends AppCompatActivity
|
|||
}
|
||||
}
|
||||
} catch (IOException e) {
|
||||
Log.e(TAG, "One or more sample lists failed to load", e);
|
||||
Toast.makeText(getApplicationContext(), R.string.sample_list_load_error, Toast.LENGTH_LONG)
|
||||
.show();
|
||||
}
|
||||
|
|
@ -260,7 +254,6 @@ public class SampleChooserActivity extends AppCompatActivity
|
|||
}
|
||||
}
|
||||
|
||||
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||
private void toggleDownload(MediaItem mediaItem) {
|
||||
RenderersFactory renderersFactory =
|
||||
DemoUtil.buildRenderersFactory(
|
||||
|
|
@ -277,10 +270,6 @@ public class SampleChooserActivity extends AppCompatActivity
|
|||
if (localConfiguration.adsConfiguration != null) {
|
||||
return R.string.download_ads_unsupported;
|
||||
}
|
||||
@Nullable MediaItem.DrmConfiguration drmConfiguration = localConfiguration.drmConfiguration;
|
||||
if (drmConfiguration != null && !drmConfiguration.scheme.equals(C.WIDEVINE_UUID)) {
|
||||
return R.string.download_only_widevine_drm_supported;
|
||||
}
|
||||
String scheme = localConfiguration.uri.getScheme();
|
||||
if (!("http".equals(scheme) || "https".equals(scheme))) {
|
||||
return R.string.download_scheme_unsupported;
|
||||
|
|
@ -293,43 +282,34 @@ public class SampleChooserActivity extends AppCompatActivity
|
|||
return menuItem != null && menuItem.isChecked();
|
||||
}
|
||||
|
||||
private final class SampleListLoader {
|
||||
|
||||
private final ExecutorService executorService;
|
||||
private final class SampleListLoader extends AsyncTask<String, Void, List<PlaylistGroup>> {
|
||||
|
||||
private boolean sawError;
|
||||
|
||||
public SampleListLoader() {
|
||||
executorService = Executors.newSingleThreadExecutor();
|
||||
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||
@Override
|
||||
protected List<PlaylistGroup> doInBackground(String... uris) {
|
||||
List<PlaylistGroup> result = new ArrayList<>();
|
||||
Context context = getApplicationContext();
|
||||
DataSource dataSource = DemoUtil.getDataSourceFactory(context).createDataSource();
|
||||
for (String uri : uris) {
|
||||
DataSpec dataSpec = new DataSpec(Uri.parse(uri));
|
||||
InputStream inputStream = new DataSourceInputStream(dataSource, dataSpec);
|
||||
try {
|
||||
readPlaylistGroups(new JsonReader(new InputStreamReader(inputStream, "UTF-8")), result);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error loading sample list: " + uri, e);
|
||||
sawError = true;
|
||||
} finally {
|
||||
DataSourceUtil.closeQuietly(dataSource);
|
||||
}
|
||||
}
|
||||
return result;
|
||||
}
|
||||
|
||||
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||
public void execute(String... uris) {
|
||||
executorService.execute(
|
||||
() -> {
|
||||
List<PlaylistGroup> result = new ArrayList<>();
|
||||
Context context = getApplicationContext();
|
||||
DataSource dataSource = DemoUtil.getDataSourceFactory(context).createDataSource();
|
||||
for (String uri : uris) {
|
||||
DataSpec dataSpec = new DataSpec(Uri.parse(uri));
|
||||
InputStream inputStream = new DataSourceInputStream(dataSource, dataSpec);
|
||||
try {
|
||||
readPlaylistGroups(
|
||||
new JsonReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)),
|
||||
result);
|
||||
} catch (Exception e) {
|
||||
Log.e(TAG, "Error loading sample list: " + uri, e);
|
||||
sawError = true;
|
||||
} finally {
|
||||
DataSourceUtil.closeQuietly(dataSource);
|
||||
}
|
||||
}
|
||||
new Handler(Looper.getMainLooper())
|
||||
.post(
|
||||
() -> {
|
||||
onPlaylistGroups(result, sawError);
|
||||
});
|
||||
});
|
||||
@Override
|
||||
protected void onPostExecute(List<PlaylistGroup> result) {
|
||||
onPlaylistGroups(result, sawError);
|
||||
}
|
||||
|
||||
private void readPlaylistGroups(JsonReader reader, List<PlaylistGroup> groups)
|
||||
|
|
@ -373,7 +353,6 @@ public class SampleChooserActivity extends AppCompatActivity
|
|||
group.playlists.addAll(playlistHolders);
|
||||
}
|
||||
|
||||
@OptIn(markerClass = UnstableApi.class) // Setting image duration.
|
||||
private PlaylistHolder readEntry(JsonReader reader, boolean insidePlaylist) throws IOException {
|
||||
Uri uri = null;
|
||||
String extension = null;
|
||||
|
|
@ -411,9 +390,6 @@ public class SampleChooserActivity extends AppCompatActivity
|
|||
case "clip_end_position_ms":
|
||||
clippingConfiguration.setEndPositionMs(reader.nextLong());
|
||||
break;
|
||||
case "image_duration_ms":
|
||||
mediaItem.setImageDurationMs(reader.nextLong());
|
||||
break;
|
||||
case "ad_tag_uri":
|
||||
mediaItem.setAdsConfiguration(
|
||||
new MediaItem.AdsConfiguration.Builder(Uri.parse(reader.nextString())).build());
|
||||
|
|
@ -666,6 +642,7 @@ public class SampleChooserActivity extends AppCompatActivity
|
|||
@RequiresApi(33)
|
||||
private static class Api33 {
|
||||
|
||||
@DoNotInline
|
||||
public static String getPostNotificationPermissionString() {
|
||||
return Manifest.permission.POST_NOTIFICATIONS;
|
||||
}
|
||||
|
|
|
|||
|
|
@ -67,8 +67,7 @@ public final class TrackSelectionDialog extends DialogFragment {
|
|||
}
|
||||
|
||||
public static final ImmutableList<Integer> SUPPORTED_TRACK_TYPES =
|
||||
ImmutableList.of(
|
||||
C.TRACK_TYPE_VIDEO, C.TRACK_TYPE_AUDIO, C.TRACK_TYPE_TEXT, C.TRACK_TYPE_IMAGE);
|
||||
ImmutableList.of(C.TRACK_TYPE_VIDEO, C.TRACK_TYPE_AUDIO, C.TRACK_TYPE_TEXT);
|
||||
|
||||
private final SparseArray<TrackSelectionViewFragment> tabFragments;
|
||||
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) {
|
||||
switch (trackType) {
|
||||
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:
|
||||
return resources.getString(R.string.track_selection_title_audio);
|
||||
return resources.getString(R.string.exo_track_selection_title_audio);
|
||||
case C.TRACK_TYPE_TEXT:
|
||||
return resources.getString(R.string.track_selection_title_text);
|
||||
case C.TRACK_TYPE_IMAGE:
|
||||
return resources.getString(R.string.track_selection_title_image);
|
||||
return resources.getString(R.string.exo_track_selection_title_text);
|
||||
default:
|
||||
throw new IllegalArgumentException();
|
||||
}
|
||||
|
|
|
|||
|
|
@ -25,6 +25,8 @@
|
|||
|
||||
<string name="error_generic">Playback failed</string>
|
||||
|
||||
<string name="error_drm_unsupported_before_api_18">DRM content not supported on API levels below 18</string>
|
||||
|
||||
<string name="error_drm_unsupported_scheme">This device does not support the required DRM scheme</string>
|
||||
|
||||
<string name="error_no_decoder">This device does not provide a decoder for <xliff:g id="mime_type">%1$s</xliff:g></string>
|
||||
|
|
@ -57,16 +59,6 @@
|
|||
|
||||
<string name="download_ads_unsupported">IMA does not support offline ads</string>
|
||||
|
||||
<string name="download_only_widevine_drm_supported">This demo app only supports downloading unencrypted or Widevine DRM content</string>
|
||||
|
||||
<string name="prefer_extension_decoders">Prefer extension decoders</string>
|
||||
|
||||
<string name="track_selection_title_video">Video</string>
|
||||
|
||||
<string name="track_selection_title_audio">Audio</string>
|
||||
|
||||
<string name="track_selection_title_text">Text</string>
|
||||
|
||||
<string name="track_selection_title_image">Image</string>
|
||||
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ apply plugin: 'kotlin-android'
|
|||
android {
|
||||
namespace 'androidx.media3.demo.session'
|
||||
|
||||
compileSdk project.ext.compileSdkVersion
|
||||
compileSdkVersion project.ext.compileSdkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
|
|
@ -34,6 +34,7 @@ android {
|
|||
versionCode project.ext.releaseVersionCode
|
||||
minSdkVersion project.ext.minSdkVersion
|
||||
targetSdkVersion project.ext.appTargetSdkVersion
|
||||
multiDexEnabled true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
|
@ -59,13 +60,11 @@ android {
|
|||
|
||||
dependencies {
|
||||
// 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.lifecycle:lifecycle-common:' + androidxLifecycleVersion
|
||||
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:' + androidxLifecycleVersion
|
||||
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
|
||||
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
|
||||
implementation 'com.google.android.material:material:' + androidxMaterialVersion
|
||||
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-guava:' + kotlinxCoroutinesVersion
|
||||
implementation project(modulePrefix + 'lib-ui')
|
||||
implementation project(modulePrefix + 'lib-session')
|
||||
implementation project(modulePrefix + 'demo-session-service')
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
limitations under the License.
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="androidx.media3.demo.session">
|
||||
|
||||
<uses-sdk/>
|
||||
|
|
@ -22,10 +23,12 @@
|
|||
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
|
||||
|
||||
<application
|
||||
android:name="androidx.multidex.MultiDexApplication"
|
||||
android:allowBackup="false"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name"
|
||||
android:theme="@style/Theme.Media3Demo">
|
||||
android:theme="@style/Theme.Media3Demo"
|
||||
tools:replace="android:name">
|
||||
|
||||
<!-- Declare that this session demo supports Android Auto. -->
|
||||
<meta-data
|
||||
|
|
@ -56,7 +59,7 @@
|
|||
android:foregroundServiceType="mediaPlayback"
|
||||
android:exported="true">
|
||||
<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.action.MEDIA_PLAY_FROM_SEARCH"/>
|
||||
</intent-filter>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,6 @@ package androidx.media3.demo.session
|
|||
import android.content.ComponentName
|
||||
import android.content.Context
|
||||
import android.os.Bundle
|
||||
import android.util.Log
|
||||
import android.view.LayoutInflater
|
||||
import android.view.View
|
||||
import android.view.ViewGroup
|
||||
|
|
@ -29,9 +28,6 @@ import android.widget.TextView
|
|||
import androidx.annotation.OptIn
|
||||
import androidx.appcompat.app.AppCompatActivity
|
||||
import androidx.core.content.ContextCompat
|
||||
import androidx.lifecycle.Lifecycle
|
||||
import androidx.lifecycle.lifecycleScope
|
||||
import androidx.lifecycle.repeatOnLifecycle
|
||||
import androidx.media3.common.C.TRACK_TYPE_TEXT
|
||||
import androidx.media3.common.MediaItem
|
||||
import androidx.media3.common.Player
|
||||
|
|
@ -44,15 +40,13 @@ import androidx.media3.session.MediaController
|
|||
import androidx.media3.session.SessionToken
|
||||
import androidx.media3.ui.PlayerView
|
||||
import com.google.common.util.concurrent.ListenableFuture
|
||||
import kotlinx.coroutines.awaitCancellation
|
||||
import kotlinx.coroutines.guava.await
|
||||
import kotlinx.coroutines.launch
|
||||
|
||||
private const val TAG = "PlayerActivity"
|
||||
import com.google.common.util.concurrent.MoreExecutors
|
||||
|
||||
class PlayerActivity : AppCompatActivity() {
|
||||
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 mediaItemListView: ListView
|
||||
|
|
@ -60,21 +54,8 @@ class PlayerActivity : AppCompatActivity() {
|
|||
private val mediaItemList: MutableList<MediaItem> = mutableListOf()
|
||||
private var lastMediaItemId: String? = null
|
||||
|
||||
@OptIn(UnstableApi::class) // PlayerView.hideController
|
||||
override fun onCreate(savedInstanceState: Bundle?) {
|
||||
super.onCreate(savedInstanceState)
|
||||
lifecycleScope.launch {
|
||||
lifecycle.repeatOnLifecycle(Lifecycle.State.STARTED) {
|
||||
try {
|
||||
initializeController()
|
||||
awaitCancellation()
|
||||
} finally {
|
||||
playerView.player = null
|
||||
releaseController()
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
setContentView(R.layout.activity_player)
|
||||
playerView = findViewById(R.id.player_view)
|
||||
|
||||
|
|
@ -83,8 +64,10 @@ class PlayerActivity : AppCompatActivity() {
|
|||
mediaItemListView.adapter = mediaItemListAdapter
|
||||
mediaItemListView.setOnItemClickListener { _, _, position, _ ->
|
||||
run {
|
||||
val controller = this.controller ?: return@run
|
||||
if (controller.currentMediaItemIndex == position) {
|
||||
controller.playWhenReady = !controller.playWhenReady
|
||||
@OptIn(UnstableApi::class) // PlayerView.hideController
|
||||
if (controller.playWhenReady) {
|
||||
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 =
|
||||
MediaController.Builder(
|
||||
this,
|
||||
SessionToken(this, ComponentName(this, PlaybackService::class.java)),
|
||||
SessionToken(this, ComponentName(this, PlaybackService::class.java))
|
||||
)
|
||||
.buildAsync()
|
||||
updateMediaMetadataUI()
|
||||
setController()
|
||||
controllerFuture.addListener({ setController() }, MoreExecutors.directExecutor())
|
||||
}
|
||||
|
||||
private fun releaseController() {
|
||||
|
|
@ -112,13 +106,9 @@ class PlayerActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
@OptIn(UnstableApi::class) // PlayerView.setShowSubtitleButton
|
||||
private suspend fun setController() {
|
||||
try {
|
||||
controller = controllerFuture.await()
|
||||
} catch (t: Throwable) {
|
||||
Log.w(TAG, "Failed to connect to MediaController", t)
|
||||
return
|
||||
}
|
||||
private fun setController() {
|
||||
val controller = this.controller ?: return
|
||||
|
||||
playerView.player = controller
|
||||
|
||||
updateCurrentPlaylistUI()
|
||||
|
|
@ -147,7 +137,8 @@ class PlayerActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
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_artist).text = ""
|
||||
return
|
||||
|
|
@ -161,9 +152,7 @@ class PlayerActivity : AppCompatActivity() {
|
|||
}
|
||||
|
||||
private fun updateCurrentPlaylistUI() {
|
||||
if (!::controller.isInitialized) {
|
||||
return
|
||||
}
|
||||
val controller = this.controller ?: return
|
||||
mediaItemList.clear()
|
||||
for (i in 0 until controller.mediaItemCount) {
|
||||
mediaItemList.add(controller.getMediaItemAt(i))
|
||||
|
|
@ -174,7 +163,7 @@ class PlayerActivity : AppCompatActivity() {
|
|||
private inner class MediaItemListAdapter(
|
||||
context: Context,
|
||||
viewID: Int,
|
||||
mediaItemList: List<MediaItem>,
|
||||
mediaItemList: List<MediaItem>
|
||||
) : ArrayAdapter<MediaItem>(context, viewID, mediaItemList) {
|
||||
override fun getView(position: Int, convertView: View?, parent: ViewGroup): View {
|
||||
val mediaItem = getItem(position)!!
|
||||
|
|
@ -184,7 +173,7 @@ class PlayerActivity : AppCompatActivity() {
|
|||
returnConvertView.findViewById<TextView>(R.id.media_item).text = mediaItem.mediaMetadata.title
|
||||
|
||||
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.
|
||||
returnConvertView.setBackgroundColor(
|
||||
ContextCompat.getColor(context, R.color.playlist_item_background)
|
||||
|
|
@ -203,6 +192,7 @@ class PlayerActivity : AppCompatActivity() {
|
|||
.setTextColor(ContextCompat.getColor(context, R.color.white))
|
||||
deleteButton.visibility = View.VISIBLE
|
||||
deleteButton.setOnClickListener {
|
||||
val controller = this@PlayerActivity.controller ?: return@setOnClickListener
|
||||
controller.removeMediaItem(position)
|
||||
updateCurrentPlaylistUI()
|
||||
}
|
||||
|
|
|
|||
|
|
@ -22,16 +22,21 @@
|
|||
android:background="@color/player_background"
|
||||
tools:context=".PlayerActivity">
|
||||
|
||||
<androidx.media3.ui.PlayerView
|
||||
android:id="@+id/player_view"
|
||||
android:background="@color/player_background"
|
||||
android:layout_width="match_parent"
|
||||
<androidx.media3.ui.AspectRatioFrameLayout
|
||||
android:layout_height="300dp"
|
||||
app:artwork_display_mode="fill"
|
||||
app:default_artwork="@drawable/artwork_placeholder"
|
||||
app:repeat_toggle_modes="one|all"
|
||||
app:show_shuffle_button="true"
|
||||
app:shutter_background_color="@color/player_background" />
|
||||
android:layout_width="match_parent"
|
||||
>
|
||||
<androidx.media3.ui.PlayerView
|
||||
android:id="@+id/player_view"
|
||||
android:background="@color/player_background"
|
||||
android:layout_width="match_parent"
|
||||
android:layout_height="match_parent"
|
||||
app: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
|
||||
android:id="@+id/media_artist"
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.Media3Demo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
|
|
@ -25,7 +25,9 @@
|
|||
<item name="colorSecondaryVariant">@color/teal_200</item>
|
||||
<item name="colorOnSecondary">@color/black</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
|
||||
<item name="android:statusBarColor" tools:targetApi="l">
|
||||
?attr/colorPrimaryVariant
|
||||
</item>
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -16,11 +16,11 @@
|
|||
<resources>
|
||||
<string name="app_name">Media3 Session Demo</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="shuffle">Shuffle</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">
|
||||
"Without notification access the app can't warn about failed background operations"</string>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -13,7 +13,7 @@
|
|||
See the License for the specific language governing permissions and
|
||||
limitations under the License.
|
||||
-->
|
||||
<resources>
|
||||
<resources xmlns:tools="http://schemas.android.com/tools">
|
||||
<!-- Base application theme. -->
|
||||
<style name="Theme.Media3Demo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
|
||||
<!-- Primary brand color. -->
|
||||
|
|
@ -25,7 +25,9 @@
|
|||
<item name="colorSecondaryVariant">@color/teal_700</item>
|
||||
<item name="colorOnSecondary">@color/black</item>
|
||||
<!-- Status bar color. -->
|
||||
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
|
||||
<item name="android:statusBarColor" tools:targetApi="l">
|
||||
?attr/colorPrimaryVariant
|
||||
</item>
|
||||
<!-- Customize your theme here. -->
|
||||
</style>
|
||||
</resources>
|
||||
|
|
|
|||
|
|
@ -18,7 +18,7 @@ apply plugin: 'kotlin-android'
|
|||
android {
|
||||
namespace 'androidx.media3.demo.session.automotive'
|
||||
|
||||
compileSdk project.ext.compileSdkVersion
|
||||
compileSdkVersion project.ext.compileSdkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
|
|
@ -34,6 +34,7 @@ android {
|
|||
versionCode project.ext.releaseVersionCode
|
||||
minSdkVersion project.ext.automotiveMinSdkVersion
|
||||
targetSdkVersion project.ext.appTargetSdkVersion
|
||||
multiDexEnabled true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
|
@ -59,6 +60,7 @@ android {
|
|||
dependencies {
|
||||
implementation 'androidx.core:core-ktx:' + androidxCoreVersion
|
||||
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
|
||||
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
|
||||
implementation 'com.google.android.material:material:' + androidxMaterialVersion
|
||||
implementation project(modulePrefix + 'lib-session')
|
||||
implementation project(modulePrefix + 'demo-session-service')
|
||||
|
|
|
|||
|
|
@ -14,6 +14,7 @@
|
|||
limitations under the License.
|
||||
-->
|
||||
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
|
||||
xmlns:tools="http://schemas.android.com/tools"
|
||||
package="androidx.media3.demo.session.automotive">
|
||||
|
||||
<uses-sdk/>
|
||||
|
|
@ -38,11 +39,13 @@
|
|||
android:resource="@xml/automotive_app_desc"/>
|
||||
|
||||
<application
|
||||
android:name="androidx.multidex.MultiDexApplication"
|
||||
android:allowBackup="false"
|
||||
android:taskAffinity=""
|
||||
android:appCategory="audio"
|
||||
android:icon="@mipmap/ic_launcher"
|
||||
android:label="@string/app_name">
|
||||
android:label="@string/app_name"
|
||||
tools:replace="android:name">
|
||||
|
||||
<meta-data
|
||||
android:name="androidx.car.app.TintableAttributionIcon"
|
||||
|
|
|
|||
|
|
@ -17,7 +17,7 @@ apply plugin: 'kotlin-android'
|
|||
android {
|
||||
namespace 'androidx.media3.demo.session.service'
|
||||
|
||||
compileSdk project.ext.compileSdkVersion
|
||||
compileSdkVersion project.ext.compileSdkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
|
|
@ -33,6 +33,7 @@ android {
|
|||
versionCode project.ext.releaseVersionCode
|
||||
minSdkVersion project.ext.minSdkVersion
|
||||
targetSdkVersion project.ext.appTargetSdkVersion
|
||||
multiDexEnabled true
|
||||
}
|
||||
|
||||
buildTypes {
|
||||
|
|
@ -53,6 +54,7 @@ android {
|
|||
dependencies {
|
||||
implementation 'androidx.core:core-ktx:' + androidxCoreVersion
|
||||
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
|
||||
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
|
||||
implementation project(modulePrefix + 'lib-exoplayer')
|
||||
implementation project(modulePrefix + 'lib-exoplayer-dash')
|
||||
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.MediaItemsWithStartPosition
|
||||
import androidx.media3.session.SessionCommand
|
||||
import androidx.media3.session.SessionError
|
||||
import androidx.media3.session.SessionResult
|
||||
import com.google.common.collect.ImmutableList
|
||||
import com.google.common.util.concurrent.Futures
|
||||
|
|
@ -41,17 +40,18 @@ open class DemoMediaLibrarySessionCallback(context: Context) :
|
|||
MediaItemTree.initialize(context.assets)
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class) // TODO: b/328238954 - Remove once new CommandButton icons are stable.
|
||||
private val customLayoutCommandButtons: List<CommandButton> =
|
||||
listOf(
|
||||
CommandButton.Builder(CommandButton.ICON_SHUFFLE_OFF)
|
||||
CommandButton.Builder()
|
||||
.setDisplayName(context.getString(R.string.exo_controls_shuffle_on_description))
|
||||
.setSessionCommand(SessionCommand(CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON, Bundle.EMPTY))
|
||||
.setIconResId(R.drawable.exo_icon_shuffle_off)
|
||||
.build(),
|
||||
CommandButton.Builder(CommandButton.ICON_SHUFFLE_ON)
|
||||
CommandButton.Builder()
|
||||
.setDisplayName(context.getString(R.string.exo_controls_shuffle_off_description))
|
||||
.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
|
||||
|
|
@ -70,7 +70,7 @@ open class DemoMediaLibrarySessionCallback(context: Context) :
|
|||
@OptIn(UnstableApi::class)
|
||||
override fun onConnect(
|
||||
session: MediaSession,
|
||||
controller: MediaSession.ControllerInfo,
|
||||
controller: MediaSession.ControllerInfo
|
||||
): MediaSession.ConnectionResult {
|
||||
if (
|
||||
session.isMediaNotificationController(controller) ||
|
||||
|
|
@ -93,7 +93,7 @@ open class DemoMediaLibrarySessionCallback(context: Context) :
|
|||
session: MediaSession,
|
||||
controller: MediaSession.ControllerInfo,
|
||||
customCommand: SessionCommand,
|
||||
args: Bundle,
|
||||
args: Bundle
|
||||
): ListenableFuture<SessionResult> {
|
||||
if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON == customCommand.customAction) {
|
||||
// Enable shuffling.
|
||||
|
|
@ -101,7 +101,7 @@ open class DemoMediaLibrarySessionCallback(context: Context) :
|
|||
// Change the custom layout to contain the `Disable shuffling` command.
|
||||
session.setCustomLayout(
|
||||
session.mediaNotificationControllerInfo!!,
|
||||
ImmutableList.of(customLayoutCommandButtons[1]),
|
||||
ImmutableList.of(customLayoutCommandButtons[1])
|
||||
)
|
||||
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
|
||||
} 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.
|
||||
session.setCustomLayout(
|
||||
session.mediaNotificationControllerInfo!!,
|
||||
ImmutableList.of(customLayoutCommandButtons[0]),
|
||||
ImmutableList.of(customLayoutCommandButtons[0])
|
||||
)
|
||||
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(
|
||||
session: MediaLibraryService.MediaLibrarySession,
|
||||
browser: MediaSession.ControllerInfo,
|
||||
params: MediaLibraryService.LibraryParams?,
|
||||
params: MediaLibraryService.LibraryParams?
|
||||
): ListenableFuture<LibraryResult<MediaItem>> {
|
||||
return Futures.immediateFuture(LibraryResult.ofItem(MediaItemTree.getRootItem(), params))
|
||||
}
|
||||
|
||||
@OptIn(UnstableApi::class) // SessionError.ERROR_BAD_VALUE
|
||||
override fun onGetItem(
|
||||
session: MediaLibraryService.MediaLibrarySession,
|
||||
browser: MediaSession.ControllerInfo,
|
||||
mediaId: String,
|
||||
mediaId: String
|
||||
): ListenableFuture<LibraryResult<MediaItem>> {
|
||||
MediaItemTree.getItem(mediaId)?.let {
|
||||
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(
|
||||
session: MediaLibraryService.MediaLibrarySession,
|
||||
browser: MediaSession.ControllerInfo,
|
||||
parentId: String,
|
||||
page: Int,
|
||||
pageSize: Int,
|
||||
params: MediaLibraryService.LibraryParams?,
|
||||
params: MediaLibraryService.LibraryParams?
|
||||
): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
|
||||
val children = MediaItemTree.getChildren(parentId)
|
||||
if (children.isNotEmpty()) {
|
||||
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(
|
||||
mediaSession: MediaSession,
|
||||
controller: MediaSession.ControllerInfo,
|
||||
mediaItems: List<MediaItem>,
|
||||
mediaItems: List<MediaItem>
|
||||
): ListenableFuture<List<MediaItem>> {
|
||||
return Futures.immediateFuture(resolveMediaItems(mediaItems))
|
||||
}
|
||||
|
|
@ -167,7 +165,7 @@ open class DemoMediaLibrarySessionCallback(context: Context) :
|
|||
browser: MediaSession.ControllerInfo,
|
||||
mediaItems: List<MediaItem>,
|
||||
startIndex: Int,
|
||||
startPositionMs: Long,
|
||||
startPositionMs: Long
|
||||
): ListenableFuture<MediaItemsWithStartPosition> {
|
||||
if (mediaItems.size == 1) {
|
||||
// Try to expand a single item to a playlist.
|
||||
|
|
@ -196,7 +194,7 @@ open class DemoMediaLibrarySessionCallback(context: Context) :
|
|||
private fun maybeExpandSingleItemToPlaylist(
|
||||
mediaItem: MediaItem,
|
||||
startIndex: Int,
|
||||
startPositionMs: Long,
|
||||
startPositionMs: Long
|
||||
): MediaItemsWithStartPosition? {
|
||||
var playlist = listOf<MediaItem>()
|
||||
var indexInPlaylist = startIndex
|
||||
|
|
@ -225,7 +223,7 @@ open class DemoMediaLibrarySessionCallback(context: Context) :
|
|||
session: MediaLibraryService.MediaLibrarySession,
|
||||
browser: MediaSession.ControllerInfo,
|
||||
query: String,
|
||||
params: MediaLibraryService.LibraryParams?,
|
||||
params: MediaLibraryService.LibraryParams?
|
||||
): ListenableFuture<LibraryResult<Void>> {
|
||||
session.notifySearchResultChanged(browser, query, MediaItemTree.search(query).size, params)
|
||||
return Futures.immediateFuture(LibraryResult.ofVoid())
|
||||
|
|
@ -237,7 +235,7 @@ open class DemoMediaLibrarySessionCallback(context: Context) :
|
|||
query: String,
|
||||
page: Int,
|
||||
pageSize: Int,
|
||||
params: MediaLibraryService.LibraryParams?,
|
||||
params: MediaLibraryService.LibraryParams?
|
||||
): ListenableFuture<LibraryResult<ImmutableList<MediaItem>>> {
|
||||
return Futures.immediateFuture(LibraryResult.ofItemList(MediaItemTree.search(query), params))
|
||||
}
|
||||
|
|
|
|||
|
|
@ -19,18 +19,17 @@ import android.Manifest
|
|||
import android.app.NotificationChannel
|
||||
import android.app.NotificationManager
|
||||
import android.app.PendingIntent
|
||||
import android.content.Intent
|
||||
import android.content.pm.PackageManager
|
||||
import android.os.Build
|
||||
import androidx.annotation.OptIn
|
||||
import androidx.core.app.NotificationCompat
|
||||
import androidx.core.app.NotificationManagerCompat
|
||||
import androidx.core.os.bundleOf
|
||||
import androidx.media3.common.AudioAttributes
|
||||
import androidx.media3.common.util.UnstableApi
|
||||
import androidx.media3.demo.session.service.R
|
||||
import androidx.media3.exoplayer.ExoPlayer
|
||||
import androidx.media3.exoplayer.util.EventLogger
|
||||
import androidx.media3.session.MediaConstants
|
||||
import androidx.media3.session.MediaLibraryService
|
||||
import androidx.media3.session.MediaSession
|
||||
import androidx.media3.session.MediaSession.ControllerInfo
|
||||
|
|
@ -91,6 +90,13 @@ open class DemoPlaybackService : MediaLibraryService() {
|
|||
return mediaLibrarySession
|
||||
}
|
||||
|
||||
override fun onTaskRemoved(rootIntent: Intent?) {
|
||||
val player = mediaLibrarySession.player
|
||||
if (!player.playWhenReady || player.mediaItemCount == 0) {
|
||||
stopSelf()
|
||||
}
|
||||
}
|
||||
|
||||
// MediaSession.setSessionActivity
|
||||
// MediaSessionService.clearListener
|
||||
@OptIn(UnstableApi::class)
|
||||
|
|
@ -113,17 +119,6 @@ open class DemoPlaybackService : MediaLibraryService() {
|
|||
MediaLibrarySession.Builder(this, player, createLibrarySessionCallback())
|
||||
.also { builder -> getSingleTopActivity()?.let { builder.setSessionActivity(it) } }
|
||||
.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
|
||||
|
|
@ -171,7 +166,7 @@ open class DemoPlaybackService : MediaLibraryService() {
|
|||
NotificationChannel(
|
||||
CHANNEL_ID,
|
||||
getString(R.string.notification_channel_name),
|
||||
NotificationManager.IMPORTANCE_DEFAULT,
|
||||
NotificationManager.IMPORTANCE_DEFAULT
|
||||
)
|
||||
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 {
|
||||
namespace 'androidx.media3.demo.surface'
|
||||
|
||||
compileSdk project.ext.compileSdkVersion
|
||||
compileSdkVersion project.ext.compileSdkVersion
|
||||
|
||||
compileOptions {
|
||||
sourceCompatibility JavaVersion.VERSION_1_8
|
||||
|
|
|
|||