diff --git a/.github/ISSUE_TEMPLATE/bug.md b/.github/ISSUE_TEMPLATE/bug.md deleted file mode 100644 index 25383cd8dd..0000000000 --- a/.github/ISSUE_TEMPLATE/bug.md +++ /dev/null @@ -1,42 +0,0 @@ ---- -name: Bug report -about: Issue template for a bug report. -title: '' -labels: bug, needs triage -assignees: '' ---- - -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 the ExoPlayer tracker: - https://github.com/google/ExoPlayer/issues?q=is%3Aissue - -When reporting a bug: -------------------------- - -Describe how the issue can be reproduced, ideally using one of the demo apps -or a small sample app that you’re able to share as source code on GitHub. To -increase the chance of your issue getting attention, please also include: - -- Clear reproduction steps including observed and expected behavior -- Output of running "adb bugreport" in the console shortly after encountering - the issue -- URI to test content for reproduction -- For protected content: - - DRM scheme and license server URL - - Authentication HTTP headers - -- AndroidX Media version number -- Android version -- Android device - -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). diff --git a/.github/ISSUE_TEMPLATE/bug.yml b/.github/ISSUE_TEMPLATE/bug.yml new file mode 100644 index 0000000000..b29b2e92b0 --- /dev/null +++ b/.github/ISSUE_TEMPLATE/bug.yml @@ -0,0 +1,100 @@ +name: Bug Report +description: Report a bug in the Media3 library +labels: ["bug", "needs triage"] +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. + + 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 the ExoPlayer tracker: https://github.com/google/ExoPlayer/issues?q=is%3Aissue + - type: dropdown + attributes: + label: Media3 Version + description: What version of Media3 are you using? + options: + - 1.0.0-beta01 + - 1.0.0-alpha03 + - 1.0.0-alpha02 + - 1.0.0-alpha01 + validations: + required: true + - type: textarea + attributes: + label: Devices that reproduce the issue + placeholder: | + Example: + * Pixel 4 running Android 12 + * Samsung S21 running Android 11 + validations: + required: true + - type: textarea + attributes: + label: Devices that do not reproduce the issue + placeholder: | + Example: + * Pixel 3 running Android Pie + - type: dropdown + attributes: + label: Reproducible in the demo app? + description: Please try and reproduce the issue in the [Media3 demo app](https://github.com/androidx/media/tree/release/demos/main). + options: + - "Yes" + - "No" + - Not tested + validations: + required: true + - type: textarea + attributes: + label: Reproduction steps + description: Clear and complete steps we can use to reproduce the problem + placeholder: | + Example: + 1. Play the attached media in the demo app + 2. Seek forward 10s + validations: + required: true + - type: textarea + attributes: + label: Expected result + placeholder: | + Example: + The media plays successfully + validations: + required: true + - type: textarea + attributes: + label: Actual result + placeholder: | + Example: + Playback crashes with the following stack trace: + ... + validations: + required: true + - type: textarea + attributes: + label: Media + description: | + Media we can use to reproduce the problem. Either: + * 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 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. + validations: + required: true + - type: checkboxes + 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 #\'. + + **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. diff --git a/.github/ISSUE_TEMPLATE/config.yml b/.github/ISSUE_TEMPLATE/config.yml new file mode 100644 index 0000000000..3ba13e0cec --- /dev/null +++ b/.github/ISSUE_TEMPLATE/config.yml @@ -0,0 +1 @@ +blank_issues_enabled: false diff --git a/RELEASENOTES.md b/RELEASENOTES.md index 204a19e7f6..febe027e2e 100644 --- a/RELEASENOTES.md +++ b/RELEASENOTES.md @@ -1,4 +1,230 @@ -# Release notes + Release notes + +### Unreleased changes + +* Extractors: + * Add support for AVI + ([#2092](https://github.com/google/ExoPlayer/issues/2092)). +* RTSP: + * Add RTP reader for H263 + ([#63](https://github.com/androidx/media/pull/63)). + +### 1.0.0-beta01 (2022-06-16) + +This release corresponds to the +[ExoPlayer 2.18.0 release](https://github.com/google/ExoPlayer/releases/tag/r2.18.0). + +* Core library: + * Enable support for Android platform diagnostics via + `MediaMetricsManager`. ExoPlayer will forward playback events and + performance data to the platform, which helps to provide system + performance and debugging information on the device. This data may also + be collected by Google + [if sharing usage and diagnostics data is enabled](https://support.google.com/accounts/answer/6078260) + by the user of the device. Apps can opt-out of contributing to platform + diagnostics for ExoPlayer with + `ExoPlayer.Builder.setUsePlatformDiagnostics(false)`. + * Fix bug that tracks are reset too often when using `MergingMediaSource`, + for example when side-loading subtitles and changing the selected + subtitle mid-playback + ([#10248](https://github.com/google/ExoPlayer/issues/10248)). + * Stop detecting 5G-NSA network type on API 29 and 30. These playbacks + will assume a 4G network. + * Disallow passing `null` to + `MediaSource.Factory.setDrmSessionManagerProvider` and + `MediaSource.Factory.setLoadErrorHandlingPolicy`. Instances of + `DefaultDrmSessionManagerProvider` and `DefaultLoadErrorHandlingPolicy` + can be passed explicitly if required. + * Add `MediaItem.RequestMetadata` to represent metadata needed to play + media when the exact `LocalConfiguration` is not known. Also remove + `MediaMetadata.mediaUrl` as this is now included in `RequestMetadata`. + * Add `Player.Command.COMMAND_SET_MEDIA_ITEM` to enable players to allow + setting a single item. +* Track selection: + * Flatten `TrackSelectionOverrides` class into `TrackSelectionParameters`, + and promote `TrackSelectionOverride` to a top level class. + * Rename `TracksInfo` to `Tracks` and `TracksInfo.TrackGroupInfo` to + `Tracks.Group`. `Player.getCurrentTracksInfo` and + `Player.Listener.onTracksInfoChanged` have also been renamed to + `Player.getCurrentTracks` and `Player.Listener.onTracksChanged`. + * Change `DefaultTrackSelector.buildUponParameters` and + `DefaultTrackSelector.Parameters.buildUpon` to return + `DefaultTrackSelector.Parameters.Builder` instead of the deprecated + `DefaultTrackSelector.ParametersBuilder`. + * Add + `DefaultTrackSelector.Parameters.constrainAudioChannelCountToDeviceCapabilities` + which is enabled by default. When enabled, the `DefaultTrackSelector` + will prefer audio tracks whose channel count does not exceed the device + output capabilities. On handheld devices, the `DefaultTrackSelector` + will prefer stereo/mono over multichannel audio formats, unless the + multichannel format can be + [Spatialized](https://developer.android.com/reference/android/media/Spatializer) + (Android 12L+) or is a Dolby surround sound format. In addition, on + devices that support audio spatialization, the `DefaultTrackSelector` + will monitor for changes in the + [Spatializer properties](https://developer.android.com/reference/android/media/Spatializer.OnSpatializerStateChangedListener) + and trigger a new track selection upon these. Devices with a + `television` + [UI mode](https://developer.android.com/guide/topics/resources/providing-resources#UiModeQualifier) + are excluded from these constraints and the format with the highest + channel count will be preferred. To enable this feature, the + `DefaultTrackSelector` instance must be constructed with a `Context`. +* Video: + * Rename `DummySurface` to `PlaceholderSurface`. + * Add AV1 support to the `MediaCodecVideoRenderer.getCodecMaxInputSize`. +* Audio: + * Use LG AC3 audio decoder advertising non-standard MIME type. + * Change the return type of `AudioAttributes.getAudioAttributesV21()` from + `android.media.AudioAttributes` to a new `AudioAttributesV21` wrapper + class, to prevent slow ART verification on API < 21. + * Query the platform (API 29+) or assume the audio encoding channel count + for audio passthrough when the format audio channel count is unset, + which occurs with HLS chunkless preparation + ([10204](https://github.com/google/ExoPlayer/issues/10204)). + * Configure `AudioTrack` with channel mask + `AudioFormat.CHANNEL_OUT_7POINT1POINT4` if the decoder outputs 12 + channel PCM audio + ([#10322](#https://github.com/google/ExoPlayer/pull/10322). +* DRM + * Ensure the DRM session is always correctly updated when seeking + immediately after a format change + ([10274](https://github.com/google/ExoPlayer/issues/10274)). +* Text: + * Change `Player.getCurrentCues()` to return `CueGroup` instead of + `List`. + * SSA: Support `OutlineColour` style setting when `BorderStyle == 3` (i.e. + `OutlineColour` sets the background of the cue) + ([#8435](https://github.com/google/ExoPlayer/issues/8435)). + * CEA-708: Parse data into multiple service blocks and ignore blocks not + associated with the currently selected service number. + * Remove `RawCcExtractor`, which was only used to handle a Google-internal + subtitle format. +* Extractors: + * Matroska: Parse `DiscardPadding` for Opus tracks. + * MP4: Parse bitrates from `esds` boxes. + * Ogg: Allow duplicate Opus ID and comment headers + ([#10038](https://github.com/google/ExoPlayer/issues/10038)). +* UI: + * Fix delivery of events to `OnClickListener`s set on `PlayerView`, in the + case that `useController=false` + ([#9605](https://github.com/google/ExoPlayer/issues/9605)). Also fix + delivery of events to `OnLongClickListener` for all view configurations. + * Fix incorrectly treating a sequence of touch events that exit the bounds + of `PlayerView` before `ACTION_UP` as a click + ([#9861](https://github.com/google/ExoPlayer/issues/9861)). + * Fix `PlayerView` accessibility issue where tapping might toggle playback + rather than hiding the controls + ([#8627](https://github.com/google/ExoPlayer/issues/8627)). + * Rewrite `TrackSelectionView` and `TrackSelectionDialogBuilder` to work + with the `Player` interface rather than `ExoPlayer`. This allows the + views to be used with other `Player` implementations, and removes the + dependency from the UI module to the ExoPlayer module. This is a + breaking change. + * Don't show forced text tracks in the `PlayerView` track selector, and + keep a suitable forced text track selected if "None" is selected + ([#9432](https://github.com/google/ExoPlayer/issues/9432)). +* DASH: + * Parse channel count from DTS `AudioChannelConfiguration` elements. This + re-enables audio passthrough for DTS streams + ([#10159](https://github.com/google/ExoPlayer/issues/10159)). + * Disallow passing `null` to + `DashMediaSource.Factory.setCompositeSequenceableLoaderFactory`. + Instances of `DefaultCompositeSequenceableLoaderFactory` can be passed + explicitly if required. +* HLS: + * Fallback to chunkful preparation if the playlist CODECS attribute does + not contain the audio codec + ([#10065](https://github.com/google/ExoPlayer/issues/10065)). + * Disallow passing `null` to + `HlsMediaSource.Factory.setCompositeSequenceableLoaderFactory`, + `HlsMediaSource.Factory.setPlaylistParserFactory`, and + `HlsMediaSource.Factory.setPlaylistTrackerFactory`. Instances of + `DefaultCompositeSequenceableLoaderFactory`, + `DefaultHlsPlaylistParserFactory`, or a reference to + `DefaultHlsPlaylistTracker.FACTORY` can be passed explicitly if + required. +* Smooth Streaming: + * Disallow passing `null` to + `SsMediaSource.Factory.setCompositeSequenceableLoaderFactory`. Instances + of `DefaultCompositeSequenceableLoaderFactory` can be passed explicitly + if required. +* RTSP: + * Add RTP reader for MPEG4 + ([#35](https://github.com/androidx/media/pull/35)). + * Add RTP reader for HEVC + ([#36](https://github.com/androidx/media/pull/36)). + * Add RTP reader for AMR. Currently only mono-channel, non-interleaved AMR + streams are supported. Compound AMR RTP payload is not supported. + ([#46](https://github.com/androidx/media/pull/46)) + * Add RTP reader for VP8 + ([#47](https://github.com/androidx/media/pull/47)). + * Add RTP reader for WAV + ([#56](https://github.com/androidx/media/pull/56)). + * Fix RTSP basic authorization header. + ([#9544](https://github.com/google/ExoPlayer/issues/9544)). + * Stop checking mandatory SDP fields as ExoPlayer doesn't need them + ([#10049](https://github.com/google/ExoPlayer/issues/10049)). + * Throw checked exception when parsing RTSP timing + ([#10165](https://github.com/google/ExoPlayer/issues/10165)). + * Add RTP reader for VP9 + ([#47](https://github.com/androidx/media/pull/64)). + * Add RTP reader for OPUS + ([#53](https://github.com/androidx/media/pull/53)). +* Session: + * Replace `MediaSession.MediaItemFiller` with + `MediaSession.Callback.onAddMediaItems` to allow asynchronous resolution + of requests. + * Support `setMediaItems(s)` methods when `MediaController` connects to a + legacy media session. + * Remove `MediaController.setMediaUri` and + `MediaSession.Callback.onSetMediaUri`. The same functionality can be + achieved by using `MediaController.setMediaItem` and + `MediaSession.Callback.onAddMediaItems`. + * Forward legacy `MediaController` calls to play media to + `MediaSession.Callback.onAddMediaItems` instead of `onSetMediaUri`. + * Add `MediaNotification.Provider` and `DefaultMediaNotificationProvider` + to provide customization of the notification. + * Add `BitmapLoader` and `SimpleBitmapLoader` for downloading artwork + images. + * Add `MediaSession.setCustomLayout()` to provide backwards compatibility + with the legacy session. + * Add `MediaSession.setSessionExtras()` to provide feature parity with + legacy session. + * Rename `MediaSession.MediaSessionCallback` to `MediaSession.Callback`, + `MediaLibrarySession.MediaLibrarySessionCallback` to + `MediaLibrarySession.Callback` and + `MediaSession.Builder.setSessionCallback` to `setCallback`. + * Fix NPE in `MediaControllerImplLegacy` + ([#59](https://github.com/androidx/media/pull/59)). + * Update session position info on timeline + change([#51](https://github.com/androidx/media/issues/51)). + * Fix NPE in `MediaControllerImplBase` after releasing controller + ([#74](https://github.com/androidx/media/issues/74)). + * Fix `IndexOutOfBoundsException` when setting less media items than in + the current playlist + ([#86](https://github.com/androidx/media/issues/86)). +* Ad playback / IMA: + * Decrease ad polling rate from every 100ms to every 200ms, to line up + with Media Rating Council (MRC) recommendations. +* FFmpeg extension: + * Update CMake version to `3.21.0+` to avoid a CMake bug causing + AndroidStudio's gradle sync to fail + ([#9933](https://github.com/google/ExoPlayer/issues/9933)). +* Remove deprecated symbols: + * Remove `Player.Listener.onTracksChanged`. Use + `Player.Listener.onTracksInfoChanged` instead. + * Remove `Player.getCurrentTrackGroups` and + `Player.getCurrentTrackSelections`. Use `Player.getCurrentTracksInfo` + instead. You can also continue to use `ExoPlayer.getCurrentTrackGroups` + and `ExoPlayer.getCurrentTrackSelections`, although these methods remain + deprecated. + * Remove `DownloadHelper` + `DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_VIEWPORT` and + `DEFAULT_TRACK_SELECTOR_PARAMETERS` constants. Use + `getDefaultTrackSelectorParameters(Context)` instead when possible, and + `DEFAULT_TRACK_SELECTOR_PARAMETERS_WITHOUT_CONTEXT` otherwise. + * Remove constructor `DefaultTrackSelector(ExoTrackSelection.Factory)`. + Use `DefaultTrackSelector(Context, ExoTrackSelection.Factory)` instead. ### 1.0.0-alpha03 (2022-03-14) diff --git a/api.txt b/api.txt new file mode 100644 index 0000000000..c4e42f5772 --- /dev/null +++ b/api.txt @@ -0,0 +1,1800 @@ +// Signature format: 3.0 +package androidx.media3.common { + + public final class AdOverlayInfo { + field public static final int PURPOSE_CLOSE_AD = 2; // 0x2 + field public static final int PURPOSE_CONTROLS = 1; // 0x1 + field public static final int PURPOSE_NOT_VISIBLE = 4; // 0x4 + field public static final int PURPOSE_OTHER = 3; // 0x3 + field @androidx.media3.common.AdOverlayInfo.Purpose public final int purpose; + field @Nullable public final String reasonDetail; + field public final android.view.View view; + } + + public static final class AdOverlayInfo.Builder { + ctor public AdOverlayInfo.Builder(android.view.View, @androidx.media3.common.AdOverlayInfo.Purpose int); + method public androidx.media3.common.AdOverlayInfo build(); + method public androidx.media3.common.AdOverlayInfo.Builder setDetailedReason(@Nullable String); + } + + @IntDef({androidx.media3.common.AdOverlayInfo.PURPOSE_CONTROLS, androidx.media3.common.AdOverlayInfo.PURPOSE_CLOSE_AD, androidx.media3.common.AdOverlayInfo.PURPOSE_OTHER, androidx.media3.common.AdOverlayInfo.PURPOSE_NOT_VISIBLE}) @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 AdOverlayInfo.Purpose { + } + + public interface AdViewProvider { + method public default java.util.List getAdOverlayInfos(); + method @Nullable public android.view.ViewGroup getAdViewGroup(); + } + + public final class AudioAttributes { + method @RequiresApi(21) public androidx.media3.common.AudioAttributes.AudioAttributesV21 getAudioAttributesV21(); + field public static final androidx.media3.common.AudioAttributes DEFAULT; + field @androidx.media3.common.C.AudioAllowedCapturePolicy public final int allowedCapturePolicy; + field @androidx.media3.common.C.AudioContentType public final int contentType; + field @androidx.media3.common.C.AudioFlags public final int flags; + field @androidx.media3.common.C.SpatializationBehavior public final int spatializationBehavior; + field @androidx.media3.common.C.AudioUsage public final int usage; + } + + @RequiresApi(21) public static final class AudioAttributes.AudioAttributesV21 { + field public final android.media.AudioAttributes audioAttributes; + } + + public static final class AudioAttributes.Builder { + ctor public AudioAttributes.Builder(); + method public androidx.media3.common.AudioAttributes build(); + method public androidx.media3.common.AudioAttributes.Builder setAllowedCapturePolicy(@androidx.media3.common.C.AudioAllowedCapturePolicy int); + method public androidx.media3.common.AudioAttributes.Builder setContentType(@androidx.media3.common.C.AudioContentType int); + method public androidx.media3.common.AudioAttributes.Builder setFlags(@androidx.media3.common.C.AudioFlags int); + method public androidx.media3.common.AudioAttributes.Builder setSpatializationBehavior(@androidx.media3.common.C.SpatializationBehavior int); + method public androidx.media3.common.AudioAttributes.Builder setUsage(@androidx.media3.common.C.AudioUsage int); + } + + public final class C { + field public static final int ALLOW_CAPTURE_BY_ALL = 1; // 0x1 + field public static final int ALLOW_CAPTURE_BY_NONE = 3; // 0x3 + field public static final int ALLOW_CAPTURE_BY_SYSTEM = 2; // 0x2 + field public static final int AUDIO_CONTENT_TYPE_MOVIE = 3; // 0x3 + field public static final int AUDIO_CONTENT_TYPE_MUSIC = 2; // 0x2 + field public static final int AUDIO_CONTENT_TYPE_SONIFICATION = 4; // 0x4 + field public static final int AUDIO_CONTENT_TYPE_SPEECH = 1; // 0x1 + field public static final int AUDIO_CONTENT_TYPE_UNKNOWN = 0; // 0x0 + field public static final java.util.UUID CLEARKEY_UUID; + field public static final java.util.UUID COMMON_PSSH_UUID; + field public static final int CONTENT_TYPE_DASH = 0; // 0x0 + field public static final int CONTENT_TYPE_HLS = 2; // 0x2 + field public static final int CONTENT_TYPE_OTHER = 4; // 0x4 + field public static final int CONTENT_TYPE_RTSP = 3; // 0x3 + field public static final int CONTENT_TYPE_SS = 1; // 0x1 + field public static final int CRYPTO_TYPE_CUSTOM_BASE = 10000; // 0x2710 + field public static final int CRYPTO_TYPE_FRAMEWORK = 2; // 0x2 + field public static final int CRYPTO_TYPE_NONE = 0; // 0x0 + field public static final int CRYPTO_TYPE_UNSUPPORTED = 1; // 0x1 + field public static final long DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS = 3000L; // 0xbb8L + field public static final long DEFAULT_SEEK_BACK_INCREMENT_MS = 5000L; // 0x1388L + field public static final long DEFAULT_SEEK_FORWARD_INCREMENT_MS = 15000L; // 0x3a98L + field public static final int FLAG_AUDIBILITY_ENFORCED = 1; // 0x1 + field public static final int INDEX_UNSET = -1; // 0xffffffff + field public static final String LANGUAGE_UNDETERMINED = "und"; + field public static final int LENGTH_UNSET = -1; // 0xffffffff + 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_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 + field public static final int ROLE_FLAG_DESCRIBES_VIDEO = 512; // 0x200 + field public static final int ROLE_FLAG_DUB = 16; // 0x10 + field public static final int ROLE_FLAG_EASY_TO_READ = 8192; // 0x2000 + field public static final int ROLE_FLAG_EMERGENCY = 32; // 0x20 + field public static final int ROLE_FLAG_ENHANCED_DIALOG_INTELLIGIBILITY = 2048; // 0x800 + field public static final int ROLE_FLAG_MAIN = 1; // 0x1 + field public static final int ROLE_FLAG_SIGN = 256; // 0x100 + field public static final int ROLE_FLAG_SUBTITLE = 128; // 0x80 + field public static final int ROLE_FLAG_SUPPLEMENTARY = 4; // 0x4 + field public static final int ROLE_FLAG_TRANSCRIBES_DIALOG = 4096; // 0x1000 + field public static final int ROLE_FLAG_TRICK_PLAY = 16384; // 0x4000 + field public static final int SELECTION_FLAG_AUTOSELECT = 4; // 0x4 + field public static final int SELECTION_FLAG_DEFAULT = 1; // 0x1 + field public static final int SELECTION_FLAG_FORCED = 2; // 0x2 + field public static final int SPATIALIZATION_BEHAVIOR_AUTO = 0; // 0x0 + field public static final int SPATIALIZATION_BEHAVIOR_NEVER = 1; // 0x1 + field public static final long TIME_END_OF_SOURCE = -9223372036854775808L; // 0x8000000000000000L + field public static final long TIME_UNSET = -9223372036854775807L; // 0x8000000000000001L + field public static final int TRACK_TYPE_AUDIO = 1; // 0x1 + field public static final int TRACK_TYPE_CAMERA_MOTION = 6; // 0x6 + field public static final int TRACK_TYPE_CUSTOM_BASE = 10000; // 0x2710 + field public static final int TRACK_TYPE_DEFAULT = 0; // 0x0 + field public static final int TRACK_TYPE_IMAGE = 4; // 0x4 + field public static final int TRACK_TYPE_METADATA = 5; // 0x5 + field public static final int TRACK_TYPE_NONE = -2; // 0xfffffffe + field public static final int TRACK_TYPE_TEXT = 3; // 0x3 + field public static final int TRACK_TYPE_UNKNOWN = -1; // 0xffffffff + field public static final int TRACK_TYPE_VIDEO = 2; // 0x2 + field public static final int USAGE_ALARM = 4; // 0x4 + field public static final int USAGE_ASSISTANCE_ACCESSIBILITY = 11; // 0xb + field public static final int USAGE_ASSISTANCE_NAVIGATION_GUIDANCE = 12; // 0xc + field public static final int USAGE_ASSISTANCE_SONIFICATION = 13; // 0xd + field public static final int USAGE_ASSISTANT = 16; // 0x10 + field public static final int USAGE_GAME = 14; // 0xe + field public static final int USAGE_MEDIA = 1; // 0x1 + field public static final int USAGE_NOTIFICATION = 5; // 0x5 + field public static final int USAGE_NOTIFICATION_COMMUNICATION_DELAYED = 9; // 0x9 + field public static final int USAGE_NOTIFICATION_COMMUNICATION_INSTANT = 8; // 0x8 + field public static final int USAGE_NOTIFICATION_COMMUNICATION_REQUEST = 7; // 0x7 + field public static final int USAGE_NOTIFICATION_EVENT = 10; // 0xa + field public static final int USAGE_NOTIFICATION_RINGTONE = 6; // 0x6 + field public static final int USAGE_UNKNOWN = 0; // 0x0 + 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 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 + field public static final java.util.UUID WIDEVINE_UUID; + } + + @IntDef({androidx.media3.common.C.ALLOW_CAPTURE_BY_ALL, androidx.media3.common.C.ALLOW_CAPTURE_BY_NONE, androidx.media3.common.C.ALLOW_CAPTURE_BY_SYSTEM}) @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.AudioAllowedCapturePolicy { + } + + @IntDef({androidx.media3.common.C.AUDIO_CONTENT_TYPE_MOVIE, androidx.media3.common.C.AUDIO_CONTENT_TYPE_MUSIC, androidx.media3.common.C.AUDIO_CONTENT_TYPE_SONIFICATION, androidx.media3.common.C.AUDIO_CONTENT_TYPE_SPEECH, androidx.media3.common.C.AUDIO_CONTENT_TYPE_UNKNOWN}) @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.AudioContentType { + } + + @IntDef(flag=true, value={androidx.media3.common.C.FLAG_AUDIBILITY_ENFORCED}) @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.AudioFlags { + } + + @IntDef({androidx.media3.common.C.USAGE_ALARM, androidx.media3.common.C.USAGE_ASSISTANCE_ACCESSIBILITY, androidx.media3.common.C.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE, androidx.media3.common.C.USAGE_ASSISTANCE_SONIFICATION, androidx.media3.common.C.USAGE_ASSISTANT, androidx.media3.common.C.USAGE_GAME, androidx.media3.common.C.USAGE_MEDIA, androidx.media3.common.C.USAGE_NOTIFICATION, androidx.media3.common.C.USAGE_NOTIFICATION_COMMUNICATION_DELAYED, androidx.media3.common.C.USAGE_NOTIFICATION_COMMUNICATION_INSTANT, androidx.media3.common.C.USAGE_NOTIFICATION_COMMUNICATION_REQUEST, androidx.media3.common.C.USAGE_NOTIFICATION_EVENT, androidx.media3.common.C.USAGE_NOTIFICATION_RINGTONE, androidx.media3.common.C.USAGE_UNKNOWN, androidx.media3.common.C.USAGE_VOICE_COMMUNICATION, androidx.media3.common.C.USAGE_VOICE_COMMUNICATION_SIGNALLING}) @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.AudioUsage { + } + + @IntDef({androidx.media3.common.C.CONTENT_TYPE_DASH, androidx.media3.common.C.CONTENT_TYPE_SS, androidx.media3.common.C.CONTENT_TYPE_HLS, androidx.media3.common.C.CONTENT_TYPE_RTSP, androidx.media3.common.C.CONTENT_TYPE_OTHER}) @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.ContentType { + } + + @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.SELECTION_FLAG_DEFAULT, androidx.media3.common.C.SELECTION_FLAG_FORCED, androidx.media3.common.C.SELECTION_FLAG_AUTOSELECT}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface C.SelectionFlags { + } + + @IntDef({androidx.media3.common.C.SPATIALIZATION_BEHAVIOR_AUTO, androidx.media3.common.C.SPATIALIZATION_BEHAVIOR_NEVER}) @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.SpatializationBehavior { + } + + @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({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 { + } + + public final class DeviceInfo { + 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 @androidx.media3.common.DeviceInfo.PlaybackType public final int playbackType; + } + + @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 { + } + + public interface ErrorMessageProvider { + method public android.util.Pair getErrorMessage(T); + } + + public final class Format { + field public static final int NO_VALUE = -1; // 0xffffffff + field public final int channelCount; + field @Nullable public final String codecs; + field @Nullable public final String containerMimeType; + field public final float frameRate; + field public final int height; + field @Nullable public final String id; + field @Nullable public final String label; + field @Nullable public final String language; + field public final float pixelWidthHeightRatio; + field @androidx.media3.common.C.RoleFlags public final int roleFlags; + field @Nullable public final String sampleMimeType; + field public final int sampleRate; + field @androidx.media3.common.C.SelectionFlags public final int selectionFlags; + field public final int width; + } + + public final class HeartRating extends androidx.media3.common.Rating { + ctor public HeartRating(); + ctor public HeartRating(boolean); + method public boolean isHeart(); + method public boolean isRated(); + } + + 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); + 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; + field public final androidx.media3.common.MediaItem.LiveConfiguration liveConfiguration; + field @Nullable public final androidx.media3.common.MediaItem.LocalConfiguration localConfiguration; + field public final String mediaId; + field public final androidx.media3.common.MediaMetadata mediaMetadata; + field public final androidx.media3.common.MediaItem.RequestMetadata requestMetadata; + } + + public static final class MediaItem.AdsConfiguration { + method public androidx.media3.common.MediaItem.AdsConfiguration.Builder buildUpon(); + field public final android.net.Uri adTagUri; + field @Nullable public final Object adsId; + } + + public static final class MediaItem.AdsConfiguration.Builder { + ctor public MediaItem.AdsConfiguration.Builder(android.net.Uri); + method public androidx.media3.common.MediaItem.AdsConfiguration build(); + method public androidx.media3.common.MediaItem.AdsConfiguration.Builder setAdTagUri(android.net.Uri); + method public androidx.media3.common.MediaItem.AdsConfiguration.Builder setAdsId(@Nullable Object); + } + + public static final class MediaItem.Builder { + ctor public MediaItem.Builder(); + method public androidx.media3.common.MediaItem build(); + method public androidx.media3.common.MediaItem.Builder setAdsConfiguration(@Nullable androidx.media3.common.MediaItem.AdsConfiguration); + method public androidx.media3.common.MediaItem.Builder setClippingConfiguration(androidx.media3.common.MediaItem.ClippingConfiguration); + method public androidx.media3.common.MediaItem.Builder setDrmConfiguration(@Nullable androidx.media3.common.MediaItem.DrmConfiguration); + method public androidx.media3.common.MediaItem.Builder setLiveConfiguration(androidx.media3.common.MediaItem.LiveConfiguration); + method public androidx.media3.common.MediaItem.Builder setMediaId(String); + method public androidx.media3.common.MediaItem.Builder setMediaMetadata(androidx.media3.common.MediaMetadata); + method public androidx.media3.common.MediaItem.Builder setMimeType(@Nullable String); + 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); + } + + public static class MediaItem.ClippingConfiguration { + method public androidx.media3.common.MediaItem.ClippingConfiguration.Builder buildUpon(); + field public static final androidx.media3.common.MediaItem.ClippingConfiguration UNSET; + field public final long endPositionMs; + field public final boolean relativeToDefaultPosition; + field public final boolean relativeToLiveWindow; + field @IntRange(from=0) public final long startPositionMs; + field public final boolean startsAtKeyFrame; + } + + public static final class MediaItem.ClippingConfiguration.Builder { + ctor public MediaItem.ClippingConfiguration.Builder(); + method public androidx.media3.common.MediaItem.ClippingConfiguration build(); + method public androidx.media3.common.MediaItem.ClippingConfiguration.Builder setEndPositionMs(long); + method public androidx.media3.common.MediaItem.ClippingConfiguration.Builder setRelativeToDefaultPosition(boolean); + method public androidx.media3.common.MediaItem.ClippingConfiguration.Builder setRelativeToLiveWindow(boolean); + method public androidx.media3.common.MediaItem.ClippingConfiguration.Builder setStartPositionMs(@IntRange(from=0) long); + method public androidx.media3.common.MediaItem.ClippingConfiguration.Builder setStartsAtKeyFrame(boolean); + } + + public static final class MediaItem.DrmConfiguration { + method public androidx.media3.common.MediaItem.DrmConfiguration.Builder buildUpon(); + method @Nullable public byte[] getKeySetId(); + field public final boolean forceDefaultLicenseUri; + field public final com.google.common.collect.ImmutableList forcedSessionTrackTypes; + field public final com.google.common.collect.ImmutableMap licenseRequestHeaders; + field @Nullable public final android.net.Uri licenseUri; + field public final boolean multiSession; + field public final boolean playClearContentWithoutKey; + field public final java.util.UUID scheme; + } + + public static final class MediaItem.DrmConfiguration.Builder { + ctor public MediaItem.DrmConfiguration.Builder(java.util.UUID); + method public androidx.media3.common.MediaItem.DrmConfiguration build(); + method public androidx.media3.common.MediaItem.DrmConfiguration.Builder setForceDefaultLicenseUri(boolean); + method public androidx.media3.common.MediaItem.DrmConfiguration.Builder setForceSessionsForAudioAndVideoTracks(boolean); + method public androidx.media3.common.MediaItem.DrmConfiguration.Builder setForcedSessionTrackTypes(java.util.List); + method public androidx.media3.common.MediaItem.DrmConfiguration.Builder setKeySetId(@Nullable byte[]); + method public androidx.media3.common.MediaItem.DrmConfiguration.Builder setLicenseRequestHeaders(java.util.Map); + method public androidx.media3.common.MediaItem.DrmConfiguration.Builder setLicenseUri(@Nullable android.net.Uri); + method public androidx.media3.common.MediaItem.DrmConfiguration.Builder setLicenseUri(@Nullable String); + method public androidx.media3.common.MediaItem.DrmConfiguration.Builder setMultiSession(boolean); + method public androidx.media3.common.MediaItem.DrmConfiguration.Builder setPlayClearContentWithoutKey(boolean); + method public androidx.media3.common.MediaItem.DrmConfiguration.Builder setScheme(java.util.UUID); + } + + public static final class MediaItem.LiveConfiguration { + method public androidx.media3.common.MediaItem.LiveConfiguration.Builder buildUpon(); + field public static final androidx.media3.common.MediaItem.LiveConfiguration UNSET; + field public final long maxOffsetMs; + field public final float maxPlaybackSpeed; + field public final long minOffsetMs; + field public final float minPlaybackSpeed; + field public final long targetOffsetMs; + } + + public static final class MediaItem.LiveConfiguration.Builder { + ctor public MediaItem.LiveConfiguration.Builder(); + method public androidx.media3.common.MediaItem.LiveConfiguration build(); + method public androidx.media3.common.MediaItem.LiveConfiguration.Builder setMaxOffsetMs(long); + method public androidx.media3.common.MediaItem.LiveConfiguration.Builder setMaxPlaybackSpeed(float); + method public androidx.media3.common.MediaItem.LiveConfiguration.Builder setMinOffsetMs(long); + method public androidx.media3.common.MediaItem.LiveConfiguration.Builder setMinPlaybackSpeed(float); + method public androidx.media3.common.MediaItem.LiveConfiguration.Builder setTargetOffsetMs(long); + } + + public static 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; + field public final com.google.common.collect.ImmutableList subtitleConfigurations; + field @Nullable public final Object tag; + field public final android.net.Uri uri; + } + + public static final class MediaItem.RequestMetadata { + method public androidx.media3.common.MediaItem.RequestMetadata.Builder buildUpon(); + field public static final androidx.media3.common.MediaItem.RequestMetadata EMPTY; + field @Nullable public final android.os.Bundle extras; + field @Nullable public final android.net.Uri mediaUri; + field @Nullable public final String searchQuery; + } + + public static final class MediaItem.RequestMetadata.Builder { + ctor public MediaItem.RequestMetadata.Builder(); + method public androidx.media3.common.MediaItem.RequestMetadata build(); + method public androidx.media3.common.MediaItem.RequestMetadata.Builder setExtras(@Nullable android.os.Bundle); + method public androidx.media3.common.MediaItem.RequestMetadata.Builder setMediaUri(@Nullable android.net.Uri); + method public androidx.media3.common.MediaItem.RequestMetadata.Builder setSearchQuery(@Nullable String); + } + + public static class MediaItem.SubtitleConfiguration { + method public androidx.media3.common.MediaItem.SubtitleConfiguration.Builder buildUpon(); + field @Nullable public final String id; + field @Nullable public final String label; + field @Nullable public final String language; + field @Nullable public final String mimeType; + field @androidx.media3.common.C.RoleFlags public final int roleFlags; + field @androidx.media3.common.C.SelectionFlags public final int selectionFlags; + field public final android.net.Uri uri; + } + + public static final class MediaItem.SubtitleConfiguration.Builder { + ctor public MediaItem.SubtitleConfiguration.Builder(android.net.Uri); + method public androidx.media3.common.MediaItem.SubtitleConfiguration build(); + method public androidx.media3.common.MediaItem.SubtitleConfiguration.Builder setId(@Nullable String); + method public androidx.media3.common.MediaItem.SubtitleConfiguration.Builder setLabel(@Nullable String); + method public androidx.media3.common.MediaItem.SubtitleConfiguration.Builder setLanguage(@Nullable String); + method public androidx.media3.common.MediaItem.SubtitleConfiguration.Builder setMimeType(@Nullable String); + method public androidx.media3.common.MediaItem.SubtitleConfiguration.Builder setRoleFlags(@androidx.media3.common.C.RoleFlags int); + method public androidx.media3.common.MediaItem.SubtitleConfiguration.Builder setSelectionFlags(@androidx.media3.common.C.SelectionFlags int); + method public androidx.media3.common.MediaItem.SubtitleConfiguration.Builder setUri(android.net.Uri); + } + + 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 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 + field public static final int PICTURE_TYPE_BAND_ARTIST_LOGO = 19; // 0x13 + field public static final int PICTURE_TYPE_BAND_ORCHESTRA = 10; // 0xa + field public static final int PICTURE_TYPE_COMPOSER = 11; // 0xb + field public static final int PICTURE_TYPE_CONDUCTOR = 9; // 0x9 + field public static final int PICTURE_TYPE_DURING_PERFORMANCE = 15; // 0xf + field public static final int PICTURE_TYPE_DURING_RECORDING = 14; // 0xe + field public static final int PICTURE_TYPE_FILE_ICON = 1; // 0x1 + field public static final int PICTURE_TYPE_FILE_ICON_OTHER = 2; // 0x2 + field public static final int PICTURE_TYPE_FRONT_COVER = 3; // 0x3 + field public static final int PICTURE_TYPE_ILLUSTRATION = 18; // 0x12 + field public static final int PICTURE_TYPE_LEAD_ARTIST_PERFORMER = 7; // 0x7 + field public static final int PICTURE_TYPE_LEAFLET_PAGE = 5; // 0x5 + field public static final int PICTURE_TYPE_LYRICIST = 12; // 0xc + field public static final int PICTURE_TYPE_MEDIA = 6; // 0x6 + field public static final int PICTURE_TYPE_MOVIE_VIDEO_SCREEN_CAPTURE = 16; // 0x10 + field public static final int PICTURE_TYPE_OTHER = 0; // 0x0 + field public static final int PICTURE_TYPE_PUBLISHER_STUDIO_LOGO = 20; // 0x14 + field public static final int PICTURE_TYPE_RECORDING_LOCATION = 13; // 0xd + field @Nullable public final CharSequence albumArtist; + field @Nullable public final CharSequence albumTitle; + field @Nullable public final CharSequence artist; + field @Nullable public final byte[] artworkData; + field @Nullable @androidx.media3.common.MediaMetadata.PictureType public final Integer artworkDataType; + field @Nullable public final android.net.Uri artworkUri; + field @Nullable public final CharSequence compilation; + field @Nullable public final CharSequence composer; + field @Nullable public final CharSequence conductor; + field @Nullable public final CharSequence description; + 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 @Nullable public final CharSequence genre; + field @Nullable public final Boolean isPlayable; + field @Nullable public final androidx.media3.common.Rating overallRating; + field @Nullable public final Integer recordingDay; + field @Nullable public final Integer recordingMonth; + field @Nullable public final Integer recordingYear; + field @Nullable public final Integer releaseDay; + field @Nullable public final Integer releaseMonth; + field @Nullable public final Integer releaseYear; + field @Nullable public final CharSequence station; + field @Nullable public final CharSequence subtitle; + field @Nullable public final CharSequence title; + field @Nullable public final Integer totalDiscCount; + field @Nullable public final Integer totalTrackCount; + field @Nullable public final Integer trackNumber; + field @Nullable public final androidx.media3.common.Rating userRating; + field @Nullable public final CharSequence writer; + } + + public static final class MediaMetadata.Builder { + ctor public MediaMetadata.Builder(); + method public androidx.media3.common.MediaMetadata build(); + method public androidx.media3.common.MediaMetadata.Builder maybeSetArtworkData(byte[], @androidx.media3.common.MediaMetadata.PictureType int); + method public androidx.media3.common.MediaMetadata.Builder setAlbumArtist(@Nullable CharSequence); + method public androidx.media3.common.MediaMetadata.Builder setAlbumTitle(@Nullable CharSequence); + method public androidx.media3.common.MediaMetadata.Builder setArtist(@Nullable CharSequence); + method public androidx.media3.common.MediaMetadata.Builder setArtworkData(@Nullable byte[], @Nullable @androidx.media3.common.MediaMetadata.PictureType Integer); + method public androidx.media3.common.MediaMetadata.Builder setArtworkUri(@Nullable android.net.Uri); + method public androidx.media3.common.MediaMetadata.Builder setCompilation(@Nullable CharSequence); + method public androidx.media3.common.MediaMetadata.Builder setComposer(@Nullable CharSequence); + method public androidx.media3.common.MediaMetadata.Builder setConductor(@Nullable CharSequence); + method public androidx.media3.common.MediaMetadata.Builder setDescription(@Nullable CharSequence); + method public androidx.media3.common.MediaMetadata.Builder setDiscNumber(@Nullable Integer); + method public androidx.media3.common.MediaMetadata.Builder setDisplayTitle(@Nullable CharSequence); + method public androidx.media3.common.MediaMetadata.Builder setExtras(@Nullable android.os.Bundle); + method 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 setIsPlayable(@Nullable Boolean); + 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); + method public androidx.media3.common.MediaMetadata.Builder setRecordingYear(@Nullable Integer); + method public androidx.media3.common.MediaMetadata.Builder setReleaseDay(@IntRange(from=1, to=31) @Nullable Integer); + method public androidx.media3.common.MediaMetadata.Builder setReleaseMonth(@IntRange(from=1, to=12) @Nullable Integer); + method public androidx.media3.common.MediaMetadata.Builder setReleaseYear(@Nullable Integer); + method public androidx.media3.common.MediaMetadata.Builder setStation(@Nullable CharSequence); + method public androidx.media3.common.MediaMetadata.Builder setSubtitle(@Nullable CharSequence); + method public androidx.media3.common.MediaMetadata.Builder setTitle(@Nullable CharSequence); + method public androidx.media3.common.MediaMetadata.Builder setTotalDiscCount(@Nullable Integer); + method public androidx.media3.common.MediaMetadata.Builder setTotalTrackCount(@Nullable Integer); + method public androidx.media3.common.MediaMetadata.Builder setTrackNumber(@Nullable Integer); + method public androidx.media3.common.MediaMetadata.Builder setUserRating(@Nullable androidx.media3.common.Rating); + 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 { + } + + @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 { + } + + public final class MimeTypes { + field public static final String APPLICATION_AIT = "application/vnd.dvb.ait"; + field public static final String APPLICATION_CEA608 = "application/cea-608"; + field public static final String APPLICATION_CEA708 = "application/cea-708"; + field public static final String APPLICATION_DVBSUBS = "application/dvbsubs"; + field public static final String APPLICATION_ID3 = "application/id3"; + field public static final String APPLICATION_M3U8 = "application/x-mpegURL"; + field public static final String APPLICATION_MATROSKA = "application/x-matroska"; + field public static final String APPLICATION_MP4 = "application/mp4"; + field public static final String APPLICATION_MP4CEA608 = "application/x-mp4-cea-608"; + 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 public static final String APPLICATION_RTSP = "application/x-rtsp"; + 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"; + field public static final String APPLICATION_TX3G = "application/x-quicktime-tx3g"; + field public static final String APPLICATION_VOBSUB = "application/vobsub"; + field public static final String APPLICATION_WEBM = "application/webm"; + field public static final String AUDIO_AAC = "audio/mp4a-latm"; + field public static final String AUDIO_AC3 = "audio/ac3"; + field public static final String AUDIO_AC4 = "audio/ac4"; + field public static final String AUDIO_ALAC = "audio/alac"; + field public static final String AUDIO_ALAW = "audio/g711-alaw"; + field public static final String AUDIO_AMR = "audio/amr"; + field public static final String AUDIO_AMR_NB = "audio/3gpp"; + field public static final String AUDIO_AMR_WB = "audio/amr-wb"; + field public static final String AUDIO_DTS = "audio/vnd.dts"; + field public static final String AUDIO_DTS_EXPRESS = "audio/vnd.dts.hd;profile=lbr"; + field public static final String AUDIO_DTS_HD = "audio/vnd.dts.hd"; + field public static final String AUDIO_E_AC3 = "audio/eac3"; + field public static final String AUDIO_E_AC3_JOC = "audio/eac3-joc"; + field public static final String AUDIO_FLAC = "audio/flac"; + field public static final String AUDIO_MIDI = "audio/midi"; + field public static final String AUDIO_MLAW = "audio/g711-mlaw"; + field public static final String AUDIO_MP4 = "audio/mp4"; + field public static final String AUDIO_MPEG = "audio/mpeg"; + field public static final String AUDIO_MPEGH_MHA1 = "audio/mha1"; + field public static final String AUDIO_MPEGH_MHM1 = "audio/mhm1"; + field public static final String AUDIO_MPEG_L1 = "audio/mpeg-L1"; + field public static final String AUDIO_MPEG_L2 = "audio/mpeg-L2"; + field public static final String AUDIO_MSGSM = "audio/gsm"; + field public static final String AUDIO_OGG = "audio/ogg"; + field public static final String AUDIO_OPUS = "audio/opus"; + field public static final String AUDIO_RAW = "audio/raw"; + field public static final String AUDIO_TRUEHD = "audio/true-hd"; + field public static final String AUDIO_VORBIS = "audio/vorbis"; + field public static final String AUDIO_WAV = "audio/wav"; + field public static final String AUDIO_WEBM = "audio/webm"; + field public static final String IMAGE_JPEG = "image/jpeg"; + field public static final String TEXT_SSA = "text/x-ssa"; + field public static final String TEXT_VTT = "text/vtt"; + field public static final String VIDEO_AV1 = "video/av01"; + field public static final String VIDEO_AVI = "video/x-msvideo"; + field public static final String VIDEO_DIVX = "video/divx"; + field public static final String VIDEO_DOLBY_VISION = "video/dolby-vision"; + field public static final String VIDEO_H263 = "video/3gpp"; + field public static final String VIDEO_H264 = "video/avc"; + field public static final String VIDEO_H265 = "video/hevc"; + field public static final String VIDEO_MJPEG = "video/mjpeg"; + field public static final String VIDEO_MP2T = "video/mp2t"; + field public static final String VIDEO_MP4 = "video/mp4"; + field public static final String VIDEO_MP42 = "video/mp42"; + field public static final String VIDEO_MP43 = "video/mp43"; + field public static final String VIDEO_MP4V = "video/mp4v-es"; + field public static final String VIDEO_MPEG = "video/mpeg"; + field public static final String VIDEO_MPEG2 = "video/mpeg2"; + field public static final String VIDEO_OGG = "video/ogg"; + field public static final String VIDEO_PS = "video/mp2p"; + field public static final String VIDEO_VC1 = "video/wvc1"; + field public static final String VIDEO_WEBM = "video/webm"; + } + + public final class PercentageRating extends androidx.media3.common.Rating { + ctor public PercentageRating(); + ctor public PercentageRating(@FloatRange(from=0, to=100) float); + method public float getPercent(); + method public boolean isRated(); + } + + 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(); + 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_WRITE_FAILED = 5002; // 0x138a + field public static final int ERROR_CODE_BEHIND_LIVE_WINDOW = 1002; // 0x3ea + 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_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 + field public static final int ERROR_CODE_DRM_LICENSE_ACQUISITION_FAILED = 6004; // 0x1774 + field public static final int ERROR_CODE_DRM_LICENSE_EXPIRED = 6008; // 0x1778 + field public static final int ERROR_CODE_DRM_PROVISIONING_FAILED = 6002; // 0x1772 + 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_FAILED_RUNTIME_CHECK = 1004; // 0x3ec + 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 + field public static final int ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE = 2003; // 0x7d3 + field public static final int ERROR_CODE_IO_NETWORK_CONNECTION_FAILED = 2001; // 0x7d1 + field public static final int ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT = 2002; // 0x7d2 + 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_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_REMOTE_ERROR = 1001; // 0x3e9 + 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 { + } + + public final class PlaybackParameters { + ctor public PlaybackParameters(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; + field public final float pitch; + field public final float speed; + } + + public interface 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 @IntRange(from=0, to=100) public int getBufferedPercentage(); + method public long getBufferedPosition(); + 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 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.0) 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 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 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(); + 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 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(@FloatRange(from=0, fromInclusive=false) float); + method public void setPlaylistMetadata(androidx.media3.common.MediaMetadata); + 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.0) float); + method public void stop(); + field public static final int COMMAND_ADJUST_DEVICE_VOLUME = 26; // 0x1a + 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 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 + field public static final int COMMAND_GET_VOLUME = 22; // 0x16 + 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_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 + field public static final int COMMAND_SEEK_TO_DEFAULT_POSITION = 4; // 0x4 + field public static final int COMMAND_SEEK_TO_MEDIA_ITEM = 10; // 0xa + field public static final int COMMAND_SEEK_TO_NEXT = 9; // 0x9 + 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_MEDIA_ITEM = 31; // 0x1f + field public static final int COMMAND_SET_MEDIA_ITEMS_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 + field public static final int COMMAND_SET_TRACK_SELECTION_PARAMETERS = 29; // 0x1d + field public static final int COMMAND_SET_VIDEO_SURFACE = 27; // 0x1b + field public static final int COMMAND_SET_VOLUME = 24; // 0x18 + field public static final int COMMAND_STOP = 3; // 0x3 + field public static final int DISCONTINUITY_REASON_AUTO_TRANSITION = 0; // 0x0 + field public static final int DISCONTINUITY_REASON_INTERNAL = 5; // 0x5 + 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_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 + field public static final int EVENT_AVAILABLE_COMMANDS_CHANGED = 13; // 0xd + field public static final int EVENT_CUES = 27; // 0x1b + field public static final int EVENT_DEVICE_INFO_CHANGED = 29; // 0x1d + field public static final int EVENT_DEVICE_VOLUME_CHANGED = 30; // 0x1e + field public static final int EVENT_IS_LOADING_CHANGED = 3; // 0x3 + field public static final int EVENT_IS_PLAYING_CHANGED = 7; // 0x7 + field public static final int EVENT_MAX_SEEK_TO_PREVIOUS_POSITION_CHANGED = 18; // 0x12 + field public static final int EVENT_MEDIA_ITEM_TRANSITION = 1; // 0x1 + field public static final int EVENT_MEDIA_METADATA_CHANGED = 14; // 0xe + field public static final int EVENT_METADATA = 28; // 0x1c + field public static final int EVENT_PLAYBACK_PARAMETERS_CHANGED = 12; // 0xc + field public static final int EVENT_PLAYBACK_STATE_CHANGED = 4; // 0x4 + field public static final int EVENT_PLAYBACK_SUPPRESSION_REASON_CHANGED = 6; // 0x6 + field public static final int EVENT_PLAYER_ERROR = 10; // 0xa + field public static final int EVENT_PLAYLIST_METADATA_CHANGED = 15; // 0xf + field public static final int EVENT_PLAY_WHEN_READY_CHANGED = 5; // 0x5 + field public static final int EVENT_POSITION_DISCONTINUITY = 11; // 0xb + field public static final int EVENT_RENDERED_FIRST_FRAME = 26; // 0x1a + field public static final int EVENT_REPEAT_MODE_CHANGED = 8; // 0x8 + field public static final int EVENT_SEEK_BACK_INCREMENT_CHANGED = 16; // 0x10 + field public static final int EVENT_SEEK_FORWARD_INCREMENT_CHANGED = 17; // 0x11 + field public static final int EVENT_SHUFFLE_MODE_ENABLED_CHANGED = 9; // 0x9 + field public static final int EVENT_SKIP_SILENCE_ENABLED_CHANGED = 23; // 0x17 + field public static final int EVENT_SURFACE_SIZE_CHANGED = 24; // 0x18 + field public static final int EVENT_TIMELINE_CHANGED = 0; // 0x0 + field public static final int EVENT_TRACKS_CHANGED = 2; // 0x2 + field public static final int EVENT_TRACK_SELECTION_PARAMETERS_CHANGED = 19; // 0x13 + field public static final int EVENT_VIDEO_SIZE_CHANGED = 25; // 0x19 + field public static final int EVENT_VOLUME_CHANGED = 22; // 0x16 + field public static final int MEDIA_ITEM_TRANSITION_REASON_AUTO = 1; // 0x1 + field public static final int MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED = 3; // 0x3 + field public static final int MEDIA_ITEM_TRANSITION_REASON_REPEAT = 0; // 0x0 + 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 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_USER_REQUEST = 1; // 0x1 + field public static final int REPEAT_MODE_ALL = 2; // 0x2 + field public static final int REPEAT_MODE_OFF = 0; // 0x0 + field public static final int REPEAT_MODE_ONE = 1; // 0x1 + field public static final int STATE_BUFFERING = 2; // 0x2 + field public static final int STATE_ENDED = 4; // 0x4 + field public static final int STATE_IDLE = 1; // 0x1 + field public static final int STATE_READY = 3; // 0x3 + field public static final int TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED = 0; // 0x0 + 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_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, androidx.media3.common.Player.COMMAND_SET_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.Command { + } + + public static final class Player.Commands { + method public boolean contains(@androidx.media3.common.Player.Command int); + method public boolean containsAny(@androidx.media3.common.Player.Command int...); + method @androidx.media3.common.Player.Command public int get(int); + method public int size(); + 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.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 { + } + + public static final class Player.Events { + method public boolean contains(@androidx.media3.common.Player.Event int); + method public boolean containsAny(@androidx.media3.common.Player.Event int...); + method @androidx.media3.common.Player.Event public int get(int); + method public int size(); + } + + public static interface Player.Listener { + method public default void onAudioAttributesChanged(androidx.media3.common.AudioAttributes); + method public default void onAvailableCommandsChanged(androidx.media3.common.Player.Commands); + method public default void onCues(androidx.media3.common.text.CueGroup); + method public default void onDeviceInfoChanged(androidx.media3.common.DeviceInfo); + method public default void onDeviceVolumeChanged(int, boolean); + method public default void onEvents(androidx.media3.common.Player, androidx.media3.common.Player.Events); + method public default void onIsLoadingChanged(boolean); + method public default void onIsPlayingChanged(boolean); + method public default void onMaxSeekToPreviousPositionChanged(long); + method public default void onMediaItemTransition(@Nullable androidx.media3.common.MediaItem, @androidx.media3.common.Player.MediaItemTransitionReason int); + method public default void onMediaMetadataChanged(androidx.media3.common.MediaMetadata); + method public default void onPlayWhenReadyChanged(boolean, @androidx.media3.common.Player.PlayWhenReadyChangeReason int); + method public default void onPlaybackParametersChanged(androidx.media3.common.PlaybackParameters); + method public default void onPlaybackStateChanged(@androidx.media3.common.Player.State int); + method public default void onPlaybackSuppressionReasonChanged(@androidx.media3.common.Player.PlaybackSuppressionReason int); + method public default void onPlayerError(androidx.media3.common.PlaybackException); + method public default void onPlayerErrorChanged(@Nullable androidx.media3.common.PlaybackException); + method public default void onPlaylistMetadataChanged(androidx.media3.common.MediaMetadata); + method public default void onPositionDiscontinuity(androidx.media3.common.Player.PositionInfo, androidx.media3.common.Player.PositionInfo, @androidx.media3.common.Player.DiscontinuityReason int); + method public default void onRenderedFirstFrame(); + method public default void onRepeatModeChanged(@androidx.media3.common.Player.RepeatMode int); + method public default void onSeekBackIncrementChanged(long); + method public default void onSeekForwardIncrementChanged(long); + method public default void onShuffleModeEnabledChanged(boolean); + method public default void onSkipSilenceEnabledChanged(boolean); + method public default void onSurfaceSizeChanged(int, int); + method public default void onTimelineChanged(androidx.media3.common.Timeline, @androidx.media3.common.Player.TimelineChangeReason int); + method public default void onTrackSelectionParametersChanged(androidx.media3.common.TrackSelectionParameters); + method public default void onTracksChanged(androidx.media3.common.Tracks); + method public default void onVideoSizeChanged(androidx.media3.common.VideoSize); + method public default void onVolumeChanged(float); + } + + @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.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 { + } + + public static final class Player.PositionInfo { + field public final int adGroupIndex; + field public final int adIndexInAdGroup; + field public final long contentPositionMs; + field public final int mediaItemIndex; + field public final int periodIndex; + field @Nullable public final Object periodUid; + field public final long positionMs; + field @Nullable public final Object windowUid; + } + + @IntDef({androidx.media3.common.Player.REPEAT_MODE_OFF, androidx.media3.common.Player.REPEAT_MODE_ONE, androidx.media3.common.Player.REPEAT_MODE_ALL}) @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.RepeatMode { + } + + @IntDef({androidx.media3.common.Player.STATE_IDLE, androidx.media3.common.Player.STATE_BUFFERING, androidx.media3.common.Player.STATE_READY, androidx.media3.common.Player.STATE_ENDED}) @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.State { + } + + @IntDef({androidx.media3.common.Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED, androidx.media3.common.Player.TIMELINE_CHANGE_REASON_SOURCE_UPDATE}) @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.TimelineChangeReason { + } + + public abstract class Rating { + method public abstract boolean isRated(); + } + + public final class StarRating extends androidx.media3.common.Rating { + ctor public StarRating(@IntRange(from=1) int); + ctor public StarRating(@IntRange(from=1) int, @FloatRange(from=0.0) float); + method @IntRange(from=1) public int getMaxStars(); + method public float getStarRating(); + method public boolean isRated(); + } + + public final class ThumbRating extends androidx.media3.common.Rating { + ctor public ThumbRating(); + ctor public ThumbRating(boolean); + method public boolean isRated(); + method public boolean isThumbsUp(); + } + + public abstract class Timeline { + method public int getFirstWindowIndex(boolean); + method public abstract int getIndexOfPeriod(Object); + method public int getLastWindowIndex(boolean); + method public final int getNextPeriodIndex(int, androidx.media3.common.Timeline.Period, androidx.media3.common.Timeline.Window, @androidx.media3.common.Player.RepeatMode int, boolean); + method public int getNextWindowIndex(int, @androidx.media3.common.Player.RepeatMode int, boolean); + method public final androidx.media3.common.Timeline.Period getPeriod(int, androidx.media3.common.Timeline.Period); + method public abstract androidx.media3.common.Timeline.Period getPeriod(int, androidx.media3.common.Timeline.Period, boolean); + method public androidx.media3.common.Timeline.Period getPeriodByUid(Object, androidx.media3.common.Timeline.Period); + method public abstract int getPeriodCount(); + method public final android.util.Pair getPeriodPositionUs(androidx.media3.common.Timeline.Window, androidx.media3.common.Timeline.Period, int, long); + method @Nullable public final android.util.Pair getPeriodPositionUs(androidx.media3.common.Timeline.Window, androidx.media3.common.Timeline.Period, int, long, long); + method public int getPreviousWindowIndex(int, @androidx.media3.common.Player.RepeatMode int, boolean); + method public abstract Object getUidOfPeriod(int); + method public final androidx.media3.common.Timeline.Window getWindow(int, androidx.media3.common.Timeline.Window); + method public abstract androidx.media3.common.Timeline.Window getWindow(int, androidx.media3.common.Timeline.Window, long); + method public abstract int getWindowCount(); + method public final boolean isEmpty(); + method public final boolean isLastPeriod(int, androidx.media3.common.Timeline.Period, androidx.media3.common.Timeline.Window, @androidx.media3.common.Player.RepeatMode int, boolean); + field public static final androidx.media3.common.Timeline EMPTY; + } + + public static final class Timeline.Period { + ctor public Timeline.Period(); + method public int getAdCountInAdGroup(int); + method public long getAdDurationUs(int, int); + method public int getAdGroupCount(); + method public int getAdGroupIndexAfterPositionUs(long); + method public int getAdGroupIndexForPositionUs(long); + method public long getAdGroupTimeUs(int); + method public long getAdResumePositionUs(); + method @Nullable public Object getAdsId(); + method public long getDurationMs(); + method public long getDurationUs(); + method public int getFirstAdIndexToPlay(int); + method public int getNextAdIndexToPlay(int, int); + method public long getPositionInWindowMs(); + method public long getPositionInWindowUs(); + method public int getRemovedAdGroupCount(); + method public boolean hasPlayedAdGroup(int); + field @Nullable public Object id; + field public boolean isPlaceholder; + field @Nullable public Object uid; + field public int windowIndex; + } + + public static final class Timeline.Window { + ctor public Timeline.Window(); + method public long getCurrentUnixTimeMs(); + method public long getDefaultPositionMs(); + method public long getDefaultPositionUs(); + method public long getDurationMs(); + method public long getDurationUs(); + method public long getPositionInFirstPeriodMs(); + method public long getPositionInFirstPeriodUs(); + method public boolean isLive(); + field public static final Object SINGLE_WINDOW_UID; + field public long elapsedRealtimeEpochOffsetMs; + field public int firstPeriodIndex; + field public boolean isDynamic; + field public boolean isPlaceholder; + field public boolean isSeekable; + field public int lastPeriodIndex; + field @Nullable public androidx.media3.common.MediaItem.LiveConfiguration liveConfiguration; + field @Nullable public Object manifest; + field public androidx.media3.common.MediaItem mediaItem; + field public long presentationStartTimeMs; + field public Object uid; + field public long windowStartTimeMs; + } + + public final class TrackGroup { + } + + public final class TrackSelectionOverride { + ctor public TrackSelectionOverride(androidx.media3.common.TrackGroup, int); + ctor public TrackSelectionOverride(androidx.media3.common.TrackGroup, java.util.List); + method @androidx.media3.common.C.TrackType public int getType(); + field public final androidx.media3.common.TrackGroup mediaTrackGroup; + field public final com.google.common.collect.ImmutableList trackIndices; + } + + public class TrackSelectionParameters { + 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(); + field public final com.google.common.collect.ImmutableSet disabledTrackTypes; + field public final boolean forceHighestSupportedBitrate; + field public final boolean forceLowestBitrate; + field @androidx.media3.common.C.SelectionFlags public final int ignoredTextSelectionFlags; + field public final int maxAudioBitrate; + field public final int maxAudioChannelCount; + field public final int maxVideoBitrate; + field public final int maxVideoFrameRate; + field public final int maxVideoHeight; + field public final int maxVideoWidth; + field public final int minVideoBitrate; + field public final int minVideoFrameRate; + field public final int minVideoHeight; + field public final int minVideoWidth; + field public final com.google.common.collect.ImmutableMap overrides; + field public final com.google.common.collect.ImmutableList preferredAudioLanguages; + field public final com.google.common.collect.ImmutableList preferredAudioMimeTypes; + field @androidx.media3.common.C.RoleFlags public final int preferredAudioRoleFlags; + field public final com.google.common.collect.ImmutableList preferredTextLanguages; + field @androidx.media3.common.C.RoleFlags public final int preferredTextRoleFlags; + field public final com.google.common.collect.ImmutableList preferredVideoMimeTypes; + field @androidx.media3.common.C.RoleFlags public final int preferredVideoRoleFlags; + field public final boolean selectUndeterminedTextLanguage; + field public final int viewportHeight; + field public final boolean viewportOrientationMayChange; + field public final int viewportWidth; + } + + public static class TrackSelectionParameters.Builder { + ctor public TrackSelectionParameters.Builder(android.content.Context); + method public androidx.media3.common.TrackSelectionParameters.Builder addOverride(androidx.media3.common.TrackSelectionOverride); + method public androidx.media3.common.TrackSelectionParameters build(); + method public androidx.media3.common.TrackSelectionParameters.Builder clearOverride(androidx.media3.common.TrackGroup); + method public androidx.media3.common.TrackSelectionParameters.Builder clearOverrides(); + method public androidx.media3.common.TrackSelectionParameters.Builder clearOverridesOfType(@androidx.media3.common.C.TrackType int); + method public androidx.media3.common.TrackSelectionParameters.Builder clearVideoSizeConstraints(); + method public androidx.media3.common.TrackSelectionParameters.Builder clearViewportSizeConstraints(); + method public androidx.media3.common.TrackSelectionParameters.Builder setForceHighestSupportedBitrate(boolean); + method public androidx.media3.common.TrackSelectionParameters.Builder setForceLowestBitrate(boolean); + method public androidx.media3.common.TrackSelectionParameters.Builder setIgnoredTextSelectionFlags(@androidx.media3.common.C.SelectionFlags int); + method public androidx.media3.common.TrackSelectionParameters.Builder setMaxAudioBitrate(int); + method public androidx.media3.common.TrackSelectionParameters.Builder setMaxAudioChannelCount(int); + method public androidx.media3.common.TrackSelectionParameters.Builder setMaxVideoBitrate(int); + method public androidx.media3.common.TrackSelectionParameters.Builder setMaxVideoFrameRate(int); + method public androidx.media3.common.TrackSelectionParameters.Builder setMaxVideoSize(int, int); + method public androidx.media3.common.TrackSelectionParameters.Builder setMaxVideoSizeSd(); + method public androidx.media3.common.TrackSelectionParameters.Builder setMinVideoBitrate(int); + method public androidx.media3.common.TrackSelectionParameters.Builder setMinVideoFrameRate(int); + method public androidx.media3.common.TrackSelectionParameters.Builder setMinVideoSize(int, int); + method public androidx.media3.common.TrackSelectionParameters.Builder setOverrideForType(androidx.media3.common.TrackSelectionOverride); + method public androidx.media3.common.TrackSelectionParameters.Builder setPreferredAudioLanguage(@Nullable String); + method public androidx.media3.common.TrackSelectionParameters.Builder setPreferredAudioLanguages(java.lang.String...); + method public androidx.media3.common.TrackSelectionParameters.Builder setPreferredAudioMimeType(@Nullable String); + method public androidx.media3.common.TrackSelectionParameters.Builder setPreferredAudioMimeTypes(java.lang.String...); + method public androidx.media3.common.TrackSelectionParameters.Builder setPreferredAudioRoleFlags(@androidx.media3.common.C.RoleFlags int); + method public androidx.media3.common.TrackSelectionParameters.Builder setPreferredTextLanguage(@Nullable String); + method public androidx.media3.common.TrackSelectionParameters.Builder setPreferredTextLanguageAndRoleFlagsToCaptioningManagerSettings(android.content.Context); + method public androidx.media3.common.TrackSelectionParameters.Builder setPreferredTextLanguages(java.lang.String...); + method public androidx.media3.common.TrackSelectionParameters.Builder setPreferredTextRoleFlags(@androidx.media3.common.C.RoleFlags int); + method public androidx.media3.common.TrackSelectionParameters.Builder setPreferredVideoMimeType(@Nullable String); + method public androidx.media3.common.TrackSelectionParameters.Builder setPreferredVideoMimeTypes(java.lang.String...); + method public androidx.media3.common.TrackSelectionParameters.Builder setPreferredVideoRoleFlags(@androidx.media3.common.C.RoleFlags int); + method public androidx.media3.common.TrackSelectionParameters.Builder setSelectUndeterminedTextLanguage(boolean); + method public androidx.media3.common.TrackSelectionParameters.Builder setTrackTypeDisabled(@androidx.media3.common.C.TrackType int, boolean); + method public androidx.media3.common.TrackSelectionParameters.Builder setViewportSize(int, int, boolean); + method public androidx.media3.common.TrackSelectionParameters.Builder setViewportSizeToPhysicalDisplaySize(android.content.Context, boolean); + } + + public final class Tracks { + method public boolean containsType(@androidx.media3.common.C.TrackType int); + method public com.google.common.collect.ImmutableList getGroups(); + method public boolean isEmpty(); + method public boolean isTypeSelected(@androidx.media3.common.C.TrackType int); + method public boolean isTypeSupported(@androidx.media3.common.C.TrackType int); + method public boolean isTypeSupported(@androidx.media3.common.C.TrackType int, boolean); + field public static final androidx.media3.common.Tracks EMPTY; + } + + public static final class Tracks.Group { + method public androidx.media3.common.TrackGroup getMediaTrackGroup(); + method public androidx.media3.common.Format getTrackFormat(int); + method @androidx.media3.common.C.TrackType public int getType(); + method public boolean isAdaptiveSupported(); + method public boolean isSelected(); + method public boolean isSupported(); + method public boolean isSupported(boolean); + method public boolean isTrackSelected(int); + method public boolean isTrackSupported(int); + method public boolean isTrackSupported(int, boolean); + method public android.os.Bundle toBundle(); + field public final int length; + } + + public final class VideoSize { + 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 @IntRange(from=0) public final int width; + } + +} + +package androidx.media3.common.text { + + public final class Cue { + field public static final int ANCHOR_TYPE_END = 2; // 0x2 + 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 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 + field public static final int TEXT_SIZE_TYPE_FRACTIONAL = 0; // 0x0 + field public static final int TEXT_SIZE_TYPE_FRACTIONAL_IGNORE_PADDING = 1; // 0x1 + field public static final int TYPE_UNSET = -2147483648; // 0x80000000 + field public static final int VERTICAL_TYPE_LR = 2; // 0x2 + field public static final int VERTICAL_TYPE_RL = 1; // 0x1 + field @Nullable public final android.graphics.Bitmap bitmap; + field public final float bitmapHeight; + field public final float line; + field @androidx.media3.common.text.Cue.AnchorType public final int lineAnchor; + field @androidx.media3.common.text.Cue.LineType public final int lineType; + field @Nullable public final android.text.Layout.Alignment multiRowAlignment; + field public final float position; + field @androidx.media3.common.text.Cue.AnchorType public final int positionAnchor; + field public final float shearDegrees; + field public final float size; + field @Nullable public final CharSequence text; + field @Nullable public final android.text.Layout.Alignment textAlignment; + field public final float textSize; + field @androidx.media3.common.text.Cue.TextSizeType public final int textSizeType; + field @androidx.media3.common.text.Cue.VerticalType public final int verticalType; + field public final int windowColor; + field public final boolean windowColorSet; + } + + @IntDef({androidx.media3.common.text.Cue.TYPE_UNSET, androidx.media3.common.text.Cue.ANCHOR_TYPE_START, androidx.media3.common.text.Cue.ANCHOR_TYPE_MIDDLE, androidx.media3.common.text.Cue.ANCHOR_TYPE_END}) @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 Cue.AnchorType { + } + + @IntDef({androidx.media3.common.text.Cue.TYPE_UNSET, androidx.media3.common.text.Cue.LINE_TYPE_FRACTION, androidx.media3.common.text.Cue.LINE_TYPE_NUMBER}) @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 Cue.LineType { + } + + @IntDef({androidx.media3.common.text.Cue.TYPE_UNSET, androidx.media3.common.text.Cue.TEXT_SIZE_TYPE_FRACTIONAL, androidx.media3.common.text.Cue.TEXT_SIZE_TYPE_FRACTIONAL_IGNORE_PADDING, androidx.media3.common.text.Cue.TEXT_SIZE_TYPE_ABSOLUTE}) @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 Cue.TextSizeType { + } + + @IntDef({androidx.media3.common.text.Cue.TYPE_UNSET, androidx.media3.common.text.Cue.VERTICAL_TYPE_RL, androidx.media3.common.text.Cue.VERTICAL_TYPE_LR}) @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 Cue.VerticalType { + } + + public final class CueGroup { + field public final com.google.common.collect.ImmutableList cues; + } + +} + +package androidx.media3.common.util { + + public final class 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 @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...); + } + +} + +package androidx.media3.datasource { + + public interface DataSource { + } + + public static interface DataSource.Factory { + } + + public class DataSourceException extends java.io.IOException { + field @androidx.media3.common.PlaybackException.ErrorCode public final int reason; + } + + public final class DefaultDataSource implements androidx.media3.datasource.DataSource { + } + + public static final class DefaultDataSource.Factory implements androidx.media3.datasource.DataSource.Factory { + ctor public DefaultDataSource.Factory(android.content.Context); + ctor public DefaultDataSource.Factory(android.content.Context, androidx.media3.datasource.DataSource.Factory); + } + + public class DefaultHttpDataSource implements androidx.media3.datasource.DataSource androidx.media3.datasource.HttpDataSource { + } + + public static final class DefaultHttpDataSource.Factory implements androidx.media3.datasource.HttpDataSource.Factory { + ctor public DefaultHttpDataSource.Factory(); + } + + public interface HttpDataSource extends androidx.media3.datasource.DataSource { + } + + public static final class HttpDataSource.CleartextNotPermittedException extends androidx.media3.datasource.HttpDataSource.HttpDataSourceException { + } + + public static interface HttpDataSource.Factory extends androidx.media3.datasource.DataSource.Factory { + } + + public static class HttpDataSource.HttpDataSourceException extends androidx.media3.datasource.DataSourceException { + field public static final int TYPE_CLOSE = 3; // 0x3 + field public static final int TYPE_OPEN = 1; // 0x1 + field public static final int TYPE_READ = 2; // 0x2 + field @androidx.media3.datasource.HttpDataSource.HttpDataSourceException.Type public final int type; + } + + @IntDef({androidx.media3.datasource.HttpDataSource.HttpDataSourceException.TYPE_OPEN, androidx.media3.datasource.HttpDataSource.HttpDataSourceException.TYPE_READ, androidx.media3.datasource.HttpDataSource.HttpDataSourceException.TYPE_CLOSE}) @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 HttpDataSource.HttpDataSourceException.Type { + } + + public static final class HttpDataSource.InvalidContentTypeException extends androidx.media3.datasource.HttpDataSource.HttpDataSourceException { + field public final String contentType; + } + + public static final class HttpDataSource.InvalidResponseCodeException extends androidx.media3.datasource.HttpDataSource.HttpDataSourceException { + field public final byte[] responseBody; + field public final int responseCode; + field @Nullable public final String responseMessage; + } + +} + +package androidx.media3.datasource.cronet { + + public class CronetDataSource implements androidx.media3.datasource.DataSource androidx.media3.datasource.HttpDataSource { + } + + public static final class CronetDataSource.Factory implements androidx.media3.datasource.HttpDataSource.Factory { + ctor public CronetDataSource.Factory(org.chromium.net.CronetEngine, java.util.concurrent.Executor); + } + + public final class CronetUtil { + method @Nullable public static org.chromium.net.CronetEngine buildCronetEngine(android.content.Context); + } + +} + +package androidx.media3.datasource.okhttp { + + public class OkHttpDataSource implements androidx.media3.datasource.DataSource androidx.media3.datasource.HttpDataSource { + } + + public static final class OkHttpDataSource.Factory implements androidx.media3.datasource.HttpDataSource.Factory { + ctor public OkHttpDataSource.Factory(okhttp3.Call.Factory); + } + +} + +package androidx.media3.exoplayer { + + public final class ExoPlaybackException extends androidx.media3.common.PlaybackException { + } + + public interface ExoPlayer extends androidx.media3.common.Player { + 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); + } + + public static final class ExoPlayer.Builder { + ctor public ExoPlayer.Builder(android.content.Context); + method public androidx.media3.exoplayer.ExoPlayer build(); + method public androidx.media3.exoplayer.ExoPlayer.Builder setAudioAttributes(androidx.media3.common.AudioAttributes, boolean); + method public androidx.media3.exoplayer.ExoPlayer.Builder setHandleAudioBecomingNoisy(boolean); + method public androidx.media3.exoplayer.ExoPlayer.Builder setMediaSourceFactory(androidx.media3.exoplayer.source.MediaSource.Factory); + method public androidx.media3.exoplayer.ExoPlayer.Builder setWakeMode(@androidx.media3.common.C.WakeMode int); + } + +} + +package androidx.media3.exoplayer.analytics { + + public interface AnalyticsListener { + } + +} + +package androidx.media3.exoplayer.drm { + + @RequiresApi(18) public final class FrameworkMediaDrm { + method public static boolean isCryptoSchemeSupported(java.util.UUID); + } + +} + +package androidx.media3.exoplayer.ima { + + public final class ImaAdsLoader implements androidx.media3.exoplayer.source.ads.AdsLoader { + method public void release(); + method public void setPlayer(@Nullable androidx.media3.common.Player); + } + + public static final class ImaAdsLoader.Builder { + ctor public ImaAdsLoader.Builder(android.content.Context); + method public androidx.media3.exoplayer.ima.ImaAdsLoader build(); + } + +} + +package androidx.media3.exoplayer.source { + + public final class DefaultMediaSourceFactory implements androidx.media3.exoplayer.source.MediaSource.Factory { + ctor public DefaultMediaSourceFactory(android.content.Context); + 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); + } + + public interface MediaSource { + } + + public static interface MediaSource.Factory { + } + +} + +package androidx.media3.exoplayer.source.ads { + + public interface AdsLoader { + method public void release(); + method public void setPlayer(@Nullable androidx.media3.common.Player); + } + + public static interface AdsLoader.Provider { + method @Nullable public androidx.media3.exoplayer.source.ads.AdsLoader getAdsLoader(androidx.media3.common.MediaItem.AdsConfiguration); + } + +} + +package androidx.media3.exoplayer.util { + + public class DebugTextViewHelper { + ctor public DebugTextViewHelper(androidx.media3.exoplayer.ExoPlayer, android.widget.TextView); + method public final void start(); + method public final void stop(); + } + + public class EventLogger implements androidx.media3.exoplayer.analytics.AnalyticsListener { + ctor public EventLogger(); + ctor public EventLogger(String); + } + +} + +package androidx.media3.session { + + public final class CommandButton { + field public final CharSequence displayName; + field @DrawableRes public final int iconResId; + field public final boolean isEnabled; + field @androidx.media3.common.Player.Command public final int playerCommand; + field @Nullable public final androidx.media3.session.SessionCommand sessionCommand; + } + + public static final class CommandButton.Builder { + ctor public CommandButton.Builder(); + method public androidx.media3.session.CommandButton build(); + method public androidx.media3.session.CommandButton.Builder setDisplayName(CharSequence); + method public androidx.media3.session.CommandButton.Builder setEnabled(boolean); + method public androidx.media3.session.CommandButton.Builder setExtras(android.os.Bundle); + method public androidx.media3.session.CommandButton.Builder setIconResId(@DrawableRes int); + method public androidx.media3.session.CommandButton.Builder setPlayerCommand(@androidx.media3.common.Player.Command int); + method public androidx.media3.session.CommandButton.Builder setSessionCommand(androidx.media3.session.SessionCommand); + } + + public final class LibraryResult { + method public static androidx.media3.session.LibraryResult ofError(@androidx.media3.session.LibraryResult.Code int); + method public static androidx.media3.session.LibraryResult ofError(@androidx.media3.session.LibraryResult.Code int, @Nullable androidx.media3.session.MediaLibraryService.LibraryParams); + method public static androidx.media3.session.LibraryResult ofItem(androidx.media3.common.MediaItem, @Nullable androidx.media3.session.MediaLibraryService.LibraryParams); + method public static androidx.media3.session.LibraryResult> ofItemList(java.util.List, @Nullable androidx.media3.session.MediaLibraryService.LibraryParams); + method public static androidx.media3.session.LibraryResult ofVoid(); + method public static androidx.media3.session.LibraryResult ofVoid(@Nullable androidx.media3.session.MediaLibraryService.LibraryParams); + field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd + field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe + field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb + field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa + field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc + field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a + field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98 + field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c + field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96 + field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97 + field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99 + field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94 + field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95 + field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff + field public static final int RESULT_INFO_SKIPPED = 1; // 0x1 + field public static final int RESULT_SUCCESS = 0; // 0x0 + field public final long completionTimeMs; + field @Nullable public final androidx.media3.session.MediaLibraryService.LibraryParams params; + field @androidx.media3.session.LibraryResult.Code public final int resultCode; + 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 { + } + + public final class MediaBrowser extends androidx.media3.session.MediaController { + method public com.google.common.util.concurrent.ListenableFuture>> getChildren(String, @IntRange(from=0) int, @IntRange(from=1) int, @Nullable androidx.media3.session.MediaLibraryService.LibraryParams); + method public com.google.common.util.concurrent.ListenableFuture> getItem(String); + method public com.google.common.util.concurrent.ListenableFuture> getLibraryRoot(@Nullable androidx.media3.session.MediaLibraryService.LibraryParams); + method public com.google.common.util.concurrent.ListenableFuture>> getSearchResult(String, @IntRange(from=0) int, @IntRange(from=1) int, @Nullable androidx.media3.session.MediaLibraryService.LibraryParams); + method public com.google.common.util.concurrent.ListenableFuture> search(String, @Nullable androidx.media3.session.MediaLibraryService.LibraryParams); + method public com.google.common.util.concurrent.ListenableFuture> subscribe(String, @Nullable androidx.media3.session.MediaLibraryService.LibraryParams); + method public com.google.common.util.concurrent.ListenableFuture> unsubscribe(String); + } + + public static final class MediaBrowser.Builder { + ctor public MediaBrowser.Builder(android.content.Context, androidx.media3.session.SessionToken); + method public com.google.common.util.concurrent.ListenableFuture buildAsync(); + method public androidx.media3.session.MediaBrowser.Builder setApplicationLooper(android.os.Looper); + method public androidx.media3.session.MediaBrowser.Builder setConnectionHints(android.os.Bundle); + method public androidx.media3.session.MediaBrowser.Builder setListener(androidx.media3.session.MediaBrowser.Listener); + } + + public static interface MediaBrowser.Listener extends androidx.media3.session.MediaController.Listener { + method public default void onChildrenChanged(androidx.media3.session.MediaBrowser, String, @IntRange(from=0) int, @Nullable androidx.media3.session.MediaLibraryService.LibraryParams); + method public default void onSearchResultChanged(androidx.media3.session.MediaBrowser, String, @IntRange(from=0) int, @Nullable androidx.media3.session.MediaLibraryService.LibraryParams); + } + + public final class MediaConstants { + field public static final int ERROR_CODE_AUTHENTICATION_EXPIRED_COMPAT = 3; // 0x3 + field public static final String EXTRAS_KEY_ERROR_RESOLUTION_ACTION_INTENT_COMPAT = "android.media.extras.ERROR_RESOLUTION_ACTION_INTENT"; + field public static final String EXTRAS_KEY_ERROR_RESOLUTION_ACTION_LABEL_COMPAT = "android.media.extras.ERROR_RESOLUTION_ACTION_LABEL"; + field public static final String EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_NEXT = "android.media.playback.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_NEXT"; + 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(); + 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(); + } + + public static final class MediaController.Builder { + ctor public MediaController.Builder(android.content.Context, androidx.media3.session.SessionToken); + method public com.google.common.util.concurrent.ListenableFuture buildAsync(); + method public androidx.media3.session.MediaController.Builder setApplicationLooper(android.os.Looper); + method public androidx.media3.session.MediaController.Builder setConnectionHints(android.os.Bundle); + method public androidx.media3.session.MediaController.Builder setListener(androidx.media3.session.MediaController.Listener); + } + + public static interface MediaController.Listener { + method public default void onAvailableSessionCommandsChanged(androidx.media3.session.MediaController, androidx.media3.session.SessionCommands); + method public default com.google.common.util.concurrent.ListenableFuture onCustomCommand(androidx.media3.session.MediaController, androidx.media3.session.SessionCommand, android.os.Bundle); + method public default void onDisconnected(androidx.media3.session.MediaController); + method public default void onExtrasChanged(androidx.media3.session.MediaController, android.os.Bundle); + method public default com.google.common.util.concurrent.ListenableFuture onSetCustomLayout(androidx.media3.session.MediaController, java.util.List); + } + + public abstract class MediaLibraryService extends androidx.media3.session.MediaSessionService { + ctor public MediaLibraryService(); + method @Nullable public abstract androidx.media3.session.MediaLibraryService.MediaLibrarySession onGetSession(androidx.media3.session.MediaSession.ControllerInfo); + field public static final String SERVICE_INTERFACE = "androidx.media3.session.MediaLibraryService"; + } + + public static final class MediaLibraryService.LibraryParams { + field public final boolean isOffline; + field public final boolean isRecent; + field public final boolean isSuggested; + } + + public static final class MediaLibraryService.LibraryParams.Builder { + ctor public MediaLibraryService.LibraryParams.Builder(); + method public androidx.media3.session.MediaLibraryService.LibraryParams build(); + method public androidx.media3.session.MediaLibraryService.LibraryParams.Builder setOffline(boolean); + method public androidx.media3.session.MediaLibraryService.LibraryParams.Builder setRecent(boolean); + method public androidx.media3.session.MediaLibraryService.LibraryParams.Builder setSuggested(boolean); + } + + public static final class MediaLibraryService.MediaLibrarySession extends androidx.media3.session.MediaSession { + method public void notifyChildrenChanged(androidx.media3.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @Nullable androidx.media3.session.MediaLibraryService.LibraryParams); + method public void notifyChildrenChanged(String, @IntRange(from=0) int, @Nullable androidx.media3.session.MediaLibraryService.LibraryParams); + method public void notifySearchResultChanged(androidx.media3.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @Nullable androidx.media3.session.MediaLibraryService.LibraryParams); + } + + public static final class MediaLibraryService.MediaLibrarySession.Builder { + ctor public MediaLibraryService.MediaLibrarySession.Builder(androidx.media3.session.MediaLibraryService, androidx.media3.common.Player, androidx.media3.session.MediaLibraryService.MediaLibrarySession.Callback); + method public androidx.media3.session.MediaLibraryService.MediaLibrarySession build(); + method public androidx.media3.session.MediaLibraryService.MediaLibrarySession.Builder setExtras(android.os.Bundle); + method public androidx.media3.session.MediaLibraryService.MediaLibrarySession.Builder setId(String); + method public androidx.media3.session.MediaLibraryService.MediaLibrarySession.Builder setSessionActivity(android.app.PendingIntent); + } + + public static interface MediaLibraryService.MediaLibrarySession.Callback extends androidx.media3.session.MediaSession.Callback { + method public default com.google.common.util.concurrent.ListenableFuture>> onGetChildren(androidx.media3.session.MediaLibraryService.MediaLibrarySession, androidx.media3.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, @Nullable androidx.media3.session.MediaLibraryService.LibraryParams); + method public default com.google.common.util.concurrent.ListenableFuture> onGetItem(androidx.media3.session.MediaLibraryService.MediaLibrarySession, androidx.media3.session.MediaSession.ControllerInfo, String); + method public default com.google.common.util.concurrent.ListenableFuture> onGetLibraryRoot(androidx.media3.session.MediaLibraryService.MediaLibrarySession, androidx.media3.session.MediaSession.ControllerInfo, @Nullable androidx.media3.session.MediaLibraryService.LibraryParams); + method public default com.google.common.util.concurrent.ListenableFuture>> onGetSearchResult(androidx.media3.session.MediaLibraryService.MediaLibrarySession, androidx.media3.session.MediaSession.ControllerInfo, String, @IntRange(from=0) int, @IntRange(from=1) int, @Nullable androidx.media3.session.MediaLibraryService.LibraryParams); + method public default com.google.common.util.concurrent.ListenableFuture> onSearch(androidx.media3.session.MediaLibraryService.MediaLibrarySession, androidx.media3.session.MediaSession.ControllerInfo, String, @Nullable androidx.media3.session.MediaLibraryService.LibraryParams); + method public default com.google.common.util.concurrent.ListenableFuture> onSubscribe(androidx.media3.session.MediaLibraryService.MediaLibrarySession, androidx.media3.session.MediaSession.ControllerInfo, String, @Nullable androidx.media3.session.MediaLibraryService.LibraryParams); + method public default com.google.common.util.concurrent.ListenableFuture> onUnsubscribe(androidx.media3.session.MediaLibraryService.MediaLibrarySession, androidx.media3.session.MediaSession.ControllerInfo, String); + } + + public final class MediaNotification { + ctor public MediaNotification(@IntRange(from=1) int, android.app.Notification); + field public final android.app.Notification notification; + 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); + } + + public static final class MediaSession.Builder { + ctor public MediaSession.Builder(android.content.Context, androidx.media3.common.Player); + method public androidx.media3.session.MediaSession build(); + method public androidx.media3.session.MediaSession.Builder setCallback(androidx.media3.session.MediaSession.Callback); + method public androidx.media3.session.MediaSession.Builder setExtras(android.os.Bundle); + method public androidx.media3.session.MediaSession.Builder setId(String); + method public androidx.media3.session.MediaSession.Builder setSessionActivity(android.app.PendingIntent); + } + + public static interface MediaSession.Callback { + method public default com.google.common.util.concurrent.ListenableFuture> onAddMediaItems(androidx.media3.session.MediaSession, androidx.media3.session.MediaSession.ControllerInfo, java.util.List); + 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 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); + } + + public static final class MediaSession.ConnectionResult { + method public static androidx.media3.session.MediaSession.ConnectionResult accept(androidx.media3.session.SessionCommands, androidx.media3.common.Player.Commands); + method public static androidx.media3.session.MediaSession.ConnectionResult reject(); + field public final androidx.media3.common.Player.Commands availablePlayerCommands; + field public final androidx.media3.session.SessionCommands availableSessionCommands; + field public final boolean isAccepted; + } + + public static final class MediaSession.ControllerInfo { + method public android.os.Bundle getConnectionHints(); + method public int getControllerVersion(); + method public String getPackageName(); + method public int getUid(); + field public static final int LEGACY_CONTROLLER_VERSION = 0; // 0x0 + } + + public abstract class MediaSessionService extends android.app.Service { + ctor public MediaSessionService(); + method public final void addSession(androidx.media3.session.MediaSession); + method public final java.util.List getSessions(); + 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 public final void removeSession(androidx.media3.session.MediaSession); + field public static final String SERVICE_INTERFACE = "androidx.media3.session.MediaSessionService"; + } + + public final class SessionCommand { + ctor public SessionCommand(@androidx.media3.session.SessionCommand.CommandCode int); + ctor public SessionCommand(String, android.os.Bundle); + field public static final int COMMAND_CODE_CUSTOM = 0; // 0x0 + field public static final int COMMAND_CODE_LIBRARY_GET_CHILDREN = 50003; // 0xc353 + field public static final int COMMAND_CODE_LIBRARY_GET_ITEM = 50004; // 0xc354 + field public static final int COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT = 50000; // 0xc350 + field public static final int COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT = 50006; // 0xc356 + field public static final int COMMAND_CODE_LIBRARY_SEARCH = 50005; // 0xc355 + field public static final int COMMAND_CODE_LIBRARY_SUBSCRIBE = 50001; // 0xc351 + field public static final int COMMAND_CODE_LIBRARY_UNSUBSCRIBE = 50002; // 0xc352 + field public static final int COMMAND_CODE_SESSION_SET_RATING = 40010; // 0x9c4a + field @androidx.media3.session.SessionCommand.CommandCode public final int commandCode; + field public final String customAction; + field public final android.os.Bundle customExtras; + } + + @IntDef({androidx.media3.session.SessionCommand.COMMAND_CODE_CUSTOM, androidx.media3.session.SessionCommand.COMMAND_CODE_SESSION_SET_RATING, androidx.media3.session.SessionCommand.COMMAND_CODE_LIBRARY_GET_LIBRARY_ROOT, androidx.media3.session.SessionCommand.COMMAND_CODE_LIBRARY_SUBSCRIBE, androidx.media3.session.SessionCommand.COMMAND_CODE_LIBRARY_UNSUBSCRIBE, androidx.media3.session.SessionCommand.COMMAND_CODE_LIBRARY_GET_CHILDREN, androidx.media3.session.SessionCommand.COMMAND_CODE_LIBRARY_GET_ITEM, androidx.media3.session.SessionCommand.COMMAND_CODE_LIBRARY_SEARCH, androidx.media3.session.SessionCommand.COMMAND_CODE_LIBRARY_GET_SEARCH_RESULT}) @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 SessionCommand.CommandCode { + } + + public final class SessionCommands { + method public androidx.media3.session.SessionCommands.Builder buildUpon(); + method public boolean contains(androidx.media3.session.SessionCommand); + method public boolean contains(@androidx.media3.session.SessionCommand.CommandCode int); + field public static final androidx.media3.session.SessionCommands EMPTY; + field public final com.google.common.collect.ImmutableSet commands; + } + + public static final class SessionCommands.Builder { + ctor public SessionCommands.Builder(); + method public androidx.media3.session.SessionCommands.Builder add(androidx.media3.session.SessionCommand); + method public androidx.media3.session.SessionCommands.Builder add(@androidx.media3.session.SessionCommand.CommandCode int); + 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); + } + + public final class SessionResult { + ctor public SessionResult(@androidx.media3.session.SessionResult.Code int); + ctor public SessionResult(@androidx.media3.session.SessionResult.Code int, android.os.Bundle); + field public static final int RESULT_ERROR_BAD_VALUE = -3; // 0xfffffffd + field public static final int RESULT_ERROR_INVALID_STATE = -2; // 0xfffffffe + field public static final int RESULT_ERROR_IO = -5; // 0xfffffffb + field public static final int RESULT_ERROR_NOT_SUPPORTED = -6; // 0xfffffffa + field public static final int RESULT_ERROR_PERMISSION_DENIED = -4; // 0xfffffffc + field public static final int RESULT_ERROR_SESSION_AUTHENTICATION_EXPIRED = -102; // 0xffffff9a + field public static final int RESULT_ERROR_SESSION_CONCURRENT_STREAM_LIMIT = -104; // 0xffffff98 + field public static final int RESULT_ERROR_SESSION_DISCONNECTED = -100; // 0xffffff9c + field public static final int RESULT_ERROR_SESSION_NOT_AVAILABLE_IN_REGION = -106; // 0xffffff96 + field public static final int RESULT_ERROR_SESSION_PARENTAL_CONTROL_RESTRICTED = -105; // 0xffffff97 + field public static final int RESULT_ERROR_SESSION_PREMIUM_ACCOUNT_REQUIRED = -103; // 0xffffff99 + field public static final int RESULT_ERROR_SESSION_SETUP_REQUIRED = -108; // 0xffffff94 + field public static final int RESULT_ERROR_SESSION_SKIP_LIMIT_REACHED = -107; // 0xffffff95 + field public static final int RESULT_ERROR_UNKNOWN = -1; // 0xffffffff + field public static final int RESULT_INFO_SKIPPED = 1; // 0x1 + field public static final int RESULT_SUCCESS = 0; // 0x0 + field public final long completionTimeMs; + field public final android.os.Bundle extras; + 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 { + } + + public final class SessionToken { + ctor public SessionToken(android.content.Context, android.content.ComponentName); + method public static com.google.common.collect.ImmutableSet getAllServiceTokens(android.content.Context); + method public android.os.Bundle getExtras(); + method public String getPackageName(); + method public String getServiceName(); + method public int getSessionVersion(); + method @androidx.media3.session.SessionToken.TokenType public int getType(); + method public int getUid(); + field public static final int TYPE_LIBRARY_SERVICE = 2; // 0x2 + field public static final int TYPE_SESSION = 0; // 0x0 + field public static final int TYPE_SESSION_SERVICE = 1; // 0x1 + } + + @IntDef({androidx.media3.session.SessionToken.TYPE_SESSION, androidx.media3.session.SessionToken.TYPE_SESSION_SERVICE, androidx.media3.session.SessionToken.TYPE_LIBRARY_SERVICE}) @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 SessionToken.TokenType { + } + +} + +package androidx.media3.ui { + + public class PlayerView extends android.widget.FrameLayout implements androidx.media3.common.AdViewProvider { + ctor public PlayerView(android.content.Context); + ctor public PlayerView(android.content.Context, @Nullable android.util.AttributeSet); + ctor public PlayerView(android.content.Context, @Nullable android.util.AttributeSet, int); + method public android.view.ViewGroup getAdViewGroup(); + method @Nullable public androidx.media3.common.Player getPlayer(); + method public boolean getUseController(); + method public void onPause(); + method public void onResume(); + method public void setControllerVisibilityListener(@Nullable androidx.media3.ui.PlayerView.ControllerVisibilityListener); + method public void setErrorMessageProvider(@Nullable androidx.media3.common.ErrorMessageProvider); + method public void setFullscreenButtonClickListener(@Nullable androidx.media3.ui.PlayerView.FullscreenButtonClickListener); + method public void setPlayer(@Nullable androidx.media3.common.Player); + method public void setUseController(boolean); + } + + public static interface PlayerView.ControllerVisibilityListener { + method public void onVisibilityChanged(int); + } + + public static interface PlayerView.FullscreenButtonClickListener { + method public void onFullscreenButtonClick(boolean); + } + +} + diff --git a/build.gradle b/build.gradle index aafc0e790c..0c15bce9e5 100644 --- a/build.gradle +++ b/build.gradle @@ -17,7 +17,7 @@ buildscript { mavenCentral() } dependencies { - classpath 'com.android.tools.build:gradle:7.0.3' + 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' } diff --git a/common_library_config.gradle b/common_library_config.gradle index 5ac9e337f6..9d14a1f601 100644 --- a/common_library_config.gradle +++ b/common_library_config.gradle @@ -29,5 +29,10 @@ android { targetCompatibility JavaVersion.VERSION_1_8 } - testOptions.unitTests.includeAndroidResources = true + testOptions { + unitTests.all { + jvmArgs "-Xmx2g" + } + unitTests.includeAndroidResources true + } } diff --git a/constants.gradle b/constants.gradle index ba974fd8e9..86c624e778 100644 --- a/constants.gradle +++ b/constants.gradle @@ -12,21 +12,21 @@ // See the License for the specific language governing permissions and // limitations under the License. project.ext { - releaseVersion = '1.0.0-alpha03' - releaseVersionCode = 1_000_000_0_03 + releaseVersion = '1.0.0-beta01' + releaseVersionCode = 1_000_000_1_01 minSdkVersion = 16 appTargetSdkVersion = 29 // Upgrading this requires [Internal ref: b/193254928] to be fixed, or some // additional robolectric config. targetSdkVersion = 30 - compileSdkVersion = 31 + compileSdkVersion = 32 dexmakerVersion = '2.28.1' 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' mockitoVersion = '3.12.4' - robolectricVersion = '4.6.1' + robolectricVersion = '4.8.1' // Keep this in sync with Google's internal Checker Framework version. checkerframeworkVersion = '3.13.0' checkerframeworkCompatVersion = '2.5.5' diff --git a/core_settings.gradle b/core_settings.gradle index d8760a1709..baca421753 100644 --- a/core_settings.gradle +++ b/core_settings.gradle @@ -87,3 +87,7 @@ include modulePrefix + 'test-data' project(modulePrefix + 'test-data').projectDir = new File(rootDir, 'libraries/test_data') include modulePrefix + 'test-utils' project(modulePrefix + 'test-utils').projectDir = new File(rootDir, 'libraries/test_utils') +include modulePrefix + 'test-session-common' +project(modulePrefix + 'test-session-common').projectDir = new File(rootDir, 'libraries/test_session_common') +include modulePrefix + 'test-session-current' +project(modulePrefix + 'test-session-current').projectDir = new File(rootDir, 'libraries/test_session_current') 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 d6aec8c4a1..aa9bd9f08e 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 @@ -230,8 +230,8 @@ public class MainActivity extends AppCompatActivity @Override public boolean onMove( RecyclerView list, RecyclerView.ViewHolder origin, RecyclerView.ViewHolder target) { - int fromPosition = origin.getAdapterPosition(); - int toPosition = target.getAdapterPosition(); + int fromPosition = origin.getBindingAdapterPosition(); + int toPosition = target.getBindingAdapterPosition(); if (draggingFromPosition == C.INDEX_UNSET) { // A drag has started, but changes to the media queue will be reflected in clearView(). draggingFromPosition = fromPosition; @@ -243,7 +243,7 @@ public class MainActivity extends AppCompatActivity @Override public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) { - int position = viewHolder.getAdapterPosition(); + int position = viewHolder.getBindingAdapterPosition(); QueueItemViewHolder queueItemHolder = (QueueItemViewHolder) viewHolder; if (playerManager.removeItem(queueItemHolder.item)) { mediaQueueListAdapter.notifyItemRemoved(position); @@ -282,7 +282,7 @@ public class MainActivity extends AppCompatActivity @Override public void onClick(View v) { - playerManager.selectQueueItem(getAdapterPosition()); + playerManager.selectQueueItem(getBindingAdapterPosition()); } } diff --git a/demos/cast/src/main/java/androidx/media3/demo/cast/PlayerManager.java b/demos/cast/src/main/java/androidx/media3/demo/cast/PlayerManager.java index 89910104f6..db29871600 100644 --- a/demos/cast/src/main/java/androidx/media3/demo/cast/PlayerManager.java +++ b/demos/cast/src/main/java/androidx/media3/demo/cast/PlayerManager.java @@ -26,7 +26,7 @@ import androidx.media3.common.Player; import androidx.media3.common.Player.DiscontinuityReason; import androidx.media3.common.Player.TimelineChangeReason; import androidx.media3.common.Timeline; -import androidx.media3.common.TracksInfo; +import androidx.media3.common.Tracks; import androidx.media3.exoplayer.ExoPlayer; import androidx.media3.ui.PlayerControlView; import androidx.media3.ui.PlayerView; @@ -57,7 +57,7 @@ import java.util.ArrayList; private final ArrayList mediaQueue; private final Listener listener; - private TracksInfo lastSeenTrackGroupInfo; + private Tracks lastSeenTracks; private int currentItemIndex; private Player currentPlayer; @@ -219,19 +219,19 @@ import java.util.ArrayList; } @Override - public void onTracksInfoChanged(TracksInfo tracksInfo) { - if (currentPlayer != localPlayer || tracksInfo == lastSeenTrackGroupInfo) { + public void onTracksChanged(Tracks tracks) { + if (currentPlayer != localPlayer || tracks == lastSeenTracks) { return; } - if (!tracksInfo.isTypeSupportedOrEmpty( - C.TRACK_TYPE_VIDEO, /* allowExceedsCapabilities= */ true)) { + if (tracks.containsType(C.TRACK_TYPE_VIDEO) + && !tracks.isTypeSupported(C.TRACK_TYPE_VIDEO, /* allowExceedsCapabilities= */ true)) { listener.onUnsupportedTrack(C.TRACK_TYPE_VIDEO); } - if (!tracksInfo.isTypeSupportedOrEmpty( - C.TRACK_TYPE_AUDIO, /* allowExceedsCapabilities= */ true)) { + if (tracks.containsType(C.TRACK_TYPE_AUDIO) + && !tracks.isTypeSupported(C.TRACK_TYPE_AUDIO, /* allowExceedsCapabilities= */ true)) { listener.onUnsupportedTrack(C.TRACK_TYPE_AUDIO); } - lastSeenTrackGroupInfo = tracksInfo; + lastSeenTracks = tracks; } // CastPlayer.SessionAvailabilityListener implementation. 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 cf3945be12..735abf4cfa 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 @@ -27,6 +27,7 @@ import android.graphics.drawable.BitmapDrawable; import android.opengl.GLES20; import android.opengl.GLUtils; import androidx.media3.common.C; +import androidx.media3.common.util.GlProgram; import androidx.media3.common.util.GlUtil; import java.io.IOException; import java.util.Locale; @@ -50,7 +51,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; private final Bitmap logoBitmap; private final Canvas overlayCanvas; - private GlUtil.@MonotonicNonNull Program program; + private @MonotonicNonNull GlProgram program; private float bitmapScaleX; private float bitmapScaleY; @@ -78,7 +79,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; public void initialize() { try { program = - new GlUtil.Program( + new GlProgram( context, /* vertexShaderFilePath= */ "bitmap_overlay_video_processor_vertex.glsl", /* fragmentShaderFilePath= */ "bitmap_overlay_video_processor_fragment.glsl"); @@ -86,9 +87,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; throw new IllegalStateException(e); } program.setBufferAttribute( - "aFramePosition", GlUtil.getNormalizedCoordinateBounds(), GlUtil.RECTANGLE_VERTICES_COUNT); + "aFramePosition", + GlUtil.getNormalizedCoordinateBounds(), + GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE); program.setBufferAttribute( - "aTexCoords", GlUtil.getTextureCoordinateBounds(), GlUtil.RECTANGLE_VERTICES_COUNT); + "aTexCoords", + GlUtil.getTextureCoordinateBounds(), + GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE); GLES20.glGenTextures(1, textures, 0); GLES20.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]); GLES20.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST); @@ -117,9 +122,9 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; GlUtil.checkGlError(); // Run the shader program. - GlUtil.Program program = checkNotNull(this.program); - program.setSamplerTexIdUniform("uTexSampler0", frameTexture, /* unit= */ 0); - program.setSamplerTexIdUniform("uTexSampler1", textures[0], /* unit= */ 1); + GlProgram program = checkNotNull(this.program); + program.setSamplerTexIdUniform("uTexSampler0", frameTexture, /* texUnitIndex= */ 0); + program.setSamplerTexIdUniform("uTexSampler1", textures[0], /* texUnitIndex= */ 1); program.setFloatUniform("uScaleX", bitmapScaleX); program.setFloatUniform("uScaleY", bitmapScaleY); program.setFloatsUniform("uTexTransform", transformMatrix); 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 bc26c037e2..4923b4a390 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 @@ -20,6 +20,7 @@ import android.content.Context; import android.content.Intent; import android.net.Uri; import android.os.Bundle; +import android.text.TextUtils; import android.widget.FrameLayout; import android.widget.Toast; import androidx.annotation.Nullable; @@ -32,7 +33,6 @@ import androidx.media3.common.util.Util; import androidx.media3.datasource.DataSource; import androidx.media3.datasource.DefaultDataSource; import androidx.media3.datasource.DefaultHttpDataSource; -import androidx.media3.datasource.HttpDataSource; import androidx.media3.exoplayer.ExoPlayer; import androidx.media3.exoplayer.dash.DashMediaSource; import androidx.media3.exoplayer.drm.DefaultDrmSessionManager; @@ -144,7 +144,7 @@ public final class MainActivity extends Activity { 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)); - HttpDataSource.Factory licenseDataSourceFactory = new DefaultHttpDataSource.Factory(); + DataSource.Factory licenseDataSourceFactory = new DefaultHttpDataSource.Factory(); HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback(drmLicenseUrl, licenseDataSourceFactory); drmSessionManager = @@ -157,13 +157,18 @@ public final class MainActivity extends Activity { DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(this); MediaSource mediaSource; - @C.ContentType int type = Util.inferContentType(uri, intent.getStringExtra(EXTENSION_EXTRA)); - if (type == C.TYPE_DASH) { + @Nullable String fileExtension = intent.getStringExtra(EXTENSION_EXTRA); + @C.ContentType + int type = + TextUtils.isEmpty(fileExtension) + ? Util.inferContentType(uri) + : Util.inferContentTypeForExtension(fileExtension); + if (type == C.CONTENT_TYPE_DASH) { mediaSource = new DashMediaSource.Factory(dataSourceFactory) .setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager) .createMediaSource(MediaItem.fromUri(uri)); - } else if (type == C.TYPE_OTHER) { + } else if (type == C.CONTENT_TYPE_OTHER) { mediaSource = new ProgressiveMediaSource.Factory(dataSourceFactory) .setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager) @@ -181,7 +186,7 @@ public final class MainActivity extends Activity { Assertions.checkNotNull(this.videoProcessingGLSurfaceView); videoProcessingGLSurfaceView.setPlayer(player); Assertions.checkNotNull(playerView).setPlayer(player); - player.addAnalyticsListener(new EventLogger(/* trackSelector= */ null)); + player.addAnalyticsListener(new EventLogger()); this.player = player; } 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 21078d8545..c14bfc4ba9 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 @@ -20,6 +20,7 @@ import static androidx.media3.demo.main.DemoUtil.DOWNLOAD_NOTIFICATION_CHANNEL_I import android.app.Notification; import android.content.Context; import androidx.annotation.Nullable; +import androidx.annotation.OptIn; import androidx.media3.common.util.NotificationUtil; import androidx.media3.common.util.Util; import androidx.media3.exoplayer.offline.Download; @@ -32,6 +33,7 @@ import androidx.media3.exoplayer.scheduler.Scheduler; import java.util.List; /** A service for downloading media. */ +@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class) public class DemoDownloadService extends DownloadService { private static final int JOB_ID = 1; 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 2e3424697a..2a5a4bfbbe 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,12 @@ package androidx.media3.demo.main; import android.content.Context; +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.HttpDataSource; import androidx.media3.datasource.cache.Cache; import androidx.media3.datasource.cache.CacheDataSource; import androidx.media3.datasource.cache.NoOpCacheEvictor; @@ -59,7 +59,7 @@ public final class DemoUtil { private static final String DOWNLOAD_CONTENT_DIRECTORY = "downloads"; private static DataSource.@MonotonicNonNull Factory dataSourceFactory; - private static HttpDataSource.@MonotonicNonNull Factory httpDataSourceFactory; + private static DataSource.@MonotonicNonNull Factory httpDataSourceFactory; private static @MonotonicNonNull DatabaseProvider databaseProvider; private static @MonotonicNonNull File downloadDirectory; private static @MonotonicNonNull Cache downloadCache; @@ -72,6 +72,7 @@ public final class DemoUtil { return BuildConfig.USE_DECODER_EXTENSIONS; } + @OptIn(markerClass = androidx.media3.common.util.UnstableApi.class) public static RenderersFactory buildRenderersFactory( Context context, boolean preferExtensionRenderer) { @DefaultRenderersFactory.ExtensionRendererMode @@ -85,7 +86,7 @@ public final class DemoUtil { .setExtensionRendererMode(extensionRendererMode); } - public static synchronized HttpDataSource.Factory getHttpDataSourceFactory(Context context) { + public static synchronized DataSource.Factory getHttpDataSourceFactory(Context context) { if (httpDataSourceFactory == null) { if (USE_CRONET_FOR_NETWORKING) { context = context.getApplicationContext(); @@ -117,6 +118,7 @@ public final class DemoUtil { return dataSourceFactory; } + @OptIn(markerClass = androidx.media3.common.util.UnstableApi.class) public static synchronized DownloadNotificationHelper getDownloadNotificationHelper( Context context) { if (downloadNotificationHelper == null) { @@ -136,6 +138,7 @@ public final class DemoUtil { return downloadTracker; } + @OptIn(markerClass = androidx.media3.common.util.UnstableApi.class) private static synchronized Cache getDownloadCache(Context context) { if (downloadCache == null) { File downloadContentDirectory = @@ -147,6 +150,7 @@ public final class DemoUtil { return downloadCache; } + @OptIn(markerClass = androidx.media3.common.util.UnstableApi.class) private static synchronized void ensureDownloadManagerInitialized(Context context) { if (downloadManager == null) { downloadManager = @@ -161,6 +165,7 @@ public final class DemoUtil { } } + @OptIn(markerClass = androidx.media3.common.util.UnstableApi.class) private static synchronized DatabaseProvider getDatabaseProvider(Context context) { if (databaseProvider == null) { databaseProvider = new StandaloneDatabaseProvider(context); @@ -178,6 +183,7 @@ public final class DemoUtil { return downloadDirectory; } + @OptIn(markerClass = androidx.media3.common.util.UnstableApi.class) private static CacheDataSource.Factory buildReadOnlyCacheDataSource( DataSource.Factory upstreamFactory, Cache cache) { return new CacheDataSource.Factory() 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 a27991519c..089644cbc9 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 @@ -15,8 +15,7 @@ */ package androidx.media3.demo.main; -import static androidx.media3.common.util.Assertions.checkNotNull; -import static androidx.media3.common.util.Assertions.checkStateNotNull; +import static com.google.common.base.Preconditions.checkNotNull; import android.content.Context; import android.content.DialogInterface; @@ -24,16 +23,18 @@ import android.net.Uri; import android.os.AsyncTask; import android.widget.Toast; import androidx.annotation.Nullable; +import androidx.annotation.OptIn; import androidx.annotation.RequiresApi; import androidx.fragment.app.FragmentManager; import androidx.media3.common.DrmInitData; import androidx.media3.common.Format; import androidx.media3.common.MediaItem; import androidx.media3.common.TrackGroup; -import androidx.media3.common.TrackGroupArray; +import androidx.media3.common.TrackSelectionParameters; +import androidx.media3.common.Tracks; import androidx.media3.common.util.Log; import androidx.media3.common.util.Util; -import androidx.media3.datasource.HttpDataSource; +import androidx.media3.datasource.DataSource; import androidx.media3.exoplayer.RenderersFactory; import androidx.media3.exoplayer.drm.DrmSession; import androidx.media3.exoplayer.drm.DrmSessionEventListener; @@ -46,13 +47,14 @@ import androidx.media3.exoplayer.offline.DownloadIndex; import androidx.media3.exoplayer.offline.DownloadManager; import androidx.media3.exoplayer.offline.DownloadRequest; import androidx.media3.exoplayer.offline.DownloadService; -import androidx.media3.exoplayer.trackselection.DefaultTrackSelector; +import androidx.media3.exoplayer.source.TrackGroupArray; import androidx.media3.exoplayer.trackselection.MappingTrackSelector.MappedTrackInfo; import java.io.IOException; import java.util.HashMap; import java.util.concurrent.CopyOnWriteArraySet; /** Tracks media that has been downloaded. */ +@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class) public class DownloadTracker { /** Listens for changes in the tracked downloads. */ @@ -65,31 +67,26 @@ public class DownloadTracker { private static final String TAG = "DownloadTracker"; private final Context context; - private final HttpDataSource.Factory httpDataSourceFactory; + private final DataSource.Factory dataSourceFactory; private final CopyOnWriteArraySet listeners; private final HashMap downloads; private final DownloadIndex downloadIndex; - private final DefaultTrackSelector.Parameters trackSelectorParameters; @Nullable private StartDownloadDialogHelper startDownloadDialogHelper; public DownloadTracker( - Context context, - HttpDataSource.Factory httpDataSourceFactory, - DownloadManager downloadManager) { + Context context, DataSource.Factory dataSourceFactory, DownloadManager downloadManager) { this.context = context.getApplicationContext(); - this.httpDataSourceFactory = httpDataSourceFactory; + this.dataSourceFactory = dataSourceFactory; listeners = new CopyOnWriteArraySet<>(); downloads = new HashMap<>(); downloadIndex = downloadManager.getDownloadIndex(); - trackSelectorParameters = DownloadHelper.getDefaultTrackSelectorParameters(context); downloadManager.addListener(new DownloadManagerListener()); loadDownloads(); } public void addListener(Listener listener) { - checkNotNull(listener); - listeners.add(listener); + listeners.add(checkNotNull(listener)); } public void removeListener(Listener listener) { @@ -120,8 +117,7 @@ public class DownloadTracker { startDownloadDialogHelper = new StartDownloadDialogHelper( fragmentManager, - DownloadHelper.forMediaItem( - context, mediaItem, renderersFactory, httpDataSourceFactory), + DownloadHelper.forMediaItem(context, mediaItem, renderersFactory, dataSourceFactory), mediaItem); } } @@ -159,7 +155,7 @@ public class DownloadTracker { private final class StartDownloadDialogHelper implements DownloadHelper.Callback, - DialogInterface.OnClickListener, + TrackSelectionDialog.TrackSelectionListener, DialogInterface.OnDismissListener { private final FragmentManager fragmentManager; @@ -167,7 +163,6 @@ public class DownloadTracker { private final MediaItem mediaItem; private TrackSelectionDialog trackSelectionDialog; - private MappedTrackInfo mappedTrackInfo; private WidevineOfflineLicenseFetchTask widevineOfflineLicenseFetchTask; @Nullable private byte[] keySetId; @@ -220,7 +215,7 @@ public class DownloadTracker { new WidevineOfflineLicenseFetchTask( format, mediaItem.localConfiguration.drmConfiguration, - httpDataSourceFactory, + dataSourceFactory, /* dialogHelper= */ this, helper); widevineOfflineLicenseFetchTask.execute(); @@ -237,21 +232,13 @@ public class DownloadTracker { Log.e(TAG, logMessage, e); } - // DialogInterface.OnClickListener implementation. + // TrackSelectionListener implementation. @Override - public void onClick(DialogInterface dialog, int which) { + public void onTracksSelected(TrackSelectionParameters trackSelectionParameters) { for (int periodIndex = 0; periodIndex < downloadHelper.getPeriodCount(); periodIndex++) { downloadHelper.clearTrackSelections(periodIndex); - for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) { - if (!trackSelectionDialog.getIsDisabled(/* rendererIndex= */ i)) { - downloadHelper.addTrackSelectionForSingleRenderer( - periodIndex, - /* rendererIndex= */ i, - trackSelectorParameters, - trackSelectionDialog.getOverrides(/* rendererIndex= */ i)); - } - } + downloadHelper.addTrackSelection(periodIndex, trackSelectionParameters); } DownloadRequest downloadRequest = buildDownloadRequest(); if (downloadRequest.streamKeys.isEmpty()) { @@ -316,21 +303,21 @@ public class DownloadTracker { return; } - mappedTrackInfo = downloadHelper.getMappedTrackInfo(/* periodIndex= */ 0); - if (!TrackSelectionDialog.willHaveContent(mappedTrackInfo)) { + Tracks tracks = downloadHelper.getTracks(/* periodIndex= */ 0); + if (!TrackSelectionDialog.willHaveContent(tracks)) { Log.d(TAG, "No dialog content. Downloading entire stream."); startDownload(); downloadHelper.release(); return; } trackSelectionDialog = - TrackSelectionDialog.createForMappedTrackInfoAndParameters( + TrackSelectionDialog.createForTracksAndParameters( /* titleId= */ R.string.exo_download_description, - mappedTrackInfo, - trackSelectorParameters, + tracks, + DownloadHelper.getDefaultTrackSelectorParameters(context), /* allowAdaptiveSelections= */ false, /* allowMultipleOverrides= */ true, - /* onClickListener= */ this, + /* onTracksSelectedListener= */ this, /* onDismissListener= */ this); trackSelectionDialog.show(fragmentManager, /* tag= */ null); } @@ -371,7 +358,7 @@ public class DownloadTracker { private final Format format; private final MediaItem.DrmConfiguration drmConfiguration; - private final HttpDataSource.Factory httpDataSourceFactory; + private final DataSource.Factory dataSourceFactory; private final StartDownloadDialogHelper dialogHelper; private final DownloadHelper downloadHelper; @@ -381,12 +368,12 @@ public class DownloadTracker { public WidevineOfflineLicenseFetchTask( Format format, MediaItem.DrmConfiguration drmConfiguration, - HttpDataSource.Factory httpDataSourceFactory, + DataSource.Factory dataSourceFactory, StartDownloadDialogHelper dialogHelper, DownloadHelper downloadHelper) { this.format = format; this.drmConfiguration = drmConfiguration; - this.httpDataSourceFactory = httpDataSourceFactory; + this.dataSourceFactory = dataSourceFactory; this.dialogHelper = dialogHelper; this.downloadHelper = downloadHelper; } @@ -397,7 +384,7 @@ public class DownloadTracker { OfflineLicenseHelper.newWidevineInstance( drmConfiguration.licenseUri.toString(), drmConfiguration.forceDefaultLicenseUri, - httpDataSourceFactory, + dataSourceFactory, drmConfiguration.licenseRequestHeaders, new DrmSessionEventListener.EventDispatcher()); try { @@ -415,7 +402,7 @@ public class DownloadTracker { if (drmSessionException != null) { dialogHelper.onOfflineLicenseFetchedError(drmSessionException); } else { - dialogHelper.onOfflineLicenseFetched(downloadHelper, checkStateNotNull(keySetId)); + 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 911dac1ff5..f8023cbe63 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 @@ -15,8 +15,9 @@ */ package androidx.media3.demo.main; -import static androidx.media3.common.util.Assertions.checkNotNull; -import static androidx.media3.common.util.Assertions.checkState; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; import android.content.Intent; import android.net.Uri; @@ -26,7 +27,6 @@ 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.util.Assertions; import androidx.media3.common.util.Util; import com.google.common.collect.ImmutableList; import java.util.ArrayList; @@ -86,7 +86,7 @@ public class IntentUtil { /** Populates the intent with the given list of {@link MediaItem media items}. */ public static void addToIntent(List mediaItems, Intent intent) { - Assertions.checkArgument(!mediaItems.isEmpty()); + checkArgument(!mediaItems.isEmpty()); if (mediaItems.size() == 1) { MediaItem mediaItem = mediaItems.get(0); MediaItem.LocalConfiguration localConfiguration = checkNotNull(mediaItem.localConfiguration); @@ -177,7 +177,7 @@ public class IntentUtil { headers.put(keyRequestPropertiesArray[i], keyRequestPropertiesArray[i + 1]); } } - @Nullable UUID drmUuid = Util.getDrmUuid(Util.castNonNull(drmSchemeExtra)); + @Nullable UUID drmUuid = Util.getDrmUuid(drmSchemeExtra); if (drmUuid != null) { builder.setDrmConfiguration( new MediaItem.DrmConfiguration.Builder(drmUuid) @@ -188,7 +188,7 @@ public class IntentUtil { intent.getBooleanExtra( DRM_FORCE_DEFAULT_LICENSE_URI_EXTRA + extrasKeySuffix, false)) .setLicenseRequestHeaders(headers) - .forceSessionsForAudioAndVideoTracks( + .setForceSessionsForAudioAndVideoTracks( intent.getBooleanExtra(DRM_SESSION_FOR_CLEAR_CONTENT + extrasKeySuffix, false)) .build()); } @@ -241,7 +241,7 @@ public class IntentUtil { drmConfiguration.forcedSessionTrackTypes; if (!forcedDrmSessionTrackTypes.isEmpty()) { // Only video and audio together are supported. - Assertions.checkState( + checkState( forcedDrmSessionTrackTypes.size() == 2 && forcedDrmSessionTrackTypes.contains(C.TRACK_TYPE_VIDEO) && forcedDrmSessionTrackTypes.contains(C.TRACK_TYPE_AUDIO)); 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 83ff915110..22ee82e179 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 @@ -17,6 +17,7 @@ package androidx.media3.demo.main; import android.content.Intent; import android.content.pm.PackageManager; +import android.os.Build; import android.os.Bundle; import android.util.Pair; import android.view.KeyEvent; @@ -27,6 +28,7 @@ import android.widget.LinearLayout; import android.widget.TextView; import android.widget.Toast; import androidx.annotation.Nullable; +import androidx.annotation.OptIn; import androidx.appcompat.app.AppCompatActivity; import androidx.media3.common.AudioAttributes; import androidx.media3.common.C; @@ -34,11 +36,14 @@ import androidx.media3.common.ErrorMessageProvider; import androidx.media3.common.MediaItem; import androidx.media3.common.PlaybackException; import androidx.media3.common.Player; -import androidx.media3.common.TracksInfo; +import androidx.media3.common.TrackSelectionParameters; +import androidx.media3.common.Tracks; +import androidx.media3.common.util.UnstableApi; import androidx.media3.common.util.Util; import androidx.media3.datasource.DataSource; import androidx.media3.exoplayer.ExoPlayer; import androidx.media3.exoplayer.RenderersFactory; +import androidx.media3.exoplayer.drm.DefaultDrmSessionManagerProvider; import androidx.media3.exoplayer.drm.FrameworkMediaDrm; import androidx.media3.exoplayer.ima.ImaAdsLoader; import androidx.media3.exoplayer.ima.ImaServerSideAdInsertionMediaSource; @@ -48,10 +53,8 @@ import androidx.media3.exoplayer.offline.DownloadRequest; import androidx.media3.exoplayer.source.DefaultMediaSourceFactory; import androidx.media3.exoplayer.source.MediaSource; import androidx.media3.exoplayer.source.ads.AdsLoader; -import androidx.media3.exoplayer.trackselection.DefaultTrackSelector; import androidx.media3.exoplayer.util.DebugTextViewHelper; import androidx.media3.exoplayer.util.EventLogger; -import androidx.media3.ui.PlayerControlView; import androidx.media3.ui.PlayerView; import java.util.ArrayList; import java.util.Collections; @@ -60,7 +63,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull; /** An activity that plays media using {@link ExoPlayer}. */ public class PlayerActivity extends AppCompatActivity - implements OnClickListener, PlayerControlView.VisibilityListener { + implements OnClickListener, PlayerView.ControllerVisibilityListener { // Saved instance state keys. @@ -79,10 +82,9 @@ public class PlayerActivity extends AppCompatActivity private Button selectTracksButton; private DataSource.Factory dataSourceFactory; private List mediaItems; - private DefaultTrackSelector trackSelector; - private DefaultTrackSelector.Parameters trackSelectionParameters; + private TrackSelectionParameters trackSelectionParameters; private DebugTextViewHelper debugViewHelper; - private TracksInfo lastSeenTracksInfo; + private Tracks lastSeenTracks; private boolean startAutoPlay; private int startItemIndex; private long startPosition; @@ -90,7 +92,12 @@ public class PlayerActivity extends AppCompatActivity // For ad playback only. @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 serverSideAdsLoaderState; @@ -113,22 +120,15 @@ public class PlayerActivity extends AppCompatActivity playerView.requestFocus(); if (savedInstanceState != null) { - // Restore as DefaultTrackSelector.Parameters in case ExoPlayer specific parameters were set. trackSelectionParameters = - DefaultTrackSelector.Parameters.CREATOR.fromBundle( + TrackSelectionParameters.fromBundle( savedInstanceState.getBundle(KEY_TRACK_SELECTION_PARAMETERS)); startAutoPlay = savedInstanceState.getBoolean(KEY_AUTO_PLAY); startItemIndex = savedInstanceState.getInt(KEY_ITEM_INDEX); startPosition = savedInstanceState.getLong(KEY_POSITION); - Bundle adsLoaderStateBundle = savedInstanceState.getBundle(KEY_SERVER_SIDE_ADS_LOADER_STATE); - if (adsLoaderStateBundle != null) { - serverSideAdsLoaderState = - ImaServerSideAdInsertionMediaSource.AdsLoader.State.CREATOR.fromBundle( - adsLoaderStateBundle); - } + restoreServerSideAdsLoaderState(savedInstanceState); } else { - trackSelectionParameters = - new DefaultTrackSelector.ParametersBuilder(/* context= */ this).build(); + trackSelectionParameters = new TrackSelectionParameters.Builder(/* context= */ this).build(); clearStartPosition(); } } @@ -145,7 +145,7 @@ public class PlayerActivity extends AppCompatActivity @Override public void onStart() { super.onStart(); - if (Util.SDK_INT > 23) { + if (Build.VERSION.SDK_INT > 23) { initializePlayer(); if (playerView != null) { playerView.onResume(); @@ -156,7 +156,7 @@ public class PlayerActivity extends AppCompatActivity @Override public void onResume() { super.onResume(); - if (Util.SDK_INT <= 23 || player == null) { + if (Build.VERSION.SDK_INT <= 23 || player == null) { initializePlayer(); if (playerView != null) { playerView.onResume(); @@ -167,7 +167,7 @@ public class PlayerActivity extends AppCompatActivity @Override public void onPause() { super.onPause(); - if (Util.SDK_INT <= 23) { + if (Build.VERSION.SDK_INT <= 23) { if (playerView != null) { playerView.onPause(); } @@ -178,7 +178,7 @@ public class PlayerActivity extends AppCompatActivity @Override public void onStop() { super.onStop(); - if (Util.SDK_INT > 23) { + if (Build.VERSION.SDK_INT > 23) { if (playerView != null) { playerView.onPause(); } @@ -218,9 +218,7 @@ public class PlayerActivity extends AppCompatActivity outState.putBoolean(KEY_AUTO_PLAY, startAutoPlay); outState.putInt(KEY_ITEM_INDEX, startItemIndex); outState.putLong(KEY_POSITION, startPosition); - if (serverSideAdsLoaderState != null) { - outState.putBundle(KEY_SERVER_SIDE_ADS_LOADER_STATE, serverSideAdsLoaderState.toBundle()); - } + saveServerSideAdsLoaderState(outState); } // Activity input @@ -237,20 +235,20 @@ public class PlayerActivity extends AppCompatActivity public void onClick(View view) { if (view == selectTracksButton && !isShowingTrackSelectionDialog - && TrackSelectionDialog.willHaveContent(trackSelector)) { + && TrackSelectionDialog.willHaveContent(player)) { isShowingTrackSelectionDialog = true; TrackSelectionDialog trackSelectionDialog = - TrackSelectionDialog.createForTrackSelector( - trackSelector, + TrackSelectionDialog.createForPlayer( + player, /* onDismissListener= */ dismissedDialog -> isShowingTrackSelectionDialog = false); trackSelectionDialog.show(getSupportFragmentManager(), /* tag= */ null); } } - // PlayerControlView.VisibilityListener implementation + // PlayerView.ControllerVisibilityListener implementation @Override - public void onVisibilityChange(int visibility) { + public void onVisibilityChanged(int visibility) { debugRootView.setVisibility(visibility); } @@ -260,7 +258,9 @@ public class PlayerActivity extends AppCompatActivity setContentView(R.layout.player_activity); } - /** @return Whether initialization was successful. */ + /** + * @return Whether initialization was successful. + */ protected boolean initializePlayer() { if (player == null) { Intent intent = getIntent(); @@ -270,26 +270,20 @@ public class PlayerActivity extends AppCompatActivity return false; } - boolean preferExtensionDecoders = - intent.getBooleanExtra(IntentUtil.PREFER_EXTENSION_DECODERS_EXTRA, false); - RenderersFactory renderersFactory = - DemoUtil.buildRenderersFactory(/* context= */ this, preferExtensionDecoders); - - trackSelector = new DefaultTrackSelector(/* context= */ this); - lastSeenTracksInfo = TracksInfo.EMPTY; - player = + lastSeenTracks = Tracks.EMPTY; + ExoPlayer.Builder playerBuilder = new ExoPlayer.Builder(/* context= */ this) - .setRenderersFactory(renderersFactory) - .setMediaSourceFactory(createMediaSourceFactory()) - .setTrackSelector(trackSelector) - .build(); + .setMediaSourceFactory(createMediaSourceFactory()); + setRenderersFactory( + playerBuilder, intent.getBooleanExtra(IntentUtil.PREFER_EXTENSION_DECODERS_EXTRA, false)); + player = playerBuilder.build(); player.setTrackSelectionParameters(trackSelectionParameters); player.addListener(new PlayerEventListener()); - player.addAnalyticsListener(new EventLogger(trackSelector)); + player.addAnalyticsListener(new EventLogger()); player.setAudioAttributes(AudioAttributes.DEFAULT, /* handleAudioFocus= */ true); player.setPlayWhenReady(startAutoPlay); playerView.setPlayer(player); - serverSideAdsLoader.setPlayer(player); + configurePlayerWithServerSideAdsLoader(); debugViewHelper = new DebugTextViewHelper(player, debugTextView); debugViewHelper.start(); } @@ -303,7 +297,12 @@ public class PlayerActivity extends AppCompatActivity return true; } + @OptIn(markerClass = UnstableApi.class) // SSAI configuration private MediaSource.Factory createMediaSourceFactory() { + DefaultDrmSessionManagerProvider drmSessionManagerProvider = + new DefaultDrmSessionManagerProvider(); + drmSessionManagerProvider.setDrmHttpDataSourceFactory( + DemoUtil.getHttpDataSourceFactory(/* context= */ this)); ImaServerSideAdInsertionMediaSource.AdsLoader.Builder serverSideAdLoaderBuilder = new ImaServerSideAdInsertionMediaSource.AdsLoader.Builder(/* context= */ this, playerView); if (serverSideAdsLoaderState != null) { @@ -312,13 +311,30 @@ public class PlayerActivity extends AppCompatActivity serverSideAdsLoader = serverSideAdLoaderBuilder.build(); ImaServerSideAdInsertionMediaSource.Factory imaServerSideAdInsertionMediaSourceFactory = new ImaServerSideAdInsertionMediaSource.Factory( - serverSideAdsLoader, new DefaultMediaSourceFactory(dataSourceFactory)); - return new DefaultMediaSourceFactory(dataSourceFactory) - .setAdsLoaderProvider(this::getClientSideAdsLoader) - .setAdViewProvider(playerView) + serverSideAdsLoader, + new DefaultMediaSourceFactory(/* context= */ this) + .setDataSourceFactory(dataSourceFactory)); + return new DefaultMediaSourceFactory(/* context= */ this) + .setDataSourceFactory(dataSourceFactory) + .setDrmSessionManagerProvider(drmSessionManagerProvider) + .setLocalAdInsertionComponents( + this::getClientSideAdsLoader, /* adViewProvider= */ playerView) .setServerSideAdInsertionMediaSourceFactory(imaServerSideAdInsertionMediaSourceFactory); } + @OptIn(markerClass = UnstableApi.class) + private void setRenderersFactory( + ExoPlayer.Builder playerBuilder, boolean preferExtensionDecoders) { + RenderersFactory renderersFactory = + DemoUtil.buildRenderersFactory(/* context= */ this, preferExtensionDecoders); + playerBuilder.setRenderersFactory(renderersFactory); + } + + @OptIn(markerClass = UnstableApi.class) + private void configurePlayerWithServerSideAdsLoader() { + serverSideAdsLoader.setPlayer(player); + } + private List createMediaItems(Intent intent) { String action = intent.getAction(); boolean actionIsListView = IntentUtil.ACTION_VIEW_LIST.equals(action); @@ -345,7 +361,7 @@ public class PlayerActivity extends AppCompatActivity MediaItem.DrmConfiguration drmConfiguration = mediaItem.localConfiguration.drmConfiguration; if (drmConfiguration != null) { - if (Util.SDK_INT < 18) { + if (Build.VERSION.SDK_INT < 18) { showToast(R.string.error_drm_unsupported_before_api_18); finish(); return Collections.emptyList(); @@ -372,8 +388,7 @@ public class PlayerActivity extends AppCompatActivity if (player != null) { updateTrackSelectorParameters(); updateStartPosition(); - serverSideAdsLoaderState = serverSideAdsLoader.release(); - serverSideAdsLoader = null; + releaseServerSideAdsLoader(); debugViewHelper.stop(); debugViewHelper = null; player.release(); @@ -388,6 +403,12 @@ public class PlayerActivity extends AppCompatActivity } } + @OptIn(markerClass = UnstableApi.class) + private void releaseServerSideAdsLoader() { + serverSideAdsLoaderState = serverSideAdsLoader.release(); + serverSideAdsLoader = null; + } + private void releaseClientSideAdsLoader() { if (clientSideAdsLoader != null) { clientSideAdsLoader.release(); @@ -396,12 +417,26 @@ 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); + } + } + private void updateTrackSelectorParameters() { if (player != null) { - // Until the demo app is fully migrated to TrackSelectionParameters, rely on ExoPlayer to use - // DefaultTrackSelector by default. - trackSelectionParameters = - (DefaultTrackSelector.Parameters) player.getTrackSelectionParameters(); + trackSelectionParameters = player.getTrackSelectionParameters(); } } @@ -422,8 +457,7 @@ public class PlayerActivity extends AppCompatActivity // User controls private void updateButtonVisibility() { - selectTracksButton.setEnabled( - player != null && TrackSelectionDialog.willHaveContent(trackSelector)); + selectTracksButton.setEnabled(player != null && TrackSelectionDialog.willHaveContent(player)); } private void showControls() { @@ -461,20 +495,20 @@ public class PlayerActivity extends AppCompatActivity @Override @SuppressWarnings("ReferenceEquality") - public void onTracksInfoChanged(TracksInfo tracksInfo) { + public void onTracksChanged(Tracks tracks) { updateButtonVisibility(); - if (tracksInfo == lastSeenTracksInfo) { + if (tracks == lastSeenTracks) { return; } - if (!tracksInfo.isTypeSupportedOrEmpty( - C.TRACK_TYPE_VIDEO, /* allowExceedsCapabilities= */ true)) { + if (tracks.containsType(C.TRACK_TYPE_VIDEO) + && !tracks.isTypeSupported(C.TRACK_TYPE_VIDEO, /* allowExceedsCapabilities= */ true)) { showToast(R.string.error_unsupported_video); } - if (!tracksInfo.isTypeSupportedOrEmpty( - C.TRACK_TYPE_AUDIO, /* allowExceedsCapabilities= */ true)) { + if (tracks.containsType(C.TRACK_TYPE_AUDIO) + && !tracks.isTypeSupported(C.TRACK_TYPE_AUDIO, /* allowExceedsCapabilities= */ true)) { showToast(R.string.error_unsupported_audio); } - lastSeenTracksInfo = tracksInfo; + lastSeenTracks = tracks; } } @@ -513,29 +547,32 @@ public class PlayerActivity extends AppCompatActivity private static List createMediaItems(Intent intent, DownloadTracker downloadTracker) { List mediaItems = new ArrayList<>(); for (MediaItem item : IntentUtil.createMediaItemsFromIntent(intent)) { - @Nullable - DownloadRequest downloadRequest = - downloadTracker.getDownloadRequest(item.localConfiguration.uri); - if (downloadRequest != null) { - MediaItem.Builder builder = item.buildUpon(); - builder - .setMediaId(downloadRequest.id) - .setUri(downloadRequest.uri) - .setCustomCacheKey(downloadRequest.customCacheKey) - .setMimeType(downloadRequest.mimeType) - .setStreamKeys(downloadRequest.streamKeys); - @Nullable - MediaItem.DrmConfiguration drmConfiguration = item.localConfiguration.drmConfiguration; - if (drmConfiguration != null) { - builder.setDrmConfiguration( - drmConfiguration.buildUpon().setKeySetId(downloadRequest.keySetId).build()); - } - - mediaItems.add(builder.build()); - } else { - mediaItems.add(item); - } + mediaItems.add( + maybeSetDownloadProperties( + item, downloadTracker.getDownloadRequest(item.localConfiguration.uri))); } return mediaItems; } + + @OptIn(markerClass = androidx.media3.common.util.UnstableApi.class) + private static MediaItem maybeSetDownloadProperties( + MediaItem item, @Nullable DownloadRequest downloadRequest) { + if (downloadRequest == null) { + return item; + } + MediaItem.Builder builder = item.buildUpon(); + builder + .setMediaId(downloadRequest.id) + .setUri(downloadRequest.uri) + .setCustomCacheKey(downloadRequest.customCacheKey) + .setMimeType(downloadRequest.mimeType) + .setStreamKeys(downloadRequest.streamKeys); + @Nullable + MediaItem.DrmConfiguration drmConfiguration = item.localConfiguration.drmConfiguration; + if (drmConfiguration != null) { + builder.setDrmConfiguration( + drmConfiguration.buildUpon().setKeySetId(downloadRequest.keySetId).build()); + } + return builder.build(); + } } 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 058fcec3b0..fc7144dc91 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 @@ -15,9 +15,9 @@ */ package androidx.media3.demo.main; -import static androidx.media3.common.util.Assertions.checkArgument; -import static androidx.media3.common.util.Assertions.checkNotNull; -import static androidx.media3.common.util.Assertions.checkState; +import static com.google.common.base.Preconditions.checkArgument; +import static com.google.common.base.Preconditions.checkNotNull; +import static com.google.common.base.Preconditions.checkState; import android.content.Context; import android.content.Intent; @@ -27,6 +27,7 @@ import android.content.res.AssetManager; import android.net.Uri; import android.os.AsyncTask; import android.os.Bundle; +import android.text.TextUtils; import android.util.JsonReader; import android.view.Menu; import android.view.MenuInflater; @@ -41,6 +42,7 @@ import android.widget.ImageButton; import android.widget.TextView; import android.widget.Toast; import androidx.annotation.Nullable; +import androidx.annotation.OptIn; import androidx.appcompat.app.AppCompatActivity; import androidx.media3.common.MediaItem; import androidx.media3.common.MediaItem.ClippingConfiguration; @@ -53,6 +55,7 @@ import androidx.media3.datasource.DataSourceUtil; import androidx.media3.datasource.DataSpec; import androidx.media3.exoplayer.RenderersFactory; import androidx.media3.exoplayer.offline.DownloadService; +import com.google.common.base.Objects; import com.google.common.collect.ImmutableList; import com.google.common.collect.ImmutableMap; import java.io.IOException; @@ -116,8 +119,12 @@ public class SampleChooserActivity extends AppCompatActivity useExtensionRenderers = DemoUtil.useExtensionRenderers(); downloadTracker = DemoUtil.getDownloadTracker(/* context= */ this); loadSample(); + startDownloadService(); + } - // Start the download service if it should be running but it's not currently. + /** Start the download service if it should be running but it's not currently. */ + @OptIn(markerClass = androidx.media3.common.util.UnstableApi.class) + private void startDownloadService() { // Starting the service in the foreground causes notification flicker if there is no scheduled // action. Starting it in the background throws an exception if the app is in the background too // (e.g. if device screen is locked). @@ -271,6 +278,7 @@ public class SampleChooserActivity extends AppCompatActivity private boolean sawError; + @OptIn(markerClass = androidx.media3.common.util.UnstableApi.class) @Override protected List doInBackground(String... uris) { List result = new ArrayList<>(); @@ -436,7 +444,10 @@ public class SampleChooserActivity extends AppCompatActivity } else { @Nullable String adaptiveMimeType = - Util.getAdaptiveMimeTypeForContentType(Util.inferContentType(uri, extension)); + Util.getAdaptiveMimeTypeForContentType( + TextUtils.isEmpty(extension) + ? Util.inferContentType(uri) + : Util.inferContentTypeForExtension(extension)); mediaItem .setUri(uri) .setMediaMetadata(new MediaMetadata.Builder().setTitle(title).build()) @@ -447,7 +458,7 @@ public class SampleChooserActivity extends AppCompatActivity new MediaItem.DrmConfiguration.Builder(drmUuid) .setLicenseUri(drmLicenseUri) .setLicenseRequestHeaders(drmLicenseRequestHeaders) - .forceSessionsForAudioAndVideoTracks(drmSessionForClearContent) + .setForceSessionsForAudioAndVideoTracks(drmSessionForClearContent) .setMultiSession(drmMultiSession) .setForceDefaultLicenseUri(drmForceDefaultLicenseUri) .build()); @@ -481,7 +492,7 @@ public class SampleChooserActivity extends AppCompatActivity private PlaylistGroup getGroup(String groupName, List groups) { for (int i = 0; i < groups.size(); i++) { - if (Util.areEqual(groupName, groups.get(i).title)) { + if (Objects.equal(groupName, groups.get(i).title)) { return groups.get(i); } } 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 d4df2a1745..d5bee96eae 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 @@ -25,27 +25,50 @@ import android.view.View; import android.view.ViewGroup; import android.widget.Button; import androidx.annotation.Nullable; +import androidx.annotation.OptIn; import androidx.appcompat.app.AppCompatDialog; import androidx.fragment.app.DialogFragment; import androidx.fragment.app.Fragment; import androidx.fragment.app.FragmentManager; import androidx.fragment.app.FragmentPagerAdapter; import androidx.media3.common.C; -import androidx.media3.common.TrackGroupArray; -import androidx.media3.common.util.Assertions; -import androidx.media3.exoplayer.trackselection.DefaultTrackSelector; -import androidx.media3.exoplayer.trackselection.DefaultTrackSelector.SelectionOverride; -import androidx.media3.exoplayer.trackselection.MappingTrackSelector.MappedTrackInfo; +import androidx.media3.common.Player; +import androidx.media3.common.TrackGroup; +import androidx.media3.common.TrackSelectionOverride; +import androidx.media3.common.TrackSelectionParameters; +import androidx.media3.common.Tracks; +import androidx.media3.common.util.UnstableApi; import androidx.media3.ui.TrackSelectionView; import androidx.viewpager.widget.ViewPager; import com.google.android.material.tabs.TabLayout; +import com.google.common.collect.ImmutableList; import java.util.ArrayList; import java.util.Collections; +import java.util.HashMap; import java.util.List; +import java.util.Map; /** Dialog to select tracks. */ +@OptIn(markerClass = UnstableApi.class) public final class TrackSelectionDialog extends DialogFragment { + /** Called when tracks are selected. */ + public interface TrackSelectionListener { + + /** + * Called when tracks are selected. + * + * @param trackSelectionParameters A {@link TrackSelectionParameters} representing the selected + * tracks. Any manual selections are defined by {@link + * TrackSelectionParameters#disabledTrackTypes} and {@link + * TrackSelectionParameters#overrides}. + */ + void onTracksSelected(TrackSelectionParameters trackSelectionParameters); + } + + public static final ImmutableList SUPPORTED_TRACK_TYPES = + ImmutableList.of(C.TRACK_TYPE_VIDEO, C.TRACK_TYPE_AUDIO, C.TRACK_TYPE_TEXT); + private final SparseArray tabFragments; private final ArrayList tabTrackTypes; @@ -55,20 +78,19 @@ public final class TrackSelectionDialog extends DialogFragment { /** * Returns whether a track selection dialog will have content to display if initialized with the - * specified {@link DefaultTrackSelector} in its current state. + * specified {@link Player}. */ - public static boolean willHaveContent(DefaultTrackSelector trackSelector) { - MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo(); - return mappedTrackInfo != null && willHaveContent(mappedTrackInfo); + public static boolean willHaveContent(Player player) { + return willHaveContent(player.getCurrentTracks()); } /** * Returns whether a track selection dialog will have content to display if initialized with the - * specified {@link MappedTrackInfo}. + * specified {@link Tracks}. */ - public static boolean willHaveContent(MappedTrackInfo mappedTrackInfo) { - for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) { - if (showTabForRenderer(mappedTrackInfo, i)) { + public static boolean willHaveContent(Tracks tracks) { + for (Tracks.Group trackGroup : tracks.getGroups()) { + if (SUPPORTED_TRACK_TYPES.contains(trackGroup.getType())) { return true; } } @@ -76,78 +98,67 @@ public final class TrackSelectionDialog extends DialogFragment { } /** - * Creates a dialog for a given {@link DefaultTrackSelector}, whose parameters will be - * automatically updated when tracks are selected. + * Creates a dialog for a given {@link Player}, whose parameters will be automatically updated + * when tracks are selected. * - * @param trackSelector The {@link DefaultTrackSelector}. + * @param player The {@link Player}. * @param onDismissListener A {@link DialogInterface.OnDismissListener} to call when the dialog is * dismissed. */ - public static TrackSelectionDialog createForTrackSelector( - DefaultTrackSelector trackSelector, DialogInterface.OnDismissListener onDismissListener) { - MappedTrackInfo mappedTrackInfo = - Assertions.checkNotNull(trackSelector.getCurrentMappedTrackInfo()); - TrackSelectionDialog trackSelectionDialog = new TrackSelectionDialog(); - DefaultTrackSelector.Parameters parameters = trackSelector.getParameters(); - trackSelectionDialog.init( - /* titleId= */ R.string.track_selection_title, - mappedTrackInfo, - /* initialParameters = */ parameters, + public static TrackSelectionDialog createForPlayer( + Player player, DialogInterface.OnDismissListener onDismissListener) { + return createForTracksAndParameters( + R.string.track_selection_title, + player.getCurrentTracks(), + player.getTrackSelectionParameters(), /* allowAdaptiveSelections= */ true, /* allowMultipleOverrides= */ false, - /* onClickListener= */ (dialog, which) -> { - DefaultTrackSelector.ParametersBuilder builder = parameters.buildUpon(); - for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) { - builder - .clearSelectionOverrides(/* rendererIndex= */ i) - .setRendererDisabled( - /* rendererIndex= */ i, - trackSelectionDialog.getIsDisabled(/* rendererIndex= */ i)); - List overrides = - trackSelectionDialog.getOverrides(/* rendererIndex= */ i); - if (!overrides.isEmpty()) { - builder.setSelectionOverride( - /* rendererIndex= */ i, - mappedTrackInfo.getTrackGroups(/* rendererIndex= */ i), - overrides.get(0)); - } - } - trackSelector.setParameters(builder); - }, + player::setTrackSelectionParameters, onDismissListener); - return trackSelectionDialog; } /** - * Creates a dialog for given {@link MappedTrackInfo} and {@link DefaultTrackSelector.Parameters}. + * Creates a dialog for given {@link Tracks} and {@link TrackSelectionParameters}. * * @param titleId The resource id of the dialog title. - * @param mappedTrackInfo The {@link MappedTrackInfo} to display. - * @param initialParameters The {@link DefaultTrackSelector.Parameters} describing the initial - * track selection. + * @param tracks The {@link Tracks} describing the tracks to display. + * @param trackSelectionParameters The initial {@link TrackSelectionParameters}. * @param allowAdaptiveSelections Whether adaptive selections (consisting of more than one track) * can be made. * @param allowMultipleOverrides Whether tracks from multiple track groups can be selected. - * @param onClickListener {@link DialogInterface.OnClickListener} called when tracks are selected. + * @param trackSelectionListener Called when tracks are selected. * @param onDismissListener {@link DialogInterface.OnDismissListener} called when the dialog is * dismissed. */ - public static TrackSelectionDialog createForMappedTrackInfoAndParameters( + public static TrackSelectionDialog createForTracksAndParameters( int titleId, - MappedTrackInfo mappedTrackInfo, - DefaultTrackSelector.Parameters initialParameters, + Tracks tracks, + TrackSelectionParameters trackSelectionParameters, boolean allowAdaptiveSelections, boolean allowMultipleOverrides, - DialogInterface.OnClickListener onClickListener, + TrackSelectionListener trackSelectionListener, DialogInterface.OnDismissListener onDismissListener) { TrackSelectionDialog trackSelectionDialog = new TrackSelectionDialog(); trackSelectionDialog.init( + tracks, + trackSelectionParameters, titleId, - mappedTrackInfo, - initialParameters, allowAdaptiveSelections, allowMultipleOverrides, - onClickListener, + /* onClickListener= */ (dialog, which) -> { + TrackSelectionParameters.Builder builder = trackSelectionParameters.buildUpon(); + for (int i = 0; i < SUPPORTED_TRACK_TYPES.size(); i++) { + int trackType = SUPPORTED_TRACK_TYPES.get(i); + builder.setTrackTypeDisabled(trackType, trackSelectionDialog.getIsDisabled(trackType)); + builder.clearOverridesOfType(trackType); + Map overrides = + trackSelectionDialog.getOverrides(trackType); + for (TrackSelectionOverride override : overrides.values()) { + builder.addOverride(override); + } + } + trackSelectionListener.onTracksSelected(builder.build()); + }, onDismissListener); return trackSelectionDialog; } @@ -160,9 +171,9 @@ public final class TrackSelectionDialog extends DialogFragment { } private void init( + Tracks tracks, + TrackSelectionParameters trackSelectionParameters, int titleId, - MappedTrackInfo mappedTrackInfo, - DefaultTrackSelector.Parameters initialParameters, boolean allowAdaptiveSelections, boolean allowMultipleOverrides, DialogInterface.OnClickListener onClickListener, @@ -170,45 +181,49 @@ public final class TrackSelectionDialog extends DialogFragment { this.titleId = titleId; this.onClickListener = onClickListener; this.onDismissListener = onDismissListener; - for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) { - if (showTabForRenderer(mappedTrackInfo, i)) { - int trackType = mappedTrackInfo.getRendererType(/* rendererIndex= */ i); - TrackGroupArray trackGroupArray = mappedTrackInfo.getTrackGroups(i); + + for (int i = 0; i < SUPPORTED_TRACK_TYPES.size(); i++) { + @C.TrackType int trackType = SUPPORTED_TRACK_TYPES.get(i); + ArrayList trackGroups = new ArrayList<>(); + for (Tracks.Group trackGroup : tracks.getGroups()) { + if (trackGroup.getType() == trackType) { + trackGroups.add(trackGroup); + } + } + if (!trackGroups.isEmpty()) { TrackSelectionViewFragment tabFragment = new TrackSelectionViewFragment(); tabFragment.init( - mappedTrackInfo, - /* rendererIndex= */ i, - initialParameters.getRendererDisabled(/* rendererIndex= */ i), - initialParameters.getSelectionOverride(/* rendererIndex= */ i, trackGroupArray), + trackGroups, + trackSelectionParameters.disabledTrackTypes.contains(trackType), + trackSelectionParameters.overrides, allowAdaptiveSelections, allowMultipleOverrides); - tabFragments.put(i, tabFragment); + tabFragments.put(trackType, tabFragment); tabTrackTypes.add(trackType); } } } /** - * Returns whether a renderer is disabled. + * Returns whether the disabled option is selected for the specified track type. * - * @param rendererIndex Renderer index. - * @return Whether the renderer is disabled. + * @param trackType The track type. + * @return Whether the disabled option is selected for the track type. */ - public boolean getIsDisabled(int rendererIndex) { - TrackSelectionViewFragment rendererView = tabFragments.get(rendererIndex); - return rendererView != null && rendererView.isDisabled; + public boolean getIsDisabled(int trackType) { + TrackSelectionViewFragment trackView = tabFragments.get(trackType); + return trackView != null && trackView.isDisabled; } /** - * Returns the list of selected track selection overrides for the specified renderer. There will - * be at most one override for each track group. + * Returns the selected track overrides for the specified track type. * - * @param rendererIndex Renderer index. - * @return The list of track selection overrides for this renderer. + * @param trackType The track type. + * @return The track overrides for the track type. */ - public List getOverrides(int rendererIndex) { - TrackSelectionViewFragment rendererView = tabFragments.get(rendererIndex); - return rendererView == null ? Collections.emptyList() : rendererView.overrides; + public Map getOverrides(int trackType) { + TrackSelectionViewFragment trackView = tabFragments.get(trackType); + return trackView == null ? Collections.emptyMap() : trackView.overrides; } @Override @@ -248,27 +263,7 @@ public final class TrackSelectionDialog extends DialogFragment { return dialogView; } - private static boolean showTabForRenderer(MappedTrackInfo mappedTrackInfo, int rendererIndex) { - TrackGroupArray trackGroupArray = mappedTrackInfo.getTrackGroups(rendererIndex); - if (trackGroupArray.length == 0) { - return false; - } - int trackType = mappedTrackInfo.getRendererType(rendererIndex); - return isSupportedTrackType(trackType); - } - - private static boolean isSupportedTrackType(int trackType) { - switch (trackType) { - case C.TRACK_TYPE_VIDEO: - case C.TRACK_TYPE_AUDIO: - case C.TRACK_TYPE_TEXT: - return true; - default: - return false; - } - } - - private static String getTrackTypeString(Resources resources, int trackType) { + 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); @@ -289,12 +284,12 @@ public final class TrackSelectionDialog extends DialogFragment { @Override public Fragment getItem(int position) { - return tabFragments.valueAt(position); + return tabFragments.get(tabTrackTypes.get(position)); } @Override public int getCount() { - return tabFragments.size(); + return tabTrackTypes.size(); } @Override @@ -307,13 +302,12 @@ public final class TrackSelectionDialog extends DialogFragment { public static final class TrackSelectionViewFragment extends Fragment implements TrackSelectionView.TrackSelectionListener { - private MappedTrackInfo mappedTrackInfo; - private int rendererIndex; + private List trackGroups; private boolean allowAdaptiveSelections; private boolean allowMultipleOverrides; /* package */ boolean isDisabled; - /* package */ List overrides; + /* package */ Map overrides; public TrackSelectionViewFragment() { // Retain instance across activity re-creation to prevent losing access to init data. @@ -321,21 +315,20 @@ public final class TrackSelectionDialog extends DialogFragment { } public void init( - MappedTrackInfo mappedTrackInfo, - int rendererIndex, - boolean initialIsDisabled, - @Nullable SelectionOverride initialOverride, + List trackGroups, + boolean isDisabled, + Map overrides, boolean allowAdaptiveSelections, boolean allowMultipleOverrides) { - this.mappedTrackInfo = mappedTrackInfo; - this.rendererIndex = rendererIndex; - this.isDisabled = initialIsDisabled; - this.overrides = - initialOverride == null - ? Collections.emptyList() - : Collections.singletonList(initialOverride); + this.trackGroups = trackGroups; + this.isDisabled = isDisabled; this.allowAdaptiveSelections = allowAdaptiveSelections; this.allowMultipleOverrides = allowMultipleOverrides; + // TrackSelectionView does this filtering internally, but we need to do it here as well to + // handle the case where the TrackSelectionView is never created. + this.overrides = + new HashMap<>( + TrackSelectionView.filterOverrides(overrides, trackGroups, allowMultipleOverrides)); } @Override @@ -351,8 +344,7 @@ public final class TrackSelectionDialog extends DialogFragment { trackSelectionView.setAllowMultipleOverrides(allowMultipleOverrides); trackSelectionView.setAllowAdaptiveSelections(allowAdaptiveSelections); trackSelectionView.init( - mappedTrackInfo, - rendererIndex, + trackGroups, isDisabled, overrides, /* trackFormatComparator= */ null, @@ -361,7 +353,8 @@ public final class TrackSelectionDialog extends DialogFragment { } @Override - public void onTrackSelectionChanged(boolean isDisabled, List overrides) { + public void onTrackSelectionChanged( + boolean isDisabled, Map overrides) { this.isDisabled = isDisabled; this.overrides = overrides; } diff --git a/demos/session/src/main/AndroidManifest.xml b/demos/session/src/main/AndroidManifest.xml index f86bcd86bb..e94b527e83 100644 --- a/demos/session/src/main/AndroidManifest.xml +++ b/demos/session/src/main/AndroidManifest.xml @@ -45,6 +45,7 @@ diff --git a/demos/session/src/main/java/androidx/media3/demo/session/PlaybackService.kt b/demos/session/src/main/java/androidx/media3/demo/session/PlaybackService.kt index b4ec76ee5a..cc8291c27d 100644 --- a/demos/session/src/main/java/androidx/media3/demo/session/PlaybackService.kt +++ b/demos/session/src/main/java/androidx/media3/demo/session/PlaybackService.kt @@ -19,35 +19,118 @@ import android.app.PendingIntent.FLAG_IMMUTABLE import android.app.PendingIntent.FLAG_UPDATE_CURRENT import android.app.TaskStackBuilder import android.content.Intent -import android.net.Uri import android.os.Build import android.os.Bundle import androidx.media3.common.AudioAttributes import androidx.media3.common.MediaItem import androidx.media3.exoplayer.ExoPlayer +import androidx.media3.session.CommandButton import androidx.media3.session.LibraryResult import androidx.media3.session.MediaLibraryService import androidx.media3.session.MediaSession +import androidx.media3.session.MediaSession.ControllerInfo +import androidx.media3.session.SessionCommand import androidx.media3.session.SessionResult import com.google.common.collect.ImmutableList import com.google.common.util.concurrent.Futures import com.google.common.util.concurrent.ListenableFuture class PlaybackService : MediaLibraryService() { + private val librarySessionCallback = CustomMediaLibrarySessionCallback() + private lateinit var player: ExoPlayer private lateinit var mediaLibrarySession: MediaLibrarySession - private val librarySessionCallback = CustomMediaLibrarySessionCallback() + private lateinit var customCommands: List + + private var customLayout = ImmutableList.of() companion object { private const val SEARCH_QUERY_PREFIX_COMPAT = "androidx://media3-session/playFromSearch" private const val SEARCH_QUERY_PREFIX = "androidx://media3-session/setMediaUri" + private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON = + "android.media3.session.demo.SHUFFLE_ON" + private const val CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF = + "android.media3.session.demo.SHUFFLE_OFF" } - private inner class CustomMediaLibrarySessionCallback : - MediaLibrarySession.MediaLibrarySessionCallback { + override fun onCreate() { + super.onCreate() + customCommands = + listOf( + getShuffleCommandButton( + SessionCommand(CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON, Bundle.EMPTY) + ), + getShuffleCommandButton( + SessionCommand(CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF, Bundle.EMPTY) + ) + ) + customLayout = ImmutableList.of(customCommands[0]) + initializeSessionAndPlayer() + } + + override fun onGetSession(controllerInfo: ControllerInfo): MediaLibrarySession { + return mediaLibrarySession + } + + override fun onDestroy() { + player.release() + mediaLibrarySession.release() + super.onDestroy() + } + + private inner class CustomMediaLibrarySessionCallback : MediaLibrarySession.Callback { + + override fun onConnect( + session: MediaSession, + controller: ControllerInfo + ): MediaSession.ConnectionResult { + val connectionResult = super.onConnect(session, controller) + val availableSessionCommands = connectionResult.availableSessionCommands.buildUpon() + customCommands.forEach { commandButton -> + // Add custom command to available session commands. + commandButton.sessionCommand?.let { availableSessionCommands.add(it) } + } + return MediaSession.ConnectionResult.accept( + availableSessionCommands.build(), + connectionResult.availablePlayerCommands + ) + } + + override fun onPostConnect(session: MediaSession, controller: ControllerInfo) { + if (!customLayout.isEmpty() && controller.controllerVersion != 0) { + // Let Media3 controller (for instance the MediaNotificationProvider) know about the custom + // layout right after it connected. + ignoreFuture(mediaLibrarySession.setCustomLayout(controller, customLayout)) + } + } + + override fun onCustomCommand( + session: MediaSession, + controller: ControllerInfo, + customCommand: SessionCommand, + args: Bundle + ): ListenableFuture { + if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON == customCommand.customAction) { + // Enable shuffling. + player.shuffleModeEnabled = true + // Change the custom layout to contain the `Disable shuffling` command. + customLayout = ImmutableList.of(customCommands[1]) + // Send the updated custom layout to controllers. + session.setCustomLayout(customLayout) + } else if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF == customCommand.customAction) { + // Disable shuffling. + player.shuffleModeEnabled = false + // Change the custom layout to contain the `Enable shuffling` command. + customLayout = ImmutableList.of(customCommands[0]) + // Send the updated custom layout to controllers. + session.setCustomLayout(customLayout) + } + return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS)) + } + override fun onGetLibraryRoot( session: MediaLibrarySession, - browser: MediaSession.ControllerInfo, + browser: ControllerInfo, params: LibraryParams? ): ListenableFuture> { return Futures.immediateFuture(LibraryResult.ofItem(MediaItemTree.getRootItem(), params)) @@ -55,7 +138,7 @@ class PlaybackService : MediaLibraryService() { override fun onGetItem( session: MediaLibrarySession, - browser: MediaSession.ControllerInfo, + browser: ControllerInfo, mediaId: String ): ListenableFuture> { val item = @@ -66,9 +149,24 @@ class PlaybackService : MediaLibraryService() { return Futures.immediateFuture(LibraryResult.ofItem(item, /* params= */ null)) } + override fun onSubscribe( + session: MediaLibrarySession, + browser: ControllerInfo, + parentId: String, + params: LibraryParams? + ): ListenableFuture> { + val children = + MediaItemTree.getChildren(parentId) + ?: return Futures.immediateFuture( + LibraryResult.ofError(LibraryResult.RESULT_ERROR_BAD_VALUE) + ) + session.notifyChildrenChanged(browser, parentId, children.size, params) + return Futures.immediateFuture(LibraryResult.ofVoid()) + } + override fun onGetChildren( session: MediaLibrarySession, - browser: MediaSession.ControllerInfo, + browser: ControllerInfo, parentId: String, page: Int, pageSize: Int, @@ -83,7 +181,21 @@ class PlaybackService : MediaLibraryService() { return Futures.immediateFuture(LibraryResult.ofItemList(children, params)) } - private fun setMediaItemFromSearchQuery(query: String) { + override fun onAddMediaItems( + mediaSession: MediaSession, + controller: MediaSession.ControllerInfo, + mediaItems: List + ): ListenableFuture> { + val updatedMediaItems: List = + mediaItems.map { mediaItem -> + if (mediaItem.requestMetadata.searchQuery != null) + getMediaItemFromSearchQuery(mediaItem.requestMetadata.searchQuery!!) + else MediaItemTree.getItem(mediaItem.mediaId) ?: mediaItem + } + return Futures.immediateFuture(updatedMediaItems) + } + + private fun getMediaItemFromSearchQuery(query: String): MediaItem { // Only accept query with pattern "play [Title]" or "[Title]" // Where [Title]: must be exactly matched // If no media with exact name found, play a random media instead @@ -94,54 +206,8 @@ class PlaybackService : MediaLibraryService() { query } - val item = MediaItemTree.getItemFromTitle(mediaTitle) ?: MediaItemTree.getRandomItem() - player.setMediaItem(item) + return MediaItemTree.getItemFromTitle(mediaTitle) ?: MediaItemTree.getRandomItem() } - - override fun onSetMediaUri( - session: MediaSession, - controller: MediaSession.ControllerInfo, - uri: Uri, - extras: Bundle - ): Int { - - if (uri.toString().startsWith(SEARCH_QUERY_PREFIX) || - uri.toString().startsWith(SEARCH_QUERY_PREFIX_COMPAT) - ) { - var searchQuery = - uri.getQueryParameter("query") ?: return SessionResult.RESULT_ERROR_NOT_SUPPORTED - setMediaItemFromSearchQuery(searchQuery) - - return SessionResult.RESULT_SUCCESS - } else { - return SessionResult.RESULT_ERROR_NOT_SUPPORTED - } - } - } - - private class CustomMediaItemFiller : MediaSession.MediaItemFiller { - override fun fillInLocalConfiguration( - session: MediaSession, - controller: MediaSession.ControllerInfo, - mediaItem: MediaItem - ): MediaItem { - return MediaItemTree.getItem(mediaItem.mediaId) ?: mediaItem - } - } - - override fun onCreate() { - super.onCreate() - initializeSessionAndPlayer() - } - - override fun onDestroy() { - player.release() - mediaLibrarySession.release() - super.onDestroy() - } - - override fun onGetSession(controllerInfo: MediaSession.ControllerInfo): MediaLibrarySession { - return mediaLibrarySession } private fun initializeSessionAndPlayer() { @@ -151,13 +217,10 @@ class PlaybackService : MediaLibraryService() { .build() MediaItemTree.initialize(assets) - val parentScreenIntent = Intent(this, MainActivity::class.java) - val intent = Intent(this, PlayerActivity::class.java) - - val pendingIntent = + val sessionActivityPendingIntent = TaskStackBuilder.create(this).run { - addNextIntent(parentScreenIntent) - addNextIntent(intent) + addNextIntent(Intent(this@PlaybackService, MainActivity::class.java)) + addNextIntent(Intent(this@PlaybackService, PlayerActivity::class.java)) val immutableFlag = if (Build.VERSION.SDK_INT >= 23) FLAG_IMMUTABLE else 0 getPendingIntent(0, immutableFlag or FLAG_UPDATE_CURRENT) @@ -165,8 +228,29 @@ class PlaybackService : MediaLibraryService() { mediaLibrarySession = MediaLibrarySession.Builder(this, player, librarySessionCallback) - .setMediaItemFiller(CustomMediaItemFiller()) - .setSessionActivity(pendingIntent) + .setSessionActivity(sessionActivityPendingIntent) .build() + if (!customLayout.isEmpty()) { + // Send custom layout to legacy session. + mediaLibrarySession.setCustomLayout(customLayout) + } + } + + private fun getShuffleCommandButton(sessionCommand: SessionCommand): CommandButton { + val isOn = sessionCommand.customAction == CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON + return CommandButton.Builder() + .setDisplayName( + getString( + if (isOn) R.string.exo_controls_shuffle_on_description + else R.string.exo_controls_shuffle_off_description + ) + ) + .setSessionCommand(sessionCommand) + .setIconResId(if (isOn) R.drawable.exo_icon_shuffle_off else R.drawable.exo_icon_shuffle_on) + .build() + } + + private fun ignoreFuture(customLayout: ListenableFuture) { + /* Do nothing. */ } } diff --git a/demos/surface/src/main/java/androidx/media3/demo/surface/MainActivity.java b/demos/surface/src/main/java/androidx/media3/demo/surface/MainActivity.java index 649a38dd95..bb3d20c094 100644 --- a/demos/surface/src/main/java/androidx/media3/demo/surface/MainActivity.java +++ b/demos/surface/src/main/java/androidx/media3/demo/surface/MainActivity.java @@ -19,6 +19,7 @@ import android.app.Activity; import android.content.Intent; import android.net.Uri; import android.os.Bundle; +import android.text.TextUtils; import android.view.Surface; import android.view.SurfaceControl; import android.view.SurfaceHolder; @@ -35,7 +36,6 @@ import androidx.media3.common.util.Util; import androidx.media3.datasource.DataSource; import androidx.media3.datasource.DefaultDataSource; import androidx.media3.datasource.DefaultHttpDataSource; -import androidx.media3.datasource.HttpDataSource; import androidx.media3.exoplayer.ExoPlayer; import androidx.media3.exoplayer.dash.DashMediaSource; import androidx.media3.exoplayer.drm.DefaultDrmSessionManager; @@ -189,7 +189,7 @@ public final class MainActivity extends Activity { 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)); - HttpDataSource.Factory licenseDataSourceFactory = new DefaultHttpDataSource.Factory(); + DataSource.Factory licenseDataSourceFactory = new DefaultHttpDataSource.Factory(); HttpMediaDrmCallback drmCallback = new HttpMediaDrmCallback(drmLicenseUrl, licenseDataSourceFactory); drmSessionManager = @@ -202,13 +202,18 @@ public final class MainActivity extends Activity { DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(this); MediaSource mediaSource; - @C.ContentType int type = Util.inferContentType(uri, intent.getStringExtra(EXTENSION_EXTRA)); - if (type == C.TYPE_DASH) { + @Nullable String fileExtension = intent.getStringExtra(EXTENSION_EXTRA); + @C.ContentType + int type = + TextUtils.isEmpty(fileExtension) + ? Util.inferContentType(uri) + : Util.inferContentTypeForExtension(fileExtension); + if (type == C.CONTENT_TYPE_DASH) { mediaSource = new DashMediaSource.Factory(dataSourceFactory) .setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager) .createMediaSource(MediaItem.fromUri(uri)); - } else if (type == C.TYPE_OTHER) { + } else if (type == C.CONTENT_TYPE_OTHER) { mediaSource = new ProgressiveMediaSource.Factory(dataSourceFactory) .setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager) diff --git a/demos/transformer/BUILD.bazel b/demos/transformer/BUILD.bazel new file mode 100644 index 0000000000..dba4a65315 --- /dev/null +++ b/demos/transformer/BUILD.bazel @@ -0,0 +1,22 @@ +# Build targets for a demo MediaPipe graph. +# See README.md for instructions on using MediaPipe in the demo. + +load("//mediapipe/java/com/google/mediapipe:mediapipe_aar.bzl", "mediapipe_aar") +load( + "//mediapipe/framework/tool:mediapipe_graph.bzl", + "mediapipe_binary_graph", +) + +mediapipe_aar( + name = "edge_detector_mediapipe_aar", + calculators = [ + "//mediapipe/calculators/image:luminance_calculator", + "//mediapipe/calculators/image:sobel_edges_calculator", + ], +) + +mediapipe_binary_graph( + name = "edge_detector_binary_graph", + graph = "edge_detector_mediapipe_graph.pbtxt", + output_name = "edge_detector_mediapipe_graph.binarypb", +) diff --git a/demos/transformer/README.md b/demos/transformer/README.md index fb2657001e..fd767ba6c8 100644 --- a/demos/transformer/README.md +++ b/demos/transformer/README.md @@ -6,4 +6,61 @@ example by removing audio or video. See the [demos README](../README.md) for instructions on how to build and run this demo. +## MediaPipe frame processing demo + +Building the demo app with [MediaPipe][] integration enabled requires some extra +manual steps. + +1. Follow the + [instructions](https://google.github.io/mediapipe/getting_started/install.html) + to install MediaPipe. +1. Copy the Transformer demo's build configuration and MediaPipe graph text + protocol buffer under the MediaPipe source tree. This makes it easy to + [build an AAR][] with bazel by reusing MediaPipe's workspace. + + ```sh + cd "" + MEDIAPIPE_ROOT="$(pwd)" + MEDIAPIPE_TRANSFORMER_ROOT="${MEDIAPIPE_ROOT}/mediapipe/java/com/google/mediapipe/transformer" + cd "" + TRANSFORMER_DEMO_ROOT="$(pwd)" + mkdir -p "${MEDIAPIPE_TRANSFORMER_ROOT}" + mkdir -p "${TRANSFORMER_DEMO_ROOT}/libs" + cp ${TRANSFORMER_DEMO_ROOT}/BUILD.bazel ${MEDIAPIPE_TRANSFORMER_ROOT}/BUILD + cp ${TRANSFORMER_DEMO_ROOT}/src/withMediaPipe/assets/edge_detector_mediapipe_graph.pbtxt \ + ${MEDIAPIPE_TRANSFORMER_ROOT} + ``` + +1. Build the AAR and the binary proto for the demo's MediaPipe graph, then copy + them to Transformer. + + ```sh + cd ${MEDIAPIPE_ROOT} + bazel build -c opt --strip=ALWAYS \ + --host_crosstool_top=@bazel_tools//tools/cpp:toolchain \ + --fat_apk_cpu=arm64-v8a,armeabi-v7a \ + --legacy_whole_archive=0 \ + --features=-legacy_whole_archive \ + --copt=-fvisibility=hidden \ + --copt=-ffunction-sections \ + --copt=-fdata-sections \ + --copt=-fstack-protector \ + --copt=-Oz \ + --copt=-fomit-frame-pointer \ + --copt=-DABSL_MIN_LOG_LEVEL=2 \ + --linkopt=-Wl,--gc-sections,--strip-all \ + mediapipe/java/com/google/mediapipe/transformer:edge_detector_mediapipe_aar.aar + cp bazel-bin/mediapipe/java/com/google/mediapipe/transformer/edge_detector_mediapipe_aar.aar \ + ${TRANSFORMER_DEMO_ROOT}/libs + bazel build mediapipe/java/com/google/mediapipe/transformer:edge_detector_binary_graph + cp bazel-bin/mediapipe/java/com/google/mediapipe/transformer/edge_detector_mediapipe_graph.binarypb \ + ${TRANSFORMER_DEMO_ROOT}/src/withMediaPipe/assets + ``` + +1. In Android Studio, gradle sync and select the `withMediaPipe` build variant + (this will only appear if the AAR is present), then build and run the demo + app and select a MediaPipe-based effect. + [Transformer]: https://exoplayer.dev/transforming-media.html +[MediaPipe]: https://google.github.io/mediapipe/ +[build an AAR]: https://google.github.io/mediapipe/getting_started/android_archive_library.html diff --git a/demos/transformer/build.gradle b/demos/transformer/build.gradle index 0146ec9e7b..a745fcea1f 100644 --- a/demos/transformer/build.gradle +++ b/demos/transformer/build.gradle @@ -45,6 +45,27 @@ android { // This demo app isn't indexed and doesn't have translations. disable 'GoogleAppIndexingWarning','MissingTranslation' } + + flavorDimensions "mediaPipe" + + productFlavors { + noMediaPipe { + dimension "mediaPipe" + } + withMediaPipe { + dimension "mediaPipe" + } + } + + // Ignore the withMediaPipe variant if the MediaPipe AAR is not present. + if (!project.file("libs/edge_detector_mediapipe_aar.aar").exists()) { + variantFilter { variant -> + def names = variant.flavors*.name + if (names.contains("withMediaPipe")) { + setIgnore(true) + } + } + } } dependencies { @@ -56,6 +77,14 @@ dependencies { 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-transformer') implementation project(modulePrefix + 'lib-ui') + + // For MediaPipe and its dependencies: + withMediaPipeImplementation fileTree(dir: 'libs', include: ['*.aar']) + withMediaPipeImplementation 'com.google.flogger:flogger:latest.release' + withMediaPipeImplementation 'com.google.flogger:flogger-system-backend:latest.release' + withMediaPipeImplementation 'com.google.code.findbugs:jsr305:latest.release' + withMediaPipeImplementation 'com.google.protobuf:protobuf-javalite:3.19.1' } diff --git a/demos/transformer/src/main/assets/fragment_shader_bitmap_overlay_es2.glsl b/demos/transformer/src/main/assets/fragment_shader_bitmap_overlay_es2.glsl new file mode 100644 index 0000000000..90ff827132 --- /dev/null +++ b/demos/transformer/src/main/assets/fragment_shader_bitmap_overlay_es2.glsl @@ -0,0 +1,37 @@ +#version 100 +// Copyright 2022 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. + +// ES 2 fragment shader that overlays the bitmap from uTexSampler1 over a video +// frame from uTexSampler0. + +precision mediump float; +// Texture containing an input video frame. +uniform sampler2D uTexSampler0; +// Texture containing the overlap bitmap. +uniform sampler2D uTexSampler1; +// Horizontal scaling factor for the overlap bitmap. +uniform float uScaleX; +// Vertical scaling factory for the overlap bitmap. +uniform float uScaleY; +varying vec2 vTexSamplingCoord; +void main() { + vec4 videoColor = texture2D(uTexSampler0, vTexSamplingCoord); + vec4 overlayColor = texture2D(uTexSampler1, + vec2(vTexSamplingCoord.x * uScaleX, + vTexSamplingCoord.y * uScaleY)); + // Blend the video decoder output and the overlay bitmap. + gl_FragColor = videoColor * (1.0 - overlayColor.a) + + overlayColor * overlayColor.a; +} diff --git a/demos/transformer/src/main/assets/fragment_shader_vignette_es2.glsl b/demos/transformer/src/main/assets/fragment_shader_vignette_es2.glsl new file mode 100644 index 0000000000..55dea952a5 --- /dev/null +++ b/demos/transformer/src/main/assets/fragment_shader_vignette_es2.glsl @@ -0,0 +1,31 @@ +#version 100 +// Copyright 2022 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. + +// ES 2 fragment shader that samples from a (non-external) texture with uTexSampler, +// copying from this texture to the current output while applying a vignette effect +// by linearly darkening the pixels between uInnerRadius and uOuterRadius. + +precision mediump float; +uniform sampler2D uTexSampler; +uniform vec2 uCenter; +uniform float uInnerRadius; +uniform float uOuterRadius; +varying vec2 vTexSamplingCoord; +void main() { + vec3 src = texture2D(uTexSampler, vTexSamplingCoord).xyz; + float dist = distance(vTexSamplingCoord, uCenter); + float scale = clamp(1.0 - (dist - uInnerRadius) / (uOuterRadius - uInnerRadius), 0.0, 1.0); + gl_FragColor = vec4(src.r * scale, src.g * scale, src.b * scale, 1.0); +} diff --git a/libraries/transformer/src/main/assets/shaders/vertex_shader_transformation_es3.glsl b/demos/transformer/src/main/assets/vertex_shader_copy_es2.glsl similarity index 70% rename from libraries/transformer/src/main/assets/shaders/vertex_shader_transformation_es3.glsl rename to demos/transformer/src/main/assets/vertex_shader_copy_es2.glsl index 9af4dcecd6..c603e252ac 100644 --- a/libraries/transformer/src/main/assets/shaders/vertex_shader_transformation_es3.glsl +++ b/demos/transformer/src/main/assets/vertex_shader_copy_es2.glsl @@ -1,4 +1,4 @@ -#version 300 es +#version 100 // Copyright 2022 The Android Open Source Project // // Licensed under the Apache License, Version 2.0 (the "License"); @@ -12,12 +12,12 @@ // 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. -in vec4 aFramePosition; -in vec4 aTexCoords; -uniform mat4 uTexTransform; -uniform mat4 uTransformationMatrix; -out vec2 vTexCoords; + +// ES 2 vertex shader that leaves the coordinates unchanged. + +attribute vec4 aFramePosition; +varying vec2 vTexSamplingCoord; void main() { - gl_Position = uTransformationMatrix * aFramePosition; - vTexCoords = (uTexTransform * aTexCoords).xy; + gl_Position = aFramePosition; + vTexSamplingCoord = vec2(aFramePosition.x * 0.5 + 0.5, aFramePosition.y * 0.5 + 0.5); } diff --git a/demos/transformer/src/main/java/androidx/media3/demo/transformer/BitmapOverlayProcessor.java b/demos/transformer/src/main/java/androidx/media3/demo/transformer/BitmapOverlayProcessor.java new file mode 100644 index 0000000000..85a8d59b6d --- /dev/null +++ b/demos/transformer/src/main/java/androidx/media3/demo/transformer/BitmapOverlayProcessor.java @@ -0,0 +1,168 @@ +/* + * Copyright 2022 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.transformer; + +import static androidx.media3.common.util.Assertions.checkStateNotNull; + +import android.content.Context; +import android.content.pm.PackageManager; +import android.graphics.Bitmap; +import android.graphics.Canvas; +import android.graphics.Color; +import android.graphics.Matrix; +import android.graphics.Paint; +import android.graphics.drawable.BitmapDrawable; +import android.opengl.GLES20; +import android.opengl.GLUtils; +import android.util.Size; +import androidx.media3.common.C; +import androidx.media3.common.util.GlProgram; +import androidx.media3.common.util.GlUtil; +import androidx.media3.transformer.FrameProcessingException; +import androidx.media3.transformer.SingleFrameGlTextureProcessor; +import java.io.IOException; +import java.util.Locale; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; + +/** + * A {@link SingleFrameGlTextureProcessor} that overlays a bitmap with a logo and timer on each + * frame. + * + *

