mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
commit
2c7201024e
42
.github/ISSUE_TEMPLATE/bug.md
vendored
42
.github/ISSUE_TEMPLATE/bug.md
vendored
@ -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).
|
|
100
.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
100
.github/ISSUE_TEMPLATE/bug.yml
vendored
Normal file
@ -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 #\<issuenumber\>' after filing this issue, and note that you will do this here.
|
||||||
|
* If you are certain the issue does not depend on the media being played, enter "Not applicable" here.
|
||||||
|
|
||||||
|
For DRM-protected media please also include the scheme and license server URL.
|
||||||
|
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 #\<issuenumber\>'.
|
||||||
|
|
||||||
|
**Note:** Logcat output is **not** the same as a full bug report, and is often missing information that's useful for diagnosing issues. Please ensure you're sending a full bug report zip file.
|
||||||
|
options:
|
||||||
|
- label: You will email the zip file produced by `adb bugreport` to dev.exoplayer@gmail.com after filing this issue.
|
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
1
.github/ISSUE_TEMPLATE/config.yml
vendored
Normal file
@ -0,0 +1 @@
|
|||||||
|
blank_issues_enabled: false
|
228
RELEASENOTES.md
228
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<Cue>`.
|
||||||
|
* 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)
|
### 1.0.0-alpha03 (2022-03-14)
|
||||||
|
|
||||||
|
@ -17,7 +17,7 @@ buildscript {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
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 'com.google.android.gms:strict-version-matcher-plugin:1.2.2'
|
||||||
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.21'
|
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.21'
|
||||||
}
|
}
|
||||||
|
@ -29,5 +29,10 @@ android {
|
|||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
|
|
||||||
testOptions.unitTests.includeAndroidResources = true
|
testOptions {
|
||||||
|
unitTests.all {
|
||||||
|
jvmArgs "-Xmx2g"
|
||||||
|
}
|
||||||
|
unitTests.includeAndroidResources true
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -12,21 +12,21 @@
|
|||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
project.ext {
|
project.ext {
|
||||||
releaseVersion = '1.0.0-alpha03'
|
releaseVersion = '1.0.0-beta01'
|
||||||
releaseVersionCode = 1_000_000_0_03
|
releaseVersionCode = 1_000_000_1_01
|
||||||
minSdkVersion = 16
|
minSdkVersion = 16
|
||||||
appTargetSdkVersion = 29
|
appTargetSdkVersion = 29
|
||||||
// Upgrading this requires [Internal ref: b/193254928] to be fixed, or some
|
// Upgrading this requires [Internal ref: b/193254928] to be fixed, or some
|
||||||
// additional robolectric config.
|
// additional robolectric config.
|
||||||
targetSdkVersion = 30
|
targetSdkVersion = 30
|
||||||
compileSdkVersion = 31
|
compileSdkVersion = 32
|
||||||
dexmakerVersion = '2.28.1'
|
dexmakerVersion = '2.28.1'
|
||||||
junitVersion = '4.13.2'
|
junitVersion = '4.13.2'
|
||||||
// Use the same Guava version as the Android repo:
|
// Use the same Guava version as the Android repo:
|
||||||
// https://cs.android.com/android/platform/superproject/+/master:external/guava/METADATA
|
// https://cs.android.com/android/platform/superproject/+/master:external/guava/METADATA
|
||||||
guavaVersion = '31.0.1-android'
|
guavaVersion = '31.0.1-android'
|
||||||
mockitoVersion = '3.12.4'
|
mockitoVersion = '3.12.4'
|
||||||
robolectricVersion = '4.6.1'
|
robolectricVersion = '4.8.1'
|
||||||
// Keep this in sync with Google's internal Checker Framework version.
|
// Keep this in sync with Google's internal Checker Framework version.
|
||||||
checkerframeworkVersion = '3.13.0'
|
checkerframeworkVersion = '3.13.0'
|
||||||
checkerframeworkCompatVersion = '2.5.5'
|
checkerframeworkCompatVersion = '2.5.5'
|
||||||
|
@ -87,3 +87,7 @@ include modulePrefix + 'test-data'
|
|||||||
project(modulePrefix + 'test-data').projectDir = new File(rootDir, 'libraries/test_data')
|
project(modulePrefix + 'test-data').projectDir = new File(rootDir, 'libraries/test_data')
|
||||||
include modulePrefix + 'test-utils'
|
include modulePrefix + 'test-utils'
|
||||||
project(modulePrefix + 'test-utils').projectDir = new File(rootDir, 'libraries/test_utils')
|
project(modulePrefix + 'test-utils').projectDir = new File(rootDir, 'libraries/test_utils')
|
||||||
|
include modulePrefix + 'test-session-common'
|
||||||
|
project(modulePrefix + 'test-session-common').projectDir = new File(rootDir, 'libraries/test_session_common')
|
||||||
|
include modulePrefix + 'test-session-current'
|
||||||
|
project(modulePrefix + 'test-session-current').projectDir = new File(rootDir, 'libraries/test_session_current')
|
||||||
|
@ -230,8 +230,8 @@ public class MainActivity extends AppCompatActivity
|
|||||||
@Override
|
@Override
|
||||||
public boolean onMove(
|
public boolean onMove(
|
||||||
RecyclerView list, RecyclerView.ViewHolder origin, RecyclerView.ViewHolder target) {
|
RecyclerView list, RecyclerView.ViewHolder origin, RecyclerView.ViewHolder target) {
|
||||||
int fromPosition = origin.getAdapterPosition();
|
int fromPosition = origin.getBindingAdapterPosition();
|
||||||
int toPosition = target.getAdapterPosition();
|
int toPosition = target.getBindingAdapterPosition();
|
||||||
if (draggingFromPosition == C.INDEX_UNSET) {
|
if (draggingFromPosition == C.INDEX_UNSET) {
|
||||||
// A drag has started, but changes to the media queue will be reflected in clearView().
|
// A drag has started, but changes to the media queue will be reflected in clearView().
|
||||||
draggingFromPosition = fromPosition;
|
draggingFromPosition = fromPosition;
|
||||||
@ -243,7 +243,7 @@ public class MainActivity extends AppCompatActivity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
|
public void onSwiped(RecyclerView.ViewHolder viewHolder, int direction) {
|
||||||
int position = viewHolder.getAdapterPosition();
|
int position = viewHolder.getBindingAdapterPosition();
|
||||||
QueueItemViewHolder queueItemHolder = (QueueItemViewHolder) viewHolder;
|
QueueItemViewHolder queueItemHolder = (QueueItemViewHolder) viewHolder;
|
||||||
if (playerManager.removeItem(queueItemHolder.item)) {
|
if (playerManager.removeItem(queueItemHolder.item)) {
|
||||||
mediaQueueListAdapter.notifyItemRemoved(position);
|
mediaQueueListAdapter.notifyItemRemoved(position);
|
||||||
@ -282,7 +282,7 @@ public class MainActivity extends AppCompatActivity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(View v) {
|
public void onClick(View v) {
|
||||||
playerManager.selectQueueItem(getAdapterPosition());
|
playerManager.selectQueueItem(getBindingAdapterPosition());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -26,7 +26,7 @@ import androidx.media3.common.Player;
|
|||||||
import androidx.media3.common.Player.DiscontinuityReason;
|
import androidx.media3.common.Player.DiscontinuityReason;
|
||||||
import androidx.media3.common.Player.TimelineChangeReason;
|
import androidx.media3.common.Player.TimelineChangeReason;
|
||||||
import androidx.media3.common.Timeline;
|
import androidx.media3.common.Timeline;
|
||||||
import androidx.media3.common.TracksInfo;
|
import androidx.media3.common.Tracks;
|
||||||
import androidx.media3.exoplayer.ExoPlayer;
|
import androidx.media3.exoplayer.ExoPlayer;
|
||||||
import androidx.media3.ui.PlayerControlView;
|
import androidx.media3.ui.PlayerControlView;
|
||||||
import androidx.media3.ui.PlayerView;
|
import androidx.media3.ui.PlayerView;
|
||||||
@ -57,7 +57,7 @@ import java.util.ArrayList;
|
|||||||
private final ArrayList<MediaItem> mediaQueue;
|
private final ArrayList<MediaItem> mediaQueue;
|
||||||
private final Listener listener;
|
private final Listener listener;
|
||||||
|
|
||||||
private TracksInfo lastSeenTrackGroupInfo;
|
private Tracks lastSeenTracks;
|
||||||
private int currentItemIndex;
|
private int currentItemIndex;
|
||||||
private Player currentPlayer;
|
private Player currentPlayer;
|
||||||
|
|
||||||
@ -219,19 +219,19 @@ import java.util.ArrayList;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTracksInfoChanged(TracksInfo tracksInfo) {
|
public void onTracksChanged(Tracks tracks) {
|
||||||
if (currentPlayer != localPlayer || tracksInfo == lastSeenTrackGroupInfo) {
|
if (currentPlayer != localPlayer || tracks == lastSeenTracks) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!tracksInfo.isTypeSupportedOrEmpty(
|
if (tracks.containsType(C.TRACK_TYPE_VIDEO)
|
||||||
C.TRACK_TYPE_VIDEO, /* allowExceedsCapabilities= */ true)) {
|
&& !tracks.isTypeSupported(C.TRACK_TYPE_VIDEO, /* allowExceedsCapabilities= */ true)) {
|
||||||
listener.onUnsupportedTrack(C.TRACK_TYPE_VIDEO);
|
listener.onUnsupportedTrack(C.TRACK_TYPE_VIDEO);
|
||||||
}
|
}
|
||||||
if (!tracksInfo.isTypeSupportedOrEmpty(
|
if (tracks.containsType(C.TRACK_TYPE_AUDIO)
|
||||||
C.TRACK_TYPE_AUDIO, /* allowExceedsCapabilities= */ true)) {
|
&& !tracks.isTypeSupported(C.TRACK_TYPE_AUDIO, /* allowExceedsCapabilities= */ true)) {
|
||||||
listener.onUnsupportedTrack(C.TRACK_TYPE_AUDIO);
|
listener.onUnsupportedTrack(C.TRACK_TYPE_AUDIO);
|
||||||
}
|
}
|
||||||
lastSeenTrackGroupInfo = tracksInfo;
|
lastSeenTracks = tracks;
|
||||||
}
|
}
|
||||||
|
|
||||||
// CastPlayer.SessionAvailabilityListener implementation.
|
// CastPlayer.SessionAvailabilityListener implementation.
|
||||||
|
@ -27,6 +27,7 @@ import android.graphics.drawable.BitmapDrawable;
|
|||||||
import android.opengl.GLES20;
|
import android.opengl.GLES20;
|
||||||
import android.opengl.GLUtils;
|
import android.opengl.GLUtils;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
|
import androidx.media3.common.util.GlProgram;
|
||||||
import androidx.media3.common.util.GlUtil;
|
import androidx.media3.common.util.GlUtil;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
@ -50,7 +51,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
private final Bitmap logoBitmap;
|
private final Bitmap logoBitmap;
|
||||||
private final Canvas overlayCanvas;
|
private final Canvas overlayCanvas;
|
||||||
|
|
||||||
private GlUtil.@MonotonicNonNull Program program;
|
private @MonotonicNonNull GlProgram program;
|
||||||
|
|
||||||
private float bitmapScaleX;
|
private float bitmapScaleX;
|
||||||
private float bitmapScaleY;
|
private float bitmapScaleY;
|
||||||
@ -78,7 +79,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
public void initialize() {
|
public void initialize() {
|
||||||
try {
|
try {
|
||||||
program =
|
program =
|
||||||
new GlUtil.Program(
|
new GlProgram(
|
||||||
context,
|
context,
|
||||||
/* vertexShaderFilePath= */ "bitmap_overlay_video_processor_vertex.glsl",
|
/* vertexShaderFilePath= */ "bitmap_overlay_video_processor_vertex.glsl",
|
||||||
/* fragmentShaderFilePath= */ "bitmap_overlay_video_processor_fragment.glsl");
|
/* fragmentShaderFilePath= */ "bitmap_overlay_video_processor_fragment.glsl");
|
||||||
@ -86,9 +87,13 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
throw new IllegalStateException(e);
|
throw new IllegalStateException(e);
|
||||||
}
|
}
|
||||||
program.setBufferAttribute(
|
program.setBufferAttribute(
|
||||||
"aFramePosition", GlUtil.getNormalizedCoordinateBounds(), GlUtil.RECTANGLE_VERTICES_COUNT);
|
"aFramePosition",
|
||||||
|
GlUtil.getNormalizedCoordinateBounds(),
|
||||||
|
GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE);
|
||||||
program.setBufferAttribute(
|
program.setBufferAttribute(
|
||||||
"aTexCoords", GlUtil.getTextureCoordinateBounds(), GlUtil.RECTANGLE_VERTICES_COUNT);
|
"aTexCoords",
|
||||||
|
GlUtil.getTextureCoordinateBounds(),
|
||||||
|
GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE);
|
||||||
GLES20.glGenTextures(1, textures, 0);
|
GLES20.glGenTextures(1, textures, 0);
|
||||||
GLES20.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
|
GLES20.glBindTexture(GL10.GL_TEXTURE_2D, textures[0]);
|
||||||
GLES20.glTexParameterf(GL10.GL_TEXTURE_2D, GL10.GL_TEXTURE_MIN_FILTER, GL10.GL_NEAREST);
|
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();
|
GlUtil.checkGlError();
|
||||||
|
|
||||||
// Run the shader program.
|
// Run the shader program.
|
||||||
GlUtil.Program program = checkNotNull(this.program);
|
GlProgram program = checkNotNull(this.program);
|
||||||
program.setSamplerTexIdUniform("uTexSampler0", frameTexture, /* unit= */ 0);
|
program.setSamplerTexIdUniform("uTexSampler0", frameTexture, /* texUnitIndex= */ 0);
|
||||||
program.setSamplerTexIdUniform("uTexSampler1", textures[0], /* unit= */ 1);
|
program.setSamplerTexIdUniform("uTexSampler1", textures[0], /* texUnitIndex= */ 1);
|
||||||
program.setFloatUniform("uScaleX", bitmapScaleX);
|
program.setFloatUniform("uScaleX", bitmapScaleX);
|
||||||
program.setFloatUniform("uScaleY", bitmapScaleY);
|
program.setFloatUniform("uScaleY", bitmapScaleY);
|
||||||
program.setFloatsUniform("uTexTransform", transformMatrix);
|
program.setFloatsUniform("uTexTransform", transformMatrix);
|
||||||
|
@ -20,6 +20,7 @@ import android.content.Context;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.widget.FrameLayout;
|
import android.widget.FrameLayout;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
@ -32,7 +33,6 @@ import androidx.media3.common.util.Util;
|
|||||||
import androidx.media3.datasource.DataSource;
|
import androidx.media3.datasource.DataSource;
|
||||||
import androidx.media3.datasource.DefaultDataSource;
|
import androidx.media3.datasource.DefaultDataSource;
|
||||||
import androidx.media3.datasource.DefaultHttpDataSource;
|
import androidx.media3.datasource.DefaultHttpDataSource;
|
||||||
import androidx.media3.datasource.HttpDataSource;
|
|
||||||
import androidx.media3.exoplayer.ExoPlayer;
|
import androidx.media3.exoplayer.ExoPlayer;
|
||||||
import androidx.media3.exoplayer.dash.DashMediaSource;
|
import androidx.media3.exoplayer.dash.DashMediaSource;
|
||||||
import androidx.media3.exoplayer.drm.DefaultDrmSessionManager;
|
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 drmScheme = Assertions.checkNotNull(intent.getStringExtra(DRM_SCHEME_EXTRA));
|
||||||
String drmLicenseUrl = Assertions.checkNotNull(intent.getStringExtra(DRM_LICENSE_URL_EXTRA));
|
String drmLicenseUrl = Assertions.checkNotNull(intent.getStringExtra(DRM_LICENSE_URL_EXTRA));
|
||||||
UUID drmSchemeUuid = Assertions.checkNotNull(Util.getDrmUuid(drmScheme));
|
UUID drmSchemeUuid = Assertions.checkNotNull(Util.getDrmUuid(drmScheme));
|
||||||
HttpDataSource.Factory licenseDataSourceFactory = new DefaultHttpDataSource.Factory();
|
DataSource.Factory licenseDataSourceFactory = new DefaultHttpDataSource.Factory();
|
||||||
HttpMediaDrmCallback drmCallback =
|
HttpMediaDrmCallback drmCallback =
|
||||||
new HttpMediaDrmCallback(drmLicenseUrl, licenseDataSourceFactory);
|
new HttpMediaDrmCallback(drmLicenseUrl, licenseDataSourceFactory);
|
||||||
drmSessionManager =
|
drmSessionManager =
|
||||||
@ -157,13 +157,18 @@ public final class MainActivity extends Activity {
|
|||||||
|
|
||||||
DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(this);
|
DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(this);
|
||||||
MediaSource mediaSource;
|
MediaSource mediaSource;
|
||||||
@C.ContentType int type = Util.inferContentType(uri, intent.getStringExtra(EXTENSION_EXTRA));
|
@Nullable String fileExtension = intent.getStringExtra(EXTENSION_EXTRA);
|
||||||
if (type == C.TYPE_DASH) {
|
@C.ContentType
|
||||||
|
int type =
|
||||||
|
TextUtils.isEmpty(fileExtension)
|
||||||
|
? Util.inferContentType(uri)
|
||||||
|
: Util.inferContentTypeForExtension(fileExtension);
|
||||||
|
if (type == C.CONTENT_TYPE_DASH) {
|
||||||
mediaSource =
|
mediaSource =
|
||||||
new DashMediaSource.Factory(dataSourceFactory)
|
new DashMediaSource.Factory(dataSourceFactory)
|
||||||
.setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager)
|
.setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager)
|
||||||
.createMediaSource(MediaItem.fromUri(uri));
|
.createMediaSource(MediaItem.fromUri(uri));
|
||||||
} else if (type == C.TYPE_OTHER) {
|
} else if (type == C.CONTENT_TYPE_OTHER) {
|
||||||
mediaSource =
|
mediaSource =
|
||||||
new ProgressiveMediaSource.Factory(dataSourceFactory)
|
new ProgressiveMediaSource.Factory(dataSourceFactory)
|
||||||
.setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager)
|
.setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager)
|
||||||
@ -181,7 +186,7 @@ public final class MainActivity extends Activity {
|
|||||||
Assertions.checkNotNull(this.videoProcessingGLSurfaceView);
|
Assertions.checkNotNull(this.videoProcessingGLSurfaceView);
|
||||||
videoProcessingGLSurfaceView.setPlayer(player);
|
videoProcessingGLSurfaceView.setPlayer(player);
|
||||||
Assertions.checkNotNull(playerView).setPlayer(player);
|
Assertions.checkNotNull(playerView).setPlayer(player);
|
||||||
player.addAnalyticsListener(new EventLogger(/* trackSelector= */ null));
|
player.addAnalyticsListener(new EventLogger());
|
||||||
this.player = player;
|
this.player = player;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ import static androidx.media3.demo.main.DemoUtil.DOWNLOAD_NOTIFICATION_CHANNEL_I
|
|||||||
import android.app.Notification;
|
import android.app.Notification;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.OptIn;
|
||||||
import androidx.media3.common.util.NotificationUtil;
|
import androidx.media3.common.util.NotificationUtil;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import androidx.media3.exoplayer.offline.Download;
|
import androidx.media3.exoplayer.offline.Download;
|
||||||
@ -32,6 +33,7 @@ import androidx.media3.exoplayer.scheduler.Scheduler;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/** A service for downloading media. */
|
/** A service for downloading media. */
|
||||||
|
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||||
public class DemoDownloadService extends DownloadService {
|
public class DemoDownloadService extends DownloadService {
|
||||||
|
|
||||||
private static final int JOB_ID = 1;
|
private static final int JOB_ID = 1;
|
||||||
|
@ -16,12 +16,12 @@
|
|||||||
package androidx.media3.demo.main;
|
package androidx.media3.demo.main;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
|
import androidx.annotation.OptIn;
|
||||||
import androidx.media3.database.DatabaseProvider;
|
import androidx.media3.database.DatabaseProvider;
|
||||||
import androidx.media3.database.StandaloneDatabaseProvider;
|
import androidx.media3.database.StandaloneDatabaseProvider;
|
||||||
import androidx.media3.datasource.DataSource;
|
import androidx.media3.datasource.DataSource;
|
||||||
import androidx.media3.datasource.DefaultDataSource;
|
import androidx.media3.datasource.DefaultDataSource;
|
||||||
import androidx.media3.datasource.DefaultHttpDataSource;
|
import androidx.media3.datasource.DefaultHttpDataSource;
|
||||||
import androidx.media3.datasource.HttpDataSource;
|
|
||||||
import androidx.media3.datasource.cache.Cache;
|
import androidx.media3.datasource.cache.Cache;
|
||||||
import androidx.media3.datasource.cache.CacheDataSource;
|
import androidx.media3.datasource.cache.CacheDataSource;
|
||||||
import androidx.media3.datasource.cache.NoOpCacheEvictor;
|
import androidx.media3.datasource.cache.NoOpCacheEvictor;
|
||||||
@ -59,7 +59,7 @@ public final class DemoUtil {
|
|||||||
private static final String DOWNLOAD_CONTENT_DIRECTORY = "downloads";
|
private static final String DOWNLOAD_CONTENT_DIRECTORY = "downloads";
|
||||||
|
|
||||||
private static DataSource.@MonotonicNonNull Factory dataSourceFactory;
|
private static DataSource.@MonotonicNonNull Factory dataSourceFactory;
|
||||||
private static HttpDataSource.@MonotonicNonNull Factory httpDataSourceFactory;
|
private static DataSource.@MonotonicNonNull Factory httpDataSourceFactory;
|
||||||
private static @MonotonicNonNull DatabaseProvider databaseProvider;
|
private static @MonotonicNonNull DatabaseProvider databaseProvider;
|
||||||
private static @MonotonicNonNull File downloadDirectory;
|
private static @MonotonicNonNull File downloadDirectory;
|
||||||
private static @MonotonicNonNull Cache downloadCache;
|
private static @MonotonicNonNull Cache downloadCache;
|
||||||
@ -72,6 +72,7 @@ public final class DemoUtil {
|
|||||||
return BuildConfig.USE_DECODER_EXTENSIONS;
|
return BuildConfig.USE_DECODER_EXTENSIONS;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||||
public static RenderersFactory buildRenderersFactory(
|
public static RenderersFactory buildRenderersFactory(
|
||||||
Context context, boolean preferExtensionRenderer) {
|
Context context, boolean preferExtensionRenderer) {
|
||||||
@DefaultRenderersFactory.ExtensionRendererMode
|
@DefaultRenderersFactory.ExtensionRendererMode
|
||||||
@ -85,7 +86,7 @@ public final class DemoUtil {
|
|||||||
.setExtensionRendererMode(extensionRendererMode);
|
.setExtensionRendererMode(extensionRendererMode);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static synchronized HttpDataSource.Factory getHttpDataSourceFactory(Context context) {
|
public static synchronized DataSource.Factory getHttpDataSourceFactory(Context context) {
|
||||||
if (httpDataSourceFactory == null) {
|
if (httpDataSourceFactory == null) {
|
||||||
if (USE_CRONET_FOR_NETWORKING) {
|
if (USE_CRONET_FOR_NETWORKING) {
|
||||||
context = context.getApplicationContext();
|
context = context.getApplicationContext();
|
||||||
@ -117,6 +118,7 @@ public final class DemoUtil {
|
|||||||
return dataSourceFactory;
|
return dataSourceFactory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||||
public static synchronized DownloadNotificationHelper getDownloadNotificationHelper(
|
public static synchronized DownloadNotificationHelper getDownloadNotificationHelper(
|
||||||
Context context) {
|
Context context) {
|
||||||
if (downloadNotificationHelper == null) {
|
if (downloadNotificationHelper == null) {
|
||||||
@ -136,6 +138,7 @@ public final class DemoUtil {
|
|||||||
return downloadTracker;
|
return downloadTracker;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||||
private static synchronized Cache getDownloadCache(Context context) {
|
private static synchronized Cache getDownloadCache(Context context) {
|
||||||
if (downloadCache == null) {
|
if (downloadCache == null) {
|
||||||
File downloadContentDirectory =
|
File downloadContentDirectory =
|
||||||
@ -147,6 +150,7 @@ public final class DemoUtil {
|
|||||||
return downloadCache;
|
return downloadCache;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||||
private static synchronized void ensureDownloadManagerInitialized(Context context) {
|
private static synchronized void ensureDownloadManagerInitialized(Context context) {
|
||||||
if (downloadManager == null) {
|
if (downloadManager == null) {
|
||||||
downloadManager =
|
downloadManager =
|
||||||
@ -161,6 +165,7 @@ public final class DemoUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||||
private static synchronized DatabaseProvider getDatabaseProvider(Context context) {
|
private static synchronized DatabaseProvider getDatabaseProvider(Context context) {
|
||||||
if (databaseProvider == null) {
|
if (databaseProvider == null) {
|
||||||
databaseProvider = new StandaloneDatabaseProvider(context);
|
databaseProvider = new StandaloneDatabaseProvider(context);
|
||||||
@ -178,6 +183,7 @@ public final class DemoUtil {
|
|||||||
return downloadDirectory;
|
return downloadDirectory;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||||
private static CacheDataSource.Factory buildReadOnlyCacheDataSource(
|
private static CacheDataSource.Factory buildReadOnlyCacheDataSource(
|
||||||
DataSource.Factory upstreamFactory, Cache cache) {
|
DataSource.Factory upstreamFactory, Cache cache) {
|
||||||
return new CacheDataSource.Factory()
|
return new CacheDataSource.Factory()
|
||||||
|
@ -15,8 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.demo.main;
|
package androidx.media3.demo.main;
|
||||||
|
|
||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
@ -24,16 +23,18 @@ import android.net.Uri;
|
|||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.OptIn;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.media3.common.DrmInitData;
|
import androidx.media3.common.DrmInitData;
|
||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
import androidx.media3.common.TrackGroup;
|
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.Log;
|
||||||
import androidx.media3.common.util.Util;
|
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.RenderersFactory;
|
||||||
import androidx.media3.exoplayer.drm.DrmSession;
|
import androidx.media3.exoplayer.drm.DrmSession;
|
||||||
import androidx.media3.exoplayer.drm.DrmSessionEventListener;
|
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.DownloadManager;
|
||||||
import androidx.media3.exoplayer.offline.DownloadRequest;
|
import androidx.media3.exoplayer.offline.DownloadRequest;
|
||||||
import androidx.media3.exoplayer.offline.DownloadService;
|
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 androidx.media3.exoplayer.trackselection.MappingTrackSelector.MappedTrackInfo;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.util.HashMap;
|
import java.util.HashMap;
|
||||||
import java.util.concurrent.CopyOnWriteArraySet;
|
import java.util.concurrent.CopyOnWriteArraySet;
|
||||||
|
|
||||||
/** Tracks media that has been downloaded. */
|
/** Tracks media that has been downloaded. */
|
||||||
|
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||||
public class DownloadTracker {
|
public class DownloadTracker {
|
||||||
|
|
||||||
/** Listens for changes in the tracked downloads. */
|
/** Listens for changes in the tracked downloads. */
|
||||||
@ -65,31 +67,26 @@ public class DownloadTracker {
|
|||||||
private static final String TAG = "DownloadTracker";
|
private static final String TAG = "DownloadTracker";
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
private final HttpDataSource.Factory httpDataSourceFactory;
|
private final DataSource.Factory dataSourceFactory;
|
||||||
private final CopyOnWriteArraySet<Listener> listeners;
|
private final CopyOnWriteArraySet<Listener> listeners;
|
||||||
private final HashMap<Uri, Download> downloads;
|
private final HashMap<Uri, Download> downloads;
|
||||||
private final DownloadIndex downloadIndex;
|
private final DownloadIndex downloadIndex;
|
||||||
private final DefaultTrackSelector.Parameters trackSelectorParameters;
|
|
||||||
|
|
||||||
@Nullable private StartDownloadDialogHelper startDownloadDialogHelper;
|
@Nullable private StartDownloadDialogHelper startDownloadDialogHelper;
|
||||||
|
|
||||||
public DownloadTracker(
|
public DownloadTracker(
|
||||||
Context context,
|
Context context, DataSource.Factory dataSourceFactory, DownloadManager downloadManager) {
|
||||||
HttpDataSource.Factory httpDataSourceFactory,
|
|
||||||
DownloadManager downloadManager) {
|
|
||||||
this.context = context.getApplicationContext();
|
this.context = context.getApplicationContext();
|
||||||
this.httpDataSourceFactory = httpDataSourceFactory;
|
this.dataSourceFactory = dataSourceFactory;
|
||||||
listeners = new CopyOnWriteArraySet<>();
|
listeners = new CopyOnWriteArraySet<>();
|
||||||
downloads = new HashMap<>();
|
downloads = new HashMap<>();
|
||||||
downloadIndex = downloadManager.getDownloadIndex();
|
downloadIndex = downloadManager.getDownloadIndex();
|
||||||
trackSelectorParameters = DownloadHelper.getDefaultTrackSelectorParameters(context);
|
|
||||||
downloadManager.addListener(new DownloadManagerListener());
|
downloadManager.addListener(new DownloadManagerListener());
|
||||||
loadDownloads();
|
loadDownloads();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void addListener(Listener listener) {
|
public void addListener(Listener listener) {
|
||||||
checkNotNull(listener);
|
listeners.add(checkNotNull(listener));
|
||||||
listeners.add(listener);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public void removeListener(Listener listener) {
|
public void removeListener(Listener listener) {
|
||||||
@ -120,8 +117,7 @@ public class DownloadTracker {
|
|||||||
startDownloadDialogHelper =
|
startDownloadDialogHelper =
|
||||||
new StartDownloadDialogHelper(
|
new StartDownloadDialogHelper(
|
||||||
fragmentManager,
|
fragmentManager,
|
||||||
DownloadHelper.forMediaItem(
|
DownloadHelper.forMediaItem(context, mediaItem, renderersFactory, dataSourceFactory),
|
||||||
context, mediaItem, renderersFactory, httpDataSourceFactory),
|
|
||||||
mediaItem);
|
mediaItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -159,7 +155,7 @@ public class DownloadTracker {
|
|||||||
|
|
||||||
private final class StartDownloadDialogHelper
|
private final class StartDownloadDialogHelper
|
||||||
implements DownloadHelper.Callback,
|
implements DownloadHelper.Callback,
|
||||||
DialogInterface.OnClickListener,
|
TrackSelectionDialog.TrackSelectionListener,
|
||||||
DialogInterface.OnDismissListener {
|
DialogInterface.OnDismissListener {
|
||||||
|
|
||||||
private final FragmentManager fragmentManager;
|
private final FragmentManager fragmentManager;
|
||||||
@ -167,7 +163,6 @@ public class DownloadTracker {
|
|||||||
private final MediaItem mediaItem;
|
private final MediaItem mediaItem;
|
||||||
|
|
||||||
private TrackSelectionDialog trackSelectionDialog;
|
private TrackSelectionDialog trackSelectionDialog;
|
||||||
private MappedTrackInfo mappedTrackInfo;
|
|
||||||
private WidevineOfflineLicenseFetchTask widevineOfflineLicenseFetchTask;
|
private WidevineOfflineLicenseFetchTask widevineOfflineLicenseFetchTask;
|
||||||
@Nullable private byte[] keySetId;
|
@Nullable private byte[] keySetId;
|
||||||
|
|
||||||
@ -220,7 +215,7 @@ public class DownloadTracker {
|
|||||||
new WidevineOfflineLicenseFetchTask(
|
new WidevineOfflineLicenseFetchTask(
|
||||||
format,
|
format,
|
||||||
mediaItem.localConfiguration.drmConfiguration,
|
mediaItem.localConfiguration.drmConfiguration,
|
||||||
httpDataSourceFactory,
|
dataSourceFactory,
|
||||||
/* dialogHelper= */ this,
|
/* dialogHelper= */ this,
|
||||||
helper);
|
helper);
|
||||||
widevineOfflineLicenseFetchTask.execute();
|
widevineOfflineLicenseFetchTask.execute();
|
||||||
@ -237,21 +232,13 @@ public class DownloadTracker {
|
|||||||
Log.e(TAG, logMessage, e);
|
Log.e(TAG, logMessage, e);
|
||||||
}
|
}
|
||||||
|
|
||||||
// DialogInterface.OnClickListener implementation.
|
// TrackSelectionListener implementation.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onClick(DialogInterface dialog, int which) {
|
public void onTracksSelected(TrackSelectionParameters trackSelectionParameters) {
|
||||||
for (int periodIndex = 0; periodIndex < downloadHelper.getPeriodCount(); periodIndex++) {
|
for (int periodIndex = 0; periodIndex < downloadHelper.getPeriodCount(); periodIndex++) {
|
||||||
downloadHelper.clearTrackSelections(periodIndex);
|
downloadHelper.clearTrackSelections(periodIndex);
|
||||||
for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) {
|
downloadHelper.addTrackSelection(periodIndex, trackSelectionParameters);
|
||||||
if (!trackSelectionDialog.getIsDisabled(/* rendererIndex= */ i)) {
|
|
||||||
downloadHelper.addTrackSelectionForSingleRenderer(
|
|
||||||
periodIndex,
|
|
||||||
/* rendererIndex= */ i,
|
|
||||||
trackSelectorParameters,
|
|
||||||
trackSelectionDialog.getOverrides(/* rendererIndex= */ i));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
DownloadRequest downloadRequest = buildDownloadRequest();
|
DownloadRequest downloadRequest = buildDownloadRequest();
|
||||||
if (downloadRequest.streamKeys.isEmpty()) {
|
if (downloadRequest.streamKeys.isEmpty()) {
|
||||||
@ -316,21 +303,21 @@ public class DownloadTracker {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
|
||||||
mappedTrackInfo = downloadHelper.getMappedTrackInfo(/* periodIndex= */ 0);
|
Tracks tracks = downloadHelper.getTracks(/* periodIndex= */ 0);
|
||||||
if (!TrackSelectionDialog.willHaveContent(mappedTrackInfo)) {
|
if (!TrackSelectionDialog.willHaveContent(tracks)) {
|
||||||
Log.d(TAG, "No dialog content. Downloading entire stream.");
|
Log.d(TAG, "No dialog content. Downloading entire stream.");
|
||||||
startDownload();
|
startDownload();
|
||||||
downloadHelper.release();
|
downloadHelper.release();
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
trackSelectionDialog =
|
trackSelectionDialog =
|
||||||
TrackSelectionDialog.createForMappedTrackInfoAndParameters(
|
TrackSelectionDialog.createForTracksAndParameters(
|
||||||
/* titleId= */ R.string.exo_download_description,
|
/* titleId= */ R.string.exo_download_description,
|
||||||
mappedTrackInfo,
|
tracks,
|
||||||
trackSelectorParameters,
|
DownloadHelper.getDefaultTrackSelectorParameters(context),
|
||||||
/* allowAdaptiveSelections= */ false,
|
/* allowAdaptiveSelections= */ false,
|
||||||
/* allowMultipleOverrides= */ true,
|
/* allowMultipleOverrides= */ true,
|
||||||
/* onClickListener= */ this,
|
/* onTracksSelectedListener= */ this,
|
||||||
/* onDismissListener= */ this);
|
/* onDismissListener= */ this);
|
||||||
trackSelectionDialog.show(fragmentManager, /* tag= */ null);
|
trackSelectionDialog.show(fragmentManager, /* tag= */ null);
|
||||||
}
|
}
|
||||||
@ -371,7 +358,7 @@ public class DownloadTracker {
|
|||||||
|
|
||||||
private final Format format;
|
private final Format format;
|
||||||
private final MediaItem.DrmConfiguration drmConfiguration;
|
private final MediaItem.DrmConfiguration drmConfiguration;
|
||||||
private final HttpDataSource.Factory httpDataSourceFactory;
|
private final DataSource.Factory dataSourceFactory;
|
||||||
private final StartDownloadDialogHelper dialogHelper;
|
private final StartDownloadDialogHelper dialogHelper;
|
||||||
private final DownloadHelper downloadHelper;
|
private final DownloadHelper downloadHelper;
|
||||||
|
|
||||||
@ -381,12 +368,12 @@ public class DownloadTracker {
|
|||||||
public WidevineOfflineLicenseFetchTask(
|
public WidevineOfflineLicenseFetchTask(
|
||||||
Format format,
|
Format format,
|
||||||
MediaItem.DrmConfiguration drmConfiguration,
|
MediaItem.DrmConfiguration drmConfiguration,
|
||||||
HttpDataSource.Factory httpDataSourceFactory,
|
DataSource.Factory dataSourceFactory,
|
||||||
StartDownloadDialogHelper dialogHelper,
|
StartDownloadDialogHelper dialogHelper,
|
||||||
DownloadHelper downloadHelper) {
|
DownloadHelper downloadHelper) {
|
||||||
this.format = format;
|
this.format = format;
|
||||||
this.drmConfiguration = drmConfiguration;
|
this.drmConfiguration = drmConfiguration;
|
||||||
this.httpDataSourceFactory = httpDataSourceFactory;
|
this.dataSourceFactory = dataSourceFactory;
|
||||||
this.dialogHelper = dialogHelper;
|
this.dialogHelper = dialogHelper;
|
||||||
this.downloadHelper = downloadHelper;
|
this.downloadHelper = downloadHelper;
|
||||||
}
|
}
|
||||||
@ -397,7 +384,7 @@ public class DownloadTracker {
|
|||||||
OfflineLicenseHelper.newWidevineInstance(
|
OfflineLicenseHelper.newWidevineInstance(
|
||||||
drmConfiguration.licenseUri.toString(),
|
drmConfiguration.licenseUri.toString(),
|
||||||
drmConfiguration.forceDefaultLicenseUri,
|
drmConfiguration.forceDefaultLicenseUri,
|
||||||
httpDataSourceFactory,
|
dataSourceFactory,
|
||||||
drmConfiguration.licenseRequestHeaders,
|
drmConfiguration.licenseRequestHeaders,
|
||||||
new DrmSessionEventListener.EventDispatcher());
|
new DrmSessionEventListener.EventDispatcher());
|
||||||
try {
|
try {
|
||||||
@ -415,7 +402,7 @@ public class DownloadTracker {
|
|||||||
if (drmSessionException != null) {
|
if (drmSessionException != null) {
|
||||||
dialogHelper.onOfflineLicenseFetchedError(drmSessionException);
|
dialogHelper.onOfflineLicenseFetchedError(drmSessionException);
|
||||||
} else {
|
} else {
|
||||||
dialogHelper.onOfflineLicenseFetched(downloadHelper, checkStateNotNull(keySetId));
|
dialogHelper.onOfflineLicenseFetched(downloadHelper, checkNotNull(keySetId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,8 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.demo.main;
|
package androidx.media3.demo.main;
|
||||||
|
|
||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static androidx.media3.common.util.Assertions.checkState;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
@ -26,7 +27,6 @@ import androidx.media3.common.MediaItem;
|
|||||||
import androidx.media3.common.MediaItem.ClippingConfiguration;
|
import androidx.media3.common.MediaItem.ClippingConfiguration;
|
||||||
import androidx.media3.common.MediaItem.SubtitleConfiguration;
|
import androidx.media3.common.MediaItem.SubtitleConfiguration;
|
||||||
import androidx.media3.common.MediaMetadata;
|
import androidx.media3.common.MediaMetadata;
|
||||||
import androidx.media3.common.util.Assertions;
|
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
@ -86,7 +86,7 @@ public class IntentUtil {
|
|||||||
|
|
||||||
/** Populates the intent with the given list of {@link MediaItem media items}. */
|
/** Populates the intent with the given list of {@link MediaItem media items}. */
|
||||||
public static void addToIntent(List<MediaItem> mediaItems, Intent intent) {
|
public static void addToIntent(List<MediaItem> mediaItems, Intent intent) {
|
||||||
Assertions.checkArgument(!mediaItems.isEmpty());
|
checkArgument(!mediaItems.isEmpty());
|
||||||
if (mediaItems.size() == 1) {
|
if (mediaItems.size() == 1) {
|
||||||
MediaItem mediaItem = mediaItems.get(0);
|
MediaItem mediaItem = mediaItems.get(0);
|
||||||
MediaItem.LocalConfiguration localConfiguration = checkNotNull(mediaItem.localConfiguration);
|
MediaItem.LocalConfiguration localConfiguration = checkNotNull(mediaItem.localConfiguration);
|
||||||
@ -177,7 +177,7 @@ public class IntentUtil {
|
|||||||
headers.put(keyRequestPropertiesArray[i], keyRequestPropertiesArray[i + 1]);
|
headers.put(keyRequestPropertiesArray[i], keyRequestPropertiesArray[i + 1]);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@Nullable UUID drmUuid = Util.getDrmUuid(Util.castNonNull(drmSchemeExtra));
|
@Nullable UUID drmUuid = Util.getDrmUuid(drmSchemeExtra);
|
||||||
if (drmUuid != null) {
|
if (drmUuid != null) {
|
||||||
builder.setDrmConfiguration(
|
builder.setDrmConfiguration(
|
||||||
new MediaItem.DrmConfiguration.Builder(drmUuid)
|
new MediaItem.DrmConfiguration.Builder(drmUuid)
|
||||||
@ -188,7 +188,7 @@ public class IntentUtil {
|
|||||||
intent.getBooleanExtra(
|
intent.getBooleanExtra(
|
||||||
DRM_FORCE_DEFAULT_LICENSE_URI_EXTRA + extrasKeySuffix, false))
|
DRM_FORCE_DEFAULT_LICENSE_URI_EXTRA + extrasKeySuffix, false))
|
||||||
.setLicenseRequestHeaders(headers)
|
.setLicenseRequestHeaders(headers)
|
||||||
.forceSessionsForAudioAndVideoTracks(
|
.setForceSessionsForAudioAndVideoTracks(
|
||||||
intent.getBooleanExtra(DRM_SESSION_FOR_CLEAR_CONTENT + extrasKeySuffix, false))
|
intent.getBooleanExtra(DRM_SESSION_FOR_CLEAR_CONTENT + extrasKeySuffix, false))
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
@ -241,7 +241,7 @@ public class IntentUtil {
|
|||||||
drmConfiguration.forcedSessionTrackTypes;
|
drmConfiguration.forcedSessionTrackTypes;
|
||||||
if (!forcedDrmSessionTrackTypes.isEmpty()) {
|
if (!forcedDrmSessionTrackTypes.isEmpty()) {
|
||||||
// Only video and audio together are supported.
|
// Only video and audio together are supported.
|
||||||
Assertions.checkState(
|
checkState(
|
||||||
forcedDrmSessionTrackTypes.size() == 2
|
forcedDrmSessionTrackTypes.size() == 2
|
||||||
&& forcedDrmSessionTrackTypes.contains(C.TRACK_TYPE_VIDEO)
|
&& forcedDrmSessionTrackTypes.contains(C.TRACK_TYPE_VIDEO)
|
||||||
&& forcedDrmSessionTrackTypes.contains(C.TRACK_TYPE_AUDIO));
|
&& forcedDrmSessionTrackTypes.contains(C.TRACK_TYPE_AUDIO));
|
||||||
|
@ -17,6 +17,7 @@ package androidx.media3.demo.main;
|
|||||||
|
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
|
import android.os.Build;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import android.view.KeyEvent;
|
import android.view.KeyEvent;
|
||||||
@ -27,6 +28,7 @@ import android.widget.LinearLayout;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.OptIn;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.media3.common.AudioAttributes;
|
import androidx.media3.common.AudioAttributes;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
@ -34,11 +36,14 @@ import androidx.media3.common.ErrorMessageProvider;
|
|||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
import androidx.media3.common.PlaybackException;
|
import androidx.media3.common.PlaybackException;
|
||||||
import androidx.media3.common.Player;
|
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.common.util.Util;
|
||||||
import androidx.media3.datasource.DataSource;
|
import androidx.media3.datasource.DataSource;
|
||||||
import androidx.media3.exoplayer.ExoPlayer;
|
import androidx.media3.exoplayer.ExoPlayer;
|
||||||
import androidx.media3.exoplayer.RenderersFactory;
|
import androidx.media3.exoplayer.RenderersFactory;
|
||||||
|
import androidx.media3.exoplayer.drm.DefaultDrmSessionManagerProvider;
|
||||||
import androidx.media3.exoplayer.drm.FrameworkMediaDrm;
|
import androidx.media3.exoplayer.drm.FrameworkMediaDrm;
|
||||||
import androidx.media3.exoplayer.ima.ImaAdsLoader;
|
import androidx.media3.exoplayer.ima.ImaAdsLoader;
|
||||||
import androidx.media3.exoplayer.ima.ImaServerSideAdInsertionMediaSource;
|
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.DefaultMediaSourceFactory;
|
||||||
import androidx.media3.exoplayer.source.MediaSource;
|
import androidx.media3.exoplayer.source.MediaSource;
|
||||||
import androidx.media3.exoplayer.source.ads.AdsLoader;
|
import androidx.media3.exoplayer.source.ads.AdsLoader;
|
||||||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector;
|
|
||||||
import androidx.media3.exoplayer.util.DebugTextViewHelper;
|
import androidx.media3.exoplayer.util.DebugTextViewHelper;
|
||||||
import androidx.media3.exoplayer.util.EventLogger;
|
import androidx.media3.exoplayer.util.EventLogger;
|
||||||
import androidx.media3.ui.PlayerControlView;
|
|
||||||
import androidx.media3.ui.PlayerView;
|
import androidx.media3.ui.PlayerView;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
@ -60,7 +63,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|||||||
|
|
||||||
/** An activity that plays media using {@link ExoPlayer}. */
|
/** An activity that plays media using {@link ExoPlayer}. */
|
||||||
public class PlayerActivity extends AppCompatActivity
|
public class PlayerActivity extends AppCompatActivity
|
||||||
implements OnClickListener, PlayerControlView.VisibilityListener {
|
implements OnClickListener, PlayerView.ControllerVisibilityListener {
|
||||||
|
|
||||||
// Saved instance state keys.
|
// Saved instance state keys.
|
||||||
|
|
||||||
@ -79,10 +82,9 @@ public class PlayerActivity extends AppCompatActivity
|
|||||||
private Button selectTracksButton;
|
private Button selectTracksButton;
|
||||||
private DataSource.Factory dataSourceFactory;
|
private DataSource.Factory dataSourceFactory;
|
||||||
private List<MediaItem> mediaItems;
|
private List<MediaItem> mediaItems;
|
||||||
private DefaultTrackSelector trackSelector;
|
private TrackSelectionParameters trackSelectionParameters;
|
||||||
private DefaultTrackSelector.Parameters trackSelectionParameters;
|
|
||||||
private DebugTextViewHelper debugViewHelper;
|
private DebugTextViewHelper debugViewHelper;
|
||||||
private TracksInfo lastSeenTracksInfo;
|
private Tracks lastSeenTracks;
|
||||||
private boolean startAutoPlay;
|
private boolean startAutoPlay;
|
||||||
private int startItemIndex;
|
private int startItemIndex;
|
||||||
private long startPosition;
|
private long startPosition;
|
||||||
@ -90,7 +92,12 @@ public class PlayerActivity extends AppCompatActivity
|
|||||||
// For ad playback only.
|
// For ad playback only.
|
||||||
|
|
||||||
@Nullable private AdsLoader clientSideAdsLoader;
|
@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;
|
@Nullable private ImaServerSideAdInsertionMediaSource.AdsLoader serverSideAdsLoader;
|
||||||
|
|
||||||
private ImaServerSideAdInsertionMediaSource.AdsLoader.@MonotonicNonNull State
|
private ImaServerSideAdInsertionMediaSource.AdsLoader.@MonotonicNonNull State
|
||||||
serverSideAdsLoaderState;
|
serverSideAdsLoaderState;
|
||||||
|
|
||||||
@ -113,22 +120,15 @@ public class PlayerActivity extends AppCompatActivity
|
|||||||
playerView.requestFocus();
|
playerView.requestFocus();
|
||||||
|
|
||||||
if (savedInstanceState != null) {
|
if (savedInstanceState != null) {
|
||||||
// Restore as DefaultTrackSelector.Parameters in case ExoPlayer specific parameters were set.
|
|
||||||
trackSelectionParameters =
|
trackSelectionParameters =
|
||||||
DefaultTrackSelector.Parameters.CREATOR.fromBundle(
|
TrackSelectionParameters.fromBundle(
|
||||||
savedInstanceState.getBundle(KEY_TRACK_SELECTION_PARAMETERS));
|
savedInstanceState.getBundle(KEY_TRACK_SELECTION_PARAMETERS));
|
||||||
startAutoPlay = savedInstanceState.getBoolean(KEY_AUTO_PLAY);
|
startAutoPlay = savedInstanceState.getBoolean(KEY_AUTO_PLAY);
|
||||||
startItemIndex = savedInstanceState.getInt(KEY_ITEM_INDEX);
|
startItemIndex = savedInstanceState.getInt(KEY_ITEM_INDEX);
|
||||||
startPosition = savedInstanceState.getLong(KEY_POSITION);
|
startPosition = savedInstanceState.getLong(KEY_POSITION);
|
||||||
Bundle adsLoaderStateBundle = savedInstanceState.getBundle(KEY_SERVER_SIDE_ADS_LOADER_STATE);
|
restoreServerSideAdsLoaderState(savedInstanceState);
|
||||||
if (adsLoaderStateBundle != null) {
|
|
||||||
serverSideAdsLoaderState =
|
|
||||||
ImaServerSideAdInsertionMediaSource.AdsLoader.State.CREATOR.fromBundle(
|
|
||||||
adsLoaderStateBundle);
|
|
||||||
}
|
|
||||||
} else {
|
} else {
|
||||||
trackSelectionParameters =
|
trackSelectionParameters = new TrackSelectionParameters.Builder(/* context= */ this).build();
|
||||||
new DefaultTrackSelector.ParametersBuilder(/* context= */ this).build();
|
|
||||||
clearStartPosition();
|
clearStartPosition();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -145,7 +145,7 @@ public class PlayerActivity extends AppCompatActivity
|
|||||||
@Override
|
@Override
|
||||||
public void onStart() {
|
public void onStart() {
|
||||||
super.onStart();
|
super.onStart();
|
||||||
if (Util.SDK_INT > 23) {
|
if (Build.VERSION.SDK_INT > 23) {
|
||||||
initializePlayer();
|
initializePlayer();
|
||||||
if (playerView != null) {
|
if (playerView != null) {
|
||||||
playerView.onResume();
|
playerView.onResume();
|
||||||
@ -156,7 +156,7 @@ public class PlayerActivity extends AppCompatActivity
|
|||||||
@Override
|
@Override
|
||||||
public void onResume() {
|
public void onResume() {
|
||||||
super.onResume();
|
super.onResume();
|
||||||
if (Util.SDK_INT <= 23 || player == null) {
|
if (Build.VERSION.SDK_INT <= 23 || player == null) {
|
||||||
initializePlayer();
|
initializePlayer();
|
||||||
if (playerView != null) {
|
if (playerView != null) {
|
||||||
playerView.onResume();
|
playerView.onResume();
|
||||||
@ -167,7 +167,7 @@ public class PlayerActivity extends AppCompatActivity
|
|||||||
@Override
|
@Override
|
||||||
public void onPause() {
|
public void onPause() {
|
||||||
super.onPause();
|
super.onPause();
|
||||||
if (Util.SDK_INT <= 23) {
|
if (Build.VERSION.SDK_INT <= 23) {
|
||||||
if (playerView != null) {
|
if (playerView != null) {
|
||||||
playerView.onPause();
|
playerView.onPause();
|
||||||
}
|
}
|
||||||
@ -178,7 +178,7 @@ public class PlayerActivity extends AppCompatActivity
|
|||||||
@Override
|
@Override
|
||||||
public void onStop() {
|
public void onStop() {
|
||||||
super.onStop();
|
super.onStop();
|
||||||
if (Util.SDK_INT > 23) {
|
if (Build.VERSION.SDK_INT > 23) {
|
||||||
if (playerView != null) {
|
if (playerView != null) {
|
||||||
playerView.onPause();
|
playerView.onPause();
|
||||||
}
|
}
|
||||||
@ -218,9 +218,7 @@ public class PlayerActivity extends AppCompatActivity
|
|||||||
outState.putBoolean(KEY_AUTO_PLAY, startAutoPlay);
|
outState.putBoolean(KEY_AUTO_PLAY, startAutoPlay);
|
||||||
outState.putInt(KEY_ITEM_INDEX, startItemIndex);
|
outState.putInt(KEY_ITEM_INDEX, startItemIndex);
|
||||||
outState.putLong(KEY_POSITION, startPosition);
|
outState.putLong(KEY_POSITION, startPosition);
|
||||||
if (serverSideAdsLoaderState != null) {
|
saveServerSideAdsLoaderState(outState);
|
||||||
outState.putBundle(KEY_SERVER_SIDE_ADS_LOADER_STATE, serverSideAdsLoaderState.toBundle());
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Activity input
|
// Activity input
|
||||||
@ -237,20 +235,20 @@ public class PlayerActivity extends AppCompatActivity
|
|||||||
public void onClick(View view) {
|
public void onClick(View view) {
|
||||||
if (view == selectTracksButton
|
if (view == selectTracksButton
|
||||||
&& !isShowingTrackSelectionDialog
|
&& !isShowingTrackSelectionDialog
|
||||||
&& TrackSelectionDialog.willHaveContent(trackSelector)) {
|
&& TrackSelectionDialog.willHaveContent(player)) {
|
||||||
isShowingTrackSelectionDialog = true;
|
isShowingTrackSelectionDialog = true;
|
||||||
TrackSelectionDialog trackSelectionDialog =
|
TrackSelectionDialog trackSelectionDialog =
|
||||||
TrackSelectionDialog.createForTrackSelector(
|
TrackSelectionDialog.createForPlayer(
|
||||||
trackSelector,
|
player,
|
||||||
/* onDismissListener= */ dismissedDialog -> isShowingTrackSelectionDialog = false);
|
/* onDismissListener= */ dismissedDialog -> isShowingTrackSelectionDialog = false);
|
||||||
trackSelectionDialog.show(getSupportFragmentManager(), /* tag= */ null);
|
trackSelectionDialog.show(getSupportFragmentManager(), /* tag= */ null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// PlayerControlView.VisibilityListener implementation
|
// PlayerView.ControllerVisibilityListener implementation
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onVisibilityChange(int visibility) {
|
public void onVisibilityChanged(int visibility) {
|
||||||
debugRootView.setVisibility(visibility);
|
debugRootView.setVisibility(visibility);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -260,7 +258,9 @@ public class PlayerActivity extends AppCompatActivity
|
|||||||
setContentView(R.layout.player_activity);
|
setContentView(R.layout.player_activity);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @return Whether initialization was successful. */
|
/**
|
||||||
|
* @return Whether initialization was successful.
|
||||||
|
*/
|
||||||
protected boolean initializePlayer() {
|
protected boolean initializePlayer() {
|
||||||
if (player == null) {
|
if (player == null) {
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
@ -270,26 +270,20 @@ public class PlayerActivity extends AppCompatActivity
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
boolean preferExtensionDecoders =
|
lastSeenTracks = Tracks.EMPTY;
|
||||||
intent.getBooleanExtra(IntentUtil.PREFER_EXTENSION_DECODERS_EXTRA, false);
|
ExoPlayer.Builder playerBuilder =
|
||||||
RenderersFactory renderersFactory =
|
|
||||||
DemoUtil.buildRenderersFactory(/* context= */ this, preferExtensionDecoders);
|
|
||||||
|
|
||||||
trackSelector = new DefaultTrackSelector(/* context= */ this);
|
|
||||||
lastSeenTracksInfo = TracksInfo.EMPTY;
|
|
||||||
player =
|
|
||||||
new ExoPlayer.Builder(/* context= */ this)
|
new ExoPlayer.Builder(/* context= */ this)
|
||||||
.setRenderersFactory(renderersFactory)
|
.setMediaSourceFactory(createMediaSourceFactory());
|
||||||
.setMediaSourceFactory(createMediaSourceFactory())
|
setRenderersFactory(
|
||||||
.setTrackSelector(trackSelector)
|
playerBuilder, intent.getBooleanExtra(IntentUtil.PREFER_EXTENSION_DECODERS_EXTRA, false));
|
||||||
.build();
|
player = playerBuilder.build();
|
||||||
player.setTrackSelectionParameters(trackSelectionParameters);
|
player.setTrackSelectionParameters(trackSelectionParameters);
|
||||||
player.addListener(new PlayerEventListener());
|
player.addListener(new PlayerEventListener());
|
||||||
player.addAnalyticsListener(new EventLogger(trackSelector));
|
player.addAnalyticsListener(new EventLogger());
|
||||||
player.setAudioAttributes(AudioAttributes.DEFAULT, /* handleAudioFocus= */ true);
|
player.setAudioAttributes(AudioAttributes.DEFAULT, /* handleAudioFocus= */ true);
|
||||||
player.setPlayWhenReady(startAutoPlay);
|
player.setPlayWhenReady(startAutoPlay);
|
||||||
playerView.setPlayer(player);
|
playerView.setPlayer(player);
|
||||||
serverSideAdsLoader.setPlayer(player);
|
configurePlayerWithServerSideAdsLoader();
|
||||||
debugViewHelper = new DebugTextViewHelper(player, debugTextView);
|
debugViewHelper = new DebugTextViewHelper(player, debugTextView);
|
||||||
debugViewHelper.start();
|
debugViewHelper.start();
|
||||||
}
|
}
|
||||||
@ -303,7 +297,12 @@ public class PlayerActivity extends AppCompatActivity
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@OptIn(markerClass = UnstableApi.class) // SSAI configuration
|
||||||
private MediaSource.Factory createMediaSourceFactory() {
|
private MediaSource.Factory createMediaSourceFactory() {
|
||||||
|
DefaultDrmSessionManagerProvider drmSessionManagerProvider =
|
||||||
|
new DefaultDrmSessionManagerProvider();
|
||||||
|
drmSessionManagerProvider.setDrmHttpDataSourceFactory(
|
||||||
|
DemoUtil.getHttpDataSourceFactory(/* context= */ this));
|
||||||
ImaServerSideAdInsertionMediaSource.AdsLoader.Builder serverSideAdLoaderBuilder =
|
ImaServerSideAdInsertionMediaSource.AdsLoader.Builder serverSideAdLoaderBuilder =
|
||||||
new ImaServerSideAdInsertionMediaSource.AdsLoader.Builder(/* context= */ this, playerView);
|
new ImaServerSideAdInsertionMediaSource.AdsLoader.Builder(/* context= */ this, playerView);
|
||||||
if (serverSideAdsLoaderState != null) {
|
if (serverSideAdsLoaderState != null) {
|
||||||
@ -312,13 +311,30 @@ public class PlayerActivity extends AppCompatActivity
|
|||||||
serverSideAdsLoader = serverSideAdLoaderBuilder.build();
|
serverSideAdsLoader = serverSideAdLoaderBuilder.build();
|
||||||
ImaServerSideAdInsertionMediaSource.Factory imaServerSideAdInsertionMediaSourceFactory =
|
ImaServerSideAdInsertionMediaSource.Factory imaServerSideAdInsertionMediaSourceFactory =
|
||||||
new ImaServerSideAdInsertionMediaSource.Factory(
|
new ImaServerSideAdInsertionMediaSource.Factory(
|
||||||
serverSideAdsLoader, new DefaultMediaSourceFactory(dataSourceFactory));
|
serverSideAdsLoader,
|
||||||
return new DefaultMediaSourceFactory(dataSourceFactory)
|
new DefaultMediaSourceFactory(/* context= */ this)
|
||||||
.setAdsLoaderProvider(this::getClientSideAdsLoader)
|
.setDataSourceFactory(dataSourceFactory));
|
||||||
.setAdViewProvider(playerView)
|
return new DefaultMediaSourceFactory(/* context= */ this)
|
||||||
|
.setDataSourceFactory(dataSourceFactory)
|
||||||
|
.setDrmSessionManagerProvider(drmSessionManagerProvider)
|
||||||
|
.setLocalAdInsertionComponents(
|
||||||
|
this::getClientSideAdsLoader, /* adViewProvider= */ playerView)
|
||||||
.setServerSideAdInsertionMediaSourceFactory(imaServerSideAdInsertionMediaSourceFactory);
|
.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<MediaItem> createMediaItems(Intent intent) {
|
private List<MediaItem> createMediaItems(Intent intent) {
|
||||||
String action = intent.getAction();
|
String action = intent.getAction();
|
||||||
boolean actionIsListView = IntentUtil.ACTION_VIEW_LIST.equals(action);
|
boolean actionIsListView = IntentUtil.ACTION_VIEW_LIST.equals(action);
|
||||||
@ -345,7 +361,7 @@ public class PlayerActivity extends AppCompatActivity
|
|||||||
|
|
||||||
MediaItem.DrmConfiguration drmConfiguration = mediaItem.localConfiguration.drmConfiguration;
|
MediaItem.DrmConfiguration drmConfiguration = mediaItem.localConfiguration.drmConfiguration;
|
||||||
if (drmConfiguration != null) {
|
if (drmConfiguration != null) {
|
||||||
if (Util.SDK_INT < 18) {
|
if (Build.VERSION.SDK_INT < 18) {
|
||||||
showToast(R.string.error_drm_unsupported_before_api_18);
|
showToast(R.string.error_drm_unsupported_before_api_18);
|
||||||
finish();
|
finish();
|
||||||
return Collections.emptyList();
|
return Collections.emptyList();
|
||||||
@ -372,8 +388,7 @@ public class PlayerActivity extends AppCompatActivity
|
|||||||
if (player != null) {
|
if (player != null) {
|
||||||
updateTrackSelectorParameters();
|
updateTrackSelectorParameters();
|
||||||
updateStartPosition();
|
updateStartPosition();
|
||||||
serverSideAdsLoaderState = serverSideAdsLoader.release();
|
releaseServerSideAdsLoader();
|
||||||
serverSideAdsLoader = null;
|
|
||||||
debugViewHelper.stop();
|
debugViewHelper.stop();
|
||||||
debugViewHelper = null;
|
debugViewHelper = null;
|
||||||
player.release();
|
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() {
|
private void releaseClientSideAdsLoader() {
|
||||||
if (clientSideAdsLoader != null) {
|
if (clientSideAdsLoader != null) {
|
||||||
clientSideAdsLoader.release();
|
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() {
|
private void updateTrackSelectorParameters() {
|
||||||
if (player != null) {
|
if (player != null) {
|
||||||
// Until the demo app is fully migrated to TrackSelectionParameters, rely on ExoPlayer to use
|
trackSelectionParameters = player.getTrackSelectionParameters();
|
||||||
// DefaultTrackSelector by default.
|
|
||||||
trackSelectionParameters =
|
|
||||||
(DefaultTrackSelector.Parameters) player.getTrackSelectionParameters();
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -422,8 +457,7 @@ public class PlayerActivity extends AppCompatActivity
|
|||||||
// User controls
|
// User controls
|
||||||
|
|
||||||
private void updateButtonVisibility() {
|
private void updateButtonVisibility() {
|
||||||
selectTracksButton.setEnabled(
|
selectTracksButton.setEnabled(player != null && TrackSelectionDialog.willHaveContent(player));
|
||||||
player != null && TrackSelectionDialog.willHaveContent(trackSelector));
|
|
||||||
}
|
}
|
||||||
|
|
||||||
private void showControls() {
|
private void showControls() {
|
||||||
@ -461,20 +495,20 @@ public class PlayerActivity extends AppCompatActivity
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("ReferenceEquality")
|
@SuppressWarnings("ReferenceEquality")
|
||||||
public void onTracksInfoChanged(TracksInfo tracksInfo) {
|
public void onTracksChanged(Tracks tracks) {
|
||||||
updateButtonVisibility();
|
updateButtonVisibility();
|
||||||
if (tracksInfo == lastSeenTracksInfo) {
|
if (tracks == lastSeenTracks) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
if (!tracksInfo.isTypeSupportedOrEmpty(
|
if (tracks.containsType(C.TRACK_TYPE_VIDEO)
|
||||||
C.TRACK_TYPE_VIDEO, /* allowExceedsCapabilities= */ true)) {
|
&& !tracks.isTypeSupported(C.TRACK_TYPE_VIDEO, /* allowExceedsCapabilities= */ true)) {
|
||||||
showToast(R.string.error_unsupported_video);
|
showToast(R.string.error_unsupported_video);
|
||||||
}
|
}
|
||||||
if (!tracksInfo.isTypeSupportedOrEmpty(
|
if (tracks.containsType(C.TRACK_TYPE_AUDIO)
|
||||||
C.TRACK_TYPE_AUDIO, /* allowExceedsCapabilities= */ true)) {
|
&& !tracks.isTypeSupported(C.TRACK_TYPE_AUDIO, /* allowExceedsCapabilities= */ true)) {
|
||||||
showToast(R.string.error_unsupported_audio);
|
showToast(R.string.error_unsupported_audio);
|
||||||
}
|
}
|
||||||
lastSeenTracksInfo = tracksInfo;
|
lastSeenTracks = tracks;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -513,10 +547,19 @@ public class PlayerActivity extends AppCompatActivity
|
|||||||
private static List<MediaItem> createMediaItems(Intent intent, DownloadTracker downloadTracker) {
|
private static List<MediaItem> createMediaItems(Intent intent, DownloadTracker downloadTracker) {
|
||||||
List<MediaItem> mediaItems = new ArrayList<>();
|
List<MediaItem> mediaItems = new ArrayList<>();
|
||||||
for (MediaItem item : IntentUtil.createMediaItemsFromIntent(intent)) {
|
for (MediaItem item : IntentUtil.createMediaItemsFromIntent(intent)) {
|
||||||
@Nullable
|
mediaItems.add(
|
||||||
DownloadRequest downloadRequest =
|
maybeSetDownloadProperties(
|
||||||
downloadTracker.getDownloadRequest(item.localConfiguration.uri);
|
item, downloadTracker.getDownloadRequest(item.localConfiguration.uri)));
|
||||||
if (downloadRequest != null) {
|
}
|
||||||
|
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();
|
MediaItem.Builder builder = item.buildUpon();
|
||||||
builder
|
builder
|
||||||
.setMediaId(downloadRequest.id)
|
.setMediaId(downloadRequest.id)
|
||||||
@ -530,12 +573,6 @@ public class PlayerActivity extends AppCompatActivity
|
|||||||
builder.setDrmConfiguration(
|
builder.setDrmConfiguration(
|
||||||
drmConfiguration.buildUpon().setKeySetId(downloadRequest.keySetId).build());
|
drmConfiguration.buildUpon().setKeySetId(downloadRequest.keySetId).build());
|
||||||
}
|
}
|
||||||
|
return builder.build();
|
||||||
mediaItems.add(builder.build());
|
|
||||||
} else {
|
|
||||||
mediaItems.add(item);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return mediaItems;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,9 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.demo.main;
|
package androidx.media3.demo.main;
|
||||||
|
|
||||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
import static com.google.common.base.Preconditions.checkArgument;
|
||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
import static com.google.common.base.Preconditions.checkNotNull;
|
||||||
import static androidx.media3.common.util.Assertions.checkState;
|
import static com.google.common.base.Preconditions.checkState;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
@ -27,6 +27,7 @@ import android.content.res.AssetManager;
|
|||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.AsyncTask;
|
import android.os.AsyncTask;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.util.JsonReader;
|
import android.util.JsonReader;
|
||||||
import android.view.Menu;
|
import android.view.Menu;
|
||||||
import android.view.MenuInflater;
|
import android.view.MenuInflater;
|
||||||
@ -41,6 +42,7 @@ import android.widget.ImageButton;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.OptIn;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
import androidx.media3.common.MediaItem.ClippingConfiguration;
|
import androidx.media3.common.MediaItem.ClippingConfiguration;
|
||||||
@ -53,6 +55,7 @@ import androidx.media3.datasource.DataSourceUtil;
|
|||||||
import androidx.media3.datasource.DataSpec;
|
import androidx.media3.datasource.DataSpec;
|
||||||
import androidx.media3.exoplayer.RenderersFactory;
|
import androidx.media3.exoplayer.RenderersFactory;
|
||||||
import androidx.media3.exoplayer.offline.DownloadService;
|
import androidx.media3.exoplayer.offline.DownloadService;
|
||||||
|
import com.google.common.base.Objects;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -116,8 +119,12 @@ public class SampleChooserActivity extends AppCompatActivity
|
|||||||
useExtensionRenderers = DemoUtil.useExtensionRenderers();
|
useExtensionRenderers = DemoUtil.useExtensionRenderers();
|
||||||
downloadTracker = DemoUtil.getDownloadTracker(/* context= */ this);
|
downloadTracker = DemoUtil.getDownloadTracker(/* context= */ this);
|
||||||
loadSample();
|
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
|
// 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
|
// action. Starting it in the background throws an exception if the app is in the background too
|
||||||
// (e.g. if device screen is locked).
|
// (e.g. if device screen is locked).
|
||||||
@ -271,6 +278,7 @@ public class SampleChooserActivity extends AppCompatActivity
|
|||||||
|
|
||||||
private boolean sawError;
|
private boolean sawError;
|
||||||
|
|
||||||
|
@OptIn(markerClass = androidx.media3.common.util.UnstableApi.class)
|
||||||
@Override
|
@Override
|
||||||
protected List<PlaylistGroup> doInBackground(String... uris) {
|
protected List<PlaylistGroup> doInBackground(String... uris) {
|
||||||
List<PlaylistGroup> result = new ArrayList<>();
|
List<PlaylistGroup> result = new ArrayList<>();
|
||||||
@ -436,7 +444,10 @@ public class SampleChooserActivity extends AppCompatActivity
|
|||||||
} else {
|
} else {
|
||||||
@Nullable
|
@Nullable
|
||||||
String adaptiveMimeType =
|
String adaptiveMimeType =
|
||||||
Util.getAdaptiveMimeTypeForContentType(Util.inferContentType(uri, extension));
|
Util.getAdaptiveMimeTypeForContentType(
|
||||||
|
TextUtils.isEmpty(extension)
|
||||||
|
? Util.inferContentType(uri)
|
||||||
|
: Util.inferContentTypeForExtension(extension));
|
||||||
mediaItem
|
mediaItem
|
||||||
.setUri(uri)
|
.setUri(uri)
|
||||||
.setMediaMetadata(new MediaMetadata.Builder().setTitle(title).build())
|
.setMediaMetadata(new MediaMetadata.Builder().setTitle(title).build())
|
||||||
@ -447,7 +458,7 @@ public class SampleChooserActivity extends AppCompatActivity
|
|||||||
new MediaItem.DrmConfiguration.Builder(drmUuid)
|
new MediaItem.DrmConfiguration.Builder(drmUuid)
|
||||||
.setLicenseUri(drmLicenseUri)
|
.setLicenseUri(drmLicenseUri)
|
||||||
.setLicenseRequestHeaders(drmLicenseRequestHeaders)
|
.setLicenseRequestHeaders(drmLicenseRequestHeaders)
|
||||||
.forceSessionsForAudioAndVideoTracks(drmSessionForClearContent)
|
.setForceSessionsForAudioAndVideoTracks(drmSessionForClearContent)
|
||||||
.setMultiSession(drmMultiSession)
|
.setMultiSession(drmMultiSession)
|
||||||
.setForceDefaultLicenseUri(drmForceDefaultLicenseUri)
|
.setForceDefaultLicenseUri(drmForceDefaultLicenseUri)
|
||||||
.build());
|
.build());
|
||||||
@ -481,7 +492,7 @@ public class SampleChooserActivity extends AppCompatActivity
|
|||||||
|
|
||||||
private PlaylistGroup getGroup(String groupName, List<PlaylistGroup> groups) {
|
private PlaylistGroup getGroup(String groupName, List<PlaylistGroup> groups) {
|
||||||
for (int i = 0; i < groups.size(); i++) {
|
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);
|
return groups.get(i);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -25,27 +25,50 @@ import android.view.View;
|
|||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.OptIn;
|
||||||
import androidx.appcompat.app.AppCompatDialog;
|
import androidx.appcompat.app.AppCompatDialog;
|
||||||
import androidx.fragment.app.DialogFragment;
|
import androidx.fragment.app.DialogFragment;
|
||||||
import androidx.fragment.app.Fragment;
|
import androidx.fragment.app.Fragment;
|
||||||
import androidx.fragment.app.FragmentManager;
|
import androidx.fragment.app.FragmentManager;
|
||||||
import androidx.fragment.app.FragmentPagerAdapter;
|
import androidx.fragment.app.FragmentPagerAdapter;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.TrackGroupArray;
|
import androidx.media3.common.Player;
|
||||||
import androidx.media3.common.util.Assertions;
|
import androidx.media3.common.TrackGroup;
|
||||||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector;
|
import androidx.media3.common.TrackSelectionOverride;
|
||||||
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector.SelectionOverride;
|
import androidx.media3.common.TrackSelectionParameters;
|
||||||
import androidx.media3.exoplayer.trackselection.MappingTrackSelector.MappedTrackInfo;
|
import androidx.media3.common.Tracks;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.ui.TrackSelectionView;
|
import androidx.media3.ui.TrackSelectionView;
|
||||||
import androidx.viewpager.widget.ViewPager;
|
import androidx.viewpager.widget.ViewPager;
|
||||||
import com.google.android.material.tabs.TabLayout;
|
import com.google.android.material.tabs.TabLayout;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/** Dialog to select tracks. */
|
/** Dialog to select tracks. */
|
||||||
|
@OptIn(markerClass = UnstableApi.class)
|
||||||
public final class TrackSelectionDialog extends DialogFragment {
|
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<Integer> SUPPORTED_TRACK_TYPES =
|
||||||
|
ImmutableList.of(C.TRACK_TYPE_VIDEO, C.TRACK_TYPE_AUDIO, C.TRACK_TYPE_TEXT);
|
||||||
|
|
||||||
private final SparseArray<TrackSelectionViewFragment> tabFragments;
|
private final SparseArray<TrackSelectionViewFragment> tabFragments;
|
||||||
private final ArrayList<Integer> tabTrackTypes;
|
private final ArrayList<Integer> 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
|
* 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) {
|
public static boolean willHaveContent(Player player) {
|
||||||
MappedTrackInfo mappedTrackInfo = trackSelector.getCurrentMappedTrackInfo();
|
return willHaveContent(player.getCurrentTracks());
|
||||||
return mappedTrackInfo != null && willHaveContent(mappedTrackInfo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether a track selection dialog will have content to display if initialized with the
|
* 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) {
|
public static boolean willHaveContent(Tracks tracks) {
|
||||||
for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) {
|
for (Tracks.Group trackGroup : tracks.getGroups()) {
|
||||||
if (showTabForRenderer(mappedTrackInfo, i)) {
|
if (SUPPORTED_TRACK_TYPES.contains(trackGroup.getType())) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -76,78 +98,67 @@ public final class TrackSelectionDialog extends DialogFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a dialog for a given {@link DefaultTrackSelector}, whose parameters will be
|
* Creates a dialog for a given {@link Player}, whose parameters will be automatically updated
|
||||||
* automatically updated when tracks are selected.
|
* 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
|
* @param onDismissListener A {@link DialogInterface.OnDismissListener} to call when the dialog is
|
||||||
* dismissed.
|
* dismissed.
|
||||||
*/
|
*/
|
||||||
public static TrackSelectionDialog createForTrackSelector(
|
public static TrackSelectionDialog createForPlayer(
|
||||||
DefaultTrackSelector trackSelector, DialogInterface.OnDismissListener onDismissListener) {
|
Player player, DialogInterface.OnDismissListener onDismissListener) {
|
||||||
MappedTrackInfo mappedTrackInfo =
|
return createForTracksAndParameters(
|
||||||
Assertions.checkNotNull(trackSelector.getCurrentMappedTrackInfo());
|
R.string.track_selection_title,
|
||||||
TrackSelectionDialog trackSelectionDialog = new TrackSelectionDialog();
|
player.getCurrentTracks(),
|
||||||
DefaultTrackSelector.Parameters parameters = trackSelector.getParameters();
|
player.getTrackSelectionParameters(),
|
||||||
trackSelectionDialog.init(
|
|
||||||
/* titleId= */ R.string.track_selection_title,
|
|
||||||
mappedTrackInfo,
|
|
||||||
/* initialParameters = */ parameters,
|
|
||||||
/* allowAdaptiveSelections= */ true,
|
/* allowAdaptiveSelections= */ true,
|
||||||
/* allowMultipleOverrides= */ false,
|
/* allowMultipleOverrides= */ false,
|
||||||
/* onClickListener= */ (dialog, which) -> {
|
player::setTrackSelectionParameters,
|
||||||
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<SelectionOverride> overrides =
|
|
||||||
trackSelectionDialog.getOverrides(/* rendererIndex= */ i);
|
|
||||||
if (!overrides.isEmpty()) {
|
|
||||||
builder.setSelectionOverride(
|
|
||||||
/* rendererIndex= */ i,
|
|
||||||
mappedTrackInfo.getTrackGroups(/* rendererIndex= */ i),
|
|
||||||
overrides.get(0));
|
|
||||||
}
|
|
||||||
}
|
|
||||||
trackSelector.setParameters(builder);
|
|
||||||
},
|
|
||||||
onDismissListener);
|
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 titleId The resource id of the dialog title.
|
||||||
* @param mappedTrackInfo The {@link MappedTrackInfo} to display.
|
* @param tracks The {@link Tracks} describing the tracks to display.
|
||||||
* @param initialParameters The {@link DefaultTrackSelector.Parameters} describing the initial
|
* @param trackSelectionParameters The initial {@link TrackSelectionParameters}.
|
||||||
* track selection.
|
|
||||||
* @param allowAdaptiveSelections Whether adaptive selections (consisting of more than one track)
|
* @param allowAdaptiveSelections Whether adaptive selections (consisting of more than one track)
|
||||||
* can be made.
|
* can be made.
|
||||||
* @param allowMultipleOverrides Whether tracks from multiple track groups can be selected.
|
* @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
|
* @param onDismissListener {@link DialogInterface.OnDismissListener} called when the dialog is
|
||||||
* dismissed.
|
* dismissed.
|
||||||
*/
|
*/
|
||||||
public static TrackSelectionDialog createForMappedTrackInfoAndParameters(
|
public static TrackSelectionDialog createForTracksAndParameters(
|
||||||
int titleId,
|
int titleId,
|
||||||
MappedTrackInfo mappedTrackInfo,
|
Tracks tracks,
|
||||||
DefaultTrackSelector.Parameters initialParameters,
|
TrackSelectionParameters trackSelectionParameters,
|
||||||
boolean allowAdaptiveSelections,
|
boolean allowAdaptiveSelections,
|
||||||
boolean allowMultipleOverrides,
|
boolean allowMultipleOverrides,
|
||||||
DialogInterface.OnClickListener onClickListener,
|
TrackSelectionListener trackSelectionListener,
|
||||||
DialogInterface.OnDismissListener onDismissListener) {
|
DialogInterface.OnDismissListener onDismissListener) {
|
||||||
TrackSelectionDialog trackSelectionDialog = new TrackSelectionDialog();
|
TrackSelectionDialog trackSelectionDialog = new TrackSelectionDialog();
|
||||||
trackSelectionDialog.init(
|
trackSelectionDialog.init(
|
||||||
|
tracks,
|
||||||
|
trackSelectionParameters,
|
||||||
titleId,
|
titleId,
|
||||||
mappedTrackInfo,
|
|
||||||
initialParameters,
|
|
||||||
allowAdaptiveSelections,
|
allowAdaptiveSelections,
|
||||||
allowMultipleOverrides,
|
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<TrackGroup, TrackSelectionOverride> overrides =
|
||||||
|
trackSelectionDialog.getOverrides(trackType);
|
||||||
|
for (TrackSelectionOverride override : overrides.values()) {
|
||||||
|
builder.addOverride(override);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
trackSelectionListener.onTracksSelected(builder.build());
|
||||||
|
},
|
||||||
onDismissListener);
|
onDismissListener);
|
||||||
return trackSelectionDialog;
|
return trackSelectionDialog;
|
||||||
}
|
}
|
||||||
@ -160,9 +171,9 @@ public final class TrackSelectionDialog extends DialogFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void init(
|
private void init(
|
||||||
|
Tracks tracks,
|
||||||
|
TrackSelectionParameters trackSelectionParameters,
|
||||||
int titleId,
|
int titleId,
|
||||||
MappedTrackInfo mappedTrackInfo,
|
|
||||||
DefaultTrackSelector.Parameters initialParameters,
|
|
||||||
boolean allowAdaptiveSelections,
|
boolean allowAdaptiveSelections,
|
||||||
boolean allowMultipleOverrides,
|
boolean allowMultipleOverrides,
|
||||||
DialogInterface.OnClickListener onClickListener,
|
DialogInterface.OnClickListener onClickListener,
|
||||||
@ -170,45 +181,49 @@ public final class TrackSelectionDialog extends DialogFragment {
|
|||||||
this.titleId = titleId;
|
this.titleId = titleId;
|
||||||
this.onClickListener = onClickListener;
|
this.onClickListener = onClickListener;
|
||||||
this.onDismissListener = onDismissListener;
|
this.onDismissListener = onDismissListener;
|
||||||
for (int i = 0; i < mappedTrackInfo.getRendererCount(); i++) {
|
|
||||||
if (showTabForRenderer(mappedTrackInfo, i)) {
|
for (int i = 0; i < SUPPORTED_TRACK_TYPES.size(); i++) {
|
||||||
int trackType = mappedTrackInfo.getRendererType(/* rendererIndex= */ i);
|
@C.TrackType int trackType = SUPPORTED_TRACK_TYPES.get(i);
|
||||||
TrackGroupArray trackGroupArray = mappedTrackInfo.getTrackGroups(i);
|
ArrayList<Tracks.Group> trackGroups = new ArrayList<>();
|
||||||
|
for (Tracks.Group trackGroup : tracks.getGroups()) {
|
||||||
|
if (trackGroup.getType() == trackType) {
|
||||||
|
trackGroups.add(trackGroup);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
if (!trackGroups.isEmpty()) {
|
||||||
TrackSelectionViewFragment tabFragment = new TrackSelectionViewFragment();
|
TrackSelectionViewFragment tabFragment = new TrackSelectionViewFragment();
|
||||||
tabFragment.init(
|
tabFragment.init(
|
||||||
mappedTrackInfo,
|
trackGroups,
|
||||||
/* rendererIndex= */ i,
|
trackSelectionParameters.disabledTrackTypes.contains(trackType),
|
||||||
initialParameters.getRendererDisabled(/* rendererIndex= */ i),
|
trackSelectionParameters.overrides,
|
||||||
initialParameters.getSelectionOverride(/* rendererIndex= */ i, trackGroupArray),
|
|
||||||
allowAdaptiveSelections,
|
allowAdaptiveSelections,
|
||||||
allowMultipleOverrides);
|
allowMultipleOverrides);
|
||||||
tabFragments.put(i, tabFragment);
|
tabFragments.put(trackType, tabFragment);
|
||||||
tabTrackTypes.add(trackType);
|
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.
|
* @param trackType The track type.
|
||||||
* @return Whether the renderer is disabled.
|
* @return Whether the disabled option is selected for the track type.
|
||||||
*/
|
*/
|
||||||
public boolean getIsDisabled(int rendererIndex) {
|
public boolean getIsDisabled(int trackType) {
|
||||||
TrackSelectionViewFragment rendererView = tabFragments.get(rendererIndex);
|
TrackSelectionViewFragment trackView = tabFragments.get(trackType);
|
||||||
return rendererView != null && rendererView.isDisabled;
|
return trackView != null && trackView.isDisabled;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the list of selected track selection overrides for the specified renderer. There will
|
* Returns the selected track overrides for the specified track type.
|
||||||
* be at most one override for each track group.
|
|
||||||
*
|
*
|
||||||
* @param rendererIndex Renderer index.
|
* @param trackType The track type.
|
||||||
* @return The list of track selection overrides for this renderer.
|
* @return The track overrides for the track type.
|
||||||
*/
|
*/
|
||||||
public List<SelectionOverride> getOverrides(int rendererIndex) {
|
public Map<TrackGroup, TrackSelectionOverride> getOverrides(int trackType) {
|
||||||
TrackSelectionViewFragment rendererView = tabFragments.get(rendererIndex);
|
TrackSelectionViewFragment trackView = tabFragments.get(trackType);
|
||||||
return rendererView == null ? Collections.emptyList() : rendererView.overrides;
|
return trackView == null ? Collections.emptyMap() : trackView.overrides;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -248,27 +263,7 @@ public final class TrackSelectionDialog extends DialogFragment {
|
|||||||
return dialogView;
|
return dialogView;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static boolean showTabForRenderer(MappedTrackInfo mappedTrackInfo, int rendererIndex) {
|
private static String getTrackTypeString(Resources resources, @C.TrackType int trackType) {
|
||||||
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) {
|
|
||||||
switch (trackType) {
|
switch (trackType) {
|
||||||
case C.TRACK_TYPE_VIDEO:
|
case C.TRACK_TYPE_VIDEO:
|
||||||
return resources.getString(R.string.exo_track_selection_title_video);
|
return resources.getString(R.string.exo_track_selection_title_video);
|
||||||
@ -289,12 +284,12 @@ public final class TrackSelectionDialog extends DialogFragment {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Fragment getItem(int position) {
|
public Fragment getItem(int position) {
|
||||||
return tabFragments.valueAt(position);
|
return tabFragments.get(tabTrackTypes.get(position));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int getCount() {
|
public int getCount() {
|
||||||
return tabFragments.size();
|
return tabTrackTypes.size();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -307,13 +302,12 @@ public final class TrackSelectionDialog extends DialogFragment {
|
|||||||
public static final class TrackSelectionViewFragment extends Fragment
|
public static final class TrackSelectionViewFragment extends Fragment
|
||||||
implements TrackSelectionView.TrackSelectionListener {
|
implements TrackSelectionView.TrackSelectionListener {
|
||||||
|
|
||||||
private MappedTrackInfo mappedTrackInfo;
|
private List<Tracks.Group> trackGroups;
|
||||||
private int rendererIndex;
|
|
||||||
private boolean allowAdaptiveSelections;
|
private boolean allowAdaptiveSelections;
|
||||||
private boolean allowMultipleOverrides;
|
private boolean allowMultipleOverrides;
|
||||||
|
|
||||||
/* package */ boolean isDisabled;
|
/* package */ boolean isDisabled;
|
||||||
/* package */ List<SelectionOverride> overrides;
|
/* package */ Map<TrackGroup, TrackSelectionOverride> overrides;
|
||||||
|
|
||||||
public TrackSelectionViewFragment() {
|
public TrackSelectionViewFragment() {
|
||||||
// Retain instance across activity re-creation to prevent losing access to init data.
|
// 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(
|
public void init(
|
||||||
MappedTrackInfo mappedTrackInfo,
|
List<Tracks.Group> trackGroups,
|
||||||
int rendererIndex,
|
boolean isDisabled,
|
||||||
boolean initialIsDisabled,
|
Map<TrackGroup, TrackSelectionOverride> overrides,
|
||||||
@Nullable SelectionOverride initialOverride,
|
|
||||||
boolean allowAdaptiveSelections,
|
boolean allowAdaptiveSelections,
|
||||||
boolean allowMultipleOverrides) {
|
boolean allowMultipleOverrides) {
|
||||||
this.mappedTrackInfo = mappedTrackInfo;
|
this.trackGroups = trackGroups;
|
||||||
this.rendererIndex = rendererIndex;
|
this.isDisabled = isDisabled;
|
||||||
this.isDisabled = initialIsDisabled;
|
|
||||||
this.overrides =
|
|
||||||
initialOverride == null
|
|
||||||
? Collections.emptyList()
|
|
||||||
: Collections.singletonList(initialOverride);
|
|
||||||
this.allowAdaptiveSelections = allowAdaptiveSelections;
|
this.allowAdaptiveSelections = allowAdaptiveSelections;
|
||||||
this.allowMultipleOverrides = allowMultipleOverrides;
|
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
|
@Override
|
||||||
@ -351,8 +344,7 @@ public final class TrackSelectionDialog extends DialogFragment {
|
|||||||
trackSelectionView.setAllowMultipleOverrides(allowMultipleOverrides);
|
trackSelectionView.setAllowMultipleOverrides(allowMultipleOverrides);
|
||||||
trackSelectionView.setAllowAdaptiveSelections(allowAdaptiveSelections);
|
trackSelectionView.setAllowAdaptiveSelections(allowAdaptiveSelections);
|
||||||
trackSelectionView.init(
|
trackSelectionView.init(
|
||||||
mappedTrackInfo,
|
trackGroups,
|
||||||
rendererIndex,
|
|
||||||
isDisabled,
|
isDisabled,
|
||||||
overrides,
|
overrides,
|
||||||
/* trackFormatComparator= */ null,
|
/* trackFormatComparator= */ null,
|
||||||
@ -361,7 +353,8 @@ public final class TrackSelectionDialog extends DialogFragment {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTrackSelectionChanged(boolean isDisabled, List<SelectionOverride> overrides) {
|
public void onTrackSelectionChanged(
|
||||||
|
boolean isDisabled, Map<TrackGroup, TrackSelectionOverride> overrides) {
|
||||||
this.isDisabled = isDisabled;
|
this.isDisabled = isDisabled;
|
||||||
this.overrides = overrides;
|
this.overrides = overrides;
|
||||||
}
|
}
|
||||||
|
@ -45,6 +45,7 @@
|
|||||||
|
|
||||||
<service
|
<service
|
||||||
android:name=".PlaybackService"
|
android:name=".PlaybackService"
|
||||||
|
android:foregroundServiceType="mediaPlayback"
|
||||||
android:exported="true">
|
android:exported="true">
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="androidx.media3.session.MediaSessionService"/>
|
<action android:name="androidx.media3.session.MediaSessionService"/>
|
||||||
|
@ -19,35 +19,118 @@ import android.app.PendingIntent.FLAG_IMMUTABLE
|
|||||||
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
import android.app.PendingIntent.FLAG_UPDATE_CURRENT
|
||||||
import android.app.TaskStackBuilder
|
import android.app.TaskStackBuilder
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
import android.net.Uri
|
|
||||||
import android.os.Build
|
import android.os.Build
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import androidx.media3.common.AudioAttributes
|
import androidx.media3.common.AudioAttributes
|
||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
|
import androidx.media3.session.CommandButton
|
||||||
import androidx.media3.session.LibraryResult
|
import androidx.media3.session.LibraryResult
|
||||||
import androidx.media3.session.MediaLibraryService
|
import androidx.media3.session.MediaLibraryService
|
||||||
import androidx.media3.session.MediaSession
|
import androidx.media3.session.MediaSession
|
||||||
|
import androidx.media3.session.MediaSession.ControllerInfo
|
||||||
|
import androidx.media3.session.SessionCommand
|
||||||
import androidx.media3.session.SessionResult
|
import androidx.media3.session.SessionResult
|
||||||
import com.google.common.collect.ImmutableList
|
import com.google.common.collect.ImmutableList
|
||||||
import com.google.common.util.concurrent.Futures
|
import com.google.common.util.concurrent.Futures
|
||||||
import com.google.common.util.concurrent.ListenableFuture
|
import com.google.common.util.concurrent.ListenableFuture
|
||||||
|
|
||||||
class PlaybackService : MediaLibraryService() {
|
class PlaybackService : MediaLibraryService() {
|
||||||
|
private val librarySessionCallback = CustomMediaLibrarySessionCallback()
|
||||||
|
|
||||||
private lateinit var player: ExoPlayer
|
private lateinit var player: ExoPlayer
|
||||||
private lateinit var mediaLibrarySession: MediaLibrarySession
|
private lateinit var mediaLibrarySession: MediaLibrarySession
|
||||||
private val librarySessionCallback = CustomMediaLibrarySessionCallback()
|
private lateinit var customCommands: List<CommandButton>
|
||||||
|
|
||||||
|
private var customLayout = ImmutableList.of<CommandButton>()
|
||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val SEARCH_QUERY_PREFIX_COMPAT = "androidx://media3-session/playFromSearch"
|
private const val SEARCH_QUERY_PREFIX_COMPAT = "androidx://media3-session/playFromSearch"
|
||||||
private const val SEARCH_QUERY_PREFIX = "androidx://media3-session/setMediaUri"
|
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"
|
||||||
|
}
|
||||||
|
|
||||||
|
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<SessionResult> {
|
||||||
|
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))
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class CustomMediaLibrarySessionCallback :
|
|
||||||
MediaLibrarySession.MediaLibrarySessionCallback {
|
|
||||||
override fun onGetLibraryRoot(
|
override fun onGetLibraryRoot(
|
||||||
session: MediaLibrarySession,
|
session: MediaLibrarySession,
|
||||||
browser: MediaSession.ControllerInfo,
|
browser: ControllerInfo,
|
||||||
params: LibraryParams?
|
params: LibraryParams?
|
||||||
): ListenableFuture<LibraryResult<MediaItem>> {
|
): ListenableFuture<LibraryResult<MediaItem>> {
|
||||||
return Futures.immediateFuture(LibraryResult.ofItem(MediaItemTree.getRootItem(), params))
|
return Futures.immediateFuture(LibraryResult.ofItem(MediaItemTree.getRootItem(), params))
|
||||||
@ -55,7 +138,7 @@ class PlaybackService : MediaLibraryService() {
|
|||||||
|
|
||||||
override fun onGetItem(
|
override fun onGetItem(
|
||||||
session: MediaLibrarySession,
|
session: MediaLibrarySession,
|
||||||
browser: MediaSession.ControllerInfo,
|
browser: ControllerInfo,
|
||||||
mediaId: String
|
mediaId: String
|
||||||
): ListenableFuture<LibraryResult<MediaItem>> {
|
): ListenableFuture<LibraryResult<MediaItem>> {
|
||||||
val item =
|
val item =
|
||||||
@ -66,9 +149,24 @@ class PlaybackService : MediaLibraryService() {
|
|||||||
return Futures.immediateFuture(LibraryResult.ofItem(item, /* params= */ null))
|
return Futures.immediateFuture(LibraryResult.ofItem(item, /* params= */ null))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
override fun onSubscribe(
|
||||||
|
session: MediaLibrarySession,
|
||||||
|
browser: ControllerInfo,
|
||||||
|
parentId: String,
|
||||||
|
params: LibraryParams?
|
||||||
|
): ListenableFuture<LibraryResult<Void>> {
|
||||||
|
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(
|
override fun onGetChildren(
|
||||||
session: MediaLibrarySession,
|
session: MediaLibrarySession,
|
||||||
browser: MediaSession.ControllerInfo,
|
browser: ControllerInfo,
|
||||||
parentId: String,
|
parentId: String,
|
||||||
page: Int,
|
page: Int,
|
||||||
pageSize: Int,
|
pageSize: Int,
|
||||||
@ -83,7 +181,21 @@ class PlaybackService : MediaLibraryService() {
|
|||||||
return Futures.immediateFuture(LibraryResult.ofItemList(children, params))
|
return Futures.immediateFuture(LibraryResult.ofItemList(children, params))
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun setMediaItemFromSearchQuery(query: String) {
|
override fun onAddMediaItems(
|
||||||
|
mediaSession: MediaSession,
|
||||||
|
controller: MediaSession.ControllerInfo,
|
||||||
|
mediaItems: List<MediaItem>
|
||||||
|
): ListenableFuture<List<MediaItem>> {
|
||||||
|
val updatedMediaItems: List<MediaItem> =
|
||||||
|
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]"
|
// Only accept query with pattern "play [Title]" or "[Title]"
|
||||||
// Where [Title]: must be exactly matched
|
// Where [Title]: must be exactly matched
|
||||||
// If no media with exact name found, play a random media instead
|
// If no media with exact name found, play a random media instead
|
||||||
@ -94,54 +206,8 @@ class PlaybackService : MediaLibraryService() {
|
|||||||
query
|
query
|
||||||
}
|
}
|
||||||
|
|
||||||
val item = MediaItemTree.getItemFromTitle(mediaTitle) ?: MediaItemTree.getRandomItem()
|
return MediaItemTree.getItemFromTitle(mediaTitle) ?: MediaItemTree.getRandomItem()
|
||||||
player.setMediaItem(item)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
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() {
|
private fun initializeSessionAndPlayer() {
|
||||||
@ -151,13 +217,10 @@ class PlaybackService : MediaLibraryService() {
|
|||||||
.build()
|
.build()
|
||||||
MediaItemTree.initialize(assets)
|
MediaItemTree.initialize(assets)
|
||||||
|
|
||||||
val parentScreenIntent = Intent(this, MainActivity::class.java)
|
val sessionActivityPendingIntent =
|
||||||
val intent = Intent(this, PlayerActivity::class.java)
|
|
||||||
|
|
||||||
val pendingIntent =
|
|
||||||
TaskStackBuilder.create(this).run {
|
TaskStackBuilder.create(this).run {
|
||||||
addNextIntent(parentScreenIntent)
|
addNextIntent(Intent(this@PlaybackService, MainActivity::class.java))
|
||||||
addNextIntent(intent)
|
addNextIntent(Intent(this@PlaybackService, PlayerActivity::class.java))
|
||||||
|
|
||||||
val immutableFlag = if (Build.VERSION.SDK_INT >= 23) FLAG_IMMUTABLE else 0
|
val immutableFlag = if (Build.VERSION.SDK_INT >= 23) FLAG_IMMUTABLE else 0
|
||||||
getPendingIntent(0, immutableFlag or FLAG_UPDATE_CURRENT)
|
getPendingIntent(0, immutableFlag or FLAG_UPDATE_CURRENT)
|
||||||
@ -165,8 +228,29 @@ class PlaybackService : MediaLibraryService() {
|
|||||||
|
|
||||||
mediaLibrarySession =
|
mediaLibrarySession =
|
||||||
MediaLibrarySession.Builder(this, player, librarySessionCallback)
|
MediaLibrarySession.Builder(this, player, librarySessionCallback)
|
||||||
.setMediaItemFiller(CustomMediaItemFiller())
|
.setSessionActivity(sessionActivityPendingIntent)
|
||||||
.setSessionActivity(pendingIntent)
|
.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()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun ignoreFuture(customLayout: ListenableFuture<SessionResult>) {
|
||||||
|
/* Do nothing. */
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,6 +19,7 @@ import android.app.Activity;
|
|||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
|
import android.text.TextUtils;
|
||||||
import android.view.Surface;
|
import android.view.Surface;
|
||||||
import android.view.SurfaceControl;
|
import android.view.SurfaceControl;
|
||||||
import android.view.SurfaceHolder;
|
import android.view.SurfaceHolder;
|
||||||
@ -35,7 +36,6 @@ import androidx.media3.common.util.Util;
|
|||||||
import androidx.media3.datasource.DataSource;
|
import androidx.media3.datasource.DataSource;
|
||||||
import androidx.media3.datasource.DefaultDataSource;
|
import androidx.media3.datasource.DefaultDataSource;
|
||||||
import androidx.media3.datasource.DefaultHttpDataSource;
|
import androidx.media3.datasource.DefaultHttpDataSource;
|
||||||
import androidx.media3.datasource.HttpDataSource;
|
|
||||||
import androidx.media3.exoplayer.ExoPlayer;
|
import androidx.media3.exoplayer.ExoPlayer;
|
||||||
import androidx.media3.exoplayer.dash.DashMediaSource;
|
import androidx.media3.exoplayer.dash.DashMediaSource;
|
||||||
import androidx.media3.exoplayer.drm.DefaultDrmSessionManager;
|
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 drmScheme = Assertions.checkNotNull(intent.getStringExtra(DRM_SCHEME_EXTRA));
|
||||||
String drmLicenseUrl = Assertions.checkNotNull(intent.getStringExtra(DRM_LICENSE_URL_EXTRA));
|
String drmLicenseUrl = Assertions.checkNotNull(intent.getStringExtra(DRM_LICENSE_URL_EXTRA));
|
||||||
UUID drmSchemeUuid = Assertions.checkNotNull(Util.getDrmUuid(drmScheme));
|
UUID drmSchemeUuid = Assertions.checkNotNull(Util.getDrmUuid(drmScheme));
|
||||||
HttpDataSource.Factory licenseDataSourceFactory = new DefaultHttpDataSource.Factory();
|
DataSource.Factory licenseDataSourceFactory = new DefaultHttpDataSource.Factory();
|
||||||
HttpMediaDrmCallback drmCallback =
|
HttpMediaDrmCallback drmCallback =
|
||||||
new HttpMediaDrmCallback(drmLicenseUrl, licenseDataSourceFactory);
|
new HttpMediaDrmCallback(drmLicenseUrl, licenseDataSourceFactory);
|
||||||
drmSessionManager =
|
drmSessionManager =
|
||||||
@ -202,13 +202,18 @@ public final class MainActivity extends Activity {
|
|||||||
|
|
||||||
DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(this);
|
DataSource.Factory dataSourceFactory = new DefaultDataSource.Factory(this);
|
||||||
MediaSource mediaSource;
|
MediaSource mediaSource;
|
||||||
@C.ContentType int type = Util.inferContentType(uri, intent.getStringExtra(EXTENSION_EXTRA));
|
@Nullable String fileExtension = intent.getStringExtra(EXTENSION_EXTRA);
|
||||||
if (type == C.TYPE_DASH) {
|
@C.ContentType
|
||||||
|
int type =
|
||||||
|
TextUtils.isEmpty(fileExtension)
|
||||||
|
? Util.inferContentType(uri)
|
||||||
|
: Util.inferContentTypeForExtension(fileExtension);
|
||||||
|
if (type == C.CONTENT_TYPE_DASH) {
|
||||||
mediaSource =
|
mediaSource =
|
||||||
new DashMediaSource.Factory(dataSourceFactory)
|
new DashMediaSource.Factory(dataSourceFactory)
|
||||||
.setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager)
|
.setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager)
|
||||||
.createMediaSource(MediaItem.fromUri(uri));
|
.createMediaSource(MediaItem.fromUri(uri));
|
||||||
} else if (type == C.TYPE_OTHER) {
|
} else if (type == C.CONTENT_TYPE_OTHER) {
|
||||||
mediaSource =
|
mediaSource =
|
||||||
new ProgressiveMediaSource.Factory(dataSourceFactory)
|
new ProgressiveMediaSource.Factory(dataSourceFactory)
|
||||||
.setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager)
|
.setDrmSessionManagerProvider(unusedMediaItem -> drmSessionManager)
|
||||||
|
22
demos/transformer/BUILD.bazel
Normal file
22
demos/transformer/BUILD.bazel
Normal file
@ -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",
|
||||||
|
)
|
@ -6,4 +6,61 @@ example by removing audio or video.
|
|||||||
See the [demos README](../README.md) for instructions on how to build and run
|
See the [demos README](../README.md) for instructions on how to build and run
|
||||||
this demo.
|
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 "<path to MediaPipe checkout>"
|
||||||
|
MEDIAPIPE_ROOT="$(pwd)"
|
||||||
|
MEDIAPIPE_TRANSFORMER_ROOT="${MEDIAPIPE_ROOT}/mediapipe/java/com/google/mediapipe/transformer"
|
||||||
|
cd "<path to the transformer demo (containing this readme)>"
|
||||||
|
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
|
[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
|
||||||
|
@ -45,6 +45,27 @@ android {
|
|||||||
// This demo app isn't indexed and doesn't have translations.
|
// This demo app isn't indexed and doesn't have translations.
|
||||||
disable 'GoogleAppIndexingWarning','MissingTranslation'
|
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 {
|
dependencies {
|
||||||
@ -56,6 +77,14 @@ dependencies {
|
|||||||
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
|
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
|
||||||
implementation 'com.google.android.material:material:' + androidxMaterialVersion
|
implementation 'com.google.android.material:material:' + androidxMaterialVersion
|
||||||
implementation project(modulePrefix + 'lib-exoplayer')
|
implementation project(modulePrefix + 'lib-exoplayer')
|
||||||
|
implementation project(modulePrefix + 'lib-exoplayer-dash')
|
||||||
implementation project(modulePrefix + 'lib-transformer')
|
implementation project(modulePrefix + 'lib-transformer')
|
||||||
implementation project(modulePrefix + 'lib-ui')
|
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'
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
@ -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);
|
||||||
|
}
|
@ -1,4 +1,4 @@
|
|||||||
#version 300 es
|
#version 100
|
||||||
// Copyright 2022 The Android Open Source Project
|
// Copyright 2022 The Android Open Source Project
|
||||||
//
|
//
|
||||||
// Licensed under the Apache License, Version 2.0 (the "License");
|
// Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
@ -12,12 +12,12 @@
|
|||||||
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
// See the License for the specific language governing permissions and
|
// See the License for the specific language governing permissions and
|
||||||
// limitations under the License.
|
// limitations under the License.
|
||||||
in vec4 aFramePosition;
|
|
||||||
in vec4 aTexCoords;
|
// ES 2 vertex shader that leaves the coordinates unchanged.
|
||||||
uniform mat4 uTexTransform;
|
|
||||||
uniform mat4 uTransformationMatrix;
|
attribute vec4 aFramePosition;
|
||||||
out vec2 vTexCoords;
|
varying vec2 vTexSamplingCoord;
|
||||||
void main() {
|
void main() {
|
||||||
gl_Position = uTransformationMatrix * aFramePosition;
|
gl_Position = aFramePosition;
|
||||||
vTexCoords = (uTexTransform * aTexCoords).xy;
|
vTexSamplingCoord = vec2(aFramePosition.x * 0.5 + 0.5, aFramePosition.y * 0.5 + 0.5);
|
||||||
}
|
}
|
@ -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.
|
||||||
|
*
|
||||||
|
* <p>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);
|
||||||
|
}
|
||||||
|
}
|
@ -32,7 +32,11 @@ import android.widget.TextView;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.appcompat.app.AlertDialog;
|
import androidx.appcompat.app.AlertDialog;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.MimeTypes;
|
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.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
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 AUDIO_MIME_TYPE = "audio_mime_type";
|
||||||
public static final String VIDEO_MIME_TYPE = "video_mime_type";
|
public static final String VIDEO_MIME_TYPE = "video_mime_type";
|
||||||
public static final String RESOLUTION_HEIGHT = "resolution_height";
|
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_X = "scale_x";
|
||||||
public static final String SCALE_Y = "scale_y";
|
public static final String SCALE_Y = "scale_y";
|
||||||
public static final String ROTATE_DEGREES = "rotate_degrees";
|
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 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 = {
|
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/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://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
|
private static final String[] URI_DESCRIPTIONS = { // same order as INPUT_URIS
|
||||||
"MP4 with H264 video and AAC audio",
|
"720p H264 video and AAC audio",
|
||||||
"MP4 with H265 video and AAC audio",
|
"1080p H265 video and AAC audio",
|
||||||
"Long MP4 with H264 video and AAC audio",
|
"360p H264 video and AAC audio",
|
||||||
"WebM with VP8 video and Vorbis 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 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 Button selectFileButton;
|
||||||
private @MonotonicNonNull TextView chosenFileTextView;
|
private @MonotonicNonNull TextView selectedFileTextView;
|
||||||
private @MonotonicNonNull CheckBox removeAudioCheckbox;
|
private @MonotonicNonNull CheckBox removeAudioCheckbox;
|
||||||
private @MonotonicNonNull CheckBox removeVideoCheckbox;
|
private @MonotonicNonNull CheckBox removeVideoCheckbox;
|
||||||
private @MonotonicNonNull CheckBox flattenForSlowMotionCheckbox;
|
private @MonotonicNonNull CheckBox flattenForSlowMotionCheckbox;
|
||||||
private @MonotonicNonNull Spinner audioMimeSpinner;
|
private @MonotonicNonNull Spinner audioMimeSpinner;
|
||||||
private @MonotonicNonNull Spinner videoMimeSpinner;
|
private @MonotonicNonNull Spinner videoMimeSpinner;
|
||||||
private @MonotonicNonNull Spinner resolutionHeightSpinner;
|
private @MonotonicNonNull Spinner resolutionHeightSpinner;
|
||||||
private @MonotonicNonNull Spinner translateSpinner;
|
|
||||||
private @MonotonicNonNull Spinner scaleSpinner;
|
private @MonotonicNonNull Spinner scaleSpinner;
|
||||||
private @MonotonicNonNull Spinner rotateSpinner;
|
private @MonotonicNonNull Spinner rotateSpinner;
|
||||||
|
private @MonotonicNonNull CheckBox trimCheckBox;
|
||||||
|
private @MonotonicNonNull CheckBox enableFallbackCheckBox;
|
||||||
|
private @MonotonicNonNull CheckBox enableRequestSdrToneMappingCheckBox;
|
||||||
private @MonotonicNonNull CheckBox enableHdrEditingCheckBox;
|
private @MonotonicNonNull CheckBox enableHdrEditingCheckBox;
|
||||||
|
private @MonotonicNonNull Button selectDemoEffectsButton;
|
||||||
|
private boolean @MonotonicNonNull [] demoEffectsSelections;
|
||||||
private int inputUriPosition;
|
private int inputUriPosition;
|
||||||
|
private long trimStartMs;
|
||||||
|
private long trimEndMs;
|
||||||
|
private float periodicVignetteCenterX;
|
||||||
|
private float periodicVignetteCenterY;
|
||||||
|
private float periodicVignetteInnerRadius;
|
||||||
|
private float periodicVignetteOuterRadius;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
@ -90,11 +139,11 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
findViewById(R.id.transform_button).setOnClickListener(this::startTransformation);
|
findViewById(R.id.transform_button).setOnClickListener(this::startTransformation);
|
||||||
|
|
||||||
chooseFileButton = findViewById(R.id.choose_file_button);
|
selectFileButton = findViewById(R.id.select_file_button);
|
||||||
chooseFileButton.setOnClickListener(this::chooseFile);
|
selectFileButton.setOnClickListener(this::selectFile);
|
||||||
|
|
||||||
chosenFileTextView = findViewById(R.id.chosen_file_text_view);
|
selectedFileTextView = findViewById(R.id.selected_file_text_view);
|
||||||
chosenFileTextView.setText(URI_DESCRIPTIONS[inputUriPosition]);
|
selectedFileTextView.setText(URI_DESCRIPTIONS[inputUriPosition]);
|
||||||
|
|
||||||
removeAudioCheckbox = findViewById(R.id.remove_audio_checkbox);
|
removeAudioCheckbox = findViewById(R.id.remove_audio_checkbox);
|
||||||
removeAudioCheckbox.setOnClickListener(this::onRemoveAudio);
|
removeAudioCheckbox.setOnClickListener(this::onRemoveAudio);
|
||||||
@ -118,11 +167,10 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
videoMimeSpinner = findViewById(R.id.video_mime_spinner);
|
videoMimeSpinner = findViewById(R.id.video_mime_spinner);
|
||||||
videoMimeSpinner.setAdapter(videoMimeAdapter);
|
videoMimeSpinner.setAdapter(videoMimeAdapter);
|
||||||
videoMimeAdapter.addAll(
|
videoMimeAdapter.addAll(
|
||||||
SAME_AS_INPUT_OPTION,
|
SAME_AS_INPUT_OPTION, MimeTypes.VIDEO_H263, MimeTypes.VIDEO_H264, MimeTypes.VIDEO_MP4V);
|
||||||
MimeTypes.VIDEO_H263,
|
if (Util.SDK_INT >= 24) {
|
||||||
MimeTypes.VIDEO_H264,
|
videoMimeAdapter.add(MimeTypes.VIDEO_H265);
|
||||||
MimeTypes.VIDEO_H265,
|
}
|
||||||
MimeTypes.VIDEO_MP4V);
|
|
||||||
|
|
||||||
ArrayAdapter<String> resolutionHeightAdapter =
|
ArrayAdapter<String> resolutionHeightAdapter =
|
||||||
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
|
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
|
||||||
@ -132,14 +180,6 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
resolutionHeightAdapter.addAll(
|
resolutionHeightAdapter.addAll(
|
||||||
SAME_AS_INPUT_OPTION, "144", "240", "360", "480", "720", "1080", "1440", "2160");
|
SAME_AS_INPUT_OPTION, "144", "240", "360", "480", "720", "1080", "1440", "2160");
|
||||||
|
|
||||||
ArrayAdapter<String> 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<String> scaleAdapter =
|
ArrayAdapter<String> scaleAdapter =
|
||||||
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
|
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
|
||||||
scaleAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_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);
|
rotateAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||||
rotateSpinner = findViewById(R.id.rotate_spinner);
|
rotateSpinner = findViewById(R.id.rotate_spinner);
|
||||||
rotateSpinner.setAdapter(rotateAdapter);
|
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);
|
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
|
@Override
|
||||||
@ -162,8 +215,8 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
super.onResume();
|
super.onResume();
|
||||||
@Nullable Uri intentUri = getIntent().getData();
|
@Nullable Uri intentUri = getIntent().getData();
|
||||||
if (intentUri != null) {
|
if (intentUri != null) {
|
||||||
checkNotNull(chooseFileButton).setEnabled(false);
|
checkNotNull(selectFileButton).setEnabled(false);
|
||||||
checkNotNull(chosenFileTextView).setText(intentUri.toString());
|
checkNotNull(selectedFileTextView).setText(intentUri.toString());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -180,13 +233,16 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
"audioMimeSpinner",
|
"audioMimeSpinner",
|
||||||
"videoMimeSpinner",
|
"videoMimeSpinner",
|
||||||
"resolutionHeightSpinner",
|
"resolutionHeightSpinner",
|
||||||
"translateSpinner",
|
|
||||||
"scaleSpinner",
|
"scaleSpinner",
|
||||||
"rotateSpinner",
|
"rotateSpinner",
|
||||||
"enableHdrEditingCheckBox"
|
"trimCheckBox",
|
||||||
|
"enableFallbackCheckBox",
|
||||||
|
"enableRequestSdrToneMappingCheckBox",
|
||||||
|
"enableHdrEditingCheckBox",
|
||||||
|
"demoEffectsSelections"
|
||||||
})
|
})
|
||||||
private void startTransformation(View view) {
|
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 bundle = new Bundle();
|
||||||
bundle.putBoolean(SHOULD_REMOVE_AUDIO, removeAudioCheckbox.isChecked());
|
bundle.putBoolean(SHOULD_REMOVE_AUDIO, removeAudioCheckbox.isChecked());
|
||||||
bundle.putBoolean(SHOULD_REMOVE_VIDEO, removeVideoCheckbox.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)) {
|
if (!SAME_AS_INPUT_OPTION.equals(selectedResolutionHeight)) {
|
||||||
bundle.putInt(RESOLUTION_HEIGHT, Integer.parseInt(selectedResolutionHeight));
|
bundle.putInt(RESOLUTION_HEIGHT, Integer.parseInt(selectedResolutionHeight));
|
||||||
}
|
}
|
||||||
String selectedTranslate = String.valueOf(translateSpinner.getSelectedItem());
|
|
||||||
if (!SAME_AS_INPUT_OPTION.equals(selectedTranslate)) {
|
|
||||||
List<String> 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());
|
String selectedScale = String.valueOf(scaleSpinner.getSelectedItem());
|
||||||
if (!SAME_AS_INPUT_OPTION.equals(selectedScale)) {
|
if (!SAME_AS_INPUT_OPTION.equals(selectedScale)) {
|
||||||
List<String> scaleXY = Arrays.asList(selectedScale.split(", "));
|
List<String> scaleXY = Arrays.asList(selectedScale.split(", "));
|
||||||
@ -221,7 +270,19 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
if (!SAME_AS_INPUT_OPTION.equals(selectedRotate)) {
|
if (!SAME_AS_INPUT_OPTION.equals(selectedRotate)) {
|
||||||
bundle.putFloat(ROTATE_DEGREES, Float.parseFloat(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.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);
|
transformerIntent.putExtras(bundle);
|
||||||
|
|
||||||
@Nullable Uri intentUri = getIntent().getData();
|
@Nullable Uri intentUri = getIntent().getData();
|
||||||
@ -231,19 +292,82 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
startActivity(transformerIntent);
|
startActivity(transformerIntent);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void chooseFile(View view) {
|
private void selectFile(View view) {
|
||||||
new AlertDialog.Builder(/* context= */ this)
|
new AlertDialog.Builder(/* context= */ this)
|
||||||
.setTitle(R.string.choose_file_title)
|
.setTitle(R.string.select_file_title)
|
||||||
.setSingleChoiceItems(URI_DESCRIPTIONS, inputUriPosition, this::selectFileInDialog)
|
.setSingleChoiceItems(URI_DESCRIPTIONS, inputUriPosition, this::selectFileInDialog)
|
||||||
.setPositiveButton(android.R.string.ok, /* listener= */ null)
|
.setPositiveButton(android.R.string.ok, /* listener= */ null)
|
||||||
.create()
|
.create()
|
||||||
.show();
|
.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<Float> 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) {
|
private void selectFileInDialog(DialogInterface dialog, int which) {
|
||||||
inputUriPosition = 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<Float> radiusRange = radiusRangeSlider.getValues();
|
||||||
|
periodicVignetteInnerRadius = radiusRange.get(0);
|
||||||
|
periodicVignetteOuterRadius = radiusRange.get(1);
|
||||||
|
})
|
||||||
|
.create()
|
||||||
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresNonNull({
|
@RequiresNonNull({
|
||||||
@ -251,10 +375,11 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
"audioMimeSpinner",
|
"audioMimeSpinner",
|
||||||
"videoMimeSpinner",
|
"videoMimeSpinner",
|
||||||
"resolutionHeightSpinner",
|
"resolutionHeightSpinner",
|
||||||
"translateSpinner",
|
|
||||||
"scaleSpinner",
|
"scaleSpinner",
|
||||||
"rotateSpinner",
|
"rotateSpinner",
|
||||||
"enableHdrEditingCheckBox"
|
"enableRequestSdrToneMappingCheckBox",
|
||||||
|
"enableHdrEditingCheckBox",
|
||||||
|
"selectDemoEffectsButton"
|
||||||
})
|
})
|
||||||
private void onRemoveAudio(View view) {
|
private void onRemoveAudio(View view) {
|
||||||
if (((CheckBox) view).isChecked()) {
|
if (((CheckBox) view).isChecked()) {
|
||||||
@ -270,10 +395,11 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
"audioMimeSpinner",
|
"audioMimeSpinner",
|
||||||
"videoMimeSpinner",
|
"videoMimeSpinner",
|
||||||
"resolutionHeightSpinner",
|
"resolutionHeightSpinner",
|
||||||
"translateSpinner",
|
|
||||||
"scaleSpinner",
|
"scaleSpinner",
|
||||||
"rotateSpinner",
|
"rotateSpinner",
|
||||||
"enableHdrEditingCheckBox"
|
"enableRequestSdrToneMappingCheckBox",
|
||||||
|
"enableHdrEditingCheckBox",
|
||||||
|
"selectDemoEffectsButton"
|
||||||
})
|
})
|
||||||
private void onRemoveVideo(View view) {
|
private void onRemoveVideo(View view) {
|
||||||
if (((CheckBox) view).isChecked()) {
|
if (((CheckBox) view).isChecked()) {
|
||||||
@ -288,26 +414,34 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
"audioMimeSpinner",
|
"audioMimeSpinner",
|
||||||
"videoMimeSpinner",
|
"videoMimeSpinner",
|
||||||
"resolutionHeightSpinner",
|
"resolutionHeightSpinner",
|
||||||
"translateSpinner",
|
|
||||||
"scaleSpinner",
|
"scaleSpinner",
|
||||||
"rotateSpinner",
|
"rotateSpinner",
|
||||||
"enableHdrEditingCheckBox"
|
"enableRequestSdrToneMappingCheckBox",
|
||||||
|
"enableHdrEditingCheckBox",
|
||||||
|
"selectDemoEffectsButton"
|
||||||
})
|
})
|
||||||
private void enableTrackSpecificOptions(boolean isAudioEnabled, boolean isVideoEnabled) {
|
private void enableTrackSpecificOptions(boolean isAudioEnabled, boolean isVideoEnabled) {
|
||||||
audioMimeSpinner.setEnabled(isAudioEnabled);
|
audioMimeSpinner.setEnabled(isAudioEnabled);
|
||||||
videoMimeSpinner.setEnabled(isVideoEnabled);
|
videoMimeSpinner.setEnabled(isVideoEnabled);
|
||||||
resolutionHeightSpinner.setEnabled(isVideoEnabled);
|
resolutionHeightSpinner.setEnabled(isVideoEnabled);
|
||||||
translateSpinner.setEnabled(isVideoEnabled);
|
|
||||||
scaleSpinner.setEnabled(isVideoEnabled);
|
scaleSpinner.setEnabled(isVideoEnabled);
|
||||||
rotateSpinner.setEnabled(isVideoEnabled);
|
rotateSpinner.setEnabled(isVideoEnabled);
|
||||||
|
enableRequestSdrToneMappingCheckBox.setEnabled(
|
||||||
|
isRequestSdrToneMappingSupported() && isVideoEnabled);
|
||||||
enableHdrEditingCheckBox.setEnabled(isVideoEnabled);
|
enableHdrEditingCheckBox.setEnabled(isVideoEnabled);
|
||||||
|
selectDemoEffectsButton.setEnabled(isVideoEnabled);
|
||||||
|
|
||||||
findViewById(R.id.audio_mime_text_view).setEnabled(isAudioEnabled);
|
findViewById(R.id.audio_mime_text_view).setEnabled(isAudioEnabled);
|
||||||
findViewById(R.id.video_mime_text_view).setEnabled(isVideoEnabled);
|
findViewById(R.id.video_mime_text_view).setEnabled(isVideoEnabled);
|
||||||
findViewById(R.id.resolution_height_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.scale).setEnabled(isVideoEnabled);
|
||||||
findViewById(R.id.rotate).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);
|
findViewById(R.id.hdr_editing).setEnabled(isVideoEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static boolean isRequestSdrToneMappingSupported() {
|
||||||
|
return Util.SDK_INT >= 31;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -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;
|
||||||
|
}
|
||||||
|
}
|
@ -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.
|
||||||
|
*
|
||||||
|
* <p>The inner radius of the vignette effect oscillates smoothly between {@code minInnerRadius}
|
||||||
|
* and {@code maxInnerRadius}.
|
||||||
|
*
|
||||||
|
* <p>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.
|
||||||
|
*
|
||||||
|
* <p>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();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -21,7 +21,6 @@ import static androidx.media3.common.util.Assertions.checkNotNull;
|
|||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.graphics.Matrix;
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
@ -32,6 +31,7 @@ import android.view.ViewGroup;
|
|||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.annotation.StringRes;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
@ -39,17 +39,24 @@ import androidx.media3.common.util.Log;
|
|||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import androidx.media3.exoplayer.ExoPlayer;
|
import androidx.media3.exoplayer.ExoPlayer;
|
||||||
import androidx.media3.exoplayer.util.DebugTextViewHelper;
|
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.ProgressHolder;
|
||||||
|
import androidx.media3.transformer.SingleFrameGlTextureProcessor;
|
||||||
import androidx.media3.transformer.TransformationException;
|
import androidx.media3.transformer.TransformationException;
|
||||||
import androidx.media3.transformer.TransformationRequest;
|
import androidx.media3.transformer.TransformationRequest;
|
||||||
|
import androidx.media3.transformer.TransformationResult;
|
||||||
import androidx.media3.transformer.Transformer;
|
import androidx.media3.transformer.Transformer;
|
||||||
import androidx.media3.ui.AspectRatioFrameLayout;
|
import androidx.media3.ui.AspectRatioFrameLayout;
|
||||||
import androidx.media3.ui.PlayerView;
|
import androidx.media3.ui.PlayerView;
|
||||||
import com.google.android.material.progressindicator.LinearProgressIndicator;
|
import com.google.android.material.progressindicator.LinearProgressIndicator;
|
||||||
import com.google.common.base.Stopwatch;
|
import com.google.common.base.Stopwatch;
|
||||||
import com.google.common.base.Ticker;
|
import com.google.common.base.Ticker;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
import java.lang.reflect.Constructor;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
@ -145,9 +152,10 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
externalCacheFile = createExternalCacheFile("transformer-output.mp4");
|
externalCacheFile = createExternalCacheFile("transformer-output.mp4");
|
||||||
String filePath = externalCacheFile.getAbsolutePath();
|
String filePath = externalCacheFile.getAbsolutePath();
|
||||||
@Nullable Bundle bundle = intent.getExtras();
|
@Nullable Bundle bundle = intent.getExtras();
|
||||||
|
MediaItem mediaItem = createMediaItem(bundle, uri);
|
||||||
Transformer transformer = createTransformer(bundle, filePath);
|
Transformer transformer = createTransformer(bundle, filePath);
|
||||||
transformationStopwatch.start();
|
transformationStopwatch.start();
|
||||||
transformer.startTransformation(MediaItem.fromUri(uri), filePath);
|
transformer.startTransformation(mediaItem, filePath);
|
||||||
this.transformer = transformer;
|
this.transformer = transformer;
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new IllegalStateException(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.
|
// Create a cache file, resetting it if it already exists.
|
||||||
private File createExternalCacheFile(String fileName) throws IOException {
|
private File createExternalCacheFile(String fileName) throws IOException {
|
||||||
File file = new File(getExternalCacheDir(), fileName);
|
File file = new File(getExternalCacheDir(), fileName);
|
||||||
@ -214,22 +240,88 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
if (resolutionHeight != C.LENGTH_UNSET) {
|
if (resolutionHeight != C.LENGTH_UNSET) {
|
||||||
requestBuilder.setResolution(resolutionHeight);
|
requestBuilder.setResolution(resolutionHeight);
|
||||||
}
|
}
|
||||||
Matrix transformationMatrix = getTransformationMatrix(bundle);
|
|
||||||
if (!transformationMatrix.isIdentity()) {
|
float scaleX = bundle.getFloat(ConfigurationActivity.SCALE_X, /* defaultValue= */ 1);
|
||||||
requestBuilder.setTransformationMatrix(transformationMatrix);
|
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(
|
requestBuilder.experimental_setEnableHdrEditing(
|
||||||
bundle.getBoolean(ConfigurationActivity.ENABLE_HDR_EDITING));
|
bundle.getBoolean(ConfigurationActivity.ENABLE_HDR_EDITING));
|
||||||
transformerBuilder
|
transformerBuilder
|
||||||
.setTransformationRequest(requestBuilder.build())
|
.setTransformationRequest(requestBuilder.build())
|
||||||
.setRemoveAudio(bundle.getBoolean(ConfigurationActivity.SHOULD_REMOVE_AUDIO))
|
.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<GlEffect> 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
|
return transformerBuilder
|
||||||
.addListener(
|
.addListener(
|
||||||
new Transformer.Listener() {
|
new Transformer.Listener() {
|
||||||
@Override
|
@Override
|
||||||
public void onTransformationCompleted(MediaItem mediaItem) {
|
public void onTransformationCompleted(
|
||||||
|
MediaItem mediaItem, TransformationResult transformationResult) {
|
||||||
TransformerActivity.this.onTransformationCompleted(filePath);
|
TransformerActivity.this.onTransformationCompleted(filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -243,26 +335,6 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
.build();
|
.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({
|
@RequiresNonNull({
|
||||||
"informationTextView",
|
"informationTextView",
|
||||||
"progressViewGroup",
|
"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 {
|
private final class DemoDebugViewProvider implements Transformer.DebugViewProvider {
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -34,18 +34,18 @@
|
|||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toTopOf="parent" />
|
app:layout_constraintTop_toTopOf="parent" />
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/choose_file_button"
|
android:id="@+id/select_file_button"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="32dp"
|
android:layout_marginTop="32dp"
|
||||||
android:layout_marginStart="32dp"
|
android:layout_marginStart="32dp"
|
||||||
android:layout_marginEnd="32dp"
|
android:layout_marginEnd="32dp"
|
||||||
android:text="@string/choose_file_title"
|
android:text="@string/select_file_title"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/configuration_text_view"
|
app:layout_constraintTop_toBottomOf="@+id/configuration_text_view"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent" />
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/chosen_file_text_view"
|
android:id="@+id/selected_file_text_view"
|
||||||
android:layout_width="0dp"
|
android:layout_width="0dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginTop="12dp"
|
android:layout_marginTop="12dp"
|
||||||
@ -57,14 +57,14 @@
|
|||||||
android:gravity="center"
|
android:gravity="center"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/choose_file_button" />
|
app:layout_constraintTop_toBottomOf="@+id/select_file_button" />
|
||||||
<androidx.core.widget.NestedScrollView
|
<androidx.core.widget.NestedScrollView
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="0dp"
|
android:layout_height="0dp"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent"
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
app:layout_constraintTop_toBottomOf="@+id/chosen_file_text_view"
|
app:layout_constraintTop_toBottomOf="@+id/selected_file_text_view"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/transform_button">
|
app:layout_constraintBottom_toTopOf="@+id/select_demo_effects_button">
|
||||||
<TableLayout
|
<TableLayout
|
||||||
android:layout_width="fill_parent"
|
android:layout_width="fill_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
@ -137,17 +137,6 @@
|
|||||||
android:layout_gravity="right|center_vertical"
|
android:layout_gravity="right|center_vertical"
|
||||||
android:gravity="right" />
|
android:gravity="right" />
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow
|
|
||||||
android:layout_weight="1"
|
|
||||||
android:gravity="center_vertical" >
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/translate"
|
|
||||||
android:text="@string/translate"/>
|
|
||||||
<Spinner
|
|
||||||
android:id="@+id/translate_spinner"
|
|
||||||
android:layout_gravity="right|center_vertical"
|
|
||||||
android:gravity="right" />
|
|
||||||
</TableRow>
|
|
||||||
<TableRow
|
<TableRow
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:gravity="center_vertical" >
|
android:gravity="center_vertical" >
|
||||||
@ -170,6 +159,36 @@
|
|||||||
android:layout_gravity="right|center_vertical"
|
android:layout_gravity="right|center_vertical"
|
||||||
android:gravity="right" />
|
android:gravity="right" />
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
<TableRow
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center_vertical" >
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/trim"
|
||||||
|
android:text="@string/trim" />
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/trim_checkbox"
|
||||||
|
android:layout_gravity="right" />
|
||||||
|
</TableRow>
|
||||||
|
<TableRow
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center_vertical" >
|
||||||
|
<TextView
|
||||||
|
android:text="@string/enable_fallback" />
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/enable_fallback_checkbox"
|
||||||
|
android:layout_gravity="right"
|
||||||
|
android:checked="true"/>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center_vertical" >
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/request_sdr_tone_mapping"
|
||||||
|
android:text="@string/request_sdr_tone_mapping" />
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/request_sdr_tone_mapping_checkbox"
|
||||||
|
android:layout_gravity="right" />
|
||||||
|
</TableRow>
|
||||||
<TableRow
|
<TableRow
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:gravity="center_vertical" >
|
android:gravity="center_vertical" >
|
||||||
@ -182,6 +201,17 @@
|
|||||||
</TableRow>
|
</TableRow>
|
||||||
</TableLayout>
|
</TableLayout>
|
||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.core.widget.NestedScrollView>
|
||||||
|
<Button
|
||||||
|
android:id="@+id/select_demo_effects_button"
|
||||||
|
android:layout_width="wrap_content"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:layout_marginStart="32dp"
|
||||||
|
android:layout_marginEnd="32dp"
|
||||||
|
android:text="@string/select_demo_effects"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/transform_button"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/transform_button"
|
android:id="@+id/transform_button"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
|
@ -0,0 +1,72 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ 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.
|
||||||
|
-->
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
tools:context=".ConfigurationActivity">
|
||||||
|
|
||||||
|
<TableLayout
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:stretchColumns="1"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:measureWithLargestChild="true"
|
||||||
|
android:paddingLeft="24dp"
|
||||||
|
android:paddingRight="12dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent">
|
||||||
|
<TableRow
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center_vertical" >
|
||||||
|
<TextView
|
||||||
|
android:text="@string/center_x" />
|
||||||
|
<com.google.android.material.slider.Slider
|
||||||
|
android:id="@+id/periodic_vignette_center_x_slider"
|
||||||
|
android:valueFrom="0.0"
|
||||||
|
android:value="0.5"
|
||||||
|
android:valueTo="1.0"
|
||||||
|
android:layout_gravity="right"/>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center_vertical" >
|
||||||
|
<TextView
|
||||||
|
android:text="@string/center_y" />
|
||||||
|
<com.google.android.material.slider.Slider
|
||||||
|
android:id="@+id/periodic_vignette_center_y_slider"
|
||||||
|
android:valueFrom="0.0"
|
||||||
|
android:value="0.5"
|
||||||
|
android:valueTo="1.0"
|
||||||
|
android:layout_gravity="right"/>
|
||||||
|
</TableRow>
|
||||||
|
<TableRow
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center_vertical" >
|
||||||
|
<TextView
|
||||||
|
android:text="@string/radius_range" />
|
||||||
|
<com.google.android.material.slider.RangeSlider
|
||||||
|
android:id="@+id/periodic_vignette_radius_range_slider"
|
||||||
|
android:valueFrom="0.0"
|
||||||
|
android:valueTo="1.414"
|
||||||
|
android:layout_gravity="right"/>
|
||||||
|
</TableRow>
|
||||||
|
</TableLayout>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
48
demos/transformer/src/main/res/layout/trim_options.xml
Normal file
48
demos/transformer/src/main/res/layout/trim_options.xml
Normal file
@ -0,0 +1,48 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ 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.
|
||||||
|
-->
|
||||||
|
<androidx.constraintlayout.widget.ConstraintLayout xmlns:android="http://schemas.android.com/apk/res/android"
|
||||||
|
xmlns:app="http://schemas.android.com/apk/res-auto"
|
||||||
|
xmlns:tools="http://schemas.android.com/tools"
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="fill_parent"
|
||||||
|
tools:context=".ConfigurationActivity">
|
||||||
|
|
||||||
|
<TableLayout
|
||||||
|
android:layout_width="fill_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:stretchColumns="1"
|
||||||
|
android:layout_marginTop="32dp"
|
||||||
|
android:measureWithLargestChild="true"
|
||||||
|
android:paddingLeft="24dp"
|
||||||
|
android:paddingRight="12dp"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent"
|
||||||
|
app:layout_constraintTop_toTopOf="parent"
|
||||||
|
app:layout_constraintBottom_toBottomOf="parent">
|
||||||
|
<TableRow
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center_vertical" >
|
||||||
|
<TextView
|
||||||
|
android:text="@string/trim_range" />
|
||||||
|
<com.google.android.material.slider.RangeSlider
|
||||||
|
android:id="@+id/trim_bounds_range_slider"
|
||||||
|
android:valueFrom="0.0"
|
||||||
|
android:valueTo="60.0"
|
||||||
|
android:layout_gravity="right"/>
|
||||||
|
</TableRow>
|
||||||
|
</TableLayout>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -17,22 +17,31 @@
|
|||||||
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
<resources xmlns:xliff="urn:oasis:names:tc:xliff:document:1.2">
|
||||||
<string name="app_name" translatable="false">Transformer Demo</string>
|
<string name="app_name" translatable="false">Transformer Demo</string>
|
||||||
<string name="configuration" translatable="false">Configuration</string>
|
<string name="configuration" translatable="false">Configuration</string>
|
||||||
<string name="choose_file_title" translatable="false">Choose file</string>
|
<string name="select_file_title" translatable="false">Choose file</string>
|
||||||
<string name="remove_audio" translatable="false">Remove audio</string>
|
<string name="remove_audio" translatable="false">Remove audio</string>
|
||||||
<string name="remove_video" translatable="false">Remove video</string>
|
<string name="remove_video" translatable="false">Remove video</string>
|
||||||
<string name="flatten_for_slow_motion" translatable="false">Flatten for slow motion</string>
|
<string name="flatten_for_slow_motion" translatable="false">Flatten for slow motion</string>
|
||||||
<string name="audio_mime" translatable="false">Output audio MIME type</string>
|
<string name="audio_mime" translatable="false">Output audio MIME type</string>
|
||||||
<string name="video_mime" translatable="false">Output video MIME type</string>
|
<string name="video_mime" translatable="false">Output video MIME type</string>
|
||||||
<string name="resolution_height" translatable="false">Output video resolution</string>
|
<string name="resolution_height" translatable="false">Output video resolution</string>
|
||||||
<string name="translate" translatable="false">Translate video</string>
|
|
||||||
<string name="scale" translatable="false">Scale video</string>
|
<string name="scale" translatable="false">Scale video</string>
|
||||||
<string name="rotate" translatable="false">Rotate video (degrees)</string>
|
<string name="rotate" translatable="false">Rotate video (degrees)</string>
|
||||||
<string name="transform" translatable="false">Transform</string>
|
<string name="enable_fallback" translatable="false">Enable fallback</string>
|
||||||
|
<string name="trim" translatable="false">Trim</string>
|
||||||
|
<string name="request_sdr_tone_mapping" translatable="false">Request SDR tone-mapping (API 31+)</string>
|
||||||
<string name="hdr_editing" translatable="false">[Experimental] HDR editing</string>
|
<string name="hdr_editing" translatable="false">[Experimental] HDR editing</string>
|
||||||
|
<string name="select_demo_effects" translatable="false">Add demo effects</string>
|
||||||
|
<string name="periodic_vignette_options" translatable="false">Periodic vignette options</string>
|
||||||
|
<string name="no_media_pipe_error" translatable="false">Failed to load MediaPipe processor. Check the README for instructions.</string>
|
||||||
|
<string name="transform" translatable="false">Transform</string>
|
||||||
<string name="debug_preview" translatable="false">Debug preview:</string>
|
<string name="debug_preview" translatable="false">Debug preview:</string>
|
||||||
<string name="debug_preview_not_available" translatable="false">No debug preview available.</string>
|
<string name="debug_preview_not_available" translatable="false">No debug preview available.</string>
|
||||||
<string name="transformation_started" translatable="false">Transformation started</string>
|
<string name="transformation_started" translatable="false">Transformation started</string>
|
||||||
<string name="transformation_timer" translatable="false">Transformation started %d seconds ago.</string>
|
<string name="transformation_timer" translatable="false">Transformation started %d seconds ago.</string>
|
||||||
<string name="transformation_completed" translatable="false">Transformation completed in %d seconds.</string>
|
<string name="transformation_completed" translatable="false">Transformation completed in %d seconds.</string>
|
||||||
<string name="transformation_error" translatable="false">Transformation error</string>
|
<string name="transformation_error" translatable="false">Transformation error</string>
|
||||||
|
<string name="center_x">Center X</string>
|
||||||
|
<string name="center_y">Center Y</string>
|
||||||
|
<string name="radius_range">Radius range</string>
|
||||||
|
<string name="trim_range">Bounds in seconds</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
19
demos/transformer/src/withMediaPipe/AndroidManifest.xml
Normal file
19
demos/transformer/src/withMediaPipe/AndroidManifest.xml
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
<?xml version="1.0" encoding="utf-8"?>
|
||||||
|
<!--
|
||||||
|
~ 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.
|
||||||
|
-->
|
||||||
|
<manifest package="androidx.media3.demo.transformer">
|
||||||
|
<uses-sdk />
|
||||||
|
</manifest>
|
@ -0,0 +1,13 @@
|
|||||||
|
# Demo MediaPipe graph that shows edges using a SobelEdgesCalculator.
|
||||||
|
input_stream: "input_video"
|
||||||
|
output_stream: "output_video"
|
||||||
|
node: {
|
||||||
|
calculator: "LuminanceCalculator"
|
||||||
|
input_stream: "input_video"
|
||||||
|
output_stream: "luma_video"
|
||||||
|
}
|
||||||
|
node: {
|
||||||
|
calculator: "SobelEdgesCalculator"
|
||||||
|
input_stream: "luma_video"
|
||||||
|
output_stream: "output_video"
|
||||||
|
}
|
@ -0,0 +1,175 @@
|
|||||||
|
/*
|
||||||
|
* 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.checkState;
|
||||||
|
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.opengl.EGL14;
|
||||||
|
import android.opengl.GLES20;
|
||||||
|
import android.util.Size;
|
||||||
|
import androidx.media3.common.util.ConditionVariable;
|
||||||
|
import androidx.media3.common.util.GlProgram;
|
||||||
|
import androidx.media3.common.util.GlUtil;
|
||||||
|
import androidx.media3.common.util.LibraryLoader;
|
||||||
|
import androidx.media3.transformer.FrameProcessingException;
|
||||||
|
import androidx.media3.transformer.SingleFrameGlTextureProcessor;
|
||||||
|
import com.google.mediapipe.components.FrameProcessor;
|
||||||
|
import com.google.mediapipe.framework.AndroidAssetUtil;
|
||||||
|
import com.google.mediapipe.framework.AppTextureFrame;
|
||||||
|
import com.google.mediapipe.framework.TextureFrame;
|
||||||
|
import com.google.mediapipe.glutil.EglManager;
|
||||||
|
import java.io.IOException;
|
||||||
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Runs a MediaPipe graph on input frames. The implementation is currently limited to graphs that
|
||||||
|
* can immediately produce one output frame per input frame.
|
||||||
|
*/
|
||||||
|
/* package */ final class MediaPipeProcessor implements SingleFrameGlTextureProcessor {
|
||||||
|
|
||||||
|
private static final LibraryLoader LOADER =
|
||||||
|
new LibraryLoader("mediapipe_jni") {
|
||||||
|
@Override
|
||||||
|
protected void loadLibrary(String name) {
|
||||||
|
System.loadLibrary(name);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
static {
|
||||||
|
// Not all build configurations require OpenCV to be loaded separately, so attempt to load the
|
||||||
|
// library but ignore the error if it's not present.
|
||||||
|
try {
|
||||||
|
System.loadLibrary("opencv_java3");
|
||||||
|
} catch (UnsatisfiedLinkError e) {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final String COPY_VERTEX_SHADER_NAME = "vertex_shader_copy_es2.glsl";
|
||||||
|
private static final String COPY_FRAGMENT_SHADER_NAME = "shaders/fragment_shader_copy_es2.glsl";
|
||||||
|
|
||||||
|
private final String graphName;
|
||||||
|
private final String inputStreamName;
|
||||||
|
private final String outputStreamName;
|
||||||
|
private final ConditionVariable frameProcessorConditionVariable;
|
||||||
|
|
||||||
|
private @MonotonicNonNull FrameProcessor frameProcessor;
|
||||||
|
private int inputWidth;
|
||||||
|
private int inputHeight;
|
||||||
|
private int inputTexId;
|
||||||
|
private @MonotonicNonNull GlProgram glProgram;
|
||||||
|
private @MonotonicNonNull TextureFrame outputFrame;
|
||||||
|
private @MonotonicNonNull RuntimeException frameProcessorPendingError;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new texture processor that wraps a MediaPipe graph.
|
||||||
|
*
|
||||||
|
* @param graphName Name of a MediaPipe graph asset to load.
|
||||||
|
* @param inputStreamName Name of the input video stream in the graph.
|
||||||
|
* @param outputStreamName Name of the input video stream in the graph.
|
||||||
|
*/
|
||||||
|
public MediaPipeProcessor(String graphName, String inputStreamName, String outputStreamName) {
|
||||||
|
checkState(LOADER.isAvailable());
|
||||||
|
this.graphName = graphName;
|
||||||
|
this.inputStreamName = inputStreamName;
|
||||||
|
this.outputStreamName = outputStreamName;
|
||||||
|
frameProcessorConditionVariable = new ConditionVariable();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void initialize(Context context, int inputTexId, int inputWidth, int inputHeight)
|
||||||
|
throws IOException {
|
||||||
|
this.inputTexId = inputTexId;
|
||||||
|
this.inputWidth = inputWidth;
|
||||||
|
this.inputHeight = inputHeight;
|
||||||
|
glProgram = new GlProgram(context, COPY_VERTEX_SHADER_NAME, COPY_FRAGMENT_SHADER_NAME);
|
||||||
|
|
||||||
|
AndroidAssetUtil.initializeNativeAssetManager(context);
|
||||||
|
|
||||||
|
EglManager eglManager = new EglManager(EGL14.eglGetCurrentContext());
|
||||||
|
frameProcessor =
|
||||||
|
new FrameProcessor(
|
||||||
|
context, eglManager.getNativeContext(), graphName, inputStreamName, outputStreamName);
|
||||||
|
|
||||||
|
// Unblock drawFrame when there is an output frame or an error.
|
||||||
|
frameProcessor.setConsumer(
|
||||||
|
frame -> {
|
||||||
|
outputFrame = frame;
|
||||||
|
frameProcessorConditionVariable.open();
|
||||||
|
});
|
||||||
|
frameProcessor.setAsynchronousErrorListener(
|
||||||
|
error -> {
|
||||||
|
frameProcessorPendingError = error;
|
||||||
|
frameProcessorConditionVariable.open();
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Size getOutputSize() {
|
||||||
|
return new Size(inputWidth, inputHeight);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void drawFrame(long presentationTimeUs) throws FrameProcessingException {
|
||||||
|
frameProcessorConditionVariable.close();
|
||||||
|
|
||||||
|
// Pass the input frame to MediaPipe.
|
||||||
|
AppTextureFrame appTextureFrame = new AppTextureFrame(inputTexId, inputWidth, inputHeight);
|
||||||
|
appTextureFrame.setTimestamp(presentationTimeUs);
|
||||||
|
checkStateNotNull(frameProcessor).onNewFrame(appTextureFrame);
|
||||||
|
|
||||||
|
// Wait for output to be passed to the consumer.
|
||||||
|
try {
|
||||||
|
frameProcessorConditionVariable.block();
|
||||||
|
} catch (InterruptedException e) {
|
||||||
|
// Propagate the interrupted flag so the next blocking operation will throw.
|
||||||
|
// TODO(b/230469581): The next processor that runs will not have valid input due to returning
|
||||||
|
// early here. This could be fixed by checking for interruption in the outer loop that runs
|
||||||
|
// through the texture processors.
|
||||||
|
Thread.currentThread().interrupt();
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
|
||||||
|
if (frameProcessorPendingError != null) {
|
||||||
|
throw new FrameProcessingException(frameProcessorPendingError);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Copy from MediaPipe's output texture to the current output.
|
||||||
|
try {
|
||||||
|
checkStateNotNull(glProgram).use();
|
||||||
|
glProgram.setSamplerTexIdUniform(
|
||||||
|
"uTexSampler", checkStateNotNull(outputFrame).getTextureName(), /* texUnitIndex= */ 0);
|
||||||
|
glProgram.setBufferAttribute(
|
||||||
|
"aFramePosition",
|
||||||
|
GlUtil.getNormalizedCoordinateBounds(),
|
||||||
|
GlUtil.HOMOGENEOUS_COORDINATE_VECTOR_SIZE);
|
||||||
|
glProgram.bindAttributesAndUniforms();
|
||||||
|
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4);
|
||||||
|
GlUtil.checkGlError();
|
||||||
|
} catch (GlUtil.GlException e) {
|
||||||
|
throw new FrameProcessingException(e);
|
||||||
|
} finally {
|
||||||
|
checkStateNotNull(outputFrame).release();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void release() {
|
||||||
|
checkStateNotNull(frameProcessor).close();
|
||||||
|
}
|
||||||
|
}
|
2
gradle/wrapper/gradle-wrapper.properties
vendored
2
gradle/wrapper/gradle-wrapper.properties
vendored
@ -1,5 +1,5 @@
|
|||||||
distributionBase=GRADLE_USER_HOME
|
distributionBase=GRADLE_USER_HOME
|
||||||
distributionPath=wrapper/dists
|
distributionPath=wrapper/dists
|
||||||
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4-all.zip
|
distributionUrl=https\://services.gradle.org/distributions/gradle-7.4.2-all.zip
|
||||||
zipStoreBase=GRADLE_USER_HOME
|
zipStoreBase=GRADLE_USER_HOME
|
||||||
zipStorePath=wrapper/dists
|
zipStorePath=wrapper/dists
|
||||||
|
@ -48,9 +48,21 @@ class CombinedJavadocPlugin implements Plugin<Project> {
|
|||||||
libraryModule.android.libraryVariants.all { variant ->
|
libraryModule.android.libraryVariants.all { variant ->
|
||||||
def name = variant.buildType.name
|
def name = variant.buildType.name
|
||||||
if (name == "release") {
|
if (name == "release") {
|
||||||
|
// Works around b/234569640 that causes different versions of the androidx.media
|
||||||
|
// jar to be on the classpath.
|
||||||
|
def allJarFiles = []
|
||||||
|
allJarFiles.addAll(variant.javaCompileProvider.get().classpath.files)
|
||||||
|
def filteredJarFiles = allJarFiles.findAll { file ->
|
||||||
|
if (file ==~ /.*media-.\..\..-api.jar$/
|
||||||
|
&& !file.path.endsWith(
|
||||||
|
"media-" + project.ext.androidxMediaVersion + "-api.jar")) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
classpath +=
|
classpath +=
|
||||||
libraryModule.project.files(
|
libraryModule.project.files(
|
||||||
variant.javaCompileProvider.get().classpath.files,
|
filteredJarFiles,
|
||||||
libraryModule.project.android.getBootClasspath())
|
libraryModule.project.android.getBootClasspath())
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -34,19 +34,15 @@ import androidx.media3.common.DeviceInfo;
|
|||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
import androidx.media3.common.MediaLibraryInfo;
|
import androidx.media3.common.MediaLibraryInfo;
|
||||||
import androidx.media3.common.MediaMetadata;
|
import androidx.media3.common.MediaMetadata;
|
||||||
import androidx.media3.common.MimeTypes;
|
|
||||||
import androidx.media3.common.PlaybackException;
|
import androidx.media3.common.PlaybackException;
|
||||||
import androidx.media3.common.PlaybackParameters;
|
import androidx.media3.common.PlaybackParameters;
|
||||||
import androidx.media3.common.Player;
|
import androidx.media3.common.Player;
|
||||||
import androidx.media3.common.Timeline;
|
import androidx.media3.common.Timeline;
|
||||||
import androidx.media3.common.TrackGroup;
|
import androidx.media3.common.TrackGroup;
|
||||||
import androidx.media3.common.TrackGroupArray;
|
|
||||||
import androidx.media3.common.TrackSelection;
|
|
||||||
import androidx.media3.common.TrackSelectionArray;
|
|
||||||
import androidx.media3.common.TrackSelectionParameters;
|
import androidx.media3.common.TrackSelectionParameters;
|
||||||
import androidx.media3.common.TracksInfo;
|
import androidx.media3.common.Tracks;
|
||||||
import androidx.media3.common.VideoSize;
|
import androidx.media3.common.VideoSize;
|
||||||
import androidx.media3.common.text.Cue;
|
import androidx.media3.common.text.CueGroup;
|
||||||
import androidx.media3.common.util.Assertions;
|
import androidx.media3.common.util.Assertions;
|
||||||
import androidx.media3.common.util.Clock;
|
import androidx.media3.common.util.Clock;
|
||||||
import androidx.media3.common.util.ListenerSet;
|
import androidx.media3.common.util.ListenerSet;
|
||||||
@ -68,7 +64,6 @@ import com.google.android.gms.common.api.PendingResult;
|
|||||||
import com.google.android.gms.common.api.ResultCallback;
|
import com.google.android.gms.common.api.ResultCallback;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|
||||||
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -107,7 +102,8 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
COMMAND_GET_MEDIA_ITEMS_METADATA,
|
COMMAND_GET_MEDIA_ITEMS_METADATA,
|
||||||
COMMAND_SET_MEDIA_ITEMS_METADATA,
|
COMMAND_SET_MEDIA_ITEMS_METADATA,
|
||||||
COMMAND_CHANGE_MEDIA_ITEMS,
|
COMMAND_CHANGE_MEDIA_ITEMS,
|
||||||
COMMAND_GET_TRACK_INFOS)
|
COMMAND_GET_TRACKS,
|
||||||
|
COMMAND_SET_MEDIA_ITEM)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static final float MIN_SPEED_SUPPORTED = 0.5f;
|
public static final float MIN_SPEED_SUPPORTED = 0.5f;
|
||||||
@ -115,13 +111,7 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
|
|
||||||
private static final String TAG = "CastPlayer";
|
private static final String TAG = "CastPlayer";
|
||||||
|
|
||||||
private static final int RENDERER_COUNT = 3;
|
|
||||||
private static final int RENDERER_INDEX_VIDEO = 0;
|
|
||||||
private static final int RENDERER_INDEX_AUDIO = 1;
|
|
||||||
private static final int RENDERER_INDEX_TEXT = 2;
|
|
||||||
private static final long PROGRESS_REPORT_PERIOD_MS = 1000;
|
private static final long PROGRESS_REPORT_PERIOD_MS = 1000;
|
||||||
private static final TrackSelectionArray EMPTY_TRACK_SELECTION_ARRAY =
|
|
||||||
new TrackSelectionArray(null, null, null);
|
|
||||||
private static final long[] EMPTY_TRACK_ID_ARRAY = new long[0];
|
private static final long[] EMPTY_TRACK_ID_ARRAY = new long[0];
|
||||||
|
|
||||||
private final CastContext castContext;
|
private final CastContext castContext;
|
||||||
@ -146,9 +136,7 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
private final StateHolder<PlaybackParameters> playbackParameters;
|
private final StateHolder<PlaybackParameters> playbackParameters;
|
||||||
@Nullable private RemoteMediaClient remoteMediaClient;
|
@Nullable private RemoteMediaClient remoteMediaClient;
|
||||||
private CastTimeline currentTimeline;
|
private CastTimeline currentTimeline;
|
||||||
private TrackGroupArray currentTrackGroups;
|
private Tracks currentTracks;
|
||||||
private TrackSelectionArray currentTrackSelection;
|
|
||||||
private TracksInfo currentTracksInfo;
|
|
||||||
private Commands availableCommands;
|
private Commands availableCommands;
|
||||||
private @Player.State int playbackState;
|
private @Player.State int playbackState;
|
||||||
private int currentWindowIndex;
|
private int currentWindowIndex;
|
||||||
@ -224,9 +212,7 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
playbackParameters = new StateHolder<>(PlaybackParameters.DEFAULT);
|
playbackParameters = new StateHolder<>(PlaybackParameters.DEFAULT);
|
||||||
playbackState = STATE_IDLE;
|
playbackState = STATE_IDLE;
|
||||||
currentTimeline = CastTimeline.EMPTY_CAST_TIMELINE;
|
currentTimeline = CastTimeline.EMPTY_CAST_TIMELINE;
|
||||||
currentTrackGroups = TrackGroupArray.EMPTY;
|
currentTracks = Tracks.EMPTY;
|
||||||
currentTrackSelection = EMPTY_TRACK_SELECTION_ARRAY;
|
|
||||||
currentTracksInfo = TracksInfo.EMPTY;
|
|
||||||
availableCommands = new Commands.Builder().addAll(PERMANENT_AVAILABLE_COMMANDS).build();
|
availableCommands = new Commands.Builder().addAll(PERMANENT_AVAILABLE_COMMANDS).build();
|
||||||
pendingSeekWindowIndex = C.INDEX_UNSET;
|
pendingSeekWindowIndex = C.INDEX_UNSET;
|
||||||
pendingSeekPositionMs = C.TIME_UNSET;
|
pendingSeekPositionMs = C.TIME_UNSET;
|
||||||
@ -473,6 +459,11 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
stop(/* reset= */ false);
|
stop(/* reset= */ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #stop()} and {@link #clearMediaItems()} (if {@code reset} is true) or
|
||||||
|
* just {@link #stop()} (if {@code reset} is false). Any player error will be cleared when
|
||||||
|
* {@link #prepare() re-preparing} the player.
|
||||||
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
public void stop(boolean reset) {
|
public void stop(boolean reset) {
|
||||||
@ -558,18 +549,8 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public TrackGroupArray getCurrentTrackGroups() {
|
public Tracks getCurrentTracks() {
|
||||||
return currentTrackGroups;
|
return currentTracks;
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TrackSelectionArray getCurrentTrackSelections() {
|
|
||||||
return currentTrackSelection;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TracksInfo getCurrentTracksInfo() {
|
|
||||||
return currentTracksInfo;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -730,10 +711,10 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
return VideoSize.UNKNOWN;
|
return VideoSize.UNKNOWN;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** This method is not supported and returns an empty list. */
|
/** This method is not supported and returns an empty {@link CueGroup}. */
|
||||||
@Override
|
@Override
|
||||||
public ImmutableList<Cue> getCurrentCues() {
|
public CueGroup getCurrentCues() {
|
||||||
return ImmutableList.of();
|
return CueGroup.EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** This method is not supported and always returns {@link DeviceInfo#UNKNOWN}. */
|
/** This method is not supported and always returns {@link DeviceInfo#UNKNOWN}. */
|
||||||
@ -842,10 +823,7 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
}
|
}
|
||||||
if (updateTracksAndSelectionsAndNotifyIfChanged()) {
|
if (updateTracksAndSelectionsAndNotifyIfChanged()) {
|
||||||
listeners.queueEvent(
|
listeners.queueEvent(
|
||||||
Player.EVENT_TRACKS_CHANGED,
|
Player.EVENT_TRACKS_CHANGED, listener -> listener.onTracksChanged(currentTracks));
|
||||||
listener -> listener.onTracksChanged(currentTrackGroups, currentTrackSelection));
|
|
||||||
listeners.queueEvent(
|
|
||||||
Player.EVENT_TRACKS_CHANGED, listener -> listener.onTracksInfoChanged(currentTracksInfo));
|
|
||||||
}
|
}
|
||||||
updateAvailableCommandsAndNotifyIfChanged();
|
updateAvailableCommandsAndNotifyIfChanged();
|
||||||
listeners.flushEvents();
|
listeners.flushEvents();
|
||||||
@ -1000,55 +978,33 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaStatus mediaStatus = getMediaStatus();
|
@Nullable MediaStatus mediaStatus = getMediaStatus();
|
||||||
MediaInfo mediaInfo = mediaStatus != null ? mediaStatus.getMediaInfo() : null;
|
@Nullable MediaInfo mediaInfo = mediaStatus != null ? mediaStatus.getMediaInfo() : null;
|
||||||
|
@Nullable
|
||||||
List<MediaTrack> castMediaTracks = mediaInfo != null ? mediaInfo.getMediaTracks() : null;
|
List<MediaTrack> castMediaTracks = mediaInfo != null ? mediaInfo.getMediaTracks() : null;
|
||||||
if (castMediaTracks == null || castMediaTracks.isEmpty()) {
|
if (castMediaTracks == null || castMediaTracks.isEmpty()) {
|
||||||
boolean hasChanged = !currentTrackGroups.isEmpty();
|
boolean hasChanged = !Tracks.EMPTY.equals(currentTracks);
|
||||||
currentTrackGroups = TrackGroupArray.EMPTY;
|
currentTracks = Tracks.EMPTY;
|
||||||
currentTrackSelection = EMPTY_TRACK_SELECTION_ARRAY;
|
|
||||||
currentTracksInfo = TracksInfo.EMPTY;
|
|
||||||
return hasChanged;
|
return hasChanged;
|
||||||
}
|
}
|
||||||
long[] activeTrackIds = mediaStatus.getActiveTrackIds();
|
@Nullable long[] activeTrackIds = mediaStatus.getActiveTrackIds();
|
||||||
if (activeTrackIds == null) {
|
if (activeTrackIds == null) {
|
||||||
activeTrackIds = EMPTY_TRACK_ID_ARRAY;
|
activeTrackIds = EMPTY_TRACK_ID_ARRAY;
|
||||||
}
|
}
|
||||||
|
|
||||||
TrackGroup[] trackGroups = new TrackGroup[castMediaTracks.size()];
|
Tracks.Group[] trackGroups = new Tracks.Group[castMediaTracks.size()];
|
||||||
@NullableType TrackSelection[] trackSelections = new TrackSelection[RENDERER_COUNT];
|
|
||||||
TracksInfo.TrackGroupInfo[] trackGroupInfos =
|
|
||||||
new TracksInfo.TrackGroupInfo[castMediaTracks.size()];
|
|
||||||
for (int i = 0; i < castMediaTracks.size(); i++) {
|
for (int i = 0; i < castMediaTracks.size(); i++) {
|
||||||
MediaTrack mediaTrack = castMediaTracks.get(i);
|
MediaTrack mediaTrack = castMediaTracks.get(i);
|
||||||
trackGroups[i] =
|
TrackGroup trackGroup =
|
||||||
new TrackGroup(/* id= */ Integer.toString(i), CastUtils.mediaTrackToFormat(mediaTrack));
|
new TrackGroup(/* id= */ Integer.toString(i), CastUtils.mediaTrackToFormat(mediaTrack));
|
||||||
|
@C.FormatSupport int[] trackSupport = new int[] {C.FORMAT_HANDLED};
|
||||||
long id = mediaTrack.getId();
|
boolean[] trackSelected = new boolean[] {isTrackActive(mediaTrack.getId(), activeTrackIds)};
|
||||||
@C.TrackType int trackType = MimeTypes.getTrackType(mediaTrack.getContentType());
|
trackGroups[i] =
|
||||||
int rendererIndex = getRendererIndexForTrackType(trackType);
|
new Tracks.Group(trackGroup, /* adaptiveSupported= */ false, trackSupport, trackSelected);
|
||||||
boolean supported = rendererIndex != C.INDEX_UNSET;
|
|
||||||
boolean selected =
|
|
||||||
isTrackActive(id, activeTrackIds) && supported && trackSelections[rendererIndex] == null;
|
|
||||||
if (selected) {
|
|
||||||
trackSelections[rendererIndex] = new CastTrackSelection(trackGroups[i]);
|
|
||||||
}
|
}
|
||||||
@C.FormatSupport
|
Tracks newTracks = new Tracks(ImmutableList.copyOf(trackGroups));
|
||||||
int[] trackSupport = new int[] {supported ? C.FORMAT_HANDLED : C.FORMAT_UNSUPPORTED_TYPE};
|
if (!newTracks.equals(currentTracks)) {
|
||||||
final boolean[] trackSelected = new boolean[] {selected};
|
currentTracks = newTracks;
|
||||||
trackGroupInfos[i] =
|
|
||||||
new TracksInfo.TrackGroupInfo(trackGroups[i], trackSupport, trackType, trackSelected);
|
|
||||||
}
|
|
||||||
TrackGroupArray newTrackGroups = new TrackGroupArray(trackGroups);
|
|
||||||
TrackSelectionArray newTrackSelections = new TrackSelectionArray(trackSelections);
|
|
||||||
TracksInfo newTracksInfo = new TracksInfo(ImmutableList.copyOf(trackGroupInfos));
|
|
||||||
|
|
||||||
if (!newTrackGroups.equals(currentTrackGroups)
|
|
||||||
|| !newTrackSelections.equals(currentTrackSelection)
|
|
||||||
|| !newTracksInfo.equals(currentTracksInfo)) {
|
|
||||||
currentTrackSelection = newTrackSelections;
|
|
||||||
currentTrackGroups = newTrackGroups;
|
|
||||||
currentTracksInfo = newTracksInfo;
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
@ -1306,14 +1262,6 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int getRendererIndexForTrackType(@C.TrackType int trackType) {
|
|
||||||
return trackType == C.TRACK_TYPE_VIDEO
|
|
||||||
? RENDERER_INDEX_VIDEO
|
|
||||||
: trackType == C.TRACK_TYPE_AUDIO
|
|
||||||
? RENDERER_INDEX_AUDIO
|
|
||||||
: trackType == C.TRACK_TYPE_TEXT ? RENDERER_INDEX_TEXT : C.INDEX_UNSET;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static int getCastRepeatMode(@RepeatMode int repeatMode) {
|
private static int getCastRepeatMode(@RepeatMode int repeatMode) {
|
||||||
switch (repeatMode) {
|
switch (repeatMode) {
|
||||||
case REPEAT_MODE_ONE:
|
case REPEAT_MODE_ONE:
|
||||||
|
@ -1,96 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2021 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package androidx.media3.cast;
|
|
||||||
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.media3.common.C;
|
|
||||||
import androidx.media3.common.Format;
|
|
||||||
import androidx.media3.common.TrackGroup;
|
|
||||||
import androidx.media3.common.TrackSelection;
|
|
||||||
import androidx.media3.common.util.Assertions;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* {@link TrackSelection} that only selects the first track of the provided {@link TrackGroup}.
|
|
||||||
*
|
|
||||||
* <p>This relies on {@link CastPlayer} track groups only having one track.
|
|
||||||
*/
|
|
||||||
/* package */ class CastTrackSelection implements TrackSelection {
|
|
||||||
|
|
||||||
private final TrackGroup trackGroup;
|
|
||||||
|
|
||||||
/** @param trackGroup The {@link TrackGroup} from which the first track will only be selected. */
|
|
||||||
public CastTrackSelection(TrackGroup trackGroup) {
|
|
||||||
this.trackGroup = trackGroup;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getType() {
|
|
||||||
return TYPE_UNSET;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public TrackGroup getTrackGroup() {
|
|
||||||
return trackGroup;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int length() {
|
|
||||||
return 1;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Format getFormat(int index) {
|
|
||||||
Assertions.checkArgument(index == 0);
|
|
||||||
return trackGroup.getFormat(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int getIndexInTrackGroup(int index) {
|
|
||||||
return index == 0 ? 0 : C.INDEX_UNSET;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("ReferenceEquality")
|
|
||||||
public int indexOf(Format format) {
|
|
||||||
return format == trackGroup.getFormat(0) ? 0 : C.INDEX_UNSET;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int indexOf(int indexInTrackGroup) {
|
|
||||||
return indexInTrackGroup == 0 ? 0 : C.INDEX_UNSET;
|
|
||||||
}
|
|
||||||
|
|
||||||
// Object overrides.
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return System.identityHashCode(trackGroup);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Track groups are compared by identity not value, as distinct groups may have the same value.
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings({"ReferenceEquality", "EqualsGetClass"})
|
|
||||||
public boolean equals(@Nullable Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null || getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
CastTrackSelection other = (CastTrackSelection) obj;
|
|
||||||
return trackGroup == other.trackGroup;
|
|
||||||
}
|
|
||||||
}
|
|
@ -36,6 +36,7 @@ import static androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM;
|
|||||||
import static androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS;
|
import static androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS;
|
||||||
import static androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM;
|
import static androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM;
|
||||||
import static androidx.media3.common.Player.COMMAND_SET_DEVICE_VOLUME;
|
import static androidx.media3.common.Player.COMMAND_SET_DEVICE_VOLUME;
|
||||||
|
import static androidx.media3.common.Player.COMMAND_SET_MEDIA_ITEM;
|
||||||
import static androidx.media3.common.Player.COMMAND_SET_MEDIA_ITEMS_METADATA;
|
import static androidx.media3.common.Player.COMMAND_SET_MEDIA_ITEMS_METADATA;
|
||||||
import static androidx.media3.common.Player.COMMAND_SET_REPEAT_MODE;
|
import static androidx.media3.common.Player.COMMAND_SET_REPEAT_MODE;
|
||||||
import static androidx.media3.common.Player.COMMAND_SET_SHUFFLE_MODE;
|
import static androidx.media3.common.Player.COMMAND_SET_SHUFFLE_MODE;
|
||||||
@ -1359,6 +1360,7 @@ public class CastPlayerTest {
|
|||||||
assertThat(castPlayer.isCommandAvailable(COMMAND_GET_MEDIA_ITEMS_METADATA)).isTrue();
|
assertThat(castPlayer.isCommandAvailable(COMMAND_GET_MEDIA_ITEMS_METADATA)).isTrue();
|
||||||
assertThat(castPlayer.isCommandAvailable(COMMAND_SET_MEDIA_ITEMS_METADATA)).isTrue();
|
assertThat(castPlayer.isCommandAvailable(COMMAND_SET_MEDIA_ITEMS_METADATA)).isTrue();
|
||||||
assertThat(castPlayer.isCommandAvailable(COMMAND_CHANGE_MEDIA_ITEMS)).isTrue();
|
assertThat(castPlayer.isCommandAvailable(COMMAND_CHANGE_MEDIA_ITEMS)).isTrue();
|
||||||
|
assertThat(castPlayer.isCommandAvailable(COMMAND_SET_MEDIA_ITEM)).isTrue();
|
||||||
assertThat(castPlayer.isCommandAvailable(COMMAND_GET_AUDIO_ATTRIBUTES)).isFalse();
|
assertThat(castPlayer.isCommandAvailable(COMMAND_GET_AUDIO_ATTRIBUTES)).isFalse();
|
||||||
assertThat(castPlayer.isCommandAvailable(COMMAND_GET_VOLUME)).isFalse();
|
assertThat(castPlayer.isCommandAvailable(COMMAND_GET_VOLUME)).isFalse();
|
||||||
assertThat(castPlayer.isCommandAvailable(COMMAND_GET_DEVICE_VOLUME)).isFalse();
|
assertThat(castPlayer.isCommandAvailable(COMMAND_GET_DEVICE_VOLUME)).isFalse();
|
||||||
|
@ -1,77 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2018 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.cast;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
|
|
||||||
import androidx.media3.common.C;
|
|
||||||
import androidx.media3.common.Format;
|
|
||||||
import androidx.media3.common.TrackGroup;
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
/** Test for {@link CastTrackSelection}. */
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
|
||||||
public class CastTrackSelectionTest {
|
|
||||||
|
|
||||||
private static final TrackGroup TRACK_GROUP =
|
|
||||||
new TrackGroup(new Format.Builder().build(), new Format.Builder().build());
|
|
||||||
|
|
||||||
private static final CastTrackSelection SELECTION = new CastTrackSelection(TRACK_GROUP);
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void length_isOne() {
|
|
||||||
assertThat(SELECTION.length()).isEqualTo(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getTrackGroup_returnsSameGroup() {
|
|
||||||
assertThat(SELECTION.getTrackGroup()).isSameInstanceAs(TRACK_GROUP);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getFormatSelectedTrack_isFirstTrack() {
|
|
||||||
assertThat(SELECTION.getFormat(0)).isSameInstanceAs(TRACK_GROUP.getFormat(0));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getIndexInTrackGroup_ofSelectedTrack_returnsFirstTrack() {
|
|
||||||
assertThat(SELECTION.getIndexInTrackGroup(0)).isEqualTo(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void getIndexInTrackGroup_onePastTheEnd_returnsIndexUnset() {
|
|
||||||
assertThat(SELECTION.getIndexInTrackGroup(1)).isEqualTo(C.INDEX_UNSET);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void indexOf_selectedTrack_returnsFirstTrack() {
|
|
||||||
assertThat(SELECTION.indexOf(0)).isEqualTo(0);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void indexOf_onePastTheEnd_returnsIndexUnset() {
|
|
||||||
assertThat(SELECTION.indexOf(1)).isEqualTo(C.INDEX_UNSET);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test(expected = Exception.class)
|
|
||||||
public void getFormat_outOfBound_throws() {
|
|
||||||
CastTrackSelection selection = new CastTrackSelection(TRACK_GROUP);
|
|
||||||
|
|
||||||
selection.getFormat(1);
|
|
||||||
}
|
|
||||||
}
|
|
@ -30,6 +30,9 @@ android {
|
|||||||
testCoverageEnabled = true
|
testCoverageEnabled = true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
lint {
|
||||||
|
baseline = file("lint-baseline.xml")
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
29
libraries/common/lint-baseline.xml
Normal file
29
libraries/common/lint-baseline.xml
Normal file
@ -0,0 +1,29 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!-- Copyright (C) 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.
|
||||||
|
-->
|
||||||
|
<issues format="6" by="lint 7.2.1" type="baseline" client="gradle" dependencies="false" name="AGP (7.2.1)" variant="all" version="7.2.1">
|
||||||
|
|
||||||
|
<issue
|
||||||
|
id="NewApi"
|
||||||
|
message="Call requires API level 33 (current min is 32): `android.media.AudioAttributes.Builder#setSpatializationBehavior`"
|
||||||
|
errorLine1=" builder.setSpatializationBehavior(spatializationBehavior);"
|
||||||
|
errorLine2=" ~~~~~~~~~~~~~~~~~~~~~~~~~">
|
||||||
|
<location
|
||||||
|
file="src/main/java/androidx/media3/common/AudioAttributes.java"
|
||||||
|
line="278"
|
||||||
|
column="15"/>
|
||||||
|
</issue>
|
||||||
|
|
||||||
|
</issues>
|
@ -97,14 +97,18 @@ public final class AdOverlayInfo {
|
|||||||
/** An optional, detailed reason that the overlay view is needed. */
|
/** An optional, detailed reason that the overlay view is needed. */
|
||||||
@Nullable public final String reasonDetail;
|
@Nullable public final String reasonDetail;
|
||||||
|
|
||||||
/** @deprecated Use {@link Builder} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link Builder} instead.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public AdOverlayInfo(View view, @Purpose int purpose) {
|
public AdOverlayInfo(View view, @Purpose int purpose) {
|
||||||
this(view, purpose, /* detailedReason= */ null);
|
this(view, purpose, /* detailedReason= */ null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Use {@link Builder} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link Builder} instead.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public AdOverlayInfo(View view, @Purpose int purpose, @Nullable String detailedReason) {
|
public AdOverlayInfo(View view, @Purpose int purpose, @Nullable String detailedReason) {
|
||||||
|
@ -827,6 +827,36 @@ public final class AdPlaybackState implements Bundleable {
|
|||||||
adsId, adGroups, adResumePositionUs, contentDurationUs, removedAdGroupCount);
|
adsId, adGroups, adResumePositionUs, contentDurationUs, removedAdGroupCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a copy of the ad playback state with the given ads ID.
|
||||||
|
*
|
||||||
|
* @param adsId The new ads ID.
|
||||||
|
* @param adPlaybackState The ad playback state to copy.
|
||||||
|
* @return The new ad playback state.
|
||||||
|
*/
|
||||||
|
public static AdPlaybackState fromAdPlaybackState(Object adsId, AdPlaybackState adPlaybackState) {
|
||||||
|
AdGroup[] adGroups =
|
||||||
|
new AdGroup[adPlaybackState.adGroupCount - adPlaybackState.removedAdGroupCount];
|
||||||
|
for (int i = 0; i < adGroups.length; i++) {
|
||||||
|
AdGroup adGroup = adPlaybackState.adGroups[i];
|
||||||
|
adGroups[i] =
|
||||||
|
new AdGroup(
|
||||||
|
adGroup.timeUs,
|
||||||
|
adGroup.count,
|
||||||
|
Arrays.copyOf(adGroup.states, adGroup.states.length),
|
||||||
|
Arrays.copyOf(adGroup.uris, adGroup.uris.length),
|
||||||
|
Arrays.copyOf(adGroup.durationsUs, adGroup.durationsUs.length),
|
||||||
|
adGroup.contentResumeOffsetUs,
|
||||||
|
adGroup.isServerSideInserted);
|
||||||
|
}
|
||||||
|
return new AdPlaybackState(
|
||||||
|
adsId,
|
||||||
|
adGroups,
|
||||||
|
adPlaybackState.adResumePositionUs,
|
||||||
|
adPlaybackState.contentDurationUs,
|
||||||
|
adPlaybackState.removedAdGroupCount);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(@Nullable Object o) {
|
public boolean equals(@Nullable Object o) {
|
||||||
if (this == o) {
|
if (this == o) {
|
||||||
|
@ -28,7 +28,6 @@ import java.lang.annotation.Documented;
|
|||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
import java.lang.annotation.Target;
|
import java.lang.annotation.Target;
|
||||||
import java.lang.reflect.Method;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attributes for audio playback, which configure the underlying platform {@link
|
* Attributes for audio playback, which configure the underlying platform {@link
|
||||||
@ -43,10 +42,31 @@ import java.lang.reflect.Method;
|
|||||||
*/
|
*/
|
||||||
public final class AudioAttributes implements Bundleable {
|
public final class AudioAttributes implements Bundleable {
|
||||||
|
|
||||||
|
/** A direct wrapper around {@link android.media.AudioAttributes}. */
|
||||||
|
@RequiresApi(21)
|
||||||
|
public static final class AudioAttributesV21 {
|
||||||
|
public final android.media.AudioAttributes audioAttributes;
|
||||||
|
|
||||||
|
private AudioAttributesV21(AudioAttributes audioAttributes) {
|
||||||
|
android.media.AudioAttributes.Builder builder =
|
||||||
|
new android.media.AudioAttributes.Builder()
|
||||||
|
.setContentType(audioAttributes.contentType)
|
||||||
|
.setFlags(audioAttributes.flags)
|
||||||
|
.setUsage(audioAttributes.usage);
|
||||||
|
if (Util.SDK_INT >= 29) {
|
||||||
|
Api29.setAllowedCapturePolicy(builder, audioAttributes.allowedCapturePolicy);
|
||||||
|
}
|
||||||
|
if (Util.SDK_INT >= 32) {
|
||||||
|
Api32.setSpatializationBehavior(builder, audioAttributes.spatializationBehavior);
|
||||||
|
}
|
||||||
|
this.audioAttributes = builder.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default audio attributes, where the content type is {@link C#CONTENT_TYPE_UNKNOWN}, usage
|
* The default audio attributes, where the content type is {@link C#AUDIO_CONTENT_TYPE_UNKNOWN},
|
||||||
* is {@link C#USAGE_MEDIA}, capture policy is {@link C#ALLOW_CAPTURE_BY_ALL} and no flags are
|
* usage is {@link C#USAGE_MEDIA}, capture policy is {@link C#ALLOW_CAPTURE_BY_ALL} and no flags
|
||||||
* set.
|
* are set.
|
||||||
*/
|
*/
|
||||||
public static final AudioAttributes DEFAULT = new Builder().build();
|
public static final AudioAttributes DEFAULT = new Builder().build();
|
||||||
|
|
||||||
@ -62,11 +82,11 @@ public final class AudioAttributes implements Bundleable {
|
|||||||
/**
|
/**
|
||||||
* Creates a new builder for {@link AudioAttributes}.
|
* Creates a new builder for {@link AudioAttributes}.
|
||||||
*
|
*
|
||||||
* <p>By default the content type is {@link C#CONTENT_TYPE_UNKNOWN}, usage is {@link
|
* <p>By default the content type is {@link C#AUDIO_CONTENT_TYPE_UNKNOWN}, usage is {@link
|
||||||
* C#USAGE_MEDIA}, capture policy is {@link C#ALLOW_CAPTURE_BY_ALL} and no flags are set.
|
* C#USAGE_MEDIA}, capture policy is {@link C#ALLOW_CAPTURE_BY_ALL} and no flags are set.
|
||||||
*/
|
*/
|
||||||
public Builder() {
|
public Builder() {
|
||||||
contentType = C.CONTENT_TYPE_UNKNOWN;
|
contentType = C.AUDIO_CONTENT_TYPE_UNKNOWN;
|
||||||
flags = 0;
|
flags = 0;
|
||||||
usage = C.USAGE_MEDIA;
|
usage = C.USAGE_MEDIA;
|
||||||
allowedCapturePolicy = C.ALLOW_CAPTURE_BY_ALL;
|
allowedCapturePolicy = C.ALLOW_CAPTURE_BY_ALL;
|
||||||
@ -97,9 +117,7 @@ public final class AudioAttributes implements Bundleable {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO[b/190759307] Update javadoc to link to AudioAttributes.Builder#setSpatializationBehavior
|
/** See {@link android.media.AudioAttributes.Builder#setSpatializationBehavior(int)}. */
|
||||||
// once compile SDK target is set to 32.
|
|
||||||
/** See {@code android.media.AudioAttributes.Builder.setSpatializationBehavior(int)}. */
|
|
||||||
public Builder setSpatializationBehavior(@C.SpatializationBehavior int spatializationBehavior) {
|
public Builder setSpatializationBehavior(@C.SpatializationBehavior int spatializationBehavior) {
|
||||||
this.spatializationBehavior = spatializationBehavior;
|
this.spatializationBehavior = spatializationBehavior;
|
||||||
return this;
|
return this;
|
||||||
@ -123,7 +141,7 @@ public final class AudioAttributes implements Bundleable {
|
|||||||
/** The {@link C.SpatializationBehavior}. */
|
/** The {@link C.SpatializationBehavior}. */
|
||||||
public final @C.SpatializationBehavior int spatializationBehavior;
|
public final @C.SpatializationBehavior int spatializationBehavior;
|
||||||
|
|
||||||
@Nullable private android.media.AudioAttributes audioAttributesV21;
|
@Nullable private AudioAttributesV21 audioAttributesV21;
|
||||||
|
|
||||||
private AudioAttributes(
|
private AudioAttributes(
|
||||||
@C.AudioContentType int contentType,
|
@C.AudioContentType int contentType,
|
||||||
@ -139,25 +157,15 @@ public final class AudioAttributes implements Bundleable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a {@link android.media.AudioAttributes} from this instance.
|
* Returns a {@link AudioAttributesV21} from this instance.
|
||||||
*
|
*
|
||||||
* <p>Field {@link AudioAttributes#allowedCapturePolicy} is ignored for API levels prior to 29.
|
* <p>Some fields are ignored if the corresponding {@link android.media.AudioAttributes.Builder}
|
||||||
|
* setter is not available on the current API level.
|
||||||
*/
|
*/
|
||||||
@RequiresApi(21)
|
@RequiresApi(21)
|
||||||
public android.media.AudioAttributes getAudioAttributesV21() {
|
public AudioAttributesV21 getAudioAttributesV21() {
|
||||||
if (audioAttributesV21 == null) {
|
if (audioAttributesV21 == null) {
|
||||||
android.media.AudioAttributes.Builder builder =
|
audioAttributesV21 = new AudioAttributesV21(this);
|
||||||
new android.media.AudioAttributes.Builder()
|
|
||||||
.setContentType(contentType)
|
|
||||||
.setFlags(flags)
|
|
||||||
.setUsage(usage);
|
|
||||||
if (Util.SDK_INT >= 29) {
|
|
||||||
Api29.setAllowedCapturePolicy(builder, allowedCapturePolicy);
|
|
||||||
}
|
|
||||||
if (Util.SDK_INT >= 32) {
|
|
||||||
Api32.setSpatializationBehavior(builder, spatializationBehavior);
|
|
||||||
}
|
|
||||||
audioAttributesV21 = builder.build();
|
|
||||||
}
|
}
|
||||||
return audioAttributesV21;
|
return audioAttributesV21;
|
||||||
}
|
}
|
||||||
@ -251,8 +259,6 @@ public final class AudioAttributes implements Bundleable {
|
|||||||
|
|
||||||
@RequiresApi(29)
|
@RequiresApi(29)
|
||||||
private static final class Api29 {
|
private static final class Api29 {
|
||||||
private Api29() {}
|
|
||||||
|
|
||||||
@DoNotInline
|
@DoNotInline
|
||||||
public static void setAllowedCapturePolicy(
|
public static void setAllowedCapturePolicy(
|
||||||
android.media.AudioAttributes.Builder builder,
|
android.media.AudioAttributes.Builder builder,
|
||||||
@ -263,20 +269,11 @@ public final class AudioAttributes implements Bundleable {
|
|||||||
|
|
||||||
@RequiresApi(32)
|
@RequiresApi(32)
|
||||||
private static final class Api32 {
|
private static final class Api32 {
|
||||||
private Api32() {}
|
|
||||||
|
|
||||||
@DoNotInline
|
@DoNotInline
|
||||||
public static void setSpatializationBehavior(
|
public static void setSpatializationBehavior(
|
||||||
android.media.AudioAttributes.Builder builder,
|
android.media.AudioAttributes.Builder builder,
|
||||||
@C.SpatializationBehavior int spatializationBehavior) {
|
@C.SpatializationBehavior int spatializationBehavior) {
|
||||||
try {
|
builder.setSpatializationBehavior(spatializationBehavior);
|
||||||
// TODO[b/190759307]: Remove reflection once compile SDK target is set to 32.
|
|
||||||
Method setSpatializationBehavior =
|
|
||||||
builder.getClass().getMethod("setSpatializationBehavior", Integer.TYPE);
|
|
||||||
setSpatializationBehavior.invoke(builder, spatializationBehavior);
|
|
||||||
} catch (Exception e) {
|
|
||||||
// Do nothing if reflection fails.
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -94,7 +94,7 @@ public abstract class BasePlayer implements Player {
|
|||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
*
|
*
|
||||||
* <p>BasePlayer and its descendents will return {@code true}.
|
* <p>BasePlayer and its descendants will return {@code true}.
|
||||||
*/
|
*/
|
||||||
@Override
|
@Override
|
||||||
public final boolean canAdvertiseSession() {
|
public final boolean canAdvertiseSession() {
|
||||||
@ -143,12 +143,18 @@ public abstract class BasePlayer implements Player {
|
|||||||
seekToOffset(getSeekForwardIncrement());
|
seekToOffset(getSeekForwardIncrement());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #hasPreviousMediaItem()} instead.
|
||||||
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
public final boolean hasPrevious() {
|
public final boolean hasPrevious() {
|
||||||
return hasPreviousMediaItem();
|
return hasPreviousMediaItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #hasPreviousMediaItem()} instead.
|
||||||
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
public final boolean hasPreviousWindow() {
|
public final boolean hasPreviousWindow() {
|
||||||
@ -160,12 +166,18 @@ public abstract class BasePlayer implements Player {
|
|||||||
return getPreviousMediaItemIndex() != C.INDEX_UNSET;
|
return getPreviousMediaItemIndex() != C.INDEX_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #seekToPreviousMediaItem()} instead.
|
||||||
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
public final void previous() {
|
public final void previous() {
|
||||||
seekToPreviousMediaItem();
|
seekToPreviousMediaItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #seekToPreviousMediaItem()} instead.
|
||||||
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
public final void seekToPreviousWindow() {
|
public final void seekToPreviousWindow() {
|
||||||
@ -198,12 +210,18 @@ public abstract class BasePlayer implements Player {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #hasNextMediaItem()} instead.
|
||||||
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
public final boolean hasNext() {
|
public final boolean hasNext() {
|
||||||
return hasNextMediaItem();
|
return hasNextMediaItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #hasNextMediaItem()} instead.
|
||||||
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
public final boolean hasNextWindow() {
|
public final boolean hasNextWindow() {
|
||||||
@ -215,12 +233,18 @@ public abstract class BasePlayer implements Player {
|
|||||||
return getNextMediaItemIndex() != C.INDEX_UNSET;
|
return getNextMediaItemIndex() != C.INDEX_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #seekToNextMediaItem()} instead.
|
||||||
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
public final void next() {
|
public final void next() {
|
||||||
seekToNextMediaItem();
|
seekToNextMediaItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #seekToNextMediaItem()} instead.
|
||||||
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
public final void seekToNextWindow() {
|
public final void seekToNextWindow() {
|
||||||
@ -253,12 +277,18 @@ public abstract class BasePlayer implements Player {
|
|||||||
setPlaybackParameters(getPlaybackParameters().withSpeed(speed));
|
setPlaybackParameters(getPlaybackParameters().withSpeed(speed));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #getCurrentMediaItemIndex()} instead.
|
||||||
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
public final int getCurrentWindowIndex() {
|
public final int getCurrentWindowIndex() {
|
||||||
return getCurrentMediaItemIndex();
|
return getCurrentMediaItemIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #getNextMediaItemIndex()} instead.
|
||||||
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
public final int getNextWindowIndex() {
|
public final int getNextWindowIndex() {
|
||||||
@ -274,6 +304,9 @@ public abstract class BasePlayer implements Player {
|
|||||||
getCurrentMediaItemIndex(), getRepeatModeForNavigation(), getShuffleModeEnabled());
|
getCurrentMediaItemIndex(), getRepeatModeForNavigation(), getShuffleModeEnabled());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #getPreviousMediaItemIndex()} instead.
|
||||||
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
public final int getPreviousWindowIndex() {
|
public final int getPreviousWindowIndex() {
|
||||||
@ -326,6 +359,9 @@ public abstract class BasePlayer implements Player {
|
|||||||
: duration == 0 ? 100 : Util.constrainValue((int) ((position * 100) / duration), 0, 100);
|
: duration == 0 ? 100 : Util.constrainValue((int) ((position * 100) / duration), 0, 100);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #isCurrentMediaItemDynamic()} instead.
|
||||||
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
public final boolean isCurrentWindowDynamic() {
|
public final boolean isCurrentWindowDynamic() {
|
||||||
@ -338,6 +374,9 @@ public abstract class BasePlayer implements Player {
|
|||||||
return !timeline.isEmpty() && timeline.getWindow(getCurrentMediaItemIndex(), window).isDynamic;
|
return !timeline.isEmpty() && timeline.getWindow(getCurrentMediaItemIndex(), window).isDynamic;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #isCurrentMediaItemLive()} instead.
|
||||||
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
public final boolean isCurrentWindowLive() {
|
public final boolean isCurrentWindowLive() {
|
||||||
@ -364,6 +403,9 @@ public abstract class BasePlayer implements Player {
|
|||||||
return window.getCurrentUnixTimeMs() - window.windowStartTimeMs - getContentPosition();
|
return window.getCurrentUnixTimeMs() - window.windowStartTimeMs - getContentPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #isCurrentMediaItemSeekable()} instead.
|
||||||
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
public final boolean isCurrentWindowSeekable() {
|
public final boolean isCurrentWindowSeekable() {
|
||||||
|
@ -69,6 +69,9 @@ public final class C {
|
|||||||
/** Represents an unset or unknown rate. */
|
/** Represents an unset or unknown rate. */
|
||||||
public static final float RATE_UNSET = -Float.MAX_VALUE;
|
public static final float RATE_UNSET = -Float.MAX_VALUE;
|
||||||
|
|
||||||
|
/** Represents an unset or unknown integer rate. */
|
||||||
|
@UnstableApi public static final int RATE_UNSET_INT = Integer.MIN_VALUE + 1;
|
||||||
|
|
||||||
/** Represents an unset or unknown length. */
|
/** Represents an unset or unknown length. */
|
||||||
public static final int LENGTH_UNSET = -1;
|
public static final int LENGTH_UNSET = -1;
|
||||||
|
|
||||||
@ -167,11 +170,17 @@ public final class C {
|
|||||||
@IntDef({CRYPTO_MODE_UNENCRYPTED, CRYPTO_MODE_AES_CTR, CRYPTO_MODE_AES_CBC})
|
@IntDef({CRYPTO_MODE_UNENCRYPTED, CRYPTO_MODE_AES_CTR, CRYPTO_MODE_AES_CBC})
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public @interface CryptoMode {}
|
public @interface CryptoMode {}
|
||||||
/** @see MediaCodec#CRYPTO_MODE_UNENCRYPTED */
|
/**
|
||||||
|
* @see MediaCodec#CRYPTO_MODE_UNENCRYPTED
|
||||||
|
*/
|
||||||
@UnstableApi public static final int CRYPTO_MODE_UNENCRYPTED = MediaCodec.CRYPTO_MODE_UNENCRYPTED;
|
@UnstableApi public static final int CRYPTO_MODE_UNENCRYPTED = MediaCodec.CRYPTO_MODE_UNENCRYPTED;
|
||||||
/** @see MediaCodec#CRYPTO_MODE_AES_CTR */
|
/**
|
||||||
|
* @see MediaCodec#CRYPTO_MODE_AES_CTR
|
||||||
|
*/
|
||||||
@UnstableApi public static final int CRYPTO_MODE_AES_CTR = MediaCodec.CRYPTO_MODE_AES_CTR;
|
@UnstableApi public static final int CRYPTO_MODE_AES_CTR = MediaCodec.CRYPTO_MODE_AES_CTR;
|
||||||
/** @see MediaCodec#CRYPTO_MODE_AES_CBC */
|
/**
|
||||||
|
* @see MediaCodec#CRYPTO_MODE_AES_CBC
|
||||||
|
*/
|
||||||
@UnstableApi public static final int CRYPTO_MODE_AES_CBC = MediaCodec.CRYPTO_MODE_AES_CBC;
|
@UnstableApi public static final int CRYPTO_MODE_AES_CBC = MediaCodec.CRYPTO_MODE_AES_CBC;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -240,11 +249,17 @@ public final class C {
|
|||||||
ENCODING_PCM_FLOAT
|
ENCODING_PCM_FLOAT
|
||||||
})
|
})
|
||||||
public @interface PcmEncoding {}
|
public @interface PcmEncoding {}
|
||||||
/** @see AudioFormat#ENCODING_INVALID */
|
/**
|
||||||
|
* @see AudioFormat#ENCODING_INVALID
|
||||||
|
*/
|
||||||
@UnstableApi public static final int ENCODING_INVALID = AudioFormat.ENCODING_INVALID;
|
@UnstableApi public static final int ENCODING_INVALID = AudioFormat.ENCODING_INVALID;
|
||||||
/** @see AudioFormat#ENCODING_PCM_8BIT */
|
/**
|
||||||
|
* @see AudioFormat#ENCODING_PCM_8BIT
|
||||||
|
*/
|
||||||
@UnstableApi public static final int ENCODING_PCM_8BIT = AudioFormat.ENCODING_PCM_8BIT;
|
@UnstableApi public static final int ENCODING_PCM_8BIT = AudioFormat.ENCODING_PCM_8BIT;
|
||||||
/** @see AudioFormat#ENCODING_PCM_16BIT */
|
/**
|
||||||
|
* @see AudioFormat#ENCODING_PCM_16BIT
|
||||||
|
*/
|
||||||
@UnstableApi public static final int ENCODING_PCM_16BIT = AudioFormat.ENCODING_PCM_16BIT;
|
@UnstableApi public static final int ENCODING_PCM_16BIT = AudioFormat.ENCODING_PCM_16BIT;
|
||||||
/** Like {@link #ENCODING_PCM_16BIT}, but with the bytes in big endian order. */
|
/** Like {@link #ENCODING_PCM_16BIT}, but with the bytes in big endian order. */
|
||||||
@UnstableApi public static final int ENCODING_PCM_16BIT_BIG_ENDIAN = 0x10000000;
|
@UnstableApi public static final int ENCODING_PCM_16BIT_BIG_ENDIAN = 0x10000000;
|
||||||
@ -252,35 +267,63 @@ public final class C {
|
|||||||
@UnstableApi public static final int ENCODING_PCM_24BIT = 0x20000000;
|
@UnstableApi public static final int ENCODING_PCM_24BIT = 0x20000000;
|
||||||
/** PCM encoding with 32 bits per sample. */
|
/** PCM encoding with 32 bits per sample. */
|
||||||
@UnstableApi public static final int ENCODING_PCM_32BIT = 0x30000000;
|
@UnstableApi public static final int ENCODING_PCM_32BIT = 0x30000000;
|
||||||
/** @see AudioFormat#ENCODING_PCM_FLOAT */
|
/**
|
||||||
|
* @see AudioFormat#ENCODING_PCM_FLOAT
|
||||||
|
*/
|
||||||
@UnstableApi public static final int ENCODING_PCM_FLOAT = AudioFormat.ENCODING_PCM_FLOAT;
|
@UnstableApi public static final int ENCODING_PCM_FLOAT = AudioFormat.ENCODING_PCM_FLOAT;
|
||||||
/** @see AudioFormat#ENCODING_MP3 */
|
/**
|
||||||
|
* @see AudioFormat#ENCODING_MP3
|
||||||
|
*/
|
||||||
@UnstableApi public static final int ENCODING_MP3 = AudioFormat.ENCODING_MP3;
|
@UnstableApi public static final int ENCODING_MP3 = AudioFormat.ENCODING_MP3;
|
||||||
/** @see AudioFormat#ENCODING_AAC_LC */
|
/**
|
||||||
|
* @see AudioFormat#ENCODING_AAC_LC
|
||||||
|
*/
|
||||||
@UnstableApi public static final int ENCODING_AAC_LC = AudioFormat.ENCODING_AAC_LC;
|
@UnstableApi public static final int ENCODING_AAC_LC = AudioFormat.ENCODING_AAC_LC;
|
||||||
/** @see AudioFormat#ENCODING_AAC_HE_V1 */
|
/**
|
||||||
|
* @see AudioFormat#ENCODING_AAC_HE_V1
|
||||||
|
*/
|
||||||
@UnstableApi public static final int ENCODING_AAC_HE_V1 = AudioFormat.ENCODING_AAC_HE_V1;
|
@UnstableApi public static final int ENCODING_AAC_HE_V1 = AudioFormat.ENCODING_AAC_HE_V1;
|
||||||
/** @see AudioFormat#ENCODING_AAC_HE_V2 */
|
/**
|
||||||
|
* @see AudioFormat#ENCODING_AAC_HE_V2
|
||||||
|
*/
|
||||||
@UnstableApi public static final int ENCODING_AAC_HE_V2 = AudioFormat.ENCODING_AAC_HE_V2;
|
@UnstableApi public static final int ENCODING_AAC_HE_V2 = AudioFormat.ENCODING_AAC_HE_V2;
|
||||||
/** @see AudioFormat#ENCODING_AAC_XHE */
|
/**
|
||||||
|
* @see AudioFormat#ENCODING_AAC_XHE
|
||||||
|
*/
|
||||||
@UnstableApi public static final int ENCODING_AAC_XHE = AudioFormat.ENCODING_AAC_XHE;
|
@UnstableApi public static final int ENCODING_AAC_XHE = AudioFormat.ENCODING_AAC_XHE;
|
||||||
/** @see AudioFormat#ENCODING_AAC_ELD */
|
/**
|
||||||
|
* @see AudioFormat#ENCODING_AAC_ELD
|
||||||
|
*/
|
||||||
@UnstableApi public static final int ENCODING_AAC_ELD = AudioFormat.ENCODING_AAC_ELD;
|
@UnstableApi public static final int ENCODING_AAC_ELD = AudioFormat.ENCODING_AAC_ELD;
|
||||||
/** AAC Error Resilient Bit-Sliced Arithmetic Coding. */
|
/** AAC Error Resilient Bit-Sliced Arithmetic Coding. */
|
||||||
@UnstableApi public static final int ENCODING_AAC_ER_BSAC = 0x40000000;
|
@UnstableApi public static final int ENCODING_AAC_ER_BSAC = 0x40000000;
|
||||||
/** @see AudioFormat#ENCODING_AC3 */
|
/**
|
||||||
|
* @see AudioFormat#ENCODING_AC3
|
||||||
|
*/
|
||||||
@UnstableApi public static final int ENCODING_AC3 = AudioFormat.ENCODING_AC3;
|
@UnstableApi public static final int ENCODING_AC3 = AudioFormat.ENCODING_AC3;
|
||||||
/** @see AudioFormat#ENCODING_E_AC3 */
|
/**
|
||||||
|
* @see AudioFormat#ENCODING_E_AC3
|
||||||
|
*/
|
||||||
@UnstableApi public static final int ENCODING_E_AC3 = AudioFormat.ENCODING_E_AC3;
|
@UnstableApi public static final int ENCODING_E_AC3 = AudioFormat.ENCODING_E_AC3;
|
||||||
/** @see AudioFormat#ENCODING_E_AC3_JOC */
|
/**
|
||||||
|
* @see AudioFormat#ENCODING_E_AC3_JOC
|
||||||
|
*/
|
||||||
@UnstableApi public static final int ENCODING_E_AC3_JOC = AudioFormat.ENCODING_E_AC3_JOC;
|
@UnstableApi public static final int ENCODING_E_AC3_JOC = AudioFormat.ENCODING_E_AC3_JOC;
|
||||||
/** @see AudioFormat#ENCODING_AC4 */
|
/**
|
||||||
|
* @see AudioFormat#ENCODING_AC4
|
||||||
|
*/
|
||||||
@UnstableApi public static final int ENCODING_AC4 = AudioFormat.ENCODING_AC4;
|
@UnstableApi public static final int ENCODING_AC4 = AudioFormat.ENCODING_AC4;
|
||||||
/** @see AudioFormat#ENCODING_DTS */
|
/**
|
||||||
|
* @see AudioFormat#ENCODING_DTS
|
||||||
|
*/
|
||||||
@UnstableApi public static final int ENCODING_DTS = AudioFormat.ENCODING_DTS;
|
@UnstableApi public static final int ENCODING_DTS = AudioFormat.ENCODING_DTS;
|
||||||
/** @see AudioFormat#ENCODING_DTS_HD */
|
/**
|
||||||
|
* @see AudioFormat#ENCODING_DTS_HD
|
||||||
|
*/
|
||||||
@UnstableApi public static final int ENCODING_DTS_HD = AudioFormat.ENCODING_DTS_HD;
|
@UnstableApi public static final int ENCODING_DTS_HD = AudioFormat.ENCODING_DTS_HD;
|
||||||
/** @see AudioFormat#ENCODING_DOLBY_TRUEHD */
|
/**
|
||||||
|
* @see AudioFormat#ENCODING_DOLBY_TRUEHD
|
||||||
|
*/
|
||||||
@UnstableApi public static final int ENCODING_DOLBY_TRUEHD = AudioFormat.ENCODING_DOLBY_TRUEHD;
|
@UnstableApi public static final int ENCODING_DOLBY_TRUEHD = AudioFormat.ENCODING_DOLBY_TRUEHD;
|
||||||
|
|
||||||
/** Represents the behavior affecting whether spatialization will be used. */
|
/** Represents the behavior affecting whether spatialization will be used. */
|
||||||
@ -290,12 +333,16 @@ public final class C {
|
|||||||
@IntDef({SPATIALIZATION_BEHAVIOR_AUTO, SPATIALIZATION_BEHAVIOR_NEVER})
|
@IntDef({SPATIALIZATION_BEHAVIOR_AUTO, SPATIALIZATION_BEHAVIOR_NEVER})
|
||||||
public @interface SpatializationBehavior {}
|
public @interface SpatializationBehavior {}
|
||||||
|
|
||||||
// TODO[b/190759307]: Update constant values and javadoc to use SDK once compile SDK target is set
|
/**
|
||||||
// to 32.
|
* @see AudioAttributes#SPATIALIZATION_BEHAVIOR_AUTO
|
||||||
/** See AudioAttributes#SPATIALIZATION_BEHAVIOR_AUTO */
|
*/
|
||||||
public static final int SPATIALIZATION_BEHAVIOR_AUTO = 0;
|
public static final int SPATIALIZATION_BEHAVIOR_AUTO =
|
||||||
/** See AudioAttributes#SPATIALIZATION_BEHAVIOR_NEVER */
|
AudioAttributes.SPATIALIZATION_BEHAVIOR_AUTO;
|
||||||
public static final int SPATIALIZATION_BEHAVIOR_NEVER = 1;
|
/**
|
||||||
|
* @see AudioAttributes#SPATIALIZATION_BEHAVIOR_NEVER
|
||||||
|
*/
|
||||||
|
public static final int SPATIALIZATION_BEHAVIOR_NEVER =
|
||||||
|
AudioAttributes.SPATIALIZATION_BEHAVIOR_NEVER;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stream types for an {@link android.media.AudioTrack}. One of {@link #STREAM_TYPE_ALARM}, {@link
|
* Stream types for an {@link android.media.AudioTrack}. One of {@link #STREAM_TYPE_ALARM}, {@link
|
||||||
@ -321,27 +368,47 @@ public final class C {
|
|||||||
STREAM_TYPE_DEFAULT
|
STREAM_TYPE_DEFAULT
|
||||||
})
|
})
|
||||||
public @interface StreamType {}
|
public @interface StreamType {}
|
||||||
/** @see AudioManager#STREAM_ALARM */
|
/**
|
||||||
|
* @see AudioManager#STREAM_ALARM
|
||||||
|
*/
|
||||||
@UnstableApi public static final int STREAM_TYPE_ALARM = AudioManager.STREAM_ALARM;
|
@UnstableApi public static final int STREAM_TYPE_ALARM = AudioManager.STREAM_ALARM;
|
||||||
/** @see AudioManager#STREAM_DTMF */
|
/**
|
||||||
|
* @see AudioManager#STREAM_DTMF
|
||||||
|
*/
|
||||||
@UnstableApi public static final int STREAM_TYPE_DTMF = AudioManager.STREAM_DTMF;
|
@UnstableApi public static final int STREAM_TYPE_DTMF = AudioManager.STREAM_DTMF;
|
||||||
/** @see AudioManager#STREAM_MUSIC */
|
/**
|
||||||
|
* @see AudioManager#STREAM_MUSIC
|
||||||
|
*/
|
||||||
@UnstableApi public static final int STREAM_TYPE_MUSIC = AudioManager.STREAM_MUSIC;
|
@UnstableApi public static final int STREAM_TYPE_MUSIC = AudioManager.STREAM_MUSIC;
|
||||||
/** @see AudioManager#STREAM_NOTIFICATION */
|
/**
|
||||||
|
* @see AudioManager#STREAM_NOTIFICATION
|
||||||
|
*/
|
||||||
@UnstableApi public static final int STREAM_TYPE_NOTIFICATION = AudioManager.STREAM_NOTIFICATION;
|
@UnstableApi public static final int STREAM_TYPE_NOTIFICATION = AudioManager.STREAM_NOTIFICATION;
|
||||||
/** @see AudioManager#STREAM_RING */
|
/**
|
||||||
|
* @see AudioManager#STREAM_RING
|
||||||
|
*/
|
||||||
@UnstableApi public static final int STREAM_TYPE_RING = AudioManager.STREAM_RING;
|
@UnstableApi public static final int STREAM_TYPE_RING = AudioManager.STREAM_RING;
|
||||||
/** @see AudioManager#STREAM_SYSTEM */
|
/**
|
||||||
|
* @see AudioManager#STREAM_SYSTEM
|
||||||
|
*/
|
||||||
@UnstableApi public static final int STREAM_TYPE_SYSTEM = AudioManager.STREAM_SYSTEM;
|
@UnstableApi public static final int STREAM_TYPE_SYSTEM = AudioManager.STREAM_SYSTEM;
|
||||||
/** @see AudioManager#STREAM_VOICE_CALL */
|
/**
|
||||||
|
* @see AudioManager#STREAM_VOICE_CALL
|
||||||
|
*/
|
||||||
@UnstableApi public static final int STREAM_TYPE_VOICE_CALL = AudioManager.STREAM_VOICE_CALL;
|
@UnstableApi public static final int STREAM_TYPE_VOICE_CALL = AudioManager.STREAM_VOICE_CALL;
|
||||||
/** The default stream type used by audio renderers. Equal to {@link #STREAM_TYPE_MUSIC}. */
|
/** The default stream type used by audio renderers. Equal to {@link #STREAM_TYPE_MUSIC}. */
|
||||||
@UnstableApi public static final int STREAM_TYPE_DEFAULT = STREAM_TYPE_MUSIC;
|
@UnstableApi public static final int STREAM_TYPE_DEFAULT = STREAM_TYPE_MUSIC;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Content types for audio attributes. One of {@link #CONTENT_TYPE_MOVIE}, {@link
|
* Content types for audio attributes. One of:
|
||||||
* #CONTENT_TYPE_MUSIC}, {@link #CONTENT_TYPE_SONIFICATION}, {@link #CONTENT_TYPE_SPEECH} or
|
*
|
||||||
* {@link #CONTENT_TYPE_UNKNOWN}.
|
* <ul>
|
||||||
|
* <li>{@link #AUDIO_CONTENT_TYPE_MOVIE}
|
||||||
|
* <li>{@link #AUDIO_CONTENT_TYPE_MUSIC}
|
||||||
|
* <li>{@link #AUDIO_CONTENT_TYPE_SONIFICATION}
|
||||||
|
* <li>{@link #AUDIO_CONTENT_TYPE_SPEECH}
|
||||||
|
* <li>{@link #AUDIO_CONTENT_TYPE_UNKNOWN}
|
||||||
|
* </ul>
|
||||||
*/
|
*/
|
||||||
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
|
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
|
||||||
// with Kotlin usages from before TYPE_USE was added.
|
// with Kotlin usages from before TYPE_USE was added.
|
||||||
@ -349,24 +416,46 @@ public final class C {
|
|||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
||||||
@IntDef({
|
@IntDef({
|
||||||
CONTENT_TYPE_MOVIE,
|
AUDIO_CONTENT_TYPE_MOVIE,
|
||||||
CONTENT_TYPE_MUSIC,
|
AUDIO_CONTENT_TYPE_MUSIC,
|
||||||
CONTENT_TYPE_SONIFICATION,
|
AUDIO_CONTENT_TYPE_SONIFICATION,
|
||||||
CONTENT_TYPE_SPEECH,
|
AUDIO_CONTENT_TYPE_SPEECH,
|
||||||
CONTENT_TYPE_UNKNOWN
|
AUDIO_CONTENT_TYPE_UNKNOWN
|
||||||
})
|
})
|
||||||
public @interface AudioContentType {}
|
public @interface AudioContentType {}
|
||||||
/** @see android.media.AudioAttributes#CONTENT_TYPE_MOVIE */
|
/** See {@link AudioAttributes#CONTENT_TYPE_MOVIE}. */
|
||||||
public static final int CONTENT_TYPE_MOVIE = android.media.AudioAttributes.CONTENT_TYPE_MOVIE;
|
public static final int AUDIO_CONTENT_TYPE_MOVIE = AudioAttributes.CONTENT_TYPE_MOVIE;
|
||||||
/** @see android.media.AudioAttributes#CONTENT_TYPE_MUSIC */
|
/**
|
||||||
public static final int CONTENT_TYPE_MUSIC = android.media.AudioAttributes.CONTENT_TYPE_MUSIC;
|
* @deprecated Use {@link #AUDIO_CONTENT_TYPE_MOVIE} instead.
|
||||||
/** @see android.media.AudioAttributes#CONTENT_TYPE_SONIFICATION */
|
*/
|
||||||
public static final int CONTENT_TYPE_SONIFICATION =
|
@UnstableApi @Deprecated public static final int CONTENT_TYPE_MOVIE = AUDIO_CONTENT_TYPE_MOVIE;
|
||||||
android.media.AudioAttributes.CONTENT_TYPE_SONIFICATION;
|
/** See {@link AudioAttributes#CONTENT_TYPE_MUSIC}. */
|
||||||
/** @see android.media.AudioAttributes#CONTENT_TYPE_SPEECH */
|
public static final int AUDIO_CONTENT_TYPE_MUSIC = AudioAttributes.CONTENT_TYPE_MUSIC;
|
||||||
public static final int CONTENT_TYPE_SPEECH = android.media.AudioAttributes.CONTENT_TYPE_SPEECH;
|
/**
|
||||||
/** @see android.media.AudioAttributes#CONTENT_TYPE_UNKNOWN */
|
* @deprecated Use {@link #AUDIO_CONTENT_TYPE_MUSIC} instead.
|
||||||
public static final int CONTENT_TYPE_UNKNOWN = android.media.AudioAttributes.CONTENT_TYPE_UNKNOWN;
|
*/
|
||||||
|
@UnstableApi @Deprecated public static final int CONTENT_TYPE_MUSIC = AUDIO_CONTENT_TYPE_MUSIC;
|
||||||
|
/** See {@link AudioAttributes#CONTENT_TYPE_SONIFICATION}. */
|
||||||
|
public static final int AUDIO_CONTENT_TYPE_SONIFICATION =
|
||||||
|
AudioAttributes.CONTENT_TYPE_SONIFICATION;
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #AUDIO_CONTENT_TYPE_SONIFICATION} instead.
|
||||||
|
*/
|
||||||
|
@UnstableApi @Deprecated
|
||||||
|
public static final int CONTENT_TYPE_SONIFICATION = AUDIO_CONTENT_TYPE_SONIFICATION;
|
||||||
|
/** See {@link AudioAttributes#CONTENT_TYPE_SPEECH}. */
|
||||||
|
public static final int AUDIO_CONTENT_TYPE_SPEECH = AudioAttributes.CONTENT_TYPE_SPEECH;
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #AUDIO_CONTENT_TYPE_SPEECH} instead.
|
||||||
|
*/
|
||||||
|
@UnstableApi @Deprecated public static final int CONTENT_TYPE_SPEECH = AUDIO_CONTENT_TYPE_SPEECH;
|
||||||
|
/** See {@link AudioAttributes#CONTENT_TYPE_UNKNOWN}. */
|
||||||
|
public static final int AUDIO_CONTENT_TYPE_UNKNOWN = AudioAttributes.CONTENT_TYPE_UNKNOWN;
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #AUDIO_CONTENT_TYPE_UNKNOWN} instead.
|
||||||
|
*/
|
||||||
|
@UnstableApi @Deprecated
|
||||||
|
public static final int CONTENT_TYPE_UNKNOWN = AUDIO_CONTENT_TYPE_UNKNOWN;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Flags for audio attributes. Possible flag value is {@link #FLAG_AUDIBILITY_ENFORCED}.
|
* Flags for audio attributes. Possible flag value is {@link #FLAG_AUDIBILITY_ENFORCED}.
|
||||||
@ -383,7 +472,9 @@ public final class C {
|
|||||||
flag = true,
|
flag = true,
|
||||||
value = {FLAG_AUDIBILITY_ENFORCED})
|
value = {FLAG_AUDIBILITY_ENFORCED})
|
||||||
public @interface AudioFlags {}
|
public @interface AudioFlags {}
|
||||||
/** @see android.media.AudioAttributes#FLAG_AUDIBILITY_ENFORCED */
|
/**
|
||||||
|
* @see android.media.AudioAttributes#FLAG_AUDIBILITY_ENFORCED
|
||||||
|
*/
|
||||||
public static final int FLAG_AUDIBILITY_ENFORCED =
|
public static final int FLAG_AUDIBILITY_ENFORCED =
|
||||||
android.media.AudioAttributes.FLAG_AUDIBILITY_ENFORCED;
|
android.media.AudioAttributes.FLAG_AUDIBILITY_ENFORCED;
|
||||||
|
|
||||||
@ -421,46 +512,78 @@ public final class C {
|
|||||||
USAGE_VOICE_COMMUNICATION_SIGNALLING
|
USAGE_VOICE_COMMUNICATION_SIGNALLING
|
||||||
})
|
})
|
||||||
public @interface AudioUsage {}
|
public @interface AudioUsage {}
|
||||||
/** @see android.media.AudioAttributes#USAGE_ALARM */
|
/**
|
||||||
|
* @see android.media.AudioAttributes#USAGE_ALARM
|
||||||
|
*/
|
||||||
public static final int USAGE_ALARM = android.media.AudioAttributes.USAGE_ALARM;
|
public static final int USAGE_ALARM = android.media.AudioAttributes.USAGE_ALARM;
|
||||||
/** @see android.media.AudioAttributes#USAGE_ASSISTANCE_ACCESSIBILITY */
|
/**
|
||||||
|
* @see android.media.AudioAttributes#USAGE_ASSISTANCE_ACCESSIBILITY
|
||||||
|
*/
|
||||||
public static final int USAGE_ASSISTANCE_ACCESSIBILITY =
|
public static final int USAGE_ASSISTANCE_ACCESSIBILITY =
|
||||||
android.media.AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY;
|
android.media.AudioAttributes.USAGE_ASSISTANCE_ACCESSIBILITY;
|
||||||
/** @see android.media.AudioAttributes#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE */
|
/**
|
||||||
|
* @see android.media.AudioAttributes#USAGE_ASSISTANCE_NAVIGATION_GUIDANCE
|
||||||
|
*/
|
||||||
public static final int USAGE_ASSISTANCE_NAVIGATION_GUIDANCE =
|
public static final int USAGE_ASSISTANCE_NAVIGATION_GUIDANCE =
|
||||||
android.media.AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE;
|
android.media.AudioAttributes.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE;
|
||||||
/** @see android.media.AudioAttributes#USAGE_ASSISTANCE_SONIFICATION */
|
/**
|
||||||
|
* @see android.media.AudioAttributes#USAGE_ASSISTANCE_SONIFICATION
|
||||||
|
*/
|
||||||
public static final int USAGE_ASSISTANCE_SONIFICATION =
|
public static final int USAGE_ASSISTANCE_SONIFICATION =
|
||||||
android.media.AudioAttributes.USAGE_ASSISTANCE_SONIFICATION;
|
android.media.AudioAttributes.USAGE_ASSISTANCE_SONIFICATION;
|
||||||
/** @see android.media.AudioAttributes#USAGE_ASSISTANT */
|
/**
|
||||||
|
* @see android.media.AudioAttributes#USAGE_ASSISTANT
|
||||||
|
*/
|
||||||
public static final int USAGE_ASSISTANT = android.media.AudioAttributes.USAGE_ASSISTANT;
|
public static final int USAGE_ASSISTANT = android.media.AudioAttributes.USAGE_ASSISTANT;
|
||||||
/** @see android.media.AudioAttributes#USAGE_GAME */
|
/**
|
||||||
|
* @see android.media.AudioAttributes#USAGE_GAME
|
||||||
|
*/
|
||||||
public static final int USAGE_GAME = android.media.AudioAttributes.USAGE_GAME;
|
public static final int USAGE_GAME = android.media.AudioAttributes.USAGE_GAME;
|
||||||
/** @see android.media.AudioAttributes#USAGE_MEDIA */
|
/**
|
||||||
|
* @see android.media.AudioAttributes#USAGE_MEDIA
|
||||||
|
*/
|
||||||
public static final int USAGE_MEDIA = android.media.AudioAttributes.USAGE_MEDIA;
|
public static final int USAGE_MEDIA = android.media.AudioAttributes.USAGE_MEDIA;
|
||||||
/** @see android.media.AudioAttributes#USAGE_NOTIFICATION */
|
/**
|
||||||
|
* @see android.media.AudioAttributes#USAGE_NOTIFICATION
|
||||||
|
*/
|
||||||
public static final int USAGE_NOTIFICATION = android.media.AudioAttributes.USAGE_NOTIFICATION;
|
public static final int USAGE_NOTIFICATION = android.media.AudioAttributes.USAGE_NOTIFICATION;
|
||||||
/** @see android.media.AudioAttributes#USAGE_NOTIFICATION_COMMUNICATION_DELAYED */
|
/**
|
||||||
|
* @see android.media.AudioAttributes#USAGE_NOTIFICATION_COMMUNICATION_DELAYED
|
||||||
|
*/
|
||||||
public static final int USAGE_NOTIFICATION_COMMUNICATION_DELAYED =
|
public static final int USAGE_NOTIFICATION_COMMUNICATION_DELAYED =
|
||||||
android.media.AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED;
|
android.media.AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_DELAYED;
|
||||||
/** @see android.media.AudioAttributes#USAGE_NOTIFICATION_COMMUNICATION_INSTANT */
|
/**
|
||||||
|
* @see android.media.AudioAttributes#USAGE_NOTIFICATION_COMMUNICATION_INSTANT
|
||||||
|
*/
|
||||||
public static final int USAGE_NOTIFICATION_COMMUNICATION_INSTANT =
|
public static final int USAGE_NOTIFICATION_COMMUNICATION_INSTANT =
|
||||||
android.media.AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT;
|
android.media.AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_INSTANT;
|
||||||
/** @see android.media.AudioAttributes#USAGE_NOTIFICATION_COMMUNICATION_REQUEST */
|
/**
|
||||||
|
* @see android.media.AudioAttributes#USAGE_NOTIFICATION_COMMUNICATION_REQUEST
|
||||||
|
*/
|
||||||
public static final int USAGE_NOTIFICATION_COMMUNICATION_REQUEST =
|
public static final int USAGE_NOTIFICATION_COMMUNICATION_REQUEST =
|
||||||
android.media.AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST;
|
android.media.AudioAttributes.USAGE_NOTIFICATION_COMMUNICATION_REQUEST;
|
||||||
/** @see android.media.AudioAttributes#USAGE_NOTIFICATION_EVENT */
|
/**
|
||||||
|
* @see android.media.AudioAttributes#USAGE_NOTIFICATION_EVENT
|
||||||
|
*/
|
||||||
public static final int USAGE_NOTIFICATION_EVENT =
|
public static final int USAGE_NOTIFICATION_EVENT =
|
||||||
android.media.AudioAttributes.USAGE_NOTIFICATION_EVENT;
|
android.media.AudioAttributes.USAGE_NOTIFICATION_EVENT;
|
||||||
/** @see android.media.AudioAttributes#USAGE_NOTIFICATION_RINGTONE */
|
/**
|
||||||
|
* @see android.media.AudioAttributes#USAGE_NOTIFICATION_RINGTONE
|
||||||
|
*/
|
||||||
public static final int USAGE_NOTIFICATION_RINGTONE =
|
public static final int USAGE_NOTIFICATION_RINGTONE =
|
||||||
android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
|
android.media.AudioAttributes.USAGE_NOTIFICATION_RINGTONE;
|
||||||
/** @see android.media.AudioAttributes#USAGE_UNKNOWN */
|
/**
|
||||||
|
* @see android.media.AudioAttributes#USAGE_UNKNOWN
|
||||||
|
*/
|
||||||
public static final int USAGE_UNKNOWN = android.media.AudioAttributes.USAGE_UNKNOWN;
|
public static final int USAGE_UNKNOWN = android.media.AudioAttributes.USAGE_UNKNOWN;
|
||||||
/** @see android.media.AudioAttributes#USAGE_VOICE_COMMUNICATION */
|
/**
|
||||||
|
* @see android.media.AudioAttributes#USAGE_VOICE_COMMUNICATION
|
||||||
|
*/
|
||||||
public static final int USAGE_VOICE_COMMUNICATION =
|
public static final int USAGE_VOICE_COMMUNICATION =
|
||||||
android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION;
|
android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION;
|
||||||
/** @see android.media.AudioAttributes#USAGE_VOICE_COMMUNICATION_SIGNALLING */
|
/**
|
||||||
|
* @see android.media.AudioAttributes#USAGE_VOICE_COMMUNICATION_SIGNALLING
|
||||||
|
*/
|
||||||
public static final int USAGE_VOICE_COMMUNICATION_SIGNALLING =
|
public static final int USAGE_VOICE_COMMUNICATION_SIGNALLING =
|
||||||
android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING;
|
android.media.AudioAttributes.USAGE_VOICE_COMMUNICATION_SIGNALLING;
|
||||||
|
|
||||||
@ -484,8 +607,9 @@ public final class C {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* Flags which can apply to a buffer containing a media sample. Possible flag values are {@link
|
* Flags which can apply to a buffer containing a media sample. Possible flag values are {@link
|
||||||
* #BUFFER_FLAG_KEY_FRAME}, {@link #BUFFER_FLAG_END_OF_STREAM}, {@link #BUFFER_FLAG_LAST_SAMPLE},
|
* #BUFFER_FLAG_KEY_FRAME}, {@link #BUFFER_FLAG_END_OF_STREAM}, {@link #BUFFER_FLAG_FIRST_SAMPLE},
|
||||||
* {@link #BUFFER_FLAG_ENCRYPTED} and {@link #BUFFER_FLAG_DECODE_ONLY}.
|
* {@link #BUFFER_FLAG_LAST_SAMPLE}, {@link #BUFFER_FLAG_ENCRYPTED} and {@link
|
||||||
|
* #BUFFER_FLAG_DECODE_ONLY}.
|
||||||
*/
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Documented
|
@Documented
|
||||||
@ -496,6 +620,7 @@ public final class C {
|
|||||||
value = {
|
value = {
|
||||||
BUFFER_FLAG_KEY_FRAME,
|
BUFFER_FLAG_KEY_FRAME,
|
||||||
BUFFER_FLAG_END_OF_STREAM,
|
BUFFER_FLAG_END_OF_STREAM,
|
||||||
|
BUFFER_FLAG_FIRST_SAMPLE,
|
||||||
BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA,
|
BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA,
|
||||||
BUFFER_FLAG_LAST_SAMPLE,
|
BUFFER_FLAG_LAST_SAMPLE,
|
||||||
BUFFER_FLAG_ENCRYPTED,
|
BUFFER_FLAG_ENCRYPTED,
|
||||||
@ -507,6 +632,8 @@ public final class C {
|
|||||||
/** Flag for empty buffers that signal that the end of the stream was reached. */
|
/** Flag for empty buffers that signal that the end of the stream was reached. */
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public static final int BUFFER_FLAG_END_OF_STREAM = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
|
public static final int BUFFER_FLAG_END_OF_STREAM = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
|
||||||
|
/** Indicates that a buffer is known to contain the first media sample of the stream. */
|
||||||
|
@UnstableApi public static final int BUFFER_FLAG_FIRST_SAMPLE = 1 << 27; // 0x08000000
|
||||||
/** Indicates that a buffer has supplemental data. */
|
/** Indicates that a buffer has supplemental data. */
|
||||||
@UnstableApi public static final int BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA = 1 << 28; // 0x10000000
|
@UnstableApi public static final int BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA = 1 << 28; // 0x10000000
|
||||||
/** Indicates that a buffer is known to contain the last media sample of the stream. */
|
/** Indicates that a buffer is known to contain the last media sample of the stream. */
|
||||||
@ -620,30 +747,59 @@ public final class C {
|
|||||||
public static final String LANGUAGE_UNDETERMINED = "und";
|
public static final String LANGUAGE_UNDETERMINED = "und";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Represents a streaming or other media type. One of {@link #TYPE_DASH}, {@link #TYPE_SS}, {@link
|
* Represents a streaming or other media type. One of:
|
||||||
* #TYPE_HLS}, {@link #TYPE_RTSP} or {@link #TYPE_OTHER}.
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link #CONTENT_TYPE_DASH}
|
||||||
|
* <li>{@link #CONTENT_TYPE_SS}
|
||||||
|
* <li>{@link #CONTENT_TYPE_HLS}
|
||||||
|
* <li>{@link #CONTENT_TYPE_RTSP}
|
||||||
|
* <li>{@link #CONTENT_TYPE_OTHER}
|
||||||
|
* </ul>
|
||||||
*/
|
*/
|
||||||
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
|
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
|
||||||
// with Kotlin usages from before TYPE_USE was added.
|
// with Kotlin usages from before TYPE_USE was added.
|
||||||
@UnstableApi
|
|
||||||
@Documented
|
@Documented
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
||||||
@IntDef({TYPE_DASH, TYPE_SS, TYPE_HLS, TYPE_RTSP, TYPE_OTHER})
|
@IntDef({
|
||||||
|
CONTENT_TYPE_DASH,
|
||||||
|
CONTENT_TYPE_SS,
|
||||||
|
CONTENT_TYPE_HLS,
|
||||||
|
CONTENT_TYPE_RTSP,
|
||||||
|
CONTENT_TYPE_OTHER
|
||||||
|
})
|
||||||
public @interface ContentType {}
|
public @interface ContentType {}
|
||||||
/** Value returned by {@link Util#inferContentType(String)} for DASH manifests. */
|
/** Value representing a DASH manifest. */
|
||||||
@UnstableApi public static final int TYPE_DASH = 0;
|
public static final int CONTENT_TYPE_DASH = 0;
|
||||||
/** Value returned by {@link Util#inferContentType(String)} for Smooth Streaming manifests. */
|
|
||||||
@UnstableApi public static final int TYPE_SS = 1;
|
|
||||||
/** Value returned by {@link Util#inferContentType(String)} for HLS manifests. */
|
|
||||||
@UnstableApi public static final int TYPE_HLS = 2;
|
|
||||||
/** Value returned by {@link Util#inferContentType(String)} for RTSP. */
|
|
||||||
@UnstableApi public static final int TYPE_RTSP = 3;
|
|
||||||
/**
|
/**
|
||||||
* Value returned by {@link Util#inferContentType(String)} for files other than DASH, HLS or
|
* @deprecated Use {@link #CONTENT_TYPE_DASH} instead.
|
||||||
* Smooth Streaming manifests, or RTSP URIs.
|
|
||||||
*/
|
*/
|
||||||
@UnstableApi public static final int TYPE_OTHER = 4;
|
@Deprecated @UnstableApi public static final int TYPE_DASH = CONTENT_TYPE_DASH;
|
||||||
|
/** Value representing a Smooth Streaming manifest. */
|
||||||
|
public static final int CONTENT_TYPE_SS = 1;
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #CONTENT_TYPE_SS} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated @UnstableApi public static final int TYPE_SS = CONTENT_TYPE_SS;
|
||||||
|
/** Value representing an HLS manifest. */
|
||||||
|
public static final int CONTENT_TYPE_HLS = 2;
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #CONTENT_TYPE_HLS} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated @UnstableApi public static final int TYPE_HLS = CONTENT_TYPE_HLS;
|
||||||
|
/** Value representing an RTSP stream. */
|
||||||
|
public static final int CONTENT_TYPE_RTSP = 3;
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #CONTENT_TYPE_RTSP} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated @UnstableApi public static final int TYPE_RTSP = CONTENT_TYPE_RTSP;
|
||||||
|
/** Value representing files other than DASH, HLS or Smooth Streaming manifests, or RTSP URIs. */
|
||||||
|
public static final int CONTENT_TYPE_OTHER = 4;
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #CONTENT_TYPE_OTHER} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated @UnstableApi public static final int TYPE_OTHER = CONTENT_TYPE_OTHER;
|
||||||
|
|
||||||
/** A return value for methods where the end of an input was encountered. */
|
/** A return value for methods where the end of an input was encountered. */
|
||||||
@UnstableApi public static final int RESULT_END_OF_INPUT = -1;
|
@UnstableApi public static final int RESULT_END_OF_INPUT = -1;
|
||||||
@ -898,11 +1054,17 @@ public final class C {
|
|||||||
@Target(TYPE_USE)
|
@Target(TYPE_USE)
|
||||||
@IntDef({Format.NO_VALUE, COLOR_SPACE_BT709, COLOR_SPACE_BT601, COLOR_SPACE_BT2020})
|
@IntDef({Format.NO_VALUE, COLOR_SPACE_BT709, COLOR_SPACE_BT601, COLOR_SPACE_BT2020})
|
||||||
public @interface ColorSpace {}
|
public @interface ColorSpace {}
|
||||||
/** @see MediaFormat#COLOR_STANDARD_BT709 */
|
/**
|
||||||
|
* @see MediaFormat#COLOR_STANDARD_BT709
|
||||||
|
*/
|
||||||
@UnstableApi public static final int COLOR_SPACE_BT709 = MediaFormat.COLOR_STANDARD_BT709;
|
@UnstableApi public static final int COLOR_SPACE_BT709 = MediaFormat.COLOR_STANDARD_BT709;
|
||||||
/** @see MediaFormat#COLOR_STANDARD_BT601_PAL */
|
/**
|
||||||
|
* @see MediaFormat#COLOR_STANDARD_BT601_PAL
|
||||||
|
*/
|
||||||
@UnstableApi public static final int COLOR_SPACE_BT601 = MediaFormat.COLOR_STANDARD_BT601_PAL;
|
@UnstableApi public static final int COLOR_SPACE_BT601 = MediaFormat.COLOR_STANDARD_BT601_PAL;
|
||||||
/** @see MediaFormat#COLOR_STANDARD_BT2020 */
|
/**
|
||||||
|
* @see MediaFormat#COLOR_STANDARD_BT2020
|
||||||
|
*/
|
||||||
@UnstableApi public static final int COLOR_SPACE_BT2020 = MediaFormat.COLOR_STANDARD_BT2020;
|
@UnstableApi public static final int COLOR_SPACE_BT2020 = MediaFormat.COLOR_STANDARD_BT2020;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -915,11 +1077,17 @@ public final class C {
|
|||||||
@Target(TYPE_USE)
|
@Target(TYPE_USE)
|
||||||
@IntDef({Format.NO_VALUE, COLOR_TRANSFER_SDR, COLOR_TRANSFER_ST2084, COLOR_TRANSFER_HLG})
|
@IntDef({Format.NO_VALUE, COLOR_TRANSFER_SDR, COLOR_TRANSFER_ST2084, COLOR_TRANSFER_HLG})
|
||||||
public @interface ColorTransfer {}
|
public @interface ColorTransfer {}
|
||||||
/** @see MediaFormat#COLOR_TRANSFER_SDR_VIDEO */
|
/**
|
||||||
|
* @see MediaFormat#COLOR_TRANSFER_SDR_VIDEO
|
||||||
|
*/
|
||||||
@UnstableApi public static final int COLOR_TRANSFER_SDR = MediaFormat.COLOR_TRANSFER_SDR_VIDEO;
|
@UnstableApi public static final int COLOR_TRANSFER_SDR = MediaFormat.COLOR_TRANSFER_SDR_VIDEO;
|
||||||
/** @see MediaFormat#COLOR_TRANSFER_ST2084 */
|
/**
|
||||||
|
* @see MediaFormat#COLOR_TRANSFER_ST2084
|
||||||
|
*/
|
||||||
@UnstableApi public static final int COLOR_TRANSFER_ST2084 = MediaFormat.COLOR_TRANSFER_ST2084;
|
@UnstableApi public static final int COLOR_TRANSFER_ST2084 = MediaFormat.COLOR_TRANSFER_ST2084;
|
||||||
/** @see MediaFormat#COLOR_TRANSFER_HLG */
|
/**
|
||||||
|
* @see MediaFormat#COLOR_TRANSFER_HLG
|
||||||
|
*/
|
||||||
@UnstableApi public static final int COLOR_TRANSFER_HLG = MediaFormat.COLOR_TRANSFER_HLG;
|
@UnstableApi public static final int COLOR_TRANSFER_HLG = MediaFormat.COLOR_TRANSFER_HLG;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -932,9 +1100,13 @@ public final class C {
|
|||||||
@Target(TYPE_USE)
|
@Target(TYPE_USE)
|
||||||
@IntDef({Format.NO_VALUE, COLOR_RANGE_LIMITED, COLOR_RANGE_FULL})
|
@IntDef({Format.NO_VALUE, COLOR_RANGE_LIMITED, COLOR_RANGE_FULL})
|
||||||
public @interface ColorRange {}
|
public @interface ColorRange {}
|
||||||
/** @see MediaFormat#COLOR_RANGE_LIMITED */
|
/**
|
||||||
|
* @see MediaFormat#COLOR_RANGE_LIMITED
|
||||||
|
*/
|
||||||
@UnstableApi public static final int COLOR_RANGE_LIMITED = MediaFormat.COLOR_RANGE_LIMITED;
|
@UnstableApi public static final int COLOR_RANGE_LIMITED = MediaFormat.COLOR_RANGE_LIMITED;
|
||||||
/** @see MediaFormat#COLOR_RANGE_FULL */
|
/**
|
||||||
|
* @see MediaFormat#COLOR_RANGE_FULL
|
||||||
|
*/
|
||||||
@UnstableApi public static final int COLOR_RANGE_FULL = MediaFormat.COLOR_RANGE_FULL;
|
@UnstableApi public static final int COLOR_RANGE_FULL = MediaFormat.COLOR_RANGE_FULL;
|
||||||
|
|
||||||
/** Video projection types. */
|
/** Video projection types. */
|
||||||
@ -1200,7 +1372,9 @@ public final class C {
|
|||||||
*/
|
*/
|
||||||
@UnstableApi public static final int FORMAT_UNSUPPORTED_TYPE = 0b000;
|
@UnstableApi public static final int FORMAT_UNSUPPORTED_TYPE = 0b000;
|
||||||
|
|
||||||
/** @deprecated Use {@link Util#usToMs(long)}. */
|
/**
|
||||||
|
* @deprecated Use {@link Util#usToMs(long)}.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@InlineMe(
|
@InlineMe(
|
||||||
replacement = "Util.usToMs(timeUs)",
|
replacement = "Util.usToMs(timeUs)",
|
||||||
@ -1210,7 +1384,9 @@ public final class C {
|
|||||||
return Util.usToMs(timeUs);
|
return Util.usToMs(timeUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Use {@link Util#msToUs(long)}. */
|
/**
|
||||||
|
* @deprecated Use {@link Util#msToUs(long)}.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@InlineMe(
|
@InlineMe(
|
||||||
replacement = "Util.msToUs(timeMs)",
|
replacement = "Util.msToUs(timeMs)",
|
||||||
@ -1220,7 +1396,9 @@ public final class C {
|
|||||||
return Util.msToUs(timeMs);
|
return Util.msToUs(timeMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Use {@link Util#generateAudioSessionIdV21(Context)}. */
|
/**
|
||||||
|
* @deprecated Use {@link Util#generateAudioSessionIdV21(Context)}.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@InlineMe(
|
@InlineMe(
|
||||||
replacement = "Util.generateAudioSessionIdV21(context)",
|
replacement = "Util.generateAudioSessionIdV21(context)",
|
||||||
@ -1231,7 +1409,9 @@ public final class C {
|
|||||||
return Util.generateAudioSessionIdV21(context);
|
return Util.generateAudioSessionIdV21(context);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Use {@link Util#getFormatSupportString(int)}. */
|
/**
|
||||||
|
* @deprecated Use {@link Util#getFormatSupportString(int)}.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@InlineMe(
|
@InlineMe(
|
||||||
replacement = "Util.getFormatSupportString(formatSupport)",
|
replacement = "Util.getFormatSupportString(formatSupport)",
|
||||||
@ -1241,7 +1421,9 @@ public final class C {
|
|||||||
return Util.getFormatSupportString(formatSupport);
|
return Util.getFormatSupportString(formatSupport);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Use {@link Util#getErrorCodeForMediaDrmErrorCode(int)}. */
|
/**
|
||||||
|
* @deprecated Use {@link Util#getErrorCodeForMediaDrmErrorCode(int)}.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@InlineMe(
|
@InlineMe(
|
||||||
replacement = "Util.getErrorCodeForMediaDrmErrorCode(mediaDrmErrorCode)",
|
replacement = "Util.getErrorCodeForMediaDrmErrorCode(mediaDrmErrorCode)",
|
||||||
|
@ -92,7 +92,9 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
|
|||||||
/** Number of {@link SchemeData}s. */
|
/** Number of {@link SchemeData}s. */
|
||||||
public final int schemeDataCount;
|
public final int schemeDataCount;
|
||||||
|
|
||||||
/** @param schemeDatas Scheme initialization data for possibly multiple DRM schemes. */
|
/**
|
||||||
|
* @param schemeDatas Scheme initialization data for possibly multiple DRM schemes.
|
||||||
|
*/
|
||||||
public DrmInitData(List<SchemeData> schemeDatas) {
|
public DrmInitData(List<SchemeData> schemeDatas) {
|
||||||
this(null, false, schemeDatas.toArray(new SchemeData[0]));
|
this(null, false, schemeDatas.toArray(new SchemeData[0]));
|
||||||
}
|
}
|
||||||
@ -105,7 +107,9 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
|
|||||||
this(schemeType, false, schemeDatas.toArray(new SchemeData[0]));
|
this(schemeType, false, schemeDatas.toArray(new SchemeData[0]));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param schemeDatas Scheme initialization data for possibly multiple DRM schemes. */
|
/**
|
||||||
|
* @param schemeDatas Scheme initialization data for possibly multiple DRM schemes.
|
||||||
|
*/
|
||||||
public DrmInitData(SchemeData... schemeDatas) {
|
public DrmInitData(SchemeData... schemeDatas) {
|
||||||
this(null, schemeDatas);
|
this(null, schemeDatas);
|
||||||
}
|
}
|
||||||
|
@ -16,10 +16,8 @@
|
|||||||
package androidx.media3.common;
|
package androidx.media3.common;
|
||||||
|
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
|
||||||
|
|
||||||
/** Converts throwables into error codes and user readable error messages. */
|
/** Converts throwables into error codes and user readable error messages. */
|
||||||
@UnstableApi
|
|
||||||
public interface ErrorMessageProvider<T extends Throwable> {
|
public interface ErrorMessageProvider<T extends Throwable> {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -37,13 +37,14 @@ public final class FileTypes {
|
|||||||
/**
|
/**
|
||||||
* File types. One of {@link #UNKNOWN}, {@link #AC3}, {@link #AC4}, {@link #ADTS}, {@link #AMR},
|
* File types. One of {@link #UNKNOWN}, {@link #AC3}, {@link #AC4}, {@link #ADTS}, {@link #AMR},
|
||||||
* {@link #FLAC}, {@link #FLV}, {@link #MATROSKA}, {@link #MP3}, {@link #MP4}, {@link #OGG},
|
* {@link #FLAC}, {@link #FLV}, {@link #MATROSKA}, {@link #MP3}, {@link #MP4}, {@link #OGG},
|
||||||
* {@link #PS}, {@link #TS}, {@link #WAV}, {@link #WEBVTT} and {@link #JPEG}.
|
* {@link #PS}, {@link #TS}, {@link #WAV}, {@link #WEBVTT}, {@link #JPEG} and {@link #MIDI}.
|
||||||
*/
|
*/
|
||||||
@Documented
|
@Documented
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@Target(TYPE_USE)
|
@Target(TYPE_USE)
|
||||||
@IntDef({
|
@IntDef({
|
||||||
UNKNOWN, AC3, AC4, ADTS, AMR, FLAC, FLV, MATROSKA, MP3, MP4, OGG, PS, TS, WAV, WEBVTT, JPEG
|
UNKNOWN, AC3, AC4, ADTS, AMR, FLAC, FLV, MATROSKA, MP3, MP4, OGG, PS, TS, WAV, WEBVTT, JPEG,
|
||||||
|
MIDI, AVI
|
||||||
})
|
})
|
||||||
public @interface Type {}
|
public @interface Type {}
|
||||||
/** Unknown file type. */
|
/** Unknown file type. */
|
||||||
@ -78,6 +79,10 @@ public final class FileTypes {
|
|||||||
public static final int WEBVTT = 13;
|
public static final int WEBVTT = 13;
|
||||||
/** File type for the JPEG format. */
|
/** File type for the JPEG format. */
|
||||||
public static final int JPEG = 14;
|
public static final int JPEG = 14;
|
||||||
|
/** File type for the MIDI format. */
|
||||||
|
public static final int MIDI = 15;
|
||||||
|
/** File type for the AVI format. */
|
||||||
|
public static final int AVI = 16;
|
||||||
|
|
||||||
@VisibleForTesting /* package */ static final String HEADER_CONTENT_TYPE = "Content-Type";
|
@VisibleForTesting /* package */ static final String HEADER_CONTENT_TYPE = "Content-Type";
|
||||||
|
|
||||||
@ -89,6 +94,9 @@ public final class FileTypes {
|
|||||||
private static final String EXTENSION_AMR = ".amr";
|
private static final String EXTENSION_AMR = ".amr";
|
||||||
private static final String EXTENSION_FLAC = ".flac";
|
private static final String EXTENSION_FLAC = ".flac";
|
||||||
private static final String EXTENSION_FLV = ".flv";
|
private static final String EXTENSION_FLV = ".flv";
|
||||||
|
private static final String EXTENSION_MID = ".mid";
|
||||||
|
private static final String EXTENSION_MIDI = ".midi";
|
||||||
|
private static final String EXTENSION_SMF = ".smf";
|
||||||
private static final String EXTENSION_PREFIX_MK = ".mk";
|
private static final String EXTENSION_PREFIX_MK = ".mk";
|
||||||
private static final String EXTENSION_WEBM = ".webm";
|
private static final String EXTENSION_WEBM = ".webm";
|
||||||
private static final String EXTENSION_PREFIX_OG = ".og";
|
private static final String EXTENSION_PREFIX_OG = ".og";
|
||||||
@ -110,6 +118,7 @@ public final class FileTypes {
|
|||||||
private static final String EXTENSION_WEBVTT = ".webvtt";
|
private static final String EXTENSION_WEBVTT = ".webvtt";
|
||||||
private static final String EXTENSION_JPG = ".jpg";
|
private static final String EXTENSION_JPG = ".jpg";
|
||||||
private static final String EXTENSION_JPEG = ".jpeg";
|
private static final String EXTENSION_JPEG = ".jpeg";
|
||||||
|
private static final String EXTENSION_AVI = ".avi";
|
||||||
|
|
||||||
private FileTypes() {}
|
private FileTypes() {}
|
||||||
|
|
||||||
@ -147,6 +156,8 @@ public final class FileTypes {
|
|||||||
return FileTypes.FLAC;
|
return FileTypes.FLAC;
|
||||||
case MimeTypes.VIDEO_FLV:
|
case MimeTypes.VIDEO_FLV:
|
||||||
return FileTypes.FLV;
|
return FileTypes.FLV;
|
||||||
|
case MimeTypes.AUDIO_MIDI:
|
||||||
|
return FileTypes.MIDI;
|
||||||
case MimeTypes.VIDEO_MATROSKA:
|
case MimeTypes.VIDEO_MATROSKA:
|
||||||
case MimeTypes.AUDIO_MATROSKA:
|
case MimeTypes.AUDIO_MATROSKA:
|
||||||
case MimeTypes.VIDEO_WEBM:
|
case MimeTypes.VIDEO_WEBM:
|
||||||
@ -171,6 +182,8 @@ public final class FileTypes {
|
|||||||
return FileTypes.WEBVTT;
|
return FileTypes.WEBVTT;
|
||||||
case MimeTypes.IMAGE_JPEG:
|
case MimeTypes.IMAGE_JPEG:
|
||||||
return FileTypes.JPEG;
|
return FileTypes.JPEG;
|
||||||
|
case MimeTypes.VIDEO_AVI:
|
||||||
|
return FileTypes.AVI;
|
||||||
default:
|
default:
|
||||||
return FileTypes.UNKNOWN;
|
return FileTypes.UNKNOWN;
|
||||||
}
|
}
|
||||||
@ -193,6 +206,10 @@ public final class FileTypes {
|
|||||||
return FileTypes.FLAC;
|
return FileTypes.FLAC;
|
||||||
} else if (filename.endsWith(EXTENSION_FLV)) {
|
} else if (filename.endsWith(EXTENSION_FLV)) {
|
||||||
return FileTypes.FLV;
|
return FileTypes.FLV;
|
||||||
|
} else if (filename.endsWith(EXTENSION_MID)
|
||||||
|
|| filename.endsWith(EXTENSION_MIDI)
|
||||||
|
|| filename.endsWith(EXTENSION_SMF)) {
|
||||||
|
return FileTypes.MIDI;
|
||||||
} else if (filename.startsWith(
|
} else if (filename.startsWith(
|
||||||
EXTENSION_PREFIX_MK,
|
EXTENSION_PREFIX_MK,
|
||||||
/* toffset= */ filename.length() - (EXTENSION_PREFIX_MK.length() + 1))
|
/* toffset= */ filename.length() - (EXTENSION_PREFIX_MK.length() + 1))
|
||||||
@ -232,6 +249,8 @@ public final class FileTypes {
|
|||||||
return FileTypes.WEBVTT;
|
return FileTypes.WEBVTT;
|
||||||
} else if (filename.endsWith(EXTENSION_JPG) || filename.endsWith(EXTENSION_JPEG)) {
|
} else if (filename.endsWith(EXTENSION_JPG) || filename.endsWith(EXTENSION_JPEG)) {
|
||||||
return FileTypes.JPEG;
|
return FileTypes.JPEG;
|
||||||
|
} else if (filename.endsWith(EXTENSION_AVI)) {
|
||||||
|
return FileTypes.AVI;
|
||||||
} else {
|
} else {
|
||||||
return FileTypes.UNKNOWN;
|
return FileTypes.UNKNOWN;
|
||||||
}
|
}
|
||||||
|
@ -769,7 +769,9 @@ public final class Format implements Bundleable {
|
|||||||
|
|
||||||
// Video.
|
// Video.
|
||||||
|
|
||||||
/** @deprecated Use {@link Format.Builder}. */
|
/**
|
||||||
|
* @deprecated Use {@link Format.Builder}.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public static Format createVideoSampleFormat(
|
public static Format createVideoSampleFormat(
|
||||||
@ -798,7 +800,9 @@ public final class Format implements Bundleable {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Use {@link Format.Builder}. */
|
/**
|
||||||
|
* @deprecated Use {@link Format.Builder}.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public static Format createVideoSampleFormat(
|
public static Format createVideoSampleFormat(
|
||||||
@ -833,7 +837,9 @@ public final class Format implements Bundleable {
|
|||||||
|
|
||||||
// Audio.
|
// Audio.
|
||||||
|
|
||||||
/** @deprecated Use {@link Format.Builder}. */
|
/**
|
||||||
|
* @deprecated Use {@link Format.Builder}.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public static Format createAudioSampleFormat(
|
public static Format createAudioSampleFormat(
|
||||||
@ -864,7 +870,9 @@ public final class Format implements Bundleable {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Use {@link Format.Builder}. */
|
/**
|
||||||
|
* @deprecated Use {@link Format.Builder}.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public static Format createAudioSampleFormat(
|
public static Format createAudioSampleFormat(
|
||||||
@ -899,7 +907,9 @@ public final class Format implements Bundleable {
|
|||||||
|
|
||||||
// Generic.
|
// Generic.
|
||||||
|
|
||||||
/** @deprecated Use {@link Format.Builder}. */
|
/**
|
||||||
|
* @deprecated Use {@link Format.Builder}.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public static Format createContainerFormat(
|
public static Format createContainerFormat(
|
||||||
@ -926,7 +936,9 @@ public final class Format implements Bundleable {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Use {@link Format.Builder}. */
|
/**
|
||||||
|
* @deprecated Use {@link Format.Builder}.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public static Format createSampleFormat(@Nullable String id, @Nullable String sampleMimeType) {
|
public static Format createSampleFormat(@Nullable String id, @Nullable String sampleMimeType) {
|
||||||
@ -986,28 +998,36 @@ public final class Format implements Bundleable {
|
|||||||
return new Builder(this);
|
return new Builder(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Use {@link #buildUpon()} and {@link Builder#setMaxInputSize(int)}. */
|
/**
|
||||||
|
* @deprecated Use {@link #buildUpon()} and {@link Builder#setMaxInputSize(int)}.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public Format copyWithMaxInputSize(int maxInputSize) {
|
public Format copyWithMaxInputSize(int maxInputSize) {
|
||||||
return buildUpon().setMaxInputSize(maxInputSize).build();
|
return buildUpon().setMaxInputSize(maxInputSize).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Use {@link #buildUpon()} and {@link Builder#setSubsampleOffsetUs(long)}. */
|
/**
|
||||||
|
* @deprecated Use {@link #buildUpon()} and {@link Builder#setSubsampleOffsetUs(long)}.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public Format copyWithSubsampleOffsetUs(long subsampleOffsetUs) {
|
public Format copyWithSubsampleOffsetUs(long subsampleOffsetUs) {
|
||||||
return buildUpon().setSubsampleOffsetUs(subsampleOffsetUs).build();
|
return buildUpon().setSubsampleOffsetUs(subsampleOffsetUs).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Use {@link #buildUpon()} and {@link Builder#setLabel(String)} . */
|
/**
|
||||||
|
* @deprecated Use {@link #buildUpon()} and {@link Builder#setLabel(String)} .
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public Format copyWithLabel(@Nullable String label) {
|
public Format copyWithLabel(@Nullable String label) {
|
||||||
return buildUpon().setLabel(label).build();
|
return buildUpon().setLabel(label).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Use {@link #withManifestFormatInfo(Format)}. */
|
/**
|
||||||
|
* @deprecated Use {@link #withManifestFormatInfo(Format)}.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public Format copyWithManifestFormatInfo(Format manifestFormat) {
|
public Format copyWithManifestFormatInfo(Format manifestFormat) {
|
||||||
@ -1092,21 +1112,27 @@ public final class Format implements Bundleable {
|
|||||||
return buildUpon().setEncoderDelay(encoderDelay).setEncoderPadding(encoderPadding).build();
|
return buildUpon().setEncoderDelay(encoderDelay).setEncoderPadding(encoderPadding).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Use {@link #buildUpon()} and {@link Builder#setFrameRate(float)}. */
|
/**
|
||||||
|
* @deprecated Use {@link #buildUpon()} and {@link Builder#setFrameRate(float)}.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public Format copyWithFrameRate(float frameRate) {
|
public Format copyWithFrameRate(float frameRate) {
|
||||||
return buildUpon().setFrameRate(frameRate).build();
|
return buildUpon().setFrameRate(frameRate).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Use {@link #buildUpon()} and {@link Builder#setDrmInitData(DrmInitData)}. */
|
/**
|
||||||
|
* @deprecated Use {@link #buildUpon()} and {@link Builder#setDrmInitData(DrmInitData)}.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public Format copyWithDrmInitData(@Nullable DrmInitData drmInitData) {
|
public Format copyWithDrmInitData(@Nullable DrmInitData drmInitData) {
|
||||||
return buildUpon().setDrmInitData(drmInitData).build();
|
return buildUpon().setDrmInitData(drmInitData).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Use {@link #buildUpon()} and {@link Builder#setMetadata(Metadata)}. */
|
/**
|
||||||
|
* @deprecated Use {@link #buildUpon()} and {@link Builder#setMetadata(Metadata)}.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public Format copyWithMetadata(@Nullable Metadata metadata) {
|
public Format copyWithMetadata(@Nullable Metadata metadata) {
|
||||||
@ -1522,7 +1548,9 @@ public final class Format implements Bundleable {
|
|||||||
bundle.putFloat(keyForField(FIELD_PIXEL_WIDTH_HEIGHT_RATIO), pixelWidthHeightRatio);
|
bundle.putFloat(keyForField(FIELD_PIXEL_WIDTH_HEIGHT_RATIO), pixelWidthHeightRatio);
|
||||||
bundle.putByteArray(keyForField(FIELD_PROJECTION_DATA), projectionData);
|
bundle.putByteArray(keyForField(FIELD_PROJECTION_DATA), projectionData);
|
||||||
bundle.putInt(keyForField(FIELD_STEREO_MODE), stereoMode);
|
bundle.putInt(keyForField(FIELD_STEREO_MODE), stereoMode);
|
||||||
bundle.putBundle(keyForField(FIELD_COLOR_INFO), BundleableUtil.toNullableBundle(colorInfo));
|
if (colorInfo != null) {
|
||||||
|
bundle.putBundle(keyForField(FIELD_COLOR_INFO), colorInfo.toBundle());
|
||||||
|
}
|
||||||
// Audio specific.
|
// Audio specific.
|
||||||
bundle.putInt(keyForField(FIELD_CHANNEL_COUNT), channelCount);
|
bundle.putInt(keyForField(FIELD_CHANNEL_COUNT), channelCount);
|
||||||
bundle.putInt(keyForField(FIELD_SAMPLE_RATE), sampleRate);
|
bundle.putInt(keyForField(FIELD_SAMPLE_RATE), sampleRate);
|
||||||
@ -1589,11 +1617,13 @@ public final class Format implements Bundleable {
|
|||||||
bundle.getFloat(
|
bundle.getFloat(
|
||||||
keyForField(FIELD_PIXEL_WIDTH_HEIGHT_RATIO), DEFAULT.pixelWidthHeightRatio))
|
keyForField(FIELD_PIXEL_WIDTH_HEIGHT_RATIO), DEFAULT.pixelWidthHeightRatio))
|
||||||
.setProjectionData(bundle.getByteArray(keyForField(FIELD_PROJECTION_DATA)))
|
.setProjectionData(bundle.getByteArray(keyForField(FIELD_PROJECTION_DATA)))
|
||||||
.setStereoMode(bundle.getInt(keyForField(FIELD_STEREO_MODE), DEFAULT.stereoMode))
|
.setStereoMode(bundle.getInt(keyForField(FIELD_STEREO_MODE), DEFAULT.stereoMode));
|
||||||
.setColorInfo(
|
Bundle colorInfoBundle = bundle.getBundle(keyForField(FIELD_COLOR_INFO));
|
||||||
BundleableUtil.fromNullableBundle(
|
if (colorInfoBundle != null) {
|
||||||
ColorInfo.CREATOR, bundle.getBundle(keyForField(FIELD_COLOR_INFO))))
|
builder.setColorInfo(ColorInfo.CREATOR.fromBundle(colorInfoBundle));
|
||||||
|
}
|
||||||
// Audio specific.
|
// Audio specific.
|
||||||
|
builder
|
||||||
.setChannelCount(bundle.getInt(keyForField(FIELD_CHANNEL_COUNT), DEFAULT.channelCount))
|
.setChannelCount(bundle.getInt(keyForField(FIELD_CHANNEL_COUNT), DEFAULT.channelCount))
|
||||||
.setSampleRate(bundle.getInt(keyForField(FIELD_SAMPLE_RATE), DEFAULT.sampleRate))
|
.setSampleRate(bundle.getInt(keyForField(FIELD_SAMPLE_RATE), DEFAULT.sampleRate))
|
||||||
.setPcmEncoding(bundle.getInt(keyForField(FIELD_PCM_ENCODING), DEFAULT.pcmEncoding))
|
.setPcmEncoding(bundle.getInt(keyForField(FIELD_PCM_ENCODING), DEFAULT.pcmEncoding))
|
||||||
|
@ -22,6 +22,7 @@ import android.view.SurfaceView;
|
|||||||
import android.view.TextureView;
|
import android.view.TextureView;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.text.Cue;
|
import androidx.media3.common.text.Cue;
|
||||||
|
import androidx.media3.common.text.CueGroup;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -298,7 +299,11 @@ public class ForwardingPlayer implements Player {
|
|||||||
player.seekForward();
|
player.seekForward();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Calls {@link Player#hasPrevious()} on the delegate and returns the result. */
|
/**
|
||||||
|
* Calls {@link Player#hasPrevious()} on the delegate and returns the result.
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link #hasPreviousMediaItem()} instead.
|
||||||
|
*/
|
||||||
@SuppressWarnings("deprecation") // Forwarding to deprecated method
|
@SuppressWarnings("deprecation") // Forwarding to deprecated method
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
@ -306,7 +311,11 @@ public class ForwardingPlayer implements Player {
|
|||||||
return player.hasPrevious();
|
return player.hasPrevious();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Calls {@link Player#hasPreviousWindow()} on the delegate and returns the result. */
|
/**
|
||||||
|
* Calls {@link Player#hasPreviousWindow()} on the delegate and returns the result.
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link #hasPreviousMediaItem()} instead.
|
||||||
|
*/
|
||||||
@SuppressWarnings("deprecation") // Forwarding to deprecated method
|
@SuppressWarnings("deprecation") // Forwarding to deprecated method
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
@ -320,7 +329,11 @@ public class ForwardingPlayer implements Player {
|
|||||||
return player.hasPreviousMediaItem();
|
return player.hasPreviousMediaItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Calls {@link Player#previous()} on the delegate. */
|
/**
|
||||||
|
* Calls {@link Player#previous()} on the delegate.
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link #seekToPreviousMediaItem()} instead.
|
||||||
|
*/
|
||||||
@SuppressWarnings("deprecation") // Forwarding to deprecated method
|
@SuppressWarnings("deprecation") // Forwarding to deprecated method
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
@ -328,7 +341,11 @@ public class ForwardingPlayer implements Player {
|
|||||||
player.previous();
|
player.previous();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Calls {@link Player#seekToPreviousWindow()} on the delegate. */
|
/**
|
||||||
|
* Calls {@link Player#seekToPreviousWindow()} on the delegate.
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link #seekToPreviousMediaItem()} instead.
|
||||||
|
*/
|
||||||
@SuppressWarnings("deprecation") // Forwarding to deprecated method
|
@SuppressWarnings("deprecation") // Forwarding to deprecated method
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
@ -354,7 +371,11 @@ public class ForwardingPlayer implements Player {
|
|||||||
return player.getMaxSeekToPreviousPosition();
|
return player.getMaxSeekToPreviousPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Calls {@link Player#hasNext()} on the delegate and returns the result. */
|
/**
|
||||||
|
* Calls {@link Player#hasNext()} on the delegate and returns the result.
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link #hasNextMediaItem()} instead.
|
||||||
|
*/
|
||||||
@SuppressWarnings("deprecation") // Forwarding to deprecated method
|
@SuppressWarnings("deprecation") // Forwarding to deprecated method
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
@ -362,7 +383,11 @@ public class ForwardingPlayer implements Player {
|
|||||||
return player.hasNext();
|
return player.hasNext();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Calls {@link Player#hasNextWindow()} on the delegate and returns the result. */
|
/**
|
||||||
|
* Calls {@link Player#hasNextWindow()} on the delegate and returns the result.
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link #hasNextMediaItem()} instead.
|
||||||
|
*/
|
||||||
@SuppressWarnings("deprecation") // Forwarding to deprecated method
|
@SuppressWarnings("deprecation") // Forwarding to deprecated method
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
@ -376,7 +401,11 @@ public class ForwardingPlayer implements Player {
|
|||||||
return player.hasNextMediaItem();
|
return player.hasNextMediaItem();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Calls {@link Player#next()} on the delegate. */
|
/**
|
||||||
|
* Calls {@link Player#next()} on the delegate.
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link #seekToNextMediaItem()} instead.
|
||||||
|
*/
|
||||||
@SuppressWarnings("deprecation") // Forwarding to deprecated method
|
@SuppressWarnings("deprecation") // Forwarding to deprecated method
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
@ -384,7 +413,11 @@ public class ForwardingPlayer implements Player {
|
|||||||
player.next();
|
player.next();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Calls {@link Player#seekToNextWindow()} on the delegate. */
|
/**
|
||||||
|
* Calls {@link Player#seekToNextWindow()} on the delegate.
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link #seekToNextMediaItem()} instead.
|
||||||
|
*/
|
||||||
@SuppressWarnings("deprecation") // Forwarding to deprecated method
|
@SuppressWarnings("deprecation") // Forwarding to deprecated method
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
@ -428,7 +461,13 @@ public class ForwardingPlayer implements Player {
|
|||||||
player.stop();
|
player.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Calls {@link Player#stop(boolean)} on the delegate. */
|
/**
|
||||||
|
* Calls {@link Player#stop(boolean)} on the delegate.
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link #stop()} and {@link #clearMediaItems()} (if {@code reset} is true) or
|
||||||
|
* just {@link #stop()} (if {@code reset} is false). Any player error will be cleared when
|
||||||
|
* {@link #prepare() re-preparing} the player.
|
||||||
|
*/
|
||||||
@SuppressWarnings("deprecation") // Forwarding to deprecated method
|
@SuppressWarnings("deprecation") // Forwarding to deprecated method
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
@ -442,26 +481,10 @@ public class ForwardingPlayer implements Player {
|
|||||||
player.release();
|
player.release();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Calls {@link Player#getCurrentTrackGroups()} on the delegate and returns the result. */
|
/** Calls {@link Player#getCurrentTracks()} on the delegate and returns the result. */
|
||||||
@SuppressWarnings("deprecation") // Forwarding to deprecated method
|
|
||||||
@Deprecated
|
|
||||||
@Override
|
@Override
|
||||||
public TrackGroupArray getCurrentTrackGroups() {
|
public Tracks getCurrentTracks() {
|
||||||
return player.getCurrentTrackGroups();
|
return player.getCurrentTracks();
|
||||||
}
|
|
||||||
|
|
||||||
/** Calls {@link Player#getCurrentTrackSelections()} on the delegate and returns the result. */
|
|
||||||
@SuppressWarnings("deprecation") // Forwarding to deprecated method
|
|
||||||
@Deprecated
|
|
||||||
@Override
|
|
||||||
public TrackSelectionArray getCurrentTrackSelections() {
|
|
||||||
return player.getCurrentTrackSelections();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Calls {@link Player#getCurrentTracksInfo()} on the delegate and returns the result. */
|
|
||||||
@Override
|
|
||||||
public TracksInfo getCurrentTracksInfo() {
|
|
||||||
return player.getCurrentTracksInfo();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Calls {@link Player#getTrackSelectionParameters()} on the delegate and returns the result. */
|
/** Calls {@link Player#getTrackSelectionParameters()} on the delegate and returns the result. */
|
||||||
@ -513,7 +536,11 @@ public class ForwardingPlayer implements Player {
|
|||||||
return player.getCurrentPeriodIndex();
|
return player.getCurrentPeriodIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Calls {@link Player#getCurrentWindowIndex()} on the delegate and returns the result. */
|
/**
|
||||||
|
* Calls {@link Player#getCurrentWindowIndex()} on the delegate and returns the result.
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link #getCurrentMediaItemIndex()} instead.
|
||||||
|
*/
|
||||||
@SuppressWarnings("deprecation") // Forwarding to deprecated method
|
@SuppressWarnings("deprecation") // Forwarding to deprecated method
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
@ -527,7 +554,11 @@ public class ForwardingPlayer implements Player {
|
|||||||
return player.getCurrentMediaItemIndex();
|
return player.getCurrentMediaItemIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Calls {@link Player#getNextWindowIndex()} on the delegate and returns the result. */
|
/**
|
||||||
|
* Calls {@link Player#getNextWindowIndex()} on the delegate and returns the result.
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link #getNextMediaItemIndex()} instead.
|
||||||
|
*/
|
||||||
@SuppressWarnings("deprecation") // Forwarding to deprecated method
|
@SuppressWarnings("deprecation") // Forwarding to deprecated method
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
@ -541,7 +572,11 @@ public class ForwardingPlayer implements Player {
|
|||||||
return player.getNextMediaItemIndex();
|
return player.getNextMediaItemIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Calls {@link Player#getPreviousWindowIndex()} on the delegate and returns the result. */
|
/**
|
||||||
|
* Calls {@link Player#getPreviousWindowIndex()} on the delegate and returns the result.
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link #getPreviousMediaItemIndex()} instead.
|
||||||
|
*/
|
||||||
@SuppressWarnings("deprecation") // Forwarding to deprecated method
|
@SuppressWarnings("deprecation") // Forwarding to deprecated method
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
@ -604,7 +639,11 @@ public class ForwardingPlayer implements Player {
|
|||||||
return player.getTotalBufferedDuration();
|
return player.getTotalBufferedDuration();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Calls {@link Player#isCurrentWindowDynamic()} on the delegate and returns the result. */
|
/**
|
||||||
|
* Calls {@link Player#isCurrentWindowDynamic()} on the delegate and returns the result.
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link #isCurrentMediaItemDynamic()} instead.
|
||||||
|
*/
|
||||||
@SuppressWarnings("deprecation") // Forwarding to deprecated method
|
@SuppressWarnings("deprecation") // Forwarding to deprecated method
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
@ -618,7 +657,11 @@ public class ForwardingPlayer implements Player {
|
|||||||
return player.isCurrentMediaItemDynamic();
|
return player.isCurrentMediaItemDynamic();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Calls {@link Player#isCurrentWindowLive()} on the delegate and returns the result. */
|
/**
|
||||||
|
* Calls {@link Player#isCurrentWindowLive()} on the delegate and returns the result.
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link #isCurrentMediaItemLive()} instead.
|
||||||
|
*/
|
||||||
@SuppressWarnings("deprecation") // Forwarding to deprecated method
|
@SuppressWarnings("deprecation") // Forwarding to deprecated method
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
@ -638,7 +681,11 @@ public class ForwardingPlayer implements Player {
|
|||||||
return player.getCurrentLiveOffset();
|
return player.getCurrentLiveOffset();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Calls {@link Player#isCurrentWindowSeekable()} on the delegate and returns the result. */
|
/**
|
||||||
|
* Calls {@link Player#isCurrentWindowSeekable()} on the delegate and returns the result.
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link #isCurrentMediaItemSeekable()} instead.
|
||||||
|
*/
|
||||||
@SuppressWarnings("deprecation") // Forwarding to deprecated method
|
@SuppressWarnings("deprecation") // Forwarding to deprecated method
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
@ -768,7 +815,7 @@ public class ForwardingPlayer implements Player {
|
|||||||
|
|
||||||
/** Calls {@link Player#getCurrentCues()} on the delegate and returns the result. */
|
/** Calls {@link Player#getCurrentCues()} on the delegate and returns the result. */
|
||||||
@Override
|
@Override
|
||||||
public List<Cue> getCurrentCues() {
|
public CueGroup getCurrentCues() {
|
||||||
return player.getCurrentCues();
|
return player.getCurrentCues();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -847,14 +894,8 @@ public class ForwardingPlayer implements Player {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@SuppressWarnings("deprecation")
|
public void onTracksChanged(Tracks tracks) {
|
||||||
public void onTracksChanged(TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {
|
listener.onTracksChanged(tracks);
|
||||||
listener.onTracksChanged(trackGroups, trackSelections);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onTracksInfoChanged(TracksInfo tracksInfo) {
|
|
||||||
listener.onTracksInfoChanged(tracksInfo);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -1014,6 +1055,11 @@ public class ForwardingPlayer implements Player {
|
|||||||
listener.onCues(cues);
|
listener.onCues(cues);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onCues(CueGroup cueGroup) {
|
||||||
|
listener.onCues(cueGroup);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onMetadata(Metadata metadata) {
|
public void onMetadata(Metadata metadata) {
|
||||||
listener.onMetadata(metadata);
|
listener.onMetadata(metadata);
|
||||||
|
@ -29,6 +29,7 @@ import androidx.media3.common.util.UnstableApi;
|
|||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.errorprone.annotations.InlineMe;
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.Documented;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
@ -84,6 +85,7 @@ public final class MediaItem implements Bundleable {
|
|||||||
// TODO: Change this to LiveConfiguration once all the deprecated individual setters
|
// TODO: Change this to LiveConfiguration once all the deprecated individual setters
|
||||||
// are removed.
|
// are removed.
|
||||||
private LiveConfiguration.Builder liveConfiguration;
|
private LiveConfiguration.Builder liveConfiguration;
|
||||||
|
private RequestMetadata requestMetadata;
|
||||||
|
|
||||||
/** Creates a builder. */
|
/** Creates a builder. */
|
||||||
@SuppressWarnings("deprecation") // Temporarily uses DrmConfiguration.Builder() constructor.
|
@SuppressWarnings("deprecation") // Temporarily uses DrmConfiguration.Builder() constructor.
|
||||||
@ -93,6 +95,7 @@ public final class MediaItem implements Bundleable {
|
|||||||
streamKeys = Collections.emptyList();
|
streamKeys = Collections.emptyList();
|
||||||
subtitleConfigurations = ImmutableList.of();
|
subtitleConfigurations = ImmutableList.of();
|
||||||
liveConfiguration = new LiveConfiguration.Builder();
|
liveConfiguration = new LiveConfiguration.Builder();
|
||||||
|
requestMetadata = RequestMetadata.EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
private Builder(MediaItem mediaItem) {
|
private Builder(MediaItem mediaItem) {
|
||||||
@ -101,6 +104,7 @@ public final class MediaItem implements Bundleable {
|
|||||||
mediaId = mediaItem.mediaId;
|
mediaId = mediaItem.mediaId;
|
||||||
mediaMetadata = mediaItem.mediaMetadata;
|
mediaMetadata = mediaItem.mediaMetadata;
|
||||||
liveConfiguration = mediaItem.liveConfiguration.buildUpon();
|
liveConfiguration = mediaItem.liveConfiguration.buildUpon();
|
||||||
|
requestMetadata = mediaItem.requestMetadata;
|
||||||
@Nullable LocalConfiguration localConfiguration = mediaItem.localConfiguration;
|
@Nullable LocalConfiguration localConfiguration = mediaItem.localConfiguration;
|
||||||
if (localConfiguration != null) {
|
if (localConfiguration != null) {
|
||||||
customCacheKey = localConfiguration.customCacheKey;
|
customCacheKey = localConfiguration.customCacheKey;
|
||||||
@ -315,12 +319,12 @@ public final class MediaItem implements Bundleable {
|
|||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Use {@link #setDrmConfiguration(DrmConfiguration)} and {@link
|
* @deprecated Use {@link #setDrmConfiguration(DrmConfiguration)} and {@link
|
||||||
* DrmConfiguration.Builder#forceSessionsForAudioAndVideoTracks(boolean)} instead.
|
* DrmConfiguration.Builder#setForceSessionsForAudioAndVideoTracks(boolean)} instead.
|
||||||
*/
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public Builder setDrmSessionForClearPeriods(boolean sessionForClearPeriods) {
|
public Builder setDrmSessionForClearPeriods(boolean sessionForClearPeriods) {
|
||||||
drmConfiguration.forceSessionsForAudioAndVideoTracks(sessionForClearPeriods);
|
drmConfiguration.setForceSessionsForAudioAndVideoTracks(sessionForClearPeriods);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -525,6 +529,12 @@ public final class MediaItem implements Bundleable {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Sets the request metadata. */
|
||||||
|
public Builder setRequestMetadata(RequestMetadata requestMetadata) {
|
||||||
|
this.requestMetadata = requestMetadata;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns a new {@link MediaItem} instance with the current builder values. */
|
/** Returns a new {@link MediaItem} instance with the current builder values. */
|
||||||
@SuppressWarnings("deprecation") // Using PlaybackProperties while it exists.
|
@SuppressWarnings("deprecation") // Using PlaybackProperties while it exists.
|
||||||
public MediaItem build() {
|
public MediaItem build() {
|
||||||
@ -549,7 +559,8 @@ public final class MediaItem implements Bundleable {
|
|||||||
clippingConfiguration.buildClippingProperties(),
|
clippingConfiguration.buildClippingProperties(),
|
||||||
localConfiguration,
|
localConfiguration,
|
||||||
liveConfiguration.build(),
|
liveConfiguration.build(),
|
||||||
mediaMetadata != null ? mediaMetadata : MediaMetadata.EMPTY);
|
mediaMetadata != null ? mediaMetadata : MediaMetadata.EMPTY,
|
||||||
|
requestMetadata);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -659,6 +670,19 @@ public final class MediaItem implements Bundleable {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #setForceSessionsForAudioAndVideoTracks(boolean)} instead.
|
||||||
|
*/
|
||||||
|
@UnstableApi
|
||||||
|
@Deprecated
|
||||||
|
@InlineMe(
|
||||||
|
replacement =
|
||||||
|
"this.setForceSessionsForAudioAndVideoTracks(forceSessionsForAudioAndVideoTracks)")
|
||||||
|
public Builder forceSessionsForAudioAndVideoTracks(
|
||||||
|
boolean forceSessionsForAudioAndVideoTracks) {
|
||||||
|
return setForceSessionsForAudioAndVideoTracks(forceSessionsForAudioAndVideoTracks);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets whether a DRM session should be used for clear tracks of type {@link
|
* Sets whether a DRM session should be used for clear tracks of type {@link
|
||||||
* C#TRACK_TYPE_VIDEO} and {@link C#TRACK_TYPE_AUDIO}.
|
* C#TRACK_TYPE_VIDEO} and {@link C#TRACK_TYPE_AUDIO}.
|
||||||
@ -666,10 +690,10 @@ public final class MediaItem implements Bundleable {
|
|||||||
* <p>This method overrides what has been set by previously calling {@link
|
* <p>This method overrides what has been set by previously calling {@link
|
||||||
* #setForcedSessionTrackTypes(List)}.
|
* #setForcedSessionTrackTypes(List)}.
|
||||||
*/
|
*/
|
||||||
public Builder forceSessionsForAudioAndVideoTracks(
|
public Builder setForceSessionsForAudioAndVideoTracks(
|
||||||
boolean useClearSessionsForAudioAndVideoTracks) {
|
boolean forceSessionsForAudioAndVideoTracks) {
|
||||||
this.setForcedSessionTrackTypes(
|
this.setForcedSessionTrackTypes(
|
||||||
useClearSessionsForAudioAndVideoTracks
|
forceSessionsForAudioAndVideoTracks
|
||||||
? ImmutableList.of(C.TRACK_TYPE_VIDEO, C.TRACK_TYPE_AUDIO)
|
? ImmutableList.of(C.TRACK_TYPE_VIDEO, C.TRACK_TYPE_AUDIO)
|
||||||
: ImmutableList.of());
|
: ImmutableList.of());
|
||||||
return this;
|
return this;
|
||||||
@ -680,10 +704,10 @@ public final class MediaItem implements Bundleable {
|
|||||||
* when the tracks are in the clear.
|
* when the tracks are in the clear.
|
||||||
*
|
*
|
||||||
* <p>For the common case of using a DRM session for {@link C#TRACK_TYPE_VIDEO} and {@link
|
* <p>For the common case of using a DRM session for {@link C#TRACK_TYPE_VIDEO} and {@link
|
||||||
* C#TRACK_TYPE_AUDIO}, {@link #forceSessionsForAudioAndVideoTracks(boolean)} can be used.
|
* C#TRACK_TYPE_AUDIO}, {@link #setForceSessionsForAudioAndVideoTracks(boolean)} can be used.
|
||||||
*
|
*
|
||||||
* <p>This method overrides what has been set by previously calling {@link
|
* <p>This method overrides what has been set by previously calling {@link
|
||||||
* #forceSessionsForAudioAndVideoTracks(boolean)}.
|
* #setForceSessionsForAudioAndVideoTracks(boolean)}.
|
||||||
*/
|
*/
|
||||||
public Builder setForcedSessionTrackTypes(
|
public Builder setForcedSessionTrackTypes(
|
||||||
List<@C.TrackType Integer> forcedSessionTrackTypes) {
|
List<@C.TrackType Integer> forcedSessionTrackTypes) {
|
||||||
@ -712,7 +736,9 @@ public final class MediaItem implements Bundleable {
|
|||||||
/** The UUID of the protection scheme. */
|
/** The UUID of the protection scheme. */
|
||||||
public final UUID scheme;
|
public final UUID scheme;
|
||||||
|
|
||||||
/** @deprecated Use {@link #scheme} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link #scheme} instead.
|
||||||
|
*/
|
||||||
@UnstableApi @Deprecated public final UUID uuid;
|
@UnstableApi @Deprecated public final UUID uuid;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -721,7 +747,9 @@ public final class MediaItem implements Bundleable {
|
|||||||
*/
|
*/
|
||||||
@Nullable public final Uri licenseUri;
|
@Nullable public final Uri licenseUri;
|
||||||
|
|
||||||
/** @deprecated Use {@link #licenseRequestHeaders} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link #licenseRequestHeaders} instead.
|
||||||
|
*/
|
||||||
@UnstableApi @Deprecated public final ImmutableMap<String, String> requestHeaders;
|
@UnstableApi @Deprecated public final ImmutableMap<String, String> requestHeaders;
|
||||||
|
|
||||||
/** The headers to attach to requests sent to the DRM license server. */
|
/** The headers to attach to requests sent to the DRM license server. */
|
||||||
@ -742,7 +770,9 @@ public final class MediaItem implements Bundleable {
|
|||||||
*/
|
*/
|
||||||
public final boolean forceDefaultLicenseUri;
|
public final boolean forceDefaultLicenseUri;
|
||||||
|
|
||||||
/** @deprecated Use {@link #forcedSessionTrackTypes}. */
|
/**
|
||||||
|
* @deprecated Use {@link #forcedSessionTrackTypes}.
|
||||||
|
*/
|
||||||
@UnstableApi @Deprecated public final ImmutableList<@C.TrackType Integer> sessionForClearTypes;
|
@UnstableApi @Deprecated public final ImmutableList<@C.TrackType Integer> sessionForClearTypes;
|
||||||
/**
|
/**
|
||||||
* The types of tracks for which to always use a DRM session even if the content is unencrypted.
|
* The types of tracks for which to always use a DRM session even if the content is unencrypted.
|
||||||
@ -929,7 +959,9 @@ public final class MediaItem implements Bundleable {
|
|||||||
|
|
||||||
/** Optional subtitles to be sideloaded. */
|
/** Optional subtitles to be sideloaded. */
|
||||||
public final ImmutableList<SubtitleConfiguration> subtitleConfigurations;
|
public final ImmutableList<SubtitleConfiguration> subtitleConfigurations;
|
||||||
/** @deprecated Use {@link #subtitleConfigurations} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link #subtitleConfigurations} instead.
|
||||||
|
*/
|
||||||
@UnstableApi @Deprecated public final List<Subtitle> subtitles;
|
@UnstableApi @Deprecated public final List<Subtitle> subtitles;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -998,7 +1030,9 @@ public final class MediaItem implements Bundleable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Use {@link LocalConfiguration}. */
|
/**
|
||||||
|
* @deprecated Use {@link LocalConfiguration}.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public static final class PlaybackProperties extends LocalConfiguration {
|
public static final class PlaybackProperties extends LocalConfiguration {
|
||||||
@ -1160,7 +1194,9 @@ public final class MediaItem implements Bundleable {
|
|||||||
builder.maxPlaybackSpeed);
|
builder.maxPlaybackSpeed);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Use {@link Builder} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link Builder} instead.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public LiveConfiguration(
|
public LiveConfiguration(
|
||||||
@ -1299,7 +1335,7 @@ public final class MediaItem implements Bundleable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Sets the MIME type. */
|
/** Sets the MIME type. */
|
||||||
public Builder setMimeType(String mimeType) {
|
public Builder setMimeType(@Nullable String mimeType) {
|
||||||
this.mimeType = mimeType;
|
this.mimeType = mimeType;
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
@ -1427,19 +1463,25 @@ public final class MediaItem implements Bundleable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Use {@link MediaItem.SubtitleConfiguration} instead */
|
/**
|
||||||
|
* @deprecated Use {@link MediaItem.SubtitleConfiguration} instead
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public static final class Subtitle extends SubtitleConfiguration {
|
public static final class Subtitle extends SubtitleConfiguration {
|
||||||
|
|
||||||
/** @deprecated Use {@link Builder} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link Builder} instead.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public Subtitle(Uri uri, String mimeType, @Nullable String language) {
|
public Subtitle(Uri uri, String mimeType, @Nullable String language) {
|
||||||
this(uri, mimeType, language, /* selectionFlags= */ 0);
|
this(uri, mimeType, language, /* selectionFlags= */ 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Use {@link Builder} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link Builder} instead.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public Subtitle(
|
public Subtitle(
|
||||||
@ -1447,7 +1489,9 @@ public final class MediaItem implements Bundleable {
|
|||||||
this(uri, mimeType, language, selectionFlags, /* roleFlags= */ 0, /* label= */ null);
|
this(uri, mimeType, language, selectionFlags, /* roleFlags= */ 0, /* label= */ null);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Use {@link Builder} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link Builder} instead.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public Subtitle(
|
public Subtitle(
|
||||||
@ -1550,7 +1594,9 @@ public final class MediaItem implements Bundleable {
|
|||||||
return buildClippingProperties();
|
return buildClippingProperties();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Use {@link #build()} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link #build()} instead.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public ClippingProperties buildClippingProperties() {
|
public ClippingProperties buildClippingProperties() {
|
||||||
@ -1680,7 +1726,9 @@ public final class MediaItem implements Bundleable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Use {@link ClippingConfiguration} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link ClippingConfiguration} instead.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public static final class ClippingProperties extends ClippingConfiguration {
|
public static final class ClippingProperties extends ClippingConfiguration {
|
||||||
@ -1692,6 +1740,146 @@ public final class MediaItem implements Bundleable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Metadata that helps the player to understand a playback request represented by a {@link
|
||||||
|
* MediaItem}.
|
||||||
|
*
|
||||||
|
* <p>This metadata is most useful for cases where playback requests are forwarded to other player
|
||||||
|
* instances (e.g. from a {@link android.media.session.MediaController}) and the player creating
|
||||||
|
* the request doesn't know the required {@link LocalConfiguration} for playback.
|
||||||
|
*/
|
||||||
|
public static final class RequestMetadata implements Bundleable {
|
||||||
|
|
||||||
|
/** Empty request metadata. */
|
||||||
|
public static final RequestMetadata EMPTY = new Builder().build();
|
||||||
|
|
||||||
|
/** Builder for {@link RequestMetadata} instances. */
|
||||||
|
public static final class Builder {
|
||||||
|
|
||||||
|
@Nullable private Uri mediaUri;
|
||||||
|
@Nullable private String searchQuery;
|
||||||
|
@Nullable private Bundle extras;
|
||||||
|
|
||||||
|
/** Constructs an instance. */
|
||||||
|
public Builder() {}
|
||||||
|
|
||||||
|
private Builder(RequestMetadata requestMetadata) {
|
||||||
|
this.mediaUri = requestMetadata.mediaUri;
|
||||||
|
this.searchQuery = requestMetadata.searchQuery;
|
||||||
|
this.extras = requestMetadata.extras;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the URI of the requested media, or null if not known or applicable. */
|
||||||
|
public Builder setMediaUri(@Nullable Uri mediaUri) {
|
||||||
|
this.mediaUri = mediaUri;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the search query for the requested media, or null if not applicable. */
|
||||||
|
public Builder setSearchQuery(@Nullable String searchQuery) {
|
||||||
|
this.searchQuery = searchQuery;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets optional extras {@link Bundle}. */
|
||||||
|
public Builder setExtras(@Nullable Bundle extras) {
|
||||||
|
this.extras = extras;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Builds the request metadata. */
|
||||||
|
public RequestMetadata build() {
|
||||||
|
return new RequestMetadata(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The URI of the requested media, or null if not known or applicable. */
|
||||||
|
@Nullable public final Uri mediaUri;
|
||||||
|
|
||||||
|
/** The search query for the requested media, or null if not applicable. */
|
||||||
|
@Nullable public final String searchQuery;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Optional extras {@link Bundle}.
|
||||||
|
*
|
||||||
|
* <p>Given the complexities of checking the equality of two {@link Bundle}s, this is not
|
||||||
|
* considered in the {@link #equals(Object)} or {@link #hashCode()}.
|
||||||
|
*/
|
||||||
|
@Nullable public final Bundle extras;
|
||||||
|
|
||||||
|
private RequestMetadata(Builder builder) {
|
||||||
|
this.mediaUri = builder.mediaUri;
|
||||||
|
this.searchQuery = builder.searchQuery;
|
||||||
|
this.extras = builder.extras;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a {@link Builder} initialized with the values of this instance. */
|
||||||
|
public Builder buildUpon() {
|
||||||
|
return new Builder(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(@Nullable Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof RequestMetadata)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
RequestMetadata that = (RequestMetadata) o;
|
||||||
|
return Util.areEqual(mediaUri, that.mediaUri) && Util.areEqual(searchQuery, that.searchQuery);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
int result = mediaUri == null ? 0 : mediaUri.hashCode();
|
||||||
|
result = 31 * result + (searchQuery == null ? 0 : searchQuery.hashCode());
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bundleable implementation.
|
||||||
|
|
||||||
|
@Documented
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@Target(TYPE_USE)
|
||||||
|
@IntDef({FIELD_MEDIA_URI, FIELD_SEARCH_QUERY, FIELD_EXTRAS})
|
||||||
|
private @interface FieldNumber {}
|
||||||
|
|
||||||
|
private static final int FIELD_MEDIA_URI = 0;
|
||||||
|
private static final int FIELD_SEARCH_QUERY = 1;
|
||||||
|
private static final int FIELD_EXTRAS = 2;
|
||||||
|
|
||||||
|
@UnstableApi
|
||||||
|
@Override
|
||||||
|
public Bundle toBundle() {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
if (mediaUri != null) {
|
||||||
|
bundle.putParcelable(keyForField(FIELD_MEDIA_URI), mediaUri);
|
||||||
|
}
|
||||||
|
if (searchQuery != null) {
|
||||||
|
bundle.putString(keyForField(FIELD_SEARCH_QUERY), searchQuery);
|
||||||
|
}
|
||||||
|
if (extras != null) {
|
||||||
|
bundle.putBundle(keyForField(FIELD_EXTRAS), extras);
|
||||||
|
}
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Object that can restore {@link RequestMetadata} from a {@link Bundle}. */
|
||||||
|
@UnstableApi
|
||||||
|
public static final Creator<RequestMetadata> CREATOR =
|
||||||
|
bundle ->
|
||||||
|
new RequestMetadata.Builder()
|
||||||
|
.setMediaUri(bundle.getParcelable(keyForField(FIELD_MEDIA_URI)))
|
||||||
|
.setSearchQuery(bundle.getString(keyForField(FIELD_SEARCH_QUERY)))
|
||||||
|
.setExtras(bundle.getBundle(keyForField(FIELD_EXTRAS)))
|
||||||
|
.build();
|
||||||
|
|
||||||
|
private static String keyForField(@RequestMetadata.FieldNumber int field) {
|
||||||
|
return Integer.toString(field, Character.MAX_RADIX);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default media ID that is used if the media ID is not explicitly set by {@link
|
* The default media ID that is used if the media ID is not explicitly set by {@link
|
||||||
* Builder#setMediaId(String)}.
|
* Builder#setMediaId(String)}.
|
||||||
@ -1709,7 +1897,9 @@ public final class MediaItem implements Bundleable {
|
|||||||
* boundaries.
|
* boundaries.
|
||||||
*/
|
*/
|
||||||
@Nullable public final LocalConfiguration localConfiguration;
|
@Nullable public final LocalConfiguration localConfiguration;
|
||||||
/** @deprecated Use {@link #localConfiguration} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link #localConfiguration} instead.
|
||||||
|
*/
|
||||||
@UnstableApi @Deprecated @Nullable public final PlaybackProperties playbackProperties;
|
@UnstableApi @Deprecated @Nullable public final PlaybackProperties playbackProperties;
|
||||||
|
|
||||||
/** The live playback configuration. */
|
/** The live playback configuration. */
|
||||||
@ -1720,9 +1910,14 @@ public final class MediaItem implements Bundleable {
|
|||||||
|
|
||||||
/** The clipping properties. */
|
/** The clipping properties. */
|
||||||
public final ClippingConfiguration clippingConfiguration;
|
public final ClippingConfiguration clippingConfiguration;
|
||||||
/** @deprecated Use {@link #clippingConfiguration} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link #clippingConfiguration} instead.
|
||||||
|
*/
|
||||||
@UnstableApi @Deprecated public final ClippingProperties clippingProperties;
|
@UnstableApi @Deprecated public final ClippingProperties clippingProperties;
|
||||||
|
|
||||||
|
/** The media {@link RequestMetadata}. */
|
||||||
|
public final RequestMetadata requestMetadata;
|
||||||
|
|
||||||
// Using PlaybackProperties and ClippingProperties until they're deleted.
|
// Using PlaybackProperties and ClippingProperties until they're deleted.
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
private MediaItem(
|
private MediaItem(
|
||||||
@ -1730,7 +1925,8 @@ public final class MediaItem implements Bundleable {
|
|||||||
ClippingProperties clippingConfiguration,
|
ClippingProperties clippingConfiguration,
|
||||||
@Nullable PlaybackProperties localConfiguration,
|
@Nullable PlaybackProperties localConfiguration,
|
||||||
LiveConfiguration liveConfiguration,
|
LiveConfiguration liveConfiguration,
|
||||||
MediaMetadata mediaMetadata) {
|
MediaMetadata mediaMetadata,
|
||||||
|
RequestMetadata requestMetadata) {
|
||||||
this.mediaId = mediaId;
|
this.mediaId = mediaId;
|
||||||
this.localConfiguration = localConfiguration;
|
this.localConfiguration = localConfiguration;
|
||||||
this.playbackProperties = localConfiguration;
|
this.playbackProperties = localConfiguration;
|
||||||
@ -1738,6 +1934,7 @@ public final class MediaItem implements Bundleable {
|
|||||||
this.mediaMetadata = mediaMetadata;
|
this.mediaMetadata = mediaMetadata;
|
||||||
this.clippingConfiguration = clippingConfiguration;
|
this.clippingConfiguration = clippingConfiguration;
|
||||||
this.clippingProperties = clippingConfiguration;
|
this.clippingProperties = clippingConfiguration;
|
||||||
|
this.requestMetadata = requestMetadata;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns a {@link Builder} initialized with the values of this instance. */
|
/** Returns a {@link Builder} initialized with the values of this instance. */
|
||||||
@ -1760,7 +1957,8 @@ public final class MediaItem implements Bundleable {
|
|||||||
&& clippingConfiguration.equals(other.clippingConfiguration)
|
&& clippingConfiguration.equals(other.clippingConfiguration)
|
||||||
&& Util.areEqual(localConfiguration, other.localConfiguration)
|
&& Util.areEqual(localConfiguration, other.localConfiguration)
|
||||||
&& Util.areEqual(liveConfiguration, other.liveConfiguration)
|
&& Util.areEqual(liveConfiguration, other.liveConfiguration)
|
||||||
&& Util.areEqual(mediaMetadata, other.mediaMetadata);
|
&& Util.areEqual(mediaMetadata, other.mediaMetadata)
|
||||||
|
&& Util.areEqual(requestMetadata, other.requestMetadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -1770,6 +1968,7 @@ public final class MediaItem implements Bundleable {
|
|||||||
result = 31 * result + liveConfiguration.hashCode();
|
result = 31 * result + liveConfiguration.hashCode();
|
||||||
result = 31 * result + clippingConfiguration.hashCode();
|
result = 31 * result + clippingConfiguration.hashCode();
|
||||||
result = 31 * result + mediaMetadata.hashCode();
|
result = 31 * result + mediaMetadata.hashCode();
|
||||||
|
result = 31 * result + requestMetadata.hashCode();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1782,7 +1981,8 @@ public final class MediaItem implements Bundleable {
|
|||||||
FIELD_MEDIA_ID,
|
FIELD_MEDIA_ID,
|
||||||
FIELD_LIVE_CONFIGURATION,
|
FIELD_LIVE_CONFIGURATION,
|
||||||
FIELD_MEDIA_METADATA,
|
FIELD_MEDIA_METADATA,
|
||||||
FIELD_CLIPPING_PROPERTIES
|
FIELD_CLIPPING_PROPERTIES,
|
||||||
|
FIELD_REQUEST_METADATA
|
||||||
})
|
})
|
||||||
private @interface FieldNumber {}
|
private @interface FieldNumber {}
|
||||||
|
|
||||||
@ -1790,6 +1990,7 @@ public final class MediaItem implements Bundleable {
|
|||||||
private static final int FIELD_LIVE_CONFIGURATION = 1;
|
private static final int FIELD_LIVE_CONFIGURATION = 1;
|
||||||
private static final int FIELD_MEDIA_METADATA = 2;
|
private static final int FIELD_MEDIA_METADATA = 2;
|
||||||
private static final int FIELD_CLIPPING_PROPERTIES = 3;
|
private static final int FIELD_CLIPPING_PROPERTIES = 3;
|
||||||
|
private static final int FIELD_REQUEST_METADATA = 4;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* {@inheritDoc}
|
* {@inheritDoc}
|
||||||
@ -1805,6 +2006,7 @@ public final class MediaItem implements Bundleable {
|
|||||||
bundle.putBundle(keyForField(FIELD_LIVE_CONFIGURATION), liveConfiguration.toBundle());
|
bundle.putBundle(keyForField(FIELD_LIVE_CONFIGURATION), liveConfiguration.toBundle());
|
||||||
bundle.putBundle(keyForField(FIELD_MEDIA_METADATA), mediaMetadata.toBundle());
|
bundle.putBundle(keyForField(FIELD_MEDIA_METADATA), mediaMetadata.toBundle());
|
||||||
bundle.putBundle(keyForField(FIELD_CLIPPING_PROPERTIES), clippingConfiguration.toBundle());
|
bundle.putBundle(keyForField(FIELD_CLIPPING_PROPERTIES), clippingConfiguration.toBundle());
|
||||||
|
bundle.putBundle(keyForField(FIELD_REQUEST_METADATA), requestMetadata.toBundle());
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1841,12 +2043,20 @@ public final class MediaItem implements Bundleable {
|
|||||||
} else {
|
} else {
|
||||||
clippingConfiguration = ClippingConfiguration.CREATOR.fromBundle(clippingConfigurationBundle);
|
clippingConfiguration = ClippingConfiguration.CREATOR.fromBundle(clippingConfigurationBundle);
|
||||||
}
|
}
|
||||||
|
@Nullable Bundle requestMetadataBundle = bundle.getBundle(keyForField(FIELD_REQUEST_METADATA));
|
||||||
|
RequestMetadata requestMetadata;
|
||||||
|
if (requestMetadataBundle == null) {
|
||||||
|
requestMetadata = RequestMetadata.EMPTY;
|
||||||
|
} else {
|
||||||
|
requestMetadata = RequestMetadata.CREATOR.fromBundle(requestMetadataBundle);
|
||||||
|
}
|
||||||
return new MediaItem(
|
return new MediaItem(
|
||||||
mediaId,
|
mediaId,
|
||||||
clippingConfiguration,
|
clippingConfiguration,
|
||||||
/* localConfiguration= */ null,
|
/* localConfiguration= */ null,
|
||||||
liveConfiguration,
|
liveConfiguration,
|
||||||
mediaMetadata);
|
mediaMetadata,
|
||||||
|
requestMetadata);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static String keyForField(@FieldNumber int field) {
|
private static String keyForField(@FieldNumber int field) {
|
||||||
|
@ -29,11 +29,11 @@ public final class MediaLibraryInfo {
|
|||||||
|
|
||||||
/** The version of the library expressed as a string, for example "1.2.3" or "1.2.3-beta01". */
|
/** The version of the library expressed as a string, for example "1.2.3" or "1.2.3-beta01". */
|
||||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
|
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
|
||||||
public static final String VERSION = "1.0.0-alpha03";
|
public static final String VERSION = "1.0.0-beta01";
|
||||||
|
|
||||||
/** The version of the library expressed as {@code TAG + "/" + VERSION}. */
|
/** The version of the library expressed as {@code TAG + "/" + VERSION}. */
|
||||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
||||||
public static final String VERSION_SLASHY = "AndroidXMedia3/1.0.0-alpha03";
|
public static final String VERSION_SLASHY = "AndroidXMedia3/1.0.0-beta01";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The version of the library expressed as an integer, for example 1002003300.
|
* The version of the library expressed as an integer, for example 1002003300.
|
||||||
@ -47,7 +47,7 @@ public final class MediaLibraryInfo {
|
|||||||
* (123-045-006-3-00).
|
* (123-045-006-3-00).
|
||||||
*/
|
*/
|
||||||
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
|
||||||
public static final int VERSION_INT = 1_000_000_0_03;
|
public static final int VERSION_INT = 1_000_000_1_01;
|
||||||
|
|
||||||
/** Whether the library was compiled with {@link Assertions} checks enabled. */
|
/** Whether the library was compiled with {@link Assertions} checks enabled. */
|
||||||
public static final boolean ASSERTIONS_ENABLED = true;
|
public static final boolean ASSERTIONS_ENABLED = true;
|
||||||
|
@ -52,7 +52,6 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
@Nullable private CharSequence displayTitle;
|
@Nullable private CharSequence displayTitle;
|
||||||
@Nullable private CharSequence subtitle;
|
@Nullable private CharSequence subtitle;
|
||||||
@Nullable private CharSequence description;
|
@Nullable private CharSequence description;
|
||||||
@Nullable private Uri mediaUri;
|
|
||||||
@Nullable private Rating userRating;
|
@Nullable private Rating userRating;
|
||||||
@Nullable private Rating overallRating;
|
@Nullable private Rating overallRating;
|
||||||
@Nullable private byte[] artworkData;
|
@Nullable private byte[] artworkData;
|
||||||
@ -88,7 +87,6 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
this.displayTitle = mediaMetadata.displayTitle;
|
this.displayTitle = mediaMetadata.displayTitle;
|
||||||
this.subtitle = mediaMetadata.subtitle;
|
this.subtitle = mediaMetadata.subtitle;
|
||||||
this.description = mediaMetadata.description;
|
this.description = mediaMetadata.description;
|
||||||
this.mediaUri = mediaMetadata.mediaUri;
|
|
||||||
this.userRating = mediaMetadata.userRating;
|
this.userRating = mediaMetadata.userRating;
|
||||||
this.overallRating = mediaMetadata.overallRating;
|
this.overallRating = mediaMetadata.overallRating;
|
||||||
this.artworkData = mediaMetadata.artworkData;
|
this.artworkData = mediaMetadata.artworkData;
|
||||||
@ -161,12 +159,6 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Sets the media {@link Uri}. */
|
|
||||||
public Builder setMediaUri(@Nullable Uri mediaUri) {
|
|
||||||
this.mediaUri = mediaUri;
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Sets the user {@link Rating}. */
|
/** Sets the user {@link Rating}. */
|
||||||
public Builder setUserRating(@Nullable Rating userRating) {
|
public Builder setUserRating(@Nullable Rating userRating) {
|
||||||
this.userRating = userRating;
|
this.userRating = userRating;
|
||||||
@ -248,7 +240,9 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Use {@link #setRecordingYear(Integer)} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link #setRecordingYear(Integer)} instead.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
public Builder setYear(@Nullable Integer year) {
|
public Builder setYear(@Nullable Integer year) {
|
||||||
@ -429,9 +423,6 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
if (mediaMetadata.description != null) {
|
if (mediaMetadata.description != null) {
|
||||||
setDescription(mediaMetadata.description);
|
setDescription(mediaMetadata.description);
|
||||||
}
|
}
|
||||||
if (mediaMetadata.mediaUri != null) {
|
|
||||||
setMediaUri(mediaMetadata.mediaUri);
|
|
||||||
}
|
|
||||||
if (mediaMetadata.userRating != null) {
|
if (mediaMetadata.userRating != null) {
|
||||||
setUserRating(mediaMetadata.userRating);
|
setUserRating(mediaMetadata.userRating);
|
||||||
}
|
}
|
||||||
@ -634,8 +625,6 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
@Nullable public final CharSequence subtitle;
|
@Nullable public final CharSequence subtitle;
|
||||||
/** Optional description. */
|
/** Optional description. */
|
||||||
@Nullable public final CharSequence description;
|
@Nullable public final CharSequence description;
|
||||||
/** Optional media {@link Uri}. */
|
|
||||||
@Nullable public final Uri mediaUri;
|
|
||||||
/** Optional user {@link Rating}. */
|
/** Optional user {@link Rating}. */
|
||||||
@Nullable public final Rating userRating;
|
@Nullable public final Rating userRating;
|
||||||
/** Optional overall {@link Rating}. */
|
/** Optional overall {@link Rating}. */
|
||||||
@ -654,7 +643,9 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
@Nullable public final @FolderType Integer folderType;
|
@Nullable public final @FolderType Integer folderType;
|
||||||
/** Optional boolean for media playability. */
|
/** Optional boolean for media playability. */
|
||||||
@Nullable public final Boolean isPlayable;
|
@Nullable public final Boolean isPlayable;
|
||||||
/** @deprecated Use {@link #recordingYear} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link #recordingYear} instead.
|
||||||
|
*/
|
||||||
@UnstableApi @Deprecated @Nullable public final Integer year;
|
@UnstableApi @Deprecated @Nullable public final Integer year;
|
||||||
/** Optional year of the recording date. */
|
/** Optional year of the recording date. */
|
||||||
@Nullable public final Integer recordingYear;
|
@Nullable public final Integer recordingYear;
|
||||||
@ -718,7 +709,6 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
this.displayTitle = builder.displayTitle;
|
this.displayTitle = builder.displayTitle;
|
||||||
this.subtitle = builder.subtitle;
|
this.subtitle = builder.subtitle;
|
||||||
this.description = builder.description;
|
this.description = builder.description;
|
||||||
this.mediaUri = builder.mediaUri;
|
|
||||||
this.userRating = builder.userRating;
|
this.userRating = builder.userRating;
|
||||||
this.overallRating = builder.overallRating;
|
this.overallRating = builder.overallRating;
|
||||||
this.artworkData = builder.artworkData;
|
this.artworkData = builder.artworkData;
|
||||||
@ -767,7 +757,6 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
&& Util.areEqual(displayTitle, that.displayTitle)
|
&& Util.areEqual(displayTitle, that.displayTitle)
|
||||||
&& Util.areEqual(subtitle, that.subtitle)
|
&& Util.areEqual(subtitle, that.subtitle)
|
||||||
&& Util.areEqual(description, that.description)
|
&& Util.areEqual(description, that.description)
|
||||||
&& Util.areEqual(mediaUri, that.mediaUri)
|
|
||||||
&& Util.areEqual(userRating, that.userRating)
|
&& Util.areEqual(userRating, that.userRating)
|
||||||
&& Util.areEqual(overallRating, that.overallRating)
|
&& Util.areEqual(overallRating, that.overallRating)
|
||||||
&& Arrays.equals(artworkData, that.artworkData)
|
&& Arrays.equals(artworkData, that.artworkData)
|
||||||
@ -803,7 +792,6 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
displayTitle,
|
displayTitle,
|
||||||
subtitle,
|
subtitle,
|
||||||
description,
|
description,
|
||||||
mediaUri,
|
|
||||||
userRating,
|
userRating,
|
||||||
overallRating,
|
overallRating,
|
||||||
Arrays.hashCode(artworkData),
|
Arrays.hashCode(artworkData),
|
||||||
@ -914,7 +902,6 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
bundle.putCharSequence(keyForField(FIELD_DISPLAY_TITLE), displayTitle);
|
bundle.putCharSequence(keyForField(FIELD_DISPLAY_TITLE), displayTitle);
|
||||||
bundle.putCharSequence(keyForField(FIELD_SUBTITLE), subtitle);
|
bundle.putCharSequence(keyForField(FIELD_SUBTITLE), subtitle);
|
||||||
bundle.putCharSequence(keyForField(FIELD_DESCRIPTION), description);
|
bundle.putCharSequence(keyForField(FIELD_DESCRIPTION), description);
|
||||||
bundle.putParcelable(keyForField(FIELD_MEDIA_URI), mediaUri);
|
|
||||||
bundle.putByteArray(keyForField(FIELD_ARTWORK_DATA), artworkData);
|
bundle.putByteArray(keyForField(FIELD_ARTWORK_DATA), artworkData);
|
||||||
bundle.putParcelable(keyForField(FIELD_ARTWORK_URI), artworkUri);
|
bundle.putParcelable(keyForField(FIELD_ARTWORK_URI), artworkUri);
|
||||||
bundle.putCharSequence(keyForField(FIELD_WRITER), writer);
|
bundle.putCharSequence(keyForField(FIELD_WRITER), writer);
|
||||||
@ -988,7 +975,6 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
.setDisplayTitle(bundle.getCharSequence(keyForField(FIELD_DISPLAY_TITLE)))
|
.setDisplayTitle(bundle.getCharSequence(keyForField(FIELD_DISPLAY_TITLE)))
|
||||||
.setSubtitle(bundle.getCharSequence(keyForField(FIELD_SUBTITLE)))
|
.setSubtitle(bundle.getCharSequence(keyForField(FIELD_SUBTITLE)))
|
||||||
.setDescription(bundle.getCharSequence(keyForField(FIELD_DESCRIPTION)))
|
.setDescription(bundle.getCharSequence(keyForField(FIELD_DESCRIPTION)))
|
||||||
.setMediaUri(bundle.getParcelable(keyForField(FIELD_MEDIA_URI)))
|
|
||||||
.setArtworkData(
|
.setArtworkData(
|
||||||
bundle.getByteArray(keyForField(FIELD_ARTWORK_DATA)),
|
bundle.getByteArray(keyForField(FIELD_ARTWORK_DATA)),
|
||||||
bundle.containsKey(keyForField(FIELD_ARTWORK_DATA_TYPE))
|
bundle.containsKey(keyForField(FIELD_ARTWORK_DATA_TYPE))
|
||||||
|
@ -62,12 +62,16 @@ public final class Metadata implements Parcelable {
|
|||||||
|
|
||||||
private final Entry[] entries;
|
private final Entry[] entries;
|
||||||
|
|
||||||
/** @param entries The metadata entries. */
|
/**
|
||||||
|
* @param entries The metadata entries.
|
||||||
|
*/
|
||||||
public Metadata(Entry... entries) {
|
public Metadata(Entry... entries) {
|
||||||
this.entries = entries;
|
this.entries = entries;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param entries The metadata entries. */
|
/**
|
||||||
|
* @param entries The metadata entries.
|
||||||
|
*/
|
||||||
public Metadata(List<? extends Entry> entries) {
|
public Metadata(List<? extends Entry> entries) {
|
||||||
this.entries = entries.toArray(new Entry[0]);
|
this.entries = entries.toArray(new Entry[0]);
|
||||||
}
|
}
|
||||||
|
@ -56,6 +56,10 @@ public final class MimeTypes {
|
|||||||
@UnstableApi public static final String VIDEO_FLV = BASE_TYPE_VIDEO + "/x-flv";
|
@UnstableApi public static final String VIDEO_FLV = BASE_TYPE_VIDEO + "/x-flv";
|
||||||
public static final String VIDEO_DOLBY_VISION = BASE_TYPE_VIDEO + "/dolby-vision";
|
public static final String VIDEO_DOLBY_VISION = BASE_TYPE_VIDEO + "/dolby-vision";
|
||||||
public static final String VIDEO_OGG = BASE_TYPE_VIDEO + "/ogg";
|
public static final String VIDEO_OGG = BASE_TYPE_VIDEO + "/ogg";
|
||||||
|
public static final String VIDEO_AVI = BASE_TYPE_VIDEO + "/x-msvideo";
|
||||||
|
public static final String VIDEO_MJPEG = BASE_TYPE_VIDEO + "/mjpeg";
|
||||||
|
public static final String VIDEO_MP42 = BASE_TYPE_VIDEO + "/mp42";
|
||||||
|
public static final String VIDEO_MP43 = BASE_TYPE_VIDEO + "/mp43";
|
||||||
@UnstableApi public static final String VIDEO_UNKNOWN = BASE_TYPE_VIDEO + "/x-unknown";
|
@UnstableApi public static final String VIDEO_UNKNOWN = BASE_TYPE_VIDEO + "/x-unknown";
|
||||||
|
|
||||||
// audio/ MIME types
|
// audio/ MIME types
|
||||||
@ -87,6 +91,7 @@ public final class MimeTypes {
|
|||||||
public static final String AUDIO_AMR_NB = BASE_TYPE_AUDIO + "/3gpp";
|
public static final String AUDIO_AMR_NB = BASE_TYPE_AUDIO + "/3gpp";
|
||||||
public static final String AUDIO_AMR_WB = BASE_TYPE_AUDIO + "/amr-wb";
|
public static final String AUDIO_AMR_WB = BASE_TYPE_AUDIO + "/amr-wb";
|
||||||
public static final String AUDIO_FLAC = BASE_TYPE_AUDIO + "/flac";
|
public static final String AUDIO_FLAC = BASE_TYPE_AUDIO + "/flac";
|
||||||
|
public static final String AUDIO_MIDI = BASE_TYPE_AUDIO + "/midi";
|
||||||
public static final String AUDIO_ALAC = BASE_TYPE_AUDIO + "/alac";
|
public static final String AUDIO_ALAC = BASE_TYPE_AUDIO + "/alac";
|
||||||
public static final String AUDIO_MSGSM = BASE_TYPE_AUDIO + "/gsm";
|
public static final String AUDIO_MSGSM = BASE_TYPE_AUDIO + "/gsm";
|
||||||
public static final String AUDIO_OGG = BASE_TYPE_AUDIO + "/ogg";
|
public static final String AUDIO_OGG = BASE_TYPE_AUDIO + "/ogg";
|
||||||
@ -108,11 +113,10 @@ public final class MimeTypes {
|
|||||||
public static final String APPLICATION_MP4 = BASE_TYPE_APPLICATION + "/mp4";
|
public static final String APPLICATION_MP4 = BASE_TYPE_APPLICATION + "/mp4";
|
||||||
public static final String APPLICATION_WEBM = BASE_TYPE_APPLICATION + "/webm";
|
public static final String APPLICATION_WEBM = BASE_TYPE_APPLICATION + "/webm";
|
||||||
|
|
||||||
@UnstableApi
|
|
||||||
public static final String APPLICATION_MATROSKA = BASE_TYPE_APPLICATION + "/x-matroska";
|
public static final String APPLICATION_MATROSKA = BASE_TYPE_APPLICATION + "/x-matroska";
|
||||||
|
|
||||||
public static final String APPLICATION_MPD = BASE_TYPE_APPLICATION + "/dash+xml";
|
public static final String APPLICATION_MPD = BASE_TYPE_APPLICATION + "/dash+xml";
|
||||||
@UnstableApi public static final String APPLICATION_M3U8 = BASE_TYPE_APPLICATION + "/x-mpegURL";
|
public static final String APPLICATION_M3U8 = BASE_TYPE_APPLICATION + "/x-mpegURL";
|
||||||
public static final String APPLICATION_SS = BASE_TYPE_APPLICATION + "/vnd.ms-sstr+xml";
|
public static final String APPLICATION_SS = BASE_TYPE_APPLICATION + "/vnd.ms-sstr+xml";
|
||||||
public static final String APPLICATION_ID3 = BASE_TYPE_APPLICATION + "/id3";
|
public static final String APPLICATION_ID3 = BASE_TYPE_APPLICATION + "/id3";
|
||||||
public static final String APPLICATION_CEA608 = BASE_TYPE_APPLICATION + "/cea-608";
|
public static final String APPLICATION_CEA608 = BASE_TYPE_APPLICATION + "/cea-608";
|
||||||
@ -135,7 +139,7 @@ public final class MimeTypes {
|
|||||||
@UnstableApi public static final String APPLICATION_EXIF = BASE_TYPE_APPLICATION + "/x-exif";
|
@UnstableApi public static final String APPLICATION_EXIF = BASE_TYPE_APPLICATION + "/x-exif";
|
||||||
@UnstableApi public static final String APPLICATION_ICY = BASE_TYPE_APPLICATION + "/x-icy";
|
@UnstableApi public static final String APPLICATION_ICY = BASE_TYPE_APPLICATION + "/x-icy";
|
||||||
public static final String APPLICATION_AIT = BASE_TYPE_APPLICATION + "/vnd.dvb.ait";
|
public static final String APPLICATION_AIT = BASE_TYPE_APPLICATION + "/vnd.dvb.ait";
|
||||||
@UnstableApi public static final String APPLICATION_RTSP = BASE_TYPE_APPLICATION + "/x-rtsp";
|
public static final String APPLICATION_RTSP = BASE_TYPE_APPLICATION + "/x-rtsp";
|
||||||
|
|
||||||
// image/ MIME types
|
// image/ MIME types
|
||||||
|
|
||||||
|
@ -401,28 +401,6 @@ public class PlaybackException extends Exception implements Bundleable {
|
|||||||
|
|
||||||
// Bundleable implementation.
|
// Bundleable implementation.
|
||||||
|
|
||||||
/**
|
|
||||||
* Identifiers for fields in a {@link Bundle} which represents a playback exception. Subclasses
|
|
||||||
* may use {@link #FIELD_CUSTOM_ID_BASE} to generate more keys using {@link #keyForField(int)}.
|
|
||||||
*
|
|
||||||
* <p>Note: Changes to the Bundleable implementation must be backwards compatible, so as to avoid
|
|
||||||
* breaking communication across different Bundleable implementation versions.
|
|
||||||
*/
|
|
||||||
@Documented
|
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
|
||||||
@Target(TYPE_USE)
|
|
||||||
@IntDef(
|
|
||||||
open = true,
|
|
||||||
value = {
|
|
||||||
FIELD_INT_ERROR_CODE,
|
|
||||||
FIELD_LONG_TIMESTAMP_MS,
|
|
||||||
FIELD_STRING_MESSAGE,
|
|
||||||
FIELD_STRING_CAUSE_CLASS_NAME,
|
|
||||||
FIELD_STRING_CAUSE_MESSAGE,
|
|
||||||
})
|
|
||||||
@UnstableApi
|
|
||||||
protected @interface FieldNumber {}
|
|
||||||
|
|
||||||
private static final int FIELD_INT_ERROR_CODE = 0;
|
private static final int FIELD_INT_ERROR_CODE = 0;
|
||||||
private static final int FIELD_LONG_TIMESTAMP_MS = 1;
|
private static final int FIELD_LONG_TIMESTAMP_MS = 1;
|
||||||
private static final int FIELD_STRING_MESSAGE = 2;
|
private static final int FIELD_STRING_MESSAGE = 2;
|
||||||
@ -430,7 +408,7 @@ public class PlaybackException extends Exception implements Bundleable {
|
|||||||
private static final int FIELD_STRING_CAUSE_MESSAGE = 4;
|
private static final int FIELD_STRING_CAUSE_MESSAGE = 4;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Defines a minimum field id value for subclasses to use when implementing {@link #toBundle()}
|
* Defines a minimum field ID value for subclasses to use when implementing {@link #toBundle()}
|
||||||
* and {@link Bundleable.Creator}.
|
* and {@link Bundleable.Creator}.
|
||||||
*
|
*
|
||||||
* <p>Subclasses should obtain their {@link Bundle Bundle's} field keys by applying a non-negative
|
* <p>Subclasses should obtain their {@link Bundle Bundle's} field keys by applying a non-negative
|
||||||
@ -458,11 +436,14 @@ public class PlaybackException extends Exception implements Bundleable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts the given {@link FieldNumber} to a string which can be used as a field key when
|
* Converts the given field number to a string which can be used as a field key when implementing
|
||||||
* implementing {@link #toBundle()} and {@link Bundleable.Creator}.
|
* {@link #toBundle()} and {@link Bundleable.Creator}.
|
||||||
|
*
|
||||||
|
* <p>Subclasses should use {@code field} values greater than or equal to {@link
|
||||||
|
* #FIELD_CUSTOM_ID_BASE}.
|
||||||
*/
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
protected static String keyForField(@FieldNumber int field) {
|
protected static String keyForField(int field) {
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
return Integer.toString(field, Character.MAX_RADIX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -32,7 +32,7 @@ import androidx.annotation.IntDef;
|
|||||||
import androidx.annotation.IntRange;
|
import androidx.annotation.IntRange;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.text.Cue;
|
import androidx.media3.common.text.Cue;
|
||||||
import androidx.media3.common.util.BundleableUtil;
|
import androidx.media3.common.text.CueGroup;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import com.google.common.base.Objects;
|
import com.google.common.base.Objects;
|
||||||
@ -57,8 +57,8 @@ import java.util.List;
|
|||||||
* <ul>
|
* <ul>
|
||||||
* <li>They can provide a {@link Timeline} representing the structure of the media being played,
|
* <li>They can provide a {@link Timeline} representing the structure of the media being played,
|
||||||
* which can be obtained by calling {@link #getCurrentTimeline()}.
|
* which can be obtained by calling {@link #getCurrentTimeline()}.
|
||||||
* <li>They can provide a {@link TracksInfo} defining the currently available tracks and which are
|
* <li>They can provide a {@link Tracks} defining the currently available tracks and which are
|
||||||
* selected to be rendered, which can be obtained by calling {@link #getCurrentTracksInfo()}.
|
* selected to be rendered, which can be obtained by calling {@link #getCurrentTracks()}.
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
public interface Player {
|
public interface Player {
|
||||||
@ -142,7 +142,9 @@ public interface Player {
|
|||||||
* The UID of the window, or {@code null} if the timeline is {@link Timeline#isEmpty() empty}.
|
* The UID of the window, or {@code null} if the timeline is {@link Timeline#isEmpty() empty}.
|
||||||
*/
|
*/
|
||||||
@Nullable public final Object windowUid;
|
@Nullable public final Object windowUid;
|
||||||
/** @deprecated Use {@link #mediaItemIndex} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link #mediaItemIndex} instead.
|
||||||
|
*/
|
||||||
@UnstableApi @Deprecated public final int windowIndex;
|
@UnstableApi @Deprecated public final int windowIndex;
|
||||||
/** The media item index. */
|
/** The media item index. */
|
||||||
public final int mediaItemIndex;
|
public final int mediaItemIndex;
|
||||||
@ -292,7 +294,9 @@ public interface Player {
|
|||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putInt(keyForField(FIELD_MEDIA_ITEM_INDEX), mediaItemIndex);
|
bundle.putInt(keyForField(FIELD_MEDIA_ITEM_INDEX), mediaItemIndex);
|
||||||
bundle.putBundle(keyForField(FIELD_MEDIA_ITEM), BundleableUtil.toNullableBundle(mediaItem));
|
if (mediaItem != null) {
|
||||||
|
bundle.putBundle(keyForField(FIELD_MEDIA_ITEM), mediaItem.toBundle());
|
||||||
|
}
|
||||||
bundle.putInt(keyForField(FIELD_PERIOD_INDEX), periodIndex);
|
bundle.putInt(keyForField(FIELD_PERIOD_INDEX), periodIndex);
|
||||||
bundle.putLong(keyForField(FIELD_POSITION_MS), positionMs);
|
bundle.putLong(keyForField(FIELD_POSITION_MS), positionMs);
|
||||||
bundle.putLong(keyForField(FIELD_CONTENT_POSITION_MS), contentPositionMs);
|
bundle.putLong(keyForField(FIELD_CONTENT_POSITION_MS), contentPositionMs);
|
||||||
@ -307,10 +311,10 @@ public interface Player {
|
|||||||
private static PositionInfo fromBundle(Bundle bundle) {
|
private static PositionInfo fromBundle(Bundle bundle) {
|
||||||
int mediaItemIndex =
|
int mediaItemIndex =
|
||||||
bundle.getInt(keyForField(FIELD_MEDIA_ITEM_INDEX), /* defaultValue= */ C.INDEX_UNSET);
|
bundle.getInt(keyForField(FIELD_MEDIA_ITEM_INDEX), /* defaultValue= */ C.INDEX_UNSET);
|
||||||
|
@Nullable Bundle mediaItemBundle = bundle.getBundle(keyForField(FIELD_MEDIA_ITEM));
|
||||||
@Nullable
|
@Nullable
|
||||||
MediaItem mediaItem =
|
MediaItem mediaItem =
|
||||||
BundleableUtil.fromNullableBundle(
|
mediaItemBundle == null ? null : MediaItem.CREATOR.fromBundle(mediaItemBundle);
|
||||||
MediaItem.CREATOR, bundle.getBundle(keyForField(FIELD_MEDIA_ITEM)));
|
|
||||||
int periodIndex =
|
int periodIndex =
|
||||||
bundle.getInt(keyForField(FIELD_PERIOD_INDEX), /* defaultValue= */ C.INDEX_UNSET);
|
bundle.getInt(keyForField(FIELD_PERIOD_INDEX), /* defaultValue= */ C.INDEX_UNSET);
|
||||||
long positionMs =
|
long positionMs =
|
||||||
@ -379,7 +383,8 @@ public interface Player {
|
|||||||
COMMAND_SET_VIDEO_SURFACE,
|
COMMAND_SET_VIDEO_SURFACE,
|
||||||
COMMAND_GET_TEXT,
|
COMMAND_GET_TEXT,
|
||||||
COMMAND_SET_TRACK_SELECTION_PARAMETERS,
|
COMMAND_SET_TRACK_SELECTION_PARAMETERS,
|
||||||
COMMAND_GET_TRACK_INFOS,
|
COMMAND_GET_TRACKS,
|
||||||
|
COMMAND_SET_MEDIA_ITEM,
|
||||||
};
|
};
|
||||||
|
|
||||||
private final FlagSet.Builder flagsBuilder;
|
private final FlagSet.Builder flagsBuilder;
|
||||||
@ -521,6 +526,11 @@ public interface Player {
|
|||||||
return flags.contains(command);
|
return flags.contains(command);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns whether the set of commands contains at least one of the given {@code commands}. */
|
||||||
|
public boolean containsAny(@Command int... commands) {
|
||||||
|
return flags.containsAny(commands);
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns the number of commands in this set. */
|
/** Returns the number of commands in this set. */
|
||||||
public int size() {
|
public int size() {
|
||||||
return flags.size();
|
return flags.size();
|
||||||
@ -669,41 +679,24 @@ public interface Player {
|
|||||||
@Nullable MediaItem mediaItem, @MediaItemTransitionReason int reason) {}
|
@Nullable MediaItem mediaItem, @MediaItemTransitionReason int reason) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the available or selected tracks change.
|
* Called when the tracks change.
|
||||||
*
|
*
|
||||||
* <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
|
* <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
|
||||||
* other events that happen in the same {@link Looper} message queue iteration.
|
* other events that happen in the same {@link Looper} message queue iteration.
|
||||||
*
|
*
|
||||||
* @param trackGroups The available tracks. Never null, but may be of length zero.
|
* @param tracks The available tracks information. Never null, but may be of length zero.
|
||||||
* @param trackSelections The selected tracks. Never null, but may contain null elements. A
|
|
||||||
* concrete implementation may include null elements if it has a fixed number of renderer
|
|
||||||
* components, wishes to report a TrackSelection for each of them, and has one or more
|
|
||||||
* renderer components that is not assigned any selected tracks.
|
|
||||||
* @deprecated Use {@link #onTracksInfoChanged(TracksInfo)} instead.
|
|
||||||
*/
|
*/
|
||||||
@UnstableApi
|
default void onTracksChanged(Tracks tracks) {}
|
||||||
@Deprecated
|
|
||||||
default void onTracksChanged(
|
|
||||||
TrackGroupArray trackGroups, TrackSelectionArray trackSelections) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the available or selected tracks change.
|
|
||||||
*
|
|
||||||
* <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
|
|
||||||
* other events that happen in the same {@link Looper} message queue iteration.
|
|
||||||
*
|
|
||||||
* @param tracksInfo The available tracks information. Never null, but may be of length zero.
|
|
||||||
*/
|
|
||||||
default void onTracksInfoChanged(TracksInfo tracksInfo) {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the combined {@link MediaMetadata} changes.
|
* Called when the combined {@link MediaMetadata} changes.
|
||||||
*
|
*
|
||||||
* <p>The provided {@link MediaMetadata} is a combination of the {@link MediaItem#mediaMetadata}
|
* <p>The provided {@link MediaMetadata} is a combination of the {@link MediaItem#mediaMetadata
|
||||||
* and the static and dynamic metadata from the {@link TrackSelection#getFormat(int) track
|
* MediaItem metadata}, the static metadata in the media's {@link Format#metadata Format}, and
|
||||||
* selections' formats} and {@link Listener#onMetadata(Metadata)}. If a field is populated in
|
* any timed metadata that has been parsed from the media and output via {@link
|
||||||
* the {@link MediaItem#mediaMetadata}, it will be prioritised above the same field coming from
|
* Listener#onMetadata(Metadata)}. If a field is populated in the {@link
|
||||||
* static or dynamic metadata.
|
* MediaItem#mediaMetadata}, it will be prioritised above the same field coming from static or
|
||||||
|
* timed metadata.
|
||||||
*
|
*
|
||||||
* <p>This method may be called multiple times in quick succession.
|
* <p>This method may be called multiple times in quick succession.
|
||||||
*
|
*
|
||||||
@ -732,7 +725,9 @@ public interface Player {
|
|||||||
*/
|
*/
|
||||||
default void onIsLoadingChanged(boolean isLoading) {}
|
default void onIsLoadingChanged(boolean isLoading) {}
|
||||||
|
|
||||||
/** @deprecated Use {@link #onIsLoadingChanged(boolean)} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link #onIsLoadingChanged(boolean)} instead.
|
||||||
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
default void onLoadingChanged(boolean isLoading) {}
|
default void onLoadingChanged(boolean isLoading) {}
|
||||||
@ -1032,16 +1027,29 @@ public interface Player {
|
|||||||
/**
|
/**
|
||||||
* Called when there is a change in the {@link Cue Cues}.
|
* Called when there is a change in the {@link Cue Cues}.
|
||||||
*
|
*
|
||||||
* <p>{@code cues} is in ascending order of priority. If any of the cue boxes overlap when
|
* <p>Both {@link #onCues(List)} and {@link #onCues(CueGroup)} are called when there is a change
|
||||||
* displayed, the {@link Cue} nearer the end of the list should be shown on top.
|
* in the cues. You should only implement one or the other.
|
||||||
*
|
*
|
||||||
* <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
|
* <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
|
||||||
* other events that happen in the same {@link Looper} message queue iteration.
|
* other events that happen in the same {@link Looper} message queue iteration.
|
||||||
*
|
*
|
||||||
* @param cues The {@link Cue Cues}. May be empty.
|
* @deprecated Use {@link #onCues(CueGroup)} instead.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
|
@UnstableApi
|
||||||
default void onCues(List<Cue> cues) {}
|
default void onCues(List<Cue> cues) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when there is a change in the {@link CueGroup}.
|
||||||
|
*
|
||||||
|
* <p>Both {@link #onCues(List)} and {@link #onCues(CueGroup)} are called when there is a change
|
||||||
|
* in the cues. You should only implement one or the other.
|
||||||
|
*
|
||||||
|
* <p>{@link #onEvents(Player, Events)} will also be called to report this event along with
|
||||||
|
* other events that happen in the same {@link Looper} message queue iteration.
|
||||||
|
*/
|
||||||
|
default void onCues(CueGroup cueGroup) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when there is metadata associated with the current playback time.
|
* Called when there is metadata associated with the current playback time.
|
||||||
*
|
*
|
||||||
@ -1316,7 +1324,7 @@ public interface Player {
|
|||||||
int EVENT_TIMELINE_CHANGED = 0;
|
int EVENT_TIMELINE_CHANGED = 0;
|
||||||
/** {@link #getCurrentMediaItem()} changed or the player started repeating the current item. */
|
/** {@link #getCurrentMediaItem()} changed or the player started repeating the current item. */
|
||||||
int EVENT_MEDIA_ITEM_TRANSITION = 1;
|
int EVENT_MEDIA_ITEM_TRANSITION = 1;
|
||||||
/** {@link #getCurrentTracksInfo()} changed. */
|
/** {@link #getCurrentTracks()} changed. */
|
||||||
int EVENT_TRACKS_CHANGED = 2;
|
int EVENT_TRACKS_CHANGED = 2;
|
||||||
/** {@link #isLoading()} ()} changed. */
|
/** {@link #isLoading()} ()} changed. */
|
||||||
int EVENT_IS_LOADING_CHANGED = 3;
|
int EVENT_IS_LOADING_CHANGED = 3;
|
||||||
@ -1395,7 +1403,8 @@ public interface Player {
|
|||||||
* #COMMAND_GET_VOLUME}, {@link #COMMAND_GET_DEVICE_VOLUME}, {@link #COMMAND_SET_VOLUME}, {@link
|
* #COMMAND_GET_VOLUME}, {@link #COMMAND_GET_DEVICE_VOLUME}, {@link #COMMAND_SET_VOLUME}, {@link
|
||||||
* #COMMAND_SET_DEVICE_VOLUME}, {@link #COMMAND_ADJUST_DEVICE_VOLUME}, {@link
|
* #COMMAND_SET_DEVICE_VOLUME}, {@link #COMMAND_ADJUST_DEVICE_VOLUME}, {@link
|
||||||
* #COMMAND_SET_VIDEO_SURFACE}, {@link #COMMAND_GET_TEXT}, {@link
|
* #COMMAND_SET_VIDEO_SURFACE}, {@link #COMMAND_GET_TEXT}, {@link
|
||||||
* #COMMAND_SET_TRACK_SELECTION_PARAMETERS} or {@link #COMMAND_GET_TRACK_INFOS}.
|
* #COMMAND_SET_TRACK_SELECTION_PARAMETERS}, {@link #COMMAND_GET_TRACKS} or {@link
|
||||||
|
* #COMMAND_SET_MEDIA_ITEM}.
|
||||||
*/
|
*/
|
||||||
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
|
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
|
||||||
// with Kotlin usages from before TYPE_USE was added.
|
// with Kotlin usages from before TYPE_USE was added.
|
||||||
@ -1433,7 +1442,8 @@ public interface Player {
|
|||||||
COMMAND_SET_VIDEO_SURFACE,
|
COMMAND_SET_VIDEO_SURFACE,
|
||||||
COMMAND_GET_TEXT,
|
COMMAND_GET_TEXT,
|
||||||
COMMAND_SET_TRACK_SELECTION_PARAMETERS,
|
COMMAND_SET_TRACK_SELECTION_PARAMETERS,
|
||||||
COMMAND_GET_TRACK_INFOS,
|
COMMAND_GET_TRACKS,
|
||||||
|
COMMAND_SET_MEDIA_ITEM,
|
||||||
})
|
})
|
||||||
@interface Command {}
|
@interface Command {}
|
||||||
/** Command to start, pause or resume playback. */
|
/** Command to start, pause or resume playback. */
|
||||||
@ -1446,24 +1456,32 @@ public interface Player {
|
|||||||
int COMMAND_SEEK_TO_DEFAULT_POSITION = 4;
|
int COMMAND_SEEK_TO_DEFAULT_POSITION = 4;
|
||||||
/** Command to seek anywhere into the current {@link MediaItem}. */
|
/** Command to seek anywhere into the current {@link MediaItem}. */
|
||||||
int COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM = 5;
|
int COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM = 5;
|
||||||
/** @deprecated Use {@link #COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link #COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM} instead.
|
||||||
|
*/
|
||||||
@UnstableApi @Deprecated int COMMAND_SEEK_IN_CURRENT_WINDOW = COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM;
|
@UnstableApi @Deprecated int COMMAND_SEEK_IN_CURRENT_WINDOW = COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM;
|
||||||
/** Command to seek to the default position of the previous {@link MediaItem}. */
|
/** Command to seek to the default position of the previous {@link MediaItem}. */
|
||||||
int COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM = 6;
|
int COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM = 6;
|
||||||
/** @deprecated Use {@link #COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link #COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM} instead.
|
||||||
|
*/
|
||||||
@UnstableApi @Deprecated
|
@UnstableApi @Deprecated
|
||||||
int COMMAND_SEEK_TO_PREVIOUS_WINDOW = COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM;
|
int COMMAND_SEEK_TO_PREVIOUS_WINDOW = COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM;
|
||||||
/** Command to seek to an earlier position in the current or previous {@link MediaItem}. */
|
/** Command to seek to an earlier position in the current or previous {@link MediaItem}. */
|
||||||
int COMMAND_SEEK_TO_PREVIOUS = 7;
|
int COMMAND_SEEK_TO_PREVIOUS = 7;
|
||||||
/** Command to seek to the default position of the next {@link MediaItem}. */
|
/** Command to seek to the default position of the next {@link MediaItem}. */
|
||||||
int COMMAND_SEEK_TO_NEXT_MEDIA_ITEM = 8;
|
int COMMAND_SEEK_TO_NEXT_MEDIA_ITEM = 8;
|
||||||
/** @deprecated Use {@link #COMMAND_SEEK_TO_NEXT_MEDIA_ITEM} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link #COMMAND_SEEK_TO_NEXT_MEDIA_ITEM} instead.
|
||||||
|
*/
|
||||||
@UnstableApi @Deprecated int COMMAND_SEEK_TO_NEXT_WINDOW = COMMAND_SEEK_TO_NEXT_MEDIA_ITEM;
|
@UnstableApi @Deprecated int COMMAND_SEEK_TO_NEXT_WINDOW = COMMAND_SEEK_TO_NEXT_MEDIA_ITEM;
|
||||||
/** Command to seek to a later position in the current or next {@link MediaItem}. */
|
/** Command to seek to a later position in the current or next {@link MediaItem}. */
|
||||||
int COMMAND_SEEK_TO_NEXT = 9;
|
int COMMAND_SEEK_TO_NEXT = 9;
|
||||||
/** Command to seek anywhere in any {@link MediaItem}. */
|
/** Command to seek anywhere in any {@link MediaItem}. */
|
||||||
int COMMAND_SEEK_TO_MEDIA_ITEM = 10;
|
int COMMAND_SEEK_TO_MEDIA_ITEM = 10;
|
||||||
/** @deprecated Use {@link #COMMAND_SEEK_TO_MEDIA_ITEM} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link #COMMAND_SEEK_TO_MEDIA_ITEM} instead.
|
||||||
|
*/
|
||||||
@UnstableApi @Deprecated int COMMAND_SEEK_TO_WINDOW = COMMAND_SEEK_TO_MEDIA_ITEM;
|
@UnstableApi @Deprecated int COMMAND_SEEK_TO_WINDOW = COMMAND_SEEK_TO_MEDIA_ITEM;
|
||||||
/** Command to seek back by a fixed increment into the current {@link MediaItem}. */
|
/** Command to seek back by a fixed increment into the current {@link MediaItem}. */
|
||||||
int COMMAND_SEEK_BACK = 11;
|
int COMMAND_SEEK_BACK = 11;
|
||||||
@ -1503,8 +1521,10 @@ public interface Player {
|
|||||||
int COMMAND_GET_TEXT = 28;
|
int COMMAND_GET_TEXT = 28;
|
||||||
/** Command to set the player's track selection parameters. */
|
/** Command to set the player's track selection parameters. */
|
||||||
int COMMAND_SET_TRACK_SELECTION_PARAMETERS = 29;
|
int COMMAND_SET_TRACK_SELECTION_PARAMETERS = 29;
|
||||||
/** Command to get track infos. */
|
/** Command to get details of the current track selection. */
|
||||||
int COMMAND_GET_TRACK_INFOS = 30;
|
int COMMAND_GET_TRACKS = 30;
|
||||||
|
/** Command to set a {@link MediaItem MediaItem}. */
|
||||||
|
int COMMAND_SET_MEDIA_ITEM = 31;
|
||||||
|
|
||||||
/** Represents an invalid {@link Command}. */
|
/** Represents an invalid {@link Command}. */
|
||||||
int COMMAND_INVALID = -1;
|
int COMMAND_INVALID = -1;
|
||||||
@ -1889,12 +1909,16 @@ public interface Player {
|
|||||||
*/
|
*/
|
||||||
void seekForward();
|
void seekForward();
|
||||||
|
|
||||||
/** @deprecated Use {@link #hasPreviousMediaItem()} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link #hasPreviousMediaItem()} instead.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
boolean hasPrevious();
|
boolean hasPrevious();
|
||||||
|
|
||||||
/** @deprecated Use {@link #hasPreviousMediaItem()} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link #hasPreviousMediaItem()} instead.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
boolean hasPreviousWindow();
|
boolean hasPreviousWindow();
|
||||||
@ -1909,12 +1933,16 @@ public interface Player {
|
|||||||
*/
|
*/
|
||||||
boolean hasPreviousMediaItem();
|
boolean hasPreviousMediaItem();
|
||||||
|
|
||||||
/** @deprecated Use {@link #seekToPreviousMediaItem()} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link #seekToPreviousMediaItem()} instead.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
void previous();
|
void previous();
|
||||||
|
|
||||||
/** @deprecated Use {@link #seekToPreviousMediaItem()} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link #seekToPreviousMediaItem()} instead.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
void seekToPreviousWindow();
|
void seekToPreviousWindow();
|
||||||
@ -1961,12 +1989,16 @@ public interface Player {
|
|||||||
*/
|
*/
|
||||||
void seekToPrevious();
|
void seekToPrevious();
|
||||||
|
|
||||||
/** @deprecated Use {@link #hasNextMediaItem()} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link #hasNextMediaItem()} instead.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
boolean hasNext();
|
boolean hasNext();
|
||||||
|
|
||||||
/** @deprecated Use {@link #hasNextMediaItem()} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link #hasNextMediaItem()} instead.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
boolean hasNextWindow();
|
boolean hasNextWindow();
|
||||||
@ -1981,12 +2013,16 @@ public interface Player {
|
|||||||
*/
|
*/
|
||||||
boolean hasNextMediaItem();
|
boolean hasNextMediaItem();
|
||||||
|
|
||||||
/** @deprecated Use {@link #seekToNextMediaItem()} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link #seekToNextMediaItem()} instead.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
void next();
|
void next();
|
||||||
|
|
||||||
/** @deprecated Use {@link #seekToNextMediaItem()} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link #seekToNextMediaItem()} instead.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
void seekToNextWindow();
|
void seekToNextWindow();
|
||||||
@ -2077,35 +2113,11 @@ public interface Player {
|
|||||||
void release();
|
void release();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the available track groups.
|
* Returns the current tracks.
|
||||||
*
|
*
|
||||||
* @see Listener#onTracksChanged(TrackGroupArray, TrackSelectionArray)
|
* @see Listener#onTracksChanged(Tracks)
|
||||||
* @deprecated Use {@link #getCurrentTracksInfo()}.
|
|
||||||
*/
|
*/
|
||||||
@UnstableApi
|
Tracks getCurrentTracks();
|
||||||
@Deprecated
|
|
||||||
TrackGroupArray getCurrentTrackGroups();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the current track selections.
|
|
||||||
*
|
|
||||||
* <p>A concrete implementation may include null elements if it has a fixed number of renderer
|
|
||||||
* components, wishes to report a TrackSelection for each of them, and has one or more renderer
|
|
||||||
* components that is not assigned any selected tracks.
|
|
||||||
*
|
|
||||||
* @see Listener#onTracksChanged(TrackGroupArray, TrackSelectionArray)
|
|
||||||
* @deprecated Use {@link #getCurrentTracksInfo()}.
|
|
||||||
*/
|
|
||||||
@UnstableApi
|
|
||||||
@Deprecated
|
|
||||||
TrackSelectionArray getCurrentTrackSelections();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the available tracks, as well as the tracks' support, type, and selection status.
|
|
||||||
*
|
|
||||||
* @see Listener#onTracksChanged(TrackGroupArray, TrackSelectionArray)
|
|
||||||
*/
|
|
||||||
TracksInfo getCurrentTracksInfo();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the parameters constraining the track selection.
|
* Returns the parameters constraining the track selection.
|
||||||
@ -2137,11 +2149,11 @@ public interface Player {
|
|||||||
* Returns the current combined {@link MediaMetadata}, or {@link MediaMetadata#EMPTY} if not
|
* Returns the current combined {@link MediaMetadata}, or {@link MediaMetadata#EMPTY} if not
|
||||||
* supported.
|
* supported.
|
||||||
*
|
*
|
||||||
* <p>This {@link MediaMetadata} is a combination of the {@link MediaItem#mediaMetadata} and the
|
* <p>This {@link MediaMetadata} is a combination of the {@link MediaItem#mediaMetadata MediaItem
|
||||||
* static and dynamic metadata from the {@link TrackSelection#getFormat(int) track selections'
|
* metadata}, the static metadata in the media's {@link Format#metadata Format}, and any timed
|
||||||
* formats} and {@link Listener#onMetadata(Metadata)}. If a field is populated in the {@link
|
* metadata that has been parsed from the media and output via {@link
|
||||||
* MediaItem#mediaMetadata}, it will be prioritised above the same field coming from static or
|
* Listener#onMetadata(Metadata)}. If a field is populated in the {@link MediaItem#mediaMetadata},
|
||||||
* dynamic metadata.
|
* it will be prioritised above the same field coming from static or timed metadata.
|
||||||
*/
|
*/
|
||||||
MediaMetadata getMediaMetadata();
|
MediaMetadata getMediaMetadata();
|
||||||
|
|
||||||
@ -2171,7 +2183,9 @@ public interface Player {
|
|||||||
/** Returns the index of the period currently being played. */
|
/** Returns the index of the period currently being played. */
|
||||||
int getCurrentPeriodIndex();
|
int getCurrentPeriodIndex();
|
||||||
|
|
||||||
/** @deprecated Use {@link #getCurrentMediaItemIndex()} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link #getCurrentMediaItemIndex()} instead.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
int getCurrentWindowIndex();
|
int getCurrentWindowIndex();
|
||||||
@ -2183,7 +2197,9 @@ public interface Player {
|
|||||||
*/
|
*/
|
||||||
int getCurrentMediaItemIndex();
|
int getCurrentMediaItemIndex();
|
||||||
|
|
||||||
/** @deprecated Use {@link #getNextMediaItemIndex()} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link #getNextMediaItemIndex()} instead.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
int getNextWindowIndex();
|
int getNextWindowIndex();
|
||||||
@ -2200,7 +2216,9 @@ public interface Player {
|
|||||||
*/
|
*/
|
||||||
int getNextMediaItemIndex();
|
int getNextMediaItemIndex();
|
||||||
|
|
||||||
/** @deprecated Use {@link #getPreviousMediaItemIndex()} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link #getPreviousMediaItemIndex()} instead.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
int getPreviousWindowIndex();
|
int getPreviousWindowIndex();
|
||||||
@ -2262,7 +2280,9 @@ public interface Player {
|
|||||||
*/
|
*/
|
||||||
long getTotalBufferedDuration();
|
long getTotalBufferedDuration();
|
||||||
|
|
||||||
/** @deprecated Use {@link #isCurrentMediaItemDynamic()} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link #isCurrentMediaItemDynamic()} instead.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
boolean isCurrentWindowDynamic();
|
boolean isCurrentWindowDynamic();
|
||||||
@ -2275,7 +2295,9 @@ public interface Player {
|
|||||||
*/
|
*/
|
||||||
boolean isCurrentMediaItemDynamic();
|
boolean isCurrentMediaItemDynamic();
|
||||||
|
|
||||||
/** @deprecated Use {@link #isCurrentMediaItemLive()} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link #isCurrentMediaItemLive()} instead.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
boolean isCurrentWindowLive();
|
boolean isCurrentWindowLive();
|
||||||
@ -2301,7 +2323,9 @@ public interface Player {
|
|||||||
*/
|
*/
|
||||||
long getCurrentLiveOffset();
|
long getCurrentLiveOffset();
|
||||||
|
|
||||||
/** @deprecated Use {@link #isCurrentMediaItemSeekable()} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link #isCurrentMediaItemSeekable()} instead.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
boolean isCurrentWindowSeekable();
|
boolean isCurrentWindowSeekable();
|
||||||
@ -2465,8 +2489,8 @@ public interface Player {
|
|||||||
*/
|
*/
|
||||||
VideoSize getVideoSize();
|
VideoSize getVideoSize();
|
||||||
|
|
||||||
/** Returns the current {@link Cue Cues}. This list may be empty. */
|
/** Returns the current {@link CueGroup}. */
|
||||||
List<Cue> getCurrentCues();
|
CueGroup getCurrentCues();
|
||||||
|
|
||||||
/** Gets the device information. */
|
/** Gets the device information. */
|
||||||
DeviceInfo getDeviceInfo();
|
DeviceInfo getDeviceInfo();
|
||||||
|
@ -32,7 +32,7 @@ import java.lang.annotation.Target;
|
|||||||
public abstract class Rating implements Bundleable {
|
public abstract class Rating implements Bundleable {
|
||||||
|
|
||||||
/** A float value that denotes the rating is unset. */
|
/** A float value that denotes the rating is unset. */
|
||||||
public static final float RATING_UNSET = -1.0f;
|
/* package */ static final float RATING_UNSET = -1.0f;
|
||||||
|
|
||||||
// Default package-private constructor to prevent extending Rating class outside this package.
|
// Default package-private constructor to prevent extending Rating class outside this package.
|
||||||
/* package */ Rating() {}
|
/* package */ Rating() {}
|
||||||
|
@ -44,7 +44,9 @@ public final class StreamKey implements Comparable<StreamKey>, Parcelable {
|
|||||||
/** The stream index. */
|
/** The stream index. */
|
||||||
public final int streamIndex;
|
public final int streamIndex;
|
||||||
|
|
||||||
/** @deprecated Use {@link #streamIndex}. */
|
/**
|
||||||
|
* @deprecated Use {@link #streamIndex}.
|
||||||
|
*/
|
||||||
@Deprecated public final int trackIndex;
|
@Deprecated public final int trackIndex;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -169,7 +169,9 @@ public abstract class Timeline implements Bundleable {
|
|||||||
*/
|
*/
|
||||||
public Object uid;
|
public Object uid;
|
||||||
|
|
||||||
/** @deprecated Use {@link #mediaItem} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link #mediaItem} instead.
|
||||||
|
*/
|
||||||
@UnstableApi @Deprecated @Nullable public Object tag;
|
@UnstableApi @Deprecated @Nullable public Object tag;
|
||||||
|
|
||||||
/** The {@link MediaItem} associated to the window. Not necessarily unique. */
|
/** The {@link MediaItem} associated to the window. Not necessarily unique. */
|
||||||
@ -212,7 +214,9 @@ public abstract class Timeline implements Bundleable {
|
|||||||
/** Whether this window may change when the timeline is updated. */
|
/** Whether this window may change when the timeline is updated. */
|
||||||
public boolean isDynamic;
|
public boolean isDynamic;
|
||||||
|
|
||||||
/** @deprecated Use {@link #isLive()} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link #isLive()} instead.
|
||||||
|
*/
|
||||||
@UnstableApi @Deprecated public boolean isLive;
|
@UnstableApi @Deprecated public boolean isLive;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1178,7 +1182,9 @@ public abstract class Timeline implements Bundleable {
|
|||||||
== C.INDEX_UNSET;
|
== C.INDEX_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Use {@link #getPeriodPositionUs(Window, Period, int, long)} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link #getPeriodPositionUs(Window, Period, int, long)} instead.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@InlineMe(replacement = "this.getPeriodPositionUs(window, period, windowIndex, windowPositionUs)")
|
@InlineMe(replacement = "this.getPeriodPositionUs(window, period, windowIndex, windowPositionUs)")
|
||||||
@ -1186,7 +1192,9 @@ public abstract class Timeline implements Bundleable {
|
|||||||
Window window, Period period, int windowIndex, long windowPositionUs) {
|
Window window, Period period, int windowIndex, long windowPositionUs) {
|
||||||
return getPeriodPositionUs(window, period, windowIndex, windowPositionUs);
|
return getPeriodPositionUs(window, period, windowIndex, windowPositionUs);
|
||||||
}
|
}
|
||||||
/** @deprecated Use {@link #getPeriodPositionUs(Window, Period, int, long, long)} instead. */
|
/**
|
||||||
|
* @deprecated Use {@link #getPeriodPositionUs(Window, Period, int, long, long)} instead.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@Nullable
|
@Nullable
|
||||||
|
@ -34,15 +34,35 @@ import java.lang.annotation.Target;
|
|||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/** Defines an immutable group of tracks identified by their format identity. */
|
/**
|
||||||
|
* An immutable group of tracks available within a media stream. All tracks in a group present the
|
||||||
|
* same content, but their formats may differ.
|
||||||
|
*
|
||||||
|
* <p>As an example of how tracks can be grouped, consider an adaptive playback where a main video
|
||||||
|
* feed is provided in five resolutions, and an alternative video feed (e.g., a different camera
|
||||||
|
* angle in a sports match) is provided in two resolutions. In this case there will be two video
|
||||||
|
* track groups, one corresponding to the main video feed containing five tracks, and a second for
|
||||||
|
* the alternative video feed containing two tracks.
|
||||||
|
*
|
||||||
|
* <p>Note that audio tracks whose languages differ are not grouped, because content in different
|
||||||
|
* languages is not considered to be the same. Conversely, audio tracks in the same language that
|
||||||
|
* only differ in properties such as bitrate, sampling rate, channel count and so on can be grouped.
|
||||||
|
* This also applies to text tracks.
|
||||||
|
*
|
||||||
|
* <p>Note also that this class only contains information derived from the media itself. Unlike
|
||||||
|
* {@link Tracks.Group}, it does not include runtime information such as the extent to which
|
||||||
|
* playback of each track is supported by the device, or which tracks are currently selected.
|
||||||
|
*/
|
||||||
public final class TrackGroup implements Bundleable {
|
public final class TrackGroup implements Bundleable {
|
||||||
|
|
||||||
private static final String TAG = "TrackGroup";
|
private static final String TAG = "TrackGroup";
|
||||||
|
|
||||||
/** The number of tracks in the group. */
|
/** The number of tracks in the group. */
|
||||||
public final int length;
|
@UnstableApi public final int length;
|
||||||
/** An identifier for the track group. */
|
/** An identifier for the track group. */
|
||||||
public final String id;
|
@UnstableApi public final String id;
|
||||||
|
/** The type of tracks in the group. */
|
||||||
|
@UnstableApi public final @C.TrackType int type;
|
||||||
|
|
||||||
private final Format[] formats;
|
private final Format[] formats;
|
||||||
|
|
||||||
@ -71,6 +91,11 @@ public final class TrackGroup implements Bundleable {
|
|||||||
this.id = id;
|
this.id = id;
|
||||||
this.formats = formats;
|
this.formats = formats;
|
||||||
this.length = formats.length;
|
this.length = formats.length;
|
||||||
|
@C.TrackType int type = MimeTypes.getTrackType(formats[0].sampleMimeType);
|
||||||
|
if (type == C.TRACK_TYPE_UNKNOWN) {
|
||||||
|
type = MimeTypes.getTrackType(formats[0].containerMimeType);
|
||||||
|
}
|
||||||
|
this.type = type;
|
||||||
verifyCorrectness();
|
verifyCorrectness();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -92,6 +117,7 @@ public final class TrackGroup implements Bundleable {
|
|||||||
* @param index The index of the track.
|
* @param index The index of the track.
|
||||||
* @return The track's format.
|
* @return The track's format.
|
||||||
*/
|
*/
|
||||||
|
@UnstableApi
|
||||||
public Format getFormat(int index) {
|
public Format getFormat(int index) {
|
||||||
return formats[index];
|
return formats[index];
|
||||||
}
|
}
|
||||||
@ -105,6 +131,7 @@ public final class TrackGroup implements Bundleable {
|
|||||||
* @return The index of the track, or {@link C#INDEX_UNSET} if no such track exists.
|
* @return The index of the track, or {@link C#INDEX_UNSET} if no such track exists.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("ReferenceEquality")
|
@SuppressWarnings("ReferenceEquality")
|
||||||
|
@UnstableApi
|
||||||
public int indexOf(Format format) {
|
public int indexOf(Format format) {
|
||||||
for (int i = 0; i < formats.length; i++) {
|
for (int i = 0; i < formats.length; i++) {
|
||||||
if (format == formats[i]) {
|
if (format == formats[i]) {
|
||||||
@ -134,7 +161,7 @@ public final class TrackGroup implements Bundleable {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
TrackGroup other = (TrackGroup) obj;
|
TrackGroup other = (TrackGroup) obj;
|
||||||
return length == other.length && id.equals(other.id) && Arrays.equals(formats, other.formats);
|
return id.equals(other.id) && Arrays.equals(formats, other.formats);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bundleable implementation.
|
// Bundleable implementation.
|
||||||
@ -162,11 +189,12 @@ public final class TrackGroup implements Bundleable {
|
|||||||
@UnstableApi
|
@UnstableApi
|
||||||
public static final Creator<TrackGroup> CREATOR =
|
public static final Creator<TrackGroup> CREATOR =
|
||||||
bundle -> {
|
bundle -> {
|
||||||
|
@Nullable
|
||||||
|
List<Bundle> formatBundles = bundle.getParcelableArrayList(keyForField(FIELD_FORMATS));
|
||||||
List<Format> formats =
|
List<Format> formats =
|
||||||
BundleableUtil.fromBundleNullableList(
|
formatBundles == null
|
||||||
Format.CREATOR,
|
? ImmutableList.of()
|
||||||
bundle.getParcelableArrayList(keyForField(FIELD_FORMATS)),
|
: BundleableUtil.fromBundleList(Format.CREATOR, formatBundles);
|
||||||
ImmutableList.of());
|
|
||||||
String id = bundle.getString(keyForField(FIELD_ID), /* defaultValue= */ "");
|
String id = bundle.getString(keyForField(FIELD_ID), /* defaultValue= */ "");
|
||||||
return new TrackGroup(id, formats.toArray(new Format[0]));
|
return new TrackGroup(id, formats.toArray(new Format[0]));
|
||||||
};
|
};
|
||||||
|
@ -0,0 +1,140 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2021 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package androidx.media3.common;
|
||||||
|
|
||||||
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
|
import static java.util.Collections.max;
|
||||||
|
import static java.util.Collections.min;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
|
import androidx.annotation.IntDef;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.primitives.Ints;
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A track selection override, consisting of a {@link TrackGroup} and the indices of the tracks
|
||||||
|
* within the group that should be selected.
|
||||||
|
*
|
||||||
|
* <p>A track selection override is applied during playback if the media being played contains a
|
||||||
|
* {@link TrackGroup} equal to the one in the override. If a {@link TrackSelectionParameters}
|
||||||
|
* contains only one override of a given track type that applies to the media, this override will be
|
||||||
|
* used to control the track selection for that type. If multiple overrides of a given track type
|
||||||
|
* apply then the player will apply only one of them.
|
||||||
|
*
|
||||||
|
* <p>If {@link #trackIndices} is empty then the override specifies that no tracks should be
|
||||||
|
* selected. Adding an empty override to a {@link TrackSelectionParameters} is similar to {@link
|
||||||
|
* TrackSelectionParameters.Builder#setTrackTypeDisabled disabling a track type}, except that an
|
||||||
|
* empty override will only be applied if the media being played contains a {@link TrackGroup} equal
|
||||||
|
* to the one in the override. Conversely, disabling a track type will prevent selection of tracks
|
||||||
|
* of that type for all media.
|
||||||
|
*/
|
||||||
|
public final class TrackSelectionOverride implements Bundleable {
|
||||||
|
|
||||||
|
/** The media {@link TrackGroup} whose {@link #trackIndices} are forced to be selected. */
|
||||||
|
public final TrackGroup mediaTrackGroup;
|
||||||
|
/** The indices of tracks in a {@link TrackGroup} to be selected. */
|
||||||
|
public final ImmutableList<Integer> trackIndices;
|
||||||
|
|
||||||
|
@Documented
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@IntDef({
|
||||||
|
FIELD_TRACK_GROUP,
|
||||||
|
FIELD_TRACKS,
|
||||||
|
})
|
||||||
|
private @interface FieldNumber {}
|
||||||
|
|
||||||
|
private static final int FIELD_TRACK_GROUP = 0;
|
||||||
|
private static final int FIELD_TRACKS = 1;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance to force {@code trackIndex} in {@code trackGroup} to be selected.
|
||||||
|
*
|
||||||
|
* @param mediaTrackGroup The media {@link TrackGroup} for which to override the track selection.
|
||||||
|
* @param trackIndex The index of the track in the {@link TrackGroup} to select.
|
||||||
|
*/
|
||||||
|
public TrackSelectionOverride(TrackGroup mediaTrackGroup, int trackIndex) {
|
||||||
|
this(mediaTrackGroup, ImmutableList.of(trackIndex));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance to force {@code trackIndices} in {@code trackGroup} to be selected.
|
||||||
|
*
|
||||||
|
* @param mediaTrackGroup The media {@link TrackGroup} for which to override the track selection.
|
||||||
|
* @param trackIndices The indices of the tracks in the {@link TrackGroup} to select.
|
||||||
|
*/
|
||||||
|
public TrackSelectionOverride(TrackGroup mediaTrackGroup, List<Integer> trackIndices) {
|
||||||
|
if (!trackIndices.isEmpty()) {
|
||||||
|
if (min(trackIndices) < 0 || max(trackIndices) >= mediaTrackGroup.length) {
|
||||||
|
throw new IndexOutOfBoundsException();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
this.mediaTrackGroup = mediaTrackGroup;
|
||||||
|
this.trackIndices = ImmutableList.copyOf(trackIndices);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the {@link C.TrackType} of the overridden track group. */
|
||||||
|
public @C.TrackType int getType() {
|
||||||
|
return mediaTrackGroup.type;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean equals(@Nullable Object obj) {
|
||||||
|
if (this == obj) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (obj == null || getClass() != obj.getClass()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
TrackSelectionOverride that = (TrackSelectionOverride) obj;
|
||||||
|
return mediaTrackGroup.equals(that.mediaTrackGroup) && trackIndices.equals(that.trackIndices);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return mediaTrackGroup.hashCode() + 31 * trackIndices.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bundleable implementation
|
||||||
|
|
||||||
|
@UnstableApi
|
||||||
|
@Override
|
||||||
|
public Bundle toBundle() {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putBundle(keyForField(FIELD_TRACK_GROUP), mediaTrackGroup.toBundle());
|
||||||
|
bundle.putIntArray(keyForField(FIELD_TRACKS), Ints.toArray(trackIndices));
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Object that can restore {@code TrackSelectionOverride} from a {@link Bundle}. */
|
||||||
|
@UnstableApi
|
||||||
|
public static final Creator<TrackSelectionOverride> CREATOR =
|
||||||
|
bundle -> {
|
||||||
|
Bundle trackGroupBundle = checkNotNull(bundle.getBundle(keyForField(FIELD_TRACK_GROUP)));
|
||||||
|
TrackGroup mediaTrackGroup = TrackGroup.CREATOR.fromBundle(trackGroupBundle);
|
||||||
|
int[] tracks = checkNotNull(bundle.getIntArray(keyForField(FIELD_TRACKS)));
|
||||||
|
return new TrackSelectionOverride(mediaTrackGroup, Ints.asList(tracks));
|
||||||
|
};
|
||||||
|
|
||||||
|
private static String keyForField(@FieldNumber int field) {
|
||||||
|
return Integer.toString(field, Character.MAX_RADIX);
|
||||||
|
}
|
||||||
|
}
|
@ -1,313 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2021 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package androidx.media3.common;
|
|
||||||
|
|
||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
|
||||||
import static androidx.media3.common.util.BundleableUtil.fromBundleNullableList;
|
|
||||||
import static androidx.media3.common.util.BundleableUtil.toBundleArrayList;
|
|
||||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
|
||||||
import static java.util.Collections.max;
|
|
||||||
import static java.util.Collections.min;
|
|
||||||
|
|
||||||
import android.os.Bundle;
|
|
||||||
import androidx.annotation.IntDef;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.media3.common.util.UnstableApi;
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import com.google.common.collect.ImmutableMap;
|
|
||||||
import com.google.common.primitives.Ints;
|
|
||||||
import java.lang.annotation.Documented;
|
|
||||||
import java.lang.annotation.Retention;
|
|
||||||
import java.lang.annotation.RetentionPolicy;
|
|
||||||
import java.lang.annotation.Target;
|
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Iterator;
|
|
||||||
import java.util.List;
|
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Forces the selection of the specified tracks in {@link TrackGroup TrackGroups}.
|
|
||||||
*
|
|
||||||
* <p>Each {@link TrackSelectionOverride override} only affects the selection of tracks of that
|
|
||||||
* {@link C.TrackType type}. For example overriding the selection of an {@link C#TRACK_TYPE_AUDIO
|
|
||||||
* audio} {@link TrackGroup} will not affect the selection of {@link C#TRACK_TYPE_VIDEO video} or
|
|
||||||
* {@link C#TRACK_TYPE_TEXT text} tracks.
|
|
||||||
*
|
|
||||||
* <p>If multiple {@link TrackGroup TrackGroups} of the same {@link C.TrackType} are overridden,
|
|
||||||
* which tracks will be selected depend on the player capabilities. For example, by default {@code
|
|
||||||
* ExoPlayer} doesn't support selecting more than one {@link TrackGroup} per {@link C.TrackType}.
|
|
||||||
*
|
|
||||||
* <p>Overrides of {@link TrackGroup} that are not currently available are ignored. For example,
|
|
||||||
* when the player transitions to the next {@link MediaItem} in a playlist, any overrides of the
|
|
||||||
* previous {@link MediaItem} are ignored.
|
|
||||||
*
|
|
||||||
* @see TrackSelectionParameters#trackSelectionOverrides
|
|
||||||
*/
|
|
||||||
public final class TrackSelectionOverrides implements Bundleable {
|
|
||||||
|
|
||||||
/** Builder for {@link TrackSelectionOverrides}. */
|
|
||||||
public static final class Builder {
|
|
||||||
// Cannot use ImmutableMap.Builder as it doesn't support removing entries.
|
|
||||||
private final HashMap<TrackGroup, TrackSelectionOverride> overrides;
|
|
||||||
|
|
||||||
/** Creates an builder with no {@link TrackSelectionOverride}. */
|
|
||||||
public Builder() {
|
|
||||||
overrides = new HashMap<>();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Builder(Map<TrackGroup, TrackSelectionOverride> overrides) {
|
|
||||||
this.overrides = new HashMap<>(overrides);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Adds an override for the provided {@link TrackGroup}. */
|
|
||||||
@UnstableApi
|
|
||||||
public Builder addOverride(TrackSelectionOverride override) {
|
|
||||||
overrides.put(override.trackGroup, override);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Removes the override associated with the provided {@link TrackGroup} if present. */
|
|
||||||
@UnstableApi
|
|
||||||
public Builder clearOverride(TrackGroup trackGroup) {
|
|
||||||
overrides.remove(trackGroup);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Set the override for the type of the provided {@link TrackGroup}. */
|
|
||||||
public Builder setOverrideForType(TrackSelectionOverride override) {
|
|
||||||
clearOverridesOfType(override.getTrackType());
|
|
||||||
overrides.put(override.trackGroup, override);
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Remove any override associated with {@link TrackGroup TrackGroups} of type {@code trackType}.
|
|
||||||
*/
|
|
||||||
public Builder clearOverridesOfType(@C.TrackType int trackType) {
|
|
||||||
for (Iterator<TrackSelectionOverride> it = overrides.values().iterator(); it.hasNext(); ) {
|
|
||||||
TrackSelectionOverride trackSelectionOverride = it.next();
|
|
||||||
if (trackSelectionOverride.getTrackType() == trackType) {
|
|
||||||
it.remove();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return this;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns a new {@link TrackSelectionOverrides} instance with the current builder values. */
|
|
||||||
public TrackSelectionOverrides build() {
|
|
||||||
return new TrackSelectionOverrides(overrides);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Forces the selection of {@link #trackIndices} for a {@link TrackGroup}.
|
|
||||||
*
|
|
||||||
* <p>If multiple tracks in {@link #trackGroup} are overridden, as many as possible will be
|
|
||||||
* selected depending on the player capabilities.
|
|
||||||
*
|
|
||||||
* <p>If {@link #trackIndices} is empty, no tracks from {@link #trackGroup} will be played. This
|
|
||||||
* is similar to {@link TrackSelectionParameters#disabledTrackTypes}, except it will only affect
|
|
||||||
* the playback of the associated {@link TrackGroup}. For example, if the only {@link
|
|
||||||
* C#TRACK_TYPE_VIDEO} {@link TrackGroup} is associated with no tracks, no video will play until
|
|
||||||
* the next video starts.
|
|
||||||
*/
|
|
||||||
public static final class TrackSelectionOverride implements Bundleable {
|
|
||||||
|
|
||||||
/** The {@link TrackGroup} whose {@link #trackIndices} are forced to be selected. */
|
|
||||||
public final TrackGroup trackGroup;
|
|
||||||
/** The indices of tracks in a {@link TrackGroup} to be selected. */
|
|
||||||
public final ImmutableList<Integer> trackIndices;
|
|
||||||
|
|
||||||
/** Constructs an instance to force all tracks in {@code trackGroup} to be selected. */
|
|
||||||
public TrackSelectionOverride(TrackGroup trackGroup) {
|
|
||||||
this.trackGroup = trackGroup;
|
|
||||||
ImmutableList.Builder<Integer> builder = new ImmutableList.Builder<>();
|
|
||||||
for (int i = 0; i < trackGroup.length; i++) {
|
|
||||||
builder.add(i);
|
|
||||||
}
|
|
||||||
this.trackIndices = builder.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Constructs an instance to force {@code trackIndices} in {@code trackGroup} to be selected.
|
|
||||||
*
|
|
||||||
* @param trackGroup The {@link TrackGroup} for which to override the track selection.
|
|
||||||
* @param trackIndices The indices of the tracks in the {@link TrackGroup} to select.
|
|
||||||
*/
|
|
||||||
public TrackSelectionOverride(TrackGroup trackGroup, List<Integer> trackIndices) {
|
|
||||||
if (!trackIndices.isEmpty()) {
|
|
||||||
if (min(trackIndices) < 0 || max(trackIndices) >= trackGroup.length) {
|
|
||||||
throw new IndexOutOfBoundsException();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
this.trackGroup = trackGroup;
|
|
||||||
this.trackIndices = ImmutableList.copyOf(trackIndices);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(@Nullable Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null || getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
TrackSelectionOverride that = (TrackSelectionOverride) obj;
|
|
||||||
return trackGroup.equals(that.trackGroup) && trackIndices.equals(that.trackIndices);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return trackGroup.hashCode() + 31 * trackIndices.hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns the {@link C.TrackType} of the overriden track group. */
|
|
||||||
public @C.TrackType int getTrackType() {
|
|
||||||
return MimeTypes.getTrackType(trackGroup.getFormat(0).sampleMimeType);
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bundleable implementation
|
|
||||||
|
|
||||||
@Documented
|
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
|
||||||
@Target(TYPE_USE)
|
|
||||||
@IntDef({
|
|
||||||
FIELD_TRACK_GROUP,
|
|
||||||
FIELD_TRACKS,
|
|
||||||
})
|
|
||||||
private @interface FieldNumber {}
|
|
||||||
|
|
||||||
private static final int FIELD_TRACK_GROUP = 0;
|
|
||||||
private static final int FIELD_TRACKS = 1;
|
|
||||||
|
|
||||||
@UnstableApi
|
|
||||||
@Override
|
|
||||||
public Bundle toBundle() {
|
|
||||||
Bundle bundle = new Bundle();
|
|
||||||
bundle.putBundle(keyForField(FIELD_TRACK_GROUP), trackGroup.toBundle());
|
|
||||||
bundle.putIntArray(keyForField(FIELD_TRACKS), Ints.toArray(trackIndices));
|
|
||||||
return bundle;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Object that can restore {@code TrackSelectionOverride} from a {@link Bundle}. */
|
|
||||||
@UnstableApi
|
|
||||||
public static final Creator<TrackSelectionOverride> CREATOR =
|
|
||||||
bundle -> {
|
|
||||||
@Nullable Bundle trackGroupBundle = bundle.getBundle(keyForField(FIELD_TRACK_GROUP));
|
|
||||||
checkNotNull(trackGroupBundle); // Mandatory as there are no reasonable defaults.
|
|
||||||
TrackGroup trackGroup = TrackGroup.CREATOR.fromBundle(trackGroupBundle);
|
|
||||||
@Nullable int[] tracks = bundle.getIntArray(keyForField(FIELD_TRACKS));
|
|
||||||
if (tracks == null) {
|
|
||||||
return new TrackSelectionOverride(trackGroup);
|
|
||||||
}
|
|
||||||
return new TrackSelectionOverride(trackGroup, Ints.asList(tracks));
|
|
||||||
};
|
|
||||||
|
|
||||||
private static String keyForField(@FieldNumber int field) {
|
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Empty {@code TrackSelectionOverrides}, where no track selection is overridden. */
|
|
||||||
public static final TrackSelectionOverrides EMPTY =
|
|
||||||
new TrackSelectionOverrides(ImmutableMap.of());
|
|
||||||
|
|
||||||
private final ImmutableMap<TrackGroup, TrackSelectionOverride> overrides;
|
|
||||||
|
|
||||||
private TrackSelectionOverrides(Map<TrackGroup, TrackSelectionOverride> overrides) {
|
|
||||||
this.overrides = ImmutableMap.copyOf(overrides);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns a {@link Builder} initialized with the values of this instance. */
|
|
||||||
public Builder buildUpon() {
|
|
||||||
return new Builder(overrides);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns a list of the {@link TrackSelectionOverride overrides}. */
|
|
||||||
@UnstableApi
|
|
||||||
public ImmutableList<TrackSelectionOverride> asList() {
|
|
||||||
return ImmutableList.copyOf(overrides.values());
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the {@link TrackSelectionOverride} of the provided {@link TrackGroup} or {@code null}
|
|
||||||
* if there is none.
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public TrackSelectionOverride getOverride(TrackGroup trackGroup) {
|
|
||||||
return overrides.get(trackGroup);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public boolean equals(@Nullable Object obj) {
|
|
||||||
if (this == obj) {
|
|
||||||
return true;
|
|
||||||
}
|
|
||||||
if (obj == null || getClass() != obj.getClass()) {
|
|
||||||
return false;
|
|
||||||
}
|
|
||||||
TrackSelectionOverrides that = (TrackSelectionOverrides) obj;
|
|
||||||
return overrides.equals(that.overrides);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public int hashCode() {
|
|
||||||
return overrides.hashCode();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Bundleable implementation
|
|
||||||
|
|
||||||
@Documented
|
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
|
||||||
@Target(TYPE_USE)
|
|
||||||
@IntDef({
|
|
||||||
FIELD_OVERRIDES,
|
|
||||||
})
|
|
||||||
private @interface FieldNumber {}
|
|
||||||
|
|
||||||
private static final int FIELD_OVERRIDES = 0;
|
|
||||||
|
|
||||||
@UnstableApi
|
|
||||||
@Override
|
|
||||||
public Bundle toBundle() {
|
|
||||||
Bundle bundle = new Bundle();
|
|
||||||
bundle.putParcelableArrayList(
|
|
||||||
keyForField(FIELD_OVERRIDES), toBundleArrayList(overrides.values()));
|
|
||||||
return bundle;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Object that can restore {@code TrackSelectionOverrides} from a {@link Bundle}. */
|
|
||||||
@UnstableApi
|
|
||||||
public static final Creator<TrackSelectionOverrides> CREATOR =
|
|
||||||
bundle -> {
|
|
||||||
List<TrackSelectionOverride> trackSelectionOverrides =
|
|
||||||
fromBundleNullableList(
|
|
||||||
TrackSelectionOverride.CREATOR,
|
|
||||||
bundle.getParcelableArrayList(keyForField(FIELD_OVERRIDES)),
|
|
||||||
ImmutableList.of());
|
|
||||||
ImmutableMap.Builder<TrackGroup, TrackSelectionOverride> builder =
|
|
||||||
new ImmutableMap.Builder<>();
|
|
||||||
for (int i = 0; i < trackSelectionOverrides.size(); i++) {
|
|
||||||
TrackSelectionOverride trackSelectionOverride = trackSelectionOverrides.get(i);
|
|
||||||
builder.put(trackSelectionOverride.trackGroup, trackSelectionOverride);
|
|
||||||
}
|
|
||||||
return new TrackSelectionOverrides(builder.buildOrThrow());
|
|
||||||
};
|
|
||||||
|
|
||||||
private static String keyForField(@FieldNumber int field) {
|
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
|
||||||
}
|
|
||||||
}
|
|
@ -16,37 +16,38 @@
|
|||||||
package androidx.media3.common;
|
package androidx.media3.common;
|
||||||
|
|
||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
import static androidx.media3.common.util.BundleableUtil.fromNullableBundle;
|
import static androidx.media3.common.util.BundleableUtil.toBundleArrayList;
|
||||||
import static com.google.common.base.MoreObjects.firstNonNull;
|
import static com.google.common.base.MoreObjects.firstNonNull;
|
||||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.graphics.Point;
|
import android.graphics.Point;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.view.accessibility.CaptioningManager;
|
import android.view.accessibility.CaptioningManager;
|
||||||
import androidx.annotation.IntDef;
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
|
import androidx.media3.common.util.BundleableUtil;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
import com.google.common.collect.ImmutableSet;
|
import com.google.common.collect.ImmutableSet;
|
||||||
import com.google.common.primitives.Ints;
|
import com.google.common.primitives.Ints;
|
||||||
import java.lang.annotation.Documented;
|
import java.util.HashMap;
|
||||||
import java.lang.annotation.Retention;
|
import java.util.HashSet;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.util.Iterator;
|
||||||
import java.lang.annotation.Target;
|
import java.util.List;
|
||||||
import java.util.Locale;
|
import java.util.Locale;
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import org.checkerframework.checker.initialization.qual.UnknownInitialization;
|
import org.checkerframework.checker.initialization.qual.UnknownInitialization;
|
||||||
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
|
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constraint parameters for track selection.
|
* Parameters for controlling track selection.
|
||||||
*
|
*
|
||||||
* <p>For example the following code modifies the parameters to restrict video track selections to
|
* <p>Parameters can be queried and set on a {@link Player}. For example the following code modifies
|
||||||
* SD, and to select a German audio track if there is one:
|
* the parameters to restrict video track selections to SD, and to select a German audio track if
|
||||||
|
* there is one:
|
||||||
*
|
*
|
||||||
* <pre>{@code
|
* <pre>{@code
|
||||||
* // Build on the current parameters.
|
* // Build on the current parameters.
|
||||||
@ -91,12 +92,13 @@ public class TrackSelectionParameters implements Bundleable {
|
|||||||
// Text
|
// Text
|
||||||
private ImmutableList<String> preferredTextLanguages;
|
private ImmutableList<String> preferredTextLanguages;
|
||||||
private @C.RoleFlags int preferredTextRoleFlags;
|
private @C.RoleFlags int preferredTextRoleFlags;
|
||||||
|
private @C.SelectionFlags int ignoredTextSelectionFlags;
|
||||||
private boolean selectUndeterminedTextLanguage;
|
private boolean selectUndeterminedTextLanguage;
|
||||||
// General
|
// General
|
||||||
private boolean forceLowestBitrate;
|
private boolean forceLowestBitrate;
|
||||||
private boolean forceHighestSupportedBitrate;
|
private boolean forceHighestSupportedBitrate;
|
||||||
private TrackSelectionOverrides trackSelectionOverrides;
|
private HashMap<TrackGroup, TrackSelectionOverride> overrides;
|
||||||
private ImmutableSet<@C.TrackType Integer> disabledTrackTypes;
|
private HashSet<@C.TrackType Integer> disabledTrackTypes;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated {@link Context} constraints will not be set using this constructor. Use {@link
|
* @deprecated {@link Context} constraints will not be set using this constructor. Use {@link
|
||||||
@ -124,12 +126,13 @@ public class TrackSelectionParameters implements Bundleable {
|
|||||||
// Text
|
// Text
|
||||||
preferredTextLanguages = ImmutableList.of();
|
preferredTextLanguages = ImmutableList.of();
|
||||||
preferredTextRoleFlags = 0;
|
preferredTextRoleFlags = 0;
|
||||||
|
ignoredTextSelectionFlags = 0;
|
||||||
selectUndeterminedTextLanguage = false;
|
selectUndeterminedTextLanguage = false;
|
||||||
// General
|
// General
|
||||||
forceLowestBitrate = false;
|
forceLowestBitrate = false;
|
||||||
forceHighestSupportedBitrate = false;
|
forceHighestSupportedBitrate = false;
|
||||||
trackSelectionOverrides = TrackSelectionOverrides.EMPTY;
|
overrides = new HashMap<>();
|
||||||
disabledTrackTypes = ImmutableSet.of();
|
disabledTrackTypes = new HashSet<>();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -224,6 +227,10 @@ public class TrackSelectionParameters implements Bundleable {
|
|||||||
bundle.getInt(
|
bundle.getInt(
|
||||||
keyForField(FIELD_PREFERRED_TEXT_ROLE_FLAGS),
|
keyForField(FIELD_PREFERRED_TEXT_ROLE_FLAGS),
|
||||||
DEFAULT_WITHOUT_CONTEXT.preferredTextRoleFlags);
|
DEFAULT_WITHOUT_CONTEXT.preferredTextRoleFlags);
|
||||||
|
ignoredTextSelectionFlags =
|
||||||
|
bundle.getInt(
|
||||||
|
keyForField(FIELD_IGNORED_TEXT_SELECTION_FLAGS),
|
||||||
|
DEFAULT_WITHOUT_CONTEXT.ignoredTextSelectionFlags);
|
||||||
selectUndeterminedTextLanguage =
|
selectUndeterminedTextLanguage =
|
||||||
bundle.getBoolean(
|
bundle.getBoolean(
|
||||||
keyForField(FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE),
|
keyForField(FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE),
|
||||||
@ -236,16 +243,24 @@ public class TrackSelectionParameters implements Bundleable {
|
|||||||
bundle.getBoolean(
|
bundle.getBoolean(
|
||||||
keyForField(FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE),
|
keyForField(FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE),
|
||||||
DEFAULT_WITHOUT_CONTEXT.forceHighestSupportedBitrate);
|
DEFAULT_WITHOUT_CONTEXT.forceHighestSupportedBitrate);
|
||||||
trackSelectionOverrides =
|
@Nullable
|
||||||
fromNullableBundle(
|
List<Bundle> overrideBundleList =
|
||||||
TrackSelectionOverrides.CREATOR,
|
bundle.getParcelableArrayList(keyForField(FIELD_SELECTION_OVERRIDES));
|
||||||
bundle.getBundle(keyForField(FIELD_SELECTION_OVERRIDE_KEYS)),
|
List<TrackSelectionOverride> overrideList =
|
||||||
TrackSelectionOverrides.EMPTY);
|
overrideBundleList == null
|
||||||
disabledTrackTypes =
|
? ImmutableList.of()
|
||||||
ImmutableSet.copyOf(
|
: BundleableUtil.fromBundleList(TrackSelectionOverride.CREATOR, overrideBundleList);
|
||||||
Ints.asList(
|
overrides = new HashMap<>();
|
||||||
firstNonNull(
|
for (int i = 0; i < overrideList.size(); i++) {
|
||||||
bundle.getIntArray(keyForField(FIELD_DISABLED_TRACK_TYPE)), new int[0])));
|
TrackSelectionOverride override = overrideList.get(i);
|
||||||
|
overrides.put(override.mediaTrackGroup, override);
|
||||||
|
}
|
||||||
|
int[] disabledTrackTypeArray =
|
||||||
|
firstNonNull(bundle.getIntArray(keyForField(FIELD_DISABLED_TRACK_TYPE)), new int[0]);
|
||||||
|
disabledTrackTypes = new HashSet<>();
|
||||||
|
for (@C.TrackType int disabledTrackType : disabledTrackTypeArray) {
|
||||||
|
disabledTrackTypes.add(disabledTrackType);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Overrides the value of the builder with the value of {@link TrackSelectionParameters}. */
|
/** Overrides the value of the builder with the value of {@link TrackSelectionParameters}. */
|
||||||
@ -254,7 +269,7 @@ public class TrackSelectionParameters implements Bundleable {
|
|||||||
"preferredAudioLanguages",
|
"preferredAudioLanguages",
|
||||||
"preferredAudioMimeTypes",
|
"preferredAudioMimeTypes",
|
||||||
"preferredTextLanguages",
|
"preferredTextLanguages",
|
||||||
"trackSelectionOverrides",
|
"overrides",
|
||||||
"disabledTrackTypes",
|
"disabledTrackTypes",
|
||||||
})
|
})
|
||||||
private void init(@UnknownInitialization Builder this, TrackSelectionParameters parameters) {
|
private void init(@UnknownInitialization Builder this, TrackSelectionParameters parameters) {
|
||||||
@ -281,12 +296,13 @@ public class TrackSelectionParameters implements Bundleable {
|
|||||||
// Text
|
// Text
|
||||||
preferredTextLanguages = parameters.preferredTextLanguages;
|
preferredTextLanguages = parameters.preferredTextLanguages;
|
||||||
preferredTextRoleFlags = parameters.preferredTextRoleFlags;
|
preferredTextRoleFlags = parameters.preferredTextRoleFlags;
|
||||||
|
ignoredTextSelectionFlags = parameters.ignoredTextSelectionFlags;
|
||||||
selectUndeterminedTextLanguage = parameters.selectUndeterminedTextLanguage;
|
selectUndeterminedTextLanguage = parameters.selectUndeterminedTextLanguage;
|
||||||
// General
|
// General
|
||||||
forceLowestBitrate = parameters.forceLowestBitrate;
|
forceLowestBitrate = parameters.forceLowestBitrate;
|
||||||
forceHighestSupportedBitrate = parameters.forceHighestSupportedBitrate;
|
forceHighestSupportedBitrate = parameters.forceHighestSupportedBitrate;
|
||||||
trackSelectionOverrides = parameters.trackSelectionOverrides;
|
disabledTrackTypes = new HashSet<>(parameters.disabledTrackTypes);
|
||||||
disabledTrackTypes = parameters.disabledTrackTypes;
|
overrides = new HashMap<>(parameters.overrides);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Overrides the value of the builder with the value of {@link TrackSelectionParameters}. */
|
/** Overrides the value of the builder with the value of {@link TrackSelectionParameters}. */
|
||||||
@ -604,6 +620,18 @@ public class TrackSelectionParameters implements Bundleable {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a bitmask of selection flags that are ignored for text track selections.
|
||||||
|
*
|
||||||
|
* @param ignoredTextSelectionFlags A bitmask of {@link C.SelectionFlags} that are ignored for
|
||||||
|
* text track selections.
|
||||||
|
* @return This builder.
|
||||||
|
*/
|
||||||
|
public Builder setIgnoredTextSelectionFlags(@C.SelectionFlags int ignoredTextSelectionFlags) {
|
||||||
|
this.ignoredTextSelectionFlags = ignoredTextSelectionFlags;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets whether a text track with undetermined language should be selected if no track with
|
* Sets whether a text track with undetermined language should be selected if no track with
|
||||||
* {@link #setPreferredTextLanguages(String...) a preferred language} is available, or if the
|
* {@link #setPreferredTextLanguages(String...) a preferred language} is available, or if the
|
||||||
@ -646,26 +674,73 @@ public class TrackSelectionParameters implements Bundleable {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Adds an override, replacing any override for the same {@link TrackGroup}. */
|
||||||
* Sets the selection overrides.
|
public Builder addOverride(TrackSelectionOverride override) {
|
||||||
*
|
overrides.put(override.mediaTrackGroup, override);
|
||||||
* @param trackSelectionOverrides The track selection overrides.
|
return this;
|
||||||
* @return This builder.
|
}
|
||||||
*/
|
|
||||||
public Builder setTrackSelectionOverrides(TrackSelectionOverrides trackSelectionOverrides) {
|
/** Sets an override, replacing all existing overrides with the same track type. */
|
||||||
this.trackSelectionOverrides = trackSelectionOverrides;
|
public Builder setOverrideForType(TrackSelectionOverride override) {
|
||||||
|
clearOverridesOfType(override.getType());
|
||||||
|
overrides.put(override.mediaTrackGroup, override);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Removes the override for the provided media {@link TrackGroup}, if there is one. */
|
||||||
|
public Builder clearOverride(TrackGroup mediaTrackGroup) {
|
||||||
|
overrides.remove(mediaTrackGroup);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Removes all overrides of the provided track type. */
|
||||||
|
public Builder clearOverridesOfType(@C.TrackType int trackType) {
|
||||||
|
Iterator<TrackSelectionOverride> it = overrides.values().iterator();
|
||||||
|
while (it.hasNext()) {
|
||||||
|
TrackSelectionOverride override = it.next();
|
||||||
|
if (override.getType() == trackType) {
|
||||||
|
it.remove();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Removes all overrides. */
|
||||||
|
public Builder clearOverrides() {
|
||||||
|
overrides.clear();
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the disabled track types, preventing all tracks of those types from being selected for
|
* Sets the disabled track types, preventing all tracks of those types from being selected for
|
||||||
* playback.
|
* playback. Any previously disabled track types are cleared.
|
||||||
*
|
*
|
||||||
* @param disabledTrackTypes The track types to disable.
|
* @param disabledTrackTypes The track types to disable.
|
||||||
* @return This builder.
|
* @return This builder.
|
||||||
|
* @deprecated Use {@link #setTrackTypeDisabled(int, boolean)}.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
|
@UnstableApi
|
||||||
public Builder setDisabledTrackTypes(Set<@C.TrackType Integer> disabledTrackTypes) {
|
public Builder setDisabledTrackTypes(Set<@C.TrackType Integer> disabledTrackTypes) {
|
||||||
this.disabledTrackTypes = ImmutableSet.copyOf(disabledTrackTypes);
|
this.disabledTrackTypes.clear();
|
||||||
|
this.disabledTrackTypes.addAll(disabledTrackTypes);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets whether a track type is disabled. If disabled, no tracks of the specified type will be
|
||||||
|
* selected for playback.
|
||||||
|
*
|
||||||
|
* @param trackType The track type.
|
||||||
|
* @param disabled Whether the track type should be disabled.
|
||||||
|
* @return This builder.
|
||||||
|
*/
|
||||||
|
public Builder setTrackTypeDisabled(@C.TrackType int trackType, boolean disabled) {
|
||||||
|
if (disabled) {
|
||||||
|
disabledTrackTypes.add(trackType);
|
||||||
|
} else {
|
||||||
|
disabledTrackTypes.remove(trackType);
|
||||||
|
}
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -842,6 +917,11 @@ public class TrackSelectionParameters implements Bundleable {
|
|||||||
* is enabled.
|
* is enabled.
|
||||||
*/
|
*/
|
||||||
public final @C.RoleFlags int preferredTextRoleFlags;
|
public final @C.RoleFlags int preferredTextRoleFlags;
|
||||||
|
/**
|
||||||
|
* Bitmask of selection flags that are ignored for text track selections. See {@link
|
||||||
|
* C.SelectionFlags}. The default value is {@code 0} (i.e., no flags are ignored).
|
||||||
|
*/
|
||||||
|
public final @C.SelectionFlags int ignoredTextSelectionFlags;
|
||||||
/**
|
/**
|
||||||
* Whether a text track with undetermined language should be selected if no track with {@link
|
* Whether a text track with undetermined language should be selected if no track with {@link
|
||||||
* #preferredTextLanguages} is available, or if {@link #preferredTextLanguages} is unset. The
|
* #preferredTextLanguages} is available, or if {@link #preferredTextLanguages} is unset. The
|
||||||
@ -860,8 +940,9 @@ public class TrackSelectionParameters implements Bundleable {
|
|||||||
*/
|
*/
|
||||||
public final boolean forceHighestSupportedBitrate;
|
public final boolean forceHighestSupportedBitrate;
|
||||||
|
|
||||||
/** Overrides to force tracks to be selected. */
|
/** Overrides to force selection of specific tracks. */
|
||||||
public final TrackSelectionOverrides trackSelectionOverrides;
|
public final ImmutableMap<TrackGroup, TrackSelectionOverride> overrides;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The track types that are disabled. No track of a disabled type will be selected, thus no track
|
* The track types that are disabled. No track of a disabled type will be selected, thus no track
|
||||||
* type contained in the set will be played. The default value is that no track type is disabled
|
* type contained in the set will be played. The default value is that no track type is disabled
|
||||||
@ -894,12 +975,13 @@ public class TrackSelectionParameters implements Bundleable {
|
|||||||
// Text
|
// Text
|
||||||
this.preferredTextLanguages = builder.preferredTextLanguages;
|
this.preferredTextLanguages = builder.preferredTextLanguages;
|
||||||
this.preferredTextRoleFlags = builder.preferredTextRoleFlags;
|
this.preferredTextRoleFlags = builder.preferredTextRoleFlags;
|
||||||
|
this.ignoredTextSelectionFlags = builder.ignoredTextSelectionFlags;
|
||||||
this.selectUndeterminedTextLanguage = builder.selectUndeterminedTextLanguage;
|
this.selectUndeterminedTextLanguage = builder.selectUndeterminedTextLanguage;
|
||||||
// General
|
// General
|
||||||
this.forceLowestBitrate = builder.forceLowestBitrate;
|
this.forceLowestBitrate = builder.forceLowestBitrate;
|
||||||
this.forceHighestSupportedBitrate = builder.forceHighestSupportedBitrate;
|
this.forceHighestSupportedBitrate = builder.forceHighestSupportedBitrate;
|
||||||
this.trackSelectionOverrides = builder.trackSelectionOverrides;
|
this.overrides = ImmutableMap.copyOf(builder.overrides);
|
||||||
this.disabledTrackTypes = builder.disabledTrackTypes;
|
this.disabledTrackTypes = ImmutableSet.copyOf(builder.disabledTrackTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Creates a new {@link Builder}, copying the initial values from this instance. */
|
/** Creates a new {@link Builder}, copying the initial values from this instance. */
|
||||||
@ -937,13 +1019,15 @@ public class TrackSelectionParameters implements Bundleable {
|
|||||||
&& maxAudioChannelCount == other.maxAudioChannelCount
|
&& maxAudioChannelCount == other.maxAudioChannelCount
|
||||||
&& maxAudioBitrate == other.maxAudioBitrate
|
&& maxAudioBitrate == other.maxAudioBitrate
|
||||||
&& preferredAudioMimeTypes.equals(other.preferredAudioMimeTypes)
|
&& preferredAudioMimeTypes.equals(other.preferredAudioMimeTypes)
|
||||||
|
// Text
|
||||||
&& preferredTextLanguages.equals(other.preferredTextLanguages)
|
&& preferredTextLanguages.equals(other.preferredTextLanguages)
|
||||||
&& preferredTextRoleFlags == other.preferredTextRoleFlags
|
&& preferredTextRoleFlags == other.preferredTextRoleFlags
|
||||||
|
&& ignoredTextSelectionFlags == other.ignoredTextSelectionFlags
|
||||||
&& selectUndeterminedTextLanguage == other.selectUndeterminedTextLanguage
|
&& selectUndeterminedTextLanguage == other.selectUndeterminedTextLanguage
|
||||||
// General
|
// General
|
||||||
&& forceLowestBitrate == other.forceLowestBitrate
|
&& forceLowestBitrate == other.forceLowestBitrate
|
||||||
&& forceHighestSupportedBitrate == other.forceHighestSupportedBitrate
|
&& forceHighestSupportedBitrate == other.forceHighestSupportedBitrate
|
||||||
&& trackSelectionOverrides.equals(other.trackSelectionOverrides)
|
&& overrides.equals(other.overrides)
|
||||||
&& disabledTrackTypes.equals(other.disabledTrackTypes);
|
&& disabledTrackTypes.equals(other.disabledTrackTypes);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -973,50 +1057,18 @@ public class TrackSelectionParameters implements Bundleable {
|
|||||||
// Text
|
// Text
|
||||||
result = 31 * result + preferredTextLanguages.hashCode();
|
result = 31 * result + preferredTextLanguages.hashCode();
|
||||||
result = 31 * result + preferredTextRoleFlags;
|
result = 31 * result + preferredTextRoleFlags;
|
||||||
|
result = 31 * result + ignoredTextSelectionFlags;
|
||||||
result = 31 * result + (selectUndeterminedTextLanguage ? 1 : 0);
|
result = 31 * result + (selectUndeterminedTextLanguage ? 1 : 0);
|
||||||
// General
|
// General
|
||||||
result = 31 * result + (forceLowestBitrate ? 1 : 0);
|
result = 31 * result + (forceLowestBitrate ? 1 : 0);
|
||||||
result = 31 * result + (forceHighestSupportedBitrate ? 1 : 0);
|
result = 31 * result + (forceHighestSupportedBitrate ? 1 : 0);
|
||||||
result = 31 * result + trackSelectionOverrides.hashCode();
|
result = 31 * result + overrides.hashCode();
|
||||||
result = 31 * result + disabledTrackTypes.hashCode();
|
result = 31 * result + disabledTrackTypes.hashCode();
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bundleable implementation
|
// Bundleable implementation
|
||||||
|
|
||||||
@Documented
|
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
|
||||||
@Target(TYPE_USE)
|
|
||||||
@IntDef({
|
|
||||||
FIELD_PREFERRED_AUDIO_LANGUAGES,
|
|
||||||
FIELD_PREFERRED_AUDIO_ROLE_FLAGS,
|
|
||||||
FIELD_PREFERRED_TEXT_LANGUAGES,
|
|
||||||
FIELD_PREFERRED_TEXT_ROLE_FLAGS,
|
|
||||||
FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE,
|
|
||||||
FIELD_MAX_VIDEO_WIDTH,
|
|
||||||
FIELD_MAX_VIDEO_HEIGHT,
|
|
||||||
FIELD_MAX_VIDEO_FRAMERATE,
|
|
||||||
FIELD_MAX_VIDEO_BITRATE,
|
|
||||||
FIELD_MIN_VIDEO_WIDTH,
|
|
||||||
FIELD_MIN_VIDEO_HEIGHT,
|
|
||||||
FIELD_MIN_VIDEO_FRAMERATE,
|
|
||||||
FIELD_MIN_VIDEO_BITRATE,
|
|
||||||
FIELD_VIEWPORT_WIDTH,
|
|
||||||
FIELD_VIEWPORT_HEIGHT,
|
|
||||||
FIELD_VIEWPORT_ORIENTATION_MAY_CHANGE,
|
|
||||||
FIELD_PREFERRED_VIDEO_MIMETYPES,
|
|
||||||
FIELD_MAX_AUDIO_CHANNEL_COUNT,
|
|
||||||
FIELD_MAX_AUDIO_BITRATE,
|
|
||||||
FIELD_PREFERRED_AUDIO_MIME_TYPES,
|
|
||||||
FIELD_FORCE_LOWEST_BITRATE,
|
|
||||||
FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE,
|
|
||||||
FIELD_SELECTION_OVERRIDE_KEYS,
|
|
||||||
FIELD_SELECTION_OVERRIDE_VALUES,
|
|
||||||
FIELD_DISABLED_TRACK_TYPE,
|
|
||||||
FIELD_PREFERRED_VIDEO_ROLE_FLAGS
|
|
||||||
})
|
|
||||||
private @interface FieldNumber {}
|
|
||||||
|
|
||||||
private static final int FIELD_PREFERRED_AUDIO_LANGUAGES = 1;
|
private static final int FIELD_PREFERRED_AUDIO_LANGUAGES = 1;
|
||||||
private static final int FIELD_PREFERRED_AUDIO_ROLE_FLAGS = 2;
|
private static final int FIELD_PREFERRED_AUDIO_ROLE_FLAGS = 2;
|
||||||
private static final int FIELD_PREFERRED_TEXT_LANGUAGES = 3;
|
private static final int FIELD_PREFERRED_TEXT_LANGUAGES = 3;
|
||||||
@ -1039,12 +1091,20 @@ public class TrackSelectionParameters implements Bundleable {
|
|||||||
private static final int FIELD_PREFERRED_AUDIO_MIME_TYPES = 20;
|
private static final int FIELD_PREFERRED_AUDIO_MIME_TYPES = 20;
|
||||||
private static final int FIELD_FORCE_LOWEST_BITRATE = 21;
|
private static final int FIELD_FORCE_LOWEST_BITRATE = 21;
|
||||||
private static final int FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE = 22;
|
private static final int FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE = 22;
|
||||||
private static final int FIELD_SELECTION_OVERRIDE_KEYS = 23;
|
private static final int FIELD_SELECTION_OVERRIDES = 23;
|
||||||
private static final int FIELD_SELECTION_OVERRIDE_VALUES = 24;
|
private static final int FIELD_DISABLED_TRACK_TYPE = 24;
|
||||||
private static final int FIELD_DISABLED_TRACK_TYPE = 25;
|
private static final int FIELD_PREFERRED_VIDEO_ROLE_FLAGS = 25;
|
||||||
private static final int FIELD_PREFERRED_VIDEO_ROLE_FLAGS = 26;
|
private static final int FIELD_IGNORED_TEXT_SELECTION_FLAGS = 26;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Defines a minimum field ID value for subclasses to use when implementing {@link #toBundle()}
|
||||||
|
* and {@link Bundleable.Creator}.
|
||||||
|
*
|
||||||
|
* <p>Subclasses should obtain keys for their {@link Bundle} representation by applying a
|
||||||
|
* non-negative offset on this constant and passing the result to {@link #keyForField(int)}.
|
||||||
|
*/
|
||||||
|
@UnstableApi protected static final int FIELD_CUSTOM_ID_BASE = 1000;
|
||||||
|
|
||||||
@UnstableApi
|
|
||||||
@Override
|
@Override
|
||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
@ -1080,25 +1140,41 @@ public class TrackSelectionParameters implements Bundleable {
|
|||||||
bundle.putStringArray(
|
bundle.putStringArray(
|
||||||
keyForField(FIELD_PREFERRED_TEXT_LANGUAGES), preferredTextLanguages.toArray(new String[0]));
|
keyForField(FIELD_PREFERRED_TEXT_LANGUAGES), preferredTextLanguages.toArray(new String[0]));
|
||||||
bundle.putInt(keyForField(FIELD_PREFERRED_TEXT_ROLE_FLAGS), preferredTextRoleFlags);
|
bundle.putInt(keyForField(FIELD_PREFERRED_TEXT_ROLE_FLAGS), preferredTextRoleFlags);
|
||||||
|
bundle.putInt(keyForField(FIELD_IGNORED_TEXT_SELECTION_FLAGS), ignoredTextSelectionFlags);
|
||||||
bundle.putBoolean(
|
bundle.putBoolean(
|
||||||
keyForField(FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE), selectUndeterminedTextLanguage);
|
keyForField(FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE), selectUndeterminedTextLanguage);
|
||||||
// General
|
// General
|
||||||
bundle.putBoolean(keyForField(FIELD_FORCE_LOWEST_BITRATE), forceLowestBitrate);
|
bundle.putBoolean(keyForField(FIELD_FORCE_LOWEST_BITRATE), forceLowestBitrate);
|
||||||
bundle.putBoolean(
|
bundle.putBoolean(
|
||||||
keyForField(FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE), forceHighestSupportedBitrate);
|
keyForField(FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE), forceHighestSupportedBitrate);
|
||||||
bundle.putBundle(
|
bundle.putParcelableArrayList(
|
||||||
keyForField(FIELD_SELECTION_OVERRIDE_KEYS), trackSelectionOverrides.toBundle());
|
keyForField(FIELD_SELECTION_OVERRIDES), toBundleArrayList(overrides.values()));
|
||||||
bundle.putIntArray(keyForField(FIELD_DISABLED_TRACK_TYPE), Ints.toArray(disabledTrackTypes));
|
bundle.putIntArray(keyForField(FIELD_DISABLED_TRACK_TYPE), Ints.toArray(disabledTrackTypes));
|
||||||
|
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Object that can restore {@code TrackSelectionParameters} from a {@link Bundle}. */
|
/** Construct an instance from a {@link Bundle} produced by {@link #toBundle()}. */
|
||||||
@UnstableApi
|
public static TrackSelectionParameters fromBundle(Bundle bundle) {
|
||||||
public static final Creator<TrackSelectionParameters> CREATOR =
|
return new Builder(bundle).build();
|
||||||
bundle -> new Builder(bundle).build();
|
}
|
||||||
|
|
||||||
private static String keyForField(@FieldNumber int field) {
|
/**
|
||||||
|
* @deprecated Use {@link #fromBundle(Bundle)} instead.
|
||||||
|
*/
|
||||||
|
@UnstableApi @Deprecated
|
||||||
|
public static final Creator<TrackSelectionParameters> CREATOR =
|
||||||
|
TrackSelectionParameters::fromBundle;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts the given field number to a string which can be used as a field key when implementing
|
||||||
|
* {@link #toBundle()} and {@link Bundleable.Creator}.
|
||||||
|
*
|
||||||
|
* <p>Subclasses should use {@code field} values greater than or equal to {@link
|
||||||
|
* #FIELD_CUSTOM_ID_BASE}.
|
||||||
|
*/
|
||||||
|
@UnstableApi
|
||||||
|
protected static String keyForField(int field) {
|
||||||
return Integer.toString(field, Character.MAX_RADIX);
|
return Integer.toString(field, Character.MAX_RADIX);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -17,14 +17,13 @@ package androidx.media3.common;
|
|||||||
|
|
||||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
import static androidx.media3.common.util.BundleableUtil.fromBundleNullableList;
|
|
||||||
import static androidx.media3.common.util.BundleableUtil.fromNullableBundle;
|
|
||||||
import static androidx.media3.common.util.BundleableUtil.toBundleArrayList;
|
import static androidx.media3.common.util.BundleableUtil.toBundleArrayList;
|
||||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.IntDef;
|
import androidx.annotation.IntDef;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.media3.common.util.BundleableUtil;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import com.google.common.base.MoreObjects;
|
import com.google.common.base.MoreObjects;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
@ -37,49 +36,72 @@ import java.util.Arrays;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/** Information about groups of tracks. */
|
/** Information about groups of tracks. */
|
||||||
public final class TracksInfo implements Bundleable {
|
public final class Tracks implements Bundleable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Information about a single group of tracks, including the underlying {@link TrackGroup}, the
|
* Information about a single group of tracks, including the underlying {@link TrackGroup}, the
|
||||||
* {@link C.TrackType type} of tracks it contains, and the level to which each track is supported
|
* level to which each track is supported by the player, and whether any of the tracks are
|
||||||
* by the player.
|
* selected.
|
||||||
*/
|
*/
|
||||||
public static final class TrackGroupInfo implements Bundleable {
|
public static final class Group implements Bundleable {
|
||||||
private final TrackGroup trackGroup;
|
|
||||||
|
/** The number of tracks in the group. */
|
||||||
|
public final int length;
|
||||||
|
|
||||||
|
private final TrackGroup mediaTrackGroup;
|
||||||
|
private final boolean adaptiveSupported;
|
||||||
private final @C.FormatSupport int[] trackSupport;
|
private final @C.FormatSupport int[] trackSupport;
|
||||||
private final @C.TrackType int trackType;
|
|
||||||
private final boolean[] trackSelected;
|
private final boolean[] trackSelected;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs a TrackGroupInfo.
|
* Constructs an instance.
|
||||||
*
|
*
|
||||||
* @param trackGroup The {@link TrackGroup} described.
|
* @param mediaTrackGroup The underlying {@link TrackGroup} defined by the media.
|
||||||
* @param trackSupport The {@link C.FormatSupport} of each track in the {@code trackGroup}.
|
* @param adaptiveSupported Whether the player supports adaptive selections containing more than
|
||||||
* @param trackType The {@link C.TrackType} of the tracks in the {@code trackGroup}.
|
* one track in the group.
|
||||||
* @param tracksSelected Whether a track is selected for each track in {@code trackGroup}.
|
* @param trackSupport The {@link C.FormatSupport} of each track in the group.
|
||||||
|
* @param trackSelected Whether each track in the {@code trackGroup} is selected.
|
||||||
*/
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public TrackGroupInfo(
|
public Group(
|
||||||
TrackGroup trackGroup,
|
TrackGroup mediaTrackGroup,
|
||||||
|
boolean adaptiveSupported,
|
||||||
@C.FormatSupport int[] trackSupport,
|
@C.FormatSupport int[] trackSupport,
|
||||||
@C.TrackType int trackType,
|
boolean[] trackSelected) {
|
||||||
boolean[] tracksSelected) {
|
length = mediaTrackGroup.length;
|
||||||
int length = trackGroup.length;
|
checkArgument(length == trackSupport.length && length == trackSelected.length);
|
||||||
checkArgument(length == trackSupport.length && length == tracksSelected.length);
|
this.mediaTrackGroup = mediaTrackGroup;
|
||||||
this.trackGroup = trackGroup;
|
this.adaptiveSupported = adaptiveSupported && length > 1;
|
||||||
this.trackSupport = trackSupport.clone();
|
this.trackSupport = trackSupport.clone();
|
||||||
this.trackType = trackType;
|
this.trackSelected = trackSelected.clone();
|
||||||
this.trackSelected = tracksSelected.clone();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the {@link TrackGroup} described by this {@code TrackGroupInfo}. */
|
/**
|
||||||
public TrackGroup getTrackGroup() {
|
* Returns the underlying {@link TrackGroup} defined by the media.
|
||||||
return trackGroup;
|
*
|
||||||
|
* <p>Unlike this class, {@link TrackGroup} only contains information defined by the media
|
||||||
|
* itself, and does not contain runtime information such as which tracks are supported and
|
||||||
|
* currently selected. This makes it suitable for use as a {@code key} in certain {@code (key,
|
||||||
|
* value)} data structures.
|
||||||
|
*/
|
||||||
|
public TrackGroup getMediaTrackGroup() {
|
||||||
|
return mediaTrackGroup;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the {@link Format} for a specified track.
|
||||||
|
*
|
||||||
|
* @param trackIndex The index of the track in the group.
|
||||||
|
* @return The {@link Format} of the track.
|
||||||
|
*/
|
||||||
|
public Format getTrackFormat(int trackIndex) {
|
||||||
|
return mediaTrackGroup.getFormat(trackIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the level of support for a specified track.
|
* Returns the level of support for a specified track.
|
||||||
*
|
*
|
||||||
* @param trackIndex The index of the track in the {@link TrackGroup}.
|
* @param trackIndex The index of the track in the group.
|
||||||
* @return The {@link C.FormatSupport} of the track.
|
* @return The {@link C.FormatSupport} of the track.
|
||||||
*/
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@ -91,7 +113,7 @@ public final class TracksInfo implements Bundleable {
|
|||||||
* Returns whether a specified track is supported for playback, without exceeding the advertised
|
* Returns whether a specified track is supported for playback, without exceeding the advertised
|
||||||
* capabilities of the device. Equivalent to {@code isTrackSupported(trackIndex, false)}.
|
* capabilities of the device. Equivalent to {@code isTrackSupported(trackIndex, false)}.
|
||||||
*
|
*
|
||||||
* @param trackIndex The index of the track in the {@link TrackGroup}.
|
* @param trackIndex The index of the track in the group.
|
||||||
* @return True if the track's format can be played, false otherwise.
|
* @return True if the track's format can be played, false otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean isTrackSupported(int trackIndex) {
|
public boolean isTrackSupported(int trackIndex) {
|
||||||
@ -101,7 +123,7 @@ public final class TracksInfo implements Bundleable {
|
|||||||
/**
|
/**
|
||||||
* Returns whether a specified track is supported for playback.
|
* Returns whether a specified track is supported for playback.
|
||||||
*
|
*
|
||||||
* @param trackIndex The index of the track in the {@link TrackGroup}.
|
* @param trackIndex The index of the track in the group.
|
||||||
* @param allowExceedsCapabilities Whether to consider the track as supported if it has a
|
* @param allowExceedsCapabilities Whether to consider the track as supported if it has a
|
||||||
* supported {@link Format#sampleMimeType MIME type}, but otherwise exceeds the advertised
|
* supported {@link Format#sampleMimeType MIME type}, but otherwise exceeds the advertised
|
||||||
* capabilities of the device. For example, a video track for which there's a corresponding
|
* capabilities of the device. For example, a video track for which there's a corresponding
|
||||||
@ -120,6 +142,11 @@ public final class TracksInfo implements Bundleable {
|
|||||||
return Booleans.contains(trackSelected, true);
|
return Booleans.contains(trackSelected, true);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns whether adaptive selections containing more than one track are supported. */
|
||||||
|
public boolean isAdaptiveSupported() {
|
||||||
|
return adaptiveSupported;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether at least one track in the group is supported for playback, without exceeding
|
* Returns whether at least one track in the group is supported for playback, without exceeding
|
||||||
* the advertised capabilities of the device. Equivalent to {@code isSupported(false)}.
|
* the advertised capabilities of the device. Equivalent to {@code isSupported(false)}.
|
||||||
@ -157,7 +184,7 @@ public final class TracksInfo implements Bundleable {
|
|||||||
* playing, however some player implementations have ways of getting such information. For
|
* playing, however some player implementations have ways of getting such information. For
|
||||||
* example, ExoPlayer provides this information via {@code ExoTrackSelection.getSelectedFormat}.
|
* example, ExoPlayer provides this information via {@code ExoTrackSelection.getSelectedFormat}.
|
||||||
*
|
*
|
||||||
* @param trackIndex The index of the track in the {@link TrackGroup}.
|
* @param trackIndex The index of the track in the group.
|
||||||
* @return True if the track is selected, false otherwise.
|
* @return True if the track is selected, false otherwise.
|
||||||
*/
|
*/
|
||||||
public boolean isTrackSelected(int trackIndex) {
|
public boolean isTrackSelected(int trackIndex) {
|
||||||
@ -165,8 +192,8 @@ public final class TracksInfo implements Bundleable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the {@link C.TrackType} of the group. */
|
/** Returns the {@link C.TrackType} of the group. */
|
||||||
public @C.TrackType int getTrackType() {
|
public @C.TrackType int getType() {
|
||||||
return trackType;
|
return mediaTrackGroup.type;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -177,18 +204,18 @@ public final class TracksInfo implements Bundleable {
|
|||||||
if (other == null || getClass() != other.getClass()) {
|
if (other == null || getClass() != other.getClass()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
TrackGroupInfo that = (TrackGroupInfo) other;
|
Group that = (Group) other;
|
||||||
return trackType == that.trackType
|
return adaptiveSupported == that.adaptiveSupported
|
||||||
&& trackGroup.equals(that.trackGroup)
|
&& mediaTrackGroup.equals(that.mediaTrackGroup)
|
||||||
&& Arrays.equals(trackSupport, that.trackSupport)
|
&& Arrays.equals(trackSupport, that.trackSupport)
|
||||||
&& Arrays.equals(trackSelected, that.trackSelected);
|
&& Arrays.equals(trackSelected, that.trackSelected);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
int result = trackGroup.hashCode();
|
int result = mediaTrackGroup.hashCode();
|
||||||
|
result = 31 * result + (adaptiveSupported ? 1 : 0);
|
||||||
result = 31 * result + Arrays.hashCode(trackSupport);
|
result = 31 * result + Arrays.hashCode(trackSupport);
|
||||||
result = 31 * result + trackType;
|
|
||||||
result = 31 * result + Arrays.hashCode(trackSelected);
|
result = 31 * result + Arrays.hashCode(trackSelected);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
@ -200,44 +227,44 @@ public final class TracksInfo implements Bundleable {
|
|||||||
@IntDef({
|
@IntDef({
|
||||||
FIELD_TRACK_GROUP,
|
FIELD_TRACK_GROUP,
|
||||||
FIELD_TRACK_SUPPORT,
|
FIELD_TRACK_SUPPORT,
|
||||||
FIELD_TRACK_TYPE,
|
|
||||||
FIELD_TRACK_SELECTED,
|
FIELD_TRACK_SELECTED,
|
||||||
|
FIELD_ADAPTIVE_SUPPORTED,
|
||||||
})
|
})
|
||||||
private @interface FieldNumber {}
|
private @interface FieldNumber {}
|
||||||
|
|
||||||
private static final int FIELD_TRACK_GROUP = 0;
|
private static final int FIELD_TRACK_GROUP = 0;
|
||||||
private static final int FIELD_TRACK_SUPPORT = 1;
|
private static final int FIELD_TRACK_SUPPORT = 1;
|
||||||
private static final int FIELD_TRACK_TYPE = 2;
|
|
||||||
private static final int FIELD_TRACK_SELECTED = 3;
|
private static final int FIELD_TRACK_SELECTED = 3;
|
||||||
|
private static final int FIELD_ADAPTIVE_SUPPORTED = 4;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putBundle(keyForField(FIELD_TRACK_GROUP), trackGroup.toBundle());
|
bundle.putBundle(keyForField(FIELD_TRACK_GROUP), mediaTrackGroup.toBundle());
|
||||||
bundle.putIntArray(keyForField(FIELD_TRACK_SUPPORT), trackSupport);
|
bundle.putIntArray(keyForField(FIELD_TRACK_SUPPORT), trackSupport);
|
||||||
bundle.putInt(keyForField(FIELD_TRACK_TYPE), trackType);
|
|
||||||
bundle.putBooleanArray(keyForField(FIELD_TRACK_SELECTED), trackSelected);
|
bundle.putBooleanArray(keyForField(FIELD_TRACK_SELECTED), trackSelected);
|
||||||
|
bundle.putBoolean(keyForField(FIELD_ADAPTIVE_SUPPORTED), adaptiveSupported);
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Object that can restores a {@code TracksInfo} from a {@link Bundle}. */
|
/** Object that can restore a group of tracks from a {@link Bundle}. */
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public static final Creator<TrackGroupInfo> CREATOR =
|
public static final Creator<Group> CREATOR =
|
||||||
bundle -> {
|
bundle -> {
|
||||||
|
// Can't create a Tracks.Group without a TrackGroup
|
||||||
TrackGroup trackGroup =
|
TrackGroup trackGroup =
|
||||||
fromNullableBundle(
|
TrackGroup.CREATOR.fromBundle(
|
||||||
TrackGroup.CREATOR, bundle.getBundle(keyForField(FIELD_TRACK_GROUP)));
|
checkNotNull(bundle.getBundle(keyForField(FIELD_TRACK_GROUP))));
|
||||||
checkNotNull(trackGroup); // Can't create a trackGroup info without a trackGroup
|
|
||||||
final @C.FormatSupport int[] trackSupport =
|
final @C.FormatSupport int[] trackSupport =
|
||||||
MoreObjects.firstNonNull(
|
MoreObjects.firstNonNull(
|
||||||
bundle.getIntArray(keyForField(FIELD_TRACK_SUPPORT)), new int[trackGroup.length]);
|
bundle.getIntArray(keyForField(FIELD_TRACK_SUPPORT)), new int[trackGroup.length]);
|
||||||
@C.TrackType
|
|
||||||
int trackType = bundle.getInt(keyForField(FIELD_TRACK_TYPE), C.TRACK_TYPE_UNKNOWN);
|
|
||||||
boolean[] selected =
|
boolean[] selected =
|
||||||
MoreObjects.firstNonNull(
|
MoreObjects.firstNonNull(
|
||||||
bundle.getBooleanArray(keyForField(FIELD_TRACK_SELECTED)),
|
bundle.getBooleanArray(keyForField(FIELD_TRACK_SELECTED)),
|
||||||
new boolean[trackGroup.length]);
|
new boolean[trackGroup.length]);
|
||||||
return new TrackGroupInfo(trackGroup, trackSupport, trackType, selected);
|
boolean adaptiveSupported =
|
||||||
|
bundle.getBoolean(keyForField(FIELD_ADAPTIVE_SUPPORTED), false);
|
||||||
|
return new Group(trackGroup, adaptiveSupported, trackSupport, selected);
|
||||||
};
|
};
|
||||||
|
|
||||||
private static String keyForField(@FieldNumber int field) {
|
private static String keyForField(@FieldNumber int field) {
|
||||||
@ -245,39 +272,52 @@ public final class TracksInfo implements Bundleable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private final ImmutableList<TrackGroupInfo> trackGroupInfos;
|
/** Empty tracks. */
|
||||||
|
public static final Tracks EMPTY = new Tracks(ImmutableList.of());
|
||||||
|
|
||||||
/** An {@code TrackInfo} that contains no tracks. */
|
private final ImmutableList<Group> groups;
|
||||||
@UnstableApi public static final TracksInfo EMPTY = new TracksInfo(ImmutableList.of());
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Constructs an instance.
|
* Constructs an instance.
|
||||||
*
|
*
|
||||||
* @param trackGroupInfos The {@link TrackGroupInfo TrackGroupInfos} describing the groups of
|
* @param groups The {@link Group groups} of tracks.
|
||||||
* tracks.
|
|
||||||
*/
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public TracksInfo(List<TrackGroupInfo> trackGroupInfos) {
|
public Tracks(List<Group> groups) {
|
||||||
this.trackGroupInfos = ImmutableList.copyOf(trackGroupInfos);
|
this.groups = ImmutableList.copyOf(groups);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the {@link TrackGroupInfo TrackGroupInfos} describing the groups of tracks. */
|
/** Returns the {@link Group groups} of tracks. */
|
||||||
public ImmutableList<TrackGroupInfo> getTrackGroupInfos() {
|
public ImmutableList<Group> getGroups() {
|
||||||
return trackGroupInfos;
|
return groups;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns {@code true} if there are no tracks, and {@code false} otherwise. */
|
||||||
|
public boolean isEmpty() {
|
||||||
|
return groups.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns true if there are tracks of type {@code trackType}, and false otherwise. */
|
||||||
|
public boolean containsType(@C.TrackType int trackType) {
|
||||||
|
for (int i = 0; i < groups.size(); i++) {
|
||||||
|
if (groups.get(i).getType() == trackType) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if at least one track of type {@code trackType} is {@link
|
* Returns true if at least one track of type {@code trackType} is {@link
|
||||||
* TrackGroupInfo#isTrackSupported(int) supported} or if there are no tracks of this type.
|
* Group#isTrackSupported(int) supported}.
|
||||||
*/
|
*/
|
||||||
public boolean isTypeSupportedOrEmpty(@C.TrackType int trackType) {
|
public boolean isTypeSupported(@C.TrackType int trackType) {
|
||||||
return isTypeSupportedOrEmpty(trackType, /* allowExceedsCapabilities= */ false);
|
return isTypeSupported(trackType, /* allowExceedsCapabilities= */ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns true if at least one track of type {@code trackType} is {@link
|
* Returns true if at least one track of type {@code trackType} is {@link
|
||||||
* TrackGroupInfo#isTrackSupported(int, boolean) supported} or if there are no tracks of this
|
* Group#isTrackSupported(int, boolean) supported}.
|
||||||
* type.
|
|
||||||
*
|
*
|
||||||
* @param allowExceedsCapabilities Whether to consider the track as supported if it has a
|
* @param allowExceedsCapabilities Whether to consider the track as supported if it has a
|
||||||
* supported {@link Format#sampleMimeType MIME type}, but otherwise exceeds the advertised
|
* supported {@link Format#sampleMimeType MIME type}, but otherwise exceeds the advertised
|
||||||
@ -285,26 +325,42 @@ public final class TracksInfo implements Bundleable {
|
|||||||
* decoder whose maximum advertised resolution is exceeded by the resolution of the track.
|
* decoder whose maximum advertised resolution is exceeded by the resolution of the track.
|
||||||
* Such tracks may be playable in some cases.
|
* Such tracks may be playable in some cases.
|
||||||
*/
|
*/
|
||||||
|
public boolean isTypeSupported(@C.TrackType int trackType, boolean allowExceedsCapabilities) {
|
||||||
|
for (int i = 0; i < groups.size(); i++) {
|
||||||
|
if (groups.get(i).getType() == trackType) {
|
||||||
|
if (groups.get(i).isSupported(allowExceedsCapabilities)) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #containsType(int)} and {@link #isTypeSupported(int)}.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
@UnstableApi
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
public boolean isTypeSupportedOrEmpty(@C.TrackType int trackType) {
|
||||||
|
return isTypeSupportedOrEmpty(trackType, /* allowExceedsCapabilities= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #containsType(int)} and {@link #isTypeSupported(int, boolean)}.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
@UnstableApi
|
||||||
public boolean isTypeSupportedOrEmpty(
|
public boolean isTypeSupportedOrEmpty(
|
||||||
@C.TrackType int trackType, boolean allowExceedsCapabilities) {
|
@C.TrackType int trackType, boolean allowExceedsCapabilities) {
|
||||||
boolean supported = true;
|
return !containsType(trackType) || isTypeSupported(trackType, allowExceedsCapabilities);
|
||||||
for (int i = 0; i < trackGroupInfos.size(); i++) {
|
|
||||||
if (trackGroupInfos.get(i).trackType == trackType) {
|
|
||||||
if (trackGroupInfos.get(i).isSupported(allowExceedsCapabilities)) {
|
|
||||||
return true;
|
|
||||||
} else {
|
|
||||||
supported = false;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return supported;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns true if at least one track of the type {@code trackType} is selected for playback. */
|
/** Returns true if at least one track of the type {@code trackType} is selected for playback. */
|
||||||
public boolean isTypeSelected(@C.TrackType int trackType) {
|
public boolean isTypeSelected(@C.TrackType int trackType) {
|
||||||
for (int i = 0; i < trackGroupInfos.size(); i++) {
|
for (int i = 0; i < groups.size(); i++) {
|
||||||
TrackGroupInfo trackGroupInfo = trackGroupInfos.get(i);
|
Group group = groups.get(i);
|
||||||
if (trackGroupInfo.isSelected() && trackGroupInfo.getTrackType() == trackType) {
|
if (group.isSelected() && group.getType() == trackType) {
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -319,13 +375,13 @@ public final class TracksInfo implements Bundleable {
|
|||||||
if (other == null || getClass() != other.getClass()) {
|
if (other == null || getClass() != other.getClass()) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
TracksInfo that = (TracksInfo) other;
|
Tracks that = (Tracks) other;
|
||||||
return trackGroupInfos.equals(that.trackGroupInfos);
|
return groups.equals(that.groups);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return trackGroupInfos.hashCode();
|
return groups.hashCode();
|
||||||
}
|
}
|
||||||
// Bundleable implementation.
|
// Bundleable implementation.
|
||||||
|
|
||||||
@ -333,31 +389,31 @@ public final class TracksInfo implements Bundleable {
|
|||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@Target(TYPE_USE)
|
@Target(TYPE_USE)
|
||||||
@IntDef({
|
@IntDef({
|
||||||
FIELD_TRACK_GROUP_INFOS,
|
FIELD_TRACK_GROUPS,
|
||||||
})
|
})
|
||||||
private @interface FieldNumber {}
|
private @interface FieldNumber {}
|
||||||
|
|
||||||
private static final int FIELD_TRACK_GROUP_INFOS = 0;
|
private static final int FIELD_TRACK_GROUPS = 0;
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Override
|
@Override
|
||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
bundle.putParcelableArrayList(
|
bundle.putParcelableArrayList(keyForField(FIELD_TRACK_GROUPS), toBundleArrayList(groups));
|
||||||
keyForField(FIELD_TRACK_GROUP_INFOS), toBundleArrayList(trackGroupInfos));
|
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Object that can restore a {@code TracksInfo} from a {@link Bundle}. */
|
/** Object that can restore tracks from a {@link Bundle}. */
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public static final Creator<TracksInfo> CREATOR =
|
public static final Creator<Tracks> CREATOR =
|
||||||
bundle -> {
|
bundle -> {
|
||||||
List<TrackGroupInfo> trackGroupInfos =
|
@Nullable
|
||||||
fromBundleNullableList(
|
List<Bundle> groupBundles = bundle.getParcelableArrayList(keyForField(FIELD_TRACK_GROUPS));
|
||||||
TrackGroupInfo.CREATOR,
|
List<Group> groups =
|
||||||
bundle.getParcelableArrayList(keyForField(FIELD_TRACK_GROUP_INFOS)),
|
groupBundles == null
|
||||||
/* defaultValue= */ ImmutableList.of());
|
? ImmutableList.of()
|
||||||
return new TracksInfo(trackGroupInfos);
|
: BundleableUtil.fromBundleList(Group.CREATOR, groupBundles);
|
||||||
|
return new Tracks(groups);
|
||||||
};
|
};
|
||||||
|
|
||||||
private static String keyForField(@FieldNumber int field) {
|
private static String keyForField(@FieldNumber int field) {
|
@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 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.common.text;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import androidx.annotation.IntDef;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.media3.common.Bundleable;
|
||||||
|
import androidx.media3.common.util.BundleableUtil;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/** Class to represent the state of active {@link Cue Cues} at a particular time. */
|
||||||
|
public final class CueGroup implements Bundleable {
|
||||||
|
|
||||||
|
/** Empty {@link CueGroup}. */
|
||||||
|
@UnstableApi public static final CueGroup EMPTY = new CueGroup(ImmutableList.of());
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The cues in this group.
|
||||||
|
*
|
||||||
|
* <p>This list is in ascending order of priority. If any of the cue boxes overlap when displayed,
|
||||||
|
* the {@link Cue} nearer the end of the list should be shown on top.
|
||||||
|
*
|
||||||
|
* <p>This list may be empty if the group represents a state with no cues.
|
||||||
|
*/
|
||||||
|
public final ImmutableList<Cue> cues;
|
||||||
|
|
||||||
|
/** Creates a CueGroup. */
|
||||||
|
@UnstableApi
|
||||||
|
public CueGroup(List<Cue> cues) {
|
||||||
|
this.cues = ImmutableList.copyOf(cues);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Bundleable implementation.
|
||||||
|
|
||||||
|
@Documented
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@Target(TYPE_USE)
|
||||||
|
@IntDef({FIELD_CUES})
|
||||||
|
private @interface FieldNumber {}
|
||||||
|
|
||||||
|
private static final int FIELD_CUES = 0;
|
||||||
|
|
||||||
|
@UnstableApi
|
||||||
|
@Override
|
||||||
|
public Bundle toBundle() {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putParcelableArrayList(
|
||||||
|
keyForField(FIELD_CUES), BundleableUtil.toBundleArrayList(filterOutBitmapCues(cues)));
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
@UnstableApi public static final Creator<CueGroup> CREATOR = CueGroup::fromBundle;
|
||||||
|
|
||||||
|
private static final CueGroup fromBundle(Bundle bundle) {
|
||||||
|
@Nullable ArrayList<Bundle> cueBundles = bundle.getParcelableArrayList(keyForField(FIELD_CUES));
|
||||||
|
List<Cue> cues =
|
||||||
|
cueBundles == null
|
||||||
|
? ImmutableList.of()
|
||||||
|
: BundleableUtil.fromBundleList(Cue.CREATOR, cueBundles);
|
||||||
|
return new CueGroup(cues);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String keyForField(@FieldNumber int field) {
|
||||||
|
return Integer.toString(field, Character.MAX_RADIX);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Filters out {@link Cue} objects containing {@link Bitmap}. It is used when transferring cues
|
||||||
|
* between processes to prevent transferring too much data.
|
||||||
|
*/
|
||||||
|
private static ImmutableList<Cue> filterOutBitmapCues(List<Cue> cues) {
|
||||||
|
ImmutableList.Builder<Cue> builder = ImmutableList.builder();
|
||||||
|
for (int i = 0; i < cues.size(); i++) {
|
||||||
|
if (cues.get(i).bitmap != null) {
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
builder.add(cues.get(i));
|
||||||
|
}
|
||||||
|
return builder.build();
|
||||||
|
}
|
||||||
|
}
|
@ -31,34 +31,6 @@ import java.util.List;
|
|||||||
@UnstableApi
|
@UnstableApi
|
||||||
public final class BundleableUtil {
|
public final class BundleableUtil {
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a {@link Bundleable} to a {@link Bundle}. It's a convenience wrapper of {@link
|
|
||||||
* Bundleable#toBundle} that can take nullable values.
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public static Bundle toNullableBundle(@Nullable Bundleable bundleable) {
|
|
||||||
return bundleable == null ? null : bundleable.toBundle();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a {@link Bundle} to a {@link Bundleable}. It's a convenience wrapper of {@link
|
|
||||||
* Bundleable.Creator#fromBundle} that can take nullable values.
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
public static <T extends Bundleable> T fromNullableBundle(
|
|
||||||
Bundleable.Creator<T> creator, @Nullable Bundle bundle) {
|
|
||||||
return bundle == null ? null : creator.fromBundle(bundle);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a {@link Bundle} to a {@link Bundleable}. It's a convenience wrapper of {@link
|
|
||||||
* Bundleable.Creator#fromBundle} that provides default value to ensure non-null.
|
|
||||||
*/
|
|
||||||
public static <T extends Bundleable> T fromNullableBundle(
|
|
||||||
Bundleable.Creator<T> creator, @Nullable Bundle bundle, T defaultValue) {
|
|
||||||
return bundle == null ? defaultValue : creator.fromBundle(bundle);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Converts a list of {@link Bundleable} to a list {@link Bundle}. */
|
/** Converts a list of {@link Bundleable} to a list {@link Bundle}. */
|
||||||
public static <T extends Bundleable> ImmutableList<Bundle> toBundleList(List<T> bundleableList) {
|
public static <T extends Bundleable> ImmutableList<Bundle> toBundleList(List<T> bundleableList) {
|
||||||
ImmutableList.Builder<Bundle> builder = ImmutableList.builder();
|
ImmutableList.Builder<Bundle> builder = ImmutableList.builder();
|
||||||
@ -81,34 +53,6 @@ public final class BundleableUtil {
|
|||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a list of {@link Bundle} to a list of {@link Bundleable}. Returns {@code defaultValue}
|
|
||||||
* if {@code bundleList} is null.
|
|
||||||
*/
|
|
||||||
public static <T extends Bundleable> List<T> fromBundleNullableList(
|
|
||||||
Bundleable.Creator<T> creator, @Nullable List<Bundle> bundleList, List<T> defaultValue) {
|
|
||||||
return (bundleList == null) ? defaultValue : fromBundleList(creator, bundleList);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Converts a {@link SparseArray} of {@link Bundle} to a {@link SparseArray} of {@link
|
|
||||||
* Bundleable}. Returns {@code defaultValue} if {@code bundleSparseArray} is null.
|
|
||||||
*/
|
|
||||||
public static <T extends Bundleable> SparseArray<T> fromBundleNullableSparseArray(
|
|
||||||
Bundleable.Creator<T> creator,
|
|
||||||
@Nullable SparseArray<Bundle> bundleSparseArray,
|
|
||||||
SparseArray<T> defaultValue) {
|
|
||||||
if (bundleSparseArray == null) {
|
|
||||||
return defaultValue;
|
|
||||||
}
|
|
||||||
// Can't use ImmutableList as it doesn't support null elements.
|
|
||||||
SparseArray<T> result = new SparseArray<>(bundleSparseArray.size());
|
|
||||||
for (int i = 0; i < bundleSparseArray.size(); i++) {
|
|
||||||
result.put(bundleSparseArray.keyAt(i), creator.fromBundle(bundleSparseArray.valueAt(i)));
|
|
||||||
}
|
|
||||||
return result;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a collection of {@link Bundleable} to an {@link ArrayList} of {@link Bundle} so that
|
* Converts a collection of {@link Bundleable} to an {@link ArrayList} of {@link Bundle} so that
|
||||||
* the returned list can be put to {@link Bundle} using {@link Bundle#putParcelableArrayList}
|
* the returned list can be put to {@link Bundle} using {@link Bundle#putParcelableArrayList}
|
||||||
@ -123,6 +67,19 @@ public final class BundleableUtil {
|
|||||||
return arrayList;
|
return arrayList;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a {@link SparseArray} of {@link Bundle} to a {@link SparseArray} of {@link
|
||||||
|
* Bundleable}.
|
||||||
|
*/
|
||||||
|
public static <T extends Bundleable> SparseArray<T> fromBundleSparseArray(
|
||||||
|
Bundleable.Creator<T> creator, SparseArray<Bundle> bundleSparseArray) {
|
||||||
|
SparseArray<T> result = new SparseArray<>(bundleSparseArray.size());
|
||||||
|
for (int i = 0; i < bundleSparseArray.size(); i++) {
|
||||||
|
result.put(bundleSparseArray.keyAt(i), creator.fromBundle(bundleSparseArray.valueAt(i)));
|
||||||
|
}
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a {@link SparseArray} of {@link Bundleable} to an {@link SparseArray} of {@link
|
* Converts a {@link SparseArray} of {@link Bundleable} to an {@link SparseArray} of {@link
|
||||||
* Bundle} so that the returned {@link SparseArray} can be put to {@link Bundle} using {@link
|
* Bundle} so that the returned {@link SparseArray} can be put to {@link Bundle} using {@link
|
||||||
|
@ -36,10 +36,14 @@ public interface Clock {
|
|||||||
*/
|
*/
|
||||||
long currentTimeMillis();
|
long currentTimeMillis();
|
||||||
|
|
||||||
/** @see android.os.SystemClock#elapsedRealtime() */
|
/**
|
||||||
|
* @see android.os.SystemClock#elapsedRealtime()
|
||||||
|
*/
|
||||||
long elapsedRealtime();
|
long elapsedRealtime();
|
||||||
|
|
||||||
/** @see android.os.SystemClock#uptimeMillis() */
|
/**
|
||||||
|
* @see android.os.SystemClock#uptimeMillis()
|
||||||
|
*/
|
||||||
long uptimeMillis();
|
long uptimeMillis();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.common.util;
|
package androidx.media3.common.util;
|
||||||
|
|
||||||
|
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||||
|
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
@ -31,6 +33,12 @@ public final class CodecSpecificDataUtil {
|
|||||||
private static final String[] HEVC_GENERAL_PROFILE_SPACE_STRINGS =
|
private static final String[] HEVC_GENERAL_PROFILE_SPACE_STRINGS =
|
||||||
new String[] {"", "A", "B", "C"};
|
new String[] {"", "A", "B", "C"};
|
||||||
|
|
||||||
|
// MP4V-ES
|
||||||
|
private static final int VISUAL_OBJECT_LAYER = 1;
|
||||||
|
private static final int VISUAL_OBJECT_LAYER_START = 0x20;
|
||||||
|
private static final int EXTENDED_PAR = 0x0F;
|
||||||
|
private static final int RECTANGULAR = 0x00;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Parses an ALAC AudioSpecificConfig (i.e. an <a
|
* Parses an ALAC AudioSpecificConfig (i.e. an <a
|
||||||
* href="https://github.com/macosforge/alac/blob/master/ALACMagicCookieDescription.txt">ALACSpecificConfig</a>).
|
* href="https://github.com/macosforge/alac/blob/master/ALACMagicCookieDescription.txt">ALACSpecificConfig</a>).
|
||||||
@ -72,6 +80,87 @@ public final class CodecSpecificDataUtil {
|
|||||||
&& initializationData.get(0)[0] == 1;
|
&& initializationData.get(0)[0] == 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Parses an MPEG-4 Visual configuration information, as defined in ISO/IEC14496-2.
|
||||||
|
*
|
||||||
|
* @param videoSpecificConfig A byte array containing the MPEG-4 Visual configuration information
|
||||||
|
* to parse.
|
||||||
|
* @return A pair of the video's width and height.
|
||||||
|
*/
|
||||||
|
public static Pair<Integer, Integer> getVideoResolutionFromMpeg4VideoConfig(
|
||||||
|
byte[] videoSpecificConfig) {
|
||||||
|
int offset = 0;
|
||||||
|
boolean foundVOL = false;
|
||||||
|
ParsableByteArray scratchBytes = new ParsableByteArray(videoSpecificConfig);
|
||||||
|
while (offset + 3 < videoSpecificConfig.length) {
|
||||||
|
if (scratchBytes.readUnsignedInt24() != VISUAL_OBJECT_LAYER
|
||||||
|
|| (videoSpecificConfig[offset + 3] & 0xF0) != VISUAL_OBJECT_LAYER_START) {
|
||||||
|
scratchBytes.setPosition(scratchBytes.getPosition() - 2);
|
||||||
|
offset++;
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
foundVOL = true;
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
|
||||||
|
checkArgument(foundVOL, "Invalid input: VOL not found.");
|
||||||
|
|
||||||
|
ParsableBitArray scratchBits = new ParsableBitArray(videoSpecificConfig);
|
||||||
|
// Skip the start codecs from the bitstream
|
||||||
|
scratchBits.skipBits((offset + 4) * 8);
|
||||||
|
scratchBits.skipBits(1); // random_accessible_vol
|
||||||
|
scratchBits.skipBits(8); // video_object_type_indication
|
||||||
|
|
||||||
|
if (scratchBits.readBit()) { // object_layer_identifier
|
||||||
|
scratchBits.skipBits(4); // video_object_layer_verid
|
||||||
|
scratchBits.skipBits(3); // video_object_layer_priority
|
||||||
|
}
|
||||||
|
|
||||||
|
int aspectRatioInfo = scratchBits.readBits(4);
|
||||||
|
if (aspectRatioInfo == EXTENDED_PAR) {
|
||||||
|
scratchBits.skipBits(8); // par_width
|
||||||
|
scratchBits.skipBits(8); // par_height
|
||||||
|
}
|
||||||
|
|
||||||
|
if (scratchBits.readBit()) { // vol_control_parameters
|
||||||
|
scratchBits.skipBits(2); // chroma_format
|
||||||
|
scratchBits.skipBits(1); // low_delay
|
||||||
|
if (scratchBits.readBit()) { // vbv_parameters
|
||||||
|
scratchBits.skipBits(79);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
int videoObjectLayerShape = scratchBits.readBits(2);
|
||||||
|
checkArgument(
|
||||||
|
videoObjectLayerShape == RECTANGULAR,
|
||||||
|
"Only supports rectangular video object layer shape.");
|
||||||
|
|
||||||
|
checkArgument(scratchBits.readBit()); // marker_bit
|
||||||
|
int vopTimeIncrementResolution = scratchBits.readBits(16);
|
||||||
|
checkArgument(scratchBits.readBit()); // marker_bit
|
||||||
|
|
||||||
|
if (scratchBits.readBit()) { // fixed_vop_rate
|
||||||
|
checkArgument(vopTimeIncrementResolution > 0);
|
||||||
|
vopTimeIncrementResolution--;
|
||||||
|
int numBitsToSkip = 0;
|
||||||
|
while (vopTimeIncrementResolution > 0) {
|
||||||
|
numBitsToSkip++;
|
||||||
|
vopTimeIncrementResolution >>= 1;
|
||||||
|
}
|
||||||
|
scratchBits.skipBits(numBitsToSkip); // fixed_vop_time_increment
|
||||||
|
}
|
||||||
|
|
||||||
|
checkArgument(scratchBits.readBit()); // marker_bit
|
||||||
|
int videoObjectLayerWidth = scratchBits.readBits(13);
|
||||||
|
checkArgument(scratchBits.readBit()); // marker_bit
|
||||||
|
int videoObjectLayerHeight = scratchBits.readBits(13);
|
||||||
|
checkArgument(scratchBits.readBit()); // marker_bit
|
||||||
|
|
||||||
|
scratchBits.skipBits(1); // interlaced
|
||||||
|
|
||||||
|
return Pair.create(videoObjectLayerWidth, videoObjectLayerHeight);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds an RFC 6381 AVC codec string using the provided parameters.
|
* Builds an RFC 6381 AVC codec string using the provided parameters.
|
||||||
*
|
*
|
||||||
|
@ -0,0 +1,411 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 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.common.util;
|
||||||
|
|
||||||
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.opengl.GLES11Ext;
|
||||||
|
import android.opengl.GLES20;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import java.io.IOException;
|
||||||
|
import java.nio.Buffer;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Represents a GLSL shader program.
|
||||||
|
*
|
||||||
|
* <p>After constructing a program, keep a reference for its lifetime and call {@link #delete()} (or
|
||||||
|
* release the current GL context) when it's no longer needed.
|
||||||
|
*/
|
||||||
|
@UnstableApi
|
||||||
|
public final class GlProgram {
|
||||||
|
|
||||||
|
// https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_YUV_target.txt
|
||||||
|
private static final int GL_SAMPLER_EXTERNAL_2D_Y2Y_EXT = 0x8BE7;
|
||||||
|
/** The identifier of a compiled and linked GLSL shader program. */
|
||||||
|
private final int programId;
|
||||||
|
|
||||||
|
private final Attribute[] attributes;
|
||||||
|
private final Uniform[] uniforms;
|
||||||
|
private final Map<String, Attribute> attributeByName;
|
||||||
|
private final Map<String, Uniform> uniformByName;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Compiles a GL shader program from vertex and fragment shader GLSL GLES20 code.
|
||||||
|
*
|
||||||
|
* @param context The {@link Context}.
|
||||||
|
* @param vertexShaderFilePath The path to a vertex shader program.
|
||||||
|
* @param fragmentShaderFilePath The path to a fragment shader program.
|
||||||
|
* @throws IOException When failing to read shader files.
|
||||||
|
*/
|
||||||
|
public GlProgram(Context context, String vertexShaderFilePath, String fragmentShaderFilePath)
|
||||||
|
throws IOException {
|
||||||
|
this(
|
||||||
|
GlUtil.loadAsset(context, vertexShaderFilePath),
|
||||||
|
GlUtil.loadAsset(context, fragmentShaderFilePath));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a GL shader program from vertex and fragment shader GLSL GLES20 code.
|
||||||
|
*
|
||||||
|
* <p>This involves slow steps, like compiling, linking, and switching the GL program, so do not
|
||||||
|
* call this in fast rendering loops.
|
||||||
|
*
|
||||||
|
* @param vertexShaderGlsl The vertex shader program.
|
||||||
|
* @param fragmentShaderGlsl The fragment shader program.
|
||||||
|
*/
|
||||||
|
public GlProgram(String vertexShaderGlsl, String fragmentShaderGlsl) {
|
||||||
|
programId = GLES20.glCreateProgram();
|
||||||
|
GlUtil.checkGlError();
|
||||||
|
|
||||||
|
// Add the vertex and fragment shaders.
|
||||||
|
addShader(programId, GLES20.GL_VERTEX_SHADER, vertexShaderGlsl);
|
||||||
|
addShader(programId, GLES20.GL_FRAGMENT_SHADER, fragmentShaderGlsl);
|
||||||
|
|
||||||
|
// Link and use the program, and enumerate attributes/uniforms.
|
||||||
|
GLES20.glLinkProgram(programId);
|
||||||
|
int[] linkStatus = new int[] {GLES20.GL_FALSE};
|
||||||
|
GLES20.glGetProgramiv(programId, GLES20.GL_LINK_STATUS, linkStatus, /* offset= */ 0);
|
||||||
|
if (linkStatus[0] != GLES20.GL_TRUE) {
|
||||||
|
GlUtil.throwGlException(
|
||||||
|
"Unable to link shader program: \n" + GLES20.glGetProgramInfoLog(programId));
|
||||||
|
}
|
||||||
|
GLES20.glUseProgram(programId);
|
||||||
|
attributeByName = new HashMap<>();
|
||||||
|
int[] attributeCount = new int[1];
|
||||||
|
GLES20.glGetProgramiv(programId, GLES20.GL_ACTIVE_ATTRIBUTES, attributeCount, /* offset= */ 0);
|
||||||
|
attributes = new Attribute[attributeCount[0]];
|
||||||
|
for (int i = 0; i < attributeCount[0]; i++) {
|
||||||
|
Attribute attribute = Attribute.create(programId, i);
|
||||||
|
attributes[i] = attribute;
|
||||||
|
attributeByName.put(attribute.name, attribute);
|
||||||
|
}
|
||||||
|
uniformByName = new HashMap<>();
|
||||||
|
int[] uniformCount = new int[1];
|
||||||
|
GLES20.glGetProgramiv(programId, GLES20.GL_ACTIVE_UNIFORMS, uniformCount, /* offset= */ 0);
|
||||||
|
uniforms = new Uniform[uniformCount[0]];
|
||||||
|
for (int i = 0; i < uniformCount[0]; i++) {
|
||||||
|
Uniform uniform = Uniform.create(programId, i);
|
||||||
|
uniforms[i] = uniform;
|
||||||
|
uniformByName.put(uniform.name, uniform);
|
||||||
|
}
|
||||||
|
GlUtil.checkGlError();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static void addShader(int programId, int type, String glsl) {
|
||||||
|
int shader = GLES20.glCreateShader(type);
|
||||||
|
GLES20.glShaderSource(shader, glsl);
|
||||||
|
GLES20.glCompileShader(shader);
|
||||||
|
|
||||||
|
int[] result = new int[] {GLES20.GL_FALSE};
|
||||||
|
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, result, /* offset= */ 0);
|
||||||
|
if (result[0] != GLES20.GL_TRUE) {
|
||||||
|
GlUtil.throwGlException(GLES20.glGetShaderInfoLog(shader) + ", source: " + glsl);
|
||||||
|
}
|
||||||
|
|
||||||
|
GLES20.glAttachShader(programId, shader);
|
||||||
|
GLES20.glDeleteShader(shader);
|
||||||
|
GlUtil.checkGlError();
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getAttributeLocation(int programId, String attributeName) {
|
||||||
|
return GLES20.glGetAttribLocation(programId, attributeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the location of an {@link Attribute}. */
|
||||||
|
private int getAttributeLocation(String attributeName) {
|
||||||
|
return getAttributeLocation(programId, attributeName);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static int getUniformLocation(int programId, String uniformName) {
|
||||||
|
return GLES20.glGetUniformLocation(programId, uniformName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the location of a {@link Uniform}. */
|
||||||
|
public int getUniformLocation(String uniformName) {
|
||||||
|
return getUniformLocation(programId, uniformName);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Uses the program.
|
||||||
|
*
|
||||||
|
* <p>Call this in the rendering loop to switch between different programs.
|
||||||
|
*/
|
||||||
|
public void use() {
|
||||||
|
GLES20.glUseProgram(programId);
|
||||||
|
GlUtil.checkGlError();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Deletes the program. Deleted programs cannot be used again. */
|
||||||
|
public void delete() {
|
||||||
|
GLES20.glDeleteProgram(programId);
|
||||||
|
GlUtil.checkGlError();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the location of an {@link Attribute}, which has been enabled as a vertex attribute
|
||||||
|
* array.
|
||||||
|
*/
|
||||||
|
public int getAttributeArrayLocationAndEnable(String attributeName) {
|
||||||
|
int location = getAttributeLocation(attributeName);
|
||||||
|
GLES20.glEnableVertexAttribArray(location);
|
||||||
|
GlUtil.checkGlError();
|
||||||
|
return location;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets a float buffer type attribute. */
|
||||||
|
public void setBufferAttribute(String name, float[] values, int size) {
|
||||||
|
checkNotNull(attributeByName.get(name)).setBuffer(values, size);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets a texture sampler type uniform.
|
||||||
|
*
|
||||||
|
* @param name The uniform's name.
|
||||||
|
* @param texId The texture identifier.
|
||||||
|
* @param texUnitIndex The texture unit index. Use a different index (0, 1, 2, ...) for each
|
||||||
|
* texture sampler in the program.
|
||||||
|
*/
|
||||||
|
public void setSamplerTexIdUniform(String name, int texId, int texUnitIndex) {
|
||||||
|
checkNotNull(uniformByName.get(name)).setSamplerTexId(texId, texUnitIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets a float type uniform. */
|
||||||
|
public void setFloatUniform(String name, float value) {
|
||||||
|
checkNotNull(uniformByName.get(name)).setFloat(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets a float array type uniform. */
|
||||||
|
public void setFloatsUniform(String name, float[] value) {
|
||||||
|
checkNotNull(uniformByName.get(name)).setFloats(value);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Binds all attributes and uniforms in the program. */
|
||||||
|
public void bindAttributesAndUniforms() {
|
||||||
|
for (Attribute attribute : attributes) {
|
||||||
|
attribute.bind();
|
||||||
|
}
|
||||||
|
for (Uniform uniform : uniforms) {
|
||||||
|
uniform.bind();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the length of the null-terminated C string in {@code cString}. */
|
||||||
|
private static int getCStringLength(byte[] cString) {
|
||||||
|
for (int i = 0; i < cString.length; ++i) {
|
||||||
|
if (cString[i] == '\0') {
|
||||||
|
return i;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return cString.length;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GL attribute, which can be attached to a buffer with {@link Attribute#setBuffer(float[], int)}.
|
||||||
|
*/
|
||||||
|
private static final class Attribute {
|
||||||
|
|
||||||
|
/* Returns the attribute at the given index in the program. */
|
||||||
|
public static Attribute create(int programId, int index) {
|
||||||
|
int[] length = new int[1];
|
||||||
|
GLES20.glGetProgramiv(
|
||||||
|
programId, GLES20.GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, length, /* offset= */ 0);
|
||||||
|
byte[] nameBytes = new byte[length[0]];
|
||||||
|
|
||||||
|
GLES20.glGetActiveAttrib(
|
||||||
|
programId,
|
||||||
|
index,
|
||||||
|
length[0],
|
||||||
|
/* unusedLength */ new int[1],
|
||||||
|
/* lengthOffset= */ 0,
|
||||||
|
/* unusedSize */ new int[1],
|
||||||
|
/* sizeOffset= */ 0,
|
||||||
|
/* unusedType */ new int[1],
|
||||||
|
/* typeOffset= */ 0,
|
||||||
|
nameBytes,
|
||||||
|
/* nameOffset= */ 0);
|
||||||
|
String name = new String(nameBytes, /* offset= */ 0, getCStringLength(nameBytes));
|
||||||
|
int location = getAttributeLocation(programId, name);
|
||||||
|
|
||||||
|
return new Attribute(name, index, location);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The name of the attribute in the GLSL sources. */
|
||||||
|
public final String name;
|
||||||
|
|
||||||
|
private final int index;
|
||||||
|
private final int location;
|
||||||
|
|
||||||
|
@Nullable private Buffer buffer;
|
||||||
|
private int size;
|
||||||
|
|
||||||
|
private Attribute(String name, int index, int location) {
|
||||||
|
this.name = name;
|
||||||
|
this.index = index;
|
||||||
|
this.location = location;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures {@link #bind()} to attach vertices in {@code buffer} (each of size {@code size}
|
||||||
|
* elements) to this {@link Attribute}.
|
||||||
|
*
|
||||||
|
* @param buffer Buffer to bind to this attribute.
|
||||||
|
* @param size Number of elements per vertex.
|
||||||
|
*/
|
||||||
|
public void setBuffer(float[] buffer, int size) {
|
||||||
|
this.buffer = GlUtil.createBuffer(buffer);
|
||||||
|
this.size = size;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the vertex attribute to whatever was attached via {@link #setBuffer(float[], int)}.
|
||||||
|
*
|
||||||
|
* <p>Should be called before each drawing call.
|
||||||
|
*/
|
||||||
|
public void bind() {
|
||||||
|
Buffer buffer = checkNotNull(this.buffer, "call setBuffer before bind");
|
||||||
|
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, /* buffer= */ 0);
|
||||||
|
GLES20.glVertexAttribPointer(
|
||||||
|
location, size, GLES20.GL_FLOAT, /* normalized= */ false, /* stride= */ 0, buffer);
|
||||||
|
GLES20.glEnableVertexAttribArray(index);
|
||||||
|
GlUtil.checkGlError();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* GL uniform, which can be attached to a sampler using {@link Uniform#setSamplerTexId(int, int)}.
|
||||||
|
*/
|
||||||
|
private static final class Uniform {
|
||||||
|
|
||||||
|
/** Returns the uniform at the given index in the program. */
|
||||||
|
public static Uniform create(int programId, int index) {
|
||||||
|
int[] length = new int[1];
|
||||||
|
GLES20.glGetProgramiv(
|
||||||
|
programId, GLES20.GL_ACTIVE_UNIFORM_MAX_LENGTH, length, /* offset= */ 0);
|
||||||
|
|
||||||
|
int[] type = new int[1];
|
||||||
|
byte[] nameBytes = new byte[length[0]];
|
||||||
|
|
||||||
|
GLES20.glGetActiveUniform(
|
||||||
|
programId,
|
||||||
|
index,
|
||||||
|
length[0],
|
||||||
|
/* unusedLength */ new int[1],
|
||||||
|
/* lengthOffset= */ 0,
|
||||||
|
/* unusedSize */ new int[1],
|
||||||
|
/*sizeOffset= */ 0,
|
||||||
|
type,
|
||||||
|
/* typeOffset= */ 0,
|
||||||
|
nameBytes,
|
||||||
|
/* nameOffset= */ 0);
|
||||||
|
String name = new String(nameBytes, /* offset= */ 0, getCStringLength(nameBytes));
|
||||||
|
int location = getUniformLocation(programId, name);
|
||||||
|
|
||||||
|
return new Uniform(name, location, type[0]);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The name of the uniform in the GLSL sources. */
|
||||||
|
public final String name;
|
||||||
|
|
||||||
|
private final int location;
|
||||||
|
private final int type;
|
||||||
|
private final float[] value;
|
||||||
|
|
||||||
|
private int texId;
|
||||||
|
private int texUnitIndex;
|
||||||
|
|
||||||
|
private Uniform(String name, int location, int type) {
|
||||||
|
this.name = name;
|
||||||
|
this.location = location;
|
||||||
|
this.type = type;
|
||||||
|
this.value = new float[16];
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures {@link #bind()} to use the specified {@code texId} for this sampler uniform.
|
||||||
|
*
|
||||||
|
* @param texId The GL texture identifier from which to sample.
|
||||||
|
* @param texUnitIndex The GL texture unit index.
|
||||||
|
*/
|
||||||
|
public void setSamplerTexId(int texId, int texUnitIndex) {
|
||||||
|
this.texId = texId;
|
||||||
|
this.texUnitIndex = texUnitIndex;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Configures {@link #bind()} to use the specified float {@code value} for this uniform. */
|
||||||
|
public void setFloat(float value) {
|
||||||
|
this.value[0] = value;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Configures {@link #bind()} to use the specified float[] {@code value} for this uniform. */
|
||||||
|
public void setFloats(float[] value) {
|
||||||
|
System.arraycopy(value, /* srcPos= */ 0, this.value, /* destPos= */ 0, value.length);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the uniform to whatever value was passed via {@link #setSamplerTexId(int, int)}, {@link
|
||||||
|
* #setFloat(float)} or {@link #setFloats(float[])}.
|
||||||
|
*
|
||||||
|
* <p>Should be called before each drawing call.
|
||||||
|
*/
|
||||||
|
public void bind() {
|
||||||
|
switch (type) {
|
||||||
|
case GLES20.GL_FLOAT:
|
||||||
|
GLES20.glUniform1fv(location, /* count= */ 1, value, /* offset= */ 0);
|
||||||
|
GlUtil.checkGlError();
|
||||||
|
break;
|
||||||
|
case GLES20.GL_FLOAT_VEC2:
|
||||||
|
GLES20.glUniform2fv(location, /* count= */ 1, value, /* offset= */ 0);
|
||||||
|
GlUtil.checkGlError();
|
||||||
|
break;
|
||||||
|
case GLES20.GL_FLOAT_VEC3:
|
||||||
|
GLES20.glUniform3fv(location, /* count= */ 1, value, /* offset= */ 0);
|
||||||
|
GlUtil.checkGlError();
|
||||||
|
break;
|
||||||
|
case GLES20.GL_FLOAT_MAT3:
|
||||||
|
GLES20.glUniformMatrix3fv(
|
||||||
|
location, /* count= */ 1, /* transpose= */ false, value, /* offset= */ 0);
|
||||||
|
GlUtil.checkGlError();
|
||||||
|
break;
|
||||||
|
case GLES20.GL_FLOAT_MAT4:
|
||||||
|
GLES20.glUniformMatrix4fv(
|
||||||
|
location, /* count= */ 1, /* transpose= */ false, value, /* offset= */ 0);
|
||||||
|
GlUtil.checkGlError();
|
||||||
|
break;
|
||||||
|
case GLES20.GL_SAMPLER_2D:
|
||||||
|
case GLES11Ext.GL_SAMPLER_EXTERNAL_OES:
|
||||||
|
case GL_SAMPLER_EXTERNAL_2D_Y2Y_EXT:
|
||||||
|
if (texId == 0) {
|
||||||
|
throw new IllegalStateException("No call to setSamplerTexId() before bind.");
|
||||||
|
}
|
||||||
|
GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + texUnitIndex);
|
||||||
|
GlUtil.checkGlError();
|
||||||
|
GlUtil.bindTexture(
|
||||||
|
type == GLES20.GL_SAMPLER_2D
|
||||||
|
? GLES20.GL_TEXTURE_2D
|
||||||
|
: GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
|
||||||
|
texId);
|
||||||
|
GLES20.glUniform1i(location, texUnitIndex);
|
||||||
|
GlUtil.checkGlError();
|
||||||
|
break;
|
||||||
|
default:
|
||||||
|
throw new IllegalStateException("Unexpected uniform type: " + type);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -16,7 +16,6 @@
|
|||||||
package androidx.media3.common.util;
|
package androidx.media3.common.util;
|
||||||
|
|
||||||
import static android.opengl.GLU.gluErrorString;
|
import static android.opengl.GLU.gluErrorString;
|
||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
@ -33,13 +32,10 @@ import androidx.annotation.RequiresApi;
|
|||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.io.InputStream;
|
import java.io.InputStream;
|
||||||
import java.nio.Buffer;
|
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
import java.nio.FloatBuffer;
|
import java.nio.FloatBuffer;
|
||||||
import java.nio.IntBuffer;
|
import java.util.List;
|
||||||
import java.util.HashMap;
|
|
||||||
import java.util.Map;
|
|
||||||
import javax.microedition.khronos.egl.EGL10;
|
import javax.microedition.khronos.egl.EGL10;
|
||||||
|
|
||||||
/** OpenGL ES utilities. */
|
/** OpenGL ES utilities. */
|
||||||
@ -55,157 +51,16 @@ public final class GlUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
// TODO(b/231937416): Consider removing this flag, enabling assertions by default, and making
|
||||||
* Represents a GLSL shader program.
|
// GlException checked.
|
||||||
*
|
|
||||||
* <p>After constructing a program, keep a reference for its lifetime and call {@link #delete()}
|
|
||||||
* (or release the current GL context) when it's no longer needed.
|
|
||||||
*/
|
|
||||||
public static final class Program {
|
|
||||||
/** The identifier of a compiled and linked GLSL shader program. */
|
|
||||||
private final int programId;
|
|
||||||
|
|
||||||
private final Attribute[] attributes;
|
|
||||||
private final Uniform[] uniforms;
|
|
||||||
private final Map<String, Attribute> attributeByName;
|
|
||||||
private final Map<String, Uniform> uniformByName;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Compiles a GL shader program from vertex and fragment shader GLSL GLES20 code.
|
|
||||||
*
|
|
||||||
* @param context The {@link Context}.
|
|
||||||
* @param vertexShaderFilePath The path to a vertex shader program.
|
|
||||||
* @param fragmentShaderFilePath The path to a fragment shader program.
|
|
||||||
* @throws IOException When failing to read shader files.
|
|
||||||
*/
|
|
||||||
public Program(Context context, String vertexShaderFilePath, String fragmentShaderFilePath)
|
|
||||||
throws IOException {
|
|
||||||
this(loadAsset(context, vertexShaderFilePath), loadAsset(context, fragmentShaderFilePath));
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a GL shader program from vertex and fragment shader GLSL GLES20 code.
|
|
||||||
*
|
|
||||||
* <p>This involves slow steps, like compiling, linking, and switching the GL program, so do not
|
|
||||||
* call this in fast rendering loops.
|
|
||||||
*
|
|
||||||
* @param vertexShaderGlsl The vertex shader program.
|
|
||||||
* @param fragmentShaderGlsl The fragment shader program.
|
|
||||||
*/
|
|
||||||
public Program(String vertexShaderGlsl, String fragmentShaderGlsl) {
|
|
||||||
programId = GLES20.glCreateProgram();
|
|
||||||
checkGlError();
|
|
||||||
|
|
||||||
// Add the vertex and fragment shaders.
|
|
||||||
addShader(programId, GLES20.GL_VERTEX_SHADER, vertexShaderGlsl);
|
|
||||||
addShader(programId, GLES20.GL_FRAGMENT_SHADER, fragmentShaderGlsl);
|
|
||||||
|
|
||||||
// Link and use the program, and enumerate attributes/uniforms.
|
|
||||||
GLES20.glLinkProgram(programId);
|
|
||||||
int[] linkStatus = new int[] {GLES20.GL_FALSE};
|
|
||||||
GLES20.glGetProgramiv(programId, GLES20.GL_LINK_STATUS, linkStatus, /* offset= */ 0);
|
|
||||||
if (linkStatus[0] != GLES20.GL_TRUE) {
|
|
||||||
throwGlException(
|
|
||||||
"Unable to link shader program: \n" + GLES20.glGetProgramInfoLog(programId));
|
|
||||||
}
|
|
||||||
GLES20.glUseProgram(programId);
|
|
||||||
attributeByName = new HashMap<>();
|
|
||||||
int[] attributeCount = new int[1];
|
|
||||||
GLES20.glGetProgramiv(
|
|
||||||
programId, GLES20.GL_ACTIVE_ATTRIBUTES, attributeCount, /* offset= */ 0);
|
|
||||||
attributes = new Attribute[attributeCount[0]];
|
|
||||||
for (int i = 0; i < attributeCount[0]; i++) {
|
|
||||||
Attribute attribute = Attribute.create(programId, i);
|
|
||||||
attributes[i] = attribute;
|
|
||||||
attributeByName.put(attribute.name, attribute);
|
|
||||||
}
|
|
||||||
uniformByName = new HashMap<>();
|
|
||||||
int[] uniformCount = new int[1];
|
|
||||||
GLES20.glGetProgramiv(programId, GLES20.GL_ACTIVE_UNIFORMS, uniformCount, /* offset= */ 0);
|
|
||||||
uniforms = new Uniform[uniformCount[0]];
|
|
||||||
for (int i = 0; i < uniformCount[0]; i++) {
|
|
||||||
Uniform uniform = Uniform.create(programId, i);
|
|
||||||
uniforms[i] = uniform;
|
|
||||||
uniformByName.put(uniform.name, uniform);
|
|
||||||
}
|
|
||||||
checkGlError();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Uses the program.
|
|
||||||
*
|
|
||||||
* <p>Call this in the rendering loop to switch between different programs.
|
|
||||||
*/
|
|
||||||
public void use() {
|
|
||||||
// TODO(http://b/205002913): When multiple GL programs are supported by Transformer, make sure
|
|
||||||
// to call use() to switch between programs.
|
|
||||||
GLES20.glUseProgram(programId);
|
|
||||||
checkGlError();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Deletes the program. Deleted programs cannot be used again. */
|
|
||||||
public void delete() {
|
|
||||||
GLES20.glDeleteProgram(programId);
|
|
||||||
checkGlError();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the location of an {@link Attribute}, which has been enabled as a vertex attribute
|
|
||||||
* array.
|
|
||||||
*/
|
|
||||||
public int getAttributeArrayLocationAndEnable(String attributeName) {
|
|
||||||
int location = getAttributeLocation(attributeName);
|
|
||||||
GLES20.glEnableVertexAttribArray(location);
|
|
||||||
checkGlError();
|
|
||||||
return location;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns the location of an {@link Attribute}. */
|
|
||||||
private int getAttributeLocation(String attributeName) {
|
|
||||||
return GlUtil.getAttributeLocation(programId, attributeName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns the location of a {@link Uniform}. */
|
|
||||||
public int getUniformLocation(String uniformName) {
|
|
||||||
return GlUtil.getUniformLocation(programId, uniformName);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Sets a float buffer type attribute. */
|
|
||||||
public void setBufferAttribute(String name, float[] values, int size) {
|
|
||||||
checkNotNull(attributeByName.get(name)).setBuffer(values, size);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Sets a texture sampler type uniform. */
|
|
||||||
public void setSamplerTexIdUniform(String name, int texId, int unit) {
|
|
||||||
checkNotNull(uniformByName.get(name)).setSamplerTexId(texId, unit);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Sets a float type uniform. */
|
|
||||||
public void setFloatUniform(String name, float value) {
|
|
||||||
checkNotNull(uniformByName.get(name)).setFloat(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Sets a float array type uniform. */
|
|
||||||
public void setFloatsUniform(String name, float[] value) {
|
|
||||||
checkNotNull(uniformByName.get(name)).setFloats(value);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Binds all attributes and uniforms in the program. */
|
|
||||||
public void bindAttributesAndUniforms() {
|
|
||||||
for (Attribute attribute : attributes) {
|
|
||||||
attribute.bind();
|
|
||||||
}
|
|
||||||
for (Uniform uniform : uniforms) {
|
|
||||||
uniform.bind();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Whether to throw a {@link GlException} in case of an OpenGL error. */
|
/** Whether to throw a {@link GlException} in case of an OpenGL error. */
|
||||||
public static boolean glAssertionsEnabled = false;
|
public static boolean glAssertionsEnabled = false;
|
||||||
|
|
||||||
/** Number of vertices in a rectangle. */
|
/** Number of elements in a 3d homogeneous coordinate vector describing a vertex. */
|
||||||
public static final int RECTANGLE_VERTICES_COUNT = 4;
|
public static final int HOMOGENEOUS_COORDINATE_VECTOR_SIZE = 4;
|
||||||
|
|
||||||
|
/** Length of the normalized device coordinate (NDC) space, which spans from -1 to 1. */
|
||||||
|
public static final float LENGTH_NDC = 2f;
|
||||||
|
|
||||||
private static final String TAG = "GlUtil";
|
private static final String TAG = "GlUtil";
|
||||||
|
|
||||||
@ -214,8 +69,6 @@ public final class GlUtil {
|
|||||||
// https://www.khronos.org/registry/EGL/extensions/KHR/EGL_KHR_surfaceless_context.txt
|
// https://www.khronos.org/registry/EGL/extensions/KHR/EGL_KHR_surfaceless_context.txt
|
||||||
private static final String EXTENSION_SURFACELESS_CONTEXT = "EGL_KHR_surfaceless_context";
|
private static final String EXTENSION_SURFACELESS_CONTEXT = "EGL_KHR_surfaceless_context";
|
||||||
|
|
||||||
// https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_YUV_target.txt
|
|
||||||
private static final int GL_SAMPLER_EXTERNAL_2D_Y2Y_EXT = 0x8BE7;
|
|
||||||
// https://www.khronos.org/registry/EGL/extensions/KHR/EGL_KHR_gl_colorspace.txt
|
// https://www.khronos.org/registry/EGL/extensions/KHR/EGL_KHR_gl_colorspace.txt
|
||||||
private static final int EGL_GL_COLORSPACE_KHR = 0x309D;
|
private static final int EGL_GL_COLORSPACE_KHR = 0x309D;
|
||||||
// https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_gl_colorspace_bt2020_linear.txt
|
// https://www.khronos.org/registry/EGL/extensions/EXT/EGL_EXT_gl_colorspace_bt2020_linear.txt
|
||||||
@ -270,9 +123,24 @@ public final class GlUtil {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Flattens the list of 4 element NDC coordinate vectors into a buffer. */
|
||||||
|
public static float[] createVertexBuffer(List<float[]> vertexList) {
|
||||||
|
float[] vertexBuffer = new float[HOMOGENEOUS_COORDINATE_VECTOR_SIZE * vertexList.size()];
|
||||||
|
for (int i = 0; i < vertexList.size(); i++) {
|
||||||
|
System.arraycopy(
|
||||||
|
/* src= */ vertexList.get(i),
|
||||||
|
/* srcPos= */ 0,
|
||||||
|
/* dest= */ vertexBuffer,
|
||||||
|
/* destPos= */ HOMOGENEOUS_COORDINATE_VECTOR_SIZE * i,
|
||||||
|
/* length= */ HOMOGENEOUS_COORDINATE_VECTOR_SIZE);
|
||||||
|
}
|
||||||
|
return vertexBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether creating a GL context with {@value #EXTENSION_PROTECTED_CONTENT} is possible.
|
* Returns whether creating a GL context with {@value #EXTENSION_PROTECTED_CONTENT} is possible.
|
||||||
* If {@code true}, the device supports a protected output path for DRM content when using GL.
|
*
|
||||||
|
* <p>If {@code true}, the device supports a protected output path for DRM content when using GL.
|
||||||
*/
|
*/
|
||||||
public static boolean isProtectedContentExtensionSupported(Context context) {
|
public static boolean isProtectedContentExtensionSupported(Context context) {
|
||||||
if (Util.SDK_INT < 24) {
|
if (Util.SDK_INT < 24) {
|
||||||
@ -299,7 +167,11 @@ public final class GlUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns whether creating a GL context with {@value #EXTENSION_SURFACELESS_CONTEXT} is possible.
|
* Returns whether the {@value #EXTENSION_SURFACELESS_CONTEXT} extension is supported.
|
||||||
|
*
|
||||||
|
* <p>This extension allows passing {@link EGL14#EGL_NO_SURFACE} for both the write and read
|
||||||
|
* surfaces in a call to {@link EGL14#eglMakeCurrent(EGLDisplay, EGLSurface, EGLSurface,
|
||||||
|
* EGLContext)}.
|
||||||
*/
|
*/
|
||||||
public static boolean isSurfacelessContextExtensionSupported() {
|
public static boolean isSurfacelessContextExtensionSupported() {
|
||||||
if (Util.SDK_INT < 17) {
|
if (Util.SDK_INT < 17) {
|
||||||
@ -359,6 +231,77 @@ public final class GlUtil {
|
|||||||
EGL_WINDOW_SURFACE_ATTRIBUTES_BT2020_PQ);
|
EGL_WINDOW_SURFACE_ATTRIBUTES_BT2020_PQ);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link EGLSurface} wrapping a pixel buffer.
|
||||||
|
*
|
||||||
|
* @param eglDisplay The {@link EGLDisplay} to attach the surface to.
|
||||||
|
* @param width The width of the pixel buffer.
|
||||||
|
* @param height The height of the pixel buffer.
|
||||||
|
*/
|
||||||
|
@RequiresApi(17)
|
||||||
|
private static EGLSurface createPbufferSurface(EGLDisplay eglDisplay, int width, int height) {
|
||||||
|
int[] pbufferAttributes =
|
||||||
|
new int[] {
|
||||||
|
EGL14.EGL_WIDTH, width,
|
||||||
|
EGL14.EGL_HEIGHT, height,
|
||||||
|
EGL14.EGL_NONE
|
||||||
|
};
|
||||||
|
return Api17.createEglPbufferSurface(
|
||||||
|
eglDisplay, EGL_CONFIG_ATTRIBUTES_RGBA_8888, pbufferAttributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a placeholder {@link EGLSurface} to use when reading and writing to the surface is not
|
||||||
|
* required.
|
||||||
|
*
|
||||||
|
* @param eglDisplay The {@link EGLDisplay} to attach the surface to.
|
||||||
|
* @return {@link EGL14#EGL_NO_SURFACE} if supported and a 1x1 pixel buffer surface otherwise.
|
||||||
|
*/
|
||||||
|
@RequiresApi(17)
|
||||||
|
public static EGLSurface createPlaceholderEglSurface(EGLDisplay eglDisplay) {
|
||||||
|
return isSurfacelessContextExtensionSupported()
|
||||||
|
? EGL14.EGL_NO_SURFACE
|
||||||
|
: createPbufferSurface(eglDisplay, /* width= */ 1, /* height= */ 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and focuses a new {@link EGLSurface} wrapping a 1x1 pixel buffer.
|
||||||
|
*
|
||||||
|
* @param eglContext The {@link EGLContext} to make current.
|
||||||
|
* @param eglDisplay The {@link EGLDisplay} to attach the surface to.
|
||||||
|
*/
|
||||||
|
@RequiresApi(17)
|
||||||
|
public static void focusPlaceholderEglSurface(EGLContext eglContext, EGLDisplay eglDisplay) {
|
||||||
|
EGLSurface eglSurface = createPbufferSurface(eglDisplay, /* width= */ 1, /* height= */ 1);
|
||||||
|
focusEglSurface(eglDisplay, eglContext, eglSurface, /* width= */ 1, /* height= */ 1);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and focuses a new {@link EGLSurface} wrapping a 1x1 pixel buffer, for HDR rendering
|
||||||
|
* with Rec. 2020 color primaries and using the PQ transfer function.
|
||||||
|
*
|
||||||
|
* @param eglContext The {@link EGLContext} to make current.
|
||||||
|
* @param eglDisplay The {@link EGLDisplay} to attach the surface to.
|
||||||
|
*/
|
||||||
|
@RequiresApi(17)
|
||||||
|
public static void focusPlaceholderEglSurfaceBt2020Pq(
|
||||||
|
EGLContext eglContext, EGLDisplay eglDisplay) {
|
||||||
|
int[] pbufferAttributes =
|
||||||
|
new int[] {
|
||||||
|
EGL14.EGL_WIDTH,
|
||||||
|
/* width= */ 1,
|
||||||
|
EGL14.EGL_HEIGHT,
|
||||||
|
/* height= */ 1,
|
||||||
|
EGL_GL_COLORSPACE_KHR,
|
||||||
|
EGL_GL_COLORSPACE_BT2020_PQ_EXT,
|
||||||
|
EGL14.EGL_NONE
|
||||||
|
};
|
||||||
|
EGLSurface eglSurface =
|
||||||
|
Api17.createEglPbufferSurface(
|
||||||
|
eglDisplay, EGL_CONFIG_ATTRIBUTES_RGBA_1010102, pbufferAttributes);
|
||||||
|
focusEglSurface(eglDisplay, eglContext, eglSurface, /* width= */ 1, /* height= */ 1);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* If there is an OpenGl error, logs the error and if {@link #glAssertionsEnabled} is true throws
|
* If there is an OpenGl error, logs the error and if {@link #glAssertionsEnabled} is true throws
|
||||||
* a {@link GlException}.
|
* a {@link GlException}.
|
||||||
@ -376,13 +319,54 @@ public final class GlUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Makes the specified {@code surface} the render target, using a viewport of {@code width} by
|
* Asserts the texture size is valid.
|
||||||
|
*
|
||||||
|
* @param width The width for a texture.
|
||||||
|
* @param height The height for a texture.
|
||||||
|
* @throws GlException If the texture width or height is invalid.
|
||||||
|
*/
|
||||||
|
public static void assertValidTextureSize(int width, int height) {
|
||||||
|
// TODO(b/201293185): Consider handling adjustments for sizes > GL_MAX_TEXTURE_SIZE
|
||||||
|
// (ex. downscaling appropriately) in a texture processor instead of asserting incorrect
|
||||||
|
// values.
|
||||||
|
|
||||||
|
// For valid GL sizes, see:
|
||||||
|
// https://www.khronos.org/registry/OpenGL-Refpages/es2.0/xhtml/glTexImage2D.xml
|
||||||
|
int[] maxTextureSizeBuffer = new int[1];
|
||||||
|
GLES20.glGetIntegerv(GLES20.GL_MAX_TEXTURE_SIZE, maxTextureSizeBuffer, 0);
|
||||||
|
int maxTextureSize = maxTextureSizeBuffer[0];
|
||||||
|
if (width < 0 || height < 0) {
|
||||||
|
throwGlException("width or height is less than 0");
|
||||||
|
}
|
||||||
|
if (width > maxTextureSize || height > maxTextureSize) {
|
||||||
|
throwGlException("width or height is greater than GL_MAX_TEXTURE_SIZE " + maxTextureSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes the specified {@code eglSurface} the render target, using a viewport of {@code width} by
|
||||||
* {@code height} pixels.
|
* {@code height} pixels.
|
||||||
*/
|
*/
|
||||||
@RequiresApi(17)
|
@RequiresApi(17)
|
||||||
public static void focusSurface(
|
public static void focusEglSurface(
|
||||||
EGLDisplay eglDisplay, EGLContext eglContext, EGLSurface surface, int width, int height) {
|
EGLDisplay eglDisplay, EGLContext eglContext, EGLSurface eglSurface, int width, int height) {
|
||||||
Api17.focusSurface(eglDisplay, eglContext, surface, width, height);
|
Api17.focusRenderTarget(
|
||||||
|
eglDisplay, eglContext, eglSurface, /* framebuffer= */ 0, width, height);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Makes the specified {@code framebuffer} the render target, using a viewport of {@code width} by
|
||||||
|
* {@code height} pixels.
|
||||||
|
*/
|
||||||
|
@RequiresApi(17)
|
||||||
|
public static void focusFramebuffer(
|
||||||
|
EGLDisplay eglDisplay,
|
||||||
|
EGLContext eglContext,
|
||||||
|
EGLSurface eglSurface,
|
||||||
|
int framebuffer,
|
||||||
|
int width,
|
||||||
|
int height) {
|
||||||
|
Api17.focusRenderTarget(eglDisplay, eglContext, eglSurface, framebuffer, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -447,49 +431,94 @@ public final class GlUtil {
|
|||||||
* GL_CLAMP_TO_EDGE wrapping.
|
* GL_CLAMP_TO_EDGE wrapping.
|
||||||
*/
|
*/
|
||||||
public static int createExternalTexture() {
|
public static int createExternalTexture() {
|
||||||
|
int texId = generateTexture();
|
||||||
|
bindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texId);
|
||||||
|
return texId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the texture identifier for a newly-allocated texture with the specified dimensions.
|
||||||
|
*
|
||||||
|
* @param width of the new texture in pixels
|
||||||
|
* @param height of the new texture in pixels
|
||||||
|
*/
|
||||||
|
public static int createTexture(int width, int height) {
|
||||||
|
assertValidTextureSize(width, height);
|
||||||
|
int texId = generateTexture();
|
||||||
|
bindTexture(GLES20.GL_TEXTURE_2D, texId);
|
||||||
|
ByteBuffer byteBuffer = ByteBuffer.allocateDirect(width * height * 4);
|
||||||
|
GLES20.glTexImage2D(
|
||||||
|
GLES20.GL_TEXTURE_2D,
|
||||||
|
/* level= */ 0,
|
||||||
|
GLES20.GL_RGBA,
|
||||||
|
width,
|
||||||
|
height,
|
||||||
|
/* border= */ 0,
|
||||||
|
GLES20.GL_RGBA,
|
||||||
|
GLES20.GL_UNSIGNED_BYTE,
|
||||||
|
byteBuffer);
|
||||||
|
checkGlError();
|
||||||
|
return texId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a new GL texture identifier. */
|
||||||
|
private static int generateTexture() {
|
||||||
|
checkEglException(
|
||||||
|
!Util.areEqual(EGL14.eglGetCurrentContext(), EGL14.EGL_NO_CONTEXT), "No current context");
|
||||||
|
|
||||||
int[] texId = new int[1];
|
int[] texId = new int[1];
|
||||||
GLES20.glGenTextures(/* n= */ 1, IntBuffer.wrap(texId));
|
GLES20.glGenTextures(/* n= */ 1, texId, /* offset= */ 0);
|
||||||
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texId[0]);
|
|
||||||
GLES20.glTexParameteri(
|
|
||||||
GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
|
|
||||||
GLES20.glTexParameteri(
|
|
||||||
GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
|
|
||||||
GLES20.glTexParameteri(
|
|
||||||
GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
|
|
||||||
GLES20.glTexParameteri(
|
|
||||||
GLES11Ext.GL_TEXTURE_EXTERNAL_OES, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
|
|
||||||
checkGlError();
|
checkGlError();
|
||||||
return texId[0];
|
return texId[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addShader(int programId, int type, String glsl) {
|
/**
|
||||||
int shader = GLES20.glCreateShader(type);
|
* Binds the texture of the given type with default configuration of GL_LINEAR filtering and
|
||||||
GLES20.glShaderSource(shader, glsl);
|
* GL_CLAMP_TO_EDGE wrapping.
|
||||||
GLES20.glCompileShader(shader);
|
*
|
||||||
|
* @param texId The texture identifier.
|
||||||
int[] result = new int[] {GLES20.GL_FALSE};
|
* @param textureTarget The target to which the texture is bound, e.g. {@link
|
||||||
GLES20.glGetShaderiv(shader, GLES20.GL_COMPILE_STATUS, result, /* offset= */ 0);
|
* GLES20#GL_TEXTURE_2D} for a two-dimensional texture or {@link
|
||||||
if (result[0] != GLES20.GL_TRUE) {
|
* GLES11Ext#GL_TEXTURE_EXTERNAL_OES} for an external texture.
|
||||||
throwGlException(GLES20.glGetShaderInfoLog(shader) + ", source: " + glsl);
|
*/
|
||||||
}
|
public static void bindTexture(int textureTarget, int texId) {
|
||||||
|
GLES20.glBindTexture(textureTarget, texId);
|
||||||
GLES20.glAttachShader(programId, shader);
|
checkGlError();
|
||||||
GLES20.glDeleteShader(shader);
|
GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
|
||||||
|
checkGlError();
|
||||||
|
GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
|
||||||
|
checkGlError();
|
||||||
|
GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
|
||||||
|
checkGlError();
|
||||||
|
GLES20.glTexParameteri(textureTarget, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
|
||||||
checkGlError();
|
checkGlError();
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int getAttributeLocation(int programId, String attributeName) {
|
/**
|
||||||
return GLES20.glGetAttribLocation(programId, attributeName);
|
* Returns a new framebuffer for the texture.
|
||||||
|
*
|
||||||
|
* @param texId The identifier of the texture to attach to the framebuffer.
|
||||||
|
*/
|
||||||
|
public static int createFboForTexture(int texId) {
|
||||||
|
checkEglException(
|
||||||
|
!Util.areEqual(EGL14.eglGetCurrentContext(), EGL14.EGL_NO_CONTEXT), "No current context");
|
||||||
|
|
||||||
|
int[] fboId = new int[1];
|
||||||
|
GLES20.glGenFramebuffers(/* n= */ 1, fboId, /* offset= */ 0);
|
||||||
|
checkGlError();
|
||||||
|
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, fboId[0]);
|
||||||
|
checkGlError();
|
||||||
|
GLES20.glFramebufferTexture2D(
|
||||||
|
GLES20.GL_FRAMEBUFFER, GLES20.GL_COLOR_ATTACHMENT0, GLES20.GL_TEXTURE_2D, texId, 0);
|
||||||
|
checkGlError();
|
||||||
|
return fboId[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int getUniformLocation(int programId, String uniformName) {
|
/* package */ static void throwGlException(String errorMsg) {
|
||||||
return GLES20.glGetUniformLocation(programId, uniformName);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static void throwGlException(String errorMsg) {
|
|
||||||
Log.e(TAG, errorMsg);
|
|
||||||
if (glAssertionsEnabled) {
|
if (glAssertionsEnabled) {
|
||||||
throw new GlException(errorMsg);
|
throw new GlException(errorMsg);
|
||||||
|
} else {
|
||||||
|
Log.e(TAG, errorMsg);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -499,205 +528,9 @@ public final class GlUtil {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the length of the null-terminated string in {@code strVal}. */
|
private static void checkEglException(String errorMessage) {
|
||||||
private static int strlen(byte[] strVal) {
|
int error = EGL14.eglGetError();
|
||||||
for (int i = 0; i < strVal.length; ++i) {
|
checkEglException(error == EGL14.EGL_SUCCESS, errorMessage + ", error code: " + error);
|
||||||
if (strVal[i] == '\0') {
|
|
||||||
return i;
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return strVal.length;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GL attribute, which can be attached to a buffer with {@link Attribute#setBuffer(float[], int)}.
|
|
||||||
*/
|
|
||||||
private static final class Attribute {
|
|
||||||
|
|
||||||
/* Returns the attribute at the given index in the program. */
|
|
||||||
public static Attribute create(int programId, int index) {
|
|
||||||
int[] length = new int[1];
|
|
||||||
GLES20.glGetProgramiv(
|
|
||||||
programId, GLES20.GL_ACTIVE_ATTRIBUTE_MAX_LENGTH, length, /* offset= */ 0);
|
|
||||||
byte[] nameBytes = new byte[length[0]];
|
|
||||||
|
|
||||||
GLES20.glGetActiveAttrib(
|
|
||||||
programId,
|
|
||||||
index,
|
|
||||||
length[0],
|
|
||||||
/* unusedLength */ new int[1],
|
|
||||||
/* lengthOffset= */ 0,
|
|
||||||
/* unusedSize */ new int[1],
|
|
||||||
/* sizeOffset= */ 0,
|
|
||||||
/* unusedType */ new int[1],
|
|
||||||
/* typeOffset= */ 0,
|
|
||||||
nameBytes,
|
|
||||||
/* nameOffset= */ 0);
|
|
||||||
String name = new String(nameBytes, /* offset= */ 0, strlen(nameBytes));
|
|
||||||
int location = getAttributeLocation(programId, name);
|
|
||||||
|
|
||||||
return new Attribute(name, index, location);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The name of the attribute in the GLSL sources. */
|
|
||||||
public final String name;
|
|
||||||
|
|
||||||
private final int index;
|
|
||||||
private final int location;
|
|
||||||
|
|
||||||
@Nullable private Buffer buffer;
|
|
||||||
private int size;
|
|
||||||
|
|
||||||
private Attribute(String name, int index, int location) {
|
|
||||||
this.name = name;
|
|
||||||
this.index = index;
|
|
||||||
this.location = location;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures {@link #bind()} to attach vertices in {@code buffer} (each of size {@code size}
|
|
||||||
* elements) to this {@link Attribute}.
|
|
||||||
*
|
|
||||||
* @param buffer Buffer to bind to this attribute.
|
|
||||||
* @param size Number of elements per vertex.
|
|
||||||
*/
|
|
||||||
public void setBuffer(float[] buffer, int size) {
|
|
||||||
this.buffer = createBuffer(buffer);
|
|
||||||
this.size = size;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the vertex attribute to whatever was attached via {@link #setBuffer(float[], int)}.
|
|
||||||
*
|
|
||||||
* <p>Should be called before each drawing call.
|
|
||||||
*/
|
|
||||||
public void bind() {
|
|
||||||
Buffer buffer = checkNotNull(this.buffer, "call setBuffer before bind");
|
|
||||||
GLES20.glBindBuffer(GLES20.GL_ARRAY_BUFFER, /* buffer= */ 0);
|
|
||||||
GLES20.glVertexAttribPointer(
|
|
||||||
location, size, GLES20.GL_FLOAT, /* normalized= */ false, /* stride= */ 0, buffer);
|
|
||||||
GLES20.glEnableVertexAttribArray(index);
|
|
||||||
checkGlError();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* GL uniform, which can be attached to a sampler using {@link Uniform#setSamplerTexId(int, int)}.
|
|
||||||
*/
|
|
||||||
private static final class Uniform {
|
|
||||||
|
|
||||||
/** Returns the uniform at the given index in the program. */
|
|
||||||
public static Uniform create(int programId, int index) {
|
|
||||||
int[] length = new int[1];
|
|
||||||
GLES20.glGetProgramiv(
|
|
||||||
programId, GLES20.GL_ACTIVE_UNIFORM_MAX_LENGTH, length, /* offset= */ 0);
|
|
||||||
|
|
||||||
int[] type = new int[1];
|
|
||||||
byte[] nameBytes = new byte[length[0]];
|
|
||||||
|
|
||||||
GLES20.glGetActiveUniform(
|
|
||||||
programId,
|
|
||||||
index,
|
|
||||||
length[0],
|
|
||||||
/* unusedLength */ new int[1],
|
|
||||||
/* lengthOffset= */ 0,
|
|
||||||
/* unusedSize */ new int[1],
|
|
||||||
/*sizeOffset= */ 0,
|
|
||||||
type,
|
|
||||||
/* typeOffset= */ 0,
|
|
||||||
nameBytes,
|
|
||||||
/* nameOffset= */ 0);
|
|
||||||
String name = new String(nameBytes, /* offset= */ 0, strlen(nameBytes));
|
|
||||||
int location = getUniformLocation(programId, name);
|
|
||||||
|
|
||||||
return new Uniform(name, location, type[0]);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** The name of the uniform in the GLSL sources. */
|
|
||||||
public final String name;
|
|
||||||
|
|
||||||
private final int location;
|
|
||||||
private final int type;
|
|
||||||
private final float[] value;
|
|
||||||
|
|
||||||
private int texId;
|
|
||||||
private int unit;
|
|
||||||
|
|
||||||
private Uniform(String name, int location, int type) {
|
|
||||||
this.name = name;
|
|
||||||
this.location = location;
|
|
||||||
this.type = type;
|
|
||||||
this.value = new float[16];
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Configures {@link #bind()} to use the specified {@code texId} for this sampler uniform.
|
|
||||||
*
|
|
||||||
* @param texId The GL texture identifier from which to sample.
|
|
||||||
* @param unit The GL texture unit index.
|
|
||||||
*/
|
|
||||||
public void setSamplerTexId(int texId, int unit) {
|
|
||||||
this.texId = texId;
|
|
||||||
this.unit = unit;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Configures {@link #bind()} to use the specified float {@code value} for this uniform. */
|
|
||||||
public void setFloat(float value) {
|
|
||||||
this.value[0] = value;
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Configures {@link #bind()} to use the specified float[] {@code value} for this uniform. */
|
|
||||||
public void setFloats(float[] value) {
|
|
||||||
System.arraycopy(value, /* srcPos= */ 0, this.value, /* destPos= */ 0, value.length);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the uniform to whatever value was passed via {@link #setSamplerTexId(int, int)}, {@link
|
|
||||||
* #setFloat(float)} or {@link #setFloats(float[])}.
|
|
||||||
*
|
|
||||||
* <p>Should be called before each drawing call.
|
|
||||||
*/
|
|
||||||
public void bind() {
|
|
||||||
if (type == GLES20.GL_FLOAT) {
|
|
||||||
GLES20.glUniform1fv(location, /* count= */ 1, value, /* offset= */ 0);
|
|
||||||
checkGlError();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == GLES20.GL_FLOAT_MAT3) {
|
|
||||||
GLES20.glUniformMatrix3fv(
|
|
||||||
location, /* count= */ 1, /* transpose= */ false, value, /* offset= */ 0);
|
|
||||||
checkGlError();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (type == GLES20.GL_FLOAT_MAT4) {
|
|
||||||
GLES20.glUniformMatrix4fv(
|
|
||||||
location, /* count= */ 1, /* transpose= */ false, value, /* offset= */ 0);
|
|
||||||
checkGlError();
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
|
|
||||||
if (texId == 0) {
|
|
||||||
throw new IllegalStateException("No call to setSamplerTexId() before bind.");
|
|
||||||
}
|
|
||||||
GLES20.glActiveTexture(GLES20.GL_TEXTURE0 + unit);
|
|
||||||
if (type == GLES11Ext.GL_SAMPLER_EXTERNAL_OES || type == GL_SAMPLER_EXTERNAL_2D_Y2Y_EXT) {
|
|
||||||
GLES20.glBindTexture(GLES11Ext.GL_TEXTURE_EXTERNAL_OES, texId);
|
|
||||||
} else if (type == GLES20.GL_SAMPLER_2D) {
|
|
||||||
GLES20.glBindTexture(GLES20.GL_TEXTURE_2D, texId);
|
|
||||||
} else {
|
|
||||||
throw new IllegalStateException("Unexpected uniform type: " + type);
|
|
||||||
}
|
|
||||||
GLES20.glUniform1i(location, unit);
|
|
||||||
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MAG_FILTER, GLES20.GL_LINEAR);
|
|
||||||
GLES20.glTexParameteri(GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_MIN_FILTER, GLES20.GL_LINEAR);
|
|
||||||
GLES20.glTexParameteri(
|
|
||||||
GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_S, GLES20.GL_CLAMP_TO_EDGE);
|
|
||||||
GLES20.glTexParameteri(
|
|
||||||
GLES20.GL_TEXTURE_2D, GLES20.GL_TEXTURE_WRAP_T, GLES20.GL_CLAMP_TO_EDGE);
|
|
||||||
checkGlError();
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(17)
|
@RequiresApi(17)
|
||||||
@ -748,25 +581,48 @@ public final class GlUtil {
|
|||||||
Object surface,
|
Object surface,
|
||||||
int[] configAttributes,
|
int[] configAttributes,
|
||||||
int[] windowSurfaceAttributes) {
|
int[] windowSurfaceAttributes) {
|
||||||
return EGL14.eglCreateWindowSurface(
|
EGLSurface eglSurface =
|
||||||
|
EGL14.eglCreateWindowSurface(
|
||||||
eglDisplay,
|
eglDisplay,
|
||||||
getEglConfig(eglDisplay, configAttributes),
|
getEglConfig(eglDisplay, configAttributes),
|
||||||
surface,
|
surface,
|
||||||
windowSurfaceAttributes,
|
windowSurfaceAttributes,
|
||||||
/* offset= */ 0);
|
/* offset= */ 0);
|
||||||
|
checkEglException("Error creating surface");
|
||||||
|
return eglSurface;
|
||||||
}
|
}
|
||||||
|
|
||||||
@DoNotInline
|
@DoNotInline
|
||||||
public static void focusSurface(
|
public static EGLSurface createEglPbufferSurface(
|
||||||
EGLDisplay eglDisplay, EGLContext eglContext, EGLSurface surface, int width, int height) {
|
EGLDisplay eglDisplay, int[] configAttributes, int[] pbufferAttributes) {
|
||||||
int[] boundFrameBuffer = new int[1];
|
EGLSurface eglSurface =
|
||||||
GLES20.glGetIntegerv(GLES20.GL_FRAMEBUFFER_BINDING, boundFrameBuffer, /* offset= */ 0);
|
EGL14.eglCreatePbufferSurface(
|
||||||
int defaultFrameBuffer = 0;
|
eglDisplay,
|
||||||
if (boundFrameBuffer[0] != defaultFrameBuffer) {
|
getEglConfig(eglDisplay, configAttributes),
|
||||||
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, defaultFrameBuffer);
|
pbufferAttributes,
|
||||||
|
/* offset= */ 0);
|
||||||
|
checkEglException("Error creating surface");
|
||||||
|
return eglSurface;
|
||||||
}
|
}
|
||||||
EGL14.eglMakeCurrent(eglDisplay, surface, surface, eglContext);
|
|
||||||
|
@DoNotInline
|
||||||
|
public static void focusRenderTarget(
|
||||||
|
EGLDisplay eglDisplay,
|
||||||
|
EGLContext eglContext,
|
||||||
|
EGLSurface eglSurface,
|
||||||
|
int framebuffer,
|
||||||
|
int width,
|
||||||
|
int height) {
|
||||||
|
int[] boundFramebuffer = new int[1];
|
||||||
|
GLES20.glGetIntegerv(GLES20.GL_FRAMEBUFFER_BINDING, boundFramebuffer, /* offset= */ 0);
|
||||||
|
if (boundFramebuffer[0] != framebuffer) {
|
||||||
|
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, framebuffer);
|
||||||
|
}
|
||||||
|
checkGlError();
|
||||||
|
EGL14.eglMakeCurrent(eglDisplay, eglSurface, eglSurface, eglContext);
|
||||||
|
checkEglException("Error making context current");
|
||||||
GLES20.glViewport(/* x= */ 0, /* y= */ 0, width, height);
|
GLES20.glViewport(/* x= */ 0, /* y= */ 0, width, height);
|
||||||
|
checkGlError();
|
||||||
}
|
}
|
||||||
|
|
||||||
@DoNotInline
|
@DoNotInline
|
||||||
@ -777,19 +633,15 @@ public final class GlUtil {
|
|||||||
}
|
}
|
||||||
EGL14.eglMakeCurrent(
|
EGL14.eglMakeCurrent(
|
||||||
eglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
|
eglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
|
||||||
int error = EGL14.eglGetError();
|
checkEglException("Error releasing context");
|
||||||
checkEglException(error == EGL14.EGL_SUCCESS, "Error releasing context: " + error);
|
|
||||||
if (eglContext != null) {
|
if (eglContext != null) {
|
||||||
EGL14.eglDestroyContext(eglDisplay, eglContext);
|
EGL14.eglDestroyContext(eglDisplay, eglContext);
|
||||||
error = EGL14.eglGetError();
|
checkEglException("Error destroying context");
|
||||||
checkEglException(error == EGL14.EGL_SUCCESS, "Error destroying context: " + error);
|
|
||||||
}
|
}
|
||||||
EGL14.eglReleaseThread();
|
EGL14.eglReleaseThread();
|
||||||
error = EGL14.eglGetError();
|
checkEglException("Error releasing thread");
|
||||||
checkEglException(error == EGL14.EGL_SUCCESS, "Error releasing thread: " + error);
|
|
||||||
EGL14.eglTerminate(eglDisplay);
|
EGL14.eglTerminate(eglDisplay);
|
||||||
error = EGL14.eglGetError();
|
checkEglException("Error terminating display");
|
||||||
checkEglException(error == EGL14.EGL_SUCCESS, "Error terminating display: " + error);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@DoNotInline
|
@DoNotInline
|
||||||
|
@ -27,7 +27,9 @@ public abstract class LibraryLoader {
|
|||||||
private boolean loadAttempted;
|
private boolean loadAttempted;
|
||||||
private boolean isAvailable;
|
private boolean isAvailable;
|
||||||
|
|
||||||
/** @param libraries The names of the libraries to load. */
|
/**
|
||||||
|
* @param libraries The names of the libraries to load.
|
||||||
|
*/
|
||||||
public LibraryLoader(String... libraries) {
|
public LibraryLoader(String... libraries) {
|
||||||
nativeLibraries = libraries;
|
nativeLibraries = libraries;
|
||||||
}
|
}
|
||||||
|
@ -82,7 +82,9 @@ public final class Log {
|
|||||||
Log.logStackTraces = logStackTraces;
|
Log.logStackTraces = logStackTraces;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @see android.util.Log#d(String, String) */
|
/**
|
||||||
|
* @see android.util.Log#d(String, String)
|
||||||
|
*/
|
||||||
@Pure
|
@Pure
|
||||||
public static void d(@Size(max = 23) String tag, String message) {
|
public static void d(@Size(max = 23) String tag, String message) {
|
||||||
if (logLevel == LOG_LEVEL_ALL) {
|
if (logLevel == LOG_LEVEL_ALL) {
|
||||||
@ -90,13 +92,17 @@ public final class Log {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @see android.util.Log#d(String, String, Throwable) */
|
/**
|
||||||
|
* @see android.util.Log#d(String, String, Throwable)
|
||||||
|
*/
|
||||||
@Pure
|
@Pure
|
||||||
public static void d(@Size(max = 23) String tag, String message, @Nullable Throwable throwable) {
|
public static void d(@Size(max = 23) String tag, String message, @Nullable Throwable throwable) {
|
||||||
d(tag, appendThrowableString(message, throwable));
|
d(tag, appendThrowableString(message, throwable));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @see android.util.Log#i(String, String) */
|
/**
|
||||||
|
* @see android.util.Log#i(String, String)
|
||||||
|
*/
|
||||||
@Pure
|
@Pure
|
||||||
public static void i(@Size(max = 23) String tag, String message) {
|
public static void i(@Size(max = 23) String tag, String message) {
|
||||||
if (logLevel <= LOG_LEVEL_INFO) {
|
if (logLevel <= LOG_LEVEL_INFO) {
|
||||||
@ -104,13 +110,17 @@ public final class Log {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @see android.util.Log#i(String, String, Throwable) */
|
/**
|
||||||
|
* @see android.util.Log#i(String, String, Throwable)
|
||||||
|
*/
|
||||||
@Pure
|
@Pure
|
||||||
public static void i(@Size(max = 23) String tag, String message, @Nullable Throwable throwable) {
|
public static void i(@Size(max = 23) String tag, String message, @Nullable Throwable throwable) {
|
||||||
i(tag, appendThrowableString(message, throwable));
|
i(tag, appendThrowableString(message, throwable));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @see android.util.Log#w(String, String) */
|
/**
|
||||||
|
* @see android.util.Log#w(String, String)
|
||||||
|
*/
|
||||||
@Pure
|
@Pure
|
||||||
public static void w(@Size(max = 23) String tag, String message) {
|
public static void w(@Size(max = 23) String tag, String message) {
|
||||||
if (logLevel <= LOG_LEVEL_WARNING) {
|
if (logLevel <= LOG_LEVEL_WARNING) {
|
||||||
@ -118,13 +128,17 @@ public final class Log {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @see android.util.Log#w(String, String, Throwable) */
|
/**
|
||||||
|
* @see android.util.Log#w(String, String, Throwable)
|
||||||
|
*/
|
||||||
@Pure
|
@Pure
|
||||||
public static void w(@Size(max = 23) String tag, String message, @Nullable Throwable throwable) {
|
public static void w(@Size(max = 23) String tag, String message, @Nullable Throwable throwable) {
|
||||||
w(tag, appendThrowableString(message, throwable));
|
w(tag, appendThrowableString(message, throwable));
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @see android.util.Log#e(String, String) */
|
/**
|
||||||
|
* @see android.util.Log#e(String, String)
|
||||||
|
*/
|
||||||
@Pure
|
@Pure
|
||||||
public static void e(@Size(max = 23) String tag, String message) {
|
public static void e(@Size(max = 23) String tag, String message) {
|
||||||
if (logLevel <= LOG_LEVEL_ERROR) {
|
if (logLevel <= LOG_LEVEL_ERROR) {
|
||||||
@ -132,7 +146,9 @@ public final class Log {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @see android.util.Log#e(String, String, Throwable) */
|
/**
|
||||||
|
* @see android.util.Log#e(String, String, Throwable)
|
||||||
|
*/
|
||||||
@Pure
|
@Pure
|
||||||
public static void e(@Size(max = 23) String tag, String message, @Nullable Throwable throwable) {
|
public static void e(@Size(max = 23) String tag, String message, @Nullable Throwable throwable) {
|
||||||
e(tag, appendThrowableString(message, throwable));
|
e(tag, appendThrowableString(message, throwable));
|
||||||
|
@ -30,7 +30,9 @@ public final class LongArray {
|
|||||||
this(DEFAULT_INITIAL_CAPACITY);
|
this(DEFAULT_INITIAL_CAPACITY);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @param initialCapacity The initial capacity of the array. */
|
/**
|
||||||
|
* @param initialCapacity The initial capacity of the array.
|
||||||
|
*/
|
||||||
public LongArray(int initialCapacity) {
|
public LongArray(int initialCapacity) {
|
||||||
values = new long[initialCapacity];
|
values = new long[initialCapacity];
|
||||||
}
|
}
|
||||||
|
@ -47,6 +47,19 @@ public final class MediaFormatUtil {
|
|||||||
// The constant value must not be changed, because it's also set by the framework MediaParser API.
|
// The constant value must not be changed, because it's also set by the framework MediaParser API.
|
||||||
public static final String KEY_PCM_ENCODING_EXTENDED = "exo-pcm-encoding-int";
|
public static final String KEY_PCM_ENCODING_EXTENDED = "exo-pcm-encoding-int";
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The {@link MediaFormat} key for the maximum bitrate in bits per second.
|
||||||
|
*
|
||||||
|
* <p>The associated value is an integer.
|
||||||
|
*
|
||||||
|
* <p>The key string constant is the same as {@code MediaFormat#KEY_MAX_BITRATE}. Values for it
|
||||||
|
* are already returned by the framework MediaExtractor; the key is a hidden field in {@code
|
||||||
|
* MediaFormat} though, which is why it's being replicated here.
|
||||||
|
*/
|
||||||
|
// The constant value must not be changed, because it's also set by the framework MediaParser and
|
||||||
|
// MediaExtractor APIs.
|
||||||
|
public static final String KEY_MAX_BIT_RATE = "max-bitrate";
|
||||||
|
|
||||||
private static final int MAX_POWER_OF_TWO_INT = 1 << 30;
|
private static final int MAX_POWER_OF_TWO_INT = 1 << 30;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -63,6 +76,7 @@ public final class MediaFormatUtil {
|
|||||||
public static MediaFormat createMediaFormatFromFormat(Format format) {
|
public static MediaFormat createMediaFormatFromFormat(Format format) {
|
||||||
MediaFormat result = new MediaFormat();
|
MediaFormat result = new MediaFormat();
|
||||||
maybeSetInteger(result, MediaFormat.KEY_BIT_RATE, format.bitrate);
|
maybeSetInteger(result, MediaFormat.KEY_BIT_RATE, format.bitrate);
|
||||||
|
maybeSetInteger(result, KEY_MAX_BIT_RATE, format.peakBitrate);
|
||||||
maybeSetInteger(result, MediaFormat.KEY_CHANNEL_COUNT, format.channelCount);
|
maybeSetInteger(result, MediaFormat.KEY_CHANNEL_COUNT, format.channelCount);
|
||||||
|
|
||||||
maybeSetColorInfo(result, format.colorInfo);
|
maybeSetColorInfo(result, format.colorInfo);
|
||||||
|
@ -25,8 +25,8 @@ import android.net.ConnectivityManager;
|
|||||||
import android.net.NetworkInfo;
|
import android.net.NetworkInfo;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import android.telephony.PhoneStateListener;
|
import android.telephony.TelephonyCallback;
|
||||||
import android.telephony.ServiceState;
|
import android.telephony.TelephonyCallback.DisplayInfoListener;
|
||||||
import android.telephony.TelephonyDisplayInfo;
|
import android.telephony.TelephonyDisplayInfo;
|
||||||
import android.telephony.TelephonyManager;
|
import android.telephony.TelephonyManager;
|
||||||
import androidx.annotation.GuardedBy;
|
import androidx.annotation.GuardedBy;
|
||||||
@ -59,24 +59,6 @@ public final class NetworkTypeObserver {
|
|||||||
void onNetworkTypeChanged(@C.NetworkType int networkType);
|
void onNetworkTypeChanged(@C.NetworkType int networkType);
|
||||||
}
|
}
|
||||||
|
|
||||||
/*
|
|
||||||
* Static configuration that may need to be set at app startup time is located in a separate
|
|
||||||
* static Config class. This allows apps to set their desired config without incurring unnecessary
|
|
||||||
* class loading costs during startup.
|
|
||||||
*/
|
|
||||||
/** Configuration for {@link NetworkTypeObserver}. */
|
|
||||||
public static final class Config {
|
|
||||||
|
|
||||||
private static volatile boolean disable5GNsaDisambiguation;
|
|
||||||
|
|
||||||
/** Disables logic to disambiguate 5G-NSA networks from 4G networks. */
|
|
||||||
public static void disable5GNsaDisambiguation() {
|
|
||||||
disable5GNsaDisambiguation = true;
|
|
||||||
}
|
|
||||||
|
|
||||||
private Config() {}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Nullable private static NetworkTypeObserver staticInstance;
|
@Nullable private static NetworkTypeObserver staticInstance;
|
||||||
|
|
||||||
private final Handler mainHandler;
|
private final Handler mainHandler;
|
||||||
@ -232,55 +214,51 @@ public final class NetworkTypeObserver {
|
|||||||
@Override
|
@Override
|
||||||
public void onReceive(Context context, Intent intent) {
|
public void onReceive(Context context, Intent intent) {
|
||||||
@C.NetworkType int networkType = getNetworkTypeFromConnectivityManager(context);
|
@C.NetworkType int networkType = getNetworkTypeFromConnectivityManager(context);
|
||||||
if (Util.SDK_INT >= 29
|
if (Util.SDK_INT >= 31 && networkType == C.NETWORK_TYPE_4G) {
|
||||||
&& !Config.disable5GNsaDisambiguation
|
|
||||||
&& networkType == C.NETWORK_TYPE_4G) {
|
|
||||||
// Delay update of the network type to check whether this is actually 5G-NSA.
|
// Delay update of the network type to check whether this is actually 5G-NSA.
|
||||||
try {
|
Api31.disambiguate4gAnd5gNsa(context, /* instance= */ NetworkTypeObserver.this);
|
||||||
// We can't access TelephonyManager getters like getServiceState() directly as they
|
|
||||||
// require special permissions. Attaching a listener is permission-free because the
|
|
||||||
// callback data is censored to not include sensitive information.
|
|
||||||
TelephonyManager telephonyManager =
|
|
||||||
checkNotNull((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE));
|
|
||||||
TelephonyManagerListener listener = new TelephonyManagerListener();
|
|
||||||
if (Util.SDK_INT < 31) {
|
|
||||||
telephonyManager.listen(listener, PhoneStateListener.LISTEN_SERVICE_STATE);
|
|
||||||
} else {
|
} else {
|
||||||
// Display info information can only be requested without permission from API 31.
|
|
||||||
telephonyManager.listen(listener, PhoneStateListener.LISTEN_DISPLAY_INFO_CHANGED);
|
|
||||||
}
|
|
||||||
// We are only interested in the initial response with the current state, so unregister
|
|
||||||
// the listener immediately.
|
|
||||||
telephonyManager.listen(listener, PhoneStateListener.LISTEN_NONE);
|
|
||||||
return;
|
|
||||||
} catch (RuntimeException e) {
|
|
||||||
// Ignore problems with listener registration and keep reporting as 4G.
|
|
||||||
}
|
|
||||||
}
|
|
||||||
updateNetworkType(networkType);
|
updateNetworkType(networkType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private class TelephonyManagerListener extends PhoneStateListener {
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onServiceStateChanged(@Nullable ServiceState serviceState) {
|
|
||||||
// This workaround to check the toString output of ServiceState only works on API 29 and 30.
|
|
||||||
String serviceStateString = serviceState == null ? "" : serviceState.toString();
|
|
||||||
boolean is5gNsa =
|
|
||||||
serviceStateString.contains("nrState=CONNECTED")
|
|
||||||
|| serviceStateString.contains("nrState=NOT_RESTRICTED");
|
|
||||||
updateNetworkType(is5gNsa ? C.NETWORK_TYPE_5G_NSA : C.NETWORK_TYPE_4G);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(31)
|
@RequiresApi(31)
|
||||||
|
private static final class Api31 {
|
||||||
|
|
||||||
|
public static void disambiguate4gAnd5gNsa(Context context, NetworkTypeObserver instance) {
|
||||||
|
try {
|
||||||
|
TelephonyManager telephonyManager =
|
||||||
|
checkNotNull((TelephonyManager) context.getSystemService(Context.TELEPHONY_SERVICE));
|
||||||
|
DisplayInfoCallback callback = new DisplayInfoCallback(instance);
|
||||||
|
telephonyManager.registerTelephonyCallback(context.getMainExecutor(), callback);
|
||||||
|
// We are only interested in the initial response with the current state, so unregister
|
||||||
|
// the listener immediately.
|
||||||
|
telephonyManager.unregisterTelephonyCallback(callback);
|
||||||
|
} catch (RuntimeException e) {
|
||||||
|
// Ignore problems with listener registration and keep reporting as 4G.
|
||||||
|
instance.updateNetworkType(C.NETWORK_TYPE_4G);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static final class DisplayInfoCallback extends TelephonyCallback
|
||||||
|
implements DisplayInfoListener {
|
||||||
|
|
||||||
|
private final NetworkTypeObserver instance;
|
||||||
|
|
||||||
|
public DisplayInfoCallback(NetworkTypeObserver instance) {
|
||||||
|
this.instance = instance;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onDisplayInfoChanged(TelephonyDisplayInfo telephonyDisplayInfo) {
|
public void onDisplayInfoChanged(TelephonyDisplayInfo telephonyDisplayInfo) {
|
||||||
int overrideNetworkType = telephonyDisplayInfo.getOverrideNetworkType();
|
int overrideNetworkType = telephonyDisplayInfo.getOverrideNetworkType();
|
||||||
boolean is5gNsa =
|
boolean is5gNsa =
|
||||||
overrideNetworkType == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA
|
overrideNetworkType == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA
|
||||||
|| overrideNetworkType == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE;
|
|| overrideNetworkType == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_NSA_MMWAVE
|
||||||
updateNetworkType(is5gNsa ? C.NETWORK_TYPE_5G_NSA : C.NETWORK_TYPE_4G);
|
|| overrideNetworkType == TelephonyDisplayInfo.OVERRIDE_NETWORK_TYPE_NR_ADVANCED;
|
||||||
|
instance.updateNetworkType(is5gNsa ? C.NETWORK_TYPE_5G_NSA : C.NETWORK_TYPE_4G);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -54,17 +54,29 @@ public final class NotificationUtil {
|
|||||||
IMPORTANCE_HIGH
|
IMPORTANCE_HIGH
|
||||||
})
|
})
|
||||||
public @interface Importance {}
|
public @interface Importance {}
|
||||||
/** @see NotificationManager#IMPORTANCE_UNSPECIFIED */
|
/**
|
||||||
|
* @see NotificationManager#IMPORTANCE_UNSPECIFIED
|
||||||
|
*/
|
||||||
public static final int IMPORTANCE_UNSPECIFIED = NotificationManager.IMPORTANCE_UNSPECIFIED;
|
public static final int IMPORTANCE_UNSPECIFIED = NotificationManager.IMPORTANCE_UNSPECIFIED;
|
||||||
/** @see NotificationManager#IMPORTANCE_NONE */
|
/**
|
||||||
|
* @see NotificationManager#IMPORTANCE_NONE
|
||||||
|
*/
|
||||||
public static final int IMPORTANCE_NONE = NotificationManager.IMPORTANCE_NONE;
|
public static final int IMPORTANCE_NONE = NotificationManager.IMPORTANCE_NONE;
|
||||||
/** @see NotificationManager#IMPORTANCE_MIN */
|
/**
|
||||||
|
* @see NotificationManager#IMPORTANCE_MIN
|
||||||
|
*/
|
||||||
public static final int IMPORTANCE_MIN = NotificationManager.IMPORTANCE_MIN;
|
public static final int IMPORTANCE_MIN = NotificationManager.IMPORTANCE_MIN;
|
||||||
/** @see NotificationManager#IMPORTANCE_LOW */
|
/**
|
||||||
|
* @see NotificationManager#IMPORTANCE_LOW
|
||||||
|
*/
|
||||||
public static final int IMPORTANCE_LOW = NotificationManager.IMPORTANCE_LOW;
|
public static final int IMPORTANCE_LOW = NotificationManager.IMPORTANCE_LOW;
|
||||||
/** @see NotificationManager#IMPORTANCE_DEFAULT */
|
/**
|
||||||
|
* @see NotificationManager#IMPORTANCE_DEFAULT
|
||||||
|
*/
|
||||||
public static final int IMPORTANCE_DEFAULT = NotificationManager.IMPORTANCE_DEFAULT;
|
public static final int IMPORTANCE_DEFAULT = NotificationManager.IMPORTANCE_DEFAULT;
|
||||||
/** @see NotificationManager#IMPORTANCE_HIGH */
|
/**
|
||||||
|
* @see NotificationManager#IMPORTANCE_HIGH
|
||||||
|
*/
|
||||||
public static final int IMPORTANCE_HIGH = NotificationManager.IMPORTANCE_HIGH;
|
public static final int IMPORTANCE_HIGH = NotificationManager.IMPORTANCE_HIGH;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -47,8 +47,27 @@ import java.lang.annotation.Target;
|
|||||||
* Android Studio, in order to alert developers to the risk of breaking changes.
|
* Android Studio, in order to alert developers to the risk of breaking changes.
|
||||||
*
|
*
|
||||||
* <p>Individual usage sites can be opted-in to suppress the lint error by using the {@link
|
* <p>Individual usage sites can be opted-in to suppress the lint error by using the {@link
|
||||||
* androidx.annotation.OptIn} annotation: {@code @androidx.annotation.OptIn(markerClass =
|
* androidx.annotation.OptIn} annotation.
|
||||||
* androidx.media3.common.util.UnstableApi.class)}.
|
*
|
||||||
|
* <p>In Java:
|
||||||
|
*
|
||||||
|
* <pre>{@code
|
||||||
|
* import androidx.annotation.OptIn;
|
||||||
|
* import androidx.media3.common.util.UnstableApi;
|
||||||
|
* ...
|
||||||
|
* @OptIn(markerClass = UnstableApi.class)
|
||||||
|
* private void methodUsingUnstableApis() { ... }
|
||||||
|
* }</pre>
|
||||||
|
*
|
||||||
|
* <p>In Kotlin:
|
||||||
|
*
|
||||||
|
* <pre>{@code
|
||||||
|
* import androidx.annotation.OptIn
|
||||||
|
* import androidx.media3.common.util.UnstableApi
|
||||||
|
* ...
|
||||||
|
* @OptIn(UnstableApi::class)
|
||||||
|
* private fun methodUsingUnstableApis() { ... }
|
||||||
|
* }</pre>
|
||||||
*
|
*
|
||||||
* <p>Whole projects can be opted-in by suppressing the specific lint error in their <a
|
* <p>Whole projects can be opted-in by suppressing the specific lint error in their <a
|
||||||
* href="https://developer.android.com/studio/write/lint#pref">{@code lint.xml} file</a>:
|
* href="https://developer.android.com/studio/write/lint#pref">{@code lint.xml} file</a>:
|
||||||
|
File diff suppressed because it is too large
Load Diff
@ -29,7 +29,7 @@ public class AudioAttributesTest {
|
|||||||
public void roundTripViaBundle_yieldsEqualInstance() {
|
public void roundTripViaBundle_yieldsEqualInstance() {
|
||||||
AudioAttributes audioAttributes =
|
AudioAttributes audioAttributes =
|
||||||
new AudioAttributes.Builder()
|
new AudioAttributes.Builder()
|
||||||
.setContentType(C.CONTENT_TYPE_SONIFICATION)
|
.setContentType(C.AUDIO_CONTENT_TYPE_SONIFICATION)
|
||||||
.setFlags(C.FLAG_AUDIBILITY_ENFORCED)
|
.setFlags(C.FLAG_AUDIBILITY_ENFORCED)
|
||||||
.setUsage(C.USAGE_ALARM)
|
.setUsage(C.USAGE_ALARM)
|
||||||
.setAllowedCapturePolicy(C.ALLOW_CAPTURE_BY_SYSTEM)
|
.setAllowedCapturePolicy(C.ALLOW_CAPTURE_BY_SYSTEM)
|
||||||
|
@ -18,22 +18,17 @@ package androidx.media3.common;
|
|||||||
import static androidx.media3.common.Player.EVENT_IS_PLAYING_CHANGED;
|
import static androidx.media3.common.Player.EVENT_IS_PLAYING_CHANGED;
|
||||||
import static androidx.media3.common.Player.EVENT_MEDIA_ITEM_TRANSITION;
|
import static androidx.media3.common.Player.EVENT_MEDIA_ITEM_TRANSITION;
|
||||||
import static androidx.media3.common.Player.EVENT_TIMELINE_CHANGED;
|
import static androidx.media3.common.Player.EVENT_TIMELINE_CHANGED;
|
||||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
import static org.mockito.ArgumentMatchers.same;
|
import static org.mockito.ArgumentMatchers.same;
|
||||||
import static org.mockito.Mockito.mock;
|
import static org.mockito.Mockito.mock;
|
||||||
import static org.mockito.Mockito.verify;
|
import static org.mockito.Mockito.verify;
|
||||||
|
|
||||||
import androidx.media3.test.utils.StubPlayer;
|
import androidx.media3.test.utils.StubPlayer;
|
||||||
|
import androidx.media3.test.utils.TestUtil;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import java.lang.reflect.Method;
|
import java.lang.reflect.Method;
|
||||||
import java.lang.reflect.Modifier;
|
|
||||||
import java.util.ArrayDeque;
|
|
||||||
import java.util.ArrayList;
|
|
||||||
import java.util.Collections;
|
|
||||||
import java.util.HashSet;
|
import java.util.HashSet;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Queue;
|
|
||||||
import java.util.Set;
|
import java.util.Set;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
@ -105,7 +100,7 @@ public class ForwardingPlayerTest {
|
|||||||
@Test
|
@Test
|
||||||
public void forwardingPlayer_overridesAllPlayerMethods() throws Exception {
|
public void forwardingPlayer_overridesAllPlayerMethods() throws Exception {
|
||||||
// Check with reflection that ForwardingPlayer overrides all Player methods.
|
// Check with reflection that ForwardingPlayer overrides all Player methods.
|
||||||
List<Method> methods = getPublicMethods(Player.class);
|
List<Method> methods = TestUtil.getPublicMethods(Player.class);
|
||||||
for (Method method : methods) {
|
for (Method method : methods) {
|
||||||
assertThat(
|
assertThat(
|
||||||
ForwardingPlayer.class
|
ForwardingPlayer.class
|
||||||
@ -119,7 +114,7 @@ public class ForwardingPlayerTest {
|
|||||||
public void forwardingListener_overridesAllListenerMethods() throws Exception {
|
public void forwardingListener_overridesAllListenerMethods() throws Exception {
|
||||||
// Check with reflection that ForwardingListener overrides all Listener methods.
|
// Check with reflection that ForwardingListener overrides all Listener methods.
|
||||||
Class<?> forwardingListenerClass = getInnerClass("ForwardingListener");
|
Class<?> forwardingListenerClass = getInnerClass("ForwardingListener");
|
||||||
List<Method> methods = getPublicMethods(Player.Listener.class);
|
List<Method> methods = TestUtil.getPublicMethods(Player.Listener.class);
|
||||||
for (Method method : methods) {
|
for (Method method : methods) {
|
||||||
assertThat(
|
assertThat(
|
||||||
forwardingListenerClass
|
forwardingListenerClass
|
||||||
@ -129,32 +124,6 @@ public class ForwardingPlayerTest {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns all the public methods of a Java interface. */
|
|
||||||
private static List<Method> getPublicMethods(Class<?> anInterface) {
|
|
||||||
checkArgument(anInterface.isInterface());
|
|
||||||
// Run a BFS over all extended interfaces to inspect them all.
|
|
||||||
Queue<Class<?>> interfacesQueue = new ArrayDeque<>();
|
|
||||||
interfacesQueue.add(anInterface);
|
|
||||||
Set<Class<?>> interfaces = new HashSet<>();
|
|
||||||
while (!interfacesQueue.isEmpty()) {
|
|
||||||
Class<?> currentInterface = interfacesQueue.remove();
|
|
||||||
if (interfaces.add(currentInterface)) {
|
|
||||||
Collections.addAll(interfacesQueue, currentInterface.getInterfaces());
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
List<Method> list = new ArrayList<>();
|
|
||||||
for (Class<?> currentInterface : interfaces) {
|
|
||||||
for (Method method : currentInterface.getDeclaredMethods()) {
|
|
||||||
if (Modifier.isPublic(method.getModifiers())) {
|
|
||||||
list.add(method);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
return list;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static Class<?> getInnerClass(String className) {
|
private static Class<?> getInnerClass(String className) {
|
||||||
for (Class<?> innerClass : ForwardingPlayer.class.getDeclaredClasses()) {
|
for (Class<?> innerClass : ForwardingPlayer.class.getDeclaredClasses()) {
|
||||||
if (innerClass.getSimpleName().equals(className)) {
|
if (innerClass.getSimpleName().equals(className)) {
|
||||||
|
@ -19,6 +19,8 @@ import static com.google.common.truth.Truth.assertThat;
|
|||||||
import static org.junit.Assert.assertThrows;
|
import static org.junit.Assert.assertThrows;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import androidx.media3.common.MediaItem.RequestMetadata;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableMap;
|
import com.google.common.collect.ImmutableMap;
|
||||||
@ -223,7 +225,7 @@ public class MediaItemTest {
|
|||||||
new MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
|
new MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
|
||||||
.setLicenseUri(licenseUri)
|
.setLicenseUri(licenseUri)
|
||||||
.setForcedSessionTrackTypes(ImmutableList.of(C.TRACK_TYPE_AUDIO))
|
.setForcedSessionTrackTypes(ImmutableList.of(C.TRACK_TYPE_AUDIO))
|
||||||
.forceSessionsForAudioAndVideoTracks(true)
|
.setForceSessionsForAudioAndVideoTracks(true)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
assertThat(drmConfiguration.sessionForClearTypes)
|
assertThat(drmConfiguration.sessionForClearTypes)
|
||||||
@ -579,6 +581,24 @@ public class MediaItemTest {
|
|||||||
assertThat(mediaItem.liveConfiguration.maxOffsetMs).isEqualTo(1234);
|
assertThat(mediaItem.liveConfiguration.maxOffsetMs).isEqualTo(1234);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void builder_setRequestMetadata_setsRequestMetadata() {
|
||||||
|
Bundle extras = new Bundle();
|
||||||
|
extras.putString("key", "value");
|
||||||
|
RequestMetadata requestMetadata =
|
||||||
|
new RequestMetadata.Builder()
|
||||||
|
.setMediaUri(Uri.parse("http://test.test"))
|
||||||
|
.setSearchQuery("Play media!")
|
||||||
|
.setExtras(extras)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
MediaItem mediaItem =
|
||||||
|
new MediaItem.Builder().setMediaId("mediaID").setRequestMetadata(requestMetadata).build();
|
||||||
|
|
||||||
|
assertThat(mediaItem.requestMetadata).isEqualTo(requestMetadata);
|
||||||
|
assertThat(mediaItem.requestMetadata.extras.getString("key")).isEqualTo("value");
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SuppressWarnings("deprecation") // Testing deprecated setter methods
|
@SuppressWarnings("deprecation") // Testing deprecated setter methods
|
||||||
public void buildUpon_individualSetters_equalsToOriginal() {
|
public void buildUpon_individualSetters_equalsToOriginal() {
|
||||||
@ -677,6 +697,11 @@ public class MediaItemTest {
|
|||||||
.setLabel("label")
|
.setLabel("label")
|
||||||
.setId("id")
|
.setId("id")
|
||||||
.build()))
|
.build()))
|
||||||
|
.setRequestMetadata(
|
||||||
|
new RequestMetadata.Builder()
|
||||||
|
.setMediaUri(Uri.parse("http://test.test"))
|
||||||
|
.setSearchQuery("search")
|
||||||
|
.build())
|
||||||
.setTag(new Object())
|
.setTag(new Object())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
@ -704,6 +729,11 @@ public class MediaItemTest {
|
|||||||
.setClipRelativeToDefaultPosition(true)
|
.setClipRelativeToDefaultPosition(true)
|
||||||
.setClipRelativeToLiveWindow(true)
|
.setClipRelativeToLiveWindow(true)
|
||||||
.setClipStartsAtKeyFrame(true)
|
.setClipStartsAtKeyFrame(true)
|
||||||
|
.setRequestMetadata(
|
||||||
|
new RequestMetadata.Builder()
|
||||||
|
.setMediaUri(Uri.parse("http://test.test"))
|
||||||
|
.setSearchQuery("search")
|
||||||
|
.build())
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
assertThat(mediaItem.localConfiguration).isNull();
|
assertThat(mediaItem.localConfiguration).isNull();
|
||||||
|
@ -41,7 +41,6 @@ public class MediaMetadataTest {
|
|||||||
assertThat(mediaMetadata.displayTitle).isNull();
|
assertThat(mediaMetadata.displayTitle).isNull();
|
||||||
assertThat(mediaMetadata.subtitle).isNull();
|
assertThat(mediaMetadata.subtitle).isNull();
|
||||||
assertThat(mediaMetadata.description).isNull();
|
assertThat(mediaMetadata.description).isNull();
|
||||||
assertThat(mediaMetadata.mediaUri).isNull();
|
|
||||||
assertThat(mediaMetadata.userRating).isNull();
|
assertThat(mediaMetadata.userRating).isNull();
|
||||||
assertThat(mediaMetadata.overallRating).isNull();
|
assertThat(mediaMetadata.overallRating).isNull();
|
||||||
assertThat(mediaMetadata.artworkData).isNull();
|
assertThat(mediaMetadata.artworkData).isNull();
|
||||||
@ -127,7 +126,6 @@ public class MediaMetadataTest {
|
|||||||
.setDisplayTitle("display title")
|
.setDisplayTitle("display title")
|
||||||
.setSubtitle("subtitle")
|
.setSubtitle("subtitle")
|
||||||
.setDescription("description")
|
.setDescription("description")
|
||||||
.setMediaUri(Uri.parse("https://www.google.com"))
|
|
||||||
.setUserRating(new HeartRating(false))
|
.setUserRating(new HeartRating(false))
|
||||||
.setOverallRating(new PercentageRating(87.4f))
|
.setOverallRating(new PercentageRating(87.4f))
|
||||||
.setArtworkData(
|
.setArtworkData(
|
||||||
|
@ -0,0 +1,71 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2021 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package androidx.media3.common;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static org.junit.Assert.assertThrows;
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import java.util.Arrays;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
/** Unit tests for {@link TrackSelectionOverride}. */
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public final class TrackSelectionOverrideTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void newTrackSelectionOverride_withOneTrack_selectsOneTrack() {
|
||||||
|
TrackSelectionOverride trackSelectionOverride =
|
||||||
|
new TrackSelectionOverride(newTrackGroupWithIds(1, 2), /* trackIndex= */ 1);
|
||||||
|
|
||||||
|
assertThat(trackSelectionOverride.mediaTrackGroup).isEqualTo(newTrackGroupWithIds(1, 2));
|
||||||
|
assertThat(trackSelectionOverride.trackIndices).containsExactly(1).inOrder();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void newTrackSelectionOverride_withTracks_selectsOnlySpecifiedTracks() {
|
||||||
|
TrackSelectionOverride trackSelectionOverride =
|
||||||
|
new TrackSelectionOverride(newTrackGroupWithIds(1, 2), ImmutableList.of(1));
|
||||||
|
|
||||||
|
assertThat(trackSelectionOverride.mediaTrackGroup).isEqualTo(newTrackGroupWithIds(1, 2));
|
||||||
|
assertThat(trackSelectionOverride.trackIndices).containsExactly(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void newTrackSelectionOverride_with0Tracks_selectsAllSpecifiedTracks() {
|
||||||
|
TrackSelectionOverride trackSelectionOverride =
|
||||||
|
new TrackSelectionOverride(newTrackGroupWithIds(1, 2), ImmutableList.of());
|
||||||
|
|
||||||
|
assertThat(trackSelectionOverride.mediaTrackGroup).isEqualTo(newTrackGroupWithIds(1, 2));
|
||||||
|
assertThat(trackSelectionOverride.trackIndices).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void newTrackSelectionOverride_withInvalidIndex_throws() {
|
||||||
|
assertThrows(
|
||||||
|
IndexOutOfBoundsException.class,
|
||||||
|
() -> new TrackSelectionOverride(newTrackGroupWithIds(1, 2), ImmutableList.of(2)));
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TrackGroup newTrackGroupWithIds(int... ids) {
|
||||||
|
return new TrackGroup(
|
||||||
|
Arrays.stream(ids)
|
||||||
|
.mapToObj(id -> new Format.Builder().setId(id).build())
|
||||||
|
.toArray(Format[]::new));
|
||||||
|
}
|
||||||
|
}
|
@ -1,172 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2021 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package androidx.media3.common;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
import static org.junit.Assert.assertThrows;
|
|
||||||
|
|
||||||
import androidx.media3.common.TrackSelectionOverrides.TrackSelectionOverride;
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import java.util.Arrays;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
/** Unit tests for {@link TrackSelectionOverrides}. */
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
|
||||||
public final class TrackSelectionOverridesTest {
|
|
||||||
|
|
||||||
public static final TrackGroup AAC_TRACK_GROUP =
|
|
||||||
new TrackGroup(new Format.Builder().setSampleMimeType(MimeTypes.AUDIO_AAC).build());
|
|
||||||
|
|
||||||
private static TrackGroup newTrackGroupWithIds(int... ids) {
|
|
||||||
return new TrackGroup(
|
|
||||||
Arrays.stream(ids)
|
|
||||||
.mapToObj(id -> new Format.Builder().setId(id).build())
|
|
||||||
.toArray(Format[]::new));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void newTrackSelectionOverride_withJustTrackGroup_selectsAllTracks() {
|
|
||||||
TrackSelectionOverride trackSelectionOverride =
|
|
||||||
new TrackSelectionOverride(newTrackGroupWithIds(1, 2));
|
|
||||||
|
|
||||||
assertThat(trackSelectionOverride.trackGroup).isEqualTo(newTrackGroupWithIds(1, 2));
|
|
||||||
assertThat(trackSelectionOverride.trackIndices).containsExactly(0, 1).inOrder();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void newTrackSelectionOverride_withTracks_selectsOnlySpecifiedTracks() {
|
|
||||||
TrackSelectionOverride trackSelectionOverride =
|
|
||||||
new TrackSelectionOverride(newTrackGroupWithIds(1, 2), ImmutableList.of(1));
|
|
||||||
|
|
||||||
assertThat(trackSelectionOverride.trackGroup).isEqualTo(newTrackGroupWithIds(1, 2));
|
|
||||||
assertThat(trackSelectionOverride.trackIndices).containsExactly(1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void newTrackSelectionOverride_with0Tracks_selectsAllSpecifiedTracks() {
|
|
||||||
TrackSelectionOverride trackSelectionOverride =
|
|
||||||
new TrackSelectionOverride(newTrackGroupWithIds(1, 2), ImmutableList.of());
|
|
||||||
|
|
||||||
assertThat(trackSelectionOverride.trackGroup).isEqualTo(newTrackGroupWithIds(1, 2));
|
|
||||||
assertThat(trackSelectionOverride.trackIndices).isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void newTrackSelectionOverride_withInvalidIndex_throws() {
|
|
||||||
assertThrows(
|
|
||||||
IndexOutOfBoundsException.class,
|
|
||||||
() -> new TrackSelectionOverride(newTrackGroupWithIds(1, 2), ImmutableList.of(2)));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void roundTripViaBundle_withOverrides_yieldsEqualInstance() {
|
|
||||||
TrackSelectionOverrides trackSelectionOverrides =
|
|
||||||
new TrackSelectionOverrides.Builder()
|
|
||||||
.setOverrideForType(
|
|
||||||
new TrackSelectionOverride(newTrackGroupWithIds(3, 4), ImmutableList.of(1)))
|
|
||||||
.addOverride(new TrackSelectionOverride(newTrackGroupWithIds(5, 6)))
|
|
||||||
.build();
|
|
||||||
|
|
||||||
TrackSelectionOverrides fromBundle =
|
|
||||||
TrackSelectionOverrides.CREATOR.fromBundle(trackSelectionOverrides.toBundle());
|
|
||||||
|
|
||||||
assertThat(fromBundle).isEqualTo(trackSelectionOverrides);
|
|
||||||
assertThat(fromBundle.asList()).isEqualTo(trackSelectionOverrides.asList());
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void builder_byDefault_isEmpty() {
|
|
||||||
TrackSelectionOverrides trackSelectionOverrides = new TrackSelectionOverrides.Builder().build();
|
|
||||||
|
|
||||||
assertThat(trackSelectionOverrides.asList()).isEmpty();
|
|
||||||
assertThat(trackSelectionOverrides).isEqualTo(TrackSelectionOverrides.EMPTY);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void addOverride_onDifferentGroups_addsOverride() {
|
|
||||||
TrackSelectionOverride override1 = new TrackSelectionOverride(newTrackGroupWithIds(1));
|
|
||||||
TrackSelectionOverride override2 = new TrackSelectionOverride(newTrackGroupWithIds(2));
|
|
||||||
|
|
||||||
TrackSelectionOverrides trackSelectionOverrides =
|
|
||||||
new TrackSelectionOverrides.Builder().addOverride(override1).addOverride(override2).build();
|
|
||||||
|
|
||||||
assertThat(trackSelectionOverrides.asList()).containsExactly(override1, override2);
|
|
||||||
assertThat(trackSelectionOverrides.getOverride(override1.trackGroup)).isEqualTo(override1);
|
|
||||||
assertThat(trackSelectionOverrides.getOverride(override2.trackGroup)).isEqualTo(override2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void addOverride_onSameGroup_replacesOverride() {
|
|
||||||
TrackGroup trackGroup = newTrackGroupWithIds(1, 2, 3);
|
|
||||||
TrackSelectionOverride override1 =
|
|
||||||
new TrackSelectionOverride(trackGroup, /* trackIndices= */ ImmutableList.of(0));
|
|
||||||
TrackSelectionOverride override2 =
|
|
||||||
new TrackSelectionOverride(trackGroup, /* trackIndices= */ ImmutableList.of(1));
|
|
||||||
|
|
||||||
TrackSelectionOverrides trackSelectionOverrides =
|
|
||||||
new TrackSelectionOverrides.Builder().addOverride(override1).addOverride(override2).build();
|
|
||||||
|
|
||||||
assertThat(trackSelectionOverrides.asList()).containsExactly(override2);
|
|
||||||
assertThat(trackSelectionOverrides.getOverride(override2.trackGroup)).isEqualTo(override2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void setOverrideForType_onSameType_replacesOverride() {
|
|
||||||
TrackSelectionOverride override1 = new TrackSelectionOverride(newTrackGroupWithIds(1));
|
|
||||||
TrackSelectionOverride override2 = new TrackSelectionOverride(newTrackGroupWithIds(2));
|
|
||||||
|
|
||||||
TrackSelectionOverrides trackSelectionOverrides =
|
|
||||||
new TrackSelectionOverrides.Builder()
|
|
||||||
.setOverrideForType(override1)
|
|
||||||
.setOverrideForType(override2)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
assertThat(trackSelectionOverrides.asList()).containsExactly(override2);
|
|
||||||
assertThat(trackSelectionOverrides.getOverride(override2.trackGroup)).isEqualTo(override2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void clearOverridesOfType_ofTypeAudio_removesAudioOverride() {
|
|
||||||
TrackSelectionOverride override1 = new TrackSelectionOverride(AAC_TRACK_GROUP);
|
|
||||||
TrackSelectionOverride override2 = new TrackSelectionOverride(newTrackGroupWithIds(1));
|
|
||||||
TrackSelectionOverrides trackSelectionOverrides =
|
|
||||||
new TrackSelectionOverrides.Builder()
|
|
||||||
.addOverride(override1)
|
|
||||||
.addOverride(override2)
|
|
||||||
.clearOverridesOfType(C.TRACK_TYPE_AUDIO)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
assertThat(trackSelectionOverrides.asList()).containsExactly(override2);
|
|
||||||
assertThat(trackSelectionOverrides.getOverride(override2.trackGroup)).isEqualTo(override2);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void clearOverride_ofTypeGroup_removesOverride() {
|
|
||||||
TrackSelectionOverride override1 = new TrackSelectionOverride(AAC_TRACK_GROUP);
|
|
||||||
TrackSelectionOverride override2 = new TrackSelectionOverride(newTrackGroupWithIds(1));
|
|
||||||
TrackSelectionOverrides trackSelectionOverrides =
|
|
||||||
new TrackSelectionOverrides.Builder()
|
|
||||||
.addOverride(override1)
|
|
||||||
.addOverride(override2)
|
|
||||||
.clearOverride(override2.trackGroup)
|
|
||||||
.build();
|
|
||||||
|
|
||||||
assertThat(trackSelectionOverrides.asList()).containsExactly(override1);
|
|
||||||
assertThat(trackSelectionOverrides.getOverride(override1.trackGroup)).isEqualTo(override1);
|
|
||||||
}
|
|
||||||
}
|
|
@ -18,10 +18,8 @@ package androidx.media3.common;
|
|||||||
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
|
import static androidx.test.core.app.ApplicationProvider.getApplicationContext;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
import androidx.media3.common.TrackSelectionOverrides.TrackSelectionOverride;
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
import com.google.common.collect.ImmutableSet;
|
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
@ -29,6 +27,9 @@ import org.junit.runner.RunWith;
|
|||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public final class TrackSelectionParametersTest {
|
public final class TrackSelectionParametersTest {
|
||||||
|
|
||||||
|
private static final TrackGroup AAC_TRACK_GROUP =
|
||||||
|
new TrackGroup(new Format.Builder().setSampleMimeType(MimeTypes.AUDIO_AAC).build());
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void defaultValue_withoutChange_isAsExpected() {
|
public void defaultValue_withoutChange_isAsExpected() {
|
||||||
TrackSelectionParameters parameters = TrackSelectionParameters.DEFAULT_WITHOUT_CONTEXT;
|
TrackSelectionParameters parameters = TrackSelectionParameters.DEFAULT_WITHOUT_CONTEXT;
|
||||||
@ -55,26 +56,25 @@ public final class TrackSelectionParametersTest {
|
|||||||
assertThat(parameters.preferredAudioMimeTypes).isEmpty();
|
assertThat(parameters.preferredAudioMimeTypes).isEmpty();
|
||||||
assertThat(parameters.preferredTextLanguages).isEmpty();
|
assertThat(parameters.preferredTextLanguages).isEmpty();
|
||||||
assertThat(parameters.preferredTextRoleFlags).isEqualTo(0);
|
assertThat(parameters.preferredTextRoleFlags).isEqualTo(0);
|
||||||
|
assertThat(parameters.ignoredTextSelectionFlags).isEqualTo(0);
|
||||||
assertThat(parameters.selectUndeterminedTextLanguage).isFalse();
|
assertThat(parameters.selectUndeterminedTextLanguage).isFalse();
|
||||||
// General
|
// General
|
||||||
assertThat(parameters.forceLowestBitrate).isFalse();
|
assertThat(parameters.forceLowestBitrate).isFalse();
|
||||||
assertThat(parameters.forceHighestSupportedBitrate).isFalse();
|
assertThat(parameters.forceHighestSupportedBitrate).isFalse();
|
||||||
assertThat(parameters.trackSelectionOverrides.asList()).isEmpty();
|
assertThat(parameters.overrides).isEmpty();
|
||||||
assertThat(parameters.disabledTrackTypes).isEmpty();
|
assertThat(parameters.disabledTrackTypes).isEmpty();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void parametersSet_fromDefault_isAsExpected() {
|
public void parametersSet_fromDefault_isAsExpected() {
|
||||||
TrackSelectionOverrides trackSelectionOverrides =
|
TrackSelectionOverride override1 =
|
||||||
new TrackSelectionOverrides.Builder()
|
new TrackSelectionOverride(
|
||||||
.addOverride(new TrackSelectionOverride(new TrackGroup(new Format.Builder().build())))
|
new TrackGroup(new Format.Builder().build()), /* trackIndex= */ 0);
|
||||||
.addOverride(
|
TrackSelectionOverride override2 =
|
||||||
new TrackSelectionOverride(
|
new TrackSelectionOverride(
|
||||||
new TrackGroup(
|
new TrackGroup(
|
||||||
new Format.Builder().setId(4).build(),
|
new Format.Builder().setId(4).build(), new Format.Builder().setId(5).build()),
|
||||||
new Format.Builder().setId(5).build()),
|
/* trackIndices= */ ImmutableList.of(1));
|
||||||
/* trackIndices= */ ImmutableList.of(1)))
|
|
||||||
.build();
|
|
||||||
TrackSelectionParameters parameters =
|
TrackSelectionParameters parameters =
|
||||||
TrackSelectionParameters.DEFAULT_WITHOUT_CONTEXT
|
TrackSelectionParameters.DEFAULT_WITHOUT_CONTEXT
|
||||||
.buildUpon()
|
.buildUpon()
|
||||||
@ -99,12 +99,22 @@ public final class TrackSelectionParametersTest {
|
|||||||
// Text
|
// Text
|
||||||
.setPreferredTextLanguages("de", "en")
|
.setPreferredTextLanguages("de", "en")
|
||||||
.setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION)
|
.setPreferredTextRoleFlags(C.ROLE_FLAG_CAPTION)
|
||||||
|
.setIgnoredTextSelectionFlags(C.SELECTION_FLAG_AUTOSELECT)
|
||||||
.setSelectUndeterminedTextLanguage(true)
|
.setSelectUndeterminedTextLanguage(true)
|
||||||
// General
|
// General
|
||||||
.setForceLowestBitrate(false)
|
.setForceLowestBitrate(false)
|
||||||
.setForceHighestSupportedBitrate(true)
|
.setForceHighestSupportedBitrate(true)
|
||||||
.setTrackSelectionOverrides(trackSelectionOverrides)
|
.addOverride(
|
||||||
.setDisabledTrackTypes(ImmutableSet.of(C.TRACK_TYPE_AUDIO, C.TRACK_TYPE_TEXT))
|
new TrackSelectionOverride(
|
||||||
|
new TrackGroup(new Format.Builder().build()), /* trackIndex= */ 0))
|
||||||
|
.addOverride(
|
||||||
|
new TrackSelectionOverride(
|
||||||
|
new TrackGroup(
|
||||||
|
new Format.Builder().setId(4).build(),
|
||||||
|
new Format.Builder().setId(5).build()),
|
||||||
|
/* trackIndices= */ ImmutableList.of(1)))
|
||||||
|
.setTrackTypeDisabled(C.TRACK_TYPE_AUDIO, /* disabled= */ true)
|
||||||
|
.setTrackTypeDisabled(C.TRACK_TYPE_TEXT, /* disabled= */ true)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
// Video
|
// Video
|
||||||
@ -133,11 +143,14 @@ public final class TrackSelectionParametersTest {
|
|||||||
// Text
|
// Text
|
||||||
assertThat(parameters.preferredTextLanguages).containsExactly("de", "en").inOrder();
|
assertThat(parameters.preferredTextLanguages).containsExactly("de", "en").inOrder();
|
||||||
assertThat(parameters.preferredTextRoleFlags).isEqualTo(C.ROLE_FLAG_CAPTION);
|
assertThat(parameters.preferredTextRoleFlags).isEqualTo(C.ROLE_FLAG_CAPTION);
|
||||||
|
assertThat(parameters.ignoredTextSelectionFlags).isEqualTo(C.SELECTION_FLAG_AUTOSELECT);
|
||||||
assertThat(parameters.selectUndeterminedTextLanguage).isTrue();
|
assertThat(parameters.selectUndeterminedTextLanguage).isTrue();
|
||||||
// General
|
// General
|
||||||
assertThat(parameters.forceLowestBitrate).isFalse();
|
assertThat(parameters.forceLowestBitrate).isFalse();
|
||||||
assertThat(parameters.forceHighestSupportedBitrate).isTrue();
|
assertThat(parameters.forceHighestSupportedBitrate).isTrue();
|
||||||
assertThat(parameters.trackSelectionOverrides).isEqualTo(trackSelectionOverrides);
|
assertThat(parameters.overrides)
|
||||||
|
.containsExactly(
|
||||||
|
override1.mediaTrackGroup, override1, override2.mediaTrackGroup, override2);
|
||||||
assertThat(parameters.disabledTrackTypes)
|
assertThat(parameters.disabledTrackTypes)
|
||||||
.containsExactly(C.TRACK_TYPE_AUDIO, C.TRACK_TYPE_TEXT);
|
.containsExactly(C.TRACK_TYPE_AUDIO, C.TRACK_TYPE_TEXT);
|
||||||
}
|
}
|
||||||
@ -178,4 +191,115 @@ public final class TrackSelectionParametersTest {
|
|||||||
assertThat(parameters.viewportHeight).isEqualTo(Integer.MAX_VALUE);
|
assertThat(parameters.viewportHeight).isEqualTo(Integer.MAX_VALUE);
|
||||||
assertThat(parameters.viewportOrientationMayChange).isTrue();
|
assertThat(parameters.viewportOrientationMayChange).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void roundTripViaBundle_withOverride_yieldsEqualInstance() {
|
||||||
|
TrackSelectionOverride override =
|
||||||
|
new TrackSelectionOverride(
|
||||||
|
newTrackGroupWithIds(3, 4), /* trackIndices= */ ImmutableList.of(1));
|
||||||
|
TrackSelectionParameters trackSelectionParameters =
|
||||||
|
new TrackSelectionParameters.Builder(getApplicationContext()).addOverride(override).build();
|
||||||
|
|
||||||
|
TrackSelectionParameters fromBundle =
|
||||||
|
TrackSelectionParameters.fromBundle(trackSelectionParameters.toBundle());
|
||||||
|
|
||||||
|
assertThat(fromBundle).isEqualTo(trackSelectionParameters);
|
||||||
|
assertThat(trackSelectionParameters.overrides)
|
||||||
|
.containsExactly(override.mediaTrackGroup, override);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addOverride_onDifferentGroups_addsOverride() {
|
||||||
|
TrackSelectionOverride override1 =
|
||||||
|
new TrackSelectionOverride(newTrackGroupWithIds(1), /* trackIndex= */ 0);
|
||||||
|
TrackSelectionOverride override2 =
|
||||||
|
new TrackSelectionOverride(newTrackGroupWithIds(2), /* trackIndex= */ 0);
|
||||||
|
|
||||||
|
TrackSelectionParameters trackSelectionParameters =
|
||||||
|
new TrackSelectionParameters.Builder(getApplicationContext())
|
||||||
|
.addOverride(override1)
|
||||||
|
.addOverride(override2)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertThat(trackSelectionParameters.overrides)
|
||||||
|
.containsExactly(
|
||||||
|
override1.mediaTrackGroup, override1, override2.mediaTrackGroup, override2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void addOverride_onSameGroup_replacesOverride() {
|
||||||
|
TrackGroup trackGroup = newTrackGroupWithIds(1, 2, 3);
|
||||||
|
TrackSelectionOverride override1 =
|
||||||
|
new TrackSelectionOverride(trackGroup, /* trackIndices= */ ImmutableList.of(0));
|
||||||
|
TrackSelectionOverride override2 =
|
||||||
|
new TrackSelectionOverride(trackGroup, /* trackIndices= */ ImmutableList.of(1));
|
||||||
|
|
||||||
|
TrackSelectionParameters trackSelectionParameters =
|
||||||
|
new TrackSelectionParameters.Builder(getApplicationContext())
|
||||||
|
.addOverride(override1)
|
||||||
|
.addOverride(override2)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertThat(trackSelectionParameters.overrides)
|
||||||
|
.containsExactly(override2.mediaTrackGroup, override2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void setOverrideForType_onSameType_replacesOverride() {
|
||||||
|
TrackSelectionOverride override1 =
|
||||||
|
new TrackSelectionOverride(newTrackGroupWithIds(1), /* trackIndex= */ 0);
|
||||||
|
TrackSelectionOverride override2 =
|
||||||
|
new TrackSelectionOverride(newTrackGroupWithIds(2), /* trackIndex= */ 0);
|
||||||
|
|
||||||
|
TrackSelectionParameters trackSelectionParameters =
|
||||||
|
new TrackSelectionParameters.Builder(getApplicationContext())
|
||||||
|
.setOverrideForType(override1)
|
||||||
|
.setOverrideForType(override2)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertThat(trackSelectionParameters.overrides)
|
||||||
|
.containsExactly(override2.mediaTrackGroup, override2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void clearOverridesOfType_ofTypeAudio_removesAudioOverride() {
|
||||||
|
TrackSelectionOverride override1 =
|
||||||
|
new TrackSelectionOverride(AAC_TRACK_GROUP, /* trackIndex= */ 0);
|
||||||
|
TrackSelectionOverride override2 =
|
||||||
|
new TrackSelectionOverride(newTrackGroupWithIds(1), /* trackIndex= */ 0);
|
||||||
|
TrackSelectionParameters trackSelectionParameters =
|
||||||
|
new TrackSelectionParameters.Builder(getApplicationContext())
|
||||||
|
.addOverride(override1)
|
||||||
|
.addOverride(override2)
|
||||||
|
.clearOverridesOfType(C.TRACK_TYPE_AUDIO)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertThat(trackSelectionParameters.overrides)
|
||||||
|
.containsExactly(override2.mediaTrackGroup, override2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void clearOverride_ofTypeGroup_removesOverride() {
|
||||||
|
TrackSelectionOverride override1 =
|
||||||
|
new TrackSelectionOverride(AAC_TRACK_GROUP, /* trackIndex= */ 0);
|
||||||
|
TrackSelectionOverride override2 =
|
||||||
|
new TrackSelectionOverride(newTrackGroupWithIds(1), /* trackIndex= */ 0);
|
||||||
|
TrackSelectionParameters trackSelectionParameters =
|
||||||
|
new TrackSelectionParameters.Builder(getApplicationContext())
|
||||||
|
.addOverride(override1)
|
||||||
|
.addOverride(override2)
|
||||||
|
.clearOverride(override2.mediaTrackGroup)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
assertThat(trackSelectionParameters.overrides)
|
||||||
|
.containsExactly(override1.mediaTrackGroup, override1);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static TrackGroup newTrackGroupWithIds(int... ids) {
|
||||||
|
Format[] formats = new Format[ids.length];
|
||||||
|
for (int i = 0; i < ids.length; i++) {
|
||||||
|
formats[i] = new Format.Builder().setId(ids[i]).build();
|
||||||
|
}
|
||||||
|
return new TrackGroup(formats);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,110 +0,0 @@
|
|||||||
/*
|
|
||||||
* Copyright (C) 2021 The Android Open Source Project
|
|
||||||
*
|
|
||||||
* Licensed under the Apache License, Version 2.0 (the "License");
|
|
||||||
* you may not use this file except in compliance with the License.
|
|
||||||
* You may obtain a copy of the License at
|
|
||||||
*
|
|
||||||
* http://www.apache.org/licenses/LICENSE-2.0
|
|
||||||
*
|
|
||||||
* Unless required by applicable law or agreed to in writing, software
|
|
||||||
* distributed under the License is distributed on an "AS IS" BASIS,
|
|
||||||
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
|
||||||
* See the License for the specific language governing permissions and
|
|
||||||
* limitations under the License.
|
|
||||||
*/
|
|
||||||
package androidx.media3.common;
|
|
||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|
||||||
import com.google.common.collect.ImmutableList;
|
|
||||||
import org.junit.Test;
|
|
||||||
import org.junit.runner.RunWith;
|
|
||||||
|
|
||||||
/** Unit test for {@link TracksInfo}. */
|
|
||||||
@RunWith(AndroidJUnit4.class)
|
|
||||||
public class TracksInfoTest {
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void roundTripViaBundle_ofEmptyTracksInfo_yieldsEqualInstance() {
|
|
||||||
TracksInfo before = TracksInfo.EMPTY;
|
|
||||||
TracksInfo after = TracksInfo.CREATOR.fromBundle(before.toBundle());
|
|
||||||
assertThat(after).isEqualTo(before);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void roundTripViaBundle_ofTracksInfo_yieldsEqualInstance() {
|
|
||||||
TracksInfo before =
|
|
||||||
new TracksInfo(
|
|
||||||
ImmutableList.of(
|
|
||||||
new TracksInfo.TrackGroupInfo(
|
|
||||||
new TrackGroup(new Format.Builder().build()),
|
|
||||||
new int[] {C.FORMAT_EXCEEDS_CAPABILITIES},
|
|
||||||
C.TRACK_TYPE_AUDIO,
|
|
||||||
new boolean[] {true}),
|
|
||||||
new TracksInfo.TrackGroupInfo(
|
|
||||||
new TrackGroup(new Format.Builder().build(), new Format.Builder().build()),
|
|
||||||
new int[] {C.FORMAT_UNSUPPORTED_DRM, C.FORMAT_UNSUPPORTED_TYPE},
|
|
||||||
C.TRACK_TYPE_VIDEO,
|
|
||||||
new boolean[] {false, true})));
|
|
||||||
TracksInfo after = TracksInfo.CREATOR.fromBundle(before.toBundle());
|
|
||||||
assertThat(after).isEqualTo(before);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void tracksInfoGetters_withoutTrack_returnExpectedValues() {
|
|
||||||
TracksInfo tracksInfo = new TracksInfo(ImmutableList.of());
|
|
||||||
|
|
||||||
assertThat(tracksInfo.isTypeSupportedOrEmpty(C.TRACK_TYPE_AUDIO)).isTrue();
|
|
||||||
assertThat(tracksInfo.isTypeSelected(C.TRACK_TYPE_AUDIO)).isFalse();
|
|
||||||
ImmutableList<TracksInfo.TrackGroupInfo> trackGroupInfos = tracksInfo.getTrackGroupInfos();
|
|
||||||
assertThat(trackGroupInfos).isEmpty();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void tracksInfo_emptyStaticInstance_isEmpty() {
|
|
||||||
TracksInfo tracksInfo = TracksInfo.EMPTY;
|
|
||||||
|
|
||||||
assertThat(tracksInfo.getTrackGroupInfos()).isEmpty();
|
|
||||||
assertThat(tracksInfo).isEqualTo(new TracksInfo(ImmutableList.of()));
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
|
||||||
public void tracksInfoGetters_ofComplexTracksInfo_returnExpectedValues() {
|
|
||||||
TracksInfo.TrackGroupInfo trackGroupInfo0 =
|
|
||||||
new TracksInfo.TrackGroupInfo(
|
|
||||||
new TrackGroup(new Format.Builder().build()),
|
|
||||||
new int[] {C.FORMAT_EXCEEDS_CAPABILITIES},
|
|
||||||
C.TRACK_TYPE_AUDIO,
|
|
||||||
/* tracksSelected= */ new boolean[] {false});
|
|
||||||
TracksInfo.TrackGroupInfo trackGroupInfo1 =
|
|
||||||
new TracksInfo.TrackGroupInfo(
|
|
||||||
new TrackGroup(new Format.Builder().build(), new Format.Builder().build()),
|
|
||||||
new int[] {C.FORMAT_UNSUPPORTED_DRM, C.FORMAT_HANDLED},
|
|
||||||
C.TRACK_TYPE_VIDEO,
|
|
||||||
/* tracksSelected= */ new boolean[] {false, true});
|
|
||||||
TracksInfo tracksInfo = new TracksInfo(ImmutableList.of(trackGroupInfo0, trackGroupInfo1));
|
|
||||||
|
|
||||||
assertThat(tracksInfo.isTypeSupportedOrEmpty(C.TRACK_TYPE_AUDIO)).isFalse();
|
|
||||||
assertThat(tracksInfo.isTypeSupportedOrEmpty(C.TRACK_TYPE_VIDEO)).isTrue();
|
|
||||||
assertThat(tracksInfo.isTypeSupportedOrEmpty(C.TRACK_TYPE_TEXT)).isTrue();
|
|
||||||
assertThat(tracksInfo.isTypeSelected(C.TRACK_TYPE_AUDIO)).isFalse();
|
|
||||||
assertThat(tracksInfo.isTypeSelected(C.TRACK_TYPE_VIDEO)).isTrue();
|
|
||||||
ImmutableList<TracksInfo.TrackGroupInfo> trackGroupInfos = tracksInfo.getTrackGroupInfos();
|
|
||||||
assertThat(trackGroupInfos).hasSize(2);
|
|
||||||
assertThat(trackGroupInfos.get(0)).isSameInstanceAs(trackGroupInfo0);
|
|
||||||
assertThat(trackGroupInfos.get(1)).isSameInstanceAs(trackGroupInfo1);
|
|
||||||
assertThat(trackGroupInfos.get(0).isTrackSupported(0)).isFalse();
|
|
||||||
assertThat(trackGroupInfos.get(1).isTrackSupported(0)).isFalse();
|
|
||||||
assertThat(trackGroupInfos.get(1).isTrackSupported(1)).isTrue();
|
|
||||||
assertThat(trackGroupInfos.get(0).getTrackSupport(0)).isEqualTo(C.FORMAT_EXCEEDS_CAPABILITIES);
|
|
||||||
assertThat(trackGroupInfos.get(1).getTrackSupport(0)).isEqualTo(C.FORMAT_UNSUPPORTED_DRM);
|
|
||||||
assertThat(trackGroupInfos.get(1).getTrackSupport(1)).isEqualTo(C.FORMAT_HANDLED);
|
|
||||||
assertThat(trackGroupInfos.get(0).isTrackSelected(0)).isFalse();
|
|
||||||
assertThat(trackGroupInfos.get(1).isTrackSelected(0)).isFalse();
|
|
||||||
assertThat(trackGroupInfos.get(1).isTrackSelected(1)).isTrue();
|
|
||||||
assertThat(trackGroupInfos.get(0).getTrackType()).isEqualTo(C.TRACK_TYPE_AUDIO);
|
|
||||||
assertThat(trackGroupInfos.get(1).getTrackType()).isEqualTo(C.TRACK_TYPE_VIDEO);
|
|
||||||
}
|
|
||||||
}
|
|
@ -0,0 +1,143 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2021 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package androidx.media3.common;
|
||||||
|
|
||||||
|
import static androidx.media3.common.MimeTypes.AUDIO_AAC;
|
||||||
|
import static androidx.media3.common.MimeTypes.VIDEO_H264;
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
/** Unit test for {@link Tracks}. */
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class TracksTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void roundTripViaBundle_ofEmptyTracks_yieldsEqualInstance() {
|
||||||
|
Tracks before = Tracks.EMPTY;
|
||||||
|
Tracks after = Tracks.CREATOR.fromBundle(before.toBundle());
|
||||||
|
assertThat(after).isEqualTo(before);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void roundTripViaBundle_ofTracks_yieldsEqualInstance() {
|
||||||
|
Tracks before =
|
||||||
|
new Tracks(
|
||||||
|
ImmutableList.of(
|
||||||
|
new Tracks.Group(
|
||||||
|
new TrackGroup(new Format.Builder().setSampleMimeType(AUDIO_AAC).build()),
|
||||||
|
/* adaptiveSupported= */ false,
|
||||||
|
new int[] {C.FORMAT_EXCEEDS_CAPABILITIES},
|
||||||
|
/* trackSelected= */ new boolean[] {true}),
|
||||||
|
new Tracks.Group(
|
||||||
|
new TrackGroup(
|
||||||
|
new Format.Builder().setSampleMimeType(VIDEO_H264).build(),
|
||||||
|
new Format.Builder().setSampleMimeType(VIDEO_H264).build()),
|
||||||
|
/* adaptiveSupported= */ true,
|
||||||
|
new int[] {C.FORMAT_UNSUPPORTED_DRM, C.FORMAT_UNSUPPORTED_TYPE},
|
||||||
|
/* trackSelected= */ new boolean[] {false, true})));
|
||||||
|
Tracks after = Tracks.CREATOR.fromBundle(before.toBundle());
|
||||||
|
assertThat(after).isEqualTo(before);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getters_withoutTrack_returnExpectedValues() {
|
||||||
|
Tracks tracks = new Tracks(ImmutableList.of());
|
||||||
|
|
||||||
|
assertThat(tracks.containsType(C.TRACK_TYPE_AUDIO)).isFalse();
|
||||||
|
assertThat(tracks.isTypeSupported(C.TRACK_TYPE_AUDIO)).isFalse();
|
||||||
|
assertThat(tracks.isTypeSupported(C.TRACK_TYPE_AUDIO, /* allowExceedsCapabilities= */ true))
|
||||||
|
.isFalse();
|
||||||
|
assertThat(tracks.isTypeSelected(C.TRACK_TYPE_AUDIO)).isFalse();
|
||||||
|
ImmutableList<Tracks.Group> trackGroups = tracks.getGroups();
|
||||||
|
assertThat(trackGroups).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void emptyStaticInstance_isEmpty() {
|
||||||
|
Tracks tracks = Tracks.EMPTY;
|
||||||
|
|
||||||
|
assertThat(tracks.getGroups()).isEmpty();
|
||||||
|
assertThat(tracks).isEqualTo(new Tracks(ImmutableList.of()));
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void getters_ofComplexTracks_returnExpectedValues() {
|
||||||
|
Tracks.Group trackGroup0 =
|
||||||
|
new Tracks.Group(
|
||||||
|
new TrackGroup(new Format.Builder().setSampleMimeType(AUDIO_AAC).build()),
|
||||||
|
/* adaptiveSupported= */ false,
|
||||||
|
new int[] {C.FORMAT_EXCEEDS_CAPABILITIES},
|
||||||
|
/* trackSelected= */ new boolean[] {false});
|
||||||
|
Tracks.Group trackGroup1 =
|
||||||
|
new Tracks.Group(
|
||||||
|
new TrackGroup(
|
||||||
|
new Format.Builder().setSampleMimeType(VIDEO_H264).build(),
|
||||||
|
new Format.Builder().setSampleMimeType(VIDEO_H264).build()),
|
||||||
|
/* adaptiveSupported= */ true,
|
||||||
|
new int[] {C.FORMAT_UNSUPPORTED_DRM, C.FORMAT_HANDLED},
|
||||||
|
/* trackSelected= */ new boolean[] {false, true});
|
||||||
|
Tracks tracks = new Tracks(ImmutableList.of(trackGroup0, trackGroup1));
|
||||||
|
|
||||||
|
assertThat(tracks.containsType(C.TRACK_TYPE_AUDIO)).isTrue();
|
||||||
|
assertThat(tracks.containsType(C.TRACK_TYPE_VIDEO)).isTrue();
|
||||||
|
assertThat(tracks.containsType(C.TRACK_TYPE_TEXT)).isFalse();
|
||||||
|
assertThat(tracks.isTypeSupported(C.TRACK_TYPE_AUDIO)).isFalse();
|
||||||
|
assertThat(tracks.isTypeSupported(C.TRACK_TYPE_VIDEO)).isTrue();
|
||||||
|
assertThat(tracks.isTypeSupported(C.TRACK_TYPE_TEXT)).isFalse();
|
||||||
|
assertThat(tracks.isTypeSupported(C.TRACK_TYPE_AUDIO, /* allowExceedsCapabilities= */ true))
|
||||||
|
.isTrue();
|
||||||
|
assertThat(tracks.isTypeSupported(C.TRACK_TYPE_VIDEO, /* allowExceedsCapabilities= */ true))
|
||||||
|
.isTrue();
|
||||||
|
assertThat(tracks.isTypeSupported(C.TRACK_TYPE_TEXT, /* allowExceedsCapabilities= */ true))
|
||||||
|
.isFalse();
|
||||||
|
assertThat(tracks.isTypeSelected(C.TRACK_TYPE_AUDIO)).isFalse();
|
||||||
|
assertThat(tracks.isTypeSelected(C.TRACK_TYPE_VIDEO)).isTrue();
|
||||||
|
ImmutableList<Tracks.Group> trackGroups = tracks.getGroups();
|
||||||
|
assertThat(trackGroups).hasSize(2);
|
||||||
|
assertThat(trackGroups.get(0)).isSameInstanceAs(trackGroup0);
|
||||||
|
assertThat(trackGroups.get(1)).isSameInstanceAs(trackGroup1);
|
||||||
|
assertThat(trackGroups.get(0).isTrackSupported(0)).isFalse();
|
||||||
|
assertThat(trackGroups.get(1).isTrackSupported(0)).isFalse();
|
||||||
|
assertThat(trackGroups.get(1).isTrackSupported(1)).isTrue();
|
||||||
|
assertThat(trackGroups.get(0).getTrackSupport(0)).isEqualTo(C.FORMAT_EXCEEDS_CAPABILITIES);
|
||||||
|
assertThat(trackGroups.get(1).getTrackSupport(0)).isEqualTo(C.FORMAT_UNSUPPORTED_DRM);
|
||||||
|
assertThat(trackGroups.get(1).getTrackSupport(1)).isEqualTo(C.FORMAT_HANDLED);
|
||||||
|
assertThat(trackGroups.get(0).isTrackSelected(0)).isFalse();
|
||||||
|
assertThat(trackGroups.get(1).isTrackSelected(0)).isFalse();
|
||||||
|
assertThat(trackGroups.get(1).isTrackSelected(1)).isTrue();
|
||||||
|
assertThat(trackGroups.get(0).getType()).isEqualTo(C.TRACK_TYPE_AUDIO);
|
||||||
|
assertThat(trackGroups.get(1).getType()).isEqualTo(C.TRACK_TYPE_VIDEO);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Tests that {@link Tracks.Group#isAdaptiveSupported} returns false if the group only contains a
|
||||||
|
* single track, even if true is passed to the constructor.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void groupWithSingleTrack_isNotAdaptive() {
|
||||||
|
Tracks.Group trackGroup =
|
||||||
|
new Tracks.Group(
|
||||||
|
new TrackGroup(new Format.Builder().setSampleMimeType(AUDIO_AAC).build()),
|
||||||
|
/* adaptiveSupported= */ true,
|
||||||
|
new int[] {C.FORMAT_EXCEEDS_CAPABILITIES},
|
||||||
|
/* trackSelected= */ new boolean[] {false});
|
||||||
|
assertThat(trackGroup.isAdaptiveSupported()).isFalse();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,55 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 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.common.text;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.os.Bundle;
|
||||||
|
import android.os.Parcel;
|
||||||
|
import android.text.SpannedString;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
/** Tests for {@link CueGroup}. */
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class CueGroupTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void bundleAndUnBundleCueGroup() {
|
||||||
|
Cue textCue = new Cue.Builder().setText(SpannedString.valueOf("text")).build();
|
||||||
|
Cue bitmapCue =
|
||||||
|
new Cue.Builder().setBitmap(Bitmap.createBitmap(1, 1, Bitmap.Config.ARGB_8888)).build();
|
||||||
|
ImmutableList<Cue> cues = ImmutableList.of(textCue, bitmapCue);
|
||||||
|
CueGroup cueGroup = new CueGroup(cues);
|
||||||
|
|
||||||
|
Parcel parcel = Parcel.obtain();
|
||||||
|
try {
|
||||||
|
parcel.writeBundle(cueGroup.toBundle());
|
||||||
|
parcel.setDataPosition(0);
|
||||||
|
|
||||||
|
Bundle bundle = parcel.readBundle();
|
||||||
|
CueGroup filteredCueGroup = CueGroup.CREATOR.fromBundle(bundle);
|
||||||
|
|
||||||
|
assertThat(filteredCueGroup.cues).containsExactly(textCue);
|
||||||
|
} finally {
|
||||||
|
parcel.recycle();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -22,6 +22,7 @@ import static android.graphics.Color.argb;
|
|||||||
import static android.graphics.Color.parseColor;
|
import static android.graphics.Color.parseColor;
|
||||||
import static androidx.media3.common.util.ColorParser.parseTtmlColor;
|
import static androidx.media3.common.util.ColorParser.parseTtmlColor;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
import static org.junit.Assert.assertThrows;
|
||||||
|
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
@ -34,24 +35,26 @@ public final class ColorParserTest {
|
|||||||
|
|
||||||
// Negative tests.
|
// Negative tests.
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test
|
||||||
public void parseUnknownColor() {
|
public void parseUnknownColor() {
|
||||||
ColorParser.parseTtmlColor("colorOfAnElectron");
|
assertThrows(
|
||||||
|
IllegalArgumentException.class, () -> ColorParser.parseTtmlColor("colorOfAnElectron"));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test
|
||||||
public void parseNull() {
|
public void parseNull() {
|
||||||
ColorParser.parseTtmlColor(null);
|
assertThrows(IllegalArgumentException.class, () -> ColorParser.parseTtmlColor(null));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test
|
||||||
public void parseEmpty() {
|
public void parseEmpty() {
|
||||||
ColorParser.parseTtmlColor("");
|
assertThrows(IllegalArgumentException.class, () -> ColorParser.parseTtmlColor(""));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test(expected = IllegalArgumentException.class)
|
@Test
|
||||||
public void rgbColorParsingRgbValuesNegative() {
|
public void rgbColorParsingRgbValuesNegative() {
|
||||||
ColorParser.parseTtmlColor("rgb(-4, 55, 209)");
|
assertThrows(
|
||||||
|
IllegalArgumentException.class, () -> ColorParser.parseTtmlColor("rgb(-4, 55, 209)"));
|
||||||
}
|
}
|
||||||
|
|
||||||
// Positive tests.
|
// Positive tests.
|
||||||
|
@ -21,6 +21,7 @@ import static androidx.media3.common.util.Util.escapeFileName;
|
|||||||
import static androidx.media3.common.util.Util.getCodecsOfType;
|
import static androidx.media3.common.util.Util.getCodecsOfType;
|
||||||
import static androidx.media3.common.util.Util.getStringForTime;
|
import static androidx.media3.common.util.Util.getStringForTime;
|
||||||
import static androidx.media3.common.util.Util.gzip;
|
import static androidx.media3.common.util.Util.gzip;
|
||||||
|
import static androidx.media3.common.util.Util.maxValue;
|
||||||
import static androidx.media3.common.util.Util.minValue;
|
import static androidx.media3.common.util.Util.minValue;
|
||||||
import static androidx.media3.common.util.Util.parseXsDateTime;
|
import static androidx.media3.common.util.Util.parseXsDateTime;
|
||||||
import static androidx.media3.common.util.Util.parseXsDuration;
|
import static androidx.media3.common.util.Util.parseXsDuration;
|
||||||
@ -102,50 +103,109 @@ public class UtilTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void inferContentType_handlesHlsIsmUris() {
|
public void inferContentType_handlesHlsIsmUris() {
|
||||||
assertThat(Util.inferContentType("http://a.b/c.ism/manifest(format=m3u8-aapl)"))
|
assertThat(Util.inferContentType(Uri.parse("http://a.b/c.ism/manifest(format=m3u8-aapl)")))
|
||||||
.isEqualTo(C.TYPE_HLS);
|
.isEqualTo(C.CONTENT_TYPE_HLS);
|
||||||
assertThat(Util.inferContentType("http://a.b/c.ism/manifest(format=m3u8-aapl,quality=hd)"))
|
assertThat(
|
||||||
.isEqualTo(C.TYPE_HLS);
|
Util.inferContentType(
|
||||||
assertThat(Util.inferContentType("http://a.b/c.ism/manifest(quality=hd,format=m3u8-aapl)"))
|
Uri.parse("http://a.b/c.ism/manifest(format=m3u8-aapl,quality=hd)")))
|
||||||
.isEqualTo(C.TYPE_HLS);
|
.isEqualTo(C.CONTENT_TYPE_HLS);
|
||||||
|
assertThat(
|
||||||
|
Util.inferContentType(
|
||||||
|
Uri.parse("http://a.b/c.ism/manifest(quality=hd,format=m3u8-aapl)")))
|
||||||
|
.isEqualTo(C.CONTENT_TYPE_HLS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void inferContentType_handlesHlsIsmV3Uris() {
|
public void inferContentType_handlesHlsIsmV3Uris() {
|
||||||
assertThat(Util.inferContentType("http://a.b/c.ism/manifest(format=m3u8-aapl-v3)"))
|
assertThat(Util.inferContentType(Uri.parse("http://a.b/c.ism/manifest(format=m3u8-aapl-v3)")))
|
||||||
.isEqualTo(C.TYPE_HLS);
|
.isEqualTo(C.CONTENT_TYPE_HLS);
|
||||||
assertThat(Util.inferContentType("http://a.b/c.ism/manifest(format=m3u8-aapl-v3,quality=hd)"))
|
assertThat(
|
||||||
.isEqualTo(C.TYPE_HLS);
|
Util.inferContentType(
|
||||||
assertThat(Util.inferContentType("http://a.b/c.ism/manifest(quality=hd,format=m3u8-aapl-v3)"))
|
Uri.parse("http://a.b/c.ism/manifest(format=m3u8-aapl-v3,quality=hd)")))
|
||||||
.isEqualTo(C.TYPE_HLS);
|
.isEqualTo(C.CONTENT_TYPE_HLS);
|
||||||
|
assertThat(
|
||||||
|
Util.inferContentType(
|
||||||
|
Uri.parse("http://a.b/c.ism/manifest(quality=hd,format=m3u8-aapl-v3)")))
|
||||||
|
.isEqualTo(C.CONTENT_TYPE_HLS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void inferContentType_handlesDashIsmUris() {
|
public void inferContentType_handlesDashIsmUris() {
|
||||||
assertThat(Util.inferContentType("http://a.b/c.isml/manifest(format=mpd-time-csf)"))
|
assertThat(Util.inferContentType(Uri.parse("http://a.b/c.isml/manifest(format=mpd-time-csf)")))
|
||||||
.isEqualTo(C.TYPE_DASH);
|
.isEqualTo(C.CONTENT_TYPE_DASH);
|
||||||
assertThat(Util.inferContentType("http://a.b/c.isml/manifest(format=mpd-time-csf,quality=hd)"))
|
assertThat(
|
||||||
.isEqualTo(C.TYPE_DASH);
|
Util.inferContentType(
|
||||||
assertThat(Util.inferContentType("http://a.b/c.isml/manifest(quality=hd,format=mpd-time-csf)"))
|
Uri.parse("http://a.b/c.isml/manifest(format=mpd-time-csf,quality=hd)")))
|
||||||
.isEqualTo(C.TYPE_DASH);
|
.isEqualTo(C.CONTENT_TYPE_DASH);
|
||||||
|
assertThat(
|
||||||
|
Util.inferContentType(
|
||||||
|
Uri.parse("http://a.b/c.isml/manifest(quality=hd,format=mpd-time-csf)")))
|
||||||
|
.isEqualTo(C.CONTENT_TYPE_DASH);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void inferContentType_handlesSmoothStreamingIsmUris() {
|
public void inferContentType_handlesSmoothStreamingIsmUris() {
|
||||||
assertThat(Util.inferContentType("http://a.b/c.ism")).isEqualTo(C.TYPE_SS);
|
assertThat(Util.inferContentType(Uri.parse("http://a.b/c.ism"))).isEqualTo(C.CONTENT_TYPE_SS);
|
||||||
assertThat(Util.inferContentType("http://a.b/c.isml")).isEqualTo(C.TYPE_SS);
|
assertThat(Util.inferContentType(Uri.parse("http://a.b/c.isml"))).isEqualTo(C.CONTENT_TYPE_SS);
|
||||||
assertThat(Util.inferContentType("http://a.b/c.ism/")).isEqualTo(C.TYPE_SS);
|
assertThat(Util.inferContentType(Uri.parse("http://a.b/c.ism/"))).isEqualTo(C.CONTENT_TYPE_SS);
|
||||||
assertThat(Util.inferContentType("http://a.b/c.isml/")).isEqualTo(C.TYPE_SS);
|
assertThat(Util.inferContentType(Uri.parse("http://a.b/c.isml/"))).isEqualTo(C.CONTENT_TYPE_SS);
|
||||||
assertThat(Util.inferContentType("http://a.b/c.ism/Manifest")).isEqualTo(C.TYPE_SS);
|
assertThat(Util.inferContentType(Uri.parse("http://a.b/c.ism/Manifest")))
|
||||||
assertThat(Util.inferContentType("http://a.b/c.isml/manifest")).isEqualTo(C.TYPE_SS);
|
.isEqualTo(C.CONTENT_TYPE_SS);
|
||||||
assertThat(Util.inferContentType("http://a.b/c.isml/manifest(filter=x)")).isEqualTo(C.TYPE_SS);
|
assertThat(Util.inferContentType(Uri.parse("http://a.b/c.isml/manifest")))
|
||||||
assertThat(Util.inferContentType("http://a.b/c.isml/manifest_hd")).isEqualTo(C.TYPE_SS);
|
.isEqualTo(C.CONTENT_TYPE_SS);
|
||||||
|
assertThat(Util.inferContentType(Uri.parse("http://a.b/c.isml/manifest(filter=x)")))
|
||||||
|
.isEqualTo(C.CONTENT_TYPE_SS);
|
||||||
|
assertThat(Util.inferContentType(Uri.parse("http://a.b/c.isml/manifest_hd")))
|
||||||
|
.isEqualTo(C.CONTENT_TYPE_SS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void inferContentType_handlesOtherIsmUris() {
|
public void inferContentType_handlesOtherIsmUris() {
|
||||||
assertThat(Util.inferContentType("http://a.b/c.ism/video.mp4")).isEqualTo(C.TYPE_OTHER);
|
assertThat(Util.inferContentType(Uri.parse("http://a.b/c.ism/video.mp4")))
|
||||||
assertThat(Util.inferContentType("http://a.b/c.ism/prefix-manifest")).isEqualTo(C.TYPE_OTHER);
|
.isEqualTo(C.CONTENT_TYPE_OTHER);
|
||||||
|
assertThat(Util.inferContentType(Uri.parse("http://a.b/c.ism/prefix-manifest")))
|
||||||
|
.isEqualTo(C.CONTENT_TYPE_OTHER);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Test that the deprecated {@link Util#inferContentType(String)} works when passed only a file
|
||||||
|
* extension and the leading dot.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
@Test
|
||||||
|
public void inferContentType_extensionAsPath() {
|
||||||
|
assertThat(Util.inferContentType(".m3u8")).isEqualTo(C.CONTENT_TYPE_HLS);
|
||||||
|
assertThat(Util.inferContentType(".mpd")).isEqualTo(C.CONTENT_TYPE_DASH);
|
||||||
|
assertThat(Util.inferContentType(".ism")).isEqualTo(C.TYPE_SS);
|
||||||
|
assertThat(Util.inferContentType(".isml")).isEqualTo(C.TYPE_SS);
|
||||||
|
assertThat(Util.inferContentType(".mp4")).isEqualTo(C.CONTENT_TYPE_OTHER);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Testing deprecated method.
|
||||||
|
@SuppressWarnings("deprecation")
|
||||||
|
@Test
|
||||||
|
public void inferContentType_extensionOverride() {
|
||||||
|
assertThat(
|
||||||
|
Util.inferContentType(
|
||||||
|
Uri.parse("file:///path/to/something.mpd"), /* overrideExtension= */ null))
|
||||||
|
.isEqualTo(C.CONTENT_TYPE_DASH);
|
||||||
|
assertThat(
|
||||||
|
Util.inferContentType(
|
||||||
|
Uri.parse("file:///path/to/something.mpd"), /* overrideExtension= */ ""))
|
||||||
|
.isEqualTo(C.CONTENT_TYPE_DASH);
|
||||||
|
assertThat(
|
||||||
|
Util.inferContentType(
|
||||||
|
Uri.parse("file:///path/to/something.mpd"), /* overrideExtension= */ "m3u8"))
|
||||||
|
.isEqualTo(C.CONTENT_TYPE_HLS);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void inferContentTypeForExtension() {
|
||||||
|
assertThat(Util.inferContentTypeForExtension("m3u8")).isEqualTo(C.CONTENT_TYPE_HLS);
|
||||||
|
assertThat(Util.inferContentTypeForExtension("mpd")).isEqualTo(C.CONTENT_TYPE_DASH);
|
||||||
|
assertThat(Util.inferContentTypeForExtension("ism")).isEqualTo(C.TYPE_SS);
|
||||||
|
assertThat(Util.inferContentTypeForExtension("isml")).isEqualTo(C.TYPE_SS);
|
||||||
|
assertThat(Util.inferContentTypeForExtension("mp4")).isEqualTo(C.CONTENT_TYPE_OTHER);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -747,6 +807,21 @@ public class UtilTest {
|
|||||||
assertThrows(NoSuchElementException.class, () -> minValue(new SparseLongArray()));
|
assertThrows(NoSuchElementException.class, () -> minValue(new SparseLongArray()));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void sparseLongArrayMaxValue_returnsMaxValue() {
|
||||||
|
SparseLongArray sparseLongArray = new SparseLongArray();
|
||||||
|
sparseLongArray.put(0, 2);
|
||||||
|
sparseLongArray.put(25, 10);
|
||||||
|
sparseLongArray.put(42, 1);
|
||||||
|
|
||||||
|
assertThat(maxValue(sparseLongArray)).isEqualTo(10);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void sparseLongArrayMaxValue_emptyArray_throws() {
|
||||||
|
assertThrows(NoSuchElementException.class, () -> maxValue(new SparseLongArray()));
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void parseXsDuration_returnsParsedDurationInMillis() {
|
public void parseXsDuration_returnsParsedDurationInMillis() {
|
||||||
assertThat(parseXsDuration("PT150.279S")).isEqualTo(150279L);
|
assertThat(parseXsDuration("PT150.279S")).isEqualTo(150279L);
|
||||||
@ -1087,6 +1162,7 @@ public class UtilTest {
|
|||||||
assertThat(Util.normalizeLanguageCode("ara-ayl")).isEqualTo("ar-ayl");
|
assertThat(Util.normalizeLanguageCode("ara-ayl")).isEqualTo("ar-ayl");
|
||||||
|
|
||||||
// Special case of short codes that are actually part of a macrolanguage.
|
// Special case of short codes that are actually part of a macrolanguage.
|
||||||
|
assertThat(Util.normalizeLanguageCode("arb")).isEqualTo("ar-arb");
|
||||||
assertThat(Util.normalizeLanguageCode("nb")).isEqualTo("no-nob");
|
assertThat(Util.normalizeLanguageCode("nb")).isEqualTo("no-nob");
|
||||||
assertThat(Util.normalizeLanguageCode("nn")).isEqualTo("no-nno");
|
assertThat(Util.normalizeLanguageCode("nn")).isEqualTo("no-nno");
|
||||||
assertThat(Util.normalizeLanguageCode("nob")).isEqualTo("no-nob");
|
assertThat(Util.normalizeLanguageCode("nob")).isEqualTo("no-nob");
|
||||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user