mirror of
https://github.com/androidx/media.git
synced 2025-04-30 06:46:50 +08:00
commit
5328d6464a
14
.github/ISSUE_TEMPLATE/bug.yml
vendored
14
.github/ISSUE_TEMPLATE/bug.yml
vendored
@ -17,10 +17,10 @@ body:
|
|||||||
tracker: https://github.com/google/ExoPlayer/issues?q=is%3Aissue
|
tracker: https://github.com/google/ExoPlayer/issues?q=is%3Aissue
|
||||||
- type: dropdown
|
- type: dropdown
|
||||||
attributes:
|
attributes:
|
||||||
label: Media3 Version
|
label: Version
|
||||||
description: What version of Media3 (or ExoPlayer) are you using?
|
description: What version of Media3 (or ExoPlayer) are you using?
|
||||||
options:
|
options:
|
||||||
- Media3 1.1.0-alpha01
|
- Media3 1.1.0
|
||||||
- Media3 1.0.2
|
- Media3 1.0.2
|
||||||
- Media3 1.0.1
|
- Media3 1.0.1
|
||||||
- Media3 1.0.0
|
- Media3 1.0.0
|
||||||
@ -33,6 +33,8 @@ body:
|
|||||||
- Media3 1.0.0-alpha02
|
- Media3 1.0.0-alpha02
|
||||||
- Media3 1.0.0-alpha01
|
- Media3 1.0.0-alpha01
|
||||||
- Media3 `main` branch
|
- Media3 `main` branch
|
||||||
|
- Media3 pre-release (alpha, beta or RC not in this list)
|
||||||
|
- ExoPlayer 2.19.0
|
||||||
- ExoPlayer 2.18.7
|
- ExoPlayer 2.18.7
|
||||||
- ExoPlayer 2.18.6
|
- ExoPlayer 2.18.6
|
||||||
- ExoPlayer 2.18.5
|
- ExoPlayer 2.18.5
|
||||||
@ -54,6 +56,12 @@ body:
|
|||||||
- Older (unsupported)
|
- Older (unsupported)
|
||||||
validations:
|
validations:
|
||||||
required: true
|
required: true
|
||||||
|
- type: textarea
|
||||||
|
attributes:
|
||||||
|
label: More version details
|
||||||
|
description: |
|
||||||
|
Required if you selected `main` or `dev-v2` (please provide an exact commit SHA),
|
||||||
|
or 'pre-release' or 'older' (please provide the version).
|
||||||
- type: textarea
|
- type: textarea
|
||||||
attributes:
|
attributes:
|
||||||
label: Devices that reproduce the issue
|
label: Devices that reproduce the issue
|
||||||
@ -114,7 +122,7 @@ body:
|
|||||||
* Attach a file here
|
* Attach a file here
|
||||||
* Include a media URL
|
* Include a media URL
|
||||||
* Refer to a piece of media from the demo app (e.g. `Misc > Dizzy (MP4)`)
|
* 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 don't want to post media publicly please email the info to android-media-github@google.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.
|
* 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.
|
For DRM-protected media please also include the scheme and license server URL.
|
||||||
|
6
.github/ISSUE_TEMPLATE/question.md
vendored
6
.github/ISSUE_TEMPLATE/question.md
vendored
@ -39,6 +39,6 @@ Don't forget to check ExoPlayer's supported formats and devices, if applicable
|
|||||||
(https://developer.android.com/guide/topics/media/exoplayer/supported-formats).
|
(https://developer.android.com/guide/topics/media/exoplayer/supported-formats).
|
||||||
|
|
||||||
If there's something you don't want to post publicly, please submit the issue,
|
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
|
then email the link/bug report to android-media-github@google.com using a
|
||||||
format "Issue #1234", where #1234 is your issue number (we don't reply to
|
subject in the format "Issue #1234", where #1234 is your issue number (we don't
|
||||||
emails).
|
reply to emails).
|
||||||
|
10
.idea/icon.svg
generated
Normal file
10
.idea/icon.svg
generated
Normal file
@ -0,0 +1,10 @@
|
|||||||
|
<svg xmlns="http://www.w3.org/2000/svg" xmlns:xlink="http://www.w3.org/1999/xlink" version="1.1" viewBox="60 60 130 130">
|
||||||
|
<g>
|
||||||
|
<path transform="matrix(1,0,0,-1,91.2359,110.7836)" d="M0 0C-1.459 1.259-2.67 2.872-3.493 4.807-5.848 10.342-4.026 16.923 .843 20.454 7.261 25.107 16.099 23.076 19.927 16.386 20.683 15.065 21.18 13.667 21.437 12.251 21.753 10.51 23.603 9.59 25.201 10.181L42.333 20.072-3.502 46.535C-9.567 50.037-17.147 45.66-17.147 38.657V-14.113L-.508-4.594C1.175-3.631 1.468-1.267 0 0" fill="#fcb64e"/>
|
||||||
|
<path transform="matrix(1,0,0,-1,74.2803,124.942)" d="M0 0C-.064 0-.128-.002-.192-.004V-.005L-.101-.058Z" fill="#fcb64e"/>
|
||||||
|
<path transform="matrix(1,0,0,-1,112.6543,151.6317)" d="M0 0C-.354-1.895-1.137-3.753-2.395-5.438-5.992-10.259-12.595-11.998-18.097-9.568-25.348-6.366-28.043 2.293-24.19 8.968-23.429 10.287-22.471 11.42-21.377 12.355-19.908 13.611-20.189 15.969-21.862 16.935L-38.566 26.579V-26.242C-38.566-33.245-30.985-37.621-24.92-34.12L20.823-7.71 4.225 1.874C2.545 2.843 .355 1.906 0 0" fill="#56a0d7"/>
|
||||||
|
<path transform="matrix(1,0,0,-1,74.0884,124.9471)" d="M0 0V-.106L.091-.053Z" fill="#56a0d7"/>
|
||||||
|
<path transform="matrix(1,0,0,-1,129.8726,137.40271)" d="M0 0C-1.352-.476-2.805-.736-4.321-.736-12.028-.736-18.18 5.928-17.328 13.809-16.665 19.936-11.683 24.817-5.545 25.38-3.585 25.559-1.707 25.302 .007 24.697 1.712 24.096 3.467 25.291 3.696 27.027V46.485C3.711 46.51 3.728 46.535 3.742 46.56V46.561L3.696 46.535V46.691L-13.436 36.8C-15.034 36.209-16.884 37.129-17.2 38.87-17.457 40.286-17.954 41.684-18.71 43.005-22.538 49.696-31.376 51.726-37.793 47.073-42.663 43.542-44.484 36.961-42.13 31.426-41.307 29.491-40.096 27.878-38.637 26.619-37.169 25.352-37.461 22.988-39.145 22.026L-55.784 12.506-55.873 12.456C-55.843 12.456-55.814 12.457-55.784 12.457-55.721 12.459-55.657 12.46-55.592 12.461L-55.693 12.403-55.784 12.35-39.081 2.706C-37.407 1.74-37.126-.618-38.595-1.874-39.689-2.809-40.647-3.942-41.408-5.261-45.262-11.936-42.566-20.595-35.315-23.797-29.813-26.227-23.21-24.488-19.613-19.667-18.355-17.982-17.572-16.124-17.218-14.229-16.863-12.323-14.673-11.386-12.994-12.355L3.605-21.939 3.696-21.991V-2.331C3.467-.592 1.709 .602 0 0" fill="#ae1e59"/>
|
||||||
|
<path transform="matrix(1,0,0,-1,179.4517,117.17461)" d="M0 0-45.835 26.463V26.461C-45.835 26.461-45.836 26.462-45.837 26.463V26.333 26.332 7.172C-45.837 7.043-45.866 6.923-45.883 6.799-46.112 5.062-47.867 3.868-49.572 4.469-51.286 5.074-53.164 5.331-55.124 5.151-61.262 4.589-66.244-.292-66.907-6.42-67.759-14.3-61.607-20.964-53.9-20.964-52.384-20.964-50.931-20.704-49.579-20.228-47.87-19.626-46.112-20.82-45.883-22.559-45.866-22.683-45.837-22.803-45.837-22.932V-42.219C-45.836-42.219-45.836-42.218-45.836-42.218L-45.835-42.22 0-15.756C6.064-12.255 6.064-3.501 0 0" fill="#ef5451"/>
|
||||||
|
</g>
|
||||||
|
</svg>
|
After Width: | Height: | Size: 2.7 KiB |
@ -38,6 +38,24 @@ you made on top of `main` using
|
|||||||
$ git diff -U0 main... | google-java-format-diff.py -p1 -i
|
$ git diff -U0 main... | google-java-format-diff.py -p1 -i
|
||||||
```
|
```
|
||||||
|
|
||||||
|
### Push access to PR branches
|
||||||
|
|
||||||
|
Please ensure maintainers of this repository have push access to your PR branch
|
||||||
|
by ticking the `Allow edits from maintainers` checkbox when creating the PR (or
|
||||||
|
after it's created). See the
|
||||||
|
[GitHub docs](https://docs.github.com/en/pull-requests/collaborating-with-pull-requests/proposing-changes-to-your-work-with-pull-requests/creating-a-pull-request-from-a-fork)
|
||||||
|
for more info. This allows us to make changes and fixes to the PR while it goes
|
||||||
|
through internal review, and ensures we don't create an
|
||||||
|
['evil' merge](https://git-scm.com/docs/gitglossary#Documentation/gitglossary.txt-aiddefevilmergeaevilmerge)
|
||||||
|
when it gets merged.
|
||||||
|
|
||||||
|
This checkbox only appears on PRs from individual-owned forks
|
||||||
|
(https://github.com/orgs/community/discussions/5634). If you open a PR from an
|
||||||
|
organization-owned fork we will ask you to open a new one from an
|
||||||
|
individual-owned fork. If this isn't possible we can still merge the PR, but it
|
||||||
|
will result in an 'evil' merge because the changes and fixes we make during
|
||||||
|
internal review will be part of the merge commit.
|
||||||
|
|
||||||
## Contributor license agreement
|
## Contributor license agreement
|
||||||
|
|
||||||
Contributions to any Google project must be accompanied by a Contributor
|
Contributions to any Google project must be accompanied by a Contributor
|
||||||
|
295
RELEASENOTES.md
295
RELEASENOTES.md
@ -1,5 +1,300 @@
|
|||||||
# Release notes
|
# Release notes
|
||||||
|
|
||||||
|
## 1.1
|
||||||
|
|
||||||
|
### 1.1.0 (2023-07-05)
|
||||||
|
|
||||||
|
This release corresponds to the
|
||||||
|
[ExoPlayer 2.19.0 release](https://github.com/google/ExoPlayer/releases/tag/r2.19.0).
|
||||||
|
|
||||||
|
This release contains the following changes since the
|
||||||
|
[1.0.2 release](#102-2023-05-18):
|
||||||
|
|
||||||
|
* Common Library:
|
||||||
|
* Add suppression reason for unsuitable audio route and play when ready
|
||||||
|
change reason for suppressed too long.
|
||||||
|
([#15](https://github.com/androidx/media/issues/15)).
|
||||||
|
* Add commands to Player:
|
||||||
|
* `COMMAND_GET_METADATA`
|
||||||
|
* `COMMAND_SET_PLAYLIST_METADATA`
|
||||||
|
* `COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS`
|
||||||
|
* `COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS`
|
||||||
|
* Add overloaded methods to Player which allow users to specify volume
|
||||||
|
flags:
|
||||||
|
* `void setDeviceVolume(int, int)`
|
||||||
|
* `void increaseDeviceVolume(int)`
|
||||||
|
* `void decreaseDeviceVolume(int)`
|
||||||
|
* `void setDeviceMuted(boolean, int)`
|
||||||
|
* Add `Builder` for `DeviceInfo` and deprecate existing constructor.
|
||||||
|
* Add `DeviceInfo.routingControllerId` to specify the routing controller
|
||||||
|
ID for remote playbacks.
|
||||||
|
* Add `Player.replaceMediaItem(s)` as a shortcut to adding and removing
|
||||||
|
items at the same position
|
||||||
|
([#8046](https://github.com/google/ExoPlayer/issues/8046)).
|
||||||
|
* ExoPlayer:
|
||||||
|
* Allow ExoPlayer to have control of device volume methods only if
|
||||||
|
explicitly opted in. Use
|
||||||
|
`ExoPlayer.Builder.setDeviceVolumeControlEnabled` to have access to:
|
||||||
|
* `getDeviceVolume()`
|
||||||
|
* `isDeviceMuted()`
|
||||||
|
* `setDeviceVolume(int)` and `setDeviceVolume(int, int)`
|
||||||
|
* `increaseDeviceVolume(int)` and `increaseDeviceVolume(int, int)`
|
||||||
|
* `decreaseDeviceVolume(int)` and `decreaseDeviceVolume(int, int)`
|
||||||
|
* Add `FilteringMediaSource` that allows to filter available track types
|
||||||
|
from a `MediaSource`.
|
||||||
|
* Add support for including Common Media Client Data (CMCD) in the
|
||||||
|
outgoing requests of adaptive streaming formats DASH, HLS, and
|
||||||
|
SmoothStreaming. The following fields, `br`, `bl`, `cid`, `rtp`, and
|
||||||
|
`sid`, have been incorporated
|
||||||
|
([#8699](https://github.com/google/ExoPlayer/issues/8699)). API
|
||||||
|
structure and API methods:
|
||||||
|
* CMCD logging is disabled by default, use
|
||||||
|
`MediaSource.Factory.setCmcdConfigurationFactory(CmcdConfiguration.Factory
|
||||||
|
cmcdConfigurationFactory)` to enable it.
|
||||||
|
* All keys are enabled by default, override
|
||||||
|
`CmcdConfiguration.RequestConfig.isKeyAllowed(String key)` to filter
|
||||||
|
out which keys are logged.
|
||||||
|
* Override `CmcdConfiguration.RequestConfig.getCustomData()` to enable
|
||||||
|
custom key logging.
|
||||||
|
* Add additional action to manifest of main demo to make it easier to
|
||||||
|
start the demo app with a custom `*.exolist.json` file
|
||||||
|
([#439](https://github.com/androidx/media/pull/439)).
|
||||||
|
* Add `ExoPlayer.setVideoEffects()` for using `Effect` during video
|
||||||
|
playback.
|
||||||
|
* Update `SampleQueue` to store `sourceId` as a `long` rather than an
|
||||||
|
`int`. This changes the signatures of public methods
|
||||||
|
`SampleQueue.sourceId` and `SampleQueue.peekSourceId`.
|
||||||
|
* Add parameters to `LoadControl` methods `shouldStartPlayback` and
|
||||||
|
`onTracksSelected` that allow associating these methods with the
|
||||||
|
relevant `MediaPeriod`.
|
||||||
|
* Change signature of
|
||||||
|
`ServerSideAdInsertionMediaSource.setAdPlaybackStates(Map<Object,
|
||||||
|
AdPlaybackState>)` by adding a timeline parameter that contains the
|
||||||
|
periods with the UIDs used as keys in the map. This is required to avoid
|
||||||
|
concurrency issues with multi-period live streams.
|
||||||
|
* Deprecate `EventDispatcher.withParameters(int windowIndex, @Nullable
|
||||||
|
MediaPeriodId mediaPeriodId, long mediaTimeOffsetMs)` and
|
||||||
|
`BaseMediaSource.createEventDispatcher(..., long mediaTimeOffsetMs)`.
|
||||||
|
The variant of the methods without the `mediaTimeOffsetUs` can be called
|
||||||
|
instead. Note that even for the deprecated variants, the offset is not
|
||||||
|
anymore added to `startTimeUs` and `endTimeUs` of the `MediaLoadData`
|
||||||
|
objects that are dispatched by the dispatcher.
|
||||||
|
* Rename `ExoTrackSelection.blacklist` to `excludeTrack` and
|
||||||
|
`isBlacklisted` to `isTrackExcluded`.
|
||||||
|
* Fix inconsistent behavior between `ExoPlayer.setMediaItem(s)` and
|
||||||
|
`addMediaItem(s)` when called on an empty playlist.
|
||||||
|
* Transformer:
|
||||||
|
* Remove `Transformer.Builder.setMediaSourceFactory(MediaSource.Factory)`.
|
||||||
|
Use `ExoPlayerAssetLoader.Factory(MediaSource.Factory)` and
|
||||||
|
`Transformer.Builder.setAssetLoaderFactory(AssetLoader.Factory)`
|
||||||
|
instead.
|
||||||
|
* Remove `Transformer.startTransformation(MediaItem,
|
||||||
|
ParcelFileDescriptor)`.
|
||||||
|
* Fix a bug where transformation could get stuck (leading to muxer
|
||||||
|
timeout) if the end of the video stream was signaled at the moment when
|
||||||
|
an input frame was pending processing.
|
||||||
|
* Query codecs via `MediaCodecList` instead of using
|
||||||
|
`findDecoder/EncoderForFormat` utilities, to expand support.
|
||||||
|
* Remove B-frame configuration in `DefaultEncoderFactory` because it
|
||||||
|
doesn't work on some devices.
|
||||||
|
* Track selection:
|
||||||
|
* Add
|
||||||
|
`DefaultTrackSelector.Parameters.allowInvalidateSelectionsForRendererCapabilitiesChange`
|
||||||
|
which is disabled by default. When enabled, the `DefaultTrackSelector`
|
||||||
|
will trigger a new track selection when the renderer capabilities
|
||||||
|
changed.
|
||||||
|
* Extractors:
|
||||||
|
* Ogg: Fix bug when seeking in files with a long duration
|
||||||
|
([#391](https://github.com/androidx/media/issues/391)).
|
||||||
|
* FMP4: Fix issue where `TimestampAdjuster` initializes a wrong timestamp
|
||||||
|
offset with metadata sample time from emsg atom
|
||||||
|
([#356](https://github.com/androidx/media/issues/356)).
|
||||||
|
* Audio:
|
||||||
|
* Fix bug where some playbacks fail when tunneling is enabled and
|
||||||
|
`AudioProcessors` are active, e.g. for gapless trimming
|
||||||
|
([#10847](https://github.com/google/ExoPlayer/issues/10847)).
|
||||||
|
* Encapsulate Opus frames in Ogg packets in direct playbacks (offload).
|
||||||
|
* Extrapolate current position during sleep with offload scheduling.
|
||||||
|
* Add `Renderer.release()` and `AudioSink.release()` for releasing the
|
||||||
|
resources at the end of player's lifecycle.
|
||||||
|
* Listen to audio capabilities changes in `DefaultAudioSink`. Add a
|
||||||
|
required parameter `context` in the constructor of `DefaultAudioSink`,
|
||||||
|
with which the `DefaultAudioSink` will register as the listener to the
|
||||||
|
`AudioCapabilitiesReceiver` and update its `audioCapabilities` property
|
||||||
|
when informed with a capabilities change.
|
||||||
|
* Propagate audio capabilities changes via a new event
|
||||||
|
`onAudioCapabilitiesChanged` in `AudioSink.Listener` interface, and a
|
||||||
|
new interface `RendererCapabilities.Listener` which triggers
|
||||||
|
`onRendererCapabilitiesChanged` events.
|
||||||
|
* Add `ChannelMixingAudioProcessor` for applying scaling/mixing to audio
|
||||||
|
channels.
|
||||||
|
* Add new int value `DISCARD_REASON_AUDIO_BYPASS_POSSIBLE` to
|
||||||
|
`DecoderDiscardReasons` to discard audio decoder when bypass mode is
|
||||||
|
possible after audio capabilities change.
|
||||||
|
* Add direct playback support for DTS Express and DTS:X
|
||||||
|
([#335](https://github.com/androidx/media/pull/335)).
|
||||||
|
* Video:
|
||||||
|
* Make `MediaCodecVideoRenderer` report a `VideoSize` with a width and
|
||||||
|
height of 0 when the renderer is disabled.
|
||||||
|
`Player.Listener.onVideoSizeChanged` is called accordingly when
|
||||||
|
`Player.getVideoSize()` changes. With this change, ExoPlayer's video
|
||||||
|
size with `MediaCodecVideoRenderer` has a width and height of 0 when
|
||||||
|
`Player.getCurrentTracks` does not support video, or the size of the
|
||||||
|
supported video track is not yet determined.
|
||||||
|
* DRM:
|
||||||
|
* Reduce the visibility of several internal-only methods on
|
||||||
|
`DefaultDrmSession` that aren't expected to be called from outside the
|
||||||
|
DRM package:
|
||||||
|
* `void onMediaDrmEvent(int)`
|
||||||
|
* `void provision()`
|
||||||
|
* `void onProvisionCompleted()`
|
||||||
|
* `onProvisionError(Exception, boolean)`
|
||||||
|
* Muxer:
|
||||||
|
* Add a new muxer library which can be used to create an MP4 container
|
||||||
|
file.
|
||||||
|
* IMA extension:
|
||||||
|
* Enable multi-period live DASH streams for DAI. Please note that the
|
||||||
|
current implementation does not yet support seeking in live streams
|
||||||
|
([#10912](https://github.com/google/ExoPlayer/issues/10912)).
|
||||||
|
* Fix a bug where a new ad group is inserted in live streams because the
|
||||||
|
calculated content position in consecutive timelines varies slightly.
|
||||||
|
* Session:
|
||||||
|
* Add helper method `MediaSession.getControllerForCurrentRequest` to
|
||||||
|
obtain information about the controller that is currently calling
|
||||||
|
a`Player` method.
|
||||||
|
* Add `androidx.media3.session.MediaButtonReceiver` to enable apps to
|
||||||
|
implement playback resumption with media button events sent by, for
|
||||||
|
example, a Bluetooth headset
|
||||||
|
([#167](https://github.com/androidx/media/issues/167)).
|
||||||
|
* Add default implementation to `MediaSession.Callback.onAddMediaItems` to
|
||||||
|
allow requested `MediaItems` to be passed onto `Player` if they have
|
||||||
|
`LocalConfiguration` (e.g. URI)
|
||||||
|
([#282](https://github.com/androidx/media/issues/282)).
|
||||||
|
* Add "seek to previous" and "seek to next" command buttons on compact
|
||||||
|
media notification view by default for Android 12 and below
|
||||||
|
([#410](https://github.com/androidx/media/issues/410)).
|
||||||
|
* Add default implementation to `MediaSession.Callback.onAddMediaItems` to
|
||||||
|
allow requested `MediaItems` to be passed onto `Player` if they have
|
||||||
|
`LocalConfiguration` (e.g. URI)
|
||||||
|
([#282](https://github.com/androidx/media/issues/282)).
|
||||||
|
* Add "seek to previous" and "seek to next" command buttons on compact
|
||||||
|
media notification view by default for Android 12 and below
|
||||||
|
([#410](https://github.com/androidx/media/issues/410)).
|
||||||
|
* UI:
|
||||||
|
* Add Util methods `shouldShowPlayButton` and
|
||||||
|
`handlePlayPauseButtonAction` to write custom UI elements with a
|
||||||
|
play/pause button.
|
||||||
|
* RTSP Extension:
|
||||||
|
* For MPEG4-LATM, use default profile-level-id value if absent in Describe
|
||||||
|
Response SDP message
|
||||||
|
([#302](https://github.com/androidx/media/issues/302)).
|
||||||
|
* Use base Uri for relative path resolution from the RTSP session if
|
||||||
|
present in DESCRIBE response header
|
||||||
|
([#11160](https://github.com/google/ExoPlayer/issues/11160)).
|
||||||
|
* DASH Extension:
|
||||||
|
* Remove the media time offset from `MediaLoadData.startTimeMs` and
|
||||||
|
`MediaLoadData.endTimeMs` for multi period DASH streams.
|
||||||
|
* Fix a bug where re-preparing a multi-period live Dash media source
|
||||||
|
produced a `IndexOutOfBoundsException`
|
||||||
|
([#10838](https://github.com/google/ExoPlayer/issues/10838)).
|
||||||
|
* HLS Extension:
|
||||||
|
* Add
|
||||||
|
`HlsMediaSource.Factory.setTimestampAdjusterInitializationTimeoutMs(long)`
|
||||||
|
to set a timeout for the loading thread to wait for the
|
||||||
|
`TimestampAdjuster` to initialize. If the initialization doesn't
|
||||||
|
complete before the timeout, a `PlaybackException` is thrown to avoid
|
||||||
|
the playback endless stalling. The timeout is set to zero by default
|
||||||
|
([#323](https://github.com/androidx/media/issues//323)).
|
||||||
|
* Test Utilities:
|
||||||
|
* Check for URI scheme case insensitivity in `DataSourceContractTest`.
|
||||||
|
* Remove deprecated symbols:
|
||||||
|
* Remove `DefaultAudioSink` constructors, use `DefaultAudioSink.Builder`
|
||||||
|
instead.
|
||||||
|
* Remove `HlsMasterPlaylist`, use `HlsMultivariantPlaylist` instead.
|
||||||
|
* Remove `Player.stop(boolean)`. Use `Player.stop()` and
|
||||||
|
`Player.clearMediaItems()` (if `reset` is `true`) instead.
|
||||||
|
* Remove two deprecated `SimpleCache` constructors, use a non-deprecated
|
||||||
|
constructor that takes a `DatabaseProvider` instead for better
|
||||||
|
performance.
|
||||||
|
* Remove `DefaultBandwidthMeter` constructor, use
|
||||||
|
`DefaultBandwidthMeter.Builder` instead.
|
||||||
|
* Remove `DefaultDrmSessionManager` constructors, use
|
||||||
|
`DefaultDrmSessionManager.Builder` instead.
|
||||||
|
* Remove two deprecated `HttpDataSource.InvalidResponseCodeException`
|
||||||
|
constructors, use a non-deprecated constructor that accepts additional
|
||||||
|
fields(`cause`, `responseBody`) to enhance error logging.
|
||||||
|
* Remove `DownloadHelper.forProgressive`, `DownloadHelper.forHls`,
|
||||||
|
`DownloadHelper.forDash`, and `DownloadHelper.forSmoothStreaming`, use
|
||||||
|
`DownloadHelper.forMediaItem` instead.
|
||||||
|
* Remove deprecated `DownloadService` constructor, use a non deprecated
|
||||||
|
constructor that includes the option to provide a
|
||||||
|
`channelDescriptionResourceId` parameter.
|
||||||
|
* Remove deprecated String constants for Charsets (`ASCII_NAME`,
|
||||||
|
`UTF8_NAME`, `ISO88591_NAME`, `UTF16_NAME` and `UTF16LE_NAME`), use
|
||||||
|
Kotlin Charsets from the `kotlin.text` package, the
|
||||||
|
`java.nio.charset.StandardCharsets` or the
|
||||||
|
`com.google.common.base.Charsets` instead.
|
||||||
|
* Remove deprecated `WorkManagerScheduler` constructor, use a non
|
||||||
|
deprecated constructor that includes the option to provide a `Context`
|
||||||
|
parameter instead.
|
||||||
|
* Remove the deprecated methods `createVideoSampleFormat`,
|
||||||
|
`createAudioSampleFormat`, `createContainerFormat`, and
|
||||||
|
`createSampleFormat`, which were used to instantiate the `Format` class.
|
||||||
|
Instead use `Format.Builder` for creating instances of `Format`.
|
||||||
|
* Remove the deprecated methods `copyWithMaxInputSize`,
|
||||||
|
`copyWithSubsampleOffsetUs`, `copyWithLabel`,
|
||||||
|
`copyWithManifestFormatInfo`, `copyWithGaplessInfo`,
|
||||||
|
`copyWithFrameRate`, `copyWithDrmInitData`, `copyWithMetadata`,
|
||||||
|
`copyWithBitrate` and `copyWithVideoSize`, use `Format.buildUpon()` and
|
||||||
|
setter methods instead.
|
||||||
|
* Remove deprecated `ExoPlayer.retry()`, use `prepare()` instead.
|
||||||
|
* Remove deprecated zero-arg `DefaultTrackSelector` constructor, use
|
||||||
|
`DefaultTrackSelector(Context)` instead.
|
||||||
|
* Remove deprecated `OfflineLicenseHelper` constructor, use
|
||||||
|
`OfflineLicenseHelper(DefaultDrmSessionManager,
|
||||||
|
DrmSessionEventListener.EventDispatcher)` instead.
|
||||||
|
* Remove deprecated `DownloadManager` constructor, use the constructor
|
||||||
|
that takes an `Executor` instead.
|
||||||
|
* Remove deprecated `Cue` constructors, use `Cue.Builder` instead.
|
||||||
|
* Remove deprecated `OfflineLicenseHelper` constructor, use
|
||||||
|
`OfflineLicenseHelper(DefaultDrmSessionManager,
|
||||||
|
DrmSessionEventListener.EventDispatcher)` instead.
|
||||||
|
* Remove four deprecated `AnalyticsListener` methods:
|
||||||
|
* `onDecoderEnabled`, use `onAudioEnabled` and/or `onVideoEnabled`
|
||||||
|
instead.
|
||||||
|
* `onDecoderInitialized`, use `onAudioDecoderInitialized` and/or
|
||||||
|
`onVideoDecoderInitialized` instead.
|
||||||
|
* `onDecoderInputFormatChanged`, use `onAudioInputFormatChanged`
|
||||||
|
and/or `onVideoInputFormatChanged` instead.
|
||||||
|
* `onDecoderDisabled`, use `onAudioDisabled` and/or `onVideoDisabled`
|
||||||
|
instead.
|
||||||
|
* Remove the deprecated `Player.Listener.onSeekProcessed` and
|
||||||
|
`AnalyticsListener.onSeekProcessed`, use `onPositionDiscontinuity` with
|
||||||
|
`DISCONTINUITY_REASON_SEEK` instead.
|
||||||
|
* Remove `ExoPlayer.setHandleWakeLock(boolean)`, use `setWakeMode(int)`
|
||||||
|
instead.
|
||||||
|
* Remove deprecated
|
||||||
|
`DefaultLoadControl.Builder.createDefaultLoadControl()`, use `build()`
|
||||||
|
instead.
|
||||||
|
* Remove deprecated `MediaItem.PlaybackProperties`, use
|
||||||
|
`MediaItem.LocalConfiguration` instead. Deprecated field
|
||||||
|
`MediaItem.playbackProperties` is now of type
|
||||||
|
`MediaItem.LocalConfiguration`.
|
||||||
|
|
||||||
|
### 1.1.0-rc01 (2023-06-21)
|
||||||
|
|
||||||
|
Use the 1.1.0 [stable version](#110-2023-07-05).
|
||||||
|
|
||||||
|
### 1.1.0-beta01 (2023-06-07)
|
||||||
|
|
||||||
|
Use the 1.1.0 [stable version](#110-2023-07-05).
|
||||||
|
|
||||||
|
### 1.1.0-alpha01 (2023-05-10)
|
||||||
|
|
||||||
|
Use the 1.1.0 [stable version](#110-2023-07-05).
|
||||||
|
|
||||||
|
## 1.0
|
||||||
|
|
||||||
### 1.0.2 (2023-05-18)
|
### 1.0.2 (2023-05-18)
|
||||||
|
|
||||||
This release corresponds to the
|
This release corresponds to the
|
||||||
|
407
api.txt
407
api.txt
@ -127,6 +127,11 @@ package androidx.media3.common {
|
|||||||
field public static final int USAGE_VOICE_COMMUNICATION = 2; // 0x2
|
field public static final int USAGE_VOICE_COMMUNICATION = 2; // 0x2
|
||||||
field public static final int USAGE_VOICE_COMMUNICATION_SIGNALLING = 3; // 0x3
|
field public static final int USAGE_VOICE_COMMUNICATION_SIGNALLING = 3; // 0x3
|
||||||
field public static final java.util.UUID UUID_NIL;
|
field public static final java.util.UUID UUID_NIL;
|
||||||
|
field public static final int VOLUME_FLAG_ALLOW_RINGER_MODES = 2; // 0x2
|
||||||
|
field public static final int VOLUME_FLAG_PLAY_SOUND = 4; // 0x4
|
||||||
|
field public static final int VOLUME_FLAG_REMOVE_SOUND_AND_VIBRATE = 8; // 0x8
|
||||||
|
field public static final int VOLUME_FLAG_SHOW_UI = 1; // 0x1
|
||||||
|
field public static final int VOLUME_FLAG_VIBRATE = 16; // 0x10
|
||||||
field public static final int WAKE_MODE_LOCAL = 1; // 0x1
|
field public static final int WAKE_MODE_LOCAL = 1; // 0x1
|
||||||
field public static final int WAKE_MODE_NETWORK = 2; // 0x2
|
field public static final int WAKE_MODE_NETWORK = 2; // 0x2
|
||||||
field public static final int WAKE_MODE_NONE = 0; // 0x0
|
field public static final int WAKE_MODE_NONE = 0; // 0x0
|
||||||
@ -163,6 +168,9 @@ package androidx.media3.common {
|
|||||||
@IntDef(open=true, value={androidx.media3.common.C.TRACK_TYPE_UNKNOWN, androidx.media3.common.C.TRACK_TYPE_DEFAULT, androidx.media3.common.C.TRACK_TYPE_AUDIO, androidx.media3.common.C.TRACK_TYPE_VIDEO, androidx.media3.common.C.TRACK_TYPE_TEXT, androidx.media3.common.C.TRACK_TYPE_IMAGE, androidx.media3.common.C.TRACK_TYPE_METADATA, androidx.media3.common.C.TRACK_TYPE_CAMERA_MOTION, androidx.media3.common.C.TRACK_TYPE_NONE}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE_USE) public static @interface C.TrackType {
|
@IntDef(open=true, value={androidx.media3.common.C.TRACK_TYPE_UNKNOWN, androidx.media3.common.C.TRACK_TYPE_DEFAULT, androidx.media3.common.C.TRACK_TYPE_AUDIO, androidx.media3.common.C.TRACK_TYPE_VIDEO, androidx.media3.common.C.TRACK_TYPE_TEXT, androidx.media3.common.C.TRACK_TYPE_IMAGE, androidx.media3.common.C.TRACK_TYPE_METADATA, androidx.media3.common.C.TRACK_TYPE_CAMERA_MOTION, androidx.media3.common.C.TRACK_TYPE_NONE}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE_USE) public static @interface C.TrackType {
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@IntDef(flag=true, value={androidx.media3.common.C.VOLUME_FLAG_SHOW_UI, androidx.media3.common.C.VOLUME_FLAG_ALLOW_RINGER_MODES, androidx.media3.common.C.VOLUME_FLAG_PLAY_SOUND, androidx.media3.common.C.VOLUME_FLAG_REMOVE_SOUND_AND_VIBRATE, androidx.media3.common.C.VOLUME_FLAG_VIBRATE}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.TYPE_USE}) public static @interface C.VolumeFlags {
|
||||||
|
}
|
||||||
|
|
||||||
@IntDef({androidx.media3.common.C.WAKE_MODE_NONE, androidx.media3.common.C.WAKE_MODE_LOCAL, androidx.media3.common.C.WAKE_MODE_NETWORK}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface C.WakeMode {
|
@IntDef({androidx.media3.common.C.WAKE_MODE_NONE, androidx.media3.common.C.WAKE_MODE_LOCAL, androidx.media3.common.C.WAKE_MODE_NETWORK}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface C.WakeMode {
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -170,9 +178,18 @@ package androidx.media3.common {
|
|||||||
field public static final int PLAYBACK_TYPE_LOCAL = 0; // 0x0
|
field public static final int PLAYBACK_TYPE_LOCAL = 0; // 0x0
|
||||||
field public static final int PLAYBACK_TYPE_REMOTE = 1; // 0x1
|
field public static final int PLAYBACK_TYPE_REMOTE = 1; // 0x1
|
||||||
field public static final androidx.media3.common.DeviceInfo UNKNOWN;
|
field public static final androidx.media3.common.DeviceInfo UNKNOWN;
|
||||||
field public final int maxVolume;
|
field @IntRange(from=0) public final int maxVolume;
|
||||||
field public final int minVolume;
|
field @IntRange(from=0) public final int minVolume;
|
||||||
field @androidx.media3.common.DeviceInfo.PlaybackType public final int playbackType;
|
field @androidx.media3.common.DeviceInfo.PlaybackType public final int playbackType;
|
||||||
|
field @Nullable public final String routingControllerId;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static final class DeviceInfo.Builder {
|
||||||
|
ctor public DeviceInfo.Builder(@androidx.media3.common.DeviceInfo.PlaybackType int);
|
||||||
|
method public androidx.media3.common.DeviceInfo build();
|
||||||
|
method public androidx.media3.common.DeviceInfo.Builder setMaxVolume(@IntRange(from=0) int);
|
||||||
|
method public androidx.media3.common.DeviceInfo.Builder setMinVolume(@IntRange(from=0) int);
|
||||||
|
method public androidx.media3.common.DeviceInfo.Builder setRoutingControllerId(@Nullable String);
|
||||||
}
|
}
|
||||||
|
|
||||||
@IntDef({androidx.media3.common.DeviceInfo.PLAYBACK_TYPE_LOCAL, androidx.media3.common.DeviceInfo.PLAYBACK_TYPE_REMOTE}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE_USE) public static @interface DeviceInfo.PlaybackType {
|
@IntDef({androidx.media3.common.DeviceInfo.PLAYBACK_TYPE_LOCAL, androidx.media3.common.DeviceInfo.PLAYBACK_TYPE_REMOTE}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE_USE) public static @interface DeviceInfo.PlaybackType {
|
||||||
@ -318,7 +335,7 @@ package androidx.media3.common {
|
|||||||
method public androidx.media3.common.MediaItem.LiveConfiguration.Builder setTargetOffsetMs(long);
|
method public androidx.media3.common.MediaItem.LiveConfiguration.Builder setTargetOffsetMs(long);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static class MediaItem.LocalConfiguration {
|
public static final class MediaItem.LocalConfiguration {
|
||||||
field @Nullable public final androidx.media3.common.MediaItem.AdsConfiguration adsConfiguration;
|
field @Nullable public final androidx.media3.common.MediaItem.AdsConfiguration adsConfiguration;
|
||||||
field @Nullable public final androidx.media3.common.MediaItem.DrmConfiguration drmConfiguration;
|
field @Nullable public final androidx.media3.common.MediaItem.DrmConfiguration drmConfiguration;
|
||||||
field @Nullable public final String mimeType;
|
field @Nullable public final String mimeType;
|
||||||
@ -369,14 +386,50 @@ package androidx.media3.common {
|
|||||||
public final class MediaMetadata {
|
public final class MediaMetadata {
|
||||||
method public androidx.media3.common.MediaMetadata.Builder buildUpon();
|
method public androidx.media3.common.MediaMetadata.Builder buildUpon();
|
||||||
field public static final androidx.media3.common.MediaMetadata EMPTY;
|
field public static final androidx.media3.common.MediaMetadata EMPTY;
|
||||||
field public static final int FOLDER_TYPE_ALBUMS = 2; // 0x2
|
field @Deprecated public static final int FOLDER_TYPE_ALBUMS = 2; // 0x2
|
||||||
field public static final int FOLDER_TYPE_ARTISTS = 3; // 0x3
|
field @Deprecated public static final int FOLDER_TYPE_ARTISTS = 3; // 0x3
|
||||||
field public static final int FOLDER_TYPE_GENRES = 4; // 0x4
|
field @Deprecated public static final int FOLDER_TYPE_GENRES = 4; // 0x4
|
||||||
field public static final int FOLDER_TYPE_MIXED = 0; // 0x0
|
field @Deprecated public static final int FOLDER_TYPE_MIXED = 0; // 0x0
|
||||||
field public static final int FOLDER_TYPE_NONE = -1; // 0xffffffff
|
field @Deprecated public static final int FOLDER_TYPE_NONE = -1; // 0xffffffff
|
||||||
field public static final int FOLDER_TYPE_PLAYLISTS = 5; // 0x5
|
field @Deprecated public static final int FOLDER_TYPE_PLAYLISTS = 5; // 0x5
|
||||||
field public static final int FOLDER_TYPE_TITLES = 1; // 0x1
|
field @Deprecated public static final int FOLDER_TYPE_TITLES = 1; // 0x1
|
||||||
field public static final int FOLDER_TYPE_YEARS = 6; // 0x6
|
field @Deprecated public static final int FOLDER_TYPE_YEARS = 6; // 0x6
|
||||||
|
field public static final int MEDIA_TYPE_ALBUM = 10; // 0xa
|
||||||
|
field public static final int MEDIA_TYPE_ARTIST = 11; // 0xb
|
||||||
|
field public static final int MEDIA_TYPE_AUDIO_BOOK = 15; // 0xf
|
||||||
|
field public static final int MEDIA_TYPE_AUDIO_BOOK_CHAPTER = 2; // 0x2
|
||||||
|
field public static final int MEDIA_TYPE_FOLDER_ALBUMS = 21; // 0x15
|
||||||
|
field public static final int MEDIA_TYPE_FOLDER_ARTISTS = 22; // 0x16
|
||||||
|
field public static final int MEDIA_TYPE_FOLDER_AUDIO_BOOKS = 26; // 0x1a
|
||||||
|
field public static final int MEDIA_TYPE_FOLDER_GENRES = 23; // 0x17
|
||||||
|
field public static final int MEDIA_TYPE_FOLDER_MIXED = 20; // 0x14
|
||||||
|
field public static final int MEDIA_TYPE_FOLDER_MOVIES = 35; // 0x23
|
||||||
|
field public static final int MEDIA_TYPE_FOLDER_NEWS = 32; // 0x20
|
||||||
|
field public static final int MEDIA_TYPE_FOLDER_PLAYLISTS = 24; // 0x18
|
||||||
|
field public static final int MEDIA_TYPE_FOLDER_PODCASTS = 27; // 0x1b
|
||||||
|
field public static final int MEDIA_TYPE_FOLDER_RADIO_STATIONS = 31; // 0x1f
|
||||||
|
field public static final int MEDIA_TYPE_FOLDER_TRAILERS = 34; // 0x22
|
||||||
|
field public static final int MEDIA_TYPE_FOLDER_TV_CHANNELS = 28; // 0x1c
|
||||||
|
field public static final int MEDIA_TYPE_FOLDER_TV_SERIES = 29; // 0x1d
|
||||||
|
field public static final int MEDIA_TYPE_FOLDER_TV_SHOWS = 30; // 0x1e
|
||||||
|
field public static final int MEDIA_TYPE_FOLDER_VIDEOS = 33; // 0x21
|
||||||
|
field public static final int MEDIA_TYPE_FOLDER_YEARS = 25; // 0x19
|
||||||
|
field public static final int MEDIA_TYPE_GENRE = 12; // 0xc
|
||||||
|
field public static final int MEDIA_TYPE_MIXED = 0; // 0x0
|
||||||
|
field public static final int MEDIA_TYPE_MOVIE = 8; // 0x8
|
||||||
|
field public static final int MEDIA_TYPE_MUSIC = 1; // 0x1
|
||||||
|
field public static final int MEDIA_TYPE_NEWS = 5; // 0x5
|
||||||
|
field public static final int MEDIA_TYPE_PLAYLIST = 13; // 0xd
|
||||||
|
field public static final int MEDIA_TYPE_PODCAST = 16; // 0x10
|
||||||
|
field public static final int MEDIA_TYPE_PODCAST_EPISODE = 3; // 0x3
|
||||||
|
field public static final int MEDIA_TYPE_RADIO_STATION = 4; // 0x4
|
||||||
|
field public static final int MEDIA_TYPE_TRAILER = 7; // 0x7
|
||||||
|
field public static final int MEDIA_TYPE_TV_CHANNEL = 17; // 0x11
|
||||||
|
field public static final int MEDIA_TYPE_TV_SEASON = 19; // 0x13
|
||||||
|
field public static final int MEDIA_TYPE_TV_SERIES = 18; // 0x12
|
||||||
|
field public static final int MEDIA_TYPE_TV_SHOW = 9; // 0x9
|
||||||
|
field public static final int MEDIA_TYPE_VIDEO = 6; // 0x6
|
||||||
|
field public static final int MEDIA_TYPE_YEAR = 14; // 0xe
|
||||||
field public static final int PICTURE_TYPE_ARTIST_PERFORMER = 8; // 0x8
|
field public static final int PICTURE_TYPE_ARTIST_PERFORMER = 8; // 0x8
|
||||||
field public static final int PICTURE_TYPE_A_BRIGHT_COLORED_FISH = 17; // 0x11
|
field public static final int PICTURE_TYPE_A_BRIGHT_COLORED_FISH = 17; // 0x11
|
||||||
field public static final int PICTURE_TYPE_BACK_COVER = 4; // 0x4
|
field public static final int PICTURE_TYPE_BACK_COVER = 4; // 0x4
|
||||||
@ -411,9 +464,11 @@ package androidx.media3.common {
|
|||||||
field @Nullable public final Integer discNumber;
|
field @Nullable public final Integer discNumber;
|
||||||
field @Nullable public final CharSequence displayTitle;
|
field @Nullable public final CharSequence displayTitle;
|
||||||
field @Nullable public final android.os.Bundle extras;
|
field @Nullable public final android.os.Bundle extras;
|
||||||
field @Nullable @androidx.media3.common.MediaMetadata.FolderType public final Integer folderType;
|
field @Deprecated @Nullable @androidx.media3.common.MediaMetadata.FolderType public final Integer folderType;
|
||||||
field @Nullable public final CharSequence genre;
|
field @Nullable public final CharSequence genre;
|
||||||
|
field @Nullable public final Boolean isBrowsable;
|
||||||
field @Nullable public final Boolean isPlayable;
|
field @Nullable public final Boolean isPlayable;
|
||||||
|
field @Nullable @androidx.media3.common.MediaMetadata.MediaType public final Integer mediaType;
|
||||||
field @Nullable public final androidx.media3.common.Rating overallRating;
|
field @Nullable public final androidx.media3.common.Rating overallRating;
|
||||||
field @Nullable public final Integer recordingDay;
|
field @Nullable public final Integer recordingDay;
|
||||||
field @Nullable public final Integer recordingMonth;
|
field @Nullable public final Integer recordingMonth;
|
||||||
@ -447,9 +502,11 @@ package androidx.media3.common {
|
|||||||
method public androidx.media3.common.MediaMetadata.Builder setDiscNumber(@Nullable Integer);
|
method public androidx.media3.common.MediaMetadata.Builder setDiscNumber(@Nullable Integer);
|
||||||
method public androidx.media3.common.MediaMetadata.Builder setDisplayTitle(@Nullable CharSequence);
|
method public androidx.media3.common.MediaMetadata.Builder setDisplayTitle(@Nullable CharSequence);
|
||||||
method public androidx.media3.common.MediaMetadata.Builder setExtras(@Nullable android.os.Bundle);
|
method public androidx.media3.common.MediaMetadata.Builder setExtras(@Nullable android.os.Bundle);
|
||||||
method public androidx.media3.common.MediaMetadata.Builder setFolderType(@Nullable @androidx.media3.common.MediaMetadata.FolderType Integer);
|
method @Deprecated public androidx.media3.common.MediaMetadata.Builder setFolderType(@Nullable @androidx.media3.common.MediaMetadata.FolderType Integer);
|
||||||
method public androidx.media3.common.MediaMetadata.Builder setGenre(@Nullable CharSequence);
|
method public androidx.media3.common.MediaMetadata.Builder setGenre(@Nullable CharSequence);
|
||||||
|
method public androidx.media3.common.MediaMetadata.Builder setIsBrowsable(@Nullable Boolean);
|
||||||
method public androidx.media3.common.MediaMetadata.Builder setIsPlayable(@Nullable Boolean);
|
method public androidx.media3.common.MediaMetadata.Builder setIsPlayable(@Nullable Boolean);
|
||||||
|
method public androidx.media3.common.MediaMetadata.Builder setMediaType(@Nullable @androidx.media3.common.MediaMetadata.MediaType Integer);
|
||||||
method public androidx.media3.common.MediaMetadata.Builder setOverallRating(@Nullable androidx.media3.common.Rating);
|
method public androidx.media3.common.MediaMetadata.Builder setOverallRating(@Nullable androidx.media3.common.Rating);
|
||||||
method public androidx.media3.common.MediaMetadata.Builder setRecordingDay(@IntRange(from=1, to=31) @Nullable Integer);
|
method public androidx.media3.common.MediaMetadata.Builder setRecordingDay(@IntRange(from=1, to=31) @Nullable Integer);
|
||||||
method public androidx.media3.common.MediaMetadata.Builder setRecordingMonth(@IntRange(from=1, to=12) @Nullable Integer);
|
method public androidx.media3.common.MediaMetadata.Builder setRecordingMonth(@IntRange(from=1, to=12) @Nullable Integer);
|
||||||
@ -467,7 +524,10 @@ package androidx.media3.common {
|
|||||||
method public androidx.media3.common.MediaMetadata.Builder setWriter(@Nullable CharSequence);
|
method public androidx.media3.common.MediaMetadata.Builder setWriter(@Nullable CharSequence);
|
||||||
}
|
}
|
||||||
|
|
||||||
@IntDef({androidx.media3.common.MediaMetadata.FOLDER_TYPE_NONE, androidx.media3.common.MediaMetadata.FOLDER_TYPE_MIXED, androidx.media3.common.MediaMetadata.FOLDER_TYPE_TITLES, androidx.media3.common.MediaMetadata.FOLDER_TYPE_ALBUMS, androidx.media3.common.MediaMetadata.FOLDER_TYPE_ARTISTS, androidx.media3.common.MediaMetadata.FOLDER_TYPE_GENRES, androidx.media3.common.MediaMetadata.FOLDER_TYPE_PLAYLISTS, androidx.media3.common.MediaMetadata.FOLDER_TYPE_YEARS}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface MediaMetadata.FolderType {
|
@Deprecated @IntDef({androidx.media3.common.MediaMetadata.FOLDER_TYPE_NONE, androidx.media3.common.MediaMetadata.FOLDER_TYPE_MIXED, androidx.media3.common.MediaMetadata.FOLDER_TYPE_TITLES, androidx.media3.common.MediaMetadata.FOLDER_TYPE_ALBUMS, androidx.media3.common.MediaMetadata.FOLDER_TYPE_ARTISTS, androidx.media3.common.MediaMetadata.FOLDER_TYPE_GENRES, androidx.media3.common.MediaMetadata.FOLDER_TYPE_PLAYLISTS, androidx.media3.common.MediaMetadata.FOLDER_TYPE_YEARS}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface MediaMetadata.FolderType {
|
||||||
|
}
|
||||||
|
|
||||||
|
@IntDef({androidx.media3.common.MediaMetadata.MEDIA_TYPE_MIXED, androidx.media3.common.MediaMetadata.MEDIA_TYPE_MUSIC, androidx.media3.common.MediaMetadata.MEDIA_TYPE_AUDIO_BOOK_CHAPTER, androidx.media3.common.MediaMetadata.MEDIA_TYPE_PODCAST_EPISODE, androidx.media3.common.MediaMetadata.MEDIA_TYPE_RADIO_STATION, androidx.media3.common.MediaMetadata.MEDIA_TYPE_NEWS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_VIDEO, androidx.media3.common.MediaMetadata.MEDIA_TYPE_TRAILER, androidx.media3.common.MediaMetadata.MEDIA_TYPE_MOVIE, androidx.media3.common.MediaMetadata.MEDIA_TYPE_TV_SHOW, androidx.media3.common.MediaMetadata.MEDIA_TYPE_ALBUM, androidx.media3.common.MediaMetadata.MEDIA_TYPE_ARTIST, androidx.media3.common.MediaMetadata.MEDIA_TYPE_GENRE, androidx.media3.common.MediaMetadata.MEDIA_TYPE_PLAYLIST, androidx.media3.common.MediaMetadata.MEDIA_TYPE_YEAR, androidx.media3.common.MediaMetadata.MEDIA_TYPE_AUDIO_BOOK, androidx.media3.common.MediaMetadata.MEDIA_TYPE_PODCAST, androidx.media3.common.MediaMetadata.MEDIA_TYPE_TV_CHANNEL, androidx.media3.common.MediaMetadata.MEDIA_TYPE_TV_SERIES, androidx.media3.common.MediaMetadata.MEDIA_TYPE_TV_SEASON, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_MIXED, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_ALBUMS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_ARTISTS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_GENRES, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_PLAYLISTS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_YEARS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_AUDIO_BOOKS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_PODCASTS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_TV_CHANNELS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_TV_SERIES, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_TV_SHOWS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_RADIO_STATIONS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_NEWS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_VIDEOS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_TRAILERS, androidx.media3.common.MediaMetadata.MEDIA_TYPE_FOLDER_MOVIES}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE_USE) public static @interface MediaMetadata.MediaType {
|
||||||
}
|
}
|
||||||
|
|
||||||
@IntDef({androidx.media3.common.MediaMetadata.PICTURE_TYPE_OTHER, androidx.media3.common.MediaMetadata.PICTURE_TYPE_FILE_ICON, androidx.media3.common.MediaMetadata.PICTURE_TYPE_FILE_ICON_OTHER, androidx.media3.common.MediaMetadata.PICTURE_TYPE_FRONT_COVER, androidx.media3.common.MediaMetadata.PICTURE_TYPE_BACK_COVER, androidx.media3.common.MediaMetadata.PICTURE_TYPE_LEAFLET_PAGE, androidx.media3.common.MediaMetadata.PICTURE_TYPE_MEDIA, androidx.media3.common.MediaMetadata.PICTURE_TYPE_LEAD_ARTIST_PERFORMER, androidx.media3.common.MediaMetadata.PICTURE_TYPE_ARTIST_PERFORMER, androidx.media3.common.MediaMetadata.PICTURE_TYPE_CONDUCTOR, androidx.media3.common.MediaMetadata.PICTURE_TYPE_BAND_ORCHESTRA, androidx.media3.common.MediaMetadata.PICTURE_TYPE_COMPOSER, androidx.media3.common.MediaMetadata.PICTURE_TYPE_LYRICIST, androidx.media3.common.MediaMetadata.PICTURE_TYPE_RECORDING_LOCATION, androidx.media3.common.MediaMetadata.PICTURE_TYPE_DURING_RECORDING, androidx.media3.common.MediaMetadata.PICTURE_TYPE_DURING_PERFORMANCE, androidx.media3.common.MediaMetadata.PICTURE_TYPE_MOVIE_VIDEO_SCREEN_CAPTURE, androidx.media3.common.MediaMetadata.PICTURE_TYPE_A_BRIGHT_COLORED_FISH, androidx.media3.common.MediaMetadata.PICTURE_TYPE_ILLUSTRATION, androidx.media3.common.MediaMetadata.PICTURE_TYPE_BAND_ARTIST_LOGO, androidx.media3.common.MediaMetadata.PICTURE_TYPE_PUBLISHER_STUDIO_LOGO}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface MediaMetadata.PictureType {
|
@IntDef({androidx.media3.common.MediaMetadata.PICTURE_TYPE_OTHER, androidx.media3.common.MediaMetadata.PICTURE_TYPE_FILE_ICON, androidx.media3.common.MediaMetadata.PICTURE_TYPE_FILE_ICON_OTHER, androidx.media3.common.MediaMetadata.PICTURE_TYPE_FRONT_COVER, androidx.media3.common.MediaMetadata.PICTURE_TYPE_BACK_COVER, androidx.media3.common.MediaMetadata.PICTURE_TYPE_LEAFLET_PAGE, androidx.media3.common.MediaMetadata.PICTURE_TYPE_MEDIA, androidx.media3.common.MediaMetadata.PICTURE_TYPE_LEAD_ARTIST_PERFORMER, androidx.media3.common.MediaMetadata.PICTURE_TYPE_ARTIST_PERFORMER, androidx.media3.common.MediaMetadata.PICTURE_TYPE_CONDUCTOR, androidx.media3.common.MediaMetadata.PICTURE_TYPE_BAND_ORCHESTRA, androidx.media3.common.MediaMetadata.PICTURE_TYPE_COMPOSER, androidx.media3.common.MediaMetadata.PICTURE_TYPE_LYRICIST, androidx.media3.common.MediaMetadata.PICTURE_TYPE_RECORDING_LOCATION, androidx.media3.common.MediaMetadata.PICTURE_TYPE_DURING_RECORDING, androidx.media3.common.MediaMetadata.PICTURE_TYPE_DURING_PERFORMANCE, androidx.media3.common.MediaMetadata.PICTURE_TYPE_MOVIE_VIDEO_SCREEN_CAPTURE, androidx.media3.common.MediaMetadata.PICTURE_TYPE_A_BRIGHT_COLORED_FISH, androidx.media3.common.MediaMetadata.PICTURE_TYPE_ILLUSTRATION, androidx.media3.common.MediaMetadata.PICTURE_TYPE_BAND_ARTIST_LOGO, androidx.media3.common.MediaMetadata.PICTURE_TYPE_PUBLISHER_STUDIO_LOGO}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface MediaMetadata.PictureType {
|
||||||
@ -486,7 +546,7 @@ package androidx.media3.common {
|
|||||||
field public static final String APPLICATION_MP4VTT = "application/x-mp4-vtt";
|
field public static final String APPLICATION_MP4VTT = "application/x-mp4-vtt";
|
||||||
field public static final String APPLICATION_MPD = "application/dash+xml";
|
field public static final String APPLICATION_MPD = "application/dash+xml";
|
||||||
field public static final String APPLICATION_PGS = "application/pgs";
|
field public static final String APPLICATION_PGS = "application/pgs";
|
||||||
field public static final String APPLICATION_RAWCC = "application/x-rawcc";
|
field @Deprecated public static final String APPLICATION_RAWCC = "application/x-rawcc";
|
||||||
field public static final String APPLICATION_RTSP = "application/x-rtsp";
|
field public static final String APPLICATION_RTSP = "application/x-rtsp";
|
||||||
field public static final String APPLICATION_SS = "application/vnd.ms-sstr+xml";
|
field public static final String APPLICATION_SS = "application/vnd.ms-sstr+xml";
|
||||||
field public static final String APPLICATION_SUBRIP = "application/x-subrip";
|
field public static final String APPLICATION_SUBRIP = "application/x-subrip";
|
||||||
@ -524,7 +584,11 @@ package androidx.media3.common {
|
|||||||
field public static final String AUDIO_VORBIS = "audio/vorbis";
|
field public static final String AUDIO_VORBIS = "audio/vorbis";
|
||||||
field public static final String AUDIO_WAV = "audio/wav";
|
field public static final String AUDIO_WAV = "audio/wav";
|
||||||
field public static final String AUDIO_WEBM = "audio/webm";
|
field public static final String AUDIO_WEBM = "audio/webm";
|
||||||
|
field public static final String IMAGE_HEIC = "image/heic";
|
||||||
|
field public static final String IMAGE_HEIF = "image/heif";
|
||||||
field public static final String IMAGE_JPEG = "image/jpeg";
|
field public static final String IMAGE_JPEG = "image/jpeg";
|
||||||
|
field public static final String IMAGE_PNG = "image/png";
|
||||||
|
field public static final String IMAGE_WEBP = "image/webp";
|
||||||
field public static final String TEXT_SSA = "text/x-ssa";
|
field public static final String TEXT_SSA = "text/x-ssa";
|
||||||
field public static final String TEXT_VTT = "text/vtt";
|
field public static final String TEXT_VTT = "text/vtt";
|
||||||
field public static final String VIDEO_AV1 = "video/av01";
|
field public static final String VIDEO_AV1 = "video/av01";
|
||||||
@ -602,7 +666,7 @@ package androidx.media3.common {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public final class PlaybackParameters {
|
public final class PlaybackParameters {
|
||||||
ctor public PlaybackParameters(float);
|
ctor public PlaybackParameters(@FloatRange(from=0, fromInclusive=false) float);
|
||||||
ctor public PlaybackParameters(@FloatRange(from=0, fromInclusive=false) float, @FloatRange(from=0, fromInclusive=false) float);
|
ctor public PlaybackParameters(@FloatRange(from=0, fromInclusive=false) float, @FloatRange(from=0, fromInclusive=false) float);
|
||||||
method @CheckResult public androidx.media3.common.PlaybackParameters withSpeed(@FloatRange(from=0, fromInclusive=false) float);
|
method @CheckResult public androidx.media3.common.PlaybackParameters withSpeed(@FloatRange(from=0, fromInclusive=false) float);
|
||||||
field public static final androidx.media3.common.PlaybackParameters DEFAULT;
|
field public static final androidx.media3.common.PlaybackParameters DEFAULT;
|
||||||
@ -623,7 +687,8 @@ package androidx.media3.common {
|
|||||||
method public void clearVideoSurfaceHolder(@Nullable android.view.SurfaceHolder);
|
method public void clearVideoSurfaceHolder(@Nullable android.view.SurfaceHolder);
|
||||||
method public void clearVideoSurfaceView(@Nullable android.view.SurfaceView);
|
method public void clearVideoSurfaceView(@Nullable android.view.SurfaceView);
|
||||||
method public void clearVideoTextureView(@Nullable android.view.TextureView);
|
method public void clearVideoTextureView(@Nullable android.view.TextureView);
|
||||||
method public void decreaseDeviceVolume();
|
method @Deprecated public void decreaseDeviceVolume();
|
||||||
|
method public void decreaseDeviceVolume(@androidx.media3.common.C.VolumeFlags int);
|
||||||
method public android.os.Looper getApplicationLooper();
|
method public android.os.Looper getApplicationLooper();
|
||||||
method public androidx.media3.common.AudioAttributes getAudioAttributes();
|
method public androidx.media3.common.AudioAttributes getAudioAttributes();
|
||||||
method public androidx.media3.common.Player.Commands getAvailableCommands();
|
method public androidx.media3.common.Player.Commands getAvailableCommands();
|
||||||
@ -667,7 +732,8 @@ package androidx.media3.common {
|
|||||||
method @FloatRange(from=0, to=1.0) public float getVolume();
|
method @FloatRange(from=0, to=1.0) public float getVolume();
|
||||||
method public boolean hasNextMediaItem();
|
method public boolean hasNextMediaItem();
|
||||||
method public boolean hasPreviousMediaItem();
|
method public boolean hasPreviousMediaItem();
|
||||||
method public void increaseDeviceVolume();
|
method @Deprecated public void increaseDeviceVolume();
|
||||||
|
method public void increaseDeviceVolume(@androidx.media3.common.C.VolumeFlags int);
|
||||||
method public boolean isCommandAvailable(@androidx.media3.common.Player.Command int);
|
method public boolean isCommandAvailable(@androidx.media3.common.Player.Command int);
|
||||||
method public boolean isCurrentMediaItemDynamic();
|
method public boolean isCurrentMediaItemDynamic();
|
||||||
method public boolean isCurrentMediaItemLive();
|
method public boolean isCurrentMediaItemLive();
|
||||||
@ -685,6 +751,8 @@ package androidx.media3.common {
|
|||||||
method public void removeListener(androidx.media3.common.Player.Listener);
|
method public void removeListener(androidx.media3.common.Player.Listener);
|
||||||
method public void removeMediaItem(int);
|
method public void removeMediaItem(int);
|
||||||
method public void removeMediaItems(int, int);
|
method public void removeMediaItems(int, int);
|
||||||
|
method public void replaceMediaItem(int, androidx.media3.common.MediaItem);
|
||||||
|
method public void replaceMediaItems(int, int, java.util.List<androidx.media3.common.MediaItem>);
|
||||||
method public void seekBack();
|
method public void seekBack();
|
||||||
method public void seekForward();
|
method public void seekForward();
|
||||||
method public void seekTo(long);
|
method public void seekTo(long);
|
||||||
@ -695,8 +763,10 @@ package androidx.media3.common {
|
|||||||
method public void seekToNextMediaItem();
|
method public void seekToNextMediaItem();
|
||||||
method public void seekToPrevious();
|
method public void seekToPrevious();
|
||||||
method public void seekToPreviousMediaItem();
|
method public void seekToPreviousMediaItem();
|
||||||
method public void setDeviceMuted(boolean);
|
method @Deprecated public void setDeviceMuted(boolean);
|
||||||
method public void setDeviceVolume(@IntRange(from=0) int);
|
method public void setDeviceMuted(boolean, @androidx.media3.common.C.VolumeFlags int);
|
||||||
|
method @Deprecated public void setDeviceVolume(@IntRange(from=0) int);
|
||||||
|
method public void setDeviceVolume(@IntRange(from=0) int, int);
|
||||||
method public void setMediaItem(androidx.media3.common.MediaItem);
|
method public void setMediaItem(androidx.media3.common.MediaItem);
|
||||||
method public void setMediaItem(androidx.media3.common.MediaItem, long);
|
method public void setMediaItem(androidx.media3.common.MediaItem, long);
|
||||||
method public void setMediaItem(androidx.media3.common.MediaItem, boolean);
|
method public void setMediaItem(androidx.media3.common.MediaItem, boolean);
|
||||||
@ -716,12 +786,14 @@ package androidx.media3.common {
|
|||||||
method public void setVideoTextureView(@Nullable android.view.TextureView);
|
method public void setVideoTextureView(@Nullable android.view.TextureView);
|
||||||
method public void setVolume(@FloatRange(from=0, to=1.0) float);
|
method public void setVolume(@FloatRange(from=0, to=1.0) float);
|
||||||
method public void stop();
|
method public void stop();
|
||||||
field public static final int COMMAND_ADJUST_DEVICE_VOLUME = 26; // 0x1a
|
field @Deprecated public static final int COMMAND_ADJUST_DEVICE_VOLUME = 26; // 0x1a
|
||||||
|
field public static final int COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS = 34; // 0x22
|
||||||
field public static final int COMMAND_CHANGE_MEDIA_ITEMS = 20; // 0x14
|
field public static final int COMMAND_CHANGE_MEDIA_ITEMS = 20; // 0x14
|
||||||
field public static final int COMMAND_GET_AUDIO_ATTRIBUTES = 21; // 0x15
|
field public static final int COMMAND_GET_AUDIO_ATTRIBUTES = 21; // 0x15
|
||||||
field public static final int COMMAND_GET_CURRENT_MEDIA_ITEM = 16; // 0x10
|
field public static final int COMMAND_GET_CURRENT_MEDIA_ITEM = 16; // 0x10
|
||||||
field public static final int COMMAND_GET_DEVICE_VOLUME = 23; // 0x17
|
field public static final int COMMAND_GET_DEVICE_VOLUME = 23; // 0x17
|
||||||
field public static final int COMMAND_GET_MEDIA_ITEMS_METADATA = 18; // 0x12
|
field @Deprecated public static final int COMMAND_GET_MEDIA_ITEMS_METADATA = 18; // 0x12
|
||||||
|
field public static final int COMMAND_GET_METADATA = 18; // 0x12
|
||||||
field public static final int COMMAND_GET_TEXT = 28; // 0x1c
|
field public static final int COMMAND_GET_TEXT = 28; // 0x1c
|
||||||
field public static final int COMMAND_GET_TIMELINE = 17; // 0x11
|
field public static final int COMMAND_GET_TIMELINE = 17; // 0x11
|
||||||
field public static final int COMMAND_GET_TRACKS = 30; // 0x1e
|
field public static final int COMMAND_GET_TRACKS = 30; // 0x1e
|
||||||
@ -729,6 +801,7 @@ package androidx.media3.common {
|
|||||||
field public static final int COMMAND_INVALID = -1; // 0xffffffff
|
field public static final int COMMAND_INVALID = -1; // 0xffffffff
|
||||||
field public static final int COMMAND_PLAY_PAUSE = 1; // 0x1
|
field public static final int COMMAND_PLAY_PAUSE = 1; // 0x1
|
||||||
field public static final int COMMAND_PREPARE = 2; // 0x2
|
field public static final int COMMAND_PREPARE = 2; // 0x2
|
||||||
|
field public static final int COMMAND_RELEASE = 32; // 0x20
|
||||||
field public static final int COMMAND_SEEK_BACK = 11; // 0xb
|
field public static final int COMMAND_SEEK_BACK = 11; // 0xb
|
||||||
field public static final int COMMAND_SEEK_FORWARD = 12; // 0xc
|
field public static final int COMMAND_SEEK_FORWARD = 12; // 0xc
|
||||||
field public static final int COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM = 5; // 0x5
|
field public static final int COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM = 5; // 0x5
|
||||||
@ -738,9 +811,11 @@ package androidx.media3.common {
|
|||||||
field public static final int COMMAND_SEEK_TO_NEXT_MEDIA_ITEM = 8; // 0x8
|
field public static final int COMMAND_SEEK_TO_NEXT_MEDIA_ITEM = 8; // 0x8
|
||||||
field public static final int COMMAND_SEEK_TO_PREVIOUS = 7; // 0x7
|
field public static final int COMMAND_SEEK_TO_PREVIOUS = 7; // 0x7
|
||||||
field public static final int COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM = 6; // 0x6
|
field public static final int COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM = 6; // 0x6
|
||||||
field public static final int COMMAND_SET_DEVICE_VOLUME = 25; // 0x19
|
field @Deprecated public static final int COMMAND_SET_DEVICE_VOLUME = 25; // 0x19
|
||||||
|
field public static final int COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS = 33; // 0x21
|
||||||
field public static final int COMMAND_SET_MEDIA_ITEM = 31; // 0x1f
|
field public static final int COMMAND_SET_MEDIA_ITEM = 31; // 0x1f
|
||||||
field public static final int COMMAND_SET_MEDIA_ITEMS_METADATA = 19; // 0x13
|
field @Deprecated public static final int COMMAND_SET_MEDIA_ITEMS_METADATA = 19; // 0x13
|
||||||
|
field public static final int COMMAND_SET_PLAYLIST_METADATA = 19; // 0x13
|
||||||
field public static final int COMMAND_SET_REPEAT_MODE = 15; // 0xf
|
field public static final int COMMAND_SET_REPEAT_MODE = 15; // 0xf
|
||||||
field public static final int COMMAND_SET_SHUFFLE_MODE = 14; // 0xe
|
field public static final int COMMAND_SET_SHUFFLE_MODE = 14; // 0xe
|
||||||
field public static final int COMMAND_SET_SPEED_AND_PITCH = 13; // 0xd
|
field public static final int COMMAND_SET_SPEED_AND_PITCH = 13; // 0xd
|
||||||
@ -791,10 +866,12 @@ package androidx.media3.common {
|
|||||||
field public static final int MEDIA_ITEM_TRANSITION_REASON_SEEK = 2; // 0x2
|
field public static final int MEDIA_ITEM_TRANSITION_REASON_SEEK = 2; // 0x2
|
||||||
field public static final int PLAYBACK_SUPPRESSION_REASON_NONE = 0; // 0x0
|
field public static final int PLAYBACK_SUPPRESSION_REASON_NONE = 0; // 0x0
|
||||||
field public static final int PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS = 1; // 0x1
|
field public static final int PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS = 1; // 0x1
|
||||||
|
field public static final int PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_ROUTE = 2; // 0x2
|
||||||
field public static final int PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY = 3; // 0x3
|
field public static final int PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY = 3; // 0x3
|
||||||
field public static final int PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS = 2; // 0x2
|
field public static final int PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS = 2; // 0x2
|
||||||
field public static final int PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM = 5; // 0x5
|
field public static final int PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM = 5; // 0x5
|
||||||
field public static final int PLAY_WHEN_READY_CHANGE_REASON_REMOTE = 4; // 0x4
|
field public static final int PLAY_WHEN_READY_CHANGE_REASON_REMOTE = 4; // 0x4
|
||||||
|
field public static final int PLAY_WHEN_READY_CHANGE_REASON_SUPPRESSED_TOO_LONG = 6; // 0x6
|
||||||
field public static final int PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST = 1; // 0x1
|
field public static final int PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST = 1; // 0x1
|
||||||
field public static final int REPEAT_MODE_ALL = 2; // 0x2
|
field public static final int REPEAT_MODE_ALL = 2; // 0x2
|
||||||
field public static final int REPEAT_MODE_OFF = 0; // 0x0
|
field public static final int REPEAT_MODE_OFF = 0; // 0x0
|
||||||
@ -807,7 +884,7 @@ package androidx.media3.common {
|
|||||||
field public static final int TIMELINE_CHANGE_REASON_SOURCE_UPDATE = 1; // 0x1
|
field public static final int TIMELINE_CHANGE_REASON_SOURCE_UPDATE = 1; // 0x1
|
||||||
}
|
}
|
||||||
|
|
||||||
@IntDef({androidx.media3.common.Player.COMMAND_INVALID, androidx.media3.common.Player.COMMAND_PLAY_PAUSE, androidx.media3.common.Player.COMMAND_PREPARE, androidx.media3.common.Player.COMMAND_STOP, androidx.media3.common.Player.COMMAND_SEEK_TO_DEFAULT_POSITION, androidx.media3.common.Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS, androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT, androidx.media3.common.Player.COMMAND_SEEK_TO_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_SEEK_BACK, androidx.media3.common.Player.COMMAND_SEEK_FORWARD, androidx.media3.common.Player.COMMAND_SET_SPEED_AND_PITCH, androidx.media3.common.Player.COMMAND_SET_SHUFFLE_MODE, androidx.media3.common.Player.COMMAND_SET_REPEAT_MODE, androidx.media3.common.Player.COMMAND_GET_CURRENT_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_GET_TIMELINE, androidx.media3.common.Player.COMMAND_GET_MEDIA_ITEMS_METADATA, androidx.media3.common.Player.COMMAND_SET_MEDIA_ITEMS_METADATA, androidx.media3.common.Player.COMMAND_SET_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_CHANGE_MEDIA_ITEMS, androidx.media3.common.Player.COMMAND_GET_AUDIO_ATTRIBUTES, androidx.media3.common.Player.COMMAND_GET_VOLUME, androidx.media3.common.Player.COMMAND_GET_DEVICE_VOLUME, androidx.media3.common.Player.COMMAND_SET_VOLUME, androidx.media3.common.Player.COMMAND_SET_DEVICE_VOLUME, androidx.media3.common.Player.COMMAND_ADJUST_DEVICE_VOLUME, androidx.media3.common.Player.COMMAND_SET_VIDEO_SURFACE, androidx.media3.common.Player.COMMAND_GET_TEXT, androidx.media3.common.Player.COMMAND_SET_TRACK_SELECTION_PARAMETERS, androidx.media3.common.Player.COMMAND_GET_TRACKS}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface Player.Command {
|
@IntDef({androidx.media3.common.Player.COMMAND_INVALID, androidx.media3.common.Player.COMMAND_PLAY_PAUSE, androidx.media3.common.Player.COMMAND_PREPARE, androidx.media3.common.Player.COMMAND_STOP, androidx.media3.common.Player.COMMAND_SEEK_TO_DEFAULT_POSITION, androidx.media3.common.Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_SEEK_TO_PREVIOUS, androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT, androidx.media3.common.Player.COMMAND_SEEK_TO_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_SEEK_BACK, androidx.media3.common.Player.COMMAND_SEEK_FORWARD, androidx.media3.common.Player.COMMAND_SET_SPEED_AND_PITCH, androidx.media3.common.Player.COMMAND_SET_SHUFFLE_MODE, androidx.media3.common.Player.COMMAND_SET_REPEAT_MODE, androidx.media3.common.Player.COMMAND_GET_CURRENT_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_GET_TIMELINE, androidx.media3.common.Player.COMMAND_GET_MEDIA_ITEMS_METADATA, androidx.media3.common.Player.COMMAND_GET_METADATA, androidx.media3.common.Player.COMMAND_SET_MEDIA_ITEMS_METADATA, androidx.media3.common.Player.COMMAND_SET_PLAYLIST_METADATA, androidx.media3.common.Player.COMMAND_SET_MEDIA_ITEM, androidx.media3.common.Player.COMMAND_CHANGE_MEDIA_ITEMS, androidx.media3.common.Player.COMMAND_GET_AUDIO_ATTRIBUTES, androidx.media3.common.Player.COMMAND_GET_VOLUME, androidx.media3.common.Player.COMMAND_GET_DEVICE_VOLUME, androidx.media3.common.Player.COMMAND_SET_VOLUME, androidx.media3.common.Player.COMMAND_SET_DEVICE_VOLUME, androidx.media3.common.Player.COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS, androidx.media3.common.Player.COMMAND_ADJUST_DEVICE_VOLUME, androidx.media3.common.Player.COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS, androidx.media3.common.Player.COMMAND_SET_VIDEO_SURFACE, androidx.media3.common.Player.COMMAND_GET_TEXT, androidx.media3.common.Player.COMMAND_SET_TRACK_SELECTION_PARAMETERS, androidx.media3.common.Player.COMMAND_GET_TRACKS, androidx.media3.common.Player.COMMAND_RELEASE}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface Player.Command {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class Player.Commands {
|
public static final class Player.Commands {
|
||||||
@ -868,10 +945,10 @@ package androidx.media3.common {
|
|||||||
@IntDef({androidx.media3.common.Player.MEDIA_ITEM_TRANSITION_REASON_REPEAT, androidx.media3.common.Player.MEDIA_ITEM_TRANSITION_REASON_AUTO, androidx.media3.common.Player.MEDIA_ITEM_TRANSITION_REASON_SEEK, androidx.media3.common.Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface Player.MediaItemTransitionReason {
|
@IntDef({androidx.media3.common.Player.MEDIA_ITEM_TRANSITION_REASON_REPEAT, androidx.media3.common.Player.MEDIA_ITEM_TRANSITION_REASON_AUTO, androidx.media3.common.Player.MEDIA_ITEM_TRANSITION_REASON_SEEK, androidx.media3.common.Player.MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface Player.MediaItemTransitionReason {
|
||||||
}
|
}
|
||||||
|
|
||||||
@IntDef({androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS, androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY, androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_REMOTE, androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface Player.PlayWhenReadyChangeReason {
|
@IntDef({androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST, androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS, androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY, androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_REMOTE, androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM, androidx.media3.common.Player.PLAY_WHEN_READY_CHANGE_REASON_SUPPRESSED_TOO_LONG}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface Player.PlayWhenReadyChangeReason {
|
||||||
}
|
}
|
||||||
|
|
||||||
@IntDef({androidx.media3.common.Player.PLAYBACK_SUPPRESSION_REASON_NONE, androidx.media3.common.Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface Player.PlaybackSuppressionReason {
|
@IntDef({androidx.media3.common.Player.PLAYBACK_SUPPRESSION_REASON_NONE, androidx.media3.common.Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS, androidx.media3.common.Player.PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_ROUTE}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface Player.PlaybackSuppressionReason {
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class Player.PositionInfo {
|
public static final class Player.PositionInfo {
|
||||||
@ -1162,11 +1239,15 @@ package androidx.media3.common.util {
|
|||||||
method public static boolean checkCleartextTrafficPermitted(androidx.media3.common.MediaItem...);
|
method public static boolean checkCleartextTrafficPermitted(androidx.media3.common.MediaItem...);
|
||||||
method @Nullable public static String getAdaptiveMimeTypeForContentType(@androidx.media3.common.C.ContentType int);
|
method @Nullable public static String getAdaptiveMimeTypeForContentType(@androidx.media3.common.C.ContentType int);
|
||||||
method @Nullable public static java.util.UUID getDrmUuid(String);
|
method @Nullable public static java.util.UUID getDrmUuid(String);
|
||||||
|
method public static boolean handlePauseButtonAction(@Nullable androidx.media3.common.Player);
|
||||||
|
method public static boolean handlePlayButtonAction(@Nullable androidx.media3.common.Player);
|
||||||
|
method public static boolean handlePlayPauseButtonAction(@Nullable androidx.media3.common.Player);
|
||||||
method @androidx.media3.common.C.ContentType public static int inferContentType(android.net.Uri);
|
method @androidx.media3.common.C.ContentType public static int inferContentType(android.net.Uri);
|
||||||
method @androidx.media3.common.C.ContentType public static int inferContentTypeForExtension(String);
|
method @androidx.media3.common.C.ContentType public static int inferContentTypeForExtension(String);
|
||||||
method @androidx.media3.common.C.ContentType public static int inferContentTypeForUriAndMimeType(android.net.Uri, @Nullable String);
|
method @androidx.media3.common.C.ContentType public static int inferContentTypeForUriAndMimeType(android.net.Uri, @Nullable String);
|
||||||
method public static boolean maybeRequestReadExternalStoragePermission(android.app.Activity, android.net.Uri...);
|
method public static boolean maybeRequestReadExternalStoragePermission(android.app.Activity, android.net.Uri...);
|
||||||
method public static boolean maybeRequestReadExternalStoragePermission(android.app.Activity, androidx.media3.common.MediaItem...);
|
method public static boolean maybeRequestReadExternalStoragePermission(android.app.Activity, androidx.media3.common.MediaItem...);
|
||||||
|
method @org.checkerframework.checker.nullness.qual.EnsuresNonNullIf(result=false, expression="#1") public static boolean shouldShowPlayButton(@Nullable androidx.media3.common.Player);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -1438,122 +1519,128 @@ package androidx.media3.session {
|
|||||||
field public static final String EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_PREV = "android.media.playback.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_PREVIOUS";
|
field public static final String EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_PREV = "android.media.playback.ALWAYS_RESERVE_SPACE_FOR.ACTION_SKIP_TO_PREVIOUS";
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MediaController implements androidx.media3.common.Player {
|
@com.google.errorprone.annotations.DoNotMock public class MediaController implements androidx.media3.common.Player {
|
||||||
method public void addListener(androidx.media3.common.Player.Listener);
|
method public final void addListener(androidx.media3.common.Player.Listener);
|
||||||
method public void addMediaItem(androidx.media3.common.MediaItem);
|
method public final void addMediaItem(androidx.media3.common.MediaItem);
|
||||||
method public void addMediaItem(int, androidx.media3.common.MediaItem);
|
method public final void addMediaItem(int, androidx.media3.common.MediaItem);
|
||||||
method public void addMediaItems(java.util.List<androidx.media3.common.MediaItem>);
|
method public final void addMediaItems(java.util.List<androidx.media3.common.MediaItem>);
|
||||||
method public void addMediaItems(int, java.util.List<androidx.media3.common.MediaItem>);
|
method public final void addMediaItems(int, java.util.List<androidx.media3.common.MediaItem>);
|
||||||
method public boolean canAdvertiseSession();
|
method public final boolean canAdvertiseSession();
|
||||||
method public void clearMediaItems();
|
method public final void clearMediaItems();
|
||||||
method public void clearVideoSurface();
|
method public final void clearVideoSurface();
|
||||||
method public void clearVideoSurface(@Nullable android.view.Surface);
|
method public final void clearVideoSurface(@Nullable android.view.Surface);
|
||||||
method public void clearVideoSurfaceHolder(@Nullable android.view.SurfaceHolder);
|
method public final void clearVideoSurfaceHolder(@Nullable android.view.SurfaceHolder);
|
||||||
method public void clearVideoSurfaceView(@Nullable android.view.SurfaceView);
|
method public final void clearVideoSurfaceView(@Nullable android.view.SurfaceView);
|
||||||
method public void clearVideoTextureView(@Nullable android.view.TextureView);
|
method public final void clearVideoTextureView(@Nullable android.view.TextureView);
|
||||||
method public void decreaseDeviceVolume();
|
method @Deprecated public final void decreaseDeviceVolume();
|
||||||
method public android.os.Looper getApplicationLooper();
|
method public final void decreaseDeviceVolume(@androidx.media3.common.C.VolumeFlags int);
|
||||||
method public androidx.media3.common.AudioAttributes getAudioAttributes();
|
method public final android.os.Looper getApplicationLooper();
|
||||||
method public androidx.media3.common.Player.Commands getAvailableCommands();
|
method public final androidx.media3.common.AudioAttributes getAudioAttributes();
|
||||||
method public androidx.media3.session.SessionCommands getAvailableSessionCommands();
|
method public final androidx.media3.common.Player.Commands getAvailableCommands();
|
||||||
method @IntRange(from=0, to=100) public int getBufferedPercentage();
|
method public final androidx.media3.session.SessionCommands getAvailableSessionCommands();
|
||||||
method public long getBufferedPosition();
|
method @IntRange(from=0, to=100) public final int getBufferedPercentage();
|
||||||
method @Nullable public androidx.media3.session.SessionToken getConnectedToken();
|
method public final long getBufferedPosition();
|
||||||
method public long getContentBufferedPosition();
|
method @Nullable public final androidx.media3.session.SessionToken getConnectedToken();
|
||||||
method public long getContentDuration();
|
method public final long getContentBufferedPosition();
|
||||||
method public long getContentPosition();
|
method public final long getContentDuration();
|
||||||
method public int getCurrentAdGroupIndex();
|
method public final long getContentPosition();
|
||||||
method public int getCurrentAdIndexInAdGroup();
|
method public final int getCurrentAdGroupIndex();
|
||||||
method public androidx.media3.common.text.CueGroup getCurrentCues();
|
method public final int getCurrentAdIndexInAdGroup();
|
||||||
method public long getCurrentLiveOffset();
|
method public final androidx.media3.common.text.CueGroup getCurrentCues();
|
||||||
method @Nullable public androidx.media3.common.MediaItem getCurrentMediaItem();
|
method public final long getCurrentLiveOffset();
|
||||||
method public int getCurrentMediaItemIndex();
|
method @Nullable public final androidx.media3.common.MediaItem getCurrentMediaItem();
|
||||||
method public int getCurrentPeriodIndex();
|
method public final int getCurrentMediaItemIndex();
|
||||||
method public long getCurrentPosition();
|
method public final int getCurrentPeriodIndex();
|
||||||
method public androidx.media3.common.Timeline getCurrentTimeline();
|
method public final long getCurrentPosition();
|
||||||
method public androidx.media3.common.Tracks getCurrentTracks();
|
method public final androidx.media3.common.Timeline getCurrentTimeline();
|
||||||
method public androidx.media3.common.DeviceInfo getDeviceInfo();
|
method public final androidx.media3.common.Tracks getCurrentTracks();
|
||||||
method @IntRange(from=0) public int getDeviceVolume();
|
method public final androidx.media3.common.DeviceInfo getDeviceInfo();
|
||||||
method public long getDuration();
|
method @IntRange(from=0) public final int getDeviceVolume();
|
||||||
method public long getMaxSeekToPreviousPosition();
|
method public final long getDuration();
|
||||||
method public androidx.media3.common.MediaItem getMediaItemAt(int);
|
method public final long getMaxSeekToPreviousPosition();
|
||||||
method public int getMediaItemCount();
|
method public final androidx.media3.common.MediaItem getMediaItemAt(int);
|
||||||
method public androidx.media3.common.MediaMetadata getMediaMetadata();
|
method public final int getMediaItemCount();
|
||||||
method public int getNextMediaItemIndex();
|
method public final androidx.media3.common.MediaMetadata getMediaMetadata();
|
||||||
method public boolean getPlayWhenReady();
|
method public final int getNextMediaItemIndex();
|
||||||
method public androidx.media3.common.PlaybackParameters getPlaybackParameters();
|
method public final boolean getPlayWhenReady();
|
||||||
method @androidx.media3.common.Player.State public int getPlaybackState();
|
method public final androidx.media3.common.PlaybackParameters getPlaybackParameters();
|
||||||
method @androidx.media3.common.Player.PlaybackSuppressionReason public int getPlaybackSuppressionReason();
|
method @androidx.media3.common.Player.State public final int getPlaybackState();
|
||||||
method @Nullable public androidx.media3.common.PlaybackException getPlayerError();
|
method @androidx.media3.common.Player.PlaybackSuppressionReason public final int getPlaybackSuppressionReason();
|
||||||
method public androidx.media3.common.MediaMetadata getPlaylistMetadata();
|
method @Nullable public final androidx.media3.common.PlaybackException getPlayerError();
|
||||||
method public int getPreviousMediaItemIndex();
|
method public final androidx.media3.common.MediaMetadata getPlaylistMetadata();
|
||||||
method @androidx.media3.common.Player.RepeatMode public int getRepeatMode();
|
method public final int getPreviousMediaItemIndex();
|
||||||
method public long getSeekBackIncrement();
|
method @androidx.media3.common.Player.RepeatMode public final int getRepeatMode();
|
||||||
method public long getSeekForwardIncrement();
|
method public final long getSeekBackIncrement();
|
||||||
method @Nullable public android.app.PendingIntent getSessionActivity();
|
method public final long getSeekForwardIncrement();
|
||||||
method public boolean getShuffleModeEnabled();
|
method @Nullable public final android.app.PendingIntent getSessionActivity();
|
||||||
method public long getTotalBufferedDuration();
|
method public final boolean getShuffleModeEnabled();
|
||||||
method public androidx.media3.common.TrackSelectionParameters getTrackSelectionParameters();
|
method public final long getTotalBufferedDuration();
|
||||||
method public androidx.media3.common.VideoSize getVideoSize();
|
method public final androidx.media3.common.TrackSelectionParameters getTrackSelectionParameters();
|
||||||
method @FloatRange(from=0, to=1) public float getVolume();
|
method public final androidx.media3.common.VideoSize getVideoSize();
|
||||||
method public boolean hasNextMediaItem();
|
method @FloatRange(from=0, to=1) public final float getVolume();
|
||||||
method public boolean hasPreviousMediaItem();
|
method public final boolean hasNextMediaItem();
|
||||||
method public void increaseDeviceVolume();
|
method public final boolean hasPreviousMediaItem();
|
||||||
method public boolean isCommandAvailable(@androidx.media3.common.Player.Command int);
|
method @Deprecated public final void increaseDeviceVolume();
|
||||||
method public boolean isConnected();
|
method public final void increaseDeviceVolume(@androidx.media3.common.C.VolumeFlags int);
|
||||||
method public boolean isCurrentMediaItemDynamic();
|
method public final boolean isCommandAvailable(@androidx.media3.common.Player.Command int);
|
||||||
method public boolean isCurrentMediaItemLive();
|
method public final boolean isConnected();
|
||||||
method public boolean isCurrentMediaItemSeekable();
|
method public final boolean isCurrentMediaItemDynamic();
|
||||||
method public boolean isDeviceMuted();
|
method public final boolean isCurrentMediaItemLive();
|
||||||
method public boolean isLoading();
|
method public final boolean isCurrentMediaItemSeekable();
|
||||||
method public boolean isPlaying();
|
method public final boolean isDeviceMuted();
|
||||||
method public boolean isPlayingAd();
|
method public final boolean isLoading();
|
||||||
method public boolean isSessionCommandAvailable(@androidx.media3.session.SessionCommand.CommandCode int);
|
method public final boolean isPlaying();
|
||||||
method public boolean isSessionCommandAvailable(androidx.media3.session.SessionCommand);
|
method public final boolean isPlayingAd();
|
||||||
method public void moveMediaItem(int, int);
|
method public final boolean isSessionCommandAvailable(@androidx.media3.session.SessionCommand.CommandCode int);
|
||||||
method public void moveMediaItems(int, int, int);
|
method public final boolean isSessionCommandAvailable(androidx.media3.session.SessionCommand);
|
||||||
method public void pause();
|
method public final void moveMediaItem(int, int);
|
||||||
method public void play();
|
method public final void moveMediaItems(int, int, int);
|
||||||
method public void prepare();
|
method public final void pause();
|
||||||
method public void release();
|
method public final void play();
|
||||||
|
method public final void prepare();
|
||||||
|
method public final void release();
|
||||||
method public static void releaseFuture(java.util.concurrent.Future<? extends androidx.media3.session.MediaController>);
|
method public static void releaseFuture(java.util.concurrent.Future<? extends androidx.media3.session.MediaController>);
|
||||||
method public void removeListener(androidx.media3.common.Player.Listener);
|
method public final void removeListener(androidx.media3.common.Player.Listener);
|
||||||
method public void removeMediaItem(int);
|
method public final void removeMediaItem(int);
|
||||||
method public void removeMediaItems(int, int);
|
method public final void removeMediaItems(int, int);
|
||||||
method public void seekBack();
|
method public final void replaceMediaItem(int, androidx.media3.common.MediaItem);
|
||||||
method public void seekForward();
|
method public final void replaceMediaItems(int, int, java.util.List<androidx.media3.common.MediaItem>);
|
||||||
method public void seekTo(long);
|
method public final void seekBack();
|
||||||
method public void seekTo(int, long);
|
method public final void seekForward();
|
||||||
method public void seekToDefaultPosition();
|
method public final void seekTo(long);
|
||||||
method public void seekToDefaultPosition(int);
|
method public final void seekTo(int, long);
|
||||||
method public void seekToNext();
|
method public final void seekToDefaultPosition();
|
||||||
method public void seekToNextMediaItem();
|
method public final void seekToDefaultPosition(int);
|
||||||
method public void seekToPrevious();
|
method public final void seekToNext();
|
||||||
method public void seekToPreviousMediaItem();
|
method public final void seekToNextMediaItem();
|
||||||
method public com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionResult> sendCustomCommand(androidx.media3.session.SessionCommand, android.os.Bundle);
|
method public final void seekToPrevious();
|
||||||
method public void setDeviceMuted(boolean);
|
method public final void seekToPreviousMediaItem();
|
||||||
method public void setDeviceVolume(@IntRange(from=0) int);
|
method public final com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionResult> sendCustomCommand(androidx.media3.session.SessionCommand, android.os.Bundle);
|
||||||
method public void setMediaItem(androidx.media3.common.MediaItem);
|
method @Deprecated public final void setDeviceMuted(boolean);
|
||||||
method public void setMediaItem(androidx.media3.common.MediaItem, long);
|
method public final void setDeviceMuted(boolean, @androidx.media3.common.C.VolumeFlags int);
|
||||||
method public void setMediaItem(androidx.media3.common.MediaItem, boolean);
|
method @Deprecated public final void setDeviceVolume(@IntRange(from=0) int);
|
||||||
method public void setMediaItems(java.util.List<androidx.media3.common.MediaItem>);
|
method public final void setDeviceVolume(@IntRange(from=0) int, @androidx.media3.common.C.VolumeFlags int);
|
||||||
method public void setMediaItems(java.util.List<androidx.media3.common.MediaItem>, boolean);
|
method public final void setMediaItem(androidx.media3.common.MediaItem);
|
||||||
method public void setMediaItems(java.util.List<androidx.media3.common.MediaItem>, int, long);
|
method public final void setMediaItem(androidx.media3.common.MediaItem, long);
|
||||||
method public void setPlayWhenReady(boolean);
|
method public final void setMediaItem(androidx.media3.common.MediaItem, boolean);
|
||||||
method public void setPlaybackParameters(androidx.media3.common.PlaybackParameters);
|
method public final void setMediaItems(java.util.List<androidx.media3.common.MediaItem>);
|
||||||
method public void setPlaybackSpeed(float);
|
method public final void setMediaItems(java.util.List<androidx.media3.common.MediaItem>, boolean);
|
||||||
method public void setPlaylistMetadata(androidx.media3.common.MediaMetadata);
|
method public final void setMediaItems(java.util.List<androidx.media3.common.MediaItem>, int, long);
|
||||||
method public com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionResult> setRating(String, androidx.media3.common.Rating);
|
method public final void setPlayWhenReady(boolean);
|
||||||
method public com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionResult> setRating(androidx.media3.common.Rating);
|
method public final void setPlaybackParameters(androidx.media3.common.PlaybackParameters);
|
||||||
method public void setRepeatMode(@androidx.media3.common.Player.RepeatMode int);
|
method public final void setPlaybackSpeed(float);
|
||||||
method public void setShuffleModeEnabled(boolean);
|
method public final void setPlaylistMetadata(androidx.media3.common.MediaMetadata);
|
||||||
method public void setTrackSelectionParameters(androidx.media3.common.TrackSelectionParameters);
|
method public final com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionResult> setRating(String, androidx.media3.common.Rating);
|
||||||
method public void setVideoSurface(@Nullable android.view.Surface);
|
method public final com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionResult> setRating(androidx.media3.common.Rating);
|
||||||
method public void setVideoSurfaceHolder(@Nullable android.view.SurfaceHolder);
|
method public final void setRepeatMode(@androidx.media3.common.Player.RepeatMode int);
|
||||||
method public void setVideoSurfaceView(@Nullable android.view.SurfaceView);
|
method public final void setShuffleModeEnabled(boolean);
|
||||||
method public void setVideoTextureView(@Nullable android.view.TextureView);
|
method public final void setTrackSelectionParameters(androidx.media3.common.TrackSelectionParameters);
|
||||||
method public void setVolume(@FloatRange(from=0, to=1) float);
|
method public final void setVideoSurface(@Nullable android.view.Surface);
|
||||||
method public void stop();
|
method public final void setVideoSurfaceHolder(@Nullable android.view.SurfaceHolder);
|
||||||
|
method public final void setVideoSurfaceView(@Nullable android.view.SurfaceView);
|
||||||
|
method public final void setVideoTextureView(@Nullable android.view.TextureView);
|
||||||
|
method public final void setVolume(@FloatRange(from=0, to=1) float);
|
||||||
|
method public final void stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class MediaController.Builder {
|
public static final class MediaController.Builder {
|
||||||
@ -1622,21 +1709,22 @@ package androidx.media3.session {
|
|||||||
field @IntRange(from=1) public final int notificationId;
|
field @IntRange(from=1) public final int notificationId;
|
||||||
}
|
}
|
||||||
|
|
||||||
public class MediaSession {
|
@com.google.errorprone.annotations.DoNotMock public class MediaSession {
|
||||||
method public void broadcastCustomCommand(androidx.media3.session.SessionCommand, android.os.Bundle);
|
method public final void broadcastCustomCommand(androidx.media3.session.SessionCommand, android.os.Bundle);
|
||||||
method public java.util.List<androidx.media3.session.MediaSession.ControllerInfo> getConnectedControllers();
|
method public final java.util.List<androidx.media3.session.MediaSession.ControllerInfo> getConnectedControllers();
|
||||||
method public String getId();
|
method @Nullable public final androidx.media3.session.MediaSession.ControllerInfo getControllerForCurrentRequest();
|
||||||
method public androidx.media3.common.Player getPlayer();
|
method public final String getId();
|
||||||
method @Nullable public android.app.PendingIntent getSessionActivity();
|
method public final androidx.media3.common.Player getPlayer();
|
||||||
method public androidx.media3.session.SessionToken getToken();
|
method @Nullable public final android.app.PendingIntent getSessionActivity();
|
||||||
method public void release();
|
method public final androidx.media3.session.SessionToken getToken();
|
||||||
method public com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionResult> sendCustomCommand(androidx.media3.session.MediaSession.ControllerInfo, androidx.media3.session.SessionCommand, android.os.Bundle);
|
method public final void release();
|
||||||
method public void setAvailableCommands(androidx.media3.session.MediaSession.ControllerInfo, androidx.media3.session.SessionCommands, androidx.media3.common.Player.Commands);
|
method public final com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionResult> sendCustomCommand(androidx.media3.session.MediaSession.ControllerInfo, androidx.media3.session.SessionCommand, android.os.Bundle);
|
||||||
method public com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionResult> setCustomLayout(androidx.media3.session.MediaSession.ControllerInfo, java.util.List<androidx.media3.session.CommandButton>);
|
method public final void setAvailableCommands(androidx.media3.session.MediaSession.ControllerInfo, androidx.media3.session.SessionCommands, androidx.media3.common.Player.Commands);
|
||||||
method public void setCustomLayout(java.util.List<androidx.media3.session.CommandButton>);
|
method public final com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionResult> setCustomLayout(androidx.media3.session.MediaSession.ControllerInfo, java.util.List<androidx.media3.session.CommandButton>);
|
||||||
method public void setPlayer(androidx.media3.common.Player);
|
method public final void setCustomLayout(java.util.List<androidx.media3.session.CommandButton>);
|
||||||
method public void setSessionExtras(android.os.Bundle);
|
method public final void setPlayer(androidx.media3.common.Player);
|
||||||
method public void setSessionExtras(androidx.media3.session.MediaSession.ControllerInfo, android.os.Bundle);
|
method public final void setSessionExtras(android.os.Bundle);
|
||||||
|
method public final void setSessionExtras(androidx.media3.session.MediaSession.ControllerInfo, android.os.Bundle);
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final class MediaSession.Builder {
|
public static final class MediaSession.Builder {
|
||||||
@ -1653,7 +1741,7 @@ package androidx.media3.session {
|
|||||||
method public default androidx.media3.session.MediaSession.ConnectionResult onConnect(androidx.media3.session.MediaSession, androidx.media3.session.MediaSession.ControllerInfo);
|
method public default androidx.media3.session.MediaSession.ConnectionResult onConnect(androidx.media3.session.MediaSession, androidx.media3.session.MediaSession.ControllerInfo);
|
||||||
method public default com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionResult> onCustomCommand(androidx.media3.session.MediaSession, androidx.media3.session.MediaSession.ControllerInfo, androidx.media3.session.SessionCommand, android.os.Bundle);
|
method public default com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionResult> onCustomCommand(androidx.media3.session.MediaSession, androidx.media3.session.MediaSession.ControllerInfo, androidx.media3.session.SessionCommand, android.os.Bundle);
|
||||||
method public default void onDisconnected(androidx.media3.session.MediaSession, androidx.media3.session.MediaSession.ControllerInfo);
|
method public default void onDisconnected(androidx.media3.session.MediaSession, androidx.media3.session.MediaSession.ControllerInfo);
|
||||||
method @androidx.media3.session.SessionResult.Code public default int onPlayerCommandRequest(androidx.media3.session.MediaSession, androidx.media3.session.MediaSession.ControllerInfo, @androidx.media3.common.Player.Command int);
|
method @Deprecated @androidx.media3.session.SessionResult.Code public default int onPlayerCommandRequest(androidx.media3.session.MediaSession, androidx.media3.session.MediaSession.ControllerInfo, @androidx.media3.common.Player.Command int);
|
||||||
method public default void onPostConnect(androidx.media3.session.MediaSession, androidx.media3.session.MediaSession.ControllerInfo);
|
method public default void onPostConnect(androidx.media3.session.MediaSession, androidx.media3.session.MediaSession.ControllerInfo);
|
||||||
method public default com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionResult> onSetRating(androidx.media3.session.MediaSession, androidx.media3.session.MediaSession.ControllerInfo, String, androidx.media3.common.Rating);
|
method public default com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionResult> onSetRating(androidx.media3.session.MediaSession, androidx.media3.session.MediaSession.ControllerInfo, String, androidx.media3.common.Rating);
|
||||||
method public default com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionResult> onSetRating(androidx.media3.session.MediaSession, androidx.media3.session.MediaSession.ControllerInfo, androidx.media3.common.Rating);
|
method public default com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionResult> onSetRating(androidx.media3.session.MediaSession, androidx.media3.session.MediaSession.ControllerInfo, androidx.media3.common.Rating);
|
||||||
@ -1682,7 +1770,8 @@ package androidx.media3.session {
|
|||||||
method public final boolean isSessionAdded(androidx.media3.session.MediaSession);
|
method public final boolean isSessionAdded(androidx.media3.session.MediaSession);
|
||||||
method @CallSuper @Nullable public android.os.IBinder onBind(@Nullable android.content.Intent);
|
method @CallSuper @Nullable public android.os.IBinder onBind(@Nullable android.content.Intent);
|
||||||
method @Nullable public abstract androidx.media3.session.MediaSession onGetSession(androidx.media3.session.MediaSession.ControllerInfo);
|
method @Nullable public abstract androidx.media3.session.MediaSession onGetSession(androidx.media3.session.MediaSession.ControllerInfo);
|
||||||
method public void onUpdateNotification(androidx.media3.session.MediaSession);
|
method @Deprecated public void onUpdateNotification(androidx.media3.session.MediaSession);
|
||||||
|
method public void onUpdateNotification(androidx.media3.session.MediaSession, boolean);
|
||||||
method public final void removeSession(androidx.media3.session.MediaSession);
|
method public final void removeSession(androidx.media3.session.MediaSession);
|
||||||
field public static final String SERVICE_INTERFACE = "androidx.media3.session.MediaSessionService";
|
field public static final String SERVICE_INTERFACE = "androidx.media3.session.MediaSessionService";
|
||||||
}
|
}
|
||||||
|
@ -17,9 +17,9 @@ buildscript {
|
|||||||
mavenCentral()
|
mavenCentral()
|
||||||
}
|
}
|
||||||
dependencies {
|
dependencies {
|
||||||
classpath 'com.android.tools.build:gradle:7.2.1'
|
classpath 'com.android.tools.build:gradle:7.2.2'
|
||||||
classpath 'com.google.android.gms:strict-version-matcher-plugin:1.2.2'
|
classpath 'com.google.android.gms:strict-version-matcher-plugin:1.2.4'
|
||||||
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.5.21'
|
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.8.20'
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
allprojects {
|
allprojects {
|
||||||
|
@ -25,9 +25,11 @@ android {
|
|||||||
aarMetadata {
|
aarMetadata {
|
||||||
minCompileSdk = project.ext.compileSdkVersion
|
minCompileSdk = project.ext.compileSdkVersion
|
||||||
}
|
}
|
||||||
|
multiDexEnabled true
|
||||||
}
|
}
|
||||||
|
|
||||||
compileOptions {
|
compileOptions {
|
||||||
|
coreLibraryDesugaringEnabled true
|
||||||
sourceCompatibility JavaVersion.VERSION_1_8
|
sourceCompatibility JavaVersion.VERSION_1_8
|
||||||
targetCompatibility JavaVersion.VERSION_1_8
|
targetCompatibility JavaVersion.VERSION_1_8
|
||||||
}
|
}
|
||||||
@ -39,3 +41,8 @@ android {
|
|||||||
unitTests.includeAndroidResources true
|
unitTests.includeAndroidResources true
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
dependencies {
|
||||||
|
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
|
||||||
|
coreLibraryDesugaring 'com.android.tools:desugar_jdk_libs:1.1.5'
|
||||||
|
}
|
||||||
|
@ -12,8 +12,8 @@
|
|||||||
// 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.2'
|
releaseVersion = '1.1.0'
|
||||||
releaseVersionCode = 1_000_002_3_00
|
releaseVersionCode = 1_001_000_3_00
|
||||||
minSdkVersion = 16
|
minSdkVersion = 16
|
||||||
appTargetSdkVersion = 33
|
appTargetSdkVersion = 33
|
||||||
// API version before restricting local file access.
|
// API version before restricting local file access.
|
||||||
@ -27,35 +27,38 @@ project.ext {
|
|||||||
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.1-android'
|
||||||
mockitoVersion = '3.12.4'
|
mockitoVersion = '3.12.4'
|
||||||
robolectricVersion = '4.8.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'
|
||||||
errorProneVersion = '2.10.0'
|
errorProneVersion = '2.18.0'
|
||||||
jsr305Version = '3.0.2'
|
jsr305Version = '3.0.2'
|
||||||
kotlinAnnotationsVersion = '1.5.31'
|
kotlinAnnotationsVersion = '1.8.20'
|
||||||
|
// Updating this to 1.4.0+ will import Kotlin stdlib [internal ref: b/277891049].
|
||||||
androidxAnnotationVersion = '1.3.0'
|
androidxAnnotationVersion = '1.3.0'
|
||||||
|
// Updating this to 1.3.0+ will import Kotlin stdlib [internal ref: b/277891049].
|
||||||
androidxAnnotationExperimentalVersion = '1.2.0'
|
androidxAnnotationExperimentalVersion = '1.2.0'
|
||||||
androidxAppCompatVersion = '1.3.1'
|
androidxAppCompatVersion = '1.6.1'
|
||||||
androidxCollectionVersion = '1.1.0'
|
androidxCollectionVersion = '1.2.0'
|
||||||
androidxConstraintLayoutVersion = '2.0.4'
|
androidxConstraintLayoutVersion = '2.1.4'
|
||||||
androidxCoreVersion = '1.7.0'
|
// Updating this to 1.9.0+ will import Kotlin stdlib [internal ref: b/277891049].
|
||||||
|
androidxCoreVersion = '1.8.0'
|
||||||
androidxFuturesVersion = '1.1.0'
|
androidxFuturesVersion = '1.1.0'
|
||||||
androidxMediaVersion = '1.6.0'
|
androidxMediaVersion = '1.6.0'
|
||||||
androidxMedia2Version = '1.2.0'
|
androidxMedia2Version = '1.2.1'
|
||||||
androidxMultidexVersion = '2.0.1'
|
androidxMultidexVersion = '2.0.1'
|
||||||
androidxRecyclerViewVersion = '1.2.1'
|
androidxRecyclerViewVersion = '1.3.0'
|
||||||
androidxMaterialVersion = '1.4.0'
|
androidxMaterialVersion = '1.8.0'
|
||||||
androidxTestCoreVersion = '1.4.0'
|
androidxTestCoreVersion = '1.5.0'
|
||||||
androidxTestJUnitVersion = '1.1.3'
|
androidxTestJUnitVersion = '1.1.5'
|
||||||
androidxTestRunnerVersion = '1.4.0'
|
androidxTestRunnerVersion = '1.5.2'
|
||||||
androidxTestRulesVersion = '1.4.0'
|
androidxTestRulesVersion = '1.5.0'
|
||||||
androidxTestServicesStorageVersion = '1.4.0'
|
androidxTestServicesStorageVersion = '1.4.2'
|
||||||
androidxTestTruthVersion = '1.4.0'
|
androidxTestTruthVersion = '1.5.0'
|
||||||
truthVersion = '1.1.3'
|
truthVersion = '1.1.3'
|
||||||
okhttpVersion = '4.9.2'
|
okhttpVersion = '4.11.0'
|
||||||
modulePrefix = ':'
|
modulePrefix = ':'
|
||||||
if (gradle.ext.has('androidxMediaModulePrefix')) {
|
if (gradle.ext.has('androidxMediaModulePrefix')) {
|
||||||
modulePrefix += gradle.ext.androidxMediaModulePrefix
|
modulePrefix += gradle.ext.androidxMediaModulePrefix
|
||||||
|
@ -21,11 +21,12 @@ if (gradle.ext.has('androidxMediaModulePrefix')) {
|
|||||||
modulePrefix += gradle.ext.androidxMediaModulePrefix
|
modulePrefix += gradle.ext.androidxMediaModulePrefix
|
||||||
}
|
}
|
||||||
|
|
||||||
rootProject.name = gradle.ext.androidxMediaProjectName
|
|
||||||
|
|
||||||
include modulePrefix + 'lib-common'
|
include modulePrefix + 'lib-common'
|
||||||
project(modulePrefix + 'lib-common').projectDir = new File(rootDir, 'libraries/common')
|
project(modulePrefix + 'lib-common').projectDir = new File(rootDir, 'libraries/common')
|
||||||
|
|
||||||
|
include modulePrefix + 'lib-container'
|
||||||
|
project(modulePrefix + 'lib-container').projectDir = new File(rootDir, 'libraries/container')
|
||||||
|
|
||||||
include modulePrefix + 'lib-session'
|
include modulePrefix + 'lib-session'
|
||||||
project(modulePrefix + 'lib-session').projectDir = new File(rootDir, 'libraries/session')
|
project(modulePrefix + 'lib-session').projectDir = new File(rootDir, 'libraries/session')
|
||||||
|
|
||||||
@ -83,6 +84,9 @@ project(modulePrefix + 'lib-cast').projectDir = new File(rootDir, 'libraries/cas
|
|||||||
include modulePrefix + 'lib-effect'
|
include modulePrefix + 'lib-effect'
|
||||||
project(modulePrefix + 'lib-effect').projectDir = new File(rootDir, 'libraries/effect')
|
project(modulePrefix + 'lib-effect').projectDir = new File(rootDir, 'libraries/effect')
|
||||||
|
|
||||||
|
include modulePrefix + 'lib-muxer'
|
||||||
|
project(modulePrefix + 'lib-muxer').projectDir = new File(rootDir, 'libraries/muxer')
|
||||||
|
|
||||||
include modulePrefix + 'lib-transformer'
|
include modulePrefix + 'lib-transformer'
|
||||||
project(modulePrefix + 'lib-transformer').projectDir = new File(rootDir, 'libraries/transformer')
|
project(modulePrefix + 'lib-transformer').projectDir = new File(rootDir, 'libraries/transformer')
|
||||||
|
|
||||||
|
@ -1,7 +1,116 @@
|
|||||||
# Cast demo
|
# Cast demo
|
||||||
|
|
||||||
This app demonstrates integration with Google Cast, as well as switching between
|
This app demonstrates switching between Google Cast and local playback by using
|
||||||
Google Cast and local playback using ExoPlayer.
|
`CastPlayer` and `ExoPlayer`.
|
||||||
|
|
||||||
|
## Building the demo app
|
||||||
|
|
||||||
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.
|
||||||
|
|
||||||
|
Test your streams by adding a `MediaItem` with URI and mime type to the
|
||||||
|
`DemoUtil` and deploy the app on a real device for casting.
|
||||||
|
|
||||||
|
## Customization with `OptionsProvider`
|
||||||
|
|
||||||
|
The Cast SDK behaviour in the demo app or your own app can be customized by
|
||||||
|
providing a custom `OptionsProvider` (see
|
||||||
|
[`DefaultCastOptionsProvider`](https://github.com/androidx/media/blob/release/libraries/cast/src/main/java/androidx/media3/cast/DefaultCastOptionsProvider.java)
|
||||||
|
also).
|
||||||
|
|
||||||
|
Replace the default options provider in the `AndroidManifest.xml` with your own:
|
||||||
|
|
||||||
|
```xml
|
||||||
|
<meta-data
|
||||||
|
android:name="com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME"
|
||||||
|
android:value="com.example.cast.MyOptionsProvider"/>
|
||||||
|
```
|
||||||
|
|
||||||
|
### Using a different Cast receiver app with the Media3 cast demo sender app
|
||||||
|
|
||||||
|
The Media3 cast demo app is an implementation of an
|
||||||
|
[Android Cast *sender app*](https://developers.google.com/cast/docs/android_sender)
|
||||||
|
that uses a *default Cast receiver app* (running on the Cast device) that is
|
||||||
|
customized to support DRM protected streams
|
||||||
|
[by passing DRM configuration via `MediaInfo`](https://developers.google.com/cast/docs/android_sender/exoplayer).
|
||||||
|
Hence Widevine DRM credentials can also be populated with a
|
||||||
|
`MediaItem.DrmConfiguration.Builder` (see the samples in `DemoUtil` marked with
|
||||||
|
`Widevine`).
|
||||||
|
|
||||||
|
If you test your own streams with this demo app, keep in mind that for your
|
||||||
|
production app you need to
|
||||||
|
[choose your own receiver app](https://developers.google.com/cast/docs/web_receiver#choose_a_web_receiver)
|
||||||
|
and have your own receiver app ID.
|
||||||
|
|
||||||
|
If you have a receiver app already and want to quickly test whether it works
|
||||||
|
well together with the `CastPlayer`, then you can configure the demo app to use
|
||||||
|
your receiver:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class MyOptionsProvider implements OptionsProvider {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public CastOptions getCastOptions(Context context) {
|
||||||
|
return new CastOptions.Builder()
|
||||||
|
.setReceiverApplicationId(YOUR_RECEIVER_APP_ID)
|
||||||
|
// other options
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
You can also use the plain
|
||||||
|
[default Cast receiver app](https://developers.google.com/cast/docs/web_receiver#default_media_web_receiver)
|
||||||
|
by using `CastMediaControlIntent.DEFAULT_MEDIA_RECEIVER_APPLICATION_ID`.
|
||||||
|
|
||||||
|
#### Converting a Media3 `MediaItem` to a Cast `MediaQueueItem`
|
||||||
|
|
||||||
|
This demo app uses the
|
||||||
|
[`DefaultMediaItemConverter`](https://github.com/androidx/media/blob/release/libraries/cast/src/main/java/androidx/media3/cast/DefaultMediaItemConverter.java)
|
||||||
|
to convert a Media3 `MediaItem` to a `MediaQueueItem` of the Cast API. Apps that
|
||||||
|
use a custom receiver app, can use a custom `MediaItemConverter` instance by
|
||||||
|
passing it into the constructor of `CastPlayer`.
|
||||||
|
|
||||||
|
### Media session and notification
|
||||||
|
|
||||||
|
This Media3 cast demo app uses the media session and notification support
|
||||||
|
provided by the Cast SDK. If your app already integrates with a `MediaSession`,
|
||||||
|
the Cast session can be disabled to avoid duplicate notifications or sessions:
|
||||||
|
|
||||||
|
```java
|
||||||
|
public class MyOptionsProvider implements OptionsProvider {
|
||||||
|
@NonNull
|
||||||
|
@Override
|
||||||
|
public CastOptions getCastOptions(Context context) {
|
||||||
|
return new CastOptions.Builder()
|
||||||
|
.setCastMediaOptions(
|
||||||
|
new CastMediaOptions.Builder()
|
||||||
|
.setMediaSessionEnabled(false)
|
||||||
|
.setNotificationOptions(null)
|
||||||
|
.build())
|
||||||
|
// other options
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
```
|
||||||
|
|
||||||
|
## Supported media formats
|
||||||
|
|
||||||
|
Whether a specific stream is supported on a Cast device largely depends on the
|
||||||
|
receiver app, the media player used by the receiver and the Cast device, rather
|
||||||
|
then the implementation of the sender that basically only provides media URI and
|
||||||
|
metadata.
|
||||||
|
|
||||||
|
Generally, Google Cast and all Cast Web Receiver applications support the media
|
||||||
|
facilities and types listed on
|
||||||
|
[this page](https://developers.google.com/cast/docs/media). If you build a
|
||||||
|
custom receiver that uses a media player different to the media player of the
|
||||||
|
Cast receiver SDK, your app may support
|
||||||
|
[other formats or features](https://github.com/shaka-project/shaka-player) than
|
||||||
|
listed in the reference above.
|
||||||
|
|
||||||
|
The Media3 team can't give support for building a receiver app or investigations
|
||||||
|
regarding support for certain media formats on a cast devices. Please consult
|
||||||
|
the Cast documentation around
|
||||||
|
[building a receiver application](https://developers.google.com/cast/docs/web_receiver)
|
||||||
|
for further details.
|
||||||
|
@ -14,6 +14,7 @@
|
|||||||
|
|
||||||
apply from: '../../constants.gradle'
|
apply from: '../../constants.gradle'
|
||||||
apply plugin: 'com.android.application'
|
apply plugin: 'com.android.application'
|
||||||
|
apply plugin: 'kotlin-android'
|
||||||
|
|
||||||
android {
|
android {
|
||||||
compileSdkVersion project.ext.compileSdkVersion
|
compileSdkVersion project.ext.compileSdkVersion
|
||||||
|
@ -52,6 +52,7 @@
|
|||||||
</intent-filter>
|
</intent-filter>
|
||||||
<intent-filter>
|
<intent-filter>
|
||||||
<action android:name="android.intent.action.VIEW"/>
|
<action android:name="android.intent.action.VIEW"/>
|
||||||
|
<action android:name="androidx.media3.demo.main.action.BROWSE"/>
|
||||||
<category android:name="android.intent.category.DEFAULT"/>
|
<category android:name="android.intent.category.DEFAULT"/>
|
||||||
<category android:name="android.intent.category.BROWSABLE"/>
|
<category android:name="android.intent.category.BROWSABLE"/>
|
||||||
<data android:scheme="http"/>
|
<data android:scheme="http"/>
|
||||||
|
@ -406,6 +406,18 @@
|
|||||||
"name": "DASH VOD: Tears of Steel (11 periods, pre/mid/post), 2/5/2 ads [5/10s]",
|
"name": "DASH VOD: Tears of Steel (11 periods, pre/mid/post), 2/5/2 ads [5/10s]",
|
||||||
"uri": "ssai://dai.google.com/?contentSourceId=2559737&videoId=tos-dash&format=0&adsId=1"
|
"uri": "ssai://dai.google.com/?contentSourceId=2559737&videoId=tos-dash&format=0&adsId=1"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"name": "DASH live: Tears of Steel (mid), 3 ads each [10 s]",
|
||||||
|
"uri": "ssai://dai.google.com/?assetKey=jNVjPZwzSkyeGiaNQTPqiQ&format=0&adsId=1"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "DASH live: New Tears of Steel (mid), 3 ads each [10 s]",
|
||||||
|
"uri": "ssai://dai.google.com/?assetKey=PSzZMzAkSXCmlJOWDmRj8Q&format=0&adsId=12"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"name": "DASH live: Unencrypted stream with 30s ad breaks every minute",
|
||||||
|
"uri": "ssai://dai.google.com/?assetKey=0ndl1dJcRmKDUPxTRjvdog&format=0&adsId=21"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"name": "Playlist: No ads - HLS VOD: Demo (skippable pre/post) - No ads",
|
"name": "Playlist: No ads - HLS VOD: Demo (skippable pre/post) - No ads",
|
||||||
"playlist": [
|
"playlist": [
|
||||||
@ -434,20 +446,6 @@
|
|||||||
}
|
}
|
||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "Playlist: No ads - HLS Live: Big Buck Bunny (mid) - No ads",
|
|
||||||
"playlist": [
|
|
||||||
{
|
|
||||||
"uri": "https://html5demos.com/assets/dizzy.mp4"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uri": "ssai://dai.google.com/?assetKey=sN_IYUG8STe1ZzhIIE_ksA&format=2&adsId=3"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"uri": "https://html5demos.com/assets/dizzy.mp4"
|
|
||||||
}
|
|
||||||
]
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "Playlist: No ads - DASH VOD: Tears of Steel (11 periods, pre/mid/post) - No ads",
|
"name": "Playlist: No ads - DASH VOD: Tears of Steel (11 periods, pre/mid/post) - No ads",
|
||||||
"playlist": [
|
"playlist": [
|
||||||
@ -494,7 +492,7 @@
|
|||||||
]
|
]
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
"name": "Audio -> Video -> Audio",
|
"name": "Audio -> Video (MKV) -> Video (MKV) -> Audio -> Video (MKV) -> Video (DASH) -> Audio",
|
||||||
"playlist": [
|
"playlist": [
|
||||||
{
|
{
|
||||||
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/audio-141.mp4"
|
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/audio-141.mp4"
|
||||||
@ -502,6 +500,18 @@
|
|||||||
{
|
{
|
||||||
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv"
|
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv"
|
||||||
},
|
},
|
||||||
|
{
|
||||||
|
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/audio-141.mp4"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"uri": "https://storage.googleapis.com/wvmedia/clear/h264/tears/tears.mpd"
|
||||||
|
},
|
||||||
{
|
{
|
||||||
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/audio-141.mp4"
|
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/audio-141.mp4"
|
||||||
}
|
}
|
||||||
|
@ -94,7 +94,7 @@ public class IntentUtil {
|
|||||||
if (mediaItem.mediaMetadata.title != null) {
|
if (mediaItem.mediaMetadata.title != null) {
|
||||||
intent.putExtra(TITLE_EXTRA, mediaItem.mediaMetadata.title);
|
intent.putExtra(TITLE_EXTRA, mediaItem.mediaMetadata.title);
|
||||||
}
|
}
|
||||||
addPlaybackPropertiesToIntent(localConfiguration, intent, /* extrasKeySuffix= */ "");
|
addLocalConfigurationToIntent(localConfiguration, intent, /* extrasKeySuffix= */ "");
|
||||||
addClippingConfigurationToIntent(
|
addClippingConfigurationToIntent(
|
||||||
mediaItem.clippingConfiguration, intent, /* extrasKeySuffix= */ "");
|
mediaItem.clippingConfiguration, intent, /* extrasKeySuffix= */ "");
|
||||||
} else {
|
} else {
|
||||||
@ -104,7 +104,7 @@ public class IntentUtil {
|
|||||||
MediaItem.LocalConfiguration localConfiguration =
|
MediaItem.LocalConfiguration localConfiguration =
|
||||||
checkNotNull(mediaItem.localConfiguration);
|
checkNotNull(mediaItem.localConfiguration);
|
||||||
intent.putExtra(URI_EXTRA + ("_" + i), localConfiguration.uri.toString());
|
intent.putExtra(URI_EXTRA + ("_" + i), localConfiguration.uri.toString());
|
||||||
addPlaybackPropertiesToIntent(localConfiguration, intent, /* extrasKeySuffix= */ "_" + i);
|
addLocalConfigurationToIntent(localConfiguration, intent, /* extrasKeySuffix= */ "_" + i);
|
||||||
addClippingConfigurationToIntent(
|
addClippingConfigurationToIntent(
|
||||||
mediaItem.clippingConfiguration, intent, /* extrasKeySuffix= */ "_" + i);
|
mediaItem.clippingConfiguration, intent, /* extrasKeySuffix= */ "_" + i);
|
||||||
if (mediaItem.mediaMetadata.title != null) {
|
if (mediaItem.mediaMetadata.title != null) {
|
||||||
@ -195,7 +195,7 @@ public class IntentUtil {
|
|||||||
return builder;
|
return builder;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void addPlaybackPropertiesToIntent(
|
private static void addLocalConfigurationToIntent(
|
||||||
MediaItem.LocalConfiguration localConfiguration, Intent intent, String extrasKeySuffix) {
|
MediaItem.LocalConfiguration localConfiguration, Intent intent, String extrasKeySuffix) {
|
||||||
intent
|
intent
|
||||||
.putExtra(MIME_TYPE_EXTRA + extrasKeySuffix, localConfiguration.mimeType)
|
.putExtra(MIME_TYPE_EXTRA + extrasKeySuffix, localConfiguration.mimeType)
|
||||||
|
@ -57,6 +57,8 @@ android {
|
|||||||
}
|
}
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
|
// For detecting and debugging leaks only. LeakCanary is not needed for demo app to work.
|
||||||
|
debugImplementation 'com.squareup.leakcanary:leakcanary-android:2.10'
|
||||||
implementation 'androidx.core:core-ktx:' + androidxCoreVersion
|
implementation 'androidx.core:core-ktx:' + androidxCoreVersion
|
||||||
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
|
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
|
||||||
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
|
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
|
||||||
|
@ -40,7 +40,9 @@
|
|||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".PlayerActivity"
|
android:name=".PlayerActivity"
|
||||||
android:exported="true"/>
|
android:exported="true"
|
||||||
|
android:launchMode="singleTop"
|
||||||
|
android:theme="@style/Theme.AppCompat.NoActionBar"/>
|
||||||
|
|
||||||
<activity
|
<activity
|
||||||
android:name=".PlayableFolderActivity"
|
android:name=".PlayableFolderActivity"
|
||||||
|
@ -501,6 +501,94 @@
|
|||||||
"totalTrackCount": 2,
|
"totalTrackCount": 2,
|
||||||
"duration": 160,
|
"duration": 160,
|
||||||
"site": "https://www.youtube.com/audiolibrary/music"
|
"site": "https://www.youtube.com/audiolibrary/music"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mixed_media_01",
|
||||||
|
"title": "Tear of steal - DASH",
|
||||||
|
"album": "Mixed media",
|
||||||
|
"artist": "Mixed artists",
|
||||||
|
"genre": "Mixed",
|
||||||
|
"source": "https://storage.googleapis.com/wvmedia/clear/h264/tears/tears.mpd",
|
||||||
|
"image": "https://storage.googleapis.com/wvmedia/clear/h264/tears/tears.mpd"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mixed_media_02",
|
||||||
|
"title": "Intro - The Way Of Waking Up (feat. Alan Watts - MP3)",
|
||||||
|
"album": "Mixed media",
|
||||||
|
"artist": "Mixed artists",
|
||||||
|
"genre": "Mixed",
|
||||||
|
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/01_-_Intro_-_The_Way_Of_Waking_Up_feat_Alan_Watts.mp3",
|
||||||
|
"image": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/art.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mixed_media_03",
|
||||||
|
"title": "TTML Netflix Japanese examples (MP4)",
|
||||||
|
"source": "https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/video-avc-baseline-480.mp4",
|
||||||
|
"album": "Mixed media",
|
||||||
|
"artist": "Mixed artists",
|
||||||
|
"genre": "Mixed",
|
||||||
|
"image": "https://cdn.pixabay.com/photo/2014/10/09/13/14/video-481821_960_720.png",
|
||||||
|
"subtitles": [
|
||||||
|
{
|
||||||
|
"subtitle_uri": "https://storage.googleapis.com/exoplayer-test-media-1/ttml/netflix_japanese_ttml.xml",
|
||||||
|
"subtitle_mime_type": "application/ttml+xml",
|
||||||
|
"subtitle_lang": "ja"
|
||||||
|
}
|
||||||
|
]
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mixed_media_04",
|
||||||
|
"title": "The Coldest Shoulder",
|
||||||
|
"album": "Mixed media",
|
||||||
|
"artist": "Mixed artists",
|
||||||
|
"genre": "Mixed",
|
||||||
|
"source": "https://storage.googleapis.com/automotive-media/The_Coldest_Shoulder.mp3",
|
||||||
|
"image": "https://storage.googleapis.com/automotive-media/album_art_3.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mixed_media_05",
|
||||||
|
"title": "Dizzy - MPEG-4 Timed Text",
|
||||||
|
"album": "Mixed media",
|
||||||
|
"artist": "Mixed artists",
|
||||||
|
"genre": "Mixed",
|
||||||
|
"source": "https://storage.googleapis.com/exoplayer-test-media-1/mp4/dizzy-with-tx3g.mp4",
|
||||||
|
"image": "https://cdn.pixabay.com/photo/2014/10/09/13/14/video-481821_960_720.png"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mixed_media_06",
|
||||||
|
"title": "Apple 4x3 basic stream (TS)",
|
||||||
|
"album": "Mixed media",
|
||||||
|
"artist": "Mixed artists",
|
||||||
|
"genre": "Mixed",
|
||||||
|
"source": "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/bipbop_4x3_variant.m3u8",
|
||||||
|
"image": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/art.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mixed_media_07",
|
||||||
|
"title": "The Calm Before The Storm",
|
||||||
|
"album": "Mixed media",
|
||||||
|
"artist": "Mixed artists",
|
||||||
|
"genre": "Mixed",
|
||||||
|
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/05_-_The_Calm_Before_The_Storm.mp3",
|
||||||
|
"image": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/art.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mixed_media_08",
|
||||||
|
"title": "Android screens (MKV)",
|
||||||
|
"album": "Mixed media",
|
||||||
|
"artist": "Mixed artists",
|
||||||
|
"genre": "Mixed",
|
||||||
|
"source": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
|
||||||
|
"image": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/art.jpg"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"id": "mixed_media_09",
|
||||||
|
"title": "No Pain, No Gain",
|
||||||
|
"album": "Mixed media",
|
||||||
|
"artist": "Mixed artists",
|
||||||
|
"genre": "Mixed",
|
||||||
|
"source": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/06_-_No_Pain_No_Gain.mp3",
|
||||||
|
"image": "https://storage.googleapis.com/uamp/The_Kyoto_Connection_-_Wake_Up/art.jpg"
|
||||||
}
|
}
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -17,7 +17,6 @@ package androidx.media3.demo.session
|
|||||||
|
|
||||||
import android.content.ComponentName
|
import android.content.ComponentName
|
||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.content.Intent
|
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.MenuItem
|
import android.view.MenuItem
|
||||||
@ -70,13 +69,13 @@ class MainActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
findViewById<ExtendedFloatingActionButton>(R.id.open_player_floating_button)
|
findViewById<ExtendedFloatingActionButton>(R.id.open_player_floating_button)
|
||||||
.setOnClickListener {
|
.setOnClickListener {
|
||||||
// display the playing media items
|
// Start the session activity that shows the playback activity. The System UI uses the same
|
||||||
val intent = Intent(this, PlayerActivity::class.java)
|
// intent in the same way to start the activity from the notification.
|
||||||
startActivity(intent)
|
browser?.sessionActivity?.send()
|
||||||
}
|
}
|
||||||
|
|
||||||
onBackPressedDispatcher.addCallback(
|
onBackPressedDispatcher.addCallback(
|
||||||
object : OnBackPressedCallback(/* enabled= */ true) {
|
object : OnBackPressedCallback(true) {
|
||||||
override fun handleOnBackPressed() {
|
override fun handleOnBackPressed() {
|
||||||
popPathStack()
|
popPathStack()
|
||||||
}
|
}
|
||||||
|
@ -20,8 +20,8 @@ import android.net.Uri
|
|||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
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.Util
|
|
||||||
import com.google.common.collect.ImmutableList
|
import com.google.common.collect.ImmutableList
|
||||||
|
import java.io.BufferedReader
|
||||||
import org.json.JSONObject
|
import org.json.JSONObject
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -91,10 +91,8 @@ object MediaItemTree {
|
|||||||
.build()
|
.build()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun loadJSONFromAsset(assets: AssetManager): String {
|
private fun loadJSONFromAsset(assets: AssetManager): String =
|
||||||
val buffer = assets.open("catalog.json").use { Util.toByteArray(it) }
|
assets.open("catalog.json").bufferedReader().use(BufferedReader::readText)
|
||||||
return String(buffer, Charsets.UTF_8)
|
|
||||||
}
|
|
||||||
|
|
||||||
fun initialize(assets: AssetManager) {
|
fun initialize(assets: AssetManager) {
|
||||||
if (isInitialized) return
|
if (isInitialized) return
|
||||||
|
@ -51,6 +51,7 @@ class PlayableFolderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
companion object {
|
companion object {
|
||||||
private const val MEDIA_ITEM_ID_KEY = "MEDIA_ITEM_ID_KEY"
|
private const val MEDIA_ITEM_ID_KEY = "MEDIA_ITEM_ID_KEY"
|
||||||
|
|
||||||
fun createIntent(context: Context, mediaItemID: String): Intent {
|
fun createIntent(context: Context, mediaItemID: String): Intent {
|
||||||
val intent = Intent(context, PlayableFolderActivity::class.java)
|
val intent = Intent(context, PlayableFolderActivity::class.java)
|
||||||
intent.putExtra(MEDIA_ITEM_ID_KEY, mediaItemID)
|
intent.putExtra(MEDIA_ITEM_ID_KEY, mediaItemID)
|
||||||
@ -77,8 +78,7 @@ class PlayableFolderActivity : AppCompatActivity() {
|
|||||||
browser.shuffleModeEnabled = false
|
browser.shuffleModeEnabled = false
|
||||||
browser.prepare()
|
browser.prepare()
|
||||||
browser.play()
|
browser.play()
|
||||||
val intent = Intent(this, PlayerActivity::class.java)
|
browser.sessionActivity?.send()
|
||||||
startActivity(intent)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -88,8 +88,7 @@ class PlayableFolderActivity : AppCompatActivity() {
|
|||||||
browser.shuffleModeEnabled = true
|
browser.shuffleModeEnabled = true
|
||||||
browser.prepare()
|
browser.prepare()
|
||||||
browser.play()
|
browser.play()
|
||||||
val intent = Intent(this, PlayerActivity::class.java)
|
browser?.sessionActivity?.send()
|
||||||
startActivity(intent)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
findViewById<Button>(R.id.play_button).setOnClickListener {
|
findViewById<Button>(R.id.play_button).setOnClickListener {
|
||||||
@ -104,9 +103,9 @@ class PlayableFolderActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
findViewById<ExtendedFloatingActionButton>(R.id.open_player_floating_button)
|
findViewById<ExtendedFloatingActionButton>(R.id.open_player_floating_button)
|
||||||
.setOnClickListener {
|
.setOnClickListener {
|
||||||
// display the playing media items
|
// Start the session activity that shows the playback activity. The System UI uses the same
|
||||||
val intent = Intent(this, PlayerActivity::class.java)
|
// intent in the same way to start the activity from the notification.
|
||||||
startActivity(intent)
|
browser?.sessionActivity?.send()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -18,6 +18,7 @@ package androidx.media3.demo.session
|
|||||||
import android.annotation.SuppressLint
|
import android.annotation.SuppressLint
|
||||||
import android.app.NotificationChannel
|
import android.app.NotificationChannel
|
||||||
import android.app.NotificationManager
|
import android.app.NotificationManager
|
||||||
|
import android.app.PendingIntent
|
||||||
import android.app.PendingIntent.*
|
import android.app.PendingIntent.*
|
||||||
import android.app.TaskStackBuilder
|
import android.app.TaskStackBuilder
|
||||||
import android.content.Intent
|
import android.content.Intent
|
||||||
@ -28,6 +29,7 @@ import androidx.core.app.NotificationManagerCompat
|
|||||||
import androidx.media3.common.AudioAttributes
|
import androidx.media3.common.AudioAttributes
|
||||||
import androidx.media3.common.MediaItem
|
import androidx.media3.common.MediaItem
|
||||||
import androidx.media3.common.util.Util
|
import androidx.media3.common.util.Util
|
||||||
|
import androidx.media3.datasource.DataSourceBitmapLoader
|
||||||
import androidx.media3.exoplayer.ExoPlayer
|
import androidx.media3.exoplayer.ExoPlayer
|
||||||
import androidx.media3.session.*
|
import androidx.media3.session.*
|
||||||
import androidx.media3.session.LibraryResult.RESULT_ERROR_NOT_SUPPORTED
|
import androidx.media3.session.LibraryResult.RESULT_ERROR_NOT_SUPPORTED
|
||||||
@ -54,6 +56,7 @@ class PlaybackService : MediaLibraryService() {
|
|||||||
"android.media3.session.demo.SHUFFLE_OFF"
|
"android.media3.session.demo.SHUFFLE_OFF"
|
||||||
private const val NOTIFICATION_ID = 123
|
private const val NOTIFICATION_ID = 123
|
||||||
private const val CHANNEL_ID = "demo_session_notification_channel_id"
|
private const val CHANNEL_ID = "demo_session_notification_channel_id"
|
||||||
|
private val immutableFlag = if (Build.VERSION.SDK_INT >= 23) FLAG_IMMUTABLE else 0
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onCreate() {
|
override fun onCreate() {
|
||||||
@ -77,14 +80,15 @@ class PlaybackService : MediaLibraryService() {
|
|||||||
}
|
}
|
||||||
|
|
||||||
override fun onTaskRemoved(rootIntent: Intent?) {
|
override fun onTaskRemoved(rootIntent: Intent?) {
|
||||||
if (!player.playWhenReady) {
|
if (!player.playWhenReady || player.mediaItemCount == 0) {
|
||||||
stopSelf()
|
stopSelf()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onDestroy() {
|
override fun onDestroy() {
|
||||||
player.release()
|
mediaLibrarySession.setSessionActivity(getBackStackedActivity())
|
||||||
mediaLibrarySession.release()
|
mediaLibrarySession.release()
|
||||||
|
player.release()
|
||||||
clearListener()
|
clearListener()
|
||||||
super.onDestroy()
|
super.onDestroy()
|
||||||
}
|
}
|
||||||
@ -234,18 +238,10 @@ class PlaybackService : MediaLibraryService() {
|
|||||||
.build()
|
.build()
|
||||||
MediaItemTree.initialize(assets)
|
MediaItemTree.initialize(assets)
|
||||||
|
|
||||||
val sessionActivityPendingIntent =
|
|
||||||
TaskStackBuilder.create(this).run {
|
|
||||||
addNextIntent(Intent(this@PlaybackService, MainActivity::class.java))
|
|
||||||
addNextIntent(Intent(this@PlaybackService, PlayerActivity::class.java))
|
|
||||||
|
|
||||||
val immutableFlag = if (Build.VERSION.SDK_INT >= 23) FLAG_IMMUTABLE else 0
|
|
||||||
getPendingIntent(0, immutableFlag or FLAG_UPDATE_CURRENT)
|
|
||||||
}
|
|
||||||
|
|
||||||
mediaLibrarySession =
|
mediaLibrarySession =
|
||||||
MediaLibrarySession.Builder(this, player, librarySessionCallback)
|
MediaLibrarySession.Builder(this, player, librarySessionCallback)
|
||||||
.setSessionActivity(sessionActivityPendingIntent)
|
.setSessionActivity(getSingleTopActivity())
|
||||||
|
.setBitmapLoader(CacheBitmapLoader(DataSourceBitmapLoader(/* context= */ this)))
|
||||||
.build()
|
.build()
|
||||||
if (!customLayout.isEmpty()) {
|
if (!customLayout.isEmpty()) {
|
||||||
// Send custom layout to legacy session.
|
// Send custom layout to legacy session.
|
||||||
@ -253,6 +249,23 @@ class PlaybackService : MediaLibraryService() {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private fun getSingleTopActivity(): PendingIntent {
|
||||||
|
return getActivity(
|
||||||
|
this,
|
||||||
|
0,
|
||||||
|
Intent(this, PlayerActivity::class.java),
|
||||||
|
immutableFlag or FLAG_UPDATE_CURRENT
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
|
private fun getBackStackedActivity(): PendingIntent {
|
||||||
|
return TaskStackBuilder.create(this).run {
|
||||||
|
addNextIntent(Intent(this@PlaybackService, MainActivity::class.java))
|
||||||
|
addNextIntent(Intent(this@PlaybackService, PlayerActivity::class.java))
|
||||||
|
getPendingIntent(0, immutableFlag or FLAG_UPDATE_CURRENT)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private fun getShuffleCommandButton(sessionCommand: SessionCommand): CommandButton {
|
private fun getShuffleCommandButton(sessionCommand: SessionCommand): CommandButton {
|
||||||
val isOn = sessionCommand.customAction == CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON
|
val isOn = sessionCommand.customAction == CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON
|
||||||
return CommandButton.Builder()
|
return CommandButton.Builder()
|
||||||
@ -285,8 +298,6 @@ class PlaybackService : MediaLibraryService() {
|
|||||||
val pendingIntent =
|
val pendingIntent =
|
||||||
TaskStackBuilder.create(this@PlaybackService).run {
|
TaskStackBuilder.create(this@PlaybackService).run {
|
||||||
addNextIntent(Intent(this@PlaybackService, MainActivity::class.java))
|
addNextIntent(Intent(this@PlaybackService, MainActivity::class.java))
|
||||||
|
|
||||||
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)
|
||||||
}
|
}
|
||||||
val builder =
|
val builder =
|
||||||
|
@ -19,7 +19,6 @@ import android.content.ComponentName
|
|||||||
import android.content.Context
|
import android.content.Context
|
||||||
import android.os.Bundle
|
import android.os.Bundle
|
||||||
import android.view.LayoutInflater
|
import android.view.LayoutInflater
|
||||||
import android.view.MenuItem
|
|
||||||
import android.view.View
|
import android.view.View
|
||||||
import android.view.ViewGroup
|
import android.view.ViewGroup
|
||||||
import android.widget.ArrayAdapter
|
import android.widget.ArrayAdapter
|
||||||
@ -46,41 +45,33 @@ class PlayerActivity : AppCompatActivity() {
|
|||||||
get() = if (controllerFuture.isDone) controllerFuture.get() else null
|
get() = if (controllerFuture.isDone) controllerFuture.get() else null
|
||||||
|
|
||||||
private lateinit var playerView: PlayerView
|
private lateinit var playerView: PlayerView
|
||||||
private lateinit var mediaList: ListView
|
private lateinit var mediaItemListView: ListView
|
||||||
private lateinit var mediaListAdapter: PlayingMediaItemArrayAdapter
|
private lateinit var mediaItemListAdapter: MediaItemListAdapter
|
||||||
private val subItemMediaList: MutableList<MediaItem> = mutableListOf()
|
private val mediaItemList: MutableList<MediaItem> = mutableListOf()
|
||||||
|
private var lastMediaItemId: String? = null
|
||||||
|
|
||||||
override fun onCreate(savedInstanceState: Bundle?) {
|
override fun onCreate(savedInstanceState: Bundle?) {
|
||||||
super.onCreate(savedInstanceState)
|
super.onCreate(savedInstanceState)
|
||||||
setContentView(R.layout.activity_player)
|
setContentView(R.layout.activity_player)
|
||||||
playerView = findViewById(R.id.player_view)
|
playerView = findViewById(R.id.player_view)
|
||||||
|
|
||||||
mediaList = findViewById(R.id.current_playing_list)
|
mediaItemListView = findViewById(R.id.current_playing_list)
|
||||||
mediaListAdapter = PlayingMediaItemArrayAdapter(this, R.layout.folder_items, subItemMediaList)
|
mediaItemListAdapter = MediaItemListAdapter(this, R.layout.folder_items, mediaItemList)
|
||||||
mediaList.adapter = mediaListAdapter
|
mediaItemListView.adapter = mediaItemListAdapter
|
||||||
mediaList.setOnItemClickListener { _, _, position, _ ->
|
mediaItemListView.setOnItemClickListener { _, _, position, _ ->
|
||||||
run {
|
run {
|
||||||
val controller = this.controller ?: return@run
|
val controller = this.controller ?: return@run
|
||||||
controller.seekToDefaultPosition(/* windowIndex= */ position)
|
if (controller.currentMediaItemIndex == position) {
|
||||||
mediaListAdapter.notifyDataSetChanged()
|
controller.playWhenReady = !controller.playWhenReady
|
||||||
|
if (controller.playWhenReady) {
|
||||||
|
playerView.hideController()
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
controller.seekToDefaultPosition(/* mediaItemIndex= */ position)
|
||||||
|
mediaItemListAdapter.notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
findViewById<ImageView>(R.id.shuffle_switch).setOnClickListener {
|
|
||||||
val controller = this.controller ?: return@setOnClickListener
|
|
||||||
controller.shuffleModeEnabled = !controller.shuffleModeEnabled
|
|
||||||
}
|
}
|
||||||
|
|
||||||
findViewById<ImageView>(R.id.repeat_switch).setOnClickListener {
|
|
||||||
val controller = this.controller ?: return@setOnClickListener
|
|
||||||
when (controller.repeatMode) {
|
|
||||||
Player.REPEAT_MODE_ALL -> controller.repeatMode = Player.REPEAT_MODE_OFF
|
|
||||||
Player.REPEAT_MODE_OFF -> controller.repeatMode = Player.REPEAT_MODE_ONE
|
|
||||||
Player.REPEAT_MODE_ONE -> controller.repeatMode = Player.REPEAT_MODE_ALL
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
supportActionBar!!.setDisplayHomeAsUpEnabled(true)
|
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onStart() {
|
override fun onStart() {
|
||||||
@ -94,14 +85,6 @@ class PlayerActivity : AppCompatActivity() {
|
|||||||
releaseController()
|
releaseController()
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onOptionsItemSelected(item: MenuItem): Boolean {
|
|
||||||
if (item.itemId == android.R.id.home) {
|
|
||||||
onBackPressed()
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return super.onOptionsItemSelected(item)
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun initializeController() {
|
private fun initializeController() {
|
||||||
controllerFuture =
|
controllerFuture =
|
||||||
MediaController.Builder(
|
MediaController.Builder(
|
||||||
@ -123,8 +106,6 @@ class PlayerActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
updateCurrentPlaylistUI()
|
updateCurrentPlaylistUI()
|
||||||
updateMediaMetadataUI(controller.mediaMetadata)
|
updateMediaMetadataUI(controller.mediaMetadata)
|
||||||
updateShuffleSwitchUI(controller.shuffleModeEnabled)
|
|
||||||
updateRepeatSwitchUI(controller.repeatMode)
|
|
||||||
playerView.setShowSubtitleButton(controller.currentTracks.isTypeSupported(TRACK_TYPE_TEXT))
|
playerView.setShowSubtitleButton(controller.currentTracks.isTypeSupported(TRACK_TYPE_TEXT))
|
||||||
|
|
||||||
controller.addListener(
|
controller.addListener(
|
||||||
@ -133,14 +114,6 @@ class PlayerActivity : AppCompatActivity() {
|
|||||||
updateMediaMetadataUI(mediaItem?.mediaMetadata ?: MediaMetadata.EMPTY)
|
updateMediaMetadataUI(mediaItem?.mediaMetadata ?: MediaMetadata.EMPTY)
|
||||||
}
|
}
|
||||||
|
|
||||||
override fun onShuffleModeEnabledChanged(shuffleModeEnabled: Boolean) {
|
|
||||||
updateShuffleSwitchUI(shuffleModeEnabled)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onRepeatModeChanged(repeatMode: Int) {
|
|
||||||
updateRepeatSwitchUI(repeatMode)
|
|
||||||
}
|
|
||||||
|
|
||||||
override fun onTracksChanged(tracks: Tracks) {
|
override fun onTracksChanged(tracks: Tracks) {
|
||||||
playerView.setShowSubtitleButton(tracks.isTypeSupported(TRACK_TYPE_TEXT))
|
playerView.setShowSubtitleButton(tracks.isTypeSupported(TRACK_TYPE_TEXT))
|
||||||
}
|
}
|
||||||
@ -148,48 +121,26 @@ class PlayerActivity : AppCompatActivity() {
|
|||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateShuffleSwitchUI(shuffleModeEnabled: Boolean) {
|
|
||||||
val resId =
|
|
||||||
if (shuffleModeEnabled) R.drawable.exo_styled_controls_shuffle_on
|
|
||||||
else R.drawable.exo_styled_controls_shuffle_off
|
|
||||||
findViewById<ImageView>(R.id.shuffle_switch)
|
|
||||||
.setImageDrawable(ContextCompat.getDrawable(this, resId))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateRepeatSwitchUI(repeatMode: Int) {
|
|
||||||
val resId: Int =
|
|
||||||
when (repeatMode) {
|
|
||||||
Player.REPEAT_MODE_OFF -> R.drawable.exo_styled_controls_repeat_off
|
|
||||||
Player.REPEAT_MODE_ONE -> R.drawable.exo_styled_controls_repeat_one
|
|
||||||
Player.REPEAT_MODE_ALL -> R.drawable.exo_styled_controls_repeat_all
|
|
||||||
else -> R.drawable.exo_styled_controls_repeat_off
|
|
||||||
}
|
|
||||||
findViewById<ImageView>(R.id.repeat_switch)
|
|
||||||
.setImageDrawable(ContextCompat.getDrawable(this, resId))
|
|
||||||
}
|
|
||||||
|
|
||||||
private fun updateMediaMetadataUI(mediaMetadata: MediaMetadata) {
|
private fun updateMediaMetadataUI(mediaMetadata: MediaMetadata) {
|
||||||
val title: CharSequence = mediaMetadata.title ?: getString(R.string.no_item_prompt)
|
val title: CharSequence = mediaMetadata.title ?: ""
|
||||||
|
|
||||||
findViewById<TextView>(R.id.video_title).text = title
|
findViewById<TextView>(R.id.media_title).text = title
|
||||||
findViewById<TextView>(R.id.video_album).text = mediaMetadata.albumTitle
|
findViewById<TextView>(R.id.media_artist).text = mediaMetadata.artist
|
||||||
findViewById<TextView>(R.id.video_artist).text = mediaMetadata.artist
|
|
||||||
findViewById<TextView>(R.id.video_genre).text = mediaMetadata.genre
|
|
||||||
|
|
||||||
// Trick to update playlist UI
|
// Trick to update playlist UI
|
||||||
mediaListAdapter.notifyDataSetChanged()
|
mediaItemListAdapter.notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
private fun updateCurrentPlaylistUI() {
|
private fun updateCurrentPlaylistUI() {
|
||||||
val controller = this.controller ?: return
|
val controller = this.controller ?: return
|
||||||
subItemMediaList.clear()
|
mediaItemList.clear()
|
||||||
for (i in 0 until controller.mediaItemCount) {
|
for (i in 0 until controller.mediaItemCount) {
|
||||||
subItemMediaList.add(controller.getMediaItemAt(i))
|
mediaItemList.add(controller.getMediaItemAt(i))
|
||||||
}
|
}
|
||||||
mediaListAdapter.notifyDataSetChanged()
|
mediaItemListAdapter.notifyDataSetChanged()
|
||||||
}
|
}
|
||||||
|
|
||||||
private inner class PlayingMediaItemArrayAdapter(
|
private inner class MediaItemListAdapter(
|
||||||
context: Context,
|
context: Context,
|
||||||
viewID: Int,
|
viewID: Int,
|
||||||
mediaItemList: List<MediaItem>
|
mediaItemList: List<MediaItem>
|
||||||
@ -201,23 +152,31 @@ class PlayerActivity : AppCompatActivity() {
|
|||||||
|
|
||||||
returnConvertView.findViewById<TextView>(R.id.media_item).text = mediaItem.mediaMetadata.title
|
returnConvertView.findViewById<TextView>(R.id.media_item).text = mediaItem.mediaMetadata.title
|
||||||
|
|
||||||
|
val deleteButton = returnConvertView.findViewById<Button>(R.id.delete_button)
|
||||||
if (position == controller?.currentMediaItemIndex) {
|
if (position == controller?.currentMediaItemIndex) {
|
||||||
returnConvertView.setBackgroundColor(ContextCompat.getColor(context, R.color.white))
|
// Styles for the current media item list item.
|
||||||
returnConvertView
|
returnConvertView.setBackgroundColor(
|
||||||
.findViewById<TextView>(R.id.media_item)
|
ContextCompat.getColor(context, R.color.playlist_item_background)
|
||||||
.setTextColor(ContextCompat.getColor(context, R.color.black))
|
)
|
||||||
} else {
|
|
||||||
returnConvertView.setBackgroundColor(ContextCompat.getColor(context, R.color.black))
|
|
||||||
returnConvertView
|
returnConvertView
|
||||||
.findViewById<TextView>(R.id.media_item)
|
.findViewById<TextView>(R.id.media_item)
|
||||||
.setTextColor(ContextCompat.getColor(context, R.color.white))
|
.setTextColor(ContextCompat.getColor(context, R.color.white))
|
||||||
}
|
deleteButton.visibility = View.GONE
|
||||||
|
} else {
|
||||||
returnConvertView.findViewById<Button>(R.id.delete_button).setOnClickListener {
|
// Styles for any other media item list item.
|
||||||
|
returnConvertView.setBackgroundColor(
|
||||||
|
ContextCompat.getColor(context, R.color.player_background)
|
||||||
|
)
|
||||||
|
returnConvertView
|
||||||
|
.findViewById<TextView>(R.id.media_item)
|
||||||
|
.setTextColor(ContextCompat.getColor(context, R.color.white))
|
||||||
|
deleteButton.visibility = View.VISIBLE
|
||||||
|
deleteButton.setOnClickListener {
|
||||||
val controller = this@PlayerActivity.controller ?: return@setOnClickListener
|
val controller = this@PlayerActivity.controller ?: return@setOnClickListener
|
||||||
controller.removeMediaItem(position)
|
controller.removeMediaItem(position)
|
||||||
updateCurrentPlaylistUI()
|
updateCurrentPlaylistUI()
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
return returnConvertView
|
return returnConvertView
|
||||||
}
|
}
|
||||||
|
BIN
demos/session/src/main/res/drawable/artwork_placeholder.png
Normal file
BIN
demos/session/src/main/res/drawable/artwork_placeholder.png
Normal file
Binary file not shown.
After Width: | Height: | Size: 40 KiB |
@ -19,7 +19,7 @@
|
|||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:background="@color/black"
|
android:background="@color/player_background"
|
||||||
tools:context=".PlayerActivity">
|
tools:context=".PlayerActivity">
|
||||||
|
|
||||||
<androidx.media3.ui.AspectRatioFrameLayout
|
<androidx.media3.ui.AspectRatioFrameLayout
|
||||||
@ -28,77 +28,48 @@
|
|||||||
>
|
>
|
||||||
<androidx.media3.ui.PlayerView
|
<androidx.media3.ui.PlayerView
|
||||||
android:id="@+id/player_view"
|
android:id="@+id/player_view"
|
||||||
|
android:background="@color/player_background"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
app:use_artwork="true" />
|
app:artwork_display_mode="fill"
|
||||||
|
app:default_artwork="@drawable/artwork_placeholder"
|
||||||
|
app:repeat_toggle_modes="one|all"
|
||||||
|
app:show_shuffle_button="true"
|
||||||
|
app:shutter_background_color="@color/player_background" />
|
||||||
</androidx.media3.ui.AspectRatioFrameLayout>
|
</androidx.media3.ui.AspectRatioFrameLayout>
|
||||||
|
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/video_title"
|
android:id="@+id/media_artist"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:paddingLeft="10dp"
|
android:paddingLeft="10dp"
|
||||||
|
android:paddingStart="10dp"
|
||||||
|
android:paddingTop="10dp"
|
||||||
|
android:textColor="@color/white"
|
||||||
|
android:textSize="14sp"/>
|
||||||
|
|
||||||
|
<TextView
|
||||||
|
android:id="@+id/media_title"
|
||||||
|
android:ellipsize="end"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:paddingLeft="10dp"
|
||||||
android:paddingBottom="10dp"
|
android:paddingBottom="10dp"
|
||||||
|
android:paddingStart="10dp"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:textSize="20sp" />
|
android:textSize="20sp" />
|
||||||
|
|
||||||
<TextView
|
<View
|
||||||
android:id="@+id/video_album"
|
android:background="@color/divider"
|
||||||
android:layout_width="match_parent"
|
android:layout_height="1dp"
|
||||||
android:layout_height="wrap_content"
|
android:layout_width="match_parent" />
|
||||||
android:paddingLeft="10dp"
|
|
||||||
android:paddingBottom="10dp"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:textSize="20sp" />
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/video_artist"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:paddingLeft="10dp"
|
|
||||||
android:paddingBottom="10dp"/>
|
|
||||||
|
|
||||||
<TextView
|
|
||||||
android:id="@+id/video_genre"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:paddingLeft="10dp"
|
|
||||||
android:paddingBottom="10dp"
|
|
||||||
android:textSize="12sp" />
|
|
||||||
|
|
||||||
<LinearLayout
|
|
||||||
android:gravity="center_vertical"
|
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:layout_height="wrap_content">
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:id="@+id/shuffle_switch"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:contentDescription="@string/shuffle"
|
|
||||||
android:src="@drawable/exo_styled_controls_shuffle_off"
|
|
||||||
android:textColor="@color/white" />
|
|
||||||
|
|
||||||
|
|
||||||
<ImageView
|
|
||||||
android:layout_margin="@dimen/exo_icon_horizontal_margin"
|
|
||||||
android:id="@+id/repeat_switch"
|
|
||||||
android:layout_width="wrap_content"
|
|
||||||
android:layout_height="wrap_content"
|
|
||||||
android:src="@drawable/exo_styled_controls_repeat_off"
|
|
||||||
android:textColor="@color/white"
|
|
||||||
android:contentDescription="@string/repeat"
|
|
||||||
/>
|
|
||||||
</LinearLayout>
|
|
||||||
|
|
||||||
<ListView
|
<ListView
|
||||||
android:id="@+id/current_playing_list"
|
android:id="@+id/current_playing_list"
|
||||||
android:layout_width="match_parent"
|
|
||||||
android:divider="@drawable/divider"
|
android:divider="@drawable/divider"
|
||||||
android:dividerHeight="1px"
|
android:dividerHeight="1px"
|
||||||
android:layout_height="wrap_content"/>
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
@ -25,6 +25,8 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:gravity="center_vertical"
|
android:gravity="center_vertical"
|
||||||
android:paddingStart="10dp"
|
android:paddingStart="10dp"
|
||||||
|
android:maxLines="1"
|
||||||
|
android:ellipsize="end"
|
||||||
android:textColor="@color/white"
|
android:textColor="@color/white"
|
||||||
android:paddingEnd="10dp"
|
android:paddingEnd="10dp"
|
||||||
android:minHeight="50dp" />
|
android:minHeight="50dp" />
|
||||||
@ -35,6 +37,7 @@
|
|||||||
android:layout_width="50dp"
|
android:layout_width="50dp"
|
||||||
android:layout_height="match_parent"
|
android:layout_height="match_parent"
|
||||||
android:id="@+id/delete_button"
|
android:id="@+id/delete_button"
|
||||||
|
android:backgroundTint="@color/playlist_item_foreground"
|
||||||
android:background="@drawable/baseline_playlist_remove_white_48"
|
android:background="@drawable/baseline_playlist_remove_white_48"
|
||||||
/>
|
/>
|
||||||
|
|
||||||
|
@ -22,4 +22,9 @@
|
|||||||
<color name="black">#FF000000</color>
|
<color name="black">#FF000000</color>
|
||||||
<color name="white">#FFFFFFFF</color>
|
<color name="white">#FFFFFFFF</color>
|
||||||
<color name="grey">#FF999999</color>
|
<color name="grey">#FF999999</color>
|
||||||
|
<color name="background">#292929</color>
|
||||||
|
<color name="player_background">#1c1c1c</color>
|
||||||
|
<color name="playlist_item_background">#363434</color>
|
||||||
|
<color name="playlist_item_foreground">#635E5E</color>
|
||||||
|
<color name="divider">#646464</color>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -22,6 +22,7 @@
|
|||||||
<uses-permission android:name="android.permission.INTERNET"/>
|
<uses-permission android:name="android.permission.INTERNET"/>
|
||||||
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
<uses-permission android:name="android.permission.ACCESS_NETWORK_STATE"/>
|
||||||
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
|
||||||
|
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
|
||||||
|
|
||||||
<application
|
<application
|
||||||
android:allowBackup="false"
|
android:allowBackup="false"
|
||||||
|
@ -1,181 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.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.Pair;
|
|
||||||
import androidx.media3.common.C;
|
|
||||||
import androidx.media3.common.FrameProcessingException;
|
|
||||||
import androidx.media3.common.util.GlProgram;
|
|
||||||
import androidx.media3.common.util.GlUtil;
|
|
||||||
import androidx.media3.effect.SingleFrameGlTextureProcessor;
|
|
||||||
import java.io.IOException;
|
|
||||||
import java.util.Locale;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* 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 extends SingleFrameGlTextureProcessor {
|
|
||||||
|
|
||||||
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 Bitmap logoBitmap;
|
|
||||||
private final Canvas overlayCanvas;
|
|
||||||
private final GlProgram glProgram;
|
|
||||||
|
|
||||||
private float bitmapScaleX;
|
|
||||||
private float bitmapScaleY;
|
|
||||||
private int bitmapTexId;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a new instance.
|
|
||||||
*
|
|
||||||
* @param context The {@link Context}.
|
|
||||||
* @param useHdr Whether input textures come from an HDR source. If {@code true}, colors will be
|
|
||||||
* in linear RGB BT.2020. If {@code false}, colors will be in linear RGB BT.709.
|
|
||||||
* @throws FrameProcessingException If a problem occurs while reading shader files.
|
|
||||||
*/
|
|
||||||
public BitmapOverlayProcessor(Context context, boolean useHdr) throws FrameProcessingException {
|
|
||||||
super(useHdr);
|
|
||||||
checkArgument(!useHdr, "BitmapOverlayProcessor does not support HDR colors.");
|
|
||||||
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);
|
|
||||||
|
|
||||||
try {
|
|
||||||
logoBitmap =
|
|
||||||
((BitmapDrawable)
|
|
||||||
context.getPackageManager().getApplicationIcon(context.getPackageName()))
|
|
||||||
.getBitmap();
|
|
||||||
} catch (PackageManager.NameNotFoundException e) {
|
|
||||||
throw new IllegalStateException(e);
|
|
||||||
}
|
|
||||||
try {
|
|
||||||
bitmapTexId =
|
|
||||||
GlUtil.createTexture(
|
|
||||||
BITMAP_WIDTH_HEIGHT,
|
|
||||||
BITMAP_WIDTH_HEIGHT,
|
|
||||||
/* useHighPrecisionColorComponents= */ false);
|
|
||||||
GLUtils.texImage2D(GLES20.GL_TEXTURE_2D, /* level= */ 0, overlayBitmap, /* border= */ 0);
|
|
||||||
|
|
||||||
glProgram = new GlProgram(context, VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH);
|
|
||||||
} catch (GlUtil.GlException | IOException e) {
|
|
||||||
throw new FrameProcessingException(e);
|
|
||||||
}
|
|
||||||
// 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("uTexSampler1", bitmapTexId, /* texUnitIndex= */ 1);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public Pair<Integer, Integer> configure(int inputWidth, int inputHeight) {
|
|
||||||
if (inputWidth > inputHeight) {
|
|
||||||
bitmapScaleX = inputWidth / (float) inputHeight;
|
|
||||||
bitmapScaleY = 1f;
|
|
||||||
} else {
|
|
||||||
bitmapScaleX = 1f;
|
|
||||||
bitmapScaleY = inputHeight / (float) inputWidth;
|
|
||||||
}
|
|
||||||
|
|
||||||
glProgram.setFloatUniform("uScaleX", bitmapScaleX);
|
|
||||||
glProgram.setFloatUniform("uScaleY", bitmapScaleY);
|
|
||||||
|
|
||||||
return Pair.create(inputWidth, inputHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void drawFrame(int inputTexId, long presentationTimeUs) throws FrameProcessingException {
|
|
||||||
try {
|
|
||||||
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.setSamplerTexIdUniform("uTexSampler0", inputTexId, /* texUnitIndex= */ 0);
|
|
||||||
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, presentationTimeUs);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void release() throws FrameProcessingException {
|
|
||||||
super.release();
|
|
||||||
try {
|
|
||||||
glProgram.delete();
|
|
||||||
} catch (GlUtil.GlException e) {
|
|
||||||
throw new FrameProcessingException(e);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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);
|
|
||||||
}
|
|
||||||
}
|
|
@ -15,20 +15,24 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.demo.transformer;
|
package androidx.media3.demo.transformer;
|
||||||
|
|
||||||
|
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
|
||||||
|
import static android.Manifest.permission.READ_MEDIA_VIDEO;
|
||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
import static androidx.media3.common.util.Assertions.checkState;
|
import static androidx.media3.common.util.Assertions.checkState;
|
||||||
|
import static androidx.media3.common.util.Util.SDK_INT;
|
||||||
|
|
||||||
import android.Manifest;
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.DialogInterface;
|
import android.content.DialogInterface;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
|
import android.graphics.Color;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.widget.ArrayAdapter;
|
import android.widget.ArrayAdapter;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
import android.widget.CheckBox;
|
import android.widget.CheckBox;
|
||||||
|
import android.widget.EditText;
|
||||||
import android.widget.Spinner;
|
import android.widget.Spinner;
|
||||||
import android.widget.TextView;
|
import android.widget.TextView;
|
||||||
import android.widget.Toast;
|
import android.widget.Toast;
|
||||||
@ -41,22 +45,24 @@ import androidx.appcompat.app.AppCompatActivity;
|
|||||||
import androidx.core.app.ActivityCompat;
|
import androidx.core.app.ActivityCompat;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.MimeTypes;
|
import androidx.media3.common.MimeTypes;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.transformer.TransformationRequest;
|
||||||
import com.google.android.material.slider.RangeSlider;
|
import com.google.android.material.slider.RangeSlider;
|
||||||
import com.google.android.material.slider.Slider;
|
import com.google.android.material.slider.Slider;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
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;
|
||||||
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An {@link Activity} that sets the configuration to use for transforming and playing media, using
|
* An {@link Activity} that sets the configuration to use for exporting and playing media, using
|
||||||
* {@link TransformerActivity}.
|
* {@link TransformerActivity}.
|
||||||
*/
|
*/
|
||||||
public final class ConfigurationActivity extends AppCompatActivity {
|
public final class ConfigurationActivity extends AppCompatActivity {
|
||||||
public static final String SHOULD_REMOVE_AUDIO = "should_remove_audio";
|
public static final String SHOULD_REMOVE_AUDIO = "should_remove_audio";
|
||||||
public static final String SHOULD_REMOVE_VIDEO = "should_remove_video";
|
public static final String SHOULD_REMOVE_VIDEO = "should_remove_video";
|
||||||
public static final String SHOULD_FLATTEN_FOR_SLOW_MOTION = "should_flatten_for_slow_motion";
|
public static final String SHOULD_FLATTEN_FOR_SLOW_MOTION = "should_flatten_for_slow_motion";
|
||||||
|
public static final String FORCE_AUDIO_TRACK = "force_audio_track";
|
||||||
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";
|
||||||
@ -67,10 +73,10 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
public static final String TRIM_END_MS = "trim_end_ms";
|
public static final String TRIM_END_MS = "trim_end_ms";
|
||||||
public static final String ENABLE_FALLBACK = "enable_fallback";
|
public static final String ENABLE_FALLBACK = "enable_fallback";
|
||||||
public static final String ENABLE_DEBUG_PREVIEW = "enable_debug_preview";
|
public static final String ENABLE_DEBUG_PREVIEW = "enable_debug_preview";
|
||||||
public static final String ENABLE_REQUEST_SDR_TONE_MAPPING = "enable_request_sdr_tone_mapping";
|
public static final String ABORT_SLOW_EXPORT = "abort_slow_export";
|
||||||
public static final String FORCE_INTERPRET_HDR_VIDEO_AS_SDR = "force_interpret_hdr_video_as_sdr";
|
public static final String HDR_MODE = "hdr_mode";
|
||||||
public static final String ENABLE_HDR_EDITING = "enable_hdr_editing";
|
public static final String AUDIO_EFFECTS_SELECTIONS = "audio_effects_selections";
|
||||||
public static final String DEMO_EFFECTS_SELECTIONS = "demo_effects_selections";
|
public static final String VIDEO_EFFECTS_SELECTIONS = "video_effects_selections";
|
||||||
public static final String PERIODIC_VIGNETTE_CENTER_X = "periodic_vignette_center_x";
|
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_CENTER_Y = "periodic_vignette_center_y";
|
||||||
public static final String PERIODIC_VIGNETTE_INNER_RADIUS = "periodic_vignette_inner_radius";
|
public static final String PERIODIC_VIGNETTE_INNER_RADIUS = "periodic_vignette_inner_radius";
|
||||||
@ -83,9 +89,37 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
public static final String HSL_ADJUSTMENTS_HUE = "hsl_adjustments_hue";
|
public static final String HSL_ADJUSTMENTS_HUE = "hsl_adjustments_hue";
|
||||||
public static final String HSL_ADJUSTMENTS_SATURATION = "hsl_adjustments_saturation";
|
public static final String HSL_ADJUSTMENTS_SATURATION = "hsl_adjustments_saturation";
|
||||||
public static final String HSL_ADJUSTMENTS_LIGHTNESS = "hsl_adjustments_lightness";
|
public static final String HSL_ADJUSTMENTS_LIGHTNESS = "hsl_adjustments_lightness";
|
||||||
|
public static final String BITMAP_OVERLAY_URI = "bitmap_overlay_uri";
|
||||||
|
public static final String BITMAP_OVERLAY_ALPHA = "bitmap_overlay_alpha";
|
||||||
|
public static final String TEXT_OVERLAY_TEXT = "text_overlay_text";
|
||||||
|
public static final String TEXT_OVERLAY_TEXT_COLOR = "text_overlay_text_color";
|
||||||
|
public static final String TEXT_OVERLAY_ALPHA = "text_overlay_alpha";
|
||||||
|
|
||||||
|
// Video effect selections.
|
||||||
|
public static final int DIZZY_CROP_INDEX = 0;
|
||||||
|
public static final int EDGE_DETECTOR_INDEX = 1;
|
||||||
|
public static final int COLOR_FILTERS_INDEX = 2;
|
||||||
|
public static final int MAP_WHITE_TO_GREEN_LUT_INDEX = 3;
|
||||||
|
public static final int RGB_ADJUSTMENTS_INDEX = 4;
|
||||||
|
public static final int HSL_ADJUSTMENT_INDEX = 5;
|
||||||
|
public static final int CONTRAST_INDEX = 6;
|
||||||
|
public static final int PERIODIC_VIGNETTE_INDEX = 7;
|
||||||
|
public static final int SPIN_3D_INDEX = 8;
|
||||||
|
public static final int ZOOM_IN_INDEX = 9;
|
||||||
|
public static final int OVERLAY_LOGO_AND_TIMER_INDEX = 10;
|
||||||
|
public static final int BITMAP_OVERLAY_INDEX = 11;
|
||||||
|
public static final int TEXT_OVERLAY_INDEX = 12;
|
||||||
|
|
||||||
|
// Audio effect selections.
|
||||||
|
public static final int HIGH_PITCHED_INDEX = 0;
|
||||||
|
public static final int SAMPLE_RATE_INDEX = 1;
|
||||||
|
public static final int SKIP_SILENCE_INDEX = 2;
|
||||||
|
|
||||||
|
// Color filter options.
|
||||||
public static final int COLOR_FILTER_GRAYSCALE = 0;
|
public static final int COLOR_FILTER_GRAYSCALE = 0;
|
||||||
public static final int COLOR_FILTER_INVERTED = 1;
|
public static final int COLOR_FILTER_INVERTED = 1;
|
||||||
public static final int COLOR_FILTER_SEPIA = 2;
|
public static final int COLOR_FILTER_SEPIA = 2;
|
||||||
|
|
||||||
public static final int FILE_PERMISSION_REQUEST_CODE = 1;
|
public static final int FILE_PERMISSION_REQUEST_CODE = 1;
|
||||||
private static final String[] PRESET_FILE_URIS = {
|
private static final String[] PRESET_FILE_URIS = {
|
||||||
"https://storage.googleapis.com/exoplayer-test-media-1/mp4/android-screens-10s.mp4",
|
"https://storage.googleapis.com/exoplayer-test-media-1/mp4/android-screens-10s.mp4",
|
||||||
@ -98,9 +132,13 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
"https://storage.googleapis.com/exoplayer-test-media-0/BigBuckBunny_320x180.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_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/portrait_rotated_avc_aac.mp4",
|
||||||
|
"https://storage.googleapis.com/exoplayer-test-media-1/jpg/london.jpg",
|
||||||
|
"https://storage.googleapis.com/exoplayer-test-media-1/jpg/tokyo.jpg",
|
||||||
"https://storage.googleapis.com/exoplayer-test-media-1/mp4/slow-motion/slowMotion_stopwatch_240fps_long.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/gen/screens/dash-vod-single-segment/manifest-baseline.mpd",
|
||||||
"https://storage.googleapis.com/exoplayer-test-media-1/mp4/samsung-s21-hdr-hdr10.mp4",
|
"https://storage.googleapis.com/exoplayer-test-media-1/mp4/samsung-s21-hdr-hdr10.mp4",
|
||||||
|
"https://storage.googleapis.com/exoplayer-test-media-1/mp4/Pixel7Pro_HLG_1080P.mp4",
|
||||||
|
"https://storage.googleapis.com/exoplayer-test-media-1/mp4/sample_video_track_only.mp4",
|
||||||
};
|
};
|
||||||
private static final String[] PRESET_FILE_URI_DESCRIPTIONS = { // same order as PRESET_FILE_URIS
|
private static final String[] PRESET_FILE_URI_DESCRIPTIONS = { // same order as PRESET_FILE_URIS
|
||||||
"720p H264 video and AAC audio",
|
"720p H264 video and AAC audio",
|
||||||
@ -111,13 +149,20 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
"8k H265 video and AAC audio",
|
"8k H265 video and AAC audio",
|
||||||
"Short 1080p H265 video and AAC audio",
|
"Short 1080p H265 video and AAC audio",
|
||||||
"Long 180p H264 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, 0°)",
|
||||||
"H264 video and AAC audio (portrait, H < W, 90\u00B0)",
|
"H264 video and AAC audio (portrait, H < W, 90°)",
|
||||||
|
"London JPG image (Plays for 5secs at 30fps)",
|
||||||
|
"Tokyo JPG image (Portrait, Plays for 5secs at 30fps)",
|
||||||
"SEF slow motion with 240 fps",
|
"SEF slow motion with 240 fps",
|
||||||
"480p DASH (non-square pixels)",
|
"480p DASH (non-square pixels)",
|
||||||
"HDR (HDR10) H265 limited range video (encoding may fail)",
|
"HDR (HDR10) H265 limited range video (encoding may fail)",
|
||||||
|
"HDR (HLG) H265 limited range video (encoding may fail)",
|
||||||
|
"720p H264 video with no audio",
|
||||||
};
|
};
|
||||||
private static final String[] DEMO_EFFECTS = {
|
private static final String[] AUDIO_EFFECTS = {
|
||||||
|
"High pitched", "Sample rate of 48000Hz", "Skip silence"
|
||||||
|
};
|
||||||
|
private static final String[] VIDEO_EFFECTS = {
|
||||||
"Dizzy crop",
|
"Dizzy crop",
|
||||||
"Edge detector (Media Pipe)",
|
"Edge detector (Media Pipe)",
|
||||||
"Color filters",
|
"Color filters",
|
||||||
@ -127,24 +172,52 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
"Contrast",
|
"Contrast",
|
||||||
"Periodic vignette",
|
"Periodic vignette",
|
||||||
"3D spin",
|
"3D spin",
|
||||||
"Overlay logo & timer",
|
|
||||||
"Zoom in start",
|
"Zoom in start",
|
||||||
|
"Overlay logo & timer",
|
||||||
|
"Custom Bitmap Overlay",
|
||||||
|
"Custom Text Overlay",
|
||||||
};
|
};
|
||||||
private static final int COLOR_FILTERS_INDEX = 2;
|
private static final ImmutableMap<String, @TransformationRequest.HdrMode Integer>
|
||||||
private static final int RGB_ADJUSTMENTS_INDEX = 4;
|
HDR_MODE_DESCRIPTIONS =
|
||||||
private static final int HSL_ADJUSTMENT_INDEX = 5;
|
new ImmutableMap.Builder<String, @TransformationRequest.HdrMode Integer>()
|
||||||
private static final int CONTRAST_INDEX = 6;
|
.put("Keep HDR", TransformationRequest.HDR_MODE_KEEP_HDR)
|
||||||
private static final int PERIODIC_VIGNETTE_INDEX = 7;
|
.put(
|
||||||
|
"MediaCodec tone-map HDR to SDR",
|
||||||
|
TransformationRequest.HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_MEDIACODEC)
|
||||||
|
.put(
|
||||||
|
"OpenGL tone-map HDR to SDR",
|
||||||
|
TransformationRequest.HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL)
|
||||||
|
.put(
|
||||||
|
"Force Interpret HDR as SDR",
|
||||||
|
TransformationRequest.HDR_MODE_EXPERIMENTAL_FORCE_INTERPRET_HDR_AS_SDR)
|
||||||
|
.build();
|
||||||
|
private static final ImmutableMap<String, Integer> OVERLAY_COLORS =
|
||||||
|
new ImmutableMap.Builder<String, Integer>()
|
||||||
|
.put("BLACK", Color.BLACK)
|
||||||
|
.put("BLUE", Color.BLUE)
|
||||||
|
.put("CYAN", Color.CYAN)
|
||||||
|
.put("DKGRAY", Color.DKGRAY)
|
||||||
|
.put("GRAY", Color.GRAY)
|
||||||
|
.put("GREEN", Color.GREEN)
|
||||||
|
.put("LTGRAY", Color.LTGRAY)
|
||||||
|
.put("MAGENTA", Color.MAGENTA)
|
||||||
|
.put("RED", Color.RED)
|
||||||
|
.put("WHITE", Color.WHITE)
|
||||||
|
.put("YELLOW", Color.YELLOW)
|
||||||
|
.build();
|
||||||
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 static final float HALF_DIAGONAL = 1f / (float) Math.sqrt(2);
|
||||||
|
|
||||||
private @MonotonicNonNull ActivityResultLauncher<Intent> localFilePickerLauncher;
|
private @MonotonicNonNull Runnable onPermissionsGranted;
|
||||||
|
private @MonotonicNonNull ActivityResultLauncher<Intent> videoLocalFilePickerLauncher;
|
||||||
|
private @MonotonicNonNull ActivityResultLauncher<Intent> overlayLocalFilePickerLauncher;
|
||||||
private @MonotonicNonNull Button selectPresetFileButton;
|
private @MonotonicNonNull Button selectPresetFileButton;
|
||||||
private @MonotonicNonNull Button selectLocalFileButton;
|
private @MonotonicNonNull Button selectLocalFileButton;
|
||||||
private @MonotonicNonNull TextView selectedFileTextView;
|
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 CheckBox forceAudioTrackCheckbox;
|
||||||
private @MonotonicNonNull Spinner audioMimeSpinner;
|
private @MonotonicNonNull Spinner audioMimeSpinner;
|
||||||
private @MonotonicNonNull Spinner videoMimeSpinner;
|
private @MonotonicNonNull Spinner videoMimeSpinner;
|
||||||
private @MonotonicNonNull Spinner resolutionHeightSpinner;
|
private @MonotonicNonNull Spinner resolutionHeightSpinner;
|
||||||
@ -153,11 +226,12 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
private @MonotonicNonNull CheckBox trimCheckBox;
|
private @MonotonicNonNull CheckBox trimCheckBox;
|
||||||
private @MonotonicNonNull CheckBox enableFallbackCheckBox;
|
private @MonotonicNonNull CheckBox enableFallbackCheckBox;
|
||||||
private @MonotonicNonNull CheckBox enableDebugPreviewCheckBox;
|
private @MonotonicNonNull CheckBox enableDebugPreviewCheckBox;
|
||||||
private @MonotonicNonNull CheckBox enableRequestSdrToneMappingCheckBox;
|
private @MonotonicNonNull CheckBox abortSlowExportCheckBox;
|
||||||
private @MonotonicNonNull CheckBox forceInterpretHdrVideoAsSdrCheckBox;
|
private @MonotonicNonNull Spinner hdrModeSpinner;
|
||||||
private @MonotonicNonNull CheckBox enableHdrEditingCheckBox;
|
private @MonotonicNonNull Button selectAudioEffectsButton;
|
||||||
private @MonotonicNonNull Button selectDemoEffectsButton;
|
private @MonotonicNonNull Button selectVideoEffectsButton;
|
||||||
private boolean @MonotonicNonNull [] demoEffectsSelections;
|
private boolean @MonotonicNonNull [] audioEffectsSelections;
|
||||||
|
private boolean @MonotonicNonNull [] videoEffectsSelections;
|
||||||
private @Nullable Uri localFileUri;
|
private @Nullable Uri localFileUri;
|
||||||
private int inputUriPosition;
|
private int inputUriPosition;
|
||||||
private long trimStartMs;
|
private long trimStartMs;
|
||||||
@ -174,15 +248,36 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
private float periodicVignetteCenterY;
|
private float periodicVignetteCenterY;
|
||||||
private float periodicVignetteInnerRadius;
|
private float periodicVignetteInnerRadius;
|
||||||
private float periodicVignetteOuterRadius;
|
private float periodicVignetteOuterRadius;
|
||||||
|
private @MonotonicNonNull String bitmapOverlayUri;
|
||||||
|
private float bitmapOverlayAlpha;
|
||||||
|
private @MonotonicNonNull String textOverlayText;
|
||||||
|
private int textOverlayTextColor;
|
||||||
|
private float textOverlayAlpha;
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
protected void onCreate(@Nullable Bundle savedInstanceState) {
|
||||||
super.onCreate(savedInstanceState);
|
super.onCreate(savedInstanceState);
|
||||||
setContentView(R.layout.configuration_activity);
|
setContentView(R.layout.configuration_activity);
|
||||||
|
|
||||||
findViewById(R.id.transform_button).setOnClickListener(this::startTransformation);
|
findViewById(R.id.export_button).setOnClickListener(this::startExport);
|
||||||
|
|
||||||
flattenForSlowMotionCheckbox = findViewById(R.id.flatten_for_slow_motion_checkbox);
|
videoLocalFilePickerLauncher =
|
||||||
|
registerForActivityResult(
|
||||||
|
new ActivityResultContracts.StartActivityForResult(),
|
||||||
|
this::videoLocalFilePickerLauncherResult);
|
||||||
|
overlayLocalFilePickerLauncher =
|
||||||
|
registerForActivityResult(
|
||||||
|
new ActivityResultContracts.StartActivityForResult(),
|
||||||
|
this::overlayLocalFilePickerLauncherResult);
|
||||||
|
|
||||||
|
selectPresetFileButton = findViewById(R.id.select_preset_file_button);
|
||||||
|
selectPresetFileButton.setOnClickListener(this::selectPresetFile);
|
||||||
|
|
||||||
|
selectLocalFileButton = findViewById(R.id.select_local_file_button);
|
||||||
|
selectLocalFileButton.setOnClickListener(
|
||||||
|
view ->
|
||||||
|
selectLocalFile(
|
||||||
|
view, checkNotNull(videoLocalFilePickerLauncher), /* mimeType= */ "video/*"));
|
||||||
|
|
||||||
selectedFileTextView = findViewById(R.id.selected_file_text_view);
|
selectedFileTextView = findViewById(R.id.selected_file_text_view);
|
||||||
selectedFileTextView.setText(PRESET_FILE_URI_DESCRIPTIONS[inputUriPosition]);
|
selectedFileTextView.setText(PRESET_FILE_URI_DESCRIPTIONS[inputUriPosition]);
|
||||||
@ -193,11 +288,9 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
removeVideoCheckbox = findViewById(R.id.remove_video_checkbox);
|
removeVideoCheckbox = findViewById(R.id.remove_video_checkbox);
|
||||||
removeVideoCheckbox.setOnClickListener(this::onRemoveVideo);
|
removeVideoCheckbox.setOnClickListener(this::onRemoveVideo);
|
||||||
|
|
||||||
selectPresetFileButton = findViewById(R.id.select_preset_file_button);
|
flattenForSlowMotionCheckbox = findViewById(R.id.flatten_for_slow_motion_checkbox);
|
||||||
selectPresetFileButton.setOnClickListener(this::selectPresetFile);
|
|
||||||
|
|
||||||
selectLocalFileButton = findViewById(R.id.select_local_file_button);
|
forceAudioTrackCheckbox = findViewById(R.id.force_audio_track_checkbox);
|
||||||
selectLocalFileButton.setOnClickListener(this::selectLocalFile);
|
|
||||||
|
|
||||||
ArrayAdapter<String> audioMimeAdapter =
|
ArrayAdapter<String> audioMimeAdapter =
|
||||||
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
|
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
|
||||||
@ -214,7 +307,7 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
videoMimeSpinner.setAdapter(videoMimeAdapter);
|
videoMimeSpinner.setAdapter(videoMimeAdapter);
|
||||||
videoMimeAdapter.addAll(
|
videoMimeAdapter.addAll(
|
||||||
SAME_AS_INPUT_OPTION, MimeTypes.VIDEO_H263, MimeTypes.VIDEO_H264, MimeTypes.VIDEO_MP4V);
|
SAME_AS_INPUT_OPTION, MimeTypes.VIDEO_H263, MimeTypes.VIDEO_H264, MimeTypes.VIDEO_MP4V);
|
||||||
if (Util.SDK_INT >= 24) {
|
if (SDK_INT >= 24) {
|
||||||
videoMimeAdapter.add(MimeTypes.VIDEO_H265);
|
videoMimeAdapter.add(MimeTypes.VIDEO_H265);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -247,21 +340,23 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
enableFallbackCheckBox = findViewById(R.id.enable_fallback_checkbox);
|
enableFallbackCheckBox = findViewById(R.id.enable_fallback_checkbox);
|
||||||
enableDebugPreviewCheckBox = findViewById(R.id.enable_debug_preview_checkbox);
|
enableDebugPreviewCheckBox = findViewById(R.id.enable_debug_preview_checkbox);
|
||||||
enableRequestSdrToneMappingCheckBox = findViewById(R.id.request_sdr_tone_mapping_checkbox);
|
|
||||||
enableRequestSdrToneMappingCheckBox.setEnabled(isRequestSdrToneMappingSupported());
|
|
||||||
findViewById(R.id.request_sdr_tone_mapping).setEnabled(isRequestSdrToneMappingSupported());
|
|
||||||
forceInterpretHdrVideoAsSdrCheckBox =
|
|
||||||
findViewById(R.id.force_interpret_hdr_video_as_sdr_checkbox);
|
|
||||||
enableHdrEditingCheckBox = findViewById(R.id.hdr_editing_checkbox);
|
|
||||||
|
|
||||||
demoEffectsSelections = new boolean[DEMO_EFFECTS.length];
|
abortSlowExportCheckBox = findViewById(R.id.abort_slow_export_checkbox);
|
||||||
selectDemoEffectsButton = findViewById(R.id.select_demo_effects_button);
|
|
||||||
selectDemoEffectsButton.setOnClickListener(this::selectDemoEffects);
|
|
||||||
|
|
||||||
localFilePickerLauncher =
|
ArrayAdapter<String> hdrModeAdapter =
|
||||||
registerForActivityResult(
|
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
|
||||||
new ActivityResultContracts.StartActivityForResult(),
|
hdrModeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||||
this::localFilePickerLauncherResult);
|
hdrModeSpinner = findViewById(R.id.hdr_mode_spinner);
|
||||||
|
hdrModeSpinner.setAdapter(hdrModeAdapter);
|
||||||
|
hdrModeAdapter.addAll(HDR_MODE_DESCRIPTIONS.keySet());
|
||||||
|
|
||||||
|
audioEffectsSelections = new boolean[AUDIO_EFFECTS.length];
|
||||||
|
selectAudioEffectsButton = findViewById(R.id.select_audio_effects_button);
|
||||||
|
selectAudioEffectsButton.setOnClickListener(this::selectAudioEffects);
|
||||||
|
|
||||||
|
videoEffectsSelections = new boolean[VIDEO_EFFECTS.length];
|
||||||
|
selectVideoEffectsButton = findViewById(R.id.select_video_effects_button);
|
||||||
|
selectVideoEffectsButton.setOnClickListener(this::selectVideoEffects);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -272,7 +367,7 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
if (requestCode == FILE_PERMISSION_REQUEST_CODE
|
if (requestCode == FILE_PERMISSION_REQUEST_CODE
|
||||||
&& grantResults.length == 1
|
&& grantResults.length == 1
|
||||||
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
&& grantResults[0] == PackageManager.PERMISSION_GRANTED) {
|
||||||
launchLocalFilePicker();
|
checkNotNull(onPermissionsGranted).run();
|
||||||
} else {
|
} else {
|
||||||
Toast.makeText(
|
Toast.makeText(
|
||||||
getApplicationContext(), getString(R.string.permission_denied), Toast.LENGTH_LONG)
|
getApplicationContext(), getString(R.string.permission_denied), Toast.LENGTH_LONG)
|
||||||
@ -301,6 +396,7 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
"removeAudioCheckbox",
|
"removeAudioCheckbox",
|
||||||
"removeVideoCheckbox",
|
"removeVideoCheckbox",
|
||||||
"flattenForSlowMotionCheckbox",
|
"flattenForSlowMotionCheckbox",
|
||||||
|
"forceAudioTrackCheckbox",
|
||||||
"audioMimeSpinner",
|
"audioMimeSpinner",
|
||||||
"videoMimeSpinner",
|
"videoMimeSpinner",
|
||||||
"resolutionHeightSpinner",
|
"resolutionHeightSpinner",
|
||||||
@ -309,17 +405,18 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
"trimCheckBox",
|
"trimCheckBox",
|
||||||
"enableFallbackCheckBox",
|
"enableFallbackCheckBox",
|
||||||
"enableDebugPreviewCheckBox",
|
"enableDebugPreviewCheckBox",
|
||||||
"enableRequestSdrToneMappingCheckBox",
|
"abortSlowExportCheckBox",
|
||||||
"forceInterpretHdrVideoAsSdrCheckBox",
|
"hdrModeSpinner",
|
||||||
"enableHdrEditingCheckBox",
|
"audioEffectsSelections",
|
||||||
"demoEffectsSelections"
|
"videoEffectsSelections"
|
||||||
})
|
})
|
||||||
private void startTransformation(View view) {
|
private void startExport(View view) {
|
||||||
Intent transformerIntent = new Intent(/* packageContext= */ 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());
|
||||||
bundle.putBoolean(SHOULD_FLATTEN_FOR_SLOW_MOTION, flattenForSlowMotionCheckbox.isChecked());
|
bundle.putBoolean(SHOULD_FLATTEN_FOR_SLOW_MOTION, flattenForSlowMotionCheckbox.isChecked());
|
||||||
|
bundle.putBoolean(FORCE_AUDIO_TRACK, forceAudioTrackCheckbox.isChecked());
|
||||||
String selectedAudioMimeType = String.valueOf(audioMimeSpinner.getSelectedItem());
|
String selectedAudioMimeType = String.valueOf(audioMimeSpinner.getSelectedItem());
|
||||||
if (!SAME_AS_INPUT_OPTION.equals(selectedAudioMimeType)) {
|
if (!SAME_AS_INPUT_OPTION.equals(selectedAudioMimeType)) {
|
||||||
bundle.putString(AUDIO_MIME_TYPE, selectedAudioMimeType);
|
bundle.putString(AUDIO_MIME_TYPE, selectedAudioMimeType);
|
||||||
@ -349,12 +446,11 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
bundle.putBoolean(ENABLE_FALLBACK, enableFallbackCheckBox.isChecked());
|
bundle.putBoolean(ENABLE_FALLBACK, enableFallbackCheckBox.isChecked());
|
||||||
bundle.putBoolean(ENABLE_DEBUG_PREVIEW, enableDebugPreviewCheckBox.isChecked());
|
bundle.putBoolean(ENABLE_DEBUG_PREVIEW, enableDebugPreviewCheckBox.isChecked());
|
||||||
bundle.putBoolean(
|
bundle.putBoolean(ABORT_SLOW_EXPORT, abortSlowExportCheckBox.isChecked());
|
||||||
ENABLE_REQUEST_SDR_TONE_MAPPING, enableRequestSdrToneMappingCheckBox.isChecked());
|
String selectedhdrMode = String.valueOf(hdrModeSpinner.getSelectedItem());
|
||||||
bundle.putBoolean(
|
bundle.putInt(HDR_MODE, checkNotNull(HDR_MODE_DESCRIPTIONS.get(selectedhdrMode)));
|
||||||
FORCE_INTERPRET_HDR_VIDEO_AS_SDR, forceInterpretHdrVideoAsSdrCheckBox.isChecked());
|
bundle.putBooleanArray(AUDIO_EFFECTS_SELECTIONS, audioEffectsSelections);
|
||||||
bundle.putBoolean(ENABLE_HDR_EDITING, enableHdrEditingCheckBox.isChecked());
|
bundle.putBooleanArray(VIDEO_EFFECTS_SELECTIONS, videoEffectsSelections);
|
||||||
bundle.putBooleanArray(DEMO_EFFECTS_SELECTIONS, demoEffectsSelections);
|
|
||||||
bundle.putInt(COLOR_FILTER_SELECTION, colorFilterSelection);
|
bundle.putInt(COLOR_FILTER_SELECTION, colorFilterSelection);
|
||||||
bundle.putFloat(CONTRAST_VALUE, contrastValue);
|
bundle.putFloat(CONTRAST_VALUE, contrastValue);
|
||||||
bundle.putFloat(RGB_ADJUSTMENT_RED_SCALE, rgbAdjustmentRedScale);
|
bundle.putFloat(RGB_ADJUSTMENT_RED_SCALE, rgbAdjustmentRedScale);
|
||||||
@ -367,6 +463,11 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
bundle.putFloat(PERIODIC_VIGNETTE_CENTER_Y, periodicVignetteCenterY);
|
bundle.putFloat(PERIODIC_VIGNETTE_CENTER_Y, periodicVignetteCenterY);
|
||||||
bundle.putFloat(PERIODIC_VIGNETTE_INNER_RADIUS, periodicVignetteInnerRadius);
|
bundle.putFloat(PERIODIC_VIGNETTE_INNER_RADIUS, periodicVignetteInnerRadius);
|
||||||
bundle.putFloat(PERIODIC_VIGNETTE_OUTER_RADIUS, periodicVignetteOuterRadius);
|
bundle.putFloat(PERIODIC_VIGNETTE_OUTER_RADIUS, periodicVignetteOuterRadius);
|
||||||
|
bundle.putString(BITMAP_OVERLAY_URI, bitmapOverlayUri);
|
||||||
|
bundle.putFloat(BITMAP_OVERLAY_ALPHA, bitmapOverlayAlpha);
|
||||||
|
bundle.putString(TEXT_OVERLAY_TEXT, textOverlayText);
|
||||||
|
bundle.putInt(TEXT_OVERLAY_TEXT_COLOR, textOverlayTextColor);
|
||||||
|
bundle.putFloat(TEXT_OVERLAY_ALPHA, textOverlayAlpha);
|
||||||
transformerIntent.putExtras(bundle);
|
transformerIntent.putExtras(bundle);
|
||||||
|
|
||||||
@Nullable Uri intentUri;
|
@Nullable Uri intentUri;
|
||||||
@ -399,39 +500,69 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
selectedFileTextView.setText(PRESET_FILE_URI_DESCRIPTIONS[inputUriPosition]);
|
selectedFileTextView.setText(PRESET_FILE_URI_DESCRIPTIONS[inputUriPosition]);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void selectLocalFile(View view) {
|
private void selectLocalFile(
|
||||||
int permissionStatus =
|
View view, ActivityResultLauncher<Intent> localFilePickerLauncher, String mimeType) {
|
||||||
ActivityCompat.checkSelfPermission(
|
String permission = SDK_INT >= 33 ? READ_MEDIA_VIDEO : READ_EXTERNAL_STORAGE;
|
||||||
ConfigurationActivity.this, Manifest.permission.READ_EXTERNAL_STORAGE);
|
if (ActivityCompat.checkSelfPermission(/* context= */ this, permission)
|
||||||
if (permissionStatus != PackageManager.PERMISSION_GRANTED) {
|
!= PackageManager.PERMISSION_GRANTED) {
|
||||||
String[] neededPermissions = {Manifest.permission.READ_EXTERNAL_STORAGE};
|
onPermissionsGranted = () -> launchLocalFilePicker(localFilePickerLauncher, mimeType);
|
||||||
ActivityCompat.requestPermissions(
|
ActivityCompat.requestPermissions(
|
||||||
ConfigurationActivity.this, neededPermissions, FILE_PERMISSION_REQUEST_CODE);
|
/* activity= */ this, new String[] {permission}, FILE_PERMISSION_REQUEST_CODE);
|
||||||
} else {
|
} else {
|
||||||
launchLocalFilePicker();
|
launchLocalFilePicker(localFilePickerLauncher, mimeType);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void launchLocalFilePicker() {
|
private void launchLocalFilePicker(
|
||||||
|
ActivityResultLauncher<Intent> localFilePickerLauncher, String mimeType) {
|
||||||
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
Intent intent = new Intent(Intent.ACTION_GET_CONTENT);
|
||||||
intent.setType("video/*");
|
intent.setType(mimeType);
|
||||||
checkNotNull(localFilePickerLauncher).launch(intent);
|
checkNotNull(localFilePickerLauncher).launch(intent);
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresNonNull("selectedFileTextView")
|
@RequiresNonNull("selectedFileTextView")
|
||||||
private void localFilePickerLauncherResult(ActivityResult result) {
|
private void videoLocalFilePickerLauncherResult(ActivityResult result) {
|
||||||
Intent data = result.getData();
|
Intent data = result.getData();
|
||||||
if (data != null) {
|
if (data != null) {
|
||||||
localFileUri = checkNotNull(data.getData());
|
localFileUri = checkNotNull(data.getData());
|
||||||
selectedFileTextView.setText(localFileUri.toString());
|
selectedFileTextView.setText(localFileUri.toString());
|
||||||
|
} else {
|
||||||
|
Toast.makeText(
|
||||||
|
getApplicationContext(),
|
||||||
|
getString(R.string.local_file_picker_failed),
|
||||||
|
Toast.LENGTH_SHORT)
|
||||||
|
.show();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void selectDemoEffects(View view) {
|
private void overlayLocalFilePickerLauncherResult(ActivityResult result) {
|
||||||
|
Intent data = result.getData();
|
||||||
|
if (data != null) {
|
||||||
|
bitmapOverlayUri = checkNotNull(data.getData()).toString();
|
||||||
|
} else {
|
||||||
|
Toast.makeText(
|
||||||
|
getApplicationContext(),
|
||||||
|
getString(R.string.local_file_picker_failed),
|
||||||
|
Toast.LENGTH_SHORT)
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private void selectAudioEffects(View view) {
|
||||||
new AlertDialog.Builder(/* context= */ this)
|
new AlertDialog.Builder(/* context= */ this)
|
||||||
.setTitle(R.string.select_demo_effects)
|
.setTitle(R.string.select_audio_effects)
|
||||||
.setMultiChoiceItems(
|
.setMultiChoiceItems(
|
||||||
DEMO_EFFECTS, checkNotNull(demoEffectsSelections), this::selectDemoEffect)
|
AUDIO_EFFECTS, checkNotNull(audioEffectsSelections), this::selectAudioEffect)
|
||||||
|
.setPositiveButton(android.R.string.ok, /* listener= */ null)
|
||||||
|
.create()
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void selectVideoEffects(View view) {
|
||||||
|
new AlertDialog.Builder(/* context= */ this)
|
||||||
|
.setTitle(R.string.select_video_effects)
|
||||||
|
.setMultiChoiceItems(
|
||||||
|
VIDEO_EFFECTS, checkNotNull(videoEffectsSelections), this::selectVideoEffect)
|
||||||
.setPositiveButton(android.R.string.ok, /* listener= */ null)
|
.setPositiveButton(android.R.string.ok, /* listener= */ null)
|
||||||
.create()
|
.create()
|
||||||
.show();
|
.show();
|
||||||
@ -444,7 +575,7 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
View dialogView = getLayoutInflater().inflate(R.layout.trim_options, /* root= */ null);
|
View dialogView = getLayoutInflater().inflate(R.layout.trim_options, /* root= */ null);
|
||||||
RangeSlider trimRangeSlider =
|
RangeSlider trimRangeSlider =
|
||||||
checkNotNull(dialogView.findViewById(R.id.trim_bounds_range_slider));
|
checkNotNull(dialogView.findViewById(R.id.trim_bounds_range_slider));
|
||||||
trimRangeSlider.setValues(0f, 10f); // seconds
|
trimRangeSlider.setValues(0f, 1f); // seconds
|
||||||
new AlertDialog.Builder(/* context= */ this)
|
new AlertDialog.Builder(/* context= */ this)
|
||||||
.setView(dialogView)
|
.setView(dialogView)
|
||||||
.setPositiveButton(
|
.setPositiveButton(
|
||||||
@ -458,9 +589,14 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresNonNull("demoEffectsSelections")
|
@RequiresNonNull("audioEffectsSelections")
|
||||||
private void selectDemoEffect(DialogInterface dialog, int which, boolean isChecked) {
|
private void selectAudioEffect(DialogInterface dialog, int which, boolean isChecked) {
|
||||||
demoEffectsSelections[which] = isChecked;
|
audioEffectsSelections[which] = isChecked;
|
||||||
|
}
|
||||||
|
|
||||||
|
@RequiresNonNull("videoEffectsSelections")
|
||||||
|
private void selectVideoEffect(DialogInterface dialog, int which, boolean isChecked) {
|
||||||
|
videoEffectsSelections[which] = isChecked;
|
||||||
if (!isChecked) {
|
if (!isChecked) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -481,6 +617,12 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
case PERIODIC_VIGNETTE_INDEX:
|
case PERIODIC_VIGNETTE_INDEX:
|
||||||
controlPeriodicVignetteSettings();
|
controlPeriodicVignetteSettings();
|
||||||
break;
|
break;
|
||||||
|
case BITMAP_OVERLAY_INDEX:
|
||||||
|
controlBitmapOverlaySettings();
|
||||||
|
break;
|
||||||
|
case TEXT_OVERLAY_INDEX:
|
||||||
|
controlTextOverlaySettings();
|
||||||
|
break;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -583,18 +725,66 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
.show();
|
.show();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void controlBitmapOverlaySettings() {
|
||||||
|
View dialogView =
|
||||||
|
getLayoutInflater().inflate(R.layout.bitmap_overlay_options, /* root= */ null);
|
||||||
|
Button uriButton = checkNotNull(dialogView.findViewById(R.id.bitmap_overlay_uri));
|
||||||
|
uriButton.setOnClickListener(
|
||||||
|
(view ->
|
||||||
|
selectLocalFile(
|
||||||
|
view, checkNotNull(overlayLocalFilePickerLauncher), /* mimeType= */ "image/*")));
|
||||||
|
Slider alphaSlider = checkNotNull(dialogView.findViewById(R.id.bitmap_overlay_alpha_slider));
|
||||||
|
new AlertDialog.Builder(/* context= */ this)
|
||||||
|
.setTitle(R.string.bitmap_overlay_settings)
|
||||||
|
.setView(dialogView)
|
||||||
|
.setPositiveButton(
|
||||||
|
android.R.string.ok,
|
||||||
|
(DialogInterface dialogInterface, int i) -> {
|
||||||
|
bitmapOverlayAlpha = alphaSlider.getValue();
|
||||||
|
})
|
||||||
|
.create()
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void controlTextOverlaySettings() {
|
||||||
|
View dialogView = getLayoutInflater().inflate(R.layout.text_overlay_options, /* root= */ null);
|
||||||
|
EditText textEditText = checkNotNull(dialogView.findViewById(R.id.text_overlay_text));
|
||||||
|
|
||||||
|
ArrayAdapter<String> textColorAdapter =
|
||||||
|
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
|
||||||
|
textColorAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
|
||||||
|
Spinner textColorSpinner = checkNotNull(dialogView.findViewById(R.id.text_overlay_text_color));
|
||||||
|
textColorSpinner.setAdapter(textColorAdapter);
|
||||||
|
textColorAdapter.addAll(OVERLAY_COLORS.keySet());
|
||||||
|
|
||||||
|
Slider alphaSlider = checkNotNull(dialogView.findViewById(R.id.text_overlay_alpha_slider));
|
||||||
|
new AlertDialog.Builder(/* context= */ this)
|
||||||
|
.setTitle(R.string.bitmap_overlay_settings)
|
||||||
|
.setView(dialogView)
|
||||||
|
.setPositiveButton(
|
||||||
|
android.R.string.ok,
|
||||||
|
(DialogInterface dialogInterface, int i) -> {
|
||||||
|
textOverlayText = textEditText.getText().toString();
|
||||||
|
String selectedTextColor = String.valueOf(textColorSpinner.getSelectedItem());
|
||||||
|
textOverlayTextColor = checkNotNull(OVERLAY_COLORS.get(selectedTextColor));
|
||||||
|
textOverlayAlpha = alphaSlider.getValue();
|
||||||
|
})
|
||||||
|
.create()
|
||||||
|
.show();
|
||||||
|
}
|
||||||
|
|
||||||
@RequiresNonNull({
|
@RequiresNonNull({
|
||||||
"removeVideoCheckbox",
|
"removeVideoCheckbox",
|
||||||
|
"forceAudioTrackCheckbox",
|
||||||
"audioMimeSpinner",
|
"audioMimeSpinner",
|
||||||
"videoMimeSpinner",
|
"videoMimeSpinner",
|
||||||
"resolutionHeightSpinner",
|
"resolutionHeightSpinner",
|
||||||
"scaleSpinner",
|
"scaleSpinner",
|
||||||
"rotateSpinner",
|
"rotateSpinner",
|
||||||
"enableDebugPreviewCheckBox",
|
"enableDebugPreviewCheckBox",
|
||||||
"enableRequestSdrToneMappingCheckBox",
|
"hdrModeSpinner",
|
||||||
"forceInterpretHdrVideoAsSdrCheckBox",
|
"selectAudioEffectsButton",
|
||||||
"enableHdrEditingCheckBox",
|
"selectVideoEffectsButton"
|
||||||
"selectDemoEffectsButton"
|
|
||||||
})
|
})
|
||||||
private void onRemoveAudio(View view) {
|
private void onRemoveAudio(View view) {
|
||||||
if (((CheckBox) view).isChecked()) {
|
if (((CheckBox) view).isChecked()) {
|
||||||
@ -607,16 +797,16 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
@RequiresNonNull({
|
@RequiresNonNull({
|
||||||
"removeAudioCheckbox",
|
"removeAudioCheckbox",
|
||||||
|
"forceAudioTrackCheckbox",
|
||||||
"audioMimeSpinner",
|
"audioMimeSpinner",
|
||||||
"videoMimeSpinner",
|
"videoMimeSpinner",
|
||||||
"resolutionHeightSpinner",
|
"resolutionHeightSpinner",
|
||||||
"scaleSpinner",
|
"scaleSpinner",
|
||||||
"rotateSpinner",
|
"rotateSpinner",
|
||||||
"enableDebugPreviewCheckBox",
|
"enableDebugPreviewCheckBox",
|
||||||
"enableRequestSdrToneMappingCheckBox",
|
"hdrModeSpinner",
|
||||||
"forceInterpretHdrVideoAsSdrCheckBox",
|
"selectAudioEffectsButton",
|
||||||
"enableHdrEditingCheckBox",
|
"selectVideoEffectsButton"
|
||||||
"selectDemoEffectsButton"
|
|
||||||
})
|
})
|
||||||
private void onRemoveVideo(View view) {
|
private void onRemoveVideo(View view) {
|
||||||
if (((CheckBox) view).isChecked()) {
|
if (((CheckBox) view).isChecked()) {
|
||||||
@ -628,42 +818,34 @@ public final class ConfigurationActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@RequiresNonNull({
|
@RequiresNonNull({
|
||||||
|
"forceAudioTrackCheckbox",
|
||||||
"audioMimeSpinner",
|
"audioMimeSpinner",
|
||||||
"videoMimeSpinner",
|
"videoMimeSpinner",
|
||||||
"resolutionHeightSpinner",
|
"resolutionHeightSpinner",
|
||||||
"scaleSpinner",
|
"scaleSpinner",
|
||||||
"rotateSpinner",
|
"rotateSpinner",
|
||||||
"enableDebugPreviewCheckBox",
|
"enableDebugPreviewCheckBox",
|
||||||
"enableRequestSdrToneMappingCheckBox",
|
"hdrModeSpinner",
|
||||||
"forceInterpretHdrVideoAsSdrCheckBox",
|
"selectAudioEffectsButton",
|
||||||
"enableHdrEditingCheckBox",
|
"selectVideoEffectsButton"
|
||||||
"selectDemoEffectsButton"
|
|
||||||
})
|
})
|
||||||
private void enableTrackSpecificOptions(boolean isAudioEnabled, boolean isVideoEnabled) {
|
private void enableTrackSpecificOptions(boolean isAudioEnabled, boolean isVideoEnabled) {
|
||||||
|
forceAudioTrackCheckbox.setEnabled(isVideoEnabled);
|
||||||
audioMimeSpinner.setEnabled(isAudioEnabled);
|
audioMimeSpinner.setEnabled(isAudioEnabled);
|
||||||
videoMimeSpinner.setEnabled(isVideoEnabled);
|
videoMimeSpinner.setEnabled(isVideoEnabled);
|
||||||
resolutionHeightSpinner.setEnabled(isVideoEnabled);
|
resolutionHeightSpinner.setEnabled(isVideoEnabled);
|
||||||
scaleSpinner.setEnabled(isVideoEnabled);
|
scaleSpinner.setEnabled(isVideoEnabled);
|
||||||
rotateSpinner.setEnabled(isVideoEnabled);
|
rotateSpinner.setEnabled(isVideoEnabled);
|
||||||
enableDebugPreviewCheckBox.setEnabled(isVideoEnabled);
|
enableDebugPreviewCheckBox.setEnabled(isVideoEnabled);
|
||||||
enableRequestSdrToneMappingCheckBox.setEnabled(
|
hdrModeSpinner.setEnabled(isVideoEnabled);
|
||||||
isRequestSdrToneMappingSupported() && isVideoEnabled);
|
selectAudioEffectsButton.setEnabled(isAudioEnabled);
|
||||||
forceInterpretHdrVideoAsSdrCheckBox.setEnabled(isVideoEnabled);
|
selectVideoEffectsButton.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.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)
|
findViewById(R.id.hdr_mode).setEnabled(isVideoEnabled);
|
||||||
.setEnabled(isRequestSdrToneMappingSupported() && isVideoEnabled);
|
|
||||||
findViewById(R.id.force_interpret_hdr_video_as_sdr).setEnabled(isVideoEnabled);
|
|
||||||
findViewById(R.id.hdr_editing).setEnabled(isVideoEnabled);
|
|
||||||
}
|
|
||||||
|
|
||||||
private static boolean isRequestSdrToneMappingSupported() {
|
|
||||||
return Util.SDK_INT >= 31;
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -19,18 +19,18 @@ import static androidx.media3.common.util.Assertions.checkArgument;
|
|||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.opengl.GLES20;
|
import android.opengl.GLES20;
|
||||||
import android.util.Pair;
|
import androidx.media3.common.VideoFrameProcessingException;
|
||||||
import androidx.media3.common.FrameProcessingException;
|
|
||||||
import androidx.media3.common.util.GlProgram;
|
import androidx.media3.common.util.GlProgram;
|
||||||
import androidx.media3.common.util.GlUtil;
|
import androidx.media3.common.util.GlUtil;
|
||||||
import androidx.media3.effect.SingleFrameGlTextureProcessor;
|
import androidx.media3.common.util.Size;
|
||||||
|
import androidx.media3.effect.SingleFrameGlShaderProgram;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link SingleFrameGlTextureProcessor} that periodically dims the frames such that pixels are
|
* A {@link SingleFrameGlShaderProgram} that periodically dims the frames such that pixels are
|
||||||
* darker the further they are away from the frame center.
|
* darker the further they are away from the frame center.
|
||||||
*/
|
*/
|
||||||
/* package */ final class PeriodicVignetteProcessor extends SingleFrameGlTextureProcessor {
|
/* package */ final class PeriodicVignetteShaderProgram extends SingleFrameGlShaderProgram {
|
||||||
|
|
||||||
private static final String VERTEX_SHADER_PATH = "vertex_shader_copy_es2.glsl";
|
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 String FRAGMENT_SHADER_PATH = "fragment_shader_vignette_es2.glsl";
|
||||||
@ -59,9 +59,9 @@ import java.io.IOException;
|
|||||||
* @param minInnerRadius The lower bound of the radius that is unaffected by 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 maxInnerRadius The upper bound of the radius that is unaffected by the effect.
|
||||||
* @param outerRadius The radius after which all pixels are black.
|
* @param outerRadius The radius after which all pixels are black.
|
||||||
* @throws FrameProcessingException If a problem occurs while reading shader files.
|
* @throws VideoFrameProcessingException If a problem occurs while reading shader files.
|
||||||
*/
|
*/
|
||||||
public PeriodicVignetteProcessor(
|
public PeriodicVignetteShaderProgram(
|
||||||
Context context,
|
Context context,
|
||||||
boolean useHdr,
|
boolean useHdr,
|
||||||
float centerX,
|
float centerX,
|
||||||
@ -69,7 +69,7 @@ import java.io.IOException;
|
|||||||
float minInnerRadius,
|
float minInnerRadius,
|
||||||
float maxInnerRadius,
|
float maxInnerRadius,
|
||||||
float outerRadius)
|
float outerRadius)
|
||||||
throws FrameProcessingException {
|
throws VideoFrameProcessingException {
|
||||||
super(useHdr);
|
super(useHdr);
|
||||||
checkArgument(minInnerRadius <= maxInnerRadius);
|
checkArgument(minInnerRadius <= maxInnerRadius);
|
||||||
checkArgument(maxInnerRadius <= outerRadius);
|
checkArgument(maxInnerRadius <= outerRadius);
|
||||||
@ -78,7 +78,7 @@ import java.io.IOException;
|
|||||||
try {
|
try {
|
||||||
glProgram = new GlProgram(context, VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH);
|
glProgram = new GlProgram(context, VERTEX_SHADER_PATH, FRAGMENT_SHADER_PATH);
|
||||||
} catch (IOException | GlUtil.GlException e) {
|
} catch (IOException | GlUtil.GlException e) {
|
||||||
throw new FrameProcessingException(e);
|
throw new VideoFrameProcessingException(e);
|
||||||
}
|
}
|
||||||
glProgram.setFloatsUniform("uCenter", new float[] {centerX, centerY});
|
glProgram.setFloatsUniform("uCenter", new float[] {centerX, centerY});
|
||||||
glProgram.setFloatsUniform("uOuterRadius", new float[] {outerRadius});
|
glProgram.setFloatsUniform("uOuterRadius", new float[] {outerRadius});
|
||||||
@ -90,12 +90,13 @@ import java.io.IOException;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public Pair<Integer, Integer> configure(int inputWidth, int inputHeight) {
|
public Size configure(int inputWidth, int inputHeight) {
|
||||||
return Pair.create(inputWidth, inputHeight);
|
return new Size(inputWidth, inputHeight);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void drawFrame(int inputTexId, long presentationTimeUs) throws FrameProcessingException {
|
public void drawFrame(int inputTexId, long presentationTimeUs)
|
||||||
|
throws VideoFrameProcessingException {
|
||||||
try {
|
try {
|
||||||
glProgram.use();
|
glProgram.use();
|
||||||
glProgram.setSamplerTexIdUniform("uTexSampler", inputTexId, /* texUnitIndex= */ 0);
|
glProgram.setSamplerTexIdUniform("uTexSampler", inputTexId, /* texUnitIndex= */ 0);
|
||||||
@ -107,17 +108,17 @@ import java.io.IOException;
|
|||||||
// The four-vertex triangle strip forms a quad.
|
// The four-vertex triangle strip forms a quad.
|
||||||
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4);
|
GLES20.glDrawArrays(GLES20.GL_TRIANGLE_STRIP, /* first= */ 0, /* count= */ 4);
|
||||||
} catch (GlUtil.GlException e) {
|
} catch (GlUtil.GlException e) {
|
||||||
throw new FrameProcessingException(e, presentationTimeUs);
|
throw new VideoFrameProcessingException(e, presentationTimeUs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void release() throws FrameProcessingException {
|
public void release() throws VideoFrameProcessingException {
|
||||||
super.release();
|
super.release();
|
||||||
try {
|
try {
|
||||||
glProgram.delete();
|
glProgram.delete();
|
||||||
} catch (GlUtil.GlException e) {
|
} catch (GlUtil.GlException e) {
|
||||||
throw new FrameProcessingException(e);
|
throw new VideoFrameProcessingException(e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -0,0 +1,66 @@
|
|||||||
|
/*
|
||||||
|
* 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.Color;
|
||||||
|
import android.opengl.Matrix;
|
||||||
|
import android.text.Spannable;
|
||||||
|
import android.text.SpannableString;
|
||||||
|
import android.text.style.ForegroundColorSpan;
|
||||||
|
import androidx.media3.common.C;
|
||||||
|
import androidx.media3.common.util.GlUtil;
|
||||||
|
import androidx.media3.effect.OverlaySettings;
|
||||||
|
import androidx.media3.effect.TextOverlay;
|
||||||
|
import androidx.media3.effect.TextureOverlay;
|
||||||
|
import java.util.Locale;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link TextureOverlay} that displays a "time elapsed" timer in the bottom left corner of the
|
||||||
|
* frame.
|
||||||
|
*/
|
||||||
|
/* package */ final class TimerOverlay extends TextOverlay {
|
||||||
|
|
||||||
|
private final OverlaySettings overlaySettings;
|
||||||
|
|
||||||
|
public TimerOverlay() {
|
||||||
|
float[] positioningMatrix = GlUtil.create4x4IdentityMatrix();
|
||||||
|
Matrix.translateM(
|
||||||
|
positioningMatrix, /* mOffset= */ 0, /* x= */ -0.7f, /* y= */ -0.95f, /* z= */ 1);
|
||||||
|
overlaySettings =
|
||||||
|
new OverlaySettings.Builder()
|
||||||
|
.setAnchor(/* x= */ -1f, /* y= */ -1f)
|
||||||
|
.setMatrix(positioningMatrix)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public SpannableString getText(long presentationTimeUs) {
|
||||||
|
SpannableString text =
|
||||||
|
new SpannableString(
|
||||||
|
String.format(Locale.US, "%.02f", presentationTimeUs / (float) C.MICROS_PER_SECOND));
|
||||||
|
text.setSpan(
|
||||||
|
new ForegroundColorSpan(Color.WHITE),
|
||||||
|
/* start= */ 0,
|
||||||
|
text.length(),
|
||||||
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
return text;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public OverlaySettings getOverlaySettings(long presentationTimeUs) {
|
||||||
|
return overlaySettings;
|
||||||
|
}
|
||||||
|
}
|
@ -16,48 +16,78 @@
|
|||||||
package androidx.media3.demo.transformer;
|
package androidx.media3.demo.transformer;
|
||||||
|
|
||||||
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
|
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
|
||||||
|
import static android.Manifest.permission.READ_MEDIA_VIDEO;
|
||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
import static androidx.media3.common.util.Assertions.checkState;
|
import static androidx.media3.common.util.Assertions.checkState;
|
||||||
|
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||||
|
import static androidx.media3.common.util.Util.SDK_INT;
|
||||||
|
import static androidx.media3.transformer.Transformer.PROGRESS_STATE_NOT_STARTED;
|
||||||
|
|
||||||
import android.app.Activity;
|
import android.app.Activity;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.Intent;
|
import android.content.Intent;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
import android.graphics.Color;
|
import android.graphics.Color;
|
||||||
|
import android.graphics.drawable.Drawable;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
|
import android.opengl.Matrix;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
|
import android.text.Spannable;
|
||||||
|
import android.text.SpannableString;
|
||||||
|
import android.text.style.ForegroundColorSpan;
|
||||||
import android.view.SurfaceHolder;
|
import android.view.SurfaceHolder;
|
||||||
import android.view.SurfaceView;
|
import android.view.SurfaceView;
|
||||||
import android.view.View;
|
import android.view.View;
|
||||||
import android.view.ViewGroup;
|
import android.view.ViewGroup;
|
||||||
import android.widget.Button;
|
import android.widget.Button;
|
||||||
|
import android.widget.ImageView;
|
||||||
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.annotation.StringRes;
|
||||||
import androidx.appcompat.app.AppCompatActivity;
|
import androidx.appcompat.app.AppCompatActivity;
|
||||||
|
import androidx.core.app.ActivityCompat;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.DebugViewProvider;
|
import androidx.media3.common.DebugViewProvider;
|
||||||
import androidx.media3.common.Effect;
|
import androidx.media3.common.Effect;
|
||||||
import androidx.media3.common.MediaItem;
|
import androidx.media3.common.MediaItem;
|
||||||
|
import androidx.media3.common.audio.AudioProcessor;
|
||||||
|
import androidx.media3.common.audio.SonicAudioProcessor;
|
||||||
|
import androidx.media3.common.util.BitmapLoader;
|
||||||
|
import androidx.media3.common.util.GlUtil;
|
||||||
import androidx.media3.common.util.Log;
|
import androidx.media3.common.util.Log;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.datasource.DataSourceBitmapLoader;
|
||||||
|
import androidx.media3.effect.BitmapOverlay;
|
||||||
import androidx.media3.effect.Contrast;
|
import androidx.media3.effect.Contrast;
|
||||||
|
import androidx.media3.effect.DrawableOverlay;
|
||||||
import androidx.media3.effect.GlEffect;
|
import androidx.media3.effect.GlEffect;
|
||||||
import androidx.media3.effect.GlTextureProcessor;
|
import androidx.media3.effect.GlShaderProgram;
|
||||||
import androidx.media3.effect.HslAdjustment;
|
import androidx.media3.effect.HslAdjustment;
|
||||||
|
import androidx.media3.effect.OverlayEffect;
|
||||||
|
import androidx.media3.effect.OverlaySettings;
|
||||||
|
import androidx.media3.effect.Presentation;
|
||||||
import androidx.media3.effect.RgbAdjustment;
|
import androidx.media3.effect.RgbAdjustment;
|
||||||
import androidx.media3.effect.RgbFilter;
|
import androidx.media3.effect.RgbFilter;
|
||||||
import androidx.media3.effect.RgbMatrix;
|
import androidx.media3.effect.RgbMatrix;
|
||||||
|
import androidx.media3.effect.ScaleAndRotateTransformation;
|
||||||
import androidx.media3.effect.SingleColorLut;
|
import androidx.media3.effect.SingleColorLut;
|
||||||
|
import androidx.media3.effect.TextOverlay;
|
||||||
|
import androidx.media3.effect.TextureOverlay;
|
||||||
import androidx.media3.exoplayer.ExoPlayer;
|
import androidx.media3.exoplayer.ExoPlayer;
|
||||||
|
import androidx.media3.exoplayer.audio.SilenceSkippingAudioProcessor;
|
||||||
import androidx.media3.exoplayer.util.DebugTextViewHelper;
|
import androidx.media3.exoplayer.util.DebugTextViewHelper;
|
||||||
|
import androidx.media3.transformer.Composition;
|
||||||
import androidx.media3.transformer.DefaultEncoderFactory;
|
import androidx.media3.transformer.DefaultEncoderFactory;
|
||||||
|
import androidx.media3.transformer.DefaultMuxer;
|
||||||
|
import androidx.media3.transformer.EditedMediaItem;
|
||||||
|
import androidx.media3.transformer.EditedMediaItemSequence;
|
||||||
|
import androidx.media3.transformer.Effects;
|
||||||
|
import androidx.media3.transformer.ExportException;
|
||||||
|
import androidx.media3.transformer.ExportResult;
|
||||||
import androidx.media3.transformer.ProgressHolder;
|
import androidx.media3.transformer.ProgressHolder;
|
||||||
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;
|
||||||
@ -66,27 +96,34 @@ 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 com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
import java.io.File;
|
import java.io.File;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.reflect.Constructor;
|
import java.lang.reflect.Constructor;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.TimeUnit;
|
import java.util.concurrent.TimeUnit;
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
|
||||||
|
|
||||||
/** An {@link Activity} that transforms and plays media using {@link Transformer}. */
|
/** An {@link Activity} that exports and plays media using {@link Transformer}. */
|
||||||
public final class TransformerActivity extends AppCompatActivity {
|
public final class TransformerActivity extends AppCompatActivity {
|
||||||
private static final String TAG = "TransformerActivity";
|
private static final String TAG = "TransformerActivity";
|
||||||
|
|
||||||
private @MonotonicNonNull Button displayInputButton;
|
private @MonotonicNonNull Button displayInputButton;
|
||||||
private @MonotonicNonNull MaterialCardView inputCardView;
|
private @MonotonicNonNull MaterialCardView inputCardView;
|
||||||
|
private @MonotonicNonNull TextView inputTextView;
|
||||||
|
private @MonotonicNonNull ImageView inputImageView;
|
||||||
private @MonotonicNonNull PlayerView inputPlayerView;
|
private @MonotonicNonNull PlayerView inputPlayerView;
|
||||||
private @MonotonicNonNull PlayerView outputPlayerView;
|
private @MonotonicNonNull PlayerView outputPlayerView;
|
||||||
|
private @MonotonicNonNull TextView outputVideoTextView;
|
||||||
private @MonotonicNonNull TextView debugTextView;
|
private @MonotonicNonNull TextView debugTextView;
|
||||||
private @MonotonicNonNull TextView informationTextView;
|
private @MonotonicNonNull TextView informationTextView;
|
||||||
private @MonotonicNonNull ViewGroup progressViewGroup;
|
private @MonotonicNonNull ViewGroup progressViewGroup;
|
||||||
private @MonotonicNonNull LinearProgressIndicator progressIndicator;
|
private @MonotonicNonNull LinearProgressIndicator progressIndicator;
|
||||||
private @MonotonicNonNull Stopwatch transformationStopwatch;
|
private @MonotonicNonNull Stopwatch exportStopwatch;
|
||||||
private @MonotonicNonNull AspectRatioFrameLayout debugFrame;
|
private @MonotonicNonNull AspectRatioFrameLayout debugFrame;
|
||||||
|
|
||||||
@Nullable private DebugTextViewHelper debugTextViewHelper;
|
@Nullable private DebugTextViewHelper debugTextViewHelper;
|
||||||
@ -101,8 +138,11 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
setContentView(R.layout.transformer_activity);
|
setContentView(R.layout.transformer_activity);
|
||||||
|
|
||||||
inputCardView = findViewById(R.id.input_card_view);
|
inputCardView = findViewById(R.id.input_card_view);
|
||||||
|
inputTextView = findViewById(R.id.input_text_view);
|
||||||
|
inputImageView = findViewById(R.id.input_image_view);
|
||||||
inputPlayerView = findViewById(R.id.input_player_view);
|
inputPlayerView = findViewById(R.id.input_player_view);
|
||||||
outputPlayerView = findViewById(R.id.output_player_view);
|
outputPlayerView = findViewById(R.id.output_player_view);
|
||||||
|
outputVideoTextView = findViewById(R.id.output_video_text_view);
|
||||||
debugTextView = findViewById(R.id.debug_text_view);
|
debugTextView = findViewById(R.id.debug_text_view);
|
||||||
informationTextView = findViewById(R.id.information_text_view);
|
informationTextView = findViewById(R.id.information_text_view);
|
||||||
progressViewGroup = findViewById(R.id.progress_view_group);
|
progressViewGroup = findViewById(R.id.progress_view_group);
|
||||||
@ -111,7 +151,7 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
displayInputButton = findViewById(R.id.display_input_button);
|
displayInputButton = findViewById(R.id.display_input_button);
|
||||||
displayInputButton.setOnClickListener(this::toggleInputVideoDisplay);
|
displayInputButton.setOnClickListener(this::toggleInputVideoDisplay);
|
||||||
|
|
||||||
transformationStopwatch =
|
exportStopwatch =
|
||||||
Stopwatch.createUnstarted(
|
Stopwatch.createUnstarted(
|
||||||
new Ticker() {
|
new Ticker() {
|
||||||
@Override
|
@Override
|
||||||
@ -127,15 +167,18 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
checkNotNull(progressIndicator);
|
checkNotNull(progressIndicator);
|
||||||
checkNotNull(informationTextView);
|
checkNotNull(informationTextView);
|
||||||
checkNotNull(transformationStopwatch);
|
checkNotNull(exportStopwatch);
|
||||||
checkNotNull(inputCardView);
|
checkNotNull(inputCardView);
|
||||||
|
checkNotNull(inputTextView);
|
||||||
|
checkNotNull(inputImageView);
|
||||||
checkNotNull(inputPlayerView);
|
checkNotNull(inputPlayerView);
|
||||||
checkNotNull(outputPlayerView);
|
checkNotNull(outputPlayerView);
|
||||||
|
checkNotNull(outputVideoTextView);
|
||||||
checkNotNull(debugTextView);
|
checkNotNull(debugTextView);
|
||||||
checkNotNull(progressViewGroup);
|
checkNotNull(progressViewGroup);
|
||||||
checkNotNull(debugFrame);
|
checkNotNull(debugFrame);
|
||||||
checkNotNull(displayInputButton);
|
checkNotNull(displayInputButton);
|
||||||
startTransformation();
|
startExport();
|
||||||
|
|
||||||
inputPlayerView.onResume();
|
inputPlayerView.onResume();
|
||||||
outputPlayerView.onResume();
|
outputPlayerView.onResume();
|
||||||
@ -148,9 +191,9 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
checkNotNull(transformer).cancel();
|
checkNotNull(transformer).cancel();
|
||||||
transformer = null;
|
transformer = null;
|
||||||
|
|
||||||
// The stop watch is reset after cancelling the transformation, in case cancelling causes the
|
// The stop watch is reset after cancelling the export, in case cancelling causes the stop watch
|
||||||
// stop watch to be stopped in a transformer callback.
|
// to be stopped in a transformer callback.
|
||||||
checkNotNull(transformationStopwatch).reset();
|
checkNotNull(exportStopwatch).reset();
|
||||||
|
|
||||||
checkNotNull(inputPlayerView).onPause();
|
checkNotNull(inputPlayerView).onPause();
|
||||||
checkNotNull(outputPlayerView).onPause();
|
checkNotNull(outputPlayerView).onPause();
|
||||||
@ -161,37 +204,49 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@RequiresNonNull({
|
@RequiresNonNull({
|
||||||
|
"displayInputButton",
|
||||||
"inputCardView",
|
"inputCardView",
|
||||||
|
"inputTextView",
|
||||||
|
"inputImageView",
|
||||||
"inputPlayerView",
|
"inputPlayerView",
|
||||||
"outputPlayerView",
|
"outputPlayerView",
|
||||||
"displayInputButton",
|
"outputVideoTextView",
|
||||||
"debugTextView",
|
"debugTextView",
|
||||||
"informationTextView",
|
"informationTextView",
|
||||||
"progressIndicator",
|
"progressIndicator",
|
||||||
"transformationStopwatch",
|
"exportStopwatch",
|
||||||
"progressViewGroup",
|
"progressViewGroup",
|
||||||
"debugFrame",
|
"debugFrame",
|
||||||
})
|
})
|
||||||
private void startTransformation() {
|
private void startExport() {
|
||||||
requestTransformerPermission();
|
requestReadVideoPermission(/* activity= */ this);
|
||||||
|
|
||||||
Intent intent = getIntent();
|
Intent intent = getIntent();
|
||||||
Uri uri = checkNotNull(intent.getData());
|
Uri inputUri = checkNotNull(intent.getData());
|
||||||
try {
|
try {
|
||||||
externalCacheFile = createExternalCacheFile("transformer-output.mp4");
|
externalCacheFile = createExternalCacheFile("transformer-output.mp4");
|
||||||
String filePath = externalCacheFile.getAbsolutePath();
|
|
||||||
@Nullable Bundle bundle = intent.getExtras();
|
|
||||||
MediaItem mediaItem = createMediaItem(bundle, uri);
|
|
||||||
Transformer transformer = createTransformer(bundle, filePath);
|
|
||||||
transformationStopwatch.start();
|
|
||||||
transformer.startTransformation(mediaItem, filePath);
|
|
||||||
this.transformer = transformer;
|
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
throw new IllegalStateException(e);
|
throw new IllegalStateException(e);
|
||||||
}
|
}
|
||||||
informationTextView.setText(R.string.transformation_started);
|
String filePath = externalCacheFile.getAbsolutePath();
|
||||||
|
@Nullable Bundle bundle = intent.getExtras();
|
||||||
|
MediaItem mediaItem = createMediaItem(bundle, inputUri);
|
||||||
|
try {
|
||||||
|
Transformer transformer = createTransformer(bundle, inputUri, filePath);
|
||||||
|
Composition composition = createComposition(mediaItem, bundle);
|
||||||
|
exportStopwatch.start();
|
||||||
|
transformer.start(composition, filePath);
|
||||||
|
this.transformer = transformer;
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
throw new IllegalStateException(e);
|
||||||
|
}
|
||||||
|
displayInputButton.setVisibility(View.GONE);
|
||||||
inputCardView.setVisibility(View.GONE);
|
inputCardView.setVisibility(View.GONE);
|
||||||
outputPlayerView.setVisibility(View.GONE);
|
outputPlayerView.setVisibility(View.GONE);
|
||||||
|
outputVideoTextView.setVisibility(View.GONE);
|
||||||
|
debugTextView.setVisibility(View.GONE);
|
||||||
|
informationTextView.setText(R.string.export_started);
|
||||||
|
progressViewGroup.setVisibility(View.VISIBLE);
|
||||||
Handler mainHandler = new Handler(getMainLooper());
|
Handler mainHandler = new Handler(getMainLooper());
|
||||||
ProgressHolder progressHolder = new ProgressHolder();
|
ProgressHolder progressHolder = new ProgressHolder();
|
||||||
mainHandler.post(
|
mainHandler.post(
|
||||||
@ -199,13 +254,10 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
@Override
|
@Override
|
||||||
public void run() {
|
public void run() {
|
||||||
if (transformer != null
|
if (transformer != null
|
||||||
&& transformer.getProgress(progressHolder)
|
&& transformer.getProgress(progressHolder) != PROGRESS_STATE_NOT_STARTED) {
|
||||||
!= Transformer.PROGRESS_STATE_NO_TRANSFORMATION) {
|
|
||||||
progressIndicator.setProgress(progressHolder.progress);
|
progressIndicator.setProgress(progressHolder.progress);
|
||||||
informationTextView.setText(
|
informationTextView.setText(
|
||||||
getString(
|
getString(R.string.export_timer, exportStopwatch.elapsed(TimeUnit.SECONDS)));
|
||||||
R.string.transformation_timer,
|
|
||||||
transformationStopwatch.elapsed(TimeUnit.SECONDS)));
|
|
||||||
mainHandler.postDelayed(/* r= */ this, /* delayMillis= */ 500);
|
mainHandler.postDelayed(/* r= */ this, /* delayMillis= */ 500);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -232,21 +284,22 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
@RequiresNonNull({
|
@RequiresNonNull({
|
||||||
"inputCardView",
|
"inputCardView",
|
||||||
|
"inputTextView",
|
||||||
|
"inputImageView",
|
||||||
"inputPlayerView",
|
"inputPlayerView",
|
||||||
"outputPlayerView",
|
"outputPlayerView",
|
||||||
|
"outputVideoTextView",
|
||||||
"displayInputButton",
|
"displayInputButton",
|
||||||
"debugTextView",
|
"debugTextView",
|
||||||
"informationTextView",
|
"informationTextView",
|
||||||
"transformationStopwatch",
|
"exportStopwatch",
|
||||||
"progressViewGroup",
|
"progressViewGroup",
|
||||||
"debugFrame",
|
"debugFrame",
|
||||||
})
|
})
|
||||||
private Transformer createTransformer(@Nullable Bundle bundle, String filePath) {
|
private Transformer createTransformer(@Nullable Bundle bundle, Uri inputUri, String filePath) {
|
||||||
Transformer.Builder transformerBuilder = new Transformer.Builder(/* context= */ this);
|
Transformer.Builder transformerBuilder = new Transformer.Builder(/* context= */ this);
|
||||||
if (bundle != null) {
|
if (bundle != null) {
|
||||||
TransformationRequest.Builder requestBuilder = new TransformationRequest.Builder();
|
TransformationRequest.Builder requestBuilder = new TransformationRequest.Builder();
|
||||||
requestBuilder.setFlattenForSlowMotion(
|
|
||||||
bundle.getBoolean(ConfigurationActivity.SHOULD_FLATTEN_FOR_SLOW_MOTION));
|
|
||||||
@Nullable String audioMimeType = bundle.getString(ConfigurationActivity.AUDIO_MIME_TYPE);
|
@Nullable String audioMimeType = bundle.getString(ConfigurationActivity.AUDIO_MIME_TYPE);
|
||||||
if (audioMimeType != null) {
|
if (audioMimeType != null) {
|
||||||
requestBuilder.setAudioMimeType(audioMimeType);
|
requestBuilder.setAudioMimeType(audioMimeType);
|
||||||
@ -255,55 +308,38 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
if (videoMimeType != null) {
|
if (videoMimeType != null) {
|
||||||
requestBuilder.setVideoMimeType(videoMimeType);
|
requestBuilder.setVideoMimeType(videoMimeType);
|
||||||
}
|
}
|
||||||
int resolutionHeight =
|
requestBuilder.setHdrMode(bundle.getInt(ConfigurationActivity.HDR_MODE));
|
||||||
bundle.getInt(
|
transformerBuilder.setTransformationRequest(requestBuilder.build());
|
||||||
ConfigurationActivity.RESOLUTION_HEIGHT, /* defaultValue= */ C.LENGTH_UNSET);
|
|
||||||
if (resolutionHeight != C.LENGTH_UNSET) {
|
|
||||||
requestBuilder.setResolution(resolutionHeight);
|
|
||||||
}
|
|
||||||
|
|
||||||
float scaleX = bundle.getFloat(ConfigurationActivity.SCALE_X, /* defaultValue= */ 1);
|
transformerBuilder.setEncoderFactory(
|
||||||
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_setForceInterpretHdrVideoAsSdr(
|
|
||||||
bundle.getBoolean(ConfigurationActivity.FORCE_INTERPRET_HDR_VIDEO_AS_SDR));
|
|
||||||
requestBuilder.experimental_setEnableHdrEditing(
|
|
||||||
bundle.getBoolean(ConfigurationActivity.ENABLE_HDR_EDITING));
|
|
||||||
transformerBuilder
|
|
||||||
.setTransformationRequest(requestBuilder.build())
|
|
||||||
.setRemoveAudio(bundle.getBoolean(ConfigurationActivity.SHOULD_REMOVE_AUDIO))
|
|
||||||
.setRemoveVideo(bundle.getBoolean(ConfigurationActivity.SHOULD_REMOVE_VIDEO))
|
|
||||||
.setEncoderFactory(
|
|
||||||
new DefaultEncoderFactory.Builder(this.getApplicationContext())
|
new DefaultEncoderFactory.Builder(this.getApplicationContext())
|
||||||
.setEnableFallback(bundle.getBoolean(ConfigurationActivity.ENABLE_FALLBACK))
|
.setEnableFallback(bundle.getBoolean(ConfigurationActivity.ENABLE_FALLBACK))
|
||||||
.build());
|
.build());
|
||||||
|
|
||||||
transformerBuilder.setVideoEffects(createVideoEffectsListFromBundle(bundle));
|
if (!bundle.getBoolean(ConfigurationActivity.ABORT_SLOW_EXPORT)) {
|
||||||
|
transformerBuilder.setMuxerFactory(
|
||||||
|
new DefaultMuxer.Factory(/* maxDelayBetweenSamplesMs= */ C.TIME_UNSET));
|
||||||
|
}
|
||||||
|
|
||||||
if (bundle.getBoolean(ConfigurationActivity.ENABLE_DEBUG_PREVIEW)) {
|
if (bundle.getBoolean(ConfigurationActivity.ENABLE_DEBUG_PREVIEW)) {
|
||||||
transformerBuilder.setDebugViewProvider(new DemoDebugViewProvider());
|
transformerBuilder.setDebugViewProvider(new DemoDebugViewProvider());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
return transformerBuilder
|
return transformerBuilder
|
||||||
.addListener(
|
.addListener(
|
||||||
new Transformer.Listener() {
|
new Transformer.Listener() {
|
||||||
@Override
|
@Override
|
||||||
public void onTransformationCompleted(
|
public void onCompleted(Composition composition, ExportResult exportResult) {
|
||||||
MediaItem mediaItem, TransformationResult transformationResult) {
|
TransformerActivity.this.onCompleted(inputUri, filePath);
|
||||||
TransformerActivity.this.onTransformationCompleted(filePath, mediaItem);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onTransformationError(
|
public void onError(
|
||||||
MediaItem mediaItem, TransformationException exception) {
|
Composition composition,
|
||||||
TransformerActivity.this.onTransformationError(exception);
|
ExportResult exportResult,
|
||||||
|
ExportException exportException) {
|
||||||
|
TransformerActivity.this.onError(exportException);
|
||||||
}
|
}
|
||||||
})
|
})
|
||||||
.build();
|
.build();
|
||||||
@ -313,26 +349,88 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
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);
|
||||||
if (file.exists() && !file.delete()) {
|
if (file.exists() && !file.delete()) {
|
||||||
throw new IllegalStateException("Could not delete the previous transformer output file");
|
throw new IllegalStateException("Could not delete the previous export output file");
|
||||||
}
|
}
|
||||||
if (!file.createNewFile()) {
|
if (!file.createNewFile()) {
|
||||||
throw new IllegalStateException("Could not create the transformer output file");
|
throw new IllegalStateException("Could not create the export output file");
|
||||||
}
|
}
|
||||||
return file;
|
return file;
|
||||||
}
|
}
|
||||||
|
|
||||||
private ImmutableList<Effect> createVideoEffectsListFromBundle(Bundle bundle) {
|
@RequiresNonNull({
|
||||||
|
"inputCardView",
|
||||||
|
"outputPlayerView",
|
||||||
|
"exportStopwatch",
|
||||||
|
"progressViewGroup",
|
||||||
|
})
|
||||||
|
private Composition createComposition(MediaItem mediaItem, @Nullable Bundle bundle)
|
||||||
|
throws PackageManager.NameNotFoundException {
|
||||||
|
EditedMediaItem.Builder editedMediaItemBuilder = new EditedMediaItem.Builder(mediaItem);
|
||||||
|
// For image inputs. Automatically ignored if input is audio/video.
|
||||||
|
editedMediaItemBuilder.setDurationUs(5_000_000).setFrameRate(30);
|
||||||
|
boolean forceAudioTrack = false;
|
||||||
|
if (bundle != null) {
|
||||||
|
ImmutableList<AudioProcessor> audioProcessors = createAudioProcessorsFromBundle(bundle);
|
||||||
|
ImmutableList<Effect> videoEffects = createVideoEffectsFromBundle(bundle);
|
||||||
|
editedMediaItemBuilder
|
||||||
|
.setRemoveAudio(bundle.getBoolean(ConfigurationActivity.SHOULD_REMOVE_AUDIO))
|
||||||
|
.setRemoveVideo(bundle.getBoolean(ConfigurationActivity.SHOULD_REMOVE_VIDEO))
|
||||||
|
.setFlattenForSlowMotion(
|
||||||
|
bundle.getBoolean(ConfigurationActivity.SHOULD_FLATTEN_FOR_SLOW_MOTION))
|
||||||
|
.setEffects(new Effects(audioProcessors, videoEffects));
|
||||||
|
forceAudioTrack = bundle.getBoolean(ConfigurationActivity.FORCE_AUDIO_TRACK);
|
||||||
|
}
|
||||||
|
List<EditedMediaItem> editedMediaItems = new ArrayList<>();
|
||||||
|
editedMediaItems.add(editedMediaItemBuilder.build());
|
||||||
|
List<EditedMediaItemSequence> sequences = new ArrayList<>();
|
||||||
|
sequences.add(new EditedMediaItemSequence(editedMediaItems));
|
||||||
|
return new Composition.Builder(sequences)
|
||||||
|
.experimentalSetForceAudioTrack(forceAudioTrack)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ImmutableList<AudioProcessor> createAudioProcessorsFromBundle(Bundle bundle) {
|
||||||
@Nullable
|
@Nullable
|
||||||
boolean[] selectedEffects =
|
boolean[] selectedAudioEffects =
|
||||||
bundle.getBooleanArray(ConfigurationActivity.DEMO_EFFECTS_SELECTIONS);
|
bundle.getBooleanArray(ConfigurationActivity.AUDIO_EFFECTS_SELECTIONS);
|
||||||
if (selectedEffects == null) {
|
|
||||||
|
if (selectedAudioEffects == null) {
|
||||||
return ImmutableList.of();
|
return ImmutableList.of();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
ImmutableList.Builder<AudioProcessor> processors = new ImmutableList.Builder<>();
|
||||||
|
|
||||||
|
if (selectedAudioEffects[ConfigurationActivity.HIGH_PITCHED_INDEX]
|
||||||
|
|| selectedAudioEffects[ConfigurationActivity.SAMPLE_RATE_INDEX]) {
|
||||||
|
SonicAudioProcessor sonicAudioProcessor = new SonicAudioProcessor();
|
||||||
|
if (selectedAudioEffects[ConfigurationActivity.HIGH_PITCHED_INDEX]) {
|
||||||
|
sonicAudioProcessor.setPitch(2f);
|
||||||
|
}
|
||||||
|
if (selectedAudioEffects[ConfigurationActivity.SAMPLE_RATE_INDEX]) {
|
||||||
|
sonicAudioProcessor.setOutputSampleRateHz(48_000);
|
||||||
|
}
|
||||||
|
processors.add(sonicAudioProcessor);
|
||||||
|
}
|
||||||
|
|
||||||
|
if (selectedAudioEffects[ConfigurationActivity.SKIP_SILENCE_INDEX]) {
|
||||||
|
SilenceSkippingAudioProcessor silenceSkippingAudioProcessor =
|
||||||
|
new SilenceSkippingAudioProcessor();
|
||||||
|
silenceSkippingAudioProcessor.setEnabled(true);
|
||||||
|
processors.add(silenceSkippingAudioProcessor);
|
||||||
|
}
|
||||||
|
|
||||||
|
return processors.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
private ImmutableList<Effect> createVideoEffectsFromBundle(Bundle bundle)
|
||||||
|
throws PackageManager.NameNotFoundException {
|
||||||
|
boolean[] selectedEffects =
|
||||||
|
checkStateNotNull(bundle.getBooleanArray(ConfigurationActivity.VIDEO_EFFECTS_SELECTIONS));
|
||||||
ImmutableList.Builder<Effect> effects = new ImmutableList.Builder<>();
|
ImmutableList.Builder<Effect> effects = new ImmutableList.Builder<>();
|
||||||
if (selectedEffects[0]) {
|
if (selectedEffects[ConfigurationActivity.DIZZY_CROP_INDEX]) {
|
||||||
effects.add(MatrixTransformationFactory.createDizzyCropEffect());
|
effects.add(MatrixTransformationFactory.createDizzyCropEffect());
|
||||||
}
|
}
|
||||||
if (selectedEffects[1]) {
|
if (selectedEffects[ConfigurationActivity.EDGE_DETECTOR_INDEX]) {
|
||||||
try {
|
try {
|
||||||
Class<?> clazz = Class.forName("androidx.media3.demo.transformer.MediaPipeProcessor");
|
Class<?> clazz = Class.forName("androidx.media3.demo.transformer.MediaPipeProcessor");
|
||||||
Constructor<?> constructor =
|
Constructor<?> constructor =
|
||||||
@ -347,7 +445,7 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
(GlEffect)
|
(GlEffect)
|
||||||
(Context context, boolean useHdr) -> {
|
(Context context, boolean useHdr) -> {
|
||||||
try {
|
try {
|
||||||
return (GlTextureProcessor)
|
return (GlShaderProgram)
|
||||||
constructor.newInstance(
|
constructor.newInstance(
|
||||||
context,
|
context,
|
||||||
useHdr,
|
useHdr,
|
||||||
@ -357,14 +455,14 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
/* outputStreamName= */ "output_video");
|
/* outputStreamName= */ "output_video");
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
runOnUiThread(() -> showToast(R.string.no_media_pipe_error));
|
runOnUiThread(() -> showToast(R.string.no_media_pipe_error));
|
||||||
throw new RuntimeException("Failed to load MediaPipe processor", e);
|
throw new RuntimeException("Failed to load MediaPipeShaderProgram", e);
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
} catch (Exception e) {
|
} catch (Exception e) {
|
||||||
showToast(R.string.no_media_pipe_error);
|
showToast(R.string.no_media_pipe_error);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (selectedEffects[2]) {
|
if (selectedEffects[ConfigurationActivity.COLOR_FILTERS_INDEX]) {
|
||||||
switch (bundle.getInt(ConfigurationActivity.COLOR_FILTER_SELECTION)) {
|
switch (bundle.getInt(ConfigurationActivity.COLOR_FILTER_SELECTION)) {
|
||||||
case ConfigurationActivity.COLOR_FILTER_GRAYSCALE:
|
case ConfigurationActivity.COLOR_FILTER_GRAYSCALE:
|
||||||
effects.add(RgbFilter.createGrayscaleFilter());
|
effects.add(RgbFilter.createGrayscaleFilter());
|
||||||
@ -390,7 +488,7 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
+ bundle.getInt(ConfigurationActivity.COLOR_FILTER_SELECTION));
|
+ bundle.getInt(ConfigurationActivity.COLOR_FILTER_SELECTION));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (selectedEffects[3]) {
|
if (selectedEffects[ConfigurationActivity.MAP_WHITE_TO_GREEN_LUT_INDEX]) {
|
||||||
int length = 3;
|
int length = 3;
|
||||||
int[][][] mapWhiteToGreenLut = new int[length][length][length];
|
int[][][] mapWhiteToGreenLut = new int[length][length][length];
|
||||||
int scale = 255 / (length - 1);
|
int scale = 255 / (length - 1);
|
||||||
@ -405,7 +503,7 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
mapWhiteToGreenLut[length - 1][length - 1][length - 1] = Color.GREEN;
|
mapWhiteToGreenLut[length - 1][length - 1][length - 1] = Color.GREEN;
|
||||||
effects.add(SingleColorLut.createFromCube(mapWhiteToGreenLut));
|
effects.add(SingleColorLut.createFromCube(mapWhiteToGreenLut));
|
||||||
}
|
}
|
||||||
if (selectedEffects[4]) {
|
if (selectedEffects[ConfigurationActivity.RGB_ADJUSTMENTS_INDEX]) {
|
||||||
effects.add(
|
effects.add(
|
||||||
new RgbAdjustment.Builder()
|
new RgbAdjustment.Builder()
|
||||||
.setRedScale(bundle.getFloat(ConfigurationActivity.RGB_ADJUSTMENT_RED_SCALE))
|
.setRedScale(bundle.getFloat(ConfigurationActivity.RGB_ADJUSTMENT_RED_SCALE))
|
||||||
@ -413,7 +511,7 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
.setBlueScale(bundle.getFloat(ConfigurationActivity.RGB_ADJUSTMENT_BLUE_SCALE))
|
.setBlueScale(bundle.getFloat(ConfigurationActivity.RGB_ADJUSTMENT_BLUE_SCALE))
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
if (selectedEffects[5]) {
|
if (selectedEffects[ConfigurationActivity.HSL_ADJUSTMENT_INDEX]) {
|
||||||
effects.add(
|
effects.add(
|
||||||
new HslAdjustment.Builder()
|
new HslAdjustment.Builder()
|
||||||
.adjustHue(bundle.getFloat(ConfigurationActivity.HSL_ADJUSTMENTS_HUE))
|
.adjustHue(bundle.getFloat(ConfigurationActivity.HSL_ADJUSTMENTS_HUE))
|
||||||
@ -421,14 +519,14 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
.adjustLightness(bundle.getFloat(ConfigurationActivity.HSL_ADJUSTMENTS_LIGHTNESS))
|
.adjustLightness(bundle.getFloat(ConfigurationActivity.HSL_ADJUSTMENTS_LIGHTNESS))
|
||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
if (selectedEffects[6]) {
|
if (selectedEffects[ConfigurationActivity.CONTRAST_INDEX]) {
|
||||||
effects.add(new Contrast(bundle.getFloat(ConfigurationActivity.CONTRAST_VALUE)));
|
effects.add(new Contrast(bundle.getFloat(ConfigurationActivity.CONTRAST_VALUE)));
|
||||||
}
|
}
|
||||||
if (selectedEffects[7]) {
|
if (selectedEffects[ConfigurationActivity.PERIODIC_VIGNETTE_INDEX]) {
|
||||||
effects.add(
|
effects.add(
|
||||||
(GlEffect)
|
(GlEffect)
|
||||||
(Context context, boolean useHdr) ->
|
(Context context, boolean useHdr) ->
|
||||||
new PeriodicVignetteProcessor(
|
new PeriodicVignetteShaderProgram(
|
||||||
context,
|
context,
|
||||||
useHdr,
|
useHdr,
|
||||||
bundle.getFloat(ConfigurationActivity.PERIODIC_VIGNETTE_CENTER_X),
|
bundle.getFloat(ConfigurationActivity.PERIODIC_VIGNETTE_CENTER_X),
|
||||||
@ -439,61 +537,144 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
ConfigurationActivity.PERIODIC_VIGNETTE_OUTER_RADIUS),
|
ConfigurationActivity.PERIODIC_VIGNETTE_OUTER_RADIUS),
|
||||||
bundle.getFloat(ConfigurationActivity.PERIODIC_VIGNETTE_OUTER_RADIUS)));
|
bundle.getFloat(ConfigurationActivity.PERIODIC_VIGNETTE_OUTER_RADIUS)));
|
||||||
}
|
}
|
||||||
if (selectedEffects[8]) {
|
if (selectedEffects[ConfigurationActivity.SPIN_3D_INDEX]) {
|
||||||
effects.add(MatrixTransformationFactory.createSpin3dEffect());
|
effects.add(MatrixTransformationFactory.createSpin3dEffect());
|
||||||
}
|
}
|
||||||
if (selectedEffects[9]) {
|
if (selectedEffects[ConfigurationActivity.ZOOM_IN_INDEX]) {
|
||||||
effects.add((GlEffect) BitmapOverlayProcessor::new);
|
|
||||||
}
|
|
||||||
if (selectedEffects[10]) {
|
|
||||||
effects.add(MatrixTransformationFactory.createZoomInTransition());
|
effects.add(MatrixTransformationFactory.createZoomInTransition());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable OverlayEffect overlayEffect = createOverlayEffectFromBundle(bundle, selectedEffects);
|
||||||
|
if (overlayEffect != null) {
|
||||||
|
effects.add(overlayEffect);
|
||||||
|
}
|
||||||
|
|
||||||
|
float scaleX = bundle.getFloat(ConfigurationActivity.SCALE_X, /* defaultValue= */ 1);
|
||||||
|
float scaleY = bundle.getFloat(ConfigurationActivity.SCALE_Y, /* defaultValue= */ 1);
|
||||||
|
float rotateDegrees =
|
||||||
|
bundle.getFloat(ConfigurationActivity.ROTATE_DEGREES, /* defaultValue= */ 0);
|
||||||
|
if (scaleX != 1f || scaleY != 1f || rotateDegrees != 0f) {
|
||||||
|
effects.add(
|
||||||
|
new ScaleAndRotateTransformation.Builder()
|
||||||
|
.setScale(scaleX, scaleY)
|
||||||
|
.setRotationDegrees(rotateDegrees)
|
||||||
|
.build());
|
||||||
|
}
|
||||||
|
|
||||||
|
int resolutionHeight =
|
||||||
|
bundle.getInt(ConfigurationActivity.RESOLUTION_HEIGHT, /* defaultValue= */ C.LENGTH_UNSET);
|
||||||
|
if (resolutionHeight != C.LENGTH_UNSET) {
|
||||||
|
effects.add(Presentation.createForHeight(resolutionHeight));
|
||||||
|
}
|
||||||
|
|
||||||
return effects.build();
|
return effects.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private OverlayEffect createOverlayEffectFromBundle(Bundle bundle, boolean[] selectedEffects)
|
||||||
|
throws PackageManager.NameNotFoundException {
|
||||||
|
ImmutableList.Builder<TextureOverlay> overlaysBuilder = new ImmutableList.Builder<>();
|
||||||
|
if (selectedEffects[ConfigurationActivity.OVERLAY_LOGO_AND_TIMER_INDEX]) {
|
||||||
|
float[] logoPositioningMatrix = GlUtil.create4x4IdentityMatrix();
|
||||||
|
Matrix.translateM(
|
||||||
|
logoPositioningMatrix, /* mOffset= */ 0, /* x= */ -0.95f, /* y= */ -0.95f, /* z= */ 1);
|
||||||
|
OverlaySettings logoSettings =
|
||||||
|
new OverlaySettings.Builder()
|
||||||
|
.setMatrix(logoPositioningMatrix)
|
||||||
|
.setAnchor(/* x= */ -1f, /* y= */ -1f)
|
||||||
|
.build();
|
||||||
|
Drawable logo = getPackageManager().getApplicationIcon(getPackageName());
|
||||||
|
logo.setBounds(
|
||||||
|
/* left= */ 0, /* top= */ 0, logo.getIntrinsicWidth(), logo.getIntrinsicHeight());
|
||||||
|
TextureOverlay logoOverlay = DrawableOverlay.createStaticDrawableOverlay(logo, logoSettings);
|
||||||
|
TextureOverlay timerOverlay = new TimerOverlay();
|
||||||
|
overlaysBuilder.add(logoOverlay, timerOverlay);
|
||||||
|
}
|
||||||
|
if (selectedEffects[ConfigurationActivity.BITMAP_OVERLAY_INDEX]) {
|
||||||
|
OverlaySettings overlaySettings =
|
||||||
|
new OverlaySettings.Builder()
|
||||||
|
.setAlpha(
|
||||||
|
bundle.getFloat(
|
||||||
|
ConfigurationActivity.BITMAP_OVERLAY_ALPHA, /* defaultValue= */ 1))
|
||||||
|
.build();
|
||||||
|
BitmapOverlay bitmapOverlay =
|
||||||
|
BitmapOverlay.createStaticBitmapOverlay(
|
||||||
|
getApplicationContext(),
|
||||||
|
Uri.parse(checkNotNull(bundle.getString(ConfigurationActivity.BITMAP_OVERLAY_URI))),
|
||||||
|
overlaySettings);
|
||||||
|
overlaysBuilder.add(bitmapOverlay);
|
||||||
|
}
|
||||||
|
if (selectedEffects[ConfigurationActivity.TEXT_OVERLAY_INDEX]) {
|
||||||
|
OverlaySettings overlaySettings =
|
||||||
|
new OverlaySettings.Builder()
|
||||||
|
.setAlpha(
|
||||||
|
bundle.getFloat(ConfigurationActivity.TEXT_OVERLAY_ALPHA, /* defaultValue= */ 1))
|
||||||
|
.build();
|
||||||
|
SpannableString overlayText =
|
||||||
|
new SpannableString(
|
||||||
|
checkNotNull(bundle.getString(ConfigurationActivity.TEXT_OVERLAY_TEXT)));
|
||||||
|
overlayText.setSpan(
|
||||||
|
new ForegroundColorSpan(bundle.getInt(ConfigurationActivity.TEXT_OVERLAY_TEXT_COLOR)),
|
||||||
|
/* start= */ 0,
|
||||||
|
overlayText.length(),
|
||||||
|
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE);
|
||||||
|
TextOverlay textOverlay = TextOverlay.createStaticTextOverlay(overlayText, overlaySettings);
|
||||||
|
overlaysBuilder.add(textOverlay);
|
||||||
|
}
|
||||||
|
|
||||||
|
ImmutableList<TextureOverlay> overlays = overlaysBuilder.build();
|
||||||
|
return overlays.isEmpty() ? null : new OverlayEffect(overlays);
|
||||||
|
}
|
||||||
|
|
||||||
@RequiresNonNull({
|
@RequiresNonNull({
|
||||||
"informationTextView",
|
"informationTextView",
|
||||||
"progressViewGroup",
|
"progressViewGroup",
|
||||||
"debugFrame",
|
"debugFrame",
|
||||||
"transformationStopwatch",
|
"exportStopwatch",
|
||||||
})
|
})
|
||||||
private void onTransformationError(TransformationException exception) {
|
private void onError(ExportException exportException) {
|
||||||
transformationStopwatch.stop();
|
exportStopwatch.stop();
|
||||||
informationTextView.setText(R.string.transformation_error);
|
informationTextView.setText(R.string.export_error);
|
||||||
progressViewGroup.setVisibility(View.GONE);
|
progressViewGroup.setVisibility(View.GONE);
|
||||||
debugFrame.removeAllViews();
|
debugFrame.removeAllViews();
|
||||||
Toast.makeText(getApplicationContext(), "Transformation error: " + exception, Toast.LENGTH_LONG)
|
Toast.makeText(getApplicationContext(), "Export error: " + exportException, Toast.LENGTH_LONG)
|
||||||
.show();
|
.show();
|
||||||
Log.e(TAG, "Transformation error", exception);
|
Log.e(TAG, "Export error", exportException);
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresNonNull({
|
@RequiresNonNull({
|
||||||
"inputCardView",
|
"inputCardView",
|
||||||
|
"inputTextView",
|
||||||
|
"inputImageView",
|
||||||
"inputPlayerView",
|
"inputPlayerView",
|
||||||
"outputPlayerView",
|
"outputPlayerView",
|
||||||
"displayInputButton",
|
"outputVideoTextView",
|
||||||
"debugTextView",
|
"debugTextView",
|
||||||
|
"displayInputButton",
|
||||||
"informationTextView",
|
"informationTextView",
|
||||||
"progressViewGroup",
|
"progressViewGroup",
|
||||||
"debugFrame",
|
"debugFrame",
|
||||||
"transformationStopwatch",
|
"exportStopwatch",
|
||||||
})
|
})
|
||||||
private void onTransformationCompleted(String filePath, MediaItem inputMediaItem) {
|
private void onCompleted(Uri inputUri, String filePath) {
|
||||||
transformationStopwatch.stop();
|
exportStopwatch.stop();
|
||||||
informationTextView.setText(
|
informationTextView.setText(
|
||||||
getString(
|
getString(R.string.export_completed, exportStopwatch.elapsed(TimeUnit.SECONDS), filePath));
|
||||||
R.string.transformation_completed, transformationStopwatch.elapsed(TimeUnit.SECONDS)));
|
|
||||||
progressViewGroup.setVisibility(View.GONE);
|
progressViewGroup.setVisibility(View.GONE);
|
||||||
debugFrame.removeAllViews();
|
debugFrame.removeAllViews();
|
||||||
inputCardView.setVisibility(View.VISIBLE);
|
inputCardView.setVisibility(View.VISIBLE);
|
||||||
outputPlayerView.setVisibility(View.VISIBLE);
|
outputPlayerView.setVisibility(View.VISIBLE);
|
||||||
|
outputVideoTextView.setVisibility(View.VISIBLE);
|
||||||
|
debugTextView.setVisibility(View.VISIBLE);
|
||||||
displayInputButton.setVisibility(View.VISIBLE);
|
displayInputButton.setVisibility(View.VISIBLE);
|
||||||
playMediaItems(inputMediaItem, MediaItem.fromUri("file://" + filePath));
|
playMediaItems(MediaItem.fromUri(inputUri), MediaItem.fromUri("file://" + filePath));
|
||||||
Log.d(TAG, "Output file path: file://" + filePath);
|
Log.d(TAG, "Output file path: file://" + filePath);
|
||||||
}
|
}
|
||||||
|
|
||||||
@RequiresNonNull({
|
@RequiresNonNull({
|
||||||
"inputCardView",
|
"inputCardView",
|
||||||
|
"inputTextView",
|
||||||
|
"inputImageView",
|
||||||
"inputPlayerView",
|
"inputPlayerView",
|
||||||
"outputPlayerView",
|
"outputPlayerView",
|
||||||
"debugTextView",
|
"debugTextView",
|
||||||
@ -503,14 +684,7 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
outputPlayerView.setPlayer(null);
|
outputPlayerView.setPlayer(null);
|
||||||
releasePlayer();
|
releasePlayer();
|
||||||
|
|
||||||
ExoPlayer inputPlayer = new ExoPlayer.Builder(/* context= */ this).build();
|
Uri uri = checkNotNull(inputMediaItem.localConfiguration).uri;
|
||||||
inputPlayerView.setPlayer(inputPlayer);
|
|
||||||
inputPlayerView.setControllerAutoShow(false);
|
|
||||||
inputPlayer.setMediaItem(inputMediaItem);
|
|
||||||
inputPlayer.prepare();
|
|
||||||
this.inputPlayer = inputPlayer;
|
|
||||||
inputPlayer.setVolume(0f);
|
|
||||||
|
|
||||||
ExoPlayer outputPlayer = new ExoPlayer.Builder(/* context= */ this).build();
|
ExoPlayer outputPlayer = new ExoPlayer.Builder(/* context= */ this).build();
|
||||||
outputPlayerView.setPlayer(outputPlayer);
|
outputPlayerView.setPlayer(outputPlayer);
|
||||||
outputPlayerView.setControllerAutoShow(false);
|
outputPlayerView.setControllerAutoShow(false);
|
||||||
@ -518,13 +692,60 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
outputPlayer.prepare();
|
outputPlayer.prepare();
|
||||||
this.outputPlayer = outputPlayer;
|
this.outputPlayer = outputPlayer;
|
||||||
|
|
||||||
|
// Only support showing jpg images.
|
||||||
|
if (uri.toString().endsWith("jpg")) {
|
||||||
|
inputPlayerView.setVisibility(View.GONE);
|
||||||
|
inputImageView.setVisibility(View.VISIBLE);
|
||||||
|
inputTextView.setText(getString(R.string.input_image));
|
||||||
|
|
||||||
|
BitmapLoader bitmapLoader = new DataSourceBitmapLoader(getApplicationContext());
|
||||||
|
ListenableFuture<Bitmap> future = bitmapLoader.loadBitmap(uri);
|
||||||
|
try {
|
||||||
|
Bitmap bitmap = future.get();
|
||||||
|
inputImageView.setImageBitmap(bitmap);
|
||||||
|
} catch (ExecutionException | InterruptedException e) {
|
||||||
|
throw new IllegalArgumentException("Failed to load bitmap.", e);
|
||||||
|
}
|
||||||
|
} else {
|
||||||
|
inputPlayerView.setVisibility(View.VISIBLE);
|
||||||
|
inputImageView.setVisibility(View.GONE);
|
||||||
|
inputTextView.setText(getString(R.string.input_video_no_sound));
|
||||||
|
|
||||||
|
ExoPlayer inputPlayer = new ExoPlayer.Builder(/* context= */ this).build();
|
||||||
|
inputPlayerView.setPlayer(inputPlayer);
|
||||||
|
inputPlayerView.setControllerAutoShow(false);
|
||||||
|
inputPlayerView.setOnClickListener(this::onClickingPlayerView);
|
||||||
|
outputPlayerView.setOnClickListener(this::onClickingPlayerView);
|
||||||
|
inputPlayer.setMediaItem(inputMediaItem);
|
||||||
|
inputPlayer.prepare();
|
||||||
|
this.inputPlayer = inputPlayer;
|
||||||
|
inputPlayer.setVolume(0f);
|
||||||
inputPlayer.play();
|
inputPlayer.play();
|
||||||
|
}
|
||||||
outputPlayer.play();
|
outputPlayer.play();
|
||||||
|
|
||||||
debugTextViewHelper = new DebugTextViewHelper(outputPlayer, debugTextView);
|
debugTextViewHelper = new DebugTextViewHelper(outputPlayer, debugTextView);
|
||||||
debugTextViewHelper.start();
|
debugTextViewHelper.start();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private void onClickingPlayerView(View view) {
|
||||||
|
if (view == inputPlayerView) {
|
||||||
|
if (inputPlayer != null && inputTextView != null) {
|
||||||
|
inputPlayer.setVolume(1f);
|
||||||
|
inputTextView.setText(R.string.input_video_playing_sound);
|
||||||
|
}
|
||||||
|
checkNotNull(outputPlayer).setVolume(0f);
|
||||||
|
checkNotNull(outputVideoTextView).setText(R.string.output_video_no_sound);
|
||||||
|
} else {
|
||||||
|
if (inputPlayer != null && inputTextView != null) {
|
||||||
|
inputPlayer.setVolume(0f);
|
||||||
|
inputTextView.setText(getString(R.string.input_video_no_sound));
|
||||||
|
}
|
||||||
|
checkNotNull(outputPlayer).setVolume(1f);
|
||||||
|
checkNotNull(outputVideoTextView).setText(R.string.output_video_playing_sound);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void releasePlayer() {
|
private void releasePlayer() {
|
||||||
if (debugTextViewHelper != null) {
|
if (debugTextViewHelper != null) {
|
||||||
debugTextViewHelper.stop();
|
debugTextViewHelper.stop();
|
||||||
@ -540,12 +761,11 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void requestTransformerPermission() {
|
private static void requestReadVideoPermission(AppCompatActivity activity) {
|
||||||
if (Util.SDK_INT < 23) {
|
String permission = SDK_INT >= 33 ? READ_MEDIA_VIDEO : READ_EXTERNAL_STORAGE;
|
||||||
return;
|
if (ActivityCompat.checkSelfPermission(activity, permission)
|
||||||
}
|
!= PackageManager.PERMISSION_GRANTED) {
|
||||||
if (checkSelfPermission(READ_EXTERNAL_STORAGE) != PackageManager.PERMISSION_GRANTED) {
|
ActivityCompat.requestPermissions(activity, new String[] {permission}, /* requestCode= */ 0);
|
||||||
requestPermissions(new String[] {READ_EXTERNAL_STORAGE}, /* requestCode= */ 0);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -562,7 +782,9 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
inputCardView.setVisibility(View.VISIBLE);
|
inputCardView.setVisibility(View.VISIBLE);
|
||||||
displayInputButton.setText(getString(R.string.hide_input_video));
|
displayInputButton.setText(getString(R.string.hide_input_video));
|
||||||
} else if (inputCardView.getVisibility() == View.VISIBLE) {
|
} else if (inputCardView.getVisibility() == View.VISIBLE) {
|
||||||
checkNotNull(inputPlayer).pause();
|
if (inputPlayer != null) {
|
||||||
|
inputPlayer.pause();
|
||||||
|
}
|
||||||
inputCardView.setVisibility(View.GONE);
|
inputCardView.setVisibility(View.GONE);
|
||||||
displayInputButton.setText(getString(R.string.show_input_video));
|
displayInputButton.setText(getString(R.string.show_input_video));
|
||||||
}
|
}
|
||||||
@ -584,7 +806,7 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
public SurfaceView getDebugPreviewSurfaceView(int width, int height) {
|
public SurfaceView getDebugPreviewSurfaceView(int width, int height) {
|
||||||
checkState(
|
checkState(
|
||||||
surfaceView == null || (this.width == width && this.height == height),
|
surfaceView == null || (this.width == width && this.height == height),
|
||||||
"Transformer should not change the output size mid-transformation.");
|
"Transformer should not change the output size mid-export.");
|
||||||
if (surfaceView != null) {
|
if (surfaceView != null) {
|
||||||
return surfaceView;
|
return surfaceView;
|
||||||
}
|
}
|
||||||
@ -594,9 +816,9 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
|
|
||||||
// Update the UI on the main thread and wait for the output surface to be available.
|
// Update the UI on the main thread and wait for the output surface to be available.
|
||||||
CountDownLatch surfaceCreatedCountDownLatch = new CountDownLatch(1);
|
CountDownLatch surfaceCreatedCountDownLatch = new CountDownLatch(1);
|
||||||
SurfaceView surfaceView = new SurfaceView(/* context= */ TransformerActivity.this);
|
|
||||||
runOnUiThread(
|
runOnUiThread(
|
||||||
() -> {
|
() -> {
|
||||||
|
surfaceView = new SurfaceView(/* context= */ TransformerActivity.this);
|
||||||
AspectRatioFrameLayout debugFrame = checkNotNull(TransformerActivity.this.debugFrame);
|
AspectRatioFrameLayout debugFrame = checkNotNull(TransformerActivity.this.debugFrame);
|
||||||
debugFrame.addView(surfaceView);
|
debugFrame.addView(surfaceView);
|
||||||
debugFrame.setAspectRatio((float) width / height);
|
debugFrame.setAspectRatio((float) width / height);
|
||||||
@ -628,7 +850,6 @@ public final class TransformerActivity extends AppCompatActivity {
|
|||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
this.surfaceView = surfaceView;
|
|
||||||
return surfaceView;
|
return surfaceView;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -0,0 +1,59 @@
|
|||||||
|
<?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/overlay_uri" />
|
||||||
|
<Button
|
||||||
|
android:id="@+id/bitmap_overlay_uri"
|
||||||
|
android:text="@string/select_local_image"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
</TableRow>
|
||||||
|
<TableRow
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center_vertical" >
|
||||||
|
<TextView android:text="@string/overlay_alpha" />
|
||||||
|
<com.google.android.material.slider.Slider
|
||||||
|
android:id="@+id/bitmap_overlay_alpha_slider"
|
||||||
|
android:valueFrom="0"
|
||||||
|
android:value="1"
|
||||||
|
android:valueTo="1"
|
||||||
|
android:layout_gravity="right|center_vertical"/>
|
||||||
|
</TableRow>
|
||||||
|
</TableLayout>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -75,11 +75,12 @@
|
|||||||
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/selected_file_text_view"
|
app:layout_constraintTop_toBottomOf="@+id/selected_file_text_view"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/select_demo_effects_button">
|
app:layout_constraintBottom_toTopOf="@+id/select_audio_effects_button">
|
||||||
<TableLayout
|
<TableLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:stretchColumns="0"
|
android:stretchColumns="0"
|
||||||
|
android:shrinkColumns="0"
|
||||||
android:layout_marginTop="32dp"
|
android:layout_marginTop="32dp"
|
||||||
android:measureWithLargestChild="true"
|
android:measureWithLargestChild="true"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
@ -112,6 +113,15 @@
|
|||||||
android:id="@+id/flatten_for_slow_motion_checkbox"
|
android:id="@+id/flatten_for_slow_motion_checkbox"
|
||||||
android:layout_gravity="end" />
|
android:layout_gravity="end" />
|
||||||
</TableRow>
|
</TableRow>
|
||||||
|
<TableRow
|
||||||
|
android:layout_weight="1">
|
||||||
|
<TextView
|
||||||
|
android:layout_gravity="center_vertical"
|
||||||
|
android:text="@string/force_audio_track" />
|
||||||
|
<CheckBox
|
||||||
|
android:id="@+id/force_audio_track_checkbox"
|
||||||
|
android:layout_gravity="end" />
|
||||||
|
</TableRow>
|
||||||
<TableRow
|
<TableRow
|
||||||
android:layout_weight="1"
|
android:layout_weight="1"
|
||||||
android:gravity="center_vertical" >
|
android:gravity="center_vertical" >
|
||||||
@ -201,53 +211,54 @@
|
|||||||
android:layout_weight="1">
|
android:layout_weight="1">
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_gravity="center_vertical"
|
android:layout_gravity="center_vertical"
|
||||||
android:id="@+id/request_sdr_tone_mapping"
|
android:text="@string/abort_slow_export" />
|
||||||
android:text="@string/request_sdr_tone_mapping" />
|
|
||||||
<CheckBox
|
<CheckBox
|
||||||
android:id="@+id/request_sdr_tone_mapping_checkbox"
|
android:id="@+id/abort_slow_export_checkbox"
|
||||||
android:layout_gravity="end" />
|
android:layout_gravity="end"/>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
<TableRow
|
<TableRow
|
||||||
android:layout_weight="1">
|
android:layout_weight="1"
|
||||||
|
android:gravity="center_vertical" >
|
||||||
<TextView
|
<TextView
|
||||||
android:layout_gravity="center_vertical"
|
android:id="@+id/hdr_mode"
|
||||||
android:id="@+id/hdr_editing"
|
android:text="@string/hdr_mode"/>
|
||||||
android:text="@string/hdr_editing" />
|
<Spinner
|
||||||
<CheckBox
|
android:id="@+id/hdr_mode_spinner"
|
||||||
android:id="@+id/hdr_editing_checkbox"
|
android:layout_gravity="right|center_vertical"
|
||||||
android:layout_gravity="end" />
|
android:gravity="right" />
|
||||||
</TableRow>
|
|
||||||
<TableRow
|
|
||||||
android:layout_weight="1">
|
|
||||||
<TextView
|
|
||||||
android:layout_gravity="center_vertical"
|
|
||||||
android:id="@+id/force_interpret_hdr_video_as_sdr"
|
|
||||||
android:text="@string/force_interpret_hdr_video_as_sdr" />
|
|
||||||
<CheckBox
|
|
||||||
android:id="@+id/force_interpret_hdr_video_as_sdr_checkbox"
|
|
||||||
android:layout_gravity="end" />
|
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableLayout>
|
</TableLayout>
|
||||||
</androidx.core.widget.NestedScrollView>
|
</androidx.core.widget.NestedScrollView>
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/select_demo_effects_button"
|
android:id="@+id/select_audio_effects_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/select_demo_effects"
|
android:text="@string/select_audio_effects"
|
||||||
app:layout_constraintBottom_toTopOf="@+id/transform_button"
|
app:layout_constraintBottom_toTopOf="@+id/select_video_effects_button"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent" />
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
<Button
|
<Button
|
||||||
android:id="@+id/transform_button"
|
android:id="@+id/select_video_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_video_effects"
|
||||||
|
app:layout_constraintBottom_toTopOf="@+id/export_button"
|
||||||
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
<Button
|
||||||
|
android:id="@+id/export_button"
|
||||||
android:layout_width="wrap_content"
|
android:layout_width="wrap_content"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="28dp"
|
android:layout_marginBottom="28dp"
|
||||||
android:layout_marginStart="32dp"
|
android:layout_marginStart="32dp"
|
||||||
android:layout_marginEnd="32dp"
|
android:layout_marginEnd="32dp"
|
||||||
android:text="@string/transform"
|
android:text="@string/export"
|
||||||
app:layout_constraintBottom_toBottomOf="parent"
|
app:layout_constraintBottom_toBottomOf="parent"
|
||||||
app:layout_constraintEnd_toEndOf="parent"
|
app:layout_constraintEnd_toEndOf="parent"
|
||||||
app:layout_constraintStart_toStartOf="parent" />
|
app:layout_constraintStart_toStartOf="parent" />
|
||||||
|
@ -0,0 +1,69 @@
|
|||||||
|
<?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/overlay_text" />
|
||||||
|
<EditText
|
||||||
|
android:id="@+id/text_overlay_text"
|
||||||
|
android:inputType="textMultiLine"
|
||||||
|
android:layout_width="0dp"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
</TableRow>
|
||||||
|
<TableRow
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center_vertical" >
|
||||||
|
<TextView
|
||||||
|
android:text="@string/overlay_text_color"/>
|
||||||
|
<Spinner
|
||||||
|
android:id="@+id/text_overlay_text_color"
|
||||||
|
android:layout_gravity="right|center_vertical"
|
||||||
|
android:gravity="right" />
|
||||||
|
</TableRow>
|
||||||
|
<TableRow
|
||||||
|
android:layout_weight="1"
|
||||||
|
android:gravity="center_vertical" >
|
||||||
|
<TextView android:text="@string/overlay_alpha" />
|
||||||
|
<com.google.android.material.slider.Slider
|
||||||
|
android:id="@+id/text_overlay_alpha_slider"
|
||||||
|
android:valueFrom="0"
|
||||||
|
android:value="1"
|
||||||
|
android:valueTo="1"
|
||||||
|
android:layout_gravity="right|center_vertical"/>
|
||||||
|
</TableRow>
|
||||||
|
</TableLayout>
|
||||||
|
</androidx.constraintlayout.widget.ConstraintLayout>
|
@ -69,16 +69,22 @@
|
|||||||
android:layout_height="wrap_content" >
|
android:layout_height="wrap_content" >
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/input_text_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_marginBottom="8dp"
|
android:layout_marginBottom="8dp"
|
||||||
android:padding="8dp"
|
android:padding="8dp"
|
||||||
android:text="@string/input_video" />
|
android:text="@string/input_video_no_sound" />
|
||||||
|
|
||||||
<FrameLayout
|
<FrameLayout
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content" >
|
android:layout_height="wrap_content" >
|
||||||
|
|
||||||
|
<ImageView
|
||||||
|
android:id="@+id/input_image_view"
|
||||||
|
android:layout_width="match_parent"
|
||||||
|
android:layout_height="wrap_content" />
|
||||||
|
|
||||||
<androidx.media3.ui.PlayerView
|
<androidx.media3.ui.PlayerView
|
||||||
android:id="@+id/input_player_view"
|
android:id="@+id/input_player_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
@ -108,10 +114,11 @@
|
|||||||
android:layout_height="wrap_content" >
|
android:layout_height="wrap_content" >
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
|
android:id="@+id/output_video_text_view"
|
||||||
android:layout_width="match_parent"
|
android:layout_width="match_parent"
|
||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:layout_margin="8dp"
|
android:layout_margin="8dp"
|
||||||
android:text="@string/output_video" />
|
android:text="@string/output_video_playing_sound" />
|
||||||
|
|
||||||
<TextView
|
<TextView
|
||||||
android:id="@+id/debug_text_view"
|
android:id="@+id/debug_text_view"
|
||||||
|
@ -41,7 +41,7 @@
|
|||||||
<com.google.android.material.slider.RangeSlider
|
<com.google.android.material.slider.RangeSlider
|
||||||
android:id="@+id/trim_bounds_range_slider"
|
android:id="@+id/trim_bounds_range_slider"
|
||||||
android:valueFrom="0.0"
|
android:valueFrom="0.0"
|
||||||
android:valueTo="60.0"
|
android:valueTo="10.0"
|
||||||
android:layout_gravity="right"/>
|
android:layout_gravity="right"/>
|
||||||
</TableRow>
|
</TableRow>
|
||||||
</TableLayout>
|
</TableLayout>
|
||||||
|
@ -19,6 +19,7 @@
|
|||||||
<string name="configuration" translatable="false">Configuration</string>
|
<string name="configuration" translatable="false">Configuration</string>
|
||||||
<string name="select_preset_file_title" translatable="false">Choose preset file</string>
|
<string name="select_preset_file_title" translatable="false">Choose preset file</string>
|
||||||
<string name="select_local_file_title">Choose local file</string>
|
<string name="select_local_file_title">Choose local file</string>
|
||||||
|
<string name="local_file_picker_failed">File couldn\'t be opened. Please try again.</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>
|
||||||
@ -29,20 +30,20 @@
|
|||||||
<string name="rotate" translatable="false">Rotate video (degrees)</string>
|
<string name="rotate" translatable="false">Rotate video (degrees)</string>
|
||||||
<string name="enable_fallback" translatable="false">Enable fallback</string>
|
<string name="enable_fallback" translatable="false">Enable fallback</string>
|
||||||
<string name="enable_debug_preview" translatable="false">Enable debug preview</string>
|
<string name="enable_debug_preview" translatable="false">Enable debug preview</string>
|
||||||
|
<string name="abort_slow_export" translatable="false">Abort slow export</string>
|
||||||
<string name="trim" translatable="false">Trim</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_mode" translatable="false">HDR mode</string>
|
||||||
<string name="force_interpret_hdr_video_as_sdr" translatable="false">Interpret HDR as SDR (API 29+)</string>
|
<string name="select_audio_effects" translatable="false">Add audio effects</string>
|
||||||
<string name="hdr_editing" translatable="false">HDR editing (API 31+)</string>
|
<string name="select_video_effects" translatable="false">Add video effects</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="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="no_media_pipe_error" translatable="false">Failed to load MediaPipeShaderProgram. Check the README for instructions.</string>
|
||||||
<string name="transform" translatable="false">Transform</string>
|
<string name="export" translatable="false">Export</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="export_started" translatable="false">Export started</string>
|
||||||
<string name="transformation_timer" translatable="false">Transformation started %d seconds ago.</string>
|
<string name="export_timer" translatable="false">Export started %d seconds ago.</string>
|
||||||
<string name="transformation_completed" translatable="false">Transformation completed in %d seconds.</string>
|
<string name="export_completed" translatable="false">Export completed in %d seconds.\nOutput: %s</string>
|
||||||
<string name="transformation_error" translatable="false">Transformation error</string>
|
<string name="export_error" translatable="false">Export error</string>
|
||||||
<string name="trim_range">Bounds in seconds</string>
|
<string name="trim_range">Bounds in seconds</string>
|
||||||
<string-array name="color_filter_options">
|
<string-array name="color_filter_options">
|
||||||
<item>Grayscale</item>
|
<item>Grayscale</item>
|
||||||
@ -61,9 +62,20 @@
|
|||||||
<string name="hue_adjustment">Hue adjustment</string>
|
<string name="hue_adjustment">Hue adjustment</string>
|
||||||
<string name="saturation_adjustment">Saturation adjustment</string>
|
<string name="saturation_adjustment">Saturation adjustment</string>
|
||||||
<string name="lightness_adjustment">Lightness adjustment</string>
|
<string name="lightness_adjustment">Lightness adjustment</string>
|
||||||
<string name="input_video">Input video:</string>
|
<string name="input_image">Input image:</string>
|
||||||
<string name="output_video">Output video:</string>
|
<string name="input_video_no_sound">Input video (tap to play sound):</string>
|
||||||
|
<string name="input_video_playing_sound">Input video (sound playing):</string>
|
||||||
|
<string name="output_video_no_sound">Output video (tap to play sound):</string>
|
||||||
|
<string name="output_video_playing_sound">Output video (sound playing):</string>
|
||||||
<string name="permission_denied">Permission Denied</string>
|
<string name="permission_denied">Permission Denied</string>
|
||||||
<string name="hide_input_video">Hide input video</string>
|
<string name="hide_input_video">Hide input video</string>
|
||||||
<string name="show_input_video">Show input video</string>
|
<string name="show_input_video">Show input video</string>
|
||||||
|
<string name="force_audio_track">Force audio track</string>
|
||||||
|
<string name="overlay_alpha">Alpha</string>
|
||||||
|
<string name="overlay_uri">Uri</string>
|
||||||
|
<string name="bitmap_overlay_settings">Specify bitmap overlay settings</string>
|
||||||
|
<string name="select_local_image">Select local image</string>
|
||||||
|
<string name="overlay_text">Text</string>
|
||||||
|
<string name="overlay_text_color">Text color</string>
|
||||||
|
<string name="text_overlay_settings">Specify text overlay settings</string>
|
||||||
</resources>
|
</resources>
|
||||||
|
@ -24,11 +24,13 @@ import android.content.Context;
|
|||||||
import android.opengl.EGL14;
|
import android.opengl.EGL14;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.FrameProcessingException;
|
import androidx.media3.common.GlObjectsProvider;
|
||||||
|
import androidx.media3.common.GlTextureInfo;
|
||||||
|
import androidx.media3.common.VideoFrameProcessingException;
|
||||||
import androidx.media3.common.util.LibraryLoader;
|
import androidx.media3.common.util.LibraryLoader;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import androidx.media3.effect.GlTextureProcessor;
|
import androidx.media3.effect.GlShaderProgram;
|
||||||
import androidx.media3.effect.TextureInfo;
|
import com.google.common.util.concurrent.MoreExecutors;
|
||||||
import com.google.mediapipe.components.FrameProcessor;
|
import com.google.mediapipe.components.FrameProcessor;
|
||||||
import com.google.mediapipe.framework.AppTextureFrame;
|
import com.google.mediapipe.framework.AppTextureFrame;
|
||||||
import com.google.mediapipe.framework.TextureFrame;
|
import com.google.mediapipe.framework.TextureFrame;
|
||||||
@ -37,13 +39,14 @@ import java.util.ArrayDeque;
|
|||||||
import java.util.Queue;
|
import java.util.Queue;
|
||||||
import java.util.concurrent.ConcurrentHashMap;
|
import java.util.concurrent.ConcurrentHashMap;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Future;
|
import java.util.concurrent.Future;
|
||||||
|
|
||||||
/** Runs a MediaPipe graph on input frames. */
|
/** Runs a MediaPipe graph on input frames. */
|
||||||
/* package */ final class MediaPipeProcessor implements GlTextureProcessor {
|
/* package */ final class MediaPipeShaderProgram implements GlShaderProgram {
|
||||||
|
|
||||||
private static final String THREAD_NAME = "Demo:MediaPipeProcessor";
|
private static final String THREAD_NAME = "Demo:MediaPipeShaderProgram";
|
||||||
private static final long RELEASE_WAIT_TIME_MS = 100;
|
private static final long RELEASE_WAIT_TIME_MS = 100;
|
||||||
private static final long RETRY_WAIT_TIME_MS = 1;
|
private static final long RETRY_WAIT_TIME_MS = 1;
|
||||||
|
|
||||||
@ -66,7 +69,7 @@ import java.util.concurrent.Future;
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final FrameProcessor frameProcessor;
|
private final FrameProcessor frameProcessor;
|
||||||
private final ConcurrentHashMap<TextureInfo, TextureFrame> outputFrames;
|
private final ConcurrentHashMap<GlTextureInfo, TextureFrame> outputFrames;
|
||||||
private final boolean isSingleFrameGraph;
|
private final boolean isSingleFrameGraph;
|
||||||
@Nullable private final ExecutorService singleThreadExecutorService;
|
@Nullable private final ExecutorService singleThreadExecutorService;
|
||||||
private final Queue<Future<?>> futures;
|
private final Queue<Future<?>> futures;
|
||||||
@ -74,14 +77,15 @@ import java.util.concurrent.Future;
|
|||||||
private InputListener inputListener;
|
private InputListener inputListener;
|
||||||
private OutputListener outputListener;
|
private OutputListener outputListener;
|
||||||
private ErrorListener errorListener;
|
private ErrorListener errorListener;
|
||||||
|
private Executor errorListenerExecutor;
|
||||||
private boolean acceptedFrame;
|
private boolean acceptedFrame;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new texture processor that wraps a MediaPipe graph.
|
* Creates a new shader program that wraps a MediaPipe graph.
|
||||||
*
|
*
|
||||||
* <p>If {@code isSingleFrameGraph} is {@code false}, the {@code MediaPipeProcessor} may waste CPU
|
* <p>If {@code isSingleFrameGraph} is {@code false}, the {@code MediaPipeShaderProgram} may waste
|
||||||
* time by continuously attempting to queue input frames to MediaPipe until they are accepted or
|
* CPU time by continuously attempting to queue input frames to MediaPipe until they are accepted
|
||||||
* waste memory if MediaPipe accepts and stores many frames internally.
|
* or waste memory if MediaPipe accepts and stores many frames internally.
|
||||||
*
|
*
|
||||||
* @param context The {@link Context}.
|
* @param context The {@link Context}.
|
||||||
* @param useHdr Whether input textures come from an HDR source. If {@code true}, colors will be
|
* @param useHdr Whether input textures come from an HDR source. If {@code true}, colors will be
|
||||||
@ -92,7 +96,7 @@ import java.util.concurrent.Future;
|
|||||||
* @param inputStreamName Name of the input video stream in the graph.
|
* @param inputStreamName Name of the input video stream in the graph.
|
||||||
* @param outputStreamName Name of the input video stream in the graph.
|
* @param outputStreamName Name of the input video stream in the graph.
|
||||||
*/
|
*/
|
||||||
public MediaPipeProcessor(
|
public MediaPipeShaderProgram(
|
||||||
Context context,
|
Context context,
|
||||||
boolean useHdr,
|
boolean useHdr,
|
||||||
String graphName,
|
String graphName,
|
||||||
@ -100,8 +104,8 @@ import java.util.concurrent.Future;
|
|||||||
String inputStreamName,
|
String inputStreamName,
|
||||||
String outputStreamName) {
|
String outputStreamName) {
|
||||||
checkState(LOADER.isAvailable());
|
checkState(LOADER.isAvailable());
|
||||||
// TODO(b/227624622): Confirm whether MediaPipeProcessor could support HDR colors.
|
// TODO(b/227624622): Confirm whether MediaPipeShaderProgram could support HDR colors.
|
||||||
checkArgument(!useHdr, "MediaPipeProcessor does not support HDR colors.");
|
checkArgument(!useHdr, "MediaPipeShaderProgram does not support HDR colors.");
|
||||||
|
|
||||||
this.isSingleFrameGraph = isSingleFrameGraph;
|
this.isSingleFrameGraph = isSingleFrameGraph;
|
||||||
singleThreadExecutorService =
|
singleThreadExecutorService =
|
||||||
@ -109,7 +113,8 @@ import java.util.concurrent.Future;
|
|||||||
futures = new ArrayDeque<>();
|
futures = new ArrayDeque<>();
|
||||||
inputListener = new InputListener() {};
|
inputListener = new InputListener() {};
|
||||||
outputListener = new OutputListener() {};
|
outputListener = new OutputListener() {};
|
||||||
errorListener = (frameProcessingException) -> {};
|
errorListener = (videoFrameProcessingException) -> {};
|
||||||
|
errorListenerExecutor = MoreExecutors.directExecutor();
|
||||||
EglManager eglManager = new EglManager(EGL14.eglGetCurrentContext());
|
EglManager eglManager = new EglManager(EGL14.eglGetCurrentContext());
|
||||||
frameProcessor =
|
frameProcessor =
|
||||||
new FrameProcessor(
|
new FrameProcessor(
|
||||||
@ -133,10 +138,11 @@ import java.util.concurrent.Future;
|
|||||||
this.outputListener = outputListener;
|
this.outputListener = outputListener;
|
||||||
frameProcessor.setConsumer(
|
frameProcessor.setConsumer(
|
||||||
frame -> {
|
frame -> {
|
||||||
TextureInfo texture =
|
GlTextureInfo texture =
|
||||||
new TextureInfo(
|
new GlTextureInfo(
|
||||||
frame.getTextureName(),
|
frame.getTextureName(),
|
||||||
/* fboId= */ C.INDEX_UNSET,
|
/* fboId= */ C.INDEX_UNSET,
|
||||||
|
/* rboId= */ C.INDEX_UNSET,
|
||||||
frame.getWidth(),
|
frame.getWidth(),
|
||||||
frame.getHeight());
|
frame.getHeight());
|
||||||
outputFrames.put(texture, frame);
|
outputFrames.put(texture, frame);
|
||||||
@ -145,16 +151,23 @@ import java.util.concurrent.Future;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setErrorListener(ErrorListener errorListener) {
|
public void setErrorListener(Executor executor, ErrorListener errorListener) {
|
||||||
|
this.errorListenerExecutor = executor;
|
||||||
this.errorListener = errorListener;
|
this.errorListener = errorListener;
|
||||||
frameProcessor.setAsynchronousErrorListener(
|
frameProcessor.setAsynchronousErrorListener(
|
||||||
error -> errorListener.onFrameProcessingError(new FrameProcessingException(error)));
|
error ->
|
||||||
|
errorListenerExecutor.execute(
|
||||||
|
() -> errorListener.onError(new VideoFrameProcessingException(error))));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void queueInputFrame(TextureInfo inputTexture, long presentationTimeUs) {
|
public void setGlObjectsProvider(GlObjectsProvider glObjectsProvider) {}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void queueInputFrame(GlTextureInfo inputTexture, long presentationTimeUs) {
|
||||||
AppTextureFrame appTextureFrame =
|
AppTextureFrame appTextureFrame =
|
||||||
new AppTextureFrame(inputTexture.texId, inputTexture.width, inputTexture.height);
|
new AppTextureFrame(
|
||||||
|
inputTexture.getTexId(), inputTexture.getWidth(), inputTexture.getHeight());
|
||||||
// TODO(b/238302213): Handle timestamps restarting from 0 when applying effects to a playlist.
|
// TODO(b/238302213): Handle timestamps restarting from 0 when applying effects to a playlist.
|
||||||
// MediaPipe will fail if the timestamps are not monotonically increasing.
|
// MediaPipe will fail if the timestamps are not monotonically increasing.
|
||||||
// Also make sure that a MediaPipe graph producing additional frames only starts producing
|
// Also make sure that a MediaPipe graph producing additional frames only starts producing
|
||||||
@ -176,14 +189,15 @@ import java.util.concurrent.Future;
|
|||||||
}
|
}
|
||||||
|
|
||||||
private boolean maybeQueueInputFrameSynchronous(
|
private boolean maybeQueueInputFrameSynchronous(
|
||||||
AppTextureFrame appTextureFrame, TextureInfo inputTexture) {
|
AppTextureFrame appTextureFrame, GlTextureInfo inputTexture) {
|
||||||
acceptedFrame = false;
|
acceptedFrame = false;
|
||||||
frameProcessor.onNewFrame(appTextureFrame);
|
frameProcessor.onNewFrame(appTextureFrame);
|
||||||
try {
|
try {
|
||||||
appTextureFrame.waitUntilReleasedWithGpuSync();
|
appTextureFrame.waitUntilReleasedWithGpuSync();
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
errorListener.onFrameProcessingError(new FrameProcessingException(e));
|
errorListenerExecutor.execute(
|
||||||
|
() -> errorListener.onError(new VideoFrameProcessingException(e)));
|
||||||
}
|
}
|
||||||
if (acceptedFrame) {
|
if (acceptedFrame) {
|
||||||
inputListener.onInputFrameProcessed(inputTexture);
|
inputListener.onInputFrameProcessed(inputTexture);
|
||||||
@ -192,7 +206,7 @@ import java.util.concurrent.Future;
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void queueInputFrameAsynchronous(
|
private void queueInputFrameAsynchronous(
|
||||||
AppTextureFrame appTextureFrame, TextureInfo inputTexture) {
|
AppTextureFrame appTextureFrame, GlTextureInfo inputTexture) {
|
||||||
removeFinishedFutures();
|
removeFinishedFutures();
|
||||||
futures.add(
|
futures.add(
|
||||||
checkStateNotNull(singleThreadExecutorService)
|
checkStateNotNull(singleThreadExecutorService)
|
||||||
@ -204,7 +218,8 @@ import java.util.concurrent.Future;
|
|||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
if (errorListener != null) {
|
if (errorListener != null) {
|
||||||
errorListener.onFrameProcessingError(new FrameProcessingException(e));
|
errorListenerExecutor.execute(
|
||||||
|
() -> errorListener.onError(new VideoFrameProcessingException(e)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -213,13 +228,19 @@ import java.util.concurrent.Future;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void releaseOutputFrame(TextureInfo outputTexture) {
|
public void releaseOutputFrame(GlTextureInfo outputTexture) {
|
||||||
checkStateNotNull(outputFrames.get(outputTexture)).release();
|
checkStateNotNull(outputFrames.get(outputTexture)).release();
|
||||||
if (isSingleFrameGraph) {
|
if (isSingleFrameGraph) {
|
||||||
inputListener.onReadyToAcceptInputFrame();
|
inputListener.onReadyToAcceptInputFrame();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void flush() {
|
||||||
|
// TODO(b/238302341) Support seeking in MediaPipeShaderProgram.
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void release() {
|
public void release() {
|
||||||
if (isSingleFrameGraph) {
|
if (isSingleFrameGraph) {
|
||||||
@ -236,11 +257,13 @@ import java.util.concurrent.Future;
|
|||||||
singleThreadExecutorService.shutdown();
|
singleThreadExecutorService.shutdown();
|
||||||
try {
|
try {
|
||||||
if (!singleThreadExecutorService.awaitTermination(RELEASE_WAIT_TIME_MS, MILLISECONDS)) {
|
if (!singleThreadExecutorService.awaitTermination(RELEASE_WAIT_TIME_MS, MILLISECONDS)) {
|
||||||
errorListener.onFrameProcessingError(new FrameProcessingException("Release timed out"));
|
errorListenerExecutor.execute(
|
||||||
|
() -> errorListener.onError(new VideoFrameProcessingException("Release timed out")));
|
||||||
}
|
}
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
errorListener.onFrameProcessingError(new FrameProcessingException(e));
|
errorListenerExecutor.execute(
|
||||||
|
() -> errorListener.onError(new VideoFrameProcessingException(e)));
|
||||||
}
|
}
|
||||||
|
|
||||||
frameProcessor.close();
|
frameProcessor.close();
|
||||||
@ -272,10 +295,12 @@ import java.util.concurrent.Future;
|
|||||||
try {
|
try {
|
||||||
futures.remove().get();
|
futures.remove().get();
|
||||||
} catch (ExecutionException e) {
|
} catch (ExecutionException e) {
|
||||||
errorListener.onFrameProcessingError(new FrameProcessingException(e));
|
errorListenerExecutor.execute(
|
||||||
|
() -> errorListener.onError(new VideoFrameProcessingException(e)));
|
||||||
} catch (InterruptedException e) {
|
} catch (InterruptedException e) {
|
||||||
Thread.currentThread().interrupt();
|
Thread.currentThread().interrupt();
|
||||||
errorListener.onFrameProcessingError(new FrameProcessingException(e));
|
errorListenerExecutor.execute(
|
||||||
|
() -> errorListener.onError(new VideoFrameProcessingException(e)));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
@ -17,11 +17,6 @@ apply from: "${buildscript.sourceFile.parentFile}/javadoc_util.gradle"
|
|||||||
class CombinedJavadocPlugin implements Plugin<Project> {
|
class CombinedJavadocPlugin implements Plugin<Project> {
|
||||||
|
|
||||||
static final String JAVADOC_TASK_NAME = "generateCombinedJavadoc"
|
static final String JAVADOC_TASK_NAME = "generateCombinedJavadoc"
|
||||||
static final String DACKKA_TASK_NAME = "generateCombinedDackka"
|
|
||||||
|
|
||||||
// Dackka snapshots are listed at https://androidx.dev/dackka/builds.
|
|
||||||
static final String DACKKA_JAR_URL =
|
|
||||||
"https://androidx.dev/dackka/builds/9221390/artifacts/dackka-1.0.4-all.jar"
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
void apply(Project project) {
|
void apply(Project project) {
|
||||||
@ -83,71 +78,6 @@ class CombinedJavadocPlugin implements Plugin<Project> {
|
|||||||
project.fixJavadoc()
|
project.fixJavadoc()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
def dackkaOutputDir = project.file("$project.buildDir/docs/dackka")
|
|
||||||
project.task(DACKKA_TASK_NAME, type: JavaExec) {
|
|
||||||
doFirst {
|
|
||||||
// Recreate the output directory to remove any leftover files from a previous run.
|
|
||||||
project.delete dackkaOutputDir
|
|
||||||
project.mkdir dackkaOutputDir
|
|
||||||
|
|
||||||
// Download the Dackka JAR.
|
|
||||||
new URL(DACKKA_JAR_URL).withInputStream {
|
|
||||||
i -> classpath.getSingleFile().withOutputStream { it << i }
|
|
||||||
}
|
|
||||||
|
|
||||||
// Build lists of source files and dependencies.
|
|
||||||
def sources = []
|
|
||||||
def dependencies = []
|
|
||||||
libraryModules.each { libraryModule ->
|
|
||||||
libraryModule.android.libraryVariants.all { variant ->
|
|
||||||
def name = variant.buildType.name
|
|
||||||
if (name == "release") {
|
|
||||||
def classpathFiles =
|
|
||||||
project.files(variant.javaCompileProvider.get().classpath.files)
|
|
||||||
variant.sourceSets.inject(sources) {
|
|
||||||
acc, val -> acc << val.javaDirectories
|
|
||||||
}
|
|
||||||
dependencies << classpathFiles.filter { f -> !(f.path.contains("/buildout/")) }
|
|
||||||
dependencies << libraryModule.project.android.getBootClasspath()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// Set command line arguments to Dackka.
|
|
||||||
def guavaPackageListFile = getGuavaPackageListFile(getTemporaryDir())
|
|
||||||
def globalLinksString = "$guavaReferenceUrl^$guavaPackageListFile^^"
|
|
||||||
def sourcesString = project.files(sources.flatten())
|
|
||||||
.filter({ f -> project.file(f).exists() }).join(";")
|
|
||||||
def dependenciesString = project.files(dependencies).asPath.replace(':', ';')
|
|
||||||
def sourceSet = [
|
|
||||||
"-src", sourcesString,
|
|
||||||
"-classpath", dependenciesString,
|
|
||||||
"-documentedVisibilities", "PUBLIC;PROTECTED"
|
|
||||||
].join(" ")
|
|
||||||
args("-moduleName", "",
|
|
||||||
"-outputDir", "$dackkaOutputDir",
|
|
||||||
"-globalLinks", "$globalLinksString",
|
|
||||||
"-loggingLevel", "WARN",
|
|
||||||
"-sourceSet", "$sourceSet",
|
|
||||||
"-offlineMode")
|
|
||||||
environment("DEVSITE_TENANT", "androidx/media3")
|
|
||||||
}
|
|
||||||
description = "Generates combined javadoc for developer.android.com."
|
|
||||||
classpath = project.files(new File(getTemporaryDir(), "dackka.jar"))
|
|
||||||
doLast {
|
|
||||||
libraryModules.each { libraryModule ->
|
|
||||||
project.copy {
|
|
||||||
from "${libraryModule.projectDir}/src/main/javadoc"
|
|
||||||
into "${dackkaOutputDir}/reference/"
|
|
||||||
}
|
|
||||||
project.copy {
|
|
||||||
from "${libraryModule.projectDir}/src/main/javadoc"
|
|
||||||
into "${dackkaOutputDir}/reference/kotlin/"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -160,17 +90,6 @@ class CombinedJavadocPlugin implements Plugin<Project> {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// Returns a file containing the list of packages that should be linked to Guava documentation.
|
|
||||||
private static File getGuavaPackageListFile(File directory) {
|
|
||||||
def packageListFile = new File(directory, "guava")
|
|
||||||
packageListFile.text = ["com.google.common.base", "com.google.common.collect",
|
|
||||||
"com.google.common.io", "com.google.common.math",
|
|
||||||
"com.google.common.net", "com.google.common.primitives",
|
|
||||||
"com.google.common.truth", "com.google.common.util.concurrent"]
|
|
||||||
.join('\n')
|
|
||||||
return packageListFile
|
|
||||||
}
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
apply plugin: CombinedJavadocPlugin
|
apply plugin: CombinedJavadocPlugin
|
||||||
|
@ -14,7 +14,7 @@
|
|||||||
apply from: "$gradle.ext.androidxMediaSettingsDir/common_library_config.gradle"
|
apply from: "$gradle.ext.androidxMediaSettingsDir/common_library_config.gradle"
|
||||||
|
|
||||||
dependencies {
|
dependencies {
|
||||||
api 'com.google.android.gms:play-services-cast-framework:21.2.0'
|
api 'com.google.android.gms:play-services-cast-framework:21.3.0'
|
||||||
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
|
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
|
||||||
implementation project(modulePrefix + 'lib-common')
|
implementation project(modulePrefix + 'lib-common')
|
||||||
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
|
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
|
||||||
|
@ -85,7 +85,7 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
|
|
||||||
/** The {@link DeviceInfo} returned by {@link #getDeviceInfo() this player}. */
|
/** The {@link DeviceInfo} returned by {@link #getDeviceInfo() this player}. */
|
||||||
public static final DeviceInfo DEVICE_INFO =
|
public static final DeviceInfo DEVICE_INFO =
|
||||||
new DeviceInfo(DeviceInfo.PLAYBACK_TYPE_REMOTE, /* minVolume= */ 0, /* maxVolume= */ 0);
|
new DeviceInfo.Builder(DeviceInfo.PLAYBACK_TYPE_REMOTE).build();
|
||||||
|
|
||||||
static {
|
static {
|
||||||
MediaLibraryInfo.registerModule("media3.cast");
|
MediaLibraryInfo.registerModule("media3.cast");
|
||||||
@ -104,11 +104,12 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
COMMAND_SET_SPEED_AND_PITCH,
|
COMMAND_SET_SPEED_AND_PITCH,
|
||||||
COMMAND_GET_CURRENT_MEDIA_ITEM,
|
COMMAND_GET_CURRENT_MEDIA_ITEM,
|
||||||
COMMAND_GET_TIMELINE,
|
COMMAND_GET_TIMELINE,
|
||||||
COMMAND_GET_MEDIA_ITEMS_METADATA,
|
COMMAND_GET_METADATA,
|
||||||
COMMAND_SET_MEDIA_ITEMS_METADATA,
|
COMMAND_SET_PLAYLIST_METADATA,
|
||||||
COMMAND_SET_MEDIA_ITEM,
|
COMMAND_SET_MEDIA_ITEM,
|
||||||
COMMAND_CHANGE_MEDIA_ITEMS,
|
COMMAND_CHANGE_MEDIA_ITEMS,
|
||||||
COMMAND_GET_TRACKS)
|
COMMAND_GET_TRACKS,
|
||||||
|
COMMAND_RELEASE)
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
public static final float MIN_SPEED_SUPPORTED = 0.5f;
|
public static final float MIN_SPEED_SUPPORTED = 0.5f;
|
||||||
@ -320,6 +321,18 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
moveMediaItemsInternal(uids, fromIndex, newIndex);
|
moveMediaItemsInternal(uids, fromIndex, newIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void replaceMediaItems(int fromIndex, int toIndex, List<MediaItem> mediaItems) {
|
||||||
|
checkArgument(fromIndex >= 0 && fromIndex <= toIndex);
|
||||||
|
int playlistSize = currentTimeline.getWindowCount();
|
||||||
|
if (fromIndex > playlistSize) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
toIndex = min(toIndex, playlistSize);
|
||||||
|
addMediaItems(toIndex, mediaItems);
|
||||||
|
removeMediaItems(fromIndex, toIndex);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void removeMediaItems(int fromIndex, int toIndex) {
|
public void removeMediaItems(int fromIndex, int toIndex) {
|
||||||
checkArgument(fromIndex >= 0 && toIndex >= fromIndex);
|
checkArgument(fromIndex >= 0 && toIndex >= fromIndex);
|
||||||
@ -393,8 +406,9 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
return playWhenReady.value;
|
return playWhenReady.value;
|
||||||
}
|
}
|
||||||
|
|
||||||
// We still call Listener#onSeekProcessed() for backwards compatibility with listeners that
|
// We still call Listener#onPositionDiscontinuity(@DiscontinuityReason int) for backwards
|
||||||
// don't implement onPositionDiscontinuity().
|
// compatibility with listeners that don't implement
|
||||||
|
// onPositionDiscontinuity(PositionInfo, PositionInfo, @DiscontinuityReason int).
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
@Override
|
@Override
|
||||||
@VisibleForTesting(otherwise = PROTECTED)
|
@VisibleForTesting(otherwise = PROTECTED)
|
||||||
@ -448,8 +462,6 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
updateAvailableCommandsAndNotifyIfChanged();
|
updateAvailableCommandsAndNotifyIfChanged();
|
||||||
} else if (pendingSeekCount == 0) {
|
|
||||||
listeners.queueEvent(/* eventFlag= */ C.INDEX_UNSET, Listener::onSeekProcessed);
|
|
||||||
}
|
}
|
||||||
listeners.flushEvents();
|
listeners.flushEvents();
|
||||||
}
|
}
|
||||||
@ -476,17 +488,6 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop() {
|
public void stop() {
|
||||||
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
|
|
||||||
@Override
|
|
||||||
public void stop(boolean reset) {
|
|
||||||
playbackState = STATE_IDLE;
|
playbackState = STATE_IDLE;
|
||||||
if (remoteMediaClient != null) {
|
if (remoteMediaClient != null) {
|
||||||
// TODO(b/69792021): Support or emulate stop without position reset.
|
// TODO(b/69792021): Support or emulate stop without position reset.
|
||||||
@ -765,22 +766,50 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** This method is not supported and does nothing. */
|
/**
|
||||||
|
* @deprecated Use {@link #setDeviceVolume(int, int)} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
public void setDeviceVolume(int volume) {}
|
public void setDeviceVolume(int volume) {}
|
||||||
|
|
||||||
/** This method is not supported and does nothing. */
|
/** This method is not supported and does nothing. */
|
||||||
@Override
|
@Override
|
||||||
|
public void setDeviceVolume(int volume, @C.VolumeFlags int flags) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #increaseDeviceVolume(int)} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
@Override
|
||||||
public void increaseDeviceVolume() {}
|
public void increaseDeviceVolume() {}
|
||||||
|
|
||||||
/** This method is not supported and does nothing. */
|
/** This method is not supported and does nothing. */
|
||||||
@Override
|
@Override
|
||||||
|
public void increaseDeviceVolume(@C.VolumeFlags int flags) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #decreaseDeviceVolume(int)} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
@Override
|
||||||
public void decreaseDeviceVolume() {}
|
public void decreaseDeviceVolume() {}
|
||||||
|
|
||||||
/** This method is not supported and does nothing. */
|
/** This method is not supported and does nothing. */
|
||||||
@Override
|
@Override
|
||||||
|
public void decreaseDeviceVolume(@C.VolumeFlags int flags) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #setDeviceMuted(boolean, int)} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
@Override
|
||||||
public void setDeviceMuted(boolean muted) {}
|
public void setDeviceMuted(boolean muted) {}
|
||||||
|
|
||||||
|
/** This method is not supported and does nothing. */
|
||||||
|
@Override
|
||||||
|
public void setDeviceMuted(boolean muted, @C.VolumeFlags int flags) {}
|
||||||
|
|
||||||
// Internal methods.
|
// Internal methods.
|
||||||
|
|
||||||
// Call deprecated callbacks.
|
// Call deprecated callbacks.
|
||||||
@ -1419,9 +1448,6 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
|
|
||||||
private final class SeekResultCallback implements ResultCallback<MediaChannelResult> {
|
private final class SeekResultCallback implements ResultCallback<MediaChannelResult> {
|
||||||
|
|
||||||
// We still call Listener#onSeekProcessed() for backwards compatibility with listeners that
|
|
||||||
// don't implement onPositionDiscontinuity().
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
@Override
|
@Override
|
||||||
public void onResult(MediaChannelResult result) {
|
public void onResult(MediaChannelResult result) {
|
||||||
int statusCode = result.getStatus().getStatusCode();
|
int statusCode = result.getStatus().getStatusCode();
|
||||||
@ -1434,7 +1460,6 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
currentWindowIndex = pendingSeekWindowIndex;
|
currentWindowIndex = pendingSeekWindowIndex;
|
||||||
pendingSeekWindowIndex = C.INDEX_UNSET;
|
pendingSeekWindowIndex = C.INDEX_UNSET;
|
||||||
pendingSeekPositionMs = C.TIME_UNSET;
|
pendingSeekPositionMs = C.TIME_UNSET;
|
||||||
listeners.sendEvent(/* eventFlag= */ C.INDEX_UNSET, Listener::onSeekProcessed);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -20,7 +20,7 @@ import static androidx.media3.common.Player.COMMAND_CHANGE_MEDIA_ITEMS;
|
|||||||
import static androidx.media3.common.Player.COMMAND_GET_AUDIO_ATTRIBUTES;
|
import static androidx.media3.common.Player.COMMAND_GET_AUDIO_ATTRIBUTES;
|
||||||
import static androidx.media3.common.Player.COMMAND_GET_CURRENT_MEDIA_ITEM;
|
import static androidx.media3.common.Player.COMMAND_GET_CURRENT_MEDIA_ITEM;
|
||||||
import static androidx.media3.common.Player.COMMAND_GET_DEVICE_VOLUME;
|
import static androidx.media3.common.Player.COMMAND_GET_DEVICE_VOLUME;
|
||||||
import static androidx.media3.common.Player.COMMAND_GET_MEDIA_ITEMS_METADATA;
|
import static androidx.media3.common.Player.COMMAND_GET_METADATA;
|
||||||
import static androidx.media3.common.Player.COMMAND_GET_TEXT;
|
import static androidx.media3.common.Player.COMMAND_GET_TEXT;
|
||||||
import static androidx.media3.common.Player.COMMAND_GET_TIMELINE;
|
import static androidx.media3.common.Player.COMMAND_GET_TIMELINE;
|
||||||
import static androidx.media3.common.Player.COMMAND_GET_VOLUME;
|
import static androidx.media3.common.Player.COMMAND_GET_VOLUME;
|
||||||
@ -37,7 +37,7 @@ 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_ITEM;
|
||||||
import static androidx.media3.common.Player.COMMAND_SET_MEDIA_ITEMS_METADATA;
|
import static androidx.media3.common.Player.COMMAND_SET_PLAYLIST_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;
|
||||||
import static androidx.media3.common.Player.COMMAND_SET_SPEED_AND_PITCH;
|
import static androidx.media3.common.Player.COMMAND_SET_SPEED_AND_PITCH;
|
||||||
@ -699,6 +699,38 @@ public class CastPlayerTest {
|
|||||||
.queueRemoveItems(new int[] {1, 2, 3, 4, 5}, /* customData= */ null);
|
.queueRemoveItems(new int[] {1, 2, 3, 4, 5}, /* customData= */ null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void replaceMediaItems_callsRemoteMediaClient() {
|
||||||
|
int[] mediaQueueItemIds = createMediaQueueItemIds(/* numberOfIds= */ 2);
|
||||||
|
List<MediaItem> mediaItems = createMediaItems(mediaQueueItemIds);
|
||||||
|
// Add two items.
|
||||||
|
addMediaItemsAndUpdateTimeline(mediaItems, mediaQueueItemIds);
|
||||||
|
String uri = "http://www.google.com/video3";
|
||||||
|
MediaItem anotherMediaItem =
|
||||||
|
new MediaItem.Builder().setUri(uri).setMimeType(MimeTypes.APPLICATION_MPD).build();
|
||||||
|
ImmutableList<MediaItem> newPlaylist = ImmutableList.of(mediaItems.get(0), anotherMediaItem);
|
||||||
|
|
||||||
|
// Replace item at position 1.
|
||||||
|
castPlayer.replaceMediaItems(
|
||||||
|
/* fromIndex= */ 1, /* toIndex= */ 2, ImmutableList.of(anotherMediaItem));
|
||||||
|
updateTimeLine(
|
||||||
|
newPlaylist,
|
||||||
|
/* mediaQueueItemIds= */ new int[] {mediaQueueItemIds[0], 123},
|
||||||
|
/* currentItemId= */ 123);
|
||||||
|
|
||||||
|
verify(mockRemoteMediaClient, times(2))
|
||||||
|
.queueInsertItems(queueItemsArgumentCaptor.capture(), anyInt(), any());
|
||||||
|
verify(mockRemoteMediaClient).queueRemoveItems(new int[] {2}, /* customData= */ null);
|
||||||
|
assertThat(queueItemsArgumentCaptor.getAllValues().get(1)[0])
|
||||||
|
.isEqualTo(mediaItemConverter.toMediaQueueItem(anotherMediaItem));
|
||||||
|
Timeline.Window currentWindow =
|
||||||
|
castPlayer
|
||||||
|
.getCurrentTimeline()
|
||||||
|
.getWindow(castPlayer.getCurrentMediaItemIndex(), new Timeline.Window());
|
||||||
|
assertThat(currentWindow.uid).isEqualTo(123);
|
||||||
|
assertThat(currentWindow.mediaItem).isEqualTo(anotherMediaItem);
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("ConstantConditions")
|
@SuppressWarnings("ConstantConditions")
|
||||||
@Test
|
@Test
|
||||||
public void addMediaItems_fillsTimeline() {
|
public void addMediaItems_fillsTimeline() {
|
||||||
@ -1360,8 +1392,8 @@ public class CastPlayerTest {
|
|||||||
assertThat(castPlayer.isCommandAvailable(COMMAND_SET_REPEAT_MODE)).isTrue();
|
assertThat(castPlayer.isCommandAvailable(COMMAND_SET_REPEAT_MODE)).isTrue();
|
||||||
assertThat(castPlayer.isCommandAvailable(COMMAND_GET_CURRENT_MEDIA_ITEM)).isTrue();
|
assertThat(castPlayer.isCommandAvailable(COMMAND_GET_CURRENT_MEDIA_ITEM)).isTrue();
|
||||||
assertThat(castPlayer.isCommandAvailable(COMMAND_GET_TIMELINE)).isTrue();
|
assertThat(castPlayer.isCommandAvailable(COMMAND_GET_TIMELINE)).isTrue();
|
||||||
assertThat(castPlayer.isCommandAvailable(COMMAND_GET_MEDIA_ITEMS_METADATA)).isTrue();
|
assertThat(castPlayer.isCommandAvailable(COMMAND_GET_METADATA)).isTrue();
|
||||||
assertThat(castPlayer.isCommandAvailable(COMMAND_SET_MEDIA_ITEMS_METADATA)).isTrue();
|
assertThat(castPlayer.isCommandAvailable(COMMAND_SET_PLAYLIST_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_SET_MEDIA_ITEM)).isTrue();
|
||||||
assertThat(castPlayer.isCommandAvailable(COMMAND_GET_AUDIO_ATTRIBUTES)).isFalse();
|
assertThat(castPlayer.isCommandAvailable(COMMAND_GET_AUDIO_ATTRIBUTES)).isFalse();
|
||||||
@ -1372,6 +1404,7 @@ public class CastPlayerTest {
|
|||||||
assertThat(castPlayer.isCommandAvailable(COMMAND_ADJUST_DEVICE_VOLUME)).isFalse();
|
assertThat(castPlayer.isCommandAvailable(COMMAND_ADJUST_DEVICE_VOLUME)).isFalse();
|
||||||
assertThat(castPlayer.isCommandAvailable(COMMAND_SET_VIDEO_SURFACE)).isFalse();
|
assertThat(castPlayer.isCommandAvailable(COMMAND_SET_VIDEO_SURFACE)).isFalse();
|
||||||
assertThat(castPlayer.isCommandAvailable(COMMAND_GET_TEXT)).isFalse();
|
assertThat(castPlayer.isCommandAvailable(COMMAND_GET_TEXT)).isFalse();
|
||||||
|
assertThat(castPlayer.isCommandAvailable(Player.COMMAND_RELEASE)).isTrue();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -311,7 +311,7 @@ public class CastTimelineTrackerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static MediaInfo getMediaInfo(long durationMs) {
|
private static MediaInfo getMediaInfo(long durationMs) {
|
||||||
return new MediaInfo.Builder(/*contentId= */ "")
|
return new MediaInfo.Builder(/* contentId= */ "")
|
||||||
.setStreamDuration(durationMs)
|
.setStreamDuration(durationMs)
|
||||||
.setContentType(MimeTypes.APPLICATION_MP4)
|
.setContentType(MimeTypes.APPLICATION_MP4)
|
||||||
.setStreamType(MediaInfo.STREAM_TYPE_NONE)
|
.setStreamType(MediaInfo.STREAM_TYPE_NONE)
|
||||||
|
@ -172,6 +172,10 @@ public final class AdPlaybackState implements Bundleable {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private boolean isLivePostrollPlaceholder() {
|
||||||
|
return isServerSideInserted && timeUs == C.TIME_END_OF_SOURCE && count == C.LENGTH_UNSET;
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(@Nullable Object o) {
|
public boolean equals(@Nullable Object o) {
|
||||||
if (this == o) {
|
if (this == o) {
|
||||||
@ -629,6 +633,7 @@ public final class AdPlaybackState implements Bundleable {
|
|||||||
// Use a linear search as the array elements may not be increasing due to TIME_END_OF_SOURCE.
|
// Use a linear search as the array elements may not be increasing due to TIME_END_OF_SOURCE.
|
||||||
// In practice we expect there to be few ad groups so the search shouldn't be expensive.
|
// In practice we expect there to be few ad groups so the search shouldn't be expensive.
|
||||||
int index = adGroupCount - 1;
|
int index = adGroupCount - 1;
|
||||||
|
index -= isLivePostrollPlaceholder(index) ? 1 : 0;
|
||||||
while (index >= 0 && isPositionBeforeAdGroup(positionUs, periodDurationUs, index)) {
|
while (index >= 0 && isPositionBeforeAdGroup(positionUs, periodDurationUs, index)) {
|
||||||
index--;
|
index--;
|
||||||
}
|
}
|
||||||
@ -976,6 +981,49 @@ public final class AdPlaybackState implements Bundleable {
|
|||||||
adsId, adGroups, adResumePositionUs, contentDurationUs, removedAdGroupCount);
|
adsId, adGroups, adResumePositionUs, contentDurationUs, removedAdGroupCount);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Appends a live postroll placeholder ad group to the ad playback state.
|
||||||
|
*
|
||||||
|
* <p>Adding such a placeholder is only required for periods of server side ad insertion live
|
||||||
|
* streams.
|
||||||
|
*
|
||||||
|
* <p>When building the media period queue, it sets {@link MediaPeriodId#nextAdGroupIndex} of a
|
||||||
|
* content period to the index of the placeholder. However, the placeholder will not produce a
|
||||||
|
* period in the media period queue. This only happens when an actual ad group is inserted at the
|
||||||
|
* given {@code nextAdGroupIndex}. In this case the newly inserted ad group will be used to insert
|
||||||
|
* an ad period into the media period queue following the content period with the given {@link
|
||||||
|
* MediaPeriodId#nextAdGroupIndex}.
|
||||||
|
*
|
||||||
|
* <p>See {@link #endsWithLivePostrollPlaceHolder()} also.
|
||||||
|
*
|
||||||
|
* @return The new ad playback state instance ending with a live postroll placeholder.
|
||||||
|
*/
|
||||||
|
public AdPlaybackState withLivePostrollPlaceholderAppended() {
|
||||||
|
return withNewAdGroup(adGroupCount, /* adGroupTimeUs= */ C.TIME_END_OF_SOURCE)
|
||||||
|
.withIsServerSideInserted(adGroupCount, true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the last ad group is a live postroll placeholder as inserted by {@link
|
||||||
|
* #withLivePostrollPlaceholderAppended()}.
|
||||||
|
*
|
||||||
|
* @return Whether the ad playback state ends with a live postroll placeholder.
|
||||||
|
*/
|
||||||
|
public boolean endsWithLivePostrollPlaceHolder() {
|
||||||
|
int adGroupIndex = adGroupCount - 1;
|
||||||
|
return adGroupIndex >= 0 && isLivePostrollPlaceholder(adGroupIndex);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the {@link AdGroup} at the given ad group index is a live postroll placeholder.
|
||||||
|
*
|
||||||
|
* @param adGroupIndex The ad group index.
|
||||||
|
* @return True if the ad group at the given index is a live postroll placeholder, false if not.
|
||||||
|
*/
|
||||||
|
public boolean isLivePostrollPlaceholder(int adGroupIndex) {
|
||||||
|
return adGroupIndex == adGroupCount - 1 && getAdGroup(adGroupIndex).isLivePostrollPlaceholder();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a copy of the ad playback state with the given ads ID.
|
* Returns a copy of the ad playback state with the given ads ID.
|
||||||
*
|
*
|
||||||
@ -1088,15 +1136,21 @@ public final class AdPlaybackState implements Bundleable {
|
|||||||
private boolean isPositionBeforeAdGroup(
|
private boolean isPositionBeforeAdGroup(
|
||||||
long positionUs, long periodDurationUs, int adGroupIndex) {
|
long positionUs, long periodDurationUs, int adGroupIndex) {
|
||||||
if (positionUs == C.TIME_END_OF_SOURCE) {
|
if (positionUs == C.TIME_END_OF_SOURCE) {
|
||||||
// The end of the content is at (but not before) any postroll ad, and after any other ads.
|
// The end of the content is at (but not before) any postroll ad, and after any other ad.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
long adGroupPositionUs = getAdGroup(adGroupIndex).timeUs;
|
AdGroup adGroup = getAdGroup(adGroupIndex);
|
||||||
|
long adGroupPositionUs = adGroup.timeUs;
|
||||||
if (adGroupPositionUs == C.TIME_END_OF_SOURCE) {
|
if (adGroupPositionUs == C.TIME_END_OF_SOURCE) {
|
||||||
return periodDurationUs == C.TIME_UNSET || positionUs < periodDurationUs;
|
// Handling postroll: The requested position is considered before a postroll when a)
|
||||||
} else {
|
// the period duration is unknown (last period in a live stream), or when b) the postroll is a
|
||||||
return positionUs < adGroupPositionUs;
|
// placeholder in a period of a multi-period live window, or when c) the position actually is
|
||||||
|
// before the given period duration.
|
||||||
|
return periodDurationUs == C.TIME_UNSET
|
||||||
|
|| (adGroup.isServerSideInserted && adGroup.count == C.LENGTH_UNSET)
|
||||||
|
|| positionUs < periodDurationUs;
|
||||||
}
|
}
|
||||||
|
return positionUs < adGroupPositionUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Bundleable implementation.
|
// Bundleable implementation.
|
||||||
|
@ -78,6 +78,12 @@ public abstract class BasePlayer implements Player {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void replaceMediaItem(int index, MediaItem mediaItem) {
|
||||||
|
replaceMediaItems(
|
||||||
|
/* fromIndex= */ index, /* toIndex= */ index + 1, ImmutableList.of(mediaItem));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void removeMediaItem(int index) {
|
public final void removeMediaItem(int index) {
|
||||||
removeMediaItems(/* fromIndex= */ index, /* toIndex= */ index + 1);
|
removeMediaItems(/* fromIndex= */ index, /* toIndex= */ index + 1);
|
||||||
|
@ -60,11 +60,13 @@ public final class C {
|
|||||||
*/
|
*/
|
||||||
public static final long TIME_UNSET = Long.MIN_VALUE + 1;
|
public static final long TIME_UNSET = Long.MIN_VALUE + 1;
|
||||||
|
|
||||||
/** Represents an unset or unknown index. */
|
/** Represents an unset or unknown index or byte position. */
|
||||||
public static final int INDEX_UNSET = -1;
|
public static final int INDEX_UNSET = -1;
|
||||||
|
|
||||||
/** Represents an unset or unknown position. */
|
/**
|
||||||
@UnstableApi public static final int POSITION_UNSET = -1;
|
* @deprecated Use {@link #INDEX_UNSET}.
|
||||||
|
*/
|
||||||
|
@Deprecated @UnstableApi public static final int POSITION_UNSET = INDEX_UNSET;
|
||||||
|
|
||||||
/** 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;
|
||||||
@ -93,36 +95,6 @@ public final class C {
|
|||||||
/** The number of bytes per float. */
|
/** The number of bytes per float. */
|
||||||
@UnstableApi public static final int BYTES_PER_FLOAT = 4;
|
@UnstableApi public static final int BYTES_PER_FLOAT = 4;
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use {@link java.nio.charset.StandardCharsets} or {@link
|
|
||||||
* com.google.common.base.Charsets} instead.
|
|
||||||
*/
|
|
||||||
@UnstableApi @Deprecated public static final String ASCII_NAME = "US-ASCII";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use {@link java.nio.charset.StandardCharsets} or {@link
|
|
||||||
* com.google.common.base.Charsets} instead.
|
|
||||||
*/
|
|
||||||
@UnstableApi @Deprecated public static final String UTF8_NAME = "UTF-8";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use {@link java.nio.charset.StandardCharsets} or {@link
|
|
||||||
* com.google.common.base.Charsets} instead.
|
|
||||||
*/
|
|
||||||
@UnstableApi @Deprecated public static final String ISO88591_NAME = "ISO-8859-1";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use {@link java.nio.charset.StandardCharsets} or {@link
|
|
||||||
* com.google.common.base.Charsets} instead.
|
|
||||||
*/
|
|
||||||
@UnstableApi @Deprecated public static final String UTF16_NAME = "UTF-16";
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use {@link java.nio.charset.StandardCharsets} or {@link
|
|
||||||
* com.google.common.base.Charsets} instead.
|
|
||||||
*/
|
|
||||||
@UnstableApi @Deprecated public static final String UTF16LE_NAME = "UTF-16LE";
|
|
||||||
|
|
||||||
/** The name of the serif font family. */
|
/** The name of the serif font family. */
|
||||||
@UnstableApi public static final String SERIF_NAME = "serif";
|
@UnstableApi public static final String SERIF_NAME = "serif";
|
||||||
|
|
||||||
@ -170,17 +142,11 @@ 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 {@link 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 {@link 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 {@link 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;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -226,6 +192,7 @@ public final class C {
|
|||||||
ENCODING_DTS_HD,
|
ENCODING_DTS_HD,
|
||||||
ENCODING_DOLBY_TRUEHD,
|
ENCODING_DOLBY_TRUEHD,
|
||||||
ENCODING_OPUS,
|
ENCODING_OPUS,
|
||||||
|
ENCODING_DTS_UHD_P2,
|
||||||
})
|
})
|
||||||
public @interface Encoding {}
|
public @interface Encoding {}
|
||||||
|
|
||||||
@ -250,17 +217,11 @@ public final class C {
|
|||||||
ENCODING_PCM_FLOAT
|
ENCODING_PCM_FLOAT
|
||||||
})
|
})
|
||||||
public @interface PcmEncoding {}
|
public @interface PcmEncoding {}
|
||||||
/**
|
/** See {@link 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 {@link 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 {@link 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;
|
||||||
@ -268,67 +229,39 @@ 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 {@link 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 {@link 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 {@link 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 {@link 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 {@link 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 {@link 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 {@link 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 {@link 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 {@link 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 {@link 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 {@link 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 {@link 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 {@link 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;
|
||||||
/**
|
// TODO(internal b/283949283): Use AudioFormat.ENCODING_DTS_UHD_P2 when Android 14 is released.
|
||||||
* @see AudioFormat#ENCODING_DOLBY_TRUEHD
|
@UnstableApi public static final int ENCODING_DTS_UHD_P2 = 0x0000001e;
|
||||||
*/
|
/** See {@link 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;
|
||||||
/**
|
/** See {@link AudioFormat#ENCODING_OPUS}. */
|
||||||
* @see AudioFormat#ENCODING_OPUS
|
|
||||||
*/
|
|
||||||
@UnstableApi public static final int ENCODING_OPUS = AudioFormat.ENCODING_OPUS;
|
@UnstableApi public static final int ENCODING_OPUS = AudioFormat.ENCODING_OPUS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -341,14 +274,10 @@ public final class C {
|
|||||||
@IntDef({SPATIALIZATION_BEHAVIOR_AUTO, SPATIALIZATION_BEHAVIOR_NEVER})
|
@IntDef({SPATIALIZATION_BEHAVIOR_AUTO, SPATIALIZATION_BEHAVIOR_NEVER})
|
||||||
public @interface SpatializationBehavior {}
|
public @interface SpatializationBehavior {}
|
||||||
|
|
||||||
/**
|
/** See {@link AudioAttributes#SPATIALIZATION_BEHAVIOR_AUTO}. */
|
||||||
* @see AudioAttributes#SPATIALIZATION_BEHAVIOR_AUTO
|
|
||||||
*/
|
|
||||||
public static final int SPATIALIZATION_BEHAVIOR_AUTO =
|
public static final int SPATIALIZATION_BEHAVIOR_AUTO =
|
||||||
AudioAttributes.SPATIALIZATION_BEHAVIOR_AUTO;
|
AudioAttributes.SPATIALIZATION_BEHAVIOR_AUTO;
|
||||||
/**
|
/** See {@link AudioAttributes#SPATIALIZATION_BEHAVIOR_NEVER}. */
|
||||||
* @see AudioAttributes#SPATIALIZATION_BEHAVIOR_NEVER
|
|
||||||
*/
|
|
||||||
public static final int SPATIALIZATION_BEHAVIOR_NEVER =
|
public static final int SPATIALIZATION_BEHAVIOR_NEVER =
|
||||||
AudioAttributes.SPATIALIZATION_BEHAVIOR_NEVER;
|
AudioAttributes.SPATIALIZATION_BEHAVIOR_NEVER;
|
||||||
|
|
||||||
@ -376,37 +305,54 @@ public final class C {
|
|||||||
STREAM_TYPE_DEFAULT
|
STREAM_TYPE_DEFAULT
|
||||||
})
|
})
|
||||||
public @interface StreamType {}
|
public @interface StreamType {}
|
||||||
/**
|
/** See {@link 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 {@link 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 {@link 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 {@link 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 {@link 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 {@link 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 {@link 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;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Volume flags to be used when setting or adjusting device volume. The value can be either 0 or a
|
||||||
|
* combination of the following flags: {@link #VOLUME_FLAG_SHOW_UI}, {@link
|
||||||
|
* #VOLUME_FLAG_ALLOW_RINGER_MODES}, {@link #VOLUME_FLAG_PLAY_SOUND}, {@link
|
||||||
|
* #VOLUME_FLAG_REMOVE_SOUND_AND_VIBRATE}, {@link #VOLUME_FLAG_VIBRATE}.
|
||||||
|
*/
|
||||||
|
@Documented
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@Target({TYPE_USE})
|
||||||
|
@IntDef(
|
||||||
|
flag = true,
|
||||||
|
value = {
|
||||||
|
VOLUME_FLAG_SHOW_UI,
|
||||||
|
VOLUME_FLAG_ALLOW_RINGER_MODES,
|
||||||
|
VOLUME_FLAG_PLAY_SOUND,
|
||||||
|
VOLUME_FLAG_REMOVE_SOUND_AND_VIBRATE,
|
||||||
|
VOLUME_FLAG_VIBRATE,
|
||||||
|
})
|
||||||
|
public @interface VolumeFlags {}
|
||||||
|
/** See {@link AudioManager#FLAG_SHOW_UI}. */
|
||||||
|
public static final int VOLUME_FLAG_SHOW_UI = AudioManager.FLAG_SHOW_UI;
|
||||||
|
/** See {@link AudioManager#FLAG_ALLOW_RINGER_MODES}. */
|
||||||
|
public static final int VOLUME_FLAG_ALLOW_RINGER_MODES = AudioManager.FLAG_ALLOW_RINGER_MODES;
|
||||||
|
/** See {@link AudioManager#FLAG_PLAY_SOUND}. */
|
||||||
|
public static final int VOLUME_FLAG_PLAY_SOUND = AudioManager.FLAG_PLAY_SOUND;
|
||||||
|
/** See {@link AudioManager#FLAG_REMOVE_SOUND_AND_VIBRATE}. */
|
||||||
|
public static final int VOLUME_FLAG_REMOVE_SOUND_AND_VIBRATE =
|
||||||
|
AudioManager.FLAG_REMOVE_SOUND_AND_VIBRATE;
|
||||||
|
/** See {@link AudioManager#FLAG_VIBRATE}. */
|
||||||
|
public static final int VOLUME_FLAG_VIBRATE = AudioManager.FLAG_VIBRATE;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Content types for audio attributes. One of:
|
* Content types for audio attributes. One of:
|
||||||
*
|
*
|
||||||
@ -480,9 +426,7 @@ public final class C {
|
|||||||
flag = true,
|
flag = true,
|
||||||
value = {FLAG_AUDIBILITY_ENFORCED})
|
value = {FLAG_AUDIBILITY_ENFORCED})
|
||||||
public @interface AudioFlags {}
|
public @interface AudioFlags {}
|
||||||
/**
|
/** See {@link 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;
|
||||||
|
|
||||||
@ -520,78 +464,46 @@ public final class C {
|
|||||||
USAGE_VOICE_COMMUNICATION_SIGNALLING
|
USAGE_VOICE_COMMUNICATION_SIGNALLING
|
||||||
})
|
})
|
||||||
public @interface AudioUsage {}
|
public @interface AudioUsage {}
|
||||||
/**
|
/** See {@link 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 {@link 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 {@link 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 {@link 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 {@link 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 {@link 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 {@link 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 {@link 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 {@link 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 {@link 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 {@link 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 {@link 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 {@link 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 {@link 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 {@link 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 {@link 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;
|
||||||
|
|
||||||
@ -1054,8 +966,8 @@ public final class C {
|
|||||||
|
|
||||||
// LINT.IfChange(color_space)
|
// LINT.IfChange(color_space)
|
||||||
/**
|
/**
|
||||||
* Video colorspaces. One of {@link Format#NO_VALUE}, {@link #COLOR_SPACE_BT601}, {@link
|
* Video color spaces, also referred to as color standards. One of {@link Format#NO_VALUE}, {@link
|
||||||
* #COLOR_SPACE_BT709} or {@link #COLOR_SPACE_BT2020}.
|
* #COLOR_SPACE_BT601}, {@link #COLOR_SPACE_BT709} or {@link #COLOR_SPACE_BT2020}.
|
||||||
*/
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Documented
|
@Documented
|
||||||
@ -1063,41 +975,52 @@ public final class C {
|
|||||||
@Target(TYPE_USE)
|
@Target(TYPE_USE)
|
||||||
@IntDef({Format.NO_VALUE, COLOR_SPACE_BT601, COLOR_SPACE_BT709, COLOR_SPACE_BT2020})
|
@IntDef({Format.NO_VALUE, COLOR_SPACE_BT601, COLOR_SPACE_BT709, COLOR_SPACE_BT2020})
|
||||||
public @interface ColorSpace {}
|
public @interface ColorSpace {}
|
||||||
/**
|
/** See {@link 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 {@link 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 {@link 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;
|
||||||
|
|
||||||
// LINT.IfChange(color_transfer)
|
// LINT.IfChange(color_transfer)
|
||||||
/**
|
/**
|
||||||
* Video color transfer characteristics. One of {@link Format#NO_VALUE}, {@link
|
* Video/image color transfer characteristics. One of {@link Format#NO_VALUE}, {@link
|
||||||
* #COLOR_TRANSFER_SDR}, {@link #COLOR_TRANSFER_ST2084} or {@link #COLOR_TRANSFER_HLG}.
|
* #COLOR_TRANSFER_LINEAR}, {@link #COLOR_TRANSFER_SDR}, {@link #COLOR_TRANSFER_SRGB}, {@link
|
||||||
|
* #COLOR_TRANSFER_GAMMA_2_2}, {@link #COLOR_TRANSFER_ST2084} or {@link #COLOR_TRANSFER_HLG}.
|
||||||
*/
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Documented
|
@Documented
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@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_LINEAR,
|
||||||
|
COLOR_TRANSFER_SDR,
|
||||||
|
COLOR_TRANSFER_SRGB,
|
||||||
|
COLOR_TRANSFER_GAMMA_2_2,
|
||||||
|
COLOR_TRANSFER_ST2084,
|
||||||
|
COLOR_TRANSFER_HLG
|
||||||
|
})
|
||||||
public @interface ColorTransfer {}
|
public @interface ColorTransfer {}
|
||||||
/**
|
/** See {@link MediaFormat#COLOR_TRANSFER_LINEAR}. */
|
||||||
* @see MediaFormat#COLOR_TRANSFER_SDR_VIDEO
|
@UnstableApi public static final int COLOR_TRANSFER_LINEAR = MediaFormat.COLOR_TRANSFER_LINEAR;
|
||||||
*/
|
/** See {@link MediaFormat#COLOR_TRANSFER_SDR_VIDEO}. The SMPTE 170M transfer function. */
|
||||||
@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 {@link android.hardware.DataSpace#TRANSFER_SRGB}. The standard RGB transfer function, used
|
||||||
|
* for some SDR use-cases like image input.
|
||||||
*/
|
*/
|
||||||
@UnstableApi public static final int COLOR_TRANSFER_ST2084 = MediaFormat.COLOR_TRANSFER_ST2084;
|
// Value sourced from ordering here:
|
||||||
|
// https://cs.android.com/android/platform/superproject/+/master:frameworks/native/headers/media_plugin/media/hardware/VideoAPI.h;drc=55e9bd7c487ee235631f302ab8626776547ac913;l=138.
|
||||||
|
@UnstableApi public static final int COLOR_TRANSFER_SRGB = 2;
|
||||||
/**
|
/**
|
||||||
* @see MediaFormat#COLOR_TRANSFER_HLG
|
* See {@link android.hardware.DataSpace#TRANSFER_GAMMA2_2}. The Gamma 2.2 transfer function, used
|
||||||
|
* for some SDR use-cases like tone-mapping.
|
||||||
*/
|
*/
|
||||||
|
@UnstableApi public static final int COLOR_TRANSFER_GAMMA_2_2 = 10;
|
||||||
|
/** See {@link MediaFormat#COLOR_TRANSFER_ST2084}. */
|
||||||
|
@UnstableApi public static final int COLOR_TRANSFER_ST2084 = MediaFormat.COLOR_TRANSFER_ST2084;
|
||||||
|
/** See {@link 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;
|
||||||
|
|
||||||
// LINT.IfChange(color_range)
|
// LINT.IfChange(color_range)
|
||||||
@ -1111,13 +1034,9 @@ 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 {@link 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 {@link 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. */
|
||||||
|
@ -19,6 +19,7 @@ import android.os.Bundle;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
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.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import org.checkerframework.dataflow.qual.Pure;
|
import org.checkerframework.dataflow.qual.Pure;
|
||||||
|
|
||||||
@ -31,6 +32,96 @@ import org.checkerframework.dataflow.qual.Pure;
|
|||||||
@UnstableApi
|
@UnstableApi
|
||||||
public final class ColorInfo implements Bundleable {
|
public final class ColorInfo implements Bundleable {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Builds {@link ColorInfo} instances.
|
||||||
|
*
|
||||||
|
* <p>Use {@link ColorInfo#buildUpon} to obtain a builder representing an existing {@link
|
||||||
|
* ColorInfo}.
|
||||||
|
*/
|
||||||
|
public static final class Builder {
|
||||||
|
private @C.ColorSpace int colorSpace;
|
||||||
|
private @C.ColorRange int colorRange;
|
||||||
|
private @C.ColorTransfer int colorTransfer;
|
||||||
|
@Nullable private byte[] hdrStaticInfo;
|
||||||
|
|
||||||
|
/** Creates a new instance with default values. */
|
||||||
|
public Builder() {
|
||||||
|
colorSpace = Format.NO_VALUE;
|
||||||
|
colorRange = Format.NO_VALUE;
|
||||||
|
colorTransfer = Format.NO_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates a new instance to build upon the provided {@link ColorInfo}. */
|
||||||
|
private Builder(ColorInfo colorInfo) {
|
||||||
|
this.colorSpace = colorInfo.colorSpace;
|
||||||
|
this.colorRange = colorInfo.colorRange;
|
||||||
|
this.colorTransfer = colorInfo.colorTransfer;
|
||||||
|
this.hdrStaticInfo = colorInfo.hdrStaticInfo;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the color space.
|
||||||
|
*
|
||||||
|
* <p>Valid values are {@link C#COLOR_SPACE_BT601}, {@link C#COLOR_SPACE_BT709}, {@link
|
||||||
|
* C#COLOR_SPACE_BT2020} or {@link Format#NO_VALUE} if unknown.
|
||||||
|
*
|
||||||
|
* @param colorSpace The color space. The default value is {@link Format#NO_VALUE}.
|
||||||
|
* @return This {@code Builder}.
|
||||||
|
*/
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public Builder setColorSpace(@C.ColorSpace int colorSpace) {
|
||||||
|
this.colorSpace = colorSpace;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the color range.
|
||||||
|
*
|
||||||
|
* <p>Valid values are {@link C#COLOR_RANGE_LIMITED}, {@link C#COLOR_RANGE_FULL} or {@link
|
||||||
|
* Format#NO_VALUE} if unknown.
|
||||||
|
*
|
||||||
|
* @param colorRange The color range. The default value is {@link Format#NO_VALUE}.
|
||||||
|
* @return This {@code Builder}.
|
||||||
|
*/
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public Builder setColorRange(@C.ColorRange int colorRange) {
|
||||||
|
this.colorRange = colorRange;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the color transfer.
|
||||||
|
*
|
||||||
|
* <p>Valid values are {@link C#COLOR_TRANSFER_LINEAR}, {@link C#COLOR_TRANSFER_HLG}, {@link
|
||||||
|
* C#COLOR_TRANSFER_ST2084}, {@link C#COLOR_TRANSFER_SDR} or {@link Format#NO_VALUE} if unknown.
|
||||||
|
*
|
||||||
|
* @param colorTransfer The color transfer. The default value is {@link Format#NO_VALUE}.
|
||||||
|
* @return This {@code Builder}.
|
||||||
|
*/
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public Builder setColorTransfer(@C.ColorTransfer int colorTransfer) {
|
||||||
|
this.colorTransfer = colorTransfer;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the HdrStaticInfo as defined in CTA-861.3.
|
||||||
|
*
|
||||||
|
* @param hdrStaticInfo The HdrStaticInfo. The default value is {@code null}.
|
||||||
|
* @return This {@code Builder}.
|
||||||
|
*/
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public Builder setHdrStaticInfo(@Nullable byte[] hdrStaticInfo) {
|
||||||
|
this.hdrStaticInfo = hdrStaticInfo;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Builds a new {@link ColorInfo} instance. */
|
||||||
|
public ColorInfo build() {
|
||||||
|
return new ColorInfo(colorSpace, colorRange, colorTransfer, hdrStaticInfo);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Color info representing SDR BT.709 limited range, which is a common SDR video color format. */
|
/** Color info representing SDR BT.709 limited range, which is a common SDR video color format. */
|
||||||
public static final ColorInfo SDR_BT709_LIMITED =
|
public static final ColorInfo SDR_BT709_LIMITED =
|
||||||
new ColorInfo(
|
new ColorInfo(
|
||||||
@ -39,6 +130,17 @@ public final class ColorInfo implements Bundleable {
|
|||||||
C.COLOR_TRANSFER_SDR,
|
C.COLOR_TRANSFER_SDR,
|
||||||
/* hdrStaticInfo= */ null);
|
/* hdrStaticInfo= */ null);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Color info representing SDR sRGB in accordance with {@link
|
||||||
|
* android.hardware.DataSpace#DATASPACE_SRGB}, which is a common SDR image color format.
|
||||||
|
*/
|
||||||
|
public static final ColorInfo SRGB_BT709_FULL =
|
||||||
|
new ColorInfo.Builder()
|
||||||
|
.setColorSpace(C.COLOR_SPACE_BT709)
|
||||||
|
.setColorRange(C.COLOR_RANGE_FULL)
|
||||||
|
.setColorTransfer(C.COLOR_TRANSFER_SRGB)
|
||||||
|
.build();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the {@link C.ColorSpace} corresponding to the given ISO color primary code, as per
|
* Returns the {@link C.ColorSpace} corresponding to the given ISO color primary code, as per
|
||||||
* table A.7.21.1 in Rec. ITU-T T.832 (03/2009), or {@link Format#NO_VALUE} if no mapping can be
|
* table A.7.21.1 in Rec. ITU-T T.832 (03/2009), or {@link Format#NO_VALUE} if no mapping can be
|
||||||
@ -74,6 +176,10 @@ public final class ColorInfo implements Bundleable {
|
|||||||
case 6: // SMPTE 170M.
|
case 6: // SMPTE 170M.
|
||||||
case 7: // SMPTE 240M.
|
case 7: // SMPTE 240M.
|
||||||
return C.COLOR_TRANSFER_SDR;
|
return C.COLOR_TRANSFER_SDR;
|
||||||
|
case 4:
|
||||||
|
return C.COLOR_TRANSFER_GAMMA_2_2;
|
||||||
|
case 13:
|
||||||
|
return C.COLOR_TRANSFER_SRGB;
|
||||||
case 16:
|
case 16:
|
||||||
return C.COLOR_TRANSFER_ST2084;
|
return C.COLOR_TRANSFER_ST2084;
|
||||||
case 18:
|
case 18:
|
||||||
@ -83,30 +189,25 @@ public final class ColorInfo implements Bundleable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns whether the {@code ColorInfo} uses an HDR {@link C.ColorTransfer}. */
|
/**
|
||||||
|
* Returns whether the {@code ColorInfo} uses an HDR {@link C.ColorTransfer}.
|
||||||
|
*
|
||||||
|
* <p>{@link C#COLOR_TRANSFER_LINEAR} is not considered to be an HDR {@link C.ColorTransfer},
|
||||||
|
* because it may represent either SDR or HDR contents.
|
||||||
|
*/
|
||||||
public static boolean isTransferHdr(@Nullable ColorInfo colorInfo) {
|
public static boolean isTransferHdr(@Nullable ColorInfo colorInfo) {
|
||||||
return colorInfo != null
|
return colorInfo != null
|
||||||
&& colorInfo.colorTransfer != Format.NO_VALUE
|
&& (colorInfo.colorTransfer == C.COLOR_TRANSFER_HLG
|
||||||
&& colorInfo.colorTransfer != C.COLOR_TRANSFER_SDR;
|
|| colorInfo.colorTransfer == C.COLOR_TRANSFER_ST2084);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** The {@link C.ColorSpace}. */
|
||||||
* The color space of the video. Valid values are {@link C#COLOR_SPACE_BT601}, {@link
|
|
||||||
* C#COLOR_SPACE_BT709}, {@link C#COLOR_SPACE_BT2020} or {@link Format#NO_VALUE} if unknown.
|
|
||||||
*/
|
|
||||||
public final @C.ColorSpace int colorSpace;
|
public final @C.ColorSpace int colorSpace;
|
||||||
|
|
||||||
/**
|
/** The {@link C.ColorRange}. */
|
||||||
* The color range of the video. Valid values are {@link C#COLOR_RANGE_LIMITED}, {@link
|
|
||||||
* C#COLOR_RANGE_FULL} or {@link Format#NO_VALUE} if unknown.
|
|
||||||
*/
|
|
||||||
public final @C.ColorRange int colorRange;
|
public final @C.ColorRange int colorRange;
|
||||||
|
|
||||||
/**
|
/** The {@link C.ColorTransfer}. */
|
||||||
* The color transfer characteristics of the video. Valid values are {@link C#COLOR_TRANSFER_HLG},
|
|
||||||
* {@link C#COLOR_TRANSFER_ST2084}, {@link C#COLOR_TRANSFER_SDR} or {@link Format#NO_VALUE} if
|
|
||||||
* unknown.
|
|
||||||
*/
|
|
||||||
public final @C.ColorTransfer int colorTransfer;
|
public final @C.ColorTransfer int colorTransfer;
|
||||||
|
|
||||||
/** HdrStaticInfo as defined in CTA-861.3, or null if none specified. */
|
/** HdrStaticInfo as defined in CTA-861.3, or null if none specified. */
|
||||||
@ -122,7 +223,9 @@ public final class ColorInfo implements Bundleable {
|
|||||||
* @param colorRange The color range of the video.
|
* @param colorRange The color range of the video.
|
||||||
* @param colorTransfer The color transfer characteristics of the video.
|
* @param colorTransfer The color transfer characteristics of the video.
|
||||||
* @param hdrStaticInfo HdrStaticInfo as defined in CTA-861.3, or null if none specified.
|
* @param hdrStaticInfo HdrStaticInfo as defined in CTA-861.3, or null if none specified.
|
||||||
|
* @deprecated Use {@link Builder}.
|
||||||
*/
|
*/
|
||||||
|
@Deprecated
|
||||||
public ColorInfo(
|
public ColorInfo(
|
||||||
@C.ColorSpace int colorSpace,
|
@C.ColorSpace int colorSpace,
|
||||||
@C.ColorRange int colorRange,
|
@C.ColorRange int colorRange,
|
||||||
@ -134,6 +237,39 @@ public final class ColorInfo implements Bundleable {
|
|||||||
this.hdrStaticInfo = hdrStaticInfo;
|
this.hdrStaticInfo = hdrStaticInfo;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns a {@link Builder} initialized with the values of this instance. */
|
||||||
|
public Builder buildUpon() {
|
||||||
|
return new Builder(this);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether this instance is valid.
|
||||||
|
*
|
||||||
|
* <p>This instance is valid if no members are {@link Format#NO_VALUE}.
|
||||||
|
*/
|
||||||
|
public boolean isValid() {
|
||||||
|
return colorSpace != Format.NO_VALUE
|
||||||
|
&& colorRange != Format.NO_VALUE
|
||||||
|
&& colorTransfer != Format.NO_VALUE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a prettier {@link String} than {@link #toString()}, intended for logging.
|
||||||
|
*
|
||||||
|
* @see Format#toLogString(Format)
|
||||||
|
*/
|
||||||
|
public String toLogString() {
|
||||||
|
if (!isValid()) {
|
||||||
|
return "NA";
|
||||||
|
}
|
||||||
|
|
||||||
|
return Util.formatInvariant(
|
||||||
|
"%s/%s/%s",
|
||||||
|
colorSpaceToString(colorSpace),
|
||||||
|
colorRangeToString(colorRange),
|
||||||
|
colorTransferToString(colorTransfer));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(@Nullable Object obj) {
|
public boolean equals(@Nullable Object obj) {
|
||||||
if (this == obj) {
|
if (this == obj) {
|
||||||
@ -152,16 +288,68 @@ public final class ColorInfo implements Bundleable {
|
|||||||
@Override
|
@Override
|
||||||
public String toString() {
|
public String toString() {
|
||||||
return "ColorInfo("
|
return "ColorInfo("
|
||||||
+ colorSpace
|
+ colorSpaceToString(colorSpace)
|
||||||
+ ", "
|
+ ", "
|
||||||
+ colorRange
|
+ colorRangeToString(colorRange)
|
||||||
+ ", "
|
+ ", "
|
||||||
+ colorTransfer
|
+ colorTransferToString(colorTransfer)
|
||||||
+ ", "
|
+ ", "
|
||||||
+ (hdrStaticInfo != null)
|
+ (hdrStaticInfo != null)
|
||||||
+ ")";
|
+ ")";
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static String colorSpaceToString(@C.ColorSpace int colorSpace) {
|
||||||
|
// LINT.IfChange(color_space)
|
||||||
|
switch (colorSpace) {
|
||||||
|
case Format.NO_VALUE:
|
||||||
|
return "Unset color space";
|
||||||
|
case C.COLOR_SPACE_BT601:
|
||||||
|
return "BT601";
|
||||||
|
case C.COLOR_SPACE_BT709:
|
||||||
|
return "BT709";
|
||||||
|
case C.COLOR_SPACE_BT2020:
|
||||||
|
return "BT2020";
|
||||||
|
default:
|
||||||
|
return "Undefined color space";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String colorTransferToString(@C.ColorTransfer int colorTransfer) {
|
||||||
|
// LINT.IfChange(color_transfer)
|
||||||
|
switch (colorTransfer) {
|
||||||
|
case Format.NO_VALUE:
|
||||||
|
return "Unset color transfer";
|
||||||
|
case C.COLOR_TRANSFER_LINEAR:
|
||||||
|
return "Linear";
|
||||||
|
case C.COLOR_TRANSFER_SDR:
|
||||||
|
return "SDR SMPTE 170M";
|
||||||
|
case C.COLOR_TRANSFER_SRGB:
|
||||||
|
return "sRGB";
|
||||||
|
case C.COLOR_TRANSFER_GAMMA_2_2:
|
||||||
|
return "Gamma 2.2";
|
||||||
|
case C.COLOR_TRANSFER_ST2084:
|
||||||
|
return "ST2084 PQ";
|
||||||
|
case C.COLOR_TRANSFER_HLG:
|
||||||
|
return "HLG";
|
||||||
|
default:
|
||||||
|
return "Undefined color transfer";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private static String colorRangeToString(@C.ColorRange int colorRange) {
|
||||||
|
// LINT.IfChange(color_range)
|
||||||
|
switch (colorRange) {
|
||||||
|
case Format.NO_VALUE:
|
||||||
|
return "Unset color range";
|
||||||
|
case C.COLOR_RANGE_LIMITED:
|
||||||
|
return "Limited range";
|
||||||
|
case C.COLOR_RANGE_FULL:
|
||||||
|
return "Full range";
|
||||||
|
default:
|
||||||
|
return "Undefined color range";
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
if (hashCode == 0) {
|
if (hashCode == 0) {
|
||||||
|
@ -19,7 +19,7 @@ import android.view.SurfaceView;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
|
||||||
/** Provider for views to show diagnostic information during a transformation, for debugging. */
|
/** Provider for views to show diagnostic information during an export, for debugging. */
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public interface DebugViewProvider {
|
public interface DebugViewProvider {
|
||||||
|
|
||||||
|
@ -17,11 +17,15 @@ package androidx.media3.common;
|
|||||||
|
|
||||||
import static java.lang.annotation.ElementType.TYPE_USE;
|
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||||
|
|
||||||
|
import android.media.MediaRouter2;
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.IntDef;
|
import androidx.annotation.IntDef;
|
||||||
|
import androidx.annotation.IntRange;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.media3.common.util.Assertions;
|
||||||
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.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
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;
|
||||||
@ -45,22 +49,116 @@ public final class DeviceInfo implements Bundleable {
|
|||||||
public static final int PLAYBACK_TYPE_REMOTE = 1;
|
public static final int PLAYBACK_TYPE_REMOTE = 1;
|
||||||
|
|
||||||
/** Unknown DeviceInfo. */
|
/** Unknown DeviceInfo. */
|
||||||
public static final DeviceInfo UNKNOWN =
|
public static final DeviceInfo UNKNOWN = new Builder(PLAYBACK_TYPE_LOCAL).build();
|
||||||
new DeviceInfo(PLAYBACK_TYPE_LOCAL, /* minVolume= */ 0, /* maxVolume= */ 0);
|
|
||||||
|
/** Builder for {@link DeviceInfo}. */
|
||||||
|
public static final class Builder {
|
||||||
|
|
||||||
|
private final @PlaybackType int playbackType;
|
||||||
|
|
||||||
|
private int minVolume;
|
||||||
|
private int maxVolume;
|
||||||
|
@Nullable private String routingControllerId;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates the builder.
|
||||||
|
*
|
||||||
|
* @param playbackType The {@link PlaybackType}.
|
||||||
|
*/
|
||||||
|
public Builder(@PlaybackType int playbackType) {
|
||||||
|
this.playbackType = playbackType;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the minimum supported device volume.
|
||||||
|
*
|
||||||
|
* <p>The minimum will be set to {@code 0} if not specified.
|
||||||
|
*
|
||||||
|
* @param minVolume The minimum device volume.
|
||||||
|
* @return This builder.
|
||||||
|
*/
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public Builder setMinVolume(@IntRange(from = 0) int minVolume) {
|
||||||
|
this.minVolume = minVolume;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the maximum supported device volume.
|
||||||
|
*
|
||||||
|
* @param maxVolume The maximum device volume, or {@code 0} to leave the maximum unspecified.
|
||||||
|
* @return This builder.
|
||||||
|
*/
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public Builder setMaxVolume(@IntRange(from = 0) int maxVolume) {
|
||||||
|
this.maxVolume = maxVolume;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@linkplain MediaRouter2.RoutingController#getId() routing controller id} of the
|
||||||
|
* associated {@link MediaRouter2.RoutingController}.
|
||||||
|
*
|
||||||
|
* <p>This id allows mapping this device information to a routing controller, which provides
|
||||||
|
* information about the media route and allows controlling its volume.
|
||||||
|
*
|
||||||
|
* <p>The set value must be null if {@link DeviceInfo#playbackType} is {@link
|
||||||
|
* #PLAYBACK_TYPE_LOCAL}.
|
||||||
|
*
|
||||||
|
* @param routingControllerId The {@linkplain MediaRouter2.RoutingController#getId() routing
|
||||||
|
* controller id} of the associated {@link MediaRouter2.RoutingController}, or null to leave
|
||||||
|
* it unspecified.
|
||||||
|
* @return This builder.
|
||||||
|
*/
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public Builder setRoutingControllerId(@Nullable String routingControllerId) {
|
||||||
|
Assertions.checkArgument(playbackType != PLAYBACK_TYPE_LOCAL || routingControllerId == null);
|
||||||
|
this.routingControllerId = routingControllerId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Builds the {@link DeviceInfo}. */
|
||||||
|
public DeviceInfo build() {
|
||||||
|
Assertions.checkArgument(minVolume <= maxVolume);
|
||||||
|
return new DeviceInfo(this);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** The type of playback. */
|
/** The type of playback. */
|
||||||
public final @PlaybackType int playbackType;
|
public final @PlaybackType int playbackType;
|
||||||
/** The minimum volume that the device supports. */
|
/** The minimum volume that the device supports. */
|
||||||
|
@IntRange(from = 0)
|
||||||
public final int minVolume;
|
public final int minVolume;
|
||||||
/** The maximum volume that the device supports. */
|
/** The maximum volume that the device supports, or {@code 0} if unspecified. */
|
||||||
|
@IntRange(from = 0)
|
||||||
public final int maxVolume;
|
public final int maxVolume;
|
||||||
|
/**
|
||||||
|
* The {@linkplain MediaRouter2.RoutingController#getId() routing controller id} of the associated
|
||||||
|
* {@link MediaRouter2.RoutingController}, or null if unset or {@link #playbackType} is {@link
|
||||||
|
* #PLAYBACK_TYPE_LOCAL}.
|
||||||
|
*
|
||||||
|
* <p>This id allows mapping this device information to a routing controller, which provides
|
||||||
|
* information about the media route and allows controlling its volume.
|
||||||
|
*/
|
||||||
|
@Nullable public final String routingControllerId;
|
||||||
|
|
||||||
/** Creates device information. */
|
/**
|
||||||
|
* @deprecated Use {@link Builder} instead.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public DeviceInfo(@PlaybackType int playbackType, int minVolume, int maxVolume) {
|
@Deprecated
|
||||||
this.playbackType = playbackType;
|
public DeviceInfo(
|
||||||
this.minVolume = minVolume;
|
@PlaybackType int playbackType,
|
||||||
this.maxVolume = maxVolume;
|
@IntRange(from = 0) int minVolume,
|
||||||
|
@IntRange(from = 0) int maxVolume) {
|
||||||
|
this(new Builder(playbackType).setMinVolume(minVolume).setMaxVolume(maxVolume));
|
||||||
|
}
|
||||||
|
|
||||||
|
private DeviceInfo(Builder builder) {
|
||||||
|
this.playbackType = builder.playbackType;
|
||||||
|
this.minVolume = builder.minVolume;
|
||||||
|
this.maxVolume = builder.maxVolume;
|
||||||
|
this.routingControllerId = builder.routingControllerId;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -74,7 +172,8 @@ public final class DeviceInfo implements Bundleable {
|
|||||||
DeviceInfo other = (DeviceInfo) obj;
|
DeviceInfo other = (DeviceInfo) obj;
|
||||||
return playbackType == other.playbackType
|
return playbackType == other.playbackType
|
||||||
&& minVolume == other.minVolume
|
&& minVolume == other.minVolume
|
||||||
&& maxVolume == other.maxVolume;
|
&& maxVolume == other.maxVolume
|
||||||
|
&& Util.areEqual(routingControllerId, other.routingControllerId);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -83,6 +182,7 @@ public final class DeviceInfo implements Bundleable {
|
|||||||
result = 31 * result + playbackType;
|
result = 31 * result + playbackType;
|
||||||
result = 31 * result + minVolume;
|
result = 31 * result + minVolume;
|
||||||
result = 31 * result + maxVolume;
|
result = 31 * result + maxVolume;
|
||||||
|
result = 31 * result + (routingControllerId == null ? 0 : routingControllerId.hashCode());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,14 +191,24 @@ public final class DeviceInfo implements Bundleable {
|
|||||||
private static final String FIELD_PLAYBACK_TYPE = Util.intToStringMaxRadix(0);
|
private static final String FIELD_PLAYBACK_TYPE = Util.intToStringMaxRadix(0);
|
||||||
private static final String FIELD_MIN_VOLUME = Util.intToStringMaxRadix(1);
|
private static final String FIELD_MIN_VOLUME = Util.intToStringMaxRadix(1);
|
||||||
private static final String FIELD_MAX_VOLUME = Util.intToStringMaxRadix(2);
|
private static final String FIELD_MAX_VOLUME = Util.intToStringMaxRadix(2);
|
||||||
|
private static final String FIELD_ROUTING_CONTROLLER_ID = Util.intToStringMaxRadix(3);
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Override
|
@Override
|
||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
|
if (playbackType != PLAYBACK_TYPE_LOCAL) {
|
||||||
bundle.putInt(FIELD_PLAYBACK_TYPE, playbackType);
|
bundle.putInt(FIELD_PLAYBACK_TYPE, playbackType);
|
||||||
|
}
|
||||||
|
if (minVolume != 0) {
|
||||||
bundle.putInt(FIELD_MIN_VOLUME, minVolume);
|
bundle.putInt(FIELD_MIN_VOLUME, minVolume);
|
||||||
|
}
|
||||||
|
if (maxVolume != 0) {
|
||||||
bundle.putInt(FIELD_MAX_VOLUME, maxVolume);
|
bundle.putInt(FIELD_MAX_VOLUME, maxVolume);
|
||||||
|
}
|
||||||
|
if (routingControllerId != null) {
|
||||||
|
bundle.putString(FIELD_ROUTING_CONTROLLER_ID, routingControllerId);
|
||||||
|
}
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -110,6 +220,11 @@ public final class DeviceInfo implements Bundleable {
|
|||||||
bundle.getInt(FIELD_PLAYBACK_TYPE, /* defaultValue= */ PLAYBACK_TYPE_LOCAL);
|
bundle.getInt(FIELD_PLAYBACK_TYPE, /* defaultValue= */ PLAYBACK_TYPE_LOCAL);
|
||||||
int minVolume = bundle.getInt(FIELD_MIN_VOLUME, /* defaultValue= */ 0);
|
int minVolume = bundle.getInt(FIELD_MIN_VOLUME, /* defaultValue= */ 0);
|
||||||
int maxVolume = bundle.getInt(FIELD_MAX_VOLUME, /* defaultValue= */ 0);
|
int maxVolume = bundle.getInt(FIELD_MAX_VOLUME, /* defaultValue= */ 0);
|
||||||
return new DeviceInfo(playbackType, minVolume, maxVolume);
|
@Nullable String routingControllerId = bundle.getString(FIELD_ROUTING_CONTROLLER_ID);
|
||||||
|
return new DeviceInfo.Builder(playbackType)
|
||||||
|
.setMinVolume(minVolume)
|
||||||
|
.setMaxVolume(maxVolume)
|
||||||
|
.setRoutingControllerId(routingControllerId)
|
||||||
|
.build();
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
@ -134,7 +134,7 @@ public final class FileTypes {
|
|||||||
/**
|
/**
|
||||||
* Returns the {@link Type} corresponding to the MIME type provided.
|
* Returns the {@link Type} corresponding to the MIME type provided.
|
||||||
*
|
*
|
||||||
* <p>Returns {@link #UNKNOWN} if the mime type is {@code null}.
|
* <p>Returns {@link #UNKNOWN} if the MIME type is {@code null}.
|
||||||
*/
|
*/
|
||||||
public static @FileTypes.Type int inferFileTypeFromMimeType(@Nullable String mimeType) {
|
public static @FileTypes.Type int inferFileTypeFromMimeType(@Nullable String mimeType) {
|
||||||
if (mimeType == null) {
|
if (mimeType == null) {
|
||||||
|
@ -748,12 +748,12 @@ public final class Format implements Bundleable {
|
|||||||
|
|
||||||
// Container specific.
|
// Container specific.
|
||||||
|
|
||||||
/** The mime type of the container, or null if unknown or not applicable. */
|
/** The MIME type of the container, or null if unknown or not applicable. */
|
||||||
@Nullable public final String containerMimeType;
|
@Nullable public final String containerMimeType;
|
||||||
|
|
||||||
// Sample specific.
|
// Sample specific.
|
||||||
|
|
||||||
/** The sample mime type, or null if unknown or not applicable. */
|
/** The sample MIME type, or null if unknown or not applicable. */
|
||||||
@Nullable public final String sampleMimeType;
|
@Nullable public final String sampleMimeType;
|
||||||
/**
|
/**
|
||||||
* The maximum size of a buffer of data (typically one sample), or {@link #NO_VALUE} if unknown or
|
* The maximum size of a buffer of data (typically one sample), or {@link #NO_VALUE} if unknown or
|
||||||
@ -846,184 +846,6 @@ public final class Format implements Bundleable {
|
|||||||
// Lazily initialized hashcode.
|
// Lazily initialized hashcode.
|
||||||
private int hashCode;
|
private int hashCode;
|
||||||
|
|
||||||
// Video.
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use {@link Format.Builder}.
|
|
||||||
*/
|
|
||||||
@UnstableApi
|
|
||||||
@Deprecated
|
|
||||||
public static Format createVideoSampleFormat(
|
|
||||||
@Nullable String id,
|
|
||||||
@Nullable String sampleMimeType,
|
|
||||||
@Nullable String codecs,
|
|
||||||
int bitrate,
|
|
||||||
int maxInputSize,
|
|
||||||
int width,
|
|
||||||
int height,
|
|
||||||
float frameRate,
|
|
||||||
@Nullable List<byte[]> initializationData,
|
|
||||||
@Nullable DrmInitData drmInitData) {
|
|
||||||
return new Builder()
|
|
||||||
.setId(id)
|
|
||||||
.setAverageBitrate(bitrate)
|
|
||||||
.setPeakBitrate(bitrate)
|
|
||||||
.setCodecs(codecs)
|
|
||||||
.setSampleMimeType(sampleMimeType)
|
|
||||||
.setMaxInputSize(maxInputSize)
|
|
||||||
.setInitializationData(initializationData)
|
|
||||||
.setDrmInitData(drmInitData)
|
|
||||||
.setWidth(width)
|
|
||||||
.setHeight(height)
|
|
||||||
.setFrameRate(frameRate)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use {@link Format.Builder}.
|
|
||||||
*/
|
|
||||||
@UnstableApi
|
|
||||||
@Deprecated
|
|
||||||
public static Format createVideoSampleFormat(
|
|
||||||
@Nullable String id,
|
|
||||||
@Nullable String sampleMimeType,
|
|
||||||
@Nullable String codecs,
|
|
||||||
int bitrate,
|
|
||||||
int maxInputSize,
|
|
||||||
int width,
|
|
||||||
int height,
|
|
||||||
float frameRate,
|
|
||||||
@Nullable List<byte[]> initializationData,
|
|
||||||
int rotationDegrees,
|
|
||||||
float pixelWidthHeightRatio,
|
|
||||||
@Nullable DrmInitData drmInitData) {
|
|
||||||
return new Builder()
|
|
||||||
.setId(id)
|
|
||||||
.setAverageBitrate(bitrate)
|
|
||||||
.setPeakBitrate(bitrate)
|
|
||||||
.setCodecs(codecs)
|
|
||||||
.setSampleMimeType(sampleMimeType)
|
|
||||||
.setMaxInputSize(maxInputSize)
|
|
||||||
.setInitializationData(initializationData)
|
|
||||||
.setDrmInitData(drmInitData)
|
|
||||||
.setWidth(width)
|
|
||||||
.setHeight(height)
|
|
||||||
.setFrameRate(frameRate)
|
|
||||||
.setRotationDegrees(rotationDegrees)
|
|
||||||
.setPixelWidthHeightRatio(pixelWidthHeightRatio)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Audio.
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use {@link Format.Builder}.
|
|
||||||
*/
|
|
||||||
@UnstableApi
|
|
||||||
@Deprecated
|
|
||||||
public static Format createAudioSampleFormat(
|
|
||||||
@Nullable String id,
|
|
||||||
@Nullable String sampleMimeType,
|
|
||||||
@Nullable String codecs,
|
|
||||||
int bitrate,
|
|
||||||
int maxInputSize,
|
|
||||||
int channelCount,
|
|
||||||
int sampleRate,
|
|
||||||
@Nullable List<byte[]> initializationData,
|
|
||||||
@Nullable DrmInitData drmInitData,
|
|
||||||
@C.SelectionFlags int selectionFlags,
|
|
||||||
@Nullable String language) {
|
|
||||||
return new Builder()
|
|
||||||
.setId(id)
|
|
||||||
.setLanguage(language)
|
|
||||||
.setSelectionFlags(selectionFlags)
|
|
||||||
.setAverageBitrate(bitrate)
|
|
||||||
.setPeakBitrate(bitrate)
|
|
||||||
.setCodecs(codecs)
|
|
||||||
.setSampleMimeType(sampleMimeType)
|
|
||||||
.setMaxInputSize(maxInputSize)
|
|
||||||
.setInitializationData(initializationData)
|
|
||||||
.setDrmInitData(drmInitData)
|
|
||||||
.setChannelCount(channelCount)
|
|
||||||
.setSampleRate(sampleRate)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use {@link Format.Builder}.
|
|
||||||
*/
|
|
||||||
@UnstableApi
|
|
||||||
@Deprecated
|
|
||||||
public static Format createAudioSampleFormat(
|
|
||||||
@Nullable String id,
|
|
||||||
@Nullable String sampleMimeType,
|
|
||||||
@Nullable String codecs,
|
|
||||||
int bitrate,
|
|
||||||
int maxInputSize,
|
|
||||||
int channelCount,
|
|
||||||
int sampleRate,
|
|
||||||
@C.PcmEncoding int pcmEncoding,
|
|
||||||
@Nullable List<byte[]> initializationData,
|
|
||||||
@Nullable DrmInitData drmInitData,
|
|
||||||
@C.SelectionFlags int selectionFlags,
|
|
||||||
@Nullable String language) {
|
|
||||||
return new Builder()
|
|
||||||
.setId(id)
|
|
||||||
.setLanguage(language)
|
|
||||||
.setSelectionFlags(selectionFlags)
|
|
||||||
.setAverageBitrate(bitrate)
|
|
||||||
.setPeakBitrate(bitrate)
|
|
||||||
.setCodecs(codecs)
|
|
||||||
.setSampleMimeType(sampleMimeType)
|
|
||||||
.setMaxInputSize(maxInputSize)
|
|
||||||
.setInitializationData(initializationData)
|
|
||||||
.setDrmInitData(drmInitData)
|
|
||||||
.setChannelCount(channelCount)
|
|
||||||
.setSampleRate(sampleRate)
|
|
||||||
.setPcmEncoding(pcmEncoding)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
// Generic.
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use {@link Format.Builder}.
|
|
||||||
*/
|
|
||||||
@UnstableApi
|
|
||||||
@Deprecated
|
|
||||||
public static Format createContainerFormat(
|
|
||||||
@Nullable String id,
|
|
||||||
@Nullable String label,
|
|
||||||
@Nullable String containerMimeType,
|
|
||||||
@Nullable String sampleMimeType,
|
|
||||||
@Nullable String codecs,
|
|
||||||
int bitrate,
|
|
||||||
@C.SelectionFlags int selectionFlags,
|
|
||||||
@C.RoleFlags int roleFlags,
|
|
||||||
@Nullable String language) {
|
|
||||||
return new Builder()
|
|
||||||
.setId(id)
|
|
||||||
.setLabel(label)
|
|
||||||
.setLanguage(language)
|
|
||||||
.setSelectionFlags(selectionFlags)
|
|
||||||
.setRoleFlags(roleFlags)
|
|
||||||
.setAverageBitrate(bitrate)
|
|
||||||
.setPeakBitrate(bitrate)
|
|
||||||
.setCodecs(codecs)
|
|
||||||
.setContainerMimeType(containerMimeType)
|
|
||||||
.setSampleMimeType(sampleMimeType)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use {@link Format.Builder}.
|
|
||||||
*/
|
|
||||||
@UnstableApi
|
|
||||||
@Deprecated
|
|
||||||
public static Format createSampleFormat(@Nullable String id, @Nullable String sampleMimeType) {
|
|
||||||
return new Builder().setId(id).setSampleMimeType(sampleMimeType).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
private Format(Builder builder) {
|
private Format(Builder builder) {
|
||||||
id = builder.id;
|
id = builder.id;
|
||||||
label = builder.label;
|
label = builder.label;
|
||||||
@ -1080,42 +902,6 @@ public final class Format implements Bundleable {
|
|||||||
return new Builder(this);
|
return new Builder(this);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use {@link #buildUpon()} and {@link Builder#setMaxInputSize(int)}.
|
|
||||||
*/
|
|
||||||
@UnstableApi
|
|
||||||
@Deprecated
|
|
||||||
public Format copyWithMaxInputSize(int maxInputSize) {
|
|
||||||
return buildUpon().setMaxInputSize(maxInputSize).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use {@link #buildUpon()} and {@link Builder#setSubsampleOffsetUs(long)}.
|
|
||||||
*/
|
|
||||||
@UnstableApi
|
|
||||||
@Deprecated
|
|
||||||
public Format copyWithSubsampleOffsetUs(long subsampleOffsetUs) {
|
|
||||||
return buildUpon().setSubsampleOffsetUs(subsampleOffsetUs).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use {@link #buildUpon()} and {@link Builder#setLabel(String)} .
|
|
||||||
*/
|
|
||||||
@UnstableApi
|
|
||||||
@Deprecated
|
|
||||||
public Format copyWithLabel(@Nullable String label) {
|
|
||||||
return buildUpon().setLabel(label).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use {@link #withManifestFormatInfo(Format)}.
|
|
||||||
*/
|
|
||||||
@UnstableApi
|
|
||||||
@Deprecated
|
|
||||||
public Format copyWithManifestFormatInfo(Format manifestFormat) {
|
|
||||||
return withManifestFormatInfo(manifestFormat);
|
|
||||||
}
|
|
||||||
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@SuppressWarnings("ReferenceEquality")
|
@SuppressWarnings("ReferenceEquality")
|
||||||
public Format withManifestFormatInfo(Format manifestFormat) {
|
public Format withManifestFormatInfo(Format manifestFormat) {
|
||||||
@ -1184,63 +970,6 @@ public final class Format implements Bundleable {
|
|||||||
.build();
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use {@link #buildUpon()}, {@link Builder#setEncoderDelay(int)} and {@link
|
|
||||||
* Builder#setEncoderPadding(int)}.
|
|
||||||
*/
|
|
||||||
@UnstableApi
|
|
||||||
@Deprecated
|
|
||||||
public Format copyWithGaplessInfo(int encoderDelay, int encoderPadding) {
|
|
||||||
return buildUpon().setEncoderDelay(encoderDelay).setEncoderPadding(encoderPadding).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use {@link #buildUpon()} and {@link Builder#setFrameRate(float)}.
|
|
||||||
*/
|
|
||||||
@UnstableApi
|
|
||||||
@Deprecated
|
|
||||||
public Format copyWithFrameRate(float frameRate) {
|
|
||||||
return buildUpon().setFrameRate(frameRate).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use {@link #buildUpon()} and {@link Builder#setDrmInitData(DrmInitData)}.
|
|
||||||
*/
|
|
||||||
@UnstableApi
|
|
||||||
@Deprecated
|
|
||||||
public Format copyWithDrmInitData(@Nullable DrmInitData drmInitData) {
|
|
||||||
return buildUpon().setDrmInitData(drmInitData).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use {@link #buildUpon()} and {@link Builder#setMetadata(Metadata)}.
|
|
||||||
*/
|
|
||||||
@UnstableApi
|
|
||||||
@Deprecated
|
|
||||||
public Format copyWithMetadata(@Nullable Metadata metadata) {
|
|
||||||
return buildUpon().setMetadata(metadata).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use {@link #buildUpon()} and {@link Builder#setAverageBitrate(int)} and {@link
|
|
||||||
* Builder#setPeakBitrate(int)}.
|
|
||||||
*/
|
|
||||||
@UnstableApi
|
|
||||||
@Deprecated
|
|
||||||
public Format copyWithBitrate(int bitrate) {
|
|
||||||
return buildUpon().setAverageBitrate(bitrate).setPeakBitrate(bitrate).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use {@link #buildUpon()}, {@link Builder#setWidth(int)} and {@link
|
|
||||||
* Builder#setHeight(int)}.
|
|
||||||
*/
|
|
||||||
@UnstableApi
|
|
||||||
@Deprecated
|
|
||||||
public Format copyWithVideoSize(int width, int height) {
|
|
||||||
return buildUpon().setWidth(width).setHeight(height).build();
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Returns a copy of this format with the specified {@link #cryptoType}. */
|
/** Returns a copy of this format with the specified {@link #cryptoType}. */
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public Format copyWithCryptoType(@C.CryptoType int cryptoType) {
|
public Format copyWithCryptoType(@C.CryptoType int cryptoType) {
|
||||||
@ -1278,6 +1007,8 @@ public final class Format implements Bundleable {
|
|||||||
+ height
|
+ height
|
||||||
+ ", "
|
+ ", "
|
||||||
+ frameRate
|
+ frameRate
|
||||||
|
+ ", "
|
||||||
|
+ colorInfo
|
||||||
+ "]"
|
+ "]"
|
||||||
+ ", ["
|
+ ", ["
|
||||||
+ channelCount
|
+ channelCount
|
||||||
@ -1444,6 +1175,9 @@ public final class Format implements Bundleable {
|
|||||||
if (format.width != NO_VALUE && format.height != NO_VALUE) {
|
if (format.width != NO_VALUE && format.height != NO_VALUE) {
|
||||||
builder.append(", res=").append(format.width).append("x").append(format.height);
|
builder.append(", res=").append(format.width).append("x").append(format.height);
|
||||||
}
|
}
|
||||||
|
if (format.colorInfo != null && format.colorInfo.isValid()) {
|
||||||
|
builder.append(", color=").append(format.colorInfo.toLogString());
|
||||||
|
}
|
||||||
if (format.frameRate != NO_VALUE) {
|
if (format.frameRate != NO_VALUE) {
|
||||||
builder.append(", fps=").append(format.frameRate);
|
builder.append(", fps=").append(format.frameRate);
|
||||||
}
|
}
|
||||||
|
@ -147,6 +147,18 @@ public class ForwardingPlayer implements Player {
|
|||||||
player.moveMediaItems(fromIndex, toIndex, newIndex);
|
player.moveMediaItems(fromIndex, toIndex, newIndex);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Calls {@link Player#replaceMediaItem(int, MediaItem)} on the delegate. */
|
||||||
|
@Override
|
||||||
|
public void replaceMediaItem(int index, MediaItem mediaItem) {
|
||||||
|
player.replaceMediaItem(index, mediaItem);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Calls {@link Player#replaceMediaItems(int, int, List)} on the delegate. */
|
||||||
|
@Override
|
||||||
|
public void replaceMediaItems(int fromIndex, int toIndex, List<MediaItem> mediaItems) {
|
||||||
|
player.replaceMediaItems(fromIndex, toIndex, mediaItems);
|
||||||
|
}
|
||||||
|
|
||||||
/** Calls {@link Player#removeMediaItem(int)} on the delegate. */
|
/** Calls {@link Player#removeMediaItem(int)} on the delegate. */
|
||||||
@Override
|
@Override
|
||||||
public void removeMediaItem(int index) {
|
public void removeMediaItem(int index) {
|
||||||
@ -478,20 +490,6 @@ public class ForwardingPlayer implements Player {
|
|||||||
player.stop();
|
player.stop();
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* 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
|
|
||||||
@Deprecated
|
|
||||||
@Override
|
|
||||||
public void stop(boolean reset) {
|
|
||||||
player.stop(reset);
|
|
||||||
}
|
|
||||||
|
|
||||||
/** Calls {@link Player#release()} on the delegate. */
|
/** Calls {@link Player#release()} on the delegate. */
|
||||||
@Override
|
@Override
|
||||||
public void release() {
|
public void release() {
|
||||||
@ -860,30 +858,66 @@ public class ForwardingPlayer implements Player {
|
|||||||
return player.isDeviceMuted();
|
return player.isDeviceMuted();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Calls {@link Player#setDeviceVolume(int)} on the delegate. */
|
/**
|
||||||
|
* @deprecated Use {@link #setDeviceVolume(int, int)} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
public void setDeviceVolume(int volume) {
|
public void setDeviceVolume(int volume) {
|
||||||
player.setDeviceVolume(volume);
|
player.setDeviceVolume(volume);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Calls {@link Player#increaseDeviceVolume()} on the delegate. */
|
/** Calls {@link Player#setDeviceVolume(int, int)} on the delegate. */
|
||||||
|
@Override
|
||||||
|
public void setDeviceVolume(int volume, @C.VolumeFlags int flags) {
|
||||||
|
player.setDeviceVolume(volume, flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #increaseDeviceVolume(int)} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
public void increaseDeviceVolume() {
|
public void increaseDeviceVolume() {
|
||||||
player.increaseDeviceVolume();
|
player.increaseDeviceVolume();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Calls {@link Player#decreaseDeviceVolume()} on the delegate. */
|
/** Calls {@link Player#increaseDeviceVolume(int)} on the delegate. */
|
||||||
|
@Override
|
||||||
|
public void increaseDeviceVolume(@C.VolumeFlags int flags) {
|
||||||
|
player.increaseDeviceVolume(flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #decreaseDeviceVolume(int)} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
public void decreaseDeviceVolume() {
|
public void decreaseDeviceVolume() {
|
||||||
player.decreaseDeviceVolume();
|
player.decreaseDeviceVolume();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Calls {@link Player#setDeviceMuted(boolean)} on the delegate. */
|
/** Calls {@link Player#decreaseDeviceVolume(int)} on the delegate. */
|
||||||
|
@Override
|
||||||
|
public void decreaseDeviceVolume(@C.VolumeFlags int flags) {
|
||||||
|
player.decreaseDeviceVolume(flags);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #setDeviceMuted(boolean, int)} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
public void setDeviceMuted(boolean muted) {
|
public void setDeviceMuted(boolean muted) {
|
||||||
player.setDeviceMuted(muted);
|
player.setDeviceMuted(muted);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Calls {@link Player#setDeviceMuted(boolean, int)} on the delegate. */
|
||||||
|
@Override
|
||||||
|
public void setDeviceMuted(boolean muted, @C.VolumeFlags int flags) {
|
||||||
|
player.setDeviceMuted(muted, flags);
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns the {@link Player} to which operations are forwarded. */
|
/** Returns the {@link Player} to which operations are forwarded. */
|
||||||
public Player getWrappedPlayer() {
|
public Player getWrappedPlayer() {
|
||||||
return player;
|
return player;
|
||||||
@ -1032,12 +1066,6 @@ public class ForwardingPlayer implements Player {
|
|||||||
listener.onMaxSeekToPreviousPositionChanged(maxSeekToPreviousPositionMs);
|
listener.onMaxSeekToPreviousPositionChanged(maxSeekToPreviousPositionMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
public void onSeekProcessed() {
|
|
||||||
listener.onSeekProcessed();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onVideoSizeChanged(VideoSize videoSize) {
|
public void onVideoSizeChanged(VideoSize videoSize) {
|
||||||
listener.onVideoSizeChanged(videoSize);
|
listener.onVideoSizeChanged(videoSize);
|
||||||
|
@ -18,10 +18,83 @@ package androidx.media3.common;
|
|||||||
import static androidx.media3.common.util.Assertions.checkArgument;
|
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||||
|
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
|
|
||||||
/** Value class specifying information about a decoded video frame. */
|
/** Value class specifying information about a decoded video frame. */
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public class FrameInfo {
|
public class FrameInfo {
|
||||||
|
|
||||||
|
/** A builder for {@link FrameInfo} instances. */
|
||||||
|
public static final class Builder {
|
||||||
|
|
||||||
|
private int width;
|
||||||
|
private int height;
|
||||||
|
private float pixelWidthHeightRatio;
|
||||||
|
private long offsetToAddUs;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance with default values.
|
||||||
|
*
|
||||||
|
* @param width The frame width, in pixels.
|
||||||
|
* @param height The frame height, in pixels.
|
||||||
|
*/
|
||||||
|
public Builder(int width, int height) {
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
pixelWidthHeightRatio = 1;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates an instance with the values of the provided {@link FrameInfo}. */
|
||||||
|
public Builder(FrameInfo frameInfo) {
|
||||||
|
width = frameInfo.width;
|
||||||
|
height = frameInfo.height;
|
||||||
|
pixelWidthHeightRatio = frameInfo.pixelWidthHeightRatio;
|
||||||
|
offsetToAddUs = frameInfo.offsetToAddUs;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the frame width, in pixels. */
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public Builder setWidth(int width) {
|
||||||
|
this.width = width;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Sets the frame height, in pixels. */
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public Builder setHeight(int height) {
|
||||||
|
this.height = height;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the ratio of width over height for each pixel.
|
||||||
|
*
|
||||||
|
* <p>The default value is {@code 1}.
|
||||||
|
*/
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public Builder setPixelWidthHeightRatio(float pixelWidthHeightRatio) {
|
||||||
|
this.pixelWidthHeightRatio = pixelWidthHeightRatio;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@linkplain FrameInfo#offsetToAddUs offset to add} to the frame presentation
|
||||||
|
* timestamp, in microseconds.
|
||||||
|
*
|
||||||
|
* <p>The default value is {@code 0}.
|
||||||
|
*/
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public Builder setOffsetToAddUs(long offsetToAddUs) {
|
||||||
|
this.offsetToAddUs = offsetToAddUs;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Builds a {@link FrameInfo} instance. */
|
||||||
|
public FrameInfo build() {
|
||||||
|
return new FrameInfo(width, height, pixelWidthHeightRatio, offsetToAddUs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** The width of the frame, in pixels. */
|
/** The width of the frame, in pixels. */
|
||||||
public final int width;
|
public final int width;
|
||||||
/** The height of the frame, in pixels. */
|
/** The height of the frame, in pixels. */
|
||||||
@ -29,32 +102,22 @@ public class FrameInfo {
|
|||||||
/** The ratio of width over height for each pixel. */
|
/** The ratio of width over height for each pixel. */
|
||||||
public final float pixelWidthHeightRatio;
|
public final float pixelWidthHeightRatio;
|
||||||
/**
|
/**
|
||||||
* An offset in microseconds that is part of the input timestamps and should be ignored for
|
* The offset that must be added to the frame presentation timestamp, in microseconds.
|
||||||
* processing but added back to the output timestamps.
|
|
||||||
*
|
*
|
||||||
* <p>The offset stays constant within a stream but changes in between streams to ensure that
|
* <p>This offset is not part of the input timestamps. It is added to the frame timestamps before
|
||||||
* frame timestamps are always monotonically increasing.
|
* processing, and is retained in the output timestamps.
|
||||||
*/
|
*/
|
||||||
public final long streamOffsetUs;
|
public final long offsetToAddUs;
|
||||||
|
|
||||||
// TODO(b/227624622): Add color space information for HDR.
|
// TODO(b/227624622): Add color space information for HDR.
|
||||||
|
|
||||||
/**
|
private FrameInfo(int width, int height, float pixelWidthHeightRatio, long offsetToAddUs) {
|
||||||
* Creates a new instance.
|
|
||||||
*
|
|
||||||
* @param width The width of the frame, in pixels.
|
|
||||||
* @param height The height of the frame, in pixels.
|
|
||||||
* @param pixelWidthHeightRatio The ratio of width over height for each pixel.
|
|
||||||
* @param streamOffsetUs An offset in microseconds that is part of the input timestamps and should
|
|
||||||
* be ignored for processing but added back to the output timestamps.
|
|
||||||
*/
|
|
||||||
public FrameInfo(int width, int height, float pixelWidthHeightRatio, long streamOffsetUs) {
|
|
||||||
checkArgument(width > 0, "width must be positive, but is: " + width);
|
checkArgument(width > 0, "width must be positive, but is: " + width);
|
||||||
checkArgument(height > 0, "height must be positive, but is: " + height);
|
checkArgument(height > 0, "height must be positive, but is: " + height);
|
||||||
|
|
||||||
this.width = width;
|
this.width = width;
|
||||||
this.height = height;
|
this.height = height;
|
||||||
this.pixelWidthHeightRatio = pixelWidthHeightRatio;
|
this.pixelWidthHeightRatio = pixelWidthHeightRatio;
|
||||||
this.streamOffsetUs = streamOffsetUs;
|
this.offsetToAddUs = offsetToAddUs;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,205 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.common;
|
|
||||||
|
|
||||||
import android.content.Context;
|
|
||||||
import android.opengl.EGLExt;
|
|
||||||
import android.view.Surface;
|
|
||||||
import androidx.annotation.Nullable;
|
|
||||||
import androidx.media3.common.util.UnstableApi;
|
|
||||||
import java.util.List;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Interface for a frame processor that applies changes to individual video frames.
|
|
||||||
*
|
|
||||||
* <p>The changes are specified by {@link Effect} instances passed to {@link Factory#create}.
|
|
||||||
*
|
|
||||||
* <p>Manages its input {@link Surface}, which can be accessed via {@link #getInputSurface()}. The
|
|
||||||
* output {@link Surface} must be set by the caller using {@link
|
|
||||||
* #setOutputSurfaceInfo(SurfaceInfo)}.
|
|
||||||
*
|
|
||||||
* <p>The caller must {@linkplain #registerInputFrame() register} input frames before rendering them
|
|
||||||
* to the input {@link Surface}.
|
|
||||||
*/
|
|
||||||
@UnstableApi
|
|
||||||
public interface FrameProcessor {
|
|
||||||
// TODO(b/243036513): Allow effects to be replaced.
|
|
||||||
|
|
||||||
/** A factory for {@link FrameProcessor} instances. */
|
|
||||||
interface Factory {
|
|
||||||
/**
|
|
||||||
* Creates a new {@link FrameProcessor} instance.
|
|
||||||
*
|
|
||||||
* @param context A {@link Context}.
|
|
||||||
* @param listener A {@link Listener}.
|
|
||||||
* @param effects The {@link Effect} instances to apply to each frame.
|
|
||||||
* @param debugViewProvider A {@link DebugViewProvider}.
|
|
||||||
* @param colorInfo The {@link ColorInfo} for input and output frames.
|
|
||||||
* @param releaseFramesAutomatically If {@code true}, the {@link FrameProcessor} will render
|
|
||||||
* output frames to the {@linkplain #setOutputSurfaceInfo(SurfaceInfo) output surface}
|
|
||||||
* automatically as {@link FrameProcessor} is done processing them. If {@code false}, the
|
|
||||||
* {@link FrameProcessor} will block until {@link #releaseOutputFrame(long)} is called, to
|
|
||||||
* render or drop the frame.
|
|
||||||
* @return A new instance.
|
|
||||||
* @throws FrameProcessingException If a problem occurs while creating the {@link
|
|
||||||
* FrameProcessor}.
|
|
||||||
*/
|
|
||||||
FrameProcessor create(
|
|
||||||
Context context,
|
|
||||||
Listener listener,
|
|
||||||
List<Effect> effects,
|
|
||||||
DebugViewProvider debugViewProvider,
|
|
||||||
ColorInfo colorInfo,
|
|
||||||
boolean releaseFramesAutomatically)
|
|
||||||
throws FrameProcessingException;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Listener for asynchronous frame processing events.
|
|
||||||
*
|
|
||||||
* <p>All listener methods must be called from the same thread.
|
|
||||||
*/
|
|
||||||
interface Listener {
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the output size changes.
|
|
||||||
*
|
|
||||||
* <p>The output size is the frame size in pixels after applying all {@linkplain Effect
|
|
||||||
* effects}.
|
|
||||||
*
|
|
||||||
* <p>The output size may differ from the size specified using {@link
|
|
||||||
* #setOutputSurfaceInfo(SurfaceInfo)}.
|
|
||||||
*/
|
|
||||||
void onOutputSizeChanged(int width, int height);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when an output frame with the given {@code presentationTimeUs} becomes available.
|
|
||||||
*
|
|
||||||
* @param presentationTimeUs The presentation time of the frame, in microseconds.
|
|
||||||
*/
|
|
||||||
void onOutputFrameAvailable(long presentationTimeUs);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when an exception occurs during asynchronous frame processing.
|
|
||||||
*
|
|
||||||
* <p>If an error occurred, consuming and producing further frames will not work as expected and
|
|
||||||
* the {@link FrameProcessor} should be released.
|
|
||||||
*/
|
|
||||||
void onFrameProcessingError(FrameProcessingException exception);
|
|
||||||
|
|
||||||
/** Called after the {@link FrameProcessor} has produced its final output frame. */
|
|
||||||
void onFrameProcessingEnded();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Indicates the frame should be released immediately after {@link #releaseOutputFrame(long)} is
|
|
||||||
* invoked.
|
|
||||||
*/
|
|
||||||
long RELEASE_OUTPUT_FRAME_IMMEDIATELY = -1;
|
|
||||||
|
|
||||||
/** Indicates the frame should be dropped after {@link #releaseOutputFrame(long)} is invoked. */
|
|
||||||
long DROP_OUTPUT_FRAME = -2;
|
|
||||||
|
|
||||||
/** Returns the input {@link Surface}, where {@link FrameProcessor} consumes input frames from. */
|
|
||||||
Surface getInputSurface();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets information about the input frames.
|
|
||||||
*
|
|
||||||
* <p>The new input information is applied from the next frame {@linkplain #registerInputFrame()
|
|
||||||
* registered} onwards.
|
|
||||||
*
|
|
||||||
* <p>Pixels are expanded using the {@link FrameInfo#pixelWidthHeightRatio} so that the output
|
|
||||||
* frames' pixels have a ratio of 1.
|
|
||||||
*
|
|
||||||
* <p>The caller should update {@link FrameInfo#streamOffsetUs} when switching input streams to
|
|
||||||
* ensure that frame timestamps are always monotonically increasing.
|
|
||||||
*/
|
|
||||||
void setInputFrameInfo(FrameInfo inputFrameInfo);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Informs the {@code FrameProcessor} that a frame will be queued to its input surface.
|
|
||||||
*
|
|
||||||
* <p>Must be called before rendering a frame to the frame processor's input surface.
|
|
||||||
*
|
|
||||||
* @throws IllegalStateException If called after {@link #signalEndOfInput()} or before {@link
|
|
||||||
* #setInputFrameInfo(FrameInfo)}.
|
|
||||||
*/
|
|
||||||
void registerInputFrame();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the number of input frames that have been {@linkplain #registerInputFrame() registered}
|
|
||||||
* but not processed off the {@linkplain #getInputSurface() input surface} yet.
|
|
||||||
*/
|
|
||||||
int getPendingInputFrameCount();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the output surface and supporting information. When output frames are released and not
|
|
||||||
* dropped, they will be rendered to this output {@link SurfaceInfo}.
|
|
||||||
*
|
|
||||||
* <p>The new output {@link SurfaceInfo} is applied from the next output frame rendered onwards.
|
|
||||||
* If the output {@link SurfaceInfo} is {@code null}, the {@code FrameProcessor} will stop
|
|
||||||
* rendering pending frames and resume rendering once a non-null {@link SurfaceInfo} is set.
|
|
||||||
*
|
|
||||||
* <p>If the dimensions given in {@link SurfaceInfo} do not match the {@linkplain
|
|
||||||
* Listener#onOutputSizeChanged(int,int) output size after applying the final effect} the frames
|
|
||||||
* are resized before rendering to the surface and letter/pillar-boxing is applied.
|
|
||||||
*
|
|
||||||
* <p>The caller is responsible for tracking the lifecycle of the {@link SurfaceInfo#surface}
|
|
||||||
* including calling this method with a new surface if it is destroyed. When this method returns,
|
|
||||||
* the previous output surface is no longer being used and can safely be released by the caller.
|
|
||||||
*/
|
|
||||||
void setOutputSurfaceInfo(@Nullable SurfaceInfo outputSurfaceInfo);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Releases the oldest unreleased output frame that has become {@linkplain
|
|
||||||
* Listener#onOutputFrameAvailable(long) available} at the given {@code releaseTimeNs}.
|
|
||||||
*
|
|
||||||
* <p>This will either render the output frame to the {@linkplain #setOutputSurfaceInfo output
|
|
||||||
* surface}, or drop the frame, per {@code releaseTimeNs}.
|
|
||||||
*
|
|
||||||
* <p>This method must only be called if {@code releaseFramesAutomatically} was set to {@code
|
|
||||||
* false} using the {@link Factory} and should be called exactly once for each frame that becomes
|
|
||||||
* {@linkplain Listener#onOutputFrameAvailable(long) available}.
|
|
||||||
*
|
|
||||||
* <p>The {@code releaseTimeNs} may be passed to {@link EGLExt#eglPresentationTimeANDROID}
|
|
||||||
* depending on the implementation.
|
|
||||||
*
|
|
||||||
* @param releaseTimeNs The release time to use for the frame, in nanoseconds. The release time
|
|
||||||
* can be before of after the current system time. Use {@link #DROP_OUTPUT_FRAME} to drop the
|
|
||||||
* frame, or {@link #RELEASE_OUTPUT_FRAME_IMMEDIATELY} to release the frame immediately.
|
|
||||||
*/
|
|
||||||
void releaseOutputFrame(long releaseTimeNs);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Informs the {@code FrameProcessor} that no further input frames should be accepted.
|
|
||||||
*
|
|
||||||
* @throws IllegalStateException If called more than once.
|
|
||||||
*/
|
|
||||||
void signalEndOfInput();
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Releases all resources.
|
|
||||||
*
|
|
||||||
* <p>If the frame processor is released before it has {@linkplain
|
|
||||||
* Listener#onFrameProcessingEnded() ended}, it will attempt to cancel processing any input frames
|
|
||||||
* that have already become available. Input frames that become available after release are
|
|
||||||
* ignored.
|
|
||||||
*
|
|
||||||
* <p>This method blocks until all resources are released or releasing times out.
|
|
||||||
*/
|
|
||||||
void release();
|
|
||||||
}
|
|
@ -0,0 +1,131 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2023 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package androidx.media3.common;
|
||||||
|
|
||||||
|
import android.opengl.EGL14;
|
||||||
|
import android.opengl.EGLContext;
|
||||||
|
import android.opengl.EGLDisplay;
|
||||||
|
import android.opengl.EGLSurface;
|
||||||
|
import androidx.annotation.IntRange;
|
||||||
|
import androidx.annotation.RequiresApi;
|
||||||
|
import androidx.media3.common.util.GlUtil;
|
||||||
|
import androidx.media3.common.util.GlUtil.GlException;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
|
||||||
|
// TODO(271433904): Expand this class to cover more methods in GlUtil.
|
||||||
|
/** Provider to customize the creation and maintenance of GL objects. */
|
||||||
|
@UnstableApi
|
||||||
|
public interface GlObjectsProvider {
|
||||||
|
/**
|
||||||
|
* @deprecated Please use {@code DefaultGlObjectsProvider} in {@code androidx.media3.effect}.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
GlObjectsProvider DEFAULT =
|
||||||
|
new GlObjectsProvider() {
|
||||||
|
@Override
|
||||||
|
@RequiresApi(17)
|
||||||
|
public EGLContext createEglContext(
|
||||||
|
EGLDisplay eglDisplay, int openGlVersion, int[] configAttributes) throws GlException {
|
||||||
|
return GlUtil.createEglContext(
|
||||||
|
EGL14.EGL_NO_CONTEXT, eglDisplay, openGlVersion, configAttributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@RequiresApi(17)
|
||||||
|
public EGLSurface createEglSurface(
|
||||||
|
EGLDisplay eglDisplay,
|
||||||
|
Object surface,
|
||||||
|
@C.ColorTransfer int colorTransfer,
|
||||||
|
boolean isEncoderInputSurface)
|
||||||
|
throws GlException {
|
||||||
|
return GlUtil.createEglSurface(eglDisplay, surface, colorTransfer, isEncoderInputSurface);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@RequiresApi(17)
|
||||||
|
public EGLSurface createFocusedPlaceholderEglSurface(
|
||||||
|
EGLContext eglContext, EGLDisplay eglDisplay, int[] configAttributes)
|
||||||
|
throws GlException {
|
||||||
|
return GlUtil.createFocusedPlaceholderEglSurface(
|
||||||
|
eglContext, eglDisplay, configAttributes);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public GlTextureInfo createBuffersForTexture(int texId, int width, int height)
|
||||||
|
throws GlException {
|
||||||
|
int fboId = GlUtil.createFboForTexture(texId);
|
||||||
|
return new GlTextureInfo(texId, fboId, /* rboId= */ C.INDEX_UNSET, width, height);
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link EGLContext} for the specified {@link EGLDisplay}.
|
||||||
|
*
|
||||||
|
* @param eglDisplay The {@link EGLDisplay} to create an {@link EGLContext} for.
|
||||||
|
* @param openGlVersion The version of OpenGL ES to configure. Accepts either {@code 2}, for
|
||||||
|
* OpenGL ES 2.0, or {@code 3}, for OpenGL ES 3.0.
|
||||||
|
* @param configAttributes The attributes to configure EGL with.
|
||||||
|
* @throws GlException If an error occurs during creation.
|
||||||
|
*/
|
||||||
|
@RequiresApi(17)
|
||||||
|
EGLContext createEglContext(
|
||||||
|
EGLDisplay eglDisplay, @IntRange(from = 2, to = 3) int openGlVersion, int[] configAttributes)
|
||||||
|
throws GlException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new {@link EGLSurface} wrapping the specified {@code surface}.
|
||||||
|
*
|
||||||
|
* @param eglDisplay The {@link EGLDisplay} to attach the surface to.
|
||||||
|
* @param surface The surface to wrap; must be a surface, surface texture or surface holder.
|
||||||
|
* @param colorTransfer The {@linkplain C.ColorTransfer color transfer characteristics} to which
|
||||||
|
* the {@code surface} is configured.
|
||||||
|
* @param isEncoderInputSurface Whether the {@code surface} is the input surface of an encoder.
|
||||||
|
* @throws GlException If an error occurs during creation.
|
||||||
|
*/
|
||||||
|
@RequiresApi(17)
|
||||||
|
EGLSurface createEglSurface(
|
||||||
|
EGLDisplay eglDisplay,
|
||||||
|
Object surface,
|
||||||
|
@C.ColorTransfer int colorTransfer,
|
||||||
|
boolean isEncoderInputSurface)
|
||||||
|
throws GlException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates and focuses a placeholder {@link EGLSurface}.
|
||||||
|
*
|
||||||
|
* @param eglContext The {@link EGLContext} to make current.
|
||||||
|
* @param eglDisplay The {@link EGLDisplay} to attach the surface to.
|
||||||
|
* @param configAttributes The attributes to configure EGL with.
|
||||||
|
* @return A placeholder {@link EGLSurface} that has been focused to allow rendering to take
|
||||||
|
* place, or {@link EGL14#EGL_NO_SURFACE} if the current context supports rendering without a
|
||||||
|
* surface.
|
||||||
|
* @throws GlException If an error occurs during creation.
|
||||||
|
*/
|
||||||
|
@RequiresApi(17)
|
||||||
|
EGLSurface createFocusedPlaceholderEglSurface(
|
||||||
|
EGLContext eglContext, EGLDisplay eglDisplay, int[] configAttributes) throws GlException;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link GlTextureInfo} containing the identifiers of the newly created buffers.
|
||||||
|
*
|
||||||
|
* @param texId The identifier of the texture to attach to the buffers.
|
||||||
|
* @param width The width of the texture in pixels.
|
||||||
|
* @param height The height of the texture in pixels.
|
||||||
|
* @throws GlException If an error occurs during creation.
|
||||||
|
*/
|
||||||
|
GlTextureInfo createBuffersForTexture(int texId, int width, int height) throws GlException;
|
||||||
|
}
|
@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* 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.common;
|
||||||
|
|
||||||
|
import static androidx.media3.common.util.Assertions.checkState;
|
||||||
|
|
||||||
|
import androidx.media3.common.util.GlUtil;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
|
||||||
|
/** Contains information describing an OpenGL texture. */
|
||||||
|
@UnstableApi
|
||||||
|
public final class GlTextureInfo {
|
||||||
|
/** A {@link GlTextureInfo} instance with all fields unset. */
|
||||||
|
public static final GlTextureInfo UNSET =
|
||||||
|
new GlTextureInfo(
|
||||||
|
/* texId= */ C.INDEX_UNSET,
|
||||||
|
/* fboId= */ C.INDEX_UNSET,
|
||||||
|
/* rboId= */ C.INDEX_UNSET,
|
||||||
|
/* width= */ C.LENGTH_UNSET,
|
||||||
|
/* height= */ C.LENGTH_UNSET);
|
||||||
|
|
||||||
|
private final int texId;
|
||||||
|
private final int fboId;
|
||||||
|
private final int rboId;
|
||||||
|
private final int width;
|
||||||
|
private final int height;
|
||||||
|
|
||||||
|
private boolean isReleased;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a new instance.
|
||||||
|
*
|
||||||
|
* @param texId The OpenGL texture identifier, or {@link C#INDEX_UNSET} if not specified.
|
||||||
|
* @param fboId Identifier of a framebuffer object associated with the texture, or {@link
|
||||||
|
* C#INDEX_UNSET} if not specified.
|
||||||
|
* @param rboId Identifier of a renderbuffer object associated with the texture, or {@link
|
||||||
|
* C#INDEX_UNSET} if not specified.
|
||||||
|
* @param width The width of the texture, in pixels, or {@link C#LENGTH_UNSET} if not specified.
|
||||||
|
* @param height The height of the texture, in pixels, or {@link C#LENGTH_UNSET} if not specified.
|
||||||
|
*/
|
||||||
|
public GlTextureInfo(int texId, int fboId, int rboId, int width, int height) {
|
||||||
|
this.texId = texId;
|
||||||
|
this.fboId = fboId;
|
||||||
|
this.rboId = rboId;
|
||||||
|
this.width = width;
|
||||||
|
this.height = height;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The OpenGL texture identifier, or {@link C#INDEX_UNSET} if not specified. */
|
||||||
|
public int getTexId() {
|
||||||
|
checkState(!isReleased);
|
||||||
|
return texId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifier of a framebuffer object associated with the texture, or {@link C#INDEX_UNSET} if not
|
||||||
|
* specified.
|
||||||
|
*/
|
||||||
|
public int getFboId() {
|
||||||
|
checkState(!isReleased);
|
||||||
|
return fboId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Identifier of a renderbuffer object attached with the framebuffer, or {@link C#INDEX_UNSET} if
|
||||||
|
* not specified.
|
||||||
|
*/
|
||||||
|
public int getRboId() {
|
||||||
|
checkState(!isReleased);
|
||||||
|
return rboId;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The width of the texture, in pixels, or {@link C#LENGTH_UNSET} if not specified. */
|
||||||
|
public int getWidth() {
|
||||||
|
checkState(!isReleased);
|
||||||
|
return width;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** The height of the texture, in pixels, or {@link C#LENGTH_UNSET} if not specified. */
|
||||||
|
public int getHeight() {
|
||||||
|
checkState(!isReleased);
|
||||||
|
return height;
|
||||||
|
}
|
||||||
|
|
||||||
|
public void release() throws GlUtil.GlException {
|
||||||
|
isReleased = true;
|
||||||
|
if (texId != C.INDEX_UNSET) {
|
||||||
|
GlUtil.deleteTexture(texId);
|
||||||
|
}
|
||||||
|
if (fboId != C.INDEX_UNSET) {
|
||||||
|
GlUtil.deleteFbo(fboId);
|
||||||
|
}
|
||||||
|
if (rboId != C.INDEX_UNSET) {
|
||||||
|
GlUtil.deleteRbo(rboId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -1,61 +0,0 @@
|
|||||||
/*
|
|
||||||
* 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.common;
|
|
||||||
|
|
||||||
import android.media.MediaPlayer;
|
|
||||||
import android.os.Looper;
|
|
||||||
import androidx.media3.common.util.UnstableApi;
|
|
||||||
import com.google.common.util.concurrent.Futures;
|
|
||||||
import com.google.common.util.concurrent.ListenableFuture;
|
|
||||||
|
|
||||||
/** A {@link Player} wrapper for the legacy Android platform {@link MediaPlayer}. */
|
|
||||||
@UnstableApi
|
|
||||||
public final class LegacyMediaPlayerWrapper extends SimpleBasePlayer {
|
|
||||||
|
|
||||||
private final MediaPlayer player;
|
|
||||||
|
|
||||||
private boolean playWhenReady;
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates the {@link MediaPlayer} wrapper.
|
|
||||||
*
|
|
||||||
* @param looper The {@link Looper} used to call all methods on.
|
|
||||||
*/
|
|
||||||
public LegacyMediaPlayerWrapper(Looper looper) {
|
|
||||||
super(looper);
|
|
||||||
this.player = new MediaPlayer();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected State getState() {
|
|
||||||
return new State.Builder()
|
|
||||||
.setAvailableCommands(new Commands.Builder().addAll(Player.COMMAND_PLAY_PAUSE).build())
|
|
||||||
.setPlayWhenReady(playWhenReady, Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST)
|
|
||||||
.build();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
protected ListenableFuture<?> handleSetPlayWhenReady(boolean playWhenReady) {
|
|
||||||
this.playWhenReady = playWhenReady;
|
|
||||||
// TODO: Only call these methods if the player is in Started or Paused state.
|
|
||||||
if (playWhenReady) {
|
|
||||||
player.start();
|
|
||||||
} else {
|
|
||||||
player.pause();
|
|
||||||
}
|
|
||||||
return Futures.immediateVoidFuture();
|
|
||||||
}
|
|
||||||
}
|
|
@ -23,6 +23,7 @@ import android.os.Bundle;
|
|||||||
import androidx.annotation.IntRange;
|
import androidx.annotation.IntRange;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.util.Assertions;
|
import androidx.media3.common.util.Assertions;
|
||||||
|
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;
|
||||||
@ -569,15 +570,14 @@ public final class MediaItem implements Bundleable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** 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.
|
|
||||||
public MediaItem build() {
|
public MediaItem build() {
|
||||||
// TODO: remove this check once all the deprecated individual DRM setters are removed.
|
// TODO: remove this check once all the deprecated individual DRM setters are removed.
|
||||||
checkState(drmConfiguration.licenseUri == null || drmConfiguration.scheme != null);
|
checkState(drmConfiguration.licenseUri == null || drmConfiguration.scheme != null);
|
||||||
@Nullable PlaybackProperties localConfiguration = null;
|
@Nullable LocalConfiguration localConfiguration = null;
|
||||||
@Nullable Uri uri = this.uri;
|
@Nullable Uri uri = this.uri;
|
||||||
if (uri != null) {
|
if (uri != null) {
|
||||||
localConfiguration =
|
localConfiguration =
|
||||||
new PlaybackProperties(
|
new LocalConfiguration(
|
||||||
uri,
|
uri,
|
||||||
mimeType,
|
mimeType,
|
||||||
drmConfiguration.scheme != null ? drmConfiguration.build() : null,
|
drmConfiguration.scheme != null ? drmConfiguration.build() : null,
|
||||||
@ -598,7 +598,7 @@ public final class MediaItem implements Bundleable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** DRM configuration for a media item. */
|
/** DRM configuration for a media item. */
|
||||||
public static final class DrmConfiguration {
|
public static final class DrmConfiguration implements Bundleable {
|
||||||
|
|
||||||
/** Builder for {@link DrmConfiguration}. */
|
/** Builder for {@link DrmConfiguration}. */
|
||||||
public static final class Builder {
|
public static final class Builder {
|
||||||
@ -773,7 +773,6 @@ public final class MediaItem implements Bundleable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public DrmConfiguration build() {
|
public DrmConfiguration build() {
|
||||||
|
|
||||||
return new DrmConfiguration(this);
|
return new DrmConfiguration(this);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -888,10 +887,87 @@ public final class MediaItem implements Bundleable {
|
|||||||
result = 31 * result + Arrays.hashCode(keySetId);
|
result = 31 * result + Arrays.hashCode(keySetId);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bundleable implementation.
|
||||||
|
|
||||||
|
private static final String FIELD_SCHEME = Util.intToStringMaxRadix(0);
|
||||||
|
private static final String FIELD_LICENSE_URI = Util.intToStringMaxRadix(1);
|
||||||
|
private static final String FIELD_LICENSE_REQUEST_HEADERS = Util.intToStringMaxRadix(2);
|
||||||
|
private static final String FIELD_MULTI_SESSION = Util.intToStringMaxRadix(3);
|
||||||
|
private static final String FIELD_PLAY_CLEAR_CONTENT_WITHOUT_KEY = Util.intToStringMaxRadix(4);
|
||||||
|
private static final String FIELD_FORCE_DEFAULT_LICENSE_URI = Util.intToStringMaxRadix(5);
|
||||||
|
private static final String FIELD_FORCED_SESSION_TRACK_TYPES = Util.intToStringMaxRadix(6);
|
||||||
|
private static final String FIELD_KEY_SET_ID = Util.intToStringMaxRadix(7);
|
||||||
|
|
||||||
|
/** An object that can restore {@link DrmConfiguration} from a {@link Bundle}. */
|
||||||
|
@UnstableApi
|
||||||
|
public static final Creator<DrmConfiguration> CREATOR = DrmConfiguration::fromBundle;
|
||||||
|
|
||||||
|
@UnstableApi
|
||||||
|
private static DrmConfiguration fromBundle(Bundle bundle) {
|
||||||
|
UUID scheme = UUID.fromString(checkNotNull(bundle.getString(FIELD_SCHEME)));
|
||||||
|
@Nullable Uri licenseUri = bundle.getParcelable(FIELD_LICENSE_URI);
|
||||||
|
Bundle licenseMapAsBundle =
|
||||||
|
BundleableUtil.getBundleWithDefault(bundle, FIELD_LICENSE_REQUEST_HEADERS, Bundle.EMPTY);
|
||||||
|
ImmutableMap<String, String> licenseRequestHeaders =
|
||||||
|
BundleableUtil.bundleToStringImmutableMap(licenseMapAsBundle);
|
||||||
|
boolean multiSession = bundle.getBoolean(FIELD_MULTI_SESSION, false);
|
||||||
|
boolean playClearContentWithoutKey =
|
||||||
|
bundle.getBoolean(FIELD_PLAY_CLEAR_CONTENT_WITHOUT_KEY, false);
|
||||||
|
boolean forceDefaultLicenseUri = bundle.getBoolean(FIELD_FORCE_DEFAULT_LICENSE_URI, false);
|
||||||
|
ArrayList<@C.TrackType Integer> forcedSessionTrackTypesArray =
|
||||||
|
BundleableUtil.getIntegerArrayListWithDefault(
|
||||||
|
bundle, FIELD_FORCED_SESSION_TRACK_TYPES, new ArrayList<>());
|
||||||
|
ImmutableList<@C.TrackType Integer> forcedSessionTrackTypes =
|
||||||
|
ImmutableList.copyOf(forcedSessionTrackTypesArray);
|
||||||
|
@Nullable byte[] keySetId = bundle.getByteArray(FIELD_KEY_SET_ID);
|
||||||
|
|
||||||
|
Builder builder = new Builder(scheme);
|
||||||
|
return builder
|
||||||
|
.setLicenseUri(licenseUri)
|
||||||
|
.setLicenseRequestHeaders(licenseRequestHeaders)
|
||||||
|
.setMultiSession(multiSession)
|
||||||
|
.setForceDefaultLicenseUri(forceDefaultLicenseUri)
|
||||||
|
.setPlayClearContentWithoutKey(playClearContentWithoutKey)
|
||||||
|
.setForcedSessionTrackTypes(forcedSessionTrackTypes)
|
||||||
|
.setKeySetId(keySetId)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@UnstableApi
|
||||||
|
@Override
|
||||||
|
public Bundle toBundle() {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putString(FIELD_SCHEME, scheme.toString());
|
||||||
|
if (licenseUri != null) {
|
||||||
|
bundle.putParcelable(FIELD_LICENSE_URI, licenseUri);
|
||||||
|
}
|
||||||
|
if (!licenseRequestHeaders.isEmpty()) {
|
||||||
|
bundle.putBundle(
|
||||||
|
FIELD_LICENSE_REQUEST_HEADERS, BundleableUtil.stringMapToBundle(licenseRequestHeaders));
|
||||||
|
}
|
||||||
|
if (multiSession) {
|
||||||
|
bundle.putBoolean(FIELD_MULTI_SESSION, multiSession);
|
||||||
|
}
|
||||||
|
if (playClearContentWithoutKey) {
|
||||||
|
bundle.putBoolean(FIELD_PLAY_CLEAR_CONTENT_WITHOUT_KEY, playClearContentWithoutKey);
|
||||||
|
}
|
||||||
|
if (forceDefaultLicenseUri) {
|
||||||
|
bundle.putBoolean(FIELD_FORCE_DEFAULT_LICENSE_URI, forceDefaultLicenseUri);
|
||||||
|
}
|
||||||
|
if (!forcedSessionTrackTypes.isEmpty()) {
|
||||||
|
bundle.putIntegerArrayList(
|
||||||
|
FIELD_FORCED_SESSION_TRACK_TYPES, new ArrayList<>(forcedSessionTrackTypes));
|
||||||
|
}
|
||||||
|
if (keySetId != null) {
|
||||||
|
bundle.putByteArray(FIELD_KEY_SET_ID, keySetId);
|
||||||
|
}
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Configuration for playing back linear ads with a media item. */
|
/** Configuration for playing back linear ads with a media item. */
|
||||||
public static final class AdsConfiguration {
|
public static final class AdsConfiguration implements Bundleable {
|
||||||
|
|
||||||
/** Builder for {@link AdsConfiguration} instances. */
|
/** Builder for {@link AdsConfiguration} instances. */
|
||||||
public static final class Builder {
|
public static final class Builder {
|
||||||
@ -975,11 +1051,43 @@ public final class MediaItem implements Bundleable {
|
|||||||
result = 31 * result + (adsId != null ? adsId.hashCode() : 0);
|
result = 31 * result + (adsId != null ? adsId.hashCode() : 0);
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bundleable implementation.
|
||||||
|
|
||||||
|
private static final String FIELD_AD_TAG_URI = Util.intToStringMaxRadix(0);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object that can restore {@link AdsConfiguration} from a {@link Bundle}.
|
||||||
|
*
|
||||||
|
* <p>The {@link #adsId} of a restored instance will always be {@code null}.
|
||||||
|
*/
|
||||||
|
@UnstableApi
|
||||||
|
public static final Creator<AdsConfiguration> CREATOR = AdsConfiguration::fromBundle;
|
||||||
|
|
||||||
|
@UnstableApi
|
||||||
|
private static AdsConfiguration fromBundle(Bundle bundle) {
|
||||||
|
@Nullable Uri adTagUri = bundle.getParcelable(FIELD_AD_TAG_URI);
|
||||||
|
checkNotNull(adTagUri);
|
||||||
|
return new AdsConfiguration.Builder(adTagUri).build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* <p>It omits the {@link #adsId} field. The {@link #adsId} of an instance restored from such a
|
||||||
|
* bundle by {@link #CREATOR} will be {@code null}.
|
||||||
|
*/
|
||||||
|
@UnstableApi
|
||||||
|
@Override
|
||||||
|
public Bundle toBundle() {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putParcelable(FIELD_AD_TAG_URI, adTagUri);
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Properties for local playback. */
|
/** Properties for local playback. */
|
||||||
// TODO: Mark this final when PlaybackProperties is deleted.
|
public static final class LocalConfiguration implements Bundleable {
|
||||||
public static class LocalConfiguration {
|
|
||||||
|
|
||||||
/** The {@link Uri}. */
|
/** The {@link Uri}. */
|
||||||
public final Uri uri;
|
public final Uri uri;
|
||||||
@ -1075,33 +1183,84 @@ public final class MediaItem implements Bundleable {
|
|||||||
result = 31 * result + (tag == null ? 0 : tag.hashCode());
|
result = 31 * result + (tag == null ? 0 : tag.hashCode());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
// Bundleable implementation.
|
||||||
|
|
||||||
|
private static final String FIELD_URI = Util.intToStringMaxRadix(0);
|
||||||
|
private static final String FIELD_MIME_TYPE = Util.intToStringMaxRadix(1);
|
||||||
|
private static final String FIELD_DRM_CONFIGURATION = Util.intToStringMaxRadix(2);
|
||||||
|
private static final String FIELD_ADS_CONFIGURATION = Util.intToStringMaxRadix(3);
|
||||||
|
private static final String FIELD_STREAM_KEYS = Util.intToStringMaxRadix(4);
|
||||||
|
private static final String FIELD_CUSTOM_CACHE_KEY = Util.intToStringMaxRadix(5);
|
||||||
|
private static final String FIELD_SUBTITLE_CONFIGURATION = Util.intToStringMaxRadix(6);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Use {@link LocalConfiguration}.
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* <p>It omits the {@link #tag} field. The {@link #tag} of an instance restored from such a
|
||||||
|
* bundle by {@link #CREATOR} will be {@code null}.
|
||||||
*/
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Deprecated
|
@Override
|
||||||
public static final class PlaybackProperties extends LocalConfiguration {
|
public Bundle toBundle() {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putParcelable(FIELD_URI, uri);
|
||||||
|
if (mimeType != null) {
|
||||||
|
bundle.putString(FIELD_MIME_TYPE, mimeType);
|
||||||
|
}
|
||||||
|
if (drmConfiguration != null) {
|
||||||
|
bundle.putBundle(FIELD_DRM_CONFIGURATION, drmConfiguration.toBundle());
|
||||||
|
}
|
||||||
|
if (adsConfiguration != null) {
|
||||||
|
bundle.putBundle(FIELD_ADS_CONFIGURATION, adsConfiguration.toBundle());
|
||||||
|
}
|
||||||
|
if (!streamKeys.isEmpty()) {
|
||||||
|
bundle.putParcelableArrayList(
|
||||||
|
FIELD_STREAM_KEYS, BundleableUtil.toBundleArrayList(streamKeys));
|
||||||
|
}
|
||||||
|
if (customCacheKey != null) {
|
||||||
|
bundle.putString(FIELD_CUSTOM_CACHE_KEY, customCacheKey);
|
||||||
|
}
|
||||||
|
if (!subtitleConfigurations.isEmpty()) {
|
||||||
|
bundle.putParcelableArrayList(
|
||||||
|
FIELD_SUBTITLE_CONFIGURATION, BundleableUtil.toBundleArrayList(subtitleConfigurations));
|
||||||
|
}
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
|
|
||||||
private PlaybackProperties(
|
/** Object that can restore {@link LocalConfiguration} from a {@link Bundle}. */
|
||||||
Uri uri,
|
@UnstableApi
|
||||||
@Nullable String mimeType,
|
public static final Creator<LocalConfiguration> CREATOR = LocalConfiguration::fromBundle;
|
||||||
@Nullable DrmConfiguration drmConfiguration,
|
|
||||||
@Nullable AdsConfiguration adsConfiguration,
|
@UnstableApi
|
||||||
List<StreamKey> streamKeys,
|
private static LocalConfiguration fromBundle(Bundle bundle) {
|
||||||
@Nullable String customCacheKey,
|
@Nullable Bundle drmBundle = bundle.getBundle(FIELD_DRM_CONFIGURATION);
|
||||||
ImmutableList<SubtitleConfiguration> subtitleConfigurations,
|
DrmConfiguration drmConfiguration =
|
||||||
@Nullable Object tag) {
|
drmBundle == null ? null : DrmConfiguration.CREATOR.fromBundle(drmBundle);
|
||||||
super(
|
@Nullable Bundle adsBundle = bundle.getBundle(FIELD_ADS_CONFIGURATION);
|
||||||
uri,
|
AdsConfiguration adsConfiguration =
|
||||||
mimeType,
|
adsBundle == null ? null : AdsConfiguration.CREATOR.fromBundle(adsBundle);
|
||||||
|
@Nullable List<Bundle> streamKeysBundles = bundle.getParcelableArrayList(FIELD_STREAM_KEYS);
|
||||||
|
List<StreamKey> streamKeys =
|
||||||
|
streamKeysBundles == null
|
||||||
|
? ImmutableList.of()
|
||||||
|
: BundleableUtil.fromBundleList(StreamKey::fromBundle, streamKeysBundles);
|
||||||
|
@Nullable
|
||||||
|
List<Bundle> subtitleBundles = bundle.getParcelableArrayList(FIELD_SUBTITLE_CONFIGURATION);
|
||||||
|
ImmutableList<SubtitleConfiguration> subtitleConfiguration =
|
||||||
|
subtitleBundles == null
|
||||||
|
? ImmutableList.of()
|
||||||
|
: BundleableUtil.fromBundleList(SubtitleConfiguration.CREATOR, subtitleBundles);
|
||||||
|
|
||||||
|
return new LocalConfiguration(
|
||||||
|
checkNotNull(bundle.getParcelable(FIELD_URI)),
|
||||||
|
bundle.getString(FIELD_MIME_TYPE),
|
||||||
drmConfiguration,
|
drmConfiguration,
|
||||||
adsConfiguration,
|
adsConfiguration,
|
||||||
streamKeys,
|
streamKeys,
|
||||||
customCacheKey,
|
bundle.getString(FIELD_CUSTOM_CACHE_KEY),
|
||||||
subtitleConfigurations,
|
subtitleConfiguration,
|
||||||
tag);
|
/* tag= */ null);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1326,7 +1485,7 @@ public final class MediaItem implements Bundleable {
|
|||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Object that can restore {@link LiveConfiguration} from a {@link Bundle}. */
|
/** An object that can restore {@link LiveConfiguration} from a {@link Bundle}. */
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public static final Creator<LiveConfiguration> CREATOR =
|
public static final Creator<LiveConfiguration> CREATOR =
|
||||||
bundle ->
|
bundle ->
|
||||||
@ -1342,7 +1501,7 @@ public final class MediaItem implements Bundleable {
|
|||||||
|
|
||||||
/** Properties for a text track. */
|
/** Properties for a text track. */
|
||||||
// TODO: Mark this final when Subtitle is deleted.
|
// TODO: Mark this final when Subtitle is deleted.
|
||||||
public static class SubtitleConfiguration {
|
public static class SubtitleConfiguration implements Bundleable {
|
||||||
|
|
||||||
/** Builder for {@link SubtitleConfiguration} instances. */
|
/** Builder for {@link SubtitleConfiguration} instances. */
|
||||||
public static final class Builder {
|
public static final class Builder {
|
||||||
@ -1513,6 +1672,67 @@ public final class MediaItem implements Bundleable {
|
|||||||
result = 31 * result + (id == null ? 0 : id.hashCode());
|
result = 31 * result + (id == null ? 0 : id.hashCode());
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// Bundleable implementation.
|
||||||
|
|
||||||
|
private static final String FIELD_URI = Util.intToStringMaxRadix(0);
|
||||||
|
private static final String FIELD_MIME_TYPE = Util.intToStringMaxRadix(1);
|
||||||
|
private static final String FIELD_LANGUAGE = Util.intToStringMaxRadix(2);
|
||||||
|
private static final String FIELD_SELECTION_FLAGS = Util.intToStringMaxRadix(3);
|
||||||
|
private static final String FIELD_ROLE_FLAGS = Util.intToStringMaxRadix(4);
|
||||||
|
private static final String FIELD_LABEL = Util.intToStringMaxRadix(5);
|
||||||
|
private static final String FIELD_ID = Util.intToStringMaxRadix(6);
|
||||||
|
|
||||||
|
/** An object that can restore {@link SubtitleConfiguration} from a {@link Bundle}. */
|
||||||
|
@UnstableApi
|
||||||
|
public static final Creator<SubtitleConfiguration> CREATOR = SubtitleConfiguration::fromBundle;
|
||||||
|
|
||||||
|
@UnstableApi
|
||||||
|
private static SubtitleConfiguration fromBundle(Bundle bundle) {
|
||||||
|
Uri uri = checkNotNull(bundle.getParcelable(FIELD_URI));
|
||||||
|
@Nullable String mimeType = bundle.getString(FIELD_MIME_TYPE);
|
||||||
|
@Nullable String language = bundle.getString(FIELD_LANGUAGE);
|
||||||
|
@C.SelectionFlags int selectionFlags = bundle.getInt(FIELD_SELECTION_FLAGS, 0);
|
||||||
|
@C.RoleFlags int roleFlags = bundle.getInt(FIELD_ROLE_FLAGS, 0);
|
||||||
|
@Nullable String label = bundle.getString(FIELD_LABEL);
|
||||||
|
@Nullable String id = bundle.getString(FIELD_ID);
|
||||||
|
|
||||||
|
SubtitleConfiguration.Builder builder = new SubtitleConfiguration.Builder(uri);
|
||||||
|
return builder
|
||||||
|
.setMimeType(mimeType)
|
||||||
|
.setLanguage(language)
|
||||||
|
.setSelectionFlags(selectionFlags)
|
||||||
|
.setRoleFlags(roleFlags)
|
||||||
|
.setLabel(label)
|
||||||
|
.setId(id)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
@UnstableApi
|
||||||
|
@Override
|
||||||
|
public Bundle toBundle() {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
bundle.putParcelable(FIELD_URI, uri);
|
||||||
|
if (mimeType != null) {
|
||||||
|
bundle.putString(FIELD_MIME_TYPE, mimeType);
|
||||||
|
}
|
||||||
|
if (language != null) {
|
||||||
|
bundle.putString(FIELD_LANGUAGE, language);
|
||||||
|
}
|
||||||
|
if (selectionFlags != 0) {
|
||||||
|
bundle.putInt(FIELD_SELECTION_FLAGS, selectionFlags);
|
||||||
|
}
|
||||||
|
if (roleFlags != 0) {
|
||||||
|
bundle.putInt(FIELD_ROLE_FLAGS, roleFlags);
|
||||||
|
}
|
||||||
|
if (label != null) {
|
||||||
|
bundle.putString(FIELD_LABEL, label);
|
||||||
|
}
|
||||||
|
if (id != null) {
|
||||||
|
bundle.putString(FIELD_ID, id);
|
||||||
|
}
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -1757,7 +1977,7 @@ public final class MediaItem implements Bundleable {
|
|||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Object that can restore {@link ClippingConfiguration} from a {@link Bundle}. */
|
/** An object that can restore {@link ClippingConfiguration} from a {@link Bundle}. */
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public static final Creator<ClippingProperties> CREATOR =
|
public static final Creator<ClippingProperties> CREATOR =
|
||||||
bundle ->
|
bundle ->
|
||||||
@ -1917,7 +2137,7 @@ public final class MediaItem implements Bundleable {
|
|||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Object that can restore {@link RequestMetadata} from a {@link Bundle}. */
|
/** An object that can restore {@link RequestMetadata} from a {@link Bundle}. */
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public static final Creator<RequestMetadata> CREATOR =
|
public static final Creator<RequestMetadata> CREATOR =
|
||||||
bundle ->
|
bundle ->
|
||||||
@ -1948,7 +2168,7 @@ public final class MediaItem implements Bundleable {
|
|||||||
/**
|
/**
|
||||||
* @deprecated Use {@link #localConfiguration} instead.
|
* @deprecated Use {@link #localConfiguration} instead.
|
||||||
*/
|
*/
|
||||||
@UnstableApi @Deprecated @Nullable public final PlaybackProperties playbackProperties;
|
@UnstableApi @Deprecated @Nullable public final LocalConfiguration playbackProperties;
|
||||||
|
|
||||||
/** The live playback configuration. */
|
/** The live playback configuration. */
|
||||||
public final LiveConfiguration liveConfiguration;
|
public final LiveConfiguration liveConfiguration;
|
||||||
@ -1966,12 +2186,12 @@ public final class MediaItem implements Bundleable {
|
|||||||
/** The media {@link RequestMetadata}. */
|
/** The media {@link RequestMetadata}. */
|
||||||
public final RequestMetadata requestMetadata;
|
public final RequestMetadata requestMetadata;
|
||||||
|
|
||||||
// Using PlaybackProperties and ClippingProperties until they're deleted.
|
// Using ClippingProperties until they're deleted.
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
private MediaItem(
|
private MediaItem(
|
||||||
String mediaId,
|
String mediaId,
|
||||||
ClippingProperties clippingConfiguration,
|
ClippingProperties clippingConfiguration,
|
||||||
@Nullable PlaybackProperties localConfiguration,
|
@Nullable LocalConfiguration localConfiguration,
|
||||||
LiveConfiguration liveConfiguration,
|
LiveConfiguration liveConfiguration,
|
||||||
MediaMetadata mediaMetadata,
|
MediaMetadata mediaMetadata,
|
||||||
RequestMetadata requestMetadata) {
|
RequestMetadata requestMetadata) {
|
||||||
@ -2026,16 +2246,10 @@ public final class MediaItem implements Bundleable {
|
|||||||
private static final String FIELD_MEDIA_METADATA = Util.intToStringMaxRadix(2);
|
private static final String FIELD_MEDIA_METADATA = Util.intToStringMaxRadix(2);
|
||||||
private static final String FIELD_CLIPPING_PROPERTIES = Util.intToStringMaxRadix(3);
|
private static final String FIELD_CLIPPING_PROPERTIES = Util.intToStringMaxRadix(3);
|
||||||
private static final String FIELD_REQUEST_METADATA = Util.intToStringMaxRadix(4);
|
private static final String FIELD_REQUEST_METADATA = Util.intToStringMaxRadix(4);
|
||||||
|
private static final String FIELD_LOCAL_CONFIGURATION = Util.intToStringMaxRadix(5);
|
||||||
|
|
||||||
/**
|
|
||||||
* {@inheritDoc}
|
|
||||||
*
|
|
||||||
* <p>It omits the {@link #localConfiguration} field. The {@link #localConfiguration} of an
|
|
||||||
* instance restored by {@link #CREATOR} will always be {@code null}.
|
|
||||||
*/
|
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Override
|
private Bundle toBundle(boolean includeLocalConfiguration) {
|
||||||
public Bundle toBundle() {
|
|
||||||
Bundle bundle = new Bundle();
|
Bundle bundle = new Bundle();
|
||||||
if (!mediaId.equals(DEFAULT_MEDIA_ID)) {
|
if (!mediaId.equals(DEFAULT_MEDIA_ID)) {
|
||||||
bundle.putString(FIELD_MEDIA_ID, mediaId);
|
bundle.putString(FIELD_MEDIA_ID, mediaId);
|
||||||
@ -2052,11 +2266,35 @@ public final class MediaItem implements Bundleable {
|
|||||||
if (!requestMetadata.equals(RequestMetadata.EMPTY)) {
|
if (!requestMetadata.equals(RequestMetadata.EMPTY)) {
|
||||||
bundle.putBundle(FIELD_REQUEST_METADATA, requestMetadata.toBundle());
|
bundle.putBundle(FIELD_REQUEST_METADATA, requestMetadata.toBundle());
|
||||||
}
|
}
|
||||||
|
if (includeLocalConfiguration && localConfiguration != null) {
|
||||||
|
bundle.putBundle(FIELD_LOCAL_CONFIGURATION, localConfiguration.toBundle());
|
||||||
|
}
|
||||||
return bundle;
|
return bundle;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Object that can restore {@link MediaItem} from a {@link Bundle}.
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* <p>It omits the {@link #localConfiguration} field. The {@link #localConfiguration} of an
|
||||||
|
* instance restored from such a bundle by {@link #CREATOR} will be {@code null}.
|
||||||
|
*/
|
||||||
|
@UnstableApi
|
||||||
|
@Override
|
||||||
|
public Bundle toBundle() {
|
||||||
|
return toBundle(/* includeLocalConfiguration= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link Bundle} representing the information stored in this {@link #MediaItem} object,
|
||||||
|
* while including the {@link #localConfiguration} field if it is not null (otherwise skips it).
|
||||||
|
*/
|
||||||
|
@UnstableApi
|
||||||
|
public Bundle toBundleIncludeLocalConfiguration() {
|
||||||
|
return toBundle(/* includeLocalConfiguration= */ true);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An object that can restore {@link MediaItem} from a {@link Bundle}.
|
||||||
*
|
*
|
||||||
* <p>The {@link #localConfiguration} of a restored instance will always be {@code null}.
|
* <p>The {@link #localConfiguration} of a restored instance will always be {@code null}.
|
||||||
*/
|
*/
|
||||||
@ -2093,10 +2331,17 @@ public final class MediaItem implements Bundleable {
|
|||||||
} else {
|
} else {
|
||||||
requestMetadata = RequestMetadata.CREATOR.fromBundle(requestMetadataBundle);
|
requestMetadata = RequestMetadata.CREATOR.fromBundle(requestMetadataBundle);
|
||||||
}
|
}
|
||||||
|
@Nullable Bundle localConfigurationBundle = bundle.getBundle(FIELD_LOCAL_CONFIGURATION);
|
||||||
|
LocalConfiguration localConfiguration;
|
||||||
|
if (localConfigurationBundle == null) {
|
||||||
|
localConfiguration = null;
|
||||||
|
} else {
|
||||||
|
localConfiguration = LocalConfiguration.CREATOR.fromBundle(localConfigurationBundle);
|
||||||
|
}
|
||||||
return new MediaItem(
|
return new MediaItem(
|
||||||
mediaId,
|
mediaId,
|
||||||
clippingConfiguration,
|
clippingConfiguration,
|
||||||
/* localConfiguration= */ null,
|
localConfiguration,
|
||||||
liveConfiguration,
|
liveConfiguration,
|
||||||
mediaMetadata,
|
mediaMetadata,
|
||||||
requestMetadata);
|
requestMetadata);
|
||||||
|
@ -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.2";
|
public static final String VERSION = "1.1.0";
|
||||||
|
|
||||||
/** 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.2";
|
public static final String VERSION_SLASHY = "AndroidXMedia3/1.1.0";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* 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_002_3_00;
|
public static final int VERSION_INT = 1_001_000_3_00;
|
||||||
|
|
||||||
/** 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;
|
||||||
|
@ -60,7 +60,11 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
@Nullable private Uri artworkUri;
|
@Nullable private Uri artworkUri;
|
||||||
@Nullable private Integer trackNumber;
|
@Nullable private Integer trackNumber;
|
||||||
@Nullable private Integer totalTrackCount;
|
@Nullable private Integer totalTrackCount;
|
||||||
@Nullable private @FolderType Integer folderType;
|
|
||||||
|
@SuppressWarnings("deprecation") // Builder for deprecated field.
|
||||||
|
@Nullable
|
||||||
|
private @FolderType Integer folderType;
|
||||||
|
|
||||||
@Nullable private Boolean isBrowsable;
|
@Nullable private Boolean isBrowsable;
|
||||||
@Nullable private Boolean isPlayable;
|
@Nullable private Boolean isPlayable;
|
||||||
@Nullable private Integer recordingYear;
|
@Nullable private Integer recordingYear;
|
||||||
@ -82,6 +86,7 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
|
|
||||||
public Builder() {}
|
public Builder() {}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation") // Assigning from deprecated fields.
|
||||||
private Builder(MediaMetadata mediaMetadata) {
|
private Builder(MediaMetadata mediaMetadata) {
|
||||||
this.title = mediaMetadata.title;
|
this.title = mediaMetadata.title;
|
||||||
this.artist = mediaMetadata.artist;
|
this.artist = mediaMetadata.artist;
|
||||||
@ -251,9 +256,11 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
/**
|
/**
|
||||||
* Sets the {@link FolderType}.
|
* Sets the {@link FolderType}.
|
||||||
*
|
*
|
||||||
* <p>This method will be deprecated. Use {@link #setIsBrowsable} to indicate if an item is a
|
* @deprecated Use {@link #setIsBrowsable} to indicate if an item is a browsable folder and use
|
||||||
* browsable folder and use {@link #setMediaType} to indicate the type of the folder.
|
* {@link #setMediaType} to indicate the type of the folder.
|
||||||
*/
|
*/
|
||||||
|
@SuppressWarnings("deprecation") // Using deprecated type.
|
||||||
|
@Deprecated
|
||||||
@CanIgnoreReturnValue
|
@CanIgnoreReturnValue
|
||||||
public Builder setFolderType(@Nullable @FolderType Integer folderType) {
|
public Builder setFolderType(@Nullable @FolderType Integer folderType) {
|
||||||
this.folderType = folderType;
|
this.folderType = folderType;
|
||||||
@ -261,7 +268,6 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Sets whether the media is a browsable folder. */
|
/** Sets whether the media is a browsable folder. */
|
||||||
@UnstableApi
|
|
||||||
@CanIgnoreReturnValue
|
@CanIgnoreReturnValue
|
||||||
public Builder setIsBrowsable(@Nullable Boolean isBrowsable) {
|
public Builder setIsBrowsable(@Nullable Boolean isBrowsable) {
|
||||||
this.isBrowsable = isBrowsable;
|
this.isBrowsable = isBrowsable;
|
||||||
@ -402,7 +408,6 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
|
|
||||||
/** Sets the {@link MediaType}. */
|
/** Sets the {@link MediaType}. */
|
||||||
@CanIgnoreReturnValue
|
@CanIgnoreReturnValue
|
||||||
@UnstableApi
|
|
||||||
public Builder setMediaType(@Nullable @MediaType Integer mediaType) {
|
public Builder setMediaType(@Nullable @MediaType Integer mediaType) {
|
||||||
this.mediaType = mediaType;
|
this.mediaType = mediaType;
|
||||||
return this;
|
return this;
|
||||||
@ -458,6 +463,7 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/** Populates all the fields from {@code mediaMetadata}, provided they are non-null. */
|
/** Populates all the fields from {@code mediaMetadata}, provided they are non-null. */
|
||||||
|
@SuppressWarnings("deprecation") // Populating deprecated fields.
|
||||||
@CanIgnoreReturnValue
|
@CanIgnoreReturnValue
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public Builder populate(@Nullable MediaMetadata mediaMetadata) {
|
public Builder populate(@Nullable MediaMetadata mediaMetadata) {
|
||||||
@ -595,7 +601,6 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
@Documented
|
@Documented
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@Target(TYPE_USE)
|
@Target(TYPE_USE)
|
||||||
@UnstableApi
|
|
||||||
@IntDef({
|
@IntDef({
|
||||||
MEDIA_TYPE_MIXED,
|
MEDIA_TYPE_MIXED,
|
||||||
MEDIA_TYPE_MUSIC,
|
MEDIA_TYPE_MUSIC,
|
||||||
@ -637,111 +642,111 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
public @interface MediaType {}
|
public @interface MediaType {}
|
||||||
|
|
||||||
/** Media of undetermined type or a mix of multiple {@linkplain MediaType media types}. */
|
/** Media of undetermined type or a mix of multiple {@linkplain MediaType media types}. */
|
||||||
@UnstableApi public static final int MEDIA_TYPE_MIXED = 0;
|
public static final int MEDIA_TYPE_MIXED = 0;
|
||||||
/** {@link MediaType} for music. */
|
/** {@link MediaType} for music. */
|
||||||
@UnstableApi public static final int MEDIA_TYPE_MUSIC = 1;
|
public static final int MEDIA_TYPE_MUSIC = 1;
|
||||||
/** {@link MediaType} for an audio book chapter. */
|
/** {@link MediaType} for an audio book chapter. */
|
||||||
@UnstableApi public static final int MEDIA_TYPE_AUDIO_BOOK_CHAPTER = 2;
|
public static final int MEDIA_TYPE_AUDIO_BOOK_CHAPTER = 2;
|
||||||
/** {@link MediaType} for a podcast episode. */
|
/** {@link MediaType} for a podcast episode. */
|
||||||
@UnstableApi public static final int MEDIA_TYPE_PODCAST_EPISODE = 3;
|
public static final int MEDIA_TYPE_PODCAST_EPISODE = 3;
|
||||||
/** {@link MediaType} for a radio station. */
|
/** {@link MediaType} for a radio station. */
|
||||||
@UnstableApi public static final int MEDIA_TYPE_RADIO_STATION = 4;
|
public static final int MEDIA_TYPE_RADIO_STATION = 4;
|
||||||
/** {@link MediaType} for news. */
|
/** {@link MediaType} for news. */
|
||||||
@UnstableApi public static final int MEDIA_TYPE_NEWS = 5;
|
public static final int MEDIA_TYPE_NEWS = 5;
|
||||||
/** {@link MediaType} for a video. */
|
/** {@link MediaType} for a video. */
|
||||||
@UnstableApi public static final int MEDIA_TYPE_VIDEO = 6;
|
public static final int MEDIA_TYPE_VIDEO = 6;
|
||||||
/** {@link MediaType} for a movie trailer. */
|
/** {@link MediaType} for a movie trailer. */
|
||||||
@UnstableApi public static final int MEDIA_TYPE_TRAILER = 7;
|
public static final int MEDIA_TYPE_TRAILER = 7;
|
||||||
/** {@link MediaType} for a movie. */
|
/** {@link MediaType} for a movie. */
|
||||||
@UnstableApi public static final int MEDIA_TYPE_MOVIE = 8;
|
public static final int MEDIA_TYPE_MOVIE = 8;
|
||||||
/** {@link MediaType} for a TV show. */
|
/** {@link MediaType} for a TV show. */
|
||||||
@UnstableApi public static final int MEDIA_TYPE_TV_SHOW = 9;
|
public static final int MEDIA_TYPE_TV_SHOW = 9;
|
||||||
/**
|
/**
|
||||||
* {@link MediaType} for a group of items (e.g., {@link #MEDIA_TYPE_MUSIC music}) belonging to an
|
* {@link MediaType} for a group of items (e.g., {@link #MEDIA_TYPE_MUSIC music}) belonging to an
|
||||||
* album.
|
* album.
|
||||||
*/
|
*/
|
||||||
@UnstableApi public static final int MEDIA_TYPE_ALBUM = 10;
|
public static final int MEDIA_TYPE_ALBUM = 10;
|
||||||
/**
|
/**
|
||||||
* {@link MediaType} for a group of items (e.g., {@link #MEDIA_TYPE_MUSIC music}) from the same
|
* {@link MediaType} for a group of items (e.g., {@link #MEDIA_TYPE_MUSIC music}) from the same
|
||||||
* artist.
|
* artist.
|
||||||
*/
|
*/
|
||||||
@UnstableApi public static final int MEDIA_TYPE_ARTIST = 11;
|
public static final int MEDIA_TYPE_ARTIST = 11;
|
||||||
/**
|
/**
|
||||||
* {@link MediaType} for a group of items (e.g., {@link #MEDIA_TYPE_MUSIC music}) of the same
|
* {@link MediaType} for a group of items (e.g., {@link #MEDIA_TYPE_MUSIC music}) of the same
|
||||||
* genre.
|
* genre.
|
||||||
*/
|
*/
|
||||||
@UnstableApi public static final int MEDIA_TYPE_GENRE = 12;
|
public static final int MEDIA_TYPE_GENRE = 12;
|
||||||
/**
|
/**
|
||||||
* {@link MediaType} for a group of items (e.g., {@link #MEDIA_TYPE_MUSIC music}) forming a
|
* {@link MediaType} for a group of items (e.g., {@link #MEDIA_TYPE_MUSIC music}) forming a
|
||||||
* playlist.
|
* playlist.
|
||||||
*/
|
*/
|
||||||
@UnstableApi public static final int MEDIA_TYPE_PLAYLIST = 13;
|
public static final int MEDIA_TYPE_PLAYLIST = 13;
|
||||||
/**
|
/**
|
||||||
* {@link MediaType} for a group of items (e.g., {@link #MEDIA_TYPE_MUSIC music}) from the same
|
* {@link MediaType} for a group of items (e.g., {@link #MEDIA_TYPE_MUSIC music}) from the same
|
||||||
* year.
|
* year.
|
||||||
*/
|
*/
|
||||||
@UnstableApi public static final int MEDIA_TYPE_YEAR = 14;
|
public static final int MEDIA_TYPE_YEAR = 14;
|
||||||
/**
|
/**
|
||||||
* {@link MediaType} for a group of items forming an audio book. Items in this group are typically
|
* {@link MediaType} for a group of items forming an audio book. Items in this group are typically
|
||||||
* of type {@link #MEDIA_TYPE_AUDIO_BOOK_CHAPTER}.
|
* of type {@link #MEDIA_TYPE_AUDIO_BOOK_CHAPTER}.
|
||||||
*/
|
*/
|
||||||
@UnstableApi public static final int MEDIA_TYPE_AUDIO_BOOK = 15;
|
public static final int MEDIA_TYPE_AUDIO_BOOK = 15;
|
||||||
/**
|
/**
|
||||||
* {@link MediaType} for a group of items belonging to a podcast. Items in this group are
|
* {@link MediaType} for a group of items belonging to a podcast. Items in this group are
|
||||||
* typically of type {@link #MEDIA_TYPE_PODCAST_EPISODE}.
|
* typically of type {@link #MEDIA_TYPE_PODCAST_EPISODE}.
|
||||||
*/
|
*/
|
||||||
@UnstableApi public static final int MEDIA_TYPE_PODCAST = 16;
|
public static final int MEDIA_TYPE_PODCAST = 16;
|
||||||
/**
|
/**
|
||||||
* {@link MediaType} for a group of items that are part of a TV channel. Items in this group are
|
* {@link MediaType} for a group of items that are part of a TV channel. Items in this group are
|
||||||
* typically of type {@link #MEDIA_TYPE_TV_SHOW}, {@link #MEDIA_TYPE_TV_SERIES} or {@link
|
* typically of type {@link #MEDIA_TYPE_TV_SHOW}, {@link #MEDIA_TYPE_TV_SERIES} or {@link
|
||||||
* #MEDIA_TYPE_MOVIE}.
|
* #MEDIA_TYPE_MOVIE}.
|
||||||
*/
|
*/
|
||||||
@UnstableApi public static final int MEDIA_TYPE_TV_CHANNEL = 17;
|
public static final int MEDIA_TYPE_TV_CHANNEL = 17;
|
||||||
/**
|
/**
|
||||||
* {@link MediaType} for a group of items that are part of a TV series. Items in this group are
|
* {@link MediaType} for a group of items that are part of a TV series. Items in this group are
|
||||||
* typically of type {@link #MEDIA_TYPE_TV_SHOW} or {@link #MEDIA_TYPE_TV_SEASON}.
|
* typically of type {@link #MEDIA_TYPE_TV_SHOW} or {@link #MEDIA_TYPE_TV_SEASON}.
|
||||||
*/
|
*/
|
||||||
@UnstableApi public static final int MEDIA_TYPE_TV_SERIES = 18;
|
public static final int MEDIA_TYPE_TV_SERIES = 18;
|
||||||
/**
|
/**
|
||||||
* {@link MediaType} for a group of items that are part of a TV series. Items in this group are
|
* {@link MediaType} for a group of items that are part of a TV series. Items in this group are
|
||||||
* typically of type {@link #MEDIA_TYPE_TV_SHOW}.
|
* typically of type {@link #MEDIA_TYPE_TV_SHOW}.
|
||||||
*/
|
*/
|
||||||
@UnstableApi public static final int MEDIA_TYPE_TV_SEASON = 19;
|
public static final int MEDIA_TYPE_TV_SEASON = 19;
|
||||||
/** {@link MediaType} for a folder with mixed or undetermined content. */
|
/** {@link MediaType} for a folder with mixed or undetermined content. */
|
||||||
@UnstableApi public static final int MEDIA_TYPE_FOLDER_MIXED = 20;
|
public static final int MEDIA_TYPE_FOLDER_MIXED = 20;
|
||||||
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_ALBUM albums}. */
|
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_ALBUM albums}. */
|
||||||
@UnstableApi public static final int MEDIA_TYPE_FOLDER_ALBUMS = 21;
|
public static final int MEDIA_TYPE_FOLDER_ALBUMS = 21;
|
||||||
/** {@link MediaType} for a folder containing {@linkplain #FIELD_ARTIST artists}. */
|
/** {@link MediaType} for a folder containing {@linkplain #FIELD_ARTIST artists}. */
|
||||||
@UnstableApi public static final int MEDIA_TYPE_FOLDER_ARTISTS = 22;
|
public static final int MEDIA_TYPE_FOLDER_ARTISTS = 22;
|
||||||
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_GENRE genres}. */
|
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_GENRE genres}. */
|
||||||
@UnstableApi public static final int MEDIA_TYPE_FOLDER_GENRES = 23;
|
public static final int MEDIA_TYPE_FOLDER_GENRES = 23;
|
||||||
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_PLAYLIST playlists}. */
|
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_PLAYLIST playlists}. */
|
||||||
@UnstableApi public static final int MEDIA_TYPE_FOLDER_PLAYLISTS = 24;
|
public static final int MEDIA_TYPE_FOLDER_PLAYLISTS = 24;
|
||||||
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_YEAR years}. */
|
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_YEAR years}. */
|
||||||
@UnstableApi public static final int MEDIA_TYPE_FOLDER_YEARS = 25;
|
public static final int MEDIA_TYPE_FOLDER_YEARS = 25;
|
||||||
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_AUDIO_BOOK audio books}. */
|
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_AUDIO_BOOK audio books}. */
|
||||||
@UnstableApi public static final int MEDIA_TYPE_FOLDER_AUDIO_BOOKS = 26;
|
public static final int MEDIA_TYPE_FOLDER_AUDIO_BOOKS = 26;
|
||||||
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_PODCAST podcasts}. */
|
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_PODCAST podcasts}. */
|
||||||
@UnstableApi public static final int MEDIA_TYPE_FOLDER_PODCASTS = 27;
|
public static final int MEDIA_TYPE_FOLDER_PODCASTS = 27;
|
||||||
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_TV_CHANNEL TV channels}. */
|
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_TV_CHANNEL TV channels}. */
|
||||||
@UnstableApi public static final int MEDIA_TYPE_FOLDER_TV_CHANNELS = 28;
|
public static final int MEDIA_TYPE_FOLDER_TV_CHANNELS = 28;
|
||||||
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_TV_SERIES TV series}. */
|
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_TV_SERIES TV series}. */
|
||||||
@UnstableApi public static final int MEDIA_TYPE_FOLDER_TV_SERIES = 29;
|
public static final int MEDIA_TYPE_FOLDER_TV_SERIES = 29;
|
||||||
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_TV_SHOW TV shows}. */
|
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_TV_SHOW TV shows}. */
|
||||||
@UnstableApi public static final int MEDIA_TYPE_FOLDER_TV_SHOWS = 30;
|
public static final int MEDIA_TYPE_FOLDER_TV_SHOWS = 30;
|
||||||
/**
|
/**
|
||||||
* {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_RADIO_STATION radio
|
* {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_RADIO_STATION radio
|
||||||
* stations}.
|
* stations}.
|
||||||
*/
|
*/
|
||||||
@UnstableApi public static final int MEDIA_TYPE_FOLDER_RADIO_STATIONS = 31;
|
public static final int MEDIA_TYPE_FOLDER_RADIO_STATIONS = 31;
|
||||||
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_NEWS news}. */
|
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_NEWS news}. */
|
||||||
@UnstableApi public static final int MEDIA_TYPE_FOLDER_NEWS = 32;
|
public static final int MEDIA_TYPE_FOLDER_NEWS = 32;
|
||||||
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_VIDEO videos}. */
|
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_VIDEO videos}. */
|
||||||
@UnstableApi public static final int MEDIA_TYPE_FOLDER_VIDEOS = 33;
|
public static final int MEDIA_TYPE_FOLDER_VIDEOS = 33;
|
||||||
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_TRAILER movie trailers}. */
|
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_TRAILER movie trailers}. */
|
||||||
@UnstableApi public static final int MEDIA_TYPE_FOLDER_TRAILERS = 34;
|
public static final int MEDIA_TYPE_FOLDER_TRAILERS = 34;
|
||||||
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_MOVIE movies}. */
|
/** {@link MediaType} for a folder containing {@linkplain #MEDIA_TYPE_MOVIE movies}. */
|
||||||
@UnstableApi public static final int MEDIA_TYPE_FOLDER_MOVIES = 35;
|
public static final int MEDIA_TYPE_FOLDER_MOVIES = 35;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The folder type of the media item.
|
* The folder type of the media item.
|
||||||
@ -753,12 +758,17 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
* <p>One of {@link #FOLDER_TYPE_NONE}, {@link #FOLDER_TYPE_MIXED}, {@link #FOLDER_TYPE_TITLES},
|
* <p>One of {@link #FOLDER_TYPE_NONE}, {@link #FOLDER_TYPE_MIXED}, {@link #FOLDER_TYPE_TITLES},
|
||||||
* {@link #FOLDER_TYPE_ALBUMS}, {@link #FOLDER_TYPE_ARTISTS}, {@link #FOLDER_TYPE_GENRES}, {@link
|
* {@link #FOLDER_TYPE_ALBUMS}, {@link #FOLDER_TYPE_ARTISTS}, {@link #FOLDER_TYPE_GENRES}, {@link
|
||||||
* #FOLDER_TYPE_PLAYLISTS} or {@link #FOLDER_TYPE_YEARS}.
|
* #FOLDER_TYPE_PLAYLISTS} or {@link #FOLDER_TYPE_YEARS}.
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link #isBrowsable} to indicate if an item is a browsable folder and use
|
||||||
|
* {@link #mediaType} to indicate the type of the folder.
|
||||||
*/
|
*/
|
||||||
// @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.
|
||||||
@Documented
|
@Documented
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
||||||
|
@Deprecated
|
||||||
|
@SuppressWarnings("deprecation") // Defining deprecated constants.
|
||||||
@IntDef({
|
@IntDef({
|
||||||
FOLDER_TYPE_NONE,
|
FOLDER_TYPE_NONE,
|
||||||
FOLDER_TYPE_MIXED,
|
FOLDER_TYPE_MIXED,
|
||||||
@ -771,22 +781,60 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
})
|
})
|
||||||
public @interface FolderType {}
|
public @interface FolderType {}
|
||||||
|
|
||||||
/** Type for an item that is not a folder. */
|
/**
|
||||||
public static final int FOLDER_TYPE_NONE = -1;
|
* Type for an item that is not a folder.
|
||||||
/** Type for a folder containing media of mixed types. */
|
*
|
||||||
public static final int FOLDER_TYPE_MIXED = 0;
|
* @deprecated Use {@link #isBrowsable} set to false instead.
|
||||||
/** Type for a folder containing only playable media. */
|
*/
|
||||||
public static final int FOLDER_TYPE_TITLES = 1;
|
@Deprecated public static final int FOLDER_TYPE_NONE = -1;
|
||||||
/** Type for a folder containing media categorized by album. */
|
/**
|
||||||
public static final int FOLDER_TYPE_ALBUMS = 2;
|
* Type for a folder containing media of mixed types.
|
||||||
/** Type for a folder containing media categorized by artist. */
|
*
|
||||||
public static final int FOLDER_TYPE_ARTISTS = 3;
|
* @deprecated Use {@link #isBrowsable} set to true and {@link #mediaType} set to {@link
|
||||||
/** Type for a folder containing media categorized by genre. */
|
* #MEDIA_TYPE_FOLDER_MIXED} instead.
|
||||||
public static final int FOLDER_TYPE_GENRES = 4;
|
*/
|
||||||
/** Type for a folder containing a playlist. */
|
@Deprecated public static final int FOLDER_TYPE_MIXED = 0;
|
||||||
public static final int FOLDER_TYPE_PLAYLISTS = 5;
|
/**
|
||||||
/** Type for a folder containing media categorized by year. */
|
* Type for a folder containing only playable media.
|
||||||
public static final int FOLDER_TYPE_YEARS = 6;
|
*
|
||||||
|
* @deprecated Use {@link #isBrowsable} set to true instead.
|
||||||
|
*/
|
||||||
|
@Deprecated public static final int FOLDER_TYPE_TITLES = 1;
|
||||||
|
/**
|
||||||
|
* Type for a folder containing media categorized by album.
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link #isBrowsable} set to true and {@link #mediaType} set to {@link
|
||||||
|
* #MEDIA_TYPE_FOLDER_ALBUMS} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated public static final int FOLDER_TYPE_ALBUMS = 2;
|
||||||
|
/**
|
||||||
|
* Type for a folder containing media categorized by artist.
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link #isBrowsable} set to true and {@link #mediaType} set to {@link
|
||||||
|
* #MEDIA_TYPE_FOLDER_ARTISTS} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated public static final int FOLDER_TYPE_ARTISTS = 3;
|
||||||
|
/**
|
||||||
|
* Type for a folder containing media categorized by genre.
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link #isBrowsable} set to true and {@link #mediaType} set to {@link
|
||||||
|
* #MEDIA_TYPE_FOLDER_GENRES} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated public static final int FOLDER_TYPE_GENRES = 4;
|
||||||
|
/**
|
||||||
|
* Type for a folder containing a playlist.
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link #isBrowsable} set to true and {@link #mediaType} set to {@link
|
||||||
|
* #MEDIA_TYPE_FOLDER_PLAYLISTS} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated public static final int FOLDER_TYPE_PLAYLISTS = 5;
|
||||||
|
/**
|
||||||
|
* Type for a folder containing media categorized by year.
|
||||||
|
*
|
||||||
|
* @deprecated Use {@link #isBrowsable} set to true and {@link #mediaType} set to {@link
|
||||||
|
* #MEDIA_TYPE_FOLDER_YEARS} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated public static final int FOLDER_TYPE_YEARS = 6;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The picture type of the artwork.
|
* The picture type of the artwork.
|
||||||
@ -895,12 +943,15 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
/**
|
/**
|
||||||
* Optional {@link FolderType}.
|
* Optional {@link FolderType}.
|
||||||
*
|
*
|
||||||
* <p>This field will be deprecated. Use {@link #isBrowsable} to indicate if an item is a
|
* @deprecated Use {@link #isBrowsable} to indicate if an item is a browsable folder and use
|
||||||
* browsable folder and use {@link #mediaType} to indicate the type of the folder.
|
* {@link #mediaType} to indicate the type of the folder.
|
||||||
*/
|
*/
|
||||||
@Nullable public final @FolderType Integer folderType;
|
@SuppressWarnings("deprecation") // Defining field of deprecated type.
|
||||||
|
@Deprecated
|
||||||
|
@Nullable
|
||||||
|
public final @FolderType Integer folderType;
|
||||||
/** Optional boolean to indicate that the media is a browsable folder. */
|
/** Optional boolean to indicate that the media is a browsable folder. */
|
||||||
@UnstableApi @Nullable public final Boolean isBrowsable;
|
@Nullable public final Boolean isBrowsable;
|
||||||
/** Optional boolean to indicate that the media is playable. */
|
/** Optional boolean to indicate that the media is playable. */
|
||||||
@Nullable public final Boolean isPlayable;
|
@Nullable public final Boolean isPlayable;
|
||||||
/**
|
/**
|
||||||
@ -953,7 +1004,7 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
/** Optional name of the station streaming the media. */
|
/** Optional name of the station streaming the media. */
|
||||||
@Nullable public final CharSequence station;
|
@Nullable public final CharSequence station;
|
||||||
/** Optional {@link MediaType}. */
|
/** Optional {@link MediaType}. */
|
||||||
@UnstableApi @Nullable public final @MediaType Integer mediaType;
|
@Nullable public final @MediaType Integer mediaType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Optional extras {@link Bundle}.
|
* Optional extras {@link Bundle}.
|
||||||
@ -963,6 +1014,7 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
*/
|
*/
|
||||||
@Nullable public final Bundle extras;
|
@Nullable public final Bundle extras;
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation") // Assigning deprecated fields.
|
||||||
private MediaMetadata(Builder builder) {
|
private MediaMetadata(Builder builder) {
|
||||||
// Handle compatibility for deprecated fields.
|
// Handle compatibility for deprecated fields.
|
||||||
@Nullable Boolean isBrowsable = builder.isBrowsable;
|
@Nullable Boolean isBrowsable = builder.isBrowsable;
|
||||||
@ -1021,6 +1073,7 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
return new Builder(/* mediaMetadata= */ this);
|
return new Builder(/* mediaMetadata= */ this);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation") // Comparing deprecated fields.
|
||||||
@Override
|
@Override
|
||||||
public boolean equals(@Nullable Object obj) {
|
public boolean equals(@Nullable Object obj) {
|
||||||
if (this == obj) {
|
if (this == obj) {
|
||||||
@ -1064,6 +1117,7 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
&& Util.areEqual(mediaType, that.mediaType);
|
&& Util.areEqual(mediaType, that.mediaType);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation") // Hashing deprecated fields.
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Objects.hashCode(
|
return Objects.hashCode(
|
||||||
@ -1138,6 +1192,7 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
private static final String FIELD_IS_BROWSABLE = Util.intToStringMaxRadix(32);
|
private static final String FIELD_IS_BROWSABLE = Util.intToStringMaxRadix(32);
|
||||||
private static final String FIELD_EXTRAS = Util.intToStringMaxRadix(1000);
|
private static final String FIELD_EXTRAS = Util.intToStringMaxRadix(1000);
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation") // Bundling deprecated fields.
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@Override
|
@Override
|
||||||
public Bundle toBundle() {
|
public Bundle toBundle() {
|
||||||
@ -1247,6 +1302,7 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
/** Object that can restore {@link MediaMetadata} from a {@link Bundle}. */
|
/** Object that can restore {@link MediaMetadata} from a {@link Bundle}. */
|
||||||
@UnstableApi public static final Creator<MediaMetadata> CREATOR = MediaMetadata::fromBundle;
|
@UnstableApi public static final Creator<MediaMetadata> CREATOR = MediaMetadata::fromBundle;
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation") // Unbundling deprecated fields.
|
||||||
private static MediaMetadata fromBundle(Bundle bundle) {
|
private static MediaMetadata fromBundle(Bundle bundle) {
|
||||||
Builder builder = new Builder();
|
Builder builder = new Builder();
|
||||||
builder
|
builder
|
||||||
@ -1329,6 +1385,7 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation") // Converting deprecated field.
|
||||||
private static @FolderType int getFolderTypeFromMediaType(@MediaType int mediaType) {
|
private static @FolderType int getFolderTypeFromMediaType(@MediaType int mediaType) {
|
||||||
switch (mediaType) {
|
switch (mediaType) {
|
||||||
case MEDIA_TYPE_ALBUM:
|
case MEDIA_TYPE_ALBUM:
|
||||||
@ -1378,6 +1435,7 @@ public final class MediaMetadata implements Bundleable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation") // Converting deprecated field.
|
||||||
private static @MediaType int getMediaTypeFromFolderType(@FolderType int folderType) {
|
private static @MediaType int getMediaTypeFromFolderType(@FolderType int folderType) {
|
||||||
switch (folderType) {
|
switch (folderType) {
|
||||||
case FOLDER_TYPE_ALBUMS:
|
case FOLDER_TYPE_ALBUMS:
|
||||||
|
@ -60,6 +60,7 @@ public final class MimeTypes {
|
|||||||
public static final String VIDEO_MJPEG = BASE_TYPE_VIDEO + "/mjpeg";
|
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_MP42 = BASE_TYPE_VIDEO + "/mp42";
|
||||||
public static final String VIDEO_MP43 = BASE_TYPE_VIDEO + "/mp43";
|
public static final String VIDEO_MP43 = BASE_TYPE_VIDEO + "/mp43";
|
||||||
|
@UnstableApi public static final String VIDEO_RAW = BASE_TYPE_VIDEO + "/raw";
|
||||||
@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
|
||||||
@ -130,7 +131,12 @@ public final class MimeTypes {
|
|||||||
public static final String APPLICATION_TX3G = BASE_TYPE_APPLICATION + "/x-quicktime-tx3g";
|
public static final String APPLICATION_TX3G = BASE_TYPE_APPLICATION + "/x-quicktime-tx3g";
|
||||||
public static final String APPLICATION_MP4VTT = BASE_TYPE_APPLICATION + "/x-mp4-vtt";
|
public static final String APPLICATION_MP4VTT = BASE_TYPE_APPLICATION + "/x-mp4-vtt";
|
||||||
public static final String APPLICATION_MP4CEA608 = BASE_TYPE_APPLICATION + "/x-mp4-cea-608";
|
public static final String APPLICATION_MP4CEA608 = BASE_TYPE_APPLICATION + "/x-mp4-cea-608";
|
||||||
public static final String APPLICATION_RAWCC = BASE_TYPE_APPLICATION + "/x-rawcc";
|
/**
|
||||||
|
* @deprecated RawCC is a Google-internal subtitle format that isn't supported by this version of
|
||||||
|
* Media3. There is no replacement for this value.
|
||||||
|
*/
|
||||||
|
@Deprecated public static final String APPLICATION_RAWCC = BASE_TYPE_APPLICATION + "/x-rawcc";
|
||||||
|
|
||||||
public static final String APPLICATION_VOBSUB = BASE_TYPE_APPLICATION + "/vobsub";
|
public static final String APPLICATION_VOBSUB = BASE_TYPE_APPLICATION + "/vobsub";
|
||||||
public static final String APPLICATION_PGS = BASE_TYPE_APPLICATION + "/pgs";
|
public static final String APPLICATION_PGS = BASE_TYPE_APPLICATION + "/pgs";
|
||||||
@UnstableApi public static final String APPLICATION_SCTE35 = BASE_TYPE_APPLICATION + "/x-scte35";
|
@UnstableApi public static final String APPLICATION_SCTE35 = BASE_TYPE_APPLICATION + "/x-scte35";
|
||||||
@ -147,7 +153,11 @@ public final class MimeTypes {
|
|||||||
|
|
||||||
// image/ MIME types
|
// image/ MIME types
|
||||||
|
|
||||||
|
public static final String IMAGE_PNG = BASE_TYPE_IMAGE + "/png";
|
||||||
|
public static final String IMAGE_WEBP = BASE_TYPE_IMAGE + "/webp";
|
||||||
public static final String IMAGE_JPEG = BASE_TYPE_IMAGE + "/jpeg";
|
public static final String IMAGE_JPEG = BASE_TYPE_IMAGE + "/jpeg";
|
||||||
|
public static final String IMAGE_HEIC = BASE_TYPE_IMAGE + "/heic";
|
||||||
|
public static final String IMAGE_HEIF = BASE_TYPE_IMAGE + "/heif";
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A non-standard codec string for E-AC3-JOC. Use of this constant allows for disambiguation
|
* A non-standard codec string for E-AC3-JOC. Use of this constant allows for disambiguation
|
||||||
@ -585,6 +595,10 @@ public final class MimeTypes {
|
|||||||
return C.ENCODING_DTS;
|
return C.ENCODING_DTS;
|
||||||
case MimeTypes.AUDIO_DTS_HD:
|
case MimeTypes.AUDIO_DTS_HD:
|
||||||
return C.ENCODING_DTS_HD;
|
return C.ENCODING_DTS_HD;
|
||||||
|
case MimeTypes.AUDIO_DTS_EXPRESS:
|
||||||
|
return C.ENCODING_DTS_HD;
|
||||||
|
case MimeTypes.AUDIO_DTS_X:
|
||||||
|
return C.ENCODING_DTS_UHD_P2;
|
||||||
case MimeTypes.AUDIO_TRUEHD:
|
case MimeTypes.AUDIO_TRUEHD:
|
||||||
return C.ENCODING_DOLBY_TRUEHD;
|
return C.ENCODING_DOLBY_TRUEHD;
|
||||||
case MimeTypes.AUDIO_OPUS:
|
case MimeTypes.AUDIO_OPUS:
|
||||||
|
@ -0,0 +1,26 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package androidx.media3.common;
|
||||||
|
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
|
||||||
|
/** A listener for processing input frames. */
|
||||||
|
@UnstableApi
|
||||||
|
public interface OnInputFrameProcessedListener {
|
||||||
|
|
||||||
|
/** Called when the given input frame has been processed. */
|
||||||
|
void onInputFrameProcessed(int textureId) throws VideoFrameProcessingException;
|
||||||
|
}
|
@ -107,4 +107,15 @@ public class ParserException extends IOException {
|
|||||||
this.contentIsMalformed = contentIsMalformed;
|
this.contentIsMalformed = contentIsMalformed;
|
||||||
this.dataType = dataType;
|
this.dataType = dataType;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public String getMessage() {
|
||||||
|
return super.getMessage()
|
||||||
|
+ "{contentIsMalformed="
|
||||||
|
+ contentIsMalformed
|
||||||
|
+ ", dataType="
|
||||||
|
+ dataType
|
||||||
|
+ "}";
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -229,6 +229,13 @@ public class PlaybackException extends Exception implements Bundleable {
|
|||||||
/** Caused by an expired DRM license being loaded into an open DRM session. */
|
/** Caused by an expired DRM license being loaded into an open DRM session. */
|
||||||
public static final int ERROR_CODE_DRM_LICENSE_EXPIRED = 6008;
|
public static final int ERROR_CODE_DRM_LICENSE_EXPIRED = 6008;
|
||||||
|
|
||||||
|
// Frame processing errors (7xxx).
|
||||||
|
|
||||||
|
/** Caused by a failure when initializing a {@link VideoFrameProcessor}. */
|
||||||
|
@UnstableApi public static final int ERROR_CODE_VIDEO_FRAME_PROCESSOR_INIT_FAILED = 7000;
|
||||||
|
/** Caused by a failure when processing a video frame. */
|
||||||
|
@UnstableApi public static final int ERROR_CODE_VIDEO_FRAME_PROCESSING_FAILED = 7001;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Player implementations that want to surface custom errors can use error codes greater than this
|
* Player implementations that want to surface custom errors can use error codes greater than this
|
||||||
* value, so as to avoid collision with other error codes defined in this class.
|
* value, so as to avoid collision with other error codes defined in this class.
|
||||||
@ -306,6 +313,10 @@ public class PlaybackException extends Exception implements Bundleable {
|
|||||||
return "ERROR_CODE_DRM_DEVICE_REVOKED";
|
return "ERROR_CODE_DRM_DEVICE_REVOKED";
|
||||||
case ERROR_CODE_DRM_LICENSE_EXPIRED:
|
case ERROR_CODE_DRM_LICENSE_EXPIRED:
|
||||||
return "ERROR_CODE_DRM_LICENSE_EXPIRED";
|
return "ERROR_CODE_DRM_LICENSE_EXPIRED";
|
||||||
|
case ERROR_CODE_VIDEO_FRAME_PROCESSOR_INIT_FAILED:
|
||||||
|
return "ERROR_CODE_VIDEO_FRAME_PROCESSOR_INIT_FAILED";
|
||||||
|
case ERROR_CODE_VIDEO_FRAME_PROCESSING_FAILED:
|
||||||
|
return "ERROR_CODE_VIDEO_FRAME_PROCESSING_FAILED";
|
||||||
default:
|
default:
|
||||||
if (errorCode >= CUSTOM_ERROR_CODE_BASE) {
|
if (errorCode >= CUSTOM_ERROR_CODE_BASE) {
|
||||||
return "custom error code";
|
return "custom error code";
|
||||||
|
@ -43,7 +43,7 @@ public final class PlaybackParameters implements Bundleable {
|
|||||||
*
|
*
|
||||||
* @param speed The factor by which playback will be sped up. Must be greater than zero.
|
* @param speed The factor by which playback will be sped up. Must be greater than zero.
|
||||||
*/
|
*/
|
||||||
public PlaybackParameters(float speed) {
|
public PlaybackParameters(@FloatRange(from = 0, fromInclusive = false) float speed) {
|
||||||
this(speed, /* pitch= */ 1f);
|
this(speed, /* pitch= */ 1f);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -46,26 +46,125 @@ import java.util.ArrayList;
|
|||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A media player interface defining traditional high-level functionality, such as the ability to
|
* A media player interface defining high-level functionality, such as the ability to play, pause,
|
||||||
* play, pause, seek and query properties of the currently playing media.
|
* seek and query properties of the currently playing media.
|
||||||
*
|
*
|
||||||
* <p>All methods must be called from a single {@linkplain #getApplicationLooper() application
|
* <h2>Player features and usage</h2>
|
||||||
* thread} unless indicated otherwise. Callbacks in registered listeners are called on the same
|
|
||||||
* thread.
|
|
||||||
*
|
|
||||||
* <p>This interface includes some convenience methods that can be implemented by calling other
|
|
||||||
* methods in the interface. {@link BasePlayer} implements these convenience methods so inheriting
|
|
||||||
* {@link BasePlayer} is recommended when implementing the interface so that only the minimal set of
|
|
||||||
* required methods can be implemented.
|
|
||||||
*
|
*
|
||||||
* <p>Some important properties of media players that implement this interface are:
|
* <p>Some important properties of media players that implement this interface are:
|
||||||
*
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>They can provide a {@link Timeline} representing the structure of the media being played,
|
* <li>All methods must be called from a single {@linkplain #getApplicationLooper() application
|
||||||
* which can be obtained by calling {@link #getCurrentTimeline()}.
|
* thread} unless indicated otherwise. Callbacks in registered listeners are called on the
|
||||||
* <li>They can provide a {@link Tracks} defining the currently available tracks and which are
|
* same thread.
|
||||||
* selected to be rendered, which can be obtained by calling {@link #getCurrentTracks()}.
|
* <li>The available functionality can be limited. Player instances provide a set of {@link
|
||||||
|
* #getAvailableCommands() availabe commands} to signal feature support and users of the
|
||||||
|
* interface must only call methods if the corresponding {@link Command} is available.
|
||||||
|
* <li>Users can register {@link Player.Listener} callbacks that get informed about state changes.
|
||||||
|
* <li>Player instances need to update the visible state immediately after each method call, even
|
||||||
|
* if the actual changes are handled on background threads or even other devices. This
|
||||||
|
* simplifies the usage for callers of methods as no asynchronous handling needs to be
|
||||||
|
* considered.
|
||||||
|
* <li>Player instances can provide playlist operations, like 'set', 'add', 'remove', 'move' or
|
||||||
|
* 'replace' of {@link MediaItem} instances. The player can also support {@linkplain
|
||||||
|
* RepeatMode repeat modes} and shuffling within this playlist. The player provides a {@link
|
||||||
|
* Timeline} representing the structure of the playlist and all its items, which can be
|
||||||
|
* obtained by calling {@link #getCurrentTimeline()}
|
||||||
|
* <li>Player instances can provide seeking within the currently playing item and to other items,
|
||||||
|
* using the various {@code seek...} methods.
|
||||||
|
* <li>Player instances can provide {@link Tracks} defining the currently available and selected
|
||||||
|
* tracks, which can be obtained by calling {@link #getCurrentTracks()}. Users can also modify
|
||||||
|
* track selection behavior by setting {@link TrackSelectionParameters} with {@link
|
||||||
|
* #setTrackSelectionParameters}.
|
||||||
|
* <li>Player instances can provide {@link MediaMetadata} about the currently playing item, which
|
||||||
|
* can be obtained by calling {@link #getMediaMetadata()}.
|
||||||
|
* <li>Player instances can provide information about ads in its media structure, for example via
|
||||||
|
* {@link #isPlayingAd()}.
|
||||||
|
* <li>Player instances can accept different types of video outputs, like {@link
|
||||||
|
* #setVideoSurfaceView SurfaceView} or {@link #setVideoTextureView TextureView} for video
|
||||||
|
* rendering.
|
||||||
|
* <li>Player instances can handle {@linkplain #setPlaybackSpeed playback speed}, {@linkplain
|
||||||
|
* #getAudioAttributes audio attributes}, and {@linkplain #setVolume audio volume}.
|
||||||
|
* <li>Player instances can provide information about the {@linkplain #getDeviceInfo playback
|
||||||
|
* device}, which may be remote, and allow to change the device's volume.
|
||||||
* </ul>
|
* </ul>
|
||||||
|
*
|
||||||
|
* <h2>API stability guarantees</h2>
|
||||||
|
*
|
||||||
|
* <p>The majority of the Player interface and its related classes are part of the stable API that
|
||||||
|
* guarantees backwards-compatibility for users of the API. Only more advances use cases may need to
|
||||||
|
* rely on {@link UnstableApi} classes and methods that are subject to incompatible changes or even
|
||||||
|
* removal in a future release. Implementors of the Player interface are not covered by these API
|
||||||
|
* stability guarantees.
|
||||||
|
*
|
||||||
|
* <h2>Player state</h2>
|
||||||
|
*
|
||||||
|
* <p>Users can listen to state changes by adding a {@link Player.Listener} with {@link
|
||||||
|
* #addListener}.
|
||||||
|
*
|
||||||
|
* <p>The main elements of the overall player state are:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>Playlist
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link MediaItem} instances can be added with methods like {@link #setMediaItem} to
|
||||||
|
* define what the player will be playing.
|
||||||
|
* <li>The current playlist can be obtained via {@link #getCurrentTimeline} and convenience
|
||||||
|
* methods like {@link #getMediaItemCount} or {@link #getCurrentMediaItem}.
|
||||||
|
* <li>With an empty playlist, the player can only be in {@link #STATE_IDLE} or {@link
|
||||||
|
* #STATE_ENDED}.
|
||||||
|
* </ul>
|
||||||
|
* <li>Playback state
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link #STATE_IDLE}: This is the initial state, the state when the player is
|
||||||
|
* {@linkplain #stop stopped}, and when playback {@linkplain #getPlayerError failed}.
|
||||||
|
* The player will hold only limited resources in this state. {@link #prepare} must be
|
||||||
|
* called to transition away from this state.
|
||||||
|
* <li>{@link #STATE_BUFFERING}: The player is not able to immediately play from its current
|
||||||
|
* position. This mostly happens because more data needs to be loaded.
|
||||||
|
* <li>{@link #STATE_READY}: The player is able to immediately play from its current
|
||||||
|
* position.
|
||||||
|
* <li>{@link #STATE_ENDED}: The player finished playing all media, or there is no media to
|
||||||
|
* play.
|
||||||
|
* </ul>
|
||||||
|
* <li>Play/Pause, playback suppression and isPlaying
|
||||||
|
* <ul>
|
||||||
|
* <li>{@linkplain #getPlayWhenReady() playWhenReady}: Indicates the user intention to play.
|
||||||
|
* It can be set with {@link #play} or {@link #pause}.
|
||||||
|
* <li>{@linkplain #getPlaybackSuppressionReason() playback suppression}: Defines a reason
|
||||||
|
* for which playback will be suppressed even if {@linkplain #getPlayWhenReady()
|
||||||
|
* playWhenReady} is {@code true}.
|
||||||
|
* <li>{@link #isPlaying()}: Whether the player is playing (that is, its position is
|
||||||
|
* advancing and media is being presented). This will only be {@code true} if playback
|
||||||
|
* state is {@link #STATE_READY}, {@linkplain #getPlayWhenReady() playWhenReady} is
|
||||||
|
* {@code true}, and playback is not suppressed.
|
||||||
|
* </ul>
|
||||||
|
* <li>Playback position
|
||||||
|
* <ul>
|
||||||
|
* <li>{@linkplain #getCurrentMediaItemIndex() media item index}: The index in the playlist.
|
||||||
|
* <li>{@linkplain #isPlayingAd() ad insertion}: Whether an inserted ad is playing and which
|
||||||
|
* {@linkplain #getCurrentAdGroupIndex() ad group index} and {@linkplain
|
||||||
|
* #getCurrentAdIndexInAdGroup() ad index in the group} it belongs to
|
||||||
|
* <li>{@linkplain #getCurrentPosition() current position}: The current position of the
|
||||||
|
* playback. This is the same as the {@linkplain #getContentPosition() content position}
|
||||||
|
* unless an ad is playing, where this indicates the position in the inserted ad.
|
||||||
|
* </ul>
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>Note that there are no callbacks for normal playback progression, only for {@linkplain
|
||||||
|
* Listener#onMediaItemTransition transitions between media items} and other {@linkplain
|
||||||
|
* Listener#onPositionDiscontinuity position discontinuities}. Code that needs to monitor playback
|
||||||
|
* progress (for example, an UI progress bar) should query the current position in appropriate
|
||||||
|
* intervals.
|
||||||
|
*
|
||||||
|
* <h2>Implementing the Player interface</h2>
|
||||||
|
*
|
||||||
|
* <p>Implementing the Player interface is complex, as the interface includes many convenience
|
||||||
|
* methods that need to provide a consistent state and behavior, requires correct handling of
|
||||||
|
* listeners and available commands, and expects immediate state changes even if methods are
|
||||||
|
* internally handled asynchronously. For this reason, implementations are advised to inherit {@link
|
||||||
|
* SimpleBasePlayer} that handles all of these complexities and provides a simpler integration point
|
||||||
|
* for implementors of the interface.
|
||||||
*/
|
*/
|
||||||
public interface Player {
|
public interface Player {
|
||||||
|
|
||||||
@ -371,8 +470,8 @@ public interface Player {
|
|||||||
COMMAND_SET_REPEAT_MODE,
|
COMMAND_SET_REPEAT_MODE,
|
||||||
COMMAND_GET_CURRENT_MEDIA_ITEM,
|
COMMAND_GET_CURRENT_MEDIA_ITEM,
|
||||||
COMMAND_GET_TIMELINE,
|
COMMAND_GET_TIMELINE,
|
||||||
COMMAND_GET_MEDIA_ITEMS_METADATA,
|
COMMAND_GET_METADATA,
|
||||||
COMMAND_SET_MEDIA_ITEMS_METADATA,
|
COMMAND_SET_PLAYLIST_METADATA,
|
||||||
COMMAND_SET_MEDIA_ITEM,
|
COMMAND_SET_MEDIA_ITEM,
|
||||||
COMMAND_CHANGE_MEDIA_ITEMS,
|
COMMAND_CHANGE_MEDIA_ITEMS,
|
||||||
COMMAND_GET_AUDIO_ATTRIBUTES,
|
COMMAND_GET_AUDIO_ATTRIBUTES,
|
||||||
@ -380,11 +479,14 @@ public interface Player {
|
|||||||
COMMAND_GET_DEVICE_VOLUME,
|
COMMAND_GET_DEVICE_VOLUME,
|
||||||
COMMAND_SET_VOLUME,
|
COMMAND_SET_VOLUME,
|
||||||
COMMAND_SET_DEVICE_VOLUME,
|
COMMAND_SET_DEVICE_VOLUME,
|
||||||
|
COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS,
|
||||||
COMMAND_ADJUST_DEVICE_VOLUME,
|
COMMAND_ADJUST_DEVICE_VOLUME,
|
||||||
|
COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS,
|
||||||
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_TRACKS,
|
COMMAND_GET_TRACKS,
|
||||||
|
COMMAND_RELEASE
|
||||||
};
|
};
|
||||||
|
|
||||||
private final FlagSet.Builder flagsBuilder;
|
private final FlagSet.Builder flagsBuilder;
|
||||||
@ -914,15 +1016,6 @@ public interface Player {
|
|||||||
*/
|
*/
|
||||||
default void onMaxSeekToPreviousPositionChanged(long maxSeekToPreviousPositionMs) {}
|
default void onMaxSeekToPreviousPositionChanged(long maxSeekToPreviousPositionMs) {}
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Seeks are processed without delay. Listen to {@link
|
|
||||||
* #onPositionDiscontinuity(PositionInfo, PositionInfo, int)} with reason {@link
|
|
||||||
* #DISCONTINUITY_REASON_SEEK} instead.
|
|
||||||
*/
|
|
||||||
@Deprecated
|
|
||||||
@UnstableApi
|
|
||||||
default void onSeekProcessed() {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the audio session ID changes.
|
* Called when the audio session ID changes.
|
||||||
*
|
*
|
||||||
@ -986,7 +1079,7 @@ public interface Player {
|
|||||||
default void onDeviceVolumeChanged(int volume, boolean muted) {}
|
default void onDeviceVolumeChanged(int volume, boolean muted) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called each time there's a change in the size of the video being rendered.
|
* Called each time when {@link Player#getVideoSize()} changes.
|
||||||
*
|
*
|
||||||
* <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.
|
||||||
@ -1091,8 +1184,9 @@ public interface Player {
|
|||||||
* #PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST}, {@link
|
* #PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST}, {@link
|
||||||
* #PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS}, {@link
|
* #PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS}, {@link
|
||||||
* #PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY}, {@link
|
* #PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY}, {@link
|
||||||
* #PLAY_WHEN_READY_CHANGE_REASON_REMOTE} or {@link
|
* #PLAY_WHEN_READY_CHANGE_REASON_REMOTE}, {@link
|
||||||
* #PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM}.
|
* #PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM} or {@link
|
||||||
|
* #PLAY_WHEN_READY_CHANGE_REASON_SUPPRESSED_TOO_LONG}.
|
||||||
*/
|
*/
|
||||||
// @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.
|
||||||
@ -1104,7 +1198,8 @@ public interface Player {
|
|||||||
PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS,
|
PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS,
|
||||||
PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY,
|
PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY,
|
||||||
PLAY_WHEN_READY_CHANGE_REASON_REMOTE,
|
PLAY_WHEN_READY_CHANGE_REASON_REMOTE,
|
||||||
PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM
|
PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM,
|
||||||
|
PLAY_WHEN_READY_CHANGE_REASON_SUPPRESSED_TOO_LONG
|
||||||
})
|
})
|
||||||
@interface PlayWhenReadyChangeReason {}
|
@interface PlayWhenReadyChangeReason {}
|
||||||
/** Playback has been started or paused by a call to {@link #setPlayWhenReady(boolean)}. */
|
/** Playback has been started or paused by a call to {@link #setPlayWhenReady(boolean)}. */
|
||||||
@ -1117,11 +1212,17 @@ public interface Player {
|
|||||||
int PLAY_WHEN_READY_CHANGE_REASON_REMOTE = 4;
|
int PLAY_WHEN_READY_CHANGE_REASON_REMOTE = 4;
|
||||||
/** Playback has been paused at the end of a media item. */
|
/** Playback has been paused at the end of a media item. */
|
||||||
int PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM = 5;
|
int PLAY_WHEN_READY_CHANGE_REASON_END_OF_MEDIA_ITEM = 5;
|
||||||
|
/**
|
||||||
|
* Playback has been paused because playback has been {@linkplain #getPlaybackSuppressionReason()
|
||||||
|
* suppressed} too long.
|
||||||
|
*/
|
||||||
|
int PLAY_WHEN_READY_CHANGE_REASON_SUPPRESSED_TOO_LONG = 6;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Reason why playback is suppressed even though {@link #getPlayWhenReady()} is {@code true}. One
|
* Reason why playback is suppressed even though {@link #getPlayWhenReady()} is {@code true}. One
|
||||||
* of {@link #PLAYBACK_SUPPRESSION_REASON_NONE} or {@link
|
* of {@link #PLAYBACK_SUPPRESSION_REASON_NONE}, {@link
|
||||||
* #PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS}.
|
* #PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS} or {@link
|
||||||
|
* #PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_ROUTE}.
|
||||||
*/
|
*/
|
||||||
// @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.
|
||||||
@ -1130,13 +1231,19 @@ public interface Player {
|
|||||||
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
||||||
@IntDef({
|
@IntDef({
|
||||||
PLAYBACK_SUPPRESSION_REASON_NONE,
|
PLAYBACK_SUPPRESSION_REASON_NONE,
|
||||||
PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS
|
PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS,
|
||||||
|
PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_ROUTE
|
||||||
})
|
})
|
||||||
@interface PlaybackSuppressionReason {}
|
@interface PlaybackSuppressionReason {}
|
||||||
/** Playback is not suppressed. */
|
/** Playback is not suppressed. */
|
||||||
int PLAYBACK_SUPPRESSION_REASON_NONE = 0;
|
int PLAYBACK_SUPPRESSION_REASON_NONE = 0;
|
||||||
/** Playback is suppressed due to transient audio focus loss. */
|
/** Playback is suppressed due to transient audio focus loss. */
|
||||||
int PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS = 1;
|
int PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS = 1;
|
||||||
|
/**
|
||||||
|
* Playback is suppressed due to no suitable audio route, such as an attempt to use an internal
|
||||||
|
* speaker instead of bluetooth headphones on Wear OS.
|
||||||
|
*/
|
||||||
|
int PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_ROUTE = 2;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Repeat modes for playback. One of {@link #REPEAT_MODE_OFF}, {@link #REPEAT_MODE_ONE} or {@link
|
* Repeat modes for playback. One of {@link #REPEAT_MODE_OFF}, {@link #REPEAT_MODE_ONE} or {@link
|
||||||
@ -1413,8 +1520,8 @@ public interface Player {
|
|||||||
* <li>{@link #COMMAND_SET_REPEAT_MODE}
|
* <li>{@link #COMMAND_SET_REPEAT_MODE}
|
||||||
* <li>{@link #COMMAND_GET_CURRENT_MEDIA_ITEM}
|
* <li>{@link #COMMAND_GET_CURRENT_MEDIA_ITEM}
|
||||||
* <li>{@link #COMMAND_GET_TIMELINE}
|
* <li>{@link #COMMAND_GET_TIMELINE}
|
||||||
* <li>{@link #COMMAND_GET_MEDIA_ITEMS_METADATA}
|
* <li>{@link #COMMAND_GET_METADATA}
|
||||||
* <li>{@link #COMMAND_SET_MEDIA_ITEMS_METADATA}
|
* <li>{@link #COMMAND_SET_PLAYLIST_METADATA}
|
||||||
* <li>{@link #COMMAND_SET_MEDIA_ITEM}
|
* <li>{@link #COMMAND_SET_MEDIA_ITEM}
|
||||||
* <li>{@link #COMMAND_CHANGE_MEDIA_ITEMS}
|
* <li>{@link #COMMAND_CHANGE_MEDIA_ITEMS}
|
||||||
* <li>{@link #COMMAND_GET_AUDIO_ATTRIBUTES}
|
* <li>{@link #COMMAND_GET_AUDIO_ATTRIBUTES}
|
||||||
@ -1422,15 +1529,19 @@ public interface Player {
|
|||||||
* <li>{@link #COMMAND_GET_DEVICE_VOLUME}
|
* <li>{@link #COMMAND_GET_DEVICE_VOLUME}
|
||||||
* <li>{@link #COMMAND_SET_VOLUME}
|
* <li>{@link #COMMAND_SET_VOLUME}
|
||||||
* <li>{@link #COMMAND_SET_DEVICE_VOLUME}
|
* <li>{@link #COMMAND_SET_DEVICE_VOLUME}
|
||||||
|
* <li>{@link #COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS}
|
||||||
* <li>{@link #COMMAND_ADJUST_DEVICE_VOLUME}
|
* <li>{@link #COMMAND_ADJUST_DEVICE_VOLUME}
|
||||||
|
* <li>{@link #COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS}
|
||||||
* <li>{@link #COMMAND_SET_VIDEO_SURFACE}
|
* <li>{@link #COMMAND_SET_VIDEO_SURFACE}
|
||||||
* <li>{@link #COMMAND_GET_TEXT}
|
* <li>{@link #COMMAND_GET_TEXT}
|
||||||
* <li>{@link #COMMAND_SET_TRACK_SELECTION_PARAMETERS}
|
* <li>{@link #COMMAND_SET_TRACK_SELECTION_PARAMETERS}
|
||||||
* <li>{@link #COMMAND_GET_TRACKS}
|
* <li>{@link #COMMAND_GET_TRACKS}
|
||||||
|
* <li>{@link #COMMAND_RELEASE}
|
||||||
* </ul>
|
* </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.
|
||||||
|
@SuppressWarnings("deprecation") // Listing deprecated constants.
|
||||||
@Documented
|
@Documented
|
||||||
@Retention(RetentionPolicy.SOURCE)
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
|
||||||
@ -1454,7 +1565,9 @@ public interface Player {
|
|||||||
COMMAND_GET_CURRENT_MEDIA_ITEM,
|
COMMAND_GET_CURRENT_MEDIA_ITEM,
|
||||||
COMMAND_GET_TIMELINE,
|
COMMAND_GET_TIMELINE,
|
||||||
COMMAND_GET_MEDIA_ITEMS_METADATA,
|
COMMAND_GET_MEDIA_ITEMS_METADATA,
|
||||||
|
COMMAND_GET_METADATA,
|
||||||
COMMAND_SET_MEDIA_ITEMS_METADATA,
|
COMMAND_SET_MEDIA_ITEMS_METADATA,
|
||||||
|
COMMAND_SET_PLAYLIST_METADATA,
|
||||||
COMMAND_SET_MEDIA_ITEM,
|
COMMAND_SET_MEDIA_ITEM,
|
||||||
COMMAND_CHANGE_MEDIA_ITEMS,
|
COMMAND_CHANGE_MEDIA_ITEMS,
|
||||||
COMMAND_GET_AUDIO_ATTRIBUTES,
|
COMMAND_GET_AUDIO_ATTRIBUTES,
|
||||||
@ -1462,11 +1575,14 @@ public interface Player {
|
|||||||
COMMAND_GET_DEVICE_VOLUME,
|
COMMAND_GET_DEVICE_VOLUME,
|
||||||
COMMAND_SET_VOLUME,
|
COMMAND_SET_VOLUME,
|
||||||
COMMAND_SET_DEVICE_VOLUME,
|
COMMAND_SET_DEVICE_VOLUME,
|
||||||
|
COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS,
|
||||||
COMMAND_ADJUST_DEVICE_VOLUME,
|
COMMAND_ADJUST_DEVICE_VOLUME,
|
||||||
|
COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS,
|
||||||
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_TRACKS,
|
COMMAND_GET_TRACKS,
|
||||||
|
COMMAND_RELEASE,
|
||||||
})
|
})
|
||||||
@interface Command {}
|
@interface Command {}
|
||||||
/**
|
/**
|
||||||
@ -1665,6 +1781,11 @@ public interface Player {
|
|||||||
*/
|
*/
|
||||||
int COMMAND_GET_TIMELINE = 17;
|
int COMMAND_GET_TIMELINE = 17;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #COMMAND_GET_METADATA} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated int COMMAND_GET_MEDIA_ITEMS_METADATA = 18;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Command to get metadata related to the playlist and current {@link MediaItem}.
|
* Command to get metadata related to the playlist and current {@link MediaItem}.
|
||||||
*
|
*
|
||||||
@ -1676,8 +1797,12 @@ public interface Player {
|
|||||||
* <li>{@link #getPlaylistMetadata()}
|
* <li>{@link #getPlaylistMetadata()}
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
// TODO(b/263132691): Rename this to COMMAND_GET_METADATA
|
int COMMAND_GET_METADATA = 18;
|
||||||
int COMMAND_GET_MEDIA_ITEMS_METADATA = 18;
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #COMMAND_SET_PLAYLIST_METADATA} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated int COMMAND_SET_MEDIA_ITEMS_METADATA = 19;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Command to set the playlist metadata.
|
* Command to set the playlist metadata.
|
||||||
@ -1685,8 +1810,7 @@ public interface Player {
|
|||||||
* <p>The {@link #setPlaylistMetadata(MediaMetadata)} method must only be called if this command
|
* <p>The {@link #setPlaylistMetadata(MediaMetadata)} method must only be called if this command
|
||||||
* is {@linkplain #isCommandAvailable(int) available}.
|
* is {@linkplain #isCommandAvailable(int) available}.
|
||||||
*/
|
*/
|
||||||
// TODO(b/263132691): Rename this to COMMAND_SET_PLAYLIST_METADATA
|
int COMMAND_SET_PLAYLIST_METADATA = 19;
|
||||||
int COMMAND_SET_MEDIA_ITEMS_METADATA = 19;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Command to set a {@link MediaItem}.
|
* Command to set a {@link MediaItem}.
|
||||||
@ -1720,6 +1844,8 @@ public interface Player {
|
|||||||
* <li>{@link #setMediaItems(List)}
|
* <li>{@link #setMediaItems(List)}
|
||||||
* <li>{@link #setMediaItems(List, boolean)}
|
* <li>{@link #setMediaItems(List, boolean)}
|
||||||
* <li>{@link #setMediaItems(List, int, long)}
|
* <li>{@link #setMediaItems(List, int, long)}
|
||||||
|
* <li>{@link #replaceMediaItem(int, MediaItem)}
|
||||||
|
* <li>{@link #replaceMediaItems(int, int, List)}
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
int COMMAND_CHANGE_MEDIA_ITEMS = 20;
|
int COMMAND_CHANGE_MEDIA_ITEMS = 20;
|
||||||
@ -1760,27 +1886,36 @@ public interface Player {
|
|||||||
* #isCommandAvailable(int) available}.
|
* #isCommandAvailable(int) available}.
|
||||||
*/
|
*/
|
||||||
int COMMAND_SET_VOLUME = 24;
|
int COMMAND_SET_VOLUME = 24;
|
||||||
/**
|
|
||||||
* Command to set the device volume.
|
|
||||||
*
|
|
||||||
* <p>The {@link #setDeviceVolume(int)} method must only be called if this command is {@linkplain
|
|
||||||
* #isCommandAvailable(int) available}.
|
|
||||||
*/
|
|
||||||
int COMMAND_SET_DEVICE_VOLUME = 25;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Command to increase and decrease the device volume and mute it.
|
* @deprecated Use {@link #COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated int COMMAND_SET_DEVICE_VOLUME = 25;
|
||||||
|
/**
|
||||||
|
* Command to set the device volume with volume flags.
|
||||||
|
*
|
||||||
|
* <p>The {@link #setDeviceVolume(int, int)} method must only be called if this command is
|
||||||
|
* {@linkplain #isCommandAvailable(int) available}.
|
||||||
|
*/
|
||||||
|
int COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS = 33;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated int COMMAND_ADJUST_DEVICE_VOLUME = 26;
|
||||||
|
/**
|
||||||
|
* Command to increase and decrease the device volume and mute it with volume flags.
|
||||||
*
|
*
|
||||||
* <p>The following methods must only be called if this command is {@linkplain
|
* <p>The following methods must only be called if this command is {@linkplain
|
||||||
* #isCommandAvailable(int) available}:
|
* #isCommandAvailable(int) available}:
|
||||||
*
|
*
|
||||||
* <ul>
|
* <ul>
|
||||||
* <li>{@link #increaseDeviceVolume()}
|
* <li>{@link #increaseDeviceVolume(int)}
|
||||||
* <li>{@link #decreaseDeviceVolume()}
|
* <li>{@link #decreaseDeviceVolume(int)}
|
||||||
* <li>{@link #setDeviceMuted(boolean)}
|
* <li>{@link #setDeviceMuted(boolean, int)}
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
int COMMAND_ADJUST_DEVICE_VOLUME = 26;
|
int COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS = 34;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Command to set and clear the surface on which to render the video.
|
* Command to set and clear the surface on which to render the video.
|
||||||
@ -1823,6 +1958,13 @@ public interface Player {
|
|||||||
* #isCommandAvailable(int) available}.
|
* #isCommandAvailable(int) available}.
|
||||||
*/
|
*/
|
||||||
int COMMAND_GET_TRACKS = 30;
|
int COMMAND_GET_TRACKS = 30;
|
||||||
|
/**
|
||||||
|
* Command to release the player.
|
||||||
|
*
|
||||||
|
* <p>The {@link #release()} method must only be called if this command is {@linkplain
|
||||||
|
* #isCommandAvailable(int) available}.
|
||||||
|
*/
|
||||||
|
int COMMAND_RELEASE = 32;
|
||||||
|
|
||||||
/** Represents an invalid {@link Command}. */
|
/** Represents an invalid {@link Command}. */
|
||||||
int COMMAND_INVALID = -1;
|
int COMMAND_INVALID = -1;
|
||||||
@ -2005,6 +2147,37 @@ public interface Player {
|
|||||||
*/
|
*/
|
||||||
void moveMediaItems(int fromIndex, int toIndex, int newIndex);
|
void moveMediaItems(int fromIndex, int toIndex, int newIndex);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces the media item at the given index of the playlist.
|
||||||
|
*
|
||||||
|
* <p>This method must only be called if {@link #COMMAND_CHANGE_MEDIA_ITEMS} is {@linkplain
|
||||||
|
* #getAvailableCommands() available}.
|
||||||
|
*
|
||||||
|
* @param index The index at which to replace the media item. If the index is larger than the size
|
||||||
|
* of the playlist, the request is ignored.
|
||||||
|
* @param mediaItem The new {@link MediaItem}.
|
||||||
|
*/
|
||||||
|
void replaceMediaItem(int index, MediaItem mediaItem);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces the media items at the given range of the playlist.
|
||||||
|
*
|
||||||
|
* <p>This method must only be called if {@link #COMMAND_CHANGE_MEDIA_ITEMS} is {@linkplain
|
||||||
|
* #getAvailableCommands() available}.
|
||||||
|
*
|
||||||
|
* <p>Note that it is possible to replace a range with an arbitrary number of new items, so that
|
||||||
|
* the number of removed items defined by {@code fromIndex} and {@code toIndex} does not have to
|
||||||
|
* match the number of added items defined by {@code mediaItems}. As result, it may also change
|
||||||
|
* the index of subsequent items not touched by this operation.
|
||||||
|
*
|
||||||
|
* @param fromIndex The start of the range. If the index is larger than the size of the playlist,
|
||||||
|
* the request is ignored.
|
||||||
|
* @param toIndex The first item not to be included in the range (exclusive). If the index is
|
||||||
|
* larger than the size of the playlist, items up to the end of the playlist are replaced.
|
||||||
|
* @param mediaItems The {@linkplain MediaItem media items} to replace the range with.
|
||||||
|
*/
|
||||||
|
void replaceMediaItems(int fromIndex, int toIndex, List<MediaItem> mediaItems);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Removes the media item at the given index of the playlist.
|
* Removes the media item at the given index of the playlist.
|
||||||
*
|
*
|
||||||
@ -2500,20 +2673,13 @@ public interface Player {
|
|||||||
*/
|
*/
|
||||||
void stop();
|
void stop();
|
||||||
|
|
||||||
/**
|
|
||||||
* @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.
|
|
||||||
*/
|
|
||||||
@UnstableApi
|
|
||||||
@Deprecated
|
|
||||||
void stop(boolean reset);
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Releases the player. This method must be called when the player is no longer required. The
|
* Releases the player. This method must be called when the player is no longer required. The
|
||||||
* player must not be used after calling this method.
|
* player must not be used after calling this method.
|
||||||
|
*
|
||||||
|
* <p>This method must only be called if {@link #COMMAND_RELEASE} is {@linkplain
|
||||||
|
* #getAvailableCommands() available}.
|
||||||
*/
|
*/
|
||||||
// TODO(b/261158047): Document that COMMAND_RELEASE must be available once it exists.
|
|
||||||
void release();
|
void release();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2565,7 +2731,7 @@ public interface Player {
|
|||||||
* Listener#onMetadata(Metadata)}. If a field is populated in the {@link MediaItem#mediaMetadata},
|
* Listener#onMetadata(Metadata)}. If a field is populated in the {@link MediaItem#mediaMetadata},
|
||||||
* it will be prioritised above the same field coming from static or timed metadata.
|
* it will be prioritised above the same field coming from static or timed metadata.
|
||||||
*
|
*
|
||||||
* <p>This method must only be called if {@link #COMMAND_GET_MEDIA_ITEMS_METADATA} is {@linkplain
|
* <p>This method must only be called if {@link #COMMAND_GET_METADATA} is {@linkplain
|
||||||
* #getAvailableCommands() available}.
|
* #getAvailableCommands() available}.
|
||||||
*/
|
*/
|
||||||
MediaMetadata getMediaMetadata();
|
MediaMetadata getMediaMetadata();
|
||||||
@ -2574,7 +2740,7 @@ public interface Player {
|
|||||||
* Returns the playlist {@link MediaMetadata}, as set by {@link
|
* Returns the playlist {@link MediaMetadata}, as set by {@link
|
||||||
* #setPlaylistMetadata(MediaMetadata)}, or {@link MediaMetadata#EMPTY} if not supported.
|
* #setPlaylistMetadata(MediaMetadata)}, or {@link MediaMetadata#EMPTY} if not supported.
|
||||||
*
|
*
|
||||||
* <p>This method must only be called if {@link #COMMAND_GET_MEDIA_ITEMS_METADATA} is {@linkplain
|
* <p>This method must only be called if {@link #COMMAND_GET_METADATA} is {@linkplain
|
||||||
* #getAvailableCommands() available}.
|
* #getAvailableCommands() available}.
|
||||||
*/
|
*/
|
||||||
MediaMetadata getPlaylistMetadata();
|
MediaMetadata getPlaylistMetadata();
|
||||||
@ -2582,7 +2748,7 @@ public interface Player {
|
|||||||
/**
|
/**
|
||||||
* Sets the playlist {@link MediaMetadata}.
|
* Sets the playlist {@link MediaMetadata}.
|
||||||
*
|
*
|
||||||
* <p>This method must only be called if {@link #COMMAND_SET_MEDIA_ITEMS_METADATA} is {@linkplain
|
* <p>This method must only be called if {@link #COMMAND_SET_PLAYLIST_METADATA} is {@linkplain
|
||||||
* #getAvailableCommands() available}.
|
* #getAvailableCommands() available}.
|
||||||
*/
|
*/
|
||||||
void setPlaylistMetadata(MediaMetadata mediaMetadata);
|
void setPlaylistMetadata(MediaMetadata mediaMetadata);
|
||||||
@ -3015,8 +3181,8 @@ public interface Player {
|
|||||||
/**
|
/**
|
||||||
* Gets the size of the video.
|
* Gets the size of the video.
|
||||||
*
|
*
|
||||||
* <p>The video's width and height are {@code 0} if there is no video or its size has not been
|
* <p>The video's width and height are {@code 0} if there is {@linkplain
|
||||||
* determined yet.
|
* Tracks#isTypeSupported(int) no supported video track} or its size has not been determined yet.
|
||||||
*
|
*
|
||||||
* @see Listener#onVideoSizeChanged(VideoSize)
|
* @see Listener#onVideoSizeChanged(VideoSize)
|
||||||
*/
|
*/
|
||||||
@ -3067,36 +3233,74 @@ public interface Player {
|
|||||||
boolean isDeviceMuted();
|
boolean isDeviceMuted();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the volume of the device.
|
* @deprecated Use {@link #setDeviceVolume(int, int)} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
void setDeviceVolume(@IntRange(from = 0) int volume);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the volume of the device with volume flags.
|
||||||
*
|
*
|
||||||
* <p>This method must only be called if {@link #COMMAND_SET_DEVICE_VOLUME} is {@linkplain
|
* <p>This method must only be called if {@link #COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS} is
|
||||||
* #getAvailableCommands() available}.
|
* {@linkplain #getAvailableCommands() available}.
|
||||||
*
|
*
|
||||||
* @param volume The volume to set.
|
* @param volume The volume to set.
|
||||||
|
* @param flags Either 0 or a bitwise combination of one or more {@link C.VolumeFlags}.
|
||||||
*/
|
*/
|
||||||
void setDeviceVolume(@IntRange(from = 0) int volume);
|
void setDeviceVolume(@IntRange(from = 0) int volume, int flags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #increaseDeviceVolume(int)} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
void increaseDeviceVolume();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Increases the volume of the device.
|
* Increases the volume of the device.
|
||||||
*
|
*
|
||||||
* <p>This method must only be called if {@link #COMMAND_ADJUST_DEVICE_VOLUME} is {@linkplain
|
* <p>The {@link #getDeviceVolume()} device volume cannot be increased above {@link
|
||||||
* #getAvailableCommands() available}.
|
* DeviceInfo#maxVolume}, if defined.
|
||||||
|
*
|
||||||
|
* <p>This method must only be called if {@link #COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS} is
|
||||||
|
* {@linkplain #getAvailableCommands() available}.
|
||||||
|
*
|
||||||
|
* @param flags Either 0 or a bitwise combination of one or more {@link C.VolumeFlags}.
|
||||||
*/
|
*/
|
||||||
void increaseDeviceVolume();
|
void increaseDeviceVolume(@C.VolumeFlags int flags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #decreaseDeviceVolume(int)} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
void decreaseDeviceVolume();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Decreases the volume of the device.
|
* Decreases the volume of the device.
|
||||||
*
|
*
|
||||||
* <p>This method must only be called if {@link #COMMAND_ADJUST_DEVICE_VOLUME} is {@linkplain
|
* <p>The {@link #getDeviceVolume()} device volume cannot be decreased below {@link
|
||||||
* #getAvailableCommands() available}.
|
* DeviceInfo#minVolume}.
|
||||||
|
*
|
||||||
|
* <p>This method must only be called if {@link #COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS} is
|
||||||
|
* {@linkplain #getAvailableCommands() available}.
|
||||||
|
*
|
||||||
|
* @param flags Either 0 or a bitwise combination of one or more {@link C.VolumeFlags}.
|
||||||
*/
|
*/
|
||||||
void decreaseDeviceVolume();
|
void decreaseDeviceVolume(@C.VolumeFlags int flags);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #setDeviceMuted(boolean, int)} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
void setDeviceMuted(boolean muted);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the mute state of the device.
|
* Sets the mute state of the device.
|
||||||
*
|
*
|
||||||
* <p>This method must only be called if {@link #COMMAND_ADJUST_DEVICE_VOLUME} is {@linkplain
|
* <p>This method must only be called if {@link #COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS} is
|
||||||
* #getAvailableCommands() available}.
|
* {@linkplain #getAvailableCommands() available}.
|
||||||
|
*
|
||||||
|
* @param muted Whether to set the device to be muted or not
|
||||||
|
* @param flags Either 0 or a bitwise combination of one or more {@link C.VolumeFlags}.
|
||||||
*/
|
*/
|
||||||
void setDeviceMuted(boolean muted);
|
void setDeviceMuted(boolean muted, @C.VolumeFlags int flags);
|
||||||
}
|
}
|
||||||
|
@ -2101,7 +2101,16 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
placeholderPlaylist.add(
|
placeholderPlaylist.add(
|
||||||
i + correctedIndex, getPlaceholderMediaItemData(mediaItems.get(i)));
|
i + correctedIndex, getPlaceholderMediaItemData(mediaItems.get(i)));
|
||||||
}
|
}
|
||||||
|
if (!state.playlist.isEmpty()) {
|
||||||
return getStateWithNewPlaylist(state, placeholderPlaylist, period);
|
return getStateWithNewPlaylist(state, placeholderPlaylist, period);
|
||||||
|
} else {
|
||||||
|
// Handle initial position update when these are the first items added to the playlist.
|
||||||
|
return getStateWithNewPlaylistAndPosition(
|
||||||
|
state,
|
||||||
|
placeholderPlaylist,
|
||||||
|
state.currentMediaItemIndex,
|
||||||
|
state.contentPositionMsSupplier.get());
|
||||||
|
}
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2132,6 +2141,45 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void replaceMediaItems(int fromIndex, int toIndex, List<MediaItem> mediaItems) {
|
||||||
|
verifyApplicationThreadAndInitState();
|
||||||
|
checkArgument(fromIndex >= 0 && fromIndex <= toIndex);
|
||||||
|
State state = this.state;
|
||||||
|
int playlistSize = state.playlist.size();
|
||||||
|
if (!shouldHandleCommand(Player.COMMAND_CHANGE_MEDIA_ITEMS) || fromIndex > playlistSize) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
int correctedToIndex = min(toIndex, playlistSize);
|
||||||
|
updateStateForPendingOperation(
|
||||||
|
/* pendingOperation= */ handleReplaceMediaItems(fromIndex, correctedToIndex, mediaItems),
|
||||||
|
/* placeholderStateSupplier= */ () -> {
|
||||||
|
ArrayList<MediaItemData> placeholderPlaylist = new ArrayList<>(state.playlist);
|
||||||
|
for (int i = 0; i < mediaItems.size(); i++) {
|
||||||
|
placeholderPlaylist.add(
|
||||||
|
i + correctedToIndex, getPlaceholderMediaItemData(mediaItems.get(i)));
|
||||||
|
}
|
||||||
|
State updatedState;
|
||||||
|
if (!state.playlist.isEmpty()) {
|
||||||
|
updatedState = getStateWithNewPlaylist(state, placeholderPlaylist, period);
|
||||||
|
} else {
|
||||||
|
// Handle initial position update when these are the first items added to the playlist.
|
||||||
|
updatedState =
|
||||||
|
getStateWithNewPlaylistAndPosition(
|
||||||
|
state,
|
||||||
|
placeholderPlaylist,
|
||||||
|
state.currentMediaItemIndex,
|
||||||
|
state.contentPositionMsSupplier.get());
|
||||||
|
}
|
||||||
|
if (fromIndex < correctedToIndex) {
|
||||||
|
Util.removeRange(placeholderPlaylist, fromIndex, correctedToIndex);
|
||||||
|
return getStateWithNewPlaylist(updatedState, placeholderPlaylist, period);
|
||||||
|
} else {
|
||||||
|
return updatedState;
|
||||||
|
}
|
||||||
|
});
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void removeMediaItems(int fromIndex, int toIndex) {
|
public final void removeMediaItems(int fromIndex, int toIndex) {
|
||||||
verifyApplicationThreadAndInitState();
|
verifyApplicationThreadAndInitState();
|
||||||
@ -2325,20 +2373,12 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public final void stop(boolean reset) {
|
|
||||||
stop();
|
|
||||||
if (reset) {
|
|
||||||
clearMediaItems();
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void release() {
|
public final void release() {
|
||||||
verifyApplicationThreadAndInitState();
|
verifyApplicationThreadAndInitState();
|
||||||
// Use a local copy to ensure the lambda below uses the current state value.
|
// Use a local copy to ensure the lambda below uses the current state value.
|
||||||
State state = this.state;
|
State state = this.state;
|
||||||
if (released) { // TODO(b/261158047): Replace by !shouldHandleCommand(Player.COMMAND_RELEASE)
|
if (!shouldHandleCommand(Player.COMMAND_RELEASE)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
updateStateForPendingOperation(
|
updateStateForPendingOperation(
|
||||||
@ -2401,7 +2441,7 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
verifyApplicationThreadAndInitState();
|
verifyApplicationThreadAndInitState();
|
||||||
// Use a local copy to ensure the lambda below uses the current state value.
|
// Use a local copy to ensure the lambda below uses the current state value.
|
||||||
State state = this.state;
|
State state = this.state;
|
||||||
if (!shouldHandleCommand(Player.COMMAND_SET_MEDIA_ITEMS_METADATA)) {
|
if (!shouldHandleCommand(Player.COMMAND_SET_PLAYLIST_METADATA)) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
updateStateForPendingOperation(
|
updateStateForPendingOperation(
|
||||||
@ -2669,6 +2709,10 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
return state.isDeviceMuted;
|
return state.isDeviceMuted;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #setDeviceVolume(int, int)} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
public final void setDeviceVolume(int volume) {
|
public final void setDeviceVolume(int volume) {
|
||||||
verifyApplicationThreadAndInitState();
|
verifyApplicationThreadAndInitState();
|
||||||
@ -2678,10 +2722,27 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
updateStateForPendingOperation(
|
updateStateForPendingOperation(
|
||||||
/* pendingOperation= */ handleSetDeviceVolume(volume),
|
/* pendingOperation= */ handleSetDeviceVolume(volume, C.VOLUME_FLAG_SHOW_UI),
|
||||||
/* placeholderStateSupplier= */ () -> state.buildUpon().setDeviceVolume(volume).build());
|
/* placeholderStateSupplier= */ () -> state.buildUpon().setDeviceVolume(volume).build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void setDeviceVolume(int volume, @C.VolumeFlags int flags) {
|
||||||
|
verifyApplicationThreadAndInitState();
|
||||||
|
// Use a local copy to ensure the lambda below uses the current state value.
|
||||||
|
State state = this.state;
|
||||||
|
if (!shouldHandleCommand(Player.COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updateStateForPendingOperation(
|
||||||
|
/* pendingOperation= */ handleSetDeviceVolume(volume, flags),
|
||||||
|
/* placeholderStateSupplier= */ () -> state.buildUpon().setDeviceVolume(volume).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #increaseDeviceVolume(int)} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
public final void increaseDeviceVolume() {
|
public final void increaseDeviceVolume() {
|
||||||
verifyApplicationThreadAndInitState();
|
verifyApplicationThreadAndInitState();
|
||||||
@ -2691,11 +2752,29 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
updateStateForPendingOperation(
|
updateStateForPendingOperation(
|
||||||
/* pendingOperation= */ handleIncreaseDeviceVolume(),
|
/* pendingOperation= */ handleIncreaseDeviceVolume(C.VOLUME_FLAG_SHOW_UI),
|
||||||
/* placeholderStateSupplier= */ () ->
|
/* placeholderStateSupplier= */ () ->
|
||||||
state.buildUpon().setDeviceVolume(state.deviceVolume + 1).build());
|
state.buildUpon().setDeviceVolume(state.deviceVolume + 1).build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void increaseDeviceVolume(@C.VolumeFlags int flags) {
|
||||||
|
verifyApplicationThreadAndInitState();
|
||||||
|
// Use a local copy to ensure the lambda below uses the current state value.
|
||||||
|
State state = this.state;
|
||||||
|
if (!shouldHandleCommand(Player.COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updateStateForPendingOperation(
|
||||||
|
/* pendingOperation= */ handleIncreaseDeviceVolume(flags),
|
||||||
|
/* placeholderStateSupplier= */ () ->
|
||||||
|
state.buildUpon().setDeviceVolume(state.deviceVolume + 1).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #decreaseDeviceVolume(int)} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
public final void decreaseDeviceVolume() {
|
public final void decreaseDeviceVolume() {
|
||||||
verifyApplicationThreadAndInitState();
|
verifyApplicationThreadAndInitState();
|
||||||
@ -2705,11 +2784,29 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
updateStateForPendingOperation(
|
updateStateForPendingOperation(
|
||||||
/* pendingOperation= */ handleDecreaseDeviceVolume(),
|
/* pendingOperation= */ handleDecreaseDeviceVolume(C.VOLUME_FLAG_SHOW_UI),
|
||||||
/* placeholderStateSupplier= */ () ->
|
/* placeholderStateSupplier= */ () ->
|
||||||
state.buildUpon().setDeviceVolume(max(0, state.deviceVolume - 1)).build());
|
state.buildUpon().setDeviceVolume(max(0, state.deviceVolume - 1)).build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void decreaseDeviceVolume(@C.VolumeFlags int flags) {
|
||||||
|
verifyApplicationThreadAndInitState();
|
||||||
|
// Use a local copy to ensure the lambda below uses the current state value.
|
||||||
|
State state = this.state;
|
||||||
|
if (!shouldHandleCommand(Player.COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updateStateForPendingOperation(
|
||||||
|
/* pendingOperation= */ handleDecreaseDeviceVolume(flags),
|
||||||
|
/* placeholderStateSupplier= */ () ->
|
||||||
|
state.buildUpon().setDeviceVolume(max(0, state.deviceVolume - 1)).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link #setDeviceMuted(boolean, int)} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
@Override
|
@Override
|
||||||
public final void setDeviceMuted(boolean muted) {
|
public final void setDeviceMuted(boolean muted) {
|
||||||
verifyApplicationThreadAndInitState();
|
verifyApplicationThreadAndInitState();
|
||||||
@ -2719,7 +2816,20 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
updateStateForPendingOperation(
|
updateStateForPendingOperation(
|
||||||
/* pendingOperation= */ handleSetDeviceMuted(muted),
|
/* pendingOperation= */ handleSetDeviceMuted(muted, C.VOLUME_FLAG_SHOW_UI),
|
||||||
|
/* placeholderStateSupplier= */ () -> state.buildUpon().setIsDeviceMuted(muted).build());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void setDeviceMuted(boolean muted, @C.VolumeFlags int flags) {
|
||||||
|
verifyApplicationThreadAndInitState();
|
||||||
|
// Use a local copy to ensure the lambda below uses the current state value.
|
||||||
|
State state = this.state;
|
||||||
|
if (!shouldHandleCommand(Player.COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updateStateForPendingOperation(
|
||||||
|
/* pendingOperation= */ handleSetDeviceMuted(muted, flags),
|
||||||
/* placeholderStateSupplier= */ () -> state.buildUpon().setIsDeviceMuted(muted).build());
|
/* placeholderStateSupplier= */ () -> state.buildUpon().setIsDeviceMuted(muted).build());
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -2837,10 +2947,11 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
/**
|
/**
|
||||||
* Handles calls to {@link Player#release}.
|
* Handles calls to {@link Player#release}.
|
||||||
*
|
*
|
||||||
|
* <p>Will only be called if {@link Player#COMMAND_RELEASE} is available.
|
||||||
|
*
|
||||||
* @return A {@link ListenableFuture} indicating the completion of all immediate {@link State}
|
* @return A {@link ListenableFuture} indicating the completion of all immediate {@link State}
|
||||||
* changes caused by this call.
|
* changes caused by this call.
|
||||||
*/
|
*/
|
||||||
// TODO(b/261158047): Add that this method will only be called if COMMAND_RELEASE is available.
|
|
||||||
@ForOverride
|
@ForOverride
|
||||||
protected ListenableFuture<?> handleRelease() {
|
protected ListenableFuture<?> handleRelease() {
|
||||||
throw new IllegalStateException("Missing implementation to handle COMMAND_RELEASE");
|
throw new IllegalStateException("Missing implementation to handle COMMAND_RELEASE");
|
||||||
@ -2907,7 +3018,7 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
/**
|
/**
|
||||||
* Handles calls to {@link Player#setPlaylistMetadata}.
|
* Handles calls to {@link Player#setPlaylistMetadata}.
|
||||||
*
|
*
|
||||||
* <p>Will only be called if {@link Player#COMMAND_SET_MEDIA_ITEMS_METADATA} is available.
|
* <p>Will only be called if {@link Player#COMMAND_SET_PLAYLIST_METADATA} is available.
|
||||||
*
|
*
|
||||||
* @param playlistMetadata The requested {@linkplain MediaMetadata playlist metadata}.
|
* @param playlistMetadata The requested {@linkplain MediaMetadata playlist metadata}.
|
||||||
* @return A {@link ListenableFuture} indicating the completion of all immediate {@link State}
|
* @return A {@link ListenableFuture} indicating the completion of all immediate {@link State}
|
||||||
@ -2916,7 +3027,7 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
@ForOverride
|
@ForOverride
|
||||||
protected ListenableFuture<?> handleSetPlaylistMetadata(MediaMetadata playlistMetadata) {
|
protected ListenableFuture<?> handleSetPlaylistMetadata(MediaMetadata playlistMetadata) {
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
"Missing implementation to handle COMMAND_SET_MEDIA_ITEMS_METADATA");
|
"Missing implementation to handle COMMAND_SET_PLAYLIST_METADATA");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -2935,60 +3046,78 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles calls to {@link Player#setDeviceVolume}.
|
* Handles calls to {@link Player#setDeviceVolume(int)} and {@link Player#setDeviceVolume(int,
|
||||||
|
* int)}.
|
||||||
*
|
*
|
||||||
* <p>Will only be called if {@link Player#COMMAND_SET_DEVICE_VOLUME} is available.
|
* <p>Will only be called if {@link Player#COMMAND_SET_DEVICE_VOLUME} or {@link
|
||||||
|
* Player#COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS} is available.
|
||||||
*
|
*
|
||||||
* @param deviceVolume The requested device volume.
|
* @param deviceVolume The requested device volume.
|
||||||
|
* @param flags Either 0 or a bitwise combination of one or more {@link C.VolumeFlags}.
|
||||||
* @return A {@link ListenableFuture} indicating the completion of all immediate {@link State}
|
* @return A {@link ListenableFuture} indicating the completion of all immediate {@link State}
|
||||||
* changes caused by this call.
|
* changes caused by this call.
|
||||||
*/
|
*/
|
||||||
@ForOverride
|
@ForOverride
|
||||||
protected ListenableFuture<?> handleSetDeviceVolume(@IntRange(from = 0) int deviceVolume) {
|
protected ListenableFuture<?> handleSetDeviceVolume(
|
||||||
throw new IllegalStateException("Missing implementation to handle COMMAND_SET_DEVICE_VOLUME");
|
@IntRange(from = 0) int deviceVolume, int flags) {
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Handles calls to {@link Player#increaseDeviceVolume()}.
|
|
||||||
*
|
|
||||||
* <p>Will only be called if {@link Player#COMMAND_ADJUST_DEVICE_VOLUME} is available.
|
|
||||||
*
|
|
||||||
* @return A {@link ListenableFuture} indicating the completion of all immediate {@link State}
|
|
||||||
* changes caused by this call.
|
|
||||||
*/
|
|
||||||
@ForOverride
|
|
||||||
protected ListenableFuture<?> handleIncreaseDeviceVolume() {
|
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
"Missing implementation to handle COMMAND_ADJUST_DEVICE_VOLUME");
|
"Missing implementation to handle COMMAND_SET_DEVICE_VOLUME or"
|
||||||
|
+ " COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles calls to {@link Player#decreaseDeviceVolume()}.
|
* Handles calls to {@link Player#increaseDeviceVolume()} and {@link
|
||||||
|
* Player#increaseDeviceVolume(int)}.
|
||||||
*
|
*
|
||||||
* <p>Will only be called if {@link Player#COMMAND_ADJUST_DEVICE_VOLUME} is available.
|
* <p>Will only be called if {@link Player#COMMAND_ADJUST_DEVICE_VOLUME} or {@link
|
||||||
|
* Player#COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS} is available.
|
||||||
*
|
*
|
||||||
|
* @param flags Either 0 or a bitwise combination of one or more {@link C.VolumeFlags}.
|
||||||
* @return A {@link ListenableFuture} indicating the completion of all immediate {@link State}
|
* @return A {@link ListenableFuture} indicating the completion of all immediate {@link State}
|
||||||
* changes caused by this call.
|
* changes caused by this call.
|
||||||
*/
|
*/
|
||||||
@ForOverride
|
@ForOverride
|
||||||
protected ListenableFuture<?> handleDecreaseDeviceVolume() {
|
protected ListenableFuture<?> handleIncreaseDeviceVolume(@C.VolumeFlags int flags) {
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
"Missing implementation to handle COMMAND_ADJUST_DEVICE_VOLUME");
|
"Missing implementation to handle COMMAND_ADJUST_DEVICE_VOLUME or"
|
||||||
|
+ " COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles calls to {@link Player#setDeviceMuted}.
|
* Handles calls to {@link Player#decreaseDeviceVolume()} and {@link
|
||||||
|
* Player#decreaseDeviceVolume(int)}.
|
||||||
*
|
*
|
||||||
* <p>Will only be called if {@link Player#COMMAND_ADJUST_DEVICE_VOLUME} is available.
|
* <p>Will only be called if {@link Player#COMMAND_ADJUST_DEVICE_VOLUME} or {@link
|
||||||
|
* Player#COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS} is available.
|
||||||
|
*
|
||||||
|
* @param flags Either 0 or a bitwise combination of one or more {@link C.VolumeFlags}.
|
||||||
|
* @return A {@link ListenableFuture} indicating the completion of all immediate {@link State}
|
||||||
|
* changes caused by this call.
|
||||||
|
*/
|
||||||
|
@ForOverride
|
||||||
|
protected ListenableFuture<?> handleDecreaseDeviceVolume(@C.VolumeFlags int flags) {
|
||||||
|
throw new IllegalStateException(
|
||||||
|
"Missing implementation to handle COMMAND_ADJUST_DEVICE_VOLUME or"
|
||||||
|
+ " COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS");
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles calls to {@link Player#setDeviceMuted(boolean)} and {@link
|
||||||
|
* Player#setDeviceMuted(boolean, int)}.
|
||||||
|
*
|
||||||
|
* <p>Will only be called if {@link Player#COMMAND_ADJUST_DEVICE_VOLUME} or {@link
|
||||||
|
* Player#COMMAND_ADJUST_DEVICE_VOLUME} is available.
|
||||||
*
|
*
|
||||||
* @param muted Whether the device was requested to be muted.
|
* @param muted Whether the device was requested to be muted.
|
||||||
|
* @param flags Either 0 or a bitwise combination of one or more {@link C.VolumeFlags}.
|
||||||
* @return A {@link ListenableFuture} indicating the completion of all immediate {@link State}
|
* @return A {@link ListenableFuture} indicating the completion of all immediate {@link State}
|
||||||
* changes caused by this call.
|
* changes caused by this call.
|
||||||
*/
|
*/
|
||||||
@ForOverride
|
@ForOverride
|
||||||
protected ListenableFuture<?> handleSetDeviceMuted(boolean muted) {
|
protected ListenableFuture<?> handleSetDeviceMuted(boolean muted, @C.VolumeFlags int flags) {
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
"Missing implementation to handle COMMAND_ADJUST_DEVICE_VOLUME");
|
"Missing implementation to handle COMMAND_ADJUST_DEVICE_VOLUME or"
|
||||||
|
+ " COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS");
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -3080,6 +3209,27 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
throw new IllegalStateException("Missing implementation to handle COMMAND_CHANGE_MEDIA_ITEMS");
|
throw new IllegalStateException("Missing implementation to handle COMMAND_CHANGE_MEDIA_ITEMS");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles calls to {@link Player#replaceMediaItem} and {@link Player#replaceMediaItems}.
|
||||||
|
*
|
||||||
|
* <p>Will only be called if {@link Player#COMMAND_CHANGE_MEDIA_ITEMS} is available.
|
||||||
|
*
|
||||||
|
* @param fromIndex The start index of the items to replace. The index is in the range 0 <=
|
||||||
|
* {@code fromIndex} < {@link #getMediaItemCount()}.
|
||||||
|
* @param toIndex The index of the first item not to be replaced (exclusive). The index is in the
|
||||||
|
* range {@code fromIndex} < {@code toIndex} <= {@link #getMediaItemCount()}.
|
||||||
|
* @param mediaItems The media items to replace the specified range with.
|
||||||
|
* @return A {@link ListenableFuture} indicating the completion of all immediate {@link State}
|
||||||
|
* changes caused by this call.
|
||||||
|
*/
|
||||||
|
@ForOverride
|
||||||
|
protected ListenableFuture<?> handleReplaceMediaItems(
|
||||||
|
int fromIndex, int toIndex, List<MediaItem> mediaItems) {
|
||||||
|
ListenableFuture<?> addFuture = handleAddMediaItems(toIndex, mediaItems);
|
||||||
|
ListenableFuture<?> removeFuture = handleRemoveMediaItems(fromIndex, toIndex);
|
||||||
|
return Util.transformFutureAsync(addFuture, unused -> removeFuture);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Handles calls to {@link Player#removeMediaItem} and {@link Player#removeMediaItems}.
|
* Handles calls to {@link Player#removeMediaItem} and {@link Player#removeMediaItems}.
|
||||||
*
|
*
|
||||||
@ -3336,9 +3486,6 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
listeners.queueEvent(
|
listeners.queueEvent(
|
||||||
Player.EVENT_METADATA, listener -> listener.onMetadata(newState.timedMetadata));
|
Player.EVENT_METADATA, listener -> listener.onMetadata(newState.timedMetadata));
|
||||||
}
|
}
|
||||||
if (positionDiscontinuityReason == Player.DISCONTINUITY_REASON_SEEK) {
|
|
||||||
listeners.queueEvent(/* eventFlag= */ C.INDEX_UNSET, Listener::onSeekProcessed);
|
|
||||||
}
|
|
||||||
if (!previousState.availableCommands.equals(newState.availableCommands)) {
|
if (!previousState.availableCommands.equals(newState.availableCommands)) {
|
||||||
listeners.queueEvent(
|
listeners.queueEvent(
|
||||||
Player.EVENT_AVAILABLE_COMMANDS_CHANGED,
|
Player.EVENT_AVAILABLE_COMMANDS_CHANGED,
|
||||||
@ -3717,7 +3864,7 @@ public abstract class SimpleBasePlayer extends BasePlayer {
|
|||||||
State.Builder stateBuilder = oldState.buildUpon();
|
State.Builder stateBuilder = oldState.buildUpon();
|
||||||
stateBuilder.setPlaylist(newPlaylist);
|
stateBuilder.setPlaylist(newPlaylist);
|
||||||
if (oldState.playbackState != Player.STATE_IDLE) {
|
if (oldState.playbackState != Player.STATE_IDLE) {
|
||||||
if (newPlaylist.isEmpty()) {
|
if (newPlaylist.isEmpty() || (newIndex != C.INDEX_UNSET && newIndex >= newPlaylist.size())) {
|
||||||
stateBuilder.setPlaybackState(Player.STATE_ENDED).setIsLoading(false);
|
stateBuilder.setPlaybackState(Player.STATE_ENDED).setIsLoading(false);
|
||||||
} else {
|
} else {
|
||||||
stateBuilder.setPlaybackState(Player.STATE_BUFFERING);
|
stateBuilder.setPlaybackState(Player.STATE_BUFFERING);
|
||||||
|
@ -15,10 +15,12 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.common;
|
package androidx.media3.common;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import android.os.Parcelable;
|
import android.os.Parcelable;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import androidx.media3.common.util.Util;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A key for a subset of media that can be separately loaded (a "stream").
|
* A key for a subset of media that can be separately loaded (a "stream").
|
||||||
@ -35,7 +37,7 @@ import androidx.media3.common.util.UnstableApi;
|
|||||||
* particular track selection.
|
* particular track selection.
|
||||||
*/
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public final class StreamKey implements Comparable<StreamKey>, Parcelable {
|
public final class StreamKey implements Comparable<StreamKey>, Parcelable, Bundleable {
|
||||||
|
|
||||||
/** The period index. */
|
/** The period index. */
|
||||||
public final int periodIndex;
|
public final int periodIndex;
|
||||||
@ -44,11 +46,6 @@ 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 public final int trackIndex;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance with {@link #periodIndex} set to 0.
|
* Creates an instance with {@link #periodIndex} set to 0.
|
||||||
*
|
*
|
||||||
@ -60,26 +57,22 @@ public final class StreamKey implements Comparable<StreamKey>, Parcelable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance.
|
* Creates an instance of {@link StreamKey} using 3 indices.
|
||||||
*
|
*
|
||||||
* @param periodIndex The period index.
|
* @param periodIndex The period index.
|
||||||
* @param groupIndex The group index.
|
* @param groupIndex The group index.
|
||||||
* @param streamIndex The stream index.
|
* @param streamIndex The stream index.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
public StreamKey(int periodIndex, int groupIndex, int streamIndex) {
|
public StreamKey(int periodIndex, int groupIndex, int streamIndex) {
|
||||||
this.periodIndex = periodIndex;
|
this.periodIndex = periodIndex;
|
||||||
this.groupIndex = groupIndex;
|
this.groupIndex = groupIndex;
|
||||||
this.streamIndex = streamIndex;
|
this.streamIndex = streamIndex;
|
||||||
trackIndex = streamIndex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
/* package */ StreamKey(Parcel in) {
|
/* package */ StreamKey(Parcel in) {
|
||||||
periodIndex = in.readInt();
|
periodIndex = in.readInt();
|
||||||
groupIndex = in.readInt();
|
groupIndex = in.readInt();
|
||||||
streamIndex = in.readInt();
|
streamIndex = in.readInt();
|
||||||
trackIndex = streamIndex;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -151,4 +144,36 @@ public final class StreamKey implements Comparable<StreamKey>, Parcelable {
|
|||||||
return new StreamKey[size];
|
return new StreamKey[size];
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
// Bundleable implementation.
|
||||||
|
|
||||||
|
private static final String FIELD_PERIOD_INDEX = Util.intToStringMaxRadix(0);
|
||||||
|
private static final String FIELD_GROUP_INDEX = Util.intToStringMaxRadix(1);
|
||||||
|
private static final String FIELD_STREAM_INDEX = Util.intToStringMaxRadix(2);
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Bundle toBundle() {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
if (periodIndex != 0) {
|
||||||
|
bundle.putInt(FIELD_PERIOD_INDEX, periodIndex);
|
||||||
|
}
|
||||||
|
if (groupIndex != 0) {
|
||||||
|
bundle.putInt(FIELD_GROUP_INDEX, groupIndex);
|
||||||
|
}
|
||||||
|
if (streamIndex != 0) {
|
||||||
|
bundle.putInt(FIELD_STREAM_INDEX, streamIndex);
|
||||||
|
}
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Constructs an instance of {@link StreamKey} from a {@link Bundle} produced by {@link
|
||||||
|
* #toBundle()}.
|
||||||
|
*/
|
||||||
|
public static StreamKey fromBundle(Bundle bundle) {
|
||||||
|
return new StreamKey(
|
||||||
|
bundle.getInt(FIELD_PERIOD_INDEX, /* defaultValue= */ 0),
|
||||||
|
bundle.getInt(FIELD_GROUP_INDEX, /* defaultValue= */ 0),
|
||||||
|
bundle.getInt(FIELD_STREAM_INDEX, /* defaultValue= */ 0));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -37,6 +37,9 @@ import com.google.errorprone.annotations.InlineMe;
|
|||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
|
// TODO(b/276289331): Revert to media3-hosted SVG links below once they're available on
|
||||||
|
// developer.android.com.
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A flexible representation of the structure of media. A timeline is able to represent the
|
* A flexible representation of the structure of media. A timeline is able to represent the
|
||||||
* structure of a wide variety of media, from simple cases like a single media file through to
|
* structure of a wide variety of media, from simple cases like a single media file through to
|
||||||
@ -138,8 +141,6 @@ import java.util.List;
|
|||||||
* <p>This case includes mid-roll ad groups, which are defined as part of the timeline's single
|
* <p>This case includes mid-roll ad groups, which are defined as part of the timeline's single
|
||||||
* period. The period can be queried for information about the ad groups and the ads they contain.
|
* period. The period can be queried for information about the ad groups and the ads they contain.
|
||||||
*/
|
*/
|
||||||
// TODO(b/276289331): Revert to media3-hosted SVG links above once they're available on
|
|
||||||
// developer.android.com.
|
|
||||||
public abstract class Timeline implements Bundleable {
|
public abstract class Timeline implements Bundleable {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -149,8 +150,9 @@ public abstract class Timeline implements Bundleable {
|
|||||||
* shows some of the information defined by a window, as well as how this information relates to
|
* shows some of the information defined by a window, as well as how this information relates to
|
||||||
* corresponding {@link Period Periods} in the timeline.
|
* corresponding {@link Period Periods} in the timeline.
|
||||||
*
|
*
|
||||||
* <p style="align:center"><img src="doc-files/timeline-window.svg" alt="Information defined by a
|
* <p style="align:center"><img
|
||||||
* timeline window">
|
* src="https://exoplayer.dev/doc/reference/com/google/android/exoplayer2/doc-files/timeline-window.svg"
|
||||||
|
* alt="Information defined by a timeline window">
|
||||||
*/
|
*/
|
||||||
public static final class Window implements Bundleable {
|
public static final class Window implements Bundleable {
|
||||||
|
|
||||||
@ -557,8 +559,9 @@ public abstract class Timeline implements Bundleable {
|
|||||||
* <p>The figure below shows some of the information defined by a period, as well as how this
|
* <p>The figure below shows some of the information defined by a period, as well as how this
|
||||||
* information relates to a corresponding {@link Window} in the timeline.
|
* information relates to a corresponding {@link Window} in the timeline.
|
||||||
*
|
*
|
||||||
* <p style="align:center"><img src="doc-files/timeline-period.svg" alt="Information defined by a
|
* <p style="align:center"><img
|
||||||
* period">
|
* src="https://exoplayer.dev/doc/reference/com/google/android/exoplayer2/doc-files/timeline-period.svg"
|
||||||
|
* alt="Information defined by a period">
|
||||||
*/
|
*/
|
||||||
public static final class Period implements Bundleable {
|
public static final class Period implements Bundleable {
|
||||||
|
|
||||||
@ -834,6 +837,18 @@ public abstract class Timeline implements Bundleable {
|
|||||||
: AD_STATE_UNAVAILABLE;
|
: AD_STATE_UNAVAILABLE;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the ad group at the given ad group index is a live postroll placeholder.
|
||||||
|
*
|
||||||
|
* @param adGroupIndex The ad group index.
|
||||||
|
* @return True if the ad group at the given index is a live postroll placeholder.
|
||||||
|
*/
|
||||||
|
@UnstableApi
|
||||||
|
public boolean isLivePostrollPlaceholder(int adGroupIndex) {
|
||||||
|
return adGroupIndex == getAdGroupCount() - 1
|
||||||
|
&& adPlaybackState.isLivePostrollPlaceholder(adGroupIndex);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns the position offset in the first unplayed ad at which to begin playback, in
|
* Returns the position offset in the first unplayed ad at which to begin playback, in
|
||||||
* microseconds.
|
* microseconds.
|
||||||
|
@ -17,27 +17,31 @@ package androidx.media3.common;
|
|||||||
|
|
||||||
import androidx.media3.common.util.UnstableApi;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
|
||||||
/** Thrown when an exception occurs while applying effects to video frames. */
|
/**
|
||||||
|
* Thrown when an exception occurs while preparing an {@link Effect}, or applying an {@link Effect}
|
||||||
|
* to video frames.
|
||||||
|
*/
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
public final class FrameProcessingException extends Exception {
|
public final class VideoFrameProcessingException extends Exception {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wraps the given exception in a {@code FrameProcessingException} if it is not already a {@code
|
* Wraps the given exception in a {@code VideoFrameProcessingException} if it is not already a
|
||||||
* FrameProcessingException} and returns the exception otherwise.
|
* {@code VideoFrameProcessingException} and returns the exception otherwise.
|
||||||
*/
|
*/
|
||||||
public static FrameProcessingException from(Exception exception) {
|
public static VideoFrameProcessingException from(Exception exception) {
|
||||||
return from(exception, /* presentationTimeUs= */ C.TIME_UNSET);
|
return from(exception, /* presentationTimeUs= */ C.TIME_UNSET);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Wraps the given exception in a {@code FrameProcessingException} with the given timestamp if it
|
* Wraps the given exception in a {@code VideoFrameProcessingException} with the given timestamp
|
||||||
* is not already a {@code FrameProcessingException} and returns the exception otherwise.
|
* if it is not already a {@code VideoFrameProcessingException} and returns the exception
|
||||||
|
* otherwise.
|
||||||
*/
|
*/
|
||||||
public static FrameProcessingException from(Exception exception, long presentationTimeUs) {
|
public static VideoFrameProcessingException from(Exception exception, long presentationTimeUs) {
|
||||||
if (exception instanceof FrameProcessingException) {
|
if (exception instanceof VideoFrameProcessingException) {
|
||||||
return (FrameProcessingException) exception;
|
return (VideoFrameProcessingException) exception;
|
||||||
} else {
|
} else {
|
||||||
return new FrameProcessingException(exception, presentationTimeUs);
|
return new VideoFrameProcessingException(exception, presentationTimeUs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -52,7 +56,7 @@ public final class FrameProcessingException extends Exception {
|
|||||||
*
|
*
|
||||||
* @param message The detail message for this exception.
|
* @param message The detail message for this exception.
|
||||||
*/
|
*/
|
||||||
public FrameProcessingException(String message) {
|
public VideoFrameProcessingException(String message) {
|
||||||
this(message, /* presentationTimeUs= */ C.TIME_UNSET);
|
this(message, /* presentationTimeUs= */ C.TIME_UNSET);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -62,7 +66,7 @@ public final class FrameProcessingException extends Exception {
|
|||||||
* @param message The detail message for this exception.
|
* @param message The detail message for this exception.
|
||||||
* @param presentationTimeUs The timestamp of the frame for which the exception occurred.
|
* @param presentationTimeUs The timestamp of the frame for which the exception occurred.
|
||||||
*/
|
*/
|
||||||
public FrameProcessingException(String message, long presentationTimeUs) {
|
public VideoFrameProcessingException(String message, long presentationTimeUs) {
|
||||||
super(message);
|
super(message);
|
||||||
this.presentationTimeUs = presentationTimeUs;
|
this.presentationTimeUs = presentationTimeUs;
|
||||||
}
|
}
|
||||||
@ -73,7 +77,7 @@ public final class FrameProcessingException extends Exception {
|
|||||||
* @param message The detail message for this exception.
|
* @param message The detail message for this exception.
|
||||||
* @param cause The cause of this exception.
|
* @param cause The cause of this exception.
|
||||||
*/
|
*/
|
||||||
public FrameProcessingException(String message, Throwable cause) {
|
public VideoFrameProcessingException(String message, Throwable cause) {
|
||||||
this(message, cause, /* presentationTimeUs= */ C.TIME_UNSET);
|
this(message, cause, /* presentationTimeUs= */ C.TIME_UNSET);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -84,7 +88,7 @@ public final class FrameProcessingException extends Exception {
|
|||||||
* @param cause The cause of this exception.
|
* @param cause The cause of this exception.
|
||||||
* @param presentationTimeUs The timestamp of the frame for which the exception occurred.
|
* @param presentationTimeUs The timestamp of the frame for which the exception occurred.
|
||||||
*/
|
*/
|
||||||
public FrameProcessingException(String message, Throwable cause, long presentationTimeUs) {
|
public VideoFrameProcessingException(String message, Throwable cause, long presentationTimeUs) {
|
||||||
super(message, cause);
|
super(message, cause);
|
||||||
this.presentationTimeUs = presentationTimeUs;
|
this.presentationTimeUs = presentationTimeUs;
|
||||||
}
|
}
|
||||||
@ -94,7 +98,7 @@ public final class FrameProcessingException extends Exception {
|
|||||||
*
|
*
|
||||||
* @param cause The cause of this exception.
|
* @param cause The cause of this exception.
|
||||||
*/
|
*/
|
||||||
public FrameProcessingException(Throwable cause) {
|
public VideoFrameProcessingException(Throwable cause) {
|
||||||
this(cause, /* presentationTimeUs= */ C.TIME_UNSET);
|
this(cause, /* presentationTimeUs= */ C.TIME_UNSET);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -104,7 +108,7 @@ public final class FrameProcessingException extends Exception {
|
|||||||
* @param cause The cause of this exception.
|
* @param cause The cause of this exception.
|
||||||
* @param presentationTimeUs The timestamp of the frame for which the exception occurred.
|
* @param presentationTimeUs The timestamp of the frame for which the exception occurred.
|
||||||
*/
|
*/
|
||||||
public FrameProcessingException(Throwable cause, long presentationTimeUs) {
|
public VideoFrameProcessingException(Throwable cause, long presentationTimeUs) {
|
||||||
super(cause);
|
super(cause);
|
||||||
this.presentationTimeUs = presentationTimeUs;
|
this.presentationTimeUs = presentationTimeUs;
|
||||||
}
|
}
|
@ -0,0 +1,320 @@
|
|||||||
|
/*
|
||||||
|
* 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.common;
|
||||||
|
|
||||||
|
import static java.lang.annotation.ElementType.TYPE_USE;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.opengl.EGLExt;
|
||||||
|
import android.view.Surface;
|
||||||
|
import androidx.annotation.IntDef;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import java.lang.annotation.Documented;
|
||||||
|
import java.lang.annotation.Retention;
|
||||||
|
import java.lang.annotation.RetentionPolicy;
|
||||||
|
import java.lang.annotation.Target;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.concurrent.Executor;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Interface for a video frame processor that applies changes to individual video frames.
|
||||||
|
*
|
||||||
|
* <p>The changes are specified by {@link Effect} instances passed to {@link Factory#create}.
|
||||||
|
*
|
||||||
|
* <p>Manages its input {@link Surface}, which can be accessed via {@link #getInputSurface()}. The
|
||||||
|
* output {@link Surface} must be set by the caller using {@link
|
||||||
|
* #setOutputSurfaceInfo(SurfaceInfo)}.
|
||||||
|
*
|
||||||
|
* <p>The caller must {@linkplain #registerInputFrame() register} input frames before rendering them
|
||||||
|
* to the input {@link Surface}.
|
||||||
|
*/
|
||||||
|
@UnstableApi
|
||||||
|
public interface VideoFrameProcessor {
|
||||||
|
// TODO(b/243036513): Allow effects to be replaced.
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Specifies how the input frames are made available to the {@link VideoFrameProcessor}. One of
|
||||||
|
* {@link #INPUT_TYPE_SURFACE}, {@link #INPUT_TYPE_BITMAP} or {@link #INPUT_TYPE_TEXTURE_ID}.
|
||||||
|
*/
|
||||||
|
@Documented
|
||||||
|
@Retention(RetentionPolicy.SOURCE)
|
||||||
|
@Target(TYPE_USE)
|
||||||
|
@IntDef({INPUT_TYPE_SURFACE, INPUT_TYPE_BITMAP, INPUT_TYPE_TEXTURE_ID})
|
||||||
|
@interface InputType {}
|
||||||
|
/** Input frames come from a {@link #getInputSurface surface}. */
|
||||||
|
int INPUT_TYPE_SURFACE = 1;
|
||||||
|
/** Input frames come from a {@link Bitmap}. */
|
||||||
|
int INPUT_TYPE_BITMAP = 2;
|
||||||
|
/**
|
||||||
|
* Input frames come from a {@linkplain android.opengl.GLES10#GL_TEXTURE_2D traditional GLES
|
||||||
|
* texture}.
|
||||||
|
*/
|
||||||
|
int INPUT_TYPE_TEXTURE_ID = 3;
|
||||||
|
|
||||||
|
/** A factory for {@link VideoFrameProcessor} instances. */
|
||||||
|
interface Factory {
|
||||||
|
|
||||||
|
// TODO(271433904): Turn parameters with default values into setters.
|
||||||
|
/**
|
||||||
|
* Creates a new {@link VideoFrameProcessor} instance.
|
||||||
|
*
|
||||||
|
* @param context A {@link Context}.
|
||||||
|
* @param effects The {@link Effect} instances to apply to each frame. Applied on the {@code
|
||||||
|
* outputColorInfo}'s color space.
|
||||||
|
* @param debugViewProvider A {@link DebugViewProvider}.
|
||||||
|
* @param inputColorInfo The {@link ColorInfo} for the input frames.
|
||||||
|
* @param outputColorInfo The {@link ColorInfo} for the output frames.
|
||||||
|
* @param renderFramesAutomatically If {@code true}, the instance will render output frames to
|
||||||
|
* the {@linkplain #setOutputSurfaceInfo(SurfaceInfo) output surface} automatically as
|
||||||
|
* {@link VideoFrameProcessor} is done processing them. If {@code false}, the {@link
|
||||||
|
* VideoFrameProcessor} will block until {@link #renderOutputFrame(long)} is called, to
|
||||||
|
* render or drop the frame.
|
||||||
|
* @param listenerExecutor The {@link Executor} on which the {@code listener} is invoked.
|
||||||
|
* @param listener A {@link Listener}.
|
||||||
|
* @return A new instance.
|
||||||
|
* @throws VideoFrameProcessingException If a problem occurs while creating the {@link
|
||||||
|
* VideoFrameProcessor}.
|
||||||
|
*/
|
||||||
|
VideoFrameProcessor create(
|
||||||
|
Context context,
|
||||||
|
List<Effect> effects,
|
||||||
|
DebugViewProvider debugViewProvider,
|
||||||
|
ColorInfo inputColorInfo,
|
||||||
|
ColorInfo outputColorInfo,
|
||||||
|
boolean renderFramesAutomatically,
|
||||||
|
Executor listenerExecutor,
|
||||||
|
Listener listener)
|
||||||
|
throws VideoFrameProcessingException;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Listener for asynchronous frame processing events.
|
||||||
|
*
|
||||||
|
* <p>All listener methods must be called from the {@link Executor} passed in at {@linkplain
|
||||||
|
* Factory#create creation}.
|
||||||
|
*/
|
||||||
|
interface Listener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the output size changes.
|
||||||
|
*
|
||||||
|
* <p>The output size is the frame size in pixels after applying all {@linkplain Effect
|
||||||
|
* effects}.
|
||||||
|
*
|
||||||
|
* <p>The output size may differ from the size specified using {@link
|
||||||
|
* #setOutputSurfaceInfo(SurfaceInfo)}.
|
||||||
|
*/
|
||||||
|
void onOutputSizeChanged(int width, int height);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an output frame with the given {@code presentationTimeUs} becomes available for
|
||||||
|
* rendering.
|
||||||
|
*
|
||||||
|
* @param presentationTimeUs The presentation time of the frame, in microseconds.
|
||||||
|
*/
|
||||||
|
void onOutputFrameAvailableForRendering(long presentationTimeUs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when an exception occurs during asynchronous video frame processing.
|
||||||
|
*
|
||||||
|
* <p>If an error occurred, consuming and producing further frames will not work as expected and
|
||||||
|
* the {@link VideoFrameProcessor} should be released.
|
||||||
|
*/
|
||||||
|
void onError(VideoFrameProcessingException exception);
|
||||||
|
|
||||||
|
/** Called after the {@link VideoFrameProcessor} has rendered its final output frame. */
|
||||||
|
void onEnded();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates the frame should be rendered immediately after {@link #renderOutputFrame(long)} is
|
||||||
|
* invoked.
|
||||||
|
*/
|
||||||
|
long RENDER_OUTPUT_FRAME_IMMEDIATELY = -1;
|
||||||
|
|
||||||
|
/** Indicates the frame should be dropped after {@link #renderOutputFrame(long)} is invoked. */
|
||||||
|
long DROP_OUTPUT_FRAME = -2;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides an input {@link Bitmap} to the {@link VideoFrameProcessor}.
|
||||||
|
*
|
||||||
|
* <p>Can be called on any thread.
|
||||||
|
*
|
||||||
|
* @param inputBitmap The {@link Bitmap} queued to the {@code VideoFrameProcessor}.
|
||||||
|
* @param durationUs The duration for which to display the {@code inputBitmap}, in microseconds.
|
||||||
|
* @param frameRate The frame rate at which to display the {@code inputBitmap}, in frames per
|
||||||
|
* second.
|
||||||
|
* @throws UnsupportedOperationException If the {@code VideoFrameProcessor} does not accept
|
||||||
|
* {@linkplain #INPUT_TYPE_BITMAP bitmap input}.
|
||||||
|
*/
|
||||||
|
// TODO(b/262693274): Remove duration and frameRate parameters when EditedMediaItem can be
|
||||||
|
// signalled down to the processors.
|
||||||
|
void queueInputBitmap(Bitmap inputBitmap, long durationUs, float frameRate);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Provides an input texture ID to the {@code VideoFrameProcessor}.
|
||||||
|
*
|
||||||
|
* <p>It must be called after the {@link #setOnInputFrameProcessedListener
|
||||||
|
* onInputFrameProcessedListener} and the {@link #setInputFrameInfo frameInfo} have been set.
|
||||||
|
*
|
||||||
|
* <p>Can be called on any thread.
|
||||||
|
*
|
||||||
|
* @param textureId The ID of the texture queued to the {@code VideoFrameProcessor}.
|
||||||
|
* @param presentationTimeUs The presentation time of the queued texture, in microseconds.
|
||||||
|
*/
|
||||||
|
void queueInputTexture(int textureId, long presentationTimeUs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link OnInputFrameProcessedListener}.
|
||||||
|
*
|
||||||
|
* <p>Can be called on any thread.
|
||||||
|
*
|
||||||
|
* @param listener The {@link OnInputFrameProcessedListener}.
|
||||||
|
*/
|
||||||
|
void setOnInputFrameProcessedListener(OnInputFrameProcessedListener listener);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the input {@link Surface}, where {@link VideoFrameProcessor} consumes input frames
|
||||||
|
* from.
|
||||||
|
*
|
||||||
|
* <p>Can be called on any thread.
|
||||||
|
*
|
||||||
|
* @throws UnsupportedOperationException If the {@code VideoFrameProcessor} does not accept
|
||||||
|
* {@linkplain #INPUT_TYPE_SURFACE surface input}.
|
||||||
|
*/
|
||||||
|
Surface getInputSurface();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Informs the {@code VideoFrameProcessor} that a new input stream will be queued.
|
||||||
|
*
|
||||||
|
* <p>Call {@link #setInputFrameInfo} before this method if the {@link FrameInfo} of the new input
|
||||||
|
* stream differs from that of the current input stream.
|
||||||
|
*/
|
||||||
|
// TODO(b/274109008) Merge this and setInputFrameInfo.
|
||||||
|
void registerInputStream(@InputType int inputType);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets information about the input frames.
|
||||||
|
*
|
||||||
|
* <p>The new input information is applied from the next frame {@linkplain #registerInputFrame()
|
||||||
|
* registered} or {@linkplain #queueInputTexture} queued} onwards.
|
||||||
|
*
|
||||||
|
* <p>Pixels are expanded using the {@link FrameInfo#pixelWidthHeightRatio} so that the output
|
||||||
|
* frames' pixels have a ratio of 1.
|
||||||
|
*
|
||||||
|
* <p>Can be called on any thread.
|
||||||
|
*/
|
||||||
|
void setInputFrameInfo(FrameInfo inputFrameInfo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Informs the {@code VideoFrameProcessor} that a frame will be queued to its {@linkplain
|
||||||
|
* #getInputSurface() input surface}.
|
||||||
|
*
|
||||||
|
* <p>Must be called before rendering a frame to the input surface.
|
||||||
|
*
|
||||||
|
* <p>Can be called on any thread.
|
||||||
|
*
|
||||||
|
* @throws UnsupportedOperationException If the {@code VideoFrameProcessor} does not accept
|
||||||
|
* {@linkplain #INPUT_TYPE_SURFACE surface input}.
|
||||||
|
* @throws IllegalStateException If called after {@link #signalEndOfInput()} or before {@link
|
||||||
|
* #setInputFrameInfo(FrameInfo)}.
|
||||||
|
*/
|
||||||
|
void registerInputFrame();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of input frames that have been made available to the {@code
|
||||||
|
* VideoFrameProcessor} but have not been processed yet.
|
||||||
|
*
|
||||||
|
* <p>Can be called on any thread.
|
||||||
|
*/
|
||||||
|
int getPendingInputFrameCount();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the output surface and supporting information. When output frames are rendered and not
|
||||||
|
* dropped, they will be rendered to this output {@link SurfaceInfo}.
|
||||||
|
*
|
||||||
|
* <p>The new output {@link SurfaceInfo} is applied from the next output frame rendered onwards.
|
||||||
|
* If the output {@link SurfaceInfo} is {@code null}, the {@code VideoFrameProcessor} will stop
|
||||||
|
* rendering pending frames and resume rendering once a non-null {@link SurfaceInfo} is set.
|
||||||
|
*
|
||||||
|
* <p>If the dimensions given in {@link SurfaceInfo} do not match the {@linkplain
|
||||||
|
* Listener#onOutputSizeChanged(int,int) output size after applying the final effect} the frames
|
||||||
|
* are resized before rendering to the surface and letter/pillar-boxing is applied.
|
||||||
|
*
|
||||||
|
* <p>The caller is responsible for tracking the lifecycle of the {@link SurfaceInfo#surface}
|
||||||
|
* including calling this method with a new surface if it is destroyed. When this method returns,
|
||||||
|
* the previous output surface is no longer being used and can safely be released by the caller.
|
||||||
|
*/
|
||||||
|
void setOutputSurfaceInfo(@Nullable SurfaceInfo outputSurfaceInfo);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Renders the oldest unrendered output frame that has become {@linkplain
|
||||||
|
* Listener#onOutputFrameAvailableForRendering(long) available for rendering} at the given {@code
|
||||||
|
* renderTimeNs}.
|
||||||
|
*
|
||||||
|
* <p>This will either render the output frame to the {@linkplain #setOutputSurfaceInfo output
|
||||||
|
* surface}, or drop the frame, per {@code renderTimeNs}.
|
||||||
|
*
|
||||||
|
* <p>This method must only be called if {@code renderFramesAutomatically} was set to {@code
|
||||||
|
* false} using the {@link Factory} and should be called exactly once for each frame that becomes
|
||||||
|
* {@linkplain Listener#onOutputFrameAvailableForRendering(long) available for rendering}.
|
||||||
|
*
|
||||||
|
* <p>The {@code renderTimeNs} may be passed to {@link EGLExt#eglPresentationTimeANDROID}
|
||||||
|
* depending on the implementation.
|
||||||
|
*
|
||||||
|
* @param renderTimeNs The render time to use for the frame, in nanoseconds. The render time can
|
||||||
|
* be before or after the current system time. Use {@link #DROP_OUTPUT_FRAME} to drop the
|
||||||
|
* frame, or {@link #RENDER_OUTPUT_FRAME_IMMEDIATELY} to render the frame immediately.
|
||||||
|
*/
|
||||||
|
void renderOutputFrame(long renderTimeNs);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Informs the {@code VideoFrameProcessor} that no further input frames should be accepted.
|
||||||
|
*
|
||||||
|
* <p>Can be called on any thread.
|
||||||
|
*
|
||||||
|
* @throws IllegalStateException If called more than once.
|
||||||
|
*/
|
||||||
|
void signalEndOfInput();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Flushes the {@code VideoFrameProcessor}.
|
||||||
|
*
|
||||||
|
* <p>All the frames that are {@linkplain #registerInputFrame() registered} prior to calling this
|
||||||
|
* method are no longer considered to be registered when this method returns.
|
||||||
|
*
|
||||||
|
* <p>{@link Listener} methods invoked prior to calling this method should be ignored.
|
||||||
|
*
|
||||||
|
* @throws UnsupportedOperationException If the {@code VideoFrameProcessor} does not accept
|
||||||
|
* {@linkplain #INPUT_TYPE_SURFACE surface input}.
|
||||||
|
*/
|
||||||
|
void flush();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Releases all resources.
|
||||||
|
*
|
||||||
|
* <p>If the {@code VideoFrameProcessor} is released before it has {@linkplain Listener#onEnded()
|
||||||
|
* ended}, it will attempt to cancel processing any input frames that have already become
|
||||||
|
* available. Input frames that become available after release are ignored.
|
||||||
|
*
|
||||||
|
* <p>This method blocks until all resources are released or releasing times out.
|
||||||
|
*
|
||||||
|
* <p>Can be called on any thread.
|
||||||
|
*/
|
||||||
|
void release();
|
||||||
|
}
|
@ -77,7 +77,7 @@ public final class VideoSize implements Bundleable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a VideoSize.
|
* Creates a new instance.
|
||||||
*
|
*
|
||||||
* @param width The video width in pixels.
|
* @param width The video width in pixels.
|
||||||
* @param height The video height in pixels.
|
* @param height The video height in pixels.
|
||||||
|
@ -0,0 +1,337 @@
|
|||||||
|
/*
|
||||||
|
* 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.common.audio;
|
||||||
|
|
||||||
|
import static androidx.media3.common.audio.AudioProcessor.EMPTY_BUFFER;
|
||||||
|
import static androidx.media3.common.util.Assertions.checkState;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.media3.common.audio.AudioProcessor.AudioFormat;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Handles passing buffers through multiple {@link AudioProcessor} instances.
|
||||||
|
*
|
||||||
|
* <p>Two instances of {@link AudioProcessingPipeline} are considered {@linkplain #equals(Object)
|
||||||
|
* equal} if they have the same underlying {@link AudioProcessor} references, in the same order.
|
||||||
|
*
|
||||||
|
* <p>To make use of this class, the caller must:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>Initialize an instance, passing in all audio processors that may be used for processing.
|
||||||
|
* <li>Call {@link #configure(AudioFormat)} with the {@link AudioFormat} of the input data. This
|
||||||
|
* method will give back the {@link AudioFormat} that will be output from the pipeline when
|
||||||
|
* this configuration is in use.
|
||||||
|
* <li>Call {@link #flush()} to apply the pending configuration.
|
||||||
|
* <li>Check if the pipeline {@link #isOperational()}. If not, then the pipeline can not be used
|
||||||
|
* to process buffers in the current configuration. This is because none of the underlying
|
||||||
|
* {@link AudioProcessor} instances are {@linkplain AudioProcessor#isActive active}.
|
||||||
|
* <li>If the pipeline {@link #isOperational()}, {@link #queueInput(ByteBuffer)} then {@link
|
||||||
|
* #getOutput()} to process buffers.
|
||||||
|
* <li>{@link #queueEndOfStream()} to inform the pipeline the current input stream is at an end.
|
||||||
|
* <li>Repeatedly call {@link #getOutput()} and handle those buffers until {@link #isEnded()}
|
||||||
|
* returns true.
|
||||||
|
* <li>When finished with the pipeline, call {@link #reset()} to release underlying resources.
|
||||||
|
* </ul>
|
||||||
|
*
|
||||||
|
* <p>If underlying {@link AudioProcessor} instances have pending configuration changes, or the
|
||||||
|
* {@link AudioFormat} of the input is changing:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>Call {@link #configure(AudioFormat)} to configure the pipeline for the new input stream.
|
||||||
|
* You can still {@link #queueInput(ByteBuffer)} and {@link #getOutput()} in the old setup at
|
||||||
|
* this time.
|
||||||
|
* <li>{@link #queueEndOfStream()} to inform the pipeline the current input stream is at an end.
|
||||||
|
* <li>Repeatedly call {@link #getOutput()} until {@link #isEnded()} returns true.
|
||||||
|
* <li>Call {@link #flush()} to apply the new configuration and flush the pipeline.
|
||||||
|
* <li>Begin {@linkplain #queueInput(ByteBuffer) queuing input} and handling the {@linkplain
|
||||||
|
* #getOutput() output} in the new configuration.
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
@UnstableApi
|
||||||
|
public final class AudioProcessingPipeline {
|
||||||
|
|
||||||
|
/** The {@link AudioProcessor} instances passed to {@link AudioProcessingPipeline}. */
|
||||||
|
private final ImmutableList<AudioProcessor> audioProcessors;
|
||||||
|
/**
|
||||||
|
* The processors that are {@linkplain AudioProcessor#isActive() active} based on the current
|
||||||
|
* configuration.
|
||||||
|
*/
|
||||||
|
private final List<AudioProcessor> activeAudioProcessors;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The buffers output by the {@link #activeAudioProcessors}. This has the same number of elements
|
||||||
|
* as {@link #activeAudioProcessors}.
|
||||||
|
*/
|
||||||
|
private ByteBuffer[] outputBuffers;
|
||||||
|
/** The {@link AudioFormat} currently being output by the pipeline. */
|
||||||
|
private AudioFormat outputAudioFormat;
|
||||||
|
/** The {@link AudioFormat} that will be output following a {@link #flush()}. */
|
||||||
|
private AudioFormat pendingOutputAudioFormat;
|
||||||
|
/** Whether input has ended, either due to configuration change or end of stream. */
|
||||||
|
private boolean inputEnded;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates an instance.
|
||||||
|
*
|
||||||
|
* @param audioProcessors The {@link AudioProcessor} instances to be used for processing buffers.
|
||||||
|
*/
|
||||||
|
public AudioProcessingPipeline(ImmutableList<AudioProcessor> audioProcessors) {
|
||||||
|
this.audioProcessors = audioProcessors;
|
||||||
|
activeAudioProcessors = new ArrayList<>();
|
||||||
|
outputBuffers = new ByteBuffer[0];
|
||||||
|
outputAudioFormat = AudioFormat.NOT_SET;
|
||||||
|
pendingOutputAudioFormat = AudioFormat.NOT_SET;
|
||||||
|
inputEnded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Configures the pipeline to process input audio with the specified format. Returns the
|
||||||
|
* configured output audio format.
|
||||||
|
*
|
||||||
|
* <p>To apply the new configuration for use, the pipeline must be {@linkplain #flush() flushed}.
|
||||||
|
* Before applying the new configuration, it is safe to queue input and get output in the old
|
||||||
|
* input/output formats/configuration. Call {@link #queueEndOfStream()} when no more input will be
|
||||||
|
* supplied for processing in the old configuration.
|
||||||
|
*
|
||||||
|
* @param inputAudioFormat The format of audio that will be queued after the next call to {@link
|
||||||
|
* #flush()}.
|
||||||
|
* @return The configured output audio format.
|
||||||
|
* @throws AudioProcessor.UnhandledAudioFormatException If the specified format is not supported
|
||||||
|
* by the pipeline.
|
||||||
|
*/
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public AudioFormat configure(AudioFormat inputAudioFormat)
|
||||||
|
throws AudioProcessor.UnhandledAudioFormatException {
|
||||||
|
if (inputAudioFormat.equals(AudioFormat.NOT_SET)) {
|
||||||
|
throw new AudioProcessor.UnhandledAudioFormatException(inputAudioFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioFormat intermediateAudioFormat = inputAudioFormat;
|
||||||
|
|
||||||
|
for (int i = 0; i < audioProcessors.size(); i++) {
|
||||||
|
AudioProcessor audioProcessor = audioProcessors.get(i);
|
||||||
|
AudioFormat nextFormat = audioProcessor.configure(intermediateAudioFormat);
|
||||||
|
if (audioProcessor.isActive()) {
|
||||||
|
checkState(!nextFormat.equals(AudioFormat.NOT_SET));
|
||||||
|
intermediateAudioFormat = nextFormat;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return pendingOutputAudioFormat = intermediateAudioFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Clears any buffered data and pending output. If any underlying audio processors are {@linkplain
|
||||||
|
* AudioProcessor#isActive() active}, this also prepares them to receive a new stream of input in
|
||||||
|
* the last {@linkplain #configure(AudioFormat) configured} (pending) format.
|
||||||
|
*
|
||||||
|
* <p>{@link #configure(AudioFormat)} must have been called at least once since the last call to
|
||||||
|
* {@link #reset()} before calling this.
|
||||||
|
*/
|
||||||
|
public void flush() {
|
||||||
|
activeAudioProcessors.clear();
|
||||||
|
outputAudioFormat = pendingOutputAudioFormat;
|
||||||
|
inputEnded = false;
|
||||||
|
|
||||||
|
for (int i = 0; i < audioProcessors.size(); i++) {
|
||||||
|
AudioProcessor audioProcessor = audioProcessors.get(i);
|
||||||
|
audioProcessor.flush();
|
||||||
|
if (audioProcessor.isActive()) {
|
||||||
|
activeAudioProcessors.add(audioProcessor);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
outputBuffers = new ByteBuffer[activeAudioProcessors.size()];
|
||||||
|
for (int i = 0; i <= getFinalOutputBufferIndex(); i++) {
|
||||||
|
outputBuffers[i] = activeAudioProcessors.get(i).getOutput();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the {@link AudioFormat} currently being output. */
|
||||||
|
public AudioFormat getOutputAudioFormat() {
|
||||||
|
return outputAudioFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Whether the pipeline can be used for processing buffers.
|
||||||
|
*
|
||||||
|
* <p>For this to happen the pipeline must be {@linkplain #configure(AudioFormat) configured},
|
||||||
|
* {@linkplain #flush() flushed} and have {@linkplain AudioProcessor#isActive() active}
|
||||||
|
* {@linkplain AudioProcessor underlying audio processors} that are ready to process buffers with
|
||||||
|
* the current configuration.
|
||||||
|
*/
|
||||||
|
public boolean isOperational() {
|
||||||
|
return !activeAudioProcessors.isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queues audio data between the position and limit of the {@code inputBuffer} for processing.
|
||||||
|
* After calling this method, processed output may be available via {@link #getOutput()}.
|
||||||
|
*
|
||||||
|
* @param inputBuffer The input buffer to process. It must be a direct {@link ByteBuffer} with
|
||||||
|
* native byte order. Its contents are treated as read-only. Its position will be advanced by
|
||||||
|
* the number of bytes consumed (which may be zero). The caller retains ownership of the
|
||||||
|
* provided buffer.
|
||||||
|
*/
|
||||||
|
public void queueInput(ByteBuffer inputBuffer) {
|
||||||
|
if (!isOperational() || inputEnded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
processData(inputBuffer);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link ByteBuffer} containing processed output data between its position and limit.
|
||||||
|
* The buffer will be empty if no output is available.
|
||||||
|
*
|
||||||
|
* <p>Buffers returned from this method are retained by pipeline, and it is necessary to consume
|
||||||
|
* the data (or copy it into another buffer) to allow the pipeline to progress.
|
||||||
|
*
|
||||||
|
* @return A buffer containing processed output data between its position and limit.
|
||||||
|
*/
|
||||||
|
public ByteBuffer getOutput() {
|
||||||
|
if (!isOperational()) {
|
||||||
|
return EMPTY_BUFFER;
|
||||||
|
}
|
||||||
|
ByteBuffer outputBuffer = outputBuffers[getFinalOutputBufferIndex()];
|
||||||
|
if (!outputBuffer.hasRemaining()) {
|
||||||
|
processData(EMPTY_BUFFER);
|
||||||
|
}
|
||||||
|
return outputBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Queues an end of stream signal. After this method has been called, {@link
|
||||||
|
* #queueInput(ByteBuffer)} should not be called until after the next call to {@link #flush()}.
|
||||||
|
* Calling {@link #getOutput()} will return any remaining output data. Multiple calls may be
|
||||||
|
* required to read all of the remaining output data. {@link #isEnded()} will return {@code true}
|
||||||
|
* once all remaining output data has been read.
|
||||||
|
*/
|
||||||
|
public void queueEndOfStream() {
|
||||||
|
if (!isOperational() || inputEnded) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
inputEnded = true;
|
||||||
|
activeAudioProcessors.get(0).queueEndOfStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether the pipeline has ended.
|
||||||
|
*
|
||||||
|
* <p>The pipeline is considered ended when:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>End of stream has been {@linkplain #queueEndOfStream() queued}.
|
||||||
|
* <li>Every {@linkplain #queueInput(ByteBuffer) input buffer} has been processed.
|
||||||
|
* <li>Every {@linkplain #getOutput() output buffer} has been fully consumed.
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
public boolean isEnded() {
|
||||||
|
return inputEnded
|
||||||
|
&& activeAudioProcessors.get(getFinalOutputBufferIndex()).isEnded()
|
||||||
|
&& !outputBuffers[getFinalOutputBufferIndex()].hasRemaining();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Resets the pipeline and its underlying {@link AudioProcessor} instances to their unconfigured
|
||||||
|
* state, releasing any resources.
|
||||||
|
*/
|
||||||
|
public void reset() {
|
||||||
|
for (int i = 0; i < audioProcessors.size(); i++) {
|
||||||
|
AudioProcessor audioProcessor = audioProcessors.get(i);
|
||||||
|
audioProcessor.flush();
|
||||||
|
audioProcessor.reset();
|
||||||
|
}
|
||||||
|
outputBuffers = new ByteBuffer[0];
|
||||||
|
outputAudioFormat = AudioFormat.NOT_SET;
|
||||||
|
pendingOutputAudioFormat = AudioFormat.NOT_SET;
|
||||||
|
inputEnded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Indicates whether some other object is "equal to" this one.
|
||||||
|
*
|
||||||
|
* <p>Two instances of {@link AudioProcessingPipeline} are considered equal if they have the same
|
||||||
|
* underlying {@link AudioProcessor} references in the same order.
|
||||||
|
*/
|
||||||
|
@Override
|
||||||
|
public boolean equals(@Nullable Object o) {
|
||||||
|
if (this == o) {
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
if (!(o instanceof AudioProcessingPipeline)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
AudioProcessingPipeline that = (AudioProcessingPipeline) o;
|
||||||
|
if (this.audioProcessors.size() != that.audioProcessors.size()) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
for (int i = 0; i < this.audioProcessors.size(); i++) {
|
||||||
|
if (this.audioProcessors.get(i) != that.audioProcessors.get(i)) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public int hashCode() {
|
||||||
|
return audioProcessors.hashCode();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void processData(ByteBuffer inputBuffer) {
|
||||||
|
boolean progressMade = true;
|
||||||
|
while (progressMade) {
|
||||||
|
progressMade = false;
|
||||||
|
for (int index = 0; index <= getFinalOutputBufferIndex(); index++) {
|
||||||
|
if (outputBuffers[index].hasRemaining()) {
|
||||||
|
// Processor at this index has output that has not been consumed. Do not queue input.
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
AudioProcessor audioProcessor = activeAudioProcessors.get(index);
|
||||||
|
|
||||||
|
if (audioProcessor.isEnded()) {
|
||||||
|
if (!outputBuffers[index].hasRemaining() && index < getFinalOutputBufferIndex()) {
|
||||||
|
activeAudioProcessors.get(index + 1).queueEndOfStream();
|
||||||
|
}
|
||||||
|
continue;
|
||||||
|
}
|
||||||
|
|
||||||
|
ByteBuffer input =
|
||||||
|
index > 0
|
||||||
|
? outputBuffers[index - 1]
|
||||||
|
: inputBuffer.hasRemaining() ? inputBuffer : EMPTY_BUFFER;
|
||||||
|
long inputBytes = input.remaining();
|
||||||
|
audioProcessor.queueInput(input);
|
||||||
|
outputBuffers[index] = audioProcessor.getOutput();
|
||||||
|
|
||||||
|
progressMade |= (inputBytes - input.remaining()) > 0 || outputBuffers[index].hasRemaining();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private int getFinalOutputBufferIndex() {
|
||||||
|
return outputBuffers.length - 1;
|
||||||
|
}
|
||||||
|
}
|
@ -37,6 +37,13 @@ public interface AudioProcessor {
|
|||||||
|
|
||||||
/** PCM audio format that may be handled by an audio processor. */
|
/** PCM audio format that may be handled by an audio processor. */
|
||||||
final class AudioFormat {
|
final class AudioFormat {
|
||||||
|
/**
|
||||||
|
* An {@link AudioFormat} instance to represent an unset {@link AudioFormat}. This should not be
|
||||||
|
* returned by {@link #configure(AudioFormat)} if the processor {@link #isActive()}.
|
||||||
|
*
|
||||||
|
* <p>Typically used to represent an inactive {@link AudioProcessor} {@linkplain
|
||||||
|
* #configure(AudioFormat) output format}.
|
||||||
|
*/
|
||||||
public static final AudioFormat NOT_SET =
|
public static final AudioFormat NOT_SET =
|
||||||
new AudioFormat(
|
new AudioFormat(
|
||||||
/* sampleRate= */ Format.NO_VALUE,
|
/* sampleRate= */ Format.NO_VALUE,
|
||||||
@ -94,11 +101,15 @@ public interface AudioProcessor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Exception thrown when a processor can't be configured for a given input audio format. */
|
/** Exception thrown when the given {@link AudioFormat} can not be handled. */
|
||||||
final class UnhandledAudioFormatException extends Exception {
|
final class UnhandledAudioFormatException extends Exception {
|
||||||
|
|
||||||
public UnhandledAudioFormatException(AudioFormat inputAudioFormat) {
|
public UnhandledAudioFormatException(AudioFormat inputAudioFormat) {
|
||||||
super("Unhandled format: " + inputAudioFormat);
|
this("Unhandled input format:", inputAudioFormat);
|
||||||
|
}
|
||||||
|
|
||||||
|
public UnhandledAudioFormatException(String message, AudioFormat audioFormat) {
|
||||||
|
super(message + " " + audioFormat);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -0,0 +1,148 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2019 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.audio;
|
||||||
|
|
||||||
|
import androidx.annotation.CallSuper;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Base class for audio processors that keep an output buffer and an internal buffer that is reused
|
||||||
|
* whenever input is queued. Subclasses should override {@link #onConfigure(AudioFormat)} to return
|
||||||
|
* the output audio format for the processor if it's active.
|
||||||
|
*/
|
||||||
|
@UnstableApi
|
||||||
|
public abstract class BaseAudioProcessor implements AudioProcessor {
|
||||||
|
|
||||||
|
/** The current input audio format. */
|
||||||
|
protected AudioFormat inputAudioFormat;
|
||||||
|
/** The current output audio format. */
|
||||||
|
protected AudioFormat outputAudioFormat;
|
||||||
|
|
||||||
|
private AudioFormat pendingInputAudioFormat;
|
||||||
|
private AudioFormat pendingOutputAudioFormat;
|
||||||
|
private ByteBuffer buffer;
|
||||||
|
private ByteBuffer outputBuffer;
|
||||||
|
private boolean inputEnded;
|
||||||
|
|
||||||
|
public BaseAudioProcessor() {
|
||||||
|
buffer = EMPTY_BUFFER;
|
||||||
|
outputBuffer = EMPTY_BUFFER;
|
||||||
|
pendingInputAudioFormat = AudioFormat.NOT_SET;
|
||||||
|
pendingOutputAudioFormat = AudioFormat.NOT_SET;
|
||||||
|
inputAudioFormat = AudioFormat.NOT_SET;
|
||||||
|
outputAudioFormat = AudioFormat.NOT_SET;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public final AudioFormat configure(AudioFormat inputAudioFormat)
|
||||||
|
throws UnhandledAudioFormatException {
|
||||||
|
pendingInputAudioFormat = inputAudioFormat;
|
||||||
|
pendingOutputAudioFormat = onConfigure(inputAudioFormat);
|
||||||
|
return isActive() ? pendingOutputAudioFormat : AudioFormat.NOT_SET;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public boolean isActive() {
|
||||||
|
return pendingOutputAudioFormat != AudioFormat.NOT_SET;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void queueEndOfStream() {
|
||||||
|
inputEnded = true;
|
||||||
|
onQueueEndOfStream();
|
||||||
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
@Override
|
||||||
|
public ByteBuffer getOutput() {
|
||||||
|
ByteBuffer outputBuffer = this.outputBuffer;
|
||||||
|
this.outputBuffer = EMPTY_BUFFER;
|
||||||
|
return outputBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@CallSuper
|
||||||
|
@SuppressWarnings("ReferenceEquality")
|
||||||
|
@Override
|
||||||
|
public boolean isEnded() {
|
||||||
|
return inputEnded && outputBuffer == EMPTY_BUFFER;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void flush() {
|
||||||
|
outputBuffer = EMPTY_BUFFER;
|
||||||
|
inputEnded = false;
|
||||||
|
inputAudioFormat = pendingInputAudioFormat;
|
||||||
|
outputAudioFormat = pendingOutputAudioFormat;
|
||||||
|
onFlush();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void reset() {
|
||||||
|
flush();
|
||||||
|
buffer = EMPTY_BUFFER;
|
||||||
|
pendingInputAudioFormat = AudioFormat.NOT_SET;
|
||||||
|
pendingOutputAudioFormat = AudioFormat.NOT_SET;
|
||||||
|
inputAudioFormat = AudioFormat.NOT_SET;
|
||||||
|
outputAudioFormat = AudioFormat.NOT_SET;
|
||||||
|
onReset();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Replaces the current output buffer with a buffer of at least {@code size} bytes and returns it.
|
||||||
|
* Callers should write to the returned buffer then {@link ByteBuffer#flip()} it so it can be read
|
||||||
|
* via {@link #getOutput()}.
|
||||||
|
*/
|
||||||
|
protected final ByteBuffer replaceOutputBuffer(int size) {
|
||||||
|
if (buffer.capacity() < size) {
|
||||||
|
buffer = ByteBuffer.allocateDirect(size).order(ByteOrder.nativeOrder());
|
||||||
|
} else {
|
||||||
|
buffer.clear();
|
||||||
|
}
|
||||||
|
outputBuffer = buffer;
|
||||||
|
return buffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns whether the current output buffer has any data remaining. */
|
||||||
|
protected final boolean hasPendingOutput() {
|
||||||
|
return outputBuffer.hasRemaining();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Called when the processor is configured for a new input format. */
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
protected AudioFormat onConfigure(AudioFormat inputAudioFormat)
|
||||||
|
throws UnhandledAudioFormatException {
|
||||||
|
return AudioFormat.NOT_SET;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Called when the end-of-stream is queued to the processor. */
|
||||||
|
protected void onQueueEndOfStream() {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Called when the processor is flushed, directly or as part of resetting. */
|
||||||
|
protected void onFlush() {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Called when the processor is reset. */
|
||||||
|
protected void onReset() {
|
||||||
|
// Do nothing.
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,110 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* http://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
package androidx.media3.common.audio;
|
||||||
|
|
||||||
|
import static androidx.media3.common.util.Assertions.checkStateNotNull;
|
||||||
|
|
||||||
|
import android.util.SparseArray;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.media3.common.C;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import androidx.media3.common.util.Util;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link AudioProcessor} that handles mixing and scaling audio channels. Call {@link
|
||||||
|
* #putChannelMixingMatrix(ChannelMixingMatrix)} specifying mixing matrices to apply for each
|
||||||
|
* possible input channel count before using the audio processor. Input and output are 16-bit PCM.
|
||||||
|
*/
|
||||||
|
@UnstableApi
|
||||||
|
public final class ChannelMixingAudioProcessor extends BaseAudioProcessor {
|
||||||
|
|
||||||
|
private final SparseArray<ChannelMixingMatrix> matrixByInputChannelCount;
|
||||||
|
|
||||||
|
/** Creates a new audio processor for mixing and scaling audio channels. */
|
||||||
|
public ChannelMixingAudioProcessor() {
|
||||||
|
matrixByInputChannelCount = new SparseArray<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Stores a channel mixing matrix for processing audio with a given {@link
|
||||||
|
* ChannelMixingMatrix#getInputChannelCount() channel count}. Overwrites any previously stored
|
||||||
|
* matrix for the same input channel count.
|
||||||
|
*/
|
||||||
|
public void putChannelMixingMatrix(ChannelMixingMatrix matrix) {
|
||||||
|
int inputChannelCount = matrix.getInputChannelCount();
|
||||||
|
matrixByInputChannelCount.put(inputChannelCount, matrix);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected AudioFormat onConfigure(AudioFormat inputAudioFormat)
|
||||||
|
throws UnhandledAudioFormatException {
|
||||||
|
if (inputAudioFormat.encoding != C.ENCODING_PCM_16BIT) {
|
||||||
|
throw new UnhandledAudioFormatException(inputAudioFormat);
|
||||||
|
}
|
||||||
|
@Nullable
|
||||||
|
ChannelMixingMatrix channelMixingMatrix =
|
||||||
|
matrixByInputChannelCount.get(inputAudioFormat.channelCount);
|
||||||
|
if (channelMixingMatrix == null) {
|
||||||
|
throw new UnhandledAudioFormatException(
|
||||||
|
"No mixing matrix for input channel count", inputAudioFormat);
|
||||||
|
}
|
||||||
|
if (channelMixingMatrix.isIdentity()) {
|
||||||
|
return AudioFormat.NOT_SET;
|
||||||
|
}
|
||||||
|
return new AudioFormat(
|
||||||
|
inputAudioFormat.sampleRate,
|
||||||
|
channelMixingMatrix.getOutputChannelCount(),
|
||||||
|
C.ENCODING_PCM_16BIT);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void queueInput(ByteBuffer inputBuffer) {
|
||||||
|
ChannelMixingMatrix channelMixingMatrix =
|
||||||
|
checkStateNotNull(matrixByInputChannelCount.get(inputAudioFormat.channelCount));
|
||||||
|
|
||||||
|
int inputFramesToMix = inputBuffer.remaining() / inputAudioFormat.bytesPerFrame;
|
||||||
|
ByteBuffer outputBuffer =
|
||||||
|
replaceOutputBuffer(inputFramesToMix * outputAudioFormat.bytesPerFrame);
|
||||||
|
int inputChannelCount = channelMixingMatrix.getInputChannelCount();
|
||||||
|
int outputChannelCount = channelMixingMatrix.getOutputChannelCount();
|
||||||
|
float[] outputFrame = new float[outputChannelCount];
|
||||||
|
while (inputBuffer.hasRemaining()) {
|
||||||
|
for (int inputChannelIndex = 0; inputChannelIndex < inputChannelCount; inputChannelIndex++) {
|
||||||
|
short inputValue = inputBuffer.getShort();
|
||||||
|
for (int outputChannelIndex = 0;
|
||||||
|
outputChannelIndex < outputChannelCount;
|
||||||
|
outputChannelIndex++) {
|
||||||
|
outputFrame[outputChannelIndex] +=
|
||||||
|
channelMixingMatrix.getMixingCoefficient(inputChannelIndex, outputChannelIndex)
|
||||||
|
* inputValue;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
for (int outputChannelIndex = 0;
|
||||||
|
outputChannelIndex < outputChannelCount;
|
||||||
|
outputChannelIndex++) {
|
||||||
|
short shortValue =
|
||||||
|
(short)
|
||||||
|
Util.constrainValue(
|
||||||
|
outputFrame[outputChannelIndex], Short.MIN_VALUE, Short.MAX_VALUE);
|
||||||
|
outputBuffer.put((byte) (shortValue & 0xFF));
|
||||||
|
outputBuffer.put((byte) ((shortValue >> 8) & 0xFF));
|
||||||
|
outputFrame[outputChannelIndex] = 0;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outputBuffer.flip();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,194 @@
|
|||||||
|
/*
|
||||||
|
* 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.common.audio;
|
||||||
|
|
||||||
|
import static androidx.media3.common.util.Assertions.checkArgument;
|
||||||
|
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An immutable matrix that describes the mapping of input channels to output channels.
|
||||||
|
*
|
||||||
|
* <p>The matrix coefficients define the scaling factor to use when mixing samples from the input
|
||||||
|
* channel (row) to the output channel (column).
|
||||||
|
*
|
||||||
|
* <p>Examples:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>Stereo to mono with each channel at half volume:
|
||||||
|
* <pre>
|
||||||
|
* [0.5 0.5]</pre>
|
||||||
|
* <li>Stereo to stereo with no mixing or scaling:
|
||||||
|
* <pre>
|
||||||
|
* [1 0
|
||||||
|
* 0 1]</pre>
|
||||||
|
* <li>Stereo to stereo with 0.7 volume:
|
||||||
|
* <pre>
|
||||||
|
* [0.7 0
|
||||||
|
* 0 0.7]</pre>
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
|
@UnstableApi
|
||||||
|
public final class ChannelMixingMatrix {
|
||||||
|
private final int inputChannelCount;
|
||||||
|
private final int outputChannelCount;
|
||||||
|
private final float[] coefficients;
|
||||||
|
private final boolean isZero;
|
||||||
|
private final boolean isDiagonal;
|
||||||
|
private final boolean isIdentity;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a standard channel mixing matrix that converts from {@code inputChannelCount} channels
|
||||||
|
* to {@code outputChannelCount} channels.
|
||||||
|
*
|
||||||
|
* <p>If the input and output channel counts match then a simple identity matrix will be returned.
|
||||||
|
* Otherwise, default matrix coefficients will be used to best match channel locations and overall
|
||||||
|
* power level.
|
||||||
|
*
|
||||||
|
* @param inputChannelCount Number of input channels.
|
||||||
|
* @param outputChannelCount Number of output channels.
|
||||||
|
* @return New channel mixing matrix.
|
||||||
|
* @throws UnsupportedOperationException If no default matrix coefficients are implemented for the
|
||||||
|
* given input and output channel counts.
|
||||||
|
*/
|
||||||
|
public static ChannelMixingMatrix create(int inputChannelCount, int outputChannelCount) {
|
||||||
|
return new ChannelMixingMatrix(
|
||||||
|
inputChannelCount,
|
||||||
|
outputChannelCount,
|
||||||
|
createMixingCoefficients(inputChannelCount, outputChannelCount));
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates a matrix with the given coefficients in row-major order.
|
||||||
|
*
|
||||||
|
* @param inputChannelCount Number of input channels (rows in the matrix).
|
||||||
|
* @param outputChannelCount Number of output channels (columns in the matrix).
|
||||||
|
* @param coefficients Non-negative matrix coefficients in row-major order.
|
||||||
|
*/
|
||||||
|
public ChannelMixingMatrix(int inputChannelCount, int outputChannelCount, float[] coefficients) {
|
||||||
|
checkArgument(inputChannelCount > 0, "Input channel count must be positive.");
|
||||||
|
checkArgument(outputChannelCount > 0, "Output channel count must be positive.");
|
||||||
|
checkArgument(
|
||||||
|
coefficients.length == inputChannelCount * outputChannelCount,
|
||||||
|
"Coefficient array length is invalid.");
|
||||||
|
this.inputChannelCount = inputChannelCount;
|
||||||
|
this.outputChannelCount = outputChannelCount;
|
||||||
|
this.coefficients = checkCoefficientsValid(coefficients);
|
||||||
|
|
||||||
|
// Calculate matrix properties.
|
||||||
|
boolean allDiagonalCoefficientsAreOne = true;
|
||||||
|
boolean allCoefficientsAreZero = true;
|
||||||
|
boolean allNonDiagonalCoefficientsAreZero = true;
|
||||||
|
for (int row = 0; row < inputChannelCount; row++) {
|
||||||
|
for (int col = 0; col < outputChannelCount; col++) {
|
||||||
|
float coefficient = getMixingCoefficient(row, col);
|
||||||
|
boolean onDiagonal = row == col;
|
||||||
|
|
||||||
|
if (coefficient != 1f && onDiagonal) {
|
||||||
|
allDiagonalCoefficientsAreOne = false;
|
||||||
|
}
|
||||||
|
if (coefficient != 0f) {
|
||||||
|
allCoefficientsAreZero = false;
|
||||||
|
if (!onDiagonal) {
|
||||||
|
allNonDiagonalCoefficientsAreZero = false;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
isZero = allCoefficientsAreZero;
|
||||||
|
isDiagonal = isSquare() && allNonDiagonalCoefficientsAreZero;
|
||||||
|
isIdentity = isDiagonal && allDiagonalCoefficientsAreOne;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getInputChannelCount() {
|
||||||
|
return inputChannelCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
public int getOutputChannelCount() {
|
||||||
|
return outputChannelCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Gets the scaling factor for the given input and output channel. */
|
||||||
|
public float getMixingCoefficient(int inputChannel, int outputChannel) {
|
||||||
|
return coefficients[inputChannel * outputChannelCount + outputChannel];
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns whether all mixing coefficients are zero. */
|
||||||
|
public boolean isZero() {
|
||||||
|
return isZero;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns whether the input and output channel count is the same. */
|
||||||
|
public boolean isSquare() {
|
||||||
|
return inputChannelCount == outputChannelCount;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns whether the matrix is square and all non-diagonal coefficients are zero. */
|
||||||
|
public boolean isDiagonal() {
|
||||||
|
return isDiagonal;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns whether this is an identity matrix. */
|
||||||
|
public boolean isIdentity() {
|
||||||
|
return isIdentity;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns a new matrix with the given scaling factor applied to all coefficients. */
|
||||||
|
public ChannelMixingMatrix scaleBy(float scale) {
|
||||||
|
float[] scaledCoefficients = new float[coefficients.length];
|
||||||
|
for (int i = 0; i < coefficients.length; i++) {
|
||||||
|
scaledCoefficients[i] = scale * coefficients[i];
|
||||||
|
}
|
||||||
|
return new ChannelMixingMatrix(inputChannelCount, outputChannelCount, scaledCoefficients);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float[] createMixingCoefficients(int inputChannelCount, int outputChannelCount) {
|
||||||
|
if (inputChannelCount == outputChannelCount) {
|
||||||
|
return initializeIdentityMatrix(outputChannelCount);
|
||||||
|
}
|
||||||
|
if (inputChannelCount == 1 && outputChannelCount == 2) {
|
||||||
|
// Mono -> stereo.
|
||||||
|
return new float[] {1f, 1f};
|
||||||
|
}
|
||||||
|
if (inputChannelCount == 2 && outputChannelCount == 1) {
|
||||||
|
// Stereo -> mono.
|
||||||
|
return new float[] {0.5f, 0.5f};
|
||||||
|
}
|
||||||
|
throw new UnsupportedOperationException(
|
||||||
|
"Default channel mixing coefficients for "
|
||||||
|
+ inputChannelCount
|
||||||
|
+ "->"
|
||||||
|
+ outputChannelCount
|
||||||
|
+ " are not yet implemented.");
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float[] initializeIdentityMatrix(int channelCount) {
|
||||||
|
float[] coefficients = new float[channelCount * channelCount];
|
||||||
|
for (int c = 0; c < channelCount; c++) {
|
||||||
|
coefficients[channelCount * c + c] = 1f;
|
||||||
|
}
|
||||||
|
return coefficients;
|
||||||
|
}
|
||||||
|
|
||||||
|
private static float[] checkCoefficientsValid(float[] coefficients) {
|
||||||
|
for (int i = 0; i < coefficients.length; i++) {
|
||||||
|
if (coefficients[i] < 0f) {
|
||||||
|
throw new IllegalArgumentException("Coefficient at index " + i + " is negative.");
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return coefficients;
|
||||||
|
}
|
||||||
|
}
|
@ -14,7 +14,7 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package androidx.media3.exoplayer.audio;
|
package androidx.media3.common.audio;
|
||||||
|
|
||||||
import static java.lang.Math.min;
|
import static java.lang.Math.min;
|
||||||
|
|
@ -0,0 +1,263 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2017 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.audio;
|
||||||
|
|
||||||
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.media3.common.C;
|
||||||
|
import androidx.media3.common.Format;
|
||||||
|
import androidx.media3.common.util.UnstableApi;
|
||||||
|
import androidx.media3.common.util.Util;
|
||||||
|
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.nio.ByteOrder;
|
||||||
|
import java.nio.ShortBuffer;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* An {@link AudioProcessor} that uses the Sonic library to modify audio speed/pitch/sample rate.
|
||||||
|
*/
|
||||||
|
@UnstableApi
|
||||||
|
public class SonicAudioProcessor implements AudioProcessor {
|
||||||
|
|
||||||
|
/** Indicates that the output sample rate should be the same as the input. */
|
||||||
|
public static final int SAMPLE_RATE_NO_CHANGE = -1;
|
||||||
|
|
||||||
|
/** The threshold below which the difference between two pitch/speed factors is negligible. */
|
||||||
|
private static final float CLOSE_THRESHOLD = 0.0001f;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* The minimum number of output bytes required for duration scaling to be calculated using the
|
||||||
|
* input and output byte counts, rather than using the current playback speed.
|
||||||
|
*/
|
||||||
|
private static final int MIN_BYTES_FOR_DURATION_SCALING_CALCULATION = 1024;
|
||||||
|
|
||||||
|
private int pendingOutputSampleRate;
|
||||||
|
private float speed;
|
||||||
|
private float pitch;
|
||||||
|
|
||||||
|
private AudioFormat pendingInputAudioFormat;
|
||||||
|
private AudioFormat pendingOutputAudioFormat;
|
||||||
|
private AudioFormat inputAudioFormat;
|
||||||
|
private AudioFormat outputAudioFormat;
|
||||||
|
|
||||||
|
private boolean pendingSonicRecreation;
|
||||||
|
@Nullable private Sonic sonic;
|
||||||
|
private ByteBuffer buffer;
|
||||||
|
private ShortBuffer shortBuffer;
|
||||||
|
private ByteBuffer outputBuffer;
|
||||||
|
private long inputBytes;
|
||||||
|
private long outputBytes;
|
||||||
|
private boolean inputEnded;
|
||||||
|
|
||||||
|
/** Creates a new Sonic audio processor. */
|
||||||
|
public SonicAudioProcessor() {
|
||||||
|
speed = 1f;
|
||||||
|
pitch = 1f;
|
||||||
|
pendingInputAudioFormat = AudioFormat.NOT_SET;
|
||||||
|
pendingOutputAudioFormat = AudioFormat.NOT_SET;
|
||||||
|
inputAudioFormat = AudioFormat.NOT_SET;
|
||||||
|
outputAudioFormat = AudioFormat.NOT_SET;
|
||||||
|
buffer = EMPTY_BUFFER;
|
||||||
|
shortBuffer = buffer.asShortBuffer();
|
||||||
|
outputBuffer = EMPTY_BUFFER;
|
||||||
|
pendingOutputSampleRate = SAMPLE_RATE_NO_CHANGE;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the target playback speed. This method may only be called after draining data through the
|
||||||
|
* processor. The value returned by {@link #isActive()} may change, and the processor must be
|
||||||
|
* {@link #flush() flushed} before queueing more data.
|
||||||
|
*
|
||||||
|
* @param speed The target factor by which playback should be sped up.
|
||||||
|
*/
|
||||||
|
public final void setSpeed(float speed) {
|
||||||
|
if (this.speed != speed) {
|
||||||
|
this.speed = speed;
|
||||||
|
pendingSonicRecreation = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the target playback pitch. This method may only be called after draining data through the
|
||||||
|
* processor. The value returned by {@link #isActive()} may change, and the processor must be
|
||||||
|
* {@link #flush() flushed} before queueing more data.
|
||||||
|
*
|
||||||
|
* @param pitch The target pitch.
|
||||||
|
*/
|
||||||
|
public final void setPitch(float pitch) {
|
||||||
|
if (this.pitch != pitch) {
|
||||||
|
this.pitch = pitch;
|
||||||
|
pendingSonicRecreation = true;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the sample rate for output audio, in Hertz. Pass {@link #SAMPLE_RATE_NO_CHANGE} to output
|
||||||
|
* audio at the same sample rate as the input. After calling this method, call {@link
|
||||||
|
* #configure(AudioFormat)} to configure the processor with the new sample rate.
|
||||||
|
*
|
||||||
|
* @param sampleRateHz The sample rate for output audio, in Hertz.
|
||||||
|
* @see #configure(AudioFormat)
|
||||||
|
*/
|
||||||
|
public final void setOutputSampleRateHz(int sampleRateHz) {
|
||||||
|
pendingOutputSampleRate = sampleRateHz;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the media duration corresponding to the specified playout duration, taking speed
|
||||||
|
* adjustment into account.
|
||||||
|
*
|
||||||
|
* <p>The scaling performed by this method will use the actual playback speed achieved by the
|
||||||
|
* audio processor, on average, since it was last flushed. This may differ very slightly from the
|
||||||
|
* target playback speed.
|
||||||
|
*
|
||||||
|
* @param playoutDuration The playout duration to scale.
|
||||||
|
* @return The corresponding media duration, in the same units as {@code duration}.
|
||||||
|
*/
|
||||||
|
public final long getMediaDuration(long playoutDuration) {
|
||||||
|
if (outputBytes >= MIN_BYTES_FOR_DURATION_SCALING_CALCULATION) {
|
||||||
|
long processedInputBytes = inputBytes - checkNotNull(sonic).getPendingInputBytes();
|
||||||
|
return outputAudioFormat.sampleRate == inputAudioFormat.sampleRate
|
||||||
|
? Util.scaleLargeTimestamp(playoutDuration, processedInputBytes, outputBytes)
|
||||||
|
: Util.scaleLargeTimestamp(
|
||||||
|
playoutDuration,
|
||||||
|
processedInputBytes * outputAudioFormat.sampleRate,
|
||||||
|
outputBytes * inputAudioFormat.sampleRate);
|
||||||
|
} else {
|
||||||
|
return (long) ((double) speed * playoutDuration);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
@CanIgnoreReturnValue
|
||||||
|
public final AudioFormat configure(AudioFormat inputAudioFormat)
|
||||||
|
throws UnhandledAudioFormatException {
|
||||||
|
if (inputAudioFormat.encoding != C.ENCODING_PCM_16BIT) {
|
||||||
|
throw new UnhandledAudioFormatException(inputAudioFormat);
|
||||||
|
}
|
||||||
|
int outputSampleRateHz =
|
||||||
|
pendingOutputSampleRate == SAMPLE_RATE_NO_CHANGE
|
||||||
|
? inputAudioFormat.sampleRate
|
||||||
|
: pendingOutputSampleRate;
|
||||||
|
pendingInputAudioFormat = inputAudioFormat;
|
||||||
|
pendingOutputAudioFormat =
|
||||||
|
new AudioFormat(outputSampleRateHz, inputAudioFormat.channelCount, C.ENCODING_PCM_16BIT);
|
||||||
|
pendingSonicRecreation = true;
|
||||||
|
return pendingOutputAudioFormat;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final boolean isActive() {
|
||||||
|
return pendingOutputAudioFormat.sampleRate != Format.NO_VALUE
|
||||||
|
&& (Math.abs(speed - 1f) >= CLOSE_THRESHOLD
|
||||||
|
|| Math.abs(pitch - 1f) >= CLOSE_THRESHOLD
|
||||||
|
|| pendingOutputAudioFormat.sampleRate != pendingInputAudioFormat.sampleRate);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void queueInput(ByteBuffer inputBuffer) {
|
||||||
|
if (!inputBuffer.hasRemaining()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
Sonic sonic = checkNotNull(this.sonic);
|
||||||
|
ShortBuffer shortBuffer = inputBuffer.asShortBuffer();
|
||||||
|
int inputSize = inputBuffer.remaining();
|
||||||
|
inputBytes += inputSize;
|
||||||
|
sonic.queueInput(shortBuffer);
|
||||||
|
inputBuffer.position(inputBuffer.position() + inputSize);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void queueEndOfStream() {
|
||||||
|
// TODO(internal b/174554082): assert sonic is non-null here and in getOutput.
|
||||||
|
if (sonic != null) {
|
||||||
|
sonic.queueEndOfStream();
|
||||||
|
}
|
||||||
|
inputEnded = true;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final ByteBuffer getOutput() {
|
||||||
|
@Nullable Sonic sonic = this.sonic;
|
||||||
|
if (sonic != null) {
|
||||||
|
int outputSize = sonic.getOutputSize();
|
||||||
|
if (outputSize > 0) {
|
||||||
|
if (buffer.capacity() < outputSize) {
|
||||||
|
buffer = ByteBuffer.allocateDirect(outputSize).order(ByteOrder.nativeOrder());
|
||||||
|
shortBuffer = buffer.asShortBuffer();
|
||||||
|
} else {
|
||||||
|
buffer.clear();
|
||||||
|
shortBuffer.clear();
|
||||||
|
}
|
||||||
|
sonic.getOutput(shortBuffer);
|
||||||
|
outputBytes += outputSize;
|
||||||
|
buffer.limit(outputSize);
|
||||||
|
outputBuffer = buffer;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
ByteBuffer outputBuffer = this.outputBuffer;
|
||||||
|
this.outputBuffer = EMPTY_BUFFER;
|
||||||
|
return outputBuffer;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final boolean isEnded() {
|
||||||
|
return inputEnded && (sonic == null || sonic.getOutputSize() == 0);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void flush() {
|
||||||
|
if (isActive()) {
|
||||||
|
inputAudioFormat = pendingInputAudioFormat;
|
||||||
|
outputAudioFormat = pendingOutputAudioFormat;
|
||||||
|
if (pendingSonicRecreation) {
|
||||||
|
sonic =
|
||||||
|
new Sonic(
|
||||||
|
inputAudioFormat.sampleRate,
|
||||||
|
inputAudioFormat.channelCount,
|
||||||
|
speed,
|
||||||
|
pitch,
|
||||||
|
outputAudioFormat.sampleRate);
|
||||||
|
} else if (sonic != null) {
|
||||||
|
sonic.flush();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
outputBuffer = EMPTY_BUFFER;
|
||||||
|
inputBytes = 0;
|
||||||
|
outputBytes = 0;
|
||||||
|
inputEnded = false;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void reset() {
|
||||||
|
speed = 1f;
|
||||||
|
pitch = 1f;
|
||||||
|
pendingInputAudioFormat = AudioFormat.NOT_SET;
|
||||||
|
pendingOutputAudioFormat = AudioFormat.NOT_SET;
|
||||||
|
inputAudioFormat = AudioFormat.NOT_SET;
|
||||||
|
outputAudioFormat = AudioFormat.NOT_SET;
|
||||||
|
buffer = EMPTY_BUFFER;
|
||||||
|
shortBuffer = buffer.asShortBuffer();
|
||||||
|
outputBuffer = EMPTY_BUFFER;
|
||||||
|
pendingOutputSampleRate = SAMPLE_RATE_NO_CHANGE;
|
||||||
|
pendingSonicRecreation = false;
|
||||||
|
sonic = null;
|
||||||
|
inputBytes = 0;
|
||||||
|
outputBytes = 0;
|
||||||
|
inputEnded = false;
|
||||||
|
}
|
||||||
|
}
|
@ -13,11 +13,11 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
package androidx.media3.exoplayer.audio;
|
package androidx.media3.common.audio;
|
||||||
|
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
import androidx.media3.common.audio.AudioProcessor;
|
import androidx.media3.common.util.UnstableApi;
|
||||||
import androidx.media3.common.util.Util;
|
import androidx.media3.common.util.Util;
|
||||||
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
import com.google.errorprone.annotations.CanIgnoreReturnValue;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
@ -35,7 +35,8 @@ import java.nio.ByteBuffer;
|
|||||||
* <li>{@link C#ENCODING_PCM_FLOAT}
|
* <li>{@link C#ENCODING_PCM_FLOAT}
|
||||||
* </ul>
|
* </ul>
|
||||||
*/
|
*/
|
||||||
/* package */ final class ResamplingAudioProcessor extends BaseAudioProcessor {
|
@UnstableApi
|
||||||
|
public final class ToInt16PcmAudioProcessor extends BaseAudioProcessor {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@CanIgnoreReturnValue
|
@CanIgnoreReturnValue
|
@ -300,162 +300,6 @@ public final class Cue implements Bundleable {
|
|||||||
*/
|
*/
|
||||||
public final float shearDegrees;
|
public final float shearDegrees;
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a text cue whose {@link #textAlignment} is null, whose type parameters are set to
|
|
||||||
* {@link #TYPE_UNSET} and whose dimension parameters are set to {@link #DIMEN_UNSET}.
|
|
||||||
*
|
|
||||||
* @param text See {@link #text}.
|
|
||||||
* @deprecated Use {@link Builder}.
|
|
||||||
*/
|
|
||||||
@UnstableApi
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
@Deprecated
|
|
||||||
public Cue(CharSequence text) {
|
|
||||||
this(
|
|
||||||
text,
|
|
||||||
/* textAlignment= */ null,
|
|
||||||
/* line= */ DIMEN_UNSET,
|
|
||||||
/* lineType= */ TYPE_UNSET,
|
|
||||||
/* lineAnchor= */ TYPE_UNSET,
|
|
||||||
/* position= */ DIMEN_UNSET,
|
|
||||||
/* positionAnchor= */ TYPE_UNSET,
|
|
||||||
/* size= */ DIMEN_UNSET);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a text cue.
|
|
||||||
*
|
|
||||||
* @param text See {@link #text}.
|
|
||||||
* @param textAlignment See {@link #textAlignment}.
|
|
||||||
* @param line See {@link #line}.
|
|
||||||
* @param lineType See {@link #lineType}.
|
|
||||||
* @param lineAnchor See {@link #lineAnchor}.
|
|
||||||
* @param position See {@link #position}.
|
|
||||||
* @param positionAnchor See {@link #positionAnchor}.
|
|
||||||
* @param size See {@link #size}.
|
|
||||||
* @deprecated Use {@link Builder}.
|
|
||||||
*/
|
|
||||||
@UnstableApi
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
@Deprecated
|
|
||||||
public Cue(
|
|
||||||
CharSequence text,
|
|
||||||
@Nullable Alignment textAlignment,
|
|
||||||
float line,
|
|
||||||
@LineType int lineType,
|
|
||||||
@AnchorType int lineAnchor,
|
|
||||||
float position,
|
|
||||||
@AnchorType int positionAnchor,
|
|
||||||
float size) {
|
|
||||||
this(
|
|
||||||
text,
|
|
||||||
textAlignment,
|
|
||||||
line,
|
|
||||||
lineType,
|
|
||||||
lineAnchor,
|
|
||||||
position,
|
|
||||||
positionAnchor,
|
|
||||||
size,
|
|
||||||
/* windowColorSet= */ false,
|
|
||||||
/* windowColor= */ Color.BLACK);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a text cue.
|
|
||||||
*
|
|
||||||
* @param text See {@link #text}.
|
|
||||||
* @param textAlignment See {@link #textAlignment}.
|
|
||||||
* @param line See {@link #line}.
|
|
||||||
* @param lineType See {@link #lineType}.
|
|
||||||
* @param lineAnchor See {@link #lineAnchor}.
|
|
||||||
* @param position See {@link #position}.
|
|
||||||
* @param positionAnchor See {@link #positionAnchor}.
|
|
||||||
* @param size See {@link #size}.
|
|
||||||
* @param textSizeType See {@link #textSizeType}.
|
|
||||||
* @param textSize See {@link #textSize}.
|
|
||||||
* @deprecated Use {@link Builder}.
|
|
||||||
*/
|
|
||||||
@UnstableApi
|
|
||||||
@Deprecated
|
|
||||||
public Cue(
|
|
||||||
CharSequence text,
|
|
||||||
@Nullable Alignment textAlignment,
|
|
||||||
float line,
|
|
||||||
@LineType int lineType,
|
|
||||||
@AnchorType int lineAnchor,
|
|
||||||
float position,
|
|
||||||
@AnchorType int positionAnchor,
|
|
||||||
float size,
|
|
||||||
@TextSizeType int textSizeType,
|
|
||||||
float textSize) {
|
|
||||||
this(
|
|
||||||
text,
|
|
||||||
textAlignment,
|
|
||||||
/* multiRowAlignment= */ null,
|
|
||||||
/* bitmap= */ null,
|
|
||||||
line,
|
|
||||||
lineType,
|
|
||||||
lineAnchor,
|
|
||||||
position,
|
|
||||||
positionAnchor,
|
|
||||||
textSizeType,
|
|
||||||
textSize,
|
|
||||||
size,
|
|
||||||
/* bitmapHeight= */ DIMEN_UNSET,
|
|
||||||
/* windowColorSet= */ false,
|
|
||||||
/* windowColor= */ Color.BLACK,
|
|
||||||
/* verticalType= */ TYPE_UNSET,
|
|
||||||
/* shearDegrees= */ 0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Creates a text cue.
|
|
||||||
*
|
|
||||||
* @param text See {@link #text}.
|
|
||||||
* @param textAlignment See {@link #textAlignment}.
|
|
||||||
* @param line See {@link #line}.
|
|
||||||
* @param lineType See {@link #lineType}.
|
|
||||||
* @param lineAnchor See {@link #lineAnchor}.
|
|
||||||
* @param position See {@link #position}.
|
|
||||||
* @param positionAnchor See {@link #positionAnchor}.
|
|
||||||
* @param size See {@link #size}.
|
|
||||||
* @param windowColorSet See {@link #windowColorSet}.
|
|
||||||
* @param windowColor See {@link #windowColor}.
|
|
||||||
* @deprecated Use {@link Builder}.
|
|
||||||
*/
|
|
||||||
@UnstableApi
|
|
||||||
@Deprecated
|
|
||||||
public Cue(
|
|
||||||
CharSequence text,
|
|
||||||
@Nullable Alignment textAlignment,
|
|
||||||
float line,
|
|
||||||
@LineType int lineType,
|
|
||||||
@AnchorType int lineAnchor,
|
|
||||||
float position,
|
|
||||||
@AnchorType int positionAnchor,
|
|
||||||
float size,
|
|
||||||
boolean windowColorSet,
|
|
||||||
int windowColor) {
|
|
||||||
this(
|
|
||||||
text,
|
|
||||||
textAlignment,
|
|
||||||
/* multiRowAlignment= */ null,
|
|
||||||
/* bitmap= */ null,
|
|
||||||
line,
|
|
||||||
lineType,
|
|
||||||
lineAnchor,
|
|
||||||
position,
|
|
||||||
positionAnchor,
|
|
||||||
/* textSizeType= */ TYPE_UNSET,
|
|
||||||
/* textSize= */ DIMEN_UNSET,
|
|
||||||
size,
|
|
||||||
/* bitmapHeight= */ DIMEN_UNSET,
|
|
||||||
windowColorSet,
|
|
||||||
windowColor,
|
|
||||||
/* verticalType= */ TYPE_UNSET,
|
|
||||||
/* shearDegrees= */ 0f);
|
|
||||||
}
|
|
||||||
|
|
||||||
private Cue(
|
private Cue(
|
||||||
@Nullable CharSequence text,
|
@Nullable CharSequence text,
|
||||||
@Nullable Alignment textAlignment,
|
@Nullable Alignment textAlignment,
|
||||||
|
@ -0,0 +1,54 @@
|
|||||||
|
/*
|
||||||
|
* 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.common.util;
|
||||||
|
|
||||||
|
import android.graphics.Bitmap;
|
||||||
|
import android.net.Uri;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.media3.common.MediaMetadata;
|
||||||
|
import com.google.common.util.concurrent.ListenableFuture;
|
||||||
|
|
||||||
|
/** Loads images. */
|
||||||
|
@UnstableApi
|
||||||
|
public interface BitmapLoader {
|
||||||
|
/** Decodes an image from compressed binary data. */
|
||||||
|
ListenableFuture<Bitmap> decodeBitmap(byte[] data);
|
||||||
|
|
||||||
|
/** Loads an image from {@code uri}. */
|
||||||
|
ListenableFuture<Bitmap> loadBitmap(Uri uri);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Loads an image from {@link MediaMetadata}. Returns null if {@code metadata} doesn't contain
|
||||||
|
* bitmap information.
|
||||||
|
*
|
||||||
|
* <p>By default, the method will try to decode an image from {@link MediaMetadata#artworkData} if
|
||||||
|
* it is present. Otherwise, the method will try to load an image from {@link
|
||||||
|
* MediaMetadata#artworkUri} if it is present. The method will return null if neither {@link
|
||||||
|
* MediaMetadata#artworkData} nor {@link MediaMetadata#artworkUri} is present.
|
||||||
|
*/
|
||||||
|
@Nullable
|
||||||
|
default ListenableFuture<Bitmap> loadBitmapFromMetadata(MediaMetadata metadata) {
|
||||||
|
@Nullable ListenableFuture<Bitmap> future;
|
||||||
|
if (metadata.artworkData != null) {
|
||||||
|
future = decodeBitmap(metadata.artworkData);
|
||||||
|
} else if (metadata.artworkUri != null) {
|
||||||
|
future = loadBitmap(metadata.artworkUri);
|
||||||
|
} else {
|
||||||
|
future = null;
|
||||||
|
}
|
||||||
|
return future;
|
||||||
|
}
|
||||||
|
}
|
@ -22,10 +22,14 @@ import android.os.Bundle;
|
|||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.Bundleable;
|
import androidx.media3.common.Bundleable;
|
||||||
|
import com.google.common.base.Function;
|
||||||
import com.google.common.collect.ImmutableList;
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.Collection;
|
import java.util.Collection;
|
||||||
|
import java.util.HashMap;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
/** Utilities for {@link Bundleable}. */
|
/** Utilities for {@link Bundleable}. */
|
||||||
@UnstableApi
|
@UnstableApi
|
||||||
@ -33,10 +37,21 @@ public final class BundleableUtil {
|
|||||||
|
|
||||||
/** 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) {
|
||||||
|
return toBundleList(bundleableList, Bundleable::toBundle);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts a list of {@link Bundleable} to a list {@link Bundle}
|
||||||
|
*
|
||||||
|
* @param bundleableList list of Bundleable items to be converted
|
||||||
|
* @param customToBundleFunc function that specifies how to bundle up each {@link Bundleable}
|
||||||
|
*/
|
||||||
|
public static <T extends Bundleable> ImmutableList<Bundle> toBundleList(
|
||||||
|
List<T> bundleableList, Function<T, Bundle> customToBundleFunc) {
|
||||||
ImmutableList.Builder<Bundle> builder = ImmutableList.builder();
|
ImmutableList.Builder<Bundle> builder = ImmutableList.builder();
|
||||||
for (int i = 0; i < bundleableList.size(); i++) {
|
for (int i = 0; i < bundleableList.size(); i++) {
|
||||||
Bundleable bundleable = bundleableList.get(i);
|
T bundleable = bundleableList.get(i);
|
||||||
builder.add(bundleable.toBundle());
|
builder.add(customToBundleFunc.apply(bundleable));
|
||||||
}
|
}
|
||||||
return builder.build();
|
return builder.build();
|
||||||
}
|
}
|
||||||
@ -94,6 +109,47 @@ public final class BundleableUtil {
|
|||||||
return sparseArray;
|
return sparseArray;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
public static Bundle stringMapToBundle(Map<String, String> bundleableMap) {
|
||||||
|
Bundle bundle = new Bundle();
|
||||||
|
for (Map.Entry<String, String> entry : bundleableMap.entrySet()) {
|
||||||
|
bundle.putString(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
return bundle;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static HashMap<String, String> bundleToStringHashMap(Bundle bundle) {
|
||||||
|
HashMap<String, String> map = new HashMap<>();
|
||||||
|
if (bundle == Bundle.EMPTY) {
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
for (String key : bundle.keySet()) {
|
||||||
|
@Nullable String value = bundle.getString(key);
|
||||||
|
if (value != null) {
|
||||||
|
map.put(key, value);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return map;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ImmutableMap<String, String> bundleToStringImmutableMap(Bundle bundle) {
|
||||||
|
if (bundle == Bundle.EMPTY) {
|
||||||
|
return ImmutableMap.of();
|
||||||
|
}
|
||||||
|
HashMap<String, String> map = bundleToStringHashMap(bundle);
|
||||||
|
return ImmutableMap.copyOf(map);
|
||||||
|
}
|
||||||
|
|
||||||
|
public static Bundle getBundleWithDefault(Bundle bundle, String field, Bundle defaultValue) {
|
||||||
|
@Nullable Bundle result = bundle.getBundle(field);
|
||||||
|
return result != null ? result : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ArrayList<Integer> getIntegerArrayListWithDefault(
|
||||||
|
Bundle bundle, String field, ArrayList<Integer> defaultValue) {
|
||||||
|
@Nullable ArrayList<Integer> result = bundle.getIntegerArrayList(field);
|
||||||
|
return result != null ? result : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the application class loader to the given {@link Bundle} if no class loader is present.
|
* Sets the application class loader to the given {@link Bundle} if no class loader is present.
|
||||||
*
|
*
|
||||||
|
@ -67,7 +67,7 @@ public final class GlProgram {
|
|||||||
* @return The content of the file to load.
|
* @return The content of the file to load.
|
||||||
* @throws IOException If the file couldn't be read.
|
* @throws IOException If the file couldn't be read.
|
||||||
*/
|
*/
|
||||||
public static String loadAsset(Context context, String assetPath) throws IOException {
|
private static String loadAsset(Context context, String assetPath) throws IOException {
|
||||||
@Nullable InputStream inputStream = null;
|
@Nullable InputStream inputStream = null;
|
||||||
try {
|
try {
|
||||||
inputStream = context.getAssets().open(assetPath);
|
inputStream = context.getAssets().open(assetPath);
|
||||||
@ -328,7 +328,7 @@ public final class GlProgram {
|
|||||||
/* unusedLength */ new int[1],
|
/* unusedLength */ new int[1],
|
||||||
/* lengthOffset= */ 0,
|
/* lengthOffset= */ 0,
|
||||||
/* unusedSize */ new int[1],
|
/* unusedSize */ new int[1],
|
||||||
/*sizeOffset= */ 0,
|
/* sizeOffset= */ 0,
|
||||||
type,
|
type,
|
||||||
/* typeOffset= */ 0,
|
/* typeOffset= */ 0,
|
||||||
nameBytes,
|
nameBytes,
|
||||||
|
@ -31,6 +31,7 @@ import android.opengl.GLES20;
|
|||||||
import android.opengl.GLES30;
|
import android.opengl.GLES30;
|
||||||
import android.opengl.Matrix;
|
import android.opengl.Matrix;
|
||||||
import androidx.annotation.DoNotInline;
|
import androidx.annotation.DoNotInline;
|
||||||
|
import androidx.annotation.IntRange;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
@ -89,7 +90,16 @@ public final class GlUtil {
|
|||||||
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
|
// https://www.khronos.org/registry/OpenGL/extensions/EXT/EXT_YUV_target.txt
|
||||||
private static final String EXTENSION_YUV_TARGET = "GL_EXT_YUV_target";
|
private static final String EXTENSION_YUV_TARGET = "GL_EXT_YUV_target";
|
||||||
|
// https://registry.khronos.org/EGL/extensions/EXT/EGL_EXT_gl_colorspace_bt2020_linear.txt
|
||||||
|
private static final String EXTENSION_COLORSPACE_BT2020_PQ = "EGL_EXT_gl_colorspace_bt2020_pq";
|
||||||
|
// https://registry.khronos.org/EGL/extensions/KHR/EGL_KHR_gl_colorspace.txt
|
||||||
|
private static final int EGL_GL_COLORSPACE_KHR = 0x309D;
|
||||||
|
// https://registry.khronos.org/EGL/extensions/EXT/EGL_EXT_gl_colorspace_bt2020_linear.txt
|
||||||
|
private static final int EGL_GL_COLORSPACE_BT2020_PQ_EXT = 0x3340;
|
||||||
|
private static final int[] EGL_WINDOW_SURFACE_ATTRIBUTES_BT2020_PQ =
|
||||||
|
new int[] {
|
||||||
|
EGL_GL_COLORSPACE_KHR, EGL_GL_COLORSPACE_BT2020_PQ_EXT, EGL14.EGL_NONE, EGL14.EGL_NONE
|
||||||
|
};
|
||||||
private static final int[] EGL_WINDOW_SURFACE_ATTRIBUTES_NONE = new int[] {EGL14.EGL_NONE};
|
private static final int[] EGL_WINDOW_SURFACE_ATTRIBUTES_NONE = new int[] {EGL14.EGL_NONE};
|
||||||
|
|
||||||
/** Class only contains static methods. */
|
/** Class only contains static methods. */
|
||||||
@ -190,7 +200,7 @@ public final class GlUtil {
|
|||||||
* Returns whether the {@link #EXTENSION_YUV_TARGET} extension is supported.
|
* Returns whether the {@link #EXTENSION_YUV_TARGET} extension is supported.
|
||||||
*
|
*
|
||||||
* <p>This extension allows sampling raw YUV values from an external texture, which is required
|
* <p>This extension allows sampling raw YUV values from an external texture, which is required
|
||||||
* for HDR.
|
* for HDR input.
|
||||||
*/
|
*/
|
||||||
public static boolean isYuvTargetExtensionSupported() {
|
public static boolean isYuvTargetExtensionSupported() {
|
||||||
if (Util.SDK_INT < 17) {
|
if (Util.SDK_INT < 17) {
|
||||||
@ -216,6 +226,13 @@ public final class GlUtil {
|
|||||||
return glExtensions != null && glExtensions.contains(EXTENSION_YUV_TARGET);
|
return glExtensions != null && glExtensions.contains(EXTENSION_YUV_TARGET);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns whether {@link #EXTENSION_COLORSPACE_BT2020_PQ} is supported. */
|
||||||
|
public static boolean isBt2020PqExtensionSupported() {
|
||||||
|
EGLDisplay display = EGL14.eglGetDisplay(EGL14.EGL_DEFAULT_DISPLAY);
|
||||||
|
@Nullable String eglExtensions = EGL14.eglQueryString(display, EGL10.EGL_EXTENSIONS);
|
||||||
|
return eglExtensions != null && eglExtensions.contains(EXTENSION_COLORSPACE_BT2020_PQ);
|
||||||
|
}
|
||||||
|
|
||||||
/** Returns an initialized default {@link EGLDisplay}. */
|
/** Returns an initialized default {@link EGLDisplay}. */
|
||||||
@RequiresApi(17)
|
@RequiresApi(17)
|
||||||
public static EGLDisplay createEglDisplay() throws GlException {
|
public static EGLDisplay createEglDisplay() throws GlException {
|
||||||
@ -232,57 +249,78 @@ public final class GlUtil {
|
|||||||
*/
|
*/
|
||||||
@RequiresApi(17)
|
@RequiresApi(17)
|
||||||
public static EGLContext createEglContext(EGLDisplay eglDisplay) throws GlException {
|
public static EGLContext createEglContext(EGLDisplay eglDisplay) throws GlException {
|
||||||
return createEglContext(eglDisplay, EGL_CONFIG_ATTRIBUTES_RGBA_8888);
|
return createEglContext(
|
||||||
|
EGL14.EGL_NO_CONTEXT, eglDisplay, /* openGlVersion= */ 2, EGL_CONFIG_ATTRIBUTES_RGBA_8888);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new {@link EGLContext} for the specified {@link EGLDisplay}.
|
* Creates a new {@link EGLContext} for the specified {@link EGLDisplay}.
|
||||||
*
|
*
|
||||||
|
* @param sharedContext The {@link EGLContext} with which to share data.
|
||||||
* @param eglDisplay The {@link EGLDisplay} to create an {@link EGLContext} for.
|
* @param eglDisplay The {@link EGLDisplay} to create an {@link EGLContext} for.
|
||||||
|
* @param openGlVersion The version of OpenGL ES to configure. Accepts either {@code 2}, for
|
||||||
|
* OpenGL ES 2.0, or {@code 3}, for OpenGL ES 3.0.
|
||||||
* @param configAttributes The attributes to configure EGL with. Accepts either {@link
|
* @param configAttributes The attributes to configure EGL with. Accepts either {@link
|
||||||
* #EGL_CONFIG_ATTRIBUTES_RGBA_1010102}, which will request OpenGL ES 3.0, or {@link
|
* #EGL_CONFIG_ATTRIBUTES_RGBA_1010102}, or {@link #EGL_CONFIG_ATTRIBUTES_RGBA_8888}.
|
||||||
* #EGL_CONFIG_ATTRIBUTES_RGBA_8888}, which will request OpenGL ES 2.0.
|
|
||||||
*/
|
*/
|
||||||
@RequiresApi(17)
|
@RequiresApi(17)
|
||||||
public static EGLContext createEglContext(EGLDisplay eglDisplay, int[] configAttributes)
|
public static EGLContext createEglContext(
|
||||||
|
EGLContext sharedContext,
|
||||||
|
EGLDisplay eglDisplay,
|
||||||
|
@IntRange(from = 2, to = 3) int openGlVersion,
|
||||||
|
int[] configAttributes)
|
||||||
throws GlException {
|
throws GlException {
|
||||||
checkArgument(
|
checkArgument(
|
||||||
Arrays.equals(configAttributes, EGL_CONFIG_ATTRIBUTES_RGBA_8888)
|
Arrays.equals(configAttributes, EGL_CONFIG_ATTRIBUTES_RGBA_8888)
|
||||||
|| Arrays.equals(configAttributes, EGL_CONFIG_ATTRIBUTES_RGBA_1010102));
|
|| Arrays.equals(configAttributes, EGL_CONFIG_ATTRIBUTES_RGBA_1010102));
|
||||||
return Api17.createEglContext(
|
checkArgument(openGlVersion == 2 || openGlVersion == 3);
|
||||||
eglDisplay,
|
return Api17.createEglContext(sharedContext, eglDisplay, openGlVersion, configAttributes);
|
||||||
/* version= */ Arrays.equals(configAttributes, EGL_CONFIG_ATTRIBUTES_RGBA_1010102) ? 3 : 2,
|
|
||||||
configAttributes);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a new {@link EGLSurface} wrapping the specified {@code surface}.
|
* Creates a new {@link EGLSurface} wrapping the specified {@code surface}.
|
||||||
*
|
*
|
||||||
* <p>The {@link EGLSurface} will configure with {@link #EGL_CONFIG_ATTRIBUTES_RGBA_8888} and
|
* <p>The {@link EGLSurface} will configure with OpenGL ES 2.0.
|
||||||
* OpenGL ES 2.0.
|
|
||||||
*
|
*
|
||||||
* @param eglDisplay The {@link EGLDisplay} to attach the surface to.
|
* @param eglDisplay The {@link EGLDisplay} to attach the surface to.
|
||||||
* @param surface The surface to wrap; must be a surface, surface texture or surface holder.
|
* @param surface The surface to wrap; must be a surface, surface texture or surface holder.
|
||||||
|
* @param colorTransfer The {@linkplain C.ColorTransfer color transfer characteristics} to which
|
||||||
|
* the {@code surface} is configured. The only accepted values are {@link
|
||||||
|
* C#COLOR_TRANSFER_SDR}, {@link C#COLOR_TRANSFER_HLG} and {@link C#COLOR_TRANSFER_ST2084}.
|
||||||
|
* @param isEncoderInputSurface Whether the {@code surface} is the input surface of an encoder.
|
||||||
*/
|
*/
|
||||||
@RequiresApi(17)
|
@RequiresApi(17)
|
||||||
public static EGLSurface getEglSurface(EGLDisplay eglDisplay, Object surface) throws GlException {
|
public static EGLSurface createEglSurface(
|
||||||
return Api17.getEglSurface(
|
EGLDisplay eglDisplay,
|
||||||
eglDisplay, surface, EGL_CONFIG_ATTRIBUTES_RGBA_8888, EGL_WINDOW_SURFACE_ATTRIBUTES_NONE);
|
Object surface,
|
||||||
|
@C.ColorTransfer int colorTransfer,
|
||||||
|
boolean isEncoderInputSurface)
|
||||||
|
throws GlException {
|
||||||
|
int[] configAttributes;
|
||||||
|
int[] windowAttributes;
|
||||||
|
if (colorTransfer == C.COLOR_TRANSFER_SDR || colorTransfer == C.COLOR_TRANSFER_GAMMA_2_2) {
|
||||||
|
configAttributes = EGL_CONFIG_ATTRIBUTES_RGBA_8888;
|
||||||
|
windowAttributes = EGL_WINDOW_SURFACE_ATTRIBUTES_NONE;
|
||||||
|
} else if (colorTransfer == C.COLOR_TRANSFER_ST2084) {
|
||||||
|
configAttributes = EGL_CONFIG_ATTRIBUTES_RGBA_1010102;
|
||||||
|
if (isEncoderInputSurface) {
|
||||||
|
// Outputting BT2020 PQ with EGL_WINDOW_SURFACE_ATTRIBUTES_BT2020_PQ to an encoder causes
|
||||||
|
// the encoder to incorrectly switch to full range color, even if the encoder is configured
|
||||||
|
// with limited range color, because EGL_WINDOW_SURFACE_ATTRIBUTES_BT2020_PQ sets full range
|
||||||
|
// color output, and GL windowAttributes overrides encoder settings.
|
||||||
|
windowAttributes = EGL_WINDOW_SURFACE_ATTRIBUTES_NONE;
|
||||||
|
} else {
|
||||||
|
// TODO(b/262259999) HDR10 PQ content looks dark on the screen.
|
||||||
|
windowAttributes = EGL_WINDOW_SURFACE_ATTRIBUTES_BT2020_PQ;
|
||||||
}
|
}
|
||||||
|
} else if (colorTransfer == C.COLOR_TRANSFER_HLG) {
|
||||||
/**
|
checkArgument(isEncoderInputSurface, "Outputting HLG to the screen is not supported.");
|
||||||
* Returns a new {@link EGLSurface} wrapping the specified {@code surface}.
|
configAttributes = EGL_CONFIG_ATTRIBUTES_RGBA_1010102;
|
||||||
*
|
windowAttributes = EGL_WINDOW_SURFACE_ATTRIBUTES_NONE;
|
||||||
* @param eglDisplay The {@link EGLDisplay} to attach the surface to.
|
} else {
|
||||||
* @param surface The surface to wrap; must be a surface, surface texture or surface holder.
|
throw new IllegalArgumentException("Unsupported color transfer: " + colorTransfer);
|
||||||
* @param configAttributes The attributes to configure EGL with. Accepts {@link
|
}
|
||||||
* #EGL_CONFIG_ATTRIBUTES_RGBA_1010102} and {@link #EGL_CONFIG_ATTRIBUTES_RGBA_8888}.
|
return Api17.createEglSurface(eglDisplay, surface, configAttributes, windowAttributes);
|
||||||
*/
|
|
||||||
@RequiresApi(17)
|
|
||||||
public static EGLSurface getEglSurface(
|
|
||||||
EGLDisplay eglDisplay, Object surface, int[] configAttributes) throws GlException {
|
|
||||||
return Api17.getEglSurface(
|
|
||||||
eglDisplay, surface, configAttributes, EGL_WINDOW_SURFACE_ATTRIBUTES_NONE);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -349,6 +387,11 @@ public final class GlUtil {
|
|||||||
return eglSurface;
|
return eglSurface;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Gets the current {@link EGLContext context}. */
|
||||||
|
public static EGLContext getCurrentContext() {
|
||||||
|
return EGL14.eglGetCurrentContext();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Collects all OpenGL errors that occurred since this method was last called and throws a {@link
|
* Collects all OpenGL errors that occurred since this method was last called and throws a {@link
|
||||||
* GlException} with the combined error message.
|
* GlException} with the combined error message.
|
||||||
@ -378,7 +421,7 @@ public final class GlUtil {
|
|||||||
*/
|
*/
|
||||||
private static void assertValidTextureSize(int width, int height) throws GlException {
|
private static void assertValidTextureSize(int width, int height) throws GlException {
|
||||||
// TODO(b/201293185): Consider handling adjustments for sizes > GL_MAX_TEXTURE_SIZE
|
// TODO(b/201293185): Consider handling adjustments for sizes > GL_MAX_TEXTURE_SIZE
|
||||||
// (ex. downscaling appropriately) in a texture processor instead of asserting incorrect
|
// (ex. downscaling appropriately) in a shader program instead of asserting incorrect
|
||||||
// values.
|
// values.
|
||||||
// For valid GL sizes, see:
|
// For valid GL sizes, see:
|
||||||
// https://www.khronos.org/registry/OpenGL-Refpages/es2.0/xhtml/glTexImage2D.xml
|
// https://www.khronos.org/registry/OpenGL-Refpages/es2.0/xhtml/glTexImage2D.xml
|
||||||
@ -401,7 +444,8 @@ public final class GlUtil {
|
|||||||
/** Fills the pixels in the current output render target with (r=0, g=0, b=0, a=0). */
|
/** Fills the pixels in the current output render target with (r=0, g=0, b=0, a=0). */
|
||||||
public static void clearOutputFrame() throws GlException {
|
public static void clearOutputFrame() throws GlException {
|
||||||
GLES20.glClearColor(/* red= */ 0, /* green= */ 0, /* blue= */ 0, /* alpha= */ 0);
|
GLES20.glClearColor(/* red= */ 0, /* green= */ 0, /* blue= */ 0, /* alpha= */ 0);
|
||||||
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT);
|
GLES20.glClearDepthf(1.0f);
|
||||||
|
GLES20.glClear(GLES20.GL_COLOR_BUFFER_BIT | GLES20.GL_DEPTH_BUFFER_BIT);
|
||||||
GlUtil.checkGlError();
|
GlUtil.checkGlError();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -450,26 +494,6 @@ public final class GlUtil {
|
|||||||
Api17.focusFramebufferUsingCurrentContext(framebuffer, width, height);
|
Api17.focusFramebufferUsingCurrentContext(framebuffer, width, height);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Deletes a GL texture.
|
|
||||||
*
|
|
||||||
* @param textureId The ID of the texture to delete.
|
|
||||||
*/
|
|
||||||
public static void deleteTexture(int textureId) throws GlException {
|
|
||||||
GLES20.glDeleteTextures(/* n= */ 1, new int[] {textureId}, /* offset= */ 0);
|
|
||||||
checkGlError();
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Destroys the {@link EGLContext} identified by the provided {@link EGLDisplay} and {@link
|
|
||||||
* EGLContext}.
|
|
||||||
*/
|
|
||||||
@RequiresApi(17)
|
|
||||||
public static void destroyEglContext(
|
|
||||||
@Nullable EGLDisplay eglDisplay, @Nullable EGLContext eglContext) throws GlException {
|
|
||||||
Api17.destroyEglContext(eglDisplay, eglContext);
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Allocates a FloatBuffer with the given data.
|
* Allocates a FloatBuffer with the given data.
|
||||||
*
|
*
|
||||||
@ -504,8 +528,8 @@ public final class GlUtil {
|
|||||||
*
|
*
|
||||||
* @param width The width of the new texture in pixels.
|
* @param width The width of the new texture in pixels.
|
||||||
* @param height The height of the new texture in pixels.
|
* @param height The height of the new texture in pixels.
|
||||||
* @param useHighPrecisionColorComponents If {@code false}, uses 8-bit unsigned bytes. If {@code
|
* @param useHighPrecisionColorComponents If {@code false}, uses colors with 8-bit unsigned bytes.
|
||||||
* true}, use 16-bit (half-precision) floating-point.
|
* If {@code true}, use 16-bit (half-precision) floating-point.
|
||||||
* @throws GlException If the texture allocation fails.
|
* @throws GlException If the texture allocation fails.
|
||||||
* @return The texture identifier for the newly-allocated texture.
|
* @return The texture identifier for the newly-allocated texture.
|
||||||
*/
|
*/
|
||||||
@ -602,6 +626,49 @@ public final class GlUtil {
|
|||||||
return fboId[0];
|
return fboId[0];
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Deletes a GL texture.
|
||||||
|
*
|
||||||
|
* @param textureId The ID of the texture to delete.
|
||||||
|
*/
|
||||||
|
public static void deleteTexture(int textureId) throws GlException {
|
||||||
|
GLES20.glDeleteTextures(/* n= */ 1, new int[] {textureId}, /* offset= */ 0);
|
||||||
|
checkGlError();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroys the {@link EGLContext} identified by the provided {@link EGLDisplay} and {@link
|
||||||
|
* EGLContext}.
|
||||||
|
*/
|
||||||
|
@RequiresApi(17)
|
||||||
|
public static void destroyEglContext(
|
||||||
|
@Nullable EGLDisplay eglDisplay, @Nullable EGLContext eglContext) throws GlException {
|
||||||
|
Api17.destroyEglContext(eglDisplay, eglContext);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Destroys the {@link EGLSurface} identified by the provided {@link EGLDisplay} and {@link
|
||||||
|
* EGLSurface}.
|
||||||
|
*/
|
||||||
|
@RequiresApi(17)
|
||||||
|
public static void destroyEglSurface(
|
||||||
|
@Nullable EGLDisplay eglDisplay, @Nullable EGLSurface eglSurface) throws GlException {
|
||||||
|
Api17.destroyEglSurface(eglDisplay, eglSurface);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Deletes a framebuffer, or silently ignores the method call if {@code fboId} is unused. */
|
||||||
|
public static void deleteFbo(int fboId) throws GlException {
|
||||||
|
GLES20.glDeleteFramebuffers(/* n= */ 1, new int[] {fboId}, /* offset= */ 0);
|
||||||
|
checkGlError();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Deletes a renderbuffer, or silently ignores the method call if {@code rboId} is unused. */
|
||||||
|
public static void deleteRbo(int rboId) throws GlException {
|
||||||
|
GLES20.glDeleteRenderbuffers(
|
||||||
|
/* n= */ 1, /* renderbuffers= */ new int[] {rboId}, /* offset= */ 0);
|
||||||
|
checkGlError();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Throws a {@link GlException} with the given message if {@code expression} evaluates to {@code
|
* Throws a {@link GlException} with the given message if {@code expression} evaluates to {@code
|
||||||
* false}.
|
* false}.
|
||||||
@ -639,13 +706,14 @@ public final class GlUtil {
|
|||||||
|
|
||||||
@DoNotInline
|
@DoNotInline
|
||||||
public static EGLContext createEglContext(
|
public static EGLContext createEglContext(
|
||||||
EGLDisplay eglDisplay, int version, int[] configAttributes) throws GlException {
|
EGLContext sharedContext, EGLDisplay eglDisplay, int version, int[] configAttributes)
|
||||||
|
throws GlException {
|
||||||
int[] contextAttributes = {EGL14.EGL_CONTEXT_CLIENT_VERSION, version, EGL14.EGL_NONE};
|
int[] contextAttributes = {EGL14.EGL_CONTEXT_CLIENT_VERSION, version, EGL14.EGL_NONE};
|
||||||
EGLContext eglContext =
|
EGLContext eglContext =
|
||||||
EGL14.eglCreateContext(
|
EGL14.eglCreateContext(
|
||||||
eglDisplay,
|
eglDisplay,
|
||||||
getEglConfig(eglDisplay, configAttributes),
|
getEglConfig(eglDisplay, configAttributes),
|
||||||
EGL14.EGL_NO_CONTEXT,
|
sharedContext,
|
||||||
contextAttributes,
|
contextAttributes,
|
||||||
/* offset= */ 0);
|
/* offset= */ 0);
|
||||||
if (eglContext == null) {
|
if (eglContext == null) {
|
||||||
@ -660,18 +728,15 @@ public final class GlUtil {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@DoNotInline
|
@DoNotInline
|
||||||
public static EGLSurface getEglSurface(
|
public static EGLSurface createEglSurface(
|
||||||
EGLDisplay eglDisplay,
|
EGLDisplay eglDisplay, Object surface, int[] configAttributes, int[] windowAttributes)
|
||||||
Object surface,
|
|
||||||
int[] configAttributes,
|
|
||||||
int[] windowSurfaceAttributes)
|
|
||||||
throws GlException {
|
throws GlException {
|
||||||
EGLSurface eglSurface =
|
EGLSurface eglSurface =
|
||||||
EGL14.eglCreateWindowSurface(
|
EGL14.eglCreateWindowSurface(
|
||||||
eglDisplay,
|
eglDisplay,
|
||||||
getEglConfig(eglDisplay, configAttributes),
|
getEglConfig(eglDisplay, configAttributes),
|
||||||
surface,
|
surface,
|
||||||
windowSurfaceAttributes,
|
windowAttributes,
|
||||||
/* offset= */ 0);
|
/* offset= */ 0);
|
||||||
checkEglException("Error creating surface");
|
checkEglException("Error creating surface");
|
||||||
return eglSurface;
|
return eglSurface;
|
||||||
@ -739,6 +804,16 @@ public final class GlUtil {
|
|||||||
checkEglException("Error terminating display");
|
checkEglException("Error terminating display");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@DoNotInline
|
||||||
|
public static void destroyEglSurface(
|
||||||
|
@Nullable EGLDisplay eglDisplay, @Nullable EGLSurface eglSurface) throws GlException {
|
||||||
|
if (eglDisplay == null || eglSurface == null) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
EGL14.eglDestroySurface(eglDisplay, eglSurface);
|
||||||
|
checkEglException("Error destroying surface");
|
||||||
|
}
|
||||||
|
|
||||||
@DoNotInline
|
@DoNotInline
|
||||||
private static EGLConfig getEglConfig(EGLDisplay eglDisplay, int[] attributes)
|
private static EGLConfig getEglConfig(EGLDisplay eglDisplay, int[] attributes)
|
||||||
throws GlException {
|
throws GlException {
|
||||||
|
@ -99,14 +99,20 @@ public final class ListenerSet<T extends @NonNull Object> {
|
|||||||
* during one {@link Looper} message queue iteration were handled by the listeners.
|
* during one {@link Looper} message queue iteration were handled by the listeners.
|
||||||
*/
|
*/
|
||||||
public ListenerSet(Looper looper, Clock clock, IterationFinishedEvent<T> iterationFinishedEvent) {
|
public ListenerSet(Looper looper, Clock clock, IterationFinishedEvent<T> iterationFinishedEvent) {
|
||||||
this(/* listeners= */ new CopyOnWriteArraySet<>(), looper, clock, iterationFinishedEvent);
|
this(
|
||||||
|
/* listeners= */ new CopyOnWriteArraySet<>(),
|
||||||
|
looper,
|
||||||
|
clock,
|
||||||
|
iterationFinishedEvent,
|
||||||
|
/* throwsWhenUsingWrongThread= */ true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private ListenerSet(
|
private ListenerSet(
|
||||||
CopyOnWriteArraySet<ListenerHolder<T>> listeners,
|
CopyOnWriteArraySet<ListenerHolder<T>> listeners,
|
||||||
Looper looper,
|
Looper looper,
|
||||||
Clock clock,
|
Clock clock,
|
||||||
IterationFinishedEvent<T> iterationFinishedEvent) {
|
IterationFinishedEvent<T> iterationFinishedEvent,
|
||||||
|
boolean throwsWhenUsingWrongThread) {
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
this.listeners = listeners;
|
this.listeners = listeners;
|
||||||
this.iterationFinishedEvent = iterationFinishedEvent;
|
this.iterationFinishedEvent = iterationFinishedEvent;
|
||||||
@ -117,7 +123,7 @@ public final class ListenerSet<T extends @NonNull Object> {
|
|||||||
@SuppressWarnings("nullness:methodref.receiver.bound")
|
@SuppressWarnings("nullness:methodref.receiver.bound")
|
||||||
HandlerWrapper handler = clock.createHandler(looper, this::handleMessage);
|
HandlerWrapper handler = clock.createHandler(looper, this::handleMessage);
|
||||||
this.handler = handler;
|
this.handler = handler;
|
||||||
throwsWhenUsingWrongThread = true;
|
this.throwsWhenUsingWrongThread = throwsWhenUsingWrongThread;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -149,7 +155,8 @@ public final class ListenerSet<T extends @NonNull Object> {
|
|||||||
@CheckResult
|
@CheckResult
|
||||||
public ListenerSet<T> copy(
|
public ListenerSet<T> copy(
|
||||||
Looper looper, Clock clock, IterationFinishedEvent<T> iterationFinishedEvent) {
|
Looper looper, Clock clock, IterationFinishedEvent<T> iterationFinishedEvent) {
|
||||||
return new ListenerSet<>(listeners, looper, clock, iterationFinishedEvent);
|
return new ListenerSet<>(
|
||||||
|
listeners, looper, clock, iterationFinishedEvent, throwsWhenUsingWrongThread);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -24,6 +24,8 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
import androidx.media3.common.ColorInfo;
|
import androidx.media3.common.ColorInfo;
|
||||||
import androidx.media3.common.Format;
|
import androidx.media3.common.Format;
|
||||||
|
import androidx.media3.common.MimeTypes;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
@ -64,6 +66,70 @@ public final class MediaFormatUtil {
|
|||||||
|
|
||||||
private static final int MAX_POWER_OF_TWO_INT = 1 << 30;
|
private static final int MAX_POWER_OF_TWO_INT = 1 << 30;
|
||||||
|
|
||||||
|
/** Returns a {@link Format} representing the given {@link MediaFormat}. */
|
||||||
|
@SuppressLint("InlinedApi") // Inlined MediaFormat keys.
|
||||||
|
public static Format createFormatFromMediaFormat(MediaFormat mediaFormat) {
|
||||||
|
Format.Builder formatBuilder =
|
||||||
|
new Format.Builder()
|
||||||
|
.setSampleMimeType(mediaFormat.getString(MediaFormat.KEY_MIME))
|
||||||
|
.setLanguage(mediaFormat.getString(MediaFormat.KEY_LANGUAGE))
|
||||||
|
.setPeakBitrate(
|
||||||
|
getInteger(mediaFormat, KEY_MAX_BIT_RATE, /* defaultValue= */ Format.NO_VALUE))
|
||||||
|
.setAverageBitrate(
|
||||||
|
getInteger(
|
||||||
|
mediaFormat, MediaFormat.KEY_BIT_RATE, /* defaultValue= */ Format.NO_VALUE))
|
||||||
|
.setCodecs(mediaFormat.getString(MediaFormat.KEY_CODECS_STRING))
|
||||||
|
.setFrameRate(getFrameRate(mediaFormat, /* defaultValue= */ Format.NO_VALUE))
|
||||||
|
.setWidth(
|
||||||
|
getInteger(mediaFormat, MediaFormat.KEY_WIDTH, /* defaultValue= */ Format.NO_VALUE))
|
||||||
|
.setHeight(
|
||||||
|
getInteger(
|
||||||
|
mediaFormat, MediaFormat.KEY_HEIGHT, /* defaultValue= */ Format.NO_VALUE))
|
||||||
|
.setPixelWidthHeightRatio(
|
||||||
|
getPixelWidthHeightRatio(mediaFormat, /* defaultValue= */ 1.0f))
|
||||||
|
.setMaxInputSize(
|
||||||
|
getInteger(
|
||||||
|
mediaFormat,
|
||||||
|
MediaFormat.KEY_MAX_INPUT_SIZE,
|
||||||
|
/* defaultValue= */ Format.NO_VALUE))
|
||||||
|
.setRotationDegrees(
|
||||||
|
getInteger(mediaFormat, MediaFormat.KEY_ROTATION, /* defaultValue= */ 0))
|
||||||
|
// TODO(b/278101856): Disallow invalid values after confirming.
|
||||||
|
.setColorInfo(getColorInfo(mediaFormat, /* allowInvalidValues= */ true))
|
||||||
|
.setSampleRate(
|
||||||
|
getInteger(
|
||||||
|
mediaFormat, MediaFormat.KEY_SAMPLE_RATE, /* defaultValue= */ Format.NO_VALUE))
|
||||||
|
.setChannelCount(
|
||||||
|
getInteger(
|
||||||
|
mediaFormat,
|
||||||
|
MediaFormat.KEY_CHANNEL_COUNT,
|
||||||
|
/* defaultValue= */ Format.NO_VALUE))
|
||||||
|
.setPcmEncoding(
|
||||||
|
getInteger(
|
||||||
|
mediaFormat,
|
||||||
|
MediaFormat.KEY_PCM_ENCODING,
|
||||||
|
/* defaultValue= */ Format.NO_VALUE));
|
||||||
|
|
||||||
|
ImmutableList.Builder<byte[]> csdBuffers = new ImmutableList.Builder<>();
|
||||||
|
int csdIndex = 0;
|
||||||
|
while (true) {
|
||||||
|
@Nullable ByteBuffer csdByteBuffer = mediaFormat.getByteBuffer("csd-" + csdIndex);
|
||||||
|
if (csdByteBuffer == null) {
|
||||||
|
break;
|
||||||
|
}
|
||||||
|
byte[] csdBufferData = new byte[csdByteBuffer.remaining()];
|
||||||
|
csdByteBuffer.get(csdBufferData);
|
||||||
|
csdByteBuffer.rewind();
|
||||||
|
|
||||||
|
csdBuffers.add(csdBufferData);
|
||||||
|
csdIndex++;
|
||||||
|
}
|
||||||
|
|
||||||
|
formatBuilder.setInitializationData(csdBuffers.build());
|
||||||
|
|
||||||
|
return formatBuilder.build();
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a {@link MediaFormat} representing the given ExoPlayer {@link Format}.
|
* Returns a {@link MediaFormat} representing the given ExoPlayer {@link Format}.
|
||||||
*
|
*
|
||||||
@ -196,23 +262,38 @@ public final class MediaFormatUtil {
|
|||||||
/**
|
/**
|
||||||
* Creates and returns a {@code ColorInfo}, if a valid instance is described in the {@link
|
* Creates and returns a {@code ColorInfo}, if a valid instance is described in the {@link
|
||||||
* MediaFormat}.
|
* MediaFormat}.
|
||||||
|
*
|
||||||
|
* <p>Under API 24, {@code null} will always be returned, because {@link MediaFormat} color keys
|
||||||
|
* like {@link MediaFormat#KEY_COLOR_STANDARD} were only added in API 24.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
public static ColorInfo getColorInfo(MediaFormat mediaFormat) {
|
public static ColorInfo getColorInfo(MediaFormat mediaFormat) {
|
||||||
if (SDK_INT < 29) {
|
return getColorInfo(mediaFormat, /* allowInvalidValues= */ false);
|
||||||
|
}
|
||||||
|
|
||||||
|
// Internal methods.
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private static ColorInfo getColorInfo(MediaFormat mediaFormat, boolean allowInvalidValues) {
|
||||||
|
if (SDK_INT < 24) {
|
||||||
|
// MediaFormat KEY_COLOR_TRANSFER and other KEY_COLOR values available from API 24.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
int colorSpace =
|
int colorSpace =
|
||||||
mediaFormat.getInteger(MediaFormat.KEY_COLOR_STANDARD, /* defaultValue= */ Format.NO_VALUE);
|
getInteger(
|
||||||
|
mediaFormat, MediaFormat.KEY_COLOR_STANDARD, /* defaultValue= */ Format.NO_VALUE);
|
||||||
int colorRange =
|
int colorRange =
|
||||||
mediaFormat.getInteger(MediaFormat.KEY_COLOR_RANGE, /* defaultValue= */ Format.NO_VALUE);
|
getInteger(mediaFormat, MediaFormat.KEY_COLOR_RANGE, /* defaultValue= */ Format.NO_VALUE);
|
||||||
int colorTransfer =
|
int colorTransfer =
|
||||||
mediaFormat.getInteger(MediaFormat.KEY_COLOR_TRANSFER, /* defaultValue= */ Format.NO_VALUE);
|
getInteger(
|
||||||
|
mediaFormat, MediaFormat.KEY_COLOR_TRANSFER, /* defaultValue= */ Format.NO_VALUE);
|
||||||
@Nullable
|
@Nullable
|
||||||
ByteBuffer hdrStaticInfoByteBuffer = mediaFormat.getByteBuffer(MediaFormat.KEY_HDR_STATIC_INFO);
|
ByteBuffer hdrStaticInfoByteBuffer = mediaFormat.getByteBuffer(MediaFormat.KEY_HDR_STATIC_INFO);
|
||||||
@Nullable
|
@Nullable
|
||||||
byte[] hdrStaticInfo =
|
byte[] hdrStaticInfo =
|
||||||
hdrStaticInfoByteBuffer != null ? getArray(hdrStaticInfoByteBuffer) : null;
|
hdrStaticInfoByteBuffer != null ? getArray(hdrStaticInfoByteBuffer) : null;
|
||||||
|
|
||||||
|
if (!allowInvalidValues) {
|
||||||
// Some devices may produce invalid values from MediaFormat#getInteger.
|
// Some devices may produce invalid values from MediaFormat#getInteger.
|
||||||
// See b/239435670 for more information.
|
// See b/239435670 for more information.
|
||||||
if (!isValidColorSpace(colorSpace)) {
|
if (!isValidColorSpace(colorSpace)) {
|
||||||
@ -224,22 +305,91 @@ public final class MediaFormatUtil {
|
|||||||
if (!isValidColorTransfer(colorTransfer)) {
|
if (!isValidColorTransfer(colorTransfer)) {
|
||||||
colorTransfer = Format.NO_VALUE;
|
colorTransfer = Format.NO_VALUE;
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
if (colorSpace != Format.NO_VALUE
|
if (colorSpace != Format.NO_VALUE
|
||||||
|| colorRange != Format.NO_VALUE
|
|| colorRange != Format.NO_VALUE
|
||||||
|| colorTransfer != Format.NO_VALUE
|
|| colorTransfer != Format.NO_VALUE
|
||||||
|| hdrStaticInfo != null) {
|
|| hdrStaticInfo != null) {
|
||||||
return new ColorInfo(colorSpace, colorRange, colorTransfer, hdrStaticInfo);
|
return new ColorInfo.Builder()
|
||||||
|
.setColorSpace(colorSpace)
|
||||||
|
.setColorRange(colorRange)
|
||||||
|
.setColorTransfer(colorTransfer)
|
||||||
|
.setHdrStaticInfo(hdrStaticInfo)
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Supports {@link MediaFormat#getInteger(String, int)} for {@code API < 29}. */
|
||||||
|
public static int getInteger(MediaFormat mediaFormat, String name, int defaultValue) {
|
||||||
|
return mediaFormat.containsKey(name) ? mediaFormat.getInteger(name) : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Supports {@link MediaFormat#getFloat(String, float)} for {@code API < 29}. */
|
||||||
|
public static float getFloat(MediaFormat mediaFormat, String name, float defaultValue) {
|
||||||
|
return mediaFormat.containsKey(name) ? mediaFormat.getFloat(name) : defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the frame rate from a {@link MediaFormat}.
|
||||||
|
*
|
||||||
|
* <p>The {@link MediaFormat#KEY_FRAME_RATE} can have both integer and float value so it returns
|
||||||
|
* which ever value is set.
|
||||||
|
*/
|
||||||
|
private static float getFrameRate(MediaFormat mediaFormat, float defaultValue) {
|
||||||
|
float frameRate = defaultValue;
|
||||||
|
if (mediaFormat.containsKey(MediaFormat.KEY_FRAME_RATE)) {
|
||||||
|
try {
|
||||||
|
frameRate = mediaFormat.getFloat(MediaFormat.KEY_FRAME_RATE);
|
||||||
|
} catch (ClassCastException ex) {
|
||||||
|
frameRate = mediaFormat.getInteger(MediaFormat.KEY_FRAME_RATE);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
return frameRate;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the ratio between a pixel's width and height for a {@link MediaFormat}. */
|
||||||
|
// Inlined MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH and MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT.
|
||||||
|
@SuppressLint("InlinedApi")
|
||||||
|
private static float getPixelWidthHeightRatio(MediaFormat mediaFormat, float defaultValue) {
|
||||||
|
if (mediaFormat.containsKey(MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH)
|
||||||
|
&& mediaFormat.containsKey(MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT)) {
|
||||||
|
return (float) mediaFormat.getInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_WIDTH)
|
||||||
|
/ (float) mediaFormat.getInteger(MediaFormat.KEY_PIXEL_ASPECT_RATIO_HEIGHT);
|
||||||
|
}
|
||||||
|
|
||||||
|
return defaultValue;
|
||||||
|
}
|
||||||
|
|
||||||
public static byte[] getArray(ByteBuffer byteBuffer) {
|
public static byte[] getArray(ByteBuffer byteBuffer) {
|
||||||
byte[] array = new byte[byteBuffer.remaining()];
|
byte[] array = new byte[byteBuffer.remaining()];
|
||||||
byteBuffer.get(array);
|
byteBuffer.get(array);
|
||||||
return array;
|
return array;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns whether a {@link MediaFormat} is a video format. */
|
||||||
|
public static boolean isVideoFormat(MediaFormat mediaFormat) {
|
||||||
|
return MimeTypes.isVideo(mediaFormat.getString(MediaFormat.KEY_MIME));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns whether a {@link MediaFormat} is an audio format. */
|
||||||
|
public static boolean isAudioFormat(MediaFormat mediaFormat) {
|
||||||
|
return MimeTypes.isAudio(mediaFormat.getString(MediaFormat.KEY_MIME));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Returns the time lapse capture FPS from the given {@link MediaFormat} if it was set. */
|
||||||
|
@Nullable
|
||||||
|
public static Integer getTimeLapseFrameRate(MediaFormat format) {
|
||||||
|
if (format.containsKey("time-lapse-enable")
|
||||||
|
&& format.getInteger("time-lapse-enable") > 0
|
||||||
|
&& format.containsKey("time-lapse-fps")) {
|
||||||
|
return format.getInteger("time-lapse-fps");
|
||||||
|
} else {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
// Internal methods.
|
// Internal methods.
|
||||||
|
|
||||||
private static void setBooleanAsInt(MediaFormat format, String key, int value) {
|
private static void setBooleanAsInt(MediaFormat format, String key, int value) {
|
||||||
@ -322,7 +472,10 @@ public final class MediaFormatUtil {
|
|||||||
/** Whether this is a valid {@link C.ColorTransfer} instance. */
|
/** Whether this is a valid {@link C.ColorTransfer} instance. */
|
||||||
private static boolean isValidColorTransfer(int colorTransfer) {
|
private static boolean isValidColorTransfer(int colorTransfer) {
|
||||||
// LINT.IfChange(color_transfer)
|
// LINT.IfChange(color_transfer)
|
||||||
return colorTransfer == C.COLOR_TRANSFER_SDR
|
// C.COLOR_TRANSFER_GAMMA_2_2 & C.COLOR_TRANSFER_SRGB aren't valid because MediaCodec, and
|
||||||
|
// hence MediaFormat, do not support them.
|
||||||
|
return colorTransfer == C.COLOR_TRANSFER_LINEAR
|
||||||
|
|| colorTransfer == C.COLOR_TRANSFER_SDR
|
||||||
|| colorTransfer == C.COLOR_TRANSFER_ST2084
|
|| colorTransfer == C.COLOR_TRANSFER_ST2084
|
||||||
|| colorTransfer == C.COLOR_TRANSFER_HLG
|
|| colorTransfer == C.COLOR_TRANSFER_HLG
|
||||||
|| colorTransfer == Format.NO_VALUE;
|
|| colorTransfer == Format.NO_VALUE;
|
||||||
|
@ -15,8 +15,13 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.common.util;
|
package androidx.media3.common.util;
|
||||||
|
|
||||||
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
|
import static androidx.media3.common.util.Assertions.checkState;
|
||||||
|
|
||||||
|
import android.os.SystemClock;
|
||||||
import androidx.annotation.GuardedBy;
|
import androidx.annotation.GuardedBy;
|
||||||
import androidx.media3.common.C;
|
import androidx.media3.common.C;
|
||||||
|
import java.util.concurrent.TimeoutException;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Adjusts and offsets sample timestamps. MPEG-2 TS timestamps scaling and adjustment is supported,
|
* Adjusts and offsets sample timestamps. MPEG-2 TS timestamps scaling and adjustment is supported,
|
||||||
@ -100,21 +105,40 @@ public final class TimestampAdjuster {
|
|||||||
* @param canInitialize Whether the caller is able to initialize the adjuster, if needed.
|
* @param canInitialize Whether the caller is able to initialize the adjuster, if needed.
|
||||||
* @param nextSampleTimestampUs The desired timestamp for the next sample loaded by the calling
|
* @param nextSampleTimestampUs The desired timestamp for the next sample loaded by the calling
|
||||||
* thread, in microseconds. Only used if {@code canInitialize} is {@code true}.
|
* thread, in microseconds. Only used if {@code canInitialize} is {@code true}.
|
||||||
|
* @param timeoutMs The timeout for the thread to wait for the timestamp adjuster to initialize,
|
||||||
|
* in milliseconds. A timeout of zero is interpreted as an infinite timeout.
|
||||||
* @throws InterruptedException If the thread is interrupted whilst blocked waiting for
|
* @throws InterruptedException If the thread is interrupted whilst blocked waiting for
|
||||||
* initialization to complete.
|
* initialization to complete.
|
||||||
|
* @throws TimeoutException If the thread is timeout whilst blocked waiting for initialization to
|
||||||
|
* complete.
|
||||||
*/
|
*/
|
||||||
public synchronized void sharedInitializeOrWait(boolean canInitialize, long nextSampleTimestampUs)
|
public synchronized void sharedInitializeOrWait(
|
||||||
throws InterruptedException {
|
boolean canInitialize, long nextSampleTimestampUs, long timeoutMs)
|
||||||
Assertions.checkState(firstSampleTimestampUs == MODE_SHARED);
|
throws InterruptedException, TimeoutException {
|
||||||
if (timestampOffsetUs != C.TIME_UNSET) {
|
checkState(firstSampleTimestampUs == MODE_SHARED);
|
||||||
// Already initialized.
|
if (isInitialized()) {
|
||||||
return;
|
return;
|
||||||
} else if (canInitialize) {
|
} else if (canInitialize) {
|
||||||
this.nextSampleTimestampUs.set(nextSampleTimestampUs);
|
this.nextSampleTimestampUs.set(nextSampleTimestampUs);
|
||||||
} else {
|
} else {
|
||||||
// Wait for another calling thread to complete initialization.
|
// Wait for another calling thread to complete initialization.
|
||||||
while (timestampOffsetUs == C.TIME_UNSET) {
|
long totalWaitDurationMs = 0;
|
||||||
|
long remainingTimeoutMs = timeoutMs;
|
||||||
|
while (!isInitialized()) {
|
||||||
|
if (timeoutMs == 0) {
|
||||||
wait();
|
wait();
|
||||||
|
} else {
|
||||||
|
checkState(remainingTimeoutMs > 0);
|
||||||
|
long waitStartingTimeMs = SystemClock.elapsedRealtime();
|
||||||
|
wait(remainingTimeoutMs);
|
||||||
|
totalWaitDurationMs += SystemClock.elapsedRealtime() - waitStartingTimeMs;
|
||||||
|
if (totalWaitDurationMs >= timeoutMs && !isInitialized()) {
|
||||||
|
String message =
|
||||||
|
"TimestampAdjuster failed to initialize in " + timeoutMs + " milliseconds";
|
||||||
|
throw new TimeoutException(message);
|
||||||
|
}
|
||||||
|
remainingTimeoutMs = timeoutMs - totalWaitDurationMs;
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -195,10 +219,10 @@ public final class TimestampAdjuster {
|
|||||||
if (timeUs == C.TIME_UNSET) {
|
if (timeUs == C.TIME_UNSET) {
|
||||||
return C.TIME_UNSET;
|
return C.TIME_UNSET;
|
||||||
}
|
}
|
||||||
if (timestampOffsetUs == C.TIME_UNSET) {
|
if (!isInitialized()) {
|
||||||
long desiredSampleTimestampUs =
|
long desiredSampleTimestampUs =
|
||||||
firstSampleTimestampUs == MODE_SHARED
|
firstSampleTimestampUs == MODE_SHARED
|
||||||
? Assertions.checkNotNull(nextSampleTimestampUs.get())
|
? checkNotNull(nextSampleTimestampUs.get())
|
||||||
: firstSampleTimestampUs;
|
: firstSampleTimestampUs;
|
||||||
timestampOffsetUs = desiredSampleTimestampUs - timeUs;
|
timestampOffsetUs = desiredSampleTimestampUs - timeUs;
|
||||||
// Notify threads waiting for the timestamp offset to be determined.
|
// Notify threads waiting for the timestamp offset to be determined.
|
||||||
@ -208,6 +232,11 @@ public final class TimestampAdjuster {
|
|||||||
return timeUs + timestampOffsetUs;
|
return timeUs + timestampOffsetUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Returns whether the instance is initialized with a timestamp offset. */
|
||||||
|
public synchronized boolean isInitialized() {
|
||||||
|
return timestampOffsetUs != C.TIME_UNSET;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts a 90 kHz clock timestamp to a timestamp in microseconds.
|
* Converts a 90 kHz clock timestamp to a timestamp in microseconds.
|
||||||
*
|
*
|
||||||
|
@ -16,6 +16,8 @@
|
|||||||
package androidx.media3.common.util;
|
package androidx.media3.common.util;
|
||||||
|
|
||||||
import static android.content.Context.UI_MODE_SERVICE;
|
import static android.content.Context.UI_MODE_SERVICE;
|
||||||
|
import static androidx.media3.common.Player.COMMAND_PLAY_PAUSE;
|
||||||
|
import static androidx.media3.common.Player.COMMAND_PREPARE;
|
||||||
import static androidx.media3.common.Player.COMMAND_SEEK_BACK;
|
import static androidx.media3.common.Player.COMMAND_SEEK_BACK;
|
||||||
import static androidx.media3.common.Player.COMMAND_SEEK_FORWARD;
|
import static androidx.media3.common.Player.COMMAND_SEEK_FORWARD;
|
||||||
import static androidx.media3.common.Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM;
|
import static androidx.media3.common.Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM;
|
||||||
@ -25,6 +27,7 @@ import static androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT;
|
|||||||
import static androidx.media3.common.Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM;
|
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.util.Assertions.checkArgument;
|
||||||
import static androidx.media3.common.util.Assertions.checkNotNull;
|
import static androidx.media3.common.util.Assertions.checkNotNull;
|
||||||
import static java.lang.Math.abs;
|
import static java.lang.Math.abs;
|
||||||
import static java.lang.Math.max;
|
import static java.lang.Math.max;
|
||||||
@ -51,6 +54,7 @@ import android.graphics.drawable.Drawable;
|
|||||||
import android.hardware.display.DisplayManager;
|
import android.hardware.display.DisplayManager;
|
||||||
import android.media.AudioFormat;
|
import android.media.AudioFormat;
|
||||||
import android.media.AudioManager;
|
import android.media.AudioManager;
|
||||||
|
import android.media.MediaCodec;
|
||||||
import android.media.MediaDrm;
|
import android.media.MediaDrm;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
import android.os.Build;
|
||||||
@ -114,6 +118,7 @@ import java.util.concurrent.CancellationException;
|
|||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.ExecutorService;
|
import java.util.concurrent.ExecutorService;
|
||||||
import java.util.concurrent.Executors;
|
import java.util.concurrent.Executors;
|
||||||
|
import java.util.concurrent.ScheduledExecutorService;
|
||||||
import java.util.regex.Matcher;
|
import java.util.regex.Matcher;
|
||||||
import java.util.regex.Pattern;
|
import java.util.regex.Pattern;
|
||||||
import java.util.zip.DataFormatException;
|
import java.util.zip.DataFormatException;
|
||||||
@ -122,6 +127,7 @@ import java.util.zip.Inflater;
|
|||||||
import org.checkerframework.checker.initialization.qual.UnknownInitialization;
|
import org.checkerframework.checker.initialization.qual.UnknownInitialization;
|
||||||
import org.checkerframework.checker.nullness.compatqual.NullableType;
|
import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||||
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
|
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
|
||||||
|
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
|
||||||
import org.checkerframework.checker.nullness.qual.PolyNull;
|
import org.checkerframework.checker.nullness.qual.PolyNull;
|
||||||
|
|
||||||
/** Miscellaneous utility methods. */
|
/** Miscellaneous utility methods. */
|
||||||
@ -201,6 +207,55 @@ public final class Util {
|
|||||||
return outputStream.toByteArray();
|
return outputStream.toByteArray();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Converts an integer into an equivalent byte array. */
|
||||||
|
@UnstableApi
|
||||||
|
public static byte[] toByteArray(int value) {
|
||||||
|
return new byte[] {
|
||||||
|
(byte) (value >> 24), (byte) (value >> 16), (byte) (value >> 8), (byte) value
|
||||||
|
};
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Converts an array of integers into an equivalent byte array.
|
||||||
|
*
|
||||||
|
* <p>Each integer is converted into 4 sequential bytes.
|
||||||
|
*/
|
||||||
|
@UnstableApi
|
||||||
|
public static byte[] toByteArray(int... values) {
|
||||||
|
byte[] array = new byte[values.length * 4];
|
||||||
|
int index = 0;
|
||||||
|
for (int value : values) {
|
||||||
|
byte[] byteArray = toByteArray(value);
|
||||||
|
array[index++] = byteArray[0];
|
||||||
|
array[index++] = byteArray[1];
|
||||||
|
array[index++] = byteArray[2];
|
||||||
|
array[index++] = byteArray[3];
|
||||||
|
}
|
||||||
|
return array;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Converts a float into an equivalent byte array. */
|
||||||
|
@UnstableApi
|
||||||
|
public static byte[] toByteArray(float value) {
|
||||||
|
return toByteArray(Float.floatToIntBits(value));
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Converts a byte array into a float. */
|
||||||
|
@UnstableApi
|
||||||
|
public static float toFloat(byte[] bytes) {
|
||||||
|
checkArgument(bytes.length == 4);
|
||||||
|
int intBits =
|
||||||
|
bytes[0] << 24 | (bytes[1] & 0xFF) << 16 | (bytes[2] & 0xFF) << 8 | (bytes[3] & 0xFF);
|
||||||
|
return Float.intBitsToFloat(intBits);
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Converts a byte array into an integer. */
|
||||||
|
@UnstableApi
|
||||||
|
public static int toInteger(byte[] bytes) {
|
||||||
|
checkArgument(bytes.length == 4);
|
||||||
|
return bytes[0] << 24 | bytes[1] << 16 | bytes[2] << 8 | bytes[3];
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Registers a {@link BroadcastReceiver} that's not intended to receive broadcasts from other
|
* Registers a {@link BroadcastReceiver} that's not intended to receive broadcasts from other
|
||||||
* apps. This will be enforced by specifying {@link Context#RECEIVER_NOT_EXPORTED} if {@link
|
* apps. This will be enforced by specifying {@link Context#RECEIVER_NOT_EXPORTED} if {@link
|
||||||
@ -440,7 +495,7 @@ public final class Util {
|
|||||||
@UnstableApi
|
@UnstableApi
|
||||||
@SuppressWarnings({"nullness:argument", "nullness:return"})
|
@SuppressWarnings({"nullness:argument", "nullness:return"})
|
||||||
public static <T> T[] nullSafeArrayCopy(T[] input, int length) {
|
public static <T> T[] nullSafeArrayCopy(T[] input, int length) {
|
||||||
Assertions.checkArgument(length <= input.length);
|
checkArgument(length <= input.length);
|
||||||
return Arrays.copyOf(input, length);
|
return Arrays.copyOf(input, length);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -455,8 +510,8 @@ public final class Util {
|
|||||||
@UnstableApi
|
@UnstableApi
|
||||||
@SuppressWarnings({"nullness:argument", "nullness:return"})
|
@SuppressWarnings({"nullness:argument", "nullness:return"})
|
||||||
public static <T> T[] nullSafeArrayCopyOfRange(T[] input, int from, int to) {
|
public static <T> T[] nullSafeArrayCopyOfRange(T[] input, int from, int to) {
|
||||||
Assertions.checkArgument(0 <= from);
|
checkArgument(0 <= from);
|
||||||
Assertions.checkArgument(to <= input.length);
|
checkArgument(to <= input.length);
|
||||||
return Arrays.copyOfRange(input, from, to);
|
return Arrays.copyOfRange(input, from, to);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -720,6 +775,17 @@ public final class Util {
|
|||||||
return Executors.newSingleThreadExecutor(runnable -> new Thread(runnable, threadName));
|
return Executors.newSingleThreadExecutor(runnable -> new Thread(runnable, threadName));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Instantiates a new single threaded scheduled executor whose thread has the specified name.
|
||||||
|
*
|
||||||
|
* @param threadName The name of the thread.
|
||||||
|
* @return The executor.
|
||||||
|
*/
|
||||||
|
@UnstableApi
|
||||||
|
public static ScheduledExecutorService newSingleThreadScheduledExecutor(String threadName) {
|
||||||
|
return Executors.newSingleThreadScheduledExecutor(runnable -> new Thread(runnable, threadName));
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Closes a {@link Closeable}, suppressing any {@link IOException} that may occur. Both {@link
|
* Closes a {@link Closeable}, suppressing any {@link IOException} that may occur. Both {@link
|
||||||
* java.io.OutputStream} and {@link InputStream} are {@code Closeable}.
|
* java.io.OutputStream} and {@link InputStream} are {@code Closeable}.
|
||||||
@ -1575,7 +1641,7 @@ public final class Util {
|
|||||||
@UnstableApi
|
@UnstableApi
|
||||||
public static int getIntegerCodeForString(String string) {
|
public static int getIntegerCodeForString(String string) {
|
||||||
int length = string.length();
|
int length = string.length();
|
||||||
Assertions.checkArgument(length <= 4);
|
checkArgument(length <= 4);
|
||||||
int result = 0;
|
int result = 0;
|
||||||
for (int i = 0; i < length; i++) {
|
for (int i = 0; i < length; i++) {
|
||||||
result <<= 8;
|
result <<= 8;
|
||||||
@ -1862,6 +1928,14 @@ public final class Util {
|
|||||||
return AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER;
|
return AudioFormat.CHANNEL_OUT_5POINT1 | AudioFormat.CHANNEL_OUT_BACK_CENTER;
|
||||||
case 8:
|
case 8:
|
||||||
return AudioFormat.CHANNEL_OUT_7POINT1_SURROUND;
|
return AudioFormat.CHANNEL_OUT_7POINT1_SURROUND;
|
||||||
|
case 10:
|
||||||
|
if (Util.SDK_INT >= 32) {
|
||||||
|
return AudioFormat.CHANNEL_OUT_5POINT1POINT4;
|
||||||
|
} else {
|
||||||
|
// Before API 32, height channel masks are not available. For those 10-channel streams
|
||||||
|
// supported on the audio output devices (e.g. DTS:X P2), we use 7.1-surround instead.
|
||||||
|
return AudioFormat.CHANNEL_OUT_7POINT1_SURROUND;
|
||||||
|
}
|
||||||
case 12:
|
case 12:
|
||||||
return AudioFormat.CHANNEL_OUT_7POINT1POINT4;
|
return AudioFormat.CHANNEL_OUT_7POINT1POINT4;
|
||||||
default:
|
default:
|
||||||
@ -2768,6 +2842,40 @@ public final class Util {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns the number of maximum pending output frames that are allowed on a {@link MediaCodec}
|
||||||
|
* decoder.
|
||||||
|
*/
|
||||||
|
@UnstableApi
|
||||||
|
public static int getMaxPendingFramesCountForMediaCodecDecoders(
|
||||||
|
Context context, String codecName, boolean requestedHdrToneMapping) {
|
||||||
|
if (SDK_INT < 29
|
||||||
|
|| context.getApplicationContext().getApplicationInfo().targetSdkVersion < 29) {
|
||||||
|
// Prior to API 29, decoders may drop frames to keep their output surface from growing out of
|
||||||
|
// bounds. From API 29, if the app targets API 29 or later, the {@link
|
||||||
|
// MediaFormat#KEY_ALLOW_FRAME_DROP} key prevents frame dropping even when the surface is
|
||||||
|
// full.
|
||||||
|
// Frame dropping is never desired, so a workaround is needed for older API levels.
|
||||||
|
// Allow a maximum of one frame to be pending at a time to prevent frame dropping.
|
||||||
|
// TODO(b/226330223): Investigate increasing this limit.
|
||||||
|
return 1;
|
||||||
|
}
|
||||||
|
// Limit the maximum amount of frames for all decoders. This is a tentative value that should be
|
||||||
|
// large enough to avoid significant performance degradation, but small enough to bypass decoder
|
||||||
|
// issues.
|
||||||
|
//
|
||||||
|
// TODO: b/278234847 - Evaluate whether this reduces decoder timeouts, and consider restoring
|
||||||
|
// prior higher limits as appropriate.
|
||||||
|
//
|
||||||
|
// Some OMX decoders don't correctly track their number of output buffers available, and get
|
||||||
|
// stuck if too many frames are rendered without being processed. This value is experimentally
|
||||||
|
// determined. See also
|
||||||
|
// b/213455700, b/230097284, b/229978305, and b/245491744.
|
||||||
|
//
|
||||||
|
// OMX video codecs should no longer exist from android.os.Build.DEVICE_INITIAL_SDK_INT 31+.
|
||||||
|
return 5;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns string representation of a {@link C.FormatSupport} flag.
|
* Returns string representation of a {@link C.FormatSupport} flag.
|
||||||
*
|
*
|
||||||
@ -2872,6 +2980,87 @@ public final class Util {
|
|||||||
return Integer.toString(i, Character.MAX_RADIX);
|
return Integer.toString(i, Character.MAX_RADIX);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns whether a play button should be presented on a UI element for playback control. If
|
||||||
|
* {@code false}, a pause button should be shown instead.
|
||||||
|
*
|
||||||
|
* <p>Use {@link #handlePlayPauseButtonAction}, {@link #handlePlayButtonAction} or {@link
|
||||||
|
* #handlePauseButtonAction} to handle the interaction with the play or pause button UI element.
|
||||||
|
*
|
||||||
|
* @param player The {@link Player}. May be null.
|
||||||
|
*/
|
||||||
|
@EnsuresNonNullIf(result = false, expression = "#1")
|
||||||
|
public static boolean shouldShowPlayButton(@Nullable Player player) {
|
||||||
|
return player == null
|
||||||
|
|| !player.getPlayWhenReady()
|
||||||
|
|| player.getPlaybackState() == Player.STATE_IDLE
|
||||||
|
|| player.getPlaybackState() == Player.STATE_ENDED;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the player to handle an interaction with a play button.
|
||||||
|
*
|
||||||
|
* <p>This method assumes the play button is enabled if {@link #shouldShowPlayButton} returns
|
||||||
|
* true.
|
||||||
|
*
|
||||||
|
* @param player The {@link Player}. May be null.
|
||||||
|
* @return Whether a player method was triggered to handle this action.
|
||||||
|
*/
|
||||||
|
public static boolean handlePlayButtonAction(@Nullable Player player) {
|
||||||
|
if (player == null) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
@Player.State int state = player.getPlaybackState();
|
||||||
|
boolean methodTriggered = false;
|
||||||
|
if (state == Player.STATE_IDLE && player.isCommandAvailable(COMMAND_PREPARE)) {
|
||||||
|
player.prepare();
|
||||||
|
methodTriggered = true;
|
||||||
|
} else if (state == Player.STATE_ENDED
|
||||||
|
&& player.isCommandAvailable(COMMAND_SEEK_TO_DEFAULT_POSITION)) {
|
||||||
|
player.seekToDefaultPosition();
|
||||||
|
methodTriggered = true;
|
||||||
|
}
|
||||||
|
if (player.isCommandAvailable(COMMAND_PLAY_PAUSE)) {
|
||||||
|
player.play();
|
||||||
|
methodTriggered = true;
|
||||||
|
}
|
||||||
|
return methodTriggered;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the player to handle an interaction with a pause button.
|
||||||
|
*
|
||||||
|
* <p>This method assumes the pause button is enabled if {@link #shouldShowPlayButton} returns
|
||||||
|
* false.
|
||||||
|
*
|
||||||
|
* @param player The {@link Player}. May be null.
|
||||||
|
* @return Whether a player method was triggered to handle this action.
|
||||||
|
*/
|
||||||
|
public static boolean handlePauseButtonAction(@Nullable Player player) {
|
||||||
|
if (player != null && player.isCommandAvailable(COMMAND_PLAY_PAUSE)) {
|
||||||
|
player.pause();
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Updates the player to handle an interaction with a play or pause button.
|
||||||
|
*
|
||||||
|
* <p>This method assumes that the UI element enables a play button if {@link
|
||||||
|
* #shouldShowPlayButton} returns true and a pause button otherwise.
|
||||||
|
*
|
||||||
|
* @param player The {@link Player}. May be null.
|
||||||
|
* @return Whether a player method was triggered to handle this action.
|
||||||
|
*/
|
||||||
|
public static boolean handlePlayPauseButtonAction(@Nullable Player player) {
|
||||||
|
if (shouldShowPlayButton(player)) {
|
||||||
|
return handlePlayButtonAction(player);
|
||||||
|
} else {
|
||||||
|
return handlePauseButtonAction(player);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
private static String getSystemProperty(String name) {
|
private static String getSystemProperty(String name) {
|
||||||
try {
|
try {
|
||||||
|
@ -487,6 +487,65 @@ public class AdPlaybackStateTest {
|
|||||||
assertThat(AdPlaybackState.AdGroup.CREATOR.fromBundle(adGroup.toBundle())).isEqualTo(adGroup);
|
assertThat(AdPlaybackState.AdGroup.CREATOR.fromBundle(adGroup.toBundle())).isEqualTo(adGroup);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void withLivePostrollPlaceholderAppended_emptyAdPlaybackState_insertsPlaceholder() {
|
||||||
|
AdPlaybackState adPlaybackState =
|
||||||
|
new AdPlaybackState("adsId").withLivePostrollPlaceholderAppended();
|
||||||
|
|
||||||
|
assertThat(adPlaybackState.adGroupCount).isEqualTo(1);
|
||||||
|
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).timeUs)
|
||||||
|
.isEqualTo(C.TIME_END_OF_SOURCE);
|
||||||
|
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).count).isEqualTo(C.LENGTH_UNSET);
|
||||||
|
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).isServerSideInserted).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void withLivePostrollPlaceholderAppended_withExistingAdGroups_appendsPlaceholder() {
|
||||||
|
AdPlaybackState adPlaybackState =
|
||||||
|
new AdPlaybackState("state", /* adGroupTimesUs...= */ 0L, 10_000_000L)
|
||||||
|
.withIsServerSideInserted(/* adGroupIndex= */ 0, true)
|
||||||
|
.withIsServerSideInserted(/* adGroupIndex= */ 1, true)
|
||||||
|
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
|
||||||
|
.withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 1)
|
||||||
|
.withAdDurationsUs(/* adGroupIndex= */ 0, /* adDurationsUs...= */ 10_000_000L)
|
||||||
|
.withAdDurationsUs(/* adGroupIndex= */ 1, /* adDurationsUs...= */ 5_000_000L);
|
||||||
|
|
||||||
|
adPlaybackState = adPlaybackState.withLivePostrollPlaceholderAppended();
|
||||||
|
|
||||||
|
assertThat(adPlaybackState.adGroupCount).isEqualTo(3);
|
||||||
|
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 2).timeUs)
|
||||||
|
.isEqualTo(C.TIME_END_OF_SOURCE);
|
||||||
|
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 2).count).isEqualTo(C.LENGTH_UNSET);
|
||||||
|
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 2).isServerSideInserted).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void endsWithLivePostrollPlaceHolder_withExistingAdGroups_postrollDetected() {
|
||||||
|
AdPlaybackState adPlaybackState =
|
||||||
|
new AdPlaybackState("adsId", /* adGroupTimesUs...= */ 0L, 10_000_000L)
|
||||||
|
.withIsServerSideInserted(/* adGroupIndex= */ 0, true)
|
||||||
|
.withIsServerSideInserted(/* adGroupIndex= */ 1, true)
|
||||||
|
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
|
||||||
|
.withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 1)
|
||||||
|
.withAdDurationsUs(/* adGroupIndex= */ 0, /* adDurationsUs...= */ 10_000_000L)
|
||||||
|
.withAdDurationsUs(/* adGroupIndex= */ 1, /* adDurationsUs...= */ 5_000_000L);
|
||||||
|
|
||||||
|
boolean endsWithLivePostrollPlaceHolder = adPlaybackState.endsWithLivePostrollPlaceHolder();
|
||||||
|
|
||||||
|
assertThat(endsWithLivePostrollPlaceHolder).isFalse();
|
||||||
|
|
||||||
|
adPlaybackState = adPlaybackState.withLivePostrollPlaceholderAppended();
|
||||||
|
endsWithLivePostrollPlaceHolder = adPlaybackState.endsWithLivePostrollPlaceHolder();
|
||||||
|
|
||||||
|
assertThat(endsWithLivePostrollPlaceHolder).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void endsWithLivePostrollPlaceHolder_emptyAdPlaybackState_postrollNotDetected() {
|
||||||
|
assertThat(AdPlaybackState.NONE.endsWithLivePostrollPlaceHolder()).isFalse();
|
||||||
|
assertThat(new AdPlaybackState("adsId").endsWithLivePostrollPlaceHolder()).isFalse();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void
|
public void
|
||||||
getAdGroupIndexAfterPositionUs_withClientSideInsertedAds_returnsNextAdGroupWithUnplayedAds() {
|
getAdGroupIndexAfterPositionUs_withClientSideInsertedAds_returnsNextAdGroupWithUnplayedAds() {
|
||||||
@ -634,4 +693,103 @@ public class AdPlaybackStateTest {
|
|||||||
/* positionUs= */ C.TIME_END_OF_SOURCE, /* periodDurationUs= */ 5000))
|
/* positionUs= */ C.TIME_END_OF_SOURCE, /* periodDurationUs= */ 5000))
|
||||||
.isEqualTo(C.INDEX_UNSET);
|
.isEqualTo(C.INDEX_UNSET);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
getAdGroupIndexAfterPositionUs_withServerSidePostrollPlaceholderForLive_placeholderAsNextAdGroupIndex() {
|
||||||
|
AdPlaybackState state =
|
||||||
|
new AdPlaybackState(/* adsId= */ new Object(), /* adGroupTimesUs...= */ 2000)
|
||||||
|
.withIsServerSideInserted(/* adGroupIndex= */ 0, /* isServerSideInserted= */ true)
|
||||||
|
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
|
||||||
|
.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0)
|
||||||
|
.withLivePostrollPlaceholderAppended();
|
||||||
|
|
||||||
|
assertThat(
|
||||||
|
state.getAdGroupIndexAfterPositionUs(
|
||||||
|
/* positionUs= */ 1999, /* periodDurationUs= */ 5000))
|
||||||
|
.isEqualTo(0);
|
||||||
|
assertThat(
|
||||||
|
state.getAdGroupIndexAfterPositionUs(
|
||||||
|
/* positionUs= */ 2000, /* periodDurationUs= */ C.TIME_UNSET))
|
||||||
|
.isEqualTo(1);
|
||||||
|
assertThat(
|
||||||
|
state.getAdGroupIndexAfterPositionUs(
|
||||||
|
/* positionUs= */ 2000, /* periodDurationUs= */ 5000))
|
||||||
|
.isEqualTo(1);
|
||||||
|
assertThat(
|
||||||
|
state.getAdGroupIndexAfterPositionUs(
|
||||||
|
/* positionUs= */ C.TIME_END_OF_SOURCE, /* periodDurationUs= */ C.TIME_UNSET))
|
||||||
|
.isEqualTo(C.INDEX_UNSET);
|
||||||
|
assertThat(
|
||||||
|
state.getAdGroupIndexAfterPositionUs(
|
||||||
|
/* positionUs= */ C.TIME_END_OF_SOURCE, /* periodDurationUs= */ 5000))
|
||||||
|
.isEqualTo(C.INDEX_UNSET);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
getAdGroupIndexForPositionUs_withServerSidePostrollPlaceholderForLive_ignoresPlaceholder() {
|
||||||
|
AdPlaybackState state =
|
||||||
|
new AdPlaybackState("adsId", /* adGroupTimesUs...= */ 0L, 5_000_000L, C.TIME_END_OF_SOURCE)
|
||||||
|
.withIsServerSideInserted(/* adGroupIndex= */ 0, /* isServerSideInserted= */ true)
|
||||||
|
.withIsServerSideInserted(/* adGroupIndex= */ 1, /* isServerSideInserted= */ true)
|
||||||
|
.withIsServerSideInserted(/* adGroupIndex= */ 2, /* isServerSideInserted= */ true)
|
||||||
|
.withAdCount(/* adGroupIndex= */ 0, /* adCount= */ 1)
|
||||||
|
.withAdCount(/* adGroupIndex= */ 1, /* adCount= */ 1)
|
||||||
|
.withPlayedAd(/* adGroupIndex= */ 0, /* adIndexInAdGroup= */ 0);
|
||||||
|
|
||||||
|
assertThat(
|
||||||
|
state.getAdGroupIndexForPositionUs(
|
||||||
|
/* positionUs= */ 4_999_999L, /* periodDurationUs= */ 10_000_000L))
|
||||||
|
.isEqualTo(C.INDEX_UNSET);
|
||||||
|
assertThat(
|
||||||
|
state.getAdGroupIndexForPositionUs(
|
||||||
|
/* positionUs= */ 4_999_999L, /* periodDurationUs= */ C.TIME_UNSET))
|
||||||
|
.isEqualTo(C.INDEX_UNSET);
|
||||||
|
assertThat(
|
||||||
|
state.getAdGroupIndexForPositionUs(
|
||||||
|
/* positionUs= */ 5_000_000L, /* periodDurationUs= */ 10_000_000L))
|
||||||
|
.isEqualTo(1);
|
||||||
|
assertThat(
|
||||||
|
state.getAdGroupIndexForPositionUs(
|
||||||
|
/* positionUs= */ 5_000_000L, /* periodDurationUs= */ C.TIME_UNSET))
|
||||||
|
.isEqualTo(1);
|
||||||
|
assertThat(
|
||||||
|
state.getAdGroupIndexForPositionUs(
|
||||||
|
/* positionUs= */ C.TIME_END_OF_SOURCE, /* periodDurationUs= */ 10_000_000L))
|
||||||
|
.isEqualTo(1);
|
||||||
|
assertThat(
|
||||||
|
state.getAdGroupIndexForPositionUs(
|
||||||
|
/* positionUs= */ C.TIME_END_OF_SOURCE, /* periodDurationUs= */ C.TIME_UNSET))
|
||||||
|
.isEqualTo(1);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
getAdGroupIndexForPositionUs_withOnlyServerSidePostrollPlaceholderForLive_ignoresPlaceholder() {
|
||||||
|
AdPlaybackState state =
|
||||||
|
new AdPlaybackState("adsId", /* adGroupTimesUs...= */ C.TIME_END_OF_SOURCE)
|
||||||
|
.withIsServerSideInserted(/* adGroupIndex= */ 0, /* isServerSideInserted= */ true);
|
||||||
|
|
||||||
|
assertThat(
|
||||||
|
state.getAdGroupIndexForPositionUs(
|
||||||
|
/* positionUs= */ 5_000_000L, /* periodDurationUs= */ 10_000_000L))
|
||||||
|
.isEqualTo(C.INDEX_UNSET);
|
||||||
|
assertThat(
|
||||||
|
state.getAdGroupIndexForPositionUs(
|
||||||
|
/* positionUs= */ 5_000_000L, /* periodDurationUs= */ C.TIME_UNSET))
|
||||||
|
.isEqualTo(C.INDEX_UNSET);
|
||||||
|
assertThat(
|
||||||
|
state.getAdGroupIndexForPositionUs(
|
||||||
|
/* positionUs= */ 10_000_001L, /* periodDurationUs= */ 10_000_000L))
|
||||||
|
.isEqualTo(C.INDEX_UNSET);
|
||||||
|
assertThat(
|
||||||
|
state.getAdGroupIndexForPositionUs(
|
||||||
|
/* positionUs= */ C.TIME_END_OF_SOURCE, /* periodDurationUs= */ 10_000_000L))
|
||||||
|
.isEqualTo(C.INDEX_UNSET);
|
||||||
|
assertThat(
|
||||||
|
state.getAdGroupIndexForPositionUs(
|
||||||
|
/* positionUs= */ C.TIME_END_OF_SOURCE, /* periodDurationUs= */ C.TIME_UNSET))
|
||||||
|
.isEqualTo(C.INDEX_UNSET);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -28,7 +28,11 @@ public class DeviceInfoTest {
|
|||||||
@Test
|
@Test
|
||||||
public void roundTripViaBundle_yieldsEqualInstance() {
|
public void roundTripViaBundle_yieldsEqualInstance() {
|
||||||
DeviceInfo deviceInfo =
|
DeviceInfo deviceInfo =
|
||||||
new DeviceInfo(DeviceInfo.PLAYBACK_TYPE_REMOTE, /* minVolume= */ 1, /* maxVolume= */ 9);
|
new DeviceInfo.Builder(DeviceInfo.PLAYBACK_TYPE_REMOTE)
|
||||||
|
.setMinVolume(1)
|
||||||
|
.setMaxVolume(9)
|
||||||
|
.setRoutingControllerId("route")
|
||||||
|
.build();
|
||||||
|
|
||||||
assertThat(DeviceInfo.CREATOR.fromBundle(deviceInfo.toBundle())).isEqualTo(deviceInfo);
|
assertThat(DeviceInfo.CREATOR.fromBundle(deviceInfo.toBundle())).isEqualTo(deviceInfo);
|
||||||
}
|
}
|
||||||
|
@ -247,6 +247,25 @@ public class MediaItemTest {
|
|||||||
.build());
|
.build());
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createDrmConfigurationInstance_roundTripViaBundle_yieldsEqualInstance() {
|
||||||
|
MediaItem.DrmConfiguration drmConfiguration =
|
||||||
|
new MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
|
||||||
|
.setLicenseUri(URI_STRING + "/license")
|
||||||
|
.setLicenseRequestHeaders(ImmutableMap.of("Referer", "http://www.google.com"))
|
||||||
|
.setMultiSession(true)
|
||||||
|
.setForceDefaultLicenseUri(true)
|
||||||
|
.setPlayClearContentWithoutKey(true)
|
||||||
|
.setForcedSessionTrackTypes(ImmutableList.of(C.TRACK_TYPE_AUDIO))
|
||||||
|
.setKeySetId(new byte[] {1, 2, 3})
|
||||||
|
.build();
|
||||||
|
|
||||||
|
MediaItem.DrmConfiguration drmConfigurationFromBundle =
|
||||||
|
MediaItem.DrmConfiguration.CREATOR.fromBundle(drmConfiguration.toBundle());
|
||||||
|
|
||||||
|
assertThat(drmConfigurationFromBundle).isEqualTo(drmConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void builderSetCustomCacheKey_setsCustomCacheKey() {
|
public void builderSetCustomCacheKey_setsCustomCacheKey() {
|
||||||
MediaItem mediaItem =
|
MediaItem mediaItem =
|
||||||
@ -319,6 +338,42 @@ public class MediaItemTest {
|
|||||||
assertThat(mediaItem.localConfiguration.subtitles).isEqualTo(subtitles);
|
assertThat(mediaItem.localConfiguration.subtitles).isEqualTo(subtitles);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
createDefaultSubtitleConfigurationInstance_toBundleSkipsDefaultValues_fromBundleRestoresThem() {
|
||||||
|
MediaItem.SubtitleConfiguration subtitleConfiguration =
|
||||||
|
new MediaItem.SubtitleConfiguration.Builder(Uri.parse(URI_STRING + "/en")).build();
|
||||||
|
|
||||||
|
Bundle subtitleConfigurationBundle = subtitleConfiguration.toBundle();
|
||||||
|
|
||||||
|
// Check that default values are skipped when bundling, only Uri field (="0") is present
|
||||||
|
assertThat(subtitleConfigurationBundle.keySet()).containsExactly("0");
|
||||||
|
|
||||||
|
MediaItem.SubtitleConfiguration subtitleConfigurationFromBundle =
|
||||||
|
MediaItem.SubtitleConfiguration.CREATOR.fromBundle(subtitleConfigurationBundle);
|
||||||
|
|
||||||
|
assertThat(subtitleConfigurationFromBundle).isEqualTo(subtitleConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createSubtitleConfigurationInstance_roundTripViaBundle_yieldsEqualInstance() {
|
||||||
|
// Creates instance by setting some non-default values
|
||||||
|
MediaItem.SubtitleConfiguration subtitleConfiguration =
|
||||||
|
new MediaItem.SubtitleConfiguration.Builder(Uri.parse(URI_STRING + "/en"))
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_TTML)
|
||||||
|
.setLanguage("en")
|
||||||
|
.setSelectionFlags(C.SELECTION_FLAG_FORCED)
|
||||||
|
.setRoleFlags(C.ROLE_FLAG_ALTERNATE)
|
||||||
|
.setLabel("label")
|
||||||
|
.setId("id")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
MediaItem.SubtitleConfiguration subtitleConfigurationFromBundle =
|
||||||
|
MediaItem.SubtitleConfiguration.CREATOR.fromBundle(subtitleConfiguration.toBundle());
|
||||||
|
|
||||||
|
assertThat(subtitleConfigurationFromBundle).isEqualTo(subtitleConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void builderSetTag_isNullByDefault() {
|
public void builderSetTag_isNullByDefault() {
|
||||||
MediaItem mediaItem = new MediaItem.Builder().setUri(URI_STRING).build();
|
MediaItem mediaItem = new MediaItem.Builder().setUri(URI_STRING).build();
|
||||||
@ -538,6 +593,21 @@ public class MediaItemTest {
|
|||||||
assertThat(mediaItem.localConfiguration.adsConfiguration.adsId).isEqualTo(adsId);
|
assertThat(mediaItem.localConfiguration.adsConfiguration.adsId).isEqualTo(adsId);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createAdsConfigurationInstance_roundTripViaBundle_yieldsEqualInstanceExceptAdsId() {
|
||||||
|
Uri adTagUri = Uri.parse(URI_STRING + "/ad");
|
||||||
|
MediaItem.AdsConfiguration adsConfiguration =
|
||||||
|
new MediaItem.AdsConfiguration.Builder(adTagUri)
|
||||||
|
.setAdsId("Something that will be lost")
|
||||||
|
.build();
|
||||||
|
|
||||||
|
MediaItem.AdsConfiguration adsConfigurationFromBundle =
|
||||||
|
MediaItem.AdsConfiguration.CREATOR.fromBundle(adsConfiguration.toBundle());
|
||||||
|
|
||||||
|
assertThat(adsConfigurationFromBundle.adTagUri).isEqualTo(adsConfiguration.adTagUri);
|
||||||
|
assertThat(adsConfigurationFromBundle.adsId).isNull();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void builderSetMediaMetadata_setsMetadata() {
|
public void builderSetMediaMetadata_setsMetadata() {
|
||||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder().setTitle("title").build();
|
MediaMetadata mediaMetadata = new MediaMetadata.Builder().setTitle("title").build();
|
||||||
@ -595,6 +665,68 @@ public class MediaItemTest {
|
|||||||
assertThat(liveConfigurationFromBundle).isEqualTo(liveConfiguration);
|
assertThat(liveConfigurationFromBundle).isEqualTo(liveConfiguration);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
createDefaultLocalConfigurationInstance_toBundleSkipsDefaultValues_fromBundleRestoresThem() {
|
||||||
|
MediaItem mediaItem = new MediaItem.Builder().setUri(URI_STRING).build();
|
||||||
|
|
||||||
|
Bundle localConfigurationBundle = mediaItem.localConfiguration.toBundle();
|
||||||
|
|
||||||
|
// Check that default values are skipped when bundling, only Uri field (="0") is present
|
||||||
|
assertThat(localConfigurationBundle.keySet()).containsExactly("0");
|
||||||
|
|
||||||
|
MediaItem.LocalConfiguration restoredLocalConfiguration =
|
||||||
|
MediaItem.LocalConfiguration.CREATOR.fromBundle(localConfigurationBundle);
|
||||||
|
|
||||||
|
assertThat(restoredLocalConfiguration).isEqualTo(mediaItem.localConfiguration);
|
||||||
|
assertThat(restoredLocalConfiguration.streamKeys).isEmpty();
|
||||||
|
assertThat(restoredLocalConfiguration.subtitleConfigurations).isEmpty();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void createLocalConfigurationInstance_roundTripViaBundle_yieldsEqualInstance() {
|
||||||
|
Map<String, String> requestHeaders = new HashMap<>();
|
||||||
|
requestHeaders.put("Referer", "http://www.google.com");
|
||||||
|
MediaItem mediaItem =
|
||||||
|
new MediaItem.Builder()
|
||||||
|
.setUri(URI_STRING)
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_MP4)
|
||||||
|
.setCustomCacheKey("key")
|
||||||
|
.setSubtitleConfigurations(
|
||||||
|
ImmutableList.of(
|
||||||
|
new MediaItem.SubtitleConfiguration.Builder(Uri.parse(URI_STRING + "/en"))
|
||||||
|
.setMimeType(MimeTypes.APPLICATION_TTML)
|
||||||
|
.setLanguage("en")
|
||||||
|
.setSelectionFlags(C.SELECTION_FLAG_FORCED)
|
||||||
|
.setRoleFlags(C.ROLE_FLAG_ALTERNATE)
|
||||||
|
.setLabel("label")
|
||||||
|
.setId("id")
|
||||||
|
.build()))
|
||||||
|
.setDrmConfiguration(
|
||||||
|
new MediaItem.DrmConfiguration.Builder(C.WIDEVINE_UUID)
|
||||||
|
.setLicenseUri(Uri.parse(URI_STRING))
|
||||||
|
.setLicenseRequestHeaders(requestHeaders)
|
||||||
|
.setMultiSession(true)
|
||||||
|
.setForceDefaultLicenseUri(true)
|
||||||
|
.setPlayClearContentWithoutKey(true)
|
||||||
|
.setForcedSessionTrackTypes(ImmutableList.of(C.TRACK_TYPE_AUDIO))
|
||||||
|
.setKeySetId(new byte[] {1, 2, 3})
|
||||||
|
.build())
|
||||||
|
.setAdsConfiguration(
|
||||||
|
new MediaItem.AdsConfiguration.Builder(Uri.parse(URI_STRING)).build())
|
||||||
|
.build();
|
||||||
|
|
||||||
|
MediaItem.LocalConfiguration localConfiguration = mediaItem.localConfiguration;
|
||||||
|
MediaItem.LocalConfiguration localConfigurationFromBundle =
|
||||||
|
MediaItem.LocalConfiguration.CREATOR.fromBundle(localConfiguration.toBundle());
|
||||||
|
MediaItem.LocalConfiguration localConfigurationFromMediaItemBundle =
|
||||||
|
MediaItem.CREATOR.fromBundle(mediaItem.toBundleIncludeLocalConfiguration())
|
||||||
|
.localConfiguration;
|
||||||
|
|
||||||
|
assertThat(localConfigurationFromBundle).isEqualTo(localConfiguration);
|
||||||
|
assertThat(localConfigurationFromMediaItemBundle).isEqualTo(localConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void builderSetLiveConfiguration() {
|
public void builderSetLiveConfiguration() {
|
||||||
MediaItem mediaItem =
|
MediaItem mediaItem =
|
||||||
@ -727,7 +859,7 @@ public class MediaItemTest {
|
|||||||
MediaItem copy = mediaItem.buildUpon().build();
|
MediaItem copy = mediaItem.buildUpon().build();
|
||||||
|
|
||||||
assertThat(copy).isEqualTo(mediaItem);
|
assertThat(copy).isEqualTo(mediaItem);
|
||||||
assertThat(copy.localConfiguration).isEqualTo(mediaItem.playbackProperties);
|
assertThat(copy.localConfiguration).isEqualTo(mediaItem.localConfiguration);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -792,7 +924,7 @@ public class MediaItemTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void roundTripViaBundle_withoutPlaybackProperties_yieldsEqualInstance() {
|
public void roundTripViaBundle_withoutLocalConfiguration_yieldsEqualInstance() {
|
||||||
MediaItem mediaItem =
|
MediaItem mediaItem =
|
||||||
new MediaItem.Builder()
|
new MediaItem.Builder()
|
||||||
.setMediaId("mediaId")
|
.setMediaId("mediaId")
|
||||||
@ -822,13 +954,25 @@ public class MediaItemTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void roundTripViaBundle_withPlaybackProperties_dropsPlaybackProperties() {
|
public void
|
||||||
|
roundTripViaDefaultBundle_mediaItemContainsLocalConfiguration_dropsLocalConfiguration() {
|
||||||
MediaItem mediaItem = new MediaItem.Builder().setUri(URI_STRING).build();
|
MediaItem mediaItem = new MediaItem.Builder().setUri(URI_STRING).build();
|
||||||
|
|
||||||
assertThat(mediaItem.localConfiguration).isNotNull();
|
assertThat(mediaItem.localConfiguration).isNotNull();
|
||||||
assertThat(MediaItem.CREATOR.fromBundle(mediaItem.toBundle()).localConfiguration).isNull();
|
assertThat(MediaItem.CREATOR.fromBundle(mediaItem.toBundle()).localConfiguration).isNull();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void
|
||||||
|
roundTripViaBundleIncludeLocalConfiguration_mediaItemContainsLocalConfiguration_restoresLocalConfiguration() {
|
||||||
|
MediaItem mediaItem = new MediaItem.Builder().setUri(URI_STRING).build();
|
||||||
|
MediaItem restoredMediaItem =
|
||||||
|
MediaItem.CREATOR.fromBundle(mediaItem.toBundleIncludeLocalConfiguration());
|
||||||
|
|
||||||
|
assertThat(mediaItem.localConfiguration).isNotNull();
|
||||||
|
assertThat(restoredMediaItem.localConfiguration).isEqualTo(mediaItem.localConfiguration);
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void createDefaultMediaItemInstance_checksDefaultValues() {
|
public void createDefaultMediaItemInstance_checksDefaultValues() {
|
||||||
MediaItem mediaItem = new MediaItem.Builder().build();
|
MediaItem mediaItem = new MediaItem.Builder().build();
|
||||||
|
@ -30,6 +30,7 @@ public class MediaMetadataTest {
|
|||||||
private static final String EXTRAS_KEY = "exampleKey";
|
private static final String EXTRAS_KEY = "exampleKey";
|
||||||
private static final String EXTRAS_VALUE = "exampleValue";
|
private static final String EXTRAS_VALUE = "exampleValue";
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation") // Testing deprecated field.
|
||||||
@Test
|
@Test
|
||||||
public void builder_minimal_correctDefaults() {
|
public void builder_minimal_correctDefaults() {
|
||||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder().build();
|
MediaMetadata mediaMetadata = new MediaMetadata.Builder().build();
|
||||||
@ -134,6 +135,7 @@ public class MediaMetadataTest {
|
|||||||
assertThat(mediaMetadataFromBundle.extras.getString(EXTRAS_KEY)).isEqualTo(EXTRAS_VALUE);
|
assertThat(mediaMetadataFromBundle.extras.getString(EXTRAS_KEY)).isEqualTo(EXTRAS_VALUE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation") // Testing deprecated setter.
|
||||||
@Test
|
@Test
|
||||||
public void builderSetFolderType_toNone_setsIsBrowsableToFalse() {
|
public void builderSetFolderType_toNone_setsIsBrowsableToFalse() {
|
||||||
MediaMetadata mediaMetadata =
|
MediaMetadata mediaMetadata =
|
||||||
@ -142,6 +144,7 @@ public class MediaMetadataTest {
|
|||||||
assertThat(mediaMetadata.isBrowsable).isFalse();
|
assertThat(mediaMetadata.isBrowsable).isFalse();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation") // Testing deprecated setter.
|
||||||
@Test
|
@Test
|
||||||
public void builderSetFolderType_toNotNone_setsIsBrowsableToTrueAndMatchingMediaType() {
|
public void builderSetFolderType_toNotNone_setsIsBrowsableToTrueAndMatchingMediaType() {
|
||||||
MediaMetadata mediaMetadata =
|
MediaMetadata mediaMetadata =
|
||||||
@ -151,6 +154,7 @@ public class MediaMetadataTest {
|
|||||||
assertThat(mediaMetadata.mediaType).isEqualTo(MediaMetadata.MEDIA_TYPE_FOLDER_PLAYLISTS);
|
assertThat(mediaMetadata.mediaType).isEqualTo(MediaMetadata.MEDIA_TYPE_FOLDER_PLAYLISTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation") // Testing deprecated setter.
|
||||||
@Test
|
@Test
|
||||||
public void
|
public void
|
||||||
builderSetFolderType_toNotNoneWithManualMediaType_setsIsBrowsableToTrueAndDoesNotOverrideMediaType() {
|
builderSetFolderType_toNotNoneWithManualMediaType_setsIsBrowsableToTrueAndDoesNotOverrideMediaType() {
|
||||||
@ -164,6 +168,7 @@ public class MediaMetadataTest {
|
|||||||
assertThat(mediaMetadata.mediaType).isEqualTo(MediaMetadata.MEDIA_TYPE_FOLDER_PODCASTS);
|
assertThat(mediaMetadata.mediaType).isEqualTo(MediaMetadata.MEDIA_TYPE_FOLDER_PODCASTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation") // Testing deprecated field.
|
||||||
@Test
|
@Test
|
||||||
public void builderSetIsBrowsable_toTrueWithoutMediaType_setsFolderTypeToMixed() {
|
public void builderSetIsBrowsable_toTrueWithoutMediaType_setsFolderTypeToMixed() {
|
||||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder().setIsBrowsable(true).build();
|
MediaMetadata mediaMetadata = new MediaMetadata.Builder().setIsBrowsable(true).build();
|
||||||
@ -171,6 +176,7 @@ public class MediaMetadataTest {
|
|||||||
assertThat(mediaMetadata.folderType).isEqualTo(MediaMetadata.FOLDER_TYPE_MIXED);
|
assertThat(mediaMetadata.folderType).isEqualTo(MediaMetadata.FOLDER_TYPE_MIXED);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation") // Testing deprecated field.
|
||||||
@Test
|
@Test
|
||||||
public void builderSetIsBrowsable_toTrueWithMediaType_setsFolderTypeToMatchMediaType() {
|
public void builderSetIsBrowsable_toTrueWithMediaType_setsFolderTypeToMatchMediaType() {
|
||||||
MediaMetadata mediaMetadata =
|
MediaMetadata mediaMetadata =
|
||||||
@ -182,6 +188,7 @@ public class MediaMetadataTest {
|
|||||||
assertThat(mediaMetadata.folderType).isEqualTo(MediaMetadata.FOLDER_TYPE_ARTISTS);
|
assertThat(mediaMetadata.folderType).isEqualTo(MediaMetadata.FOLDER_TYPE_ARTISTS);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation") // Testing deprecated field.
|
||||||
@Test
|
@Test
|
||||||
public void builderSetFolderType_toFalse_setsFolderTypeToNone() {
|
public void builderSetFolderType_toFalse_setsFolderTypeToNone() {
|
||||||
MediaMetadata mediaMetadata = new MediaMetadata.Builder().setIsBrowsable(false).build();
|
MediaMetadata mediaMetadata = new MediaMetadata.Builder().setIsBrowsable(false).build();
|
||||||
@ -189,6 +196,7 @@ public class MediaMetadataTest {
|
|||||||
assertThat(mediaMetadata.folderType).isEqualTo(MediaMetadata.FOLDER_TYPE_NONE);
|
assertThat(mediaMetadata.folderType).isEqualTo(MediaMetadata.FOLDER_TYPE_NONE);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("deprecation") // Setting deprecated fields.
|
||||||
private static MediaMetadata getFullyPopulatedMediaMetadata() {
|
private static MediaMetadata getFullyPopulatedMediaMetadata() {
|
||||||
Bundle extras = new Bundle();
|
Bundle extras = new Bundle();
|
||||||
extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
|
extras.putString(EXTRAS_KEY, EXTRAS_VALUE);
|
||||||
|
@ -0,0 +1,41 @@
|
|||||||
|
/*
|
||||||
|
* Copyright 2023 The Android Open Source Project
|
||||||
|
*
|
||||||
|
* Licensed under the Apache License, Version 2.0 (the "License");
|
||||||
|
* you may not use this file except in compliance with the License.
|
||||||
|
* You may obtain a copy of the License at
|
||||||
|
*
|
||||||
|
* https://www.apache.org/licenses/LICENSE-2.0
|
||||||
|
*
|
||||||
|
* Unless required by applicable law or agreed to in writing, software
|
||||||
|
* distributed under the License is distributed on an "AS IS" BASIS,
|
||||||
|
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
|
||||||
|
* See the License for the specific language governing permissions and
|
||||||
|
* limitations under the License.
|
||||||
|
*/
|
||||||
|
|
||||||
|
package androidx.media3.common;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import java.lang.reflect.Method;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
/** Tests for {@link Player}. */
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class PlayerTest {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* This test picks a method on the {@link Player} interface that is known will never be
|
||||||
|
* stabilised, and asserts that it is required to be implemented (therefore enforcing that {@link
|
||||||
|
* Player} is unstable-for-implementors). If this test fails because the {@link Player#next()}
|
||||||
|
* method is removed, it should be replaced with an equivalent unstable, unimplemented method.
|
||||||
|
*/
|
||||||
|
@Test
|
||||||
|
public void testAtLeastOneUnstableUnimplementedMethodExists() throws Exception {
|
||||||
|
Method nextMethod = Player.class.getMethod("next");
|
||||||
|
assertThat(nextMethod.isDefault()).isFalse();
|
||||||
|
}
|
||||||
|
}
|
File diff suppressed because it is too large
Load Diff
@ -17,6 +17,7 @@ package androidx.media3.common;
|
|||||||
|
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import android.os.Bundle;
|
||||||
import android.os.Parcel;
|
import android.os.Parcel;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@ -28,7 +29,8 @@ public class StreamKeyTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void parcelable() {
|
public void parcelable() {
|
||||||
StreamKey streamKeyToParcel = new StreamKey(1, 2, 3);
|
StreamKey streamKeyToParcel =
|
||||||
|
new StreamKey(/* periodIndex= */ 1, /* groupIndex= */ 2, /* streamIndex= */ 3);
|
||||||
Parcel parcel = Parcel.obtain();
|
Parcel parcel = Parcel.obtain();
|
||||||
streamKeyToParcel.writeToParcel(parcel, 0);
|
streamKeyToParcel.writeToParcel(parcel, 0);
|
||||||
parcel.setDataPosition(0);
|
parcel.setDataPosition(0);
|
||||||
@ -38,4 +40,36 @@ public class StreamKeyTest {
|
|||||||
|
|
||||||
parcel.recycle();
|
parcel.recycle();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void roundTripViaBundle_withDefaultPeriodIndex_yieldsEqualInstance() {
|
||||||
|
StreamKey originalStreamKey = new StreamKey(/* groupIndex= */ 1, /* streamIndex= */ 2);
|
||||||
|
|
||||||
|
StreamKey streamKeyFromBundle = StreamKey.fromBundle(originalStreamKey.toBundle());
|
||||||
|
|
||||||
|
assertThat(originalStreamKey).isEqualTo(streamKeyFromBundle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void roundTripViaBundle_toBundleSkipsDefaultValues_fromBundleRestoresThem() {
|
||||||
|
StreamKey originalStreamKey = new StreamKey(/* groupIndex= */ 0, /* streamIndex= */ 0);
|
||||||
|
|
||||||
|
Bundle streamKeyBundle = originalStreamKey.toBundle();
|
||||||
|
|
||||||
|
assertThat(streamKeyBundle.keySet()).isEmpty();
|
||||||
|
|
||||||
|
StreamKey streamKeyFromBundle = StreamKey.fromBundle(streamKeyBundle);
|
||||||
|
|
||||||
|
assertThat(originalStreamKey).isEqualTo(streamKeyFromBundle);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void roundTripViaBundle_yieldsEqualInstance() {
|
||||||
|
StreamKey originalStreamKey =
|
||||||
|
new StreamKey(/* periodIndex= */ 10, /* groupIndex= */ 11, /* streamIndex= */ 12);
|
||||||
|
|
||||||
|
StreamKey streamKeyFromBundle = StreamKey.fromBundle(originalStreamKey.toBundle());
|
||||||
|
|
||||||
|
assertThat(originalStreamKey).isEqualTo(streamKeyFromBundle);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -15,12 +15,15 @@
|
|||||||
*/
|
*/
|
||||||
package androidx.media3.common;
|
package androidx.media3.common;
|
||||||
|
|
||||||
|
import static androidx.media3.test.utils.FakeMultiPeriodLiveTimeline.AD_PERIOD_DURATION_MS;
|
||||||
|
import static androidx.media3.test.utils.FakeMultiPeriodLiveTimeline.PERIOD_DURATION_MS;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media3.common.MediaItem.LiveConfiguration;
|
import androidx.media3.common.MediaItem.LiveConfiguration;
|
||||||
import androidx.media3.exoplayer.source.ShuffleOrder.DefaultShuffleOrder;
|
import androidx.media3.exoplayer.source.ShuffleOrder.DefaultShuffleOrder;
|
||||||
|
import androidx.media3.test.utils.FakeMultiPeriodLiveTimeline;
|
||||||
import androidx.media3.test.utils.FakeTimeline;
|
import androidx.media3.test.utils.FakeTimeline;
|
||||||
import androidx.media3.test.utils.FakeTimeline.TimelineWindowDefinition;
|
import androidx.media3.test.utils.FakeTimeline.TimelineWindowDefinition;
|
||||||
import androidx.media3.test.utils.TimelineAsserts;
|
import androidx.media3.test.utils.TimelineAsserts;
|
||||||
@ -431,6 +434,34 @@ public class TimelineTest {
|
|||||||
/* expectedPeriod= */ period, /* actualPeriod= */ restoredPeriod);
|
/* expectedPeriod= */ period, /* actualPeriod= */ restoredPeriod);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void periodIsLivePostrollPlaceholder_recognizesLivePostrollPlaceholder() {
|
||||||
|
FakeMultiPeriodLiveTimeline timeline =
|
||||||
|
new FakeMultiPeriodLiveTimeline(
|
||||||
|
/* availabilityStartTimeMs= */ 0,
|
||||||
|
/* liveWindowDurationUs= */ 60_000_000,
|
||||||
|
/* nowUs= */ 60_000_000,
|
||||||
|
/* adSequencePattern= */ new boolean[] {false, true, true},
|
||||||
|
/* periodDurationMsPattern= */ new long[] {
|
||||||
|
PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS, AD_PERIOD_DURATION_MS
|
||||||
|
},
|
||||||
|
/* isContentTimeline= */ false,
|
||||||
|
/* populateAds= */ true,
|
||||||
|
/* playedAds= */ false);
|
||||||
|
|
||||||
|
assertThat(timeline.getPeriodCount()).isEqualTo(4);
|
||||||
|
assertThat(
|
||||||
|
timeline
|
||||||
|
.getPeriod(/* periodIndex= */ 1, new Timeline.Period())
|
||||||
|
.isLivePostrollPlaceholder(/* adGroupIndex= */ 0))
|
||||||
|
.isFalse();
|
||||||
|
assertThat(
|
||||||
|
timeline
|
||||||
|
.getPeriod(/* periodIndex= */ 1, new Timeline.Period())
|
||||||
|
.isLivePostrollPlaceholder(/* adGroupIndex= */ 1))
|
||||||
|
.isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
@SuppressWarnings("deprecation") // Populates the deprecated window.tag property.
|
@SuppressWarnings("deprecation") // Populates the deprecated window.tag property.
|
||||||
private static Timeline.Window populateWindow(
|
private static Timeline.Window populateWindow(
|
||||||
@Nullable MediaItem mediaItem, @Nullable Object tag) {
|
@Nullable MediaItem mediaItem, @Nullable Object tag) {
|
||||||
|
@ -181,9 +181,9 @@ public final class TrackSelectionParametersTest {
|
|||||||
TrackSelectionParameters parameters =
|
TrackSelectionParameters parameters =
|
||||||
new TrackSelectionParameters.Builder(getApplicationContext())
|
new TrackSelectionParameters.Builder(getApplicationContext())
|
||||||
.setViewportSize(
|
.setViewportSize(
|
||||||
/*viewportWidth=*/ 1,
|
/* viewportWidth= */ 1,
|
||||||
/*viewportHeight=*/ 2,
|
/* viewportHeight= */ 2,
|
||||||
/*viewportOrientationMayChange=*/ false)
|
/* viewportOrientationMayChange= */ false)
|
||||||
.clearViewportSizeConstraints()
|
.clearViewportSizeConstraints()
|
||||||
.build();
|
.build();
|
||||||
|
|
||||||
|
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