The bitmap is drawn using an Android {@link Canvas}. + */ +// TODO(b/227625365): Delete this class and use a texture processor from the Transformer library, +// once overlaying a bitmap and text is supported in Transformer. +/* package */ final class BitmapOverlayProcessor implements SingleFrameGlTextureProcessor { + static { + GlUtil.glAssertionsEnabled = true; + } + + private static final String VERTEX_SHADER_PATH = "vertex_shader_copy_es2.glsl"; + private static final String FRAGMENT_SHADER_PATH = "fragment_shader_bitmap_overlay_es2.glsl"; + + private static final int BITMAP_WIDTH_HEIGHT = 512; + + private final Paint paint; + private final Bitmap overlayBitmap; + private final Canvas overlayCanvas; + + private float bitmapScaleX; + private float bitmapScaleY; + private int bitmapTexId; + private @MonotonicNonNull Size outputSize; + private @MonotonicNonNull Bitmap logoBitmap; + private @MonotonicNonNull GlProgram glProgram; + + public BitmapOverlayProcessor() { + paint = new Paint(); + paint.setTextSize(64); + paint.setAntiAlias(true); + paint.setARGB(0xFF, 0xFF, 0xFF, 0xFF); + paint.setColor(Color.GRAY); + overlayBitmap = + Bitmap.createBitmap(BITMAP_WIDTH_HEIGHT, BITMAP_WIDTH_HEIGHT, Bitmap.Config.ARGB_8888); + overlayCanvas = new Canvas(overlayBitmap); + } + + @Override + public void initialize(Context context, int inputTexId, int inputWidth, int inputHeight) + throws IOException { + if (inputWidth > inputHeight) { + bitmapScaleX = inputWidth / (float) inputHeight; + bitmapScaleY = 1f; + } else { + bitmapScaleX = 1f; + bitmapScaleY = inputHeight / (float) inputWidth; + } + outputSize = new Size(inputWidth, inputHeight); + + try { + logoBitmap = + ((BitmapDrawable) + context.getPackageManager().getApplicationIcon(context.getPackageName())) + .getBitmap(); + } catch (PackageManager.NameNotFoundException e) { + throw new IllegalStateException(e); + } + bitmapTexId = GlUtil.createTexture(BITMAP_WIDTH_HEIGHT, BITMAP_WIDTH_HEIGHT); + GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, /* level= */ 0, overlayBitmap, /* border= */ 0); + + glProgram = new GlProgram(context, VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH); + // Draw the frame on the entire normalized device coordinate space, from -1 to 1, for x and y. + glProgram.setBufferAttribute( + "aFramePosition", + GlUtil.getNormalizedCoordinateBounds(), + GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE); + glProgram.setSamplerTexIdUniform("uTexSampler0", inputTexId, /* texUnitIndex= */ 0); + glProgram.setSamplerTexIdUniform("uTexSampler1", bitmapTexId, /* texUnitIndex= */ 1); + glProgram.setFloatUniform("uScaleX", bitmapScaleX); + glProgram.setFloatUniform("uScaleY", bitmapScaleY); + } + + @Override + public Size getOutputSize() { + return checkStateNotNull(outputSize); + } + + @Override + public void drawFrame(long presentationTimeUs) throws FrameProcessingException { + try { + checkStateNotNull(glProgram).use(); + + // Draw to the canvas and store it in a texture. + String text = + String.format(Locale.US, "%.02f", presentationTimeUs / (float) C.MICROS_PER_SECOND); + overlayBitmap.eraseColor(Color.TRANSPARENT); + overlayCanvas.drawBitmap(checkStateNotNull(logoBitmap), /* left= */ 3, /* top= */ 378, paint); + overlayCanvas.drawText(text, /* x= */ 160, /* y= */ 466, paint); + GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, bitmapTexId); + GLUtils.texSubImage2D( + GLES20.GL_TEXTURE_2D, + /* level= */ 0, + /* xoffset= */ 0, + /* yoffset= */ 0, + flipBitmapVertically(overlayBitmap)); + GlUtil.checkGlError(); + + glProgram.bindAttributesAndUniforms(); + // The four-vertex triangle strip forms a quad. + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4); + GlUtil.checkGlError(); + } catch (GlUtil.GlException e) { + throw new FrameProcessingException(e); + } + } + + @Override + public void release() { + if (glProgram != null) { + glProgram.delete(); + } + } + + private static Bitmap flipBitmapVertically(Bitmap bitmap) { + Matrix flip = new Matrix(); + flip.postScale(1f, -1f); + return Bitmap.createBitmap( + bitmap, + /* x= */ 0, + /* y= */ 0, + bitmap.getWidth(), + bitmap.getHeight(), + flip, + /* filter= */ true); + } +} diff --git a/demos/transformer/src/main/java/androidx/media3/demo/transformer/ConfigurationActivity.java b/demos/transformer/src/main/java/androidx/media3/demo/transformer/ConfigurationActivity.java index 97fdc6e6d2..026f396091 100644 --- a/demos/transformer/src/main/java/androidx/media3/demo/transformer/ConfigurationActivity.java +++ b/demos/transformer/src/main/java/androidx/media3/demo/transformer/ConfigurationActivity.java @@ -32,7 +32,11 @@ import android.widget.TextView; import androidx.annotation.Nullable; import androidx.appcompat.app.AlertDialog; import androidx.appcompat.app.AppCompatActivity; +import androidx.media3.common.C; import androidx.media3.common.MimeTypes; +import androidx.media3.common.util.Util; +import com.google.android.material.slider.RangeSlider; +import com.google.android.material.slider.Slider; import java.util.Arrays; import java.util.List; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -49,39 +53,84 @@ public final class ConfigurationActivity extends AppCompatActivity { public static final String AUDIO_MIME_TYPE = "audio_mime_type"; public static final String VIDEO_MIME_TYPE = "video_mime_type"; public static final String RESOLUTION_HEIGHT = "resolution_height"; - public static final String TRANSLATE_X = "translate_x"; - public static final String TRANSLATE_Y = "translate_y"; public static final String SCALE_X = "scale_x"; public static final String SCALE_Y = "scale_y"; public static final String ROTATE_DEGREES = "rotate_degrees"; + public static final String TRIM_START_MS = "trim_start_ms"; + public static final String TRIM_END_MS = "trim_end_ms"; + public static final String ENABLE_FALLBACK = "enable_fallback"; + public static final String ENABLE_REQUEST_SDR_TONE_MAPPING = "enable_request_sdr_tone_mapping"; public static final String ENABLE_HDR_EDITING = "enable_hdr_editing"; + public static final String DEMO_EFFECTS_SELECTIONS = "demo_effects_selections"; + public static final String PERIODIC_VIGNETTE_CENTER_X = "periodic_vignette_center_x"; + public static final String PERIODIC_VIGNETTE_CENTER_Y = "periodic_vignette_center_y"; + public static final String PERIODIC_VIGNETTE_INNER_RADIUS = "periodic_vignette_inner_radius"; + public static final String PERIODIC_VIGNETTE_OUTER_RADIUS = "periodic_vignette_outer_radius"; private static final String[] INPUT_URIS = { - "https://html5demos.com/assets/dizzy.mp4", + "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://storage.googleapis.com/exoplayer-test-media-0/BigBuckBunny_320x180.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-hdr-hdr10.mp4", }; private static final String[] URI_DESCRIPTIONS = { // same order as INPUT_URIS - "MP4 with H264 video and AAC audio", - "MP4 with H265 video and AAC audio", - "Long MP4 with H264 video and AAC audio", - "WebM with VP8 video and Vorbis audio", + "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\u00B0)", + "H264 video and AAC audio (portrait, H < W, 90\u00B0)", + "SEF slow motion with 240 fps", + "480p DASH (non-square pixels)", + "HDR (HDR10) H265 video (encoding may fail)", }; + private static final String[] DEMO_EFFECTS = { + "Dizzy crop", + "Edge detector (Media Pipe)", + "Periodic vignette", + "3D spin", + "Overlay logo & timer", + "Zoom in start", + }; + private static final int PERIODIC_VIGNETTE_INDEX = 2; private static final String SAME_AS_INPUT_OPTION = "same as input"; + private static final float HALF_DIAGONAL = 1f / (float) Math.sqrt(2); - private @MonotonicNonNull Button chooseFileButton; - private @MonotonicNonNull TextView chosenFileTextView; + private @MonotonicNonNull Button selectFileButton; + private @MonotonicNonNull TextView selectedFileTextView; private @MonotonicNonNull CheckBox removeAudioCheckbox; private @MonotonicNonNull CheckBox removeVideoCheckbox; private @MonotonicNonNull CheckBox flattenForSlowMotionCheckbox; private @MonotonicNonNull Spinner audioMimeSpinner; private @MonotonicNonNull Spinner videoMimeSpinner; private @MonotonicNonNull Spinner resolutionHeightSpinner; - private @MonotonicNonNull Spinner translateSpinner; private @MonotonicNonNull Spinner scaleSpinner; private @MonotonicNonNull Spinner rotateSpinner; + private @MonotonicNonNull CheckBox trimCheckBox; + private @MonotonicNonNull CheckBox enableFallbackCheckBox; + private @MonotonicNonNull CheckBox enableRequestSdrToneMappingCheckBox; private @MonotonicNonNull CheckBox enableHdrEditingCheckBox; + private @MonotonicNonNull Button selectDemoEffectsButton; + private boolean @MonotonicNonNull [] demoEffectsSelections; private int inputUriPosition; + private long trimStartMs; + private long trimEndMs; + private float periodicVignetteCenterX; + private float periodicVignetteCenterY; + private float periodicVignetteInnerRadius; + private float periodicVignetteOuterRadius; @Override protected void onCreate(@Nullable Bundle savedInstanceState) { @@ -90,11 +139,11 @@ public final class ConfigurationActivity extends AppCompatActivity { findViewById(R.id.transform_button).setOnClickListener(this::startTransformation); - chooseFileButton = findViewById(R.id.choose_file_button); - chooseFileButton.setOnClickListener(this::chooseFile); + selectFileButton = findViewById(R.id.select_file_button); + selectFileButton.setOnClickListener(this::selectFile); - chosenFileTextView = findViewById(R.id.chosen_file_text_view); - chosenFileTextView.setText(URI_DESCRIPTIONS[inputUriPosition]); + selectedFileTextView = findViewById(R.id.selected_file_text_view); + selectedFileTextView.setText(URI_DESCRIPTIONS[inputUriPosition]); removeAudioCheckbox = findViewById(R.id.remove_audio_checkbox); removeAudioCheckbox.setOnClickListener(this::onRemoveAudio); @@ -118,11 +167,10 @@ public final class ConfigurationActivity extends AppCompatActivity { videoMimeSpinner = findViewById(R.id.video_mime_spinner); videoMimeSpinner.setAdapter(videoMimeAdapter); videoMimeAdapter.addAll( - SAME_AS_INPUT_OPTION, - MimeTypes.VIDEO_H263, - MimeTypes.VIDEO_H264, - MimeTypes.VIDEO_H265, - MimeTypes.VIDEO_MP4V); + SAME_AS_INPUT_OPTION, MimeTypes.VIDEO_H263, MimeTypes.VIDEO_H264, MimeTypes.VIDEO_MP4V); + if (Util.SDK_INT >= 24) { + videoMimeAdapter.add(MimeTypes.VIDEO_H265); + } ArrayAdapter resolutionHeightAdapter = new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item); @@ -132,14 +180,6 @@ public final class ConfigurationActivity extends AppCompatActivity { resolutionHeightAdapter.addAll( SAME_AS_INPUT_OPTION, "144", "240", "360", "480", "720", "1080", "1440", "2160"); - ArrayAdapter translateAdapter = - new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item); - translateAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); - translateSpinner = findViewById(R.id.translate_spinner); - translateSpinner.setAdapter(translateAdapter); - translateAdapter.addAll( - SAME_AS_INPUT_OPTION, "-.1, -.1", "0, 0", ".5, 0", "0, .5", "1, 1", "1.9, 0", "0, 1.9"); - ArrayAdapter scaleAdapter = new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item); scaleAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); @@ -152,9 +192,22 @@ public final class ConfigurationActivity extends AppCompatActivity { rotateAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item); rotateSpinner = findViewById(R.id.rotate_spinner); rotateSpinner.setAdapter(rotateAdapter); - rotateAdapter.addAll(SAME_AS_INPUT_OPTION, "0", "10", "45", "90", "180"); + rotateAdapter.addAll(SAME_AS_INPUT_OPTION, "0", "10", "45", "60", "90", "180"); + trimCheckBox = findViewById(R.id.trim_checkbox); + trimCheckBox.setOnCheckedChangeListener(this::selectTrimBounds); + trimStartMs = C.TIME_UNSET; + trimEndMs = C.TIME_UNSET; + + enableFallbackCheckBox = findViewById(R.id.enable_fallback_checkbox); + enableRequestSdrToneMappingCheckBox = findViewById(R.id.request_sdr_tone_mapping_checkbox); + enableRequestSdrToneMappingCheckBox.setEnabled(isRequestSdrToneMappingSupported()); + findViewById(R.id.request_sdr_tone_mapping).setEnabled(isRequestSdrToneMappingSupported()); enableHdrEditingCheckBox = findViewById(R.id.hdr_editing_checkbox); + + demoEffectsSelections = new boolean[DEMO_EFFECTS.length]; + selectDemoEffectsButton = findViewById(R.id.select_demo_effects_button); + selectDemoEffectsButton.setOnClickListener(this::selectDemoEffects); } @Override @@ -162,8 +215,8 @@ public final class ConfigurationActivity extends AppCompatActivity { super.onResume(); @Nullable Uri intentUri = getIntent().getData(); if (intentUri != null) { - checkNotNull(chooseFileButton).setEnabled(false); - checkNotNull(chosenFileTextView).setText(intentUri.toString()); + checkNotNull(selectFileButton).setEnabled(false); + checkNotNull(selectedFileTextView).setText(intentUri.toString()); } } @@ -180,13 +233,16 @@ public final class ConfigurationActivity extends AppCompatActivity { "audioMimeSpinner", "videoMimeSpinner", "resolutionHeightSpinner", - "translateSpinner", "scaleSpinner", "rotateSpinner", - "enableHdrEditingCheckBox" + "trimCheckBox", + "enableFallbackCheckBox", + "enableRequestSdrToneMappingCheckBox", + "enableHdrEditingCheckBox", + "demoEffectsSelections" }) private void startTransformation(View view) { - Intent transformerIntent = new Intent(this, TransformerActivity.class); + Intent transformerIntent = new Intent(/* packageContext= */ this, TransformerActivity.class); Bundle bundle = new Bundle(); bundle.putBoolean(SHOULD_REMOVE_AUDIO, removeAudioCheckbox.isChecked()); bundle.putBoolean(SHOULD_REMOVE_VIDEO, removeVideoCheckbox.isChecked()); @@ -203,13 +259,6 @@ public final class ConfigurationActivity extends AppCompatActivity { if (!SAME_AS_INPUT_OPTION.equals(selectedResolutionHeight)) { bundle.putInt(RESOLUTION_HEIGHT, Integer.parseInt(selectedResolutionHeight)); } - String selectedTranslate = String.valueOf(translateSpinner.getSelectedItem()); - if (!SAME_AS_INPUT_OPTION.equals(selectedTranslate)) { - List translateXY = Arrays.asList(selectedTranslate.split(", ")); - checkState(translateXY.size() == 2); - bundle.putFloat(TRANSLATE_X, Float.parseFloat(translateXY.get(0))); - bundle.putFloat(TRANSLATE_Y, Float.parseFloat(translateXY.get(1))); - } String selectedScale = String.valueOf(scaleSpinner.getSelectedItem()); if (!SAME_AS_INPUT_OPTION.equals(selectedScale)) { List scaleXY = Arrays.asList(selectedScale.split(", ")); @@ -221,7 +270,19 @@ public final class ConfigurationActivity extends AppCompatActivity { if (!SAME_AS_INPUT_OPTION.equals(selectedRotate)) { bundle.putFloat(ROTATE_DEGREES, Float.parseFloat(selectedRotate)); } + if (trimCheckBox.isChecked()) { + bundle.putLong(TRIM_START_MS, trimStartMs); + bundle.putLong(TRIM_END_MS, trimEndMs); + } + bundle.putBoolean(ENABLE_FALLBACK, enableFallbackCheckBox.isChecked()); + bundle.putBoolean( + ENABLE_REQUEST_SDR_TONE_MAPPING, enableRequestSdrToneMappingCheckBox.isChecked()); bundle.putBoolean(ENABLE_HDR_EDITING, enableHdrEditingCheckBox.isChecked()); + bundle.putBooleanArray(DEMO_EFFECTS_SELECTIONS, demoEffectsSelections); + bundle.putFloat(PERIODIC_VIGNETTE_CENTER_X, periodicVignetteCenterX); + bundle.putFloat(PERIODIC_VIGNETTE_CENTER_Y, periodicVignetteCenterY); + bundle.putFloat(PERIODIC_VIGNETTE_INNER_RADIUS, periodicVignetteInnerRadius); + bundle.putFloat(PERIODIC_VIGNETTE_OUTER_RADIUS, periodicVignetteOuterRadius); transformerIntent.putExtras(bundle); @Nullable Uri intentUri = getIntent().getData(); @@ -231,19 +292,82 @@ public final class ConfigurationActivity extends AppCompatActivity { startActivity(transformerIntent); } - private void chooseFile(View view) { + private void selectFile(View view) { new AlertDialog.Builder(/* context= */ this) - .setTitle(R.string.choose_file_title) + .setTitle(R.string.select_file_title) .setSingleChoiceItems(URI_DESCRIPTIONS, inputUriPosition, this::selectFileInDialog) .setPositiveButton(android.R.string.ok, /* listener= */ null) .create() .show(); } - @RequiresNonNull("chosenFileTextView") + private void selectDemoEffects(View view) { + new AlertDialog.Builder(/* context= */ this) + .setTitle(R.string.select_demo_effects) + .setMultiChoiceItems( + DEMO_EFFECTS, checkNotNull(demoEffectsSelections), this::selectDemoEffect) + .setPositiveButton(android.R.string.ok, /* listener= */ null) + .create() + .show(); + } + + private void selectTrimBounds(View view, boolean isChecked) { + if (!isChecked) { + return; + } + View dialogView = getLayoutInflater().inflate(R.layout.trim_options, /* root= */ null); + RangeSlider radiusRangeSlider = + checkNotNull(dialogView.findViewById(R.id.trim_bounds_range_slider)); + radiusRangeSlider.setValues(0f, 60f); // seconds + new AlertDialog.Builder(/* context= */ this) + .setView(dialogView) + .setPositiveButton( + android.R.string.ok, + (DialogInterface dialogInterface, int i) -> { + List radiusRange = radiusRangeSlider.getValues(); + trimStartMs = 1000 * radiusRange.get(0).longValue(); + trimEndMs = 1000 * radiusRange.get(1).longValue(); + }) + .create() + .show(); + } + + @RequiresNonNull("selectedFileTextView") private void selectFileInDialog(DialogInterface dialog, int which) { inputUriPosition = which; - chosenFileTextView.setText(URI_DESCRIPTIONS[inputUriPosition]); + selectedFileTextView.setText(URI_DESCRIPTIONS[inputUriPosition]); + } + + @RequiresNonNull("demoEffectsSelections") + private void selectDemoEffect(DialogInterface dialog, int which, boolean isChecked) { + demoEffectsSelections[which] = isChecked; + if (!isChecked || which != PERIODIC_VIGNETTE_INDEX) { + return; + } + + View dialogView = + getLayoutInflater().inflate(R.layout.periodic_vignette_options, /* root= */ null); + Slider centerXSlider = + checkNotNull(dialogView.findViewById(R.id.periodic_vignette_center_x_slider)); + Slider centerYSlider = + checkNotNull(dialogView.findViewById(R.id.periodic_vignette_center_y_slider)); + RangeSlider radiusRangeSlider = + checkNotNull(dialogView.findViewById(R.id.periodic_vignette_radius_range_slider)); + radiusRangeSlider.setValues(0f, HALF_DIAGONAL); + new AlertDialog.Builder(/* context= */ this) + .setTitle(R.string.periodic_vignette_options) + .setView(dialogView) + .setPositiveButton( + android.R.string.ok, + (DialogInterface dialogInterface, int i) -> { + periodicVignetteCenterX = centerXSlider.getValue(); + periodicVignetteCenterY = centerYSlider.getValue(); + List radiusRange = radiusRangeSlider.getValues(); + periodicVignetteInnerRadius = radiusRange.get(0); + periodicVignetteOuterRadius = radiusRange.get(1); + }) + .create() + .show(); } @RequiresNonNull({ @@ -251,10 +375,11 @@ public final class ConfigurationActivity extends AppCompatActivity { "audioMimeSpinner", "videoMimeSpinner", "resolutionHeightSpinner", - "translateSpinner", "scaleSpinner", "rotateSpinner", - "enableHdrEditingCheckBox" + "enableRequestSdrToneMappingCheckBox", + "enableHdrEditingCheckBox", + "selectDemoEffectsButton" }) private void onRemoveAudio(View view) { if (((CheckBox) view).isChecked()) { @@ -270,10 +395,11 @@ public final class ConfigurationActivity extends AppCompatActivity { "audioMimeSpinner", "videoMimeSpinner", "resolutionHeightSpinner", - "translateSpinner", "scaleSpinner", "rotateSpinner", - "enableHdrEditingCheckBox" + "enableRequestSdrToneMappingCheckBox", + "enableHdrEditingCheckBox", + "selectDemoEffectsButton" }) private void onRemoveVideo(View view) { if (((CheckBox) view).isChecked()) { @@ -288,26 +414,34 @@ public final class ConfigurationActivity extends AppCompatActivity { "audioMimeSpinner", "videoMimeSpinner", "resolutionHeightSpinner", - "translateSpinner", "scaleSpinner", "rotateSpinner", - "enableHdrEditingCheckBox" + "enableRequestSdrToneMappingCheckBox", + "enableHdrEditingCheckBox", + "selectDemoEffectsButton" }) private void enableTrackSpecificOptions(boolean isAudioEnabled, boolean isVideoEnabled) { audioMimeSpinner.setEnabled(isAudioEnabled); videoMimeSpinner.setEnabled(isVideoEnabled); resolutionHeightSpinner.setEnabled(isVideoEnabled); - translateSpinner.setEnabled(isVideoEnabled); scaleSpinner.setEnabled(isVideoEnabled); rotateSpinner.setEnabled(isVideoEnabled); + enableRequestSdrToneMappingCheckBox.setEnabled( + isRequestSdrToneMappingSupported() && isVideoEnabled); enableHdrEditingCheckBox.setEnabled(isVideoEnabled); + selectDemoEffectsButton.setEnabled(isVideoEnabled); findViewById(R.id.audio_mime_text_view).setEnabled(isAudioEnabled); findViewById(R.id.video_mime_text_view).setEnabled(isVideoEnabled); findViewById(R.id.resolution_height_text_view).setEnabled(isVideoEnabled); - findViewById(R.id.translate).setEnabled(isVideoEnabled); findViewById(R.id.scale).setEnabled(isVideoEnabled); findViewById(R.id.rotate).setEnabled(isVideoEnabled); + findViewById(R.id.request_sdr_tone_mapping) + .setEnabled(isRequestSdrToneMappingSupported() && isVideoEnabled); findViewById(R.id.hdr_editing).setEnabled(isVideoEnabled); } + + private static boolean isRequestSdrToneMappingSupported() { + return Util.SDK_INT >= 31; + } } diff --git a/demos/transformer/src/main/java/androidx/media3/demo/transformer/MatrixTransformationFactory.java b/demos/transformer/src/main/java/androidx/media3/demo/transformer/MatrixTransformationFactory.java new file mode 100644 index 0000000000..2aded6a470 --- /dev/null +++ b/demos/transformer/src/main/java/androidx/media3/demo/transformer/MatrixTransformationFactory.java @@ -0,0 +1,93 @@ +/* + * Copyright 2022 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.transformer; + +import android.graphics.Matrix; +import androidx.media3.common.C; +import androidx.media3.common.util.Util; +import androidx.media3.transformer.GlMatrixTransformation; +import androidx.media3.transformer.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 {@value + * #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 = 1_500_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/transformer/src/main/java/androidx/media3/demo/transformer/PeriodicVignetteProcessor.java b/demos/transformer/src/main/java/androidx/media3/demo/transformer/PeriodicVignetteProcessor.java new file mode 100644 index 0000000000..74c1a31294 --- /dev/null +++ b/demos/transformer/src/main/java/androidx/media3/demo/transformer/PeriodicVignetteProcessor.java @@ -0,0 +1,123 @@ +/* + * Copyright 2022 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.transformer; + +import static androidx.media3.common.util.Assertions.checkArgument; +import static androidx.media3.common.util.Assertions.checkStateNotNull; + +import android.content.Context; +import android.opengl.GLES20; +import android.util.Size; +import androidx.media3.common.util.GlProgram; +import androidx.media3.common.util.GlUtil; +import androidx.media3.transformer.FrameProcessingException; +import androidx.media3.transformer.SingleFrameGlTextureProcessor; +import java.io.IOException; +import org.checkerframework.checker.nullness.qual.MonotonicNonNull; + +/** + * A {@link SingleFrameGlTextureProcessor} that periodically dims the frames such that pixels are + * darker the further they are away from the frame center. + */ +/* package */ final class PeriodicVignetteProcessor implements SingleFrameGlTextureProcessor { + static { + GlUtil.glAssertionsEnabled = true; + } + + private static final String VERTEX_SHADER_PATH = "vertex_shader_copy_es2.glsl"; + private static final String FRAGMENT_SHADER_PATH = "fragment_shader_vignette_es2.glsl"; + private static final float DIMMING_PERIOD_US = 5_600_000f; + + private float centerX; + private float centerY; + private float minInnerRadius; + private float deltaInnerRadius; + private float outerRadius; + + private @MonotonicNonNull Size outputSize; + private @MonotonicNonNull GlProgram glProgram; + + /** + * Creates a new instance. + * + *

