diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml index c32439333a..74fbe78811 100644 --- a/.github/ISSUE_TEMPLATE/bug.yml +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -5,37 +5,31 @@ body: - type: markdown attributes: value: | - We can only process bug reports that are actionable. Unclear bug reports or reports with - insufficient information may not get attention. + We can only process bug reports that are actionable. Unclear bug reports or reports with insufficient information may not get attention. Before filing a bug: ------------------------- - - Search existing issues, including issues that are closed: - https://github.com/androidx/media/issues?q=is%3Aissue - - For ExoPlayer-related bugs, please also check for existing issues on the ExoPlayer - tracker: https://github.com/google/ExoPlayer/issues?q=is%3Aissue + - Search existing issues, including issues that are closed: https://github.com/androidx/media/issues?q=is%3Aissue + - For ExoPlayer-related bugs, please also check for existing issues on the ExoPlayer tracker: https://github.com/google/ExoPlayer/issues?q=is%3Aissue - type: dropdown attributes: - label: Media3 Version + label: Version description: What version of Media3 (or ExoPlayer) are you using? options: - - Media3 1.1.0-alpha01 - - Media3 1.0.2 - - Media3 1.0.1 - - Media3 1.0.0 - - Media3 1.0.0-rc02 - - Media3 1.0.0-rc01 - - Media3 1.0.0-beta03 - - Media3 1.0.0-beta02 - - Media3 1.0.0-beta01 - - Media3 1.0.0-alpha03 - - Media3 1.0.0-alpha02 - - Media3 1.0.0-alpha01 - - Media3 `main` branch - - ExoPlayer 2.18.7 - - ExoPlayer 2.18.6 - - ExoPlayer 2.18.5 + - Media3 main branch + - Media3 pre-release (alpha, beta or RC not in this list) + - Media3 1.4.1 + - Media3 1.4.0 + - Media3 1.3.1 + - Media3 1.3.0 + - Media3 1.2.1 + - Media3 1.2.0 + - Media3 1.1.1 / ExoPlayer 2.19.1 + - Media3 1.1.0 / ExoPlayer 2.19.0 + - Media3 1.0.2 / ExoPlayer 2.18.7 + - Media3 1.0.1 / ExoPlayer 2.18.6 + - Media3 1.0.0 / ExoPlayer 2.18.5 - ExoPlayer 2.18.4 - ExoPlayer 2.18.3 - ExoPlayer 2.18.2 @@ -50,10 +44,16 @@ body: - ExoPlayer 2.14.2 - ExoPlayer 2.14.1 - ExoPlayer 2.14.0 - - ExoPlayer `dev-v2` branch + - ExoPlayer dev-v2 branch - Older (unsupported) validations: required: true + - type: textarea + attributes: + label: More version details + description: > + Required if you selected `main` or `dev-v2` (please provide an exact commit SHA), + or 'pre-release' or 'older' (please provide the version). - type: textarea attributes: label: Devices that reproduce the issue @@ -114,7 +114,7 @@ body: * Attach a file here * Include a media URL * Refer to a piece of media from the demo app (e.g. `Misc > Dizzy (MP4)`) - * If you don't want to post media publicly please email the info to dev.exoplayer@gmail.com with subject 'Issue #\' after filing this issue, and note that you will do this here. + * If you don't want to post media publicly please email the info to android-media-github@google.com with subject 'Issue #\' after filing this issue, and note that you will do this here. * If you are certain the issue does not depend on the media being played, enter "Not applicable" here. For DRM-protected media please also include the scheme and license server URL. @@ -124,8 +124,8 @@ body: attributes: label: Bug Report description: | - After filing this issue please run `adb bugreport` shortly after reproducing the problem (ideally in the [demo app](https://github.com/androidx/media/tree/release/demos/main)) to capture a zip file, and email this to dev.exoplayer@gmail.com with subject 'Issue #\'. + After filing this issue please run `adb bugreport` shortly after reproducing the problem (ideally in the [demo app](https://github.com/androidx/media/tree/release/demos/main)) to capture a zip file, and email this to android-media-github@google.com with subject 'Issue #\'. **Note:** Logcat output is **not** the same as a full bug report, and is often missing information that's useful for diagnosing issues. Please ensure you're sending a full bug report zip file. options: - - label: You will email the zip file produced by `adb bugreport` to dev.exoplayer@gmail.com after filing this issue. + - label: You will email the zip file produced by `adb bugreport` to android-media-github@google.com after filing this issue. diff --git a/.github/ISSUE_TEMPLATE/question.md b/.github/ISSUE_TEMPLATE/question.md index 386abe6bf9..3f753dbd18 100644 --- a/.github/ISSUE_TEMPLATE/question.md +++ b/.github/ISSUE_TEMPLATE/question.md @@ -39,6 +39,6 @@ Don't forget to check ExoPlayer's supported formats and devices, if applicable (https://developer.android.com/guide/topics/media/exoplayer/supported-formats). If there's something you don't want to post publicly, please submit the issue, -then email the link/bug report to dev.exoplayer@gmail.com using a subject in the -format "Issue #1234", where #1234 is your issue number (we don't reply to -emails). +then email the link/bug report to android-media-github@google.com using a +subject in the format "Issue #1234", where #1234 is your issue number (we don't +reply to emails). diff --git a/.idea/icon.svg b/.idea/icon.svg new file mode 100644 index 0000000000..f5071cd805 --- /dev/null +++ b/.idea/icon.svg @@ -0,0 +1,10 @@ + + + + + + + + + + diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md index 5908bd201c..04120620fe 100644 --- a/CONTRIBUTING.md +++ b/CONTRIBUTING.md @@ -38,6 +38,24 @@ you made on top of `main` using $ git diff -U0 main... | google-java-format-diff.py -p1 -i ``` +### Push access to PR branches + +Please ensure maintainers of this repository have push access to your PR branch +by ticking the `Allow edits from maintainers` checkbox when creating the PR (or +after it's created). See the +[GitHub docs](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork) +for more info. This allows us to make changes and fixes to the PR while it goes +through internal review, and ensures we don't create an +['evil' merge](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefevilmergeaevilmerge) +when it gets merged. + +This checkbox only appears on PRs from individual-owned forks +(https://github.com/orgs/community/discussions/5634). If you open a PR from an +organization-owned fork we will ask you to open a new one from an +individual-owned fork. If this isn't possible we can still merge the PR, but it +will result in an 'evil' merge because the changes and fixes we make during +internal review will be part of the merge commit. + ## Contributor license agreement Contributions to any Google project must be accompanied by a Contributor diff --git a/README.md b/README.md index 39a1e3e868..874df26145 100644 --- a/README.md +++ b/README.md @@ -1,19 +1,21 @@ # AndroidX Media AndroidX Media is a collection of libraries for implementing media use cases on -Android, including local playback (via ExoPlayer) and media sessions. +Android, including local playback (via ExoPlayer), video editing (via Transformer) and media sessions. ## Documentation * The [developer guide][] provides a wealth of information. * The [class reference][] documents the classes and methods. * The [release notes][] document the major changes in each release. +* The [media dev center][] provides samples and guidelines. * Follow our [developer blog][] to keep up to date with the latest developments! [developer guide]: https://developer.android.com/guide/topics/media/media3 [class reference]: https://developer.android.com/reference/androidx/media3/common/package-summary [release notes]: RELEASENOTES.md +[media dev center]: https://developer.android.com/media [developer blog]: https://medium.com/google-exoplayer ## Migration for existing ExoPlayer and MediaSession projects @@ -45,13 +47,21 @@ also possible to clone this GitHub repository and depend on the modules locally. #### 1. Add module dependencies The easiest way to get started using AndroidX Media is to add gradle -dependencies on the libraries you need in the `build.gradle` file of your app -module. +dependencies on the libraries you need in the `build.gradle.kts` file of your +app module. For example, to depend on ExoPlayer with DASH playback support and UI components you can add dependencies on the modules like this: -```gradle +```kotlin +implementation("androidx.media3:media3-exoplayer:1.X.X") +implementation("androidx.media3:media3-exoplayer-dash:1.X.X") +implementation("androidx.media3:media3-ui:1.X.X") +``` + +Or in Gradle Groovy DSL `build.gradle`: + +```groovy implementation 'androidx.media3:media3-exoplayer:1.X.X' implementation 'androidx.media3:media3-exoplayer-dash:1.X.X' implementation 'androidx.media3:media3-ui:1.X.X' @@ -73,21 +83,23 @@ details. #### 2. Turn on Java 8 support If not enabled already, you also need to turn on Java 8 support in all -`build.gradle` files depending on AndroidX Media, by adding the following to the -`android` section: +`build.gradle.kts` files depending on AndroidX Media, by adding the following to +the `android` section: -```gradle +```kotlin +compileOptions { + targetCompatibility = JavaVersion.VERSION_1_8 +} +``` + +Or in Gradle Groovy DSL `build.gradle`: + +```groovy compileOptions { targetCompatibility JavaVersion.VERSION_1_8 } ``` -#### 3. Enable multidex - -If your Gradle `minSdkVersion` is 20 or lower, you should -[enable multidex](https://developer.android.com/studio/build/multidex) in order -to prevent build errors. - ### Locally Cloning the repository and depending on the modules locally is required when @@ -98,24 +110,59 @@ First, clone the repository into a local directory: ```sh git clone https://github.com/androidx/media.git -cd media ``` -Next, add the following to your project's `settings.gradle` file, replacing +Next, add the following to your project's `settings.gradle.kts` file, replacing `path/to/media` with the path to your local copy: -```gradle -gradle.ext.androidxMediaModulePrefix = 'media-' +```kotlin +(gradle as ExtensionAware).extra["androidxMediaModulePrefix"] = "media3-" +apply(from = file("path/to/media/core_settings.gradle")) +``` + +Or in Gradle Groovy DSL `settings.gradle`: + +```groovy +gradle.ext.androidxMediaModulePrefix = 'media3-' apply from: file("path/to/media/core_settings.gradle") ``` You should now see the AndroidX Media modules appear as part of your project. -You can depend on them as you would on any other local module, for example: +You can depend on them from `build.gradle.kts` as you would on any other local +module, for example: -```gradle -implementation project(':media-lib-exoplayer') -implementation project(':media-lib-exoplayer-dash') -implementation project(':media-lib-ui') +```kotlin +implementation(project(":media3-lib-exoplayer")) +implementation(project(":media3-lib-exoplayer-dash")) +implementation(project(":media3-lib-ui")) +``` + +Or in Gradle Groovy DSL `build.gradle`: + +```groovy +implementation project(':media3-lib-exoplayer') +implementation project(':media3-lib-exoplayer-dash') +implementation project(':media3-lib-ui') +``` + +#### MIDI module + +By default the [MIDI module](libraries/decoder_midi) is disabled as a local +dependency, because it requires additional Maven repository config. If you want +to use it as a local dependency, please configure the JitPack repository as +[described in the module README](libraries/decoder_midi/README.md#getting-the-module), +and then enable building the module in your `settings.gradle.kts` file: + +```kotlin +gradle.extra.apply { + set("androidxMediaEnableMidiModule", true) +} +``` + +Or in Gradle Groovy DSL `settings.gradle`: + +```groovy +gradle.ext.androidxMediaEnableMidiModule = true ``` ## Developing AndroidX Media diff --git a/RELEASENOTES.md b/RELEASENOTES.md index fc703b1471..ffa1d47520 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,5 +1,1724 @@ # Release notes +### Unreleased changes + +* Common Library: + * Remove `@DoNotInline` annotations from manually out-of-lined inner + classes designed to avoid + [runtime class verification failures](https://chromium.googlesource.com/chromium/src/+/HEAD/build/android/docs/class_verification_failures.md). + Recent versions of [R8](https://developer.android.com/build/shrink-code) + now automatically out-of-line calls like these to avoid the runtime + failures (so the manual out-of-lining is no longer required). All Gradle + users of the library must already be a using a version of the Android + Gradle Plugin that uses a version of R8 which does this, + [due to `compileSdk = 35`](https://issuetracker.google.com/345472586#comment7). + Users of the library with non-Gradle build systems will need to ensure + their R8-equivalent shrinking/obfuscating step does a similar automatic + out-of-lining process in order to avoid runtime class verification + failures. This change has + [already been done in other AndroidX libraries](http://r.android.com/3156141). +* ExoPlayer: + * Fix `MediaCodec.CryptoException` sometimes being reported as an + "unexpected runtime error" when `MediaCodec` is operated in asynchronous + mode (default behaviour on API 31+). + * Deprecated `MediaCodecUtil.getCodecProfileAndLevel`. Use + `androidx.media3.common.util.CodecSpecificDataUtil.getCodecProfileAndLevel` + instead. + * Pass `bufferedDurationUs` instead of `bufferedPositionUs` with + `PreloadMediaSource.PreloadControl.onContinueLoadingRequested()`. Also + changes `DefaultPreloadManager.Status.STAGE_LOADED_TO_POSITION_MS` to + `DefaultPreloadManager.Status.STAGE_LOADED_FOR_DURATION_MS`, apps then + need to pass a value representing a specific duration from the default + start position for which the corresponding media source has to be + preloaded with this IntDef, instead of a position. + * Add `ForwardingRenderer` implementation that forwards all method calls + to another renderer + ([1703](https://github.com/androidx/media/pull/1703)). +* Transformer: +* Track Selection: +* Extractors: + * Fix preroll sample handling for non-keyframe media start positions when + processing edit lists in MP4 files + ([#1659](https://github.com/google/ExoPlayer/issues/1659)). + * Improved frame rate calculation by using media duration from the `mdhd` + box in `Mp4Extractor` and `FragmentedMp4Extractor` + ([#1531](https://github.com/androidx/media/issues/1531)). +* DataSource: +* Audio: +* Video: + * Add workaround for a device issue on Galaxy Tab S7 FE that causes 60fps + secure H264 streams to be marked as unsupported + ([#1619](https://github.com/androidx/media/issues/1619)). +* Text: +* Metadata: +* Image: +* DataSource: +* DRM: + * Fix `IllegalStateException` from + `DefaultDrmSession.requiresSecureDecoder` after opening a DRM session + failed. This issue was introduced in `1.5.0-alpha01`. +* Effect: +* Muxers: +* IMA extension: +* Session: +* UI: + * Make the stretched/cropped video in + `PlayerView`-in-Compose-`AndroidView` workaround opt-in, due to issues + with XML-based shared transitions. Apps using `PlayerView` inside + `AndroidView` need to call + `PlayerView.setEnableComposeSurfaceSyncWorkaround` in order to opt-in + ([#1237](https://github.com/androidx/media/issues/1237), + [#1594](https://github.com/androidx/media/issues/1594)). + * Add `setFullscreenButtonState` to `PlayerView` to allow updates of + fullscreen button's icon on demand, i.e. out-of-band and not reactively + to a click interaction + ([#1590](https://github.com/androidx/media/issues/1590), + [#184](https://github.com/androidx/media/issues/184)). +* Downloads: +* OkHttp Extension: +* Cronet Extension: +* RTMP Extension: +* HLS Extension: +* DASH Extension: +* Smooth Streaming Extension: +* RTSP Extension: + * Fix user info removal for URLs that contain encoded @ characters + ([#1138](https://github.com/androidx/media/pull/1138)). +* Decoder Extensions (FFmpeg, VP9, AV1, etc.): +* MIDI extension: +* Leanback extension: +* Cast Extension: + * Stop cleaning the timeline after the CastSession disconnects, which + enables the sender app to resume playback locally after a disconnection. + * Populate CastPlayer's `DeviceInfo` when a `Context` is provided. This + enables linking the `MediaSession` to a `RoutingSession`, which is + necessary for integrating Output Switcher + ([#1056](https://github.com/androidx/media/issues/1056)). +* Test Utilities: +* Demo app: +* Remove deprecated symbols: + +## 1.5 + +### 1.5.0-alpha01 (2024-09-06) + +This release includes the following changes since the +[1.4.1 release](#141-2024-08-23): + +* Common Library: + * Add `ForwardingSimpleBasePlayer` that allows forwarding to another + player with small adjustments while ensuring full consistency and + listener handling + ([#1183](https://github.com/androidx/media/issues/1183)). + * Replace `SimpleBasePlayer.State.playlist` by `getPlaylist()` method. + * Add override for `SimpleBasePlayer.State.Builder.setPlaylist()` to + directly specify a `Timeline` and current `Tracks` and `Metadata` + instead of building a playlist structure. + * Increase `minSdk` to 21 (Android Lollipop). This is aligned with all + other AndroidX libraries. + * Add `androidx.media3:media3-common-ktx` artifact which provides + Kotlin-specific functionality built on top of the Common library + * Add `Player.listen` suspending extension function to spin a coroutine to + listen to `Player.Events` to the `media3-common-ktx` library. +* ExoPlayer: + * `MediaCodecRenderer.onProcessedStreamChange()` can now be called for + every media item. Previously it was not called for the first one. Use + `MediaCodecRenderer.experimentalEnableProcessedStreamChangedAtStart()` + to enable this. + * Add `PreloadMediaSource.PreloadControl.onPreloadError` to allow + `PreloadMediaSource.PreloadControl` implementations to take actions when + error occurs. + * Add `BasePreloadManager.Listener` to propagate preload events to apps. + * Allow changing SNTP client timeout and retry alternative addresses on + timeout ([#1540](https://github.com/androidx/media/issues/1540)). + * Remove `MediaCodecAdapter.Configuration.flags` as the field was always + zero. + * Allow the user to select the built-in speaker for playback on Wear OS + API 35+ (where the device advertises support for this). + * Defer the blocking call to + `Context.getSystemService(Context.AUDIO_SERVICE)` until audio focus + handling is enabled. This ensures the blocking call isn't done if audio + focus handling is not enabled + ([#1616](https://github.com/androidx/media/pull/1616)). + * Allow playback regardless of buffered duration when loading fails + ([#1571](https://github.com/androidx/media/issues/1571)). + * Add `AnalyticsListener.onRendererReadyChanged()` to signal when + individual renderers allow playback to be ready. +* Transformer: + * Add `SurfaceAssetLoader`, which supports queueing video data to + Transformer via a `Surface`. + * `ImageAssetLoader` reports unsupported input via `AssetLoader.onError` + instead of throwing an `IllegalStateException`. +* Extractors: + * Allow `Mp4Extractor` and `FragmentedMp4Extractor` to identify H264 + samples that are not used as reference by subsequent samples. + * Add option to enable index-based seeking in `AmrExtractor`. + * Treat MP3 files with more than 128kB between valid frames as truncated + (instead of invalid). This means files with non-MP3 data at the end, + with no other metadata to indicate the length of the MP3 bytes, now stop + playback at the end of the MP3 data instead of failing with + `ParserException: Searched too many bytes.{contentIsMalformed=true, + dataType=1}` ([#1563](https://github.com/androidx/media/issues/1563)). +* DataSource: + * Update `HttpEngineDataSource` to allow use starting at version S + extension 7 instead of API level 34 + ([#1262](https://github.com/androidx/media/issues/1262)). +* Audio: + * Automatically configure CTA-2075 loudness metadata on the codec if + present in the media. + * Ensure smooth volume ramp down when seeking. +* Video: + * `MediaCodecVideoRenderer` avoids decoding samples that are neither + rendered nor used as reference by other samples. + * On API 35 and above, `MediaCodecAdapter` may now receive a `null` + `Surface` in `configure` and calls to a new method `detachOutputSurface` + to remove a previously set `Surface` if the codec supports this + (`MediaCodecInfo.detachedSurfaceSupported`). + * Use `MediaCodecAdapter` supplied pixel aspect ratio values if provided + when processing `onOutputFormatChanged` + ([#1371](https://github.com/androidx/media/pull/1371)). +* Text: + * Add a custom `VoiceSpan` and populate it for + [WebVTT voice spans](https://www.w3.org/TR/webvtt1/#webvtt-cue-voice-span) + ([#1632](https://github.com/androidx/media/issues/1632)). +* Image: + * Add `ExternallyLoadedImageDecoder` for simplified integration with + external image loading libraries like Glide or Coil. +* DataSource: + * Add `FileDescriptorDataSource`, a new `DataSource` that can be used to + read from a `FileDescriptor` + ([#3757](https://github.com/google/ExoPlayer/issues/3757)). +* Effect: + * Add `DefaultVideoFrameProcessor` workaround for minor `SurfaceTexture` + scaling. `SurfaceTexture` may include a small scaling that cuts off a + 1-texel border around the edge of a cropped buffer. This is now handled + such that output is closer to expected. + * Speed up `DefaultVideoFrameProcessor.queueInputBitmap()`. As a result, + exporting images to videos with `Transformer` is faster. +* IMA extension: + * Fix bug where clearing the playlist may cause an + `ArrayIndexOutOfBoundsException` in + `ImaServerSideAdInsertionMediaSource`. +* Session: + * Add `MediaButtonReceiver.shouldStartForegroundService(Intent)` to allow + apps to suppress a play command coming in for playback resumption by + overriding this method. By default, the service is always started and + playback can't be suppressed without the system crashing the service + with a `ForegroundServiceDidNotStartInTimeException` + ([#1528](https://github.com/google/ExoPlayer/issues/1528)). + * Fix bug that caused custom commands sent from a `MediaBrowser` being + dispatched to the `MediaSessionCompat.Callback` instead of the + `MediaBrowserServiceCompat` variant of the method when connected to a + legacy service. This prevented the `MediaBrowser` to receive the actual + return value sent back by the legacy service + ([#1474](https://github.com/androidx/media/issues/1474)). +* UI: +* Downloads: +* OkHttp Extension: +* Cronet Extension: +* RTMP Extension: +* HLS Extension: +* DASH Extension: + * Add support for periods starting in the middle of a segment + ([#1440](https://github.com/androidx/media/issues/1440)). +* Decoder Extensions (FFmpeg, VP9, AV1, etc.): + * Add the IAMF decoder module, which provides support for playback of MP4 + files containing IAMF tracks using the libiamf native library to + synthesize audio. + * Playback is enabled with a stereo layout as well as 5.1 with + spatialization together with optional head tracking enabled, but + binaural playback support is currently not available. +* Test Utilities: + * `DataSourceContractTest` now includes tests to verify: + * Input stream `read position` is updated. + * Output buffer `offset` is applied correctly. +* Remove deprecated symbols: + * Remove deprecated `Player.hasPrevious`, `Player.hasPreviousWindow()`. + Use `Player.hasPreviousMediaItem()` instead. + * Remove deprecated `Player.previous()`method. Use + `Player.seekToPreviousMediaItem()` instead. + * Remove deprecated `DrmSessionEventListener.onDrmSessionAcquired` method. + +## 1.4 + +### 1.4.1 (2024-08-23) + +This release includes the following changes since the +[1.4.0 release](#140-2024-07-24): + +* ExoPlayer: + * Handle preload callbacks asynchronously in `PreloadMediaSource` + ([#1568](https://github.com/androidx/media/issues/1568)). + * Allow playback regardless of buffered duration when loading fails + ([#1571](https://github.com/androidx/media/issues/1571)). +* Extractors: + * MP3: Fix `Searched too many bytes` error by correctly ignoring trailing + non-MP3 data based on the length field in an `Info` frame + ([#1480](https://github.com/androidx/media/issues/1480)). +* Text: + * TTML: Fix handling of percentage `tts:fontSize` values to ensure they + are correctly inherited from parent nodes with percentage `tts:fontSize` + values. + * Fix `IndexOutOfBoundsException` in `LegacySubtitleUtil` due to + incorrectly handling the case of the requested output start time being + greater than or equal to the final event time in the `Subtitle` + ([#1516](https://github.com/androidx/media/issues/1516)). +* DRM: + * Fix `android.media.MediaCodec$CryptoException: Operation not supported + in this configuration: ERROR_DRM_CANNOT_HANDLE` error on API 31+ devices + playing L1 Widevine content. This error is caused by an incomplete + implementation of the framework + [`MediaDrm.requiresSecureDecoder`](https://developer.android.com/reference/android/media/MediaDrm#requiresSecureDecoder\(java.lang.String\)) + method ([#1603](https://github.com/androidx/media/issues/1603)). +* Effect: + * Add a `release()` method to `GlObjectsProvider`. +* Session: + * Transform a double-tap of `KEYCODE_HEADSETHOOK` into a 'seek to next' + action, as + [documented](https://developer.android.com/reference/androidx/media3/session/MediaSession#media-key-events-mapping) + ([#1493](https://github.com/androidx/media/issues/1493)). + * Handle `KEYCODE_HEADSETHOOK` as a 'play' command in + `MediaButtonReceiver` when deciding whether to ignore it to avoid a + `ForegroundServiceDidNotStartInTimeException` + ([#1581](https://github.com/androidx/media/issues/1581)). +* RTSP Extension: + * Skip invalid Media Descriptions in SDP parsing + ([#1087](https://github.com/androidx/media/issues/1472)). + +### 1.4.0 (2024-07-24) + +This release includes the following changes since the +[1.3.1 release](#131-2024-04-11): + +* Common Library: + * Forward presumed no-op seek calls to the protected `BasePlayer.seekTo()` + and `SimpleBasePlayer.handleSeek()` methods instead of ignoring them. If + you are implementing these methods in a custom player, you may need to + handle these additional calls with `mediaItemIndex == C.INDEX_UNSET`. + * Remove compile dependency on enhanced Java 8 desugaring + ([#1312](https://github.com/androidx/media/issues/1312)). + * Ensure the duration passed to `MediaItem.Builder.setImageDurationMs()` + is ignored for a non-image `MediaItem` (as documented). + * Add `Format.customData` to store app-provided custom information about + `Format` instances. +* ExoPlayer: + * Add `BasePreloadManager` which coordinates the preloading for multiple + sources based on the priorities defined by their `rankingData`. + Customization is possible by extending this class. Add + `DefaultPreloadManager` which uses `PreloadMediaSource` to preload media + samples of the sources into memory, and uses an integer `rankingData` + that indicates the index of an item on the UI. + * Add `PlayerId` to most methods of `LoadControl` to enable `LoadControl` + implementations to support multiple players. + * Remove `Buffer.isDecodeOnly()` and `C.BUFFER_FLAG_DECODE_ONLY`. There is + no need to set this flag as renderers and decoders will decide to skip + buffers based on timestamp. Custom `Renderer` implementations should + check if the buffer time is at least + `BaseRenderer.getLastResetPositionUs()` to decide whether a sample + should be shown. Custom `SimpleDecoder` implementations can check + `isAtLeastOutputStartTimeUs()` if needed or mark other buffers with + `DecoderOutputBuffer.shouldBeSkipped` to skip them. + * Allow a null value to be returned by + `TargetPreloadStatusControl.getTargetPreloadStatus(T)` to indicate not + to preload a `MediaSource` with the given `rankingData`. + * Add `remove(MediaSource)` to `BasePreloadManager`. + * Add `reset()` to `BasePreloadManager` to release all the holding sources + while keep the preload manager instance. + * Add `ExoPlayer.setPriority()` (and `Builder.setPriority()`) to define + the priority value used in `PriorityTaskManager` and for MediaCodec + importance from API 35. + * Fix issue with updating the last rebuffer time which resulted in + incorrect `bs` (buffer starvation) key in CMCD + ([#1124](https://github.com/androidx/media/issues/1124)). + * Add + `PreloadMediaSource.PreloadControl.onLoadedToTheEndOfSource(PreloadMediaSource)` + to indicate that the source has loaded to the end. This allows the + `DefaultPreloadManager` and the custom + `PreloadMediaSource.PreloadControl` implementations to preload the next + source or take other actions. + * Fix bug where silence skipping at the end of items can trigger a + playback exception. + * Add `clear` to `PreloadMediaSource` to discard the preloading period. + * Add new error code + `PlaybackException.ERROR_CODE_DECODING_RESOURCES_RECLAIMED` that is used + when codec resources are reclaimed for higher priority tasks. + * Let `AdsMediaSource` load preroll ads before initial content media + preparation completes + ([#1358](https://github.com/androidx/media/issues/1358)). + * Fix bug where playback moved to `STATE_ENDED` when re-preparing a + multi-period DASH live stream after the original period was already + removed from the manifest. + * Rename `onTimelineRefreshed()` to `onSourcePrepared()` and + `onPrepared()` to `onTracksSelected()` in + `PreloadMediaSource.PreloadControl`. Also rename the IntDefs in + `DefaultPreloadManager.Stage` accordingly. + * Add experimental support for dynamic scheduling to better align work + with CPU wake-cycles and delay waking up to when renderers can progress. + You can enable this using `experimentalSetDynamicSchedulingEnabled()` + when setting up your ExoPlayer instance. + * Add `Renderer.getDurationToProgressUs()`. A `Renderer` can implement + this method to return to ExoPlayer the duration that playback must + advance for the renderer to progress. If `ExoPlayer` is set with + `experimentalSetDynamicSchedulingEnabled()` then `ExoPlayer` will call + this method when calculating the time to schedule its work task. + * Add `MediaCodecAdapter#OnBufferAvailableListener` to alert when input + and output buffers are available for use by `MediaCodecRenderer`. + `MediaCodecRenderer` will signal `ExoPlayer` when receiving these + callbacks and if `ExoPlayer` is set with + `experimentalSetDynamicSchedulingEnabled()`, then `ExoPlayer` will + schedule its work loop as renderers can make progress. + * Use data class for `LoadControl` methods instead of individual + parameters. + * Add `ExoPlayer.isReleased()` to check whether `Exoplayer.release()` has + been called. + * Add `ExoPlayer.Builder.setMaxSeekToPreviousPositionMs()` to configure + the maximum position for which `seekToPrevious()` seeks to the previous + item ([#1425](https://github.com/androidx/media/issues/1425)). + * Fix some audio focus inconsistencies, e.g. not reporting full or + transient focus loss while the player is paused + ([#1436](https://github.com/androidx/media/issues/1436)). + * Fix potential `IndexOutOfBoundsException` caused by extractors reporting + additional tracks after the initial preparation step + ([#1476](https://github.com/androidx/media/issues/1476)). + * `Effects` in `ExoPlayer.setVideoEffect()` will receive the timestamps + with the renderer offset removed + ([#1098](https://github.com/androidx/media/issues/1098)). + * Fix potential `IllegalArgumentException` when handling player error that + happened while reading ahead into another playlist item + ([#1483](https://github.com/androidx/media/issues/1483)). +* Transformer: + * Add `audioConversionProcess` and `videoConversionProcess` to + `ExportResult` indicating how the respective track in the output file + was made. + * Relax trim optimization H.264 level checks. + * Add support for changing between SDR and HDR input media in a sequence. + * Add support for composition-level audio effects. + * Add support for transcoding Ultra HDR images into HDR videos. + * Fix issue where the `DefaultAudioMixer` does not output the correct + amount of bytes after being reset and reused. + * Work around a decoder bug where the number of audio channels was capped + at stereo when handling PCM input. + * When selecting tracks in `ExoPlayerAssetLoader`, ignore audio channel + count constraints as they only apply for playback. + * Replace `androidx.media3.transformer.Muxer` interface with + `androidx.media3.muxer.Muxer` and remove + `androidx.media3.transformer.Muxer`. + * Fix HEIC image loading from content URI schemes. + ([#1373](https://github.com/androidx/media/issues/1373)). + * Adjust audio track duration in `AudioGraphInput` to improve AV sync. + * Remove `ExportResult.processedInputs` field. If you use this field for + codec details, then use `DefaultDecoderFactory.listener` instead. In + case of a codec exception, codec details will be available in the + `ExportException.codecInfo`. +* Extractors: + * MPEG-TS: Roll forward the change ensuring the last frame is rendered by + passing the last access unit of a stream to the sample queue + ([#7909](https://github.com/google/ExoPlayer/issues/7909)). + Incorporating fixes to resolve the issues that emerged in I-frame only + HLS streams([#1150](https://github.com/androidx/media/issues/1150)) and + H.262 HLS streams + ([#1126](https://github.com/androidx/media/issues/1126)). + * MP3: Prefer the data size from an `Info` frame over the size reported by + the underlying stream (e.g. file size, or HTTP `Content-Length` header). + This helps to exclude non-playable trailer data (e.g. album artwork) + from constant bitrate seeking calculations, making seeks more accurate + ([#1376](https://github.com/androidx/media/issues/1376)). + * MP3: Use the frame count and other data in an `Info` frame (if present) + to compute an average bitrate for constant bitrate seeking, rather than + extrapolating from the bitrate of the frame after the `Info` frame, + which may be artificially small, e.g. `PCUT` frame + ([#1376](https://github.com/androidx/media/issues/1376)). + * Fix PCM audio format extraction in AVI containers. +* Audio: + * Fix DTS:X Profile 2 encoding attributes for passthrough playback + ([#1299](https://github.com/androidx/media/pull/1299)). + * For offloaded playback, reset the tracking field for stream completion + in `DefaultAudioSink` prior to calling `AudioTrack.stop()` so that + `AudioTrack.StreamEventCallback#onPresentationEnded` correctly + identifies when all pending data has been played. + * Fix bug in `SilenceSkippingAudioProcessor` where transitions between + different audio formats (for example stereo to mono) can cause the + processor to throw an exception + ([#1352](https://github.com/androidx/media/issues/1352)). + * Implement `MediaCodecAudioRenderer.getDurationToProgressUs()` so that + ExoPlayer will dynamically schedule its main work loop to when the + MediaCodecAudioRenderer can make progress. +* Video: + * Fix issue where `Listener.onRenderedFirstFrame()` arrives too early when + switching surfaces mid-playback. + * Fix decoder fallback logic for Dolby Vision to use a compatible AV1 + decoder if needed + ([#1389](https://github.com/androidx/media/pull/1389)). + * Fix codec exception that may be caused by enabling a video renderer + mid-playback. +* Text: + * Fix issue where subtitles starting before a seek position are skipped. + This issue was only introduced in Media3 1.4.0-alpha01. + * Change default subtitle parsing behavior so it happens during extraction + instead of during rendering (see + [ExoPlayer's architecture diagram](https://developer.android.com/media/media3/exoplayer/glossary#exoplayer) + for the difference between extraction and rendering). + * This change can be overridden by calling **both** + `MediaSource.Factory.experimentalParseSubtitlesDuringExtraction(false)` + and `TextRenderer.experimentalSetLegacyDecodingEnabled(true)`. See + the + [docs on customization](https://developer.android.com/media/media3/exoplayer/customization) + for how to plumb these components into an `ExoPlayer` instance. + These methods (and all support for legacy subtitle decoding) will be + removed in a future release. + * Apps with custom `SubtitleDecoder` implementations need to update + them to implement `SubtitleParser` instead (and + `SubtitleParser.Factory` instead of `SubtitleDecoderFactory`). + * PGS: Fix run-length decoding to resolve `0` as a color index, instead of + a literal color value + ([#1367](https://github.com/androidx/media/pull/1367)). + * CEA-708: Ignore `rowLock` value. The CEA-708-E S-2023 spec states that + `rowLock` and `columnLock` should both be assumed to be true, regardless + of the values present in the stream (`columnLock` support is not + implemented, so it's effectively assumed to always be false). + * This was originally included in the `1.3.0-alpha01` release notes, + but the change was accidentally reverted before the `1.3.0-rc01` + release. This is now fixed, so the change is present again. + * CEA-708: Avoid duplicate newlines being added by ExoPlayer's naive + handling of the 'set pen location' command + ([#1315](https://github.com/androidx/media/pull/1315)). + * Fix an `IllegalArgumentException` from `LegacySubtitleUtil` when a + WebVTT subtitle sample contains no cues, e.g. as part of a DASH stream + ([#1516](https://github.com/androidx/media/issues/1516)). +* Metadata: + * Fix mapping of MP4 to ID3 sort tags. Previously the 'album sort' + (`soal`), 'artist sort' (`soar`) and 'album artist sort' (`soaa`) MP4 + tags were wrongly mapped to the `TSO2`, `TSOA` and `TSOP` ID3 tags + ([#1302](https://github.com/androidx/media/issues/1302)). + * Fix reading of MP4 (/iTunes) numeric `gnre` (genre) and `tmpo` (tempo) + tags when the value is more than one byte long. + * Propagate ID3 `TCON` frame to `MediaMetadata.genre` + ([#1305](https://github.com/androidx/media/issues/1305)). +* Image: + * Add support for non-square DASH thumbnail grids + ([#1300](https://github.com/androidx/media/pull/1300)). + * Add support for AVIF for API 34+. + * Allow `null` as parameter for `ExoPlayer.setImageOutput()` to clear a + previously set `ImageOutput`. +* DataSource: + * Implement support for `android.resource://package/id` raw resource URIs + where `package` is different to the package of the current application. + This wasn't previously documented to work, but is a more efficient way + of accessing resources in another package than by name. + * Eagerly check `url` is non-null in the `DataSpec` constructors. This + parameter was already annotated to be non-null. + * Allow `ByteArrayDataSource` to resolve a URI to a byte array during + `open()`, instead of being hard-coded at construction + ([#1405](https://github.com/androidx/media/issues/1405)). +* DRM: + * Allow setting a `LoadErrorHandlingPolicy` on + `DefaultDrmSessionManagerProvider` + ([#1271](https://github.com/androidx/media/issues/1271)). +* Effect: + * Support multiple speed changes within the same `EditedMediaItem` or + `Composition` in `SpeedChangeEffect`. + * Support for HLG and PQ output from ultra HDR bitmap input. + * Add support for EGL_GL_COLORSPACE_BT2020_HLG_EXT, which improves HLG + surface output in ExoPlayer.setVideoEffect and Transformer's Debug + SurfaceView. + * Update Overlay matrix implementation to make it consistent with the + documentation by flipping the x and y values applied in + `setOverlayFrameAnchor()`. If using + `OverlaySettings.Builder.setOverlayFrameAnchor()`, flip their x and y + values by multiplying them by `-1`. + * Fix bug where `TimestampWrapper` crashes when used with + `ExoPlayer#setVideoEffects` + ([#821](https://github.com/androidx/media/issues/821)). + * Change default SDR color working space from linear colors to electrical + BT 709 SDR video. Also provide third option to retain the original + colorspace. + * Allow defining indeterminate z-order of EditedMediaItemSequences + ([#1055](https://github.com/androidx/media/pull/1055)). + * Maintain a consistent luminance range across different pieces of HDR + content (uses the HLG range). + * Add support for Ultra HDR (bitmap) overlays on HDR content. + * Allow `SeparableConvolution` effects to be used before API 26. + * Remove unused `OverlaySettings.useHdr` since dynamic range of overlay + and frame must match. + * Add HDR support for `TextOverlay`. Luminance of the text overlay can be + adjusted with `OverlaySettings.Builder.setHdrLuminanceMultiplier()`. +* IMA extension: + * Promote API that is required for apps to play + [DAI ad streams](https://developers.google.com/ad-manager/dynamic-ad-insertion/full-service) + to stable. + * Add `replaceAdTagParameters(Map )` to + `ImaServerSideAdInsertionMediaSource.AdLoader` that allows replacing ad + tag parameters at runtime. + * Fix bug where `VideoAdPlayer.VideoAdPlayerCallback.onError()` was not + called when a player error happened during ad playback + ([#1334](https://github.com/androidx/media/issues/1334)). + * Bump IMA SDK version to 3.33.0 to fix a `NullPointerException` when + using `data://` ad tag URIs + ([#700](https://github.com/androidx/media/issues/700)). +* Session: + * Change default of `CommandButton.enabled` to `true` and ensure the value + can stay false for controllers even if the associated command is + available. + * Add icon constants for `CommandButton` that should be used instead of + custom icon resources. + * Add `MediaSessionService.isPlaybackOngoing()` to let apps query whether + the service needs to be stopped in `onTaskRemoved()` + ([#1219](https://github.com/androidx/media/issues/1219)). + * Add `MediaSessionService.pauseAllPlayersAndStopSelf()` that conveniently + allows to pause playback of all sessions and call `stopSelf()` to + terminate the lifecycle of the `MediaSessionService`. + * Override `MediaSessionService.onTaskRemoved(Intent)` to provide a safe + default implementation that keeps the service running in the foreground + if playback is ongoing or stops the service otherwise. + * Hide seekbar in the media notification for live streams by not setting + the duration into the platform session metadata + ([#1256](https://github.com/androidx/media/issues/1256)). + * Align conversion of `MediaMetadata` to `MediaDescriptionCompat`, to use + the same preferred order and logic when selecting metadata properties as + in media1. + * Add `MediaSession.sendError()` that allows sending non-fatal errors to + Media3 controller. When using the notification controller (see + `MediaSession.getMediaNotificationControllerInfo()`), the custom error + is used to update the `PlaybackState` of the platform session to an + error state with the given error information + ([#543](https://github.com/androidx/media/issues/543)). + * Add `MediaSession.Callback.onPlayerInteractionFinished()` to inform + sessions when a series of player interactions from a specific controller + finished. + * Add `SessionError` and use it in `SessionResult` and `LibraryResult` + instead of the error code to provide more information about the error + and how to resolve the error if possible. + * Publish the code for the media3 controller test app that can be used to + test interactions with apps publishing a media session. + * Propagate extras passed to media3's + `MediaSession[Builder].setSessionExtras()` to a media1 controller's + `PlaybackStateCompat.getExtras()`. + * Map fatal and non-fatal errors to and from the platform session. A + `PlaybackException` is mapped to a fatal error state of the + `PlaybackStateCompat`. A `SessionError` sent to the media notification + controller with `MediaSession.sendError(ControllerInfo, SessionError)` + is mapped to a non-fatal error in `PlaybackStateCompat` which means that + error code and message are set but the state of the platform session + remains different to `STATE_ERROR`. + * Allow the session activity to be set per controller to override the + global session activity. The session activity can be defined for a + controller at connection time by creating a `ConnectionResult` with + `AcceptedResultBuilder.setSessionActivivty(PendingIntent)`. Once + connected, the session activity can be updated with + `MediaSession.setSessionActivity(ControllerInfo, PendingIntent)`. + * Improve error replication of calls to `MediaLibrarySession.Callback`. + Error replication can now be configured by using + `MediaLibrarySession.Builder.setLibraryErrorReplicationMode()` for + choosing the error type or opt-ing out of error replication which is on + by default. +* UI: + * Add image display support to `PlayerView` when connected to an + `ExoPlayer` ([#1144](https://github.com/androidx/media/issues/1144)). + * Add customization of various icons in `PlayerControlView` through xml + attributes to allow different drawables per `PlayerView` instance, + rather than global overrides + ([#1200](https://github.com/androidx/media/issues/1200)). + * Work around a platform bug causing stretched/cropped video when using + `SurfaceView` inside a Compose `AndroidView` on API 34 + ([#1237](https://github.com/androidx/media/issues/1237)). +* Downloads: + * Ensure that `DownloadHelper` does not leak unreleased `Renderer` + instances, which can eventually result in an app crashing with + `IllegalStateException: Too many receivers, total of 1000, registered + for pid` ([#1224](https://github.com/androidx/media/issues/1224)). +* Cronet Extension: + * Fix `SocketTimeoutException` in `CronetDataSource`. In some versions of + Cronet, the request provided by the callback is not always the same. + This leads to callback not completing and request timing out + (https://issuetracker.google.com/328442628). +* HLS Extension: + * Fix bug where pending EMSG samples waiting for a discontinuity were + delegated in `HlsSampleStreamWrapper` with an incorrect offset causing + an `IndexOutOfBoundsException` or an `IllegalArgumentException` + ([#1002](https://github.com/androidx/media/issues/1002)). + * Fix bug where non-primary playlists keep reloading for LL-HLS streams + ([#1240](https://github.com/androidx/media/issues/1240)). + * Fix bug where enabling CMCD for HLS with initialization segments + resulted in `Source Error` and `IllegalArgumentException`. + * Fix bug where non-primary playing playlists are not refreshed during + live playback ([#1240](https://github.com/androidx/media/issues/1240)). + * Fix bug where enabling CMCD for HLS live streams causes + `ArrayIndexOutOfBoundsException` + ([#1395](https://github.com/androidx/media/issues/1395)). +* DASH Extension: + * Fix bug where re-preparing a multi-period live stream can throw an + `IndexOutOfBoundsException` + ([#1329](https://github.com/androidx/media/issues/1329)). + * Add support for `dashif:Laurl` license urls + ([#1345](https://github.com/androidx/media/issues/1345)). +* Cast Extension: + * Fix bug that converted the album title of the `MediaQueueItem` to the + artist in the Media3 media item + ([#1255](https://github.com/androidx/media/pull/1255)). +* Test Utilities: + * Implement `onInit()` and `onRelease()` in `FakeRenderer`. + * Change `TestPlayerRunHelper.runUntil()/playUntil()` methods to fail on + nonfatal errors (e.g. those reported to + `AnalyticsListener.onVideoCodecError()`). Use the new + `TestPlayerRunHelper.run(player).ignoringNonFatalErrors().untilXXX()` + method chain to disable this behavior. +* Demo app: + * Use `DefaultPreloadManager` in the short form demo app. + * Allow setting repeat mode with `Intent` arguments from command line + ([#1266](https://github.com/androidx/media/pull/1266)). + * Use `HttpEngineDataSource` as the `HttpDataSource` when supported by the + device. +* Remove deprecated symbols: + * Remove `CronetDataSourceFactory`. Use `CronetDataSource.Factory` + instead. + * Remove some `DataSpec` constructors. Use `DataSpec.Builder` instead. + * Remove `setContentTypePredicate(Predicate)` method from + `DefaultHttpDataSource`, `OkHttpDataSource` and `CronetDataSource`. Use + the equivalent method on each `XXXDataSource.Factory` instead. + * Remove `OkHttpDataSource` constructors and `OkHttpDataSourceFactory`. + Use `OkHttpDataSource.Factory` instead. + * Remove `PlayerMessage.setHandler(Handler)`. Use `setLooper(Looper)` + instead. + * Remove `Timeline.Window.isLive` field. Use the `isLive()` method + instead. + * Remove `DefaultHttpDataSource` constructors. Use + `DefaultHttpDataSource.Factory` instead. + * Remove `DashMediaSource.DEFAULT_LIVE_PRESENTATION_DELAY_MS`. Use + `DashMediaSource.DEFAULT_FALLBACK_TARGET_LIVE_OFFSET_MS` instead. + * Remove `MediaCodecInfo.isSeamlessAdaptationSupported(Format, Format, + boolean)`. Use `MediaCodecInfo.canReuseCodec(Format, Format)` instead. + * Remove `DrmSessionManager.DUMMY` and `getDummyDrmSessionManager()` + method. Use `DrmSessionManager.DRM_UNSUPPORTED` instead. + * Remove `AnalyticsListener.onAudioInputFormatChanged(EventTime, Format)`, + `AnalyticsListener.onVideoInputFormatChanged(EventTime, Format)`, + `AudioRendererEventListener.onAudioInputFormatChanged(Format)`, + `VideoRendererEventListener.onVideoInputFormatChanged(Format)`. Use the + overloads that take a `DecoderReuseEvaluation` instead. + * Remove `RendererSupport.FormatSupport` IntDef and `FORMAT_HANDLED`, + `FORMAT_EXCEEDS_CAPABILITIES`, `FORMAT_UNSUPPORTED_DRM`, + `FORMAT_UNSUPPORTED_SUBTYPE`, `FORMAT_UNSUPPORTED_TYPE` constants. Use + the equivalent IntDef and constants in `androidx.media3.common.C` + instead (e.g. `C.FORMAT_HANDLED`). + * Remove `Bundleable` interface. This includes removing all + `Bundleable.Creator CREATOR` constant fields. Callers should use + the `Bundle toBundle()` and `static Foo fromBundle(Bundle)` methods on + each type instead. + +### 1.4.0-rc01 (2024-07-11) + +Use the 1.4.0 [stable version](#140-2024-07-24). + +### 1.4.0-beta01 (2024-06-21) + +Use the 1.4.0 [stable version](#140-2024-07-24). + +### 1.4.0-alpha02 (2024-06-06) + +Use the 1.4.0 [stable version](#140-2024-07-24). + +### 1.4.0-alpha01 (2024-04-11) + +Use the 1.4.0 [stable version](#140-2024-07-24). + +## 1.3 + +### 1.3.1 (2024-04-11) + +This release includes the following changes since the +[1.3.0 release](#130-2024-03-06): + +* Common Library: + * Add `Format.labels` to allow localized or other alternative labels. +* ExoPlayer: + * Fix issue where `PreloadMediaPeriod` cannot retain the streams when it + is preloaded again. + * Apply the correct corresponding `TrackSelectionResult` to the playing + period in track reselection. + * Start early-enabled renderers only after advancing the playing period + when transitioning between media items + ([#1017](https://github.com/androidx/media/issues/1017)). + * Add missing return type to proguard `-keepclasseswithmembers` rule for + `DefaultVideoFrameProcessor.Factory.Builder.build()` + ([#1187](https://github.com/androidx/media/issues/1187)). +* Transformer: + * Add workaround for exception thrown due to `MediaMuxer` not supporting + negative presentation timestamps before API 30. +* Track Selection: + * `DefaultTrackSelector`: Prefer video tracks with a 'reasonable' frame + rate (>=10fps) over those with a lower or unset frame rate. This ensures + the player selects the 'real' video track in MP4s extracted from motion + photos that can contain two HEVC tracks where one has a higher + resolution but a very small number of frames + ([#1051](https://github.com/androidx/media/issues/1051)). +* Extractors: + * Fix issue where padding was not skipped when reading odd-sized chunks + from WAV files ([#1117](https://github.com/androidx/media/pull/1117)). + * MP3: Populate `Format.averageBitrate` from metadata frames such as + `XING` and `VBRI`. + * MPEG-TS: Revert a change that aimed to ensure the last frame is rendered + by passing the last access unit of a stream to the sample queue + ([#7909](https://github.com/google/ExoPlayer/issues/7909)). This is due + to the change causing new problems with I-frame only HLS streams + ([#1150](https://github.com/androidx/media/issues/1150)) and H.262 HLS + streams ([#1126](https://github.com/androidx/media/issues/1126)). +* Audio: + * Allow renderer recovery by disabling offload if audio track fails to + initialize in offload mode. + * For offloaded playback, use the `AudioTrack.StreamEventCallback` method + `onPresentationEnded` to identify when all pending data has been played. +* Video: + * Add workaround for a device issue on Galaxy Tab S7 FE, Chromecast with + Google TV, and Lenovo M10 FHD Plus that causes 60fps H265 streams to be + marked as unsupported + * Add workaround that ensures the first frame is always rendered while + tunneling even if the device does not do this automatically as required + by the API ([#1169](https://github.com/androidx/media/issues/1169)). + ([#966](https://github.com/androidx/media/issues/966)). + * Fix issue where HDR color info handling causes codec misbehavior and + prevents adaptive format switches for SDR video tracks + ([#1158](https://github.com/androidx/media/issues/1158)). +* Text: + * WebVTT: Prevent directly consecutive cues from creating spurious + additional `CuesWithTiming` instances from `WebvttParser.parse` + ([#1177](https://github.com/androidx/media/issues/1177)). +* DRM: + * Work around a `NoSuchMethodError` which can be thrown by the `MediaDrm` + framework instead of `ResourceBusyException` or + `NotProvisionedException` on some Android 14 devices + ([#1145](https://github.com/androidx/media/issues/1145)). +* Effect: + * Improved PQ to SDR tone-mapping by converting color spaces. +* Session: + * Fix issue where the current position jumps back when the controller + replaces the current item + ([#951](https://github.com/androidx/media/issues/951)). + * Fix issue where `MediaMetadata` with just non-null `extras` is not + transmitted between media controllers and sessions + ([#1176](https://github.com/androidx/media/issues/1176)). +* UI: + * Fallback to include audio track language name if `Locale` cannot + identify a display name + ([#988](https://github.com/androidx/media/issues/988)). +* DASH Extension: + * Populate all `Label` elements from the manifest into `Format.labels` + ([#1054](https://github.com/androidx/media/pull/1054)). +* RTSP Extension: + * Skip empty session information values (i-tags) in SDP parsing + ([#1087](https://github.com/androidx/media/issues/1087)). +* Decoder Extensions (FFmpeg, VP9, AV1, MIDI, etc.): + * Disable the MIDI extension as a local dependency by default because it + requires an additional Maven repository to be configured. Users who need + this module from a local dependency + [can re-enable it](https://github.com/androidx/media/blob/main/README.md#midi-module). + +### 1.3.0 (2024-03-06) + +This release includes the following changes since the +[1.2.1 release](#121-2024-01-09): + +* Common Library: + * Implement support for `android.resource://package/[type/]name` raw + resource URIs where `package` is different to the package of the current + application. This has always been documented to work, but wasn't + correctly implemented until now. + * Normalize MIME types set by app code or read from media to be fully + lower-case. + * Define ads with a full `MediaItem` instead of a single `Uri` in + `AdPlaybackState`. + * Increase `minSdk` to 19 (Android KitKat). This is + [aligned with all other AndroidX libraries](https://android-developers.googleblog.com/2023/10/androidx-minsdkversion-19.html), + and is required for us to upgrade to the latest versions of our AndroidX + dependencies. + * Populate both `artworkUri` and `artworkData` in + `MediaMetadata.Builder.populate(MediaMetadata)` when at least one of + them is non-null ([#964](https://github.com/androidx/media/issues/964)). +* ExoPlayer: + * Add `PreloadMediaSource` and `PreloadMediaPeriod` that allows apps to + preload a content media source at a specific start position before + playback. `PreloadMediaSource` takes care of preparing the content media + source to receive the `Timeline`, preparing and caching the period at + the given start position, selecting tracks and loading media data for + the period. Apps control the preload progress by implementing + `PreloadMediaSource.PreloadControl` and set the preloaded source to the + player for playback. + * Add `ExoPlayer.setImageOutput` that allows apps to set + `ImageRenderer.ImageOutput`. + * `DefaultRenderersFactory` now provides an `ImageRenderer` to the player + by default with null `ImageOutput` and `ImageDecoder.Factory.DEFAULT`. + * Emit `Player.Listener.onPositionDiscontinuity` event when silence is + skipped ([#765](https://github.com/androidx/media/issues/765)). + * Add experimental support for parsing subtitles during extraction. You + can enable this using + `MediaSource.Factory.experimentalParseSubtitlesDuringExtraction()`. + * Support adaptive media sources with `PreloadMediaSource`. + * Implement `HttpEngineDataSource`, an `HttpDataSource` using the + [HttpEngine](https://developer.android.com/reference/android/net/http/HttpEngine) + API. + * Prevent subclassing `CompositeSequenceableLoader`. This component was + [previously made extensible](https://github.com/androidx/media/commit/0de57cbfae7165dd3bb829e323d089cd312b4b1b) + but was never subclassed within the library. Customizations can be done + by wrapping an instance using the + [decorator pattern](https://en.wikipedia.org/wiki/Decorator_pattern) and + implementing a custom `CompositeSequenceableLoaderFactory`. + * Fix issue where repeating the same time causes metadata from this item + to be cleared ([#1007](https://github.com/androidx/media/issues/1007)). + * Rename `experimentalSetSubtitleParserFactory` methods on + `BundledChunkExtractor.Factory` and `DefaultHlsExtractorFactory` to + `setSubtitleParserFactory` and disallow passing `null`. Use the new + `experimentalParseSubtitlesDuringExtraction(boolean)` methods to control + parsing behaviour. + * Add support for customising the `SubtitleParser.Factory` used during + extraction. This can be achieved with + `MediaSource.Factory.setSubtitleParserFactory()`. + * Add source prefix to all `Format.id` fields generated from + `MergingMediaSource`. This helps to identify which source produced a + `Format` ([#883](https://github.com/androidx/media/issues/883)). + * Fix the regex used for validating custom Common Media Client Data (CMCD) + key names by modifying it to only check for hyphen + ([#1028](https://github.com/androidx/media/issues/1028)). + * Stop double-encoding CMCD query parameters + ([#1075](https://github.com/androidx/media/issues/1075)). +* Transformer: + * Add support for flattening H.265/HEVC SEF slow motion videos. + * Increase transmuxing speed, especially for 'remove video' edits. + * Add API to ensure that the output file starts on a video frame. This can + make the output of trimming operations more compatible with player + implementations that don't show the first video frame until its + presentation timestamp + ([#829](https://github.com/androidx/media/issues/829)). + * Add support for optimizing single asset mp4 trim operations. + * Add support to ensure a video frame has the first timestamp in the + output file. Fixes output files beginning with black frame on iOS based + players ([#829](https://github.com/androidx/media/issues/829)). +* Track Selection: + * Add `DefaultTrackSelector.selectImageTrack` to enable image track + selection. + * Add `TrackSelectionParameters.isPrioritizeImageOverVideoEnabled` to + determine whether to select an image track if both an image track and a + video track are available. The default value is `false` which means + selecting a video track is prioritized. +* Extractors: + * Add additional AV1C parsing to MP4 extractor to retrieve + `ColorInfo.colorSpace`, `ColorInfo.colorTransfer`, and + `ColorInfo.colorRange` values + ([#692](https://github.com/androidx/media/pull/692)). + * MP3: Use constant bitrate (CBR) seeking for files with an `Info` header + (the CBR equivalent of the `Xing` header). Previously we used the seek + table from the `Info` header, but this results in less precise seeking + than if we ignore it and assume the file is CBR. + * MPEG2-TS: Add DTS, DTS-LBR and DTS:X Profile2 support + ([#275](https://github.com/androidx/media/pull/275)). + * Extract audio types from TS descriptors and map them to role flags, + allowing users to make better-informed audio track selections + ([#973](https://github.com/androidx/media/pull/973)). +* Audio: + * Improve silence skipping algorithm with smooth volume ramp; retained + minimal silence and more natural silence durations + ([#7423](https://github.com/google/ExoPlayer/issues/7423)). + * Report the skipped silence more deterministically + ([#1035](https://github.com/androidx/media/issues/1035)). +* Video: + * Change the `MediaCodecVideoRenderer` constructor that takes a + `VideoFrameProcessor.Factory` argument and replace it with a constructor + that takes a `VideoSinkProvider` argument. Apps that want to inject a + custom `VideoFrameProcessor.Factory` can instantiate a + `CompositingVideoSinkProvider` that uses the custom + `VideoFrameProcessor.Factory` and pass the video sink provider to + `MediaCodecVideoRenderer`. +* Text: + * Fix serialization of bitmap cues to resolve `Tried to marshall a Parcel + that contained Binder objects` error when using + `DefaultExtractorsFactory.setTextTrackTranscodingEnabled` + ([#836](https://github.com/androidx/media/issues/836)). + * CEA-708: Ignore `rowLock` value. The CEA-708-E S-2023 spec states that + `rowLock` and `columnLock` should both be assumed to be true, regardless + of the values present in the stream (`columnLock` support is not + implemented, so it's effectively assumed to always be false). +* Image: + * Add support for DASH thumbnails. Grid images are cropped and individual + thumbnails are provided to `ImageOutput` close to their presentation + times. +* DRM: + * Play 'clear lead' unencrypted samples in DRM content immediately by + default, even if the keys for the later encrypted samples aren't ready + yet. This may lead to mid-playback stalls if the keys still aren't ready + when the playback position reaches the encrypted samples (but previously + playback wouldn't have started at all by this point). This behavior can + be disabled with + [`MediaItem.DrmConfiguration.Builder.setPlayClearContentWithoutKey`](https://developer.android.com/reference/androidx/media3/common/MediaItem.DrmConfiguration.Builder#setPlayClearContentWithoutKey\(boolean\)) + or + [`DefaultDrmSessionManager.Builder.setPlayClearSamplesWithoutKeys`](https://developer.android.com/reference/androidx/media3/exoplayer/drm/DefaultDrmSessionManager.Builder#setPlayClearSamplesWithoutKeys\(boolean\)). +* IMA extension: + * Fix issue where DASH and HLS ads without the appropriate file extension + can't be played. +* Session: + * Disable double-click detection for TV apps + ([#962](https://github.com/androidx/media/issues/962)). + * Fix issue where `MediaItem.RequestMetadata` with just non-null extras is + not transmitted between media controllers and sessions. + * Add constructor to `MediaLibrarySession.Builder` that only takes a + `Context` instead of a `MediaLibraryService`. +* HLS Extension: + * Reduce `HlsMediaPeriod` to package-private visibility. This type + shouldn't be directly depended on from outside the HLS package. + * Resolve seeks to beginning of a segment more efficiently + ([#1031](https://github.com/androidx/media/pull/1031)). +* Decoder Extensions (FFmpeg, VP9, AV1, MIDI, etc.): + * MIDI decoder: Ignore SysEx event messages + ([#710](https://github.com/androidx/media/pull/710)). +* Test Utilities: + * Don't pause playback in `TestPlayerRunHelper.playUntilPosition`. The + test keeps the playback in a playing state, but suspends progress until + the test is able to add assertions and further actions. +* Demo app: + * Add a shortform demo module to demo the usage of `PreloadMediaSource` + with the short-form content use case. + +### 1.3.0-rc01 (2024-02-22) + +Use the 1.3.0 [stable version](#130-2024-03-06). + +### 1.3.0-beta01 (2024-02-07) + +Use the 1.3.0 [stable version](#130-2024-03-06). + +### 1.3.0-alpha01 (2024-01-15) + +Use the 1.3.0 [stable version](#130-2024-03-06). + +## 1.2 + +### 1.2.1 (2024-01-09) + +This release includes the following changes since the +[1.2.0 release](#120-2023-11-15): + +* ExoPlayer: + * Fix issue where manual seeks outside of the + `LiveConfiguration.min/maxOffset` range keep adjusting the offset back + to `min/maxOffset`. + * Fix issue that OPUS and VORBIS channel layouts are wrong for 3, 5, 6, 7 + and 8 channels + ([#8396](https://github.com/google/ExoPlayer/issues/8396)). + * Fix issue where track selections after seek to zero in a live stream + incorrectly let the stream start at its default position + ([#9347](https://github.com/google/ExoPlayer/issues/9347)). + * Fix the issue where new instances of `CmcdData.Factory` were receiving + negative values for `bufferedDurationUs` from chunk sources, resulting + in an `IllegalArgumentException` + ([#888](https://github.com/androidx/media/issues/888)). +* Transformer: + * Work around an issue where the encoder would throw at configuration time + due to setting a high operating rate. +* Extractors: + * Mark secondary (unplayable) HEVC tracks in JPEG motion photos as + `ROLE_FLAG_ALTERNATE` to prevent them being automatically selected for + playback because of their higher resolution. + * Fix wrong keyframe detection for TS H264 streams + ([#864](https://github.com/androidx/media/pull/864)). + * Fix duration estimation of TS streams that are longer than 47721 seconds + ([#855](https://github.com/androidx/media/issues/855)). +* Audio: + * Fix handling of EOS for `SilenceSkippingAudioProcessor` when called + multiple times ([#712](https://github.com/androidx/media/issues/712)). +* Video: + * Add workaround for a device issue on Galaxy Tab S7 FE, Chromecast with + Google TV, and Lenovo M10 FHD Plus that causes 60fps AVC streams to be + marked as unsupported + ([#693](https://github.com/androidx/media/issues/693)). +* Metadata: + * Fix bug where `MediaMetadata` was only populated from Vorbis comments + with upper-case keys + ([#876](https://github.com/androidx/media/issues/876)). + * Catch `OutOfMemoryError` when parsing very large ID3 frames, meaning + playback can continue without the tag info instead of playback failing + completely. +* DRM: + * Extend workaround for spurious ClearKey `https://default.url` license + URL to API 33+ (previously the workaround only applied on API 33 + exactly) ([#837](https://github.com/androidx/media/pull/837)). + * Fix `ERROR_DRM_SESSION_NOT_OPENED` when switching from encrypted to + clear content without a surface attached to the player. The error was + due to incorrectly using a secure decoder to play the clear content. +* Session: + * Put the custom keys and values in `MediaMetadataCompat` to + `MediaMetadata.extras` and `MediaMetadata.extras` to + `MediaMetadataCompat` + ([#756](https://github.com/androidx/media/issues/756), + [#802](https://github.com/androidx/media/issues/802)). + * Fix broadcasting `notifyChildrenChanged` for legacy controllers + ([#644](https://github.com/androidx/media/issues/644)). + * Fix a bug where setting a negative time for a disabled `setWhen` timer + of the notification caused a crash on some devices + ([#903](https://github.com/androidx/media/issues/903)). + * Fix `IllegalStateException` when the media notification controller + hasn't completed connecting when the first notification update is + requested ([#917](https://github.com/androidx/media/issues/917)). +* UI: + * Fix issue where forward and rewind buttons are not visible when used + with Material Design in a BottomSheetDialogFragment + ([#511](https://github.com/androidx/media/issues/511)). + * Fix issue where the numbers in the fast forward button of the + `PlayerControlView` were misaligned + ([#547](https://github.com/androidx/media/issues/547)). +* DASH Extension: + * Parse "f800" as channel count of 5 for Dolby in DASH manifest + ([#688](https://github.com/androidx/media/issues/688)). +* Decoder Extensions (FFmpeg, VP9, AV1, MIDI, etc.): + * MIDI: Fix issue where seeking forward skips the Program Change events + ([#704](https://github.com/androidx/media/issues/704)). + * Migrate to FFmpeg 6.0 and update supported NDK to `r26b` + ([#707](https://github.com/androidx/media/pull/707), + [#867](https://github.com/androidx/media/pull/867)). +* Cast Extension: + * Sanitize creation of a `Timeline` to not crash the app when loading + media fails on the cast device + ([#708](https://github.com/androidx/media/issues/708)). + +### 1.2.0 (2023-11-15) + +This release includes the following changes since the +[1.1.1 release](#111-2023-08-14): + +* Common Library: + * Add a `@Nullable Throwable` parameter to the methods in the `Log.Logger` + interface. The `message` parameter to these methods no longer contains + any information about the `Throwable` passed to the `Log.{d,i,w,e}()` + methods, so implementations will need to manually append this + information if desired (possibly using + `Logger.appendThrowableString(String, Throwable)`). + * Fix Kotlin compatibility issue where nullable generic type parameters + and nullable array element types are not detected as nullable. Examples + are `TrackSelectorResult` and `SimpleDecoder` method parameters + ([#6792](https://github.com/google/ExoPlayer/issues/6792)). + * Change default UI and notification behavior in + `Util.shouldShowPlayButton` to show a "play" button while playback is + temporarily suppressed (e.g. due to transient audio focus loss). The + legacy behavior can be maintained by using + `PlayerView.setShowPlayButtonIfPlaybackIsSuppressed(false)` or + `MediaSession.Builder.setShowPlayButtonIfPlaybackIsSuppressed(false)` + ([#11213](https://github.com/google/ExoPlayer/issues/11213)). + * Upgrade `androidx.annotation:annotation-experimental` to `1.3.1` to fix + https://issuetracker.google.com/251172715. + * Move `ExoPlayer.setAudioAttributes` to the `Player` interface. +* ExoPlayer: + * Fix seeking issues in AC4 streams caused by not identifying decode-only + samples correctly + ([#11000](https://github.com/google/ExoPlayer/issues/11000)). + * Add suppression of playback on unsuitable audio output devices (e.g. the + built-in speaker on Wear OS devices) when this feature is enabled via + `ExoPlayer.Builder.setSuppressPlaybackOnUnsuitableOutput`. The playback + suppression reason will be updated as + `Player.PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT` if playback + is attempted when no suitable audio outputs are available, or if all + suitable outputs are disconnected during playback. The suppression + reason will be removed when a suitable output is connected. + * Add `MediaSource.canUpdateMediaItem` and `MediaSource.updateMediaItem` + to accept `MediaItem` updates after creation via + `Player.replaceMediaItem(s)`. + * Allow `MediaItem` updates for all `MediaSource` classes provided by the + library via `Player.replaceMediaItem(s)` + ([#33](https://github.com/androidx/media/issues/33), + [#9978](https://github.com/google/ExoPlayer/issues/9978)). + * Rename `MimeTypes.TEXT_EXOPLAYER_CUES` to + `MimeTypes.APPLICATION_MEDIA3_CUES`. + * Add `PngExtractor` that sends and reads a whole PNG file into the + `TrackOutput` as one sample. + * Enhance `SequenceableLoader.continueLoading(long)` method in the + `SequenceableLoader` interface to + `SequenceableLoader.continueLoading(LoadingInfo loadingInfo)`. + `LoadingInfo` contains additional parameters, including `playbackSpeed` + and `lastRebufferRealtimeMs` in addition to the existing + `playbackPositionUs`. + * Enhance `ChunkSource.getNextChunk(long, long, List, ChunkHolder)` method + in the `ChunkSource` interface to `ChunkSource.getNextChunk(LoadingInfo, + long, List, ChunkHolder)`. + * Add additional fields to Common Media Client Data (CMCD) logging: buffer + starvation (`bs`), deadline (`dl`), playback rate (`pr`) and startup + (`su`) ([#8699](https://github.com/google/ExoPlayer/issues/8699)). + * Add luma and chroma bitdepth to `ColorInfo` + ([#491](https://github.com/androidx/media/pull/491)). + * Add additional fields to Common Media Client Data (CMCD) logging: next + object request (`nor`) and next range request (`nrr`) + ([#8699](https://github.com/google/ExoPlayer/issues/8699)). + * Add functionality to transmit Common Media Client Data (CMCD) data using + query parameters ([#553](https://github.com/androidx/media/issues/553)). + * Fix `ConcurrentModificationException` in `ExperimentalBandwidthMeter` + ([#612](https://github.com/androidx/media/issues/612)). + * Add `MediaPeriodId` parameter to + `CompositeMediaSource.getMediaTimeForChildMediaTime`. + * Support `ClippingMediaSource` (and other sources with period/window time + offsets) in `ConcatenatingMediaSource2` + ([#11226](https://github.com/google/ExoPlayer/issues/11226)). + * Change `BaseRenderer.onStreamChanged()` to also receive a + `MediaPeriodId` argument. +* Transformer: + * Parse EXIF rotation data for image inputs. + * Remove `TransformationRequest.HdrMode` annotation type and its + associated constants. Use `Composition.HdrMode` and its associated + constants instead. + * Simplify the `OverlaySettings` to fix rotation issues. + * Changed `frameRate` and `durationUs` parameters of + `SampleConsumer.queueInputBitmap` to `TimestampIterator`. +* Track Selection: + * Add `DefaultTrackSelector.Parameters.allowAudioNonSeamlessAdaptiveness` + to explicitly allow or disallow non-seamless adaptation. The default + stays at its current behavior of `true`. +* Extractors: + * MPEG-TS: Ensure the last frame is rendered by passing the last access + unit of a stream to the sample queue + ([#7909](https://github.com/google/ExoPlayer/issues/7909)). + * Fix typo when determining `rotationDegrees`. Changed + `projectionPosePitch` to `projectionPoseRoll` + ([#461](https://github.com/androidx/media/pull/461)). + * Remove the assumption that `Extractor` instances can be directly + inspected with `instanceof`. If you want runtime access to the + implementation details of an `Extractor` you must first call + `Extractor.getUnderlyingInstance`. + * Add `BmpExtractor`. + * Add `WebpExtractor`. + * Add `HeifExtractor`. + * Add + [QuickTime classic](https://developer.apple.com/standards/qtff-2001.pdf) + support to `Mp4Extractor`. +* Audio: + * Add support for 24/32-bit big-endian PCM in MP4 and Matroska, and parse + PCM encoding for `lpcm` in MP4. + * Add support for extracting Vorbis audio in MP4. + * Add `AudioSink.getFormatOffloadSupport(Format)` that retrieves level of + offload support the sink can provide for the format through a + `DefaultAudioOffloadSupportProvider`. It returns the new + `AudioOffloadSupport` that contains `isFormatSupported`, + `isGaplessSupported`, and `isSpeedChangeSupported`. + * Add `AudioSink.setOffloadMode()` through which the offload configuration + on the audio sink is configured. Default is + `AudioSink.OFFLOAD_MODE_DISABLED`. + * Offload can be enabled through `setAudioOffloadPreference` in + `TrackSelectionParameters`. If the set preference is to enable, the + device supports offload for the format, and the track selection is a + single audio track, then audio offload will be enabled. + * If `audioOffloadModePreference` is set to + `AUDIO_OFFLOAD_MODE_PREFERENCE_REQUIRED`, then the + `DefaultTrackSelector` will only select an audio track and only if that + track's format is supported in offload. If no audio track is supported + in offload, then no track will be selected. + * Disabling gapless support for offload when pre-API level 33 due to + playback position issue after track transition. + * Remove parameter `enableOffload` from + `DefaultRenderersFactory.buildAudioSink` method signature. + * Remove method `DefaultAudioSink.Builder.setOffloadMode`. + * Remove intdef value + `DefaultAudioSink.OffloadMode.OFFLOAD_MODE_ENABLED_GAPLESS_DISABLED`. + * Add support for Opus gapless metadata during offload playback. + * Allow renderer recovery by disabling offload if failed at first write + ([#627](https://github.com/androidx/media/issues/627)). + * Enable Offload Scheduling by default for audio-only offloaded playback. + * Delete `ExoPlayer.experimentalSetOffloadSchedulingEnabled` and + `AudioOffloadListener.onExperimentalOffloadSchedulingEnabledChanged`. + * Renamed `onExperimentalSleepingForOffloadChanged` as + `onSleepingForOffloadChanged` and `onExperimentalOffloadedPlayback` as + `onOffloadedPlayback`. + * Move audio offload mode related `TrackSelectionParameters` interfaces + and definitions to an inner `AudioOffloadPreferences` class. + * Add `onAudioTrackInitialized` and `onAudioTrackReleased` callbacks to + `AnalyticsListener`, `AudioRendererEventListener` and + `AudioSink.Listener`. + * Fix DTS Express audio buffer underflow issue + ([#650](https://github.com/androidx/media/pull/650)). + * Fix bug where the capabilities check for E-AC3-JOC throws an + `IllegalArgumentException` + ([#677](https://github.com/androidx/media/issues/677)). +* Video: + * Allow `MediaCodecVideoRenderer` to use a custom + `VideoFrameProcessor.Factory`. + * Fix bug where the first frame couldn't be rendered if the audio stream + starts with negative timestamps + ([#291](https://github.com/androidx/media/issues/291)). +* Text: + * Remove `ExoplayerCuesDecoder`. Text tracks with `sampleMimeType = + application/x-media3-cues` are now directly handled by `TextRenderer` + without needing a `SubtitleDecoder` instance. +* Metadata: + * `MetadataDecoder.decode` will no longer be called for "decode-only" + samples as the implementation must return null anyway. +* Effect: + * Add `VideoFrameProcessor.queueInputBitmap(Bitmap, Iterator)` + queuing bitmap input by timestamp. + * Change `VideoFrameProcessor.registerInputStream()` to be non-blocking. + Apps must implement + `VideoFrameProcessor.Listener#onInputStreamRegistered()`. + * Changed `frameRate` and `durationUs` parameters of + `VideoFrameProcessor.queueInputBitmap` to `TimestampIterator`. +* IMA extension: + * Fix bug where a multi-period DASH live stream that is not the first item + in a playlist can throw an exception + ([#571](https://github.com/androidx/media/issues/571)). + * Release StreamManager before calling `AdsLoader.destroy()` + * Bump IMA SDK version to 3.31.0. +* Session: + * Set the notifications foreground service behavior to + `FOREGROUND_SERVICE_IMMEDIATE` in `DefaultMediaNotificationProvider` + ([#167](https://github.com/androidx/media/issues/167)). + * Use only + `android.media.session.MediaSession.setMediaButtonBroadcastReceiver()` + above API 31 to avoid problems with deprecated API on Samsung devices + ([#167](https://github.com/androidx/media/issues/167)). + * Use the media notification controller as proxy to set available commands + and custom layout used to populate the notification and the platform + session. + * Convert media button events that are received by + `MediaSessionService.onStartCommand()` within Media3 instead of routing + them to the platform session and back to Media3. With this, the caller + controller is always the media notification controller and apps can + easily recognize calls coming from the notification in the same way on + all supported API levels. + * Fix bug where `MediaController.getCurrentPosition()` is not advancing + when connected to a legacy `MediaSessionCompat`. + * Add `MediaLibrarySession.getSubscribedControllers(mediaId)` for + convenience. + * Override `MediaLibrarySession.Callback.onSubscribe()` to assert the + availability of the parent ID for which the controller subscribes. If + successful, the subscription is accepted and `notifyChildrenChanged()` + is called immediately to inform the browser + ([#561](https://github.com/androidx/media/issues/561)). + * Add session demo module for Automotive OS and enable session demo for + Android Auto. + * Do not set the queue of the framework session when + `COMMAND_GET_TIMELINE` is not available for the media notification + controller. With Android Auto as the client controller reading from the + framework session, this has the effect that the `queue` button in the UI + of Android Auto is not displayed + ([#339](https://github.com/androidx/media/issues/339)). + * Use `DataSourceBitmapLoader` by default instead of `SimpleBitmapLoader` + ([#271](https://github.com/androidx/media/issues/271), + [#327](https://github.com/androidx/media/issues/327)). + * Add `MediaSession.Callback.onMediaButtonEvent(Intent)` that allows apps + to override the default media button event handling. +* UI: + * Add a `Player.Listener` implementation for Wear OS devices that handles + playback suppression due to + `Player.PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT` by + launching a system dialog to allow a user to connect a suitable audio + output (e.g. bluetooth headphones). The listener will auto-resume + playback if a suitable device is connected within a configurable timeout + (default is 5 minutes). +* Downloads: + * Declare "data sync" foreground service type for `DownloadService` for + Android 14 compatibility. When using this service, the app also needs to + add `dataSync` as `foregroundServiceType` in the manifest and add the + `FOREGROUND_SERVICE_DATA_SYNC` permission + ([#11239](https://github.com/google/ExoPlayer/issues/11239)). +* HLS Extension: + * Refresh the HLS live playlist with an interval calculated from the last + load start time rather than the last load completed time + ([#663](https://github.com/androidx/media/issues/663)). +* DASH Extension: + * Allow multiple of the same DASH identifier in segment template url. + * Add experimental support for parsing subtitles during extraction. This + has better support for merging overlapping subtitles, including + resolving flickering when transitioning between subtitle segments. You + can enable this using + `DashMediaSource.Factory.experimentalParseSubtitlesDuringExtraction()` + ([#288](https://github.com/androidx/media/issues/288)). +* RTSP Extension: + * Fix a race condition that could lead to `IndexOutOfBoundsException` when + falling back to TCP, or playback hanging in some situations. + * Check state in RTSP setup when returning loading state of + `RtspMediaPeriod` + ([#577](https://github.com/androidx/media/issues/577)). + * Ignore custom Rtsp request methods in Options response public header + ([#613](https://github.com/androidx/media/issues/613)). + * Use RTSP Setup Response timeout value in time interval of sending + keep-alive RTSP Options requests + ([#662](https://github.com/androidx/media/issues/662)). +* Decoder Extensions (FFmpeg, VP9, AV1, MIDI, etc.): + * Release the MIDI decoder module, which provides support for playback of + standard MIDI files using the Jsyn library to synthesize audio. + * Add `DecoderOutputBuffer.shouldBeSkipped` to directly mark output + buffers that don't need to be presented. This is preferred over + `C.BUFFER_FLAG_DECODE_ONLY` that will be deprecated. + * Add `Decoder.setOutputStartTimeUs` and + `SimpleDecoder.isAtLeastOutputStartTimeUs` to allow decoders to drop + decode-only samples before the start time. This should be preferred to + `Buffer.isDecodeOnly` that will be deprecated. + * Fix bug publishing MIDI decoder artifact to Maven repository. The + artifact is renamed to `media3-exoplayer-midi` + ([#734](https://github.com/androidx/media/issues/734)). +* Leanback extension: + * Fix bug where disabling a surface can cause an `ArithmeticException` in + Leanback code ([#617](https://github.com/androidx/media/issues/617)). +* Test Utilities: + * Make `TestExoPlayerBuilder` and `FakeClock` compatible with Espresso UI + tests and Compose UI tests. This fixes a bug where playback advances + non-deterministically during Espresso or Compose view interactions. +* Remove deprecated symbols: + * Remove + `TransformationRequest.Builder.setEnableRequestSdrToneMapping(boolean)` + and + `TransformationRequest.Builder.experimental_setEnableHdrEditing(boolean)`. + Use `Composition.Builder.setHdrMode(int)` and pass the `Composition` to + `Transformer.start(Composition, String)` instead. + * Remove deprecated `DownloadNotificationHelper.buildProgressNotification` + method, use a non deprecated method that takes a `notMetRequirements` + parameter instead. + +### 1.2.0-rc01 (2023-11-01) + +Use the 1.2.0 [stable version](#120-2023-11-15). + +### 1.2.0-beta01 (2023-10-18) + +Use the 1.2.0 [stable version](#120-2023-11-15). + +### 1.2.0-alpha02 (2023-09-29) + +Use the 1.2.0 [stable version](#120-2023-11-15). + +### 1.2.0-alpha01 (2023-08-17) + +Use the 1.2.0 [stable version](#120-2023-11-15). + +## 1.1 + +### 1.1.1 (2023-08-14) + +This release corresponds to the +[ExoPlayer 2.19.1 release](https://github.com/google/ExoPlayer/releases/tag/r2.19.1). + +This release includes the following changes since the +[1.1.0 release](#110-2023-07-05): + +* Common Library: + * Remove accidentally added `multidex` dependency from all modules + ([#499](https://github.com/androidx/media/issues/499)). +* ExoPlayer: + * Fix issue in `PlaybackStatsListener` where spurious `PlaybackStats` are + created after the playlist is cleared. + * Add additional fields to Common Media Client Data (CMCD) logging: + streaming format (sf), stream type (st), version (v), top birate (tb), + object duration (d), measured throughput (mtp) and object type (ot) + ([#8699](https://github.com/google/ExoPlayer/issues/8699)). +* Audio: + * Fix a bug where `Player.getState()` never transitioned to `STATE_ENDED` + when playing very short files + ([#538](https://github.com/androidx/media/issues/538)). +* Audio Offload: + * Prepend Ogg ID Header and Comment Header Pages to bitstream for + offloaded Opus playback in accordance with RFC 7845. +* Video: + * H.265/HEVC: Fix parsing SPS short and long term reference picture info. +* Text: + * CEA-608: Change cue truncation logic to only consider visible text. + Previously indent and tab offset were included when limiting the cue + length to 32 characters (which was technically correct by the spec) + ([#11019](https://github.com/google/ExoPlayer/issues/11019)). +* IMA extension: + * Bump IMA SDK version to 3.30.3. +* Session: + * Add custom layout to the state of the controller and provide a getter to + access it. When the custom layout changes, + `MediaController.Listener.onCustomLayoutChanged` is called. Apps that + want to send different custom layouts to different Media3 controller can + do this in `MediaSession.Callback.onConnect` by using an + `AcceptedResultBuilder` to make sure the custom layout is available to + the controller when connection completes. + * Fix cases where `MediaLibraryServiceLegacyStub` sent an error to a + `Result` that didn't support this which produced an + `UnsupportedOperationException` + ([#78](https://github.com/androidx/media/issues/78)). + * Fix the way `PlayerWrapper` creates a `VolumeProviderCompat` by + determining `volumeControlType` through both legacy commands + (`COMMAND_ADJUST_DEVICE_VOLUME` and `COMMAND_SET_DEVICE_VOLUME`) and new + commands (`COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS` and + `COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS`) + ([#554](https://github.com/androidx/media/issues/554)). + +### 1.1.0 (2023-07-05) + +This release corresponds to the +[ExoPlayer 2.19.0 release](https://github.com/google/ExoPlayer/releases/tag/r2.19.0). + +This release contains the following changes since the +[1.0.2 release](#102-2023-05-18): + +* Common Library: + * Add suppression reason for unsuitable audio route and play when ready + change reason for suppressed too long. + ([#15](https://github.com/androidx/media/issues/15)). + * Add commands to Player: + * `COMMAND_GET_METADATA` + * `COMMAND_SET_PLAYLIST_METADATA` + * `COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS` + * `COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS` + * Add overloaded methods to Player which allow users to specify volume + flags: + * `void setDeviceVolume(int, int)` + * `void increaseDeviceVolume(int)` + * `void decreaseDeviceVolume(int)` + * `void setDeviceMuted(boolean, int)` + * Add `Builder` for `DeviceInfo` and deprecate existing constructor. + * Add `DeviceInfo.routingControllerId` to specify the routing controller + ID for remote playbacks. + * Add `Player.replaceMediaItem(s)` as a shortcut to adding and removing + items at the same position + ([#8046](https://github.com/google/ExoPlayer/issues/8046)). +* ExoPlayer: + * Allow ExoPlayer to have control of device volume methods only if + explicitly opted in. Use + `ExoPlayer.Builder.setDeviceVolumeControlEnabled` to have access to: + * `getDeviceVolume()` + * `isDeviceMuted()` + * `setDeviceVolume(int)` and `setDeviceVolume(int, int)` + * `increaseDeviceVolume(int)` and `increaseDeviceVolume(int, int)` + * `decreaseDeviceVolume(int)` and `decreaseDeviceVolume(int, int)` + * Add `FilteringMediaSource` that allows to filter available track types + from a `MediaSource`. + * Add support for including Common Media Client Data (CMCD) in the + outgoing requests of adaptive streaming formats DASH, HLS, and + SmoothStreaming. The following fields, `br`, `bl`, `cid`, `rtp`, and + `sid`, have been incorporated + ([#8699](https://github.com/google/ExoPlayer/issues/8699)). API + structure and API methods: + * CMCD logging is disabled by default, use + `MediaSource.Factory.setCmcdConfigurationFactory(CmcdConfiguration.Factory + cmcdConfigurationFactory)` to enable it. + * All keys are enabled by default, override + `CmcdConfiguration.RequestConfig.isKeyAllowed(String key)` to filter + out which keys are logged. + * Override `CmcdConfiguration.RequestConfig.getCustomData()` to enable + custom key logging. + * Add additional action to manifest of main demo to make it easier to + start the demo app with a custom `*.exolist.json` file + ([#439](https://github.com/androidx/media/pull/439)). + * Add `ExoPlayer.setVideoEffects()` for using `Effect` during video + playback. + * Update `SampleQueue` to store `sourceId` as a `long` rather than an + `int`. This changes the signatures of public methods + `SampleQueue.sourceId` and `SampleQueue.peekSourceId`. + * Add parameters to `LoadControl` methods `shouldStartPlayback` and + `onTracksSelected` that allow associating these methods with the + relevant `MediaPeriod`. + * Change signature of + `ServerSideAdInsertionMediaSource.setAdPlaybackStates(Map)` by adding a timeline parameter that contains the + periods with the UIDs used as keys in the map. This is required to avoid + concurrency issues with multi-period live streams. + * Deprecate `EventDispatcher.withParameters(int windowIndex, @Nullable + MediaPeriodId mediaPeriodId, long mediaTimeOffsetMs)` and + `BaseMediaSource.createEventDispatcher(..., long mediaTimeOffsetMs)`. + The variant of the methods without the `mediaTimeOffsetUs` can be called + instead. Note that even for the deprecated variants, the offset is not + anymore added to `startTimeUs` and `endTimeUs` of the `MediaLoadData` + objects that are dispatched by the dispatcher. + * Rename `ExoTrackSelection.blacklist` to `excludeTrack` and + `isBlacklisted` to `isTrackExcluded`. + * Fix inconsistent behavior between `ExoPlayer.setMediaItem(s)` and + `addMediaItem(s)` when called on an empty playlist. +* Transformer: + * Remove `Transformer.Builder.setMediaSourceFactory(MediaSource.Factory)`. + Use `ExoPlayerAssetLoader.Factory(MediaSource.Factory)` and + `Transformer.Builder.setAssetLoaderFactory(AssetLoader.Factory)` + instead. + * Remove `Transformer.startTransformation(MediaItem, + ParcelFileDescriptor)`. + * Fix a bug where transformation could get stuck (leading to muxer + timeout) if the end of the video stream was signaled at the moment when + an input frame was pending processing. + * Query codecs via `MediaCodecList` instead of using + `findDecoder/EncoderForFormat` utilities, to expand support. + * Remove B-frame configuration in `DefaultEncoderFactory` because it + doesn't work on some devices. +* Track selection: + * Add + `DefaultTrackSelector.Parameters.allowInvalidateSelectionsForRendererCapabilitiesChange` + which is disabled by default. When enabled, the `DefaultTrackSelector` + will trigger a new track selection when the renderer capabilities + changed. +* Extractors: + * Ogg: Fix bug when seeking in files with a long duration + ([#391](https://github.com/androidx/media/issues/391)). + * FMP4: Fix issue where `TimestampAdjuster` initializes a wrong timestamp + offset with metadata sample time from emsg atom + ([#356](https://github.com/androidx/media/issues/356)). +* Audio: + * Fix bug where some playbacks fail when tunneling is enabled and + `AudioProcessors` are active, e.g. for gapless trimming + ([#10847](https://github.com/google/ExoPlayer/issues/10847)). + * Encapsulate Opus frames in Ogg packets in direct playbacks (offload). + * Extrapolate current position during sleep with offload scheduling. + * Add `Renderer.release()` and `AudioSink.release()` for releasing the + resources at the end of player's lifecycle. + * Listen to audio capabilities changes in `DefaultAudioSink`. Add a + required parameter `context` in the constructor of `DefaultAudioSink`, + with which the `DefaultAudioSink` will register as the listener to the + `AudioCapabilitiesReceiver` and update its `audioCapabilities` property + when informed with a capabilities change. + * Propagate audio capabilities changes via a new event + `onAudioCapabilitiesChanged` in `AudioSink.Listener` interface, and a + new interface `RendererCapabilities.Listener` which triggers + `onRendererCapabilitiesChanged` events. + * Add `ChannelMixingAudioProcessor` for applying scaling/mixing to audio + channels. + * Add new int value `DISCARD_REASON_AUDIO_BYPASS_POSSIBLE` to + `DecoderDiscardReasons` to discard audio decoder when bypass mode is + possible after audio capabilities change. + * Add direct playback support for DTS Express and DTS:X + ([#335](https://github.com/androidx/media/pull/335)). +* Video: + * Make `MediaCodecVideoRenderer` report a `VideoSize` with a width and + height of 0 when the renderer is disabled. + `Player.Listener.onVideoSizeChanged` is called accordingly when + `Player.getVideoSize()` changes. With this change, ExoPlayer's video + size with `MediaCodecVideoRenderer` has a width and height of 0 when + `Player.getCurrentTracks` does not support video, or the size of the + supported video track is not yet determined. +* DRM: + * Reduce the visibility of several internal-only methods on + `DefaultDrmSession` that aren't expected to be called from outside the + DRM package: + * `void onMediaDrmEvent(int)` + * `void provision()` + * `void onProvisionCompleted()` + * `onProvisionError(Exception, boolean)` +* Muxer: + * Add a new muxer library which can be used to create an MP4 container + file. +* IMA extension: + * Enable multi-period live DASH streams for DAI. Please note that the + current implementation does not yet support seeking in live streams + ([#10912](https://github.com/google/ExoPlayer/issues/10912)). + * Fix a bug where a new ad group is inserted in live streams because the + calculated content position in consecutive timelines varies slightly. +* Session: + * Add helper method `MediaSession.getControllerForCurrentRequest` to + obtain information about the controller that is currently calling + a`Player` method. + * Add `androidx.media3.session.MediaButtonReceiver` to enable apps to + implement playback resumption with media button events sent by, for + example, a Bluetooth headset + ([#167](https://github.com/androidx/media/issues/167)). + * Add default implementation to `MediaSession.Callback.onAddMediaItems` to + allow requested `MediaItems` to be passed onto `Player` if they have + `LocalConfiguration` (e.g. URI) + ([#282](https://github.com/androidx/media/issues/282)). + * Add "seek to previous" and "seek to next" command buttons on compact + media notification view by default for Android 12 and below + ([#410](https://github.com/androidx/media/issues/410)). +* UI: + * Add Util methods `shouldShowPlayButton` and + `handlePlayPauseButtonAction` to write custom UI elements with a + play/pause button. +* RTSP Extension: + * For MPEG4-LATM, use default profile-level-id value if absent in Describe + Response SDP message + ([#302](https://github.com/androidx/media/issues/302)). + * Use base Uri for relative path resolution from the RTSP session if + present in DESCRIBE response header + ([#11160](https://github.com/google/ExoPlayer/issues/11160)). +* DASH Extension: + * Remove the media time offset from `MediaLoadData.startTimeMs` and + `MediaLoadData.endTimeMs` for multi period DASH streams. + * Fix a bug where re-preparing a multi-period live Dash media source + produced a `IndexOutOfBoundsException` + ([#10838](https://github.com/google/ExoPlayer/issues/10838)). +* HLS Extension: + * Add + `HlsMediaSource.Factory.setTimestampAdjusterInitializationTimeoutMs(long)` + to set a timeout for the loading thread to wait for the + `TimestampAdjuster` to initialize. If the initialization doesn't + complete before the timeout, a `PlaybackException` is thrown to avoid + the playback endless stalling. The timeout is set to zero by default + ([#323](https://github.com/androidx/media/issues//323)). +* Test Utilities: + * Check for URI scheme case insensitivity in `DataSourceContractTest`. +* Remove deprecated symbols: + * Remove `DefaultAudioSink` constructors, use `DefaultAudioSink.Builder` + instead. + * Remove `HlsMasterPlaylist`, use `HlsMultivariantPlaylist` instead. + * Remove `Player.stop(boolean)`. Use `Player.stop()` and + `Player.clearMediaItems()` (if `reset` is `true`) instead. + * Remove two deprecated `SimpleCache` constructors, use a non-deprecated + constructor that takes a `DatabaseProvider` instead for better + performance. + * Remove `DefaultBandwidthMeter` constructor, use + `DefaultBandwidthMeter.Builder` instead. + * Remove `DefaultDrmSessionManager` constructors, use + `DefaultDrmSessionManager.Builder` instead. + * Remove two deprecated `HttpDataSource.InvalidResponseCodeException` + constructors, use a non-deprecated constructor that accepts additional + fields(`cause`, `responseBody`) to enhance error logging. + * Remove `DownloadHelper.forProgressive`, `DownloadHelper.forHls`, + `DownloadHelper.forDash`, and `DownloadHelper.forSmoothStreaming`, use + `DownloadHelper.forMediaItem` instead. + * Remove deprecated `DownloadService` constructor, use a non deprecated + constructor that includes the option to provide a + `channelDescriptionResourceId` parameter. + * Remove deprecated String constants for Charsets (`ASCII_NAME`, + `UTF8_NAME`, `ISO88591_NAME`, `UTF16_NAME` and `UTF16LE_NAME`), use + Kotlin Charsets from the `kotlin.text` package, the + `java.nio.charset.StandardCharsets` or the + `com.google.common.base.Charsets` instead. + * Remove deprecated `WorkManagerScheduler` constructor, use a non + deprecated constructor that includes the option to provide a `Context` + parameter instead. + * Remove the deprecated methods `createVideoSampleFormat`, + `createAudioSampleFormat`, `createContainerFormat`, and + `createSampleFormat`, which were used to instantiate the `Format` class. + Instead use `Format.Builder` for creating instances of `Format`. + * Remove the deprecated methods `copyWithMaxInputSize`, + `copyWithSubsampleOffsetUs`, `copyWithLabel`, + `copyWithManifestFormatInfo`, `copyWithGaplessInfo`, + `copyWithFrameRate`, `copyWithDrmInitData`, `copyWithMetadata`, + `copyWithBitrate` and `copyWithVideoSize`, use `Format.buildUpon()` and + setter methods instead. + * Remove deprecated `ExoPlayer.retry()`, use `prepare()` instead. + * Remove deprecated zero-arg `DefaultTrackSelector` constructor, use + `DefaultTrackSelector(Context)` instead. + * Remove deprecated `OfflineLicenseHelper` constructor, use + `OfflineLicenseHelper(DefaultDrmSessionManager, + DrmSessionEventListener.EventDispatcher)` instead. + * Remove deprecated `DownloadManager` constructor, use the constructor + that takes an `Executor` instead. + * Remove deprecated `Cue` constructors, use `Cue.Builder` instead. + * Remove deprecated `OfflineLicenseHelper` constructor, use + `OfflineLicenseHelper(DefaultDrmSessionManager, + DrmSessionEventListener.EventDispatcher)` instead. + * Remove four deprecated `AnalyticsListener` methods: + * `onDecoderEnabled`, use `onAudioEnabled` and/or `onVideoEnabled` + instead. + * `onDecoderInitialized`, use `onAudioDecoderInitialized` and/or + `onVideoDecoderInitialized` instead. + * `onDecoderInputFormatChanged`, use `onAudioInputFormatChanged` + and/or `onVideoInputFormatChanged` instead. + * `onDecoderDisabled`, use `onAudioDisabled` and/or `onVideoDisabled` + instead. + * Remove the deprecated `Player.Listener.onSeekProcessed` and + `AnalyticsListener.onSeekProcessed`, use `onPositionDiscontinuity` with + `DISCONTINUITY_REASON_SEEK` instead. + * Remove `ExoPlayer.setHandleWakeLock(boolean)`, use `setWakeMode(int)` + instead. + * Remove deprecated + `DefaultLoadControl.Builder.createDefaultLoadControl()`, use `build()` + instead. + * Remove deprecated `MediaItem.PlaybackProperties`, use + `MediaItem.LocalConfiguration` instead. Deprecated field + `MediaItem.playbackProperties` is now of type + `MediaItem.LocalConfiguration`. + +### 1.1.0-rc01 (2023-06-21) + +Use the 1.1.0 [stable version](#110-2023-07-05). + +### 1.1.0-beta01 (2023-06-07) + +Use the 1.1.0 [stable version](#110-2023-07-05). + +### 1.1.0-alpha01 (2023-05-10) + +Use the 1.1.0 [stable version](#110-2023-07-05). + +## 1.0 + ### 1.0.2 (2023-05-18) This release corresponds to the diff --git a/api.txt b/api.txt index c2f1a94d1a..946ed8a3d1 100644 --- a/api.txt +++ b/api.txt @@ -1,4 +1,4 @@ -// Signature format: 3.0 +// Signature format: 2.0 package androidx.media3.common { public final class AdOverlayInfo { @@ -26,7 +26,7 @@ package androidx.media3.common { } public final class AudioAttributes { - method @RequiresApi(21) public androidx.media3.common.AudioAttributes.AudioAttributesV21 getAudioAttributesV21(); + method public androidx.media3.common.AudioAttributes.AudioAttributesV21 getAudioAttributesV21(); field public static final androidx.media3.common.AudioAttributes DEFAULT; field @androidx.media3.common.C.AudioAllowedCapturePolicy public final int allowedCapturePolicy; field @androidx.media3.common.C.AudioContentType public final int contentType; @@ -35,7 +35,7 @@ package androidx.media3.common { field @androidx.media3.common.C.AudioUsage public final int usage; } - @RequiresApi(21) public static final class AudioAttributes.AudioAttributesV21 { + public static final class AudioAttributes.AudioAttributesV21 { field public final android.media.AudioAttributes audioAttributes; } @@ -79,6 +79,7 @@ package androidx.media3.common { field public static final java.util.UUID PLAYREADY_UUID; field public static final float RATE_UNSET = -3.4028235E38f; field public static final int ROLE_FLAG_ALTERNATE = 2; // 0x2 + field public static final int ROLE_FLAG_AUXILIARY = 32768; // 0x8000 field public static final int ROLE_FLAG_CAPTION = 64; // 0x40 field public static final int ROLE_FLAG_COMMENTARY = 8; // 0x8 field public static final int ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND = 1024; // 0x400 @@ -127,6 +128,11 @@ package androidx.media3.common { field public static final int USAGE_VOICE_COMMUNICATION = 2; // 0x2 field public static final int USAGE_VOICE_COMMUNICATION_SIGNALLING = 3; // 0x3 field public static final java.util.UUID UUID_NIL; + field public static final int VOLUME_FLAG_ALLOW_RINGER_MODES = 2; // 0x2 + field public static final int VOLUME_FLAG_PLAY_SOUND = 4; // 0x4 + field public static final int VOLUME_FLAG_REMOVE_SOUND_AND_VIBRATE = 8; // 0x8 + field public static final int VOLUME_FLAG_SHOW_UI = 1; // 0x1 + field public static final int VOLUME_FLAG_VIBRATE = 16; // 0x10 field public static final int WAKE_MODE_LOCAL = 1; // 0x1 field public static final int WAKE_MODE_NETWORK = 2; // 0x2 field public static final int WAKE_MODE_NONE = 0; // 0x0 @@ -151,7 +157,7 @@ package androidx.media3.common { @IntDef(open=true, value={androidx.media3.common.C.CRYPTO_TYPE_UNSUPPORTED, androidx.media3.common.C.CRYPTO_TYPE_NONE, androidx.media3.common.C.CRYPTO_TYPE_FRAMEWORK}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE_USE) public static @interface C.CryptoType { } - @IntDef(flag=true, value={androidx.media3.common.C.ROLE_FLAG_MAIN, androidx.media3.common.C.ROLE_FLAG_ALTERNATE, androidx.media3.common.C.ROLE_FLAG_SUPPLEMENTARY, androidx.media3.common.C.ROLE_FLAG_COMMENTARY, androidx.media3.common.C.ROLE_FLAG_DUB, androidx.media3.common.C.ROLE_FLAG_EMERGENCY, androidx.media3.common.C.ROLE_FLAG_CAPTION, androidx.media3.common.C.ROLE_FLAG_SUBTITLE, androidx.media3.common.C.ROLE_FLAG_SIGN, androidx.media3.common.C.ROLE_FLAG_DESCRIBES_VIDEO, androidx.media3.common.C.ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND, androidx.media3.common.C.ROLE_FLAG_ENHANCED_DIALOG_INTELLIGIBILITY, androidx.media3.common.C.ROLE_FLAG_TRANSCRIBES_DIALOG, androidx.media3.common.C.ROLE_FLAG_EASY_TO_READ, androidx.media3.common.C.ROLE_FLAG_TRICK_PLAY}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface C.RoleFlags { + @IntDef(flag=true, value={androidx.media3.common.C.ROLE_FLAG_MAIN, androidx.media3.common.C.ROLE_FLAG_ALTERNATE, androidx.media3.common.C.ROLE_FLAG_SUPPLEMENTARY, androidx.media3.common.C.ROLE_FLAG_COMMENTARY, androidx.media3.common.C.ROLE_FLAG_DUB, androidx.media3.common.C.ROLE_FLAG_EMERGENCY, androidx.media3.common.C.ROLE_FLAG_CAPTION, androidx.media3.common.C.ROLE_FLAG_SUBTITLE, androidx.media3.common.C.ROLE_FLAG_SIGN, androidx.media3.common.C.ROLE_FLAG_DESCRIBES_VIDEO, androidx.media3.common.C.ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND, androidx.media3.common.C.ROLE_FLAG_ENHANCED_DIALOG_INTELLIGIBILITY, androidx.media3.common.C.ROLE_FLAG_TRANSCRIBES_DIALOG, androidx.media3.common.C.ROLE_FLAG_EASY_TO_READ, androidx.media3.common.C.ROLE_FLAG_TRICK_PLAY, androidx.media3.common.C.ROLE_FLAG_AUXILIARY}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface C.RoleFlags { } @IntDef(flag=true, value={androidx.media3.common.C.SELECTION_FLAG_DEFAULT, androidx.media3.common.C.SELECTION_FLAG_FORCED, androidx.media3.common.C.SELECTION_FLAG_AUTOSELECT}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface C.SelectionFlags { @@ -163,6 +169,9 @@ package androidx.media3.common { @IntDef(open=true, value={androidx.media3.common.C.TRACK_TYPE_UNKNOWN, androidx.media3.common.C.TRACK_TYPE_DEFAULT, androidx.media3.common.C.TRACK_TYPE_AUDIO, androidx.media3.common.C.TRACK_TYPE_VIDEO, androidx.media3.common.C.TRACK_TYPE_TEXT, androidx.media3.common.C.TRACK_TYPE_IMAGE, androidx.media3.common.C.TRACK_TYPE_METADATA, androidx.media3.common.C.TRACK_TYPE_CAMERA_MOTION, androidx.media3.common.C.TRACK_TYPE_NONE}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE_USE) public static @interface C.TrackType { } + @IntDef(flag=true, value={androidx.media3.common.C.VOLUME_FLAG_SHOW_UI, androidx.media3.common.C.VOLUME_FLAG_ALLOW_RINGER_MODES, androidx.media3.common.C.VOLUME_FLAG_PLAY_SOUND, androidx.media3.common.C.VOLUME_FLAG_REMOVE_SOUND_AND_VIBRATE, androidx.media3.common.C.VOLUME_FLAG_VIBRATE}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE_USE}) public static @interface C.VolumeFlags { + } + @IntDef({androidx.media3.common.C.WAKE_MODE_NONE, androidx.media3.common.C.WAKE_MODE_LOCAL, androidx.media3.common.C.WAKE_MODE_NETWORK}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface C.WakeMode { } @@ -170,9 +179,18 @@ package androidx.media3.common { field public static final int PLAYBACK_TYPE_LOCAL = 0; // 0x0 field public static final int PLAYBACK_TYPE_REMOTE = 1; // 0x1 field public static final androidx.media3.common.DeviceInfo UNKNOWN; - field public final int maxVolume; - field public final int minVolume; + field @IntRange(from=0) public final int maxVolume; + field @IntRange(from=0) public final int minVolume; field @androidx.media3.common.DeviceInfo.PlaybackType public final int playbackType; + field @Nullable public final String routingControllerId; + } + + public static final class DeviceInfo.Builder { + ctor public DeviceInfo.Builder(@androidx.media3.common.DeviceInfo.PlaybackType int); + method public androidx.media3.common.DeviceInfo build(); + method public androidx.media3.common.DeviceInfo.Builder setMaxVolume(@IntRange(from=0) int); + method public androidx.media3.common.DeviceInfo.Builder setMinVolume(@IntRange(from=0) int); + method public androidx.media3.common.DeviceInfo.Builder setRoutingControllerId(@Nullable String); } @IntDef({androidx.media3.common.DeviceInfo.PLAYBACK_TYPE_LOCAL, androidx.media3.common.DeviceInfo.PLAYBACK_TYPE_REMOTE}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE_USE) public static @interface DeviceInfo.PlaybackType { @@ -209,8 +227,8 @@ package androidx.media3.common { public final class MediaItem { method public androidx.media3.common.MediaItem.Builder buildUpon(); - method public static androidx.media3.common.MediaItem fromUri(String); method public static androidx.media3.common.MediaItem fromUri(android.net.Uri); + method public static androidx.media3.common.MediaItem fromUri(String); field public static final String DEFAULT_MEDIA_ID = ""; field public static final androidx.media3.common.MediaItem EMPTY; field public final androidx.media3.common.MediaItem.ClippingConfiguration clippingConfiguration; @@ -247,8 +265,8 @@ package androidx.media3.common { method public androidx.media3.common.MediaItem.Builder setRequestMetadata(androidx.media3.common.MediaItem.RequestMetadata); method public androidx.media3.common.MediaItem.Builder setSubtitleConfigurations(java.util.List); method public androidx.media3.common.MediaItem.Builder setTag(@Nullable Object); - method public androidx.media3.common.MediaItem.Builder setUri(@Nullable String); method public androidx.media3.common.MediaItem.Builder setUri(@Nullable android.net.Uri); + method public androidx.media3.common.MediaItem.Builder setUri(@Nullable String); } public static class MediaItem.ClippingConfiguration { @@ -318,7 +336,7 @@ package androidx.media3.common { method public androidx.media3.common.MediaItem.LiveConfiguration.Builder setTargetOffsetMs(long); } - public static class MediaItem.LocalConfiguration { + public static final class MediaItem.LocalConfiguration { field @Nullable public final androidx.media3.common.MediaItem.AdsConfiguration adsConfiguration; field @Nullable public final androidx.media3.common.MediaItem.DrmConfiguration drmConfiguration; field @Nullable public final String mimeType; @@ -369,14 +387,50 @@ package androidx.media3.common { public final class MediaMetadata { method public androidx.media3.common.MediaMetadata.Builder buildUpon(); field public static final androidx.media3.common.MediaMetadata EMPTY; - field public static final int FOLDER_TYPE_ALBUMS = 2; // 0x2 - field public static final int FOLDER_TYPE_ARTISTS = 3; // 0x3 - field public static final int FOLDER_TYPE_GENRES = 4; // 0x4 - field public static final int FOLDER_TYPE_MIXED = 0; // 0x0 - field public static final int FOLDER_TYPE_NONE = -1; // 0xffffffff - field public static final int FOLDER_TYPE_PLAYLISTS = 5; // 0x5 - field public static final int FOLDER_TYPE_TITLES = 1; // 0x1 - field public static final int FOLDER_TYPE_YEARS = 6; // 0x6 + field @Deprecated public static final int FOLDER_TYPE_ALBUMS = 2; // 0x2 + field @Deprecated public static final int FOLDER_TYPE_ARTISTS = 3; // 0x3 + field @Deprecated public static final int FOLDER_TYPE_GENRES = 4; // 0x4 + field @Deprecated public static final int FOLDER_TYPE_MIXED = 0; // 0x0 + field @Deprecated public static final int FOLDER_TYPE_NONE = -1; // 0xffffffff + field @Deprecated public static final int FOLDER_TYPE_PLAYLISTS = 5; // 0x5 + field @Deprecated public static final int FOLDER_TYPE_TITLES = 1; // 0x1 + field @Deprecated public static final int FOLDER_TYPE_YEARS = 6; // 0x6 + field public static final int MEDIA_TYPE_ALBUM = 10; // 0xa + field public static final int MEDIA_TYPE_ARTIST = 11; // 0xb + field public static final int MEDIA_TYPE_AUDIO_BOOK = 15; // 0xf + field public static final int MEDIA_TYPE_AUDIO_BOOK_CHAPTER = 2; // 0x2 + field public static final int MEDIA_TYPE_FOLDER_ALBUMS = 21; // 0x15 + field public static final int MEDIA_TYPE_FOLDER_ARTISTS = 22; // 0x16 + field public static final int MEDIA_TYPE_FOLDER_AUDIO_BOOKS = 26; // 0x1a + field public static final int MEDIA_TYPE_FOLDER_GENRES = 23; // 0x17 + field public static final int MEDIA_TYPE_FOLDER_MIXED = 20; // 0x14 + field public static final int MEDIA_TYPE_FOLDER_MOVIES = 35; // 0x23 + field public static final int MEDIA_TYPE_FOLDER_NEWS = 32; // 0x20 + field public static final int MEDIA_TYPE_FOLDER_PLAYLISTS = 24; // 0x18 + field public static final int MEDIA_TYPE_FOLDER_PODCASTS = 27; // 0x1b + field public static final int MEDIA_TYPE_FOLDER_RADIO_STATIONS = 31; // 0x1f + field public static final int MEDIA_TYPE_FOLDER_TRAILERS = 34; // 0x22 + field public static final int MEDIA_TYPE_FOLDER_TV_CHANNELS = 28; // 0x1c + field public static final int MEDIA_TYPE_FOLDER_TV_SERIES = 29; // 0x1d + field public static final int MEDIA_TYPE_FOLDER_TV_SHOWS = 30; // 0x1e + field public static final int MEDIA_TYPE_FOLDER_VIDEOS = 33; // 0x21 + field public static final int MEDIA_TYPE_FOLDER_YEARS = 25; // 0x19 + field public static final int MEDIA_TYPE_GENRE = 12; // 0xc + field public static final int MEDIA_TYPE_MIXED = 0; // 0x0 + field public static final int MEDIA_TYPE_MOVIE = 8; // 0x8 + field public static final int MEDIA_TYPE_MUSIC = 1; // 0x1 + field public static final int MEDIA_TYPE_NEWS = 5; // 0x5 + field public static final int MEDIA_TYPE_PLAYLIST = 13; // 0xd + field public static final int MEDIA_TYPE_PODCAST = 16; // 0x10 + field public static final int MEDIA_TYPE_PODCAST_EPISODE = 3; // 0x3 + field public static final int MEDIA_TYPE_RADIO_STATION = 4; // 0x4 + field public static final int MEDIA_TYPE_TRAILER = 7; // 0x7 + field public static final int MEDIA_TYPE_TV_CHANNEL = 17; // 0x11 + field public static final int MEDIA_TYPE_TV_SEASON = 19; // 0x13 + field public static final int MEDIA_TYPE_TV_SERIES = 18; // 0x12 + field public static final int MEDIA_TYPE_TV_SHOW = 9; // 0x9 + field public static final int MEDIA_TYPE_VIDEO = 6; // 0x6 + field public static final int MEDIA_TYPE_YEAR = 14; // 0xe field public static final int PICTURE_TYPE_ARTIST_PERFORMER = 8; // 0x8 field public static final int PICTURE_TYPE_A_BRIGHT_COLORED_FISH = 17; // 0x11 field public static final int PICTURE_TYPE_BACK_COVER = 4; // 0x4 @@ -411,9 +465,11 @@ package androidx.media3.common { field @Nullable public final Integer discNumber; field @Nullable public final CharSequence displayTitle; field @Nullable public final android.os.Bundle extras; - field @Nullable @androidx.media3.common.MediaMetadata.FolderType public final Integer folderType; + field @Deprecated @Nullable @androidx.media3.common.MediaMetadata.FolderType public final Integer folderType; field @Nullable public final CharSequence genre; + field @Nullable public final Boolean isBrowsable; field @Nullable public final Boolean isPlayable; + field @Nullable @androidx.media3.common.MediaMetadata.MediaType public final Integer mediaType; field @Nullable public final androidx.media3.common.Rating overallRating; field @Nullable public final Integer recordingDay; field @Nullable public final Integer recordingMonth; @@ -447,9 +503,11 @@ package androidx.media3.common { method public androidx.media3.common.MediaMetadata.Builder setDiscNumber(@Nullable Integer); method public androidx.media3.common.MediaMetadata.Builder setDisplayTitle(@Nullable CharSequence); method public androidx.media3.common.MediaMetadata.Builder setExtras(@Nullable android.os.Bundle); - method public androidx.media3.common.MediaMetadata.Builder setFolderType(@Nullable @androidx.media3.common.MediaMetadata.FolderType Integer); + method @Deprecated public androidx.media3.common.MediaMetadata.Builder setFolderType(@Nullable @androidx.media3.common.MediaMetadata.FolderType Integer); method public androidx.media3.common.MediaMetadata.Builder setGenre(@Nullable CharSequence); + method public androidx.media3.common.MediaMetadata.Builder setIsBrowsable(@Nullable Boolean); method public androidx.media3.common.MediaMetadata.Builder setIsPlayable(@Nullable Boolean); + method public androidx.media3.common.MediaMetadata.Builder setMediaType(@Nullable @androidx.media3.common.MediaMetadata.MediaType Integer); method public androidx.media3.common.MediaMetadata.Builder setOverallRating(@Nullable androidx.media3.common.Rating); method public androidx.media3.common.MediaMetadata.Builder setRecordingDay(@IntRange(from=1, to=31) @Nullable Integer); method public androidx.media3.common.MediaMetadata.Builder setRecordingMonth(@IntRange(from=1, to=12) @Nullable Integer); @@ -467,7 +525,10 @@ package androidx.media3.common { method public androidx.media3.common.MediaMetadata.Builder setWriter(@Nullable CharSequence); } - @IntDef({androidx.media3.common.MediaMetadata.FOLDER_TYPE_NONE, androidx.media3.common.MediaMetadata.FOLDER_TYPE_MIXED, androidx.media3.common.MediaMetadata.FOLDER_TYPE_TITLES, androidx.media3.common.MediaMetadata.FOLDER_TYPE_ALBUMS, androidx.media3.common.MediaMetadata.FOLDER_TYPE_ARTISTS, androidx.media3.common.MediaMetadata.FOLDER_TYPE_GENRES, androidx.media3.common.MediaMetadata.FOLDER_TYPE_PLAYLISTS, androidx.media3.common.MediaMetadata.FOLDER_TYPE_YEARS}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface MediaMetadata.FolderType { + @Deprecated @IntDef({androidx.media3.common.MediaMetadata.FOLDER_TYPE_NONE, androidx.media3.common.MediaMetadata.FOLDER_TYPE_MIXED, androidx.media3.common.MediaMetadata.FOLDER_TYPE_TITLES, androidx.media3.common.MediaMetadata.FOLDER_TYPE_ALBUMS, androidx.media3.common.MediaMetadata.FOLDER_TYPE_ARTISTS, androidx.media3.common.MediaMetadata.FOLDER_TYPE_GENRES, androidx.media3.common.MediaMetadata.FOLDER_TYPE_PLAYLISTS, androidx.media3.common.MediaMetadata.FOLDER_TYPE_YEARS}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface MediaMetadata.FolderType { + } + + @IntDef({androidx.media3.common.MediaMetadata.MEDIA_TYPE_MIXED, androidx.media3.common.MediaMetadata.MEDIA_TYPE_MUSIC, androidx.media3.common.MediaMetadata.MEDIA_TYPE_AUDIO_BOOK_CHAPTER, androidx.media3.common.MediaMetadata.MEDIA_TYPE_PODCAST_EPISODE, androidx.media3.common.MediaMetadata.MEDIA_TYPE_RADIO_STATION, androidx.media3.common.MediaMetadata.MEDIA_TYPE_NEWS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_VIDEO, androidx.media3.common.MediaMetadata.MEDIA_TYPE_TRAILER, androidx.media3.common.MediaMetadata.MEDIA_TYPE_MOVIE, androidx.media3.common.MediaMetadata.MEDIA_TYPE_TV_SHOW, androidx.media3.common.MediaMetadata.MEDIA_TYPE_ALBUM, androidx.media3.common.MediaMetadata.MEDIA_TYPE_ARTIST, androidx.media3.common.MediaMetadata.MEDIA_TYPE_GENRE, androidx.media3.common.MediaMetadata.MEDIA_TYPE_PLAYLIST, androidx.media3.common.MediaMetadata.MEDIA_TYPE_YEAR, androidx.media3.common.MediaMetadata.MEDIA_TYPE_AUDIO_BOOK, androidx.media3.common.MediaMetadata.MEDIA_TYPE_PODCAST, androidx.media3.common.MediaMetadata.MEDIA_TYPE_TV_CHANNEL, androidx.media3.common.MediaMetadata.MEDIA_TYPE_TV_SERIES, androidx.media3.common.MediaMetadata.MEDIA_TYPE_TV_SEASON, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_MIXED, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_ALBUMS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_ARTISTS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_GENRES, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_PLAYLISTS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_YEARS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_AUDIO_BOOKS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_PODCASTS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_TV_CHANNELS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_TV_SERIES, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_TV_SHOWS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_RADIO_STATIONS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_NEWS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_VIDEOS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_TRAILERS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_MOVIES}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE_USE) public static @interface MediaMetadata.MediaType { } @IntDef({androidx.media3.common.MediaMetadata.PICTURE_TYPE_OTHER, androidx.media3.common.MediaMetadata.PICTURE_TYPE_FILE_ICON, androidx.media3.common.MediaMetadata.PICTURE_TYPE_FILE_ICON_OTHER, androidx.media3.common.MediaMetadata.PICTURE_TYPE_FRONT_COVER, androidx.media3.common.MediaMetadata.PICTURE_TYPE_BACK_COVER, androidx.media3.common.MediaMetadata.PICTURE_TYPE_LEAFLET_PAGE, androidx.media3.common.MediaMetadata.PICTURE_TYPE_MEDIA, androidx.media3.common.MediaMetadata.PICTURE_TYPE_LEAD_ARTIST_PERFORMER, androidx.media3.common.MediaMetadata.PICTURE_TYPE_ARTIST_PERFORMER, androidx.media3.common.MediaMetadata.PICTURE_TYPE_CONDUCTOR, androidx.media3.common.MediaMetadata.PICTURE_TYPE_BAND_ORCHESTRA, androidx.media3.common.MediaMetadata.PICTURE_TYPE_COMPOSER, androidx.media3.common.MediaMetadata.PICTURE_TYPE_LYRICIST, androidx.media3.common.MediaMetadata.PICTURE_TYPE_RECORDING_LOCATION, androidx.media3.common.MediaMetadata.PICTURE_TYPE_DURING_RECORDING, androidx.media3.common.MediaMetadata.PICTURE_TYPE_DURING_PERFORMANCE, androidx.media3.common.MediaMetadata.PICTURE_TYPE_MOVIE_VIDEO_SCREEN_CAPTURE, androidx.media3.common.MediaMetadata.PICTURE_TYPE_A_BRIGHT_COLORED_FISH, androidx.media3.common.MediaMetadata.PICTURE_TYPE_ILLUSTRATION, androidx.media3.common.MediaMetadata.PICTURE_TYPE_BAND_ARTIST_LOGO, androidx.media3.common.MediaMetadata.PICTURE_TYPE_PUBLISHER_STUDIO_LOGO}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface MediaMetadata.PictureType { @@ -486,8 +547,9 @@ package androidx.media3.common { field public static final String APPLICATION_MP4VTT = "application/x-mp4-vtt"; field public static final String APPLICATION_MPD = "application/dash+xml"; field public static final String APPLICATION_PGS = "application/pgs"; - field public static final String APPLICATION_RAWCC = "application/x-rawcc"; + field @Deprecated public static final String APPLICATION_RAWCC = "application/x-rawcc"; field public static final String APPLICATION_RTSP = "application/x-rtsp"; + field public static final String APPLICATION_SDP = "application/sdp"; field public static final String APPLICATION_SS = "application/vnd.ms-sstr+xml"; field public static final String APPLICATION_SUBRIP = "application/x-subrip"; field public static final String APPLICATION_TTML = "application/ttml+xml"; @@ -557,17 +619,24 @@ package androidx.media3.common { public class PlaybackException extends java.lang.Exception { method @CallSuper public boolean errorInfoEquals(@Nullable androidx.media3.common.PlaybackException); - method public static String getErrorCodeName(@androidx.media3.common.PlaybackException.ErrorCode int); method public final String getErrorCodeName(); + method public static String getErrorCodeName(@androidx.media3.common.PlaybackException.ErrorCode int); field public static final int CUSTOM_ERROR_CODE_BASE = 1000000; // 0xf4240 field public static final int ERROR_CODE_AUDIO_TRACK_INIT_FAILED = 5001; // 0x1389 + field public static final int ERROR_CODE_AUDIO_TRACK_OFFLOAD_INIT_FAILED = 5004; // 0x138c + field public static final int ERROR_CODE_AUDIO_TRACK_OFFLOAD_WRITE_FAILED = 5003; // 0x138b field public static final int ERROR_CODE_AUDIO_TRACK_WRITE_FAILED = 5002; // 0x138a + field public static final int ERROR_CODE_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a + field public static final int ERROR_CODE_BAD_VALUE = -3; // 0xfffffffd field public static final int ERROR_CODE_BEHIND_LIVE_WINDOW = 1002; // 0x3ea + field public static final int ERROR_CODE_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98 + field public static final int ERROR_CODE_CONTENT_ALREADY_PLAYING = -110; // 0xffffff92 field public static final int ERROR_CODE_DECODER_INIT_FAILED = 4001; // 0xfa1 field public static final int ERROR_CODE_DECODER_QUERY_FAILED = 4002; // 0xfa2 field public static final int ERROR_CODE_DECODING_FAILED = 4003; // 0xfa3 field public static final int ERROR_CODE_DECODING_FORMAT_EXCEEDS_CAPABILITIES = 4004; // 0xfa4 field public static final int ERROR_CODE_DECODING_FORMAT_UNSUPPORTED = 4005; // 0xfa5 + field public static final int ERROR_CODE_DISCONNECTED = -100; // 0xffffff9c field public static final int ERROR_CODE_DRM_CONTENT_ERROR = 6003; // 0x1773 field public static final int ERROR_CODE_DRM_DEVICE_REVOKED = 6007; // 0x1777 field public static final int ERROR_CODE_DRM_DISALLOWED_OPERATION = 6005; // 0x1775 @@ -577,7 +646,9 @@ package androidx.media3.common { field public static final int ERROR_CODE_DRM_SCHEME_UNSUPPORTED = 6001; // 0x1771 field public static final int ERROR_CODE_DRM_SYSTEM_ERROR = 6006; // 0x1776 field public static final int ERROR_CODE_DRM_UNSPECIFIED = 6000; // 0x1770 + field public static final int ERROR_CODE_END_OF_PLAYLIST = -109; // 0xffffff93 field public static final int ERROR_CODE_FAILED_RUNTIME_CHECK = 1004; // 0x3ec + field public static final int ERROR_CODE_INVALID_STATE = -2; // 0xfffffffe field public static final int ERROR_CODE_IO_BAD_HTTP_STATUS = 2004; // 0x7d4 field public static final int ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED = 2007; // 0x7d7 field public static final int ERROR_CODE_IO_FILE_NOT_FOUND = 2005; // 0x7d5 @@ -587,22 +658,29 @@ package androidx.media3.common { field public static final int ERROR_CODE_IO_NO_PERMISSION = 2006; // 0x7d6 field public static final int ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE = 2008; // 0x7d8 field public static final int ERROR_CODE_IO_UNSPECIFIED = 2000; // 0x7d0 + field public static final int ERROR_CODE_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96 + field public static final int ERROR_CODE_NOT_SUPPORTED = -6; // 0xfffffffa + field public static final int ERROR_CODE_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97 field public static final int ERROR_CODE_PARSING_CONTAINER_MALFORMED = 3001; // 0xbb9 field public static final int ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED = 3003; // 0xbbb field public static final int ERROR_CODE_PARSING_MANIFEST_MALFORMED = 3002; // 0xbba field public static final int ERROR_CODE_PARSING_MANIFEST_UNSUPPORTED = 3004; // 0xbbc + field public static final int ERROR_CODE_PERMISSION_DENIED = -4; // 0xfffffffc + field public static final int ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99 field public static final int ERROR_CODE_REMOTE_ERROR = 1001; // 0x3e9 + field public static final int ERROR_CODE_SETUP_REQUIRED = -108; // 0xffffff94 + field public static final int ERROR_CODE_SKIP_LIMIT_REACHED = -107; // 0xffffff95 field public static final int ERROR_CODE_TIMEOUT = 1003; // 0x3eb field public static final int ERROR_CODE_UNSPECIFIED = 1000; // 0x3e8 field @androidx.media3.common.PlaybackException.ErrorCode public final int errorCode; field public final long timestampMs; } - @IntDef(open=true, value={androidx.media3.common.PlaybackException.ERROR_CODE_UNSPECIFIED, androidx.media3.common.PlaybackException.ERROR_CODE_REMOTE_ERROR, androidx.media3.common.PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW, androidx.media3.common.PlaybackException.ERROR_CODE_TIMEOUT, androidx.media3.common.PlaybackException.ERROR_CODE_FAILED_RUNTIME_CHECK, androidx.media3.common.PlaybackException.ERROR_CODE_IO_UNSPECIFIED, androidx.media3.common.PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT, androidx.media3.common.PlaybackException.ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE, androidx.media3.common.PlaybackException.ERROR_CODE_IO_BAD_HTTP_STATUS, androidx.media3.common.PlaybackException.ERROR_CODE_IO_FILE_NOT_FOUND, androidx.media3.common.PlaybackException.ERROR_CODE_IO_NO_PERMISSION, androidx.media3.common.PlaybackException.ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED, androidx.media3.common.PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE, androidx.media3.common.PlaybackException.ERROR_CODE_PARSING_CONTAINER_MALFORMED, androidx.media3.common.PlaybackException.ERROR_CODE_PARSING_MANIFEST_MALFORMED, androidx.media3.common.PlaybackException.ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED, androidx.media3.common.PlaybackException.ERROR_CODE_PARSING_MANIFEST_UNSUPPORTED, androidx.media3.common.PlaybackException.ERROR_CODE_DECODER_INIT_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DECODER_QUERY_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DECODING_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DECODING_FORMAT_EXCEEDS_CAPABILITIES, androidx.media3.common.PlaybackException.ERROR_CODE_DECODING_FORMAT_UNSUPPORTED, androidx.media3.common.PlaybackException.ERROR_CODE_AUDIO_TRACK_INIT_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_AUDIO_TRACK_WRITE_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_UNSPECIFIED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_SCHEME_UNSUPPORTED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_PROVISIONING_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_CONTENT_ERROR, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_LICENSE_ACQUISITION_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_DISALLOWED_OPERATION, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_SYSTEM_ERROR, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_DEVICE_REVOKED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_LICENSE_EXPIRED}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface PlaybackException.ErrorCode { + @IntDef(open=true, value={androidx.media3.common.PlaybackException.ERROR_CODE_INVALID_STATE, androidx.media3.common.PlaybackException.ERROR_CODE_BAD_VALUE, androidx.media3.common.PlaybackException.ERROR_CODE_PERMISSION_DENIED, androidx.media3.common.PlaybackException.ERROR_CODE_NOT_SUPPORTED, androidx.media3.common.PlaybackException.ERROR_CODE_DISCONNECTED, androidx.media3.common.PlaybackException.ERROR_CODE_AUTHENTICATION_EXPIRED, androidx.media3.common.PlaybackException.ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED, androidx.media3.common.PlaybackException.ERROR_CODE_CONCURRENT_STREAM_LIMIT, androidx.media3.common.PlaybackException.ERROR_CODE_PARENTAL_CONTROL_RESTRICTED, androidx.media3.common.PlaybackException.ERROR_CODE_NOT_AVAILABLE_IN_REGION, androidx.media3.common.PlaybackException.ERROR_CODE_SKIP_LIMIT_REACHED, androidx.media3.common.PlaybackException.ERROR_CODE_SETUP_REQUIRED, androidx.media3.common.PlaybackException.ERROR_CODE_END_OF_PLAYLIST, androidx.media3.common.PlaybackException.ERROR_CODE_CONTENT_ALREADY_PLAYING, androidx.media3.common.PlaybackException.ERROR_CODE_UNSPECIFIED, androidx.media3.common.PlaybackException.ERROR_CODE_REMOTE_ERROR, androidx.media3.common.PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW, androidx.media3.common.PlaybackException.ERROR_CODE_TIMEOUT, androidx.media3.common.PlaybackException.ERROR_CODE_FAILED_RUNTIME_CHECK, androidx.media3.common.PlaybackException.ERROR_CODE_IO_UNSPECIFIED, androidx.media3.common.PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT, androidx.media3.common.PlaybackException.ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE, androidx.media3.common.PlaybackException.ERROR_CODE_IO_BAD_HTTP_STATUS, androidx.media3.common.PlaybackException.ERROR_CODE_IO_FILE_NOT_FOUND, androidx.media3.common.PlaybackException.ERROR_CODE_IO_NO_PERMISSION, androidx.media3.common.PlaybackException.ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED, androidx.media3.common.PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE, androidx.media3.common.PlaybackException.ERROR_CODE_PARSING_CONTAINER_MALFORMED, androidx.media3.common.PlaybackException.ERROR_CODE_PARSING_MANIFEST_MALFORMED, androidx.media3.common.PlaybackException.ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED, androidx.media3.common.PlaybackException.ERROR_CODE_PARSING_MANIFEST_UNSUPPORTED, androidx.media3.common.PlaybackException.ERROR_CODE_DECODER_INIT_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DECODER_QUERY_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DECODING_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DECODING_FORMAT_EXCEEDS_CAPABILITIES, androidx.media3.common.PlaybackException.ERROR_CODE_DECODING_FORMAT_UNSUPPORTED, androidx.media3.common.PlaybackException.ERROR_CODE_AUDIO_TRACK_INIT_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_AUDIO_TRACK_WRITE_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_AUDIO_TRACK_OFFLOAD_INIT_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_AUDIO_TRACK_OFFLOAD_WRITE_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_UNSPECIFIED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_SCHEME_UNSUPPORTED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_PROVISIONING_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_CONTENT_ERROR, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_LICENSE_ACQUISITION_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_DISALLOWED_OPERATION, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_SYSTEM_ERROR, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_DEVICE_REVOKED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_LICENSE_EXPIRED}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface PlaybackException.ErrorCode { } public final class PlaybackParameters { - ctor public PlaybackParameters(float); + ctor public PlaybackParameters(@FloatRange(from=0, fromInclusive=false) float); ctor public PlaybackParameters(@FloatRange(from=0, fromInclusive=false) float, @FloatRange(from=0, fromInclusive=false) float); method @CheckResult public androidx.media3.common.PlaybackParameters withSpeed(@FloatRange(from=0, fromInclusive=false) float); field public static final androidx.media3.common.PlaybackParameters DEFAULT; @@ -614,8 +692,8 @@ package androidx.media3.common { method public void addListener(androidx.media3.common.Player.Listener); method public void addMediaItem(androidx.media3.common.MediaItem); method public void addMediaItem(int, androidx.media3.common.MediaItem); - method public void addMediaItems(java.util.List); method public void addMediaItems(int, java.util.List); + method public void addMediaItems(java.util.List); method public boolean canAdvertiseSession(); method public void clearMediaItems(); method public void clearVideoSurface(); @@ -623,7 +701,8 @@ package androidx.media3.common { method public void clearVideoSurfaceHolder(@Nullable android.view.SurfaceHolder); method public void clearVideoSurfaceView(@Nullable android.view.SurfaceView); method public void clearVideoTextureView(@Nullable android.view.TextureView); - method public void decreaseDeviceVolume(); + method @Deprecated public void decreaseDeviceVolume(); + method public void decreaseDeviceVolume(@androidx.media3.common.C.VolumeFlags int); method public android.os.Looper getApplicationLooper(); method public androidx.media3.common.AudioAttributes getAudioAttributes(); method public androidx.media3.common.Player.Commands getAvailableCommands(); @@ -667,7 +746,8 @@ package androidx.media3.common { method @FloatRange(from=0, to=1.0) public float getVolume(); method public boolean hasNextMediaItem(); method public boolean hasPreviousMediaItem(); - method public void increaseDeviceVolume(); + method @Deprecated public void increaseDeviceVolume(); + method public void increaseDeviceVolume(@androidx.media3.common.C.VolumeFlags int); method public boolean isCommandAvailable(@androidx.media3.common.Player.Command int); method public boolean isCurrentMediaItemDynamic(); method public boolean isCurrentMediaItemLive(); @@ -685,21 +765,26 @@ package androidx.media3.common { method public void removeListener(androidx.media3.common.Player.Listener); method public void removeMediaItem(int); method public void removeMediaItems(int, int); + method public void replaceMediaItem(int, androidx.media3.common.MediaItem); + method public void replaceMediaItems(int, int, java.util.List); method public void seekBack(); method public void seekForward(); - method public void seekTo(long); method public void seekTo(int, long); + method public void seekTo(long); method public void seekToDefaultPosition(); method public void seekToDefaultPosition(int); method public void seekToNext(); method public void seekToNextMediaItem(); method public void seekToPrevious(); method public void seekToPreviousMediaItem(); - method public void setDeviceMuted(boolean); - method public void setDeviceVolume(@IntRange(from=0) int); + method public void setAudioAttributes(androidx.media3.common.AudioAttributes, boolean); + method @Deprecated public void setDeviceMuted(boolean); + method public void setDeviceMuted(boolean, @androidx.media3.common.C.VolumeFlags int); + method @Deprecated public void setDeviceVolume(@IntRange(from=0) int); + method public void setDeviceVolume(@IntRange(from=0) int, @androidx.media3.common.C.VolumeFlags int); method public void setMediaItem(androidx.media3.common.MediaItem); - method public void setMediaItem(androidx.media3.common.MediaItem, long); method public void setMediaItem(androidx.media3.common.MediaItem, boolean); + method public void setMediaItem(androidx.media3.common.MediaItem, long); method public void setMediaItems(java.util.List); method public void setMediaItems(java.util.List, boolean); method public void setMediaItems(java.util.List, int, long); @@ -716,12 +801,14 @@ package androidx.media3.common { method public void setVideoTextureView(@Nullable android.view.TextureView); method public void setVolume(@FloatRange(from=0, to=1.0) float); method public void stop(); - field public static final int COMMAND_ADJUST_DEVICE_VOLUME = 26; // 0x1a + field @Deprecated public static final int COMMAND_ADJUST_DEVICE_VOLUME = 26; // 0x1a + field public static final int COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS = 34; // 0x22 field public static final int COMMAND_CHANGE_MEDIA_ITEMS = 20; // 0x14 field public static final int COMMAND_GET_AUDIO_ATTRIBUTES = 21; // 0x15 field public static final int COMMAND_GET_CURRENT_MEDIA_ITEM = 16; // 0x10 field public static final int COMMAND_GET_DEVICE_VOLUME = 23; // 0x17 - field public static final int COMMAND_GET_MEDIA_ITEMS_METADATA = 18; // 0x12 + field @Deprecated public static final int COMMAND_GET_MEDIA_ITEMS_METADATA = 18; // 0x12 + field public static final int COMMAND_GET_METADATA = 18; // 0x12 field public static final int COMMAND_GET_TEXT = 28; // 0x1c field public static final int COMMAND_GET_TIMELINE = 17; // 0x11 field public static final int COMMAND_GET_TRACKS = 30; // 0x1e @@ -729,6 +816,7 @@ package androidx.media3.common { field public static final int COMMAND_INVALID = -1; // 0xffffffff field public static final int COMMAND_PLAY_PAUSE = 1; // 0x1 field public static final int COMMAND_PREPARE = 2; // 0x2 + field public static final int COMMAND_RELEASE = 32; // 0x20 field public static final int COMMAND_SEEK_BACK = 11; // 0xb field public static final int COMMAND_SEEK_FORWARD = 12; // 0xc field public static final int COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM = 5; // 0x5 @@ -738,9 +826,12 @@ package androidx.media3.common { field public static final int COMMAND_SEEK_TO_NEXT_MEDIA_ITEM = 8; // 0x8 field public static final int COMMAND_SEEK_TO_PREVIOUS = 7; // 0x7 field public static final int COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM = 6; // 0x6 - field public static final int COMMAND_SET_DEVICE_VOLUME = 25; // 0x19 + field public static final int COMMAND_SET_AUDIO_ATTRIBUTES = 35; // 0x23 + field @Deprecated public static final int COMMAND_SET_DEVICE_VOLUME = 25; // 0x19 + field public static final int COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS = 33; // 0x21 field public static final int COMMAND_SET_MEDIA_ITEM = 31; // 0x1f - field public static final int COMMAND_SET_MEDIA_ITEMS_METADATA = 19; // 0x13 + field @Deprecated public static final int COMMAND_SET_MEDIA_ITEMS_METADATA = 19; // 0x13 + field public static final int COMMAND_SET_PLAYLIST_METADATA = 19; // 0x13 field public static final int COMMAND_SET_REPEAT_MODE = 15; // 0xf field public static final int COMMAND_SET_SHUFFLE_MODE = 14; // 0xe field public static final int COMMAND_SET_SPEED_AND_PITCH = 13; // 0xd @@ -753,6 +844,7 @@ package androidx.media3.common { field public static final int DISCONTINUITY_REASON_REMOVE = 4; // 0x4 field public static final int DISCONTINUITY_REASON_SEEK = 1; // 0x1 field public static final int DISCONTINUITY_REASON_SEEK_ADJUSTMENT = 2; // 0x2 + field public static final int DISCONTINUITY_REASON_SILENCE_SKIP = 6; // 0x6 field public static final int DISCONTINUITY_REASON_SKIP = 3; // 0x3 field public static final int EVENT_AUDIO_ATTRIBUTES_CHANGED = 20; // 0x14 field public static final int EVENT_AUDIO_SESSION_ID = 21; // 0x15 @@ -791,10 +883,13 @@ package androidx.media3.common { field public static final int MEDIA_ITEM_TRANSITION_REASON_SEEK = 2; // 0x2 field public static final int PLAYBACK_SUPPRESSION_REASON_NONE = 0; // 0x0 field public static final int PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS = 1; // 0x1 + field public static final int PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT = 3; // 0x3 + field @Deprecated public static final int PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_ROUTE = 2; // 0x2 field public static final int PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY = 3; // 0x3 field public static final int PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS = 2; // 0x2 field public static final int PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM = 5; // 0x5 field public static final int PLAY_WHEN_READY_CHANGE_REASON_REMOTE = 4; // 0x4 + field public static final int PLAY_WHEN_READY_CHANGE_REASON_SUPPRESSED_TOO_LONG = 6; // 0x6 field public static final int PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST = 1; // 0x1 field public static final int REPEAT_MODE_ALL = 2; // 0x2 field public static final int REPEAT_MODE_OFF = 0; // 0x0 @@ -807,7 +902,7 @@ package androidx.media3.common { field public static final int TIMELINE_CHANGE_REASON_SOURCE_UPDATE = 1; // 0x1 } - @IntDef({androidx.media3.common.Player.COMMAND_INVALID, androidx.media3.common.Player.COMMAND_PLAY_PAUSE, androidx.media3.common.Player.COMMAND_PREPARE, androidx.media3.common.Player.COMMAND_STOP, androidx.media3.common.Player.COMMAND_SEEK_TO_DEFAULT_POSITION, androidx.media3.common.Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS, androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT, androidx.media3.common.Player.COMMAND_SEEK_TO_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_SEEK_BACK, androidx.media3.common.Player.COMMAND_SEEK_FORWARD, androidx.media3.common.Player.COMMAND_SET_SPEED_AND_PITCH, androidx.media3.common.Player.COMMAND_SET_SHUFFLE_MODE, androidx.media3.common.Player.COMMAND_SET_REPEAT_MODE, androidx.media3.common.Player.COMMAND_GET_CURRENT_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_GET_TIMELINE, androidx.media3.common.Player.COMMAND_GET_MEDIA_ITEMS_METADATA, androidx.media3.common.Player.COMMAND_SET_MEDIA_ITEMS_METADATA, androidx.media3.common.Player.COMMAND_SET_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_CHANGE_MEDIA_ITEMS, androidx.media3.common.Player.COMMAND_GET_AUDIO_ATTRIBUTES, androidx.media3.common.Player.COMMAND_GET_VOLUME, androidx.media3.common.Player.COMMAND_GET_DEVICE_VOLUME, androidx.media3.common.Player.COMMAND_SET_VOLUME, androidx.media3.common.Player.COMMAND_SET_DEVICE_VOLUME, androidx.media3.common.Player.COMMAND_ADJUST_DEVICE_VOLUME, androidx.media3.common.Player.COMMAND_SET_VIDEO_SURFACE, androidx.media3.common.Player.COMMAND_GET_TEXT, androidx.media3.common.Player.COMMAND_SET_TRACK_SELECTION_PARAMETERS, androidx.media3.common.Player.COMMAND_GET_TRACKS}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface Player.Command { + @IntDef({androidx.media3.common.Player.COMMAND_INVALID, androidx.media3.common.Player.COMMAND_PLAY_PAUSE, androidx.media3.common.Player.COMMAND_PREPARE, androidx.media3.common.Player.COMMAND_STOP, androidx.media3.common.Player.COMMAND_SEEK_TO_DEFAULT_POSITION, androidx.media3.common.Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS, androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT, androidx.media3.common.Player.COMMAND_SEEK_TO_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_SEEK_BACK, androidx.media3.common.Player.COMMAND_SEEK_FORWARD, androidx.media3.common.Player.COMMAND_SET_SPEED_AND_PITCH, androidx.media3.common.Player.COMMAND_SET_SHUFFLE_MODE, androidx.media3.common.Player.COMMAND_SET_REPEAT_MODE, androidx.media3.common.Player.COMMAND_GET_CURRENT_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_GET_TIMELINE, androidx.media3.common.Player.COMMAND_GET_MEDIA_ITEMS_METADATA, androidx.media3.common.Player.COMMAND_GET_METADATA, androidx.media3.common.Player.COMMAND_SET_MEDIA_ITEMS_METADATA, androidx.media3.common.Player.COMMAND_SET_PLAYLIST_METADATA, androidx.media3.common.Player.COMMAND_SET_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_CHANGE_MEDIA_ITEMS, androidx.media3.common.Player.COMMAND_GET_AUDIO_ATTRIBUTES, androidx.media3.common.Player.COMMAND_GET_VOLUME, androidx.media3.common.Player.COMMAND_GET_DEVICE_VOLUME, androidx.media3.common.Player.COMMAND_SET_VOLUME, androidx.media3.common.Player.COMMAND_SET_DEVICE_VOLUME, androidx.media3.common.Player.COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS, androidx.media3.common.Player.COMMAND_ADJUST_DEVICE_VOLUME, androidx.media3.common.Player.COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS, androidx.media3.common.Player.COMMAND_SET_AUDIO_ATTRIBUTES, androidx.media3.common.Player.COMMAND_SET_VIDEO_SURFACE, androidx.media3.common.Player.COMMAND_GET_TEXT, androidx.media3.common.Player.COMMAND_SET_TRACK_SELECTION_PARAMETERS, androidx.media3.common.Player.COMMAND_GET_TRACKS, androidx.media3.common.Player.COMMAND_RELEASE}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface Player.Command { } public static final class Player.Commands { @@ -818,7 +913,7 @@ package androidx.media3.common { field public static final androidx.media3.common.Player.Commands EMPTY; } - @IntDef({androidx.media3.common.Player.DISCONTINUITY_REASON_AUTO_TRANSITION, androidx.media3.common.Player.DISCONTINUITY_REASON_SEEK, androidx.media3.common.Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT, androidx.media3.common.Player.DISCONTINUITY_REASON_SKIP, androidx.media3.common.Player.DISCONTINUITY_REASON_REMOVE, androidx.media3.common.Player.DISCONTINUITY_REASON_INTERNAL}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface Player.DiscontinuityReason { + @IntDef({androidx.media3.common.Player.DISCONTINUITY_REASON_AUTO_TRANSITION, androidx.media3.common.Player.DISCONTINUITY_REASON_SEEK, androidx.media3.common.Player.DISCONTINUITY_REASON_SEEK_ADJUSTMENT, androidx.media3.common.Player.DISCONTINUITY_REASON_SKIP, androidx.media3.common.Player.DISCONTINUITY_REASON_REMOVE, androidx.media3.common.Player.DISCONTINUITY_REASON_INTERNAL, androidx.media3.common.Player.DISCONTINUITY_REASON_SILENCE_SKIP}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface Player.DiscontinuityReason { } @IntDef({androidx.media3.common.Player.EVENT_TIMELINE_CHANGED, androidx.media3.common.Player.EVENT_MEDIA_ITEM_TRANSITION, androidx.media3.common.Player.EVENT_TRACKS_CHANGED, androidx.media3.common.Player.EVENT_IS_LOADING_CHANGED, androidx.media3.common.Player.EVENT_PLAYBACK_STATE_CHANGED, androidx.media3.common.Player.EVENT_PLAY_WHEN_READY_CHANGED, androidx.media3.common.Player.EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED, androidx.media3.common.Player.EVENT_IS_PLAYING_CHANGED, androidx.media3.common.Player.EVENT_REPEAT_MODE_CHANGED, androidx.media3.common.Player.EVENT_SHUFFLE_MODE_ENABLED_CHANGED, androidx.media3.common.Player.EVENT_PLAYER_ERROR, androidx.media3.common.Player.EVENT_POSITION_DISCONTINUITY, androidx.media3.common.Player.EVENT_PLAYBACK_PARAMETERS_CHANGED, androidx.media3.common.Player.EVENT_AVAILABLE_COMMANDS_CHANGED, androidx.media3.common.Player.EVENT_MEDIA_METADATA_CHANGED, androidx.media3.common.Player.EVENT_PLAYLIST_METADATA_CHANGED, androidx.media3.common.Player.EVENT_SEEK_BACK_INCREMENT_CHANGED, androidx.media3.common.Player.EVENT_SEEK_FORWARD_INCREMENT_CHANGED, androidx.media3.common.Player.EVENT_MAX_SEEK_TO_PREVIOUS_POSITION_CHANGED, androidx.media3.common.Player.EVENT_TRACK_SELECTION_PARAMETERS_CHANGED, androidx.media3.common.Player.EVENT_AUDIO_ATTRIBUTES_CHANGED, androidx.media3.common.Player.EVENT_AUDIO_SESSION_ID, androidx.media3.common.Player.EVENT_VOLUME_CHANGED, androidx.media3.common.Player.EVENT_SKIP_SILENCE_ENABLED_CHANGED, androidx.media3.common.Player.EVENT_SURFACE_SIZE_CHANGED, androidx.media3.common.Player.EVENT_VIDEO_SIZE_CHANGED, androidx.media3.common.Player.EVENT_RENDERED_FIRST_FRAME, androidx.media3.common.Player.EVENT_CUES, androidx.media3.common.Player.EVENT_METADATA, androidx.media3.common.Player.EVENT_DEVICE_INFO_CHANGED, androidx.media3.common.Player.EVENT_DEVICE_VOLUME_CHANGED}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface Player.Event { @@ -868,10 +963,10 @@ package androidx.media3.common { @IntDef({androidx.media3.common.Player.MEDIA_ITEM_TRANSITION_REASON_REPEAT, androidx.media3.common.Player.MEDIA_ITEM_TRANSITION_REASON_AUTO, androidx.media3.common.Player.MEDIA_ITEM_TRANSITION_REASON_SEEK, androidx.media3.common.Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface Player.MediaItemTransitionReason { } - @IntDef({androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS, androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY, androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_REMOTE, androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface Player.PlayWhenReadyChangeReason { + @IntDef({androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS, androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY, androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_REMOTE, androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM, androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_SUPPRESSED_TOO_LONG}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface Player.PlayWhenReadyChangeReason { } - @IntDef({androidx.media3.common.Player.PLAYBACK_SUPPRESSION_REASON_NONE, androidx.media3.common.Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface Player.PlaybackSuppressionReason { + @IntDef({androidx.media3.common.Player.PLAYBACK_SUPPRESSION_REASON_NONE, androidx.media3.common.Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS, androidx.media3.common.Player.PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_ROUTE, androidx.media3.common.Player.PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface Player.PlaybackSuppressionReason { } public static final class Player.PositionInfo { @@ -999,7 +1094,7 @@ package androidx.media3.common { method public androidx.media3.common.TrackSelectionParameters.Builder buildUpon(); method public static androidx.media3.common.TrackSelectionParameters fromBundle(android.os.Bundle); method public static androidx.media3.common.TrackSelectionParameters getDefaults(android.content.Context); - method public android.os.Bundle toBundle(); + method @CallSuper public android.os.Bundle toBundle(); field public final com.google.common.collect.ImmutableSet disabledTrackTypes; field public final boolean forceHighestSupportedBitrate; field public final boolean forceLowestBitrate; @@ -1097,7 +1192,7 @@ package androidx.media3.common { field public static final androidx.media3.common.VideoSize UNKNOWN; field @IntRange(from=0) public final int height; field @FloatRange(from=0, fromInclusive=false) public final float pixelWidthHeightRatio; - field @IntRange(from=0, to=359) public final int unappliedRotationDegrees; + field @Deprecated @IntRange(from=0, to=359) public final int unappliedRotationDegrees; field @IntRange(from=0) public final int width; } @@ -1110,7 +1205,7 @@ package androidx.media3.common.text { field public static final int ANCHOR_TYPE_MIDDLE = 1; // 0x1 field public static final int ANCHOR_TYPE_START = 0; // 0x0 field public static final float DIMEN_UNSET = -3.4028235E38f; - field public static final androidx.media3.common.text.Cue EMPTY; + field @Deprecated public static final androidx.media3.common.text.Cue EMPTY; field public static final int LINE_TYPE_FRACTION = 0; // 0x0 field public static final int LINE_TYPE_NUMBER = 1; // 0x1 field public static final int TEXT_SIZE_TYPE_ABSOLUTE = 2; // 0x2 @@ -1162,11 +1257,16 @@ package androidx.media3.common.util { method public static boolean checkCleartextTrafficPermitted(androidx.media3.common.MediaItem...); method @Nullable public static String getAdaptiveMimeTypeForContentType(@androidx.media3.common.C.ContentType int); method @Nullable public static java.util.UUID getDrmUuid(String); + method public static boolean handlePauseButtonAction(@Nullable androidx.media3.common.Player); + method public static boolean handlePlayButtonAction(@Nullable androidx.media3.common.Player); + method public static boolean handlePlayPauseButtonAction(@Nullable androidx.media3.common.Player); method @androidx.media3.common.C.ContentType public static int inferContentType(android.net.Uri); method @androidx.media3.common.C.ContentType public static int inferContentTypeForExtension(String); method @androidx.media3.common.C.ContentType public static int inferContentTypeForUriAndMimeType(android.net.Uri, @Nullable String); - method public static boolean maybeRequestReadExternalStoragePermission(android.app.Activity, android.net.Uri...); - method public static boolean maybeRequestReadExternalStoragePermission(android.app.Activity, androidx.media3.common.MediaItem...); + method @Deprecated public static boolean maybeRequestReadExternalStoragePermission(android.app.Activity, android.net.Uri...); + method @Deprecated public static boolean maybeRequestReadExternalStoragePermission(android.app.Activity, androidx.media3.common.MediaItem...); + method public static boolean maybeRequestReadStoragePermission(android.app.Activity, androidx.media3.common.MediaItem...); + method @org.checkerframework.checker.nullness.qual.EnsuresNonNullIf(result=false, expression="#1") public static boolean shouldShowPlayButton(@Nullable androidx.media3.common.Player); } } @@ -1264,7 +1364,6 @@ package androidx.media3.exoplayer { method public void addAnalyticsListener(androidx.media3.exoplayer.analytics.AnalyticsListener); method @Nullable public androidx.media3.exoplayer.ExoPlaybackException getPlayerError(); method public void removeAnalyticsListener(androidx.media3.exoplayer.analytics.AnalyticsListener); - method public void setAudioAttributes(androidx.media3.common.AudioAttributes, boolean); method public void setHandleAudioBecomingNoisy(boolean); method public void setWakeMode(@androidx.media3.common.C.WakeMode int); } @@ -1289,7 +1388,7 @@ package androidx.media3.exoplayer.analytics { package androidx.media3.exoplayer.drm { - @RequiresApi(18) public final class FrameworkMediaDrm { + public final class FrameworkMediaDrm { method public static boolean isCryptoSchemeSupported(java.util.UUID); } @@ -1307,6 +1406,29 @@ package androidx.media3.exoplayer.ima { method public androidx.media3.exoplayer.ima.ImaAdsLoader build(); } + public final class ImaServerSideAdInsertionMediaSource implements androidx.media3.exoplayer.source.MediaSource { + } + + public static final class ImaServerSideAdInsertionMediaSource.AdsLoader { + method public androidx.media3.exoplayer.ima.ImaServerSideAdInsertionMediaSource.AdsLoader.State release(); + method public void setPlayer(androidx.media3.common.Player); + } + + public static final class ImaServerSideAdInsertionMediaSource.AdsLoader.Builder { + ctor public ImaServerSideAdInsertionMediaSource.AdsLoader.Builder(android.content.Context, androidx.media3.common.AdViewProvider); + method public androidx.media3.exoplayer.ima.ImaServerSideAdInsertionMediaSource.AdsLoader build(); + method public androidx.media3.exoplayer.ima.ImaServerSideAdInsertionMediaSource.AdsLoader.Builder setAdsLoaderState(androidx.media3.exoplayer.ima.ImaServerSideAdInsertionMediaSource.AdsLoader.State); + } + + public static class ImaServerSideAdInsertionMediaSource.AdsLoader.State { + method public static androidx.media3.exoplayer.ima.ImaServerSideAdInsertionMediaSource.AdsLoader.State fromBundle(android.os.Bundle); + method public android.os.Bundle toBundle(); + } + + public static final class ImaServerSideAdInsertionMediaSource.Factory implements androidx.media3.exoplayer.source.MediaSource.Factory { + ctor public ImaServerSideAdInsertionMediaSource.Factory(androidx.media3.exoplayer.ima.ImaServerSideAdInsertionMediaSource.AdsLoader, androidx.media3.exoplayer.source.MediaSource.Factory); + } + } package androidx.media3.exoplayer.source { @@ -1316,6 +1438,7 @@ package androidx.media3.exoplayer.source { method public androidx.media3.exoplayer.source.DefaultMediaSourceFactory clearLocalAdInsertionComponents(); method public androidx.media3.exoplayer.source.DefaultMediaSourceFactory setDataSourceFactory(androidx.media3.datasource.DataSource.Factory); method public androidx.media3.exoplayer.source.DefaultMediaSourceFactory setLocalAdInsertionComponents(androidx.media3.exoplayer.source.ads.AdsLoader.Provider, androidx.media3.common.AdViewProvider); + method public androidx.media3.exoplayer.source.DefaultMediaSourceFactory setServerSideAdInsertionMediaSourceFactory(@Nullable androidx.media3.exoplayer.source.MediaSource.Factory); } public interface MediaSource { @@ -1404,7 +1527,7 @@ package androidx.media3.session { field @Nullable public final V value; } - @IntDef({androidx.media3.session.LibraryResult.RESULT_SUCCESS, androidx.media3.session.LibraryResult.RESULT_ERROR_UNKNOWN, androidx.media3.session.LibraryResult.RESULT_ERROR_INVALID_STATE, androidx.media3.session.LibraryResult.RESULT_ERROR_BAD_VALUE, androidx.media3.session.LibraryResult.RESULT_ERROR_PERMISSION_DENIED, androidx.media3.session.LibraryResult.RESULT_ERROR_IO, androidx.media3.session.LibraryResult.RESULT_INFO_SKIPPED, androidx.media3.session.LibraryResult.RESULT_ERROR_SESSION_DISCONNECTED, androidx.media3.session.LibraryResult.RESULT_ERROR_NOT_SUPPORTED, androidx.media3.session.LibraryResult.RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED, androidx.media3.session.LibraryResult.RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED, androidx.media3.session.LibraryResult.RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT, androidx.media3.session.LibraryResult.RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED, androidx.media3.session.LibraryResult.RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION, androidx.media3.session.LibraryResult.RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED, androidx.media3.session.LibraryResult.RESULT_ERROR_SESSION_SETUP_REQUIRED}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE_USE) public static @interface LibraryResult.Code { + @IntDef({androidx.media3.session.LibraryResult.RESULT_SUCCESS, androidx.media3.session.SessionError.INFO_CANCELLED, androidx.media3.session.SessionError.ERROR_UNKNOWN, androidx.media3.session.SessionError.ERROR_INVALID_STATE, androidx.media3.session.SessionError.ERROR_BAD_VALUE, androidx.media3.session.SessionError.ERROR_PERMISSION_DENIED, androidx.media3.session.SessionError.ERROR_IO, androidx.media3.session.SessionError.ERROR_SESSION_DISCONNECTED, androidx.media3.session.SessionError.ERROR_NOT_SUPPORTED, androidx.media3.session.SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED, androidx.media3.session.SessionError.ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED, androidx.media3.session.SessionError.ERROR_SESSION_CONCURRENT_STREAM_LIMIT, androidx.media3.session.SessionError.ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED, androidx.media3.session.SessionError.ERROR_SESSION_NOT_AVAILABLE_IN_REGION, androidx.media3.session.SessionError.ERROR_SESSION_SKIP_LIMIT_REACHED, androidx.media3.session.SessionError.ERROR_SESSION_SETUP_REQUIRED}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE_USE) public static @interface LibraryResult.Code { } public final class MediaBrowser extends androidx.media3.session.MediaController { @@ -1438,122 +1561,129 @@ package androidx.media3.session { field public static final String EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_PREV = "android.media.playback.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_PREVIOUS"; } - public class MediaController implements androidx.media3.common.Player { - method public void addListener(androidx.media3.common.Player.Listener); - method public void addMediaItem(androidx.media3.common.MediaItem); - method public void addMediaItem(int, androidx.media3.common.MediaItem); - method public void addMediaItems(java.util.List); - method public void addMediaItems(int, java.util.List); - method public boolean canAdvertiseSession(); - method public void clearMediaItems(); - method public void clearVideoSurface(); - method public void clearVideoSurface(@Nullable android.view.Surface); - method public void clearVideoSurfaceHolder(@Nullable android.view.SurfaceHolder); - method public void clearVideoSurfaceView(@Nullable android.view.SurfaceView); - method public void clearVideoTextureView(@Nullable android.view.TextureView); - method public void decreaseDeviceVolume(); - method public android.os.Looper getApplicationLooper(); - method public androidx.media3.common.AudioAttributes getAudioAttributes(); - method public androidx.media3.common.Player.Commands getAvailableCommands(); - method public androidx.media3.session.SessionCommands getAvailableSessionCommands(); - method @IntRange(from=0, to=100) public int getBufferedPercentage(); - method public long getBufferedPosition(); - method @Nullable public androidx.media3.session.SessionToken getConnectedToken(); - method public long getContentBufferedPosition(); - method public long getContentDuration(); - method public long getContentPosition(); - method public int getCurrentAdGroupIndex(); - method public int getCurrentAdIndexInAdGroup(); - method public androidx.media3.common.text.CueGroup getCurrentCues(); - method public long getCurrentLiveOffset(); - method @Nullable public androidx.media3.common.MediaItem getCurrentMediaItem(); - method public int getCurrentMediaItemIndex(); - method public int getCurrentPeriodIndex(); - method public long getCurrentPosition(); - method public androidx.media3.common.Timeline getCurrentTimeline(); - method public androidx.media3.common.Tracks getCurrentTracks(); - method public androidx.media3.common.DeviceInfo getDeviceInfo(); - method @IntRange(from=0) public int getDeviceVolume(); - method public long getDuration(); - method public long getMaxSeekToPreviousPosition(); - method public androidx.media3.common.MediaItem getMediaItemAt(int); - method public int getMediaItemCount(); - method public androidx.media3.common.MediaMetadata getMediaMetadata(); - method public int getNextMediaItemIndex(); - method public boolean getPlayWhenReady(); - method public androidx.media3.common.PlaybackParameters getPlaybackParameters(); - method @androidx.media3.common.Player.State public int getPlaybackState(); - method @androidx.media3.common.Player.PlaybackSuppressionReason public int getPlaybackSuppressionReason(); - method @Nullable public androidx.media3.common.PlaybackException getPlayerError(); - method public androidx.media3.common.MediaMetadata getPlaylistMetadata(); - method public int getPreviousMediaItemIndex(); - method @androidx.media3.common.Player.RepeatMode public int getRepeatMode(); - method public long getSeekBackIncrement(); - method public long getSeekForwardIncrement(); - method @Nullable public android.app.PendingIntent getSessionActivity(); - method public boolean getShuffleModeEnabled(); - method public long getTotalBufferedDuration(); - method public androidx.media3.common.TrackSelectionParameters getTrackSelectionParameters(); - method public androidx.media3.common.VideoSize getVideoSize(); - method @FloatRange(from=0, to=1) public float getVolume(); - method public boolean hasNextMediaItem(); - method public boolean hasPreviousMediaItem(); - method public void increaseDeviceVolume(); - method public boolean isCommandAvailable(@androidx.media3.common.Player.Command int); - method public boolean isConnected(); - method public boolean isCurrentMediaItemDynamic(); - method public boolean isCurrentMediaItemLive(); - method public boolean isCurrentMediaItemSeekable(); - method public boolean isDeviceMuted(); - method public boolean isLoading(); - method public boolean isPlaying(); - method public boolean isPlayingAd(); - method public boolean isSessionCommandAvailable(@androidx.media3.session.SessionCommand.CommandCode int); - method public boolean isSessionCommandAvailable(androidx.media3.session.SessionCommand); - method public void moveMediaItem(int, int); - method public void moveMediaItems(int, int, int); - method public void pause(); - method public void play(); - method public void prepare(); - method public void release(); + @com.google.errorprone.annotations.DoNotMock public class MediaController implements androidx.media3.common.Player { + method public final void addListener(androidx.media3.common.Player.Listener); + method public final void addMediaItem(androidx.media3.common.MediaItem); + method public final void addMediaItem(int, androidx.media3.common.MediaItem); + method public final void addMediaItems(int, java.util.List); + method public final void addMediaItems(java.util.List); + method public final boolean canAdvertiseSession(); + method public final void clearMediaItems(); + method public final void clearVideoSurface(); + method public final void clearVideoSurface(@Nullable android.view.Surface); + method public final void clearVideoSurfaceHolder(@Nullable android.view.SurfaceHolder); + method public final void clearVideoSurfaceView(@Nullable android.view.SurfaceView); + method public final void clearVideoTextureView(@Nullable android.view.TextureView); + method @Deprecated public final void decreaseDeviceVolume(); + method public final void decreaseDeviceVolume(@androidx.media3.common.C.VolumeFlags int); + method public final android.os.Looper getApplicationLooper(); + method public final androidx.media3.common.AudioAttributes getAudioAttributes(); + method public final androidx.media3.common.Player.Commands getAvailableCommands(); + method public final androidx.media3.session.SessionCommands getAvailableSessionCommands(); + method @IntRange(from=0, to=100) public final int getBufferedPercentage(); + method public final long getBufferedPosition(); + method @Nullable public final androidx.media3.session.SessionToken getConnectedToken(); + method public final long getContentBufferedPosition(); + method public final long getContentDuration(); + method public final long getContentPosition(); + method public final int getCurrentAdGroupIndex(); + method public final int getCurrentAdIndexInAdGroup(); + method public final androidx.media3.common.text.CueGroup getCurrentCues(); + method public final long getCurrentLiveOffset(); + method @Nullable public final androidx.media3.common.MediaItem getCurrentMediaItem(); + method public final int getCurrentMediaItemIndex(); + method public final int getCurrentPeriodIndex(); + method public final long getCurrentPosition(); + method public final androidx.media3.common.Timeline getCurrentTimeline(); + method public final androidx.media3.common.Tracks getCurrentTracks(); + method public final androidx.media3.common.DeviceInfo getDeviceInfo(); + method @IntRange(from=0) public final int getDeviceVolume(); + method public final long getDuration(); + method public final long getMaxSeekToPreviousPosition(); + method public final androidx.media3.common.MediaItem getMediaItemAt(int); + method public final int getMediaItemCount(); + method public final androidx.media3.common.MediaMetadata getMediaMetadata(); + method public final int getNextMediaItemIndex(); + method public final boolean getPlayWhenReady(); + method public final androidx.media3.common.PlaybackParameters getPlaybackParameters(); + method @androidx.media3.common.Player.State public final int getPlaybackState(); + method @androidx.media3.common.Player.PlaybackSuppressionReason public final int getPlaybackSuppressionReason(); + method @Nullable public final androidx.media3.common.PlaybackException getPlayerError(); + method public final androidx.media3.common.MediaMetadata getPlaylistMetadata(); + method public final int getPreviousMediaItemIndex(); + method @androidx.media3.common.Player.RepeatMode public final int getRepeatMode(); + method public final long getSeekBackIncrement(); + method public final long getSeekForwardIncrement(); + method @Nullable public final android.app.PendingIntent getSessionActivity(); + method public final boolean getShuffleModeEnabled(); + method public final long getTotalBufferedDuration(); + method public final androidx.media3.common.TrackSelectionParameters getTrackSelectionParameters(); + method public final androidx.media3.common.VideoSize getVideoSize(); + method @FloatRange(from=0, to=1) public final float getVolume(); + method public final boolean hasNextMediaItem(); + method public final boolean hasPreviousMediaItem(); + method @Deprecated public final void increaseDeviceVolume(); + method public final void increaseDeviceVolume(@androidx.media3.common.C.VolumeFlags int); + method public final boolean isCommandAvailable(@androidx.media3.common.Player.Command int); + method public final boolean isConnected(); + method public final boolean isCurrentMediaItemDynamic(); + method public final boolean isCurrentMediaItemLive(); + method public final boolean isCurrentMediaItemSeekable(); + method public final boolean isDeviceMuted(); + method public final boolean isLoading(); + method public final boolean isPlaying(); + method public final boolean isPlayingAd(); + method public final boolean isSessionCommandAvailable(androidx.media3.session.SessionCommand); + method public final boolean isSessionCommandAvailable(@androidx.media3.session.SessionCommand.CommandCode int); + method public final void moveMediaItem(int, int); + method public final void moveMediaItems(int, int, int); + method public final void pause(); + method public final void play(); + method public final void prepare(); + method public final void release(); method public static void releaseFuture(java.util.concurrent.Future); - method public void removeListener(androidx.media3.common.Player.Listener); - method public void removeMediaItem(int); - method public void removeMediaItems(int, int); - method public void seekBack(); - method public void seekForward(); - method public void seekTo(long); - method public void seekTo(int, long); - method public void seekToDefaultPosition(); - method public void seekToDefaultPosition(int); - method public void seekToNext(); - method public void seekToNextMediaItem(); - method public void seekToPrevious(); - method public void seekToPreviousMediaItem(); - method public com.google.common.util.concurrent.ListenableFuture sendCustomCommand(androidx.media3.session.SessionCommand, android.os.Bundle); - method public void setDeviceMuted(boolean); - method public void setDeviceVolume(@IntRange(from=0) int); - method public void setMediaItem(androidx.media3.common.MediaItem); - method public void setMediaItem(androidx.media3.common.MediaItem, long); - method public void setMediaItem(androidx.media3.common.MediaItem, boolean); - method public void setMediaItems(java.util.List); - method public void setMediaItems(java.util.List, boolean); - method public void setMediaItems(java.util.List, int, long); - method public void setPlayWhenReady(boolean); - method public void setPlaybackParameters(androidx.media3.common.PlaybackParameters); - method public void setPlaybackSpeed(float); - method public void setPlaylistMetadata(androidx.media3.common.MediaMetadata); - method public com.google.common.util.concurrent.ListenableFuture setRating(String, androidx.media3.common.Rating); - method public com.google.common.util.concurrent.ListenableFuture setRating(androidx.media3.common.Rating); - method public void setRepeatMode(@androidx.media3.common.Player.RepeatMode int); - method public void setShuffleModeEnabled(boolean); - method public void setTrackSelectionParameters(androidx.media3.common.TrackSelectionParameters); - method public void setVideoSurface(@Nullable android.view.Surface); - method public void setVideoSurfaceHolder(@Nullable android.view.SurfaceHolder); - method public void setVideoSurfaceView(@Nullable android.view.SurfaceView); - method public void setVideoTextureView(@Nullable android.view.TextureView); - method public void setVolume(@FloatRange(from=0, to=1) float); - method public void stop(); + method public final void removeListener(androidx.media3.common.Player.Listener); + method public final void removeMediaItem(int); + method public final void removeMediaItems(int, int); + method public final void replaceMediaItem(int, androidx.media3.common.MediaItem); + method public final void replaceMediaItems(int, int, java.util.List); + method public final void seekBack(); + method public final void seekForward(); + method public final void seekTo(int, long); + method public final void seekTo(long); + method public final void seekToDefaultPosition(); + method public final void seekToDefaultPosition(int); + method public final void seekToNext(); + method public final void seekToNextMediaItem(); + method public final void seekToPrevious(); + method public final void seekToPreviousMediaItem(); + method public final com.google.common.util.concurrent.ListenableFuture sendCustomCommand(androidx.media3.session.SessionCommand, android.os.Bundle); + method public final void setAudioAttributes(androidx.media3.common.AudioAttributes, boolean); + method @Deprecated public final void setDeviceMuted(boolean); + method public final void setDeviceMuted(boolean, @androidx.media3.common.C.VolumeFlags int); + method @Deprecated public final void setDeviceVolume(@IntRange(from=0) int); + method public final void setDeviceVolume(@IntRange(from=0) int, @androidx.media3.common.C.VolumeFlags int); + method public final void setMediaItem(androidx.media3.common.MediaItem); + method public final void setMediaItem(androidx.media3.common.MediaItem, boolean); + method public final void setMediaItem(androidx.media3.common.MediaItem, long); + method public final void setMediaItems(java.util.List); + method public final void setMediaItems(java.util.List, boolean); + method public final void setMediaItems(java.util.List, int, long); + method public final void setPlayWhenReady(boolean); + method public final void setPlaybackParameters(androidx.media3.common.PlaybackParameters); + method public final void setPlaybackSpeed(float); + method public final void setPlaylistMetadata(androidx.media3.common.MediaMetadata); + method public final com.google.common.util.concurrent.ListenableFuture setRating(androidx.media3.common.Rating); + method public final com.google.common.util.concurrent.ListenableFuture setRating(String, androidx.media3.common.Rating); + method public final void setRepeatMode(@androidx.media3.common.Player.RepeatMode int); + method public final void setShuffleModeEnabled(boolean); + method public final void setTrackSelectionParameters(androidx.media3.common.TrackSelectionParameters); + method public final void setVideoSurface(@Nullable android.view.Surface); + method public final void setVideoSurfaceHolder(@Nullable android.view.SurfaceHolder); + method public final void setVideoSurfaceView(@Nullable android.view.SurfaceView); + method public final void setVideoTextureView(@Nullable android.view.TextureView); + method public final void setVolume(@FloatRange(from=0, to=1) float); + method public final void stop(); } public static final class MediaController.Builder { @@ -1622,21 +1752,22 @@ package androidx.media3.session { field @IntRange(from=1) public final int notificationId; } - public class MediaSession { - method public void broadcastCustomCommand(androidx.media3.session.SessionCommand, android.os.Bundle); - method public java.util.List getConnectedControllers(); - method public String getId(); - method public androidx.media3.common.Player getPlayer(); - method @Nullable public android.app.PendingIntent getSessionActivity(); - method public androidx.media3.session.SessionToken getToken(); - method public void release(); - method public com.google.common.util.concurrent.ListenableFuture sendCustomCommand(androidx.media3.session.MediaSession.ControllerInfo, androidx.media3.session.SessionCommand, android.os.Bundle); - method public void setAvailableCommands(androidx.media3.session.MediaSession.ControllerInfo, androidx.media3.session.SessionCommands, androidx.media3.common.Player.Commands); - method public com.google.common.util.concurrent.ListenableFuture setCustomLayout(androidx.media3.session.MediaSession.ControllerInfo, java.util.List); - method public void setCustomLayout(java.util.List); - method public void setPlayer(androidx.media3.common.Player); - method public void setSessionExtras(android.os.Bundle); - method public void setSessionExtras(androidx.media3.session.MediaSession.ControllerInfo, android.os.Bundle); + @com.google.errorprone.annotations.DoNotMock public class MediaSession { + method public final void broadcastCustomCommand(androidx.media3.session.SessionCommand, android.os.Bundle); + method public final java.util.List getConnectedControllers(); + method @Nullable public final androidx.media3.session.MediaSession.ControllerInfo getControllerForCurrentRequest(); + method public final String getId(); + method public final androidx.media3.common.Player getPlayer(); + method @Nullable public final android.app.PendingIntent getSessionActivity(); + method public final androidx.media3.session.SessionToken getToken(); + method public final void release(); + method public final com.google.common.util.concurrent.ListenableFuture sendCustomCommand(androidx.media3.session.MediaSession.ControllerInfo, androidx.media3.session.SessionCommand, android.os.Bundle); + method public final void setAvailableCommands(androidx.media3.session.MediaSession.ControllerInfo, androidx.media3.session.SessionCommands, androidx.media3.common.Player.Commands); + method public final com.google.common.util.concurrent.ListenableFuture setCustomLayout(androidx.media3.session.MediaSession.ControllerInfo, java.util.List); + method public final void setCustomLayout(java.util.List); + method public final void setPlayer(androidx.media3.common.Player); + method public final void setSessionExtras(android.os.Bundle); + method public final void setSessionExtras(androidx.media3.session.MediaSession.ControllerInfo, android.os.Bundle); } public static final class MediaSession.Builder { @@ -1653,10 +1784,10 @@ package androidx.media3.session { method public default androidx.media3.session.MediaSession.ConnectionResult onConnect(androidx.media3.session.MediaSession, androidx.media3.session.MediaSession.ControllerInfo); method public default com.google.common.util.concurrent.ListenableFuture onCustomCommand(androidx.media3.session.MediaSession, androidx.media3.session.MediaSession.ControllerInfo, androidx.media3.session.SessionCommand, android.os.Bundle); method public default void onDisconnected(androidx.media3.session.MediaSession, androidx.media3.session.MediaSession.ControllerInfo); - method @androidx.media3.session.SessionResult.Code public default int onPlayerCommandRequest(androidx.media3.session.MediaSession, androidx.media3.session.MediaSession.ControllerInfo, @androidx.media3.common.Player.Command int); + method @Deprecated @androidx.media3.session.SessionResult.Code public default int onPlayerCommandRequest(androidx.media3.session.MediaSession, androidx.media3.session.MediaSession.ControllerInfo, @androidx.media3.common.Player.Command int); method public default void onPostConnect(androidx.media3.session.MediaSession, androidx.media3.session.MediaSession.ControllerInfo); - method public default com.google.common.util.concurrent.ListenableFuture onSetRating(androidx.media3.session.MediaSession, androidx.media3.session.MediaSession.ControllerInfo, String, androidx.media3.common.Rating); method public default com.google.common.util.concurrent.ListenableFuture onSetRating(androidx.media3.session.MediaSession, androidx.media3.session.MediaSession.ControllerInfo, androidx.media3.common.Rating); + method public default com.google.common.util.concurrent.ListenableFuture onSetRating(androidx.media3.session.MediaSession, androidx.media3.session.MediaSession.ControllerInfo, String, androidx.media3.common.Rating); } public static final class MediaSession.ConnectionResult { @@ -1672,6 +1803,7 @@ package androidx.media3.session { method public int getControllerVersion(); method public String getPackageName(); method public int getUid(); + field public static final String LEGACY_CONTROLLER_PACKAGE_NAME = "android.media.session.MediaController"; field public static final int LEGACY_CONTROLLER_VERSION = 0; // 0x0 } @@ -1682,7 +1814,8 @@ package androidx.media3.session { method public final boolean isSessionAdded(androidx.media3.session.MediaSession); method @CallSuper @Nullable public android.os.IBinder onBind(@Nullable android.content.Intent); method @Nullable public abstract androidx.media3.session.MediaSession onGetSession(androidx.media3.session.MediaSession.ControllerInfo); - method public void onUpdateNotification(androidx.media3.session.MediaSession); + method @Deprecated public void onUpdateNotification(androidx.media3.session.MediaSession); + method public void onUpdateNotification(androidx.media3.session.MediaSession, boolean); method public final void removeSession(androidx.media3.session.MediaSession); field public static final String SERVICE_INTERFACE = "androidx.media3.session.MediaSessionService"; } @@ -1719,6 +1852,7 @@ package androidx.media3.session { ctor public SessionCommands.Builder(); method public androidx.media3.session.SessionCommands.Builder add(androidx.media3.session.SessionCommand); method public androidx.media3.session.SessionCommands.Builder add(@androidx.media3.session.SessionCommand.CommandCode int); + method public androidx.media3.session.SessionCommands.Builder addSessionCommands(java.util.Collection); method public androidx.media3.session.SessionCommands build(); method public androidx.media3.session.SessionCommands.Builder remove(androidx.media3.session.SessionCommand); method public androidx.media3.session.SessionCommands.Builder remove(@androidx.media3.session.SessionCommand.CommandCode int); @@ -1748,7 +1882,7 @@ package androidx.media3.session { field @androidx.media3.session.SessionResult.Code public final int resultCode; } - @IntDef({androidx.media3.session.SessionResult.RESULT_SUCCESS, androidx.media3.session.SessionResult.RESULT_ERROR_UNKNOWN, androidx.media3.session.SessionResult.RESULT_ERROR_INVALID_STATE, androidx.media3.session.SessionResult.RESULT_ERROR_BAD_VALUE, androidx.media3.session.SessionResult.RESULT_ERROR_PERMISSION_DENIED, androidx.media3.session.SessionResult.RESULT_ERROR_IO, androidx.media3.session.SessionResult.RESULT_INFO_SKIPPED, androidx.media3.session.SessionResult.RESULT_ERROR_SESSION_DISCONNECTED, androidx.media3.session.SessionResult.RESULT_ERROR_NOT_SUPPORTED, androidx.media3.session.SessionResult.RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED, androidx.media3.session.SessionResult.RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED, androidx.media3.session.SessionResult.RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT, androidx.media3.session.SessionResult.RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED, androidx.media3.session.SessionResult.RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION, androidx.media3.session.SessionResult.RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED, androidx.media3.session.SessionResult.RESULT_ERROR_SESSION_SETUP_REQUIRED}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE_USE) public static @interface SessionResult.Code { + @IntDef({androidx.media3.session.SessionResult.RESULT_SUCCESS, androidx.media3.session.SessionError.INFO_CANCELLED, androidx.media3.session.SessionError.ERROR_UNKNOWN, androidx.media3.session.SessionError.ERROR_INVALID_STATE, androidx.media3.session.SessionError.ERROR_BAD_VALUE, androidx.media3.session.SessionError.ERROR_PERMISSION_DENIED, androidx.media3.session.SessionError.ERROR_IO, androidx.media3.session.SessionError.ERROR_SESSION_DISCONNECTED, androidx.media3.session.SessionError.ERROR_NOT_SUPPORTED, androidx.media3.session.SessionError.ERROR_SESSION_AUTHENTICATION_EXPIRED, androidx.media3.session.SessionError.ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED, androidx.media3.session.SessionError.ERROR_SESSION_CONCURRENT_STREAM_LIMIT, androidx.media3.session.SessionError.ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED, androidx.media3.session.SessionError.ERROR_SESSION_NOT_AVAILABLE_IN_REGION, androidx.media3.session.SessionError.ERROR_SESSION_SKIP_LIMIT_REACHED, androidx.media3.session.SessionError.ERROR_SESSION_SETUP_REQUIRED}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE_USE) public static @interface SessionResult.Code { } public final class SessionToken { diff --git a/build.gradle b/build.gradle index 0c15bce9e5..71669d042e 100644 --- a/build.gradle +++ b/build.gradle @@ -17,15 +17,21 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.2.1' - classpath 'com.google.android.gms:strict-version-matcher-plugin:1.2.2' - classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.21' + classpath 'com.android.tools.build:gradle:8.3.2' + classpath 'com.google.android.gms:strict-version-matcher-plugin:1.2.4' + classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.10' } } allprojects { repositories { google() mavenCentral() + maven { + url 'https://jitpack.io' + content { + includeGroup "com.github.philburk" + } + } } if (it.hasProperty('externalBuildDir')) { if (!new File(externalBuildDir).isAbsolute()) { @@ -35,5 +41,3 @@ allprojects { } group = 'androidx.media3' } - -apply from: 'javadoc_combined.gradle' diff --git a/common_library_config.gradle b/common_library_config.gradle index a831ddea0b..c54e686f53 100644 --- a/common_library_config.gradle +++ b/common_library_config.gradle @@ -14,6 +14,8 @@ apply from: "$gradle.ext.androidxMediaSettingsDir/constants.gradle" apply plugin: 'com.android.library' +group = 'androidx.media3' + android { compileSdkVersion project.ext.compileSdkVersion diff --git a/constants.gradle b/constants.gradle index e159877c50..36b6fd512f 100644 --- a/constants.gradle +++ b/constants.gradle @@ -12,50 +12,52 @@ // See the License for the specific language governing permissions and // limitations under the License. project.ext { - releaseVersion = '1.0.2' - releaseVersionCode = 1_000_002_3_00 - minSdkVersion = 16 - appTargetSdkVersion = 33 - // API version before restricting local file access. - // https://developer.android.com/training/data-storage/app-specific - mainDemoAppTargetSdkVersion = 29 + releaseVersion = '1.5.0-alpha01' + releaseVersionCode = 1_005_000_0_01 + minSdkVersion = 21 + // See https://developer.android.com/training/cars/media/automotive-os#automotive-module + automotiveMinSdkVersion = 28 + appTargetSdkVersion = 34 // Upgrading this requires [Internal ref: b/193254928] to be fixed, or some // additional robolectric config. targetSdkVersion = 30 - compileSdkVersion = 33 + compileSdkVersion = 35 dexmakerVersion = '2.28.3' + // Use the same JUnit version as the Android repo: + // https://cs.android.com/android/platform/superproject/main/+/main:external/junit/METADATA junitVersion = '4.13.2' // Use the same Guava version as the Android repo: - // https://cs.android.com/android/platform/superproject/+/master:external/guava/METADATA - guavaVersion = '31.0.1-android' + // https://cs.android.com/android/platform/superproject/main/+/main:external/guava/METADATA + guavaVersion = '33.0.0-android' + glideVersion = '4.14.2' + kotlinxCoroutinesVersion = '1.8.1' + leakCanaryVersion = '2.10' mockitoVersion = '3.12.4' - robolectricVersion = '4.8.1' + robolectricVersion = '4.11' // Keep this in sync with Google's internal Checker Framework version. checkerframeworkVersion = '3.13.0' - checkerframeworkCompatVersion = '2.5.5' - errorProneVersion = '2.10.0' + errorProneVersion = '2.18.0' jsr305Version = '3.0.2' - kotlinAnnotationsVersion = '1.5.31' - androidxAnnotationVersion = '1.3.0' - androidxAnnotationExperimentalVersion = '1.2.0' - androidxAppCompatVersion = '1.3.1' - androidxCollectionVersion = '1.1.0' - androidxConstraintLayoutVersion = '2.0.4' - androidxCoreVersion = '1.7.0' - androidxFuturesVersion = '1.1.0' - androidxMediaVersion = '1.6.0' - androidxMedia2Version = '1.2.0' - androidxMultidexVersion = '2.0.1' - androidxRecyclerViewVersion = '1.2.1' - androidxMaterialVersion = '1.4.0' - androidxTestCoreVersion = '1.4.0' - androidxTestJUnitVersion = '1.1.3' - androidxTestRunnerVersion = '1.4.0' - androidxTestRulesVersion = '1.4.0' - androidxTestServicesStorageVersion = '1.4.0' - androidxTestTruthVersion = '1.4.0' - truthVersion = '1.1.3' - okhttpVersion = '4.9.2' + kotlinAnnotationsVersion = '1.9.0' + androidxAnnotationVersion = '1.6.0' + androidxAnnotationExperimentalVersion = '1.3.1' + androidxAppCompatVersion = '1.6.1' + androidxCollectionVersion = '1.2.0' + androidxConstraintLayoutVersion = '2.1.4' + androidxCoreVersion = '1.8.0' + androidxExifInterfaceVersion = '1.3.6' + androidxLifecycleVersion = '2.6.0' + androidxMediaVersion = '1.7.0' + androidxRecyclerViewVersion = '1.3.0' + androidxMaterialVersion = '1.8.0' + androidxTestCoreVersion = '1.5.0' + androidxTestEspressoVersion = '3.5.1' + androidxTestJUnitVersion = '1.1.5' + androidxTestRunnerVersion = '1.5.2' + androidxTestRulesVersion = '1.5.0' + androidxTestTruthVersion = '1.5.0' + truthVersion = '1.4.0' + okhttpVersion = '4.12.0' modulePrefix = ':' if (gradle.ext.has('androidxMediaModulePrefix')) { modulePrefix += gradle.ext.androidxMediaModulePrefix diff --git a/core_settings.gradle b/core_settings.gradle index 0ef4e443c4..eb976e28cb 100644 --- a/core_settings.gradle +++ b/core_settings.gradle @@ -21,11 +21,15 @@ if (gradle.ext.has('androidxMediaModulePrefix')) { modulePrefix += gradle.ext.androidxMediaModulePrefix } -rootProject.name = gradle.ext.androidxMediaProjectName - include modulePrefix + 'lib-common' project(modulePrefix + 'lib-common').projectDir = new File(rootDir, 'libraries/common') +include modulePrefix + 'lib-common-ktx' +project(modulePrefix + 'lib-common-ktx').projectDir = new File(rootDir, 'libraries/common_ktx') + +include modulePrefix + 'lib-container' +project(modulePrefix + 'lib-container').projectDir = new File(rootDir, 'libraries/container') + include modulePrefix + 'lib-session' project(modulePrefix + 'lib-session').projectDir = new File(rootDir, 'libraries/session') @@ -56,6 +60,8 @@ include modulePrefix + 'lib-datasource' project(modulePrefix + 'lib-datasource').projectDir = new File(rootDir, 'libraries/datasource') include modulePrefix + 'lib-datasource-cronet' project(modulePrefix + 'lib-datasource-cronet').projectDir = new File(rootDir, 'libraries/datasource_cronet') +include modulePrefix + 'lib-datasource-httpengine' +project(modulePrefix + 'lib-datasource-httpengine').projectDir = new File(rootDir, 'libraries/datasource_httpengine') include modulePrefix + 'lib-datasource-rtmp' project(modulePrefix + 'lib-datasource-rtmp').projectDir = new File(rootDir, 'libraries/datasource_rtmp') include modulePrefix + 'lib-datasource-okhttp' @@ -69,6 +75,12 @@ include modulePrefix + 'lib-decoder-ffmpeg' project(modulePrefix + 'lib-decoder-ffmpeg').projectDir = new File(rootDir, 'libraries/decoder_ffmpeg') include modulePrefix + 'lib-decoder-flac' project(modulePrefix + 'lib-decoder-flac').projectDir = new File(rootDir, 'libraries/decoder_flac') +include modulePrefix + 'lib-decoder-iamf' +project(modulePrefix + 'lib-decoder-iamf').projectDir = new File(rootDir, 'libraries/decoder_iamf') +if (gradle.ext.has('androidxMediaEnableMidiModule') && gradle.ext.androidxMediaEnableMidiModule) { + include modulePrefix + 'lib-decoder-midi' + project(modulePrefix + 'lib-decoder-midi').projectDir = new File(rootDir, 'libraries/decoder_midi') +} include modulePrefix + 'lib-decoder-opus' project(modulePrefix + 'lib-decoder-opus').projectDir = new File(rootDir, 'libraries/decoder_opus') include modulePrefix + 'lib-decoder-vp9' @@ -83,6 +95,9 @@ project(modulePrefix + 'lib-cast').projectDir = new File(rootDir, 'libraries/cas include modulePrefix + 'lib-effect' project(modulePrefix + 'lib-effect').projectDir = new File(rootDir, 'libraries/effect') +include modulePrefix + 'lib-muxer' +project(modulePrefix + 'lib-muxer').projectDir = new File(rootDir, 'libraries/muxer') + include modulePrefix + 'lib-transformer' project(modulePrefix + 'lib-transformer').projectDir = new File(rootDir, 'libraries/transformer') @@ -92,7 +107,3 @@ include modulePrefix + 'test-data' project(modulePrefix + 'test-data').projectDir = new File(rootDir, 'libraries/test_data') include modulePrefix + 'test-utils' project(modulePrefix + 'test-utils').projectDir = new File(rootDir, 'libraries/test_utils') -include modulePrefix + 'test-session-common' -project(modulePrefix + 'test-session-common').projectDir = new File(rootDir, 'libraries/test_session_common') -include modulePrefix + 'test-session-current' -project(modulePrefix + 'test-session-current').projectDir = new File(rootDir, 'libraries/test_session_current') diff --git a/demos/cast/README.md b/demos/cast/README.md index b636d2c2e0..aae757dd62 100644 --- a/demos/cast/README.md +++ b/demos/cast/README.md @@ -1,7 +1,116 @@ # Cast demo -This app demonstrates integration with Google Cast, as well as switching between -Google Cast and local playback using ExoPlayer. +This app demonstrates switching between Google Cast and local playback by using +`CastPlayer` and `ExoPlayer`. + +## Building the demo app See the [demos README](../README.md) for instructions on how to build and run this demo. + +Test your streams by adding a `MediaItem` with URI and mime type to the +`DemoUtil` and deploy the app on a real device for casting. + +## Customization with `OptionsProvider` + +The Cast SDK behaviour in the demo app or your own app can be customized by +providing a custom `OptionsProvider` (see +[`DefaultCastOptionsProvider`](https://github.com/androidx/media/blob/release/libraries/cast/src/main/java/androidx/media3/cast/DefaultCastOptionsProvider.java) +also). + +Replace the default options provider in the `AndroidManifest.xml` with your own: + +```xml + +``` + +### Using a different Cast receiver app with the Media3 cast demo sender app + +The Media3 cast demo app is an implementation of an +[Android Cast *sender app*](https://developers.google.com/cast/docs/android_sender) +that uses a *default Cast receiver app* (running on the Cast device) that is +customized to support DRM protected streams +[by passing DRM configuration via `MediaInfo`](https://developers.google.com/cast/docs/android_sender/exoplayer). +Hence Widevine DRM credentials can also be populated with a +`MediaItem.DrmConfiguration.Builder` (see the samples in `DemoUtil` marked with +`Widevine`). + +If you test your own streams with this demo app, keep in mind that for your +production app you need to +[choose your own receiver app](https://developers.google.com/cast/docs/web_receiver#choose_a_web_receiver) +and have your own receiver app ID. + +If you have a receiver app already and want to quickly test whether it works +well together with the `CastPlayer`, then you can configure the demo app to use +your receiver: + +```java +public class MyOptionsProvider implements OptionsProvider { + @NonNull + @Override + public CastOptions getCastOptions(Context context) { + return new CastOptions.Builder() + .setReceiverApplicationId(YOUR_RECEIVER_APP_ID) + // other options + .build(); + } +} +``` + +You can also use the plain +[default Cast receiver app](https://developers.google.com/cast/docs/web_receiver#default_media_web_receiver) +by using `CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID`. + +#### Converting a Media3 `MediaItem` to a Cast `MediaQueueItem` + +This demo app uses the +[`DefaultMediaItemConverter`](https://github.com/androidx/media/blob/release/libraries/cast/src/main/java/androidx/media3/cast/DefaultMediaItemConverter.java) +to convert a Media3 `MediaItem` to a `MediaQueueItem` of the Cast API. Apps that +use a custom receiver app, can use a custom `MediaItemConverter` instance by +passing it into the constructor of `CastPlayer`. + +### Media session and notification + +This Media3 cast demo app uses the media session and notification support +provided by the Cast SDK. If your app already integrates with a `MediaSession`, +the Cast session can be disabled to avoid duplicate notifications or sessions: + +```java +public class MyOptionsProvider implements OptionsProvider { + @NonNull + @Override + public CastOptions getCastOptions(Context context) { + return new CastOptions.Builder() + .setCastMediaOptions( + new CastMediaOptions.Builder() + .setMediaSessionEnabled(false) + .setNotificationOptions(null) + .build()) + // other options + .build(); + } +} +``` + +## Supported media formats + +Whether a specific stream is supported on a Cast device largely depends on the +receiver app, the media player used by the receiver and the Cast device, rather +then the implementation of the sender that basically only provides media URI and +metadata. + +Generally, Google Cast and all Cast Web Receiver applications support the media +facilities and types listed on +[this page](https://developers.google.com/cast/docs/media). If you build a +custom receiver that uses a media player different to the media player of the +Cast receiver SDK, your app may support +[other formats or features](https://github.com/shaka-project/shaka-player) than +listed in the reference above. + +The Media3 team can't give support for building a receiver app or investigations +regarding support for certain media formats on a cast devices. Please consult +the Cast documentation around +[building a receiver application](https://developers.google.com/cast/docs/web_receiver) +for further details. diff --git a/demos/cast/build.gradle b/demos/cast/build.gradle index 641ecddedc..a60081ea3a 100644 --- a/demos/cast/build.gradle +++ b/demos/cast/build.gradle @@ -15,7 +15,9 @@ apply from: '../../constants.gradle' apply plugin: 'com.android.application' android { - compileSdkVersion project.ext.compileSdkVersion + namespace 'androidx.media3.demo.cast' + + compileSdk project.ext.compileSdkVersion compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 @@ -27,7 +29,6 @@ android { versionCode project.ext.releaseVersionCode minSdkVersion project.ext.minSdkVersion targetSdkVersion project.ext.appTargetSdkVersion - multiDexEnabled true } buildTypes { @@ -60,7 +61,6 @@ dependencies { implementation project(modulePrefix + 'lib-ui') implementation project(modulePrefix + 'lib-cast') implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion - implementation 'androidx.multidex:multidex:' + androidxMultidexVersion implementation 'androidx.recyclerview:recyclerview:' + androidxRecyclerViewVersion implementation 'com.google.android.material:material:' + androidxMaterialVersion } diff --git a/demos/cast/src/main/AndroidManifest.xml b/demos/cast/src/main/AndroidManifest.xml index b6a9b56eac..e852ca39fa 100644 --- a/demos/cast/src/main/AndroidManifest.xml +++ b/demos/cast/src/main/AndroidManifest.xml @@ -23,7 +23,6 @@ samples = new ArrayList<>(); - - // Clear content. + // HLS streams. samples.add( new MediaItem.Builder() - .setUri("https://storage.googleapis.com/wvmedia/clear/h264/tears/tears.mpd") - .setMediaMetadata(new MediaMetadata.Builder().setTitle("Clear DASH: Tears").build()) - .setMimeType(MIME_TYPE_DASH) - .build()); - samples.add( - new MediaItem.Builder() - .setUri("https://storage.googleapis.com/shaka-demo-assets/angel-one-hls/hls.m3u8") - .setMediaMetadata(new MediaMetadata.Builder().setTitle("Clear HLS: Angel one").build()) + .setUri( + "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/bipbop_4x3_variant.m3u8") + .setMediaMetadata( + new MediaMetadata.Builder() + .setTitle("HLS (adaptive): Apple 4x3 basic stream (TS/h264/aac)") + .build()) .setMimeType(MIME_TYPE_HLS) .build()); + samples.add( + new MediaItem.Builder() + .setUri( + "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_16x9/bipbop_16x9_variant.m3u8") + .setMediaMetadata( + new MediaMetadata.Builder() + .setTitle("HLS (adaptive): Apple 16x9 basic stream (TS/h264/aac)") + .build()) + .setMimeType(MIME_TYPE_HLS) + .build()); + samples.add( + new MediaItem.Builder() + .setUri( + "https://commondatastorage.googleapis.com/gtv-videos-bucket/CastVideos/hls/DesigningForGoogleCast.m3u8") + .setMediaMetadata( + new MediaMetadata.Builder() + .setTitle("HLS (1280x720): Designing For Google Cast (TS/h264/aac)") + .build()) + .setMimeType(MIME_TYPE_HLS) + .build()); + // DASH streams + samples.add( + new MediaItem.Builder() + .setUri("https://storage.googleapis.com/wvmedia/clear/h264/tears/tears.mpd") + .setMediaMetadata( + new MediaMetadata.Builder() + .setTitle("DASH (adaptive): Tears of steal (HD, MP4, H264/aac)") + .build()) + .setMimeType(MIME_TYPE_DASH) + .build()); + samples.add( + new MediaItem.Builder() + .setUri("https://storage.googleapis.com/wvmedia/clear/h264/tears/tears_uhd.mpd") + .setMediaMetadata( + new MediaMetadata.Builder() + .setTitle("DASH (3840x1714): Tears of steal (MP4, H264/aac)") + .build()) + .setMimeType(MIME_TYPE_DASH) + .build()); + // Progressive video streams samples.add( new MediaItem.Builder() .setUri("https://html5demos.com/assets/dizzy.mp4") - .setMediaMetadata(new MediaMetadata.Builder().setTitle("Clear MP4: Dizzy").build()) + .setMediaMetadata( + new MediaMetadata.Builder().setTitle("MP4 (480x360): Dizzy (H264/aac)").build()) .setMimeType(MIME_TYPE_VIDEO_MP4) .build()); - + samples.add( + new MediaItem.Builder() + .setUri( + "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv") + .setMediaMetadata( + new MediaMetadata.Builder().setTitle("MKV (1280x720): Screens (h264/aac)").build()) + .setMimeType(MIME_TYPE_VIDEO_MP4) + .build()); + // Progressive audio streams with artwork + samples.add( + new MediaItem.Builder() + .setUri("https://storage.googleapis.com/automotive-media/Keys_To_The_Kingdom.mp3") + .setMediaMetadata( + new MediaMetadata.Builder() + .setTitle("MP3: Keys To The Kingdom (44100/stereo/320kb/s)") + .setArtist("The 126ers") + .setAlbumTitle("Youtube Audio Library Rock 2") + .setGenre("Rock") + .setTrackNumber(1) + .setTotalTrackCount(4) + .setArtworkUri( + Uri.parse( + "https://storage.googleapis.com/automotive-media/album_art_3.jpg")) + .build()) + .setMimeType(MimeTypes.AUDIO_MPEG) + .build()); // DRM content. samples.add( new MediaItem.Builder() diff --git a/demos/cast/src/main/java/androidx/media3/demo/cast/MainActivity.java b/demos/cast/src/main/java/androidx/media3/demo/cast/MainActivity.java index aa9bd9f08e..ae49a6f45a 100644 --- a/demos/cast/src/main/java/androidx/media3/demo/cast/MainActivity.java +++ b/demos/cast/src/main/java/androidx/media3/demo/cast/MainActivity.java @@ -44,6 +44,7 @@ import androidx.recyclerview.widget.RecyclerView.ViewHolder; import com.google.android.gms.cast.framework.CastButtonFactory; import com.google.android.gms.cast.framework.CastContext; import com.google.android.gms.dynamite.DynamiteModule; +import com.google.common.util.concurrent.MoreExecutors; /** * An activity that plays video using {@link ExoPlayer} and supports casting using ExoPlayer's Cast @@ -65,7 +66,7 @@ public class MainActivity extends AppCompatActivity super.onCreate(savedInstanceState); // Getting the cast context later than onStart can cause device discovery not to take place. try { - castContext = CastContext.getSharedInstance(this); + castContext = CastContext.getSharedInstance(this, MoreExecutors.directExecutor()).getResult(); } catch (RuntimeException e) { Throwable cause = e.getCause(); while (cause != null) { diff --git a/demos/cast/src/main/java/androidx/media3/demo/cast/package-info.java b/demos/cast/src/main/java/androidx/media3/demo/cast/package-info.java index abd0359226..b2977f6849 100644 --- a/demos/cast/src/main/java/androidx/media3/demo/cast/package-info.java +++ b/demos/cast/src/main/java/androidx/media3/demo/cast/package-info.java @@ -14,6 +14,9 @@ * limitations under the License. */ @NonNullApi +@OptIn(markerClass = UnstableApi.class) package androidx.media3.demo.cast; +import androidx.annotation.OptIn; import androidx.media3.common.util.NonNullApi; +import androidx.media3.common.util.UnstableApi; diff --git a/demos/cast/src/main/res/drawable/ic_baseline_cast_connected_400.xml b/demos/cast/src/main/res/drawable/ic_baseline_cast_connected_400.xml index e65129b686..ac7c75ab73 100644 --- a/demos/cast/src/main/res/drawable/ic_baseline_cast_connected_400.xml +++ b/demos/cast/src/main/res/drawable/ic_baseline_cast_connected_400.xml @@ -13,8 +13,11 @@ ~ See the License for the specific language governing permissions and ~ limitations under the License. --> + + android:width="400dp" xmlns:android="http://schemas.android.com/apk/res/android" + tools:ignore="NewApi" xmlns:tools="http://schemas.android.com/tools"> diff --git a/demos/cast/src/main/res/drawable/ic_plus.xml b/demos/cast/src/main/res/drawable/ic_plus.xml index 5a5a5154c9..d90d7f1ff9 100644 --- a/demos/cast/src/main/res/drawable/ic_plus.xml +++ b/demos/cast/src/main/res/drawable/ic_plus.xml @@ -13,11 +13,15 @@ See the License for the specific language governing permissions and limitations under the License. --> + + android:width="24.0dp" + tools:ignore="NewApi" > diff --git a/demos/compose/README.md b/demos/compose/README.md new file mode 100644 index 0000000000..a70fc11022 --- /dev/null +++ b/demos/compose/README.md @@ -0,0 +1,14 @@ +# ExoPlayer demo with Compose integration + +This is an experimental ExoPlayer demo app that is built fully using Compose +features. This should be taken as Work-In-Progress, rather than experimental API +for testing out application development with the media3 and Jetpack Compose +libraries. Please await further announcement via Release Notes for when the +implementation is fully integrated into the library. + +For an intermediate solution, use Jetpack Compose Interop with AndroidView and +PlayerView. However, note that it provides limited functionality and some +features may not be supported. + +See the [demos README](../README.md) for instructions on how to build and run +this demo. diff --git a/demos/compose/build.gradle b/demos/compose/build.gradle new file mode 100644 index 0000000000..94680ec4c2 --- /dev/null +++ b/demos/compose/build.gradle @@ -0,0 +1,87 @@ +// Copyright 2024 The Android Open Source Project +// +// Licensed under the Apache License, Version 2.0 (the "License"); +// you may not use this file except in compliance with the License. +// You may obtain a copy of the License at +// +// https://www.apache.org/licenses/LICENSE-2.0 +// +// Unless required by applicable law or agreed to in writing, software +// distributed under the License is distributed on an "AS IS" BASIS, +// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. +// See the License for the specific language governing permissions and +// limitations under the License. +apply from: '../../constants.gradle' +apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' + +android { + namespace 'androidx.media3.demo.compose' + + compileSdk project.ext.compileSdkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + kotlinOptions { + jvmTarget = '1.8' + } + + defaultConfig { + versionName project.ext.releaseVersion + versionCode project.ext.releaseVersionCode + minSdkVersion project.ext.minSdkVersion + targetSdkVersion project.ext.appTargetSdkVersion + } + + buildTypes { + release { + shrinkResources true + minifyEnabled true + signingConfig signingConfigs.debug + } + debug { + jniDebuggable = true + } + } + + lintOptions { + // The demo app isn't indexed, and doesn't have translations. + disable 'GoogleAppIndexingWarning','MissingTranslation' + } + buildFeatures { + viewBinding true + compose true + } + composeOptions { + kotlinCompilerExtensionVersion = "1.5.3" + } + + testOptions { + unitTests { + includeAndroidResources = true + } + } +} + +dependencies { + def composeBom = platform('androidx.compose:compose-bom:2024.05.00') + implementation composeBom + + implementation 'androidx.activity:activity-compose:1.9.0' + implementation 'androidx.compose.foundation:foundation-android:1.6.7' + implementation 'androidx.compose.material3:material3-android:1.2.1' + implementation 'com.google.android.material:material:' + androidxMaterialVersion + + implementation project(modulePrefix + 'lib-exoplayer') + + // For detecting and debugging leaks only. LeakCanary is not needed for demo app to work. + debugImplementation 'com.squareup.leakcanary:leakcanary-android:' + leakCanaryVersion + + testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:' + kotlinxCoroutinesVersion + testImplementation 'org.robolectric:robolectric:' + robolectricVersion + testImplementation project(modulePrefix + 'test-utils') + +} diff --git a/demos/compose/src/main/AndroidManifest.xml b/demos/compose/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..a348407347 --- /dev/null +++ b/demos/compose/src/main/AndroidManifest.xml @@ -0,0 +1,38 @@ + + + + + + + + + + + + + + + + + diff --git a/demos/compose/src/main/java/androidx/media3/demo/compose/MainActivity.kt b/demos/compose/src/main/java/androidx/media3/demo/compose/MainActivity.kt new file mode 100644 index 0000000000..07dc6e1212 --- /dev/null +++ b/demos/compose/src/main/java/androidx/media3/demo/compose/MainActivity.kt @@ -0,0 +1,63 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.media3.demo.compose + +import android.os.Bundle +import androidx.activity.ComponentActivity +import androidx.activity.compose.setContent +import androidx.activity.enableEdgeToEdge +import androidx.compose.foundation.layout.Arrangement +import androidx.compose.foundation.layout.Column +import androidx.compose.material3.Surface +import androidx.compose.runtime.remember +import androidx.compose.ui.Alignment +import androidx.compose.ui.Modifier +import androidx.compose.ui.platform.LocalContext +import androidx.media3.common.MediaItem +import androidx.media3.common.Player +import androidx.media3.demo.compose.data.videos +import androidx.media3.exoplayer.ExoPlayer + +class MainActivity : ComponentActivity() { + + override fun onCreate(savedInstanceState: Bundle?) { + super.onCreate(savedInstanceState) + enableEdgeToEdge() + setContent { + Surface { + Column( + verticalArrangement = Arrangement.Center, + horizontalAlignment = Alignment.CenterHorizontally, + ) { + val context = LocalContext.current + val exoPlayer = remember { + ExoPlayer.Builder(context).build().apply { + setMediaItem(MediaItem.fromUri(videos[0])) + prepare() + playWhenReady = true + repeatMode = Player.REPEAT_MODE_ONE + } + } + PlayerSurface( + player = exoPlayer, + surfaceType = SURFACE_TYPE_SURFACE_VIEW, + modifier = Modifier.align(Alignment.CenterHorizontally), + ) + } + } + } + } +} diff --git a/demos/compose/src/main/java/androidx/media3/demo/compose/PlayerSurface.kt b/demos/compose/src/main/java/androidx/media3/demo/compose/PlayerSurface.kt new file mode 100644 index 0000000000..d26cc74733 --- /dev/null +++ b/demos/compose/src/main/java/androidx/media3/demo/compose/PlayerSurface.kt @@ -0,0 +1,76 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ + +package androidx.media3.demo.compose + +import android.view.Surface +import android.view.SurfaceView +import android.view.TextureView +import androidx.annotation.IntDef +import androidx.compose.foundation.AndroidEmbeddedExternalSurface +import androidx.compose.foundation.AndroidExternalSurface +import androidx.compose.foundation.AndroidExternalSurfaceScope +import androidx.compose.runtime.Composable +import androidx.compose.ui.Modifier +import androidx.media3.common.Player + +/** + * Provides a dedicated drawing [Surface] for media playbacks using a [Player]. + * + * The player's video output is displayed with either a [SurfaceView]/[AndroidExternalSurface] or a + * [TextureView]/[AndroidEmbeddedExternalSurface]. + * + * [Player] takes care of attaching the rendered output to the [Surface] and clearing it, when it is + * destroyed. + * + * See + * [Choosing a surface type](https://developer.android.com/media/media3/ui/playerview#surfacetype) + * for more information. + */ +@Composable +fun PlayerSurface(player: Player, surfaceType: @SurfaceType Int, modifier: Modifier = Modifier) { + val onSurfaceCreated: (Surface) -> Unit = { surface -> player.setVideoSurface(surface) } + val onSurfaceDestroyed: () -> Unit = { player.setVideoSurface(null) } + val onSurfaceInitialized: AndroidExternalSurfaceScope.() -> Unit = { + onSurface { surface, _, _ -> + onSurfaceCreated(surface) + surface.onDestroyed { onSurfaceDestroyed() } + } + } + + when (surfaceType) { + SURFACE_TYPE_SURFACE_VIEW -> + AndroidExternalSurface(modifier = modifier, onInit = onSurfaceInitialized) + SURFACE_TYPE_TEXTURE_VIEW -> + AndroidEmbeddedExternalSurface(modifier = modifier, onInit = onSurfaceInitialized) + else -> throw IllegalArgumentException("Unrecognized surface type: $surfaceType") + } +} + +/** + * The type of surface view used for media playbacks. One of [SURFACE_TYPE_SURFACE_VIEW] or + * [SURFACE_TYPE_TEXTURE_VIEW]. + */ +@MustBeDocumented +@Retention(AnnotationRetention.SOURCE) +@Target(AnnotationTarget.CLASS, AnnotationTarget.TYPE, AnnotationTarget.TYPE_PARAMETER) +@IntDef(SURFACE_TYPE_SURFACE_VIEW, SURFACE_TYPE_TEXTURE_VIEW) +annotation class SurfaceType + +/** Surface type equivalent to [SurfaceView] . */ +const val SURFACE_TYPE_SURFACE_VIEW = 1 +/** Surface type equivalent to [TextureView]. */ +const val SURFACE_TYPE_TEXTURE_VIEW = 2 diff --git a/demos/compose/src/main/java/androidx/media3/demo/compose/data/videos.kt b/demos/compose/src/main/java/androidx/media3/demo/compose/data/videos.kt new file mode 100644 index 0000000000..0da7be9aba --- /dev/null +++ b/demos/compose/src/main/java/androidx/media3/demo/compose/data/videos.kt @@ -0,0 +1,23 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * https://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.media3.demo.compose.data + +val videos = + listOf( + "https://html5demos.com/assets/dizzy.mp4", + "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/gear0/fileSequence0.aac", + "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/video-vp9-360.webm", + ) diff --git a/demos/compose/src/main/res/drawable/divider.xml b/demos/compose/src/main/res/drawable/divider.xml new file mode 100644 index 0000000000..1602867ace --- /dev/null +++ b/demos/compose/src/main/res/drawable/divider.xml @@ -0,0 +1,20 @@ + + + + + + diff --git a/demos/compose/src/main/res/mipmap-hdpi/ic_launcher.png b/demos/compose/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..adaa93220e Binary files /dev/null and b/demos/compose/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/demos/compose/src/main/res/mipmap-mdpi/ic_launcher.png b/demos/compose/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..9b6f7d5e80 Binary files /dev/null and b/demos/compose/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/demos/compose/src/main/res/mipmap-xhdpi/ic_launcher.png b/demos/compose/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..2101026c9f Binary files /dev/null and b/demos/compose/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/demos/compose/src/main/res/mipmap-xxhdpi/ic_launcher.png b/demos/compose/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..223ec8bd11 Binary files /dev/null and b/demos/compose/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/demos/compose/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/demos/compose/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..698ed68c42 Binary files /dev/null and b/demos/compose/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/demos/compose/src/main/res/values-night/themes.xml b/demos/compose/src/main/res/values-night/themes.xml new file mode 100644 index 0000000000..0197d80442 --- /dev/null +++ b/demos/compose/src/main/res/values-night/themes.xml @@ -0,0 +1,31 @@ + + + + + + diff --git a/demos/compose/src/main/res/values/colors.xml b/demos/compose/src/main/res/values/colors.xml new file mode 100644 index 0000000000..dac0cb81c7 --- /dev/null +++ b/demos/compose/src/main/res/values/colors.xml @@ -0,0 +1,30 @@ + + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + #FF999999 + #292929 + #1c1c1c + #363434 + #635E5E + #646464 + diff --git a/demos/compose/src/main/res/values/strings.xml b/demos/compose/src/main/res/values/strings.xml new file mode 100644 index 0000000000..a11306db98 --- /dev/null +++ b/demos/compose/src/main/res/values/strings.xml @@ -0,0 +1,26 @@ + + + + Media3 Compose Demo + Current playlist + Click to view your play list + Added %1$s to playlist + Shuffle + Play + Waiting for playlist to load… + + "Without notification access the app can't warn about failed background operations" + diff --git a/demos/compose/src/main/res/values/themes.xml b/demos/compose/src/main/res/values/themes.xml new file mode 100644 index 0000000000..c45328c17c --- /dev/null +++ b/demos/compose/src/main/res/values/themes.xml @@ -0,0 +1,31 @@ + + + + + + diff --git a/demos/composition/README.md b/demos/composition/README.md new file mode 100644 index 0000000000..9162757673 --- /dev/null +++ b/demos/composition/README.md @@ -0,0 +1,8 @@ +# Composition demo + +This app is an **EXPERIMENTAL** demo app created to explore the potential of `Composition` and `CompositionPlayer` APIs. It may exhibit limited features, occasional bugs, or unexpected behaviors. + +**Attention**: `CompositionPlayer` APIs should be taken as work in progress, rather than experimental API. Please await further announcement via [Release Notes](https://github.com/androidx/media/releases) for when the APIs are fully integrated. + +See the [demos README](../README.md) for instructions on how to build and run +this demo. diff --git a/demos/composition/build.gradle b/demos/composition/build.gradle new file mode 100644 index 0000000000..a421b8a386 --- /dev/null +++ b/demos/composition/build.gradle @@ -0,0 +1,61 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +apply from: '../../constants.gradle' +apply plugin: 'com.android.application' + +android { + namespace 'androidx.media3.demo.composition' + + compileSdk project.ext.compileSdkVersion + + compileOptions { + sourceCompatibility JavaVersion.VERSION_1_8 + targetCompatibility JavaVersion.VERSION_1_8 + } + + defaultConfig { + versionName project.ext.releaseVersion + versionCode project.ext.releaseVersionCode + minSdkVersion project.ext.minSdkVersion + targetSdkVersion project.ext.appTargetSdkVersion + } + + buildTypes { + release { + shrinkResources true + minifyEnabled true + proguardFiles getDefaultProguardFile('proguard-android-optimize.txt'), 'proguard-rules.txt' + signingConfig signingConfigs.debug + } + } + + lintOptions { + // This demo app isn't indexed and doesn't have translations. + disable 'GoogleAppIndexingWarning','MissingTranslation' + } +} + +dependencies { + implementation 'androidx.appcompat:appcompat:1.6.1' + implementation 'com.google.android.material:material:' + androidxMaterialVersion + implementation project(modulePrefix + 'lib-effect') + implementation project(modulePrefix + 'lib-exoplayer') + implementation project(modulePrefix + 'lib-exoplayer-dash') + implementation project(modulePrefix + 'lib-transformer') + implementation project(modulePrefix + 'lib-ui') + implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion + compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion +} diff --git a/demos/composition/proguard-rules.txt b/demos/composition/proguard-rules.txt new file mode 100644 index 0000000000..cd85b36a6c --- /dev/null +++ b/demos/composition/proguard-rules.txt @@ -0,0 +1 @@ +# Proguard rules specific to the composition demo app. diff --git a/demos/composition/src/main/AndroidManifest.xml b/demos/composition/src/main/AndroidManifest.xml new file mode 100644 index 0000000000..a521883c12 --- /dev/null +++ b/demos/composition/src/main/AndroidManifest.xml @@ -0,0 +1,51 @@ + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demos/composition/src/main/java/androidx/media3/demo/composition/AssetItemAdapter.java b/demos/composition/src/main/java/androidx/media3/demo/composition/AssetItemAdapter.java new file mode 100644 index 0000000000..620fdf6903 --- /dev/null +++ b/demos/composition/src/main/java/androidx/media3/demo/composition/AssetItemAdapter.java @@ -0,0 +1,69 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.media3.demo.composition; + +import android.view.LayoutInflater; +import android.view.View; +import android.view.ViewGroup; +import android.widget.TextView; +import androidx.recyclerview.widget.RecyclerView; +import java.util.List; + +/** A {@link RecyclerView.Adapter} that displays assets in a sequence in a {@link RecyclerView}. */ +public final class AssetItemAdapter extends RecyclerView.Adapter { + private static final String TAG = "AssetItemAdapter"; + + private final List data; + + /** + * Creates a new instance + * + * @param data A list of items to populate RecyclerView with. + */ + public AssetItemAdapter(List 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; + } + } +} diff --git a/demos/composition/src/main/java/androidx/media3/demo/composition/CompositionPreviewActivity.java b/demos/composition/src/main/java/androidx/media3/demo/composition/CompositionPreviewActivity.java new file mode 100644 index 0000000000..8f15b89ceb --- /dev/null +++ b/demos/composition/src/main/java/androidx/media3/demo/composition/CompositionPreviewActivity.java @@ -0,0 +1,367 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.media3.demo.composition; + +import android.app.Activity; +import android.content.DialogInterface; +import android.os.Bundle; +import android.widget.Toast; +import androidx.annotation.Nullable; +import androidx.appcompat.app.AlertDialog; +import androidx.appcompat.app.AppCompatActivity; +import androidx.appcompat.widget.AppCompatButton; +import androidx.appcompat.widget.AppCompatCheckBox; +import androidx.appcompat.widget.AppCompatTextView; +import androidx.media3.common.Effect; +import androidx.media3.common.MediaItem; +import androidx.media3.common.PlaybackException; +import androidx.media3.common.Player; +import androidx.media3.common.audio.SonicAudioProcessor; +import androidx.media3.common.util.Clock; +import androidx.media3.common.util.Log; +import androidx.media3.common.util.Util; +import androidx.media3.effect.RgbFilter; +import androidx.media3.transformer.Composition; +import androidx.media3.transformer.CompositionPlayer; +import androidx.media3.transformer.EditedMediaItem; +import androidx.media3.transformer.EditedMediaItemSequence; +import androidx.media3.transformer.Effects; +import androidx.media3.transformer.ExportException; +import androidx.media3.transformer.ExportResult; +import androidx.media3.transformer.JsonUtil; +import androidx.media3.transformer.Transformer; +import androidx.media3.ui.PlayerView; +import androidx.recyclerview.widget.DividerItemDecoration; +import androidx.recyclerview.widget.LinearLayoutManager; +import androidx.recyclerview.widget.RecyclerView; +import com.google.common.base.Stopwatch; +import com.google.common.base.Ticker; +import com.google.common.collect.ImmutableList; +import java.io.File; +import java.io.IOException; +import java.util.ArrayList; +import java.util.List; +import java.util.concurrent.TimeUnit; +import org.json.JSONException; +import org.json.JSONObject; + +/** + * An {@link Activity} that previews compositions, using {@link + * androidx.media3.transformer.CompositionPlayer}. + */ +public final class CompositionPreviewActivity extends AppCompatActivity { + private static final String TAG = "CompPreviewActivity"; + private static final String AUDIO_URI = + "https://storage.googleapis.com/exoplayer-test-media-0/play.mp3"; + + private ArrayList sequenceAssetTitles; + private boolean[] selectedMediaItems; + private String[] presetDescriptions; + private AssetItemAdapter assetItemAdapter; + @Nullable private CompositionPlayer compositionPlayer; + @Nullable private Transformer transformer; + @Nullable private File outputFile; + private PlayerView playerView; + private AppCompatButton exportButton; + private AppCompatTextView exportInformationTextView; + private Stopwatch exportStopwatch; + private boolean includeBackgroundAudioTrack; + private boolean appliesVideoEffects; + + @Override + protected void onCreate(@Nullable Bundle savedInstanceState) { + super.onCreate(savedInstanceState); + setContentView(R.layout.composition_preview_activity); + playerView = findViewById(R.id.composition_player_view); + + findViewById(R.id.preview_button).setOnClickListener(view -> previewComposition()); + findViewById(R.id.edit_sequence_button).setOnClickListener(view -> selectPreset()); + RecyclerView presetList = findViewById(R.id.composition_preset_list); + presetList.addItemDecoration(new DividerItemDecoration(this, DividerItemDecoration.VERTICAL)); + LinearLayoutManager layoutManager = + new LinearLayoutManager(this, LinearLayoutManager.VERTICAL, /* reverseLayout= */ false); + presetList.setLayoutManager(layoutManager); + + exportInformationTextView = findViewById(R.id.export_information_text); + exportButton = findViewById(R.id.composition_export_button); + exportButton.setOnClickListener(view -> exportComposition()); + + AppCompatCheckBox backgroundAudioCheckBox = findViewById(R.id.background_audio_checkbox); + backgroundAudioCheckBox.setOnCheckedChangeListener( + (compoundButton, checked) -> includeBackgroundAudioTrack = checked); + + AppCompatCheckBox applyVideoEffectsCheckBox = findViewById(R.id.apply_video_effects_checkbox); + applyVideoEffectsCheckBox.setOnCheckedChangeListener( + ((compoundButton, checked) -> appliesVideoEffects = checked)); + + presetDescriptions = getResources().getStringArray(R.array.preset_descriptions); + // Select two media items by default. + selectedMediaItems = new boolean[presetDescriptions.length]; + selectedMediaItems[0] = true; + selectedMediaItems[2] = true; + sequenceAssetTitles = new ArrayList<>(); + for (int i = 0; i < selectedMediaItems.length; i++) { + if (selectedMediaItems[i]) { + sequenceAssetTitles.add(presetDescriptions[i]); + } + } + assetItemAdapter = new AssetItemAdapter(sequenceAssetTitles); + presetList.setAdapter(assetItemAdapter); + + exportStopwatch = + Stopwatch.createUnstarted( + new Ticker() { + @Override + public long read() { + return android.os.SystemClock.elapsedRealtimeNanos(); + } + }); + } + + @Override + protected void onStart() { + super.onStart(); + playerView.onResume(); + } + + @Override + protected void onStop() { + super.onStop(); + playerView.onPause(); + releasePlayer(); + cancelExport(); + exportStopwatch.reset(); + } + + private Composition prepareComposition() { + String[] presetUris = getResources().getStringArray(/* id= */ R.array.preset_uris); + int[] presetDurationsUs = getResources().getIntArray(/* id= */ R.array.preset_durations); + List mediaItems = new ArrayList<>(); + ImmutableList videoEffects = + appliesVideoEffects + ? ImmutableList.of( + MatrixTransformationFactory.createDizzyCropEffect(), + RgbFilter.createGrayscaleFilter()) + : ImmutableList.of(); + // Preview requires all sequences to be the same duration, so calculate main sequence duration + // and limit background sequence duration to match. + long videoSequenceDurationUs = 0; + for (int i = 0; i < selectedMediaItems.length; i++) { + if (selectedMediaItems[i]) { + SonicAudioProcessor pitchChanger = new SonicAudioProcessor(); + pitchChanger.setPitch(mediaItems.size() % 2 == 0 ? 2f : 0.2f); + MediaItem mediaItem = + new MediaItem.Builder() + .setUri(presetUris[i]) + .setImageDurationMs(Util.usToMs(presetDurationsUs[i])) // Ignored for audio/video + .build(); + EditedMediaItem.Builder itemBuilder = + new EditedMediaItem.Builder(mediaItem) + .setEffects( + new Effects( + /* audioProcessors= */ ImmutableList.of(pitchChanger), + /* videoEffects= */ videoEffects)) + .setDurationUs(presetDurationsUs[i]); + videoSequenceDurationUs += presetDurationsUs[i]; + mediaItems.add(itemBuilder.build()); + } + } + EditedMediaItemSequence videoSequence = new EditedMediaItemSequence(mediaItems); + List compositionSequences = new ArrayList<>(); + compositionSequences.add(videoSequence); + if (includeBackgroundAudioTrack) { + compositionSequences.add(getAudioBackgroundSequence(Util.usToMs(videoSequenceDurationUs))); + } + SonicAudioProcessor sampleRateChanger = new SonicAudioProcessor(); + sampleRateChanger.setOutputSampleRateHz(8_000); + return new Composition.Builder(/* sequences= */ compositionSequences) + .setEffects( + new Effects( + /* audioProcessors= */ ImmutableList.of(sampleRateChanger), + /* videoEffects= */ ImmutableList.of())) + .build(); + } + + private EditedMediaItemSequence getAudioBackgroundSequence(long durationMs) { + MediaItem audioMediaItem = + new MediaItem.Builder() + .setUri(AUDIO_URI) + .setClippingConfiguration( + new MediaItem.ClippingConfiguration.Builder() + .setStartPositionMs(0) + .setEndPositionMs(durationMs) + .build()) + .build(); + EditedMediaItem audioItem = + new EditedMediaItem.Builder(audioMediaItem).setDurationUs(59_000_000).build(); + return new EditedMediaItemSequence(audioItem); + } + + private void previewComposition() { + releasePlayer(); + Composition composition = prepareComposition(); + playerView.setPlayer(null); + + CompositionPlayer player = new CompositionPlayer.Builder(getApplicationContext()).build(); + this.compositionPlayer = player; + playerView.setPlayer(compositionPlayer); + playerView.setControllerAutoShow(false); + player.addListener( + new Player.Listener() { + @Override + public void onPlayerError(PlaybackException error) { + Toast.makeText(getApplicationContext(), "Preview error: " + error, Toast.LENGTH_LONG) + .show(); + Log.e(TAG, "Preview error", error); + } + }); + player.setRepeatMode(Player.REPEAT_MODE_ALL); + player.setComposition(composition); + player.prepare(); + player.play(); + } + + private void selectPreset() { + new AlertDialog.Builder(/* context= */ this) + .setTitle(R.string.select_preset_title) + .setMultiChoiceItems(presetDescriptions, selectedMediaItems, this::selectPresetInDialog) + .setPositiveButton(android.R.string.ok, /* listener= */ null) + .setCancelable(false) + .create() + .show(); + } + + private void selectPresetInDialog(DialogInterface dialog, int which, boolean isChecked) { + selectedMediaItems[which] = isChecked; + // The items will be added to a the sequence in the order they were selected. + if (isChecked) { + sequenceAssetTitles.add(presetDescriptions[which]); + assetItemAdapter.notifyItemInserted(sequenceAssetTitles.size() - 1); + } else { + int index = sequenceAssetTitles.indexOf(presetDescriptions[which]); + sequenceAssetTitles.remove(presetDescriptions[which]); + assetItemAdapter.notifyItemRemoved(index); + } + } + + private void exportComposition() { + // Cancel and clean up files from any ongoing export. + cancelExport(); + + Composition composition = prepareComposition(); + + try { + outputFile = + createExternalCacheFile( + "composition-preview-" + Clock.DEFAULT.elapsedRealtime() + ".mp4"); + } catch (IOException e) { + Toast.makeText( + getApplicationContext(), + "Aborting export! Unable to create output file: " + e, + Toast.LENGTH_LONG) + .show(); + Log.e(TAG, "Aborting export! Unable to create output file: ", e); + return; + } + String filePath = outputFile.getAbsolutePath(); + + transformer = + new Transformer.Builder(/* context= */ this) + .addListener( + new Transformer.Listener() { + @Override + public void onCompleted(Composition composition, ExportResult exportResult) { + exportStopwatch.stop(); + long elapsedTimeMs = exportStopwatch.elapsed(TimeUnit.MILLISECONDS); + String details = + getString(R.string.export_completed, elapsedTimeMs / 1000.f, filePath); + Log.i(TAG, details); + exportInformationTextView.setText(details); + + try { + JSONObject resultJson = + JsonUtil.exportResultAsJsonObject(exportResult) + .put("elapsedTimeMs", elapsedTimeMs) + .put("device", JsonUtil.getDeviceDetailsAsJsonObject()); + for (String line : Util.split(resultJson.toString(2), "\n")) { + Log.i(TAG, line); + } + } catch (JSONException e) { + Log.w(TAG, "Unable to convert exportResult to JSON", e); + } + } + + @Override + public void onError( + Composition composition, + ExportResult exportResult, + ExportException exportException) { + exportStopwatch.stop(); + Toast.makeText( + getApplicationContext(), + "Export error: " + exportException, + Toast.LENGTH_LONG) + .show(); + Log.e(TAG, "Export error", exportException); + exportInformationTextView.setText(R.string.export_error); + } + }) + .build(); + + exportInformationTextView.setText(R.string.export_started); + exportStopwatch.reset(); + exportStopwatch.start(); + transformer.start(composition, filePath); + Log.i(TAG, "Export started"); + } + + private void releasePlayer() { + if (compositionPlayer != null) { + compositionPlayer.release(); + compositionPlayer = null; + } + } + + /** Cancels any ongoing export operation, and deletes output file contents. */ + private void cancelExport() { + if (transformer != null) { + transformer.cancel(); + transformer = null; + } + if (outputFile != null) { + outputFile.delete(); + outputFile = null; + } + exportInformationTextView.setText(""); + } + + /** + * Creates a {@link File} of the {@code fileName} in the application cache directory. + * + *

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; + } +} diff --git a/demos/composition/src/main/java/androidx/media3/demo/composition/MatrixTransformationFactory.java b/demos/composition/src/main/java/androidx/media3/demo/composition/MatrixTransformationFactory.java new file mode 100644 index 0000000000..85cc61d19f --- /dev/null +++ b/demos/composition/src/main/java/androidx/media3/demo/composition/MatrixTransformationFactory.java @@ -0,0 +1,93 @@ +/* + * Copyright 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +package androidx.media3.demo.composition; + +import android.graphics.Matrix; +import androidx.media3.common.C; +import androidx.media3.common.util.Util; +import androidx.media3.effect.GlMatrixTransformation; +import androidx.media3.effect.MatrixTransformation; + +/** + * Factory for {@link GlMatrixTransformation GlMatrixTransformations} and {@link + * MatrixTransformation MatrixTransformations} that create video effects by applying transformation + * matrices to the individual video frames. + */ +/* package */ final class MatrixTransformationFactory { + /** + * Returns a {@link MatrixTransformation} that rescales the frames over the first {@link + * #ZOOM_DURATION_SECONDS} seconds, such that the rectangle filled with the input frame increases + * linearly in size from a single point to filling the full output frame. + */ + public static MatrixTransformation createZoomInTransition() { + return MatrixTransformationFactory::calculateZoomInTransitionMatrix; + } + + /** + * Returns a {@link MatrixTransformation} that crops frames to a rectangle that moves on an + * ellipse. + */ + public static MatrixTransformation createDizzyCropEffect() { + return MatrixTransformationFactory::calculateDizzyCropMatrix; + } + + /** + * Returns a {@link GlMatrixTransformation} that rotates a frame in 3D around the y-axis and + * applies perspective projection to 2D. + */ + public static GlMatrixTransformation createSpin3dEffect() { + return MatrixTransformationFactory::calculate3dSpinMatrix; + } + + private static final float ZOOM_DURATION_SECONDS = 2f; + private static final float DIZZY_CROP_ROTATION_PERIOD_US = 5_000_000f; + + private static Matrix calculateZoomInTransitionMatrix(long presentationTimeUs) { + Matrix transformationMatrix = new Matrix(); + float scale = Math.min(1, presentationTimeUs / (C.MICROS_PER_SECOND * ZOOM_DURATION_SECONDS)); + transformationMatrix.postScale(/* sx= */ scale, /* sy= */ scale); + return transformationMatrix; + } + + private static android.graphics.Matrix calculateDizzyCropMatrix(long presentationTimeUs) { + double theta = presentationTimeUs * 2 * Math.PI / DIZZY_CROP_ROTATION_PERIOD_US; + float centerX = 0.5f * (float) Math.cos(theta); + float centerY = 0.5f * (float) Math.sin(theta); + android.graphics.Matrix transformationMatrix = new android.graphics.Matrix(); + transformationMatrix.postTranslate(/* dx= */ centerX, /* dy= */ centerY); + transformationMatrix.postScale(/* sx= */ 2f, /* sy= */ 2f); + return transformationMatrix; + } + + private static float[] calculate3dSpinMatrix(long presentationTimeUs) { + float[] transformationMatrix = new float[16]; + android.opengl.Matrix.frustumM( + transformationMatrix, + /* offset= */ 0, + /* left= */ -1f, + /* right= */ 1f, + /* bottom= */ -1f, + /* top= */ 1f, + /* near= */ 3f, + /* far= */ 5f); + android.opengl.Matrix.translateM( + transformationMatrix, /* mOffset= */ 0, /* x= */ 0f, /* y= */ 0f, /* z= */ -4f); + float theta = Util.usToMs(presentationTimeUs) / 10f; + android.opengl.Matrix.rotateM( + transformationMatrix, /* mOffset= */ 0, theta, /* x= */ 0f, /* y= */ 1f, /* z= */ 0f); + return transformationMatrix; + } +} diff --git a/demos/composition/src/main/java/androidx/media3/demo/composition/package-info.java b/demos/composition/src/main/java/androidx/media3/demo/composition/package-info.java new file mode 100644 index 0000000000..068f941e6b --- /dev/null +++ b/demos/composition/src/main/java/androidx/media3/demo/composition/package-info.java @@ -0,0 +1,22 @@ +/* + * Copyright (C) 2024 The Android Open Source Project + * + * Licensed under the Apache License, Version 2.0 (the "License"); + * you may not use this file except in compliance with the License. + * You may obtain a copy of the License at + * + * http://www.apache.org/licenses/LICENSE-2.0 + * + * Unless required by applicable law or agreed to in writing, software + * distributed under the License is distributed on an "AS IS" BASIS, + * WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. + * See the License for the specific language governing permissions and + * limitations under the License. + */ +@NonNullApi +@OptIn(markerClass = UnstableApi.class) +package androidx.media3.demo.composition; + +import androidx.annotation.OptIn; +import androidx.media3.common.util.NonNullApi; +import androidx.media3.common.util.UnstableApi; diff --git a/demos/composition/src/main/res/layout/composition_preview_activity.xml b/demos/composition/src/main/res/layout/composition_preview_activity.xml new file mode 100644 index 0000000000..600065ee83 --- /dev/null +++ b/demos/composition/src/main/res/layout/composition_preview_activity.xml @@ -0,0 +1,136 @@ + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + diff --git a/demos/composition/src/main/res/layout/preset_item.xml b/demos/composition/src/main/res/layout/preset_item.xml new file mode 100644 index 0000000000..41933efc0c --- /dev/null +++ b/demos/composition/src/main/res/layout/preset_item.xml @@ -0,0 +1,30 @@ + + + + + + + diff --git a/demos/composition/src/main/res/mipmap-hdpi/ic_launcher.png b/demos/composition/src/main/res/mipmap-hdpi/ic_launcher.png new file mode 100644 index 0000000000..adaa93220e Binary files /dev/null and b/demos/composition/src/main/res/mipmap-hdpi/ic_launcher.png differ diff --git a/demos/composition/src/main/res/mipmap-mdpi/ic_launcher.png b/demos/composition/src/main/res/mipmap-mdpi/ic_launcher.png new file mode 100644 index 0000000000..9b6f7d5e80 Binary files /dev/null and b/demos/composition/src/main/res/mipmap-mdpi/ic_launcher.png differ diff --git a/demos/composition/src/main/res/mipmap-xhdpi/ic_launcher.png b/demos/composition/src/main/res/mipmap-xhdpi/ic_launcher.png new file mode 100644 index 0000000000..2101026c9f Binary files /dev/null and b/demos/composition/src/main/res/mipmap-xhdpi/ic_launcher.png differ diff --git a/demos/composition/src/main/res/mipmap-xxhdpi/ic_launcher.png b/demos/composition/src/main/res/mipmap-xxhdpi/ic_launcher.png new file mode 100644 index 0000000000..223ec8bd11 Binary files /dev/null and b/demos/composition/src/main/res/mipmap-xxhdpi/ic_launcher.png differ diff --git a/demos/composition/src/main/res/mipmap-xxxhdpi/ic_launcher.png b/demos/composition/src/main/res/mipmap-xxxhdpi/ic_launcher.png new file mode 100644 index 0000000000..698ed68c42 Binary files /dev/null and b/demos/composition/src/main/res/mipmap-xxxhdpi/ic_launcher.png differ diff --git a/demos/composition/src/main/res/values-night/themes.xml b/demos/composition/src/main/res/values-night/themes.xml new file mode 100644 index 0000000000..5194fe967e --- /dev/null +++ b/demos/composition/src/main/res/values-night/themes.xml @@ -0,0 +1,31 @@ + + + + + + diff --git a/demos/composition/src/main/res/values/arrays.xml b/demos/composition/src/main/res/values/arrays.xml new file mode 100644 index 0000000000..365301c44f --- /dev/null +++ b/demos/composition/src/main/res/values/arrays.xml @@ -0,0 +1,77 @@ + + + + + 720p H264 video and AAC audio + 1080p H265 video and AAC audio + 360p H264 video and AAC audio + 360p VP8 video and Vorbis audio + 4K H264 video and AAC audio (portrait, no B-frames) + 8k H265 video and AAC audio + Short 1080p H265 video and AAC audio + Long 180p H264 video and AAC audio + H264 video and AAC audio (portrait, H > W, 0°) + H264 video and AAC audio (portrait, H < W, 90°) + SEF slow motion with 240 fps + 480p DASH (non-square pixels) + HDR (HDR10+) H265 limited range video (encoding may fail) + HDR (HLG) H265 limited range video (encoding may fail) + 720p H264 video with no audio + London JPG image (plays for 5 secs at 30 fps) + Tokyo JPG image (portrait, plays for 5 secs at 30 fps) + Pixel 7 shorter audio track + + + https://storage.googleapis.com/exoplayer-test-media-1/mp4/android-screens-10s.mp4 + https://storage.googleapis.com/exoplayer-test-media-0/android-block-1080-hevc.mp4 + https://html5demos.com/assets/dizzy.mp4 + https://html5demos.com/assets/dizzy.webm + https://storage.googleapis.com/exoplayer-test-media-1/mp4/portrait_4k60.mp4 + https://storage.googleapis.com/exoplayer-test-media-1/mp4/8k24fps_4s.mp4 + https://storage.googleapis.com/exoplayer-test-media-1/mp4/1920w_1080h_4s.mp4 + https://storage.googleapis.com/exoplayer-test-media-0/BigBuckBunny_320x180.mp4 + https://storage.googleapis.com/exoplayer-test-media-1/mp4/portrait_avc_aac.mp4 + https://storage.googleapis.com/exoplayer-test-media-1/mp4/portrait_rotated_avc_aac.mp4 + https://storage.googleapis.com/exoplayer-test-media-1/mp4/slow-motion/slowMotion_stopwatch_240fps_long.mp4 + https://storage.googleapis.com/exoplayer-test-media-1/gen/screens/dash-vod-single-segment/manifest-baseline.mpd + https://storage.googleapis.com/exoplayer-test-media-1/mp4/samsung-s21-hdr-hdr10.mp4 + https://storage.googleapis.com/exoplayer-test-media-1/mp4/Pixel7Pro_HLG_1080P.mp4 + https://storage.googleapis.com/exoplayer-test-media-1/mp4/sample_video_track_only.mp4 + https://storage.googleapis.com/exoplayer-test-media-1/jpg/london.jpg + https://storage.googleapis.com/exoplayer-test-media-1/jpg/tokyo.jpg + https://storage.googleapis.com/exoplayer-temp/audio-blip/metronome_selfie_pixel.mp4 + + + 10024000 + 23823000 + 25000000 + 25000000 + 3745000 + 4421000 + 3923000 + 596459000 + 3687000 + 2235000 + 47987000 + 128270000 + 4236000 + 5167000 + 1001000 + 5000000 + 5000000 + 2170000 + + diff --git a/demos/composition/src/main/res/values/colors.xml b/demos/composition/src/main/res/values/colors.xml new file mode 100644 index 0000000000..91d1b1023f --- /dev/null +++ b/demos/composition/src/main/res/values/colors.xml @@ -0,0 +1,24 @@ + + + + #FFBB86FC + #FF6200EE + #FF3700B3 + #FF03DAC5 + #FF018786 + #FF000000 + #FFFFFFFF + diff --git a/demos/composition/src/main/res/values/strings.xml b/demos/composition/src/main/res/values/strings.xml new file mode 100644 index 0000000000..ced548099a --- /dev/null +++ b/demos/composition/src/main/res/values/strings.xml @@ -0,0 +1,29 @@ + + + + Composition Demo + Edit + Add effects + Preview + Composition preview + Video sequence items: + Choose preset input + Export + Export completed in %.3f seconds.\nOutput: %s + Export error + Export started + Add background audio + diff --git a/demos/composition/src/main/res/values/themes.xml b/demos/composition/src/main/res/values/themes.xml new file mode 100644 index 0000000000..29ccfdfc53 --- /dev/null +++ b/demos/composition/src/main/res/values/themes.xml @@ -0,0 +1,31 @@ + + + + + + diff --git a/demos/gl/build.gradle b/demos/gl/build.gradle index 8666c45aa0..7e28ec0e00 100644 --- a/demos/gl/build.gradle +++ b/demos/gl/build.gradle @@ -15,7 +15,9 @@ apply from: '../../constants.gradle' apply plugin: 'com.android.application' android { - compileSdkVersion project.ext.compileSdkVersion + namespace 'androidx.media3.demo.gl' + + compileSdk project.ext.compileSdkVersion compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 @@ -27,7 +29,6 @@ android { versionCode project.ext.releaseVersionCode minSdkVersion project.ext.minSdkVersion targetSdkVersion project.ext.appTargetSdkVersion - multiDexEnabled true } buildTypes { @@ -53,7 +54,5 @@ dependencies { implementation project(modulePrefix + 'lib-exoplayer-smoothstreaming') implementation project(modulePrefix + 'lib-ui') implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion - implementation 'androidx.multidex:multidex:' + androidxMultidexVersion compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion - compileOnly 'org.checkerframework:checker-compat-qual:' + checkerframeworkCompatVersion } diff --git a/demos/gl/src/main/AndroidManifest.xml b/demos/gl/src/main/AndroidManifest.xml index d44f8ea343..d4d79a7053 100644 --- a/demos/gl/src/main/AndroidManifest.xml +++ b/demos/gl/src/main/AndroidManifest.xml @@ -22,7 +22,6 @@ diff --git a/demos/gl/src/main/java/androidx/media3/demo/gl/BitmapOverlayVideoProcessor.java b/demos/gl/src/main/java/androidx/media3/demo/gl/BitmapOverlayVideoProcessor.java index 793e1fd493..89743d700d 100644 --- a/demos/gl/src/main/java/androidx/media3/demo/gl/BitmapOverlayVideoProcessor.java +++ b/demos/gl/src/main/java/androidx/media3/demo/gl/BitmapOverlayVideoProcessor.java @@ -63,7 +63,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; paint = new Paint(); paint.setTextSize(64); paint.setAntiAlias(true); - paint.setARGB(0xFF, 0xFF, 0xFF, 0xFF); + paint.setColor(Color.WHITE); textures = new int[1]; overlayBitmap = Bitmap.createBitmap(OVERLAY_WIDTH, OVERLAY_HEIGHT, Bitmap.Config.ARGB_8888); overlayCanvas = new Canvas(overlayBitmap); diff --git a/demos/gl/src/main/java/androidx/media3/demo/gl/MainActivity.java b/demos/gl/src/main/java/androidx/media3/demo/gl/MainActivity.java index c9e7ccd11c..bd6ba4b1d8 100644 --- a/demos/gl/src/main/java/androidx/media3/demo/gl/MainActivity.java +++ b/demos/gl/src/main/java/androidx/media3/demo/gl/MainActivity.java @@ -143,7 +143,7 @@ public final class MainActivity extends Activity { ? Assertions.checkNotNull(intent.getData()) : Uri.parse(DEFAULT_MEDIA_URI); DrmSessionManager drmSessionManager; - if (Util.SDK_INT >= 18 && intent.hasExtra(DRM_SCHEME_EXTRA)) { + if (intent.hasExtra(DRM_SCHEME_EXTRA)) { String drmScheme = Assertions.checkNotNull(intent.getStringExtra(DRM_SCHEME_EXTRA)); String drmLicenseUrl = Assertions.checkNotNull(intent.getStringExtra(DRM_LICENSE_URL_EXTRA)); UUID drmSchemeUuid = Assertions.checkNotNull(Util.getDrmUuid(drmScheme)); diff --git a/demos/gl/src/main/java/androidx/media3/demo/gl/package-info.java b/demos/gl/src/main/java/androidx/media3/demo/gl/package-info.java index 5a97794da8..d44a6522f1 100644 --- a/demos/gl/src/main/java/androidx/media3/demo/gl/package-info.java +++ b/demos/gl/src/main/java/androidx/media3/demo/gl/package-info.java @@ -14,6 +14,9 @@ * limitations under the License. */ @NonNullApi +@OptIn(markerClass = UnstableApi.class) package androidx.media3.demo.gl; +import androidx.annotation.OptIn; import androidx.media3.common.util.NonNullApi; +import androidx.media3.common.util.UnstableApi; diff --git a/demos/main/build.gradle b/demos/main/build.gradle index 2d7d92a0fb..de97527346 100644 --- a/demos/main/build.gradle +++ b/demos/main/build.gradle @@ -14,9 +14,12 @@ apply from: '../../constants.gradle' apply plugin: 'com.android.application' +apply plugin: 'kotlin-android' android { - compileSdkVersion project.ext.compileSdkVersion + namespace 'androidx.media3.demo.main' + + compileSdk project.ext.compileSdkVersion compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 @@ -27,10 +30,7 @@ android { versionName project.ext.releaseVersion versionCode project.ext.releaseVersionCode minSdkVersion project.ext.minSdkVersion - // Not using appTargetSDKVersion to allow local file access on API 29 - // and higher [Internal ref: b/191644662] - targetSdkVersion project.ext.mainDemoAppTargetSdkVersion - multiDexEnabled true + targetSdkVersion project.ext.appTargetSdkVersion } buildTypes { @@ -54,7 +54,9 @@ android { disable 'GoogleAppIndexingWarning','MissingTranslation','IconDensities' } - flavorDimensions "decoderExtensions" + flavorDimensions = ["decoderExtensions"] + + buildFeatures.buildConfig true productFlavors { noDecoderExtensions { @@ -72,7 +74,6 @@ dependencies { compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion - implementation 'androidx.multidex:multidex:' + androidxMultidexVersion implementation 'com.google.android.material:material:' + androidxMaterialVersion implementation project(modulePrefix + 'lib-exoplayer') implementation project(modulePrefix + 'lib-exoplayer-dash') @@ -86,7 +87,9 @@ dependencies { withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-ffmpeg') withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-flac') withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-opus') + withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-iamf') withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-vp9') + withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-midi') withDecoderExtensionsImplementation project(modulePrefix + 'lib-datasource-rtmp') } diff --git a/demos/main/src/main/AndroidManifest.xml b/demos/main/src/main/AndroidManifest.xml index 21d07e4ee5..1a3dff3102 100644 --- a/demos/main/src/main/AndroidManifest.xml +++ b/demos/main/src/main/AndroidManifest.xml @@ -21,8 +21,12 @@ + + + + @@ -35,9 +39,7 @@ android:banner="@drawable/ic_banner" android:largeHeap="true" android:allowBackup="false" - android:requestLegacyExternalStorage="true" android:supportsRtl="true" - android:name="androidx.multidex.MultiDexApplication" tools:targetApi="29"> + @@ -93,7 +96,8 @@ + android:exported="false" + android:foregroundServiceType="dataSync"> diff --git a/demos/main/src/main/assets/media.exolist.json b/demos/main/src/main/assets/media.exolist.json index ac7b5ce749..63bbe26b47 100644 --- a/demos/main/src/main/assets/media.exolist.json +++ b/demos/main/src/main/assets/media.exolist.json @@ -143,6 +143,12 @@ "drm_scheme": "widevine", "drm_license_uri": "https://proxy.uat.widevine.com/proxy?video_id=GTS_HW_SECURE_ALL&provider=widevine_test" }, + { + "name": "20s license with renewal", + "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd", + "drm_scheme": "widevine", + "drm_license_uri": "https://proxy.uat.widevine.com/proxy?video_id=GTS_CAN_RENEW&provider=widevine_test" + }, { "name": "30s license (fails at ~30s)", "uri": "https://storage.googleapis.com/wvmedia/cenc/h264/tears/tears.mpd", @@ -406,6 +412,18 @@ "name": "DASH VOD: Tears of Steel (11 periods, pre/mid/post), 2/5/2 ads [5/10s]", "uri": "ssai://dai.google.com/?contentSourceId=2559737&videoId=tos-dash&format=0&adsId=1" }, + { + "name": "DASH live: Tears of Steel (mid), 3 ads each [10 s]", + "uri": "ssai://dai.google.com/?assetKey=jNVjPZwzSkyeGiaNQTPqiQ&format=0&adsId=1" + }, + { + "name": "DASH live: New Tears of Steel (mid), 3 ads each [10 s]", + "uri": "ssai://dai.google.com/?assetKey=PSzZMzAkSXCmlJOWDmRj8Q&format=0&adsId=12" + }, + { + "name": "DASH live: Unencrypted stream with 30s ad breaks every minute", + "uri": "ssai://dai.google.com/?assetKey=0ndl1dJcRmKDUPxTRjvdog&format=0&adsId=21" + }, { "name": "Playlist: No ads - HLS VOD: Demo (skippable pre/post) - No ads", "playlist": [ @@ -434,20 +452,6 @@ } ] }, - { - "name": "Playlist: No ads - HLS Live: Big Buck Bunny (mid) - No ads", - "playlist": [ - { - "uri": "https://html5demos.com/assets/dizzy.mp4" - }, - { - "uri": "ssai://dai.google.com/?assetKey=sN_IYUG8STe1ZzhIIE_ksA&format=2&adsId=3" - }, - { - "uri": "https://html5demos.com/assets/dizzy.mp4" - } - ] - }, { "name": "Playlist: No ads - DASH VOD: Tears of Steel (11 periods, pre/mid/post) - No ads", "playlist": [ @@ -476,6 +480,34 @@ "uri": "https://html5demos.com/assets/dizzy.mp4" } ] + }, + { + "name": "Playlist: No ads - DASH live: Tears of Steel (mid) - No ads", + "playlist": [ + { + "uri": "https://html5demos.com/assets/dizzy.mp4" + }, + { + "uri": "ssai://dai.google.com/?assetKey=PSzZMzAkSXCmlJOWDmRj8Q&format=0&adsId=1" + }, + { + "uri": "https://html5demos.com/assets/dizzy.mp4" + } + ] + }, + { + "name": "Playlist: No ads - HLS live: Big Buck Bunny - No ads", + "playlist": [ + { + "uri": "https://html5demos.com/assets/dizzy.mp4" + }, + { + "uri": "ssai://dai.google.com/?assetKey=sN_IYUG8STe1ZzhIIE_ksA&format=2&adsId=3" + }, + { + "uri": "https://html5demos.com/assets/dizzy.mp4" + } + ] } ] }, @@ -494,7 +526,7 @@ ] }, { - "name": "Audio -> Video -> Audio", + "name": "Audio -> Video (MKV) -> Video (MKV) -> Audio -> Video (MKV) -> Video (DASH) -> Audio", "playlist": [ { "uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/audio-141.mp4" @@ -502,6 +534,18 @@ { "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv" }, + { + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv" + }, + { + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/audio-141.mp4" + }, + { + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv" + }, + { + "uri": "https://storage.googleapis.com/wvmedia/clear/h264/tears/tears.mpd" + }, { "uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/audio-141.mp4" } @@ -549,6 +593,27 @@ "clip_start_position_ms": 10000 } ] + }, + { + "name": "Image -> Video -> Image -> Image", + "playlist": [ + { + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/jpg/tokyo.jpg", + "image_duration_ms": 2000 + }, + { + "uri": "https://html5demos.com/assets/dizzy.mp4", + "clip_end_position_ms": 2000 + }, + { + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/jpg/london.jpg", + "image_duration_ms": 2000 + }, + { + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/jpg/tokyo.jpg", + "image_duration_ms": 2000 + } + ] } ] }, @@ -628,11 +693,19 @@ { "name": "MPEG-4 Timed Text", "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mp4/dizzy-with-tx3g.mp4" + }, + { + "name": "SubRip muxed into MKV", + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-with-subrip.mkv" + }, + { + "name": "Overlapping SSA muxed into MKV", + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-with-overlapping-ssa.mkv" } ] }, { - "name": "Misc", + "name": "Progressive", "samples": [ { "name": "Dizzy (MP4)", @@ -685,6 +758,44 @@ { "name": "One hour frame counter (MP4)", "uri": "https://storage.googleapis.com/exoplayer-test-media-1/mp4/frame-counter-one-hour.mp4" + }, + { + "name": "Immersive Audio Format Sample (MP4, IAMF)", + "uri": "https://github.com/AOMediaCodec/libiamf/raw/main/tests/test_000036_s.mp4" + } + ] + }, + { + "name": "Images", + "samples": [ + { + "name": "JPEG (wide)", + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/jpg/london.jpg", + "image_duration_ms": 2000 + }, + { + "name": "JPEG (tall)", + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/jpg/tokyo.jpg", + "image_duration_ms": 2000 + }, + { + "name": "PNG", + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/png/media3test.png", + "image_duration_ms": 2000 + }, + { + "name": "JPEG motion photo (still)", + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/jpg/london_motion_photo.jpg", + "image_duration_ms": 2000 + }, + { + "name": "JPEG motion photo (motion)", + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/jpg/london_motion_photo.jpg" + }, + { + "name": "JPEG (Ultra HDR)", + "uri": "https://storage.googleapis.com/exoplayer-test-media-1/jpg/ultra_hdr.jpg", + "image_duration_ms": 2000 } ] } diff --git a/demos/main/src/main/java/androidx/media3/demo/main/DemoDownloadService.java b/demos/main/src/main/java/androidx/media3/demo/main/DemoDownloadService.java index c14bfc4ba9..d32fa90c83 100644 --- a/demos/main/src/main/java/androidx/media3/demo/main/DemoDownloadService.java +++ b/demos/main/src/main/java/androidx/media3/demo/main/DemoDownloadService.java @@ -63,7 +63,7 @@ public class DemoDownloadService extends DownloadService { @Override protected Scheduler getScheduler() { - return Util.SDK_INT >= 21 ? new PlatformScheduler(this, JOB_ID) : null; + return new PlatformScheduler(this, JOB_ID); } @Override diff --git a/demos/main/src/main/java/androidx/media3/demo/main/DemoUtil.java b/demos/main/src/main/java/androidx/media3/demo/main/DemoUtil.java index 2a5a4bfbbe..5d73c58cbb 100644 --- a/demos/main/src/main/java/androidx/media3/demo/main/DemoUtil.java +++ b/demos/main/src/main/java/androidx/media3/demo/main/DemoUtil.java @@ -16,12 +16,16 @@ package androidx.media3.demo.main; import android.content.Context; +import android.net.http.HttpEngine; +import android.os.Build; +import android.os.ext.SdkExtensions; import androidx.annotation.OptIn; import androidx.media3.database.DatabaseProvider; import androidx.media3.database.StandaloneDatabaseProvider; import androidx.media3.datasource.DataSource; import androidx.media3.datasource.DefaultDataSource; import androidx.media3.datasource.DefaultHttpDataSource; +import androidx.media3.datasource.HttpEngineDataSource; import androidx.media3.datasource.cache.Cache; import androidx.media3.datasource.cache.CacheDataSource; import androidx.media3.datasource.cache.NoOpCacheEvictor; @@ -47,24 +51,35 @@ 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). + * Whether the demo application uses Cronet for networking when {@link HttpEngine} is not + * supported. Note that Cronet does not provide automatic support for cookies + * (https://github.com/google/ExoPlayer/issues/5975). * - *