The inner radius of the vignette effect oscillates smoothly between {@code minInnerRadius} + * and {@code maxInnerRadius}. + * + *

The pixels between the inner radius and the {@code outerRadius} are darkened linearly based + * on their distance from {@code innerRadius}. All pixels outside {@code outerRadius} are black. + * + *

The parameters are given in normalized texture coordinates from 0 to 1. + * + * @param centerX The x-coordinate of the center of the effect. + * @param centerY The y-coordinate of the center of the effect. + * @param minInnerRadius The lower bound of the radius that is unaffected by the effect. + * @param maxInnerRadius The upper bound of the radius that is unaffected by the effect. + * @param outerRadius The radius after which all pixels are black. + */ + public PeriodicVignetteProcessor( + float centerX, float centerY, float minInnerRadius, float maxInnerRadius, float outerRadius) { + checkArgument(minInnerRadius <= maxInnerRadius); + checkArgument(maxInnerRadius <= outerRadius); + this.centerX = centerX; + this.centerY = centerY; + this.minInnerRadius = minInnerRadius; + this.deltaInnerRadius = maxInnerRadius - minInnerRadius; + this.outerRadius = outerRadius; + } + + @Override + public void initialize(Context context, int inputTexId, int inputWidth, int inputHeight) + throws IOException { + outputSize = new Size(inputWidth, inputHeight); + glProgram = new GlProgram(context, VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH); + glProgram.setSamplerTexIdUniform("uTexSampler", inputTexId, /* texUnitIndex= */ 0); + glProgram.setFloatsUniform("uCenter", new float[] {centerX, centerY}); + glProgram.setFloatsUniform("uOuterRadius", new float[] {outerRadius}); + // Draw the frame on the entire normalized device coordinate space, from -1 to 1, for x and y. + glProgram.setBufferAttribute( + "aFramePosition", + GlUtil.getNormalizedCoordinateBounds(), + GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE); + } + + @Override + public Size getOutputSize() { + return checkStateNotNull(outputSize); + } + + @Override + public void drawFrame(long presentationTimeUs) throws FrameProcessingException { + try { + checkStateNotNull(glProgram).use(); + double theta = presentationTimeUs * 2 * Math.PI / DIMMING_PERIOD_US; + float innerRadius = + minInnerRadius + deltaInnerRadius * (0.5f - 0.5f * (float) Math.cos(theta)); + glProgram.setFloatsUniform("uInnerRadius", new float[] {innerRadius}); + glProgram.bindAttributesAndUniforms(); + // The four-vertex triangle strip forms a quad. + GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4); + } catch (GlUtil.GlException e) { + throw new FrameProcessingException(e); + } + } + + @Override + public void release() { + if (glProgram != null) { + glProgram.delete(); + } + } +} diff --git a/demos/transformer/src/main/java/androidx/media3/demo/transformer/TransformerActivity.java b/demos/transformer/src/main/java/androidx/media3/demo/transformer/TransformerActivity.java index 0dcd9aa84d..594459e315 100644 --- a/demos/transformer/src/main/java/androidx/media3/demo/transformer/TransformerActivity.java +++ b/demos/transformer/src/main/java/androidx/media3/demo/transformer/TransformerActivity.java @@ -21,7 +21,6 @@ import static androidx.media3.common.util.Assertions.checkNotNull; import android.app.Activity; import android.content.Intent; import android.content.pm.PackageManager; -import android.graphics.Matrix; import android.net.Uri; import android.os.Bundle; import android.os.Handler; @@ -32,6 +31,7 @@ import android.view.ViewGroup; import android.widget.TextView; import android.widget.Toast; import androidx.annotation.Nullable; +import androidx.annotation.StringRes; import androidx.appcompat.app.AppCompatActivity; import androidx.media3.common.C; import androidx.media3.common.MediaItem; @@ -39,17 +39,24 @@ import androidx.media3.common.util.Log; import androidx.media3.common.util.Util; import androidx.media3.exoplayer.ExoPlayer; import androidx.media3.exoplayer.util.DebugTextViewHelper; +import androidx.media3.transformer.DefaultEncoderFactory; +import androidx.media3.transformer.EncoderSelector; +import androidx.media3.transformer.GlEffect; import androidx.media3.transformer.ProgressHolder; +import androidx.media3.transformer.SingleFrameGlTextureProcessor; import androidx.media3.transformer.TransformationException; import androidx.media3.transformer.TransformationRequest; +import androidx.media3.transformer.TransformationResult; import androidx.media3.transformer.Transformer; import androidx.media3.ui.AspectRatioFrameLayout; import androidx.media3.ui.PlayerView; import com.google.android.material.progressindicator.LinearProgressIndicator; 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.lang.reflect.Constructor; import java.util.concurrent.CountDownLatch; import java.util.concurrent.TimeUnit; import org.checkerframework.checker.nullness.qual.MonotonicNonNull; @@ -145,9 +152,10 @@ public final class TransformerActivity extends AppCompatActivity { externalCacheFile = createExternalCacheFile("transformer-output.mp4"); String filePath = externalCacheFile.getAbsolutePath(); @Nullable Bundle bundle = intent.getExtras(); + MediaItem mediaItem = createMediaItem(bundle, uri); Transformer transformer = createTransformer(bundle, filePath); transformationStopwatch.start(); - transformer.startTransformation(MediaItem.fromUri(uri), filePath); + transformer.startTransformation(mediaItem, filePath); this.transformer = transformer; } catch (IOException e) { throw new IllegalStateException(e); @@ -174,6 +182,24 @@ public final class TransformerActivity extends AppCompatActivity { }); } + private MediaItem createMediaItem(@Nullable Bundle bundle, Uri uri) { + MediaItem.Builder mediaItemBuilder = new MediaItem.Builder().setUri(uri); + if (bundle != null) { + long trimStartMs = + bundle.getLong(ConfigurationActivity.TRIM_START_MS, /* defaultValue= */ C.TIME_UNSET); + long trimEndMs = + bundle.getLong(ConfigurationActivity.TRIM_END_MS, /* defaultValue= */ C.TIME_UNSET); + if (trimStartMs != C.TIME_UNSET && trimEndMs != C.TIME_UNSET) { + mediaItemBuilder.setClippingConfiguration( + new MediaItem.ClippingConfiguration.Builder() + .setStartPositionMs(trimStartMs) + .setEndPositionMs(trimEndMs) + .build()); + } + } + return mediaItemBuilder.build(); + } + // Create a cache file, resetting it if it already exists. private File createExternalCacheFile(String fileName) throws IOException { File file = new File(getExternalCacheDir(), fileName); @@ -214,22 +240,88 @@ public final class TransformerActivity extends AppCompatActivity { if (resolutionHeight != C.LENGTH_UNSET) { requestBuilder.setResolution(resolutionHeight); } - Matrix transformationMatrix = getTransformationMatrix(bundle); - if (!transformationMatrix.isIdentity()) { - requestBuilder.setTransformationMatrix(transformationMatrix); - } + + float scaleX = bundle.getFloat(ConfigurationActivity.SCALE_X, /* defaultValue= */ 1); + float scaleY = bundle.getFloat(ConfigurationActivity.SCALE_Y, /* defaultValue= */ 1); + requestBuilder.setScale(scaleX, scaleY); + + float rotateDegrees = + bundle.getFloat(ConfigurationActivity.ROTATE_DEGREES, /* defaultValue= */ 0); + requestBuilder.setRotationDegrees(rotateDegrees); + + requestBuilder.setEnableRequestSdrToneMapping( + bundle.getBoolean(ConfigurationActivity.ENABLE_REQUEST_SDR_TONE_MAPPING)); requestBuilder.experimental_setEnableHdrEditing( bundle.getBoolean(ConfigurationActivity.ENABLE_HDR_EDITING)); transformerBuilder .setTransformationRequest(requestBuilder.build()) .setRemoveAudio(bundle.getBoolean(ConfigurationActivity.SHOULD_REMOVE_AUDIO)) - .setRemoveVideo(bundle.getBoolean(ConfigurationActivity.SHOULD_REMOVE_VIDEO)); + .setRemoveVideo(bundle.getBoolean(ConfigurationActivity.SHOULD_REMOVE_VIDEO)) + .setEncoderFactory( + new DefaultEncoderFactory( + EncoderSelector.DEFAULT, + /* enableFallback= */ bundle.getBoolean(ConfigurationActivity.ENABLE_FALLBACK))); + + ImmutableList.Builder effects = new ImmutableList.Builder<>(); + @Nullable + boolean[] selectedEffects = + bundle.getBooleanArray(ConfigurationActivity.DEMO_EFFECTS_SELECTIONS); + if (selectedEffects != null) { + if (selectedEffects[0]) { + effects.add(MatrixTransformationFactory.createDizzyCropEffect()); + } + if (selectedEffects[1]) { + try { + Class clazz = Class.forName("androidx.media3.demo.transformer.MediaPipeProcessor"); + Constructor constructor = + clazz.getConstructor(String.class, String.class, String.class); + effects.add( + () -> { + try { + return (SingleFrameGlTextureProcessor) + constructor.newInstance( + /* graphName= */ "edge_detector_mediapipe_graph.binarypb", + /* inputStreamName= */ "input_video", + /* outputStreamName= */ "output_video"); + } catch (Exception e) { + runOnUiThread(() -> showToast(R.string.no_media_pipe_error)); + throw new RuntimeException("Failed to load MediaPipe processor", e); + } + }); + } catch (Exception e) { + showToast(R.string.no_media_pipe_error); + } + } + if (selectedEffects[2]) { + effects.add( + () -> + new PeriodicVignetteProcessor( + bundle.getFloat(ConfigurationActivity.PERIODIC_VIGNETTE_CENTER_X), + bundle.getFloat(ConfigurationActivity.PERIODIC_VIGNETTE_CENTER_Y), + /* minInnerRadius= */ bundle.getFloat( + ConfigurationActivity.PERIODIC_VIGNETTE_INNER_RADIUS), + /* maxInnerRadius= */ bundle.getFloat( + ConfigurationActivity.PERIODIC_VIGNETTE_OUTER_RADIUS), + bundle.getFloat(ConfigurationActivity.PERIODIC_VIGNETTE_OUTER_RADIUS))); + } + if (selectedEffects[3]) { + effects.add(MatrixTransformationFactory.createSpin3dEffect()); + } + if (selectedEffects[4]) { + effects.add(BitmapOverlayProcessor::new); + } + if (selectedEffects[5]) { + effects.add(MatrixTransformationFactory.createZoomInTransition()); + } + transformerBuilder.setVideoFrameEffects(effects.build()); + } } return transformerBuilder .addListener( new Transformer.Listener() { @Override - public void onTransformationCompleted(MediaItem mediaItem) { + public void onTransformationCompleted( + MediaItem mediaItem, TransformationResult transformationResult) { TransformerActivity.this.onTransformationCompleted(filePath); } @@ -243,26 +335,6 @@ public final class TransformerActivity extends AppCompatActivity { .build(); } - private static Matrix getTransformationMatrix(Bundle bundle) { - Matrix transformationMatrix = new Matrix(); - - float translateX = bundle.getFloat(ConfigurationActivity.TRANSLATE_X, /* defaultValue= */ 0); - float translateY = bundle.getFloat(ConfigurationActivity.TRANSLATE_Y, /* defaultValue= */ 0); - // TODO(b/213198690): Get resolution for aspect ratio and scale all translations' translateX - // by this aspect ratio. - transformationMatrix.postTranslate(translateX, translateY); - - float scaleX = bundle.getFloat(ConfigurationActivity.SCALE_X, /* defaultValue= */ 1); - float scaleY = bundle.getFloat(ConfigurationActivity.SCALE_Y, /* defaultValue= */ 1); - transformationMatrix.postScale(scaleX, scaleY); - - float rotateDegrees = - bundle.getFloat(ConfigurationActivity.ROTATE_DEGREES, /* defaultValue= */ 0); - transformationMatrix.postRotate(rotateDegrees); - - return transformationMatrix; - } - @RequiresNonNull({ "informationTextView", "progressViewGroup", @@ -335,6 +407,10 @@ public final class TransformerActivity extends AppCompatActivity { } } + private void showToast(@StringRes int messageResource) { + Toast.makeText(getApplicationContext(), getString(messageResource), Toast.LENGTH_LONG).show(); + } + private final class DemoDebugViewProvider implements Transformer.DebugViewProvider { @Nullable diff --git a/demos/transformer/src/main/res/layout/configuration_activity.xml b/demos/transformer/src/main/res/layout/configuration_activity.xml index b50d5eaba0..2879d6a637 100644 --- a/demos/transformer/src/main/res/layout/configuration_activity.xml +++ b/demos/transformer/src/main/res/layout/configuration_activity.xml @@ -34,18 +34,18 @@ app:layout_constraintStart_toStartOf="parent" app:layout_constraintTop_toTopOf="parent" />