If set to false, the platform's default network stack is used with a {@link CookieManager} - * configured in {@link #getHttpDataSourceFactory}. + *

If set to false, the {@link DefaultHttpDataSource} is used with a {@link CookieManager} + * configured in {@link #getHttpDataSourceFactory} when {@link HttpEngine} is not supported. */ - private static final boolean USE_CRONET_FOR_NETWORKING = true; + private static final boolean ALLOW_CRONET_FOR_NETWORKING = true; private static final String TAG = "DemoUtil"; private static final String DOWNLOAD_CONTENT_DIRECTORY = "downloads"; private static DataSource.@MonotonicNonNull Factory dataSourceFactory; private static DataSource.@MonotonicNonNull Factory httpDataSourceFactory; + + @OptIn(markerClass = androidx.media3.common.util.UnstableApi.class) private static @MonotonicNonNull DatabaseProvider databaseProvider; + private static @MonotonicNonNull File downloadDirectory; + + @OptIn(markerClass = androidx.media3.common.util.UnstableApi.class) private static @MonotonicNonNull Cache downloadCache; + + @OptIn(markerClass = androidx.media3.common.util.UnstableApi.class) private static @MonotonicNonNull DownloadManager downloadManager; + private static @MonotonicNonNull DownloadTracker downloadTracker; + + @OptIn(markerClass = androidx.media3.common.util.UnstableApi.class) private static @MonotonicNonNull DownloadNotificationHelper downloadNotificationHelper; /** Returns whether extension renderers should be used. */ @@ -86,24 +101,33 @@ public final class DemoUtil { .setExtensionRendererMode(extensionRendererMode); } + @OptIn(markerClass = androidx.media3.common.util.UnstableApi.class) public static synchronized DataSource.Factory getHttpDataSourceFactory(Context context) { - if (httpDataSourceFactory == null) { - if (USE_CRONET_FOR_NETWORKING) { - context = context.getApplicationContext(); - @Nullable CronetEngine cronetEngine = CronetUtil.buildCronetEngine(context); - if (cronetEngine != null) { - httpDataSourceFactory = - new CronetDataSource.Factory(cronetEngine, Executors.newSingleThreadExecutor()); - } - } - if (httpDataSourceFactory == null) { - // We don't want to use Cronet, or we failed to instantiate a CronetEngine. - CookieManager cookieManager = new CookieManager(); - cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER); - CookieHandler.setDefault(cookieManager); - httpDataSourceFactory = new DefaultHttpDataSource.Factory(); + if (httpDataSourceFactory != null) { + return httpDataSourceFactory; + } + context = context.getApplicationContext(); + if (Build.VERSION.SDK_INT >= 30 + && SdkExtensions.getExtensionVersion(Build.VERSION_CODES.S) >= 7) { + HttpEngine httpEngine = new HttpEngine.Builder(context).build(); + httpDataSourceFactory = + new HttpEngineDataSource.Factory(httpEngine, Executors.newSingleThreadExecutor()); + return httpDataSourceFactory; + } + if (ALLOW_CRONET_FOR_NETWORKING) { + @Nullable CronetEngine cronetEngine = CronetUtil.buildCronetEngine(context); + if (cronetEngine != null) { + httpDataSourceFactory = + new CronetDataSource.Factory(cronetEngine, Executors.newSingleThreadExecutor()); + return httpDataSourceFactory; } } + // The device doesn't support HttpEngine or we don't want to allow 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(); return httpDataSourceFactory; } @@ -128,6 +152,7 @@ public final class DemoUtil { return downloadNotificationHelper; } + @OptIn(markerClass = androidx.media3.common.util.UnstableApi.class) public static synchronized DownloadManager getDownloadManager(Context context) { ensureDownloadManagerInitialized(context); return downloadManager; diff --git a/demos/main/src/main/java/androidx/media3/demo/main/DownloadTracker.java b/demos/main/src/main/java/androidx/media3/demo/main/DownloadTracker.java index 089644cbc9..2a322a6a47 100644 --- a/demos/main/src/main/java/androidx/media3/demo/main/DownloadTracker.java +++ b/demos/main/src/main/java/androidx/media3/demo/main/DownloadTracker.java @@ -16,16 +16,18 @@ package androidx.media3.demo.main; import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; import android.content.Context; import android.content.DialogInterface; import android.net.Uri; -import android.os.AsyncTask; +import android.os.Handler; +import android.os.Looper; import android.widget.Toast; import androidx.annotation.Nullable; import androidx.annotation.OptIn; -import androidx.annotation.RequiresApi; import androidx.fragment.app.FragmentManager; +import androidx.media3.common.C; import androidx.media3.common.DrmInitData; import androidx.media3.common.Format; import androidx.media3.common.MediaItem; @@ -51,7 +53,11 @@ import androidx.media3.exoplayer.source.TrackGroupArray; import androidx.media3.exoplayer.trackselection.MappingTrackSelector.MappedTrackInfo; import java.io.IOException; import java.util.HashMap; +import java.util.UUID; import java.util.concurrent.CopyOnWriteArraySet; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; +import java.util.concurrent.Future; /** Tracks media that has been downloaded. */ @OptIn(markerClass = androidx.media3.common.util.UnstableApi.class) @@ -180,7 +186,7 @@ public class DownloadTracker { trackSelectionDialog.dismiss(); } if (widevineOfflineLicenseFetchTask != null) { - widevineOfflineLicenseFetchTask.cancel(false); + widevineOfflineLicenseFetchTask.cancel(); } } @@ -195,14 +201,9 @@ public class DownloadTracker { } // The content is DRM protected. We need to acquire an offline license. - if (Util.SDK_INT < 18) { - Toast.makeText(context, R.string.error_drm_unsupported_before_api_18, Toast.LENGTH_LONG) - .show(); - Log.e(TAG, "Downloading DRM protected content is not supported on API versions below 18"); - return; - } + // TODO(internal b/163107948): Support cases where DrmInitData are not in the manifest. - if (!hasSchemaData(format.drmInitData)) { + if (!hasNonNullWidevineSchemaData(format.drmInitData)) { Toast.makeText(context, R.string.download_start_error_offline_license, Toast.LENGTH_LONG) .show(); Log.e( @@ -323,12 +324,14 @@ public class DownloadTracker { } /** - * Returns whether any the {@link DrmInitData.SchemeData} contained in {@code drmInitData} has - * non-null {@link DrmInitData.SchemeData#data}. + * Returns whether any {@link DrmInitData.SchemeData} that {@linkplain + * DrmInitData.SchemeData#matches(UUID) matches} {@link C#WIDEVINE_UUID} has non-null {@link + * DrmInitData.SchemeData#data}. */ - private boolean hasSchemaData(DrmInitData drmInitData) { + private boolean hasNonNullWidevineSchemaData(DrmInitData drmInitData) { for (int i = 0; i < drmInitData.schemeDataCount; i++) { - if (drmInitData.get(i).hasData()) { + DrmInitData.SchemeData schemeData = drmInitData.get(i); + if (schemeData.matches(C.WIDEVINE_UUID) && schemeData.hasData()) { return true; } } @@ -353,15 +356,16 @@ public class DownloadTracker { } /** Downloads a Widevine offline license in a background thread. */ - @RequiresApi(18) - private static final class WidevineOfflineLicenseFetchTask extends AsyncTask { + private static final class WidevineOfflineLicenseFetchTask { private final Format format; private final MediaItem.DrmConfiguration drmConfiguration; private final DataSource.Factory dataSourceFactory; private final StartDownloadDialogHelper dialogHelper; private final DownloadHelper downloadHelper; + private final ExecutorService executorService; + @Nullable Future future; @Nullable private byte[] keySetId; @Nullable private DrmSession.DrmSessionException drmSessionException; @@ -371,6 +375,8 @@ public class DownloadTracker { DataSource.Factory dataSourceFactory, StartDownloadDialogHelper dialogHelper, DownloadHelper downloadHelper) { + checkState(drmConfiguration.scheme.equals(C.WIDEVINE_UUID)); + this.executorService = Executors.newSingleThreadExecutor(); this.format = format; this.drmConfiguration = drmConfiguration; this.dataSourceFactory = dataSourceFactory; @@ -378,32 +384,41 @@ public class DownloadTracker { this.downloadHelper = downloadHelper; } - @Override - protected Void doInBackground(Void... voids) { - OfflineLicenseHelper offlineLicenseHelper = - OfflineLicenseHelper.newWidevineInstance( - drmConfiguration.licenseUri.toString(), - drmConfiguration.forceDefaultLicenseUri, - dataSourceFactory, - drmConfiguration.licenseRequestHeaders, - new DrmSessionEventListener.EventDispatcher()); - try { - keySetId = offlineLicenseHelper.downloadLicense(format); - } catch (DrmSession.DrmSessionException e) { - drmSessionException = e; - } finally { - offlineLicenseHelper.release(); + public void cancel() { + if (future != null) { + future.cancel(/* mayInterruptIfRunning= */ false); } - return null; } - @Override - protected void onPostExecute(Void aVoid) { - if (drmSessionException != null) { - dialogHelper.onOfflineLicenseFetchedError(drmSessionException); - } else { - dialogHelper.onOfflineLicenseFetched(downloadHelper, checkNotNull(keySetId)); - } + public void execute() { + future = + executorService.submit( + () -> { + OfflineLicenseHelper offlineLicenseHelper = + OfflineLicenseHelper.newWidevineInstance( + drmConfiguration.licenseUri.toString(), + drmConfiguration.forceDefaultLicenseUri, + dataSourceFactory, + drmConfiguration.licenseRequestHeaders, + new DrmSessionEventListener.EventDispatcher()); + try { + keySetId = offlineLicenseHelper.downloadLicense(format); + } catch (DrmSession.DrmSessionException e) { + drmSessionException = e; + } finally { + offlineLicenseHelper.release(); + new Handler(Looper.getMainLooper()) + .post( + () -> { + if (drmSessionException != null) { + dialogHelper.onOfflineLicenseFetchedError(drmSessionException); + } else { + dialogHelper.onOfflineLicenseFetched( + downloadHelper, checkNotNull(keySetId)); + } + }); + } + }); } } } diff --git a/demos/main/src/main/java/androidx/media3/demo/main/IntentUtil.java b/demos/main/src/main/java/androidx/media3/demo/main/IntentUtil.java index f8023cbe63..c2710f42a0 100644 --- a/demos/main/src/main/java/androidx/media3/demo/main/IntentUtil.java +++ b/demos/main/src/main/java/androidx/media3/demo/main/IntentUtil.java @@ -22,11 +22,14 @@ import static com.google.common.base.Preconditions.checkState; import android.content.Intent; import android.net.Uri; import androidx.annotation.Nullable; +import androidx.annotation.OptIn; import androidx.media3.common.C; import androidx.media3.common.MediaItem; import androidx.media3.common.MediaItem.ClippingConfiguration; import androidx.media3.common.MediaItem.SubtitleConfiguration; import androidx.media3.common.MediaMetadata; +import androidx.media3.common.Player; +import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; import com.google.common.collect.ImmutableList; import java.util.ArrayList; @@ -53,6 +56,7 @@ public class IntentUtil { public static final String MIME_TYPE_EXTRA = "mime_type"; public static final String CLIP_START_POSITION_MS_EXTRA = "clip_start_position_ms"; public static final String CLIP_END_POSITION_MS_EXTRA = "clip_end_position_ms"; + public static final String IMAGE_DURATION_MS = "image_duration_ms"; public static final String AD_TAG_URI_EXTRA = "ad_tag_uri"; @@ -66,6 +70,21 @@ public class IntentUtil { public static final String SUBTITLE_URI_EXTRA = "subtitle_uri"; public static final String SUBTITLE_MIME_TYPE_EXTRA = "subtitle_mime_type"; public static final String SUBTITLE_LANGUAGE_EXTRA = "subtitle_language"; + public static final String REPEAT_MODE_EXTRA = "repeat_mode"; + + public static @Player.RepeatMode int parseRepeatModeExtra(String repeatMode) { + switch (repeatMode) { + case "OFF": + return Player.REPEAT_MODE_OFF; + case "ONE": + return Player.REPEAT_MODE_ONE; + case "ALL": + return Player.REPEAT_MODE_ALL; + default: + throw new IllegalArgumentException( + "Argument " + repeatMode + " does not match any of the repeat modes: OFF|ONE|ALL"); + } + } /** Creates a list of {@link MediaItem media items} from an {@link Intent}. */ public static List createMediaItemsFromIntent(Intent intent) { @@ -94,7 +113,7 @@ public class IntentUtil { if (mediaItem.mediaMetadata.title != null) { intent.putExtra(TITLE_EXTRA, mediaItem.mediaMetadata.title); } - addPlaybackPropertiesToIntent(localConfiguration, intent, /* extrasKeySuffix= */ ""); + addLocalConfigurationToIntent(localConfiguration, intent, /* extrasKeySuffix= */ ""); addClippingConfigurationToIntent( mediaItem.clippingConfiguration, intent, /* extrasKeySuffix= */ ""); } else { @@ -104,7 +123,7 @@ public class IntentUtil { MediaItem.LocalConfiguration localConfiguration = checkNotNull(mediaItem.localConfiguration); intent.putExtra(URI_EXTRA + ("_" + i), localConfiguration.uri.toString()); - addPlaybackPropertiesToIntent(localConfiguration, intent, /* extrasKeySuffix= */ "_" + i); + addLocalConfigurationToIntent(localConfiguration, intent, /* extrasKeySuffix= */ "_" + i); addClippingConfigurationToIntent( mediaItem.clippingConfiguration, intent, /* extrasKeySuffix= */ "_" + i); if (mediaItem.mediaMetadata.title != null) { @@ -114,6 +133,7 @@ public class IntentUtil { } } + @OptIn(markerClass = UnstableApi.class) // Setting image duration. private static MediaItem createMediaItemFromIntent( Uri uri, Intent intent, String extrasKeySuffix) { @Nullable String mimeType = intent.getStringExtra(MIME_TYPE_EXTRA + extrasKeySuffix); @@ -122,6 +142,7 @@ public class IntentUtil { @Nullable SubtitleConfiguration subtitleConfiguration = createSubtitleConfiguration(intent, extrasKeySuffix); + long imageDurationMs = intent.getLongExtra(IMAGE_DURATION_MS + extrasKeySuffix, C.TIME_UNSET); MediaItem.Builder builder = new MediaItem.Builder() .setUri(uri) @@ -134,7 +155,8 @@ public class IntentUtil { .setEndPositionMs( intent.getLongExtra( CLIP_END_POSITION_MS_EXTRA + extrasKeySuffix, C.TIME_END_OF_SOURCE)) - .build()); + .build()) + .setImageDurationMs(imageDurationMs); if (adTagUri != null) { builder.setAdsConfiguration( new MediaItem.AdsConfiguration.Builder(Uri.parse(adTagUri)).build()); @@ -195,7 +217,8 @@ public class IntentUtil { return builder; } - private static void addPlaybackPropertiesToIntent( + @OptIn(markerClass = UnstableApi.class) // Accessing image duration. + private static void addLocalConfigurationToIntent( MediaItem.LocalConfiguration localConfiguration, Intent intent, String extrasKeySuffix) { intent .putExtra(MIME_TYPE_EXTRA + extrasKeySuffix, localConfiguration.mimeType) @@ -215,6 +238,9 @@ public class IntentUtil { intent.putExtra(SUBTITLE_MIME_TYPE_EXTRA + extrasKeySuffix, subtitleConfiguration.mimeType); intent.putExtra(SUBTITLE_LANGUAGE_EXTRA + extrasKeySuffix, subtitleConfiguration.language); } + if (localConfiguration.imageDurationMs != C.TIME_UNSET) { + intent.putExtra(IMAGE_DURATION_MS + extrasKeySuffix, localConfiguration.imageDurationMs); + } } private static void addDrmConfigurationToIntent( diff --git a/demos/main/src/main/java/androidx/media3/demo/main/PlayerActivity.java b/demos/main/src/main/java/androidx/media3/demo/main/PlayerActivity.java index d812c2ffbf..6be37323bd 100644 --- a/demos/main/src/main/java/androidx/media3/demo/main/PlayerActivity.java +++ b/demos/main/src/main/java/androidx/media3/demo/main/PlayerActivity.java @@ -93,9 +93,6 @@ public class PlayerActivity extends AppCompatActivity @Nullable private AdsLoader clientSideAdsLoader; - // TODO: Annotate this and serverSideAdsLoaderState below with @OptIn when it can be applied to - // fields (needs http://r.android.com/2004032 to be released into a version of - // androidx.annotation:annotation-experimental). @Nullable private ImaServerSideAdInsertionMediaSource.AdsLoader serverSideAdsLoader; private ImaServerSideAdInsertionMediaSource.AdsLoader.@MonotonicNonNull State @@ -262,8 +259,8 @@ public class PlayerActivity extends AppCompatActivity * @return Whether initialization was successful. */ protected boolean initializePlayer() { + Intent intent = getIntent(); if (player == null) { - Intent intent = getIntent(); mediaItems = createMediaItems(intent); if (mediaItems.isEmpty()) { @@ -293,11 +290,15 @@ public class PlayerActivity extends AppCompatActivity } player.setMediaItems(mediaItems, /* resetPosition= */ !haveStartPosition); player.prepare(); + String repeatModeExtra = intent.getStringExtra(IntentUtil.REPEAT_MODE_EXTRA); + if (repeatModeExtra != null) { + player.setRepeatMode(IntentUtil.parseRepeatModeExtra(repeatModeExtra)); + } updateButtonVisibility(); return true; } - @OptIn(markerClass = UnstableApi.class) // SSAI configuration + @OptIn(markerClass = UnstableApi.class) // DRM configuration private MediaSource.Factory createMediaSourceFactory() { DefaultDrmSessionManagerProvider drmSessionManagerProvider = new DefaultDrmSessionManagerProvider(); @@ -330,7 +331,6 @@ public class PlayerActivity extends AppCompatActivity playerBuilder.setRenderersFactory(renderersFactory); } - @OptIn(markerClass = UnstableApi.class) private void configurePlayerWithServerSideAdsLoader() { serverSideAdsLoader.setPlayer(player); } @@ -354,18 +354,14 @@ public class PlayerActivity extends AppCompatActivity finish(); return Collections.emptyList(); } - if (Util.maybeRequestReadExternalStoragePermission(/* activity= */ this, mediaItem)) { + if (Util.maybeRequestReadStoragePermission(/* activity= */ this, mediaItem)) { // The player will be reinitialized if the permission is granted. return Collections.emptyList(); } MediaItem.DrmConfiguration drmConfiguration = mediaItem.localConfiguration.drmConfiguration; if (drmConfiguration != null) { - if (Build.VERSION.SDK_INT < 18) { - showToast(R.string.error_drm_unsupported_before_api_18); - finish(); - return Collections.emptyList(); - } else if (!FrameworkMediaDrm.isCryptoSchemeSupported(drmConfiguration.scheme)) { + if (!FrameworkMediaDrm.isCryptoSchemeSupported(drmConfiguration.scheme)) { showToast(R.string.error_drm_unsupported_scheme); finish(); return Collections.emptyList(); @@ -403,7 +399,6 @@ public class PlayerActivity extends AppCompatActivity } } - @OptIn(markerClass = UnstableApi.class) private void releaseServerSideAdsLoader() { serverSideAdsLoaderState = serverSideAdsLoader.release(); serverSideAdsLoader = null; @@ -417,20 +412,17 @@ public class PlayerActivity extends AppCompatActivity } } - @OptIn(markerClass = UnstableApi.class) private void saveServerSideAdsLoaderState(Bundle outState) { if (serverSideAdsLoaderState != null) { outState.putBundle(KEY_SERVER_SIDE_ADS_LOADER_STATE, serverSideAdsLoaderState.toBundle()); } } - @OptIn(markerClass = UnstableApi.class) private void restoreServerSideAdsLoaderState(Bundle savedInstanceState) { Bundle adsLoaderStateBundle = savedInstanceState.getBundle(KEY_SERVER_SIDE_ADS_LOADER_STATE); if (adsLoaderStateBundle != null) { serverSideAdsLoaderState = - ImaServerSideAdInsertionMediaSource.AdsLoader.State.CREATOR.fromBundle( - adsLoaderStateBundle); + ImaServerSideAdInsertionMediaSource.AdsLoader.State.fromBundle(adsLoaderStateBundle); } } @@ -514,7 +506,7 @@ public class PlayerActivity extends AppCompatActivity private class PlayerErrorMessageProvider implements ErrorMessageProvider { - @OptIn(markerClass = androidx.media3.common.util.UnstableApi.class) + @OptIn(markerClass = UnstableApi.class) // Using decoder exceptions @Override public Pair getErrorMessage(PlaybackException e) { String errorString = getString(R.string.error_generic); @@ -555,7 +547,7 @@ public class PlayerActivity extends AppCompatActivity return mediaItems; } - @OptIn(markerClass = androidx.media3.common.util.UnstableApi.class) + @OptIn(markerClass = UnstableApi.class) // Using Download API private static MediaItem maybeSetDownloadProperties( MediaItem item, @Nullable DownloadRequest downloadRequest) { if (downloadRequest == null) { diff --git a/demos/main/src/main/java/androidx/media3/demo/main/SampleChooserActivity.java b/demos/main/src/main/java/androidx/media3/demo/main/SampleChooserActivity.java index 5afa2613e3..e5ad2d175e 100644 --- a/demos/main/src/main/java/androidx/media3/demo/main/SampleChooserActivity.java +++ b/demos/main/src/main/java/androidx/media3/demo/main/SampleChooserActivity.java @@ -26,11 +26,13 @@ import android.content.SharedPreferences; import android.content.pm.PackageManager; import android.content.res.AssetManager; import android.net.Uri; -import android.os.AsyncTask; import android.os.Build; import android.os.Bundle; +import android.os.Handler; +import android.os.Looper; import android.text.TextUtils; import android.util.JsonReader; +import android.util.Log; import android.view.Menu; import android.view.MenuInflater; import android.view.MenuItem; @@ -43,15 +45,15 @@ import android.widget.ExpandableListView.OnChildClickListener; import android.widget.ImageButton; import android.widget.TextView; import android.widget.Toast; -import androidx.annotation.DoNotInline; import androidx.annotation.Nullable; import androidx.annotation.OptIn; import androidx.annotation.RequiresApi; import androidx.appcompat.app.AppCompatActivity; +import androidx.media3.common.C; import androidx.media3.common.MediaItem; import androidx.media3.common.MediaItem.ClippingConfiguration; import androidx.media3.common.MediaMetadata; -import androidx.media3.common.util.Log; +import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; import androidx.media3.datasource.DataSource; import androidx.media3.datasource.DataSourceInputStream; @@ -65,6 +67,7 @@ import com.google.common.collect.ImmutableMap; import java.io.IOException; import java.io.InputStream; import java.io.InputStreamReader; +import java.nio.charset.StandardCharsets; import java.util.ArrayList; import java.util.Arrays; import java.util.Collections; @@ -72,6 +75,8 @@ import java.util.HashMap; import java.util.List; import java.util.Map; import java.util.UUID; +import java.util.concurrent.ExecutorService; +import java.util.concurrent.Executors; /** An activity for selecting from a list of media samples. */ public class SampleChooserActivity extends AppCompatActivity @@ -80,7 +85,6 @@ public class SampleChooserActivity extends AppCompatActivity private static final String TAG = "SampleChooserActivity"; private static final String GROUP_POSITION_PREFERENCE_KEY = "sample_chooser_group_position"; private static final String CHILD_POSITION_PREFERENCE_KEY = "sample_chooser_child_position"; - private static final int POST_NOTIFICATION_PERMISSION_REQUEST_CODE = 100; private String[] uris; private boolean useExtensionRenderers; @@ -115,6 +119,7 @@ public class SampleChooserActivity extends AppCompatActivity } } } catch (IOException e) { + Log.e(TAG, "One or more sample lists failed to load", e); Toast.makeText(getApplicationContext(), R.string.sample_list_load_error, Toast.LENGTH_LONG) .show(); } @@ -179,14 +184,6 @@ public class SampleChooserActivity extends AppCompatActivity public void onRequestPermissionsResult( int requestCode, String[] permissions, int[] grantResults) { super.onRequestPermissionsResult(requestCode, permissions, grantResults); - if (requestCode == POST_NOTIFICATION_PERMISSION_REQUEST_CODE) { - handlePostNotificationPermissionGrantResults(grantResults); - } else { - handleExternalStoragePermissionGrantResults(grantResults); - } - } - - private void handlePostNotificationPermissionGrantResults(int[] grantResults) { if (!notificationPermissionToastShown && (grantResults.length == 0 || grantResults[0] != PackageManager.PERMISSION_GRANTED)) { Toast.makeText( @@ -201,30 +198,8 @@ public class SampleChooserActivity extends AppCompatActivity } } - private void handleExternalStoragePermissionGrantResults(int[] grantResults) { - if (grantResults.length == 0) { - // Empty results are triggered if a permission is requested while another request was already - // pending and can be safely ignored in this case. - return; - } else if (grantResults[0] == PackageManager.PERMISSION_GRANTED) { - loadSample(); - } else { - Toast.makeText(getApplicationContext(), R.string.sample_list_load_error, Toast.LENGTH_LONG) - .show(); - finish(); - } - } - private void loadSample() { checkNotNull(uris); - - for (int i = 0; i < uris.length; i++) { - Uri uri = Uri.parse(uris[i]); - if (Util.maybeRequestReadExternalStoragePermission(this, uri)) { - return; - } - } - SampleListLoader loaderTask = new SampleListLoader(); loaderTask.execute(uris); } @@ -279,13 +254,13 @@ public class SampleChooserActivity extends AppCompatActivity != PackageManager.PERMISSION_GRANTED) { downloadMediaItemWaitingForNotificationPermission = playlistHolder.mediaItems.get(0); requestPermissions( - new String[] {Api33.getPostNotificationPermissionString()}, - /* requestCode= */ POST_NOTIFICATION_PERMISSION_REQUEST_CODE); + new String[] {Api33.getPostNotificationPermissionString()}, /* requestCode= */ 0); } else { toggleDownload(playlistHolder.mediaItems.get(0)); } } + @OptIn(markerClass = androidx.media3.common.util.UnstableApi.class) private void toggleDownload(MediaItem mediaItem) { RenderersFactory renderersFactory = DemoUtil.buildRenderersFactory( @@ -302,6 +277,10 @@ public class SampleChooserActivity extends AppCompatActivity if (localConfiguration.adsConfiguration != null) { return R.string.download_ads_unsupported; } + @Nullable MediaItem.DrmConfiguration drmConfiguration = localConfiguration.drmConfiguration; + if (drmConfiguration != null && !drmConfiguration.scheme.equals(C.WIDEVINE_UUID)) { + return R.string.download_only_widevine_drm_supported; + } String scheme = localConfiguration.uri.getScheme(); if (!("http".equals(scheme) || "https".equals(scheme))) { return R.string.download_scheme_unsupported; @@ -314,34 +293,43 @@ public class SampleChooserActivity extends AppCompatActivity return menuItem != null && menuItem.isChecked(); } - private final class SampleListLoader extends AsyncTask> { + private final class SampleListLoader { + + private final ExecutorService executorService; private boolean sawError; - @OptIn(markerClass = androidx.media3.common.util.UnstableApi.class) - @Override - protected List doInBackground(String... uris) { - List result = new ArrayList<>(); - Context context = getApplicationContext(); - DataSource dataSource = DemoUtil.getDataSourceFactory(context).createDataSource(); - for (String uri : uris) { - DataSpec dataSpec = new DataSpec(Uri.parse(uri)); - InputStream inputStream = new DataSourceInputStream(dataSource, dataSpec); - try { - readPlaylistGroups(new JsonReader(new InputStreamReader(inputStream, "UTF-8")), result); - } catch (Exception e) { - Log.e(TAG, "Error loading sample list: " + uri, e); - sawError = true; - } finally { - DataSourceUtil.closeQuietly(dataSource); - } - } - return result; + public SampleListLoader() { + executorService = Executors.newSingleThreadExecutor(); } - @Override - protected void onPostExecute(List result) { - onPlaylistGroups(result, sawError); + @OptIn(markerClass = androidx.media3.common.util.UnstableApi.class) + public void execute(String... uris) { + executorService.execute( + () -> { + List result = new ArrayList<>(); + Context context = getApplicationContext(); + DataSource dataSource = DemoUtil.getDataSourceFactory(context).createDataSource(); + for (String uri : uris) { + DataSpec dataSpec = new DataSpec(Uri.parse(uri)); + InputStream inputStream = new DataSourceInputStream(dataSource, dataSpec); + try { + readPlaylistGroups( + new JsonReader(new InputStreamReader(inputStream, StandardCharsets.UTF_8)), + result); + } catch (Exception e) { + Log.e(TAG, "Error loading sample list: " + uri, e); + sawError = true; + } finally { + DataSourceUtil.closeQuietly(dataSource); + } + } + new Handler(Looper.getMainLooper()) + .post( + () -> { + onPlaylistGroups(result, sawError); + }); + }); } private void readPlaylistGroups(JsonReader reader, List groups) @@ -385,6 +373,7 @@ public class SampleChooserActivity extends AppCompatActivity group.playlists.addAll(playlistHolders); } + @OptIn(markerClass = UnstableApi.class) // Setting image duration. private PlaylistHolder readEntry(JsonReader reader, boolean insidePlaylist) throws IOException { Uri uri = null; String extension = null; @@ -422,6 +411,9 @@ public class SampleChooserActivity extends AppCompatActivity case "clip_end_position_ms": clippingConfiguration.setEndPositionMs(reader.nextLong()); break; + case "image_duration_ms": + mediaItem.setImageDurationMs(reader.nextLong()); + break; case "ad_tag_uri": mediaItem.setAdsConfiguration( new MediaItem.AdsConfiguration.Builder(Uri.parse(reader.nextString())).build()); @@ -674,7 +666,6 @@ public class SampleChooserActivity extends AppCompatActivity @RequiresApi(33) private static class Api33 { - @DoNotInline public static String getPostNotificationPermissionString() { return Manifest.permission.POST_NOTIFICATIONS; } diff --git a/demos/main/src/main/java/androidx/media3/demo/main/TrackSelectionDialog.java b/demos/main/src/main/java/androidx/media3/demo/main/TrackSelectionDialog.java index d5bee96eae..a51b8238a2 100644 --- a/demos/main/src/main/java/androidx/media3/demo/main/TrackSelectionDialog.java +++ b/demos/main/src/main/java/androidx/media3/demo/main/TrackSelectionDialog.java @@ -67,7 +67,8 @@ public final class TrackSelectionDialog extends DialogFragment { } public static final ImmutableList SUPPORTED_TRACK_TYPES = - ImmutableList.of(C.TRACK_TYPE_VIDEO, C.TRACK_TYPE_AUDIO, C.TRACK_TYPE_TEXT); + ImmutableList.of( + C.TRACK_TYPE_VIDEO, C.TRACK_TYPE_AUDIO, C.TRACK_TYPE_TEXT, C.TRACK_TYPE_IMAGE); private final SparseArray tabFragments; private final ArrayList tabTrackTypes; @@ -266,11 +267,13 @@ public final class TrackSelectionDialog extends DialogFragment { private static String getTrackTypeString(Resources resources, @C.TrackType int trackType) { switch (trackType) { case C.TRACK_TYPE_VIDEO: - return resources.getString(R.string.exo_track_selection_title_video); + return resources.getString(R.string.track_selection_title_video); case C.TRACK_TYPE_AUDIO: - return resources.getString(R.string.exo_track_selection_title_audio); + return resources.getString(R.string.track_selection_title_audio); case C.TRACK_TYPE_TEXT: - return resources.getString(R.string.exo_track_selection_title_text); + return resources.getString(R.string.track_selection_title_text); + case C.TRACK_TYPE_IMAGE: + return resources.getString(R.string.track_selection_title_image); default: throw new IllegalArgumentException(); } diff --git a/demos/main/src/main/res/values/strings.xml b/demos/main/src/main/res/values/strings.xml index b8f6e7c320..d416020034 100644 --- a/demos/main/src/main/res/values/strings.xml +++ b/demos/main/src/main/res/values/strings.xml @@ -25,8 +25,6 @@ Playback failed - DRM content not supported on API levels below 18 - This device does not support the required DRM scheme This device does not provide a decoder for %1$s @@ -59,6 +57,16 @@ IMA does not support offline ads + This demo app only supports downloading unencrypted or Widevine DRM content + Prefer extension decoders + Video + + Audio + + Text + + Image + diff --git a/demos/session/build.gradle b/demos/session/build.gradle index 376c69534d..496f7f4e49 100644 --- a/demos/session/build.gradle +++ b/demos/session/build.gradle @@ -16,7 +16,9 @@ apply plugin: 'com.android.application' apply plugin: 'kotlin-android' android { - compileSdkVersion project.ext.compileSdkVersion + namespace 'androidx.media3.demo.session' + + compileSdk project.ext.compileSdkVersion compileOptions { sourceCompatibility JavaVersion.VERSION_1_8 @@ -32,7 +34,6 @@ android { versionCode project.ext.releaseVersionCode minSdkVersion project.ext.minSdkVersion targetSdkVersion project.ext.appTargetSdkVersion - multiDexEnabled true } buildTypes { @@ -57,13 +58,15 @@ android { } dependencies { + // For detecting and debugging leaks only. LeakCanary is not needed for demo app to work. + debugImplementation 'com.squareup.leakcanary:leakcanary-android:' + leakCanaryVersion implementation 'androidx.core:core-ktx:' + androidxCoreVersion + implementation 'androidx.lifecycle:lifecycle-common:' + androidxLifecycleVersion + implementation 'androidx.lifecycle:lifecycle-runtime-ktx:' + androidxLifecycleVersion implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion - implementation 'androidx.multidex:multidex:' + androidxMultidexVersion implementation 'com.google.android.material:material:' + androidxMaterialVersion - implementation project(modulePrefix + 'lib-exoplayer') - implementation project(modulePrefix + 'lib-exoplayer-dash') - implementation project(modulePrefix + 'lib-exoplayer-hls') + implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-guava:' + kotlinxCoroutinesVersion implementation project(modulePrefix + 'lib-ui') implementation project(modulePrefix + 'lib-session') + implementation project(modulePrefix + 'demo-session-service') } diff --git a/demos/session/src/main/AndroidManifest.xml b/demos/session/src/main/AndroidManifest.xml index 90557fc6c7..e6b8d37879 100644 --- a/demos/session/src/main/AndroidManifest.xml +++ b/demos/session/src/main/AndroidManifest.xml @@ -14,20 +14,23 @@ limitations under the License. --> + + android:theme="@style/Theme.Media3Demo"> + + + + android:exported="true" + android:launchMode="singleTop" + android:theme="@style/Theme.AppCompat.NoActionBar"/> - + + diff --git a/demos/session/src/main/java/androidx/media3/demo/session/MainActivity.kt b/demos/session/src/main/java/androidx/media3/demo/session/MainActivity.kt index 9328a059e9..4788496f81 100644 --- a/demos/session/src/main/java/androidx/media3/demo/session/MainActivity.kt +++ b/demos/session/src/main/java/androidx/media3/demo/session/MainActivity.kt @@ -15,9 +15,11 @@ */ package androidx.media3.demo.session +import android.Manifest import android.content.ComponentName import android.content.Context -import android.content.Intent +import android.content.pm.PackageManager +import android.os.Build import android.os.Bundle import android.view.LayoutInflater import android.view.MenuItem @@ -26,6 +28,7 @@ import android.view.ViewGroup import android.widget.ArrayAdapter import android.widget.ListView import android.widget.TextView +import android.widget.Toast import androidx.activity.OnBackPressedCallback import androidx.appcompat.app.AppCompatActivity import androidx.core.content.ContextCompat @@ -70,18 +73,26 @@ class MainActivity : AppCompatActivity() { findViewById(R.id.open_player_floating_button) .setOnClickListener { - // display the playing media items - val intent = Intent(this, PlayerActivity::class.java) - startActivity(intent) + // Start the session activity that shows the playback activity. The System UI uses the same + // intent in the same way to start the activity from the notification. + browser?.sessionActivity?.send() } onBackPressedDispatcher.addCallback( - object : OnBackPressedCallback(/* enabled= */ true) { + object : OnBackPressedCallback(true) { override fun handleOnBackPressed() { popPathStack() } } ) + + if ( + Build.VERSION.SDK_INT >= 33 && + checkSelfPermission(Manifest.permission.POST_NOTIFICATIONS) != + PackageManager.PERMISSION_GRANTED + ) { + requestPermissions(arrayOf(Manifest.permission.POST_NOTIFICATIONS), /* requestCode= */ 0) + } } override fun onOptionsItemSelected(item: MenuItem): Boolean { @@ -102,6 +113,23 @@ class MainActivity : AppCompatActivity() { super.onStop() } + override fun onRequestPermissionsResult( + requestCode: Int, + permissions: Array, + grantResults: IntArray + ) { + super.onRequestPermissionsResult(requestCode, permissions, grantResults) + if (grantResults.isEmpty()) { + // Empty results are triggered if a permission is requested while another request was already + // pending and can be safely ignored in this case. + return + } + if (grantResults[0] != PackageManager.PERMISSION_GRANTED) { + Toast.makeText(applicationContext, R.string.notification_permission_denied, Toast.LENGTH_LONG) + .show() + } + } + private fun initializeBrowser() { browserFuture = MediaBrowser.Builder( diff --git a/demos/session/src/main/java/androidx/media3/demo/session/PlayableFolderActivity.kt b/demos/session/src/main/java/androidx/media3/demo/session/PlayableFolderActivity.kt index 9c6f3e5b4a..97cf2b8110 100644 --- a/demos/session/src/main/java/androidx/media3/demo/session/PlayableFolderActivity.kt +++ b/demos/session/src/main/java/androidx/media3/demo/session/PlayableFolderActivity.kt @@ -43,7 +43,7 @@ import com.google.common.util.concurrent.ListenableFuture class PlayableFolderActivity : AppCompatActivity() { private lateinit var browserFuture: ListenableFuture private val browser: MediaBrowser? - get() = if (browserFuture.isDone) browserFuture.get() else null + get() = if (browserFuture.isDone && !browserFuture.isCancelled) browserFuture.get() else null private lateinit var mediaList: ListView private lateinit var mediaListAdapter: PlayableMediaItemArrayAdapter @@ -51,6 +51,7 @@ class PlayableFolderActivity : AppCompatActivity() { companion object { private const val MEDIA_ITEM_ID_KEY = "MEDIA_ITEM_ID_KEY" + fun createIntent(context: Context, mediaItemID: String): Intent { val intent = Intent(context, PlayableFolderActivity::class.java) intent.putExtra(MEDIA_ITEM_ID_KEY, mediaItemID) @@ -77,8 +78,7 @@ class PlayableFolderActivity : AppCompatActivity() { browser.shuffleModeEnabled = false browser.prepare() browser.play() - val intent = Intent(this, PlayerActivity::class.java) - startActivity(intent) + browser.sessionActivity?.send() } } @@ -88,8 +88,7 @@ class PlayableFolderActivity : AppCompatActivity() { browser.shuffleModeEnabled = true browser.prepare() browser.play() - val intent = Intent(this, PlayerActivity::class.java) - startActivity(intent) + browser.sessionActivity?.send() } findViewById