Compare commits

...

811 Commits

Author SHA1 Message Date
tonihei
51efcad672 Merge branch 'release' into release-1.6.0 2025-03-27 12:55:05 +00:00
tonihei
fdbf2a48f7 Add missing 1.6.0 entry to GH bug template
PiperOrigin-RevId: 741120336
(cherry picked from commit f74abed9843dcfc75fe81ac88452e9eef5196f01)
2025-03-27 12:53:29 +00:00
tonihei
df3b138391 Update release notes for 1.6.0
PiperOrigin-RevId: 738443368
(cherry picked from commit a37e906a0a44e166a75a1d75b12c39685b0a5806)
2025-03-19 17:29:46 +00:00
tonihei
7c0fb628ac Version bump to Media3 1.6.0
PiperOrigin-RevId: 738421167
(cherry picked from commit ecad8a53577dc9c843a939e5528b2b41dec23cc4)
2025-03-19 17:29:46 +00:00
tonihei
05827ea5ed Avoid unsupported mock operation
Calling a real method on an interface is not supported by
the Mockito version run by Gradle.

#cherrypick

PiperOrigin-RevId: 738358342
(cherry picked from commit d6b9988eb073d30c7074bae44dba2a48e5d6e687)
2025-03-19 13:53:29 +00:00
Googler
c558ae43c5 Fix a race condition in HttpEngineDataSource
Pass the temporary CookieHandler as a parameter instead of setting it as a temporary process-default. This avoids a rare race condition, where the player is sending a request with preserveCookies option and runs the `CookieHandler.getDefault()` before a different thread sets a default cookie handler. Over 5 or so lines of code the new default might be reverted to null - this is now fixed.

PiperOrigin-RevId: 738052152
(cherry picked from commit d0d76f214a3417ec39f86b1003dd0850a88638d9)
2025-03-18 18:01:40 +00:00
tonihei
8212b964d1 Exclude Xiaomi and OPPO devices from detached surface mode
Ideally, we'd find a more targeted exclusion as it may depend on
specific codecs. The current workaround should help with the
reported issues that are limited to Xiaomi and OPPO.

Issue: androidx/media#2059

#cherrypick

PiperOrigin-RevId: 738017969
(cherry picked from commit 06c0f5549e366071ad98050e85ed0e8f1cd36fdf)
2025-03-18 18:01:40 +00:00
tianyifeng
bd104b1cc4 Consider audio output latency when source has ended
With a [previous change](f05e6a7d6e), we makes `hasPendingData()` return `false` once we've found that the `AudioTrack` has played out all the written frames, to avoid it permanently stays `true` even when the source has ended. However, this is aggressive as the audio output device can still have latency in playing out those frames. So `hasPendingData()` should stay `true` a bit longer (for the duration of `latencyUs`) until finally turn to `false`, as well as the `getCurrentPositionUs()` should increment smoothly without a jump for the duration of `latencyUs`.

PiperOrigin-RevId: 738004292
(cherry picked from commit 6470c97af415d91ad46a1f21c7f2ab5b0716f39c)
2025-03-18 18:01:40 +00:00
tonihei
efb109dd88 Update release notes for 1.6.0-rc02
PiperOrigin-RevId: 737986781
(cherry picked from commit 0991dbcd7dfacc97a191c9053b8bdc59234447bd)
2025-03-18 18:01:37 +00:00
tonihei
dadd970f6e Version bump to Media3 1.6.0-rc02
PiperOrigin-RevId: 737948053
(cherry picked from commit b1068e47d35ac479f111a88fed870d1fdef52376)
2025-03-18 18:00:56 +00:00
Googler
25dc53551d Add CookieHandler support to HttpEngineDataSource.
The original behaviour of the `handleSetCookieRequests` is preserved.
A bug is addressed, where it woulld set `Set-Cookie` header in the client request (as opposed to the expected `Cookies`).

PiperOrigin-RevId: 732945338
(cherry picked from commit 814d368d9f54f47f6f0372c7905846104c549322)
2025-03-18 18:00:56 +00:00
ibaker
53a3144f96 Rollback of 3b38a7a43b
PiperOrigin-RevId: 731248658
(cherry picked from commit a6debf49048f59544757df880b30a23ed88c277a)
2025-03-18 18:00:56 +00:00
Googler
89cfee6e31 Add CookieHandler support to HttpEngineDataSource.
The original behaviour of the `handleSetCookieRequests` is preserved.
A bug is addressed, where it woulld set `Set-Cookie` header in the client request (as opposed to the expected `Cookies`).

PiperOrigin-RevId: 730857996
(cherry picked from commit 3b38a7a43bc35de1f77338a30bd5dce1d6991e82)
2025-03-18 18:00:56 +00:00
tonihei
8fa785fc27 Adjust dump files to different dumping logic
In particular, 796df136 and df489e2f are not part of the release branch
2025-03-18 12:27:04 +00:00
ibaker
5c1e0d964a Add 32-bit FLAC test files, and use them in some tests
The matroska file works without further changes. The FLAC container
file works with `lib-flac-decoder` depending on a bundled `libflac`
implementation, but not when using
`androidx.media3.extractor.flac.FlacExtractor` with a `MediaCodec` FLAC
decoder because the media3 extractor doesn't support 32-bit FLAC yet.
The fix for that is in a follow-up change.

`bear_32bit.flac` was generated from `bear.flac` with `ffmpeg`:

```shell
$ ffmpeg \
    -i bear.flac \
    -c:a flac \
    -sample_fmt s32 \
    -strict experimental \
    bear_32bit.flac
```

`bear-flac-32bit.mka` was generated by re-muxing `bear_32bit.flac`,
also with `ffmpeg` (trying to convert `bear-flac-24bit.mka` to 32-bit
didn't work, the resulting file was still 24-bit):

```shell
$ ffmpeg \
    -i ../flac/bear_32bit.flac \
    -c:a copy \
    bear-flac-32bit.mka
```

Issue: androidx/media#2197
PiperOrigin-RevId: 736552251
(cherry picked from commit dfebe72b6a175ed92420c1946bd06b952d85132d)
2025-03-18 11:39:46 +00:00
tonihei
fa71bf229e Wait for first frame when using placeholder surface
We currently pretend to be ready when using a placeholder
surface irrespective of whether the renderer is actually
ready to immediately render when a surface is attached.

This causes issues in two ways:
 - Apps don't know when a player without a surface can be
   connected to a surface and immediately start playing
 - A paused player without a surface will use the 1 second
   default doSomeWork loop, causing any pending decoding
   to be extremely slow (see Issue: androidx/media#1973).

The fix is to let the placeholder surface case use the same
paths as the regular surface and with marking the first
frame as available as soon as the codec output buffer for it
is pending and ready for release.

PiperOrigin-RevId: 737942496
(cherry picked from commit eef678f26382e74edbbd872173508c8642621160)
2025-03-18 11:20:44 +00:00
tonihei
e2017e35db Move decode-only and no surface logic inside VideoFrameReleaseControl
This brings the parts related to video frame release decision making
in a single place and simplifies the calling side in
MediaCodecVideoRenderer.

PiperOrigin-RevId: 737941729
(cherry picked from commit 0e169ab1bea3a4cd9ff2772d77618c66b5262f3c)
2025-03-18 11:16:38 +00:00
tonihei
0a284f4927 Refine logic of when to skip placeholder surface buffers
We want to skip the buffers in sync with playback, which
only makes progress once started. This means we can simplify
the logic to only apply the 30ms threashold when started
(which was indirectly the threashold used already because the
frame release would return TRY_AGAIN_LATER).

This means we can remove the
shouldSkipLateBuffersWhileUsingPlaceholderSurface as it was
only used in tests to prevent skipping while we were not
started.

PiperOrigin-RevId: 736533043
(cherry picked from commit 816d5cb86b13629a7ca23dba122f943f175d3bb9)
2025-03-18 11:09:27 +00:00
tonihei
8e2ed3bf4d Avoid unsupported mock operation
Calling a real method on an interface is not supported by
the Mockito version run by Gradle.

#cherrypick

PiperOrigin-RevId: 737940266
(cherry picked from commit 4daa43b25727d1d197095d0a9e2cc5a3610d881a)
2025-03-18 11:05:35 +00:00
tonihei
d8914c31e2 Update release notes for 1.6.0-rc02
PiperOrigin-RevId: 737624189
(cherry picked from commit ff0a359e9311b9eadb0346b5e1c38115c0e07082)
2025-03-18 11:05:32 +00:00
tonihei
1512b5b622 Clip start time to window duration when clipping
We currently throw an exception if the start time exceeds the
auto-detected end time at the duration even though an app can't
know this in advance in all cases. We should still throw an
exception if app-provided input values are non-sensical, but
auto-adjust the start time to the duration if needed similar to
how we already adjust the end time.

PiperOrigin-RevId: 737585207
(cherry picked from commit 343a7b054e3e90974c6930f56230b2e96c440d4e)
2025-03-18 11:04:17 +00:00
tianyifeng
f357f0a966 Loosen the condition for seeking to sync positions in a HLS stream
Previously we only enable `SeekParameter.*_SYNC` for HLS when `EXT-X-INDEPENDENT-SEGMENTS` is set in the playlist. However, this condition can actually be loosened. To seek in HLS, we need to download the segment in which the resolved seek position locates under any circumstance. If `SeekParameter.PREVIOUS_SYNC` or `SeekParameter.CLOSEST_SYNC` is passed, and that segment happens to start with sync samples, then the seek can be done quicker with that adjusted seek position. And if that segment doesn't start with sync samples, then the behaviour will be the same as we set the adjusted seek position to the exact original position. But we still cannot safely enable `SeekParameter.NEXT_SYNC` as it will potentially cause the seeking to miss more content than seeking to the exact position.

Issue: androidx/media#2209
PiperOrigin-RevId: 737580861
(cherry picked from commit 42b71c29e8bca0369381d100d5cec912e1c1e7ef)
2025-03-18 11:04:14 +00:00
ibaker
5957daadee Indicate MediaCodec FLAC decoder doesn't support 32-bit below API 34
This transforms the reported format support from `supported=YES` to
`supported=NO_EXCEEDS_CAPABILITIES`. Playback is still attempted in the
main demo app, and hangs as described in
https://github.com/androidx/media/issues/2197#issuecomment-2722322954.

PiperOrigin-RevId: 737568955
(cherry picked from commit 27eb204542dd461a8d77ff58d4ec9599ce336a33)
2025-03-18 11:03:35 +00:00
ibaker
9a91b2774e Add support for 32-bit FLAC files in the built-in FLAC extractor
Without this, 32-bit files fail to play with `Playback stuck buffering
and not loading`. With this change, playback works on devices where the
`MediaCodec` FLAC decoder supports 32-bit, and crashes on devices with a
`MediaCodec` FLAC decoder that does not support 32-bit.

A follow-up change will aim to transform the 'unsupported' case from a
crash into a report that the track format is not supported.

32-bit support was only fully incorporated into the spec when RFC 9639
was [published in December
2024](https://xiph.org/flac/2024/12/19/rfc-9639-published.html), and
it was been supported by `libflac` (for encode and decode) [since
September 2022](https://xiph.org/flac/2022/09/09/flac-1-4-0-released.html).
The original version of this `FlacExtractor` was written before either
of these, so only supported up to 24-bit.

Issue: androidx/media#2197
PiperOrigin-RevId: 737559285
(cherry picked from commit 8837ab25643bf4ed8a0c973ac637b3221e778f6a)
2025-03-18 11:03:31 +00:00
tonihei
a3f4b3dd1f Add toString to TrackGroup(Array)
These classes are often logged in error messages or tests. The
current output is just the hash code which makes it hard to debug.

PiperOrigin-RevId: 736799086
(cherry picked from commit 54c64b41c45397f7cc5892a27c541fdbf6a3cd58)
2025-03-18 11:01:58 +00:00
ivanbuper
f2739302af Add createShortBuffer() to TestUtil
PiperOrigin-RevId: 730875642
(cherry picked from commit 1399e772490b3ee602b298d58f5683ae0a00f688)
2025-03-17 15:00:53 +00:00
tonihei
24b3bf21b8 Check language/role flags before merging adaptation sets
The spec technically allows to mark adaptation sets with the switching
property if they allow seamless switching with non-overlapping
segments. This is typically only used for compatible content (e.g.
different codecs), but the spec allows it to be used for other
content as well (e.g. different languages, roles). ExoPlayer's concept
of a TrackGroup only allows formats with the same language and role
flags to be merged, so we should check that before merging.

Issue: androidx/media#2222

PiperOrigin-RevId: 736564055
(cherry picked from commit d37f05238a2d8b45ea2e5f4ac026084b917f30df)
2025-03-17 14:06:03 +00:00
tonihei
23468ed55c Clarify that method can only be called after onCreate
And improve error message if access to the notification controller
happens without a base context.

#cherrypick

PiperOrigin-RevId: 736545574
(cherry picked from commit 41722be02e34199543dc92c44e12ff95a35cb378)
2025-03-17 14:02:19 +00:00
ivanbuper
97606094ca Add float PCM support to TrimmingAudioProcessor
This was requested in Issue: androidx/media#2191.

PiperOrigin-RevId: 735375746
(cherry picked from commit 06163f3dfaf9eb13d84790273179157e762122ed)
2025-03-17 14:02:16 +00:00
tonihei
b215670445 Make foreground service timeout configurable
This allows apps to set a shorter timeout if so desired.

Issue: androidx/media#2206

#cherrypick

PiperOrigin-RevId: 735360459
(cherry picked from commit 222950cfd1bdc942b90e0991e6b96a3f2c86518a)
2025-03-17 14:01:30 +00:00
tonihei
67ec5b76ad Ensure notification is removed when shouldShowNotification==false
We currently combine stopping the FGS and optionally removing the
notification in one method, which unnecessarily gates its logic on
checking the desired foreground state again. This causes a bug where
the notification should be removed (because shouldShowNotification
returns false), but stays visible because the service is allowed
to stay in the foreground and the notification removal code is not
triggered.

Issue: androidx/media#2211
PiperOrigin-RevId: 735126704
(cherry picked from commit 91ecc16198bbb48d114a6d581669a9e670c161da)
2025-03-17 14:01:27 +00:00
jbibik
6b003993a5 [ui-compose] Move ComposeBom from implementation to api
PlayerSurface exposing the Modifier argument means the gradle dependency needs to be stricter

#cherrypick

PiperOrigin-RevId: 734237616
(cherry picked from commit 8dcfa1afbead8d7af1e11e871df9359052f107c6)
2025-03-17 14:00:26 +00:00
ivanbuper
d2703a12cc Add support for FLOAT_PCM in ChannelMappingAudioProcessor
This was requested in Issue: androidx/media#2191 for playback of Opus and Vorbis
files with more than two channels with a float PCM pipeline.

Also, add ChannelMappingAudioProcessorTest.

PiperOrigin-RevId: 733766680
(cherry picked from commit f996a5e3e4b395fe1782392ae90fb088143aa806)
2025-03-17 14:00:22 +00:00
jbibik
fb57e45a58 Align release notes labels spelling
PiperOrigin-RevId: 733717073
(cherry picked from commit bc9a974e07d117c146d2508b363a524c38c0630b)
2025-03-17 13:58:57 +00:00
jbibik
d9deda7b6e [ui-compose] Add PlaybackSpeedState to control playbackParameters.speed
A state holder that handles interaction with a UI component that toggles through a range of playback speeds.

[demo-compose] Use PlaybackSpeedState to create PlaybackSpeedTextButton

Add the button to the bottom extra controls.

PiperOrigin-RevId: 731449526
(cherry picked from commit addf01b9a84bcea945107b3b2993540ec59fbb54)
2025-03-17 13:56:51 +00:00
jbibik
5d1e8d1279 Version bump for Media3 1.6.0-rc01
#cherrypick

PiperOrigin-RevId: 733036051
(cherry picked from commit cdb112a85a412d12803d0d3e237f5223b74fba52)
2025-03-03 21:26:19 +00:00
jbibik
3b55f18013 Update release notes for Media3 1.6.0-rc01 release
PiperOrigin-RevId: 732964311
(cherry picked from commit 7419a81aa748ba4210471f28ada9db41c1f3aa65)
2025-03-03 21:26:15 +00:00
Googler
e1d328db11 Fix resizing in debug preview
PiperOrigin-RevId: 732905018
(cherry picked from commit 1e4a10651a931ed9c95898bff1151d8f16a322af)
2025-03-03 16:27:28 +00:00
tonihei
766dbd497e Add util to obtain AudioManager
AudioManager internally assumes the thread is was created on can be
used as a callback thread. Since the AudioManager is only created
once in the lifetime of an app, we need to make sure its obtained
on a thread that stays alive.

#cherrypick

PiperOrigin-RevId: 732072255
(cherry picked from commit 2088697a19ac85feb26ba2521a70647803246571)
2025-03-03 16:27:28 +00:00
ibaker
595fd48cd7 Disable subtitlesRespectClipping_multiplePeriods test on API 21
PiperOrigin-RevId: 732065230
(cherry picked from commit f8c3af52e9091db3f683c4661b53e2f92de6fbb4)
2025-03-03 16:27:28 +00:00
jbibik
768f25e163 [ui-compose] Better KDoc formatting
#cherrypick

PiperOrigin-RevId: 731712846
(cherry picked from commit 66ef013cb85e8d7db5679242190202745abf3110)
2025-03-03 16:27:28 +00:00
tonihei
2cb6d4312b Do not assume default callback for Audio focus requests is main looper
On API < 26, the callback thread couldn't be set and the current compat
code assumes it's always the main thread. This isn't true, however,
because AudioManager uses the thread on which it was first instantiated
anywhere in the app.

#cherrypick

PiperOrigin-RevId: 731696188
(cherry picked from commit 1ac82d982460f47589484052f57e27a63b0eb08b)
2025-03-03 16:27:28 +00:00
jbibik
c7fa2c3de6 [ui-compose] Add KDoc to observe() methods for button states
#cherrypick

PiperOrigin-RevId: 731686021
(cherry picked from commit b465cbc22c61acc07875550d1c1f24aa5d080f13)
2025-03-03 16:27:28 +00:00
Copybara-Service
c66c221b28 Merge pull request #2190 from nift4:main
PiperOrigin-RevId: 731316955
(cherry picked from commit 366e5eccf889be2d88f126846ebc4395f0e5cff7)
2025-03-03 16:27:28 +00:00
ibaker
fd2e874b5d Rewrite ClearKey UUID to Common PSSH UUID in PSSH box on API < 27
Without this change, content that contains a PSSH box with only
the ClearKey UUID is not playable on devices with API < 27.

This includes our `sample_fragmented_clearkey.mp4` test content.

PiperOrigin-RevId: 731308444
(cherry picked from commit 275e7d3dbddd04792b472a06ff808f748237f8d5)
2025-03-03 16:27:27 +00:00
simakova
3083a48ecb Add missing links to the effect demo README
PiperOrigin-RevId: 731305090
(cherry picked from commit 5417279982729b8bf5107c48a3cca0904b0c6300)
2025-03-03 16:27:27 +00:00
jbibik
f7479347c0 [ui-compose] Add default value to PlayerSurface's surfaceType
PlayerView's default surface type was also SURFACE_VIEW.

This allows for a simple `PlayerSurface(player)` usage by most developers.

The order of the arguments needs to be changed, because `modifier` should be the first default argument (https://android.googlesource.com/platform/frameworks/support/+/androidx-main/compose/docs/compose-component-api-guidelines.md#parameter)

PiperOrigin-RevId: 731294658
(cherry picked from commit afea6962c254b6621eee2b8f075d6fdb5283602a)
2025-03-03 16:27:27 +00:00
Copybara-Service
4d3f71a0c6 Merge pull request #2180 from nift4:main
PiperOrigin-RevId: 731276598
(cherry picked from commit ac1cf206c89ea656b5a88b807a178f4d3f94d9f0)
2025-03-03 16:27:27 +00:00
sheenachhabra
9ba0a1b470 Reset maxTrackDurationUs when a new fragment is written
When there is both audio and video track, then fragment
creation is driven by video track (a combination of duration
so far + next key frame). But if there is no video track, then
the duration so far drives the fragment creation.

Due to bug, when there is only audio track, only first
fragment was created as expected and then a new fragment is
created for every audio sample.

PiperOrigin-RevId: 731257696
(cherry picked from commit d16fdcb8cc832909b1ff531a00e595c64df5c799)
2025-03-03 16:27:25 +00:00
ibaker
f739e2d68c Suppress some instrumentation tests on older API levels
PiperOrigin-RevId: 731257575
(cherry picked from commit 20ea05063bcccec8b83c9848fc5b3dbf4edc290c)
2025-03-03 16:26:31 +00:00
tonihei
fc4112beee Move audio focus management to ExoPlayerImplInternal
This ensures all AudioManager calls are moved off the main
thread.

Having the audio focus management on the playback thread
also allows future improvements like requesting audio focus
only just before the player becomes ready (which is recommended
but not currently done by ExoPlayer).

PiperOrigin-RevId: 730962299
(cherry picked from commit 19c7b2127568e05b829efe2d9943be04657cefd1)
2025-03-03 16:26:31 +00:00
Copybara-Service
9052313245 Merge pull request #2113 from colinkho:lastrebufferinssp
PiperOrigin-RevId: 730919539
(cherry picked from commit 2c866ce50be132858d9cb4c30c174790577f7560)
2025-03-03 16:26:31 +00:00
Copybara-Service
c4a77475c4 Merge pull request #2135 from bubenheimer:handleMoveMediaItems_docfix
PiperOrigin-RevId: 730886871
(cherry picked from commit 57d4f8354a90960adeb2241683c2b175c03b3ce7)
2025-03-03 16:26:31 +00:00
ibaker
954a1d44e4 Bump play-services-cast-framework cast dependency to 21.5.0
This resolves an app crash on devices with Google Play Services
installed but disabled due to `FLAG_MUTABLE` on a `PendingIntent`.

Issue: androidx/media#2178

#cherrypick

PiperOrigin-RevId: 730885329
(cherry picked from commit c4eef6042bbd814cbf25b3bb91ae1433618a290a)
2025-03-03 16:26:31 +00:00
ibaker
8b9e9203a1 Add missing call to adjustUuid in FrameworkMediaDrm
Before API 27, the platform DRM components incorrectly expected
`C.COMMON_PSSH_UUID` instead of `C.CLEARKEY_UUID` in order to perform
ClearKey decryption. `FrameworkMediaDrm` is responsible for doing this
adjustment on these API levels, but this call was missed when
refactoring some DRM code in
c872af4bc0.

This led to `MediaCodec$CryptoException: Operation not supported in this
configuration` errors when doing ClearKey playback on devices with
API < 27. This was because the earlier, clearer error from the
`MediaCrypto` constructor was being swallowed and transformed to
`requiresSecureDecoder = true` by the `catch` logic in
`FrameworkMediaDrm.requiresSecureDecoder`.

This change also makes the error handling in
`FrameworkMediaDrm.requiresSecureDecoder` more robust by assuming
`requiresSecure = false` for ClearKey (and true for all other DRM
schemes), since we know that ClearKey never supports secure decoding.
This will help avoid more ClearKey playback failures if we see other,
unrelated, errors at this point.

Issue: androidx/media#1732

#cherrypick

PiperOrigin-RevId: 730882278
(cherry picked from commit ecb83f3b738db25f503153617ba9db7bb9dc5b4b)
2025-03-03 16:26:31 +00:00
tonihei
317bf3c6c0 Add kotlin-stdlib to aar workaround list
#cherrypick

PiperOrigin-RevId: 730880594
(cherry picked from commit a3f281dff827cbc63482d5e2fbdbedb2427cbb95)
2025-03-03 16:26:31 +00:00
dancho
521c385d4b Add experimentalSetCodecsToParseWithinGopSampleDependencies to HLS
HLS extractors were missing
experimentalSetCodecsToParseWithinGopSampleDependencies prior to this patch.

PiperOrigin-RevId: 730878460
(cherry picked from commit da402cfd64ef3ae60607491730f01138177a077b)
2025-03-03 16:26:31 +00:00
Copybara-Service
a578d43324 Merge pull request #2115 from MGaetan89:use_objects_equals
PiperOrigin-RevId: 730860597
(cherry picked from commit cc44de8757501cab7e4bb70fd0b090c6dc9799e1)
2025-03-03 16:26:31 +00:00
Copybara-Service
28b70f7e85 Merge pull request #2145 from v-novaltd:dsparano-exo328
PiperOrigin-RevId: 730853667
(cherry picked from commit 85467b9b57ab8ee38cfcb6c9e0f484fd167df372)
2025-03-03 16:26:31 +00:00
ibaker
d755a0477d Fix casting in ParsableByteArray.peekCharacterAndSize
This was introduced in 841bdc6efe.

There's no need to cast the `char` (2 bytes) down to a `byte` in order
to pack it into an `int` (4 bytes) alongside a short (2 bytes).

We also don't need to use `short` to track the character count (max 4
with UTF-8) - a single byte is enough. This change also uses
`Ints.fromBytes` to avoid having to reason about how casting &
bit-shifting interact.

This change introduces a test which reproduces the failure reported in
Issue: androidx/media#2167.

#cherrypick

PiperOrigin-RevId: 730809219
(cherry picked from commit fe19d8c9be94bbf1a1be8d3f49b2de52f0e2f1ae)
2025-03-03 16:26:31 +00:00
tonihei
1f691106f1 Clarify that multiple LibraryParams fields can be set
Issue: androidx/media#2159
PiperOrigin-RevId: 730805485
(cherry picked from commit 3f493eaf9e01909094e99c173cec96b1256f5ca4)
2025-03-03 16:26:31 +00:00
ivanbuper
77c8ddd884 Rollback of 6e9a2cc0cd
PiperOrigin-RevId: 730482824
(cherry picked from commit ee6eb98d4bd4f63270744e25b606208a4a950288)
2025-03-03 16:26:31 +00:00
tonihei
50f6bdd1fd Do not change audio capabilities for error recovery without context
If the deprecated path without a context is used, the capabilities
are set externally and can't recover automatically back to the real
capabilities.

Issue: androidx/media#2168
PiperOrigin-RevId: 730427339
(cherry picked from commit 5610cc846589aafa416c39567f4f5599e98a77ef)
2025-03-03 16:26:31 +00:00
shahddaghash
ff65d7b05b Enable metrics collection by default
PiperOrigin-RevId: 730383523
(cherry picked from commit c90ca4e86ee042f6ce8c6971e075bf0f8d893901)
2025-03-03 16:26:31 +00:00
bachinger
6c143230d7 Avoid rare NPE in MediaSessionServiceTest
PiperOrigin-RevId: 730363453
(cherry picked from commit 76df13d390e57f4462e9e44b20997c6e7dfb8148)
2025-03-03 16:26:31 +00:00
dancho
62ef83b68e MCVR: limit the number of consecutive dropped input frame headers
Dropping too many consecutive input buffers reduces the update
frequency of MCVR.shouldDropDecoderInputBuffers and can lead to
dropping too many consecutive input buffers.

Discarding input buffers of type OBU_FRAME_HEADER with
show_existing_frame = 1 saves a smaller amount of resources
than discarding input buffers of type OBU_FRAME.
PiperOrigin-RevId: 730362707

(cherry picked from commit 67e99f46481dbc5fff9ffd04ff13119dac9199bb)
2025-03-03 16:26:30 +00:00
bachinger
8ee2532db3 Deflake MediaSessionServiceTest
`onConnect_controllerInfo_sameInstanceFromServiceToConnectedControllerManager`
was flaky because `onDestroy()` of `MockMediaSessionService` was cleaning
up the `TestServiceRegistry`. This includes releasing the session of the service
after which no further `MediaSession.Callback` methods are called. This created
a race between `Callback.onDisconnected` and the `Mediasession.release()` being
called.

`onDestroy()` is called unexpectedly early because the controller unbinds as the
last controller from the service that never was started (`onStartCommand` never
called). In such a case the service is immediately terminated by the system after
the last client unbinds from the service, which called `onDestroy()` on the mock
service.

This change making `MockMediaSessionService` optionally not clean up the
registry in `onDestroy()` prevents the session from being released too early.
Alternatively, starting playback and hence starting the service into the
foreground would prevent the problem as well for the purpose of the test.
Another alternative to fix the test would be removing the session from the
service before releasing the controller.

PiperOrigin-RevId: 730361199
(cherry picked from commit fbe186a70ccb588889d0909ccfc07affaf49ec51)
2025-03-03 16:26:30 +00:00
Copybara-Service
4d50e8a815 Merge pull request #2170 from wischnow:main
PiperOrigin-RevId: 729558029
(cherry picked from commit 7c2e8c1c4b608bc5da5649d7f2d7f9674dba5d36)
2025-03-03 16:26:30 +00:00
Copybara-Service
2c123dedbb Merge pull request #2149 from bubenheimer:util_postorrun_fix
PiperOrigin-RevId: 729495445
(cherry picked from commit 05c8a66dc256b2658bfc4cf43b7e9435dbc75b54)
2025-03-03 16:26:30 +00:00
Copybara-Service
7ed1ebea3a Merge pull request #2139 from bubenheimer:handleReplaceMediaItems_removerangecodefix
PiperOrigin-RevId: 729490370
(cherry picked from commit 2b12a574473c6304511f76325e1927ca50c264bf)
2025-03-03 16:26:30 +00:00
Copybara-Service
b8468bbee3 Merge pull request #2136 from bubenheimer:handleReplaceMediaItems_docfix
PiperOrigin-RevId: 729474558
(cherry picked from commit 378e70e15fe38eae33ab7ed74ea9614ba6ec45cb)
2025-03-03 16:26:30 +00:00
shahddaghash
a5d7be959b Update exporter and muxer name to match expected pattern
If the name does not match patten, it gets skipped when reporting.

PiperOrigin-RevId: 729463344
(cherry picked from commit 23ebea7ab4afe1cff4b4c3fce222e33639dfd34e)
2025-03-03 16:26:30 +00:00
tonihei
073e50a893 Ensure DataSource is closed in CacheWriter at non-IOException
We currently only catch IOExceptions to close the opened data source.
This means any other type of exception leaves the data source open,
which can caused SimpleCache to be blocked forever when another
data source tries to access the same cache area (which is still
locked by the open data source).

This problem was the cause for flakiness in DownloadManagerDashTest.

#cherrypick

Issue: google/ExoPlayer#9760
PiperOrigin-RevId: 729441817
(cherry picked from commit 72c85aa48393e47642688c8147bdf18d6e3d4bbf)
2025-03-03 16:26:30 +00:00
tianyifeng
8e5368a807 Mask ExoPlayer.isLoading when it transitions to IDLE or ENDED states
In some cases, the ExoPlayer immediately transitions to `STATE_IDLE` or `STATE_ENDED` on application thread, while `isLoading` can still remain as `true` before it is finally updated from playback thread.

Issue: androidx/media#2133

#cherrypick

PiperOrigin-RevId: 728724157
(cherry picked from commit daf8f9ff584e52128b94b4b08a1e9cf7ba94dee2)
2025-03-03 16:26:30 +00:00
shahddaghash
816a119617 Verify each export had its own session
This is done by getting the session ID for the `EditingSession` and making sure it's unique from the other session ID.

PiperOrigin-RevId: 728680628
(cherry picked from commit ae3eed234382d95bd9187c965c6b949be97fda63)
2025-03-03 16:26:30 +00:00
Googler
13e14b8e31 Update the list of supported video mimetypes.
Add VP9 and APV codecs to the list of supported video mimetypes for Mp4Muxer and Fragmented Mp4Muxer.

PiperOrigin-RevId: 728603222
(cherry picked from commit 1461e9e93a94e2cb27c096dea2c6c2eee934c3df)
2025-03-03 16:26:30 +00:00
jbibik
41062d3910 Fix Shuffle and Repeat references in tests
PiperOrigin-RevId: 728309204
(cherry picked from commit 4045acd13b17c68d7fe532cb7ba746517f83f089)
2025-03-03 16:26:30 +00:00
shahddaghash
e393587787 Add test for usePlatformDiagnostics disabled
PiperOrigin-RevId: 728276994
(cherry picked from commit 90844f11e88ad9dbb26c7fd4bd72d99c577b9c54)
2025-03-03 16:26:30 +00:00
tonihei
d880ab5d4f Make SimpleBasePlayer.State public
This ensures it's easier to handle these State updates in other
helper classes if needed.

Issue: androidx/media#2128

PiperOrigin-RevId: 728264396
(cherry picked from commit f1c62c12394e3c408b24d98ed2472ed88db3e918)
2025-03-03 16:26:25 +00:00
tonihei
75da514b7f Decouple gradle publish task from lint and test
The publish task currently forces to run lint and test
even though there is no technical dependency. If a
process (e.g. releasing a new library) wants to verify
lint and test work, it should run these steps explicitly.

#cherrypick

PiperOrigin-RevId: 728234811
(cherry picked from commit 4b991ad42f3139e7e528a87bf19056bfdb5d9f5a)
2025-03-03 16:25:01 +00:00
shahddaghash
cb2df35a5b Add EditingMetricsCollector test for export cancellation
PiperOrigin-RevId: 727885381
(cherry picked from commit 116fbeaa81b648b6a4bd2f8b202a2140f2450a98)
2025-03-03 16:25:01 +00:00
shahddaghash
ed05bc67c3 Move FrameBlockingMuxer into AndroidTestUtil
This allows it to be reused in upcoming tests.

PiperOrigin-RevId: 727835985
(cherry picked from commit f8d5f5a828d0783725696a27a12b78f577effaa0)
2025-03-03 16:25:01 +00:00
shahddaghash
4a8d28cef8 Add EditingMetricsCollector test for export error
PiperOrigin-RevId: 727835981
(cherry picked from commit 6f60aa2548c556370f1e0bb1aaf33a42c460f33c)
2025-03-03 16:25:00 +00:00
shahddaghash
16add9922d Add EditingMetricsCollector test for export success
PiperOrigin-RevId: 727826326
(cherry picked from commit 3ce6a2e6b8b0b7147a06c5ec38aaf2ee36282720)
2025-03-03 16:25:00 +00:00
shahddaghash
e58d9120bc Create a new MetricsReporter.Factory
This includes creating a new Factory for `MetricsReporter` and adding it to Transformer. This is done as we need to create a new `MetricsReporter` for each export operation, so it makes sense to have a Factory.

PiperOrigin-RevId: 727798528
(cherry picked from commit 6d408c2d31bddacb254bb0cc917e4ecd5d0e4b66)
2025-03-03 16:25:00 +00:00
bachinger
bb358241b9 Make setSessionActivity accept null
Issue: androidx/media#2109
PiperOrigin-RevId: 728160580
(cherry picked from commit 2b8700beaaabd51365daf9099c42bfa64a2dd4fe)
2025-02-18 14:11:51 +00:00
tonihei
bc872503b9 Ignore .kotlin directory
#cherrypick

PiperOrigin-RevId: 728119609
(cherry picked from commit 0a50741abec718c09e52c5f00cc241a00eeba23d)
2025-02-18 14:09:17 +00:00
tonihei
b49374c8fe Remove unreleased section from RELEASENOTES 2025-02-18 09:51:26 +00:00
tonihei
3f10fa92bb Version bump for Media3 1.6.0-beta01
#cherrypick

PiperOrigin-RevId: 727889651
(cherry picked from commit 78cf53c0b2f604b4d04ab749250f4e4fe88e240f)
2025-02-18 09:42:24 +00:00
Googler
625ff234e4 Update CSD and apvC box logic
Update the CSD to contain only the APVDecoderConfigurationRecord and the apvC box to be a full box. The apv clip is also updated to be consistent with the new [specification](https://github.com/AcademySoftwareFoundation/openapv/blob/main/readme/apv_isobmff.md#isobmff-binding-for-apv).

The clip is provided by the openAPV team under BSD-3 license.

PiperOrigin-RevId: 727868656
(cherry picked from commit 653470f73be98e01e972513ec80e58c2d36cbb3a)
2025-02-18 09:42:24 +00:00
tonihei
88e7636ae0 Use different authorities for AssetContentProvider
Otherwise it's impossible to install the androidTest apk
of both lib-datasource and lib-exoplayer on the same device

#cherrypick

PiperOrigin-RevId: 727867871
(cherry picked from commit 527e1d52aedbff53e3ba670d2b6a3cf0b0a84abb)
2025-02-18 09:42:24 +00:00
tonihei
57bbb6c5c6 Update release notes for 1.6.0-beta01
#cherrypick

PiperOrigin-RevId: 727862233
(cherry picked from commit 18f38647732c7fbbafce30d99ab8062da6be2aff)
2025-02-18 09:42:23 +00:00
kimvde
9ad06570b5 Transformer: fix gradle dependency type
PiperOrigin-RevId: 727760498
(cherry picked from commit c1eb381948d5e936bdc9cfb37ca529b816bfcc1b)
2025-02-18 09:42:23 +00:00
tonihei
7db4802f02 Update ignore files for most recent FLAC extension code
Following the README, a libflac directory is created, which
needs to be ignored in VCSs

#cherrypick

PiperOrigin-RevId: 727103140
(cherry picked from commit b306b1059772c2e279020f65ac60ae975fca3fbf)
2025-02-18 09:42:23 +00:00
tonihei
1ceafc7a61 Add timeout to foreground service handling
Currently, as soon as the playback is considered disengaged (not
ready and playing), the foreground service is stopped.
This causes problems if the app or the user attempts to restart
playback after a short amount of time, where apps may run into
foreground service start restrictions.

Almost all of these short-term interaction issues can be solved
by keeping the foreground service running for an additional
timeout period, which is chosen to be 10 minutes to match the
behavior of future Android system enforcements. For any longer
term interactions, apps need to implement playback resumption
paths that can restart the service with the previous playback.

One caveat is that we currently use player.pause() as a way to
stop the foreground service in onTaskRemoved() if the app wants
to abandon playback at this point. With the timeout, the service
can no longer be stopped immediately just by calling pause(),
so we need to explicitly disable the timeout in the corresponding
helper method.

Issue: androidx/media#1928
Issue: androidx/media#111
PiperOrigin-RevId: 726942625
(cherry picked from commit 8a888d0d1801ce018b5bca5dbab78be44507286e)
2025-02-14 18:41:15 +00:00
tonihei
3b897241d2 Increase timeout for AudioPositionAdvancingTest
500ms may not be enough to start playback on a slow device

#cherrypick

PiperOrigin-RevId: 726940099
(cherry picked from commit 792a2ae05dbade27d9cd5a8923a1f3cebf083a91)
2025-02-14 18:41:15 +00:00
tonihei
2a3e733161 Post initial media button preferences update
Controller and browsers are typically obtained with
Futures.addCallback(future, getMainExecutor()), which triggers
the onSuccess callback with a message post. We currently send
the initial media button preferences inline, causing the callback
in MediaController.Listener.onMediaButtonPreferencesChanged to
arrive before the FutureCallback.onSuccess callback.

In the test controller app, which causes crashed when
connecting to existing sessions for example. We can make this
more robust by also posting the initial update of the media button
preferences.

PiperOrigin-RevId: 726923498
(cherry picked from commit d5df227b3ae4d87ec4876cc19ecca814c91c5fd7)
2025-02-14 18:41:15 +00:00
tonihei
22e5f25648 Refine logic to set and interpret ACTION_PAUSE/ACTION_PLAY
We currently set both actions depending on the playWhenReady state
and we require both actions when converting a platform session to
a Media3 session (if ACTION_PLAY_PAUSE isn't set anyway).

This causes problems in two situations:
 - A controller using the platform ACTION_PAUSE/ACTION_PLAY to
   determine which button to show in a UI. This needs to be aligned
   to the existing Util.shouldShowPlayButton we already use when
   setting the PlaybackStateCompat state.
 - A session only setting either ACTION_PAUSE or ACTION_PLAY
   depending on its state. We should check if the action triggered
   by setPlayWhenReady(...) is possible and allow COMMAND_PLAY_PAUSE
   accordingly.

PiperOrigin-RevId: 726916720
(cherry picked from commit 28bfb27fb59ceb0cd209ac1eebd803d31f78afa4)
2025-02-14 18:41:15 +00:00
tonihei
71a2b7d72d Refine conversion logic from platform to Media3 state for STATE_ENDED
When converting to platform states, STATE_ENDED is mapped to
STATE_STOPPED, but the reverse mapping always assumes STATE_STOPPED
means STATE_IDLE at the moment. We can use the same logic we already
apply for STATE_PAUSED to figure out if we are ended.

PiperOrigin-RevId: 726902064
(cherry picked from commit 982403a0ccab31de1f293fb0b8591e2132e11dd2)
2025-02-14 18:41:15 +00:00
tonihei
89b5420bc8 Show notification even in STATE_IDLE
Currently the notification disappears immediately when the player
enters an error or stopped state, but still has its media and
could resume on user request.

This can be fixed by only checking the existence of media and not
the state when deciding to show a notification.

PiperOrigin-RevId: 726901050
(cherry picked from commit f0da364d3fdd8574c8506f0682a7d2ecf8135ad8)
2025-02-14 18:41:15 +00:00
rohks
d14807c836 Add contract tests for DRM and logging to MediaExtractorContractTest
Added tests for APIs `getDrmInitData()`, `getPsshInfo()`, `getLogSessionId()` and `setLogSessionId(LogSessionId)`.

The Widevine encrypted sample was created from already existing `sample_fragmented.mp4` using `mp4encrypt`.

PiperOrigin-RevId: 726881977
(cherry picked from commit e9df85b48d5c8c0727146ace4528293e3f6ecfd5)
2025-02-14 18:41:15 +00:00
tonihei
be76766f95 Add dedicated EPII method to set volume
This means the volume is available in the internal player,
which is a preparation step to moving the audio focus
handling to the internal player too.

PiperOrigin-RevId: 726880098
(cherry picked from commit ef9b6d212e74c304730161434200244183ae23e6)
2025-02-14 18:41:15 +00:00
tonihei
8bd1db5f2c Move DefaultSuitableOutputChecker operations to playback thread
PiperOrigin-RevId: 726879236
(cherry picked from commit e0ef6e51829dd1627d952d2677f532230cb2dbc9)
2025-02-14 18:41:15 +00:00
tonihei
41af00f100 Move unsuitable output path logic <API31 into SuitableOutputChecker
This avoids distributing the logic between multiple classes and
keeps ExoPlayerImpl simpler.

PiperOrigin-RevId: 726874038
(cherry picked from commit 1015ef8b565ed04e88a9c596798d294327d05536)
2025-02-14 18:41:15 +00:00
tonihei
841e27ae5c Move MediaMetricsListener creation and reporting off main thread
The creation can be moved to the playback thread (to guarantee it
happens in sync other initialization after playback start) and the
potentially blocking calls to the reporting methods can be moved
to the generic shared BackgroundExecutor (it can't use the playback
thread because it no longer exists when the session is ended after
the player is released).

PiperOrigin-RevId: 726872818
(cherry picked from commit d386e002d2b34817178d088f277ced3bf3943ef2)
2025-02-14 18:41:15 +00:00
tonihei
cd6e61d856 Send renderer settings messages to secondary renderers in ExoPlayer
They are currently only sent to the primary renderer.

PiperOrigin-RevId: 726864595
2025-02-14 04:06:36 -08:00
tonihei
12afdfbaea Improve placeholder value in StreamVolumeManager for muting
The mute value usually changes in line with volume == 0.

Also update the test to provide better coverage of the
immediate and delayed state changes.

PiperOrigin-RevId: 726839927
2025-02-14 02:28:34 -08:00
tonihei
dfd2b75720 Stablize command button icons
These are the preferred replacement for custom icon res ids.

PiperOrigin-RevId: 726821168
2025-02-14 01:24:18 -08:00
tonihei
385498c24e Move audio session id generation to playback thread
PiperOrigin-RevId: 726556015
2025-02-13 10:57:19 -08:00
dancho
22853a5c4c MCVR: fix dropped input buffer count
updateDroppedBufferCountersWithInputBuffers should receive
buffer timestamps before adjusting offset

PiperOrigin-RevId: 726473967
2025-02-13 07:17:41 -08:00
ibaker
12072f7248 Rename TestPlayerRunHelper.run(...) to advance(...)
This avoids a clash with the `run` keyword in Kotlin.

PiperOrigin-RevId: 726461032
2025-02-13 06:37:31 -08:00
andrewlewis
9e22f03718 Add support for screen recording to the Transformer demo
Screen recording continues even if the transformer activity is backgrounded,
to support recording other apps.

PiperOrigin-RevId: 726454538
2025-02-13 06:16:54 -08:00
andrewlewis
04d9a751c6 Add encoder setting to set frame repeat interval
This can be used for screen recording to produce files without large gaps
between frames.

PiperOrigin-RevId: 726451010
2025-02-13 06:05:52 -08:00
kimvde
2a91d47ea9 Add tests for prewarming disabled
PiperOrigin-RevId: 726411904
2025-02-13 03:49:14 -08:00
Googler
f5e583332b Copy the isPlaceholder flag into CurrentMediaItemOnlyTimeline
PiperOrigin-RevId: 726405776
2025-02-13 03:32:08 -08:00
kimvde
cea67e8826 Do not record test skipped for tests skipped on emulator
AndroiTestUtil.recordTestSkipped is useful for MH tests

PiperOrigin-RevId: 726397349
2025-02-13 03:01:18 -08:00
sheenachhabra
56bd32da96 Add Lint.IfChange/ThenChange for muxer codec support logic
Updating the list of supported codec in Mp4Muxer and
FragmenetdMp4Muxer is often missed when a new codec is added
in Boxes.java.

PiperOrigin-RevId: 726397337
2025-02-13 02:59:20 -08:00
dancho
38cfd7dc36 Add dropped input buffers to DebugTextViewHelper
The field is more relevant with the
`experimentalSetLateThresholdToDropDecoderInputUs` API.

PiperOrigin-RevId: 726383978
2025-02-13 02:10:57 -08:00
dancho
b9ef0353cf Reduce memory allocations of AnnexBToAvccConverter
Add a new ByteBufferAllocator interface, with a simple
LinearByteBufferAllocator implementation that supports basic memory
allocations and reuse.

Add an AnnexBToAvccConverter.process override that takes a custom allocator

PiperOrigin-RevId: 726380184
2025-02-13 01:59:02 -08:00
ibaker
ee4f9d9140 Remove media1 dependency from media3-ui lib
Also remove deprecated
`PlayerNotificationManager.setMediaSessionToken(MediaSessionCompat)`
method.

PiperOrigin-RevId: 726159462
2025-02-12 12:46:27 -08:00
ibaker
2155c37b08 Switch most intra-lib deps from implementation to api
In all these cases I found at least one public method that takes or
returns a type from the dependency, or a type that inherits from a type
defined in the dependency.

PiperOrigin-RevId: 726130595
2025-02-12 11:29:50 -08:00
kimvde
6e332e9b91 Do not fail the export in case of ExoPlayer release time out
PiperOrigin-RevId: 725996730
2025-02-12 04:13:27 -08:00
bachinger
813973bb58 Explicitly test custom extra entries for legacy conversions
Issue: androidx/media#2127
PiperOrigin-RevId: 725984132
2025-02-12 03:25:22 -08:00
kimvde
be51913b81 Refactor GlObjectsProvider release logic
When possible, make it the caller's responsibility to release the
GlObjectsProvider. It's good practice that the class creating an object
also releases it. It avoids releasing the same object (here, the
GlObjectsProvider) multiple times, which was causing
DefaultVideoCompositorPixelTest to fail.

PiperOrigin-RevId: 725983417
2025-02-12 03:23:49 -08:00
ibaker
5de3ee86e3 Remove the lib-exoplayer dep from lib-common-ktx
This dependency is not used.

PiperOrigin-RevId: 725983258
2025-02-12 03:21:31 -08:00
Googler
1310496809 Revert DefaultEncoderFactory to previous defaults
Also changed encoder factory to not set operating rate or priority if they equal `RATE_UNSET` rather than `NO_VALUE`

PiperOrigin-RevId: 725964682
2025-02-12 02:11:39 -08:00
rohks
75607ac1eb Add tests for setDataSource APIs to MediaExtractorContractTest
Added remaining tests to cover all `setDataSource` APIs in `MediaExtractor`.

PiperOrigin-RevId: 725616816
2025-02-11 07:11:06 -08:00
Googler
a5e5374ba0 Enable ALL_CODECS MediaCodec list when decoding MV-HEVC video.
This is needed since MV-HEVC decoder is marked as a special codec in the media_codecs.xml file.

PiperOrigin-RevId: 725616290
2025-02-11 07:10:08 -08:00
rohks
a5689735a1 Replace assumeTrue with @SdkSuppress for SDK version checks
PiperOrigin-RevId: 725616043
2025-02-11 07:08:16 -08:00
Googler
447d784636 Use encoder input format for sample rate fallback.
This ensures that when the input sample rate handled by the encoder differs from the output of the AudioGraph, that output audio will not be broken.

PiperOrigin-RevId: 725610384
2025-02-11 06:43:58 -08:00
shahddaghash
1b882fec0c Add unit tests for EditingMetricsCollector
The change includes adding a `MetricsReporter` interface with a default implementation to allow testing the `EditingMetricsCollector`.

PiperOrigin-RevId: 725607330
2025-02-11 06:33:39 -08:00
Googler
fafd12bcfe Rollback of 4ed9abd05b
PiperOrigin-RevId: 725575345
2025-02-11 04:26:50 -08:00
kimvde
9f60eb3825 Fix TODOs formatting in Transformer
PiperOrigin-RevId: 725572106
2025-02-11 04:15:32 -08:00
dancho
5510635620 MCVR Parse AV1 sequence headers in onQueueInputBuffer
AV1 random access points (sync samples) contain updated sequence headers
that are needed for later frame parsing.

PiperOrigin-RevId: 725565810
2025-02-11 03:50:32 -08:00
Googler
50d4e66308 Add getInputFormat to Codec interface
PiperOrigin-RevId: 725552831
2025-02-11 02:58:55 -08:00
jbibik
d022b570f2 Upgrade Kotlin to 2.0.20
This upgrade allows us to use https://developer.android.com/develop/ui/compose/compiler as specified in https://developer.android.com/jetpack/androidx/releases/compose-kotlin#pre-release_kotlin_compatibility

kotlinOptions is considered deprecated in preference of kotlin.compilerOptions (https://kotlinlang.org/docs/gradle-compiler-options.html#how-to-define-options)

PiperOrigin-RevId: 725266131
2025-02-10 10:32:47 -08:00
andrewlewis
1a996d87ca Add MediaProjectionAssetLoader
The new asset loader is a wrapper around a provided `MediaProjection` and
`SurfaceAssetLoader`.

The test relies on automating the UI to get a `MediaProjection` instance, which
doesn't work reliably on all devices, so it's restricted to Pixel devices from
API 29 onwards.

For now only video is supported. The plan is to add support for audio playback
capture (also using `MediaProjection`) in a later change(s).

PiperOrigin-RevId: 725241962
2025-02-10 09:23:34 -08:00
kimvde
faf555e12b MultipleInputVideoGraph: Destroy EGL context on GL thread
PiperOrigin-RevId: 725159572
2025-02-10 04:25:32 -08:00
kimvde
cadecf0219 Add video prewarming to CompositionPlayer
PiperOrigin-RevId: 725153751
2025-02-10 04:04:19 -08:00
Googler
aa6183e883 Update EncoderPerformanceAnalysisTest
PiperOrigin-RevId: 725150395
2025-02-10 03:48:38 -08:00
Googler
0ba3bf66c6 Add raw audio support to Mp4Muxer.
The fourcc used is `sowt` for little endian 16 bit PCM and `twos` for big endian 16 bit PCM.

PiperOrigin-RevId: 724391195
2025-02-07 10:26:48 -08:00
tianyifeng
1190980616 Remove deprecated DownloadHelper constructor and util method
PiperOrigin-RevId: 724300907
2025-02-07 04:50:49 -08:00
dancho
65e7b599d8 AV1 treat show_existing_frame headers as not depended on
PiperOrigin-RevId: 724300793
2025-02-07 04:49:10 -08:00
claincly
babc2dd416 Match with ExoPlayer seeking when using CompositionPlayer
In a image sequence, seek to after the image duration should render the final
image frame. But currently it hangs as ConstantRateTimestampIterator doesn't
output any timestamp in this case

PiperOrigin-RevId: 724295475
2025-02-07 04:26:00 -08:00
ibaker
5e6fb88372 Add missing LINT.If/ThenChange comments
PiperOrigin-RevId: 724291745
2025-02-07 04:10:41 -08:00
claincly
d23d4fc314 Add more test cases to the seek test
This test also changes the image seeking behavior to match ExoPlayer: now if
seeking to after the end of the image, the last frame of the image would still
be presented.

PiperOrigin-RevId: 724054371
2025-02-06 13:35:40 -08:00
tianyifeng
d641f6a04c Allow setting byte range for progressive media in DownloadRequest
PiperOrigin-RevId: 723995315
2025-02-06 10:52:10 -08:00
ibaker
097771306d Remove LINT.IfChange comments with no following LINT.ThenChange
PiperOrigin-RevId: 723918659
2025-02-06 06:58:11 -08:00
kimvde
e229f957e6 A few nits
PiperOrigin-RevId: 723859603
2025-02-06 02:57:11 -08:00
dancho
a56a0bd928 MCVR: drop decoder input buffers when the decoder is late
* Add experimentalSetMinEarlyUsToDropDecoderInput to DefaultRenderersFactory
and MediaCodecVideoRenderer
* Enable dropping decoder input buffers inside MCVR.shouldIgnoreFrame
* Track consecutive dropped buffers via priority queue for reordering

PiperOrigin-RevId: 723837356
2025-02-06 01:40:28 -08:00
rohks
baf46d36d9 Bump Media3 version to 1.6.0-alpha03
#cherrypick

PiperOrigin-RevId: 723531635
2025-02-05 09:03:49 -08:00
rohks
6a4aa4515e Update release notes for Media3 1.6.0-alpha03 release
#cherrypick

PiperOrigin-RevId: 723512116
2025-02-05 08:03:50 -08:00
michaelkatz
edc44eefd2 Limit dynamic scheduling by the playing period transition point
In the case of replace stream media item transition, it is important that dynamic scheduling does not set the next work task later than the transition boundary.

#cherrypick

PiperOrigin-RevId: 723502204
2025-02-05 07:31:03 -08:00
Googler
85158ec841 Update operating rate adjustment visibility
PiperOrigin-RevId: 723501709
2025-02-05 07:27:57 -08:00
dancho
b90610b95a Force signal encoder end of stream after all output has been processed
Fixes a rare failure on some devices.
Only available when `experimentalSetMaxFramesInEncoder` is enabled.

PiperOrigin-RevId: 723498652
2025-02-05 07:15:23 -08:00
dancho
a80e7be029 MCVR support skipping parts of AV1 input buffers
AV1 input buffers contain multiple compressed pictures.
Enable skipping only the last showable frame, while leaving
any reference pictures to be decoded later, as part of
the next decoder input buffer.

Partial skipping of AV1 input buffer is only applied when:
* fewer than 8 OBUs are delayed
* there's likely to be enough capacity in the decoder input buffer
  for the next frame

PiperOrigin-RevId: 723496060
2025-02-05 07:09:20 -08:00
rohks
4f3ed2490a Rollback of 99f2a9f152
PiperOrigin-RevId: 723496012
2025-02-05 07:06:09 -08:00
dancho
2fe92bfca5 Use the AV1 sample dependency parser in MCVR
Parsing AV1 bitstreams allows us to identify frames that are
not used as reference, and improve seeking or frame dropping
behavior.

The AV1 bitstream format is relatively quick to parse

PiperOrigin-RevId: 723462680
2025-02-05 04:51:02 -08:00
shahddaghash
bcce7b5949 Pass the dataType to MediaItemInfo for Transformer
From the `sampleMimeType`, we can know whether the media item contains video data, image data, and audio data. This is done for input media items, and the output media item.

PiperOrigin-RevId: 723459732
2025-02-05 04:38:06 -08:00
michaelkatz
449b81d510 Fix ordering of MediaCodecRenderer.getDurationToProgressUs parameters
Parameter list should match the base Renderer.getDurationToProgressUs more closely.

#cherrypick

PiperOrigin-RevId: 723449756
2025-02-05 04:03:33 -08:00
kimvde
92a06606b4 Remove ability to run the DefaultVideoCompositor on its own GL thread.
- It's never used and handling multi-threading is costly.
- If the VideoCompositor and the VideoFrameProcessors use separate
threads and the same GlObjectsProvider, the GlObjectsProvider is
accessed from multiple threads. This class doesn't seem designed for
multi-threading.

PiperOrigin-RevId: 723448013
2025-02-05 03:55:42 -08:00
michaelkatz
4c163553e7 Remove pending adLoad timeout tasks when resuming content
When a timeline change occurs, the `AdTagLoader` will post an adLoad timeout task if currently waiting for an ad to load. Once sdk calls `loadAd`, any pending timeout tasks are removed. If the timeout occurs, the ad group is canceled and content will resume.

If the SDK proceeded to resume content without calling `loadAd`, then any pending timeout tasks should be removed or else a future ad group may error due to a previous timeout task.

PiperOrigin-RevId: 723442852
2025-02-05 03:31:13 -08:00
michaelkatz
97a1d31b5d Reset prewarming renderers when seek resets prewarming media period
Seeking will reset media periods following the current playing period. This includes resetting any current pre-warming media periods. Therefore while seeking, any current pre-warming should be disabled and reset.

#cherrypick

PiperOrigin-RevId: 723439145
2025-02-05 03:14:38 -08:00
dancho
f8f66bdfaa Mp4Muxer: disable sample batching and copying by default.
When sample batching is disabled, copying of the ByteBuffer data
is not necessary as samples are written as they arrive.
Copying of the BufferInfo is necessary because the info is needed
for writing the moov atom.

The input ByteBuffer can be in little endian order, or have its
position set. AnnexBUtils now ensures big endian order before
inspecting bytes, and supports reading from a non-zero position.

This change reduces the amount of memory allocations by Mp4Muxer
in its default configuration

PiperOrigin-RevId: 723401822
2025-02-05 01:05:22 -08:00
shahddaghash
05e66d9cf6 Fix metrics codec names collection
Previously, the codec names for the input were collected from `Format.codecs` which return the RFC 6381 string not the codec name used. This was changed to retain the decoder name from `ProcessedInput` instead.

PiperOrigin-RevId: 723129667
2025-02-04 10:05:37 -08:00
Copybara-Service
a19f68c87e Merge pull request #2107 from MGaetan89:issue_384699964
PiperOrigin-RevId: 723124733
2025-02-04 09:53:43 -08:00
sheenachhabra
ae3f962769 Extract "is last item in sequence" check
This improves readability.

PiperOrigin-RevId: 723116229
2025-02-04 09:27:48 -08:00
Gaëtan Muller
a4cc0f2384
Replace Util.MODEL with Build.MODEL 2025-02-04 15:34:33 +01:00
Gaëtan Muller
2aab921aa2
Replace Util.MANUFACTURER with Build.MANUFACTURER 2025-02-04 15:04:08 +01:00
Gaëtan Muller
d0a3d31e56
Replace Util.DEVICE with Build.DEVICE 2025-02-04 15:00:22 +01:00
tianyifeng
3e56d2a6fb Add ProgressiveMediaSource.Listener interface and onSeekMap event
This will allow the listeners who are interested in the `SeekMap` to get informed once the period has done the preparation.

PiperOrigin-RevId: 723027718
2025-02-04 04:30:29 -08:00
michaelkatz
decfb9b0a9 Refactor MediaCodecVideoRenderer to use a Builder
MediaCodecVideoRenderer is becoming unwieldy with the numerous constructors and optional settings. This refactors MediaCodecVideoRenderer to use a builder pattern for simplicity.

PiperOrigin-RevId: 723022129
2025-02-04 04:08:56 -08:00
shahddaghash
1431497e7f Pass output media item information to MediaItemInfo
PiperOrigin-RevId: 723007882
2025-02-04 03:10:24 -08:00
Googler
6e9a2cc0cd Rollback of 79b61d05a6
PiperOrigin-RevId: 722741988
2025-02-03 11:55:06 -08:00
Googler
aaa7e9e3cb Open visibility to HttpDataSourceTestEnv constants
PiperOrigin-RevId: 722712331
2025-02-03 10:37:43 -08:00
dancho
93c129449a Adjust threshold for analyzeVideo performance test
PiperOrigin-RevId: 722651555
2025-02-03 07:33:53 -08:00
shahddaghash
35d5bd9675 Pass output media items's video size and frame count to MediaItemInfo
PiperOrigin-RevId: 722613631
2025-02-03 05:10:48 -08:00
Googler
4ed9abd05b Rollback of 0fb4e3ba11
PiperOrigin-RevId: 722585306
2025-02-03 03:18:59 -08:00
tonihei
82cb1d8ac7 Move AudioBecomingNoisyManager system calls off main thread
PiperOrigin-RevId: 722569306
2025-02-03 02:18:02 -08:00
shahddaghash
5f4c30c431 Add color changing for Text overlay effect
This includes adding a colors dropdown menu for the text color.

PiperOrigin-RevId: 721830591
2025-01-31 10:59:16 -08:00
shahddaghash
9c0a9c19b7 Add Custom Text Overlay to Effect Demo
It includes entering a custom text and setting the alpha scale. When the effect is applied, it shows the text in the center of the screen. A following change will include changing the color of the text.

PiperOrigin-RevId: 721828892
2025-01-31 10:54:50 -08:00
rohks
99f2a9f152 Remove publishing for media3-ui-compose temporarily
Disabled until we find a way to exclude it from media3 Javadoc prebuilts

PiperOrigin-RevId: 721765815
2025-01-31 07:27:59 -08:00
ivanbuper
79b61d05a6 Rollback of 492574bded
PiperOrigin-RevId: 721758531
2025-01-31 06:56:13 -08:00
Googler
d6844699c5 Recognize QC's secure MV-HEVC decoder.
PiperOrigin-RevId: 721749833
2025-01-31 06:20:49 -08:00
Googler
0fb4e3ba11 Add DebugViewEffect
Also updated DefaultVideoFrameProcessor to create GlShaderPrograms with the working ColorInfo rather than the output ColorInfo.

PiperOrigin-RevId: 721748002
2025-01-31 06:13:57 -08:00
claincly
2b07ece0e5 Allow sequences to have non-matching durations
And only repeat the secondary sequence if `isLooping` is set to true

PiperOrigin-RevId: 721713830
2025-01-31 03:46:15 -08:00
tonihei
39d0881083 Add option to ClippingMediaSource to clip unseekable media
This means we need convert some of the assertions in
ClippingMediaPeriod to contrain the output value to clipped
range instead, because unseekable media will return zero
as a start and seek position in all cases.

PiperOrigin-RevId: 721463824
2025-01-30 11:40:46 -08:00
tonihei
df575a8d19 Add ClippingMediaSource.Builder
This prevents complicated constructor changes when we add new options.

PiperOrigin-RevId: 721415339
2025-01-30 09:23:49 -08:00
rohks
344214d711 Fix RELEASENOTES based on style guide
PiperOrigin-RevId: 721401564
2025-01-30 08:36:39 -08:00
Copybara-Service
fc1d133454 Merge pull request #1905 from khouzam:placeholderSurface
PiperOrigin-RevId: 721367514
2025-01-30 06:34:26 -08:00
shahddaghash
9f96fe81f3 Pass video data space to output's MediaItemInfo
The DataSpace contains the Color Standard, Range, and Transfer.

PiperOrigin-RevId: 721341719
2025-01-30 04:39:24 -08:00
microkatz
ad18ae9c42 Added release note 2025-01-30 11:40:56 +00:00
Gilles Khouzam
8466a957c3 Make MediaCodecVideoRenderer::shouldUsePlaceholderSurface protected.
This enables a derived renderer to disable the placeholder surface.
2025-01-30 11:35:39 +00:00
rohks
045b8e6a52 Fix lint warnings in RELEASENOTES
PiperOrigin-RevId: 721326280
2025-01-30 03:35:04 -08:00
shahddaghash
3f4e0bdb04 Report output's MediaItemInfo to EditingEndedEvent
Most of the values for the output `MediaItemInfo` will be retained from the `ExportResult`, so it's now passed to `onExportSuccess` and `onExportError` directly. For now, the duration of the ouput is recorded for metrics, and more values will be added in the following CLs.

PiperOrigin-RevId: 721324689
2025-01-30 03:27:25 -08:00
tonihei
17100259cd Move Util.getCountryCode off main thread in DefaultBandwidthMeter
PiperOrigin-RevId: 721293354
2025-01-30 01:23:56 -08:00
tonihei
6b31b4620c Move NetworkTypeObserver operations off main thread
PiperOrigin-RevId: 721291681
2025-01-30 01:17:22 -08:00
kimvde
9af43c7381 CompositionPlayer: skip decode-only frames upstream of the VideoGraph
This is necessary for prewarming. With prewarming, in a sequence of 2
videos, the second renderer is enabled before the first one is disabled,
and decode-only frames should be skipped before the second renderer is
started. The problem is that the second renderer will forward frames to
a BufferingVideoSink before it is started, which  will delay the frame
handling and therefore not skip the frame before the renderer is
started.

PiperOrigin-RevId: 721032049
2025-01-29 10:44:56 -08:00
rohks
3c0e2ee198 Suppress UseSdkSuppress warning in MediaExtractorContractTest
The fix is included in Android Gradle Plugin `8.9.0-alpha05`. Remove this suppression after upgrading.

PiperOrigin-RevId: 721009745
2025-01-29 09:42:37 -08:00
rohks
c9a936e153 Bump Media3 version to 1.6.0-alpha02
#cherrypick

PiperOrigin-RevId: 720990835
2025-01-29 08:43:21 -08:00
ivanbuper
fc6df77831 Remove deprecated androidx.media3.exoplayer.audio.SonicAudioProcessor
Also, make `androidx.media3.common.audio.SonicAudioProcessor` final.

PiperOrigin-RevId: 720987023
2025-01-29 08:30:09 -08:00
rohks
98f52a1dbb Rollback of 5c3c3b91f3
PiperOrigin-RevId: 720968647
2025-01-29 07:33:56 -08:00
rohks
6e53d9b3bd Update release notes for Media3 1.6.0-alpha02 release
#cherrypick

PiperOrigin-RevId: 720963785
2025-01-29 07:16:55 -08:00
rohks
dfef16d5c6 Restrict test to API 24+ in MediaExtractorContractTest
`MediaExtractor.setDataSource(AssetFileDescriptor afd)` requires API 24+.

#cherrypick

PiperOrigin-RevId: 720943416
2025-01-29 06:05:39 -08:00
rohks
e15438322d Fix ArrayIndexOutOfBoundsException in MP4 edit lists
The exception occurred when an edit list started at a non-sync frame with no preceding sync frame. The fix searches forward for the next sync frame in such cases, preventing the out-of-bounds access.

Issue: androidx/media#2062

#cherrypick

PiperOrigin-RevId: 720642687
2025-01-28 11:13:38 -08:00
dancho
bb37aad170 Add an option to use different texture filtering
Add an option to GlMatrixTransformation to choose the OpenGL texture
minification filter.
When mipmaps are requested, mipmaps are generated with
`glGenerateMipmap()`.

PiperOrigin-RevId: 720629807
2025-01-28 10:44:26 -08:00
shahddaghash
bb9b3bd660 Pass codec names of input media items to MediaItemInfo
PiperOrigin-RevId: 720579315
2025-01-28 08:12:42 -08:00
rohks
8d22482f79 Add MediaExtractor contract test structure with one basic test
Additional tests will be added in future changes.

PiperOrigin-RevId: 720555580
2025-01-28 06:44:40 -08:00
tonihei
ddcf455d03 Convert context dependent TrackSelectionParameters to boolean
This avoids that these settings have to be resolved inline,
potentially blocking the main thread. They can be resolved at
the time of track selection on a background thread instead.

As a side effect, we can also remove the context parameter from
the Builder. Having the Context in the Builder is also a bad sign
in the first place because it implies the potentially blocking
calls can happen.

PiperOrigin-RevId: 720523139
2025-01-28 04:26:15 -08:00
shahddaghash
c058d97a97 Pass input media items audio information to MediaItemInfo
Audio `sampleMimeType`, `channelCount`, and `sampleRate` were retained from `ExportResult.ProcessedInput`'s audio format.

PiperOrigin-RevId: 720500611
2025-01-28 02:54:32 -08:00
tonihei
706c363104 Remove potentially blocking calls from DefaultTrackSelector UI thread
The calls to Util.isTV and the interactions with the spatializer are
potentially blocking and were triggered from the constructor,
setAudioAttributes and release.

setAudioAttributes and release are both documented to be called by
the Player and should be triggered on the playback thread anyway.

The constructor initialization can be delayed until the spatializer
might be needed to avoid the blocking call.

The threading clean-up also allows to remove the lock from the
audioAttributes and the spatializer fields as they are now
accessed on the playback thread only.

PiperOrigin-RevId: 720488979
2025-01-28 02:09:28 -08:00
shahddaghash
80e6fa2aa7 Separate ProcessedInput's format into audio and video
Before the change, `SequenceAssetLoader#onTrackAdded` was being called twice, for audio and video. `ExportResult.ProcessedInput` took one format, which was the latest to be written. The change leads to capturing both, the audio and video formats of the input, and prevents the issue of a format overwriting the other.

PiperOrigin-RevId: 720487697
2025-01-28 02:04:02 -08:00
kimvde
0b9ca1e70b Ignore 1 test in CompositionMultipleSequencePlaybackTest
playback_sequencesOfVideos_effectsReceiveCorrectTimestamps is failing
for prewarming. The following is happening:
- For prewarming, there are 2 alternating video renderers per sequence.
- When the first MediaItem ends (for both sequences), signalEndOfInput
is not called on the InputVideoSink (which is expected).
- The DefaultVideoCompositor doesn't receive the end-of-input-source
signal (which is also expected).
- As a result, the DefaultVideoCompositor never outputs the last frame
because it waits for more input frames to be fed.
- The VideoGraph thus doesn't output the last frame either, and the
first video renderer never ends.
- This causes playback to get stuck.

This is similar to the problem of supporting multiple video sequences
with images and videos in CompositionPlayer.

PiperOrigin-RevId: 720106413
2025-01-27 03:50:32 -08:00
dancho
80a734f4f1 AV1 bitstream parser that identifies frames with no dependencies
Usage:
* call queueInputBuffer() with initialization data or sample data in decode
order - updates the parser state
* call sampleLimitAfterSkippingNonReferenceFrame to identify if the sample
contains a frame without dependencies
PiperOrigin-RevId: 720098835
2025-01-27 03:19:36 -08:00
shahddaghash
c3962d2fe6 Collect clipped duration instead of media item's duration
Previously, the input media item's duration was collected from `ProcessedInput.durationUs`. However, this value turned out to be the duration of the media item after clipping. Getting the duration of the input media item before clipping is tricky, so it will be dropped from Editing Metrics V1.

PiperOrigin-RevId: 719254077
2025-01-24 04:53:35 -08:00
dancho
227a4d76b1 AV1 Frame Header parsing
PiperOrigin-RevId: 719253811
2025-01-24 04:50:11 -08:00
ibaker
1772050ece Remove confusing null from ParserException.getMessage()
When reading quickly this suggests something 'real' was null (similar to
a `NullPointerException`), but it's actually just the message from the
superclass.

Seen in stack trace in Issue: androidx/media#2074:

```
Caused by: androidx.media3.common.ParserException: null {contentIsMalformed=true, dataType=1}
```

PiperOrigin-RevId: 719240235
2025-01-24 03:55:19 -08:00
shahddaghash
382f30616a Rollback of b25d6ef249
PiperOrigin-RevId: 719238418
2025-01-24 03:46:54 -08:00
dancho
dddcdf1613 AV1 sequence header parsing
PiperOrigin-RevId: 719200478
2025-01-24 01:25:58 -08:00
ibaker
a7a5d6e92b Suppress (and log) subtitle errors in SubtitleTranscodingTrackOutput
This is equivalent to the error suppression for legacy subtitles in
`TextRenderer`:
76088cd6af/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/text/TextRenderer.java (L357-L359)

This new suppression only affects errors thrown from files with
subtitles muxed together with audio/video. Standalone subtitle
files, and containers containing only text tracks, are handled
by the existing error suppression/reporting added in
49dec5db8b.

Issue: androidx/media#2052
PiperOrigin-RevId: 718930243
2025-01-23 10:49:23 -08:00
claincly
6b54372df8 Have Sequence Player end as audio sink position is passed
The fix is to update `AudioGraphInputAudioSink.lastHandledPositionUs` when a buffer is handled, and end the `AudioGraphInputAudioSink` as the final audio sink plays out further than this position.

PiperOrigin-RevId: 718901825
2025-01-23 09:38:19 -08:00
Copybara-Service
c1242ffef1 Merge pull request #1235 from DolbyLaboratories:dlb/dovi-transformer/dev
PiperOrigin-RevId: 718856455
2025-01-23 07:26:21 -08:00
dancho
61b7dfd7ba Add ObuParser to split a ByteBuffer into AV1 OBUs
PiperOrigin-RevId: 718813571
2025-01-23 04:46:38 -08:00
Copybara-Service
916018d9fe Merge pull request #1936 from colinkho:eval-buf
PiperOrigin-RevId: 718809019
2025-01-23 04:27:16 -08:00
microkatz
b6d4886887 Reevaluate ongoing chunk loading when pausing 2025-01-22 16:23:55 +00:00
ibaker
9bb254f697 Skip SSA cues where end time is before or equal to start time
The file in Issue: androidx/media#2052 contains a cue with the following timecode:

```
0:00:00:00,0:00:00:00
```

The content of this cue seems to be some 'converted by' metadata, i.e.
it's basically a comment and clearly not intended to be shown on
screen (since it has zero duration).

There is some fiddly logic later in `SsaParser` to support overlapping
cues with the old `Subtitle` structure [1], and this logic gets tripped
up by the start and end time being equal, which results in a
**single**, empty `List<Cue>` being added - which trips up another
assumption that every SSA cue line results in at least two `List<Cue>`
entries (one containing the cue text, and another containing an empty
list to signal the end of the cues).

This fiddly logic is no longer required, because overlapping
`CuesWithTiming` objects can now be merged in `TextRenderer`, so there
is a possible future simplification to `SsaParser` which removes a lot
of this complexity.

[1] Added in <unknown commit>

PiperOrigin-RevId: 718380386
2025-01-22 07:41:24 -08:00
Colin Kho
8b0cfda178 Move queue reevaluate buffer to only execute when playWhenReady is set to false 2025-01-22 15:18:16 +00:00
Colin Kho
15bdbf735d Reevaluate buffer during a playWhenReady change 2025-01-22 15:18:16 +00:00
sheenachhabra
431efc9c50 Skip exportTranscodeBt2020Sdr test on sm-a325f API 33
The decode over reports its resolution capabilities and
decoder initialization eventually fails.

PiperOrigin-RevId: 718294047
2025-01-22 02:37:15 -08:00
kimvde
635e699965 Various improvements to BufferingVideoSink
PiperOrigin-RevId: 717807436
2025-01-21 01:54:45 -08:00
ibaker
5421a74d06 Tighten two MediaCodecRenderer fields to @MonotonicNonNull
Remove some `checkNotNull` calls that are no longer needed.

PiperOrigin-RevId: 717597046
2025-01-20 12:14:56 -08:00
sheenachhabra
7c10ef03e4 Check full format support in decoder for Transformer
For the case where a hardware decoder does not fully
support the requested format, a software decoder will
be preferred.

PiperOrigin-RevId: 717584768
2025-01-20 11:27:13 -08:00
sheenachhabra
6fe011beb4 Make Mp4Muxers and FragmentedMp4Muxers implement AutoCloseable
PiperOrigin-RevId: 717549236
2025-01-20 09:02:25 -08:00
sheenachhabra
a31c7ad9a8 Move Muxer.java interface into transformer module
Muxer interface is used only in transformer.

PiperOrigin-RevId: 717538306
2025-01-20 08:13:42 -08:00
sheenachhabra
fb58bdfd52 Refactor: Mp4Muxer and FragmentedMp4Muxer no longer implement Muxer
Mp4Muxer and FragmentedMp4Muxer has different contracts
and they can not implement same interface.

PiperOrigin-RevId: 717526288
2025-01-20 07:20:48 -08:00
sheenachhabra
c22798f99c Removing redundant call to setLastSampleDurationBehavior on muxer
The default value is already
LAST_SAMPLE_DURATION_BEHAVIOR_SET_FROM_END_OF_STREAM_BUFFER_OR_DUPLICATE_PREVIOUS

PiperOrigin-RevId: 717497116
2025-01-20 05:14:47 -08:00
kimvde
8abd36fb27 Simplify MediaCodecRenderer.maybeInitializeProcessingPipeline
PiperOrigin-RevId: 717466577
2025-01-20 03:07:59 -08:00
tonihei
190563b8eb Move StreamVolumeManager system calls to playback thread
This requires some additional state handling to update the full
state atomically and guess placeholder states while updates are
in progress, using the newly added BackgroundThreadStateHander.

Some tests also needed to be adjusted to account for the fact
that the actual audio system change doesn't happen inline
anymore.

PiperOrigin-RevId: 716702141
2025-01-17 09:47:33 -08:00
sheenachhabra
c797249998 Move supported sample mime types list in specific muxer
This will reduce the risk of list becoming stale
(its already stale, to be fixed).

PiperOrigin-RevId: 716685242
2025-01-17 08:51:44 -08:00
claincly
a0618eb0ec Adds a test on using composition level effects
Also simplifies the test a bit.

PiperOrigin-RevId: 716683585
2025-01-17 08:46:44 -08:00
sheenachhabra
4ac4f7e2e0 Split InAppMuxer into InApp Mp4Muxer and FragmentedMp4Muxer
This is pre work required to remove `Muxer.java` interface
from the muxer module.
`Mp4Muxer` and `FragmentedMp4Muxer` will no longer implement
the `Muxer` interface.

PiperOrigin-RevId: 716669531
2025-01-17 08:01:28 -08:00
tonihei
a4d9a3e096 Move Wake/WifiLockManager system calls to playback thread
PiperOrigin-RevId: 716235808
2025-01-16 07:58:32 -08:00
tonihei
297b2b9956 Add util to handle background state updates and placeholder states.
This is a common pattern in media3 libraries where tasks are handled on a
background thread, but the calling thread sees an immediate state update
with a best-guess placeholder. This makes the integration for the caller
very easy as the API surface appears to be synchronous.

This util is a helper class to handle this logic and test it separately.

PiperOrigin-RevId: 716233966
2025-01-16 07:53:15 -08:00
Copybara-Service
fda8b8a35d Merge pull request #2023 from DolbyLaboratories:dlb/dovi-format/dev
PiperOrigin-RevId: 716226613
2025-01-16 07:26:46 -08:00
tonihei
4328d29f34 Lazily initialize current audio capabilities in DefaultAudioSink
PiperOrigin-RevId: 716224052
2025-01-16 07:17:26 -08:00
claincly
1732892927 Add tests for multi input video
PiperOrigin-RevId: 716208222
2025-01-16 06:19:47 -08:00
kimvde
b2c31b0743 Small nits in SequenceRenderersFactory
PiperOrigin-RevId: 716187537
2025-01-16 04:56:49 -08:00
claincly
0d8e42238e Stop checking all inputs ended when releasing compositor
This could happen when the player is released before ending so not all input
has ended.

PiperOrigin-RevId: 716173884
2025-01-16 04:11:21 -08:00
shahddaghash
f5f5b63bf7 Pass video data space to MediaItemInfo
The `DataSpace` contains the Color Standard, Range, and Transfer. These values were mapped as follows:
* Standard: From `@C.ColorSpace` to `@DataSpace.DataSpaceStandard`
* Range: From `@C.ColorRange` to `@DataSpace.DataSpaceRange`
* Transfer: From `@C.ColorTransfer` to `@DataSpace.DataSpaceTransfer`

PiperOrigin-RevId: 716157142
2025-01-16 03:03:19 -08:00
simakova
22142e0db3 Add keepScreenOn flag in Composition demo
PiperOrigin-RevId: 716139653
2025-01-16 01:57:53 -08:00
Googler
492574bded Rollback of 871381288c
PiperOrigin-RevId: 715891934
2025-01-15 12:08:40 -08:00
shahddaghash
e9b82ee951 Pass video frame rate and video size to MediaItemInfo
The frame rate, height, and width are extracted from the format of processedInput.

PiperOrigin-RevId: 715843086
2025-01-15 10:09:12 -08:00
shahddaghash
04c2d22178 Pass containerMimeType and sampleMimeType to MediaItemInfo
If a format is passed to `ExportResult.ProcessedInput`, the `containerMimeType` and `sampleMimeType` are extracted from the Format and passed inside the `MediaItemInfo` object.

PiperOrigin-RevId: 715840400
2025-01-15 10:03:57 -08:00
bachinger
b49eaf9e87 Add two MetadataRenderer instances by default
To receive multiple schemes of metadata emitted by a stream, multiple
`MetadataRenderer` instances need to be used. This change makes
`DefaultRenderersFactory` add two metadata renderers by default.

PiperOrigin-RevId: 715790821
2025-01-15 07:31:13 -08:00
dancho
0936b549ae Enable non-reference frame skipping in FrameExtractor
Speed up FrameExtractor for videos with non-ref frames

PiperOrigin-RevId: 715789816
2025-01-15 07:27:54 -08:00
kimvde
c5feb28838 Increase FrameExtractorTest timeout
FrameExtractorTest.extractFrame_pastDuration_returnsLastFrame is
sometimes timing out on emulator because it's too slow.

PiperOrigin-RevId: 715771071
2025-01-15 06:18:10 -08:00
michaelkatz
fa4cc7c65c Reduce flakiness for MediaCodecAudioRendererTests past SDK 30
PiperOrigin-RevId: 715770321
2025-01-15 06:15:26 -08:00
michaelkatz
62341f31f9 Reduce flakiness for MediaCodecVideoRendererTests past SDK 30
PiperOrigin-RevId: 715761006
2025-01-15 05:37:40 -08:00
tonihei
a2016f03c6 Remove ExoPlayer components
They have been deprecated since ExoPlayer 2.16.0 and can be easily
replaced by the exact same call in ExoPlayer directly.

PiperOrigin-RevId: 715755105
2025-01-15 05:11:09 -08:00
dancho
ee4a0ea3df Remove deprecated experimentalParseWithinGopSampleDependencies
Remove deprecated API in favor of
experimentalSetCodecsToParseWithinGopSampleDependencies.

PiperOrigin-RevId: 715731718
2025-01-15 03:43:55 -08:00
ibaker
736c7528cb Ignore EOS buffer timestamps in CeaDecoder.queueInputBuffer
This is aligned with the documentation of `MediaCodec` which says the
timestamp of a buffer with `BUFFER_FLAG_END_OF_STREAM` should be
ignored:
https://developer.android.com/reference/android/media/MediaCodec#end-of-stream-handling

Add a test that exercises this by clipping off the end of a sample with
CEA-608 captions, because this creates an EOS-flagged buffer with a
non-EOS timestamp.

Also add a straightforward playback test for the
`fragmented_captions.mp4` sample.

PiperOrigin-RevId: 715716036
2025-01-15 02:40:07 -08:00
Googler
2b9c7f8392 Add minimum size for OpusDecoder's streamMap.
PiperOrigin-RevId: 715453498
2025-01-14 11:17:09 -08:00
shahddaghash
465399b57e Disable default collection of platform diagnostics
This is to prevent facing errors in apps using Transformer. It will be enabled again once the project is done and comprehensive tests are added.

PiperOrigin-RevId: 715435613
2025-01-14 10:33:50 -08:00
sheenachhabra
1b2e391971 Change muxer instrumentation tests to robolectric
These tests were earlier using `MediaExtractor`, hence
they were in androidTest. Now `MediaExtractor` has been
replaced with `media3 MediaExtractorCompat` so these
test can be robolectric.

PiperOrigin-RevId: 715424470
2025-01-14 10:09:25 -08:00
kimvde
fbf9be2f00 Fix composition player repeat mode
Some checks in SingleInputVideoGraph were causing CompositionPlayer to
throw for a single media item sequence when repeat mode was enabled. The
reason was that, in this case, no new input stream is registered to the
VideoFrameProcessor.

PiperOrigin-RevId: 715409509
2025-01-14 09:20:14 -08:00
michaelkatz
2361624222 Make MediaCodecAudioRenderer.getDurationToProgressUs protected
This method is not intended to be exposed as 'public' as it was meant
to be internal to the class/subclasses.

PiperOrigin-RevId: 715402007
2025-01-14 09:00:37 -08:00
claincly
e9e0569425 Integrate MultiInputVideoGraph in CompositionPlayer
Currently doesn't support image/video transitions

PiperOrigin-RevId: 715393251
2025-01-14 08:33:39 -08:00
dancho
8f17ab84f8 Add API for sample dependency reading to DefaultMediaSourceFactory
PiperOrigin-RevId: 715372196
2025-01-14 07:28:03 -08:00
michaelkatz
2eb8e53f8c Fix getDurationToProgress for audio renderers to reset after seeking
Currently, if a seek occurs when the audio sink was providing back pressure, the audio renderers will provide a durationToProgress based on the last non-writable sample prior to the seek.

Solution is to reset the value used for the `getDurationToProgress` when onPositionReset occurs.

This CL also makes sure to reset the value whenever the audiosink is flushed or reset.

PiperOrigin-RevId: 715361834
2025-01-14 06:49:43 -08:00
tianyifeng
f05e6a7d6e Accurately determine hasPendingData after handling the end of stream
In `AudioTrackPositionTracker.hasPendingData()` method, `sourceEnded=false` is always passed. Thus, if the `AudioTimestampPoller` hasn't entered the `STATE_TIMESTAMP_ADVANCING` before the stream ends, then the current position can be inaccurately calculated in the absence of a correct `sourceEnded` value, and `AudioTrackPositionTracker.hasPendingData` will return a wrong result.

We used to avoid calling `getPlayheadPosition()` too often, which involves expensive binder request. However, here we need to adjust a bit back to compare the `writtenFrames` directly with `getPlayheadPosition()` after handling the end of stream, when not being able to declare `sourceEnded=true` can have significant difference.

PiperOrigin-RevId: 715358137
2025-01-14 06:37:02 -08:00
shahddaghash
adb9306e2d Add format of media item to Transformer's ExportResult.ProcessedInput
This will allow us to collect info from the Format for metrics.

PiperOrigin-RevId: 715336943
2025-01-14 05:16:08 -08:00
ibaker
eefad8cbfa Deprecate mutable static MediaParserChunkExtractor.FACTORY
Follow-up to <unknown commit>

PiperOrigin-RevId: 715329316
2025-01-14 04:46:32 -08:00
ibaker
6300a55eb2 Remove usage of mutable static BundledChunkExtractor.FACTORY
PiperOrigin-RevId: 715328246
2025-01-14 04:42:20 -08:00
dancho
1892435fb3 Parse within GOP sample dependencies in ExtractorsFactory
Add helper methods to Mp4Extractor and FragmentedMp4Extractor
to convert between VideoCodecFlags and *ExtractorFlags

PiperOrigin-RevId: 715307495
2025-01-14 03:16:49 -08:00
shahddaghash
b25d6ef249 Rollback of 72a71e8b9c
PiperOrigin-RevId: 714997645
2025-01-13 09:30:39 -08:00
ibaker
a4d28b4f10 Add PGS test file
Transformed from `sample_with_srt.mkv` and provided in
https://github.com/androidx/media/pull/1979#issuecomment-2574007148

PiperOrigin-RevId: 714955725
2025-01-13 07:14:13 -08:00
jbibik
52387bb975 [ui-compose] Rename showSurface to coverSurface in PresentationState
This renaming helps prevent the consumer from including `PlayerSurface` in a Compose tree based on `state.showSurface`. This field intuitively felt like the default would be not to show the surface (correct), but had an unintended consequence of not initialising `AndroidExternal(Embedded)Surface` (since the PlayerSurface Composable would be omitted).

To avoid this confusion, the `PresentationState` should communicate to the consumer when the Surface is not ready and hence should be covered with some overlay (shutter) to prevent poor UX from observing Surface resizing/freezing/flickering.

PiperOrigin-RevId: 714951607
2025-01-13 06:59:44 -08:00
Copybara-Service
d18ad57e30 Merge pull request #1979 from wischnow:main
PiperOrigin-RevId: 714911017
2025-01-13 04:09:58 -08:00
dancho
8b33a0a50f Add a ChunkExtractor API to select codecs for sample dependency parsing
Deprecate BundledChunkExtractor.experimentalParseWithinGopSampleDependencies
in favour of
ChunkExtractor.experimentalSetCodecsToParseWithinGopSampleDependencies
which takes a VideoCodecFlags IntDef flags that represent a set of codecs.

Add a DASH test using the new API with an H.265 video.

PiperOrigin-RevId: 714901602
2025-01-13 03:30:10 -08:00
andrewlewis
0a27e7946f When reregistering frames automatically, drop after signaling EOS
In this mode there isn't an explicit signal from upstream that an input frame
is going to arrive at the `SurfaceTexture`. This means that when end-of-stream
is signaled we don't know when there won't be any more input frames.

Reject new frames in `onFrameAvailable` after input EOS is handled for the case
of reregistering frames automatically. This avoids those frames entering the
pipeline and causing us to unexpectedly handle frames after EOS.

PiperOrigin-RevId: 714858039
2025-01-13 00:43:45 -08:00
kimvde
b183bfb0cb CompositionPlayerTest: use correct video duration
PiperOrigin-RevId: 714847208
2025-01-13 00:01:42 -08:00
sheenachhabra
36be62ee8c Skip duplicate chunks in stsc box writing
Earlier muxer wrote a different entry for every chunk but as
per the spec (ISO 14496-12) exact same chunks can be combined
and only the first chunk number can be written.

PiperOrigin-RevId: 714154531
2025-01-10 13:27:02 -08:00
jbibik
28027c64fd [ui-compose] Add aspect ratio to PresentationState
It captures the information needed for the UI logic related to rendering of the video track (and later images) to the surface.

The video size will be correct only after the Player decoded the video onto the surface and can't be reliably extracted from MediaItem's metadata. The information about the video's true aspect ratio helps inform the UI elements (such as PlayerSurface Composable) how to customize the Modifiers.

Use this state in demo-compose to show off functionality of changing `PlayerSurface`'s aspectRatio

PiperOrigin-RevId: 714104260
2025-01-10 10:50:53 -08:00
sheenachhabra
33c3d5140e Rollback of 750baf2c05
PiperOrigin-RevId: 714097260
2025-01-10 10:34:56 -08:00
dancho
93f9e6574c Add missing Mp4Extractor.Flags to IntDef
PiperOrigin-RevId: 714084405
2025-01-10 09:54:19 -08:00
ibaker
e61b521b4a Clear SEI reordering queue when seeking in MPEG-TS files
This avoids outputting spurious samples after the seek.

PiperOrigin-RevId: 714082343
2025-01-10 09:45:21 -08:00
Ian Baker
6291cfda86 Fix dump file to match @GraphicsMode(NATIVE) 2025-01-10 17:35:56 +00:00
Ian Baker
77790df5a2 Plumb palette through as a method parameter to satisfy nullness checker 2025-01-10 17:17:28 +00:00
shahddaghash
30c5c66db4 Pass input MediaItemInfo list with duration
Added the creation of `MediaItemInfo` for each MediaItem and passed the duration of the MediaItem to it. This is done for export operations that complete with success or with error.

PiperOrigin-RevId: 714056270
2025-01-10 08:11:45 -08:00
Googler
d9b61b060f Disable sample rate fallback tests.
In some cases encoders include a sample rate in their AudioCapabilities but do not support encoding at that sample rate. This breaks the assumption used to write these tests, so disable them for now until the fallback logic is updated to handle this limitation.

PiperOrigin-RevId: 714055556
2025-01-10 08:08:57 -08:00
Ian Baker
4744f082af Add release note 2025-01-10 16:04:04 +00:00
Ian Baker
b2f9c1e252 Reformat with google-java-format 2025-01-10 16:02:24 +00:00
Ian Baker
ce0512845f Fix lint/style issues, and log exception 2025-01-10 16:02:24 +00:00
Ian Baker
6658dc097b Rename new fixed test file to old name, and add output dump 2025-01-10 16:02:24 +00:00
Sven Wischnowsky
90c91fa5d7 Fixed subtitle track length. 2025-01-10 16:02:24 +00:00
Sven Wischnowsky
47a5166a1d Small fix for an off-by-two error. 2025-01-10 16:02:24 +00:00
Ian Baker
671810235f Assorted nullness and control flow clean-ups 2025-01-10 16:02:24 +00:00
Ian Baker
8520c66fd8 Add some nullness annotations, re-jig some logic and reformat 2025-01-10 16:02:24 +00:00
Sven Wischnowsky
391b72e257 Various cleanup and improvements as suggested by mainainer. 2025-01-10 16:02:24 +00:00
Sven Wischnowsky
f52d98eafd Use Util.maybeInflate() 2025-01-10 16:02:24 +00:00
Sven Wischnowsky
3cd0e1ad3d Add maybeInflate() which inflates only if the source buffer starts with the INFLATE_HEADER 2025-01-10 16:02:24 +00:00
Sven Wischnowsky
0254b2d16e Add Vobsub sample file 2025-01-10 16:02:24 +00:00
Sven Wischnowsky
250bc86669 Add comments about needing the size in the IDX file. 2025-01-10 16:02:24 +00:00
Sven Wischnowsky
27a6883768 Another small fix 2025-01-10 16:02:24 +00:00
Sven Wischnowsky
7fbf511d30 Small fix 2025-01-10 16:02:23 +00:00
Sven Wischnowsky
721776c30e Support for Vobsub subtitles 2025-01-10 16:02:23 +00:00
dancho
c26a633d83 Read sample dependencies for H.265 in FragmentedMp4
Add a new flag to FragmentedMp4Extractor
FLAG_READ_WITHIN_GOP_SAMPLE_DEPENDENCIES_H265
Read two bytes from H.265 videos to determine NAL unit type and
temporal layer id.

PiperOrigin-RevId: 714046987
2025-01-10 07:39:37 -08:00
tonihei
71cb246913 Fix bug where changing index does not reevaluate derived metadata
When the current MediaMetadata is automatically derived from the
MediaItem and Tracks, the result may change when state.buildUpon()
is used and new input data is provided (e.g. new current index).
To avoid reusing the previously derived MediaMetadata, we need to
reset it to null and re-evaluate it on every call to build()

Issue: androidx/media#1940
PiperOrigin-RevId: 714021424
2025-01-10 05:59:45 -08:00
tonihei
c3b13a60a3 Mark internal BasePlayer.seekTo method as @ForOverride
This method is not meant to be called directly, it only needs to
be implemented by subclasses.

PiperOrigin-RevId: 714000806
2025-01-10 04:32:32 -08:00
shahddaghash
72a71e8b9c Report exporter and muxer names metrics to EditingEndedEvent
Added reporting 2 values from Transformer:

- `exporterName`: name of the package calling for export. Example: "androidx.media3.transformer".
- `muxerName`: name of the muxer used for the export operation. Example: "androidx.media3.muxer".

PiperOrigin-RevId: 714000672
2025-01-10 04:29:25 -08:00
tonihei
f6eb2e6dd5 Fix discontinuity reporting in ClippingMediaPeriod
The initial discontinuity is currently only reported if
the period is prepared at the clip start position. However,
we need the discontinuity whenever we prepare at a non-zero
position (unless we know all samples are sync samples).

PiperOrigin-RevId: 713994155
2025-01-10 04:04:03 -08:00
ibaker
d0b757886e Fix handling of length-delimited NAL units with 1 or 2 byte length
ExoPlayer assumed 4-bytes for length in two places (by assuming the
length is the same as the 4-byte NAL start code):

1. In `AvcConfig` we transform length-delimited to start-delimited
   before writing into `initializationData`, and then skip
   'nal unit length field' bytes when parsing from `initializationData`
   (when we should skip 'start code length' bytes instead).

2. In `Mp4Extractor.readSample` we modify the local variable
   `sampleSize` to fix the difference between length field length and
   start code length, but **only on the first attempt to read a
   sample**. If we are resuming in the middle of reading a sample (after
   a recoverable I/O error), this fix for `sampleSize` is not done,
   which means we end up missing the last 2-3 bytes of the sample when
   the NAL length is 1-2 bytes.
     * This is fixed by moving the `sampleSize` 'fixing' code to outside
       the `if (sampleCurrentNalBytesRemaining == 0)` block.
     * `FragmentedMp4Extractor` has very similar code, but uses a
       field for `sampleSize`, rather than a local, so doesn't look
       vulnerable to the same problem (though I haven't totally
       tested this).

This change adds a test file with 2-byte NAL lengths, generated by
hacking the media3 muxer to emit 2-byte NAL lengths and transforming
`sample.mp4` using the transformer demo app.

PiperOrigin-RevId: 713709203
2025-01-09 10:06:13 -08:00
shahddaghash
64462b99fc Add mediaItem durationUs to Transformer's ExportResult.ProcessedInput
This will allow us to access the duration of the input media items and collect them for metrics.

PiperOrigin-RevId: 713686613
2025-01-09 08:51:00 -08:00
tonihei
b225383958 Don't force discontinuity when clipping a merged source duration
We only care about the duration and don't want to force an initial
discontinuity.

The problem is not currently visible due to a bug in
ClippingMediaPeriod that ignores all dicontinuities if they don't
happen at the clip start position.

PiperOrigin-RevId: 713686333
2025-01-09 08:47:33 -08:00
kimvde
2df50f209e CompositionPlayerTest: increase timeout for emulator tests
Some tests are sometimes timing out because the emulator is too slow.

PiperOrigin-RevId: 713671851
2025-01-09 07:56:55 -08:00
dancho
cd511ea60b Add a test file for FragmentedMp4 captions
The previous FragmentedMp4 captions test asset doesn't have captions.
Fix a bug where captions before extractor seek were output after.

PiperOrigin-RevId: 713665817
2025-01-09 07:35:21 -08:00
tonihei
b9d12837b4 Use live providers in CompositionPlayer to remove workaround
When CompositionPlayer added repeat modes in d0afb96c40, it
changed the logic in SimpleBasePlayer to workaround the specific
scenario in CompositionPlayer that didn't seem to work correctly.
The workaround is not really generically applicable though. The
source of the original problem was that the position values of
all state objects were connected to the "live" source of the
current player so that the repetition case couldn't be detected
(the position before and after the seek looked the same). The
solution is to use the newly added LivePositionSuppliers and
disconnect them before seeking.

PiperOrigin-RevId: 713659638
2025-01-09 07:10:14 -08:00
tonihei
9608ae4e3d Add LivePositionSupplier util to SimpleBasePlayer
In some cases the position is supplied from a "live" source
that keeps changing its value at repeated calls. This typically
happens when forwarding to another backend with unpredictable
position values. The problem is that as soon as the backend
system has a position discontinuity, any previously created
PositionSupplier now returns a position that no longer makes
sense in the context of the State object it belongs to.

ForwardingSimpleBasePlayer already works around this issue by
having a util class to disconnect these live sources. This
change moves the same util to SimpleBasePlayer itself so it
can be reused by other implementations.

PiperOrigin-RevId: 713658046
2025-01-09 07:05:21 -08:00
Googler
8a709a7d76 Remove setPendingVideoEffects from VideoSink.
VideoSink#onInputStreamChanged(int, Format, List<Effect>) should now be used to set video effects on a new input stream.

PiperOrigin-RevId: 713627389
2025-01-09 04:48:21 -08:00
andrewlewis
e1b57c130d Make auto frame registration test non-flaky
Wait for each frame to be rendered before unblocking queueing the next one.

PiperOrigin-RevId: 713621811
2025-01-09 04:21:39 -08:00
michaelkatz
0e6d39de29 Add DefaultRenderersFactory api for experimental MCVR prewarming
If `experimentalSetEnableMediaCodecVideoRendererPrewarming` is set with `true`, `DefaultRenderersFactory` will provide `ExoPlayer` with a secondary `MediaCodecVideoRenderer` for use in pre-warming through its `createSecondaryRenderer` and `buildSecondaryVideoRenderer` implementations. `ExoPlayer` will then pre-process video of subsequent media items to reduce media item transition latency.

PiperOrigin-RevId: 713605911
2025-01-09 03:08:41 -08:00
kimvde
04ff17d939 CompositionPlayerSeekTest: skip some tests on API 31 emulator
The audio decoder is sometimes failing with error "previous call to
queue exceeded timeout - Codec reported err 0x80000000, actionCode 0,
while in state 6/STARTED". This is probably because the emulator is too
slow.

PiperOrigin-RevId: 713564117
2025-01-09 00:24:31 -08:00
jbibik
709d3fd35e [demo-compose] Refactor modifier with better Kotlin syntax
PiperOrigin-RevId: 713476190
2025-01-09 00:22:17 -08:00
michaelkatz
314413365b Implement transfer of resources API for renderer pre-warming
At the point of playing period transition pre-warming has completed and the renderers should receive necessary resources for playback. This CL adds the `Renderer.MessageType` `MSG_TRANSFER_RESOURCES` to direct a renderer to transfer relevant resources to another renderer.

PiperOrigin-RevId: 713372754
2025-01-08 12:12:48 -08:00
tonihei
7d4ddfbb91 Don't force discontinuities for server-side ad insertion transitions
We currently force discontinuities for all clipped content periods
that don't start from zero. This shouldn't be done for server-side
ad insertion streams where we are guaranteed to get a continuous
underlying stream.

To enable the right decision, we need to add a flag to
MediaPeriodInfo for isPrecededByTransitionFromSameStream, which
mirrors the existing isFollowedByTransitionToSameStream.

The problem is currently not visible due to a bug in
ClippingMediaPeriod that automatically ignores most discontinuities
that don't match the start of the clip.

PiperOrigin-RevId: 713315398
2025-01-08 09:23:13 -08:00
kimvde
d9776e74c8 PlayerListener: do not swallow exceptions thrown before waiting
If an error occurred in the player before the test was calling one of
the waitUntilSomething() methods, a timeout exception was thrown instead
of the actual error.

PiperOrigin-RevId: 713292292
2025-01-08 08:03:46 -08:00
tonihei
b321c8d3bd Discard already written sample data for clipped DASH periods
DASH periods can have a duration that is less than the end of the
last chunk in the period. In these cases, the sample data needs to
be clipped to the declared period duration. This already happens
IF the period duration is known at the point where we start loading
the media chunk. However, if the duration becomes known later or is
reduced (e.g. in a live stream), the existing media chunks are not
clipped. This causes unclean transitions across periods where the
player tries to transition to the next period, but renderers struggle
to output all the remaining surplus samples that should have been
clipped.

This can be fixed by asking ChunkSampleStream to discard surplus
samples that were loaded beyond a clipped duration when evaluating
the sample queue between chunk loads.

Issue: androidx/media#1698
PiperOrigin-RevId: 713288221
2025-01-08 07:47:54 -08:00
kimvde
bb3b85a359 CompositionPlayerSeekTest: don't hide exception while waiting on latch
If a playback exception was thrown before or during awaiting the
frameCountBeforeBlockLatch, a TimeoutException was thrown instead of the
playback exception, hiding the actual cause of the failing test.

PiperOrigin-RevId: 713279627
2025-01-08 07:10:09 -08:00
kimvde
30038079c4 Split CompositionPlayerSeekTest.playSequenceAndGetTimestampsUs
This method is already quite complex and I will need to add more
complexity, so split it into multiple smaller methods.

This CL is a refactoring. There are no functional changes.

PiperOrigin-RevId: 713266816
2025-01-08 06:18:48 -08:00
jbibik
79d41aac7e [ui-compose] Fix PlayerSurface's use of Player
The new `currentPlayer` was only used to check the available command. It should also be used for setting and clearing the Surface.

The PlayerSurface was still behaving correctly, perhaps due to the lambda being reevaluated with the new player (since `currentPlayer` is of type MutableState). Which meant it was also using the latest `player` that `PlayerSurface` was recomposed with (in the argument), rather than holding onto the original object.

PiperOrigin-RevId: 713264041
2025-01-08 06:07:14 -08:00
kimvde
48e3eaa623 CompositionPlayerSeekTest: increase test timeout for emulators
Some tests are sometimes timing out because the emulator is too slow.

PiperOrigin-RevId: 713259939
2025-01-08 05:48:18 -08:00
kimvde
3a00960831 Remove stale Javadoc
PiperOrigin-RevId: 713253227
2025-01-08 05:16:01 -08:00
dancho
0cf52ed45d Read sample dependencies for H.265 in MP4
Add a new Mp4Extractor.FLAG_READ_WITHIN_GOP_SAMPLE_DEPENDENCIES_H265
Read two bytes from H.265 videos to determine NAL unit type and
temporal layer id.

PiperOrigin-RevId: 713248154
2025-01-08 04:55:31 -08:00
dancho
b54d8737cf Add number of temporal layers to Format
The number of temporal sub-layers is required for
H.265 non-reference frame identification as
only frames from the highest temporal sub-layer can be
discarded.

PiperOrigin-RevId: 713247354
2025-01-08 04:50:45 -08:00
dancho
281a0e7ac8 Populate HevcConfig with number of temporal layers
The number of temporal sub-layers is required for
H.265 non-reference frame identification as
only frames from the highest temporal sub-layer can be
discarded.

PiperOrigin-RevId: 713242894
2025-01-08 04:31:24 -08:00
dancho
b3e18729d3 H.265 parse number of temporal layers from SPS
Expose NalUnitUtil.H265SpsData.maxSubLayersMinus1
Required for H.265 non-reference frame identification as
only frames from the highest temporal sub-layer can be
discarded.

PiperOrigin-RevId: 713232825
2025-01-08 03:48:24 -08:00
shahddaghash
1bea11637b Add setting final progress at export error or cancellation
The change includes setting the final progress percentage when the export completes with error or is cancelled. The value for the progress is collected by calling `Transformer.getProgress()` before `transformerInternal` is set to null.

PiperOrigin-RevId: 713227568
2025-01-08 03:24:18 -08:00
ybai001
d16d80ca49 Update Dolby Vision format handling
* Add the missing DV profile 10 handling
* Set DV codec string instead of compatible format codec string in MP4 extractor
2025-01-08 10:40:03 +08:00
ybai001
39c35b779b
Merge pull request #16 from androidx/main
Merge from androidx/media main branch
2025-01-08 09:52:29 +08:00
shahddaghash
d894217a4e Fix typo for precreateEditingEndedEventBuilder method
PiperOrigin-RevId: 712969879
2025-01-07 10:56:15 -08:00
sheenachhabra
bcae7abde9 Remove TrackToken and replace with int trackId
This is to make API simpler and to have parity with `MediaMuxer`

PiperOrigin-RevId: 712954669
2025-01-07 10:16:31 -08:00
shahddaghash
8dca430e42 Add setting final progress when export success for metrics
Set the progress to 100% for a successful export operation.

PiperOrigin-RevId: 712946218
2025-01-07 09:52:43 -08:00
shahddaghash
6f187a3859 Add confetti overlay to Effect demo
Added the first overlay effect to the Effect demo. It includes an emitter of confetti that drops from the center of the frame.

PiperOrigin-RevId: 712940163
2025-01-07 09:33:36 -08:00
dancho
229aadc91b Update javadoc for FLAG_READ_WITHIN_GOP_SAMPLE_DEPENDENCIES
Reflects that FLAG_READ_WITHIN_GOP_SAMPLE_DEPENDENCIES only parses
H.264 bitstream, and that H.265 parsing will be controlled with
another flag.

PiperOrigin-RevId: 712921990
2025-01-07 08:33:14 -08:00
bachinger
ec50358784 Do not override pending info when masking
PiperOrigin-RevId: 712919739
2025-01-07 08:26:19 -08:00
andrewlewis
e11aabb794 Allow RawAssetLoader media to have unset duration
This will be useful for recording use cases, including screen recording with
audio queued via `RawAssetLoader`, where the duration is unknown.

PiperOrigin-RevId: 712895577
2025-01-07 06:59:21 -08:00
ivanbuper
871381288c Use static methods for getSpeedAdjustedTimeAsync and getMediaDurationUs
This change simplifies SpeedChangingAudioProcessor by removing unneeded
heuristics and synchronization added as workarounds to estimate input
and output frame counts.

The synchronization between the video processing and audio processing
threads cannot be completely removed yet because the static methods
depend on the input sample rate for the calculations. However, once
`SpeedChangingAudioProcessor` has been configured, then
`#getSpeedAdjustedTimeAsync()` should invoke the callback immediately.

PiperOrigin-RevId: 712893246
2025-01-07 06:49:49 -08:00
jbibik
2dc6af1fae [ui-compose] Add PresentationState for first-frame info
It captures some information needed for the UI logic related to rendering of the video track (and later images) to the surface.

Supporting `EVENT_RENDERED_FIRST_FRAME` helps improve the UX by covering the surface with an overlay (scrim/shutter) until the first frame is ready. This helps avoid sudden flickering during MediaItem transitions.

PiperOrigin-RevId: 712889568
2025-01-07 06:34:02 -08:00
ibaker
31e5142b72 Restrict SubRip and WebVTT parsing to exactly 3 decimal places
We previously parsed an arbitrary number of decimal places, but assumed
the value was in milliseconds, which doesn't make sense if there is
greater or fewer than 3. This change restricts the parsing to match
exactly 3, meaning the millisecond assumption is always true.

The WebVTT spec requires there to be exactly 3 decimal places:
https://www.w3.org/TR/webvtt1/#webvtt-timestamp

The SubRip spec is less clearly defined, but the Wikipedia article
defines it as having exactly 3 decimal places
(https://en.wikipedia.org/wiki/SubRip#Format) and ExoPlayer has always
assumed 3 decimal places (anything else is already handled incorrectly),
so this change just ensures we don't show subtitles at the wrong time.

Issue: androidx/media#1997
PiperOrigin-RevId: 712885023
2025-01-07 06:13:15 -08:00
andrewlewis
9ac91aaadd Return concrete SurfaceAssetLoader from factory
If the caller is another asset loader (or another asset loader factory) that is
delegating to a `SurfaceAssetLoader`, it may want to call methods on the
`SurfaceAssetLoader`. Return the concrete type so that a cast isn't necessary
at the call site.

PiperOrigin-RevId: 712881130
2025-01-07 06:02:24 -08:00
Copybara-Service
38363acc8d Merge pull request #1785 from DolbyLaboratories:dlb/dovi-supplemental-codecs/dev
PiperOrigin-RevId: 712867412
2025-01-07 04:58:21 -08:00
tonihei
a9f9928eb3 Temporarily remove test class to avoid build breakage
PiperOrigin-RevId: 712862960
2025-01-07 04:40:28 -08:00
andrewlewis
5a87aab2f7 Use Util to request permissions in Transformer demo
This avoids requesting storage permissions for URLs that don't require it.

Also tidy clean-up into `cleanUpExport`, rename `releasePlayer` to
`releasePlayers` as there are players for both input and output, and reduce
live range of `uri` variable.

PiperOrigin-RevId: 712857115
2025-01-07 04:13:36 -08:00
shahddaghash
0074a97333 Remove unnecessary released condition in getProgress
Removed the check for `released` flag to be able to pass the actual progress when the export completes.

Also, removed resetting the `progressState` and `progressValue` to be able to get the actual progress. This block is also not necessary to have at this point.

PiperOrigin-RevId: 712851526
2025-01-07 03:53:06 -08:00
andrewlewis
0044d0cebc Fix GLES31 javadoc reference to be unambiguous and link correctly
PiperOrigin-RevId: 712848161
2025-01-07 03:35:30 -08:00
Googler
b2aa8d6d21 Add sample rate fallback to AudioSampleExporter
After this change, if the sample rate supported by the encoder differs from the requested sample rate and enableFallback is true, the AudioSampleExporter will convert audio to a sample rate supported by the encoder. This fixes a bug where the audio track is distorted when an unsupported sample rate is requested.

PiperOrigin-RevId: 712822358
2025-01-07 01:52:57 -08:00
Googler
6c2d25184c Make FragmentedMp4Muxer use OutputStream instead of FileOutputStream.
This lets us provide a append-only output stream with overridden write() methods - unlocking use cases where we process the muxed data in a streaming fashion, as it's generated by the fragmented muxer.

PiperOrigin-RevId: 712581859
2025-01-06 10:46:00 -08:00
jbibik
575a2ebbd8 [ui-compose] Add gradle version to androidx.compose.foundation
This will ensure that the new `ui-compose` module gets published on Maven

The fix is not necessary for `demo-effect` and `demo-compose` because they are not publishable

PiperOrigin-RevId: 712555702
2025-01-06 09:34:44 -08:00
Copybara-Service
8fe0b7ac69 Merge pull request #1987 from ojoNetgem:Support_for_origin_and_extent_declared_in_ttml_styles
PiperOrigin-RevId: 712554507
2025-01-06 09:30:15 -08:00
jbibik
ff80ab220f [demo-compose] Add Activity lifecycle methods
Take care of releasing the player and re-initialising when returning to the app to prevent memory leaks.

Instead of overriding onStart, onPause, onResume, onStop - use Lifecycle*Effects for a more native compose cleanup. Requires a higher version of the lifecycle library.

PiperOrigin-RevId: 712542884
2025-01-06 08:49:41 -08:00
Ian Baker
8d9cb7e5d0 Resolve some TODO comments that are now fixed by the new parsing support 2025-01-06 15:13:32 +00:00
Ian Baker
618c2cf952 Add a release note and format with google-java-format 2025-01-06 15:13:32 +00:00
Olivier Jouvenaux
82bed39140 Add unit tests for TTML region attrs in style, chained styles UT 2025-01-06 15:13:32 +00:00
Olivier Jouvenaux
026700a9d9 Add unit tests for region attrs in style 2025-01-06 15:13:32 +00:00
Olivier Jouvenaux
fbd6dfa439 Add support for TTML origin and extent attributes defined in style instead of region 2025-01-06 15:13:32 +00:00
jbibik
d8333b37cf Upgrade compose bom to 2024.12.01
The latest version of `androidx.compose.foundation:foundation` fixed an issue where `AndroidEmbeddedExternalSurface` would not reset properly. This in turn prevented PlayerSurface Composable with type `SURFACE_TYPE_TEXTURE_VIEW` from being redrawn after returning to a stopped activity.

PiperOrigin-RevId: 712501647
2025-01-06 06:05:30 -08:00
jbibik
d01d10ce0e [demo-compose] Refactor MediaPlayerScreen() into ComposeDemoApp()
PiperOrigin-RevId: 712495147
2025-01-06 05:33:32 -08:00
Copybara-Service
871c15946b Merge pull request #1947 from totallyunknown:fix/dash-playlist-infinite-loop
PiperOrigin-RevId: 712494399
2025-01-06 05:28:26 -08:00
shahddaghash
115f09dd6d Report ErrorCode and elapsed time metrics for Transformer
Added reporting 2 values from Transformer:
- elapsed time since creation of the `editingSession` in milliseconds.
- the `ErrorCode` for export errors. The error codes were mapped from `@ExportException.ErrorCode` int to `@EditingEndedEvent.ErrorCode`.

PiperOrigin-RevId: 712472849
2025-01-06 03:55:32 -08:00
sheenachhabra
750baf2c05 Rollback of 9cc4532f84
PiperOrigin-RevId: 711804077
2025-01-03 11:18:03 -08:00
ivanbuper
7ecaebe3d6 Fix underflow in Sonic#getOutputSize() after #queueEndOfStream()
For the [0.5; 1) speed range, the combination of having a "slow down"
speed (i.e. more output frames than input frames), and Sonic potentially
needing to copy more input frames that are available in the input buffer
can lead to an unexpected underflow.

Specifically, the underflow happens in Sonic#queueEndOfStream() when the
following conditions are met (skipping some minor ones):

1. `inputFrameCount < remainingInputToCopyFrameCount`
2. `0.5f <= speed < 1`.
3. `outputFrameCount <
    (inputFrameCount / remainingInputToCopyFrameCount) / 2`.

This underflow caused `SonicAudioProcessor#isEnded()` to return a false
negative (because `getOutputSize() != 0`), which would stall the
`DefaultAudioSink` waiting for processing to end after EOS.

In practical terms, the underflow is relatively easy to reproduce if we
consume all of Sonic's output and then immediately queue EOS without
queueing any more input in between. This should cause both
`inputFrameCount` and `outputFrameCount` to drop to 0.

PiperOrigin-RevId: 711773565
2025-01-03 09:26:32 -08:00
shahddaghash
cd5d5bde27 Implement basic Metrics Collector in Transformer
Added a new `EditingMediaMetrics` class that interacts with the platform's `MediaMetricsManager` through an `EditingSession` created when export starts.

Currently, only the `finalState` of the export event is reported to the `EditingSession`. Future changes will collect additional metrics based on the export operation's output.

PiperOrigin-RevId: 711721801
2025-01-03 05:27:20 -08:00
dancho
9b71f2a3ba Mp4Extractor: allow zero length NAL units
Some videos include zero length NAL units in the
length-delimited MP4 samples.
Empty NAL units are not spec-compliant (see ISO/IEC 14496-15 section 4.3.3.3),
but other players are able to play these videos (with warnings or errors).

With this change, we check track.sampleTable.sizes[sampleIndex] before
reading a byte from the NAL unit itself.

PiperOrigin-RevId: 711720621
2025-01-03 05:21:10 -08:00
ivanbuper
682889f91d Implement Sonic method to get input frame count from output frame count
The static method can estimate the number of input frames needed to get
a given number of output frames with a given Sonic configuration.

This CL is prework to remove the dependency of
`SpeedChangingAudioProcessor#getMediaDurationUs()` on non-static output
based heuristics and simplify `SpeedChangingAudioProcessor`.

PiperOrigin-RevId: 711446999
2025-01-02 09:36:12 -08:00
sheenachhabra
01c51d8475 Allow dolby vision transcode test to use alternative output mime type
On many devices (for example pixel 7) a dolby vision file can be
decoded (either using dolby vision decoder or H265 decoder)
but they can't be re-encoded to dolby vision.
Allow test to encode to H265.

The test now passes on pixel 7. It was getting skipped earlier.

PiperOrigin-RevId: 711383794
2025-01-02 04:15:27 -08:00
sheenachhabra
9cc4532f84 Use same decoder selection logic is test as in DefaultDecoderFactory
The decoder selection logic in DefaultDecoderFactory is more mature
and can find alternative decoders.

PiperOrigin-RevId: 711378121
2025-01-02 03:49:52 -08:00
sheenachhabra
969c50d60f Fix HDR editing support check in dolby vision test
A device (for example pixel 7) might support HDR encoding
but with H265 sample mime type but the existing condition made
assumption false on such devices.

The test still does not pass on pixel 7 because the format
matching condition is incorrect. It will be fixed in a separate CL.

PiperOrigin-RevId: 711368798
2025-01-02 03:00:08 -08:00
Googler
2caa0d39eb Mp4Extractor: Update APV clip
Replaced an existing APV file as bitstream syntax has now changed and
previous clip is not a valid bitstream anymore.

This clip was provided by the openAPV team and is under BSD-3 license.

PiperOrigin-RevId: 711318578
2025-01-01 22:34:31 -08:00
ivanbuper
48e3c6fd75 Rollback of f60d2b4146
PiperOrigin-RevId: 710998501
2024-12-31 10:26:16 -08:00
ivanbuper
d6e4642bcf Rollback of 5ab9a7856f
PiperOrigin-RevId: 710770581
2024-12-30 12:24:05 -08:00
jbibik
afd601f670 Refactor non-ripple Modifier into a separate file
Modifiers.kt will be a place for other extensions on Modifier class

PiperOrigin-RevId: 709416429
2024-12-24 15:27:21 -08:00
jbibik
6da58758a8 Add vertical videos to demo-compose
Useful for future testing of aspect ratio functionality.

PiperOrigin-RevId: 709354104
2024-12-24 08:47:42 -08:00
rohks
3b9737f88a Fix release notes based on Devsite linter warnings
PiperOrigin-RevId: 709329498
2024-12-24 06:17:43 -08:00
sheenachhabra
16617f5a8b Improve javadoc in Mp4LocationData and Mp4OrientationData class
PiperOrigin-RevId: 709061777
2024-12-23 07:57:58 -08:00
claincly
515de89973 Support MediaCodec tonemapping in CompositionPlayer
PiperOrigin-RevId: 708995517
2024-12-23 02:06:08 -08:00
sheenachhabra
f22d91d9e5 Clarify the stream closing responsibility in muxer
PiperOrigin-RevId: 708359779
2024-12-20 10:24:48 -08:00
claincly
8188f7a865 Repect Composition's HDR mode setting using OpenGL
Subsequent CLs will add support for other HDR modes

PiperOrigin-RevId: 708340216
2024-12-20 09:11:00 -08:00
rohks
5c40498054 Fix quotes around parameter name in release notes
PiperOrigin-RevId: 708334464
2024-12-20 08:47:17 -08:00
sheenachhabra
1b0e2fb75e Rename "setSampleCopyEnabled" to "setSampleCopyingEnabled" in muxer
PiperOrigin-RevId: 708322440
2024-12-20 07:57:59 -08:00
sheenachhabra
684c394019 Fix java doc of setSampleBatchingEnabled method
PiperOrigin-RevId: 708310532
2024-12-20 06:58:54 -08:00
Googler
f06bfc2e15 Log errors in BaseGlShaderProgram and QueueingGlShaderProgram.
PiperOrigin-RevId: 708297631
2024-12-20 05:59:18 -08:00
Googler
c12b1768a6 Add sample rate fallback to DefaultEncoderFactory
After this change if a sample rate is requested that is not supported by the available encoders and enableFallback is true, DefaultEncoderFactory will fall back to the encoder with the closest matching sample rate.

In the case when an encoding profile is included in requestedAudioEncoderSettings, an encoder matching that profile will continue to be preferenced.

PiperOrigin-RevId: 708295869
2024-12-20 05:50:25 -08:00
rohks
5c3c3b91f3 Remove publishing for media3-ui-compose temporarily
This is due to a missing version error for the `androidx.compose.foundation` dependency.

PiperOrigin-RevId: 707944220
2024-12-19 09:54:59 -08:00
shahddaghash
915130eb00 Add usePlatformDiagnostics in Transformer.
Added a new `usePlatformDiagnostics` in Transformer. This parameter enables/disables forwarding editing events and performance data to the platform.

This is pre-work for metrics support in Transformer. In the following CLs, metrics collection and forwarding will be implemented.

PiperOrigin-RevId: 707930368
2024-12-19 09:03:19 -08:00
tonihei
cb578c0f37 Additional comments and limit update interval to manifest value 2024-12-19 16:20:44 +00:00
Nils Juenemann
5da67a0033 fix: prevent infinite loop causing stream freeze in DASH multi-period streams
- Added a condition to ensure `requiredIntervalUs > 0` in `DashMediaSource`.
- Resolves an issue where negative `requiredIntervalUs` triggered an infinite loop in `onPlaylistUpdateRequested()`, leading to application/stream freezes.

This fix restores smooth playback for multi-period DASH streams.
2024-12-19 16:18:55 +00:00
ibaker
59789b7209 Fix typo in 1.6.0-alpha01 release notes
#cherrypick

PiperOrigin-RevId: 707907431
2024-12-19 07:31:52 -08:00
rohks
506bc91dd8 Bump Media3 version to 1.6.0-alpha01
PiperOrigin-RevId: 707896534
2024-12-19 06:45:57 -08:00
rohks
9d610894cf Update release notes for Media3 1.6.0-alpha01 release
PiperOrigin-RevId: 707880171
2024-12-19 05:30:04 -08:00
ybai001
1ac847ad60 Update code according to review result 2024-12-19 16:28:33 +08:00
rohks
ca58867816 Hard-code channel count and sample rate for AMR-NB and AMR-WB
AMR-NB and AMR-WB are inherently mono, so channel count is set to 1. Sample rate is also hard-coded to adhere to codec standards.

Also removed unused parameter `hasAdditionalViews` in `StriData`.

#cherrypick

PiperOrigin-RevId: 707606245
2024-12-18 11:03:26 -08:00
sheenachhabra
44aa87c832 Extract MuxerException into a separate file
This is to avoid very long name as the FQN earlier
contained muxer 3 times

PiperOrigin-RevId: 707591813
2024-12-18 10:21:51 -08:00
shahddaghash
c3b58f2434 Bump Media3 version to 1.5.1
PiperOrigin-RevId: 707576152
2024-12-18 09:29:43 -08:00
shahddaghash
896bd0d330 Update release notes for Media3 1.5.1 release
PiperOrigin-RevId: 707558817
2024-12-18 08:25:16 -08:00
Copybara-Service
2f507650cc Merge pull request #1871 from colinkho:cmc
PiperOrigin-RevId: 707528211
2024-12-18 06:32:41 -08:00
Googler
2d37823695 Add VP9 Mp4 clip to Mp4MuxerEndToEndParameterizedAndroidTest
The dump file for VP9 mp4 clips varied across SDK versions due to inconsistent CSDs from the platform extractor. By replacing the platform extractor with `MediaExtractorCompat`, the Media3 extractor will provide consistent CSDs across all SDK versions.

PiperOrigin-RevId: 707509473
2024-12-18 05:16:55 -08:00
tonihei
1c044c98ad Formatting fixes 2024-12-18 12:58:32 +00:00
siroberts
16dcf91c1c Reduce log severity to info for unhandled media buttons
PiperOrigin-RevId: 707485315
2024-12-18 03:47:58 -08:00
Googler
6d2331fd13 Add ApvC codec support to Mp4Muxer.
PiperOrigin-RevId: 707482517
2024-12-18 03:38:20 -08:00
Googler
9b628d4542 Update Muxer Test with MediaExtractorCompact
Replace Platform extractor with MediaExtractorCompact in AndroidMuxerTestUtils.

PiperOrigin-RevId: 707469755
2024-12-18 02:50:11 -08:00
bachinger
d4f4a2c1d4 Allow the number of ad groups to grow with AdsMediaSource
This enables `AdsMediaSource` to be used with a live media
source that has a growing `AdPlaybackState` to which ad groups
can be appended.

Before this change, `AdsMediaSource` asserted that the number
of ad groups was kept the same, else an exception was thrown.
After this change, the assertion checks the validity of the
update and throws in case the update isn't considered valid.

An update is valid if ad groups are appended to the existing
`AdPlaybackState` or ads are appended to existing ad groups.
Further the `adGroupIndex` and `timeUs`of an existing ad
group can not be changed and once a media item is set for a
given ad, that media item can't be changed either.

PiperOrigin-RevId: 707244455
2024-12-17 14:10:27 -08:00
bachinger
aa2ee8f702 Add client side post roll placeholder
PiperOrigin-RevId: 707142019
2024-12-17 09:59:16 -08:00
ibaker
32ab258c43 Use Build.MANUFACTURER instead of Util alias in AudioCapabilities
It seems that changes via Robolectric's `ShadowBuild.setManufacturer()`
and similar methods don't propagate correctly (or quickly?) to these
aliases.

This change resolves a failure caused by different test ordering in
`AudioCapabilitiesTest` in order to unblock the 1.6.0-alpha01 release.
A follow-up change will migrate other usages from `Util.XXX` to
`Build.XXX`.

PiperOrigin-RevId: 707125446
2024-12-17 09:10:40 -08:00
bachinger
3dede2415d Add AdsMediaSourceFactory for HLS interstitials
PiperOrigin-RevId: 707109323
2024-12-17 08:25:58 -08:00
shahddaghash
acc41cb5f7 Add contrast effect to effect demo
Added a contrast effect and the connection needed to apply the video effects to ExoPlayer.

The effect can be applied to the video by checking the "Contrast" card, and use the slider to change the contrast value. The effects are applied when `Apply effects` button is clicked.

PiperOrigin-RevId: 707092041
2024-12-17 07:33:52 -08:00
Colin Kho
6287a22285 Added getter methods to ContainerMediaChunk & a callback method whenever a load loop is finished 2024-12-17 15:29:59 +00:00
tonihei
2d11a339de Import AudioManagerCompat and AudioFocusRequest from androidx.media
Both classes provide utilities widely used by apps that are not
yet available in Media3. This change imports the existing logic as
it is with style adjustments to the Media3 codebase.

PiperOrigin-RevId: 707067512
2024-12-17 06:14:36 -08:00
rohks
4b26eb2800 Fix lint errors
- Add required mutability flag to `ConnectionStateTest` to fix
  `Missing PendingIntent mutability flag`.
- Suppress `WrongConstant` lint error caused by Kotlin's spread operator
  not propagating the `@IntDef` annotation.

#cherrypick

PiperOrigin-RevId: 707061163
2024-12-17 05:53:19 -08:00
ibaker
601b025b3c Clarify the base of releaseTimeNs in VideoFrameMetadataListener
Also remove docs about API < 21, since the `minSdk` for the library is
now 21.

PiperOrigin-RevId: 707038503
2024-12-17 04:20:44 -08:00
tonihei
71f82df57f Switch default of async crypto mode to disabled
There are reproducible issues with codec timeouts when using
this API, so we disable it entirely until we know more about
potential fixes and where they are available.

Issue: androidx/media#1641
#cherrypick
PiperOrigin-RevId: 707025950
2024-12-17 03:25:32 -08:00
bachinger
12566a50d5 Reorder IMA samples for easier QA testing
PiperOrigin-RevId: 707014618
2024-12-17 02:33:21 -08:00
bachinger
3fe1f2a734 Add adId to AdGroup
PiperOrigin-RevId: 706761644
2024-12-16 10:47:17 -08:00
michaelkatz
d5d85558c1 Skip just-early video frames only if directed to release the frame
`MediaCodecVideoRenderer` will skip frames if a surface has not been set and video frame presentation time is early but too close to the current playback position. In the case that the `VideoFrameReleaseControl` says to `FRAME_RELEASE_TRY_AGAIN_LATER`, these frames should not be skipped.

PiperOrigin-RevId: 706711734
2024-12-16 08:05:30 -08:00
sheenachhabra
319ac2e5af Remove Parcelable interface from Metadata and Metadata.Entry
`Parcelable` is not safe for IPCs between binaries with potentially
different class definitions (e.g. two apps built from different versions
of media3).

If we get a use case to bundle metadata, then the `Metadata` and
`Metadata.Entry` classes needs to provide a `toBundle()` method.

PiperOrigin-RevId: 706678892
2024-12-16 05:58:02 -08:00
sheenachhabra
1326a92350 Do not bundle metadata when creating Format bundle
Metadata does not provide a `toBundle` method. It implements
`Parcelable` which is not safe for IPCs between binaries with
potentially different class definitions (e.g. two apps built from
different versions of media3).

If we get a use case to bundle metadata as well, then the `Metadata`
class needs to provide a `toBundle()` method.

PiperOrigin-RevId: 705920231
2024-12-13 10:12:37 -08:00
ibaker
36466cf883 Add some session methods to the stable API
This change adds a new stable overload of
`SessionToken.createSessionToken` which takes the platform `Token` type
(instead of `Parcelable`). It also stabilises
`MediaController.getCustomLayout` & the corresponding listener.

PiperOrigin-RevId: 705917161
2024-12-13 10:03:46 -08:00
dancho
72672b7f87 Implement audioNeedsEncoding based on requested settings
Do not use ForceEncodeFactory in tests

PiperOrigin-RevId: 705835243
2024-12-13 04:23:35 -08:00
bachinger
07b658a6da Ignore invalid interstitials instead of throwing
The HLS spec says so in rfc8216bis (4.4.5.1)

PiperOrigin-RevId: 705829265
2024-12-13 03:58:55 -08:00
dancho
522883bf16 Less granular skipping for HE-AAC support
PiperOrigin-RevId: 705822449
2024-12-13 03:28:51 -08:00
tonihei
3bce3af1a3 Switch play FGS exemption to use custom action instead of commands
Custom actions are more naturally associated with a user intent
than commands (that are meant to be used for automated inter-app
communication without user interaction).

PiperOrigin-RevId: 705797057
2024-12-13 01:43:21 -08:00
bachinger
83b5eb0040 Add HlsInterstitialsAdsLoader
Reads HLS interstitials information from the playlist and
populates the `AdPlaybackState` accordingly to play the ads.

An app can register a `Listener` to be informed about ad
related events.

Only VOD streams are supported and X-ASSET-LIST attibutes
are ignored with this change.

PiperOrigin-RevId: 705604201
2024-12-12 13:04:10 -08:00
jbibik
e0496ff88d [demo-compose] Initialize the Player outside of Compose
PiperOrigin-RevId: 705533932
2024-12-12 09:39:50 -08:00
jbibik
beda44520a [ui-compose] Fix PlayerSurface's use of Player
When `PlayerSurface` composable gets a new `Player` object, it should initialise a new Android(Embedded)External Surface with it. However, the lambdas used for that are remembered with the reference to the old Player, even if it has been null'd and released properly.

Android(Embedded)ExternalSurface is an interop - `AndroidView` wrapping `SurfaceView` and `TextureView` under the hood. It uses the `onInit` lambda in its factory to create the View and reuses it on later recompositions, making it a longer-lived object than our Player.

`RememberUpdatedState` acts makes a mutable State out of the Player and always gets its latest value. One can think of it as creating a reference to the Player object and telling the lambdas to always resolve that reference in the moment, rather than hold onto the Player than was passed-by-value.

This change is a precursor to a better lifecycle management of Player and Surface in demo-compose.

PiperOrigin-RevId: 705529626
2024-12-12 09:22:40 -08:00
tonihei
684273e4e1 Add utilities to resolve button preferences to display constraints
PiperOrigin-RevId: 705491402
2024-12-12 07:08:43 -08:00
kimvde
c636d9181b Delete ExoPlayerEffectPlaybackSeekTest
This test was testing a scenario that never happens in production.
Indeed, in this test, a frame was registered to the
ExternalTextureManager but never rendered on the decoder ouput surface.
This never happens in production because both actions are normally
performed in
PlaybackVideoGraphWrapper.InputVideoSink.handleInputFrame().

This test was also failing on API 31 emulator. As the frame was never
rendered, the timeout to release all registered frames in
ExternalTextureManager was always exceeded. This sometimes caused the
decoder to fail on this emulator because there was no interaction with
the decoder for a long time.

PiperOrigin-RevId: 705490910
2024-12-12 07:05:34 -08:00
jbibik
77a215f39c [ui-compose] Check Player commands in PlayerSurface actions
Setting and destroying the Surface should only happen if the `Player` has the required command

PiperOrigin-RevId: 705488781
2024-12-12 06:57:47 -08:00
rohks
4997ea82fa Refactor CmcdData to handle object type determination internally
Moved the `getObjectType` method from `CmcdData.Factory` to `CmcdData` and updated the logic to derive the object type directly within `CmcdData`. This change eliminates the need for chunk source classes to set this value explicitly.

PiperOrigin-RevId: 705457755
2024-12-12 04:36:16 -08:00
ibaker
0b0c198f59 Publish some internal tests now we depend on Robolectric 4.14.1
PiperOrigin-RevId: 705411400
2024-12-12 01:28:26 -08:00
sheenachhabra
f587ac2a67 Remove bundle-serialization methods from ExoPlaybackException
The `toBundle()` method is implemented incorrectly as it uses
`parcelable` implementation to bundle `Format.metadata`.
The `parcelable` is not compatible and is likely to cause crashes if
a new field is added or removed.

Moreover, the `fromBundle` method is likely unused as in session
module the exception is [unbundled](df887a9422/libraries/session/src/main/java/androidx/media3/session/PlayerInfo.java (L1021)) as `PlaybackException`.

This is a non breaking change as all the calls to `toBundle()` and
`fromBundle` will now go to the parent class `PlaybackException`.

If this specific bundling is required in future then `Format.Metadata`
and all the `Metadata.Entry` classes need to provide `toBundle()`
and `fromBundle()` method.

PiperOrigin-RevId: 705167002
2024-12-11 11:06:01 -08:00
Googler
3936c27b6d Don't check codec's profile for MV-HEVC video.
Currently as there is no formal support for MV-HEVC within Android framework, the profile is not correctly specified by the underlying codec; just assume the profile obtained from the MV-HEVC sample is supported.

PiperOrigin-RevId: 705164738
2024-12-11 10:59:26 -08:00
rohks
de31a3745c Make some no-op refactoring in CmcdData
- Moved static fields for object type, stream type, etc. from `CmcdData.Factory` to `CmcdData`.
- Removed redundant `CmcdData` prefix from `@ObjectType` and `@StreamingFormat` annotations.

#cleanup

PiperOrigin-RevId: 705159876
2024-12-11 10:46:22 -08:00
Copybara-Service
893b3775d4 Merge pull request #1963 from khouzam:codecInitialize
PiperOrigin-RevId: 705123773
2024-12-11 08:56:52 -08:00
Copybara-Service
c222bb8e03 Merge pull request #1968 from colinkho:opus
PiperOrigin-RevId: 705106170
2024-12-11 07:55:46 -08:00
andrewlewis
da05a1a66b Fix handling of CBR audio in some AVI files
Before this change, the value of the `dwLength` in the stream header was
interpreted as the number of chunks in the file. Seeking and timestamp
calculation use the media duration and total chunk count. However, in some
files the `dwLength` field appears not to store the number of chunks. For
example, there are CBR MP3 and AC3 files where this field seems to store the
total number of bytes of compressed media instead. That caused seeking and
timestamp calculation to give much smaller values than expected (because the
`dwLength` is very large), which broke seeking.

Work around this using the `idx1` index header if present. We only support
audio formats where every audio sample is a sync sample in AVI, and all chunks
should therefore be listed in this index. Based on testing on many sample AVI
files this gives a reliable total chunk count and fixes seeking.

The test media file is a transcoded version of Big Buck Bunny but manually
edited to overwrite the length, rate and sample size header files to simulate
the error case.

PiperOrigin-RevId: 705103651
2024-12-11 07:43:57 -08:00
microkatz
2f880bf051 Added release note 2024-12-11 15:31:00 +00:00
microkatz
55d9626b5a Format with google-java-format 2024-12-11 14:56:25 +00:00
Gilles Khouzam
f149cb28a3 Add MediaCodecInfo to MediaCodecRenderer::onReadyToInitializeCodec 2024-12-11 14:56:25 +00:00
Rohit Singh
b2e8489119 Removed empty line 2024-12-11 14:47:04 +00:00
kimvde
424b43d2eb Fix CompositionPlayerSeekTest timeout
Playback was never ending in the case where the video graph rendered
the last frame to the output surface before the end-of-current-input
signal was received. This CL handles this specific case.

PiperOrigin-RevId: 705086366
2024-12-11 06:39:49 -08:00
dancho
f55e0f3a3f VideoFrameProcessorTestRunner add support for portrait video
Fixes test failures due to differences test util bug that introduced
a crop in external texture sampling

PiperOrigin-RevId: 705061267
2024-12-11 04:45:12 -08:00
Colin Kho
af32e859ed Remove ChannelCount Max Limitation on OpusDecoder 2024-12-11 16:03:42 +08:00
rohks
8d2f531470 Enable sending CmcdData for manifest requests in DASH, HLS and SS
Issue: androidx/media#1951
PiperOrigin-RevId: 704875765
2024-12-10 15:59:39 -08:00
ibaker
c377a34a5a Don't call onAudioPositionAdvancing if AudioTrack is paused
`AudioTrackPositionTracker.pause` "re-arms" the event, to ensure it
fires again when playback resumes. However the `AudioTrack` position
advances by about 100ms **after** `AudioTrack.pause()`, which
consistently causes the event to fire immediately after pausing (and
then **not** after a subsequent resumption).

This change checks whether the `AudioTrack` is paused before firing
the event, to avoid this spurious trigger.

PiperOrigin-RevId: 704759929
2024-12-10 10:31:46 -08:00
tonihei
3e3cd8e6ac Fix Javadoc reference link for VORBIS mapping
PiperOrigin-RevId: 704737734
2024-12-10 09:27:36 -08:00
tonihei
b34a5b4e5d Align logic to ignore disabled and non-custom buttons in custom layout
The legacy custom layout doesn't support disabled buttons or buttons
with non-custom actions. These are currently filtered out, but in
inconsistent ways:
 - MediaControllerImplBase doesn't filter at all.
 - DefaultNotificationProvider filters before converting
   mediaButtonPreferences, PlayerWrapper filters after the conversion.
 - PlayerWrapper doesn't disable buttons that are unavailable.

To ensure it's consistent, we can add these checks to the existing util
method in CommandButton and also make sure PlayerWrapper disables
unavailable buttons before triggering the util method.

This also means we can simplify some of the tests that rely on the
mediaButtonPreference to customLayout conversion in MediaController.

This change also includes two bug fixes in PlayerWrapper that
became evident in the tests: The extras need to be copied to
avoid modifying a Bundle instance that be used elsewhere and
we need to update the custom layout and potentially the session
extras when the available commands change, as they depend on them.

PiperOrigin-RevId: 704678404
2024-12-10 06:13:54 -08:00
shahddaghash
dc21f3add2 Add preset input picker functionality
Added functionality to `Choose preset input` button. The picker allows the user to select a preset input from a list of loaded preset playlists. The selected preset input is then used to populate the ExoPlayer with the corresponding media items.

PiperOrigin-RevId: 704626144
2024-12-10 02:51:06 -08:00
michaelkatz
6689fee2b2 Implement error handling support for pre-warming renderers
PiperOrigin-RevId: 704408379
2024-12-09 14:04:00 -08:00
michaelkatz
be63e156bb Implement mediasource list update support for pre-warming renderers
PiperOrigin-RevId: 704381778
2024-12-09 12:41:37 -08:00
michaelkatz
bb62278627 Implement track reselection support for pre-warming renderers
PiperOrigin-RevId: 704371028
2024-12-09 12:07:58 -08:00
ibaker
ef19740c92 Remove Forwarding from assertForwardingClassOverridesAllMethods
And use it in a "non-forwarding" context in
`DefaultAnalyticsCollectorTest`.

PiperOrigin-RevId: 704331859
2024-12-09 10:22:06 -08:00
sheenachhabra
e017213ee8 Fix some MP4-AT file format related naming
Also add spec link in the code.

PiperOrigin-RevId: 704329143
2024-12-09 10:14:34 -08:00
michaelkatz
459162c692 Implement seeking support for pre-warming renderer feature
PiperOrigin-RevId: 704328162
2024-12-09 10:12:11 -08:00
michaelkatz
987869e456 Implement secondary renderer feature for pre-warming
`RenderersFactory#createSecondaryRenderer` can be implemented to provide secondary renderers for pre-warming. These renderers must match their primaries in terms of reported track type support and `RendererCapabilities`.

If a secondary renderer is provided, ExoPlayer will enable it for a subsequent media item as soon as its `MediaPeriod` is prepared. This will cause the renderer to start decoding and processing content so that it is ready to play as soon as playback transitions to that media item.

PiperOrigin-RevId: 704326302
2024-12-09 10:07:33 -08:00
andrewlewis
fc3f096888 Fix handling of 'no chunk readers' in AVI
Before this change the chunk readers array was accessed out of bounds in files
for which no chunk readers are created.

PiperOrigin-RevId: 704323919
2024-12-09 10:01:13 -08:00
andrewlewis
1f6c795407 Read init data for AAC (and not other formats) in AVI files
AAC is the only format where the codec data buffer is actually used.

Invalid or obscure versions of the relevant syntax can break extraction when we
try to read the codec data, so it's better to read it only when it's required.

PiperOrigin-RevId: 704308925
2024-12-09 09:15:29 -08:00
ibaker
12546070ee Add vorbis comment support for track/disc numbering fields, and genre
Only `TRACKNUMBER` and `GENRE` are listed here:
https://xiph.org/vorbis/doc/v-comment.html

The rest are derived from the example in Issue: androidx/media#1958.

It's possible that other formats exist in the wild:
https://hydrogenaud.io/index.php/topic,69292.msg613808.html#msg613808

Issue: androidx/media#1958
PiperOrigin-RevId: 704308788
2024-12-09 09:12:57 -08:00
andrewlewis
aa0e7298ca Make some no-op fixes to AVI extraction code
PiperOrigin-RevId: 704292203
2024-12-09 08:16:54 -08:00
ibaker
38d12f25e1 Consistently ignore non-fatal errors in subtitle loading error tests
It's expected that the subtitle load errors are emitted as a non-fatal
error, but it's not defined exactly when they will be emitted. By
consistently ignoring these every time we 'advance' the player, this
de-flakes the test (currently it fails when `untilFullyBuffered()`
triggers a non-fatal error).

PiperOrigin-RevId: 704219317
2024-12-09 03:39:57 -08:00
kimvde
1a7da45dd9 Add a TestTransformerBuilder
This is inspired from TestExoPlayerBuilder.

Adding this class has a few advantages:
- It makes testing easier for apps by configuring Transformer for unit
  tests (for example, by setting the clock).
- It removes the Transformer setters that apps should not use for unit
  testing (for example, the video MIME type should not be set because
  it would cause Transformer to transcode.
- It allows us to add additional setters in the future, for example to
  build a Transformer that always fails.

PiperOrigin-RevId: 704181927
2024-12-09 01:06:51 -08:00
sheenachhabra
0a75447785 Rename terminologies as per the MP4-AT spec
https://developer.android.com/media/platform/mp4-at-file-format

PiperOrigin-RevId: 703531148
2024-12-06 09:58:11 -08:00
dancho
1d2ffcb165 Frame extraction support changing MediaItem
Add FrameExtraction.setMediaItem() method
Prefer non-vendor software codecs by default because
vendor codecs sometimes fail when we attempt reuse.

PiperOrigin-RevId: 703486235
2024-12-06 06:57:21 -08:00
rohks
b816e2f284 Add E2E test for DASH stream with CMCD enabled and init segment
The test complements the existing E2E test for HLS in `HlsPlaybackTest` by verifying similar functionality for DASH in `DashPlaybackTest`.

PiperOrigin-RevId: 703454388
2024-12-06 04:39:14 -08:00
jbibik
038d7c8cb9 Remove kotlin-android plugin from main demo
This fixes the problem with `compileDebugKotlin` gradle task and `Unknown Kotlin JVM target: 21`.

PiperOrigin-RevId: 703427496
2024-12-06 02:46:21 -08:00
shahddaghash
862791f837 Fixes after removing deprecated methods from Transformer.Builder
After removing `Transformer.Builder.setFlattenForSlowMotion()`, there is no need to keep `flattenForSlowMotion` in Transformer since it's now set in `EditedMediaItem`.
The change also includes making `audioProcessors` and `videoEffects` attributes final.

PiperOrigin-RevId: 703076023
2024-12-05 05:14:59 -08:00
shahddaghash
b6724e2115 Load preset input from JSON file in EffectActivity
The JSON file contains a list of playlists, each with a name and a list of media items. The EffectActivity loads the JSON file and creates a list of PlaylistHolder objects, which contain the playlist name and the list of media items.

PiperOrigin-RevId: 703069411
2024-12-05 04:46:47 -08:00
kimvde
f1a0e4b0b7 Fix timeouts in CompositionPlaybackTest
Some of the tests were timing out because the emulator was too slow.

PiperOrigin-RevId: 703056616
2024-12-05 03:54:49 -08:00
ibaker
b5a1efdbce ForwardingTimeline: Implement & finalize some methods
`equals`, `hashCode`, and `getPeriodByUid` are correctly implemented on
`Timeline`. Overriding these in a way that maintains correctness is
fiddly, so this CL prevents that for the 'simple' case of subclasses of
`ForwardingTimeline`. Implementations of `Timeline` that need to
override these methods for performance should extend `Timeline` or
`AbstractConcatenatedTimeline` instead of `ForwardingTimeline`.

PiperOrigin-RevId: 703035721
2024-12-05 02:24:19 -08:00
ibaker
92e0b5978f Add ForwardingTimelineTest
PiperOrigin-RevId: 703029260
2024-12-05 01:59:07 -08:00
Googler
5ab9a7856f Rollback of 646a6352a2
PiperOrigin-RevId: 702960045
2024-12-04 20:36:15 -08:00
Googler
f60d2b4146 Rollback of 66e8b53b43
PiperOrigin-RevId: 702943864
2024-12-04 19:22:54 -08:00
dancho
6193f7c38f Generate static HDR metadata when using DefaultEncoderFactory
* Select an encoder that supports HDR editing.
* Set KEY_PROFILE to an HDR10 option
* Use DecodeOneFrameUtil test util to return the MediaCodec format,
  which includes HDR_STATIC_INFO

PiperOrigin-RevId: 702752639
2024-12-04 09:31:13 -08:00
ibaker
3e94bd6125 Enable lint errors in tests for remaining playback modules
Also fix existing violations

This enables the lint for tests in:
* `decoder-iamf`
* `exoplayer-rtsp`
* `test-exoplayer-playback`
* `test-session-current`
* `ui-compose`

PiperOrigin-RevId: 702728438
2024-12-04 08:08:01 -08:00
rohks
fb2d5f905d Fix typo in Javadoc for constructor in SsMediaSource.Factory
PiperOrigin-RevId: 702718127
2024-12-04 07:31:54 -08:00
rohks
e927d7b986 Fix ReplacingCuesResolver.discardCuesBeforeTimeUs to retain active cue
The method previously discarded the cue that was active at `timeUs`,
meaning it had started before but had not ended by `timeUs`.

Issue: androidx/media#1939

#cherrypick

PiperOrigin-RevId: 702707611
2024-12-04 06:51:12 -08:00
kimvde
06b94f5448 DefaultVideoSink: implement handleInputFrame() and listeners
With this CL, PlaybackVideoGraphWrapper doesn't call the
VideoFrameRenderControl directly anymore (which is one of the goals of
DefaultVideoSink).

PiperOrigin-RevId: 702673821
2024-12-04 04:29:03 -08:00
ibaker
c770a6ab6f Enable lint errors in database tests
Also fix existing violations.

PiperOrigin-RevId: 702665065
2024-12-04 03:55:33 -08:00
tonihei
46ab6cf030 Fix wrong copy-pasted comment
PiperOrigin-RevId: 702647886
2024-12-04 02:38:11 -08:00
Googler
bffa253e3a Add initialization data for VP9 and AV1 codec in MatroskaExtractor
Update `MatroskaExtractor` to populate the CSD information for VP9 and AV1 codecs with their codec private data.

For reference: [CodecPrivate for VP9](https://www.webmproject.org/docs/container/#vp9-codec-feature-metadata-codecprivate), [CSD for AV1](https://aomediacodec.github.io/av1-isobmff/#av1codecconfigurationbox-syntax).

PiperOrigin-RevId: 702631838
2024-12-04 01:39:41 -08:00
ibaker
9828d104b5 Improve reflective instantiation in ForwardingFoo test util
This code previously passed null or 'default' for every parameter.
Now it tries to mock non-final types, and recursively constructor final
types if possible, eventually giving up and passing null instead.

The previous null-passing behaviour led to a quite confusing failure in
`ForwardingPlayer.addListener` when writing 25c927e9f3 due to an NPE
when trying to compare parameter equality in `Mockito.verify` [1].
With this change, the failure is much clearer [2].

There's a relatively simple case this code still doesn't handle: A final
type like `PlaybackParameters` where the constructor parameters **have**
to be non-default primitives (greater than zero in that case).

-------

[1]

```
java.lang.reflect.InvocationTargetException
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:569)
	at androidx.media3.test.utils.TestUtil.assertForwardingClassForwardsAllMethodsExcept(TestUtil.java:687)
	at androidx.media3.common.ForwardingPlayerTest.forwardingPlayer_forwardsAllPlayerMethods(ForwardingPlayerTest.java:110)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
	at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke(NativeMethodAccessorImpl.java:77)
	at java.base/jdk.internal.reflect.DelegatingMethodAccessorImpl.invoke(DelegatingMethodAccessorImpl.java:43)
	at java.base/java.lang.reflect.Method.invoke(Method.java:569)
	at org.junit.runners.model.FrameworkMethod$1.runReflectiveCall(FrameworkMethod.java:59)
	at org.junit.internal.runners.model.ReflectiveCallable.run(ReflectiveCallable.java:12)
	at org.junit.runners.model.FrameworkMethod.invokeExplosively(FrameworkMethod.java:56)
	at org.junit.internal.runners.statements.InvokeMethod.evaluate(InvokeMethod.java:17)
	at org.junit.runners.ParentRunner$3.evaluate(ParentRunner.java:306)
	at org.robolectric.RobolectricTestRunner$HelperTestRunner$1.evaluate(RobolectricTestRunner.java:588)
	at org.robolectric.internal.SandboxTestRunner$2.lambda$evaluate$2(SandboxTestRunner.java:290)
	at org.robolectric.internal.bytecode.Sandbox.lambda$runOnMainThread$0(Sandbox.java:101)
	at java.base/java.util.concurrent.FutureTask.run(FutureTask.java:264)
	at java.base/java.util.concurrent.ThreadPoolExecutor.runWorker(ThreadPoolExecutor.java:1136)
	at java.base/java.util.concurrent.ThreadPoolExecutor$Worker.run(ThreadPoolExecutor.java:635)
	at java.base/java.lang.Thread.run(Thread.java:840)
Caused by: java.lang.NullPointerException: Cannot invoke "Object.hashCode()" because "this.listener" is null
	at androidx.media3.common.ForwardingPlayer$ForwardingListener.hashCode(ForwardingPlayer.java:1140)
	at java.base/java.lang.Object.toString(Object.java:256)
	at java.base/java.lang.String.valueOf(String.java:4220)
	at org.mockito.internal.verification.argumentmatching.ArgumentMatchingTool.toStringEquals(ArgumentMatchingTool.java:54)
	at org.mockito.internal.verification.argumentmatching.ArgumentMatchingTool.getSuspiciouslyNotMatchingArgsIndexes(ArgumentMatchingTool.java:36)
	at org.mockito.internal.verification.checkers.MissingInvocationChecker.checkMissingInvocation(MissingInvocationChecker.java:45)
	at org.mockito.internal.verification.Times.verify(Times.java:37)
	at org.mockito.internal.verification.MockAwareVerificationMode.verify(MockAwareVerificationMode.java:30)
	at org.mockito.internal.handler.MockHandlerImpl.handle(MockHandlerImpl.java:75)
	at org.mockito.internal.handler.NullResultGuardian.handle(NullResultGuardian.java:29)
	at org.mockito.internal.handler.InvocationNotifierHandler.handle(InvocationNotifierHandler.java:34)
	at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.doIntercept(MockMethodInterceptor.java:82)
	at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor.doIntercept(MockMethodInterceptor.java:56)
	at org.mockito.internal.creation.bytebuddy.MockMethodInterceptor$DispatcherDefaultingToRealMethod.interceptAbstract(MockMethodInterceptor.java:161)
	at androidx.media3.common.Player$MockitoMock$1276619531.addListener(Unknown Source)
	... 22 more
```

----

[2]

```
Argument(s) are different! Wanted:
player.addListener(
    Mock for Listener, hashCode: 107929032
);
-> at java.base/jdk.internal.reflect.NativeMethodAccessorImpl.invoke0(Native Method)
Actual invocations have different arguments:
player.addListener(
    androidx.media3.common.ForwardingPlayer$ForwardingListener@af47cec0
);
-> at androidx.media3.common.ForwardingPlayer.addListener(ForwardingPlayer.java:81)
```

PiperOrigin-RevId: 702378861
2024-12-03 10:10:39 -08:00
ibaker
593899c2b2 Enable lint errors in datasource tests
Fix existing violations

PiperOrigin-RevId: 702377837
2024-12-03 10:07:19 -08:00
ibaker
c870fb7594 Enable lint errors in extractor tests
Fix existing violations.

PiperOrigin-RevId: 702366292
2024-12-03 09:29:30 -08:00
ibaker
876abe315b Increase library target SDK to 31
This only affects the default SDK level that Robolectric tests run at.
Also upgrade the Robolectric version to 4.14.1 to pick up
4f32042afe
which is needed for async `ShadowMediaCodec` support (the default in
ExoPlayer from API 31+).

Some tests fail on Robolectric at API 31. This change configures them to
continue running on API 30, so the failures can be investigated and
fixed in follow-up changes.

PiperOrigin-RevId: 702357124
2024-12-03 08:57:53 -08:00
tianyifeng
148641f049 Move BasePreloadManager.Listener to a top level PreloadManagerListener
PiperOrigin-RevId: 702351887
2024-12-03 08:38:35 -08:00
ibaker
d72453a6cd Use ForwardingTrackOutput in MidiExtractor
PiperOrigin-RevId: 702328995
2024-12-03 07:14:58 -08:00
Copybara-Service
9a3dc3f03a Merge pull request #1889 from yschimke:fix_build
PiperOrigin-RevId: 702323944
2024-12-03 06:56:53 -08:00
Yuri Schimke
10ca39a779 Specify androidx.activity:activity-compose as 1.9.0
The Compose BOM doesn't provide this.
https://developer.android.com/develop/ui/compose/bom/bom-mapping
2024-12-03 14:39:08 +00:00
dancho
e8f60d4fec Remove earlier frame extraction prototypes
Earlier this year, we explored frame extraction built on top of Transformer
or ImageReader.

We decided against these approaches because random-access of frames
is a key requirement. And ImageReader behaviour is unreliable.

PiperOrigin-RevId: 702303490
2024-12-03 05:32:09 -08:00
dancho
7821e7702b Frame extractor: explain color accuracy issues
This is only a javadoc change, setting expectations.

PiperOrigin-RevId: 702295210
2024-12-03 04:56:38 -08:00
Copybara-Service
e4993779db Merge pull request #1943 from DolbyLaboratories:dlb/ac4-ajoc/dev
PiperOrigin-RevId: 702281314
2024-12-03 04:04:52 -08:00
ibaker
18d156fe3c Configure ProgressiveMediaSource with a Format for subtitles
This means we can complete preparation (and trigger track selection)
before opening a `DataSource`, which then means we only end up loading
the data for a selected subtitle track (instead of all tracks as
currently happens).

By making preparation trivial in this case (with no reasonable cause
of error), we can also remove the `suppressPrepareError` option added in
b3290eff10.

This change also fixes the implementation of
`ProgressiveMediaPeriod.maybeStartDeferredRetry` to only short-circuit
return `false` if the chosen track is not audio or video **and** there
is at least one audio or video track in this period.

Issue: androidx/media#1721
PiperOrigin-RevId: 702275968
2024-12-03 03:40:49 -08:00
kimvde
0037388660 Simplify VideoSink.handleInputFrame
This method was taking positionUs and elapsedRealtimeUs parameters and
throwing a VideoSinkException because it was calling render() to make
room for a new input frame. Instead of doing that, this CL makes sure
render() is called before  handleInputFrame() in the renderer (even
though it may not make a significant difference).

PiperOrigin-RevId: 702273587
2024-12-03 03:29:30 -08:00
ybai001
1df0ee2052 Minor change to comment 2024-12-03 15:29:57 +08:00
ybai001
e503535ec2 LFE channel should always be counted for AC-4 A-JOC content on MPEG-4 layer 2024-12-03 13:29:57 +08:00
ybai001
547a164f42
Merge pull request #15 from androidx/main
Merge from androidx/media main branch
2024-12-03 10:14:07 +08:00
claincly
675c1d898a Move interface locations
PiperOrigin-RevId: 702096389
2024-12-02 15:03:37 -08:00
claincly
ecdf7c5df7 Propagate sourceEnded to AudioGraphInputAudioSink
PiperOrigin-RevId: 702076933
2024-12-02 13:59:15 -08:00
claincly
8908d82cac Signal end of stream to VideoFrameProcessor in previewing
This will be used by VideoCompositor

PiperOrigin-RevId: 702031907
2024-12-02 11:30:20 -08:00
ibaker
25c927e9f3 Standardize ForwardingXXX tests
Add missing ones for `ForwardingExtractorInput` and `ForwardingSeekMap`.
`ForwardingTimeline` is a bit more fiddly, so it's left for a follow-up
change.

PiperOrigin-RevId: 701988492
2024-12-02 09:12:53 -08:00
dancho
b2ba5da09b Implement cancellation of FrameExtractor.getFrame
Previously cancelling one FrameExtractor.getFrame
ListenableFuture caused the following requests to fail.
Implement ability to cancel Futures, and correctly issue
next player seek commands after all past requests complete.

Removes uses of SettableFuture, and replaces them with
CallbackToFutureAdapter.
Adds a dependency on androidx.concurrent:concurrent-futures

PiperOrigin-RevId: 701941403
2024-12-02 06:09:33 -08:00
Googler
19b276d6a7 Allow track selection parameters to be set in ExoPlayerAssetLoader.
PiperOrigin-RevId: 701926949
2024-12-02 05:04:26 -08:00
dancho
d214e90ce4 Fix Frame Extractor getting stuck
Frame Extractor was getting stuck with SeekParameters.CLOSEST_SYNC.
onPositionDiscontinuity callback was sometimes being called with a
non-adjusted new position.

Fix this by monitoring player state ready.
For the player to become ready, we have to override renderer isReady.

PiperOrigin-RevId: 701924752
2024-12-02 04:56:26 -08:00
tonihei
da4376d48d Avoid modifying legacy custom layout in MediaControllerImplBase
When a session sets a custom layout, we currently convert it to the
media button preferences in MediaControllerImplBase and convert it back
to a custom layout for consumers of MediaController.getCustomLayout.

To avoid unnecessary conversions and potential changes, we can
directly use the provided custom layout to get the same logic as
before. This also means we avoid notifying a change to the custom layout
if only the implicit slots changed that weren't relevant before
introducing them (=we can remove some tests for this behavior).

PiperOrigin-RevId: 701903475
2024-12-02 03:19:51 -08:00
ibaker
77d33645cc Clarify CommandButton javadoc around iconResId and ICON_UNDEFINED
PiperOrigin-RevId: 701898620
2024-12-02 02:59:32 -08:00
Copybara-Service
f113a78e0d Merge pull request #1870 from colinkho:fwd-extractor-clz
PiperOrigin-RevId: 701313111
2024-11-29 10:18:34 -08:00
Copybara-Service
e27238666a Merge pull request #1849 from colinkho:stuckwhenready
PiperOrigin-RevId: 701306291
2024-11-29 09:34:56 -08:00
Ian Baker
7f62f74737 Fix review comments 2024-11-29 17:10:45 +00:00
Ian Baker
21e627da1c Fix review comments 2024-11-28 16:54:00 +00:00
Copybara-Service
55c81f6ed6 Merge pull request #1876 from colinkho:extinputgetreader
PiperOrigin-RevId: 701021720
2024-11-28 07:36:28 -08:00
Ian Baker
037a6f3219 Fix nullness errors 2024-11-28 14:47:57 +00:00
Ian Baker
38145b6e89 Tweak javadoc & reformat 2024-11-28 14:20:27 +00:00
Colin Kho
55a1290bfd Add a getter for DataReader on DefaultExtractorInput 2024-11-28 14:20:27 +00:00
Colin Kho
24ea6c4c14 Relax PlaybackStuck criteria to only when playWhenReady is true 2024-11-28 14:06:44 +00:00
Ian Baker
c5af8ba360 Add & fix copyright headers 2024-11-28 14:04:38 +00:00
Ian Baker
2683331299 Add tests and missing overrides to ForwardingExtractor 2024-11-28 14:01:14 +00:00
Ian Baker
01422d8322 Reformat with google-java-format 2024-11-28 11:45:09 +00:00
Colin Kho
6d265476e4 Added Forwarding classes for Extractor and ExtractorOutput 2024-11-28 11:45:09 +00:00
kimvde
4d925bde81 Remove stale TODO
Handling (very) late buffers is already supported.

PiperOrigin-RevId: 700965581
2024-11-28 03:02:59 -08:00
kimvde
852091f2d9 Implement DefaultVideoSink.isEnded
To do that, I had to add VideoFrameRenderControl.signalEndOfInput() and
isEnded() to match the VideoSink interface.

PiperOrigin-RevId: 700957098
2024-11-28 02:27:50 -08:00
kimvde
f3f4646296 Add VideoSink.signalEndOfCurrentInputStream()
The isLastBuffer flag passed to MediaCodecRenderer.processOutputBuffer()
is unreliable. It is true for the last frames (plural) of a video
stream, which makes it possible for the video renderer to end before all
the frames have been rendered to the output surface.

PiperOrigin-RevId: 700941685
2024-11-28 01:23:55 -08:00
kimvde
dfe3c90f9a Fix CompositionPlayerTest.imagePreview_imagePlaysForSetDuration
PiperOrigin-RevId: 700931124
2024-11-28 00:36:46 -08:00
rohks
08619bf6fa Add KEY_TRACK_ID to MediaFormat returned by getTrackFormat(int)
This aligns the behavior with `MediaExtractor`, which sets this key in its [`MediaFormat`](https://developer.android.com/reference/android/media/MediaExtractor#getTrackFormat(int)) output.

Additionally, unnecessary `selectTrack` calls have been removed from the existing `getTrackFormat_...` tests, as they are not required to fetch the track format.

PiperOrigin-RevId: 700741326
2024-11-27 10:26:30 -08:00
dancho
6e29b96337 Add support for extracting HDR frames as HLG Bitmaps
* Add GlUtils to create RGB10_A2 textures.
* Add OETF conversion to FrameReadingGlShaderProgram.
* Add FrameExtractor configuration API to request HDR output: API 34+

PiperOrigin-RevId: 700738895
2024-11-27 10:18:57 -08:00
rohks
270543555d Add getSampleCryptoInfo API to MediaExtractorCompat
This method enables handling encrypted samples by providing the necessary decryption details.

PiperOrigin-RevId: 700729949
2024-11-27 09:48:11 -08:00
kimvde
c861947bee Skip failing test on API 31 emulator
PiperOrigin-RevId: 700722123
2024-11-27 09:17:52 -08:00
rohks
52e45e0864 Prepare for CryptoInfo support by removing cached size
Currently, `size` is cached in `SampleMetadata` when samples are committed in `SampleQueue`, which worked because we didn't support reading files with `CryptoInfo`. This change prepares for future `CryptoInfo` support, as the size changes when reading samples with encrypted data. Caching the size at commit time could lead to incorrect buffer `size` calculations.

Existing tests verify the correctness of the `getSampleSize()` API.

PiperOrigin-RevId: 700714178
2024-11-27 08:47:37 -08:00
Copybara-Service
bff5523bb6 Merge pull request #1823 from MGaetan89:remove_outdated_sdk_check
PiperOrigin-RevId: 700706152
2024-11-27 08:16:02 -08:00
ibaker
60133b0c7e Recommend ForwardingSimpleBasePlayer in ForwardingPlayer javadoc
Also add an explicit warning about how fiddly `ForwardingPlayer` can be
to use correctly.

PiperOrigin-RevId: 700698032
2024-11-27 07:42:24 -08:00
rohks
a371824dbc Remove unnecessary @SuppressLint("Override")
The Android SKD version 30 is stable, allowing us to remove the `@SuppressLint("Override")` annotations from `InputReaderAdapterV30` and `OutputConsumerAdapterV30`.

PiperOrigin-RevId: 700681115
2024-11-27 06:32:24 -08:00
ibaker
6cf3004d62 Bump IMA dependency to 3.35.1
The previous version (3.33.0) is known to have some bugs, and the latest
version (3.36.0) is also known to be buggy.

PiperOrigin-RevId: 700657484
2024-11-27 04:56:28 -08:00
shahddaghash
d5d5771cd2 Add custom MediaSource.Factory attribute to CompositionPlayer
Introduced a `mediaSourceFactory` attribute to CompositionPlayer. This allows for the use of a custom MediaSource.Factory when creating media sources, providing more flexibility and control over media source creation. Previously, the default `MediaSource.Factory` was used in all cases.

PiperOrigin-RevId: 700391911
2024-11-26 10:48:22 -08:00
claincly
96c35966d8 Remove unnecessary parentherses
PiperOrigin-RevId: 700356007
2024-11-26 08:49:20 -08:00
claincly
de76d7932f Add missing test cases in SeekTest
PiperOrigin-RevId: 700354445
2024-11-26 08:43:06 -08:00
ibaker
465873ac28 Add androidx.compose.foundation:foundation to deps with AAR files
Also remove a dependency that is no longer used.

PiperOrigin-RevId: 700341461
2024-11-26 07:55:29 -08:00
ibaker
b19b6ccc60 MP3 VBRI: Use sampleCountToDurationUs() and samplesPerFrame
PiperOrigin-RevId: 700340564
2024-11-26 07:52:18 -08:00
ibaker
46578ee0a6 MP3: Use bytes field from VBRI frame instead of deriving from ToC
The previous code assumed that the `VBRI` Table of Contents (ToC)
covers all the MP3 data in the file. In a file with an invalid VBRI ToC
where this isn't the case, this results in playback silently stopping
mid-playback (and either advancing to the next item, or continuing to
count up the playback clock forever). This change considers the `bytes`
field to determine the end of the MP3 data, in addition to deriving it
from the ToC. If they disagree we log a warning and take the max value.
This is because we handle accidentally reading non-MP3 data at the end
(or hitting EoF) better than stopping reading valid MP3 data partway
through.

Issue: androidx/media#1904

#cherrypick

PiperOrigin-RevId: 700319250
2024-11-26 06:18:29 -08:00
claincly
2a4bae01ef Add two missing test cases in seeking
PiperOrigin-RevId: 700284751
2024-11-26 03:44:23 -08:00
claincly
e357629400 Make back button pause player instead of quitting app
PiperOrigin-RevId: 700282443
2024-11-26 03:33:45 -08:00
ibaker
f257e5511f MP3: Exclude VBRI frame from ToC position calculations
The current code assumes that the first Table of Contents segment
includes the `VBRI` frame, but I don't think this is correct and it
should only include real/audible MP3 ata - so this change updates the
logic to assume the first ToC segment starts at the frame **after** the
`VBRI` frame.

Issue: androidx/media#1904

#cherrypick

PiperOrigin-RevId: 700269811
2024-11-26 02:34:33 -08:00
kimvde
6a3def3ccb Turn off repeat mode on Composition demo app
This is to make debugging easier.

PiperOrigin-RevId: 700265039
2024-11-26 02:14:04 -08:00
Gaëtan Muller
2fc379a61b Fix documentation for Format.Builder.setTileCountHorizontal and Format.Builder.setTileCountVertical 2024-11-26 10:05:16 +00:00
ibaker
3eb36d67bd Add MP3 test asset with VBRI frame
This was hand-crafted with a 4-entry ToC by modifying
`bear-vbr-xing-header.mp3` in a hex editor.

The output difference from 117 samples to 116 samples is due to the
calculation in `VbriSeeker` assuming that the ToC includes the VBRI
frame itself, which I don't think is correct (fix is in a follow-up
change).

Issue: androidx/media#1904

#cherrypick

PiperOrigin-RevId: 700254516
2024-11-26 01:33:39 -08:00
dancho
bb20eb4975 Frame Extractor HDR: tone map to SDR
Support extracting frames from HDR input by tone mapping
to SDR (BT.709).

ExperimentalFrameExtractor must be public because HDR tests
live in a different package.

PiperOrigin-RevId: 699994112
2024-11-25 08:41:42 -08:00
dancho
0d8f1d5ab9 Add Frame Extractor configuration for mediacodecselector
Adds a configuration option to Frame Extractor to choose MediaCodecSelector.
Add a MediaCodecSelector that lists software decoders first

PiperOrigin-RevId: 699962365
2024-11-25 06:40:28 -08:00
claincly
7ef1a7ab8b Support renderer joining in CompositionPlayer
Renderers join when they can start processing but not produce output.

In CompositionPlayer before this change, the VideoSink will be flushed when
position is reset regardless. This is useful for seeking, as seeking triggers position reset. But with pre-warming, a video renderer can be joining -  it is
enabled (which also triggers position reset) before the previous image renderer
is disabled. At this moment, as the image renderer is still producing frames,
we should not flush the video sink just now.

PiperOrigin-RevId: 699943882
2024-11-25 05:22:22 -08:00
kimvde
25a782e10a Stop delaying registering input stream on VideoFrameProcessor
We are currently waiting until stream 1 is completely rendered on screen
before registering stream 2 but that is not necessary anymore because
VideoFrameProcessor supports registering stream 2 before stream 1 is
fully processed.

PiperOrigin-RevId: 699929943
2024-11-25 04:17:32 -08:00
dancho
c0fb4b7aff Composition demo app: show extended brightness
Request COLOR_MODE_HDR and show HDR content
as brighter-than-SDR.

PiperOrigin-RevId: 699887643
2024-11-25 01:22:54 -08:00
ibaker
827966b7a4 Add pixel aspect ratio to Format.toLogString
#cherrypick

PiperOrigin-RevId: 698770714
2024-11-21 07:21:11 -08:00
shahddaghash
73c4bb6e1f Bump Media3 version to 1.5.0
PiperOrigin-RevId: 698761734
2024-11-21 06:47:36 -08:00
kimvde
52dfe25542 Fix flushing issue in DefaultVideoFrameProcessor
Before this CL, the following scenario could happen:
- A new input stream is registered to the DefaultVideoFrameProcessor.
- Before the pipeline is reconfigured, a seek is issued andd the
  DefaultVideoFrameProcessor is flushed.
- As a result, the new input stream registration is never taken into
  account.

As a result:
- If an input stream is registered after the seek (before queueing any
  frame), registerInputStream will block indefinitely because
  inputStreamRegisteredCondition will be closed.
- If a frame is queued after the seek, it will be linked to the input
  stream information of the previous frames.

This CL makes sure that any pending input stream is registered after a
flush.

PiperOrigin-RevId: 698736866
2024-11-21 05:00:32 -08:00
kimvde
79effe7b60 Remove call to getFrameReleaseAction prior to applying effects
This call does 2 things:
1. It's used to estimate the frame rate early but this is incorrect as
   the frame rate can be changed by effects. I have tested playback on
   my device and I don't see any difference.
2. For ExoPlayer.setVideoEffects() only: it allows dropping all the
   frames until the next key frame in case the input frame is "very
   late". This doesn't seem really necessary though because because we
   do the same thing after applying effects.

This change is also a step towards moving all the calls to
VideoFrameRelease/RenderControl to DefaultVideoSink.

PiperOrigin-RevId: 698732161
2024-11-21 04:38:54 -08:00
michaelkatz
5282fe3125 Rollback of 854566dbfe
PiperOrigin-RevId: 698730105
2024-11-21 04:29:19 -08:00
shahddaghash
754dfd76c8 Simplify use of defaultMediaSourceFactory in CompositionPlayer
Changes includes making sure the same `defaultMediaSourceFactory` is used when creating a new media source in `setPrimaryPlayerSequence` and `setSecondaryPlayerSequence`.
This is pre-work for introducing a new attribute `mediaSourceFactory` that can be optionally used instead of the default one. This will allow developers to pass a customized media source factory to CompositionPlayer.

PiperOrigin-RevId: 698721139
2024-11-21 03:52:58 -08:00
shahddaghash
e5110e6442 Merge release notes for media3 1.5.0 stable release
PiperOrigin-RevId: 698713460
2024-11-21 03:16:14 -08:00
sheenachhabra
407bc4fec9 Manage all color value conversions in ColorInfo class
This CL also aligns supported color space in `media3 common` and `media3 muxer`.

Earlier `muxer` would take even those values which are considered invalid
in `media3` in general.

Earlier muxer would throw if a given `color standard` is not recognized
but with the new change, it will rather put default `unspecified` value.

#cherrypick

PiperOrigin-RevId: 698683312
2024-11-21 01:09:02 -08:00
Copybara-Service
089eaa1d5d Merge pull request #1898 from DolbyLaboratories:dlb/ac4-level4-dash/dev
PiperOrigin-RevId: 698456089
2024-11-20 11:18:59 -08:00
shahddaghash
cf4488aa1f Move misplaced release note to Unreleased changes section
PiperOrigin-RevId: 698426838
2024-11-20 10:01:03 -08:00
dancho
d92c9aa8a1 Add a Frame Extractor test with rotated input
Adds a Frame extractor test that verifies decoder
respects rotation metadata from the mp4 container.

Do not rely on the MediaCodec decoder rotate the input.
Rotate via a video effect instead.

PiperOrigin-RevId: 698381282
2024-11-20 07:20:50 -08:00
ivanbuper
66e8b53b43 Make SpeedChangingAudioProcessor direct subclass of AudioProcessor
This non-functional refactor inlines and simplifies the logic of
`BaseAudioProcessor` used by `SpeedChangingAudioProcessor`, and makes
the latter implement `AudioProcessor` directly.

`SpeedChangingAudioProcessor` acts as a wrapper over
`SonicAudioProcessor` and does not actually modify any samples,
so it had very little use for the affordances provided by
`BaseAudioProcessor`.

PiperOrigin-RevId: 698342962
2024-11-20 04:27:27 -08:00
claincly
3c01500a4e Use dynamic scheduling to speed up rendering the first image frame
3P app reported it's slow to see the first image frame on the screen, when
`playWhenReady` is false. This is because the player would wake up less
frequently when `playWhenReady` is false. This CL adds a runnable to wake up
the player any time a new frame is made available.

PiperOrigin-RevId: 698066887
2024-11-19 10:33:59 -08:00
dancho
dc7a0ca22f Move SDK == 33 tests to SDK 33+
Ensure tests run on any recent emulator.

PiperOrigin-RevId: 698015529
2024-11-19 07:55:32 -08:00
ivanbuper
646a6352a2 Remove bypass of SonicAudioProcessor in SpeedChangingAudioProcessor
SpeedChangingAudioProcessor is redundantly bypassing SonicAudioProcessor
when the speed is set to 1f. SpeedChangingAudioProcessor should not make
assumptions about the underlying audio processing and moreover should
not be bypassing any AudioProcessor based on parameter values. Default
parameter values are a valid state for an active AudioProcessor.

Sonic already handles the "default case" state by just copying the input
buffer onto the output buffer.

This CL also simplifies SonicAudioProcessor, which would mark itself as
inactive when configured with a valid set of default parameters. The API
contract for `isActive()` makes no mention about parameter state, which
makes changes in `isActive()` after applying new valid parameters quite
unintuitive.

PiperOrigin-RevId: 698000500
2024-11-19 07:02:20 -08:00
rohks
d702e1d496 Add getPsshInfo() API to MediaExtractorCompat
This method retrieves PSSH (Protection System Specific Header) data from the media as a map of UUID-to-bytes.

PiperOrigin-RevId: 697974134
2024-11-19 05:14:55 -08:00
bachinger
b9c9d95b90 Add JSON asset list parser
PiperOrigin-RevId: 697966021
2024-11-19 04:41:17 -08:00
Copybara-Service
98723dc0a8 Merge pull request #1888 from DolbyLaboratories:dlb/dash-parser/dev
PiperOrigin-RevId: 697956473
2024-11-19 04:01:30 -08:00
dancho
ccc7b22ff4 Implement custom Frame Extractor renderer
Render only one frame per seek to reduce the amount of work done

PiperOrigin-RevId: 697946350
2024-11-19 03:14:57 -08:00
dancho
26f10effc2 Add frame extractor tests for decoder counters.
Right now, decoders produce an unspecified (but large)
number of frames. This will be resolved in a follow-up

PiperOrigin-RevId: 697945076
2024-11-19 03:10:17 -08:00
bachinger
c7ca9fbe7d Sanitize duration parsing for HLS interstitials
PiperOrigin-RevId: 697941445
2024-11-19 02:54:53 -08:00
kimvde
b782cdff52 Don't drop frames to ignore in RenderControl
The following was happening:
- VideoFrameRenderControl.render() was calling
VideoFrameReleaseAction.getFrameReleaseAction().
- VideoFrameReleaseAction.getFrameReleaseAction() was calling
FrameTimingEvaluator.shouldIgnoreFrame(), which is implemented in
MediaCodecVideoRenderer.
- MediaCodecVideoRenderer.shouldIgnoreFrame(), when returning true,
was also flushing the VideoSink, which was flushing the
VideoFrameRenderControl.
- VideoFrameRenderControl.render() was removing the frame from the
presentationTimestampsUs queue, but the frame had already been removed
by the flush operation, causing an exception to be thrown.

This fix removes the last step.

PiperOrigin-RevId: 697915692
2024-11-19 01:15:17 -08:00
claincly
9c6d9e9e47 Reduce test name lengths
Some parametrized names are too long (>255 chars). Remove putting parameter
info in the test name and instead log them.

PiperOrigin-RevId: 697677513
2024-11-18 10:34:06 -08:00
bachinger
9ae136becb Add context as a parameter to shouldStartForegroundService
Issue: androidx/media#1887
PiperOrigin-RevId: 697650546
2024-11-18 09:15:46 -08:00
ivanbuper
fff6e2e169 Fix incorrect Media3 1.5.0-rc01 release notes
PiperOrigin-RevId: 697626185
2024-11-18 07:57:57 -08:00
ibaker
66ec022bf0 Fix broken anchor in 1.5.0-rc02 release notes
PiperOrigin-RevId: 697596494
2024-11-18 05:54:28 -08:00
kimvde
8be2556efd Implement DefaultVideoSink.setStreamTimestampInfo
To do this, refactor stream start position handling so that
VideoFrameRenderControl is only passed a start position when it should
be applied, and therefore doesn't need to take a timestamp associated
with each start position anymore

PiperOrigin-RevId: 697544416
2024-11-18 02:01:52 -08:00
kimvde
2568ff73cb Remove unnecessary checkStateNotNull checks
LongArrayQueue.remove() throws a NoSuchElementException if the queue is
empty.

PiperOrigin-RevId: 697531458
2024-11-18 01:05:34 -08:00
ybai001
7b3effb871 Update test code based on the latest code structure 2024-11-18 10:29:40 +08:00
ibaker
cbb8e2f1e6 Bump media3 version to 1.5.0-rc02
PiperOrigin-RevId: 696912494
2024-11-15 09:33:11 -08:00
tianyifeng
0143884cd7 Deflake the DefaultPreloadManagerTest
From [ the last change in `DefaultPreloadManagerTest`](2b54b1ebbe), the preloadManager began to use a separate `preloadThread` in `release_returnZeroCount_sourcesAndRendererCapabilitiesListReleased`, which unveils a bug in `PreloadMediaSource`. When `PreloadMediaSource.releasePreloadMediaSource` is called, `preloadHandler` will post a `Runnable` on the preload looper to release the internal resources. Before this `Runnable` is executed, it is possible that the [`stopPreloading`](https://github.com/androidx/media/blob/main/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/source/preload/PreloadMediaSource.java#L442) method is executed just as the result of preloading has completed. This is expected to remove the posted `Runnable`s for further preloading, however, the posted `Runnable` for releasing will also be removed from the message queue.

Ideally we should use `postDelayed(runnable, token, delayMillis)` to post the runnables so that the token will be useful to identify which messages to remove in `removeCallbacksAndMessages(token)`, but that `postDelayed` method is only available from API 28. So in this change we are using a separate handler for releasing, and then the call of `preloadHandler.removeCallbacksAndMessages` won't impact the runnable for releasing.

#cherrypick

PiperOrigin-RevId: 696894483
2024-11-15 08:22:54 -08:00
rohks
ecc5cd889f Add getMetrics() API to MediaExtractorCompat
This method returns a `PersistableBundle` containing metrics data for the current media container. The bundle includes attributes and values for the media container, as described in `MediaExtractor.MetricsConstants`.

PiperOrigin-RevId: 696893276
2024-11-15 08:17:26 -08:00
bachinger
1af0b5b432 Add handleContentTimelineChanged to AdsLoader
The new callback allows an app to read data from the content
timeline to populate the `AdPlaybackState`. To avoid a deadlock
between the `AdMediaSource` waiting for the `AdPlaybackState` and
the app waiting for the content `Timeline`, a boolean flag
`useLazyContentSourcePreparation` is introduced to tell the
`AdsMediaSource` to prepare the content source immediately
to make the `Timeline` available.

A unit test verifies that in none of the cases (lazy preparation or
immediate preparation) a `Timeline` without ad data is leaked to the
caller. This ensures that in the case of a preroll, the player won't
initially and accidentally read content media data before starting to
load the preroll ad. While the content source is prepared early, no
content media period must be created before the preroll starts.

PiperOrigin-RevId: 696885392
2024-11-15 07:49:41 -08:00
rohks
c3d4a3d683 Set container MIME type for extractors in ts module
PiperOrigin-RevId: 696882489
2024-11-15 07:37:43 -08:00
ibaker
c50867c81d Release notes for 1.5.0-rc01
PiperOrigin-RevId: 696879276
2024-11-15 07:23:51 -08:00
rohks
68c0b8115f Set container MIME type for Flv, Matroska, and image extractors
Image extractors (e.g., `PngExtractor`) use `SingleSampleExtractor`
for extraction.

PiperOrigin-RevId: 696869657
2024-11-15 06:42:12 -08:00
dancho
11fc0871ac Implement video effects for Frame Extraction
Test that downscaled images match MediaMetadataRetriever.

PiperOrigin-RevId: 696862566
2024-11-15 06:09:55 -08:00
ybai001
c503a63878 Update comment for MPEG_CHANNEL_CONFIGURATION_MAPPING
ISO/IEC 23001-8 has been withdrawn. The corresponding definition for channel configuration is available in ISO/IEC 23091-3. Update the comment to reflect this change.
2024-11-15 16:31:52 +08:00
ybai001
3b7d19e94e
Merge pull request #13 from androidx/main
Merge from androidx/media main branch
2024-11-15 15:58:20 +08:00
ibaker
e6448f3498 Don't assume MP4 keyframe metadata is correct for CEA re-ordering
The content in Issue: androidx/media#1863 has every sample incorrectly marked as a
sync sample in the MP4 metadata, which results in flushing the
re-ordering queue on every sample, so nothing gets re-ordered, so the
subtitles are garbled.

There are currently two "uses" for this call on every keyframe:
1. It offers a safety valve if we don't read a `maxNumReorderSamples`
value from the video. Without this, the queue will just keep growing
and end up swallowing all subtitle data (similar to the bug fixed by
39c734963f).

2. When we do read (or infer) a `maxNumReorderSamples` it means we can
emit samples from the queue slightly earlier - but this is pretty
marginal, given i think the max possible value for
`maxNumReorderSamples` is 16, so the most benefit we would get is 16
frames (~0.53s at 30fps) - in most cases we will have more than 0.5s
of buffer ahead of the playback position, so the subtitles will still
get shown at the right time with no problem.

(1) is resolved in this change by setting the queue size to zero (no
reordering) if we don't have a value for `maxNumReorderSamples`.

(2) has minimal impact, so we just accept it.

We may be able to inspect the NAL unit to determine IDR vs non-IDR
instead - we will consider this as a follow-up change, but given the
minimal impact of (2) we may not pursue this.

#cherrypick

PiperOrigin-RevId: 696583702
2024-11-14 11:05:02 -08:00
rohks
2379d0f18c Set container MIME type for audio-only extractors
PiperOrigin-RevId: 696560053
2024-11-14 10:03:17 -08:00
rohks
93b4c6ef47 Set container MIME type in Mp4Extractor and FragmentedMp4Extractor
PiperOrigin-RevId: 696513199
2024-11-14 07:23:22 -08:00
bachinger
6453054878 Parse interstitials into HLS media playlist
PiperOrigin-RevId: 696454575
2024-11-14 03:09:12 -08:00
dancho
16a15b94ca Add Configration for Frame Extraction for specific SeekParameters
Expose ExoPlayer seek parameters via FrameExtractor API

PiperOrigin-RevId: 696449874
2024-11-14 02:48:05 -08:00
dancho
301ef207f2 Add pixel comparison to FrameExtractorTest
Add PSNR comparison with the output of MedaiMetadataRetriever.

PiperOrigin-RevId: 696190585
2024-11-13 10:43:36 -08:00
Copybara-Service
74611bbdc0 Merge pull request #1265 from DolbyLaboratories:dlb/ac4-level4/dev_new2
PiperOrigin-RevId: 696157037
2024-11-13 09:08:22 -08:00
ibaker
0f96ad247d Fix 'narrowing conversion' warning in FragmentedMp4Extractor
PiperOrigin-RevId: 696119977
2024-11-13 07:08:12 -08:00
tianyifeng
c3d4722197 Resolve the memory leaks in demo short-form app
Issue: androidx/media#1839
#cherrypick
PiperOrigin-RevId: 696080063
2024-11-13 04:42:47 -08:00
kimvde
9db66d6c6b Simplify bufferTimestampAdjustment handling
PiperOrigin-RevId: 696060496
2024-11-13 03:21:52 -08:00
kimvde
8393c2e445 Implement/document trivial methods in DefaultVideoSink
PiperOrigin-RevId: 696047835
2024-11-13 02:33:16 -08:00
dancho
175dca41df Read Bitmap in ExperimentalFrameExtractor
Add a MatrixTransformation GlEffect to flip between
OpenGL and Bitmap coordinates

PiperOrigin-RevId: 696029842
2024-11-13 01:27:02 -08:00
kimvde
64e92cb8e1 Various refactorings in VideoFrameRenderControl
- Rename a few methods/variable to improve readability.
- Refactor how video size changes are tracked. This will simplify the
  upcoming logic to refactor the timestamp logic in
  VideoFrameRenderControl because we will use the same logic for
  outputVideoSize and for outputStreamStartPositionUs.

PiperOrigin-RevId: 696026515
2024-11-13 01:15:58 -08:00
Googler
838c621d00 Add support to ApvC codec in Mp4Extractor
PiperOrigin-RevId: 695819965
2024-11-12 11:59:56 -08:00
sheenachhabra
59106bba1c Start muxer watchdog timer on export start
Earlier watchdog timer was started only after all the tracks are
added. For the cases where export is stuck before adding tracks,
the export would hang forever and will not timeout automatically.

With the changes, watchdog timer is started as soon as export is
started/resumed. Now if the pipeline is stuck before writing any samples,
it will timeout and report error to the caller.

Existing test case: c35a9d62ba/libraries/transformer/src/test/java/androidx/media3/transformer/MediaItemExportTest.java (L996)

PiperOrigin-RevId: 695781654
2024-11-12 10:19:13 -08:00
ibaker
19b38c83b6 Handle C.TIME_END_OF_SOURCE buffer timestamps in CeaDecoder
The behaviour was changed in 1.4.0 with 0f42dd4752,
so that the buffer timestamp is compared to `outputStartTimeUs` when
deciding whether to discard a "decode only" buffer before decoding
(instead of the deprecated/removed `isDecodeOnly` property). This breaks
when the buffer timestamp is `TIME_END_OF_SOURCE` (which is
`Long.MIN_VALUE`), because `TIME_END_OF_SOURCE < outputStartTimeUs` is
always true, so the end-of-stream buffer is never passed to the decoder
and on to `TextRenderer` where it is used to
[set `inputStreamEnded = true`](40f187e4b4/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/text/TextRenderer.java (L434-L436))
and so playback hangs.

#cherrypick

Issue: androidx/media#1863
PiperOrigin-RevId: 695767247
2024-11-12 09:38:44 -08:00
dancho
40f187e4b4 Block calls to FrameExtractor.release
Block calls to release() method, and handle
calls on the player looper or not.

PiperOrigin-RevId: 695697602
2024-11-12 05:49:19 -08:00
rohks
de0e08397e Add caching status APIs to MediaExtractorCompat
Implemented `getCachedDuration()` to provide an estimate of cached data in memory and `hasCacheReachedEndOfStream()` to indicate if the cache has reached the end of the stream.

Note: The Javadoc for the newly added methods closely follows that of the platform `MediaExtractor`. While the current implementation always uses a cache and therefore never returns `-1` from `getCachedDuration`, this leaves room for future changes should caching behavior or conditions evolve.
PiperOrigin-RevId: 695694460
2024-11-12 05:34:42 -08:00
dancho
ed288fca46 Experimental frame extraction based on ExoPlayer
A skeleton implementation that doesn't actually return decoded frames.
In the current state, we use ExoPlayer to seek to a position, and
ExoPlayer.setVideoEffects to record the presentation time selected.

Seeking and processing frames are synchronized via ListenableFuture
callbacks.

PiperOrigin-RevId: 695691183
2024-11-12 05:18:01 -08:00
Googler
4acd1b970c Make MediaSource.Factory a nullable parameter.
In some cases, callers will need to pass in both BitmapLoader and Codec.DecoderFactory without specifying a custom MediaSource.Factory. Omitting the annotation will result in NULL_FOR_NONNULL_TYPE compilation errors in Kotlin.

PiperOrigin-RevId: 695481606
2024-11-11 14:26:14 -08:00
claincly
01c784775e Rename signalEndOfInputStream()
This is because the method is called for each media item, the current name
implies that the method is only called once for all videos in a sequence.

PiperOrigin-RevId: 695332916
2024-11-11 07:44:15 -08:00
bachinger
fa790bd73c Fix supportedCommands in MediaMetadata
#cherrypick

PiperOrigin-RevId: 695304782
2024-11-11 06:07:02 -08:00
michaelkatz
0c982a7994 Fix error where non-enabled renderers were set to final
If the renderer has no stream, then it should not be called with `setCurrentStreamFinal` on it.

PiperOrigin-RevId: 695284225
2024-11-11 04:35:25 -08:00
ivanbuper
f84f44bf6b Implement static method to return estimated output samples
This method will replace the current estimation mechanism in place on
`getSpeedAdjustedTimeAsync()`, which relies on heuristics from previous
output rates and depends on the state of the `AudioProcessor`. The new
static method should be considerably more accurate.

PiperOrigin-RevId: 694607264
2024-11-08 12:54:47 -08:00
ibaker
53953dd377 Re-define 'max size' of SEI queue to operate on unique timestamps
This ensures it works correctly when there are multiple SEI messages per
sample and the max size is set from e.g. H.264's
`max_num_reorder_frames`.

PiperOrigin-RevId: 694526152
2024-11-08 09:20:57 -08:00
michaelkatz
b329806859 Fix unit test that confirms AAC_LC passthrough is unsupported
PiperOrigin-RevId: 694494792
2024-11-08 07:49:11 -08:00
tianyifeng
2b54b1ebbe Release internal components on preload thread in DefaultPreloadManager
The `RendererCapabilities` and `TrackSelector` objects are accessed on the preload thread during the preloading, when releasing them, they need to be released on the same thread. Otherwise, it is possible that they have already released on the application thread, while the PreloadMediaSource still tries to access them on the preload thread before the source is released.

#cherrypick

PiperOrigin-RevId: 694173131
2024-11-07 10:50:22 -08:00
rohks
91dfaab93f Add getDrmInitData() API to MediaExtractorCompat
Note: `androidx.media3.common.DrmInitData` is returned instead of the platform `android.media.DrmInitData` because the platform class has a package-private constructor, making it impossible to implement the abstract class outside the platform.
PiperOrigin-RevId: 694096542
2024-11-07 06:33:43 -08:00
Copybara-Service
19d71266ec Merge pull request #1277 from consp1racy:patch-1
PiperOrigin-RevId: 694095488
2024-11-07 06:28:45 -08:00
Eugen Pechanec
b956861442 Git: Ignore .cxx intermediate output directory
This is produced by CMake e.g. in FFmpeg extension.
The directory is created in projectDir rather than buildDir.
2024-11-07 10:56:36 +00:00
shahddaghash
f6116a121a Add functionality for Choose local file button
Added the functionality for `Choose local file` button to be able to go and select a local file from the device. The file was then displayed by using ExoPlayer inside PlayerView.

PiperOrigin-RevId: 693756565
2024-11-06 09:36:08 -08:00
shahddaghash
7b9cfd1964 Add basic UI to effect demo app
Added a basic UI to the effect demo app, including a PlayerView, buttons to choose preset input and choose local file, and a button to apply effects. The buttons are currently not implemented, and the app will show a snackbar message when they are clicked.

PiperOrigin-RevId: 693751272
2024-11-06 09:18:44 -08:00
shahddaghash
d164ce221a Change lib-common dependency scope to api in lib-ui
Changed the dependency of `lib-common` in `lib-ui` from an `implementation` dependency to an `api` dependency. This change ensures that classes and interfaces from `lib-common` used in the public API of `lib-ui` are correctly exposed to consumers of `lib-ui`.

For example, `PlayerView implements AdViewProvider` where the latter is from `androidx.media3.common.AdViewProvider`.

PiperOrigin-RevId: 693734349
2024-11-06 08:21:16 -08:00
rohks
3d51b36e99 Fix wrong class name in error message of MediaExtractorCompatTest
PiperOrigin-RevId: 693685232
2024-11-06 05:08:57 -08:00
dancho
31ae260db4 Support alternative sample MIME types in MuxerWrapper
Enable transformer to transmux into alternative sample MIME types.
For example, some Dolby Vision profiles have a backwards-compatible
AVC or HEVC layer. MV-HEVC is backwards compatible with HEVC.

This change enables Transformer to transmux into the backwards compatible
format to improve compatibility with legacy APIs such as
MediaMetadataRetriever.

PiperOrigin-RevId: 693667597
2024-11-06 03:58:02 -08:00
dancho
f9fd8badec Prevent repeated release of the same EGLContext
All instances of PlaybackVideoGraphWrapper use the same
VIDEO_FRAME_PROCESSOR_FACTORY_SUPPLIER which uses the same
DefaultGlObjectsProvider.

Each call to DefaultGlObjectsProvider.release() releases
all previously created EGLContexts.

Lazily create a new DefaultGlObjectsProvider for each
DefaultVideoFrameProcessor (not one per factory).

PiperOrigin-RevId: 693658458
2024-11-06 03:18:42 -08:00
ibaker
ab723fc8ff Mark every media3-cues sample as a keyframe
This format doesn't require any context from previous samples, so every
sample is a keyframe.

PiperOrigin-RevId: 693658370
2024-11-06 03:15:59 -08:00
tonihei
5336d71c22 Add TestPlayerRunHelper run(player).untilFullyBuffered
This simplifies some common test setup steps that rely on
a fully buffered player before making further test progress.

PiperOrigin-RevId: 693651493
2024-11-06 02:45:51 -08:00
Googler
08470140ac Internal
PiperOrigin-RevId: 693418471
2024-11-05 11:24:39 -08:00
tonihei
d38aba92fe Fix flakiness in HlsPlaybackTest and DashPlaybackTest
The tests became more flaky after 76e4abe428, likely because playback
was able to start slightly earlier, exaggerating any existing race
conditions. Fix the flakiness by letting all tests with subtitle
parsing wait until all data is fully loaded before starting to play.

PiperOrigin-RevId: 693380656
2024-11-05 09:42:53 -08:00
michaelkatz
28b75f7d29 Schedule doSomeWork after setVideoOutput
PiperOrigin-RevId: 693374521
2024-11-05 09:22:17 -08:00
ibaker
286273c10e Enable lint in tests for modules that require no fixes
PiperOrigin-RevId: 693313908
2024-11-05 05:40:03 -08:00
tonihei
12cb803486 Compat logic for MediaController.getCustomLayout
When a new media session sets media button preferences, we need to
"translate" them back to a custom layout to ensure the user preferences
are represented as closely as possible when the controller uses the
old button placement rules.

PiperOrigin-RevId: 693306153
2024-11-05 05:06:47 -08:00
tonihei
9fb4ed91b6 Make getCustomLayoutFromMediaButtonPreferences side-effect free
The method currently modifies the input Bundle, but it's easier to
reason about it if the method is side-effect free and the places
that need to modify a Bundle do this after calling the method.

PiperOrigin-RevId: 693288031
2024-11-05 03:54:40 -08:00
tonihei
76e4abe428 Reduce default values for DefaultLoadControl buffer for playback
PiperOrigin-RevId: 693284512
2024-11-05 03:39:16 -08:00
ibaker
6f81b5792b Add 'session extras' and MediaMetadata.durationMs to the stable API
PiperOrigin-RevId: 693279664
2024-11-05 03:17:06 -08:00
ibaker
212bd943f5 Fix lint violations in MediaExtractorCompatTest
I missed these as part of aebf822c3c

PiperOrigin-RevId: 692982120
2024-11-04 08:51:47 -08:00
rohks
278eaf47ad Add log session ID APIs to MediaExtractorCompat
Implemented `setLogSessionId(LogSessionId)` and `getLogSessionId()` methods.

Note: The `LogSessionId` is currently **not** forwarded to `MediaParser`, but this will be addressed once `MediaParser` can be used to configure `MediaExtractorCompat`.
PiperOrigin-RevId: 692945255
2024-11-04 06:38:38 -08:00
jbibik
0270267e08 Add ShuffleButtonState and RepeatButtonState to ui-compose
* Provide a helper Composable for remembering the state instance and launching the listening coroutine that observes the changes in the Player
* Add an example to demo-compose of using Shuffle- and Repeat- ButtonStates inside a Shuffle- and RepeatButton Composable.
* Reformat the MainActivity usage of `Shuffle` and `Repeat` buttons to form extra Player Controls and combine Prev/Play-Pause/Next with Shuffle/Repeat (Minimal controls + Extra controls = `PlayerControls`)

PiperOrigin-RevId: 692939825
2024-11-04 06:12:57 -08:00
rohks
261ca326c5 Move MediaExtractorCompatTest from test/ to androidTest/
The test has been moved to an instrumentation test as it relies on APIs that vary by SDK version. Robolectric’s emulation lacks sufficient realism in some cases, which impacts test accuracy. By using an instrumentation test, we ensure that the tests run in a real Android environment, providing reliable results for SDK-dependent APIs.

PiperOrigin-RevId: 692933259
2024-11-04 05:42:45 -08:00
ibaker
aebf822c3c Enable lint errors in exoplayer tests
Follow-up to 76db936d68

PiperOrigin-RevId: 692925617
2024-11-04 05:08:34 -08:00
sheenachhabra
c3e72a87e5 Make error messages unique in Boxes.java
PiperOrigin-RevId: 692920946
2024-11-04 04:49:27 -08:00
ibaker
af1c13524c Enable lint in lib-common tests
Also move the `lint.xml` config which disables the `NewApi` check from
`lib-session` to the existing top-level file, and limit it to cover all
Robolectric tests by path matching.

Follow-up to 76db936d68

PiperOrigin-RevId: 692913646
2024-11-04 04:16:39 -08:00
shahddaghash
a8ed6494c3 Create an empty module for Effect demo
The new demo module aims to showcase different `Effect` capabilities.

PiperOrigin-RevId: 692912895
2024-11-04 04:12:55 -08:00
dancho
bd90ef38b0 MCVR.hasSurfaceForCodec considers VideoSink
This change keeps getSurfaceForCodec and hasSurfaceForCodec in sync.
Before this change when ExoPlayer is configured with setVideoEffects
and no display surface, codecs that need Set Output Surface Workaround
would not be initialized because hasSurfaceForCodec always returns false

PiperOrigin-RevId: 692900899
2024-11-04 03:17:54 -08:00
Copybara-Service
aebda58314 Merge pull request #1829 from colinkho:ssms-custom-exec-2
PiperOrigin-RevId: 692897382
2024-11-04 03:02:46 -08:00
rohks
d8ad18383d Optimize sample metadata handling in MediaExtractorCompat
Restructured sample metadata management in `MediaExtractorCompat` to maintain a queue of `SampleMetadata` objects, each containing `timeUs`, `flags`, `size`, and `trackIndex`, rather than storing only track indices.

Introduced a pooling mechanism for `SampleMetadata` to reduce object allocation and improve memory efficiency. The new `SampleMetaDataQueue` centralizes metadata for easier access and management, eliminating the need to allocate a buffer or peek into the `SampleQueue` to retrieve sample metadata.

This change does not change existing behavior, it optimizes the internal structure, and existing tests already verify the relevant APIs.

PiperOrigin-RevId: 692285581
2024-11-01 14:07:11 -07:00
ivanbuper
7edbaa3f2c Use sample-aligned speed changes in SpeedChangingAudioProcessor
This CL adds utility methods to obtain sample-aligned timestamps from a
`SpeedProvider` to solve in a unified way the issues arising from
conversions between sample positions and microseconds.

The new utility methods will also help with the implementation of a
static method like `getDurationAfterProcessorApplied()` that will remove
the need for thread synchronization between the video and audio pipeline
for speed changing effects.

PiperOrigin-RevId: 692233318
2024-11-01 11:09:18 -07:00
ivanbuper
0b1695124b Bump Media3 to 1.5.0-rc01
#cherrypick

PiperOrigin-RevId: 692221696
2024-11-01 10:30:45 -07:00
tonihei
06718c5df3 Fix position tracking bug for inaccurate audio processors
If audio processors report a drifting position, we currently update
the media position parameters to correct this drift. However, this
means we pass in the wrong value to
audioProcessorChain.getMediaDuration, which reuqires the time since
the last flush.

To fix this problem, we can instead save the drift seperately and
apply it where needed.

PiperOrigin-RevId: 692202219
2024-11-01 09:28:15 -07:00
claincly
b0c6106882 Fix trim optimization logic when edit lists exist
Also makes muxer shift the first video timestamp to zero, if it's not.

The trim position should respect the media timeline.

For example in a video that is 10s long (without edit list), if an edit list
adds 1_000ms to each video sample, and trimming 100ms, here's the expected:

- The video duration is 10.9s (`10s + 1s edit - 0.1s trim`)
- The first video frame time would be at 0.9s (`1s edit - 0.1s trim`)

PiperOrigin-RevId: 692187399
2024-11-01 08:36:08 -07:00
ivanbuper
38e1efafc2 Prepare RELEASENOTES.md for Media3 1.5.0-rc01 release
This change also fixes two notes added incorrectly onto the previous
beta01 release section.

#cherrypick

PiperOrigin-RevId: 692169335
2024-11-01 07:32:10 -07:00
Copybara-Service
4910b2cdc0 Merge pull request #1225 from Kekelic:support-for-parsing-rtsp-packets-with-header-extension
PiperOrigin-RevId: 692156233
2024-11-01 06:35:16 -07:00
tonihei
544d7aa2dc Annotate parameters in RepeatModeUtil
PiperOrigin-RevId: 692129684
2024-11-01 04:33:31 -07:00
jbibik
1b302e879a Add PreviousButtonState and NextButtonState to ui-compose
* Provide a helper Composable for remembering the state instance and launching the listening coroutine that observes the changes in the Player
* Add an example to demo-compose of using Previous- and Next- ButtonStates inside a Previous- and NextButton Composable.
* Reformat the MainActivity usage of `Previous`, `PlayPause`, `Next` buttons to form Minimal Player Controls

PiperOrigin-RevId: 691943147
2024-10-31 14:52:38 -07:00
jbibik
676a3872a5 Add PlayPauseButtonState to ui-compose
Provide a helper Composable for remembering the state instance and launching the listening coroutine that observes the changes in the Player

Add an example to demo-compose of using PlayPauseButtonState inside a PlayPauseButton Composable.

The smart State object has been deemed a preferred solution over collecting Flows due to queuing/timing/buffering limitations. Instead, it uses the new `Player.listen` suspending extension function to catch the relevant events.

PiperOrigin-RevId: 691879975
2024-10-31 11:42:14 -07:00
ivanbuper
f991e1f023 Move release note in 1.5.0-beta01 to "Unreleased" section
The item was incorrectly added to the beta01 section in 2a49ffcb23.

#cherrypick

PiperOrigin-RevId: 691876672
2024-10-31 11:34:46 -07:00
microkatz
0e020f778c Added release note 2024-10-31 12:42:03 +00:00
microkatz
71bc177e39 Format with google-java-format 2024-10-31 11:57:47 +00:00
Kekelic
47f4dabb98 Fix documentation and improve readability of unit test with rtpDataWithHeaderExtension example 2024-10-31 11:57:47 +00:00
Kekelic
84f8beb884 Remove saving unnecessary header extension data 2024-10-31 11:57:47 +00:00
Kekelic
5a8a250a1a Add reading length of extension payload from header extension in RtpPacket 2024-10-31 11:57:47 +00:00
Kekelic
1ecb7c3fbf Format changes 2024-10-31 11:57:46 +00:00
Kekelic
7dd428534e Change RtpPacket class to parse header extension if exists 2024-10-31 11:57:46 +00:00
ibaker
b5db8a6cbe Fix empty LoadEventInfo.uri passed to onLoadStarted
This affects both `AnalyticsListener` and `MediaSourceEventListener`

This was introduced by d051b4b993

Also fix a missing 'load started' event for HLS media playlists (this
was also introduced by d051b4b993).

PiperOrigin-RevId: 691580183
2024-10-30 15:52:48 -07:00
jbibik
6cbf77b3f0 Refactor shouldEnablePlayPauseButton out of PlayerControlView into Util
PiperOrigin-RevId: 691502799
2024-10-30 11:59:25 -07:00
rohks
2b27e33784 Refactor OpusDecoderTest to use OpusLibrary.isAvailable()
Replaced the custom `LibraryLoader` instance with `OpusLibrary.isAvailable()` to verify the library loading. This simplifies the code by leveraging the existing library loading mechanism.

#cherrypick

PiperOrigin-RevId: 691457871
2024-10-30 10:02:46 -07:00
ivanbuper
2a9963424b Refactor readBytes to readFrames in SpeedChangingAudioProcessor
Using frames instead of bytes helps simplify the processing logic for
the component and will help with the move towards sample-based
`SpeedProvider`s. By using frames, we also abstract away the complexity
related to sample encoding.

This is a non-functional refactor.

PiperOrigin-RevId: 691444106
2024-10-30 09:21:54 -07:00
rohks
15583f7c64 Set track duration in AmrExtractor, Mp3Extractor and OggExtractor
PiperOrigin-RevId: 691433024
2024-10-30 08:46:45 -07:00
ibaker
0462349902 Deprecate NalUnitUtil.isNalUnitSei(String, byte)
The overload that takes a `Format` is preferred, because it can detect
SEI NAL units in Dolby Vision tracks too.

Submitting this in a separate change so we can avoid cherry-picking it
into `1.5.0-rc01`, otherwise it would naturally be part of
27371db225.

Issue: androidx/media#1820
PiperOrigin-RevId: 691408725
2024-10-30 07:21:26 -07:00
rohks
df07fa35d8 Populate MediaFormat.KEY_DURATION in MediaExtractorCompat
Previously, `getTrackFormat()` in `MediaExtractorCompat` returned a `MediaFormat` without setting `MediaFormat.KEY_DURATION`. With this change:

- `MediaFormat.KEY_DURATION` is set based on the track's duration, if available.
- If the track duration is unset, the duration from the seek map is used as a fallback.
- When neither duration is set, `MediaFormat.KEY_DURATION` remains unset.

This ensures that `MediaFormat.KEY_DURATION` is populated when possible, enhancing duration information availability.

PiperOrigin-RevId: 691395114
2024-10-30 06:29:47 -07:00
rohks
27de9f02e0 Add missing DefaultRenderersFactoryTest for decoder extensions
Added `DefaultRenderersFactoryTest` for `IAMF`, `AV1`, and `MIDI` decoder extensions.

PiperOrigin-RevId: 691381816
2024-10-30 05:33:38 -07:00
ibaker
27371db225 Support CEA-608 subtitles in Dolby Vision
Issue: androidx/media#1820

#cherrypick

PiperOrigin-RevId: 691378476
2024-10-30 05:18:14 -07:00
ibaker
08a141328d Mark ProgressiveMediaSource.setSuppressPrepareError package-private
This method will likely be removed in the next release, and is currently
only needed from within the `source` package.

#cherrypick

PiperOrigin-RevId: 691351449
2024-10-30 03:19:14 -07:00
rohks
129cf8ea72 Use assumeTrue for libiamf availability check in IamfDecoderTest
This change ensures that the test uses `assumeTrue` to avoid failures when the `libiamf` library is not pre-built.

#cherrypick

PiperOrigin-RevId: 691333564
2024-10-30 02:01:05 -07:00
kimvde
96b923b610 Fix failing portrait export test
The logic in assumeFormatsSupported assumes that portrait videos are
always rotated before encoding but that's not the case when portrait
encoding is enabled.

PiperOrigin-RevId: 691042881
2024-10-29 09:10:59 -07:00
kimvde
14094b5094 Fix offset passed to VideoFrameReleaseControl
In some cases, the streamOffsetUs was passed to
VideoFrameReleaseControl.getFrameReleaseAction() but it should be the
streamStartPositionUs.

PiperOrigin-RevId: 691040172
2024-10-29 09:04:35 -07:00
shahddaghash
7f94aaf49f Remove deprecated SingleFrameGlShaderProgram class.
Class was deprecated in Media3 1.2.0. Use BaseGlShaderProgram class instead.

PiperOrigin-RevId: 691001562
2024-10-29 06:53:45 -07:00
tonihei
7c0cffdca8 Improve position estimate when transitioning to another checkpoint
When transitioning to the next media position parameter checkpoint
we estimate the position because the audio processor chain no longer
provides access to the actual playout duration.

The estimate using the declared speed and the last checkpoint may
have drifted over time, so we currently estimate relative to the
next checkpoint, which is closer and presumably provides a better
estimate. However, this assumes that these checkpoint are perfectly
aligned without any position jumps.

The current approach has two issues:
 - The next checkpoint may include a position jump by design, e.g.
   if it was set for a new item in the playlist and the duration of
   the current item wasn't perfectly accurate.
 - The sudden switch between two estimation methods may cause a jump
   in the output position, which is visible when we add new media
   position checkpoints to the queue, not when we actually reach the
   playback position of the checkpoint.

We can fix both issues by taking a slightly different approach:
 - Continuously monitor the estimate using the current checkpoint. If
   it starts drifting, we can adjust it directly. This way the estimate
   is always aligned with the actual position.
 - The change above means we can safely switch to using the estimate
   based on the previous checkpoint. This way we don't have to make
   assumptions about the next checkpoint and any position jumps will
   only happen when we actually reach this checkpoint (which is more
   what a user expects to see, e.g. at a playlist item transition).

Issue: androidx/media#1698
PiperOrigin-RevId: 690979859
2024-10-29 05:31:25 -07:00
shahddaghash
7a8f05f736 Remove deprecated Transformer methods/variables
Removed deprecated `Transformer.PROGRESS_STATE_NO_TRANSFORMATION`, `Transformer.setListener`, and `Transformer.startTransformation` from Transformer.

PiperOrigin-RevId: 690971992
2024-10-29 05:02:31 -07:00
rohks
b1f2efd218 Fix handling of cues that exceed total duration in MatroskaExtractor
Adjusted logic to accurately calculate sizes and durations for the last valid cue point when cue timestamps are greater than the total duration.

Fixes the issue where the reported duration of the MKV file was greater than the total duration specified by the duration element. Verified this using `mkvinfo` and `mediainfo` tools.

PiperOrigin-RevId: 690961276
2024-10-29 04:18:38 -07:00
shahddaghash
51055d7e79 Remove deprecated TransformationException class.
Class was deprecated in Media3 1.1.0. Use `ExportException` class instead.

PiperOrigin-RevId: 690955853
2024-10-29 03:59:15 -07:00
dancho
b0df3b2da3 DecodeOneFrameUtil: do not access release players
A player that is being released may report an error with
null `player.getPlayerError()`. Do not try to read errors
of players that are released.

PiperOrigin-RevId: 690953083
2024-10-29 03:48:29 -07:00
shahddaghash
dd6e88889a Remove deprecated TransformationResult class.
Class was deprecated in Media3 1.1.0. Use `ExportResult` class instead.

PiperOrigin-RevId: 690694730
2024-10-28 11:51:02 -07:00
ivanbuper
7e7764de5e Implement getExpectedFrameCountAfterProcessorApplied() in Sonic
This method allows `Sonic` to statically and accurately report the
expected number of output frames for any given parameter configuration.

This change is required prework for `SpeedChangingAudioProcessor` to
implement a similar static method and allow precise, non-blocking
timestamp adjustments for the experimental speed changing effect.

PiperOrigin-RevId: 690669627
2024-10-28 10:43:03 -07:00
shahddaghash
772bd20f7d Remove deprecated Transformer.Listener methods
Methods were deprecated between Media3 1.0.0 and Media3 1.1.0.

PiperOrigin-RevId: 690656410
2024-10-28 10:09:20 -07:00
dancho
4601a42aee Use PSNR for comparing equality in one HDR10 test
Also, fix incorrectly stretched golden file that was not detected with previous, less sensitive, average pixel error comparison.

PiperOrigin-RevId: 690643520
2024-10-28 09:38:34 -07:00
dancho
e67660f326 Consistently use highp for HDR colors
GLSL 3.00 mediump doesn't require enough precision near zero
to correctly represent PQ colors where the [0, 1] GL range
represents [0, 10_000] nits.
This caused `noEffects_hlg10InputAndHdr10Output_matchesGoldenFile`
to fail on some devices.

PiperOrigin-RevId: 690627069
2024-10-28 08:50:19 -07:00
dancho
fe14525a97 Use ExoPlayer in DecodeOneFrameUtil
The old code that uses MediaCodec directly has a race condition
that causes the decoder to incorrectly crop the decoded picture.

PiperOrigin-RevId: 690620868
2024-10-28 08:32:23 -07:00
shahddaghash
9e088ac2b8 Remove 7 deprecated Transformer.Builder methods
PiperOrigin-RevId: 690617093
2024-10-28 08:19:41 -07:00
dancho
5bb9e1a932 UltraHDR Ovelays: remove texture unit clash
Do not use the same texture unit for two different textures.
This was causing tests to fail on multiple devices.

PiperOrigin-RevId: 690610094
2024-10-28 07:59:09 -07:00
ivanbuper
9653fa64fe Move calculateAccumulatedTruncationErrorForResampling() to Sonic
This is prework for `Sonic` to provide a precise calculation of output
sample counts, and thus allow `SpeedChangingAudioProcessor` to replace
`getSpeedAdjustedTimeAsync()` with an accurate synchronous calculation.

This is a non functional change.

PiperOrigin-RevId: 690608520
2024-10-28 07:53:44 -07:00
rohks
a913a16db4 Add ignore exclusions for IAMF and MPEG-H decoder extensions
PiperOrigin-RevId: 690537114
2024-10-28 03:12:51 -07:00
rohks
84ab67cca3 Fix .gitignore paths for extensions
#cherrypick

PiperOrigin-RevId: 690534708
2024-10-28 03:03:42 -07:00
ibaker
39c734963f H264Reader: Add missing propagation of max_num_reorder_frames
This method is already called below in the
`else if (sps.isCompleted())` block which applies when
`hasOutputFormat == true`, but this is only ever entered if we are
parsing SPS and PPS NAL units **after** we've emitted a format, which
is only the case if
`DefaultTsPayloadReaderFactory.FLAG_DETECT_ACCESS_UNITS` is set (which
it isn't by default).

The equivalent call in `H265Reader` is already inside the
`if (!hasOutputFormat)` block, so doesn't need a similar fix.

#cherrypick

PiperOrigin-RevId: 689809529
2024-10-25 09:13:53 -07:00
tonihei
a15571c8ee Add logic to convert button preferences to custom layout in sessions
This ensures that media button preferences with slots for BACK,
FORWARD and OVERFLOW are converted to the legacy custom layout
according to the implicit placement rules. This has to happen
when populating the MediaSessionCompat and when generating the
notification for older APIs. Sending these preferences to older
Media3 controllers can filter them down to the custom layout the
session would have used previously, but there is no need to adjust
the reservation extras because the older Media3 custom layout has
no concept of these extras.

PiperOrigin-RevId: 689761637
2024-10-25 06:09:21 -07:00
tonihei
8811b454bb Use slots to fill in default compact view indices
This is equivalent to the current logic, but also allows to easily pick
up other buttons that use the BACK, CENTRAL or FORWARD slots for the
compact view by default.

Also fix the test to actually assert the output order.

PiperOrigin-RevId: 689747844
2024-10-25 05:12:07 -07:00
tonihei
0fdc930444 Use media button preferences in session demo app
PiperOrigin-RevId: 689738802
2024-10-25 04:34:21 -07:00
tonihei
aeb9d12a16 Add logic to convert custom layout to button preferences in controller
This ensures that the slots are set according to the implicit placement
rules for the legacy custom layouts. This has to be done when receiving
custom actions from a MediaControllerCompat and for Media3 sessions
setting the custom layout field.

PiperOrigin-RevId: 689737951
2024-10-25 04:29:05 -07:00
Copybara-Service
d051b4b993 Merge pull request #1768 from colinkho:onloadstarted-retry
PiperOrigin-RevId: 689728139
2024-10-25 03:51:35 -07:00
rohks
e25bc2c1a5 Populate track duration(s) in Avi, Flac and Wav extractors
PiperOrigin-RevId: 689716977
2024-10-25 03:03:08 -07:00
Ian Baker
683a5b8403 Fix review comments 2024-10-25 10:58:48 +01:00
Ian Baker
7d3e6b0f27 Add release note 2024-10-25 10:58:29 +01:00
Ian Baker
37f795ea82 Try removing duplicate calls 2024-10-25 10:58:29 +01:00
Ian Baker
7cf500bc2d Fix compilation of MergingMediaSourceTest 2024-10-25 10:58:29 +01:00
Ian Baker
473a4a7680 Call both old and new onLoadStarted methods in DefaultAnalyticsCollector 2024-10-25 10:58:29 +01:00
Ian Baker
d3298391b2 Fix onLoadStarted event accumulation in DefaultAnalyticsCollectorTest 2024-10-25 10:58:29 +01:00
Ian Baker
b565f47d18 Reformat with google-java-format 2024-10-25 10:58:29 +01:00
Ian Baker
e7f5d4d441 Remove backwards-compat onLoadStarted from MediaSourceEventListener 2024-10-25 10:58:29 +01:00
Colin Kho
5581529bfb Use long as test constant for DefaultAnalyticsCollectorTest 2024-10-25 10:58:28 +01:00
Colin Kho
16f65cee58 Deprecate onLoadStarted w/o retry count on AnalyticsListener 2024-10-25 10:58:28 +01:00
Colin Kho
5dbe1efa9e Treat onLoadStarted with retryCount to be a new method rather than redirect calls 2024-10-25 10:58:28 +01:00
Colin Kho
8adcd35c75 Incorporate MediaSourceEventListener.onLoadStarted with retryCount parameter with backward compatibility 2024-10-25 10:58:25 +01:00
Colin Kho
2d05744333 Add DownloadExecutor to SingleSampleMediaSource 2024-10-24 11:59:35 -07:00
samrobinson
45317394da Implement constant power matrices for channel mixing to mono & stereo.
Calculations are derived from the android platform channel mixing.

PiperOrigin-RevId: 689427658
2024-10-24 10:23:07 -07:00
dancho
d2fb779929 Ignore flaky preview tests
Tests are flaky because shader program creation is slow

PiperOrigin-RevId: 689420905
2024-10-24 10:04:57 -07:00
Rohit Kumar Singh
af1b5b5102 Merge Issue: androidx/media#1826: add extension for MPEG-H decoding
Imported from GitHub PR https://github.com/androidx/media/pull/1826

Merge 6b59a1602b022ebc44411ae3440e274c51c223a7 into b5615d5e919b297def6450b45320a3165c34548c

COPYBARA_INTEGRATE_REVIEW=https://github.com/androidx/media/pull/1826 from androidx:mpegh_extension 6b59a1602b022ebc44411ae3440e274c51c223a7
PiperOrigin-RevId: 689417378
2024-10-24 09:53:45 -07:00
ibaker
757f223d8a Remove // Do nothing overrides from EventLogger
These methods are marked `default` on the `AnalyticsListener` interface
with an empty implementation, so there's no need to override them just
to re-define the empty implementation.

PiperOrigin-RevId: 689416584
2024-10-24 09:50:54 -07:00
dancho
94e37ca7d2 Fix flaky test
* Use asset without audio for independent video time progress
* Use longer item duration to avoid ExoPlayer STATE_READY workaround
  (see https://github.com/google/ExoPlayer/issues/1874)

PiperOrigin-RevId: 689409427
2024-10-24 09:29:35 -07:00
michaelkatz
2f198c4c06 Implement getDurationToProgressUs for DecoderAudioRenderer
Add `DecoderAudioRenderer.getDurationToProgressUs()` so that `ExoPlayer`, if set with `experimentalSetDynamicSchedulingEnabled()`, will dynamically schedule its main work loop to when the `DecoderAudioRenderer` can make progress.

PiperOrigin-RevId: 689377247
2024-10-24 07:46:42 -07:00
sheenachhabra
f181855c5e Add an API to disable sample batching in Mp4Muxer
Mp4Muxer caches the samples and then writes them in batches.
The new API allows disabling the batching and writing sample
immediately.

PiperOrigin-RevId: 689352771
2024-10-24 06:15:25 -07:00
ibaker
b36de302f7 Remove some un-needed proguard-rules.txt symlinks
PiperOrigin-RevId: 689344803
2024-10-24 05:43:59 -07:00
rohks
5f99955f31 Make minor improvements for IAMF decoder module
- Create `LibiamfAudioRenderer` with `DefaultRenderersFactory` in `ExoPlayerModuleProguard`.
- Remove redundant library availability check from `IamfModuleProguard`.
- Move `proguard-rules.txt` to the root folder.
- Removed unused `cryptoType` parameter from `setLibraries()` method in `IamfLibrary`.
- Added log when `LibiamfAudioRenderer` is loaded in `DefaultRenderersFactory`.
- Annotated missing classes with `@UnstableApi`.
- Check for library availability and throw exception in `IamfDecoder` constructor.

#cherrypick

PiperOrigin-RevId: 689330016
2024-10-24 04:46:13 -07:00
ibaker
4a406be1bf DataSourceContractTest: Tighten assertions around 'not found' URIs
This change:
1. Updates `DataSourceContractTest` to allow multiple "not found"
   resources, and to include additional info (e.g. headers) on them.
2. Updates the contract test to assert that `DataSource.getUri()`
   returns the expected (non-null) value for "not found" resources
   between the failed `open()` call and a subsequent `close()` call.
   The `DataSource` is 'open' at this point (since it needs to be
   'closed' later), so `getUri()` must return non-null.
    * This change also fixes some implementations to comply with this
      contract. It also renames some imprecisely named `opened`
      booleans that **don't** track whether the `DataSource` is open
      or not.
3. Updates the contract test assertions to enforce that
   `DataSource.getResponseHeaders()` returns any headers associated
   with the 'not found' resource.
4. Configures `HttpDataSourceTestEnv` to provide both 404 and "server
   not found" resources, with the former having expected headers
   associated with it.

PiperOrigin-RevId: 689316121
2024-10-24 03:49:27 -07:00
kimvde
d25a423888 Remove unused parameter from VideoFrameRenderControl.FrameRenderer
PiperOrigin-RevId: 689281689
2024-10-24 01:27:20 -07:00
Copybara-Service
b5615d5e91 Merge pull request #1794 from stevemayhew:p-fix-ntp-time-update-main
PiperOrigin-RevId: 689121191
2024-10-23 15:22:22 -07:00
jbibik
7da71f792b Move PlayerSurface from demo-compose to media3-ui-compose
The new `media3-ui-compose` module does not have any Material library dependencies, but will rely on androidx.compose + foundation/runtime/ui and other non-theming/opinionated libraries. Surface handling is one such usecase.

PiperOrigin-RevId: 689004504
2024-10-23 10:00:07 -07:00
tonihei
21526588be Removed unused constructor
PiperOrigin-RevId: 688960856
2024-10-23 07:50:31 -07:00
michaelkatz
f11130d59e Move setVideoOutput message handling to playback thread
PiperOrigin-RevId: 688951964
2024-10-23 07:16:50 -07:00
tonihei
dfb7636138 Suppress not-applicable lint warning
PiperOrigin-RevId: 688948857
2024-10-23 07:11:58 -07:00
ibaker
7b66209bca Add missing overrides in DefaultTrackSelector.Parameters.Builder
Also add a test for this to avoid missing any others in future. Also
flesh out the existing test for the deprecated builder, to assert the
return type is correctly updated.

PiperOrigin-RevId: 688948768
2024-10-23 07:08:03 -07:00
kimvde
75f29b6997 Implement DefaultVideoSink.render()
PiperOrigin-RevId: 688948682
2024-10-23 07:05:53 -07:00
michaelkatz
e5133e78f5 Abstract EPII renderer logic into holder class
Move EPII calls to renderers to a separate managing class.

PiperOrigin-RevId: 688932712
2024-10-23 06:04:21 -07:00
rohks
e677c8dccd Fix duration calculation for AVI files
The duration is now correctly calculated as the maximum of all track durations, instead of being overwritten by the last track's value. This aligns with how other Extractor implementations handle durations for multiple tracks.

PiperOrigin-RevId: 688896743
2024-10-23 03:34:04 -07:00
bachinger
0038dda3c3 Fix flakiness of MediaBrowserListenerWithMediaBrowserServiceCompatTest
PiperOrigin-RevId: 688870397
2024-10-23 01:54:22 -07:00
kimvde
6c932722e4 Set release control frame rate in DefaultVideoSink
All the calls to the VideoFrameReleaseControl and
VideoFrameRenderControl will be moved to the DefaultVideoSink

PiperOrigin-RevId: 688857699
2024-10-23 01:06:04 -07:00
tonihei
e43720b83a Remove deprecated MediaSession.getSessionCompatToken
And also the deprecated override of
ControllerInfo.createTestOnlyControllerInfo using the
androidx.media classes.

PiperOrigin-RevId: 688625484
2024-10-22 11:30:28 -07:00
jbibik
a645f704b8 Create a new media3-ui-compose module
It will be used for UI components built with Jetpack Compose

`implementation("androidx.media3:media3-ui-compose:1.X.Y")`

PiperOrigin-RevId: 688595899
2024-10-22 10:14:26 -07:00
ivanbuper
36a5a83b22 Implement parameterized testing for SpeedChangingAudioProcessor
`RandomParameterizedSpeedChangingAudioProcessorTest` follows the
structure of `RandomParameterizedSonicAudioProcessorTest` and will help
improve coverage and confidence in the output length of
`SpeedChangingAudioProcessor`.

This CL is prework for removing the synchronization between the video
pipeline and the audio pipeline for speed changing effects.

PiperOrigin-RevId: 688578597
2024-10-22 09:27:08 -07:00
ibaker
cabc541a6f Use Guava's HttpHeaders consistently in HTTP testing machinery
PiperOrigin-RevId: 688576776
2024-10-22 09:21:09 -07:00
rohks
7545a8929b Add support for identifying h263 box in MP4 files for H.263 video
Issue: androidx/media#1821

#cherrypick

PiperOrigin-RevId: 688570141
2024-10-22 09:02:33 -07:00
jbibik
ee4f0c40bc Align spelling of fullScreen to fullscreen
Currently most of the public APIs use `fullscreen` (apart from the deprecated `PlayerControlView.OnFullScreenModeChangedListener` which will have to remain as is). This code changes mostly private variable naming.

PiperOrigin-RevId: 688559509
2024-10-22 08:30:10 -07:00
ibaker
219565c15e DataSourceContractTest: Add expected response headers
PiperOrigin-RevId: 688556440
2024-10-22 08:19:10 -07:00
kimvde
8260bb3d2e Refactor frame rate notification to release control
Frame rate change is currently notified to the release control when the
video frame processor input frame rate changes, but it should be when
the video frame processor output frame rate changes.

PiperOrigin-RevId: 688512300
2024-10-22 05:44:41 -07:00
sheenachhabra
31ece8cbd2 Fix color info conversion in vpccBox method
The color space should be used to determine the color
primaries and matrix coefficients, not the video range.

PiperOrigin-RevId: 688489212
2024-10-22 04:14:33 -07:00
tonihei
d9ca3c734a Ensure session extras Bundle is copied at least once
If not copied, the extras Bundle can be accidentally changed by the
app if it modifies the instance passed into MediaSession. On the flip
side, the Bundle should be modifiable once created so that the session
can amend the extras if needed without crashing.

PiperOrigin-RevId: 688485963
2024-10-22 04:06:06 -07:00
kimvde
be8c58d51e Use Format object in VideoFrameProcessor
This is to use an existing media3 object rather than creating a new one.

PiperOrigin-RevId: 688481323
2024-10-22 03:46:03 -07:00
claincly
7335754b23 Spell out VFP as VideoFrameProcessor
PiperOrigin-RevId: 688465940
2024-10-22 02:40:22 -07:00
Marc Baechinger
70f2d516a0 Add getter/setter and disable re-initialization by default 2024-10-21 19:18:58 +02:00
tonihei
1780986270 Stabilize ERROR_CODE_DECODING_RESOURCES_RECLAIMED
PiperOrigin-RevId: 688175306
2024-10-21 10:04:22 -07:00
shahddaghash
320cbc09f4 Remove deprecated AudioMixer.create() method
Use `DefaultAudioMixer.Factory().create()` instead.

PiperOrigin-RevId: 688167322
2024-10-21 09:39:34 -07:00
ibaker
b04b37074b Add video language to DefaultTrackSelector
PiperOrigin-RevId: 688155680
2024-10-21 09:02:19 -07:00
rohks
e926b0df1e Populate track duration in Mp4Extractor and FragmentedMp4Extractor
PiperOrigin-RevId: 688125056
2024-10-21 07:16:34 -07:00
claincly
73790cf2a4 Don't optimize for trim if the media is not clipped
PiperOrigin-RevId: 688120868
2024-10-21 06:58:59 -07:00
rohks
0ecd35e24c Improve error logging for IllegalClippingException
Added start and end time details to the error message for `REASON_START_EXCEEDS_END`, helping to debug cases where the start time exceeds the end time.

PiperOrigin-RevId: 688117440
2024-10-21 06:45:35 -07:00
rohks
847c1252e2 Add default method durationUs(long) in TrackOutput
The method allows extractors to set track duration when available. The `default` implementation does nothing, allowing implementers of `TrackOutput` to override it if they need to handle track duration.

Implemented it in `FakeExtractor` to print track duration in dump files.

PiperOrigin-RevId: 688110288
2024-10-21 06:18:00 -07:00
rohks
457bc55a4d Fix media duration parsing in mdhd box of MP4 files to handle -1 values
Treats the media duration as unknown (`C.TIME_UNSET`) when all bytes are `-1` to prevent exceptions during playback.

Issue: androidx/media#1819

#cherrypick

PiperOrigin-RevId: 688103949
2024-10-21 05:54:44 -07:00
ibaker
40cd64ab19 Test ResolvingDataSource resolveReportedUri functionality
PiperOrigin-RevId: 688102934
2024-10-21 05:49:50 -07:00
shahddaghash
5088e87195 Bump Media3 version to 1.5.0-beta01
PiperOrigin-RevId: 688079507
2024-10-21 04:15:18 -07:00
ibaker
74bbd7727d DataSourceContractTest: Add tests for resolved vs original URI
PiperOrigin-RevId: 688076205
2024-10-21 04:06:37 -07:00
Copybara-Service
f2ecca3b6a Merge pull request #1742 from colinkho:trackselection-playwhenready
PiperOrigin-RevId: 688050467
2024-10-21 02:27:20 -07:00
ibaker
49337d9667 Fix some markdown-in-javadoc
PiperOrigin-RevId: 687354846
2024-10-18 10:48:43 -07:00
tonihei
513ebf67b7 Add initial playWhenReady setting and tests
Also includes a new test for a similar logic that already exists
for speed changes.
2024-10-18 18:00:55 +01:00
Colin Kho
e4b32e4e31 Fix javadoc formatting 2024-10-18 18:00:18 +01:00
Colin Kho
943e165f1f Add variable to track playWhenReady status of a TrackSelection instance 2024-10-18 18:00:18 +01:00
tonihei
ceac959c29 Let FakeTrackSelection extend BaseTrackSelection
This fixes a bug in getIndexInTrackGroup

PiperOrigin-RevId: 687336621
2024-10-18 09:54:50 -07:00
ivanbuper
0108fb938e Make conversions to durationsUs consistent in SpeedProviders
PiperOrigin-RevId: 687334787
2024-10-18 09:47:55 -07:00
ibaker
b64bf88272 Rename playback thread in MediaSourceTestRunner
This makes it more clearly "the playback thread" when logging its name
during tests. The 'real' playback thread used in
`ExoPlayerImplInternal` is [called
`ExoPlayer:Playback`](49dec5db8b/libraries/exoplayer/src/main/java/androidx/media3/exoplayer/PlaybackLooperProvider.java (L87)).

PiperOrigin-RevId: 687307440
2024-10-18 08:14:08 -07:00
ibaker
2f01900e83 Release the Surface at the end of every playback test
Without this an error is logged which obfuscates real test failures.

PiperOrigin-RevId: 687302953
2024-10-18 08:01:50 -07:00
claincly
f52c7a1d5c Add an example to integrate android.animation
PiperOrigin-RevId: 687274900
2024-10-18 06:05:26 -07:00
claincly
2a49ffcb23 Make OverlaySettings dynamic
PiperOrigin-RevId: 687269731
2024-10-18 05:43:33 -07:00
shahddaghash
709246ac6a Fix dropped full stop in release notes
PiperOrigin-RevId: 687252101
2024-10-18 04:26:58 -07:00
tonihei
5fffe03312 Remove unneeded @Nullable from PlayerWrapper.legacyExtras
All values passed in via the constructor or setLegacyExtras
are guaranteed to be non-null.

PiperOrigin-RevId: 687245475
2024-10-18 04:04:32 -07:00
shahddaghash
4d711050bb Update release notes for Media3 1.5.0-beta01 release
PiperOrigin-RevId: 687243739
2024-10-18 03:55:16 -07:00
bachinger
3e556a4b53 Accept resource URIs for command buttons
PiperOrigin-RevId: 687239119
2024-10-18 03:35:50 -07:00
ibaker
13d6f37a84 Remove unused Surface from DashPlaybackTest
Follow-up to cb90bb38ee

PiperOrigin-RevId: 687235908
2024-10-18 03:21:07 -07:00
ibaker
3ba2fa6c07 Add @ForOverride annotation to DataSourceContractTest
PiperOrigin-RevId: 687223500
2024-10-18 02:33:14 -07:00
claincly
ba3d2b3fef Add a CanvasOverlay for easier drawing, and an example in demo app
PiperOrigin-RevId: 687223353
2024-10-18 02:29:43 -07:00
bachinger
08e6f30b68 Look for METADATA_KEY_ART_URI for legacy media items
PiperOrigin-RevId: 687222830
2024-10-18 02:26:47 -07:00
Steve Mayhew
621a9aedba SntpClient periodically re-syncs the offset to NTP server
Many of the Android TV platforms do not report accurate `SystemClock.elapsedRealtime()`
In addition this drift is not consistant accross multiple boxes, so the time offset
between any pair of boxes will vary over time.

This variance will lead to a drift in the Live Offset calculation.

Fix is simple, inValidate the NTP server query after a period of time (10 minutes)
and re-issue the call
2024-10-14 22:03:00 +02:00
ybai001
545d37c104 Fix two bugs in the updated code
If we handle the "codecs" in  HlsPlaylistParser.java rather than in  HlsMediaPeriod.java, since the "codecs" property includes both audio and video codec names here, we can't update it directly. Only video part should be updated by Dolby codec string.
2024-10-14 17:25:36 +08:00
ybai001
e8d29dc3d6 Remove redundant blank line
Remove redundant blank line.
2024-10-14 13:55:06 +08:00
ybai001
c18c6b31ef Remove the modification on Format
According to Google's comments, removing the modification on Format.java since it is redundant.
2024-10-14 13:51:40 +08:00
ybai001
79078c390d Add SUPPLEMENTAL-CODECS support in both DASH and HLS 2024-10-08 10:07:43 +08:00
ybai001
1381c92d76 Add AC-4 channel configuration support in DASH 2024-09-25 09:17:50 +08:00
ybai001
e7254b809a
Merge pull request #11 from androidx/main
Merge from androidx/media
2024-09-25 09:11:27 +08:00
ybai001
b650119d4e
Merge pull request #10 from androidx/main
Merge latest code from androidx media main branch
2024-09-20 11:22:08 +08:00
ybai001
62e1383a5b Refine code to match the latest branch 2024-08-08 17:25:06 +08:00
ybai001
0f43554db8
Merge branch 'main' into dlb/dovi-transformer/dev 2024-08-08 14:06:28 +08:00
ybai001
ffecefbec2 Fix a bug in HdrEditingTest.java 2024-08-08 13:33:08 +08:00
ybai001
b8d02d9a43 Update code again based on latest comments 2024-08-08 11:13:12 +08:00
ybai001
fd133459f8 Remove unused imported class 2024-08-06 11:19:42 +08:00
ybai001
d8321cd0fd Replace assumeTrue() with @SdkSupress() 2024-08-06 11:09:15 +08:00
ybai001
2822919df6 Update test code based on Google comments 2024-08-05 16:20:03 +08:00
ybai001
6b6d66c7ce Update code based on Google comments 2024-07-29 16:12:29 +08:00
ybai001
5eb064d2d1 Add test cases for Dolby Vision transformer support 2024-07-03 16:22:43 +08:00
ybai001
7b09e6c1f2 Merge branch 'dlb/dovi-transformer/dev' of https://github.com/DolbyLaboratories/media into dlb/dovi-transformer/dev 2024-06-18 14:00:08 +08:00
ybai001
6acfa0cc68 Add Dolby Vision Transcoding and Editing Support 2024-06-18 13:56:20 +08:00
ybai001
78964d0a90 Move AC-4 Level 4 test case to new location 2024-04-11 12:09:51 +08:00
ybai001
64a006b665 Merge branch 'dlb/ac4-level4/dev' of https://github.com/DolbyLaboratories/media into dlb/ac4-level4/dev 2024-04-11 12:01:59 +08:00
ybai001
e41181d4c3 Merge branch 'dlb/ac4-level4/dev' of https://github.com/DolbyLaboratories/media into dlb/ac4-level4/dev 2024-04-11 12:01:36 +08:00
ybai001
ac94afa4b1 Add AC-4 Level-4 ISO base media file format support 2024-04-11 12:01:11 +08:00
ybai001
29bda3bb5c Add AC-4 Level-4 ISO base media file format support 2024-04-11 12:00:45 +08:00
ybai001
b8c559ed32
Merge pull request #8 from androidx/main
Merge latest code from androidx/media main branch
2024-04-11 11:57:29 +08:00
ybai001
a5524c09c5 Add Dolby Vision Transcoding and Editing Support 2024-04-02 12:59:06 +08:00
ybai001
563f4d845a
Merge pull request #4 from androidx/main
Merge the code from androidx/media main branch
2024-03-18 11:11:27 +08:00
2232 changed files with 89359 additions and 18147 deletions

View File

@ -19,6 +19,7 @@ body:
options:
- Media3 main branch
- Media3 pre-release (alpha, beta or RC not in this list)
- Media3 1.6.0
- Media3 1.5.1
- Media3 1.5.0
- Media3 1.4.1

8
.gitignore vendored
View File

@ -4,6 +4,7 @@ gen
libs
obj
lint.xml
.kotlin
# IntelliJ IDEA
.idea
@ -68,6 +69,7 @@ libraries/decoder_opus/src/main/jni/libopus
# FLAC decoder extension
libraries/decoder_flac/src/main/jni/flac
libraries/decoder_flac/src/main/jni/libflac
# FFmpeg decoder extension
libraries/decoder_ffmpeg/src/main/jni/ffmpeg
@ -80,3 +82,9 @@ libraries/datasource_cronet/libs/*
# MIDI decoder extension
libraries/decoder_midi/lib
# IAMF decoder extension
libraries/decoder_iamf/src/main/jni/libiamf
# MPEG-H decoder extension
libraries/decoder_mpegh/src/main/jni/libmpegh

View File

@ -1,5 +1,375 @@
# Release notes
## 1.6
### 1.6.0 (2025-03-26)
This release includes the following changes since the
[1.5.1 release](#151-2024-12-19):
* Common Library:
* Add `AudioManagerCompat` and `AudioFocusRequestCompat` to replace the
equivalent classes in `androidx.media`.
* Upgrade Kotlin from 1.9.20 to 2.0.20 and use Compose Compiler Gradle
plugin. Upgrade KotlinX Coroutines library from 1.8.1 to 1.9.0.
* Remove `Format.toBundle(boolean excludeMetadata)` method, use
`Format.toBundle()` instead.
* Fix bug in `SimpleBasePlayer` where setting a new
`currentMediaItemIndex` in `State` after `setPlaylist` with `null`
`MediaMetadata` does not reevaluate the metadata
([#1940](https://github.com/androidx/media/issues/1940)).
* Change `SimpleBasePlayer.State` access from protected to public to make
it easier to handle updates in other classes
([#2128](https://github.com/androidx/media/issues/2128)).
* ExoPlayer:
* Add `MediaExtractorCompat`, a new class that provides equivalent
features to platform `MediaExtractor`.
* Add experimental 'ExoPlayer' pre-warming support for playback using
`MediaCodecVideoRenderer`. You can configure `DefaultRenderersFactory`
through `experimentalSetEnableMediaCodecVideoRendererPrewarming` to
provide a secondary `MediaCodecVideoRenderer` to `ExoPlayer`. If
enabled, `ExoPlayer` pre-processes the video of consecutive media items
during playback to reduce media item transition latency.
* Reduce default values for `bufferForPlaybackMs` and
`bufferForPlaybackAfterRebufferMs` in `DefaultLoadControl` to 1000 and
2000 ms respectively.
* Initialize `DeviceInfo` and device volume asynchronously (if enabled
using `setDeviceVolumeControlEnabled`). These values aren't available
instantly after `ExoPlayer.Builder.build()`, and `Player.Listener`
notifies changes through `onDeviceInfoChanged` and
`onDeviceVolumeChanged`.
* Initial audio session id is no longer immediately available after
creating the player. You can use
`AnalyticsListener.onAudioSessionIdChanged` to listen to the initial
update if required.
* Consider language when selecting a video track. By default, select a
'main' video track that matches the language of the selected audio
track, if available. Explicit video language preferences can be
expressed with
`TrackSelectionParameters.Builder.setPreferredVideoLanguage(s)`.
* Add `selectedAudioLanguage` parameter to
`DefaultTrackSelector.selectVideoTrack()` method.
* Add `retryCount` parameter to `MediaSourceEventListener.onLoadStarted`
and corresponding `MediaSourceEventListener.EventDispatcher` methods.
* Fix bug where playlist items or periods in multi-period DASH streams
with durations that don't match the actual content could cause frame
freezes at the end of the item
([#1698](https://github.com/androidx/media/issues/1698)).
* Move `BasePreloadManager.Listener` to a top-level
`PreloadManagerListener`.
* `RenderersFactory.createSecondaryRenderer` can be implemented to provide
secondary renderers for pre-warming. Pre-warming enables quicker media
item transitions during playback.
* Enable sending `CmcdData` for manifest requests in adaptive streaming
formats DASH, HLS, and SmoothStreaming
([#1951](https://github.com/androidx/media/issues/1951)).
* Provide `MediaCodecInfo` of the codec that will be initialized in
`MediaCodecRenderer.onReadyToInitializeCodec`
([#1963](https://github.com/androidx/media/pull/1963)).
* Change `AdsMediaSource` to allow the `AdPlaybackStates` to grow by
appending ad groups. Invalid modifications are detected and throw an
exception.
* Fix issue where additional decode-only frames may be displayed in quick
succession when transitioning to content media after a mid-roll ad.
* Make `DefaultRenderersFactory` add two `MetadataRenderer` instances to
enable apps to receive two different schemes of metadata by default.
* Reevaluate whether the ongoing load of a chunk should be cancelled when
playback is paused
([#1785](https://github.com/androidx/media/pull/1785)).
* Add option to `ClippingMediaSource` to allow clipping in unseekable
media.
* Fix bug where seeking with pre-warming could block following media item
transition.
* Fix a bug where `ExoPlayer.isLoading()` remains `true` while it has
transitioned to `STATE_IDLE` or `STATE_ENDED`
([#2133](https://github.com/androidx/media/issues/2133)).
* Add `lastRebufferRealtimeMs` to `LoadControl.Parameter`
([#2113](https://github.com/androidx/media/pull/2113)).
* Transformer:
* Add support for transmuxing into alternative backward compatible
formats.
* Add support for transcoding and transmuxing Dolby Vision (profile 8)
format.
* Update parameters of `VideoFrameProcessor.registerInputStream` and
`VideoFrameProcessor.Listener.onInputStreamRegistered` to use `Format`.
* Generate HDR static metadata when using `DefaultEncoderFactory`.
* Enable support for Android platform diagnostics using
`MediaMetricsManager`. Transformer forwards editing events and
performance data to the platform, which helps to provide system
performance and debugging information on the device. This data may also
be collected by Google
[if sharing usage and diagnostics data is enabled](https://support.google.com/accounts/answer/6078260)
by the user of the device. Apps can opt-out of contributing to platform
diagnostics for Transformer with
`Transformer.Builder.setUsePlatformDiagnostics(false)`.
* Split `InAppMuxer` into `InAppMp4Muxer` and `InAppFragmentedMp4Muxer`.
You use `InAppMp4Muxer` to produce a non-fragmented MP4 file, while
`InAppFragmentedMp4Muxer` is for producing a fragmented MP4 file.
* Move `Muxer` interface from `media3-muxer` to `media3-transformer`.
* Add `MediaProjectionAssetLoader`, which provides media from a
`MediaProjection` for screen recording, and add support for screen
recording to the Transformer demo app.
* Add `#getInputFormat()` to `Codec` interface.
* Shift the responsibility to release the `GlObjectsProvider` onto the
caller in `DefaultVideoFrameProcessor` and `DefaultVideoCompositor` when
possible.
* Extractors:
* AVI: Fix handling of files with constant bitrate compressed audio where
the stream header stores the number of bytes instead of the number of
chunks.
* Fix handling of NAL units with lengths expressed in 1 or 2 bytes (rather
than 4).
* Fix `ArrayIndexOutOfBoundsException` in MP4 edit lists when the edit
list starts at a non-sync frame with no preceding sync frame
([#2062](https://github.com/androidx/media/issues/2062)).
* Fix issue where TS streams can get stuck on some devices
([#2069](https://github.com/androidx/media/issues/2069)).
* FLAC: Add support for 32-bit FLAC files. Previously these would fail to
play with `IllegalStateException: Playback stuck buffering and not
loading` ([#2197](https://github.com/androidx/media/issues/2197)).
* Audio:
* Fix `onAudioPositionAdvancing` to be called when playback resumes
(previously it was called when playback was paused).
* Don't bypass `SonicAudioProcessor` when `SpeedChangingAudioProcessor` is
configured with default parameters.
* Fix underflow in `Sonic#getOutputSize()` that could cause
`DefaultAudioSink` to stall.
* Fix `MediaCodecAudioRenderer.getDurationToProgressUs()` and
`DecoderAudioRenderer.getDurationToProgressUs()` so that seeks correctly
reset the provided durations.
* Make `androidx.media3.common.audio.SonicAudioProcessor` final.
* Add support for float PCM to `ChannelMappingAudioProcessor` and
`TrimmingAudioProcessor`.
* Video:
* Change `MediaCodecVideoRenderer.shouldUsePlaceholderSurface` to
protected so that applications can override to block usage of
placeholder surfaces
([#1905](https://github.com/androidx/media/pull/1905)).
* Add experimental `ExoPlayer` AV1 sample dependency parsing to speed up
seeking. Enable it with the new
`DefaultRenderersFactory.experimentalSetParseAv1SampleDependencies` API.
* Add experimental `ExoPlayer` API to drop late `MediaCodecVideoRenderer`
decoder input buffers that are not depended on. Enable it with
`DefaultRenderersFactory.experimentalSetLateThresholdToDropDecoderInputUs`.
* Fix issue where a player without a surface was ready immediately and
very slow decoding any pending frames
([#1973](https://github.com/androidx/media/issues/1973)).
* Exclude Xiaomi and OPPO devices from detached surface mode to avoid
screen flickering
([#2059](https://github.com/androidx/media/issues/2059)).
* Text:
* Add support for VobSub subtitles
([#8260](https://github.com/google/ExoPlayer/issues/8260)).
* Stop eagerly loading all subtitle files configured with
`MediaItem.Builder.setSubtitleConfigurations`, and instead only load one
if it is selected by track selection
([#1721](https://github.com/androidx/media/issues/1721)).
* TTML: Add support for referencing `tts:origin` and `tts:extent` using
`style` ([#2953](https://github.com/google/ExoPlayer/issues/2953)).
* Restrict WebVTT and SubRip timestamps to exactly 3 decimal places.
Previously we incorrectly parsed any number of decimal places but always
assumed the value was in milliseconds, leading to incorrect timestamps
([#1997](https://github.com/androidx/media/issues/1997)).
* Fix playback hanging when a playlist contains clipped items with CEA-608
or CEA-708 captions.
* Fix `IllegalStateException` when an SSA file contains a cue with zero
duration (start and end time equal)
([#2052](https://github.com/androidx/media/issues/2052)).
* Suppress (and log) subtitle parsing errors when subtitles are muxed into
the same container as audio and video
([#2052](https://github.com/androidx/media/issues/2052)).
* Fix handling of multi-byte UTF-8 characters in WebVTT files using CR
line endings ([#2167](https://github.com/androidx/media/issues/2167)).
* DRM:
* Fix `MediaCodec$CryptoException: Operation not supported in this
configuration` error when playing ClearKey content on API < 27 devices
([#1732](https://github.com/androidx/media/issues/1732)).
* Effect:
* Moved the functionality of `OverlaySettings` into
`StaticOverlaySettings`. `OverlaySettings` can be subclassed to allow
dynamic overlay settings.
* Muxers:
* Moved `MuxerException` out of `Muxer` interface to avoid a very long
fully qualified name.
* Renamed `setSampleCopyEnabled()` method to `setSampleCopyingEnabled()`
in both `Mp4Muxer.Builder` and `FragmentedMp4Muxer.Builder`.
* `Mp4Muxer.addTrack()` and `FragmentedMp4Muxer.addTrack()` now return an
`int` track ID instead of a `TrackToken`.
* `Mp4Muxer` and `FragmentedMp4Muxer` no longer implement `Muxer`
interface.
* Disable `Mp4Muxer` sample batching and copying by default.
* Fix a bug in `FragmentedMp4Muxer` that creates a lot of fragments when
only audio track is written.
* Session:
* Keep foreground service state for an additional 10 minutes when playback
pauses, stops or fails. This allows users to resume playback within this
timeout without risking foreground service restrictions on various
devices. Note that simply calling `player.pause()` can no longer be used
to stop the foreground service before `stopSelf()` when overriding
`onTaskRemoved`, use `MediaSessionService.pauseAllPlayersAndStopSelf()`
instead.
* Keep notification visible when playback enters an error or stopped
state. The notification is only removed if the playlist is cleared or
the player is released.
* Improve handling of Android platform MediaSession actions ACTION_PLAY
and ACTION_PAUSE to only set one of them according to the available
commands and also accept if only one of them is set.
* Add `Context` as a parameter to
`MediaButtonReceiver.shouldStartForegroundService`
([#1887](https://github.com/androidx/media/issues/1887)).
* Fix bug where calling a `Player` method on a `MediaController` connected
to a legacy session dropped changes from a pending update.
* Make `MediaSession.setSessionActivity(PendingIntent)` accept null
([#2109](https://github.com/androidx/media/issues/2109)).
* Fix bug where a stale notification stays visible when the playlist is
cleared ([#2211](https://github.com/androidx/media/issues/2211)).
* UI:
* Add state holders and composables to the `media3-ui-compose` module for
`PlayerSurface`, `PresentationState`, `PlayPauseButtonState`,
`NextButtonState`, `PreviousButtonState`, `RepeatButtonState`,
`ShuffleButtonState` and `PlaybackSpeedState`.
* Downloads:
* Fix bug in `CacheWriter` that leaves data sources open and cache areas
locked in case the data source throws an `Exception` other than
`IOException`
([#9760](https://github.com/google/ExoPlayer/issues/9760)).
* HLS extension:
* Add a first version of `HlsInterstitialsAdsLoader`. The ads loader reads
the HLS interstitials of an HLS media playlist and maps them to the
`AdPlaybackState` that is passed to the `AdsMediaSource`. This initial
version only supports HLS VOD streams with `X-ASSET-URI` attributes.
* Add `HlsInterstitialsAdsLoader.AdsMediaSourceFactory`. Apps can use it
to create `AdsMediaSource` instances that use an
`HlsInterstitialsAdsLoader` in a convenient and safe way.
* Parse `SUPPLEMENTAL-CODECS` tag from HLS playlist to detect Dolby Vision
formats ([#1785](https://github.com/androidx/media/pull/1785)).
* Loosen the condition for seeking to sync positions in an HLS stream
([#2209](https://github.com/androidx/media/issues/2209)).
* DASH extension:
* Add AC-4 Level-4 format support for DASH
([#1898](https://github.com/androidx/media/pull/1898)).
* Fix issue when calculating the update interval for ad insertion in
multi-period live streams
([#1698](https://github.com/androidx/media/issues/1698)).
* Parse `scte214:supplementalCodecs` attribute from DASH manifest to
detect Dolby Vision formats
([#1785](https://github.com/androidx/media/pull/1785)).
* Improve handling of period transitions in live streams where the period
contains media samples beyond the declared period duration
([#1698](https://github.com/androidx/media/issues/1698)).
* Fix issue where adaptation sets marked with `adaptation-set-switching`
but different languages or role flags are merged together
([#2222](https://github.com/androidx/media/issues/2222)).
* Decoder extensions (FFmpeg, VP9, AV1, etc.):
* Add the MPEG-H decoder module which uses the native MPEG-H decoder
module to decode MPEG-H audio
([#1826](https://github.com/androidx/media/pull/1826)).
* MIDI extension:
* Plumb custom `AudioSink` and `AudioRendererEventListener` instances into
`MidiRenderer`.
* Cast extension:
* Bump the `play-services-cast-framework` dependency to 21.5.0 to fix a
`FLAG_MUTABLE` crash in apps targeting API 34+ on devices with Google
Play services installed but disabled
([#2178](https://github.com/androidx/media/issues/2178)).
* Demo app:
* Extend `demo-compose` with additional buttons and enhance
`PlayerSurface` integration with scaling and shutter support.
* Remove deprecated symbols:
* Remove deprecated `AudioMixer.create()` method. Use
`DefaultAudioMixer.Factory().create()` instead.
* Remove the following deprecated `Transformer.Builder` methods:
* `setTransformationRequest()`, use `setAudioMimeType()`,
`setVideoMimeType()`, and `setHdrMode()` instead.
* `setAudioProcessors()`, set the audio processor in an
`EditedMediaItem.Builder.setEffects()`, and pass it to
`Transformer.start()` instead.
* `setVideoEffects()`, set video effect in an
`EditedMediaItem.Builder.setEffects()`, and pass it to
`Transformer.start()` instead.
* `setRemoveAudio()`, use `EditedMediaItem.Builder.setRemoveAudio()`
to remove the audio from the `EditedMediaItem` passed to
`Transformer.start()` instead.
* `setRemoveVideo()`, use `EditedMediaItem.Builder.setRemoveVideo()`
to remove the video from the `EditedMediaItem` passed to
`Transformer.start()` instead.
* `setFlattenForSlowMotion()`, use
`EditedMediaItem.Builder.setFlattenForSlowMotion()` to flatten the
`EditedMediaItem` passed to `Transformer.start()` instead.
* `setListener()`, use `addListener()`, `removeListener()` or
`removeAllListeners()` instead.
* Remove the following deprecated `Transformer.Listener` methods:
* `onTransformationCompleted(MediaItem)`, use
`onCompleted(Composition, ExportResult)` instead.
* `onTransformationCompleted(MediaItem, TransformationResult)`, use
`onCompleted(Composition, ExportResult)` instead.
* `onTransformationError(MediaItem, Exception)`, use
`onError(Composition, ExportResult, ExportException)` instead.
* `onTransformationError(MediaItem, TransformationException)`, use
`onError(Composition, ExportResult, ExportException)` instead.
* `onTransformationError(MediaItem, TransformationResult,
TransformationException)`, use `onError(Composition, ExportResult,
ExportException)` instead.
* `onFallbackApplied(MediaItem, TransformationRequest,
TransformationRequest)`, use `onFallbackApplied(Composition,
TransformationRequest, TransformationRequest)` instead.
* Remove deprecated `TransformationResult` class. Use `ExportResult`
instead.
* Remove deprecated `TransformationException` class. Use `ExportException`
instead.
* Remove deprecated `Transformer.PROGRESS_STATE_NO_TRANSFORMATION`. Use
`Transformer.PROGRESS_STATE_NOT_STARTED` instead.
* Remove deprecated `Transformer.setListener()`. Use
`Transformer.addListener()`, `Transformer.removeListener()` or
`Transformer.removeAllListeners()` instead.
* Remove deprecated `Transformer.startTransformation()`. Use
`Transformer.start(MediaItem, String)` instead.
* Remove deprecated `SingleFrameGlShaderProgram`. Use
`BaseGlShaderProgram` instead.
* Remove `Transformer.flattenForSlowMotion`. Use
`EditedMediaItem.flattenForSlowMotion` instead.
* Removed `ExoPlayer.VideoComponent`, `ExoPlayer.AudioComponent`,
`ExoPlayer.TextComponent` and `ExoPlayer.DeviceComponent`.
* Removed `androidx.media3.exoplayer.audio.SonicAudioProcessor`.
* Removed the following deprecated `DownloadHelper` methods:
* Constructor `DownloadHelper(MediaItem, @Nullable MediaSource,
TrackSelectionParameters, RendererCapabilities[])`, use
`DownloadHelper(MediaItem, @Nullable MediaSource,
TrackSelectionParameters, RendererCapabilitiesList)` instead.
* `getRendererCapabilities(RenderersFactory)`, equivalent
functionality can be achieved by creating a
`DefaultRendererCapabilitiesList` with a `RenderersFactory`, and
calling `DefaultRendererCapabilitiesList.getRendererCapabilities()`.
* Removed
`PlayerNotificationManager.setMediaSessionToken(MediaSessionCompat)`
method. Use
`PlayerNotificationManager.setMediaSessionToken(MediaSession.Token)` and
pass in `(MediaSession.Token) compatToken.getToken()` instead.
### 1.6.0-rc02 (2025-03-18)
Use the 1.6.0 [stable version](#160-2025-03-26).
### 1.6.0-rc01 (2025-03-12)
Use the 1.6.0 [stable version](#160-2025-03-26).
### 1.6.0-beta01 (2025-02-26)
Use the 1.6.0 [stable version](#160-2025-03-26).
### 1.6.0-alpha03 (2025-02-06)
Use the 1.6.0 [stable version](#160-2025-03-26).
### 1.6.0-alpha02 (2025-01-30)
Use the 1.6.0 [stable version](#160-2025-03-26).
### 1.6.0-alpha01 (2024-12-20)
Use the 1.6.0 [stable version](#160-2025-03-26).
## 1.5
### 1.5.1 (2024-12-19)
@ -312,19 +682,19 @@ This release includes the following changes since the
[#184](https://github.com/androidx/media/issues/184)).
* Fix bug where the "None" choice in the text selection is not working if
there are app-defined text track selection preferences.
* DASH Extension:
* DASH extension:
* Add support for periods starting in the middle of a segment
([#1440](https://github.com/androidx/media/issues/1440)).
* Smooth Streaming Extension:
* Smooth Streaming extension:
* Fix a `Bad magic number for Bundle` error when playing SmoothStreaming
streams with text tracks
([#1779](https://github.com/androidx/media/issues/1779)).
* RTSP Extension:
* RTSP extension:
* Fix user info removal for URLs that contain encoded @ characters
([#1138](https://github.com/androidx/media/pull/1138)).
* Fix crashing when parsing of RTP packets with header extensions
([#1225](https://github.com/androidx/media/pull/1225)).
* Decoder Extensions (FFmpeg, VP9, AV1, etc.):
* Decoder extensions (FFmpeg, VP9, AV1, etc.):
* Add the IAMF decoder module, which provides support for playback of MP4
files containing IAMF tracks using the libiamf native library to
synthesize audio.
@ -333,7 +703,7 @@ This release includes the following changes since the
binaural playback support is currently not available.
* Add 16 KB page support for decoder extensions on Android 15
([#1685](https://github.com/androidx/media/issues/1685)).
* Cast Extension:
* Cast extension:
* Stop cleaning the timeline after the CastSession disconnects, which
enables the sender app to resume playback locally after a disconnection.
* Populate CastPlayer's `DeviceInfo` when a `Context` is provided. This
@ -414,7 +784,7 @@ This release includes the following changes since the
`MediaButtonReceiver` when deciding whether to ignore it to avoid a
`ForegroundServiceDidNotStartInTimeException`
([#1581](https://github.com/androidx/media/issues/1581)).
* RTSP Extension:
* RTSP extension:
* Skip invalid Media Descriptions in SDP parsing
([#1087](https://github.com/androidx/media/issues/1472)).
@ -759,12 +1129,12 @@ This release includes the following changes since the
instances, which can eventually result in an app crashing with
`IllegalStateException: Too many receivers, total of 1000, registered
for pid` ([#1224](https://github.com/androidx/media/issues/1224)).
* Cronet Extension:
* Cronet extension:
* Fix `SocketTimeoutException` in `CronetDataSource`. In some versions of
Cronet, the request provided by the callback is not always the same.
This leads to callback not completing and request timing out
(https://issuetracker.google.com/328442628).
* HLS Extension:
* HLS extension:
* Fix bug where pending EMSG samples waiting for a discontinuity were
delegated in `HlsSampleStreamWrapper` with an incorrect offset causing
an `IndexOutOfBoundsException` or an `IllegalArgumentException`
@ -778,13 +1148,13 @@ This release includes the following changes since the
* Fix bug where enabling CMCD for HLS live streams causes
`ArrayIndexOutOfBoundsException`
([#1395](https://github.com/androidx/media/issues/1395)).
* DASH Extension:
* DASH extension:
* Fix bug where re-preparing a multi-period live stream can throw an
`IndexOutOfBoundsException`
([#1329](https://github.com/androidx/media/issues/1329)).
* Add support for `dashif:Laurl` license urls
([#1345](https://github.com/androidx/media/issues/1345)).
* Cast Extension:
* Cast extension:
* Fix bug that converted the album title of the `MediaQueueItem` to the
artist in the Media3 media item
([#1255](https://github.com/androidx/media/pull/1255)).
@ -932,13 +1302,13 @@ This release includes the following changes since the
* Fallback to include audio track language name if `Locale` cannot
identify a display name
([#988](https://github.com/androidx/media/issues/988)).
* DASH Extension:
* DASH extension:
* Populate all `Label` elements from the manifest into `Format.labels`
([#1054](https://github.com/androidx/media/pull/1054)).
* RTSP Extension:
* RTSP extension:
* Skip empty session information values (i-tags) in SDP parsing
([#1087](https://github.com/androidx/media/issues/1087)).
* Decoder Extensions (FFmpeg, VP9, AV1, MIDI, etc.):
* Decoder extensions (FFmpeg, VP9, AV1, MIDI, etc.):
* Disable the MIDI extension as a local dependency by default because it
requires an additional Maven repository to be configured. Users who need
this module from a local dependency
@ -1091,12 +1461,12 @@ This release includes the following changes since the
not transmitted between media controllers and sessions.
* Add constructor to `MediaLibrarySession.Builder` that only takes a
`Context` instead of a `MediaLibraryService`.
* HLS Extension:
* HLS extension:
* Reduce `HlsMediaPeriod` to package-private visibility. This type
shouldn't be directly depended on from outside the HLS package.
* Resolve seeks to beginning of a segment more efficiently
([#1031](https://github.com/androidx/media/pull/1031)).
* Decoder Extensions (FFmpeg, VP9, AV1, MIDI, etc.):
* Decoder extensions (FFmpeg, VP9, AV1, MIDI, etc.):
* MIDI decoder: Ignore SysEx event messages
([#710](https://github.com/androidx/media/pull/710)).
* Test Utilities:
@ -1194,16 +1564,16 @@ This release includes the following changes since the
* Fix issue where the numbers in the fast forward button of the
`PlayerControlView` were misaligned
([#547](https://github.com/androidx/media/issues/547)).
* DASH Extension:
* DASH extension:
* Parse "f800" as channel count of 5 for Dolby in DASH manifest
([#688](https://github.com/androidx/media/issues/688)).
* Decoder Extensions (FFmpeg, VP9, AV1, MIDI, etc.):
* Decoder extensions (FFmpeg, VP9, AV1, MIDI, etc.):
* MIDI: Fix issue where seeking forward skips the Program Change events
([#704](https://github.com/androidx/media/issues/704)).
* Migrate to FFmpeg 6.0 and update supported NDK to `r26b`
([#707](https://github.com/androidx/media/pull/707),
[#867](https://github.com/androidx/media/pull/867)).
* Cast Extension:
* Cast extension:
* Sanitize creation of a `Timeline` to not crash the app when loading
media fails on the cast device
([#708](https://github.com/androidx/media/issues/708)).
@ -1441,11 +1811,11 @@ This release includes the following changes since the
add `dataSync` as `foregroundServiceType` in the manifest and add the
`FOREGROUND_SERVICE_DATA_SYNC` permission
([#11239](https://github.com/google/ExoPlayer/issues/11239)).
* HLS Extension:
* HLS extension:
* Refresh the HLS live playlist with an interval calculated from the last
load start time rather than the last load completed time
([#663](https://github.com/androidx/media/issues/663)).
* DASH Extension:
* DASH extension:
* Allow multiple of the same DASH identifier in segment template url.
* Add experimental support for parsing subtitles during extraction. This
has better support for merging overlapping subtitles, including
@ -1453,7 +1823,7 @@ This release includes the following changes since the
can enable this using
`DashMediaSource.Factory.experimentalParseSubtitlesDuringExtraction()`
([#288](https://github.com/androidx/media/issues/288)).
* RTSP Extension:
* RTSP extension:
* Fix a race condition that could lead to `IndexOutOfBoundsException` when
falling back to TCP, or playback hanging in some situations.
* Check state in RTSP setup when returning loading state of
@ -1464,7 +1834,7 @@ This release includes the following changes since the
* Use RTSP Setup Response timeout value in time interval of sending
keep-alive RTSP Options requests
([#662](https://github.com/androidx/media/issues/662)).
* Decoder Extensions (FFmpeg, VP9, AV1, MIDI, etc.):
* Decoder extensions (FFmpeg, VP9, AV1, MIDI, etc.):
* Release the MIDI decoder module, which provides support for playback of
standard MIDI files using the Jsyn library to synthesize audio.
* Add `DecoderOutputBuffer.shouldBeSkipped` to directly mark output
@ -1741,20 +2111,20 @@ This release contains the following changes since the
* Add Util methods `shouldShowPlayButton` and
`handlePlayPauseButtonAction` to write custom UI elements with a
play/pause button.
* RTSP Extension:
* 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:
* 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:
* HLS extension:
* Add
`HlsMediaSource.Factory.setTimestampAdjusterInitializationTimeoutMs(long)`
to set a timeout for the loading thread to wait for the

108
api.txt
View File

@ -464,6 +464,7 @@ package androidx.media3.common {
field @Nullable public final CharSequence description;
field @Nullable public final Integer discNumber;
field @Nullable public final CharSequence displayTitle;
field @Nullable public final Long durationMs;
field @Nullable public final android.os.Bundle extras;
field @Deprecated @Nullable @androidx.media3.common.MediaMetadata.FolderType public final Integer folderType;
field @Nullable public final CharSequence genre;
@ -502,6 +503,7 @@ package androidx.media3.common {
method public androidx.media3.common.MediaMetadata.Builder setDescription(@Nullable CharSequence);
method public androidx.media3.common.MediaMetadata.Builder setDiscNumber(@Nullable Integer);
method public androidx.media3.common.MediaMetadata.Builder setDisplayTitle(@Nullable CharSequence);
method public androidx.media3.common.MediaMetadata.Builder setDurationMs(@Nullable Long);
method public androidx.media3.common.MediaMetadata.Builder setExtras(@Nullable android.os.Bundle);
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);
@ -636,6 +638,7 @@ package androidx.media3.common {
field public static final int ERROR_CODE_DECODING_FAILED = 4003; // 0xfa3
field public static final int ERROR_CODE_DECODING_FORMAT_EXCEEDS_CAPABILITIES = 4004; // 0xfa4
field public static final int ERROR_CODE_DECODING_FORMAT_UNSUPPORTED = 4005; // 0xfa5
field public static final int ERROR_CODE_DECODING_RESOURCES_RECLAIMED = 4006; // 0xfa6
field public static final int ERROR_CODE_DISCONNECTED = -100; // 0xffffff9c
field public static final int ERROR_CODE_DRM_CONTENT_ERROR = 6003; // 0x1773
field public static final int ERROR_CODE_DRM_DEVICE_REVOKED = 6007; // 0x1777
@ -676,7 +679,7 @@ package androidx.media3.common {
field public final long timestampMs;
}
@IntDef(open=true, value={androidx.media3.common.PlaybackException.ERROR_CODE_INVALID_STATE, androidx.media3.common.PlaybackException.ERROR_CODE_BAD_VALUE, androidx.media3.common.PlaybackException.ERROR_CODE_PERMISSION_DENIED, androidx.media3.common.PlaybackException.ERROR_CODE_NOT_SUPPORTED, androidx.media3.common.PlaybackException.ERROR_CODE_DISCONNECTED, androidx.media3.common.PlaybackException.ERROR_CODE_AUTHENTICATION_EXPIRED, androidx.media3.common.PlaybackException.ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED, androidx.media3.common.PlaybackException.ERROR_CODE_CONCURRENT_STREAM_LIMIT, androidx.media3.common.PlaybackException.ERROR_CODE_PARENTAL_CONTROL_RESTRICTED, androidx.media3.common.PlaybackException.ERROR_CODE_NOT_AVAILABLE_IN_REGION, androidx.media3.common.PlaybackException.ERROR_CODE_SKIP_LIMIT_REACHED, androidx.media3.common.PlaybackException.ERROR_CODE_SETUP_REQUIRED, androidx.media3.common.PlaybackException.ERROR_CODE_END_OF_PLAYLIST, androidx.media3.common.PlaybackException.ERROR_CODE_CONTENT_ALREADY_PLAYING, androidx.media3.common.PlaybackException.ERROR_CODE_UNSPECIFIED, androidx.media3.common.PlaybackException.ERROR_CODE_REMOTE_ERROR, androidx.media3.common.PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW, androidx.media3.common.PlaybackException.ERROR_CODE_TIMEOUT, androidx.media3.common.PlaybackException.ERROR_CODE_FAILED_RUNTIME_CHECK, androidx.media3.common.PlaybackException.ERROR_CODE_IO_UNSPECIFIED, androidx.media3.common.PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT, androidx.media3.common.PlaybackException.ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE, androidx.media3.common.PlaybackException.ERROR_CODE_IO_BAD_HTTP_STATUS, androidx.media3.common.PlaybackException.ERROR_CODE_IO_FILE_NOT_FOUND, androidx.media3.common.PlaybackException.ERROR_CODE_IO_NO_PERMISSION, androidx.media3.common.PlaybackException.ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED, androidx.media3.common.PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE, androidx.media3.common.PlaybackException.ERROR_CODE_PARSING_CONTAINER_MALFORMED, androidx.media3.common.PlaybackException.ERROR_CODE_PARSING_MANIFEST_MALFORMED, androidx.media3.common.PlaybackException.ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED, androidx.media3.common.PlaybackException.ERROR_CODE_PARSING_MANIFEST_UNSUPPORTED, androidx.media3.common.PlaybackException.ERROR_CODE_DECODER_INIT_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DECODER_QUERY_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DECODING_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DECODING_FORMAT_EXCEEDS_CAPABILITIES, androidx.media3.common.PlaybackException.ERROR_CODE_DECODING_FORMAT_UNSUPPORTED, androidx.media3.common.PlaybackException.ERROR_CODE_AUDIO_TRACK_INIT_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_AUDIO_TRACK_WRITE_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_AUDIO_TRACK_OFFLOAD_INIT_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_AUDIO_TRACK_OFFLOAD_WRITE_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_UNSPECIFIED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_SCHEME_UNSUPPORTED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_PROVISIONING_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_CONTENT_ERROR, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_LICENSE_ACQUISITION_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_DISALLOWED_OPERATION, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_SYSTEM_ERROR, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_DEVICE_REVOKED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_LICENSE_EXPIRED}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface PlaybackException.ErrorCode {
@IntDef(open=true, value={androidx.media3.common.PlaybackException.ERROR_CODE_INVALID_STATE, androidx.media3.common.PlaybackException.ERROR_CODE_BAD_VALUE, androidx.media3.common.PlaybackException.ERROR_CODE_PERMISSION_DENIED, androidx.media3.common.PlaybackException.ERROR_CODE_NOT_SUPPORTED, androidx.media3.common.PlaybackException.ERROR_CODE_DISCONNECTED, androidx.media3.common.PlaybackException.ERROR_CODE_AUTHENTICATION_EXPIRED, androidx.media3.common.PlaybackException.ERROR_CODE_PREMIUM_ACCOUNT_REQUIRED, androidx.media3.common.PlaybackException.ERROR_CODE_CONCURRENT_STREAM_LIMIT, androidx.media3.common.PlaybackException.ERROR_CODE_PARENTAL_CONTROL_RESTRICTED, androidx.media3.common.PlaybackException.ERROR_CODE_NOT_AVAILABLE_IN_REGION, androidx.media3.common.PlaybackException.ERROR_CODE_SKIP_LIMIT_REACHED, androidx.media3.common.PlaybackException.ERROR_CODE_SETUP_REQUIRED, androidx.media3.common.PlaybackException.ERROR_CODE_END_OF_PLAYLIST, androidx.media3.common.PlaybackException.ERROR_CODE_CONTENT_ALREADY_PLAYING, androidx.media3.common.PlaybackException.ERROR_CODE_UNSPECIFIED, androidx.media3.common.PlaybackException.ERROR_CODE_REMOTE_ERROR, androidx.media3.common.PlaybackException.ERROR_CODE_BEHIND_LIVE_WINDOW, androidx.media3.common.PlaybackException.ERROR_CODE_TIMEOUT, androidx.media3.common.PlaybackException.ERROR_CODE_FAILED_RUNTIME_CHECK, androidx.media3.common.PlaybackException.ERROR_CODE_IO_UNSPECIFIED, androidx.media3.common.PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_IO_NETWORK_CONNECTION_TIMEOUT, androidx.media3.common.PlaybackException.ERROR_CODE_IO_INVALID_HTTP_CONTENT_TYPE, androidx.media3.common.PlaybackException.ERROR_CODE_IO_BAD_HTTP_STATUS, androidx.media3.common.PlaybackException.ERROR_CODE_IO_FILE_NOT_FOUND, androidx.media3.common.PlaybackException.ERROR_CODE_IO_NO_PERMISSION, androidx.media3.common.PlaybackException.ERROR_CODE_IO_CLEARTEXT_NOT_PERMITTED, androidx.media3.common.PlaybackException.ERROR_CODE_IO_READ_POSITION_OUT_OF_RANGE, androidx.media3.common.PlaybackException.ERROR_CODE_PARSING_CONTAINER_MALFORMED, androidx.media3.common.PlaybackException.ERROR_CODE_PARSING_MANIFEST_MALFORMED, androidx.media3.common.PlaybackException.ERROR_CODE_PARSING_CONTAINER_UNSUPPORTED, androidx.media3.common.PlaybackException.ERROR_CODE_PARSING_MANIFEST_UNSUPPORTED, androidx.media3.common.PlaybackException.ERROR_CODE_DECODER_INIT_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DECODER_QUERY_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DECODING_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DECODING_FORMAT_EXCEEDS_CAPABILITIES, androidx.media3.common.PlaybackException.ERROR_CODE_DECODING_FORMAT_UNSUPPORTED, androidx.media3.common.PlaybackException.ERROR_CODE_DECODING_RESOURCES_RECLAIMED, androidx.media3.common.PlaybackException.ERROR_CODE_AUDIO_TRACK_INIT_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_AUDIO_TRACK_WRITE_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_AUDIO_TRACK_OFFLOAD_INIT_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_AUDIO_TRACK_OFFLOAD_WRITE_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_UNSPECIFIED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_SCHEME_UNSUPPORTED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_PROVISIONING_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_CONTENT_ERROR, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_LICENSE_ACQUISITION_FAILED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_DISALLOWED_OPERATION, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_SYSTEM_ERROR, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_DEVICE_REVOKED, androidx.media3.common.PlaybackException.ERROR_CODE_DRM_LICENSE_EXPIRED}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface PlaybackException.ErrorCode {
}
public final class PlaybackParameters {
@ -1093,12 +1096,13 @@ package androidx.media3.common {
public class TrackSelectionParameters {
method public androidx.media3.common.TrackSelectionParameters.Builder buildUpon();
method public static androidx.media3.common.TrackSelectionParameters fromBundle(android.os.Bundle);
method public static androidx.media3.common.TrackSelectionParameters getDefaults(android.content.Context);
method @Deprecated public static androidx.media3.common.TrackSelectionParameters getDefaults(android.content.Context);
method @CallSuper public android.os.Bundle toBundle();
field public final com.google.common.collect.ImmutableSet<java.lang.Integer> disabledTrackTypes;
field public final boolean forceHighestSupportedBitrate;
field public final boolean forceLowestBitrate;
field @androidx.media3.common.C.SelectionFlags public final int ignoredTextSelectionFlags;
field public final boolean isViewportSizeLimitedByPhysicalDisplaySize;
field public final int maxAudioBitrate;
field public final int maxAudioChannelCount;
field public final int maxVideoBitrate;
@ -1118,13 +1122,15 @@ package androidx.media3.common {
field public final com.google.common.collect.ImmutableList<java.lang.String> preferredVideoMimeTypes;
field @androidx.media3.common.C.RoleFlags public final int preferredVideoRoleFlags;
field public final boolean selectUndeterminedTextLanguage;
field public final boolean usePreferredTextLanguagesAndRoleFlagsFromCaptioningManager;
field public final int viewportHeight;
field public final boolean viewportOrientationMayChange;
field public final int viewportWidth;
}
public static class TrackSelectionParameters.Builder {
ctor public TrackSelectionParameters.Builder(android.content.Context);
ctor public TrackSelectionParameters.Builder();
ctor @Deprecated @com.google.errorprone.annotations.InlineMe(replacement="this()") public TrackSelectionParameters.Builder(android.content.Context);
method public androidx.media3.common.TrackSelectionParameters.Builder addOverride(androidx.media3.common.TrackSelectionOverride);
method public androidx.media3.common.TrackSelectionParameters build();
method public androidx.media3.common.TrackSelectionParameters.Builder clearOverride(androidx.media3.common.TrackGroup);
@ -1151,7 +1157,8 @@ package androidx.media3.common {
method public androidx.media3.common.TrackSelectionParameters.Builder setPreferredAudioMimeTypes(java.lang.String...);
method public androidx.media3.common.TrackSelectionParameters.Builder setPreferredAudioRoleFlags(@androidx.media3.common.C.RoleFlags int);
method public androidx.media3.common.TrackSelectionParameters.Builder setPreferredTextLanguage(@Nullable String);
method public androidx.media3.common.TrackSelectionParameters.Builder setPreferredTextLanguageAndRoleFlagsToCaptioningManagerSettings(android.content.Context);
method public androidx.media3.common.TrackSelectionParameters.Builder setPreferredTextLanguageAndRoleFlagsToCaptioningManagerSettings();
method @Deprecated public androidx.media3.common.TrackSelectionParameters.Builder setPreferredTextLanguageAndRoleFlagsToCaptioningManagerSettings(android.content.Context);
method public androidx.media3.common.TrackSelectionParameters.Builder setPreferredTextLanguages(java.lang.String...);
method public androidx.media3.common.TrackSelectionParameters.Builder setPreferredTextRoleFlags(@androidx.media3.common.C.RoleFlags int);
method public androidx.media3.common.TrackSelectionParameters.Builder setPreferredVideoMimeType(@Nullable String);
@ -1160,7 +1167,8 @@ package androidx.media3.common {
method public androidx.media3.common.TrackSelectionParameters.Builder setSelectUndeterminedTextLanguage(boolean);
method public androidx.media3.common.TrackSelectionParameters.Builder setTrackTypeDisabled(@androidx.media3.common.C.TrackType int, boolean);
method public androidx.media3.common.TrackSelectionParameters.Builder setViewportSize(int, int, boolean);
method public androidx.media3.common.TrackSelectionParameters.Builder setViewportSizeToPhysicalDisplaySize(android.content.Context, boolean);
method @Deprecated public androidx.media3.common.TrackSelectionParameters.Builder setViewportSizeToPhysicalDisplaySize(android.content.Context, boolean);
method public androidx.media3.common.TrackSelectionParameters.Builder setViewportSizeToPhysicalDisplaySize(boolean);
}
public final class Tracks {
@ -1480,7 +1488,82 @@ package androidx.media3.exoplayer.util {
package androidx.media3.session {
public final class CommandButton {
field public static final int ICON_ALBUM = 57369; // 0xe019
field public static final int ICON_ARTIST = 57370; // 0xe01a
field public static final int ICON_BLOCK = 57675; // 0xe14b
field public static final int ICON_BOOKMARK_FILLED = 1042534; // 0xfe866
field public static final int ICON_BOOKMARK_UNFILLED = 59494; // 0xe866
field public static final int ICON_CHECK_CIRCLE_FILLED = 1042540; // 0xfe86c
field public static final int ICON_CHECK_CIRCLE_UNFILLED = 59500; // 0xe86c
field public static final int ICON_CLOSED_CAPTIONS = 57372; // 0xe01c
field public static final int ICON_CLOSED_CAPTIONS_OFF = 61916; // 0xf1dc
field public static final int ICON_FAST_FORWARD = 57375; // 0xe01f
field public static final int ICON_FEED = 57573; // 0xe0e5
field public static final int ICON_FLAG_FILLED = 1040723; // 0xfe153
field public static final int ICON_FLAG_UNFILLED = 57683; // 0xe153
field public static final int ICON_HEART_FILLED = 1042557; // 0xfe87d
field public static final int ICON_HEART_UNFILLED = 59517; // 0xe87d
field public static final int ICON_MINUS = 57691; // 0xe15b
field public static final int ICON_MINUS_CIRCLE_FILLED = 1040712; // 0xfe148
field public static final int ICON_MINUS_CIRCLE_UNFILLED = 1040713; // 0xfe149
field public static final int ICON_NEXT = 57412; // 0xe044
field public static final int ICON_PAUSE = 57396; // 0xe034
field public static final int ICON_PLAY = 57399; // 0xe037
field public static final int ICON_PLAYBACK_SPEED = 57448; // 0xe068
field public static final int ICON_PLAYBACK_SPEED_0_5 = 62690; // 0xf4e2
field public static final int ICON_PLAYBACK_SPEED_0_8 = 1045730; // 0xff4e2
field public static final int ICON_PLAYBACK_SPEED_1_0 = 61389; // 0xefcd
field public static final int ICON_PLAYBACK_SPEED_1_2 = 62689; // 0xf4e1
field public static final int ICON_PLAYBACK_SPEED_1_5 = 62688; // 0xf4e0
field public static final int ICON_PLAYBACK_SPEED_1_8 = 1045728; // 0xff4e0
field public static final int ICON_PLAYBACK_SPEED_2_0 = 62699; // 0xf4eb
field public static final int ICON_PLAYLIST_ADD = 57403; // 0xe03b
field public static final int ICON_PLAYLIST_REMOVE = 60288; // 0xeb80
field public static final int ICON_PLUS = 57669; // 0xe145
field public static final int ICON_PLUS_CIRCLE_FILLED = 1040711; // 0xfe147
field public static final int ICON_PLUS_CIRCLE_UNFILLED = 57671; // 0xe147
field public static final int ICON_PREVIOUS = 57413; // 0xe045
field public static final int ICON_QUALITY = 58409; // 0xe429
field public static final int ICON_QUEUE_ADD = 57436; // 0xe05c
field public static final int ICON_QUEUE_NEXT = 57446; // 0xe066
field public static final int ICON_QUEUE_REMOVE = 57447; // 0xe067
field public static final int ICON_RADIO = 58654; // 0xe51e
field public static final int ICON_REPEAT_ALL = 57408; // 0xe040
field public static final int ICON_REPEAT_OFF = 1040448; // 0xfe040
field public static final int ICON_REPEAT_ONE = 57409; // 0xe041
field public static final int ICON_REWIND = 57376; // 0xe020
field public static final int ICON_SETTINGS = 59576; // 0xe8b8
field public static final int ICON_SHARE = 59405; // 0xe80d
field public static final int ICON_SHUFFLE_OFF = 1040452; // 0xfe044
field public static final int ICON_SHUFFLE_ON = 57411; // 0xe043
field public static final int ICON_SHUFFLE_STAR = 1040451; // 0xfe043
field public static final int ICON_SIGNAL = 61512; // 0xf048
field public static final int ICON_SKIP_BACK = 57410; // 0xe042
field public static final int ICON_SKIP_BACK_10 = 57433; // 0xe059
field public static final int ICON_SKIP_BACK_15 = 1040473; // 0xfe059
field public static final int ICON_SKIP_BACK_30 = 57434; // 0xe05a
field public static final int ICON_SKIP_BACK_5 = 57435; // 0xe05b
field public static final int ICON_SKIP_FORWARD = 63220; // 0xf6f4
field public static final int ICON_SKIP_FORWARD_10 = 57430; // 0xe056
field public static final int ICON_SKIP_FORWARD_15 = 1040470; // 0xfe056
field public static final int ICON_SKIP_FORWARD_30 = 57431; // 0xe057
field public static final int ICON_SKIP_FORWARD_5 = 57432; // 0xe058
field public static final int ICON_STAR_FILLED = 1042488; // 0xfe838
field public static final int ICON_STAR_UNFILLED = 59448; // 0xe838
field public static final int ICON_STOP = 57415; // 0xe047
field public static final int ICON_SUBTITLES = 57416; // 0xe048
field public static final int ICON_SUBTITLES_OFF = 61298; // 0xef72
field public static final int ICON_SYNC = 58919; // 0xe627
field public static final int ICON_THUMB_DOWN_FILLED = 1042651; // 0xfe8db
field public static final int ICON_THUMB_DOWN_UNFILLED = 59611; // 0xe8db
field public static final int ICON_THUMB_UP_FILLED = 1042652; // 0xfe8dc
field public static final int ICON_THUMB_UP_UNFILLED = 59612; // 0xe8dc
field public static final int ICON_UNDEFINED = 0; // 0x0
field public static final int ICON_VOLUME_DOWN = 57421; // 0xe04d
field public static final int ICON_VOLUME_OFF = 57423; // 0xe04f
field public static final int ICON_VOLUME_UP = 57424; // 0xe050
field public final CharSequence displayName;
field @androidx.media3.session.CommandButton.Icon public final int icon;
field @DrawableRes public final int iconResId;
field public final boolean isEnabled;
field @androidx.media3.common.Player.Command public final int playerCommand;
@ -1488,16 +1571,21 @@ package androidx.media3.session {
}
public static final class CommandButton.Builder {
ctor public CommandButton.Builder();
ctor @Deprecated public CommandButton.Builder();
ctor public CommandButton.Builder(@androidx.media3.session.CommandButton.Icon int);
method public androidx.media3.session.CommandButton build();
method public androidx.media3.session.CommandButton.Builder setCustomIconResId(@DrawableRes int);
method public androidx.media3.session.CommandButton.Builder setDisplayName(CharSequence);
method public androidx.media3.session.CommandButton.Builder setEnabled(boolean);
method public androidx.media3.session.CommandButton.Builder setExtras(android.os.Bundle);
method public androidx.media3.session.CommandButton.Builder setIconResId(@DrawableRes int);
method @Deprecated public androidx.media3.session.CommandButton.Builder setIconResId(@DrawableRes int);
method public androidx.media3.session.CommandButton.Builder setPlayerCommand(@androidx.media3.common.Player.Command int);
method public androidx.media3.session.CommandButton.Builder setSessionCommand(androidx.media3.session.SessionCommand);
}
@IntDef({androidx.media3.session.CommandButton.ICON_UNDEFINED, androidx.media3.session.CommandButton.ICON_PLAY, androidx.media3.session.CommandButton.ICON_PAUSE, androidx.media3.session.CommandButton.ICON_STOP, androidx.media3.session.CommandButton.ICON_NEXT, androidx.media3.session.CommandButton.ICON_PREVIOUS, androidx.media3.session.CommandButton.ICON_SKIP_FORWARD, androidx.media3.session.CommandButton.ICON_SKIP_FORWARD_5, androidx.media3.session.CommandButton.ICON_SKIP_FORWARD_10, androidx.media3.session.CommandButton.ICON_SKIP_FORWARD_15, androidx.media3.session.CommandButton.ICON_SKIP_FORWARD_30, androidx.media3.session.CommandButton.ICON_SKIP_BACK, androidx.media3.session.CommandButton.ICON_SKIP_BACK_5, androidx.media3.session.CommandButton.ICON_SKIP_BACK_10, androidx.media3.session.CommandButton.ICON_SKIP_BACK_15, androidx.media3.session.CommandButton.ICON_SKIP_BACK_30, androidx.media3.session.CommandButton.ICON_FAST_FORWARD, androidx.media3.session.CommandButton.ICON_REWIND, androidx.media3.session.CommandButton.ICON_REPEAT_ALL, androidx.media3.session.CommandButton.ICON_REPEAT_ONE, androidx.media3.session.CommandButton.ICON_REPEAT_OFF, androidx.media3.session.CommandButton.ICON_SHUFFLE_ON, androidx.media3.session.CommandButton.ICON_SHUFFLE_OFF, androidx.media3.session.CommandButton.ICON_SHUFFLE_STAR, androidx.media3.session.CommandButton.ICON_HEART_FILLED, androidx.media3.session.CommandButton.ICON_HEART_UNFILLED, androidx.media3.session.CommandButton.ICON_STAR_FILLED, androidx.media3.session.CommandButton.ICON_STAR_UNFILLED, androidx.media3.session.CommandButton.ICON_BOOKMARK_FILLED, androidx.media3.session.CommandButton.ICON_BOOKMARK_UNFILLED, androidx.media3.session.CommandButton.ICON_THUMB_UP_FILLED, androidx.media3.session.CommandButton.ICON_THUMB_UP_UNFILLED, androidx.media3.session.CommandButton.ICON_THUMB_DOWN_FILLED, androidx.media3.session.CommandButton.ICON_THUMB_DOWN_UNFILLED, androidx.media3.session.CommandButton.ICON_FLAG_FILLED, androidx.media3.session.CommandButton.ICON_FLAG_UNFILLED, androidx.media3.session.CommandButton.ICON_PLUS, androidx.media3.session.CommandButton.ICON_MINUS, androidx.media3.session.CommandButton.ICON_PLAYLIST_ADD, androidx.media3.session.CommandButton.ICON_PLAYLIST_REMOVE, androidx.media3.session.CommandButton.ICON_QUEUE_ADD, androidx.media3.session.CommandButton.ICON_QUEUE_NEXT, androidx.media3.session.CommandButton.ICON_QUEUE_REMOVE, androidx.media3.session.CommandButton.ICON_BLOCK, androidx.media3.session.CommandButton.ICON_PLUS_CIRCLE_FILLED, androidx.media3.session.CommandButton.ICON_PLUS_CIRCLE_UNFILLED, androidx.media3.session.CommandButton.ICON_MINUS_CIRCLE_FILLED, androidx.media3.session.CommandButton.ICON_MINUS_CIRCLE_UNFILLED, androidx.media3.session.CommandButton.ICON_CHECK_CIRCLE_FILLED, androidx.media3.session.CommandButton.ICON_CHECK_CIRCLE_UNFILLED, androidx.media3.session.CommandButton.ICON_PLAYBACK_SPEED, androidx.media3.session.CommandButton.ICON_PLAYBACK_SPEED_0_5, androidx.media3.session.CommandButton.ICON_PLAYBACK_SPEED_0_8, androidx.media3.session.CommandButton.ICON_PLAYBACK_SPEED_1_0, androidx.media3.session.CommandButton.ICON_PLAYBACK_SPEED_1_2, androidx.media3.session.CommandButton.ICON_PLAYBACK_SPEED_1_5, androidx.media3.session.CommandButton.ICON_PLAYBACK_SPEED_1_8, androidx.media3.session.CommandButton.ICON_PLAYBACK_SPEED_2_0, androidx.media3.session.CommandButton.ICON_SETTINGS, androidx.media3.session.CommandButton.ICON_QUALITY, androidx.media3.session.CommandButton.ICON_SUBTITLES, androidx.media3.session.CommandButton.ICON_SUBTITLES_OFF, androidx.media3.session.CommandButton.ICON_CLOSED_CAPTIONS, androidx.media3.session.CommandButton.ICON_CLOSED_CAPTIONS_OFF, androidx.media3.session.CommandButton.ICON_SYNC, androidx.media3.session.CommandButton.ICON_SHARE, androidx.media3.session.CommandButton.ICON_VOLUME_UP, androidx.media3.session.CommandButton.ICON_VOLUME_DOWN, androidx.media3.session.CommandButton.ICON_VOLUME_OFF, androidx.media3.session.CommandButton.ICON_ARTIST, androidx.media3.session.CommandButton.ICON_ALBUM, androidx.media3.session.CommandButton.ICON_RADIO, androidx.media3.session.CommandButton.ICON_SIGNAL, androidx.media3.session.CommandButton.ICON_FEED}) @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 CommandButton.Icon {
}
public final class LibraryResult<V> {
method public static <V> androidx.media3.session.LibraryResult<V> ofError(@androidx.media3.session.LibraryResult.Code int);
method public static <V> androidx.media3.session.LibraryResult<V> ofError(@androidx.media3.session.LibraryResult.Code int, @Nullable androidx.media3.session.MediaLibraryService.LibraryParams);
@ -1596,6 +1684,7 @@ package androidx.media3.session {
method public final long getCurrentPosition();
method public final androidx.media3.common.Timeline getCurrentTimeline();
method public final androidx.media3.common.Tracks getCurrentTracks();
method public final com.google.common.collect.ImmutableList<androidx.media3.session.CommandButton> getCustomLayout();
method public final androidx.media3.common.DeviceInfo getDeviceInfo();
method @IntRange(from=0) public final int getDeviceVolume();
method public final long getDuration();
@ -1615,6 +1704,7 @@ package androidx.media3.session {
method public final long getSeekBackIncrement();
method public final long getSeekForwardIncrement();
method @Nullable public final android.app.PendingIntent getSessionActivity();
method public final android.os.Bundle getSessionExtras();
method public final boolean getShuffleModeEnabled();
method public final long getTotalBufferedDuration();
method public final androidx.media3.common.TrackSelectionParameters getTrackSelectionParameters();
@ -1697,6 +1787,7 @@ package androidx.media3.session {
public static interface MediaController.Listener {
method public default void onAvailableSessionCommandsChanged(androidx.media3.session.MediaController, androidx.media3.session.SessionCommands);
method public default com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionResult> onCustomCommand(androidx.media3.session.MediaController, androidx.media3.session.SessionCommand, android.os.Bundle);
method public default void onCustomLayoutChanged(androidx.media3.session.MediaController, java.util.List<androidx.media3.session.CommandButton>);
method public default void onDisconnected(androidx.media3.session.MediaController);
method public default void onExtrasChanged(androidx.media3.session.MediaController, android.os.Bundle);
method public default com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionResult> onSetCustomLayout(androidx.media3.session.MediaController, java.util.List<androidx.media3.session.CommandButton>);
@ -1759,6 +1850,7 @@ package androidx.media3.session {
method public final String getId();
method public final androidx.media3.common.Player getPlayer();
method @Nullable public final android.app.PendingIntent getSessionActivity();
method public android.os.Bundle getSessionExtras();
method public final androidx.media3.session.SessionToken getToken();
method public final void release();
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);
@ -1777,6 +1869,7 @@ package androidx.media3.session {
method public androidx.media3.session.MediaSession.Builder setExtras(android.os.Bundle);
method public androidx.media3.session.MediaSession.Builder setId(String);
method public androidx.media3.session.MediaSession.Builder setSessionActivity(android.app.PendingIntent);
method public androidx.media3.session.MediaSession.Builder setSessionExtras(android.os.Bundle);
}
public static interface MediaSession.Callback {
@ -1887,6 +1980,7 @@ package androidx.media3.session {
public final class SessionToken {
ctor public SessionToken(android.content.Context, android.content.ComponentName);
method public static com.google.common.util.concurrent.ListenableFuture<androidx.media3.session.SessionToken> createSessionToken(android.content.Context, android.media.session.MediaSession.Token);
method public static com.google.common.collect.ImmutableSet<androidx.media3.session.SessionToken> getAllServiceTokens(android.content.Context);
method public android.os.Bundle getExtras();
method public String getPackageName();

View File

@ -19,7 +19,8 @@ buildscript {
dependencies {
classpath 'com.android.tools.build:gradle:8.3.2'
classpath 'com.google.android.gms:strict-version-matcher-plugin:1.2.4'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.10'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:2.0.20'
classpath 'org.jetbrains.kotlin:compose-compiler-gradle-plugin:2.0.20'
}
}
allprojects {

View File

@ -29,6 +29,10 @@ android {
}
}
lintOptions {
checkTestSources true
}
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8

View File

@ -12,15 +12,15 @@
// See the License for the specific language governing permissions and
// limitations under the License.
project.ext {
releaseVersion = '1.5.1'
releaseVersionCode = 1_005_001_3_00
releaseVersion = '1.6.0'
releaseVersionCode = 1_006_000_3_00
minSdkVersion = 21
// See https://developer.android.com/training/cars/media/automotive-os#automotive-module
automotiveMinSdkVersion = 28
appTargetSdkVersion = 34
// Upgrading this requires [Internal ref: b/193254928] to be fixed, or some
// additional robolectric config.
targetSdkVersion = 30
targetSdkVersion = 31
compileSdkVersion = 35
dexmakerVersion = '2.28.3'
// Use the same JUnit version as the Android repo:
@ -30,10 +30,11 @@ project.ext {
// https://cs.android.com/android/platform/superproject/main/+/main:external/guava/METADATA
guavaVersion = '33.3.1-android'
glideVersion = '4.14.2'
kotlinxCoroutinesVersion = '1.8.1'
// Not the same as kotlin version, https://github.com/Kotlin/kotlinx.coroutines/releases
kotlinxCoroutinesVersion = '1.9.0'
leakCanaryVersion = '2.10'
mockitoVersion = '3.12.4'
robolectricVersion = '4.11'
robolectricVersion = '4.14.1'
// Keep this in sync with Google's internal Checker Framework version.
checkerframeworkVersion = '3.13.0'
errorProneVersion = '2.18.0'
@ -46,11 +47,13 @@ project.ext {
androidxConstraintLayoutVersion = '2.1.4'
androidxCoreVersion = '1.8.0'
androidxExifInterfaceVersion = '1.3.6'
androidxLifecycleVersion = '2.6.0'
androidxLifecycleVersion = '2.8.7'
androidxMediaVersion = '1.7.0'
androidxRecyclerViewVersion = '1.3.0'
androidxMaterialVersion = '1.8.0'
androidxTestCoreVersion = '1.5.0'
androidxTestUiAutomatorVersion = '2.3.0'
androidxWindowVersion = '1.3.0'
androidxTestEspressoVersion = '3.5.1'
androidxTestJUnitVersion = '1.1.5'
androidxTestRunnerVersion = '1.5.2'

View File

@ -52,6 +52,8 @@ include modulePrefix + 'lib-ui'
project(modulePrefix + 'lib-ui').projectDir = new File(rootDir, 'libraries/ui')
include modulePrefix + 'lib-ui-leanback'
project(modulePrefix + 'lib-ui-leanback').projectDir = new File(rootDir, 'libraries/ui_leanback')
include modulePrefix + 'lib-ui-compose'
project(modulePrefix + 'lib-ui-compose').projectDir = new File(rootDir, 'libraries/ui_compose')
include modulePrefix + 'lib-database'
project(modulePrefix + 'lib-database').projectDir = new File(rootDir, 'libraries/database')
@ -81,6 +83,8 @@ if (gradle.ext.has('androidxMediaEnableMidiModule') && gradle.ext.androidxMediaE
include modulePrefix + 'lib-decoder-midi'
project(modulePrefix + 'lib-decoder-midi').projectDir = new File(rootDir, 'libraries/decoder_midi')
}
include modulePrefix + 'lib-decoder-mpegh'
project(modulePrefix + 'lib-decoder-mpegh').projectDir = new File(rootDir, 'libraries/decoder_mpegh')
include modulePrefix + 'lib-decoder-opus'
project(modulePrefix + 'lib-decoder-opus').projectDir = new File(rootDir, 'libraries/decoder_opus')
include modulePrefix + 'lib-decoder-vp9'

View File

@ -14,6 +14,7 @@
apply from: '../../constants.gradle'
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'org.jetbrains.kotlin.plugin.compose'
android {
namespace 'androidx.media3.demo.compose'
@ -52,12 +53,8 @@ android {
disable 'GoogleAppIndexingWarning','MissingTranslation'
}
buildFeatures {
viewBinding true
compose true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.3"
}
testOptions {
unitTests {
@ -67,21 +64,21 @@ android {
}
dependencies {
def composeBom = platform('androidx.compose:compose-bom:2024.05.00')
def composeBom = platform('androidx.compose:compose-bom:2024.12.01')
implementation composeBom
implementation 'androidx.activity:activity-compose:1.9.0'
implementation 'androidx.compose.foundation:foundation-android:1.6.7'
implementation 'androidx.compose.material3:material3-android:1.2.1'
implementation 'androidx.compose.foundation:foundation'
implementation 'androidx.compose.material3:material3'
implementation 'androidx.compose.material:material-icons-extended'
implementation 'androidx.compose.ui:ui-tooling-preview'
implementation 'androidx.lifecycle:lifecycle-runtime-compose:' + androidxLifecycleVersion
implementation 'com.google.android.material:material:' + androidxMaterialVersion
implementation project(modulePrefix + 'lib-exoplayer')
implementation project(modulePrefix + 'lib-ui-compose')
debugImplementation 'androidx.compose.ui:ui-tooling'
// For detecting and debugging leaks only. LeakCanary is not needed for demo app to work.
debugImplementation 'com.squareup.leakcanary:leakcanary-android:' + leakCanaryVersion
testImplementation 'org.jetbrains.kotlinx:kotlinx-coroutines-test:' + kotlinxCoroutinesVersion
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
testImplementation project(modulePrefix + 'test-utils')
}

20
demos/compose/lint.xml Normal file
View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<lint>
<issue id="UnsafeOptInUsageError">
<option name="opt-in" value="androidx.media3.common.util.UnstableApi" />
</issue>
</lint>

View File

@ -15,49 +15,139 @@
*/
package androidx.media3.demo.compose
import android.content.Context
import android.os.Build
import android.os.Bundle
import androidx.activity.ComponentActivity
import androidx.activity.compose.setContent
import androidx.activity.enableEdgeToEdge
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Column
import androidx.compose.material3.Surface
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.navigationBarsPadding
import androidx.compose.foundation.layout.padding
import androidx.compose.material3.Button
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableIntStateOf
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.unit.dp
import androidx.lifecycle.compose.LifecycleResumeEffect
import androidx.lifecycle.compose.LifecycleStartEffect
import androidx.media3.common.MediaItem
import androidx.media3.common.Player
import androidx.media3.demo.compose.buttons.ExtraControls
import androidx.media3.demo.compose.buttons.MinimalControls
import androidx.media3.demo.compose.data.videos
import androidx.media3.demo.compose.layout.CONTENT_SCALES
import androidx.media3.demo.compose.layout.noRippleClickable
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.compose.PlayerSurface
import androidx.media3.ui.compose.SURFACE_TYPE_SURFACE_VIEW
import androidx.media3.ui.compose.modifiers.resizeWithContentScale
import androidx.media3.ui.compose.state.rememberPresentationState
class MainActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
enableEdgeToEdge()
setContent {
Surface {
Column(
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
val context = LocalContext.current
val exoPlayer = remember {
ExoPlayer.Builder(context).build().apply {
setMediaItem(MediaItem.fromUri(videos[0]))
prepare()
playWhenReady = true
repeatMode = Player.REPEAT_MODE_ONE
}
}
PlayerSurface(
player = exoPlayer,
surfaceType = SURFACE_TYPE_SURFACE_VIEW,
modifier = Modifier.align(Alignment.CenterHorizontally),
)
}
setContent { ComposeDemoApp() }
}
}
@Composable
fun ComposeDemoApp(modifier: Modifier = Modifier) {
val context = LocalContext.current
var player by remember { mutableStateOf<Player?>(null) }
// See the following resources
// https://developer.android.com/topic/libraries/architecture/lifecycle#onStop-and-savedState
// https://developer.android.com/develop/ui/views/layout/support-multi-window-mode#multi-window_mode_configuration
// https://developer.android.com/develop/ui/compose/layouts/adaptive/support-multi-window-mode#android_9
if (Build.VERSION.SDK_INT > 23) {
// Initialize/release in onStart()/onStop() only because in a multi-window environment multiple
// apps can be visible at the same time. The apps that are out-of-focus are paused, but video
// playback should continue.
LifecycleStartEffect(Unit) {
player = initializePlayer(context)
onStopOrDispose {
player?.apply { release() }
player = null
}
}
} else {
// Call to onStop() is not guaranteed, hence we release the Player in onPause() instead
LifecycleResumeEffect(Unit) {
player = initializePlayer(context)
onPauseOrDispose {
player?.apply { release() }
player = null
}
}
}
player?.let { MediaPlayerScreen(player = it, modifier = modifier.fillMaxSize()) }
}
private fun initializePlayer(context: Context): Player =
ExoPlayer.Builder(context).build().apply {
setMediaItems(videos.map(MediaItem::fromUri))
prepare()
}
@Composable
private fun MediaPlayerScreen(player: Player, modifier: Modifier = Modifier) {
var showControls by remember { mutableStateOf(true) }
var currentContentScaleIndex by remember { mutableIntStateOf(0) }
val contentScale = CONTENT_SCALES[currentContentScaleIndex].second
val presentationState = rememberPresentationState(player)
val scaledModifier = Modifier.resizeWithContentScale(contentScale, presentationState.videoSizeDp)
// Only use MediaPlayerScreen's modifier once for the top level Composable
Box(modifier) {
// Always leave PlayerSurface to be part of the Compose tree because it will be initialised in
// the process. If this composable is guarded by some condition, it might never become visible
// because the Player will not emit the relevant event, e.g. the first frame being ready.
PlayerSurface(
player = player,
surfaceType = SURFACE_TYPE_SURFACE_VIEW,
modifier = scaledModifier.noRippleClickable { showControls = !showControls },
)
if (presentationState.coverSurface) {
// Cover the surface that is being prepared with a shutter
// Do not use scaledModifier here, makes the Box be measured at 0x0
Box(Modifier.matchParentSize().background(Color.Black))
}
if (showControls) {
// drawn on top of a potential shutter
MinimalControls(player, Modifier.align(Alignment.Center))
ExtraControls(
player,
Modifier.fillMaxWidth()
.align(Alignment.BottomCenter)
.background(Color.Gray.copy(alpha = 0.4f))
.navigationBarsPadding(),
)
}
Button(
onClick = { currentContentScaleIndex = currentContentScaleIndex.inc() % CONTENT_SCALES.size },
modifier = Modifier.align(Alignment.TopCenter).padding(top = 48.dp),
) {
Text("ContentScale is ${CONTENT_SCALES[currentContentScaleIndex].first}")
}
}
}

View File

@ -0,0 +1,37 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.demo.compose.buttons
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.media3.common.Player
@Composable
internal fun ExtraControls(player: Player, modifier: Modifier = Modifier) {
Row(
modifier = modifier,
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
) {
PlaybackSpeedPopUpButton(player)
ShuffleButton(player)
RepeatButton(player)
}
}

View File

@ -0,0 +1,51 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.demo.compose.buttons
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.runtime.Composable
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.unit.dp
import androidx.media3.common.Player
/**
* Minimal playback controls for a [Player].
*
* Includes buttons for seeking to a previous/next items or playing/pausing the playback.
*/
@Composable
internal fun MinimalControls(player: Player, modifier: Modifier = Modifier) {
val graySemiTransparentBackground = Color.Gray.copy(alpha = 0.1f)
val modifierForIconButton =
modifier.size(80.dp).background(graySemiTransparentBackground, CircleShape)
Row(
modifier = modifier.fillMaxWidth(),
horizontalArrangement = Arrangement.SpaceEvenly,
verticalAlignment = Alignment.CenterVertically,
) {
PreviousButton(player, modifierForIconButton)
PlayPauseButton(player, modifierForIconButton)
NextButton(player, modifierForIconButton)
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.demo.compose.buttons
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.SkipNext
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.media3.common.Player
import androidx.media3.demo.compose.R
import androidx.media3.ui.compose.state.rememberNextButtonState
@Composable
internal fun NextButton(player: Player, modifier: Modifier = Modifier) {
val state = rememberNextButtonState(player)
IconButton(onClick = state::onClick, modifier = modifier, enabled = state.isEnabled) {
Icon(
Icons.Default.SkipNext,
contentDescription = stringResource(R.string.next_button),
modifier = modifier,
)
}
}

View File

@ -0,0 +1,41 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.demo.compose.buttons
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Pause
import androidx.compose.material.icons.filled.PlayArrow
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.media3.common.Player
import androidx.media3.demo.compose.R
import androidx.media3.ui.compose.state.rememberPlayPauseButtonState
@Composable
internal fun PlayPauseButton(player: Player, modifier: Modifier = Modifier) {
val state = rememberPlayPauseButtonState(player)
val icon = if (state.showPlay) Icons.Default.PlayArrow else Icons.Default.Pause
val contentDescription =
if (state.showPlay) stringResource(R.string.playpause_button_play)
else stringResource(R.string.playpause_button_pause)
IconButton(onClick = state::onClick, modifier = modifier, enabled = state.isEnabled) {
Icon(icon, contentDescription = contentDescription, modifier = modifier)
}
}

View File

@ -0,0 +1,108 @@
/*
* Copyright 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.demo.compose.buttons
import android.view.Gravity
import androidx.compose.foundation.background
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.wrapContentSize
import androidx.compose.foundation.layout.wrapContentWidth
import androidx.compose.foundation.text.BasicText
import androidx.compose.material3.Text
import androidx.compose.material3.TextButton
import androidx.compose.runtime.Composable
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.platform.LocalView
import androidx.compose.ui.text.font.FontWeight
import androidx.compose.ui.window.Dialog
import androidx.compose.ui.window.DialogProperties
import androidx.compose.ui.window.DialogWindowProvider
import androidx.media3.common.Player
import androidx.media3.ui.compose.state.rememberPlaybackSpeedState
@Composable
internal fun PlaybackSpeedPopUpButton(
player: Player,
modifier: Modifier = Modifier,
speedSelection: List<Float> = listOf(0.5f, 0.75f, 1.0f, 1.25f, 1.5f, 1.75f, 2.0f),
) {
val state = rememberPlaybackSpeedState(player)
var openDialog by remember { mutableStateOf(false) }
TextButton(onClick = { openDialog = true }, modifier = modifier, enabled = state.isEnabled) {
// TODO: look into TextMeasurer to ensure 1.1 and 2.2 occupy the same space
BasicText("%.1fx".format(state.playbackSpeed))
}
if (openDialog) {
BottomDialogOfChoices(
currentSpeed = state.playbackSpeed,
choices = speedSelection,
onDismissRequest = { openDialog = false },
onSelectChoice = state::updatePlaybackSpeed,
)
}
}
@Composable
private fun BottomDialogOfChoices(
currentSpeed: Float,
choices: List<Float>,
onDismissRequest: () -> Unit,
onSelectChoice: (Float) -> Unit,
) {
Dialog(
onDismissRequest = onDismissRequest,
properties = DialogProperties(usePlatformDefaultWidth = false),
) {
val dialogWindowProvider = LocalView.current.parent as? DialogWindowProvider
dialogWindowProvider?.window?.let { window ->
window.setGravity(Gravity.BOTTOM) // Move down, by default dialogs are in the centre
window.setDimAmount(0f) // Remove dimmed background of ongoing playback
}
Box(modifier = Modifier.wrapContentSize().background(Color.LightGray)) {
Column(
modifier = Modifier.fillMaxWidth().wrapContentWidth(),
verticalArrangement = Arrangement.Center,
horizontalAlignment = Alignment.CenterHorizontally,
) {
choices.forEach { speed ->
TextButton(
onClick = {
onSelectChoice(speed)
onDismissRequest()
}
) {
var fontWeight = FontWeight(400)
if (speed == currentSpeed) {
fontWeight = FontWeight(1000)
}
Text("%.1fx".format(speed), fontWeight = fontWeight)
}
}
}
}
}
}

View File

@ -0,0 +1,40 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.demo.compose.buttons
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.SkipPrevious
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.media3.common.Player
import androidx.media3.demo.compose.R
import androidx.media3.ui.compose.state.rememberPreviousButtonState
@Composable
internal fun PreviousButton(player: Player, modifier: Modifier = Modifier) {
val state = rememberPreviousButtonState(player)
IconButton(onClick = state::onClick, modifier = modifier, enabled = state.isEnabled) {
Icon(
Icons.Default.SkipPrevious,
contentDescription = stringResource(R.string.previous_button),
modifier = modifier,
)
}
}

View File

@ -0,0 +1,58 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.demo.compose.buttons
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Repeat
import androidx.compose.material.icons.filled.RepeatOn
import androidx.compose.material.icons.filled.RepeatOneOn
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.vector.ImageVector
import androidx.compose.ui.res.stringResource
import androidx.media3.common.Player
import androidx.media3.demo.compose.R
import androidx.media3.ui.compose.state.rememberRepeatButtonState
@Composable
internal fun RepeatButton(player: Player, modifier: Modifier = Modifier) {
val state = rememberRepeatButtonState(player)
val icon = repeatModeIcon(state.repeatModeState)
val contentDescription = repeatModeContentDescription(state.repeatModeState)
IconButton(onClick = state::onClick, modifier = modifier, enabled = state.isEnabled) {
Icon(icon, contentDescription = contentDescription, modifier = modifier)
}
}
private fun repeatModeIcon(repeatMode: @Player.RepeatMode Int): ImageVector {
return when (repeatMode) {
Player.REPEAT_MODE_OFF -> Icons.Default.Repeat
Player.REPEAT_MODE_ONE -> Icons.Default.RepeatOneOn
else -> Icons.Default.RepeatOn
}
}
@Composable
private fun repeatModeContentDescription(repeatMode: @Player.RepeatMode Int): String {
return when (repeatMode) {
Player.REPEAT_MODE_OFF -> stringResource(R.string.repeat_button_repeat_off_description)
Player.REPEAT_MODE_ONE -> stringResource(R.string.repeat_button_repeat_one_description)
else -> stringResource(R.string.repeat_button_repeat_all_description)
}
}

View File

@ -0,0 +1,44 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.demo.compose.buttons
import androidx.compose.material.icons.Icons
import androidx.compose.material.icons.filled.Shuffle
import androidx.compose.material.icons.filled.ShuffleOn
import androidx.compose.material3.Icon
import androidx.compose.material3.IconButton
import androidx.compose.runtime.Composable
import androidx.compose.ui.Modifier
import androidx.compose.ui.res.stringResource
import androidx.media3.common.Player
import androidx.media3.demo.compose.R
import androidx.media3.ui.compose.state.rememberShuffleButtonState
@Composable
internal fun ShuffleButton(player: Player, modifier: Modifier = Modifier) {
val state = rememberShuffleButtonState(player)
val icon = if (state.shuffleOn) Icons.Default.ShuffleOn else Icons.Default.Shuffle
val contentDescription =
if (state.shuffleOn) {
stringResource(R.string.shuffle_button_shuffle_on_description)
} else {
stringResource(R.string.shuffle_button_shuffle_off_description)
}
IconButton(onClick = state::onClick, modifier = modifier, enabled = state.isEnabled) {
Icon(icon, contentDescription = contentDescription, modifier = modifier)
}
}

View File

@ -18,6 +18,7 @@ package androidx.media3.demo.compose.data
val videos =
listOf(
"https://html5demos.com/assets/dizzy.mp4",
"https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/gear0/fileSequence0.aac",
"https://storage.googleapis.com/exoplayer-test-media-0/shortform_2.mp4",
"https://storage.googleapis.com/exoplayer-test-media-1/gen-3/screens/dash-vod-single-segment/video-vp9-360.webm",
"https://storage.googleapis.com/exoplayer-test-media-0/shortform_3.mp4",
)

View File

@ -0,0 +1,32 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.demo.compose.layout
import androidx.compose.foundation.clickable
import androidx.compose.foundation.interaction.MutableInteractionSource
import androidx.compose.runtime.Composable
import androidx.compose.runtime.remember
import androidx.compose.ui.Modifier
@Composable
internal fun Modifier.noRippleClickable(onClick: () -> Unit): Modifier =
clickable(
interactionSource = remember { MutableInteractionSource() },
indication = null, // to prevent the ripple from the tap
) {
onClick()
}

View File

@ -0,0 +1,30 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.demo.compose.layout
import androidx.compose.ui.layout.ContentScale
val CONTENT_SCALES =
listOf(
"Fit" to ContentScale.Fit,
"Crop" to ContentScale.Crop,
"None" to ContentScale.None,
"Inside" to ContentScale.Inside,
"FillBounds" to ContentScale.FillBounds,
"FillHeight" to ContentScale.FillHeight,
"FillWidth" to ContentScale.FillWidth,
)

View File

@ -15,12 +15,13 @@
-->
<resources>
<string name="app_name">Media3 Compose Demo</string>
<string name="current_playlist_name">Current playlist</string>
<string name="open_player_content_description">Click to view your play list</string>
<string name="added_media_item_format">Added %1$s to playlist</string>
<string name="shuffle">Shuffle</string>
<string name="play_button">Play</string>
<string name="waiting_for_metadata">Waiting for playlist to load…</string>
<string name="notification_permission_denied">
"Without notification access the app can't warn about failed background operations"</string>
<string name="playpause_button_play">Play</string>
<string name="playpause_button_pause">Pause</string>
<string name="next_button">Next</string>
<string name="previous_button">Previous</string>
<string name="repeat_button_repeat_off_description">Current mode: Repeat none. Toggle repeat mode.</string>
<string name="repeat_button_repeat_one_description">Current mode: Repeat one. Toggle repeat mode.</string>
<string name="repeat_button_repeat_all_description">Current mode: Repeat all. Toggle repeat mode.</string>
<string name="shuffle_button_shuffle_on_description">Disable shuffle mode.</string>
<string name="shuffle_button_shuffle_off_description">Enable shuffle mode</string>
</resources>

View File

@ -15,6 +15,8 @@
*/
package androidx.media3.demo.composition;
import static android.content.pm.ActivityInfo.COLOR_MODE_HDR;
import static androidx.media3.common.util.Util.SDK_INT;
import static androidx.media3.transformer.Composition.HDR_MODE_EXPERIMENTAL_FORCE_INTERPRET_HDR_AS_SDR;
import static androidx.media3.transformer.Composition.HDR_MODE_KEEP_HDR;
import static androidx.media3.transformer.Composition.HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_MEDIACODEC;
@ -55,7 +57,8 @@ import androidx.media3.transformer.EditedMediaItemSequence;
import androidx.media3.transformer.Effects;
import androidx.media3.transformer.ExportException;
import androidx.media3.transformer.ExportResult;
import androidx.media3.transformer.InAppMuxer;
import androidx.media3.transformer.InAppFragmentedMp4Muxer;
import androidx.media3.transformer.InAppMp4Muxer;
import androidx.media3.transformer.JsonUtil;
import androidx.media3.transformer.Transformer;
import androidx.media3.ui.PlayerView;
@ -111,6 +114,9 @@ public final class CompositionPreviewActivity extends AppCompatActivity {
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
super.onCreate(savedInstanceState);
if (SDK_INT >= 26) {
getWindow().setColorMode(COLOR_MODE_HDR);
}
setContentView(R.layout.composition_preview_activity);
playerView = findViewById(R.id.composition_player_view);
@ -186,6 +192,18 @@ public final class CompositionPreviewActivity extends AppCompatActivity {
exportStopwatch.reset();
}
@SuppressWarnings("MissingSuperCall")
@Override
public void onBackPressed() {
if (compositionPlayer != null) {
compositionPlayer.pause();
}
if (exportStopwatch.isRunning()) {
cancelExport();
exportStopwatch.reset();
}
}
private Composition prepareComposition() {
String[] presetUris = getResources().getStringArray(/* id= */ R.array.preset_uris);
int[] presetDurationsUs = getResources().getIntArray(/* id= */ R.array.preset_durations);
@ -279,7 +297,6 @@ public final class CompositionPreviewActivity extends AppCompatActivity {
Log.e(TAG, "Preview error", error);
}
});
player.setRepeatMode(Player.REPEAT_MODE_ALL);
player.setComposition(composition);
player.prepare();
player.play();
@ -346,21 +363,20 @@ public final class CompositionPreviewActivity extends AppCompatActivity {
enableDebugTracingCheckBox.setOnCheckedChangeListener(
(buttonView, isChecked) -> DebugTraceUtil.enableTracing = isChecked);
// Connect producing fragmented MP4 to using Media3 Muxer
CheckBox useMedia3MuxerCheckBox =
exportSettingsDialogView.findViewById(R.id.use_media3_muxer_checkbox);
CheckBox produceFragmentedMp4CheckBox =
exportSettingsDialogView.findViewById(R.id.produce_fragmented_mp4_checkbox);
useMedia3MuxerCheckBox.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
if (!isChecked) {
produceFragmentedMp4CheckBox.setChecked(false);
}
});
produceFragmentedMp4CheckBox.setOnCheckedChangeListener(
CheckBox useMedia3Mp4MuxerCheckBox =
exportSettingsDialogView.findViewById(R.id.use_media3_mp4_muxer_checkbox);
CheckBox useMedia3FragmentedMp4MuxerCheckBox =
exportSettingsDialogView.findViewById(R.id.use_media3_fragmented_mp4_muxer_checkbox);
useMedia3Mp4MuxerCheckBox.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
if (isChecked) {
useMedia3MuxerCheckBox.setChecked(true);
useMedia3FragmentedMp4MuxerCheckBox.setChecked(false);
}
});
useMedia3FragmentedMp4MuxerCheckBox.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
if (isChecked) {
useMedia3Mp4MuxerCheckBox.setChecked(false);
}
});
@ -403,15 +419,15 @@ public final class CompositionPreviewActivity extends AppCompatActivity {
transformerBuilder.setVideoMimeType(selectedVideoMimeType);
}
CheckBox useMedia3MuxerCheckBox =
exportSettingsDialogView.findViewById(R.id.use_media3_muxer_checkbox);
CheckBox produceFragmentedMp4CheckBox =
exportSettingsDialogView.findViewById(R.id.produce_fragmented_mp4_checkbox);
if (useMedia3MuxerCheckBox.isChecked()) {
transformerBuilder.setMuxerFactory(
new InAppMuxer.Factory.Builder()
.setOutputFragmentedMp4(produceFragmentedMp4CheckBox.isChecked())
.build());
CheckBox useMedia3Mp4MuxerCheckBox =
exportSettingsDialogView.findViewById(R.id.use_media3_mp4_muxer_checkbox);
CheckBox useMedia3FragmentedMp4MuxerCheckBox =
exportSettingsDialogView.findViewById(R.id.use_media3_fragmented_mp4_muxer_checkbox);
if (useMedia3Mp4MuxerCheckBox.isChecked()) {
transformerBuilder.setMuxerFactory(new InAppMp4Muxer.Factory());
}
if (useMedia3FragmentedMp4MuxerCheckBox.isChecked()) {
transformerBuilder.setMuxerFactory(new InAppFragmentedMp4Muxer.Factory());
}
transformer =

View File

@ -18,6 +18,7 @@
xmlns:app="http://schemas.android.com/apk/res-auto"
android:layout_width="match_parent"
android:layout_height="match_parent"
android:keepScreenOn="true"
android:padding="16dp">
<com.google.android.material.card.MaterialCardView

View File

@ -79,12 +79,12 @@
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:text="@string/use_media3_muxer"
android:text="@string/use_media3_mp4_muxer"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="1" />
<CheckBox
android:id="@+id/use_media3_muxer_checkbox"
android:id="@+id/use_media3_mp4_muxer_checkbox"
android:layout_gravity="end"
android:checked="false"
android:layout_height="wrap_content"
@ -96,12 +96,12 @@
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:text="@string/produce_fragmented_mp4"
android:text="@string/use_media3_fragmented_mp4_muxer"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="1" />
<CheckBox
android:id="@+id/produce_fragmented_mp4_checkbox"
android:id="@+id/use_media3_fragmented_mp4_muxer_checkbox"
android:layout_gravity="end"
android:checked="false"
android:layout_height="wrap_content"

View File

@ -34,6 +34,6 @@
<string name="output_audio_mime_type" translatable="false">Output audio MIME type</string>
<string name="output_video_mime_type" translatable="false">Output video MIME type</string>
<string name="enable_debug_tracing" translatable="false">Enable debug tracing</string>
<string name="use_media3_muxer" translatable="false">Use Media3 muxer</string>
<string name="produce_fragmented_mp4" translatable="false">Produce fragmented MP4</string>
<string name="use_media3_mp4_muxer" translatable="false">Use Media3 Mp4Muxer</string>
<string name="use_media3_fragmented_mp4_muxer" translatable="false">Use Media3 FragmentedMp4Muxer</string>
</resources>

10
demos/effect/README.md Normal file
View File

@ -0,0 +1,10 @@
# Effect demo
This app demonstrates how to use the [Effect][] API to modify videos. It uses
`setVideoEffects` method to add different effects to [ExoPlayer][].
See the [demos README](../README.md) for instructions on how to build and run
this demo.
[Effect]: https://github.com/androidx/media/tree/release/libraries/effect
[ExoPlayer]: https://github.com/androidx/media/tree/release/libraries/exoplayer

80
demos/effect/build.gradle Normal file
View File

@ -0,0 +1,80 @@
// Copyright 2024 The Android Open Source Project
//
// Licensed under the Apache License, Version 2.0 (the "License");
// you may not use this file except in compliance with the License.
// You may obtain a copy of the License at
//
// https://www.apache.org/licenses/LICENSE-2.0
//
// Unless required by applicable law or agreed to in writing, software
// distributed under the License is distributed on an "AS IS" BASIS,
// WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
// See the License for the specific language governing permissions and
// limitations under the License.
apply from: '../../constants.gradle'
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
apply plugin: 'org.jetbrains.kotlin.plugin.compose'
android {
namespace 'androidx.media3.demo.effect'
compileSdk project.ext.compileSdkVersion
compileOptions {
sourceCompatibility JavaVersion.VERSION_1_8
targetCompatibility JavaVersion.VERSION_1_8
}
kotlinOptions {
jvmTarget = '1.8'
}
defaultConfig {
versionName project.ext.releaseVersion
versionCode project.ext.releaseVersionCode
minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.appTargetSdkVersion
}
buildTypes {
release {
shrinkResources true
minifyEnabled true
signingConfig signingConfigs.debug
}
debug {
jniDebuggable = true
}
}
lintOptions {
// The demo app isn't indexed, and doesn't have translations.
disable 'GoogleAppIndexingWarning','MissingTranslation'
}
buildFeatures {
compose true
}
testOptions {
unitTests {
includeAndroidResources = true
}
}
}
dependencies {
def composeBom = platform('androidx.compose:compose-bom:2024.10.00')
implementation composeBom
implementation 'androidx.activity:activity-compose:1.9.0'
implementation 'androidx.compose.foundation:foundation'
implementation 'androidx.compose.material3:material3'
implementation 'com.google.android.material:material:' + androidxMaterialVersion
implementation project(modulePrefix + 'lib-exoplayer')
implementation project(modulePrefix + 'lib-ui')
implementation project(modulePrefix + 'lib-effect')
// For detecting and debugging leaks only. LeakCanary is not needed for demo app to work.
debugImplementation 'com.squareup.leakcanary:leakcanary-android:' + leakCanaryVersion
}

20
demos/effect/lint.xml Normal file
View File

@ -0,0 +1,20 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<lint>
<issue id="UnsafeOptInUsageError">
<option name="opt-in" value="androidx.media3.common.util.UnstableApi" />
</issue>
</lint>

View File

@ -0,0 +1,40 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<manifest xmlns:android="http://schemas.android.com/apk/res/android"
xmlns:tools="http://schemas.android.com/tools"
package="androidx.media3.demo.effect">
<uses-sdk/>
<uses-permission android:name="android.permission.INTERNET" />
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
<application
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.Media3EffectDemo">
<activity
android:name="androidx.media3.demo.effect.EffectActivity"
android:exported="true">
<intent-filter>
<action android:name="android.intent.action.MAIN" />
<category android:name="android.intent.category.LAUNCHER" />
</intent-filter>
</activity>
</application>
</manifest>

View File

@ -0,0 +1,27 @@
[
{
"name": "Cats -> Dogs",
"playlist": [
{
"uri": "https://html5demos.com/assets/dizzy.mp4"
},
{
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv"
}
]
},
{
"name": "Android Block -> Dogs -> BigBuckBunny",
"playlist": [
{
"uri": "https://storage.googleapis.com/exoplayer-test-media-0/android-block-1080-hevc.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/exoplayer-test-media-0/BigBuckBunny_320x180.mp4"
}
]
}
]

View File

@ -0,0 +1,140 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.demo.effect
import android.graphics.Canvas
import android.graphics.Color
import android.graphics.Paint
import android.graphics.PorterDuff
import android.os.Handler
import androidx.media3.common.VideoFrameProcessingException
import androidx.media3.common.util.Size
import androidx.media3.common.util.Util
import androidx.media3.effect.CanvasOverlay
import kotlin.math.abs
import kotlin.random.Random
/** Mimics an emitter of confetti, dropping from the center of the frame. */
internal class ConfettiOverlay : CanvasOverlay(/* useInputFrameSize= */ true) {
private val confettiList = mutableListOf<Confetti>()
private val paint = Paint()
private val handler = Handler(Util.getCurrentOrMainLooper())
private var addConfettiTask: (() -> Unit)? = null
private var width = 0f
private var height = 0f
private var started = false
override fun configure(videoSize: Size) {
super.configure(videoSize)
this.width = videoSize.width.toFloat()
this.height = videoSize.height.toFloat()
}
@Synchronized
override fun onDraw(canvas: Canvas, presentationTimeUs: Long) {
if (!started) {
start()
}
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR)
confettiList.removeAll { confetti ->
confetti.y > height / 2 || confetti.x <= 0 || confetti.x > width
}
for (confetti in confettiList) {
confetti.draw(canvas, paint)
confetti.update()
}
}
@Throws(VideoFrameProcessingException::class)
override fun release() {
super.release()
handler.post(this::stop)
}
/** Starts the confetti. */
fun start() {
addConfettiTask = this::addConfetti
handler.post(checkNotNull(addConfettiTask))
started = true
}
/** Stops the confetti. */
fun stop() {
handler.removeCallbacks(checkNotNull(addConfettiTask))
confettiList.clear()
started = false
addConfettiTask = null
}
@Synchronized
fun addConfetti() {
repeat(5) {
confettiList.add(
Confetti(
text = CONFETTI_TEXTS[abs(Random.nextInt()) % CONFETTI_TEXTS.size],
x = width / 2f,
y = EMITTER_POSITION_Y.toFloat(),
size = CONFETTI_BASE_SIZE + Random.nextInt(CONFETTI_SIZE_VARIATION),
color = Color.HSVToColor(floatArrayOf(Random.nextInt(360).toFloat(), 0.6f, 0.8f)),
)
)
}
handler.postDelayed(this::addConfetti, /* delayMillis= */ 100)
}
private class Confetti(
private val text: String,
private val size: Int,
private val color: Int,
var x: Float,
var y: Float,
) {
private val speedX = 4 * (Random.nextFloat() * 2 - 1) // Random speed in x direction
private val speedY = 4 * Random.nextFloat() // Random speed in y direction
private val rotationSpeed = (Random.nextFloat() - 0.5f) * 4f // Random rotation speed
private var rotation = Random.nextFloat() * 360f
/** Draws the [Confetti] on the [Canvas]. */
fun draw(canvas: Canvas, paint: Paint) {
canvas.save()
paint.color = color
canvas.translate(x, y)
canvas.rotate(rotation)
paint.textSize = size.toFloat()
canvas.drawText(text, /* x= */ 0f, /* y= */ 0f, paint) // Only draw text
canvas.restore()
}
/** Updates the [Confetti]. */
fun update() {
x += speedX
y += speedY
rotation += rotationSpeed
}
}
private companion object {
val CONFETTI_TEXTS = listOf("", "", "", "✦︎", "♥︎", "☕︎")
const val EMITTER_POSITION_Y = -50
const val CONFETTI_BASE_SIZE = 30
const val CONFETTI_SIZE_VARIATION = 10
}
}

View File

@ -0,0 +1,588 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.demo.effect
import android.Manifest
import android.net.Uri
import android.os.Bundle
import android.text.Spannable
import android.text.SpannableString
import android.text.style.ForegroundColorSpan
import androidx.activity.ComponentActivity
import androidx.activity.compose.rememberLauncherForActivityResult
import androidx.activity.compose.setContent
import androidx.activity.result.contract.ActivityResultContracts
import androidx.annotation.OptIn
import androidx.compose.animation.animateContentSize
import androidx.compose.animation.core.LinearEasing
import androidx.compose.animation.core.tween
import androidx.compose.foundation.background
import androidx.compose.foundation.clickable
import androidx.compose.foundation.layout.Arrangement
import androidx.compose.foundation.layout.Box
import androidx.compose.foundation.layout.Column
import androidx.compose.foundation.layout.Row
import androidx.compose.foundation.layout.fillMaxSize
import androidx.compose.foundation.layout.fillMaxWidth
import androidx.compose.foundation.layout.height
import androidx.compose.foundation.layout.padding
import androidx.compose.foundation.layout.size
import androidx.compose.foundation.lazy.LazyColumn
import androidx.compose.foundation.selection.selectable
import androidx.compose.foundation.shape.CircleShape
import androidx.compose.material3.AlertDialog
import androidx.compose.material3.Button
import androidx.compose.material3.Card
import androidx.compose.material3.Checkbox
import androidx.compose.material3.DropdownMenuItem
import androidx.compose.material3.ExperimentalMaterial3Api
import androidx.compose.material3.ExposedDropdownMenuBox
import androidx.compose.material3.ExposedDropdownMenuDefaults
import androidx.compose.material3.MaterialTheme
import androidx.compose.material3.MenuAnchorType
import androidx.compose.material3.OutlinedTextField
import androidx.compose.material3.RadioButton
import androidx.compose.material3.Scaffold
import androidx.compose.material3.Slider
import androidx.compose.material3.SnackbarHost
import androidx.compose.material3.SnackbarHostState
import androidx.compose.material3.Text
import androidx.compose.runtime.Composable
import androidx.compose.runtime.LaunchedEffect
import androidx.compose.runtime.getValue
import androidx.compose.runtime.mutableStateOf
import androidx.compose.runtime.remember
import androidx.compose.runtime.rememberCoroutineScope
import androidx.compose.runtime.saveable.rememberSaveable
import androidx.compose.runtime.setValue
import androidx.compose.ui.Alignment
import androidx.compose.ui.Modifier
import androidx.compose.ui.graphics.Color
import androidx.compose.ui.graphics.toArgb
import androidx.compose.ui.platform.LocalContext
import androidx.compose.ui.res.dimensionResource
import androidx.compose.ui.res.stringResource
import androidx.compose.ui.viewinterop.AndroidView
import androidx.core.content.ContextCompat
import androidx.lifecycle.lifecycleScope
import androidx.media3.common.Effect
import androidx.media3.common.MediaItem
import androidx.media3.common.util.UnstableApi
import androidx.media3.common.util.Util.SDK_INT
import androidx.media3.effect.Contrast
import androidx.media3.effect.OverlayEffect
import androidx.media3.effect.StaticOverlaySettings
import androidx.media3.effect.TextOverlay
import androidx.media3.effect.TextureOverlay
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.ui.PlayerView
import com.google.common.collect.ImmutableList
import kotlinx.coroutines.launch
class EffectActivity : ComponentActivity() {
override fun onCreate(savedInstanceState: Bundle?) {
super.onCreate(savedInstanceState)
val playlistHolderList = mutableStateOf<List<PlaylistHolder>>(emptyList())
lifecycleScope.launch {
playlistHolderList.value =
loadPlaylistsFromJson(JSON_FILENAME, this@EffectActivity, "EffectActivity")
}
setContent { EffectDemo(playlistHolderList.value) }
}
@OptIn(UnstableApi::class)
@Composable
private fun EffectDemo(playlistHolderList: List<PlaylistHolder>) {
val snackbarHostState = remember { SnackbarHostState() }
val coroutineScope = rememberCoroutineScope()
val context = LocalContext.current
val exoPlayer by remember {
mutableStateOf(ExoPlayer.Builder(context).build().apply { playWhenReady = true })
}
var effectsEnabled by remember { mutableStateOf(false) }
Scaffold(
modifier = Modifier.fillMaxSize(),
snackbarHost = { SnackbarHost(hostState = snackbarHostState) },
) { paddingValues ->
Column(
modifier = Modifier.fillMaxWidth().padding(paddingValues),
verticalArrangement = Arrangement.Top,
horizontalAlignment = Alignment.CenterHorizontally,
) {
InputChooser(
playlistHolderList,
onException = { message ->
coroutineScope.launch { snackbarHostState.showSnackbar(message) }
},
) { mediaItems ->
effectsEnabled = true
exoPlayer.apply {
setMediaItems(mediaItems)
setVideoEffects(emptyList())
prepare()
}
}
PlayerScreen(exoPlayer)
EffectControls(
effectsEnabled,
onApplyEffectsClicked = { videoEffects ->
exoPlayer.apply {
setVideoEffects(videoEffects)
prepare()
}
},
)
}
}
}
@Composable
private fun InputChooser(
playlistHolderList: List<PlaylistHolder>,
onException: (String) -> Unit,
onNewMediaItems: (List<MediaItem>) -> Unit,
) {
var showPresetInputChooser by remember { mutableStateOf(false) }
var showLocalFileChooser by remember { mutableStateOf(false) }
Row(
Modifier.padding(vertical = dimensionResource(id = R.dimen.regular_padding)),
horizontalArrangement = Arrangement.spacedBy(dimensionResource(id = R.dimen.regular_padding)),
) {
Button(onClick = { showPresetInputChooser = true }) {
Text(text = stringResource(id = R.string.choose_preset_input))
}
Button(onClick = { showLocalFileChooser = true }) {
Text(text = stringResource(id = R.string.choose_local_file))
}
}
if (showPresetInputChooser) {
if (playlistHolderList.isNotEmpty()) {
PresetInputChooser(
playlistHolderList,
onDismissRequest = { showPresetInputChooser = false },
) { mediaItems ->
onNewMediaItems(mediaItems)
showPresetInputChooser = false
}
} else {
onException(stringResource(id = R.string.no_loaded_playlists_error))
showPresetInputChooser = false
}
}
if (showLocalFileChooser) {
LocalFileChooser(
onException = { message ->
onException(message)
showLocalFileChooser = false
}
) { mediaItems ->
onNewMediaItems(mediaItems)
showLocalFileChooser = false
}
}
}
@Composable
private fun PresetInputChooser(
playlistHolderList: List<PlaylistHolder>,
onDismissRequest: () -> Unit,
onInputSelected: (List<MediaItem>) -> Unit,
) {
var selectedOption by remember { mutableStateOf(playlistHolderList.first()) }
AlertDialog(
onDismissRequest = onDismissRequest,
title = { Text(stringResource(id = R.string.choose_preset_input)) },
confirmButton = {
Button(onClick = { onInputSelected(selectedOption.mediaItems) }) {
Text(text = stringResource(id = R.string.ok))
}
},
text = {
Column {
playlistHolderList.forEach { playlistHolder ->
Row(
Modifier.fillMaxWidth()
.selectable(
(playlistHolder == selectedOption),
onClick = { selectedOption = playlistHolder },
),
verticalAlignment = Alignment.CenterVertically,
) {
RadioButton(
selected = (playlistHolder == selectedOption),
onClick = { selectedOption = playlistHolder },
)
Text(playlistHolder.title)
}
}
}
},
)
}
@OptIn(UnstableApi::class)
@Composable
private fun LocalFileChooser(
onException: (String) -> Unit,
onFileSelected: (List<MediaItem>) -> Unit,
) {
val context = LocalContext.current
val localFileChooserLauncher =
rememberLauncherForActivityResult(
contract = ActivityResultContracts.OpenDocument(),
onResult = { uri: Uri? ->
if (uri != null) {
onFileSelected(listOf(MediaItem.fromUri(uri)))
} else {
onException(getString(R.string.can_not_open_file_error))
}
},
)
val permissionLauncher =
rememberLauncherForActivityResult(
contract = ActivityResultContracts.RequestPermission(),
onResult = { isGranted: Boolean ->
if (isGranted) {
localFileChooserLauncher.launch(arrayOf("video/*"))
} else {
onException(getString(R.string.permission_not_granted_error))
}
},
)
LaunchedEffect(Unit) {
val permission =
if (SDK_INT >= 33) Manifest.permission.READ_MEDIA_VIDEO
else Manifest.permission.READ_EXTERNAL_STORAGE
val permissionCheck = ContextCompat.checkSelfPermission(context, permission)
if (permissionCheck == android.content.pm.PackageManager.PERMISSION_GRANTED) {
localFileChooserLauncher.launch(arrayOf("video/*"))
} else {
permissionLauncher.launch(permission)
}
}
}
@Composable
private fun PlayerScreen(exoPlayer: ExoPlayer) {
val context = LocalContext.current
AndroidView(
factory = { PlayerView(context).apply { player = exoPlayer } },
modifier =
Modifier.height(dimensionResource(id = R.dimen.android_view_height))
.padding(all = dimensionResource(id = R.dimen.regular_padding)),
)
}
@OptIn(UnstableApi::class)
@Composable
private fun EffectControls(enabled: Boolean, onApplyEffectsClicked: (List<Effect>) -> Unit) {
var effectControlsState by remember { mutableStateOf(EffectControlsState()) }
Button(
enabled = enabled && effectControlsState.effectsChanged,
onClick = {
val effectsList = mutableListOf<Effect>()
if (effectControlsState.contrastValue != 0f) {
effectsList += Contrast(effectControlsState.contrastValue)
}
val overlaysBuilder = ImmutableList.builder<TextureOverlay>()
if (effectControlsState.confettiOverlayChecked) {
overlaysBuilder.add(ConfettiOverlay())
}
val textOverlayText = effectControlsState.textOverlayText
if (effectControlsState.textOverlayChecked && textOverlayText != null) {
val spannableOverlayText = SpannableString(textOverlayText)
spannableOverlayText.setSpan(
ForegroundColorSpan(effectControlsState.textOverlayColor.toArgb()),
/* start= */ 0,
textOverlayText.length,
Spannable.SPAN_EXCLUSIVE_EXCLUSIVE,
)
val staticOverlaySettings =
StaticOverlaySettings.Builder()
.setAlphaScale(effectControlsState.textOverlayAlpha)
.build()
overlaysBuilder.add(
TextOverlay.createStaticTextOverlay(spannableOverlayText, staticOverlaySettings)
)
}
effectsList += OverlayEffect(overlaysBuilder.build())
onApplyEffectsClicked(effectsList)
effectControlsState = effectControlsState.copy(effectsChanged = false)
},
) {
Text(text = stringResource(id = R.string.apply_effects))
}
EffectControlsList(enabled, effectControlsState) { newEffectControlsState ->
effectControlsState = newEffectControlsState
}
}
@Composable
private fun EffectControlsList(
enabled: Boolean,
effectControlsState: EffectControlsState,
onEffectControlsStateChange: (EffectControlsState) -> Unit,
) {
LazyColumn(Modifier.padding(vertical = dimensionResource(id = R.dimen.small_padding))) {
item {
EffectItem(
name = stringResource(id = R.string.contrast),
enabled = enabled,
onCheckedChange = {
onEffectControlsStateChange(
effectControlsState.copy(effectsChanged = true, contrastValue = 0f)
)
},
) {
Row {
Text(
text = "%.2f".format(effectControlsState.contrastValue),
style = MaterialTheme.typography.bodyLarge,
modifier = Modifier.padding(dimensionResource(id = R.dimen.large_padding)).weight(1f),
)
Slider(
value = effectControlsState.contrastValue,
onValueChange = { newContrastValue ->
val newRoundedContrastValue = "%.2f".format(newContrastValue).toFloat()
onEffectControlsStateChange(
effectControlsState.copy(
effectsChanged = true,
contrastValue = newRoundedContrastValue,
)
)
},
valueRange = -1f..1f,
modifier = Modifier.weight(4f),
)
}
}
}
item {
EffectItem(
name = stringResource(R.string.confetti_overlay),
enabled = enabled,
onCheckedChange = { checked ->
onEffectControlsStateChange(
effectControlsState.copy(effectsChanged = true, confettiOverlayChecked = checked)
)
},
)
}
item {
EffectItem(
name = stringResource(R.string.custom_text_overlay),
enabled = enabled,
onCheckedChange = { checked ->
onEffectControlsStateChange(
effectControlsState.copy(effectsChanged = !checked, textOverlayChecked = checked)
)
},
) {
Column {
OutlinedTextField(
value = effectControlsState.textOverlayText ?: "",
onValueChange = { newTextOverlayText ->
onEffectControlsStateChange(
effectControlsState.copy(
effectsChanged = true,
textOverlayText = newTextOverlayText.ifEmpty { null },
)
)
},
label = { Text(stringResource(R.string.text)) },
singleLine = true,
modifier =
Modifier.fillMaxWidth().padding(bottom = dimensionResource(R.dimen.large_padding)),
)
Row {
ColorsDropDownMenu(effectControlsState.textOverlayColor) { color ->
onEffectControlsStateChange(
effectControlsState.copy(
effectsChanged = effectControlsState.textOverlayText != null,
textOverlayColor = color,
)
)
}
}
Row {
Text(
text =
stringResource(R.string.alpha) +
" = %.2f".format(effectControlsState.textOverlayAlpha),
style = MaterialTheme.typography.bodyLarge,
modifier =
Modifier.padding(dimensionResource(id = R.dimen.large_padding)).weight(1f),
)
Slider(
value = effectControlsState.textOverlayAlpha,
onValueChange = { newAlphaValue ->
val newRoundedAlphaValue = "%.2f".format(newAlphaValue).toFloat()
onEffectControlsStateChange(
effectControlsState.copy(
effectsChanged = effectControlsState.textOverlayText != null,
textOverlayAlpha = newRoundedAlphaValue,
)
)
},
valueRange = 0f..1f,
modifier = Modifier.weight(2f),
)
}
}
}
}
}
}
@kotlin.OptIn(ExperimentalMaterial3Api::class)
@Composable
fun ColorsDropDownMenu(color: Color, onItemSelected: (Color) -> Unit) {
var expanded by remember { mutableStateOf(false) }
ExposedDropdownMenuBox(
expanded = expanded,
onExpandedChange = { expanded = it },
modifier = Modifier.fillMaxWidth().padding(bottom = dimensionResource(R.dimen.large_padding)),
) {
OutlinedTextField(
modifier = Modifier.fillMaxWidth().menuAnchor(MenuAnchorType.PrimaryNotEditable),
value = COLOR_NAMES[color] ?: stringResource(R.string.unknown_color),
onValueChange = {},
readOnly = true,
singleLine = true,
label = { Text(stringResource(R.string.text_color)) },
trailingIcon = { ExposedDropdownMenuDefaults.TrailingIcon(expanded = expanded) },
colors = ExposedDropdownMenuDefaults.textFieldColors(),
)
ExposedDropdownMenu(expanded = expanded, onDismissRequest = { expanded = false }) {
for (color in COLORS) {
DropdownMenuItem(
text = {
Text(
COLOR_NAMES[color] ?: stringResource(R.string.unknown_color),
style = MaterialTheme.typography.bodyLarge,
)
},
onClick = {
onItemSelected(color)
expanded = false
},
contentPadding = ExposedDropdownMenuDefaults.ItemContentPadding,
leadingIcon = {
Box(
modifier =
Modifier.size(dimensionResource(R.dimen.color_circle_size))
.background(color, CircleShape)
)
},
)
}
}
}
}
@Composable
fun EffectItem(
name: String,
enabled: Boolean,
onCheckedChange: (Boolean) -> Unit = {},
content: @Composable () -> Unit = {},
) {
var checked by rememberSaveable { mutableStateOf(false) }
Card(
modifier =
Modifier.padding(
vertical = dimensionResource(id = R.dimen.small_padding),
horizontal = dimensionResource(id = R.dimen.regular_padding),
)
.clickable(enabled = enabled && !checked) {
checked = !checked
onCheckedChange(checked)
}
) {
Column(
Modifier.padding(dimensionResource(id = R.dimen.large_padding))
.animateContentSize(animationSpec = tween(durationMillis = 200, easing = LinearEasing))
) {
Row {
Column(Modifier.weight(1f).padding(dimensionResource(id = R.dimen.large_padding))) {
Text(text = name, style = MaterialTheme.typography.bodyLarge)
}
Checkbox(
enabled = enabled,
checked = checked,
onCheckedChange = {
checked = !checked
onCheckedChange(checked)
},
)
}
if (checked) {
content()
}
}
}
}
private data class EffectControlsState(
val effectsChanged: Boolean = false,
val contrastValue: Float = 0f,
val confettiOverlayChecked: Boolean = false,
val textOverlayChecked: Boolean = false,
val textOverlayText: String? = null,
val textOverlayColor: Color = COLORS[0],
val textOverlayAlpha: Float = 1f,
)
private companion object {
const val JSON_FILENAME = "media.playlist.json"
val COLORS =
listOf(
Color.Black,
Color.DarkGray,
Color.Gray,
Color.LightGray,
Color.White,
Color.Red,
Color.Green,
Color.Blue,
Color.Yellow,
Color.Cyan,
Color.Magenta,
)
val COLOR_NAMES =
mapOf(
Color.Black to "Black",
Color.DarkGray to "Dark Gray",
Color.Gray to "Gray",
Color.LightGray to "Light Gray",
Color.White to "White",
Color.Red to "Red",
Color.Green to "Green",
Color.Blue to "Blue",
Color.Yellow to "Yellow",
Color.Cyan to "Cyan",
Color.Magenta to "Magenta",
)
}
}

View File

@ -0,0 +1,78 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* https://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.demo.effect
import android.content.Context
import android.net.Uri
import android.util.JsonReader
import android.util.Log
import androidx.media3.common.MediaItem
import java.io.IOException
import java.io.InputStreamReader
import java.nio.charset.StandardCharsets
import kotlinx.coroutines.Dispatchers
import kotlinx.coroutines.withContext
internal suspend fun loadPlaylistsFromJson(
jsonFilename: String,
context: Context,
tag: String,
): List<PlaylistHolder> =
withContext(Dispatchers.IO) {
try {
context.assets.open(jsonFilename).use { inputStream ->
val reader = JsonReader(InputStreamReader(inputStream, StandardCharsets.UTF_8))
val playlistHolders = buildList {
reader.beginArray()
while (reader.hasNext()) {
readPlaylist(reader)?.let { add(it) }
}
reader.endArray()
}
playlistHolders
}
} catch (e: IOException) {
Log.e(tag, context.getString(R.string.playlist_loading_error, jsonFilename, e))
emptyList()
}
}
private fun readPlaylist(reader: JsonReader): PlaylistHolder? {
val playlistHolder = PlaylistHolder("", emptyList())
reader.beginObject()
while (reader.hasNext()) {
val name = reader.nextName()
if (name.equals("name")) {
playlistHolder.title = reader.nextString()
} else if (name.equals("playlist")) {
playlistHolder.mediaItems = buildList {
reader.beginArray()
while (reader.hasNext()) {
reader.beginObject()
reader.nextName()
add(MediaItem.fromUri(Uri.parse(reader.nextString())))
reader.endObject()
}
reader.endArray()
}
}
}
reader.endObject()
// Only return the playlistHolder object if it has media items
return if (playlistHolder.mediaItems.isNotEmpty()) playlistHolder else null
}
internal data class PlaylistHolder(var title: String, var mediaItems: List<MediaItem>)

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.1 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.8 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 7.3 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 10 KiB

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<!-- Base application theme. -->
<style name="Theme.Media3EffectDemo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_200</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/black</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@ -0,0 +1,24 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<color name="purple_200">#FFBB86FC</color>
<color name="purple_500">#FF6200EE</color>
<color name="purple_700">#FF3700B3</color>
<color name="teal_200">#FF03DAC5</color>
<color name="teal_700">#FF018786</color>
<color name="black">#FF000000</color>
<color name="white">#FFFFFFFF</color>
</resources>

View File

@ -0,0 +1,22 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<dimen name="small_padding">4dp</dimen>
<dimen name="regular_padding">8dp</dimen>
<dimen name="large_padding">12dp</dimen>
<dimen name="android_view_height">256dp</dimen>
<dimen name="color_circle_size">40dp</dimen>
</resources>

View File

@ -0,0 +1,33 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<string name="app_name">Effect Demo</string>
<string name="choose_preset_input">Choose preset input</string>
<string name="choose_local_file">Choose local file</string>
<string name="apply_effects">Apply effects</string>
<string name="ok">OK</string>
<string name="playlist_loading_error">Error loading playlist from %1$s: %2$s</string>
<string name="no_loaded_playlists_error">There are no loaded preset inputs.</string>
<string name="can_not_open_file_error">"File couldn't be opened. Please try again."</string>
<string name="permission_not_granted_error">"Permission was not granted."</string>
<string name="contrast">Contrast</string>
<string name="confetti_overlay">Confetti Overlay</string>
<string name="custom_text_overlay">Custom Text Overlay</string>
<string name="text">Text</string>
<string name="text_color">Text color</string>
<string name="unknown_color">Unknown color</string>
<string name="alpha">Alpha</string>
</resources>

View File

@ -0,0 +1,31 @@
<?xml version="1.0" encoding="utf-8"?>
<!-- Copyright 2024 The Android Open Source Project
Licensed under the Apache License, Version 2.0 (the "License");
you may not use this file except in compliance with the License.
You may obtain a copy of the License at
https://www.apache.org/licenses/LICENSE-2.0
Unless required by applicable law or agreed to in writing, software
distributed under the License is distributed on an "AS IS" BASIS,
WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources>
<!-- Base application theme. -->
<style name="Theme.Media3EffectDemo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
<item name="colorPrimary">@color/purple_500</item>
<item name="colorPrimaryVariant">@color/purple_700</item>
<item name="colorOnPrimary">@color/white</item>
<!-- Secondary brand color. -->
<item name="colorSecondary">@color/teal_200</item>
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View File

@ -14,7 +14,6 @@
apply from: '../../constants.gradle'
apply plugin: 'com.android.application'
apply plugin: 'kotlin-android'
android {
namespace 'androidx.media3.demo.main'
@ -90,6 +89,7 @@ dependencies {
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-iamf')
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-vp9')
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-midi')
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-mpegh')
withDecoderExtensionsImplementation project(modulePrefix + 'lib-datasource-rtmp')
}

View File

@ -257,6 +257,10 @@
{
"name": "Apple media playlist (AAC)",
"uri": "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/gear0/prog_index.m3u8"
},
{
"name": "Bitmovin (FMP4)",
"uri": "https://bitdash-a.akamaihd.net/content/MI201109210084_1/m3u8s-fmp4/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8"
}
]
},
@ -280,15 +284,25 @@
"name": "IMA sample ad tags",
"samples": [
{
"name": "Single inline linear",
"name": "VMAP pre-roll single ad, mid-roll standard pods with 5 ads every 10 seconds for 1:40, post-roll single ad",
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
"ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dlinear&correlator="
"ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpostlongpod&cmsid=496&vid=short_tencue&correlator="
},
{
"name": "VMAP empty midroll",
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
"ad_tag_uri": "https://vastsynthesizer.appspot.com/empty-midroll"
},
{
"name": "Single skippable inline",
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
"ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dskippablelinear&correlator="
},
{
"name": "Single inline linear",
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
"ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/single_ad_samples&ciu_szs=300x250&impl=s&gdfp_req=1&env=vp&output=vast&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ct%3Dlinear&correlator="
},
{
"name": "Single redirect linear",
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
@ -349,16 +363,6 @@
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
"ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpostoptimizedpodbumper&cmsid=496&vid=short_onecue&correlator="
},
{
"name": "VMAP pre-roll single ad, mid-roll standard pods with 5 ads every 10 seconds for 1:40, post-roll single ad",
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
"ad_tag_uri": "https://pubads.g.doubleclick.net/gampad/ads?sz=640x480&iu=/124319096/external/ad_rule_samples&ciu_szs=300x250&ad_rule=1&impl=s&gdfp_req=1&env=vp&output=vmap&unviewed_position_start=1&cust_params=deployment%3Ddevsite%26sample_ar%3Dpremidpostlongpod&cmsid=496&vid=short_tencue&correlator="
},
{
"name": "VMAP empty midroll",
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
"ad_tag_uri": "https://vastsynthesizer.appspot.com/empty-midroll"
},
{
"name": "VMAP full, empty, full midrolls",
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mkv/android-screens-lavf-56.36.100-aac-avc-main-1280x720.mkv",
@ -396,6 +400,10 @@
{
"name": "IMA DAI streams",
"samples": [
{
"name": "DASH VOD: Tears of Steel (11 periods, pre/mid/post), 2/5/2 ads [5/10s]",
"uri": "ssai://dai.google.com/?contentSourceId=2559737&videoId=tos-dash&format=0&adsId=1"
},
{
"name": "HLS VOD: Demo (skippable pre/post), single ads [30 s]",
"uri": "ssai://dai.google.com/?contentSourceId=2483977&videoId=ima-vod-skippable-test&format=2&adsId=1"
@ -408,10 +416,6 @@
"name": "HLS Live: Big Buck Bunny (mid), 3 ads [10/10/10s]",
"uri": "ssai://dai.google.com/?assetKey=sN_IYUG8STe1ZzhIIE_ksA&format=2&adsId=3"
},
{
"name": "DASH VOD: Tears of Steel (11 periods, pre/mid/post), 2/5/2 ads [5/10s]",
"uri": "ssai://dai.google.com/?contentSourceId=2559737&videoId=tos-dash&format=0&adsId=1"
},
{
"name": "DASH live: Tears of Steel (mid), 3 ads each [10 s]",
"uri": "ssai://dai.google.com/?assetKey=jNVjPZwzSkyeGiaNQTPqiQ&format=0&adsId=1"
@ -420,6 +424,34 @@
"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": "Playlist: No ads - HLS live: Big Buck Bunny - No ads",
"playlist": [
{
"uri": "https://html5demos.com/assets/dizzy.mp4"
},
{
"uri": "ssai://dai.google.com/?assetKey=sN_IYUG8STe1ZzhIIE_ksA&format=2&adsId=3"
},
{
"uri": "https://html5demos.com/assets/dizzy.mp4"
}
]
},
{
"name": "Playlist: No ads - DASH live: Tears of Steel (mid) - No ads",
"playlist": [
{
"uri": "https://html5demos.com/assets/dizzy.mp4"
},
{
"uri": "ssai://dai.google.com/?assetKey=PSzZMzAkSXCmlJOWDmRj8Q&format=0&adsId=1"
},
{
"uri": "https://html5demos.com/assets/dizzy.mp4"
}
]
},
{
"name": "DASH live: Unencrypted stream with 30s ad breaks every minute",
"uri": "ssai://dai.google.com/?assetKey=0ndl1dJcRmKDUPxTRjvdog&format=0&adsId=21"
@ -480,34 +512,6 @@
"uri": "https://html5demos.com/assets/dizzy.mp4"
}
]
},
{
"name": "Playlist: No ads - DASH live: Tears of Steel (mid) - No ads",
"playlist": [
{
"uri": "https://html5demos.com/assets/dizzy.mp4"
},
{
"uri": "ssai://dai.google.com/?assetKey=PSzZMzAkSXCmlJOWDmRj8Q&format=0&adsId=1"
},
{
"uri": "https://html5demos.com/assets/dizzy.mp4"
}
]
},
{
"name": "Playlist: No ads - HLS live: Big Buck Bunny - No ads",
"playlist": [
{
"uri": "https://html5demos.com/assets/dizzy.mp4"
},
{
"uri": "ssai://dai.google.com/?assetKey=sN_IYUG8STe1ZzhIIE_ksA&format=2&adsId=3"
},
{
"uri": "https://html5demos.com/assets/dizzy.mp4"
}
]
}
]
},
@ -762,6 +766,10 @@
{
"name": "Immersive Audio Format Sample (MP4, IAMF)",
"uri": "https://github.com/AOMediaCodec/libiamf/raw/main/tests/test_000036_s.mp4"
},
{
"name": "MPEG-H HD (MP4, H265)",
"uri": "https://media.githubusercontent.com/media/Fraunhofer-IIS/mpegh-test-content/main/TRI_Fileset_17_514H_D1_D2_D3_O1_24bit1080p50.mp4"
}
]
},

View File

@ -315,7 +315,7 @@ public class DownloadTracker {
TrackSelectionDialog.createForTracksAndParameters(
/* titleId= */ R.string.exo_download_description,
tracks,
DownloadHelper.getDefaultTrackSelectorParameters(context),
DownloadHelper.DEFAULT_TRACK_SELECTOR_PARAMETERS,
/* allowAdaptiveSelections= */ false,
/* allowMultipleOverrides= */ true,
/* onTracksSelectedListener= */ this,

View File

@ -61,7 +61,6 @@ import androidx.media3.datasource.DataSourceUtil;
import androidx.media3.datasource.DataSpec;
import androidx.media3.exoplayer.RenderersFactory;
import androidx.media3.exoplayer.offline.DownloadService;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.io.IOException;
@ -74,6 +73,7 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
@ -524,7 +524,7 @@ public class SampleChooserActivity extends AppCompatActivity
private PlaylistGroup getGroup(String groupName, List<PlaylistGroup> groups) {
for (int i = 0; i < groups.size(); i++) {
if (Objects.equal(groupName, groups.get(i).title)) {
if (Objects.equals(groupName, groups.get(i).title)) {
return groups.get(i);
}
}

View File

@ -41,8 +41,7 @@ open class DemoMediaLibrarySessionCallback(context: Context) :
MediaItemTree.initialize(context.assets)
}
@OptIn(UnstableApi::class) // TODO: b/328238954 - Remove once new CommandButton icons are stable.
private val customLayoutCommandButtons: List<CommandButton> =
private val commandButtons: List<CommandButton> =
listOf(
CommandButton.Builder(CommandButton.ICON_SHUFFLE_OFF)
.setDisplayName(context.getString(R.string.exo_controls_shuffle_on_description))
@ -59,7 +58,7 @@ open class DemoMediaLibrarySessionCallback(context: Context) :
MediaSession.ConnectionResult.DEFAULT_SESSION_AND_LIBRARY_COMMANDS.buildUpon()
.also { builder ->
// Put all custom session commands in the list that may be used by the notification.
customLayoutCommandButtons.forEach { commandButton ->
commandButtons.forEach { commandButton ->
commandButton.sessionCommand?.let { builder.add(it) }
}
}
@ -78,13 +77,13 @@ open class DemoMediaLibrarySessionCallback(context: Context) :
session.isAutoCompanionController(controller)
) {
// Select the button to display.
val customLayout = customLayoutCommandButtons[if (session.player.shuffleModeEnabled) 1 else 0]
val customButton = commandButtons[if (session.player.shuffleModeEnabled) 1 else 0]
return MediaSession.ConnectionResult.AcceptedResultBuilder(session)
.setAvailableSessionCommands(mediaNotificationSessionCommands)
.setCustomLayout(ImmutableList.of(customLayout))
.setMediaButtonPreferences(ImmutableList.of(customButton))
.build()
}
// Default commands without custom layout for common controllers.
// Default commands without media button preferences for common controllers.
return MediaSession.ConnectionResult.AcceptedResultBuilder(session).build()
}
@ -98,19 +97,19 @@ open class DemoMediaLibrarySessionCallback(context: Context) :
if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_ON == customCommand.customAction) {
// Enable shuffling.
session.player.shuffleModeEnabled = true
// Change the custom layout to contain the `Disable shuffling` command.
session.setCustomLayout(
// Change the media button preferences to contain the `Disable shuffling` button.
session.setMediaButtonPreferences(
session.mediaNotificationControllerInfo!!,
ImmutableList.of(customLayoutCommandButtons[1]),
ImmutableList.of(commandButtons[1]),
)
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
} else if (CUSTOM_COMMAND_TOGGLE_SHUFFLE_MODE_OFF == customCommand.customAction) {
// Disable shuffling.
session.player.shuffleModeEnabled = false
// Change the custom layout to contain the `Enable shuffling` command.
session.setCustomLayout(
// Change the media button preferences to contain the `Enable shuffling` button.
session.setMediaButtonPreferences(
session.mediaNotificationControllerInfo!!,
ImmutableList.of(customLayoutCommandButtons[0]),
ImmutableList.of(commandButtons[0]),
)
return Futures.immediateFuture(SessionResult(SessionResult.RESULT_SUCCESS))
}

View File

@ -76,6 +76,7 @@ dependencies {
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
implementation 'androidx.constraintlayout:constraintlayout:' + androidxConstraintLayoutVersion
implementation 'androidx.recyclerview:recyclerview:' + androidxRecyclerViewVersion
implementation 'androidx.window:window:' + androidxWindowVersion
implementation 'com.google.android.material:material:' + androidxMaterialVersion
implementation project(modulePrefix + 'lib-effect')
implementation project(modulePrefix + 'lib-exoplayer')

View File

@ -24,6 +24,10 @@
<uses-permission android:name="android.permission.READ_EXTERNAL_STORAGE"/>
<uses-permission android:name="android.permission.READ_MEDIA_VIDEO"/>
<!-- For media projection. -->
<uses-permission android:name="android.permission.FOREGROUND_SERVICE"/>
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PROJECTION"/>
<application
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
@ -64,5 +68,9 @@
android:label="@string/app_name"
android:exported="true"
android:theme="@style/Theme.MaterialComponents.DayNight.NoActionBar"/>
<service
android:name=".TransformerActivity$DemoMediaProjectionService"
android:foregroundServiceType="mediaProjection"
android:exported="false"/>
</application>
</manifest>

View File

@ -0,0 +1,188 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.demo.transformer;
import android.animation.FloatEvaluator;
import android.animation.Keyframe;
import android.animation.PropertyValuesHolder;
import android.animation.TypeEvaluator;
import android.animation.ValueAnimator;
import android.content.Context;
import android.content.pm.PackageManager;
import android.graphics.drawable.Drawable;
import android.os.Handler;
import android.util.Pair;
import android.view.animation.LinearInterpolator;
import androidx.media3.common.OverlaySettings;
import androidx.media3.common.VideoFrameProcessingException;
import androidx.media3.common.util.Util;
import androidx.media3.effect.DrawableOverlay;
import androidx.media3.effect.TextureOverlay;
import java.util.concurrent.CountDownLatch;
/**
* An animated {@link TextureOverlay} using {@link android.animation}.
*
* <p>The rotation is controlled by a simple {@link ValueAnimator}, while the position is controlled
* by key frames.
*/
public class AnimatedLogoOverlay extends DrawableOverlay {
private static final long ROTATION_PERIOD_MS = 2_000;
private static final long POSITION_PERIOD_MS = 5_000;
private static final float POSITION_X_BOUND = 0.8f;
private static final float POSITION_Y_BOUND = 0.7f;
private final Drawable logo;
private final AnimatedOverlaySettings overlaySettings;
public AnimatedLogoOverlay(Context context) {
try {
logo = context.getPackageManager().getApplicationIcon(context.getPackageName());
} catch (PackageManager.NameNotFoundException e) {
throw new IllegalStateException(e);
}
logo.setBounds(
/* left= */ 0, /* top= */ 0, logo.getIntrinsicWidth(), logo.getIntrinsicHeight());
ValueAnimator rotationAnimator = ValueAnimator.ofFloat(0, 360);
rotationAnimator.setRepeatMode(ValueAnimator.RESTART);
rotationAnimator.setRepeatCount(ValueAnimator.INFINITE);
rotationAnimator.setDuration(ROTATION_PERIOD_MS);
// Rotate the logo with a constant angular velocity.
rotationAnimator.setInterpolator(new LinearInterpolator());
Keyframe[] keyFrames = new Keyframe[5];
keyFrames[0] =
Keyframe.ofObject(/* fraction= */ 0f, Pair.create(-POSITION_X_BOUND, -POSITION_Y_BOUND));
keyFrames[2] =
Keyframe.ofObject(/* fraction= */ 0.5f, Pair.create(POSITION_X_BOUND, POSITION_Y_BOUND));
keyFrames[1] =
Keyframe.ofObject(/* fraction= */ 0.25f, Pair.create(-POSITION_X_BOUND, POSITION_Y_BOUND));
keyFrames[3] =
Keyframe.ofObject(/* fraction= */ 0.75f, Pair.create(POSITION_X_BOUND, -POSITION_Y_BOUND));
keyFrames[4] =
Keyframe.ofObject(/* fraction= */ 1f, Pair.create(-POSITION_X_BOUND, -POSITION_Y_BOUND));
PropertyValuesHolder positionValuesHolder =
PropertyValuesHolder.ofKeyframe("position", keyFrames);
ValueAnimator positionAnimator = ValueAnimator.ofPropertyValuesHolder(positionValuesHolder);
// The position can also be animated using separate animators for x and y, the purpose of
// PairEvaluator is to use one animator for both x and y.
positionAnimator.setEvaluator(new AnimatedOverlaySettings.PairEvaluator());
positionAnimator.setRepeatMode(ValueAnimator.RESTART);
positionAnimator.setRepeatCount(ValueAnimator.INFINITE);
positionAnimator.setDuration(POSITION_PERIOD_MS);
overlaySettings = new AnimatedOverlaySettings(rotationAnimator, positionAnimator);
}
@Override
public Drawable getDrawable(long presentationTimeUs) {
return logo;
}
@Override
public OverlaySettings getOverlaySettings(long presentationTimeUs) {
overlaySettings.setCurrentPresentationTimeUs(presentationTimeUs);
return overlaySettings;
}
@Override
public void release() throws VideoFrameProcessingException {
super.release();
overlaySettings.stopAnimation();
}
private static class AnimatedOverlaySettings implements OverlaySettings {
private final ValueAnimator rotationAnimator;
private final ValueAnimator positionAnimator;
private final Handler mainThreadHandler;
private boolean started;
public AnimatedOverlaySettings(ValueAnimator rotationAnimator, ValueAnimator positionAnimator) {
this.rotationAnimator = rotationAnimator;
this.positionAnimator = positionAnimator;
mainThreadHandler = new Handler(Util.getCurrentOrMainLooper());
}
public void setCurrentPresentationTimeUs(long presentationTimeUs) {
// Sets the animation time to the video presentation time, so the animation is presentation
// time based.
rotationAnimator.setCurrentPlayTime(presentationTimeUs / 1000);
positionAnimator.setCurrentPlayTime(presentationTimeUs / 1000);
}
@Override
public float getRotationDegrees() {
maybeStartAnimator();
return (float) rotationAnimator.getAnimatedValue();
}
@Override
public Pair<Float, Float> getBackgroundFrameAnchor() {
maybeStartAnimator();
return (Pair<Float, Float>) positionAnimator.getAnimatedValue();
}
public void stopAnimation() {
mainThreadHandler.post(
() -> {
rotationAnimator.cancel();
positionAnimator.cancel();
});
}
private void maybeStartAnimator() {
if (!started) {
CountDownLatch latch = new CountDownLatch(1);
mainThreadHandler.post(
() -> {
rotationAnimator.start();
positionAnimator.start();
latch.countDown();
});
try {
// Block until the animators are actually started, or they'll return null values.
latch.await();
} catch (InterruptedException e) {
Thread.currentThread().interrupt();
throw new IllegalStateException(e);
}
started = true;
}
}
/** An {@link TypeEvaluator} to animate position in the form of {@link Pair} of floats. */
private static class PairEvaluator implements TypeEvaluator<Pair<Float, Float>> {
private final FloatEvaluator floatEvaluator;
private PairEvaluator() {
floatEvaluator = new FloatEvaluator();
}
@Override
public Pair<Float, Float> evaluate(
float fraction, Pair<Float, Float> startValue, Pair<Float, Float> endValue) {
return Pair.create(
floatEvaluator.evaluate(fraction, startValue.first, endValue.first),
floatEvaluator.evaluate(fraction, startValue.second, endValue.second));
}
}
}
}

View File

@ -0,0 +1,106 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.demo.transformer;
import static java.lang.Math.toRadians;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.graphics.RectF;
import androidx.media3.common.OverlaySettings;
import androidx.media3.effect.CanvasOverlay;
import androidx.media3.effect.StaticOverlaySettings;
/* package */ final class ClockOverlay extends CanvasOverlay {
private static final int CLOCK_COLOR = Color.WHITE;
private static final int DIAL_SIZE = 200;
private static final float DIAL_WIDTH = 3.f;
private static final float NEEDLE_WIDTH = 3.f;
private static final int NEEDLE_LENGTH = DIAL_SIZE / 2 - 20;
private static final int CENTRE_X = DIAL_SIZE / 2;
private static final int CENTRE_Y = DIAL_SIZE / 2;
private static final int DIAL_INSET = 5;
private static final RectF DIAL_BOUND =
new RectF(
/* left= */ DIAL_INSET,
/* top= */ DIAL_INSET,
/* right= */ DIAL_SIZE - DIAL_INSET,
/* bottom= */ DIAL_SIZE - DIAL_INSET);
private static final int HUB_SIZE = 5;
private static final float BOTTOM_RIGHT_ANCHOR_X = 1.f;
private static final float BOTTOM_RIGHT_ANCHOR_Y = -1.f;
private static final float ANCHOR_INSET_X = 0.1f;
private static final float ANCHOR_INSET_Y = -0.1f;
private final Paint dialPaint;
private final Paint needlePaint;
private final Paint hubPaint;
public ClockOverlay() {
super(/* useInputFrameSize= */ false);
setCanvasSize(/* width= */ DIAL_SIZE, /* height= */ DIAL_SIZE);
dialPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
dialPaint.setStyle(Paint.Style.STROKE);
dialPaint.setStrokeWidth(DIAL_WIDTH);
dialPaint.setColor(CLOCK_COLOR);
needlePaint = new Paint(Paint.ANTI_ALIAS_FLAG);
needlePaint.setStrokeWidth(NEEDLE_WIDTH);
needlePaint.setColor(CLOCK_COLOR);
hubPaint = new Paint(Paint.ANTI_ALIAS_FLAG);
hubPaint.setColor(CLOCK_COLOR);
}
@Override
public void onDraw(Canvas canvas, long presentationTimeUs) {
// Clears the canvas
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
// Draw the dial
canvas.drawArc(
DIAL_BOUND, /* startAngle= */ 0, /* sweepAngle= */ 360, /* useCenter= */ false, dialPaint);
// Draw the needle
float angle = 6 * presentationTimeUs / 1_000_000.f - 90;
double radians = toRadians(angle);
float startX = CENTRE_X - (float) (10 * Math.cos(radians));
float startY = CENTRE_Y - (float) (10 * Math.sin(radians));
float endX = CENTRE_X + (float) (NEEDLE_LENGTH * Math.cos(radians));
float endY = CENTRE_Y + (float) (NEEDLE_LENGTH * Math.sin(radians));
canvas.drawLine(startX, startY, endX, endY, needlePaint);
// Draw a small hub at the center
canvas.drawCircle(CENTRE_X, CENTRE_Y, HUB_SIZE, hubPaint);
}
@Override
public OverlaySettings getOverlaySettings(long presentationTimeUs) {
return new StaticOverlaySettings.Builder()
.setBackgroundFrameAnchor(
BOTTOM_RIGHT_ANCHOR_X - ANCHOR_INSET_X, BOTTOM_RIGHT_ANCHOR_Y - ANCHOR_INSET_Y)
.setOverlayFrameAnchor(BOTTOM_RIGHT_ANCHOR_X, BOTTOM_RIGHT_ANCHOR_Y)
.build();
}
}

View File

@ -0,0 +1,162 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.demo.transformer;
import static androidx.media3.common.util.Assertions.checkStateNotNull;
import android.graphics.Canvas;
import android.graphics.Color;
import android.graphics.Paint;
import android.graphics.PorterDuff;
import android.os.Handler;
import androidx.annotation.Nullable;
import androidx.media3.common.VideoFrameProcessingException;
import androidx.media3.common.util.Size;
import androidx.media3.common.util.Util;
import androidx.media3.effect.CanvasOverlay;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.List;
import java.util.Random;
/** Mimics an emitter of confetti, dropping from the center of the frame. */
/* package */ final class ConfettiOverlay extends CanvasOverlay {
private static final ImmutableList<String> CONFETTI_TEXTS =
ImmutableList.of("", "", "", "✦︎", "♥︎", "☕︎");
private static final int EMITTER_POSITION_Y = -50;
private static final int CONFETTI_BASE_SIZE = 30;
private static final int CONFETTI_SIZE_VARIATION = 10;
private final List<Confetti> confettiList;
private final Random random;
private final Paint paint;
private final Handler handler;
@Nullable private Runnable runnable;
private int width;
private int height;
private boolean started;
public ConfettiOverlay() {
super(/* useInputFrameSize= */ true);
confettiList = new ArrayList<>();
random = new Random();
paint = new Paint();
paint.setAntiAlias(true);
handler = new Handler(Util.getCurrentOrMainLooper());
}
@Override
public void configure(Size videoSize) {
super.configure(videoSize);
this.width = videoSize.getWidth();
this.height = videoSize.getHeight();
}
@Override
public synchronized void onDraw(Canvas canvas, long presentationTimeUs) {
if (!started) {
start();
}
canvas.drawColor(Color.TRANSPARENT, PorterDuff.Mode.CLEAR);
for (int i = 0; i < confettiList.size(); i++) {
Confetti confetti = confettiList.get(i);
if (confetti.y > (float) height / 2 || confetti.x <= 0 || confetti.x > width) {
confettiList.remove(confetti);
continue;
}
confetti.draw(canvas, paint);
confetti.update();
}
}
/** Starts the confetti. */
public void start() {
runnable = this::addConfetti;
handler.post(runnable);
started = true;
}
/** Stops the confetti. */
public void stop() {
checkStateNotNull(runnable);
handler.removeCallbacks(runnable);
confettiList.clear();
started = false;
runnable = null;
}
@Override
public void release() throws VideoFrameProcessingException {
super.release();
handler.post(this::stop);
}
private synchronized void addConfetti() {
for (int i = 0; i < 5; i++) {
confettiList.add(
new Confetti(
CONFETTI_TEXTS.get(Math.abs(random.nextInt()) % CONFETTI_TEXTS.size()),
random,
/* x= */ (float) width / 2,
/* y= */ EMITTER_POSITION_Y,
/* size= */ CONFETTI_BASE_SIZE + random.nextInt(CONFETTI_SIZE_VARIATION),
/* color= */ Color.HSVToColor(
new float[] {
/* hue= */ random.nextInt(360), /* saturation= */ 0.6f, /* value= */ 0.8f
})));
}
handler.postDelayed(this::addConfetti, /* delayMillis= */ 100);
}
private static final class Confetti {
private final String text;
private final float speedX;
private final float speedY;
private final int size;
private final int color;
private float x;
private float y;
public Confetti(String text, Random random, float x, float y, int size, int color) {
this.text = text;
this.x = x;
this.y = y;
this.size = size;
this.color = color;
speedX = 4 * (random.nextFloat() * 2 - 1); // Random speed in x direction
speedY = 4 * random.nextFloat(); // Random downward speed
}
/** Draws the {@code Confetti} on the {@link Canvas}. */
public void draw(Canvas canvas, Paint paint) {
canvas.save();
paint.setColor(color);
paint.setTextSize(size);
canvas.drawText(text, x, y, paint);
canvas.restore();
}
/** Updates the {@code Confetti}. */
public void update() {
x += speedX;
y += speedY;
}
}
}

View File

@ -77,8 +77,8 @@ public final class ConfigurationActivity extends AppCompatActivity {
public static final String ENABLE_ANALYZER_MODE = "enable_analyzer_mode";
public static final String ENABLE_DEBUG_PREVIEW = "enable_debug_preview";
public static final String ABORT_SLOW_EXPORT = "abort_slow_export";
public static final String USE_MEDIA3_MUXER = "use_media3_muxer";
public static final String PRODUCE_FRAGMENTED_MP4 = "produce_fragmented_mp4";
public static final String USE_MEDIA3_MP4_MUXER = "use_media3_mp4_muxer";
public static final String USE_MEDIA3_FRAGMENTED_MP4_MUXER = "use_media3_fragmented_mp4_muxer";
public static final String HDR_MODE = "hdr_mode";
public static final String AUDIO_EFFECTS_SELECTIONS = "audio_effects_selections";
public static final String VIDEO_EFFECTS_SELECTIONS = "video_effects_selections";
@ -114,13 +114,17 @@ public final class ConfigurationActivity extends AppCompatActivity {
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;
public static final int CLOCK_OVERLAY_INDEX = 13;
public static final int CONFETTI_OVERLAY_INDEX = 14;
public static final int ANIMATING_LOGO_OVERLAY = 15;
// 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;
public static final int CHANNEL_MIXING_INDEX = 3;
public static final int VOLUME_SCALING_INDEX = 4;
public static final int SAMPLE_RATE_48K_INDEX = 1;
public static final int SAMPLE_RATE_96K_INDEX = 2;
public static final int SKIP_SILENCE_INDEX = 3;
public static final int CHANNEL_MIXING_INDEX = 4;
public static final int VOLUME_SCALING_INDEX = 5;
// Color filter options.
public static final int COLOR_FILTER_GRAYSCALE = 0;
@ -173,8 +177,8 @@ public final class ConfigurationActivity extends AppCompatActivity {
private CheckBox enableDebugPreviewCheckBox;
private CheckBox enableDebugTracingCheckBox;
private CheckBox abortSlowExportCheckBox;
private CheckBox useMedia3Muxer;
private CheckBox produceFragmentedMp4CheckBox;
private CheckBox useMedia3Mp4Muxer;
private CheckBox useMedia3FragmentedMp4Muxer;
private Spinner hdrModeSpinner;
private Button selectAudioEffectsButton;
private Button selectVideoEffectsButton;
@ -262,7 +266,8 @@ public final class ConfigurationActivity extends AppCompatActivity {
MimeTypes.VIDEO_H264,
MimeTypes.VIDEO_H265,
MimeTypes.VIDEO_MP4V,
MimeTypes.VIDEO_AV1);
MimeTypes.VIDEO_AV1,
MimeTypes.VIDEO_DOLBY_VISION);
ArrayAdapter<String> resolutionHeightAdapter =
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
@ -299,18 +304,18 @@ public final class ConfigurationActivity extends AppCompatActivity {
(buttonView, isChecked) -> DebugTraceUtil.enableTracing = isChecked);
abortSlowExportCheckBox = findViewById(R.id.abort_slow_export_checkbox);
useMedia3Muxer = findViewById(R.id.use_media3_muxer_checkbox);
produceFragmentedMp4CheckBox = findViewById(R.id.produce_fragmented_mp4_checkbox);
useMedia3Muxer.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
if (!isChecked) {
produceFragmentedMp4CheckBox.setChecked(false);
}
});
produceFragmentedMp4CheckBox.setOnCheckedChangeListener(
useMedia3Mp4Muxer = findViewById(R.id.use_media3_mp4_muxer_checkbox);
useMedia3FragmentedMp4Muxer = findViewById(R.id.use_media3_fragmented_mp4_muxer_checkbox);
useMedia3Mp4Muxer.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
if (isChecked) {
useMedia3Muxer.setChecked(true);
useMedia3FragmentedMp4Muxer.setChecked(false);
}
});
useMedia3FragmentedMp4Muxer.setOnCheckedChangeListener(
(buttonView, isChecked) -> {
if (isChecked) {
useMedia3Mp4Muxer.setChecked(false);
}
});
@ -403,8 +408,8 @@ public final class ConfigurationActivity extends AppCompatActivity {
bundle.putBoolean(ENABLE_ANALYZER_MODE, enableAnalyzerModeCheckBox.isChecked());
bundle.putBoolean(ENABLE_DEBUG_PREVIEW, enableDebugPreviewCheckBox.isChecked());
bundle.putBoolean(ABORT_SLOW_EXPORT, abortSlowExportCheckBox.isChecked());
bundle.putBoolean(USE_MEDIA3_MUXER, useMedia3Muxer.isChecked());
bundle.putBoolean(PRODUCE_FRAGMENTED_MP4, produceFragmentedMp4CheckBox.isChecked());
bundle.putBoolean(USE_MEDIA3_MP4_MUXER, useMedia3Mp4Muxer.isChecked());
bundle.putBoolean(USE_MEDIA3_FRAGMENTED_MP4_MUXER, useMedia3FragmentedMp4Muxer.isChecked());
String selectedHdrMode = String.valueOf(hdrModeSpinner.getSelectedItem());
bundle.putInt(HDR_MODE, HDR_MODE_DESCRIPTIONS.get(selectedHdrMode));
bundle.putBooleanArray(AUDIO_EFFECTS_SELECTIONS, audioEffectsSelections);

View File

@ -20,7 +20,8 @@ import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
import androidx.media3.common.C;
import androidx.media3.effect.OverlaySettings;
import androidx.media3.common.OverlaySettings;
import androidx.media3.effect.StaticOverlaySettings;
import androidx.media3.effect.TextOverlay;
import androidx.media3.effect.TextureOverlay;
import java.util.Locale;
@ -31,11 +32,11 @@ import java.util.Locale;
*/
/* package */ final class TimerOverlay extends TextOverlay {
private final OverlaySettings overlaySettings;
private final StaticOverlaySettings overlaySettings;
public TimerOverlay() {
overlaySettings =
new OverlaySettings.Builder()
new StaticOverlaySettings.Builder()
// Place the timer in the bottom left corner of the screen with some padding from the
// edges.
.setOverlayFrameAnchor(/* x= */ -1f, /* y= */ -1f)

View File

@ -15,25 +15,33 @@
*/
package androidx.media3.demo.transformer;
import static android.Manifest.permission.READ_EXTERNAL_STORAGE;
import static android.Manifest.permission.READ_MEDIA_VIDEO;
import static android.content.pm.ServiceInfo.FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.common.util.Util.SDK_INT;
import static androidx.media3.exoplayer.DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS;
import static androidx.media3.exoplayer.DefaultLoadControl.DEFAULT_BUFFER_FOR_PLAYBACK_MS;
import static androidx.media3.transformer.Transformer.PROGRESS_STATE_NOT_STARTED;
import android.app.Activity;
import android.app.Notification;
import android.app.NotificationChannel;
import android.app.NotificationManager;
import android.app.Service;
import android.content.BroadcastReceiver;
import android.content.Context;
import android.content.Intent;
import android.content.IntentFilter;
import android.content.pm.PackageManager;
import android.graphics.Bitmap;
import android.graphics.Color;
import android.graphics.Rect;
import android.graphics.drawable.Drawable;
import android.media.projection.MediaProjection;
import android.media.projection.MediaProjectionManager;
import android.net.Uri;
import android.os.Bundle;
import android.os.Handler;
import android.os.IBinder;
import android.text.Spannable;
import android.text.SpannableString;
import android.text.style.ForegroundColorSpan;
@ -45,10 +53,13 @@ import android.widget.Button;
import android.widget.ImageView;
import android.widget.TextView;
import android.widget.Toast;
import androidx.activity.result.contract.ActivityResultContracts;
import androidx.annotation.Nullable;
import androidx.annotation.StringRes;
import androidx.appcompat.app.AppCompatActivity;
import androidx.core.app.ActivityCompat;
import androidx.core.app.NotificationCompat;
import androidx.core.content.ContextCompat;
import androidx.localbroadcastmanager.content.LocalBroadcastManager;
import androidx.media3.common.C;
import androidx.media3.common.DebugViewProvider;
import androidx.media3.common.Effect;
@ -71,13 +82,13 @@ import androidx.media3.effect.GlShaderProgram;
import androidx.media3.effect.HslAdjustment;
import androidx.media3.effect.LanczosResample;
import androidx.media3.effect.OverlayEffect;
import androidx.media3.effect.OverlaySettings;
import androidx.media3.effect.Presentation;
import androidx.media3.effect.RgbAdjustment;
import androidx.media3.effect.RgbFilter;
import androidx.media3.effect.RgbMatrix;
import androidx.media3.effect.ScaleAndRotateTransformation;
import androidx.media3.effect.SingleColorLut;
import androidx.media3.effect.StaticOverlaySettings;
import androidx.media3.effect.TextOverlay;
import androidx.media3.effect.TextureOverlay;
import androidx.media3.exoplayer.DefaultLoadControl;
@ -92,12 +103,16 @@ import androidx.media3.transformer.Effects;
import androidx.media3.transformer.ExperimentalAnalyzerModeFactory;
import androidx.media3.transformer.ExportException;
import androidx.media3.transformer.ExportResult;
import androidx.media3.transformer.InAppMuxer;
import androidx.media3.transformer.InAppFragmentedMp4Muxer;
import androidx.media3.transformer.InAppMp4Muxer;
import androidx.media3.transformer.JsonUtil;
import androidx.media3.transformer.MediaProjectionAssetLoader;
import androidx.media3.transformer.ProgressHolder;
import androidx.media3.transformer.Transformer;
import androidx.media3.transformer.VideoEncoderSettings;
import androidx.media3.ui.AspectRatioFrameLayout;
import androidx.media3.ui.PlayerView;
import androidx.window.layout.WindowMetricsCalculator;
import com.google.android.material.card.MaterialCardView;
import com.google.android.material.progressindicator.LinearProgressIndicator;
import com.google.common.base.Stopwatch;
@ -109,7 +124,6 @@ import java.io.File;
import java.io.FileWriter;
import java.io.IOException;
import java.lang.reflect.Constructor;
import java.util.Arrays;
import java.util.concurrent.CountDownLatch;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.TimeUnit;
@ -120,12 +134,13 @@ import org.json.JSONObject;
public final class TransformerActivity extends AppCompatActivity {
private static final String TAG = "TransformerActivity";
private static final int IMAGE_DURATION_MS = 5_000;
private static final int IMAGE_FRAME_RATE_FPS = 30;
private static final int DEFAULT_FRAME_RATE_FPS = 30;
private static int LOAD_CONTROL_MIN_BUFFER_MS = 5_000;
private static int LOAD_CONTROL_MAX_BUFFER_MS = 5_000;
private Button displayInputButton;
private MaterialCardView inputCardView;
private MaterialCardView outputCardView;
private TextView inputTextView;
private ImageView inputImageView;
private PlayerView inputPlayerView;
@ -137,10 +152,13 @@ public final class TransformerActivity extends AppCompatActivity {
private LinearProgressIndicator progressIndicator;
private Button pauseButton;
private Button resumeButton;
private Button stopCaptureButton;
private Stopwatch exportStopwatch;
private AspectRatioFrameLayout debugFrame;
@Nullable private DebugTextViewHelper debugTextViewHelper;
@Nullable private Intent screenCaptureToken;
@Nullable private MediaProjection mediaProjection;
@Nullable private ExoPlayer inputPlayer;
@Nullable private ExoPlayer outputPlayer;
@Nullable private Transformer transformer;
@ -153,6 +171,7 @@ public final class TransformerActivity extends AppCompatActivity {
setContentView(R.layout.transformer_activity);
inputCardView = findViewById(R.id.input_card_view);
outputCardView = findViewById(R.id.output_card_view);
inputTextView = findViewById(R.id.input_text_view);
inputImageView = findViewById(R.id.input_image_view);
inputPlayerView = findViewById(R.id.input_player_view);
@ -166,6 +185,8 @@ public final class TransformerActivity extends AppCompatActivity {
pauseButton.setOnClickListener(view -> pauseExport());
resumeButton = findViewById(R.id.resume_button);
resumeButton.setOnClickListener(view -> startExport());
stopCaptureButton = findViewById(R.id.stop_capture_button);
stopCaptureButton.setOnClickListener(view -> mediaProjection.stop());
debugFrame = findViewById(R.id.debug_aspect_ratio_frame_layout);
displayInputButton = findViewById(R.id.display_input_button);
displayInputButton.setOnClickListener(view -> toggleInputVideoDisplay());
@ -184,7 +205,10 @@ public final class TransformerActivity extends AppCompatActivity {
protected void onStart() {
super.onStart();
startExport();
// Restart exporting, unless this is a capture session which can run in the background.
if (!isUsingMediaProjection()) {
startExport();
}
inputPlayerView.onResume();
outputPlayerView.onResume();
@ -194,32 +218,72 @@ public final class TransformerActivity extends AppCompatActivity {
protected void onStop() {
super.onStop();
if (transformer != null) {
transformer.cancel();
transformer = null;
}
// The stop watch is reset after cancelling the export, in case cancelling causes the stop watch
// to be stopped in a transformer callback.
exportStopwatch.reset();
inputPlayerView.onPause();
outputPlayerView.onPause();
releasePlayer();
outputFile.delete();
outputFile = null;
if (oldOutputFile != null) {
oldOutputFile.delete();
oldOutputFile = null;
// Keep the capture session going to allow capturing other apps while backgrounded.
if (!isUsingMediaProjection()) {
releasePlayers();
cleanUpExport();
}
}
private void startExport() {
requestReadVideoPermission(/* activity= */ this);
@Override
protected void onDestroy() {
super.onDestroy();
if (isUsingMediaProjection()) {
releasePlayers();
mediaProjection.stop();
mediaProjection = null;
screenCaptureToken = null;
}
cleanUpExport();
}
private void startExport() {
Intent intent = getIntent();
Uri inputUri = checkNotNull(intent.getData());
if (inputUri.toString().equals("transformer_surface_asset:media_projection")
&& screenCaptureToken == null) {
// MediaProjection can only start once the foreground service is running.
MediaProjectionManager mediaProjectionManager =
(MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
Context context = this;
LocalBroadcastManager.getInstance(context)
.registerReceiver(
new BroadcastReceiver() {
@Override
public void onReceive(Context context, Intent intent) {
String action = checkNotNull(intent.getAction());
if (action.equals(DemoMediaProjectionService.ACTION_EVENT_STARTED)) {
LocalBroadcastManager.getInstance(context)
.unregisterReceiver(/* receiver= */ this);
// The service has started so media projection can start.
startExport();
}
}
},
new IntentFilter(DemoMediaProjectionService.ACTION_EVENT_STARTED));
registerForActivityResult(
new ActivityResultContracts.StartActivityForResult(),
activityResult -> {
int resultCode = activityResult.getResultCode();
if (resultCode == RESULT_OK) {
screenCaptureToken = activityResult.getData();
Intent startServiceIntent = new Intent(context, DemoMediaProjectionService.class);
ContextCompat.startForegroundService(context, startServiceIntent);
} else if (resultCode == RESULT_CANCELED) {
finish();
}
})
.launch(mediaProjectionManager.createScreenCaptureIntent());
inputCardView.setVisibility(View.GONE);
outputCardView.setVisibility(View.GONE);
return;
}
try {
outputFile =
createExternalCacheFile("transformer-output-" + Clock.DEFAULT.elapsedRealtime() + ".mp4");
@ -229,6 +293,7 @@ public final class TransformerActivity extends AppCompatActivity {
String outputFilePath = outputFile.getAbsolutePath();
@Nullable Bundle bundle = intent.getExtras();
MediaItem mediaItem = createMediaItem(bundle, inputUri);
Util.maybeRequestReadStoragePermission(/* activity= */ this, mediaItem);
Transformer transformer = createTransformer(bundle, inputUri, outputFilePath);
Composition composition = createComposition(mediaItem, bundle);
exportStopwatch.reset();
@ -245,10 +310,20 @@ public final class TransformerActivity extends AppCompatActivity {
outputVideoTextView.setVisibility(View.GONE);
debugTextView.setVisibility(View.GONE);
informationTextView.setText(R.string.export_started);
outputCardView.setVisibility(View.VISIBLE);
progressViewGroup.setVisibility(View.VISIBLE);
pauseButton.setVisibility(View.VISIBLE);
resumeButton.setVisibility(View.GONE);
progressIndicator.setProgress(0);
if (isUsingMediaProjection()) {
pauseButton.setVisibility(View.GONE);
resumeButton.setVisibility(View.GONE);
stopCaptureButton.setVisibility(View.VISIBLE);
} else {
pauseButton.setVisibility(View.VISIBLE);
resumeButton.setVisibility(View.GONE);
stopCaptureButton.setVisibility(View.GONE);
}
Handler mainHandler = new Handler(getMainLooper());
ProgressHolder progressHolder = new ProgressHolder();
mainHandler.post(
@ -314,21 +389,16 @@ public final class TransformerActivity extends AppCompatActivity {
transformerBuilder.setVideoMimeType(videoMimeType);
}
transformerBuilder.setEncoderFactory(
new DefaultEncoderFactory.Builder(this.getApplicationContext())
.setEnableFallback(bundle.getBoolean(ConfigurationActivity.ENABLE_FALLBACK))
.build());
if (!bundle.getBoolean(ConfigurationActivity.ABORT_SLOW_EXPORT)) {
transformerBuilder.setMaxDelayBetweenMuxerSamplesMs(C.TIME_UNSET);
}
if (bundle.getBoolean(ConfigurationActivity.USE_MEDIA3_MUXER)) {
transformerBuilder.setMuxerFactory(
new InAppMuxer.Factory.Builder()
.setOutputFragmentedMp4(
bundle.getBoolean(ConfigurationActivity.PRODUCE_FRAGMENTED_MP4))
.build());
if (bundle.getBoolean(ConfigurationActivity.USE_MEDIA3_MP4_MUXER)) {
transformerBuilder.setMuxerFactory(new InAppMp4Muxer.Factory());
}
if (bundle.getBoolean(ConfigurationActivity.USE_MEDIA3_FRAGMENTED_MP4_MUXER)) {
transformerBuilder.setMuxerFactory(new InAppFragmentedMp4Muxer.Factory());
}
if (bundle.getBoolean(ConfigurationActivity.ENABLE_DEBUG_PREVIEW)) {
@ -341,6 +411,32 @@ public final class TransformerActivity extends AppCompatActivity {
}
}
VideoEncoderSettings videoEncoderSettings = VideoEncoderSettings.DEFAULT;
if (screenCaptureToken != null) {
MediaProjectionManager mediaProjectionManager =
(MediaProjectionManager) getSystemService(MEDIA_PROJECTION_SERVICE);
MediaProjection mediaProjection =
mediaProjectionManager.getMediaProjection(RESULT_OK, checkNotNull(screenCaptureToken));
Rect bounds =
WindowMetricsCalculator.getOrCreate()
.computeCurrentWindowMetrics(/* activity= */ this)
.getBounds();
int densityDpi = getResources().getConfiguration().densityDpi;
transformerBuilder.setAssetLoaderFactory(
new MediaProjectionAssetLoader.Factory(mediaProjection, bounds, densityDpi));
this.mediaProjection = mediaProjection;
videoEncoderSettings =
videoEncoderSettings
.buildUpon()
.setRepeatPreviousFrameIntervalUs(C.MICROS_PER_SECOND / DEFAULT_FRAME_RATE_FPS)
.build();
}
transformerBuilder.setEncoderFactory(
new DefaultEncoderFactory.Builder(this.getApplicationContext())
.setEnableFallback(bundle.getBoolean(ConfigurationActivity.ENABLE_FALLBACK))
.setRequestedVideoEncoderSettings(videoEncoderSettings)
.build());
return transformerBuilder.build();
}
@ -359,7 +455,7 @@ public final class TransformerActivity extends AppCompatActivity {
private Composition createComposition(MediaItem mediaItem, @Nullable Bundle bundle) {
EditedMediaItem.Builder editedMediaItemBuilder = new EditedMediaItem.Builder(mediaItem);
// For image inputs. Automatically ignored if input is audio/video.
editedMediaItemBuilder.setFrameRate(IMAGE_FRAME_RATE_FPS);
editedMediaItemBuilder.setFrameRate(DEFAULT_FRAME_RATE_FPS);
if (bundle != null) {
ImmutableList<AudioProcessor> audioProcessors = createAudioProcessorsFromBundle(bundle);
ImmutableList<Effect> videoEffects = createVideoEffectsFromBundle(bundle);
@ -394,14 +490,18 @@ public final class TransformerActivity extends AppCompatActivity {
ImmutableList.Builder<AudioProcessor> processors = new ImmutableList.Builder<>();
if (selectedAudioEffects[ConfigurationActivity.HIGH_PITCHED_INDEX]
|| selectedAudioEffects[ConfigurationActivity.SAMPLE_RATE_INDEX]) {
|| selectedAudioEffects[ConfigurationActivity.SAMPLE_RATE_48K_INDEX]
|| selectedAudioEffects[ConfigurationActivity.SAMPLE_RATE_96K_INDEX]) {
SonicAudioProcessor sonicAudioProcessor = new SonicAudioProcessor();
if (selectedAudioEffects[ConfigurationActivity.HIGH_PITCHED_INDEX]) {
sonicAudioProcessor.setPitch(2f);
}
if (selectedAudioEffects[ConfigurationActivity.SAMPLE_RATE_INDEX]) {
if (selectedAudioEffects[ConfigurationActivity.SAMPLE_RATE_48K_INDEX]) {
sonicAudioProcessor.setOutputSampleRateHz(48_000);
}
if (selectedAudioEffects[ConfigurationActivity.SAMPLE_RATE_96K_INDEX]) {
sonicAudioProcessor.setOutputSampleRateHz(96_000);
}
processors.add(sonicAudioProcessor);
}
@ -417,20 +517,9 @@ public final class TransformerActivity extends AppCompatActivity {
if (mixToMono || scaleVolumeToHalf) {
ChannelMixingAudioProcessor mixingAudioProcessor = new ChannelMixingAudioProcessor();
for (int inputChannelCount = 1; inputChannelCount <= 6; inputChannelCount++) {
ChannelMixingMatrix matrix;
if (mixToMono) {
float[] mixingCoefficients = new float[inputChannelCount];
// Each channel is equally weighted in the mix to mono.
Arrays.fill(mixingCoefficients, 1f / inputChannelCount);
matrix =
new ChannelMixingMatrix(
inputChannelCount, /* outputChannelCount= */ 1, mixingCoefficients);
} else {
// Identity matrix.
matrix =
ChannelMixingMatrix.create(
inputChannelCount, /* outputChannelCount= */ inputChannelCount);
}
ChannelMixingMatrix matrix =
ChannelMixingMatrix.createForConstantPower(
inputChannelCount, /* outputChannelCount= */ mixToMono ? 1 : inputChannelCount);
// Apply the volume adjustment.
mixingAudioProcessor.putChannelMixingMatrix(
@ -599,8 +688,8 @@ public final class TransformerActivity extends AppCompatActivity {
private OverlayEffect createOverlayEffectFromBundle(Bundle bundle, boolean[] selectedEffects) {
ImmutableList.Builder<TextureOverlay> overlaysBuilder = new ImmutableList.Builder<>();
if (selectedEffects[ConfigurationActivity.OVERLAY_LOGO_AND_TIMER_INDEX]) {
OverlaySettings logoSettings =
new OverlaySettings.Builder()
StaticOverlaySettings logoSettings =
new StaticOverlaySettings.Builder()
// Place the logo in the bottom left corner of the screen with some padding from the
// edges.
.setOverlayFrameAnchor(/* x= */ -1f, /* y= */ -1f)
@ -619,8 +708,8 @@ public final class TransformerActivity extends AppCompatActivity {
overlaysBuilder.add(logoOverlay, timerOverlay);
}
if (selectedEffects[ConfigurationActivity.BITMAP_OVERLAY_INDEX]) {
OverlaySettings overlaySettings =
new OverlaySettings.Builder()
StaticOverlaySettings overlaySettings =
new StaticOverlaySettings.Builder()
.setAlphaScale(
bundle.getFloat(
ConfigurationActivity.BITMAP_OVERLAY_ALPHA, /* defaultValue= */ 1))
@ -633,8 +722,8 @@ public final class TransformerActivity extends AppCompatActivity {
overlaysBuilder.add(bitmapOverlay);
}
if (selectedEffects[ConfigurationActivity.TEXT_OVERLAY_INDEX]) {
OverlaySettings overlaySettings =
new OverlaySettings.Builder()
StaticOverlaySettings overlaySettings =
new StaticOverlaySettings.Builder()
.setAlphaScale(
bundle.getFloat(ConfigurationActivity.TEXT_OVERLAY_ALPHA, /* defaultValue= */ 1))
.build();
@ -648,6 +737,15 @@ public final class TransformerActivity extends AppCompatActivity {
TextOverlay textOverlay = TextOverlay.createStaticTextOverlay(overlayText, overlaySettings);
overlaysBuilder.add(textOverlay);
}
if (selectedEffects[ConfigurationActivity.CLOCK_OVERLAY_INDEX]) {
overlaysBuilder.add(new ClockOverlay());
}
if (selectedEffects[ConfigurationActivity.CONFETTI_OVERLAY_INDEX]) {
overlaysBuilder.add(new ConfettiOverlay());
}
if (selectedEffects[ConfigurationActivity.ANIMATING_LOGO_OVERLAY]) {
overlaysBuilder.add(new AnimatedLogoOverlay(this.getApplicationContext()));
}
ImmutableList<TextureOverlay> overlays = overlaysBuilder.build();
return overlays.isEmpty() ? null : new OverlayEffect(overlays);
@ -658,6 +756,9 @@ public final class TransformerActivity extends AppCompatActivity {
informationTextView.setText(R.string.export_error);
progressViewGroup.setVisibility(View.GONE);
debugFrame.removeAllViews();
if (isUsingMediaProjection()) {
mediaProjection.stop();
}
Toast.makeText(getApplicationContext(), "Export error: " + exportException, Toast.LENGTH_LONG)
.show();
Log.e(TAG, "Export error", exportException);
@ -701,9 +802,8 @@ public final class TransformerActivity extends AppCompatActivity {
private void playMediaItems(MediaItem inputMediaItem, MediaItem outputMediaItem) {
inputPlayerView.setPlayer(null);
outputPlayerView.setPlayer(null);
releasePlayer();
releasePlayers();
Uri uri = checkNotNull(inputMediaItem.localConfiguration).uri;
ExoPlayer outputPlayer =
new ExoPlayer.Builder(/* context= */ this)
.setLoadControl(
@ -722,6 +822,7 @@ public final class TransformerActivity extends AppCompatActivity {
this.outputPlayer = outputPlayer;
// Only support showing jpg images.
Uri uri = checkNotNull(inputMediaItem.localConfiguration).uri;
if (uri.toString().endsWith("jpg")) {
inputPlayerView.setVisibility(View.GONE);
inputImageView.setVisibility(View.VISIBLE);
@ -735,6 +836,12 @@ public final class TransformerActivity extends AppCompatActivity {
} catch (ExecutionException | InterruptedException e) {
throw new IllegalArgumentException("Failed to load bitmap.", e);
}
} else if (isUsingMediaProjection()) {
inputCardView.setVisibility(View.GONE);
displayInputButton.setVisibility(View.GONE);
Intent stopIntent = new Intent(/* context= */ this, DemoMediaProjectionService.class);
stopIntent.setAction(DemoMediaProjectionService.ACTION_STOP);
ContextCompat.startForegroundService(/* context= */ this, stopIntent);
} else {
inputPlayerView.setVisibility(View.VISIBLE);
inputImageView.setVisibility(View.GONE);
@ -785,7 +892,7 @@ public final class TransformerActivity extends AppCompatActivity {
}
}
private void releasePlayer() {
private void releasePlayers() {
if (debugTextViewHelper != null) {
debugTextViewHelper.stop();
debugTextViewHelper = null;
@ -800,12 +907,22 @@ public final class TransformerActivity extends AppCompatActivity {
}
}
private static void requestReadVideoPermission(AppCompatActivity activity) {
String permission = SDK_INT >= 33 ? READ_MEDIA_VIDEO : READ_EXTERNAL_STORAGE;
if (ActivityCompat.checkSelfPermission(activity, permission)
!= PackageManager.PERMISSION_GRANTED) {
ActivityCompat.requestPermissions(activity, new String[] {permission}, /* requestCode= */ 0);
private void cleanUpExport() {
if (transformer != null) {
transformer.cancel();
transformer = null;
}
if (outputFile != null) {
outputFile.delete();
outputFile = null;
}
if (oldOutputFile != null) {
oldOutputFile.delete();
oldOutputFile = null;
}
// The stop watch is reset after cancelling the export, in case cancelling causes the stop watch
// to be stopped in a transformer callback.
exportStopwatch.reset();
}
private void showToast(@StringRes int messageResource) {
@ -837,6 +954,54 @@ public final class TransformerActivity extends AppCompatActivity {
oldOutputFile = outputFile;
}
private boolean isUsingMediaProjection() {
return mediaProjection != null;
}
/** Foreground service that's required by the media projection APIs. */
public static final class DemoMediaProjectionService extends Service {
private static final String CHANNEL_ID = "DemoMediaProjectionServiceChannel";
private static final String CHANNEL_NAME = "Media projection";
private static final int NOTIFICATION_ID = 1;
private static final String ACTION_EVENT_STARTED = "started";
private static final String ACTION_STOP = "stop";
@Override
@Nullable
public IBinder onBind(Intent intent) {
return null;
}
@Override
public int onStartCommand(Intent intent, int flags, int startId) {
if (ACTION_STOP.equals(intent.getAction())) {
stopSelf();
} else {
Context context = this;
Notification notification =
new NotificationCompat.Builder(context, CHANNEL_ID)
.setOngoing(true)
.setSmallIcon(R.drawable.exo_icon_play)
.build();
if (Util.SDK_INT >= 26) {
NotificationChannel channel =
new NotificationChannel(
CHANNEL_ID, CHANNEL_NAME, NotificationManager.IMPORTANCE_HIGH);
NotificationManager manager = getSystemService(NotificationManager.class);
manager.createNotificationChannel(channel);
}
if (Util.SDK_INT >= 29) {
startForeground(NOTIFICATION_ID, notification, FOREGROUND_SERVICE_TYPE_MEDIA_PROJECTION);
} else {
startForeground(NOTIFICATION_ID, notification);
}
// Notify that the service is started (and it's now safe to set up media projection).
LocalBroadcastManager.getInstance(context).sendBroadcast(new Intent(ACTION_EVENT_STARTED));
}
return START_STICKY;
}
}
private final class DemoDebugViewProvider implements DebugViewProvider {
@Nullable private SurfaceView surfaceView;

View File

@ -239,18 +239,18 @@
android:layout_weight="1">
<TextView
android:layout_gravity="center_vertical"
android:text="@string/use_media3_muxer" />
android:text="@string/use_media3_mp4_muxer" />
<CheckBox
android:id="@+id/use_media3_muxer_checkbox"
android:id="@+id/use_media3_mp4_muxer_checkbox"
android:layout_gravity="end"/>
</TableRow>
<TableRow
android:layout_weight="1">
<TextView
android:layout_gravity="center_vertical"
android:text="@string/produce_fragmented_mp4" />
android:text="@string/use_media3_fragmented_mp4_muxer" />
<CheckBox
android:id="@+id/produce_fragmented_mp4_checkbox"
android:id="@+id/use_media3_fragmented_mp4_muxer_checkbox"
android:layout_gravity="end"/>
</TableRow>
<TableRow

View File

@ -165,6 +165,12 @@
android:layout_width="match_parent"
android:text="@string/resume"/>
<Button
android:id="@+id/stop_capture_button"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:text="@string/stop_capture"/>
<androidx.media3.ui.AspectRatioFrameLayout
android:id="@+id/debug_aspect_ratio_frame_layout"
android:layout_width="match_parent"

View File

@ -28,10 +28,14 @@
<item>Overlay logo and timer</item>
<item>Custom Bitmap Overlay</item>
<item>Custom Text Overlay</item>
<item>Clock Overlay</item>
<item>Confetti Overlay</item>
<item>Animated logo</item>
</string-array>
<string-array name="audio_effects_names">
<item>High pitched</item>
<item>Sample rate of 48000Hz</item>
<item>Sample rate of 96000Hz</item>
<item>Skip silence</item>
<item>Mix channels into mono</item>
<item>Scale volume to 50%</item>
@ -54,6 +58,7 @@
<item>HDR (HDR10+) H265 limited range video (encoding may fail)</item>
<item>HDR (HLG) H265 limited range video (encoding may fail)</item>
<item>720p H264 video with no audio (B-frames)</item>
<item>Record screen</item>
</string-array>
<string-array name="preset_uris">
<item>https://storage.googleapis.com/exoplayer-test-media-1/mp4/android-screens-10s.mp4</item>
@ -73,5 +78,6 @@
<item>https://storage.googleapis.com/exoplayer-test-media-1/mp4/samsung-s21-hdr-hdr10.mp4</item>
<item>https://storage.googleapis.com/exoplayer-test-media-1/mp4/Pixel7Pro_HLG_1080P.mp4</item>
<item>https://storage.googleapis.com/exoplayer-test-media-1/mp4/sample_video_track_only.mp4</item>
<item>transformer_surface_asset:media_projection</item>
</string-array>
</resources>

View File

@ -32,8 +32,8 @@
<string name="enable_debug_preview" translatable="false">Enable debug preview</string>
<string name="enable_debug_tracing" translatable="false">Enable debug tracing</string>
<string name="abort_slow_export" translatable="false">Abort slow export</string>
<string name="use_media3_muxer" translatable="false">Use Media3 muxer</string>
<string name="produce_fragmented_mp4" translatable="false">Produce fragmented MP4</string>
<string name="use_media3_mp4_muxer" translatable="false">Use Media3 Mp4Muxer</string>
<string name="use_media3_fragmented_mp4_muxer" translatable="false">Use Media3 FragmentedMp4Muxer</string>
<string name="trim" translatable="false">Trim</string>
<string name="hdr_mode" translatable="false">HDR mode</string>
<string name="select_audio_effects" translatable="false">Add audio effects</string>
@ -44,6 +44,7 @@
<string name="debug_preview" translatable="false">Debug preview:</string>
<string name="pause" translatable="false">Pause</string>
<string name="resume" translatable="false">Resume</string>
<string name="stop_capture" translatable="false">Stop capture</string>
<string name="debug_preview_not_available" translatable="false">No debug preview available.</string>
<string name="export_started" translatable="false">Export started</string>
<string name="export_timer" translatable="false">Export started %d seconds ago.</string>

View File

@ -24,9 +24,9 @@ android {
}
dependencies {
api 'com.google.android.gms:play-services-cast-framework:21.3.0'
api 'com.google.android.gms:play-services-cast-framework:21.5.0'
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
implementation project(modulePrefix + 'lib-common')
api project(modulePrefix + 'lib-common')
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
testImplementation project(modulePrefix + 'test-utils')

View File

@ -15,7 +15,6 @@
*/
package androidx.media3.cast;
import static androidx.annotation.VisibleForTesting.PROTECTED;
import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Util.SDK_INT;
import static androidx.media3.common.util.Util.castNonNull;
@ -74,6 +73,7 @@ import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.ResultCallback;
import com.google.common.collect.ImmutableList;
import java.util.List;
import java.util.Objects;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/**
@ -467,8 +467,7 @@ public final class CastPlayer extends BasePlayer {
// onPositionDiscontinuity(PositionInfo, PositionInfo, @DiscontinuityReason int).
@SuppressWarnings("deprecation")
@Override
@VisibleForTesting(otherwise = PROTECTED)
public void seekTo(
protected void seekTo(
int mediaItemIndex,
long positionMs,
@Player.Command int seekCommand,
@ -639,7 +638,7 @@ public final class CastPlayer extends BasePlayer {
@Override
public TrackSelectionParameters getTrackSelectionParameters() {
return TrackSelectionParameters.DEFAULT_WITHOUT_CONTEXT;
return TrackSelectionParameters.DEFAULT;
}
@Override
@ -911,7 +910,7 @@ public final class CastPlayer extends BasePlayer {
? currentTimeline.getPeriod(currentWindowIndex, period, /* setIds= */ true).uid
: null;
if (!playingPeriodChangedByTimelineChange
&& !Util.areEqual(oldPeriodUid, currentPeriodUid)
&& !Objects.equals(oldPeriodUid, currentPeriodUid)
&& pendingSeekCount == 0) {
// Report discontinuity and media item auto transition.
currentTimeline.getPeriod(oldWindowIndex, period, /* setIds= */ true);

View File

@ -35,12 +35,14 @@ import androidx.annotation.VisibleForTesting;
import androidx.media3.common.util.NullableType;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import com.google.errorprone.annotations.InlineMe;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.Objects;
/**
* Represents ad group times and information on the state and URIs of ads within each ad group.
@ -70,7 +72,7 @@ public final class AdPlaybackState {
/**
* The original number of ads in the ad group in case the ad group is only partially available,
* or {@link C#LENGTH_UNSET} if unknown. An ad can be partially available when a server side
* or {@link C#LENGTH_UNSET} if unknown. An ad can be partially available when a server-side
* inserted ad live stream is joined while an ad is already playing and some ad information is
* missing.
*/
@ -90,6 +92,9 @@ public final class AdPlaybackState {
/** The durations of each ad in the ad group, in microseconds. */
public final long[] durationsUs;
/** The optional IDs of the ads. */
public final @NullableType String[] ids;
/**
* The offset in microseconds which should be added to the content stream when resuming playback
* after the ad group.
@ -99,6 +104,9 @@ public final class AdPlaybackState {
/** Whether this ad group is server-side inserted and part of the content stream. */
public final boolean isServerSideInserted;
/** Whether this is an ignorable placeholder that must not be attempted to be played. */
public final boolean isPlaceholder;
/**
* Creates a new ad group with an unspecified number of ads.
*
@ -114,7 +122,9 @@ public final class AdPlaybackState {
/* mediaItems= */ new MediaItem[0],
/* durationsUs= */ new long[0],
/* contentResumeOffsetUs= */ 0,
/* isServerSideInserted= */ false);
/* isServerSideInserted= */ false,
/* ids= */ new String[0],
/* isPlaceholder= */ false);
}
@SuppressWarnings("deprecation") // Intentionally assigning deprecated field
@ -126,7 +136,9 @@ public final class AdPlaybackState {
@NullableType MediaItem[] mediaItems,
long[] durationsUs,
long contentResumeOffsetUs,
boolean isServerSideInserted) {
boolean isServerSideInserted,
@NullableType String[] ids,
boolean isPlaceholder) {
checkArgument(states.length == mediaItems.length);
this.timeUs = timeUs;
this.count = count;
@ -140,6 +152,8 @@ public final class AdPlaybackState {
for (int i = 0; i < uris.length; i++) {
uris[i] = mediaItems[i] == null ? null : checkNotNull(mediaItems[i].localConfiguration).uri;
}
this.ids = ids;
this.isPlaceholder = isPlaceholder;
}
/**
@ -155,7 +169,7 @@ public final class AdPlaybackState {
* lastPlayedAdIndex}, or {@link #count} if no later ads should be played. If no ads have been
* played, pass -1 to get the index of the first ad to play.
*
* <p>Note: {@linkplain #isServerSideInserted Server side inserted ads} are always considered
* <p>Note: {@linkplain #isServerSideInserted server-side inserted ads} are always considered
* playable.
*/
public int getNextAdIndexToPlay(@IntRange(from = -1) int lastPlayedAdIndex) {
@ -191,8 +205,24 @@ public final class AdPlaybackState {
return false;
}
private boolean isLivePostrollPlaceholder() {
return isServerSideInserted && timeUs == C.TIME_END_OF_SOURCE && count == C.LENGTH_UNSET;
/**
* Returns whether this is a is a placeholder ad group.
*
* @param isServerSideInserted Whether the postroll placeholder must be server-side inserted.
* @return true only if this ad group has a matching {@link #isServerSideInserted} flag.
*/
public boolean isLivePostrollPlaceholder(boolean isServerSideInserted) {
return (this.isServerSideInserted == isServerSideInserted) && isLivePostrollPlaceholder();
}
/**
* Returns whether this is a placeholder ad group. It can be server-side inserted or not. Use
* {@link #isLivePostrollPlaceholder(boolean)} if you want to differentiate.
*
* @return true only if this is a live postroll placeholder.
*/
public boolean isLivePostrollPlaceholder() {
return isPlaceholder && timeUs == C.TIME_END_OF_SOURCE && count == C.LENGTH_UNSET;
}
@Override
@ -211,7 +241,9 @@ public final class AdPlaybackState {
&& Arrays.equals(states, adGroup.states)
&& Arrays.equals(durationsUs, adGroup.durationsUs)
&& contentResumeOffsetUs == adGroup.contentResumeOffsetUs
&& isServerSideInserted == adGroup.isServerSideInserted;
&& isServerSideInserted == adGroup.isServerSideInserted
&& Arrays.equals(ids, adGroup.ids)
&& isPlaceholder == adGroup.isPlaceholder;
}
@Override
@ -224,6 +256,8 @@ public final class AdPlaybackState {
result = 31 * result + Arrays.hashCode(durationsUs);
result = 31 * result + (int) (contentResumeOffsetUs ^ (contentResumeOffsetUs >>> 32));
result = 31 * result + (isServerSideInserted ? 1 : 0);
result = 31 * result + Arrays.hashCode(ids);
result = 31 * result + (isPlaceholder ? 1 : 0);
return result;
}
@ -238,7 +272,9 @@ public final class AdPlaybackState {
mediaItems,
durationsUs,
contentResumeOffsetUs,
isServerSideInserted);
isServerSideInserted,
ids,
isPlaceholder);
}
/** Returns a new instance with the ad count set to {@code count}. */
@ -247,6 +283,7 @@ public final class AdPlaybackState {
@AdState int[] states = copyStatesWithSpaceForAdCount(this.states, count);
long[] durationsUs = copyDurationsUsWithSpaceForAdCount(this.durationsUs, count);
@NullableType MediaItem[] mediaItems = Arrays.copyOf(this.mediaItems, count);
@NullableType String[] ids = Arrays.copyOf(this.ids, count);
return new AdGroup(
timeUs,
count,
@ -255,7 +292,9 @@ public final class AdPlaybackState {
mediaItems,
durationsUs,
contentResumeOffsetUs,
isServerSideInserted);
isServerSideInserted,
ids,
isPlaceholder);
}
/**
@ -281,6 +320,9 @@ public final class AdPlaybackState {
@NullableType MediaItem[] mediaItems = Arrays.copyOf(this.mediaItems, states.length);
mediaItems[index] = mediaItem;
states[index] = AD_STATE_AVAILABLE;
@NullableType
String[] ids =
this.ids.length == states.length ? this.ids : Arrays.copyOf(this.ids, states.length);
return new AdGroup(
timeUs,
count,
@ -289,7 +331,9 @@ public final class AdPlaybackState {
mediaItems,
durationsUs,
contentResumeOffsetUs,
isServerSideInserted);
isServerSideInserted,
ids,
isPlaceholder);
}
/**
@ -317,6 +361,9 @@ public final class AdPlaybackState {
this.mediaItems.length == states.length
? this.mediaItems
: Arrays.copyOf(this.mediaItems, states.length);
@NullableType
String[] ids =
this.ids.length == states.length ? this.ids : Arrays.copyOf(this.ids, states.length);
states[index] = state;
return new AdGroup(
timeUs,
@ -326,7 +373,9 @@ public final class AdPlaybackState {
mediaItems,
durationsUs,
contentResumeOffsetUs,
isServerSideInserted);
isServerSideInserted,
ids,
isPlaceholder);
}
/** Returns a new instance with the specified ad durations, in microseconds. */
@ -345,7 +394,39 @@ public final class AdPlaybackState {
mediaItems,
durationsUs,
contentResumeOffsetUs,
isServerSideInserted);
isServerSideInserted,
ids,
isPlaceholder);
}
/** Returns a new instance with the specified ID for the given ad index. */
@CheckResult
public AdGroup withAdId(String adId, @IntRange(from = 0) int index) {
@AdState int[] states = copyStatesWithSpaceForAdCount(this.states, index + 1);
long[] durationsUs =
this.durationsUs.length == states.length
? this.durationsUs
: copyDurationsUsWithSpaceForAdCount(this.durationsUs, states.length);
@NullableType
MediaItem[] mediaItems =
this.mediaItems.length == states.length
? this.mediaItems
: Arrays.copyOf(this.mediaItems, states.length);
@NullableType
String[] ids =
this.ids.length == states.length ? this.ids : Arrays.copyOf(this.ids, states.length);
ids[index] = adId;
return new AdGroup(
timeUs,
count,
originalCount,
states,
mediaItems,
durationsUs,
contentResumeOffsetUs,
isServerSideInserted,
ids,
isPlaceholder);
}
/** Returns an instance with the specified {@link #contentResumeOffsetUs}. */
@ -359,7 +440,9 @@ public final class AdPlaybackState {
mediaItems,
durationsUs,
contentResumeOffsetUs,
isServerSideInserted);
isServerSideInserted,
ids,
isPlaceholder);
}
/** Returns an instance with the specified value for {@link #isServerSideInserted}. */
@ -373,7 +456,9 @@ public final class AdPlaybackState {
mediaItems,
durationsUs,
contentResumeOffsetUs,
isServerSideInserted);
isServerSideInserted,
ids,
isPlaceholder);
}
/** Returns an instance with the specified value for {@link #originalCount}. */
@ -386,7 +471,9 @@ public final class AdPlaybackState {
mediaItems,
durationsUs,
contentResumeOffsetUs,
isServerSideInserted);
isServerSideInserted,
ids,
isPlaceholder);
}
/** Removes the last ad from the ad group. */
@ -398,6 +485,7 @@ public final class AdPlaybackState {
if (durationsUs.length > newCount) {
newDurationsUs = Arrays.copyOf(durationsUs, newCount);
}
@NullableType String[] newIds = Arrays.copyOf(ids, newCount);
return new AdGroup(
timeUs,
newCount,
@ -406,7 +494,9 @@ public final class AdPlaybackState {
newMediaItems,
newDurationsUs,
/* contentResumeOffsetUs= */ Util.sum(newDurationsUs),
isServerSideInserted);
isServerSideInserted,
newIds,
isPlaceholder);
}
/**
@ -424,7 +514,9 @@ public final class AdPlaybackState {
/* mediaItems= */ new MediaItem[0],
/* durationsUs= */ new long[0],
contentResumeOffsetUs,
isServerSideInserted);
isServerSideInserted,
ids,
isPlaceholder);
}
int count = this.states.length;
@AdState int[] states = Arrays.copyOf(this.states, count);
@ -441,7 +533,9 @@ public final class AdPlaybackState {
mediaItems,
durationsUs,
contentResumeOffsetUs,
isServerSideInserted);
isServerSideInserted,
ids,
isPlaceholder);
}
/**
@ -470,7 +564,36 @@ public final class AdPlaybackState {
mediaItems,
durationsUs,
contentResumeOffsetUs,
isServerSideInserted);
isServerSideInserted,
ids,
isPlaceholder);
}
private AdGroup withIsPlaceholder(boolean isPlaceholder, boolean isServerSideInserted) {
return new AdGroup(
timeUs,
count,
originalCount,
states,
mediaItems,
durationsUs,
contentResumeOffsetUs,
isServerSideInserted,
ids,
isPlaceholder);
}
/**
* Returns the index of the ad with the given ad ID, or {@link C#INDEX_UNSET} if the ad ID can't
* be found.
*/
public int getIndexOfAdId(String adId) {
for (int i = 0; i < ids.length; i++) {
if (Objects.equals(ids[i], adId)) {
return i;
}
}
return C.INDEX_UNSET;
}
@CheckResult
@ -500,6 +623,8 @@ public final class AdPlaybackState {
private static final String FIELD_IS_SERVER_SIDE_INSERTED = Util.intToStringMaxRadix(6);
private static final String FIELD_ORIGINAL_COUNT = Util.intToStringMaxRadix(7);
@VisibleForTesting static final String FIELD_MEDIA_ITEMS = Util.intToStringMaxRadix(8);
static final String FIELD_IDS = Util.intToStringMaxRadix(9);
static final String FIELD_IS_PLACEHOLDER = Util.intToStringMaxRadix(10);
// Intentionally assigning deprecated field.
// putParcelableArrayList actually supports null elements.
@ -516,6 +641,8 @@ public final class AdPlaybackState {
bundle.putLongArray(FIELD_DURATIONS_US, durationsUs);
bundle.putLong(FIELD_CONTENT_RESUME_OFFSET_US, contentResumeOffsetUs);
bundle.putBoolean(FIELD_IS_SERVER_SIDE_INSERTED, isServerSideInserted);
bundle.putStringArrayList(FIELD_IDS, new ArrayList<>(Arrays.asList(ids)));
bundle.putBoolean(FIELD_IS_PLACEHOLDER, isPlaceholder);
return bundle;
}
@ -536,6 +663,8 @@ public final class AdPlaybackState {
@Nullable long[] durationsUs = bundle.getLongArray(FIELD_DURATIONS_US);
long contentResumeOffsetUs = bundle.getLong(FIELD_CONTENT_RESUME_OFFSET_US);
boolean isServerSideInserted = bundle.getBoolean(FIELD_IS_SERVER_SIDE_INSERTED);
@Nullable ArrayList<String> ids = bundle.getStringArrayList(FIELD_IDS);
boolean isPlaceholder = bundle.getBoolean(FIELD_IS_PLACEHOLDER);
return new AdGroup(
timeUs,
count,
@ -544,7 +673,9 @@ public final class AdPlaybackState {
getMediaItemsFromBundleArrays(mediaItemBundleList, uriList),
durationsUs == null ? new long[0] : durationsUs,
contentResumeOffsetUs,
isServerSideInserted);
isServerSideInserted,
ids == null ? new String[0] : ids.toArray(new String[0]),
isPlaceholder);
}
private ArrayList<@NullableType Bundle> getMediaItemsArrayBundles() {
@ -844,14 +975,21 @@ public final class AdPlaybackState {
adsId, adGroups, adResumePositionUs, contentDurationUs, removedAdGroupCount);
}
/** Returns an instance with the specified value for {@link #adsId}. */
@CheckResult
public AdPlaybackState withAdsId(Object adsId) {
return new AdPlaybackState(
adsId, adGroups, adResumePositionUs, contentDurationUs, removedAdGroupCount);
}
/**
* Returns an instance with the specified ad marked as {@linkplain #AD_STATE_AVAILABLE available}.
*
* <p>Must not be called with client side inserted ad groups. Client side inserted ads should use
* <p>Must not be called with client-side inserted ad groups. Client-side inserted ads should use
* {@link #withAvailableAdMediaItem}.
*
* @throws IllegalStateException in case this methods is called on an ad group that {@linkplain
* AdGroup#isServerSideInserted is not server side inserted}.
* AdGroup#isServerSideInserted is not server-side inserted}.
*/
@CheckResult
public AdPlaybackState withAvailableAd(
@ -907,6 +1045,17 @@ public final class AdPlaybackState {
adsId, adGroups, adResumePositionUs, contentDurationUs, removedAdGroupCount);
}
/** Returns an instance with the specified ad ID for the given ad. */
@CheckResult
public AdPlaybackState withAdId(
@IntRange(from = 0) int adGroupIndex, @IntRange(from = 0) int adIndexInAdGroup, String adId) {
int adjustedIndex = adGroupIndex - removedAdGroupCount;
AdGroup[] adGroups = Util.nullSafeArrayCopy(this.adGroups, this.adGroups.length);
adGroups[adjustedIndex] = adGroups[adjustedIndex].withAdId(adId, adIndexInAdGroup);
return new AdPlaybackState(
adsId, adGroups, adResumePositionUs, contentDurationUs, removedAdGroupCount);
}
/**
* Returns an instance with all ads in the specified ad group skipped (except for those already
* marked as played or in the error state).
@ -1066,25 +1215,56 @@ public final class AdPlaybackState {
adsId, adGroups, adResumePositionUs, contentDurationUs, removedAdGroupCount);
}
/**
* @deprecated Use {@link #withLivePostrollPlaceholderAppended(boolean)} and pass {@code true}
* instead.
*/
@InlineMe(replacement = "this.withLivePostrollPlaceholderAppended(true)")
@Deprecated
public AdPlaybackState withLivePostrollPlaceholderAppended() {
return withLivePostrollPlaceholderAppended(/* isServerSideInserted= */ true);
}
/**
* 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. A player is not expected to play this placeholder. It is only used to indicate that
* another ad group with this ad group index will be inserted in the future.
* <p>Adding such a placeholder is only required for periods of live streams. A player is not
* expected to play this placeholder. It is only used to indicate that another ad group with this
* ad group index will be inserted in the future.
*
* <p>See {@link #endsWithLivePostrollPlaceHolder()} also.
* <p>See {@link #endsWithLivePostrollPlaceHolder()} and {@link
* #endsWithLivePostrollPlaceHolder(boolean)} also.
*
* @param isServerSideInserted Whether this is a server-side inserted ad (single stream).
* @return The new ad playback state instance ending with a live postroll placeholder.
*/
public AdPlaybackState withLivePostrollPlaceholderAppended() {
public AdPlaybackState withLivePostrollPlaceholderAppended(boolean isServerSideInserted) {
return withNewAdGroup(adGroupCount, /* adGroupTimeUs= */ C.TIME_END_OF_SOURCE)
.withIsServerSideInserted(adGroupCount, true);
.withIsPlaceholder(adGroupCount, /* isPlaceholder= */ true, isServerSideInserted);
}
@VisibleForTesting
/* package */ AdPlaybackState withIsPlaceholder(
int adGroupIndex, boolean isPlaceholder, boolean isServerSideInserted) {
int adjustedIndex = adGroupIndex - removedAdGroupCount;
if (adGroups[adjustedIndex].isPlaceholder == isPlaceholder
&& adGroups[adjustedIndex].isServerSideInserted == isServerSideInserted) {
return this;
}
AdGroup[] adGroups = Util.nullSafeArrayCopy(this.adGroups, this.adGroups.length);
adGroups[adjustedIndex] =
adGroups[adjustedIndex].withIsPlaceholder(isPlaceholder, isServerSideInserted);
return new AdPlaybackState(
adsId, adGroups, adResumePositionUs, contentDurationUs, removedAdGroupCount);
}
/**
* Returns whether the last ad group is a live postroll placeholder as inserted by {@link
* #withLivePostrollPlaceholderAppended()}.
* #withLivePostrollPlaceholderAppended(boolean)}.
*
* <p>Note: That either server-side or client-side inserted placeholders are considered. Use
* {@link #endsWithLivePostrollPlaceHolder(boolean)} if you want to test for one or the other
* only.
*
* @return Whether the ad playback state ends with a live postroll placeholder.
*/
@ -1094,7 +1274,22 @@ public final class AdPlaybackState {
}
/**
* Whether the {@link AdGroup} at the given ad group index is a live postroll placeholder.
* Returns whether the last ad group is a live postroll placeholder as inserted by {@link
* #withLivePostrollPlaceholderAppended(boolean)} .
*
* @param isServerSideInserted Whether the trailing placeholder is server-side inserted.
* @return Whether the ad playback state ends with a live postroll placeholder.
*/
public boolean endsWithLivePostrollPlaceHolder(boolean isServerSideInserted) {
int adGroupIndex = adGroupCount - 1;
return adGroupIndex >= 0 && isLivePostrollPlaceholder(adGroupIndex, isServerSideInserted);
}
/**
* Returns whether the {@link AdGroup} at the given ad group index is a live postroll placeholder.
*
* <p>Note: That either server-side or client-side inserted placeholders return true. Use {@link
* #isLivePostrollPlaceholder(int, boolean)} if you want to test for one or the other only.
*
* @param adGroupIndex The ad group index.
* @return True if the ad group at the given index is a live postroll placeholder, false if not.
@ -1103,6 +1298,31 @@ public final class AdPlaybackState {
return adGroupIndex == adGroupCount - 1 && getAdGroup(adGroupIndex).isLivePostrollPlaceholder();
}
/**
* Returns whether the {@link AdGroup} at the given ad group index is a live postroll placeholder
* and either server or client-side inserted.
*
* @param adGroupIndex The ad group index.
* @param isServerSideInserted Whether the placeholder is server-side inserted.
* @return True if the ad group at the given index is a live postroll placeholder, false if not.
*/
public boolean isLivePostrollPlaceholder(int adGroupIndex, boolean isServerSideInserted) {
return adGroupIndex == adGroupCount - 1
&& getAdGroup(adGroupIndex).isLivePostrollPlaceholder(isServerSideInserted);
}
/**
* Returns the index of the ad with the given ad ID in the given ad group, or {@link
* C#INDEX_UNSET} if the ad ID can't be found.
*
* @param adGroupIndex The ad group index.
* @param adId The ad ID.
* @return The ad index in the ad group, or {@link C#INDEX_UNSET} if the ad ID is not found.
*/
public int getAdIndexOfAdId(int adGroupIndex, String adId) {
return getAdGroup(adGroupIndex).getIndexOfAdId(adId);
}
/**
* Returns a copy of the ad playback state with the given ads ID.
*
@ -1124,7 +1344,9 @@ public final class AdPlaybackState {
Arrays.copyOf(adGroup.mediaItems, adGroup.mediaItems.length),
Arrays.copyOf(adGroup.durationsUs, adGroup.durationsUs.length),
adGroup.contentResumeOffsetUs,
adGroup.isServerSideInserted);
adGroup.isServerSideInserted,
adGroup.ids,
adGroup.isPlaceholder);
}
return new AdPlaybackState(
adsId,
@ -1143,7 +1365,7 @@ public final class AdPlaybackState {
return false;
}
AdPlaybackState that = (AdPlaybackState) o;
return Util.areEqual(adsId, that.adsId)
return Objects.equals(adsId, that.adsId)
&& adGroupCount == that.adGroupCount
&& adResumePositionUs == that.adResumePositionUs
&& contentDurationUs == that.contentDurationUs
@ -1226,7 +1448,7 @@ public final class AdPlaybackState {
// 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)
|| adGroup.isLivePostrollPlaceholder()
|| positionUs < periodDurationUs;
}
return positionUs < adGroupPositionUs;

View File

@ -170,6 +170,43 @@ public final class AudioAttributes {
return audioAttributesV21;
}
/** Returns the {@link C.StreamType} corresponding to these audio attributes. */
@UnstableApi
public @C.StreamType int getStreamType() {
// Flags to stream type mapping
if ((flags & C.FLAG_AUDIBILITY_ENFORCED) == C.FLAG_AUDIBILITY_ENFORCED) {
return C.STREAM_TYPE_SYSTEM;
}
// Usage to stream type mapping
switch (usage) {
case C.USAGE_ASSISTANCE_SONIFICATION:
return C.STREAM_TYPE_SYSTEM;
case C.USAGE_VOICE_COMMUNICATION:
return C.STREAM_TYPE_VOICE_CALL;
case C.USAGE_VOICE_COMMUNICATION_SIGNALLING:
return C.STREAM_TYPE_DTMF;
case C.USAGE_ALARM:
return C.STREAM_TYPE_ALARM;
case C.USAGE_NOTIFICATION_RINGTONE:
return C.STREAM_TYPE_RING;
case C.USAGE_NOTIFICATION:
case C.USAGE_NOTIFICATION_COMMUNICATION_REQUEST:
case C.USAGE_NOTIFICATION_COMMUNICATION_INSTANT:
case C.USAGE_NOTIFICATION_COMMUNICATION_DELAYED:
case C.USAGE_NOTIFICATION_EVENT:
return C.STREAM_TYPE_NOTIFICATION;
case C.USAGE_ASSISTANCE_ACCESSIBILITY:
return C.STREAM_TYPE_ACCESSIBILITY;
case C.USAGE_MEDIA:
case C.USAGE_GAME:
case C.USAGE_ASSISTANCE_NAVIGATION_GUIDANCE:
case C.USAGE_ASSISTANT:
case C.USAGE_UNKNOWN:
default:
return C.STREAM_TYPE_MUSIC;
}
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {

View File

@ -15,15 +15,14 @@
*/
package androidx.media3.common;
import static androidx.annotation.VisibleForTesting.PROTECTED;
import static java.lang.Math.max;
import static java.lang.Math.min;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.ForOverride;
import java.util.List;
/** Abstract base {@link Player} which implements common implementation independent methods. */
@ -276,8 +275,8 @@ public abstract class BasePlayer implements Player {
* @param seekCommand The {@link Player.Command} used to trigger the seek.
* @param isRepeatingCurrentItem Whether this seeks repeats the current item.
*/
@VisibleForTesting(otherwise = PROTECTED)
public abstract void seekTo(
@ForOverride
protected abstract void seekTo(
int mediaItemIndex,
long positionMs,
@Player.Command int seekCommand,

View File

@ -29,7 +29,6 @@ import androidx.media3.common.util.Util;
import com.google.common.collect.ImmutableList;
import java.util.List;
// LINT.IfChange(javadoc)
/**
* A {@link Binder} to transfer a list of {@link Bundle Bundles} across processes by splitting the
* list into multiple transactions.

View File

@ -30,6 +30,7 @@ import android.media.MediaCodec;
import android.media.MediaCrypto;
import android.media.MediaFormat;
import android.net.Uri;
import android.opengl.GLES20;
import android.view.Surface;
import androidx.annotation.IntDef;
import androidx.media3.common.util.UnstableApi;
@ -327,8 +328,8 @@ public final class C {
/**
* Stream types for an {@link android.media.AudioTrack}. One of {@link #STREAM_TYPE_ALARM}, {@link
* #STREAM_TYPE_DTMF}, {@link #STREAM_TYPE_MUSIC}, {@link #STREAM_TYPE_NOTIFICATION}, {@link
* #STREAM_TYPE_RING}, {@link #STREAM_TYPE_SYSTEM}, {@link #STREAM_TYPE_VOICE_CALL} or {@link
* #STREAM_TYPE_DEFAULT}.
* #STREAM_TYPE_RING}, {@link #STREAM_TYPE_SYSTEM}, {@link #STREAM_TYPE_VOICE_CALL}, {@link
* #STREAM_TYPE_ACCESSIBILITY} or {@link #STREAM_TYPE_DEFAULT}.
*/
// @Target list includes both 'default' targets and TYPE_USE, to ensure backwards compatibility
// with Kotlin usages from before TYPE_USE was added.
@ -345,6 +346,7 @@ public final class C {
STREAM_TYPE_RING,
STREAM_TYPE_SYSTEM,
STREAM_TYPE_VOICE_CALL,
STREAM_TYPE_ACCESSIBILITY,
STREAM_TYPE_DEFAULT
})
public @interface StreamType {}
@ -370,6 +372,10 @@ public final class C {
/** See {@link AudioManager#STREAM_VOICE_CALL}. */
@UnstableApi public static final int STREAM_TYPE_VOICE_CALL = AudioManager.STREAM_VOICE_CALL;
/** See {@link AudioManager#STREAM_ACCESSIBILITY}. */
@UnstableApi
public static final int STREAM_TYPE_ACCESSIBILITY = AudioManager.STREAM_ACCESSIBILITY;
/** 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;
@ -611,6 +617,28 @@ public final class C {
/** See {@link android.media.AudioAttributes#ALLOW_CAPTURE_BY_SYSTEM}. */
public static final int ALLOW_CAPTURE_BY_SYSTEM = AudioAttributes.ALLOW_CAPTURE_BY_SYSTEM;
/**
* Flags which represent a set of video codecs.
*
* <p>Possible flag values are:
*
* <ul>
* <li>{@link #VIDEO_CODEC_FLAG_H264}
* <li>{@link #VIDEO_CODEC_FLAG_H265}
* </ul>
*/
@UnstableApi
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE)
@IntDef(
flag = true,
value = {VIDEO_CODEC_FLAG_H264, VIDEO_CODEC_FLAG_H265})
public @interface VideoCodecFlags {}
@UnstableApi public static final int VIDEO_CODEC_FLAG_H264 = 1;
@UnstableApi public static final int VIDEO_CODEC_FLAG_H265 = 2;
/**
* Flags which can apply to a buffer containing a media sample.
*
@ -670,6 +698,7 @@ public final class C {
/** A non-realtime (as fast as possible) {@linkplain MediaFormat#KEY_PRIORITY codec priority}. */
@UnstableApi public static final int MEDIA_CODEC_PRIORITY_NON_REALTIME = 1;
// LINT.IfChange
/**
* Video decoder output modes. Possible modes are {@link #VIDEO_OUTPUT_MODE_NONE}, {@link
* #VIDEO_OUTPUT_MODE_YUV} and {@link #VIDEO_OUTPUT_MODE_SURFACE_YUV}.
@ -690,6 +719,11 @@ public final class C {
/** Video decoder output mode that renders 4:2:0 YUV planes directly to a surface. */
@UnstableApi public static final int VIDEO_OUTPUT_MODE_SURFACE_YUV = 1;
// LINT.ThenChange(
// ../../../../../../../decoder_av1/src/main/jni/gav1_jni.cc,
// ../../../../../../../decoder_vp9/src/main/jni/vpx_jni.cc
// )
/**
* Video scaling modes for {@link MediaCodec}-based renderers. One of {@link
* #VIDEO_SCALING_MODE_SCALE_TO_FIT}, {@link #VIDEO_SCALING_MODE_SCALE_TO_FIT_WITH_CROPPING} or
@ -781,6 +815,8 @@ public final class C {
*/
public static final int SELECTION_FLAG_AUTOSELECT = 1 << 2; // 4
// LINT.ThenChange("util/Util.java:selection_flags")
/** Represents an undetermined language as an ISO 639-2 language code. */
public static final String LANGUAGE_UNDETERMINED = "und";
@ -1162,6 +1198,11 @@ public final class C {
/** See {@link MediaFormat#COLOR_STANDARD_BT2020}. */
@UnstableApi public static final int COLOR_SPACE_BT2020 = MediaFormat.COLOR_STANDARD_BT2020;
// LINT.ThenChange(
// util/MediaFormatUtil.java:color_space,
// ColorInfo.java:color_space,
// )
// LINT.IfChange(color_transfer)
/**
* Video/image color transfer characteristics. One of {@link Format#NO_VALUE}, {@link
@ -1209,6 +1250,14 @@ public final class C {
/** See {@link MediaFormat#COLOR_TRANSFER_HLG}. */
@UnstableApi public static final int COLOR_TRANSFER_HLG = MediaFormat.COLOR_TRANSFER_HLG;
// LINT.ThenChange(
// util/MediaFormatUtil.java:color_transfer,
// ColorInfo.java:color_transfer,
// ../../../../../../../effect/src/main/assets/shaders/fragment_shader_transformation_sdr_external_es2.glsl:color_transfer,
// ../../../../../../../effect/src/main/assets/shaders/fragment_shader_transformation_external_yuv_es3.glsl:color_transfer,
// ../../../../../../../effect/src/main/assets/shaders/fragment_shader_oetf_es3.glsl:color_transfer,
// )
// LINT.IfChange(color_range)
/**
* Video color range. One of {@link Format#NO_VALUE}, {@link #COLOR_RANGE_LIMITED} or {@link
@ -1227,6 +1276,11 @@ public final class C {
/** See {@link MediaFormat#COLOR_RANGE_FULL}. */
@UnstableApi public static final int COLOR_RANGE_FULL = MediaFormat.COLOR_RANGE_FULL;
// LINT.ThenChange(
// util/MediaFormatUtil.java:color_range,
// ColorInfo.java:color_range,
// )
/** Video projection types. */
@UnstableApi
@Documented
@ -1520,6 +1574,8 @@ public final class C {
*/
public static final int ROLE_FLAG_AUXILIARY = 1 << 15;
// LINT.ThenChange("util/Util.java:role_flags")
/**
* {@linkplain #ROLE_FLAG_AUXILIARY Auxiliary track types}. One of {@link
* #AUXILIARY_TRACK_TYPE_UNDEFINED}, {@link #AUXILIARY_TRACK_TYPE_ORIGINAL}, {@link
@ -1565,6 +1621,8 @@ public final class C {
/** A timed metadata of depth video track. */
@UnstableApi public static final int AUXILIARY_TRACK_TYPE_DEPTH_METADATA = 4;
// LINT.ThenChange("util/Util.java:auxiliary_track_type")
/**
* Level of support for a format. One of {@link #FORMAT_HANDLED}, {@link
* #FORMAT_EXCEEDS_CAPABILITIES}, {@link #FORMAT_UNSUPPORTED_DRM}, {@link
@ -1666,6 +1724,40 @@ public final class C {
/** The first frame was rendered. */
@UnstableApi public static final int FIRST_FRAME_RENDERED = 3;
/**
* Texture filtering algorithm for minification.
*
* <p>Possible values are:
*
* <ul>
* <li>{@link #TEXTURE_MIN_FILTER_LINEAR}
* <li>{@link #TEXTURE_MIN_FILTER_LINEAR_MIPMAP_LINEAR}
* </ul>
*
* <p>The algorithms are ordered by increasing visual quality and computational cost.
*/
@UnstableApi
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE)
@IntDef({TEXTURE_MIN_FILTER_LINEAR, TEXTURE_MIN_FILTER_LINEAR_MIPMAP_LINEAR})
public @interface TextureMinFilter {}
/**
* Returns the weighted average of the four texture elements that are closest to the specified
* texture coordinates.
*/
@UnstableApi public static final int TEXTURE_MIN_FILTER_LINEAR = GLES20.GL_LINEAR;
/**
* Chooses the two mipmaps that most closely match the size of the pixel being textured and uses
* the {@link C#TEXTURE_MIN_FILTER_LINEAR} criterion (a weighted average of the texture elements
* that are closest to the specified texture coordinates) to produce a texture value from each
* mipmap. The final texture value is a weighted average of those two values.
*/
@UnstableApi
public static final int TEXTURE_MIN_FILTER_LINEAR_MIPMAP_LINEAR = GLES20.GL_LINEAR_MIPMAP_LINEAR;
/**
* @deprecated Use {@link Util#usToMs(long)}.
*/

View File

@ -487,6 +487,7 @@ public final class ColorInfo {
default:
return "Undefined color space " + colorSpace;
}
// LINT.ThenChange(C.java:color_space)
}
private static String colorTransferToString(@C.ColorTransfer int colorTransfer) {
@ -509,6 +510,7 @@ public final class ColorInfo {
default:
return "Undefined color transfer " + colorTransfer;
}
// LINT.ThenChange(C.java:color_transfer)
}
private static String colorRangeToString(@C.ColorRange int colorRange) {
@ -523,6 +525,7 @@ public final class ColorInfo {
default:
return "Undefined color range " + colorRange;
}
// LINT.ThenChange(C.java:color_range)
}
private static final String FIELD_COLOR_SPACE = Util.intToStringMaxRadix(0);

View File

@ -30,6 +30,7 @@ import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Objects;
/** Information about the playback device. */
public final class DeviceInfo {
@ -178,7 +179,7 @@ public final class DeviceInfo {
return playbackType == other.playbackType
&& minVolume == other.minVolume
&& maxVolume == other.maxVolume
&& Util.areEqual(routingControllerId, other.routingControllerId);
&& Objects.equals(routingControllerId, other.routingControllerId);
}
@Override
@ -227,5 +228,4 @@ public final class DeviceInfo {
.setRoutingControllerId(routingControllerId)
.build();
}
;
}

View File

@ -28,6 +28,7 @@ import java.util.ArrayList;
import java.util.Arrays;
import java.util.Comparator;
import java.util.List;
import java.util.Objects;
import java.util.UUID;
/** Initialization data for one or more DRM schemes. */
@ -160,7 +161,7 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
*/
@CheckResult
public DrmInitData copyWithSchemeType(@Nullable String schemeType) {
if (Util.areEqual(this.schemeType, schemeType)) {
if (Objects.equals(this.schemeType, schemeType)) {
return this;
}
return new DrmInitData(schemeType, false, schemeDatas);
@ -204,7 +205,7 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
return false;
}
DrmInitData other = (DrmInitData) obj;
return Util.areEqual(schemeType, other.schemeType)
return Objects.equals(schemeType, other.schemeType)
&& Arrays.equals(schemeDatas, other.schemeDatas);
}
@ -352,9 +353,9 @@ public final class DrmInitData implements Comparator<SchemeData>, Parcelable {
return true;
}
SchemeData other = (SchemeData) obj;
return Util.areEqual(licenseServerUrl, other.licenseServerUrl)
&& Util.areEqual(mimeType, other.mimeType)
&& Util.areEqual(uuid, other.uuid)
return Objects.equals(licenseServerUrl, other.licenseServerUrl)
&& Objects.equals(mimeType, other.mimeType)
&& Objects.equals(uuid, other.uuid)
&& Arrays.equals(data, other.data);
}

View File

@ -103,6 +103,7 @@ import java.util.UUID;
* <li>{@link #projectionData}
* <li>{@link #stereoMode}
* <li>{@link #colorInfo}
* <li>{@link #maxSubLayers}
* </ul>
*
* <h2 id="audio-formats">Fields relevant to audio formats</h2>
@ -179,6 +180,7 @@ public final class Format {
@Nullable private byte[] projectionData;
private @C.StereoMode int stereoMode;
@Nullable private ColorInfo colorInfo;
private int maxSubLayers;
// Audio specific.
@ -217,6 +219,7 @@ public final class Format {
frameRate = NO_VALUE;
pixelWidthHeightRatio = 1.0f;
stereoMode = NO_VALUE;
maxSubLayers = NO_VALUE;
// Audio specific.
channelCount = NO_VALUE;
sampleRate = NO_VALUE;
@ -268,6 +271,7 @@ public final class Format {
this.projectionData = format.projectionData;
this.stereoMode = format.stereoMode;
this.colorInfo = format.colorInfo;
this.maxSubLayers = format.maxSubLayers;
// Audio specific.
this.channelCount = format.channelCount;
this.sampleRate = format.sampleRate;
@ -656,6 +660,18 @@ public final class Format {
return this;
}
/**
* Sets {@link Format#maxSubLayers}. The default value is {@link #NO_VALUE}.
*
* @param maxSubLayers The {@link Format#maxSubLayers}.
* @return The builder.
*/
@CanIgnoreReturnValue
public Builder setMaxSubLayers(int maxSubLayers) {
this.maxSubLayers = maxSubLayers;
return this;
}
// Audio specific.
/**
@ -1009,6 +1025,12 @@ public final class Format {
/** The color metadata associated with the video, or null if not applicable. */
@UnstableApi @Nullable public final ColorInfo colorInfo;
/**
* The maximum number of temporal scalable sub-layers in the video bitstream, or {@link #NO_VALUE}
* if not applicable.
*/
@UnstableApi public final int maxSubLayers;
// Audio specific.
/** The number of audio channels, or {@link #NO_VALUE} if unknown or not applicable. */
@ -1127,6 +1149,7 @@ public final class Format {
projectionData = builder.projectionData;
stereoMode = builder.stereoMode;
colorInfo = builder.colorInfo;
maxSubLayers = builder.maxSubLayers;
// Audio specific.
channelCount = builder.channelCount;
sampleRate = builder.sampleRate;
@ -1309,6 +1332,7 @@ public final class Format {
// [Omitted] projectionData.
result = 31 * result + stereoMode;
// [Omitted] colorInfo.
result = 31 * result + maxSubLayers;
// Audio specific.
result = 31 * result + channelCount;
result = 31 * result + sampleRate;
@ -1351,6 +1375,7 @@ public final class Format {
&& height == other.height
&& rotationDegrees == other.rotationDegrees
&& stereoMode == other.stereoMode
&& maxSubLayers == other.maxSubLayers
&& channelCount == other.channelCount
&& sampleRate == other.sampleRate
&& pcmEncoding == other.pcmEncoding
@ -1452,6 +1477,9 @@ public final class Format {
if (format.frameRate != NO_VALUE) {
builder.append(", fps=").append(format.frameRate);
}
if (format.maxSubLayers != NO_VALUE) {
builder.append(", maxSubLayers=").append(format.maxSubLayers);
}
if (format.channelCount != NO_VALUE) {
builder.append(", channels=").append(format.channelCount);
}
@ -1496,7 +1524,8 @@ public final class Format {
private static final String FIELD_AVERAGE_BITRATE = Util.intToStringMaxRadix(5);
private static final String FIELD_PEAK_BITRATE = Util.intToStringMaxRadix(6);
private static final String FIELD_CODECS = Util.intToStringMaxRadix(7);
private static final String FIELD_METADATA = Util.intToStringMaxRadix(8);
// Do not reuse this key.
private static final String UNUSED_FIELD_METADATA = Util.intToStringMaxRadix(8);
private static final String FIELD_CONTAINER_MIME_TYPE = Util.intToStringMaxRadix(9);
private static final String FIELD_SAMPLE_MIME_TYPE = Util.intToStringMaxRadix(10);
private static final String FIELD_MAX_INPUT_SIZE = Util.intToStringMaxRadix(11);
@ -1522,22 +1551,14 @@ public final class Format {
private static final String FIELD_TILE_COUNT_VERTICAL = Util.intToStringMaxRadix(31);
private static final String FIELD_LABELS = Util.intToStringMaxRadix(32);
private static final String FIELD_AUXILIARY_TRACK_TYPE = Util.intToStringMaxRadix(33);
/**
* @deprecated Use {@link #toBundle(boolean)} instead.
*/
@UnstableApi
@Deprecated
public Bundle toBundle() {
return toBundle(/* excludeMetadata= */ false);
}
private static final String FIELD_MAX_SUB_LAYERS = Util.intToStringMaxRadix(34);
/**
* Returns a {@link Bundle} representing the information stored in this object. If {@code
* excludeMetadata} is true, {@linkplain Format#metadata metadata} is excluded.
*/
@UnstableApi
public Bundle toBundle(boolean excludeMetadata) {
public Bundle toBundle() {
Bundle bundle = new Bundle();
bundle.putString(FIELD_ID, id);
bundle.putString(FIELD_LABEL, label);
@ -1552,10 +1573,7 @@ public final class Format {
bundle.putInt(FIELD_AVERAGE_BITRATE, averageBitrate);
bundle.putInt(FIELD_PEAK_BITRATE, peakBitrate);
bundle.putString(FIELD_CODECS, codecs);
if (!excludeMetadata) {
// TODO (internal ref: b/239701618)
bundle.putParcelable(FIELD_METADATA, metadata);
}
// The metadata does not implement toBundle() method, hence can not be added.
// Container specific.
bundle.putString(FIELD_CONTAINER_MIME_TYPE, containerMimeType);
// Sample specific.
@ -1579,6 +1597,7 @@ public final class Format {
if (colorInfo != null) {
bundle.putBundle(FIELD_COLOR_INFO, colorInfo.toBundle());
}
bundle.putInt(FIELD_MAX_SUB_LAYERS, maxSubLayers);
// Audio specific.
bundle.putInt(FIELD_CHANNEL_COUNT, channelCount);
bundle.putInt(FIELD_SAMPLE_RATE, sampleRate);
@ -1618,7 +1637,6 @@ public final class Format {
.setAverageBitrate(bundle.getInt(FIELD_AVERAGE_BITRATE, DEFAULT.averageBitrate))
.setPeakBitrate(bundle.getInt(FIELD_PEAK_BITRATE, DEFAULT.peakBitrate))
.setCodecs(defaultIfNull(bundle.getString(FIELD_CODECS), DEFAULT.codecs))
.setMetadata(defaultIfNull(bundle.getParcelable(FIELD_METADATA), DEFAULT.metadata))
// Container specific.
.setContainerMimeType(
defaultIfNull(bundle.getString(FIELD_CONTAINER_MIME_TYPE), DEFAULT.containerMimeType))
@ -1647,7 +1665,8 @@ public final class Format {
.setPixelWidthHeightRatio(
bundle.getFloat(FIELD_PIXEL_WIDTH_HEIGHT_RATIO, DEFAULT.pixelWidthHeightRatio))
.setProjectionData(bundle.getByteArray(FIELD_PROJECTION_DATA))
.setStereoMode(bundle.getInt(FIELD_STEREO_MODE, DEFAULT.stereoMode));
.setStereoMode(bundle.getInt(FIELD_STEREO_MODE, DEFAULT.stereoMode))
.setMaxSubLayers(bundle.getInt(FIELD_MAX_SUB_LAYERS, DEFAULT.maxSubLayers));
Bundle colorInfoBundle = bundle.getBundle(FIELD_COLOR_INFO);
if (colorInfoBundle != null) {
builder.setColorInfo(ColorInfo.fromBundle(colorInfoBundle));

View File

@ -25,7 +25,6 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import java.util.List;
// LINT.IfChange(javadoc)
/**
* A {@link SimpleBasePlayer} that forwards all calls to another {@link Player} instance.
*
@ -60,7 +59,7 @@ public class ForwardingSimpleBasePlayer extends SimpleBasePlayer {
private final Player player;
private ForwardingPositionSupplier currentPositionSupplier;
private LivePositionSuppliers livePositionSuppliers;
private Metadata lastTimedMetadata;
private @Player.PlayWhenReadyChangeReason int playWhenReadyChangeReason;
private @Player.DiscontinuityReason int pendingDiscontinuityReason;
@ -78,7 +77,7 @@ public class ForwardingSimpleBasePlayer extends SimpleBasePlayer {
this.lastTimedMetadata = new Metadata(/* presentationTimeUs= */ C.TIME_UNSET);
this.playWhenReadyChangeReason = Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST;
this.pendingDiscontinuityReason = Player.DISCONTINUITY_REASON_INTERNAL;
this.currentPositionSupplier = new ForwardingPositionSupplier(player);
this.livePositionSuppliers = new LivePositionSuppliers(player);
player.addListener(
new Listener() {
@Override
@ -99,15 +98,8 @@ public class ForwardingSimpleBasePlayer extends SimpleBasePlayer {
@Player.DiscontinuityReason int reason) {
pendingDiscontinuityReason = reason;
pendingPositionDiscontinuityNewPositionMs = newPosition.positionMs;
// Any previously created State will directly call through to player.getCurrentPosition
// via the existing position supplier. From this point onwards, this is wrong as the
// player had a discontinuity and will now return a new position unrelated to the old
// State. We can disconnect these old State objects from the underlying Player by fixing
// the position to the one before the discontinuity and using a new (live) position
// supplier for future State objects.
currentPositionSupplier.setConstant(
oldPosition.positionMs, oldPosition.contentPositionMs);
currentPositionSupplier = new ForwardingPositionSupplier(player);
livePositionSuppliers.disconnect(oldPosition.positionMs, oldPosition.contentPositionMs);
livePositionSuppliers = new LivePositionSuppliers(player);
}
@Override
@ -132,18 +124,18 @@ public class ForwardingSimpleBasePlayer extends SimpleBasePlayer {
protected State getState() {
// Ordered alphabetically by State.Builder setters.
State.Builder state = new State.Builder();
ForwardingPositionSupplier positionSupplier = currentPositionSupplier;
LivePositionSuppliers positionSuppliers = livePositionSuppliers;
if (player.isCommandAvailable(Player.COMMAND_GET_CURRENT_MEDIA_ITEM)) {
state.setAdBufferedPositionMs(positionSupplier::getBufferedPositionMs);
state.setAdPositionMs(positionSupplier::getCurrentPositionMs);
state.setAdBufferedPositionMs(positionSuppliers.bufferedPositionSupplier);
state.setAdPositionMs(positionSuppliers.currentPositionSupplier);
}
if (player.isCommandAvailable(Player.COMMAND_GET_AUDIO_ATTRIBUTES)) {
state.setAudioAttributes(player.getAudioAttributes());
}
state.setAvailableCommands(player.getAvailableCommands());
if (player.isCommandAvailable(Player.COMMAND_GET_CURRENT_MEDIA_ITEM)) {
state.setContentBufferedPositionMs(positionSupplier::getContentBufferedPositionMs);
state.setContentPositionMs(positionSupplier::getContentPositionMs);
state.setContentBufferedPositionMs(positionSuppliers.contentBufferedPositionSupplier);
state.setContentPositionMs(positionSuppliers.contentPositionSupplier);
if (player.isCommandAvailable(Player.COMMAND_GET_TIMELINE)) {
state.setCurrentAd(player.getCurrentAdGroupIndex(), player.getCurrentAdIndexInAdGroup());
}
@ -194,7 +186,7 @@ public class ForwardingSimpleBasePlayer extends SimpleBasePlayer {
state.setSurfaceSize(player.getSurfaceSize());
state.setTimedMetadata(lastTimedMetadata);
if (player.isCommandAvailable(Player.COMMAND_GET_CURRENT_MEDIA_ITEM)) {
state.setTotalBufferedDurationMs(positionSupplier::getTotalBufferedDurationMs);
state.setTotalBufferedDurationMs(positionSuppliers.totalBufferedPositionSupplier);
}
state.setTrackSelectionParameters(player.getTrackSelectionParameters());
state.setVideoSize(player.getVideoSize());
@ -456,44 +448,29 @@ public class ForwardingSimpleBasePlayer extends SimpleBasePlayer {
* Forwards to the changing position values of the wrapped player until the forwarding is
* deactivated with constant values.
*/
private static final class ForwardingPositionSupplier {
private static final class LivePositionSuppliers {
private final Player player;
public final LivePositionSupplier currentPositionSupplier;
public final LivePositionSupplier bufferedPositionSupplier;
public final LivePositionSupplier contentPositionSupplier;
public final LivePositionSupplier contentBufferedPositionSupplier;
public final LivePositionSupplier totalBufferedPositionSupplier;
private long positionsMs;
private long contentPositionMs;
public ForwardingPositionSupplier(Player player) {
this.player = player;
this.positionsMs = C.TIME_UNSET;
this.contentPositionMs = C.TIME_UNSET;
public LivePositionSuppliers(Player player) {
currentPositionSupplier = new LivePositionSupplier(player::getCurrentPosition);
bufferedPositionSupplier = new LivePositionSupplier(player::getBufferedPosition);
contentPositionSupplier = new LivePositionSupplier(player::getContentPosition);
contentBufferedPositionSupplier =
new LivePositionSupplier(player::getContentBufferedPosition);
totalBufferedPositionSupplier = new LivePositionSupplier(player::getTotalBufferedDuration);
}
public void setConstant(long positionMs, long contentPositionMs) {
this.positionsMs = positionMs;
this.contentPositionMs = contentPositionMs;
}
public long getCurrentPositionMs() {
return positionsMs == C.TIME_UNSET ? player.getCurrentPosition() : positionsMs;
}
public long getBufferedPositionMs() {
return positionsMs == C.TIME_UNSET ? player.getBufferedPosition() : positionsMs;
}
public long getContentPositionMs() {
return contentPositionMs == C.TIME_UNSET ? player.getContentPosition() : contentPositionMs;
}
public long getContentBufferedPositionMs() {
return contentPositionMs == C.TIME_UNSET
? player.getContentBufferedPosition()
: contentPositionMs;
}
public long getTotalBufferedDurationMs() {
return positionsMs == C.TIME_UNSET ? player.getTotalBufferedDuration() : 0;
public void disconnect(long positionMs, long contentPositionMs) {
currentPositionSupplier.disconnect(positionMs);
bufferedPositionSupplier.disconnect(positionMs);
contentPositionSupplier.disconnect(contentPositionMs);
contentBufferedPositionSupplier.disconnect(contentPositionMs);
totalBufferedPositionSupplier.disconnect(/* finalValue= */ 0);
}
}
}

View File

@ -18,123 +18,34 @@ package androidx.media3.common;
import static androidx.media3.common.util.Assertions.checkArgument;
import androidx.media3.common.util.UnstableApi;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
/** Value class specifying information about a decoded video frame. */
@UnstableApi
public class FrameInfo {
/** A builder for {@link FrameInfo} instances. */
public static final class Builder {
private ColorInfo colorInfo;
private int width;
private int height;
private float pixelWidthHeightRatio;
private long offsetToAddUs;
/**
* Creates an instance with default values.
*
* @param colorInfo The {@link ColorInfo}.
* @param width The frame width, in pixels.
* @param height The frame height, in pixels.
*/
public Builder(ColorInfo colorInfo, int width, int height) {
this.colorInfo = colorInfo;
this.width = width;
this.height = height;
pixelWidthHeightRatio = 1;
}
/** Creates an instance with the values of the provided {@link FrameInfo}. */
public Builder(FrameInfo frameInfo) {
colorInfo = frameInfo.colorInfo;
width = frameInfo.width;
height = frameInfo.height;
pixelWidthHeightRatio = frameInfo.pixelWidthHeightRatio;
offsetToAddUs = frameInfo.offsetToAddUs;
}
/** Sets the {@link ColorInfo}. */
@CanIgnoreReturnValue
public Builder setColorInfo(ColorInfo colorInfo) {
this.colorInfo = colorInfo;
return this;
}
/** 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(colorInfo, width, height, pixelWidthHeightRatio, offsetToAddUs);
}
}
/** The {@link ColorInfo} of the frame. */
public final ColorInfo colorInfo;
/** The width of the frame, in pixels. */
public final int width;
/** The height of the frame, in pixels. */
public final int height;
/** The ratio of width over height for each pixel. */
public final float pixelWidthHeightRatio;
/**
* The offset that must be added to the frame presentation timestamp, in microseconds.
* The {@link Format} of the frame.
*
* <p>This offset is not part of the input timestamps. It is added to the frame timestamps before
* processing, and is retained in the output timestamps.
* <p>The {@link Format#colorInfo} must be set, and the {@link Format#width} and {@link
* Format#height} must be greater than 0.
*/
public final Format format;
/** The offset that must be added to the frame presentation timestamp, in microseconds. */
public final long offsetToAddUs;
private FrameInfo(
ColorInfo colorInfo, int width, int height, float pixelWidthHeightRatio, long offsetToAddUs) {
checkArgument(width > 0, "width must be positive, but is: " + width);
checkArgument(height > 0, "height must be positive, but is: " + height);
/**
* Creates an instance.
*
* @param format See {@link #format}.
* @param offsetToAddUs See {@link #offsetToAddUs}.
*/
public FrameInfo(Format format, long offsetToAddUs) {
checkArgument(format.colorInfo != null, "format colorInfo must be set");
checkArgument(format.width > 0, "format width must be positive, but is: " + format.width);
checkArgument(format.height > 0, "format height must be positive, but is: " + format.height);
this.colorInfo = colorInfo;
this.width = width;
this.height = height;
this.pixelWidthHeightRatio = pixelWidthHeightRatio;
this.format = format;
this.offsetToAddUs = offsetToAddUs;
}
}

View File

@ -21,7 +21,7 @@ import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import com.google.common.base.Objects;
import java.util.Objects;
/**
* A rating expressed as "heart" or "no heart". It can be used to indicate whether the content is a
@ -60,7 +60,7 @@ public final class HeartRating extends Rating {
@Override
public int hashCode() {
return Objects.hashCode(rated, isHeart);
return Objects.hash(rated, isHeart);
}
@Override

View File

@ -21,6 +21,7 @@ import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import java.util.Objects;
/** A label for a {@link Format}. */
@UnstableApi
@ -55,7 +56,7 @@ public class Label {
return false;
}
Label label = (Label) o;
return Util.areEqual(language, label.language) && Util.areEqual(value, label.value);
return Objects.equals(language, label.language) && Objects.equals(value, label.value);
}
@Override

View File

@ -39,6 +39,7 @@ import java.util.Arrays;
import java.util.Collections;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.UUID;
/** Representation of a media item. */
@ -915,8 +916,8 @@ public final class MediaItem {
DrmConfiguration other = (DrmConfiguration) obj;
return scheme.equals(other.scheme)
&& Util.areEqual(licenseUri, other.licenseUri)
&& Util.areEqual(licenseRequestHeaders, other.licenseRequestHeaders)
&& Objects.equals(licenseUri, other.licenseUri)
&& Objects.equals(licenseRequestHeaders, other.licenseRequestHeaders)
&& multiSession == other.multiSession
&& forceDefaultLicenseUri == other.forceDefaultLicenseUri
&& playClearContentWithoutKey == other.playClearContentWithoutKey
@ -1090,7 +1091,7 @@ public final class MediaItem {
}
AdsConfiguration other = (AdsConfiguration) obj;
return adTagUri.equals(other.adTagUri) && Util.areEqual(adsId, other.adsId);
return adTagUri.equals(other.adTagUri) && Objects.equals(adsId, other.adsId);
}
@Override
@ -1209,14 +1210,14 @@ public final class MediaItem {
LocalConfiguration other = (LocalConfiguration) obj;
return uri.equals(other.uri)
&& Util.areEqual(mimeType, other.mimeType)
&& Util.areEqual(drmConfiguration, other.drmConfiguration)
&& Util.areEqual(adsConfiguration, other.adsConfiguration)
&& Objects.equals(mimeType, other.mimeType)
&& Objects.equals(drmConfiguration, other.drmConfiguration)
&& Objects.equals(adsConfiguration, other.adsConfiguration)
&& streamKeys.equals(other.streamKeys)
&& Util.areEqual(customCacheKey, other.customCacheKey)
&& Objects.equals(customCacheKey, other.customCacheKey)
&& subtitleConfigurations.equals(other.subtitleConfigurations)
&& Util.areEqual(tag, other.tag)
&& Util.areEqual(imageDurationMs, other.imageDurationMs);
&& Objects.equals(tag, other.tag)
&& imageDurationMs == other.imageDurationMs;
}
@Override
@ -1714,12 +1715,12 @@ public final class MediaItem {
SubtitleConfiguration other = (SubtitleConfiguration) obj;
return uri.equals(other.uri)
&& Util.areEqual(mimeType, other.mimeType)
&& Util.areEqual(language, other.language)
&& Objects.equals(mimeType, other.mimeType)
&& Objects.equals(language, other.language)
&& selectionFlags == other.selectionFlags
&& roleFlags == other.roleFlags
&& Util.areEqual(label, other.label)
&& Util.areEqual(id, other.id);
&& Objects.equals(label, other.label)
&& Objects.equals(id, other.id);
}
@Override
@ -2216,8 +2217,8 @@ public final class MediaItem {
return false;
}
RequestMetadata that = (RequestMetadata) o;
return Util.areEqual(mediaUri, that.mediaUri)
&& Util.areEqual(searchQuery, that.searchQuery)
return Objects.equals(mediaUri, that.mediaUri)
&& Objects.equals(searchQuery, that.searchQuery)
&& ((extras == null) == (that.extras == null));
}
@ -2337,12 +2338,12 @@ public final class MediaItem {
MediaItem other = (MediaItem) obj;
return Util.areEqual(mediaId, other.mediaId)
return Objects.equals(mediaId, other.mediaId)
&& clippingConfiguration.equals(other.clippingConfiguration)
&& Util.areEqual(localConfiguration, other.localConfiguration)
&& Util.areEqual(liveConfiguration, other.liveConfiguration)
&& Util.areEqual(mediaMetadata, other.mediaMetadata)
&& Util.areEqual(requestMetadata, other.requestMetadata);
&& Objects.equals(localConfiguration, other.localConfiguration)
&& Objects.equals(liveConfiguration, other.liveConfiguration)
&& Objects.equals(mediaMetadata, other.mediaMetadata)
&& Objects.equals(requestMetadata, other.requestMetadata);
}
@Override

View File

@ -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.0-beta01". */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
public static final String VERSION = "1.5.1";
public static final String VERSION = "1.6.0";
/** The version of the library expressed as {@code TAG + "/" + VERSION}. */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
public static final String VERSION_SLASHY = "AndroidXMedia3/1.5.1";
public static final String VERSION_SLASHY = "AndroidXMedia3/1.6.0";
/**
* 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).
*/
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION) or vice versa.
public static final int VERSION_INT = 1_005_001_3_00;
public static final int VERSION_INT = 1_006_000_3_00;
/** Whether the library was compiled with {@link Assertions} checks enabled. */
public static final boolean ASSERTIONS_ENABLED = true;

View File

@ -29,7 +29,6 @@ import androidx.annotation.IntRange;
import androidx.annotation.Nullable;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.lang.annotation.Documented;
@ -39,6 +38,7 @@ import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* Metadata of a {@link MediaItem}, playlist, or a combination of multiple sources of {@link
@ -194,7 +194,6 @@ public final class MediaMetadata {
*
* @throws IllegalArgumentException if the duration is negative.
*/
@UnstableApi
@CanIgnoreReturnValue
public Builder setDurationMs(@Nullable Long durationMs) {
checkArgument(durationMs == null || durationMs >= 0);
@ -250,8 +249,8 @@ public final class MediaMetadata {
@CanIgnoreReturnValue
public Builder maybeSetArtworkData(byte[] artworkData, @PictureType int artworkDataType) {
if (this.artworkData == null
|| Util.areEqual(artworkDataType, PICTURE_TYPE_FRONT_COVER)
|| !Util.areEqual(this.artworkDataType, PICTURE_TYPE_FRONT_COVER)) {
|| artworkDataType == PICTURE_TYPE_FRONT_COVER
|| !Objects.equals(this.artworkDataType, PICTURE_TYPE_FRONT_COVER)) {
this.artworkData = artworkData.clone();
this.artworkDataType = artworkDataType;
}
@ -1029,7 +1028,7 @@ public final class MediaMetadata {
* informational purpose only. For retrieving the duration of the media item currently being
* played, use {@link Player#getDuration()} instead.
*/
@UnstableApi @Nullable public final Long durationMs;
@Nullable public final Long durationMs;
/** Optional user {@link Rating}. */
@Nullable public final Rating userRating;
@ -1222,47 +1221,47 @@ public final class MediaMetadata {
return false;
}
MediaMetadata that = (MediaMetadata) obj;
return Util.areEqual(title, that.title)
&& Util.areEqual(artist, that.artist)
&& Util.areEqual(albumTitle, that.albumTitle)
&& Util.areEqual(albumArtist, that.albumArtist)
&& Util.areEqual(displayTitle, that.displayTitle)
&& Util.areEqual(subtitle, that.subtitle)
&& Util.areEqual(description, that.description)
&& Util.areEqual(durationMs, that.durationMs)
&& Util.areEqual(userRating, that.userRating)
&& Util.areEqual(overallRating, that.overallRating)
return Objects.equals(title, that.title)
&& Objects.equals(artist, that.artist)
&& Objects.equals(albumTitle, that.albumTitle)
&& Objects.equals(albumArtist, that.albumArtist)
&& Objects.equals(displayTitle, that.displayTitle)
&& Objects.equals(subtitle, that.subtitle)
&& Objects.equals(description, that.description)
&& Objects.equals(durationMs, that.durationMs)
&& Objects.equals(userRating, that.userRating)
&& Objects.equals(overallRating, that.overallRating)
&& Arrays.equals(artworkData, that.artworkData)
&& Util.areEqual(artworkDataType, that.artworkDataType)
&& Util.areEqual(artworkUri, that.artworkUri)
&& Util.areEqual(trackNumber, that.trackNumber)
&& Util.areEqual(totalTrackCount, that.totalTrackCount)
&& Util.areEqual(folderType, that.folderType)
&& Util.areEqual(isBrowsable, that.isBrowsable)
&& Util.areEqual(isPlayable, that.isPlayable)
&& Util.areEqual(recordingYear, that.recordingYear)
&& Util.areEqual(recordingMonth, that.recordingMonth)
&& Util.areEqual(recordingDay, that.recordingDay)
&& Util.areEqual(releaseYear, that.releaseYear)
&& Util.areEqual(releaseMonth, that.releaseMonth)
&& Util.areEqual(releaseDay, that.releaseDay)
&& Util.areEqual(writer, that.writer)
&& Util.areEqual(composer, that.composer)
&& Util.areEqual(conductor, that.conductor)
&& Util.areEqual(discNumber, that.discNumber)
&& Util.areEqual(totalDiscCount, that.totalDiscCount)
&& Util.areEqual(genre, that.genre)
&& Util.areEqual(compilation, that.compilation)
&& Util.areEqual(station, that.station)
&& Util.areEqual(mediaType, that.mediaType)
&& Util.areEqual(supportedCommands, that.supportedCommands)
&& Objects.equals(artworkDataType, that.artworkDataType)
&& Objects.equals(artworkUri, that.artworkUri)
&& Objects.equals(trackNumber, that.trackNumber)
&& Objects.equals(totalTrackCount, that.totalTrackCount)
&& Objects.equals(folderType, that.folderType)
&& Objects.equals(isBrowsable, that.isBrowsable)
&& Objects.equals(isPlayable, that.isPlayable)
&& Objects.equals(recordingYear, that.recordingYear)
&& Objects.equals(recordingMonth, that.recordingMonth)
&& Objects.equals(recordingDay, that.recordingDay)
&& Objects.equals(releaseYear, that.releaseYear)
&& Objects.equals(releaseMonth, that.releaseMonth)
&& Objects.equals(releaseDay, that.releaseDay)
&& Objects.equals(writer, that.writer)
&& Objects.equals(composer, that.composer)
&& Objects.equals(conductor, that.conductor)
&& Objects.equals(discNumber, that.discNumber)
&& Objects.equals(totalDiscCount, that.totalDiscCount)
&& Objects.equals(genre, that.genre)
&& Objects.equals(compilation, that.compilation)
&& Objects.equals(station, that.station)
&& Objects.equals(mediaType, that.mediaType)
&& Objects.equals(supportedCommands, that.supportedCommands)
&& ((extras == null) == (that.extras == null));
}
@SuppressWarnings("deprecation") // Hashing deprecated fields.
@Override
public int hashCode() {
return Objects.hashCode(
return Objects.hash(
title,
artist,
albumTitle,

View File

@ -15,8 +15,6 @@
*/
package androidx.media3.common;
import android.os.Parcel;
import android.os.Parcelable;
import androidx.annotation.Nullable;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
@ -26,10 +24,10 @@ import java.util.List;
/** A collection of metadata entries. */
@UnstableApi
public final class Metadata implements Parcelable {
public final class Metadata {
/** A metadata entry. */
public interface Entry extends Parcelable {
public interface Entry {
/**
* Returns the {@link Format} that can be used to decode the wrapped metadata in {@link
@ -100,14 +98,6 @@ public final class Metadata implements Parcelable {
this(presentationTimeUs, entries.toArray(new Entry[0]));
}
/* package */ Metadata(Parcel in) {
entries = new Metadata.Entry[in.readInt()];
for (int i = 0; i < entries.length; i++) {
entries[i] = in.readParcelable(Entry.class.getClassLoader());
}
presentationTimeUs = in.readLong();
}
/** Returns the number of metadata entries. */
public int length() {
return entries.length;
@ -190,33 +180,4 @@ public final class Metadata implements Parcelable {
+ Arrays.toString(entries)
+ (presentationTimeUs == C.TIME_UNSET ? "" : ", presentationTimeUs=" + presentationTimeUs);
}
// Parcelable implementation.
@Override
public int describeContents() {
return 0;
}
@Override
public void writeToParcel(Parcel dest, int flags) {
dest.writeInt(entries.length);
for (Entry entry : entries) {
dest.writeParcelable(entry, 0);
}
dest.writeLong(presentationTimeUs);
}
public static final Parcelable.Creator<Metadata> CREATOR =
new Parcelable.Creator<Metadata>() {
@Override
public Metadata createFromParcel(Parcel in) {
return new Metadata(in);
}
@Override
public Metadata[] newArray(int size) {
return new Metadata[size];
}
};
}

View File

@ -44,6 +44,7 @@ public final class MimeTypes {
public static final String VIDEO_WEBM = BASE_TYPE_VIDEO + "/webm";
public static final String VIDEO_H263 = BASE_TYPE_VIDEO + "/3gpp";
public static final String VIDEO_H264 = BASE_TYPE_VIDEO + "/avc";
@UnstableApi public static final String VIDEO_APV = BASE_TYPE_VIDEO + "/apv";
public static final String VIDEO_H265 = BASE_TYPE_VIDEO + "/hevc";
@UnstableApi public static final String VIDEO_VP8 = BASE_TYPE_VIDEO + "/x-vnd.on2.vp8";
@UnstableApi public static final String VIDEO_VP9 = BASE_TYPE_VIDEO + "/x-vnd.on2.vp9";
@ -579,6 +580,36 @@ public final class MimeTypes {
}
}
/**
* Returns whether the given {@code codecs} and {@code supplementalCodecs} correspond to a valid
* Dolby Vision codec.
*
* @param codecs An RFC 6381 codecs string for the base codec. may be null.
* @param supplementalCodecs An optional RFC 6381 codecs string for supplemental codecs.
* @return Whether the given {@code codecs} and {@code supplementalCodecs} correspond to a valid
* Dolby Vision codec.
*/
@UnstableApi
public static boolean isDolbyVisionCodec(
@Nullable String codecs, @Nullable String supplementalCodecs) {
if (codecs == null) {
return false;
}
if (codecs.startsWith("dvhe") || codecs.startsWith("dvh1")) {
// profile 5
return true;
}
if (supplementalCodecs == null) {
return false;
}
// profiles 8, 9 and 10
return (supplementalCodecs.startsWith("dvhe") && codecs.startsWith("hev1"))
|| (supplementalCodecs.startsWith("dvh1") && codecs.startsWith("hvc1"))
|| (supplementalCodecs.startsWith("dvav") && codecs.startsWith("avc3"))
|| (supplementalCodecs.startsWith("dva1") && codecs.startsWith("avc1"))
|| (supplementalCodecs.startsWith("dav1") && codecs.startsWith("av01"));
}
/**
* Returns the {@link C.TrackType track type} constant corresponding to a specified MIME type,
* which may be {@link C#TRACK_TYPE_UNKNOWN} if it could not be determined.

View File

@ -0,0 +1,133 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.common;
import android.util.Pair;
import androidx.media3.common.util.UnstableApi;
/**
* Provides information of how an input texture (for example, a {@code TextureOverlay} or in {@code
* VideoCompositor}) is presented.
*/
@UnstableApi
public interface OverlaySettings {
/** The default alpha scale value of the overlay. */
float DEFAULT_ALPHA_SCALE = 1f;
/** The default coordinates for the anchor point of the overlay within the background frame. */
Pair<Float, Float> DEFAULT_BACKGROUND_FRAME_ANCHOR = Pair.create(0f, 0f);
/** The default coordinates for the anchor point of the overlay frame. */
Pair<Float, Float> DEFAULT_OVERLAY_FRAME_ANCHOR = Pair.create(0f, 0f);
/** The default scaling of the overlay. */
Pair<Float, Float> DEFAULT_SCALE = Pair.create(1f, 1f);
/** The default rotation of the overlay, counter-clockwise. */
float DEFAULT_ROTATION_DEGREES = 0f;
/** The default luminance multiplier of an SDR overlay when overlaid on a HDR frame. */
float DEFAULT_HDR_LUMINANCE_MULTIPLIER = 1f;
/**
* Returns the alpha scale value of the overlay, altering its translucency.
*
* <p>An {@code alphaScale} value of {@code 1} means no change is applied. A value below {@code 1}
* increases translucency, and a value above {@code 1} reduces translucency.
*
* <p>The default value is {@link #DEFAULT_ALPHA_SCALE}.
*/
default float getAlphaScale() {
return DEFAULT_ALPHA_SCALE;
}
/**
* Returns the coordinates for the anchor point of the overlay within the background frame.
*
* <p>The coordinates are specified in Normalised Device Coordinates (NDCs) relative to the
* background frame. The ranges for x and y are from {@code -1} to {@code 1}. The default value is
* {@code (0,0)}, the center of the background frame.
*
* <p>The overlay's {@linkplain #getOverlayFrameAnchor anchor point} will be positioned at the
* anchor point returned from this method. For example, a value of {@code (1,1)} will move the
* {@linkplain #getOverlayFrameAnchor overlay's anchor} to the top right corner. That is, if the
* overlay's anchor is at {@code (1,1)} (the top right corner), the overlay's top right corner
* will be aligned with that of the background frame; whereas if the overlay's anchor is at {@code
* (0,0)} (the center), the overlay's center will be positioned at the top right corner of the
* background frame.
*
* <p>The default value is {@link #DEFAULT_BACKGROUND_FRAME_ANCHOR}.
*/
default Pair<Float, Float> getBackgroundFrameAnchor() {
return DEFAULT_BACKGROUND_FRAME_ANCHOR;
}
/**
* Returns the coordinates for the anchor point within the overlay.
*
* <p>The anchor point is the point inside the overlay that is placed on the {@linkplain
* #getBackgroundFrameAnchor background frame anchor}
*
* <p>The coordinates are specified in Normalised Device Coordinates (NDCs) relative to the
* overlay. The ranges for x and y are from {@code -1} to {@code 1}. The default value is {@code
* (0,0)}, the center of the overlay.
*
* <p>See {@link #getBackgroundFrameAnchor} for examples of how to position an overlay.
*
* <p>The default value is {@link #DEFAULT_OVERLAY_FRAME_ANCHOR}.
*/
default Pair<Float, Float> getOverlayFrameAnchor() {
return DEFAULT_OVERLAY_FRAME_ANCHOR;
}
/**
* Returns the scaling of the overlay.
*
* <p>The default value is {@link #DEFAULT_SCALE}.
*/
default Pair<Float, Float> getScale() {
return DEFAULT_SCALE;
}
/**
* Returns the rotation of the overlay, counter-clockwise.
*
* <p>The overlay is rotated at the center of its frame.
*
* <p>The default value is {@link #DEFAULT_ROTATION_DEGREES}.
*/
default float getRotationDegrees() {
return DEFAULT_ROTATION_DEGREES;
}
/**
* Returns the luminance multiplier of an SDR overlay when overlaid on a HDR frame.
*
* <p>Scales the luminance of the overlay to adjust the output brightness of the overlay on the
* frame. The default value is 1, which scales the overlay colors into the standard HDR luminance
* within the processing pipeline. Use 0.5 to scale the luminance of the overlay to SDR range, so
* that no extra luminance is added.
*
* <p>Currently only supported on text overlays
*
* <p>The default value is {@link #DEFAULT_HDR_LUMINANCE_MULTIPLIER}.
*/
default float getHdrLuminanceMultiplier() {
return DEFAULT_HDR_LUMINANCE_MULTIPLIER;
}
}

View File

@ -109,11 +109,11 @@ public class ParserException extends IOException {
this.dataType = dataType;
}
@Nullable
@Override
public String getMessage() {
return super.getMessage()
+ " {contentIsMalformed="
String superMessage = super.getMessage();
return (superMessage != null ? superMessage + " " : "")
+ "{contentIsMalformed="
+ contentIsMalformed
+ ", dataType="
+ dataType

View File

@ -22,7 +22,7 @@ import androidx.annotation.FloatRange;
import androidx.annotation.Nullable;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import com.google.common.base.Objects;
import java.util.Objects;
/** A rating expressed as a percentage. */
public final class PercentageRating extends Rating {
@ -59,7 +59,7 @@ public final class PercentageRating extends Rating {
@Override
public int hashCode() {
return Objects.hashCode(percent);
return Objects.hash(percent);
}
@Override

View File

@ -36,6 +36,7 @@ import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Objects;
/** Thrown when a non locally recoverable playback failure occurs. */
public class PlaybackException extends Exception {
@ -91,6 +92,7 @@ public class PlaybackException extends Exception {
ERROR_CODE_DECODING_FAILED,
ERROR_CODE_DECODING_FORMAT_EXCEEDS_CAPABILITIES,
ERROR_CODE_DECODING_FORMAT_UNSUPPORTED,
ERROR_CODE_DECODING_RESOURCES_RECLAIMED,
ERROR_CODE_AUDIO_TRACK_INIT_FAILED,
ERROR_CODE_AUDIO_TRACK_WRITE_FAILED,
ERROR_CODE_AUDIO_TRACK_OFFLOAD_INIT_FAILED,
@ -272,9 +274,8 @@ public class PlaybackException extends Exception {
/** Caused by trying to decode content whose format is not supported. */
public static final int ERROR_CODE_DECODING_FORMAT_UNSUPPORTED = 4005;
// TODO: b/322943860 - Stabilize error code and add to IntDef
/** Caused by higher priority task reclaiming resources needed for decoding. */
@UnstableApi public static final int ERROR_CODE_DECODING_RESOURCES_RECLAIMED = 4006;
public static final int ERROR_CODE_DECODING_RESOURCES_RECLAIMED = 4006;
// AudioTrack errors (5xxx).
@ -553,17 +554,17 @@ public class PlaybackException extends Exception {
@Nullable Throwable thisCause = getCause();
@Nullable Throwable thatCause = other.getCause();
if (thisCause != null && thatCause != null) {
if (!Util.areEqual(thisCause.getMessage(), thatCause.getMessage())) {
if (!Objects.equals(thisCause.getMessage(), thatCause.getMessage())) {
return false;
}
if (!Util.areEqual(thisCause.getClass(), thatCause.getClass())) {
if (!Objects.equals(thisCause.getClass(), thatCause.getClass())) {
return false;
}
} else if (thisCause != null || thatCause != null) {
return false;
}
return errorCode == other.errorCode
&& Util.areEqual(getMessage(), other.getMessage())
&& Objects.equals(getMessage(), other.getMessage())
&& timestampMs == other.timestampMs;
}

View File

@ -37,7 +37,6 @@ import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.Size;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import com.google.common.base.Objects;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
@ -45,6 +44,7 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* A media player interface defining high-level functionality, such as the ability to play, pause,
@ -352,13 +352,13 @@ public interface Player {
}
PositionInfo that = (PositionInfo) o;
return equalsForBundling(that)
&& Objects.equal(windowUid, that.windowUid)
&& Objects.equal(periodUid, that.periodUid);
&& Objects.equals(windowUid, that.windowUid)
&& Objects.equals(periodUid, that.periodUid);
}
@Override
public int hashCode() {
return Objects.hashCode(
return Objects.hash(
windowUid,
mediaItemIndex,
mediaItem,
@ -382,7 +382,7 @@ public interface Player {
&& contentPositionMs == other.contentPositionMs
&& adGroupIndex == other.adGroupIndex
&& adIndexInAdGroup == other.adIndexInAdGroup
&& Objects.equal(mediaItem, other.mediaItem);
&& Objects.equals(mediaItem, other.mediaItem);
}
@VisibleForTesting static final String FIELD_MEDIA_ITEM_INDEX = Util.intToStringMaxRadix(0);
@ -2855,7 +2855,6 @@ public interface Player {
*/
TrackSelectionParameters getTrackSelectionParameters();
// LINT.IfChange(set_track_selection_parameters)
/**
* Sets the parameters constraining the track selection.
*
@ -3384,8 +3383,7 @@ public interface Player {
*
* <p>For devices with {@link DeviceInfo#PLAYBACK_TYPE_LOCAL local playback}, the volume returned
* by this method varies according to the current {@link C.StreamType stream type}. The stream
* type is determined by {@link AudioAttributes#usage} which can be converted to stream type with
* {@link Util#getStreamTypeForAudioUsage(int)}.
* type is determined by {@link AudioAttributes#getStreamType()}.
*
* <p>For devices with {@link DeviceInfo#PLAYBACK_TYPE_REMOTE remote playback}, the volume of the
* remote device is returned.
@ -3508,10 +3506,6 @@ public interface Player {
* <p>If tunneling is enabled by the track selector, the specified audio attributes will be
* ignored, but they will take effect if audio is later played without tunneling.
*
* <p>If the device is running a build before platform API version 21, audio attributes cannot be
* set directly on the underlying audio track. In this case, the usage will be mapped onto an
* equivalent stream type using {@link Util#getStreamTypeForAudioUsage(int)}.
*
* <p>If audio focus should be handled, the {@link AudioAttributes#usage} must be {@link
* C#USAGE_MEDIA} or {@link C#USAGE_GAME}. Other usages will throw an {@link
* IllegalArgumentException}.

View File

@ -35,6 +35,7 @@ public interface PreviewingVideoGraph extends VideoGraph {
* @param debugViewProvider A {@link DebugViewProvider}.
* @param listener A {@link Listener}.
* @param listenerExecutor The {@link Executor} on which the {@code listener} is invoked.
* @param videoCompositorSettings The {@link VideoCompositorSettings}.
* @param compositionEffects A list of {@linkplain Effect effects} to apply to the composition.
* @param initialTimestampOffsetUs The timestamp offset for the first frame, in microseconds.
* @return A new instance.
@ -47,9 +48,16 @@ public interface PreviewingVideoGraph extends VideoGraph {
DebugViewProvider debugViewProvider,
Listener listener,
Executor listenerExecutor,
VideoCompositorSettings videoCompositorSettings,
List<Effect> compositionEffects,
long initialTimestampOffsetUs)
throws VideoFrameProcessingException;
/**
* Returns whether the {@link VideoGraph} implementation supports {@linkplain #registerInput
* registering} multiple inputs.
*/
boolean supportsMultipleInputs();
}
/**

View File

@ -15,7 +15,6 @@
*/
package androidx.media3.common;
import static androidx.annotation.VisibleForTesting.PROTECTED;
import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Util.castNonNull;
@ -35,7 +34,6 @@ import android.view.TextureView;
import androidx.annotation.FloatRange;
import androidx.annotation.IntRange;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.Clock;
import androidx.media3.common.util.HandlerWrapper;
@ -98,7 +96,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
public abstract class SimpleBasePlayer extends BasePlayer {
/** An immutable state description of the player. */
protected static final class State {
public static final class State {
/** A builder for {@link State} objects. */
public static final class Builder {
@ -161,7 +159,7 @@ public abstract class SimpleBasePlayer extends BasePlayer {
seekForwardIncrementMs = C.DEFAULT_SEEK_FORWARD_INCREMENT_MS;
maxSeekToPreviousPositionMs = C.DEFAULT_MAX_SEEK_TO_PREVIOUS_POSITION_MS;
playbackParameters = PlaybackParameters.DEFAULT;
trackSelectionParameters = TrackSelectionParameters.DEFAULT_WITHOUT_CONTEXT;
trackSelectionParameters = TrackSelectionParameters.DEFAULT;
audioAttributes = AudioAttributes.DEFAULT;
volume = 1f;
videoSize = VideoSize.UNKNOWN;
@ -222,7 +220,7 @@ public abstract class SimpleBasePlayer extends BasePlayer {
this.playlist = ((PlaylistTimeline) state.timeline).playlist;
} else {
this.currentTracks = state.currentTracks;
this.currentMetadata = state.currentMetadata;
this.currentMetadata = state.usesDerivedMediaMetadata ? null : state.currentMetadata;
}
this.playlistMetadata = state.playlistMetadata;
this.currentMediaItemIndex = state.currentMediaItemIndex;
@ -958,9 +956,12 @@ public abstract class SimpleBasePlayer extends BasePlayer {
*/
public final long discontinuityPositionMs;
private final boolean usesDerivedMediaMetadata;
private State(Builder builder) {
Tracks currentTracks = builder.currentTracks;
MediaMetadata currentMetadata = builder.currentMetadata;
boolean usesDerivedMediaMetadata = false;
if (builder.timeline.isEmpty()) {
checkArgument(
builder.playbackState == Player.STATE_IDLE
@ -1016,6 +1017,7 @@ public abstract class SimpleBasePlayer extends BasePlayer {
getCombinedMediaMetadata(
builder.timeline.getWindow(mediaItemIndex, new Timeline.Window()).mediaItem,
checkNotNull(currentTracks));
usesDerivedMediaMetadata = true;
}
}
if (builder.playerError != null) {
@ -1092,6 +1094,7 @@ public abstract class SimpleBasePlayer extends BasePlayer {
this.hasPositionDiscontinuity = builder.hasPositionDiscontinuity;
this.positionDiscontinuityReason = builder.positionDiscontinuityReason;
this.discontinuityPositionMs = builder.discontinuityPositionMs;
this.usesDerivedMediaMetadata = usesDerivedMediaMetadata;
}
/** Returns a {@link Builder} pre-populated with the current state values. */
@ -1790,9 +1793,9 @@ public abstract class SimpleBasePlayer extends BasePlayer {
return this.uid.equals(mediaItemData.uid)
&& this.tracks.equals(mediaItemData.tracks)
&& this.mediaItem.equals(mediaItemData.mediaItem)
&& Util.areEqual(this.mediaMetadata, mediaItemData.mediaMetadata)
&& Util.areEqual(this.manifest, mediaItemData.manifest)
&& Util.areEqual(this.liveConfiguration, mediaItemData.liveConfiguration)
&& Objects.equals(this.mediaMetadata, mediaItemData.mediaMetadata)
&& Objects.equals(this.manifest, mediaItemData.manifest)
&& Objects.equals(this.liveConfiguration, mediaItemData.liveConfiguration)
&& this.presentationStartTimeMs == mediaItemData.presentationStartTimeMs
&& this.windowStartTimeMs == mediaItemData.windowStartTimeMs
&& this.elapsedRealtimeEpochOffsetMs == mediaItemData.elapsedRealtimeEpochOffsetMs
@ -2069,7 +2072,21 @@ public abstract class SimpleBasePlayer extends BasePlayer {
}
}
/** A supplier for a position. */
/**
* A supplier for a position.
*
* <p>Convenience methods and classes for creating position suppliers:
*
* <ul>
* <li>Use {@link #getConstant} for constant or non-moving positions.
* <li>Use {@link #getExtrapolating} for positions advancing with the system clock from a
* provided start time.
* <li>Use {@link LivePositionSupplier} for positions that can be directly obtained from a live
* system. Note that these suppliers should be {@linkplain LivePositionSupplier#disconnect
* disconnected} from the live source as soon as the position is no longer valid, for
* example after a position discontinuity.
* </ul>
*/
protected interface PositionSupplier {
/** An instance returning a constant position of zero. */
@ -2102,6 +2119,48 @@ public abstract class SimpleBasePlayer extends BasePlayer {
long get();
}
/**
* A {@link PositionSupplier} connected to a live provider that returns a new value on each
* invocation until it is {@linkplain #disconnect disconnected} from the live source.
*
* <p>The recommended usage of this class is to create a new instance connected to the live source
* and keep returning this instance as long as the position source is still valid. As soon as the
* position source becomes invalid, for example when handling a position discontinuity, call
* {@link #disconnect} with the final position that will be returned for all future invocations.
*/
protected static final class LivePositionSupplier implements PositionSupplier {
private final PositionSupplier livePosition;
private long finalValue;
/**
* Creates the live position supplier.
*
* @param livePosition The function returning the live position.
*/
public LivePositionSupplier(PositionSupplier livePosition) {
this.livePosition = livePosition;
this.finalValue = C.TIME_UNSET;
}
/**
* Disconnects the position supplier from the live source.
*
* <p>All future invocations of {@link #get()} will return the provided final position.
*
* @param finalValue The final position value.
*/
public void disconnect(long finalValue) {
this.finalValue = finalValue;
}
@Override
public long get() {
return finalValue != C.TIME_UNSET ? finalValue : livePosition.get();
}
}
/**
* Position difference threshold below which we do not automatically report a position
* discontinuity, in milliseconds.
@ -2450,8 +2509,7 @@ public abstract class SimpleBasePlayer extends BasePlayer {
}
@Override
@VisibleForTesting(otherwise = PROTECTED)
public final void seekTo(
protected final void seekTo(
int mediaItemIndex,
long positionMs,
@Player.Command int seekCommand,
@ -3402,7 +3460,7 @@ public abstract class SimpleBasePlayer extends BasePlayer {
* index is in the range {@code fromIndex} &lt; {@code toIndex} &lt;= {@link
* #getMediaItemCount()}.
* @param newIndex The new index of the first moved item. The index is in the range {@code 0}
* &lt;= {@code newIndex} &lt; {@link #getMediaItemCount() - (toIndex - fromIndex)}.
* &lt;= {@code newIndex} &lt;= {@link #getMediaItemCount() - (toIndex - fromIndex)}.
* @return A {@link ListenableFuture} indicating the completion of all immediate {@link State}
* changes caused by this call.
*/
@ -3417,9 +3475,9 @@ public abstract class SimpleBasePlayer extends BasePlayer {
* <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 &lt;=
* {@code fromIndex} &lt; {@link #getMediaItemCount()}.
* {@code fromIndex} &lt;= {@link #getMediaItemCount()}.
* @param toIndex The index of the first item not to be replaced (exclusive). The index is in the
* range {@code fromIndex} &lt; {@code toIndex} &lt;= {@link #getMediaItemCount()}.
* range {@code fromIndex} &lt;= {@code toIndex} &lt;= {@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.
@ -3428,6 +3486,9 @@ public abstract class SimpleBasePlayer extends BasePlayer {
protected ListenableFuture<?> handleReplaceMediaItems(
int fromIndex, int toIndex, List<MediaItem> mediaItems) {
ListenableFuture<?> addFuture = handleAddMediaItems(toIndex, mediaItems);
if (fromIndex == toIndex) {
return addFuture;
}
ListenableFuture<?> removeFuture = handleRemoveMediaItems(fromIndex, toIndex);
return Util.transformFutureAsync(addFuture, unused -> removeFuture);
}
@ -3561,7 +3622,7 @@ public abstract class SimpleBasePlayer extends BasePlayer {
Player.EVENT_MEDIA_ITEM_TRANSITION,
listener -> listener.onMediaItemTransition(mediaItem, mediaItemTransitionReason));
}
if (!Util.areEqual(previousState.playerError, newState.playerError)) {
if (!Objects.equals(previousState.playerError, newState.playerError)) {
listeners.queueEvent(
Player.EVENT_PLAYER_ERROR,
listener -> listener.onPlayerErrorChanged(newState.playerError));
@ -4008,14 +4069,10 @@ public abstract class SimpleBasePlayer extends BasePlayer {
}
// Only mark changes within the current item as a transition if we are repeating automatically
// or via a seek to next/previous.
if (positionDiscontinuityReason == DISCONTINUITY_REASON_AUTO_TRANSITION) {
if ((getContentPositionMsInternal(previousState, window)
> getContentPositionMsInternal(newState, window))
|| (newState.hasPositionDiscontinuity
&& newState.discontinuityPositionMs == C.TIME_UNSET
&& isRepeatingCurrentItem)) {
return MEDIA_ITEM_TRANSITION_REASON_REPEAT;
}
if (positionDiscontinuityReason == DISCONTINUITY_REASON_AUTO_TRANSITION
&& getContentPositionMsInternal(previousState, window)
> getContentPositionMsInternal(newState, window)) {
return MEDIA_ITEM_TRANSITION_REASON_REPEAT;
}
if (positionDiscontinuityReason == DISCONTINUITY_REASON_SEEK && isRepeatingCurrentItem) {
return MEDIA_ITEM_TRANSITION_REASON_SEEK;

View File

@ -23,7 +23,7 @@ import androidx.annotation.IntRange;
import androidx.annotation.Nullable;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import com.google.common.base.Objects;
import java.util.Objects;
/** A rating expressed as a fractional number of stars. */
public final class StarRating extends Rating {
@ -84,7 +84,7 @@ public final class StarRating extends Rating {
@Override
public int hashCode() {
return Objects.hashCode(maxStars, starRating);
return Objects.hash(maxStars, starRating);
}
@Override

View File

@ -21,7 +21,7 @@ import android.os.Bundle;
import androidx.annotation.Nullable;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import com.google.common.base.Objects;
import java.util.Objects;
/** A rating expressed as "thumbs up" or "thumbs down". */
public final class ThumbRating extends Rating {
@ -57,7 +57,7 @@ public final class ThumbRating extends Rating {
@Override
public int hashCode() {
return Objects.hashCode(rated, isThumbsUp);
return Objects.hash(rated, isThumbsUp);
}
@Override

View File

@ -36,6 +36,7 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.InlineMe;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import org.checkerframework.checker.nullness.qual.NonNull;
/**
@ -371,10 +372,10 @@ public abstract class Timeline {
return false;
}
Window that = (Window) obj;
return Util.areEqual(uid, that.uid)
&& Util.areEqual(mediaItem, that.mediaItem)
&& Util.areEqual(manifest, that.manifest)
&& Util.areEqual(liveConfiguration, that.liveConfiguration)
return Objects.equals(uid, that.uid)
&& Objects.equals(mediaItem, that.mediaItem)
&& Objects.equals(manifest, that.manifest)
&& Objects.equals(liveConfiguration, that.liveConfiguration)
&& presentationStartTimeMs == that.presentationStartTimeMs
&& windowStartTimeMs == that.windowStartTimeMs
&& elapsedRealtimeEpochOffsetMs == that.elapsedRealtimeEpochOffsetMs
@ -871,13 +872,13 @@ public abstract class Timeline {
return false;
}
Period that = (Period) obj;
return Util.areEqual(id, that.id)
&& Util.areEqual(uid, that.uid)
return Objects.equals(id, that.id)
&& Objects.equals(uid, that.uid)
&& windowIndex == that.windowIndex
&& durationUs == that.durationUs
&& positionInWindowUs == that.positionInWindowUs
&& isPlaceholder == that.isPlaceholder
&& Util.areEqual(adPlaybackState, that.adPlaybackState);
&& Objects.equals(adPlaybackState, that.adPlaybackState);
}
@Override

View File

@ -161,6 +161,11 @@ public final class TrackGroup {
return id.equals(other.id) && Arrays.equals(formats, other.formats);
}
@Override
public String toString() {
return id + ": " + Arrays.toString(formats);
}
private static final String FIELD_FORMATS = Util.intToStringMaxRadix(0);
private static final String FIELD_ID = Util.intToStringMaxRadix(1);
@ -169,7 +174,7 @@ public final class TrackGroup {
Bundle bundle = new Bundle();
ArrayList<Bundle> arrayList = new ArrayList<>(formats.length);
for (Format format : formats) {
arrayList.add(format.toBundle(/* excludeMetadata= */ true));
arrayList.add(format.toBundle());
}
bundle.putParcelableArrayList(FIELD_FORMATS, arrayList);
bundle.putString(FIELD_ID, id);

View File

@ -22,9 +22,7 @@ import static java.lang.annotation.ElementType.TYPE_USE;
import static java.lang.annotation.RetentionPolicy.SOURCE;
import android.content.Context;
import android.graphics.Point;
import android.os.Bundle;
import android.os.Looper;
import android.view.accessibility.CaptioningManager;
import androidx.annotation.CallSuper;
import androidx.annotation.IntDef;
@ -37,6 +35,7 @@ import com.google.common.collect.ImmutableMap;
import com.google.common.collect.ImmutableSet;
import com.google.common.primitives.Ints;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import com.google.errorprone.annotations.InlineMe;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.Target;
@ -44,12 +43,10 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.Iterator;
import java.util.List;
import java.util.Locale;
import java.util.Set;
import org.checkerframework.checker.initialization.qual.UnknownInitialization;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
// LINT.IfChange(javadoc)
/**
* Parameters for controlling track selection.
*
@ -88,8 +85,10 @@ public class TrackSelectionParameters {
private int minVideoBitrate;
private int viewportWidth;
private int viewportHeight;
private boolean isViewportSizeLimitedByPhysicalDisplaySize;
private boolean viewportOrientationMayChange;
private ImmutableList<String> preferredVideoMimeTypes;
private ImmutableList<String> preferredVideoLanguages;
private @C.RoleFlags int preferredVideoRoleFlags;
// Audio
private ImmutableList<String> preferredAudioLanguages;
@ -101,6 +100,7 @@ public class TrackSelectionParameters {
// Text
private ImmutableList<String> preferredTextLanguages;
private @C.RoleFlags int preferredTextRoleFlags;
private boolean usePreferredTextLanguagesAndRoleFlagsFromCaptioningManager;
private @C.SelectionFlags int ignoredTextSelectionFlags;
private boolean selectUndeterminedTextLanguage;
// Image
@ -111,12 +111,7 @@ public class TrackSelectionParameters {
private HashMap<TrackGroup, TrackSelectionOverride> overrides;
private HashSet<@C.TrackType Integer> disabledTrackTypes;
/**
* @deprecated {@link Context} constraints will not be set using this constructor. Use {@link
* #Builder(Context)} instead.
*/
@UnstableApi
@Deprecated
/** Creates a builder with default initial values. */
public Builder() {
// Video
maxVideoWidth = Integer.MAX_VALUE;
@ -125,8 +120,10 @@ public class TrackSelectionParameters {
maxVideoBitrate = Integer.MAX_VALUE;
viewportWidth = Integer.MAX_VALUE;
viewportHeight = Integer.MAX_VALUE;
isViewportSizeLimitedByPhysicalDisplaySize = true;
viewportOrientationMayChange = true;
preferredVideoMimeTypes = ImmutableList.of();
preferredVideoLanguages = ImmutableList.of();
preferredVideoRoleFlags = 0;
// Audio
preferredAudioLanguages = ImmutableList.of();
@ -138,6 +135,7 @@ public class TrackSelectionParameters {
// Text
preferredTextLanguages = ImmutableList.of();
preferredTextRoleFlags = 0;
usePreferredTextLanguagesAndRoleFlagsFromCaptioningManager = true;
ignoredTextSelectionFlags = 0;
selectUndeterminedTextLanguage = false;
// Image
@ -150,15 +148,12 @@ public class TrackSelectionParameters {
}
/**
* Creates a builder with default initial values.
*
* @param context Any context.
* @deprecated Use {@link #Builder()} instead.
*/
@SuppressWarnings({"deprecation", "method.invocation"}) // Methods invoked are setter only.
@Deprecated
@InlineMe(replacement = "this()")
public Builder(Context context) {
this();
setPreferredTextLanguageAndRoleFlagsToCaptioningManagerSettings(context);
setViewportSizeToPhysicalDisplaySize(context, /* viewportOrientationMayChange= */ true);
}
/** Creates a builder with the initial values specified in {@code initialValues}. */
@ -171,44 +166,42 @@ public class TrackSelectionParameters {
@UnstableApi
protected Builder(Bundle bundle) {
// Video
maxVideoWidth = bundle.getInt(FIELD_MAX_VIDEO_WIDTH, DEFAULT_WITHOUT_CONTEXT.maxVideoWidth);
maxVideoHeight =
bundle.getInt(FIELD_MAX_VIDEO_HEIGHT, DEFAULT_WITHOUT_CONTEXT.maxVideoHeight);
maxVideoFrameRate =
bundle.getInt(FIELD_MAX_VIDEO_FRAMERATE, DEFAULT_WITHOUT_CONTEXT.maxVideoFrameRate);
maxVideoBitrate =
bundle.getInt(FIELD_MAX_VIDEO_BITRATE, DEFAULT_WITHOUT_CONTEXT.maxVideoBitrate);
minVideoWidth = bundle.getInt(FIELD_MIN_VIDEO_WIDTH, DEFAULT_WITHOUT_CONTEXT.minVideoWidth);
minVideoHeight =
bundle.getInt(FIELD_MIN_VIDEO_HEIGHT, DEFAULT_WITHOUT_CONTEXT.minVideoHeight);
minVideoFrameRate =
bundle.getInt(FIELD_MIN_VIDEO_FRAMERATE, DEFAULT_WITHOUT_CONTEXT.minVideoFrameRate);
minVideoBitrate =
bundle.getInt(FIELD_MIN_VIDEO_BITRATE, DEFAULT_WITHOUT_CONTEXT.minVideoBitrate);
viewportWidth = bundle.getInt(FIELD_VIEWPORT_WIDTH, DEFAULT_WITHOUT_CONTEXT.viewportWidth);
viewportHeight = bundle.getInt(FIELD_VIEWPORT_HEIGHT, DEFAULT_WITHOUT_CONTEXT.viewportHeight);
maxVideoWidth = bundle.getInt(FIELD_MAX_VIDEO_WIDTH, DEFAULT.maxVideoWidth);
maxVideoHeight = bundle.getInt(FIELD_MAX_VIDEO_HEIGHT, DEFAULT.maxVideoHeight);
maxVideoFrameRate = bundle.getInt(FIELD_MAX_VIDEO_FRAMERATE, DEFAULT.maxVideoFrameRate);
maxVideoBitrate = bundle.getInt(FIELD_MAX_VIDEO_BITRATE, DEFAULT.maxVideoBitrate);
minVideoWidth = bundle.getInt(FIELD_MIN_VIDEO_WIDTH, DEFAULT.minVideoWidth);
minVideoHeight = bundle.getInt(FIELD_MIN_VIDEO_HEIGHT, DEFAULT.minVideoHeight);
minVideoFrameRate = bundle.getInt(FIELD_MIN_VIDEO_FRAMERATE, DEFAULT.minVideoFrameRate);
minVideoBitrate = bundle.getInt(FIELD_MIN_VIDEO_BITRATE, DEFAULT.minVideoBitrate);
viewportWidth = bundle.getInt(FIELD_VIEWPORT_WIDTH, DEFAULT.viewportWidth);
viewportHeight = bundle.getInt(FIELD_VIEWPORT_HEIGHT, DEFAULT.viewportHeight);
isViewportSizeLimitedByPhysicalDisplaySize =
viewportWidth == Integer.MAX_VALUE
&& viewportHeight == Integer.MAX_VALUE
&& bundle.getBoolean(
FIELD_IS_VIEWPORT_SIZE_LIMITED_BY_PHYSICAL_DISPLAY_SIZE,
DEFAULT.isViewportSizeLimitedByPhysicalDisplaySize);
viewportOrientationMayChange =
bundle.getBoolean(
FIELD_VIEWPORT_ORIENTATION_MAY_CHANGE,
DEFAULT_WITHOUT_CONTEXT.viewportOrientationMayChange);
FIELD_VIEWPORT_ORIENTATION_MAY_CHANGE, DEFAULT.viewportOrientationMayChange);
preferredVideoMimeTypes =
ImmutableList.copyOf(
firstNonNull(bundle.getStringArray(FIELD_PREFERRED_VIDEO_MIMETYPES), new String[0]));
preferredVideoLanguages =
ImmutableList.copyOf(
firstNonNull(bundle.getStringArray(FIELD_PREFERRED_VIDEO_LANGUAGES), new String[0]));
preferredVideoRoleFlags =
bundle.getInt(
FIELD_PREFERRED_VIDEO_ROLE_FLAGS, DEFAULT_WITHOUT_CONTEXT.preferredVideoRoleFlags);
bundle.getInt(FIELD_PREFERRED_VIDEO_ROLE_FLAGS, DEFAULT.preferredVideoRoleFlags);
// Audio
String[] preferredAudioLanguages1 =
firstNonNull(bundle.getStringArray(FIELD_PREFERRED_AUDIO_LANGUAGES), new String[0]);
preferredAudioLanguages = normalizeLanguageCodes(preferredAudioLanguages1);
preferredAudioRoleFlags =
bundle.getInt(
FIELD_PREFERRED_AUDIO_ROLE_FLAGS, DEFAULT_WITHOUT_CONTEXT.preferredAudioRoleFlags);
bundle.getInt(FIELD_PREFERRED_AUDIO_ROLE_FLAGS, DEFAULT.preferredAudioRoleFlags);
maxAudioChannelCount =
bundle.getInt(
FIELD_MAX_AUDIO_CHANNEL_COUNT, DEFAULT_WITHOUT_CONTEXT.maxAudioChannelCount);
maxAudioBitrate =
bundle.getInt(FIELD_MAX_AUDIO_BITRATE, DEFAULT_WITHOUT_CONTEXT.maxAudioBitrate);
bundle.getInt(FIELD_MAX_AUDIO_CHANNEL_COUNT, DEFAULT.maxAudioChannelCount);
maxAudioBitrate = bundle.getInt(FIELD_MAX_AUDIO_BITRATE, DEFAULT.maxAudioBitrate);
preferredAudioMimeTypes =
ImmutableList.copyOf(
firstNonNull(bundle.getStringArray(FIELD_PREFERRED_AUDIO_MIME_TYPES), new String[0]));
@ -218,29 +211,29 @@ public class TrackSelectionParameters {
normalizeLanguageCodes(
firstNonNull(bundle.getStringArray(FIELD_PREFERRED_TEXT_LANGUAGES), new String[0]));
preferredTextRoleFlags =
bundle.getInt(
FIELD_PREFERRED_TEXT_ROLE_FLAGS, DEFAULT_WITHOUT_CONTEXT.preferredTextRoleFlags);
bundle.getInt(FIELD_PREFERRED_TEXT_ROLE_FLAGS, DEFAULT.preferredTextRoleFlags);
usePreferredTextLanguagesAndRoleFlagsFromCaptioningManager =
preferredTextLanguages.isEmpty()
&& preferredTextRoleFlags == 0
&& bundle.getBoolean(
FIELD_USE_PREFERRED_TEXT_LANGUAGES_AND_ROLE_FLAGS_FROM_CAPTIONING_MANAGER,
DEFAULT.usePreferredTextLanguagesAndRoleFlagsFromCaptioningManager);
ignoredTextSelectionFlags =
bundle.getInt(
FIELD_IGNORED_TEXT_SELECTION_FLAGS,
DEFAULT_WITHOUT_CONTEXT.ignoredTextSelectionFlags);
bundle.getInt(FIELD_IGNORED_TEXT_SELECTION_FLAGS, DEFAULT.ignoredTextSelectionFlags);
selectUndeterminedTextLanguage =
bundle.getBoolean(
FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE,
DEFAULT_WITHOUT_CONTEXT.selectUndeterminedTextLanguage);
FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE, DEFAULT.selectUndeterminedTextLanguage);
// Image
isPrioritizeImageOverVideoEnabled =
bundle.getBoolean(
FIELD_IS_PREFER_IMAGE_OVER_VIDEO_ENABLED,
DEFAULT_WITHOUT_CONTEXT.isPrioritizeImageOverVideoEnabled);
FIELD_IS_PREFER_IMAGE_OVER_VIDEO_ENABLED, DEFAULT.isPrioritizeImageOverVideoEnabled);
// General
forceLowestBitrate =
bundle.getBoolean(FIELD_FORCE_LOWEST_BITRATE, DEFAULT_WITHOUT_CONTEXT.forceLowestBitrate);
bundle.getBoolean(FIELD_FORCE_LOWEST_BITRATE, DEFAULT.forceLowestBitrate);
forceHighestSupportedBitrate =
bundle.getBoolean(
FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE,
DEFAULT_WITHOUT_CONTEXT.forceHighestSupportedBitrate);
FIELD_FORCE_HIGHEST_SUPPORTED_BITRATE, DEFAULT.forceHighestSupportedBitrate);
@Nullable
List<Bundle> overrideBundleList = bundle.getParcelableArrayList(FIELD_SELECTION_OVERRIDES);
List<TrackSelectionOverride> overrideList =
@ -284,6 +277,7 @@ public class TrackSelectionParameters {
/** Overrides the value of the builder with the value of {@link TrackSelectionParameters}. */
@EnsuresNonNull({
"preferredVideoMimeTypes",
"preferredVideoLanguages",
"preferredAudioLanguages",
"preferredAudioMimeTypes",
"audioOffloadPreferences",
@ -303,8 +297,11 @@ public class TrackSelectionParameters {
minVideoBitrate = parameters.minVideoBitrate;
viewportWidth = parameters.viewportWidth;
viewportHeight = parameters.viewportHeight;
isViewportSizeLimitedByPhysicalDisplaySize =
parameters.isViewportSizeLimitedByPhysicalDisplaySize;
viewportOrientationMayChange = parameters.viewportOrientationMayChange;
preferredVideoMimeTypes = parameters.preferredVideoMimeTypes;
preferredVideoLanguages = parameters.preferredVideoLanguages;
preferredVideoRoleFlags = parameters.preferredVideoRoleFlags;
// Audio
preferredAudioLanguages = parameters.preferredAudioLanguages;
@ -316,6 +313,8 @@ public class TrackSelectionParameters {
// Text
preferredTextLanguages = parameters.preferredTextLanguages;
preferredTextRoleFlags = parameters.preferredTextRoleFlags;
usePreferredTextLanguagesAndRoleFlagsFromCaptioningManager =
parameters.usePreferredTextLanguagesAndRoleFlagsFromCaptioningManager;
ignoredTextSelectionFlags = parameters.ignoredTextSelectionFlags;
selectUndeterminedTextLanguage = parameters.selectUndeterminedTextLanguage;
// Image
@ -434,20 +433,31 @@ public class TrackSelectionParameters {
}
/**
* Equivalent to calling {@link #setViewportSize(int, int, boolean)} with the viewport size
* obtained from {@link Util#getCurrentDisplayModeSize(Context)}.
* Sets whether the viewport size should be assumed to the physical display size if no other
* specific viewport size constraint is specified. Constrains video track selections for
* adaptive content so that only tracks suitable for the viewport are selected.
*
* @param context Any context.
* @param viewportOrientationMayChange Whether the viewport orientation may change during
* playback.
* @return This builder.
*/
@CanIgnoreReturnValue
public Builder setViewportSizeToPhysicalDisplaySize(boolean viewportOrientationMayChange) {
this.isViewportSizeLimitedByPhysicalDisplaySize = true;
this.viewportOrientationMayChange = viewportOrientationMayChange;
this.viewportHeight = Integer.MAX_VALUE;
this.viewportWidth = Integer.MAX_VALUE;
return this;
}
/**
* @deprecated Use {@link #setViewportSizeToPhysicalDisplaySize(boolean)} instead.
*/
@Deprecated
@CanIgnoreReturnValue
public Builder setViewportSizeToPhysicalDisplaySize(
Context context, boolean viewportOrientationMayChange) {
// Assume the viewport is fullscreen.
Point viewportSize = Util.getCurrentDisplayModeSize(context);
return setViewportSize(viewportSize.x, viewportSize.y, viewportOrientationMayChange);
return setViewportSizeToPhysicalDisplaySize(viewportOrientationMayChange);
}
/**
@ -477,6 +487,7 @@ public class TrackSelectionParameters {
this.viewportWidth = viewportWidth;
this.viewportHeight = viewportHeight;
this.viewportOrientationMayChange = viewportOrientationMayChange;
this.isViewportSizeLimitedByPhysicalDisplaySize = false;
return this;
}
@ -504,6 +515,36 @@ public class TrackSelectionParameters {
return this;
}
/**
* Sets the preferred language for video tracks.
*
* @param preferredVideoLanguage Preferred video language as an IETF BCP 47 conformant tag, or
* {@code null} to express no language preference for video track selection.
* @return This builder.
*/
@UnstableApi
@CanIgnoreReturnValue
public Builder setPreferredVideoLanguage(@Nullable String preferredVideoLanguage) {
return preferredVideoLanguage == null
? setPreferredVideoLanguages()
: setPreferredVideoLanguages(preferredVideoLanguage);
}
/**
* Sets the preferred languages for video tracks.
*
* @param preferredVideoLanguages Preferred video languages as IETF BCP 47 conformant tags in
* order of preference, or an empty array to express no language preference for video track
* selection.
* @return This builder.
*/
@UnstableApi
@CanIgnoreReturnValue
public Builder setPreferredVideoLanguages(String... preferredVideoLanguages) {
this.preferredVideoLanguages = normalizeLanguageCodes(preferredVideoLanguages);
return this;
}
/**
* Sets the preferred {@link C.RoleFlags} for video tracks.
*
@ -620,33 +661,29 @@ public class TrackSelectionParameters {
// Text
/**
* Sets the preferred language and role flags for text tracks based on the accessibility
* settings of {@link CaptioningManager}.
* Sets whether the preferred languages and the preferred role flags for text tracks should be
* set according the {@link CaptioningManager} preferences, if enabled in the system settings
* and no other explicit language or role flag preferences are specified.
*
* <p>Does nothing when the {@link CaptioningManager} is disabled.
*
* @param context A {@link Context}.
* @return This builder.
*/
@CanIgnoreReturnValue
public Builder setPreferredTextLanguageAndRoleFlagsToCaptioningManagerSettings() {
usePreferredTextLanguagesAndRoleFlagsFromCaptioningManager = true;
preferredTextLanguages = ImmutableList.of();
preferredTextRoleFlags = 0;
return this;
}
/**
* @deprecated Use {@link #setPreferredTextLanguageAndRoleFlagsToCaptioningManagerSettings()}
* instead.
*/
@Deprecated
@CanIgnoreReturnValue
public Builder setPreferredTextLanguageAndRoleFlagsToCaptioningManagerSettings(
Context context) {
if (Util.SDK_INT < 23 && Looper.myLooper() == null) {
// Android platform bug (pre-Marshmallow) that causes RuntimeExceptions when
// CaptioningService is instantiated from a non-Looper thread. See [internal: b/143779904].
return this;
}
CaptioningManager captioningManager =
(CaptioningManager) context.getSystemService(Context.CAPTIONING_SERVICE);
if (captioningManager == null || !captioningManager.isEnabled()) {
return this;
}
preferredTextRoleFlags = C.ROLE_FLAG_CAPTION | C.ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND;
Locale preferredLocale = captioningManager.getLocale();
if (preferredLocale != null) {
preferredTextLanguages = ImmutableList.of(Util.getLocaleLanguageTag(preferredLocale));
}
return this;
return setPreferredTextLanguageAndRoleFlagsToCaptioningManagerSettings();
}
/**
@ -673,6 +710,7 @@ public class TrackSelectionParameters {
@CanIgnoreReturnValue
public Builder setPreferredTextLanguages(String... preferredTextLanguages) {
this.preferredTextLanguages = normalizeLanguageCodes(preferredTextLanguages);
this.usePreferredTextLanguagesAndRoleFlagsFromCaptioningManager = false;
return this;
}
@ -685,6 +723,7 @@ public class TrackSelectionParameters {
@CanIgnoreReturnValue
public Builder setPreferredTextRoleFlags(@C.RoleFlags int preferredTextRoleFlags) {
this.preferredTextRoleFlags = preferredTextRoleFlags;
this.usePreferredTextLanguagesAndRoleFlagsFromCaptioningManager = false;
return this;
}
@ -1043,35 +1082,21 @@ public class TrackSelectionParameters {
}
}
/**
* An instance with default values, except those obtained from the {@link Context}.
*
* <p>If possible, use {@link #getDefaults(Context)} instead.
*
* <p>This instance will not have the following settings:
*
* <ul>
* <li>{@link Builder#setViewportSizeToPhysicalDisplaySize(Context, boolean) Viewport
* constraints} configured for the primary display.
* <li>{@link Builder#setPreferredTextLanguageAndRoleFlagsToCaptioningManagerSettings(Context)
* Preferred text language and role flags} configured to the accessibility settings of
* {@link CaptioningManager}.
* </ul>
*/
@UnstableApi
@SuppressWarnings("deprecation")
public static final TrackSelectionParameters DEFAULT_WITHOUT_CONTEXT = new Builder().build();
/** An instance with default parameters. */
@UnstableApi public static final TrackSelectionParameters DEFAULT = new Builder().build();
/**
* @deprecated This instance is not configured using {@link Context} constraints. Use {@link
* #getDefaults(Context)} instead.
* @deprecated Use {@link #DEFAULT} instead.
*/
@UnstableApi @Deprecated
public static final TrackSelectionParameters DEFAULT = DEFAULT_WITHOUT_CONTEXT;
public static final TrackSelectionParameters DEFAULT_WITHOUT_CONTEXT = DEFAULT;
/** Returns an instance configured with default values. */
/**
* @deprecated Use {@link #DEFAULT} instead.
*/
@Deprecated
public static TrackSelectionParameters getDefaults(Context context) {
return new Builder(context).build();
return DEFAULT;
}
// Video
@ -1135,6 +1160,13 @@ public class TrackSelectionParameters {
*/
public final int viewportHeight;
/**
* Whether the viewport size should be assumed to the physical display size if no other specific
* viewport size constraint is specified. Constrains video track selections for adaptive content
* so that only tracks suitable for the viewport are selected. The default value is {@code true}.
*/
public final boolean isViewportSizeLimitedByPhysicalDisplaySize;
/**
* Whether the viewport orientation may change during playback. Constrains video track selections
* for adaptive content so that only tracks suitable for the viewport are selected. The default
@ -1148,6 +1180,11 @@ public class TrackSelectionParameters {
*/
public final ImmutableList<String> preferredVideoMimeTypes;
/**
* The preferred languages for video tracks as IETF BCP 47 conformant tags in order of preference.
*/
@UnstableApi public final ImmutableList<String> preferredVideoLanguages;
/**
* The preferred {@link C.RoleFlags} for video tracks. {@code 0} selects the default track if
* there is one, or the first track if there's no default. The default value is {@code 0}.
@ -1196,19 +1233,24 @@ public class TrackSelectionParameters {
/**
* The preferred languages for text tracks as IETF BCP 47 conformant tags in order of preference.
* An empty list selects the default track if there is one, or no track otherwise. The default
* value is an empty list, or the language of the accessibility {@link CaptioningManager} if
* enabled.
* value is an empty list.
*/
public final ImmutableList<String> preferredTextLanguages;
/**
* The preferred {@link C.RoleFlags} for text tracks. {@code 0} selects the default track if there
* is one, or no track otherwise. The default value is {@code 0}, or {@link C#ROLE_FLAG_SUBTITLE}
* | {@link C#ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND} if the accessibility {@link CaptioningManager}
* is enabled.
* is one, or no track otherwise. The default value is {@code 0}.
*/
public final @C.RoleFlags int preferredTextRoleFlags;
/**
* Whether the preferred languages and the preferred role flags for text tracks should be set
* according the {@link CaptioningManager} preferences, if enabled in the system settings and no
* other explicit language or role flag preferences are specified. The default value is {@code
* true}.
*/
public final boolean usePreferredTextLanguagesAndRoleFlagsFromCaptioningManager;
/**
* Bitmask of selection flags that are ignored for text track selections. See {@link
* C.SelectionFlags}. The default value is {@code 0} (i.e., no flags are ignored).
@ -1265,8 +1307,11 @@ public class TrackSelectionParameters {
this.minVideoBitrate = builder.minVideoBitrate;
this.viewportWidth = builder.viewportWidth;
this.viewportHeight = builder.viewportHeight;
this.isViewportSizeLimitedByPhysicalDisplaySize =
builder.isViewportSizeLimitedByPhysicalDisplaySize;
this.viewportOrientationMayChange = builder.viewportOrientationMayChange;
this.preferredVideoMimeTypes = builder.preferredVideoMimeTypes;
this.preferredVideoLanguages = builder.preferredVideoLanguages;
this.preferredVideoRoleFlags = builder.preferredVideoRoleFlags;
// Audio
this.preferredAudioLanguages = builder.preferredAudioLanguages;
@ -1278,6 +1323,8 @@ public class TrackSelectionParameters {
// Text
this.preferredTextLanguages = builder.preferredTextLanguages;
this.preferredTextRoleFlags = builder.preferredTextRoleFlags;
this.usePreferredTextLanguagesAndRoleFlagsFromCaptioningManager =
builder.usePreferredTextLanguagesAndRoleFlagsFromCaptioningManager;
this.ignoredTextSelectionFlags = builder.ignoredTextSelectionFlags;
this.selectUndeterminedTextLanguage = builder.selectUndeterminedTextLanguage;
// Image
@ -1316,7 +1363,10 @@ public class TrackSelectionParameters {
&& viewportOrientationMayChange == other.viewportOrientationMayChange
&& viewportWidth == other.viewportWidth
&& viewportHeight == other.viewportHeight
&& isViewportSizeLimitedByPhysicalDisplaySize
== other.isViewportSizeLimitedByPhysicalDisplaySize
&& preferredVideoMimeTypes.equals(other.preferredVideoMimeTypes)
&& preferredVideoLanguages.equals(other.preferredVideoLanguages)
&& preferredVideoRoleFlags == other.preferredVideoRoleFlags
// Audio
&& preferredAudioLanguages.equals(other.preferredAudioLanguages)
@ -1328,6 +1378,8 @@ public class TrackSelectionParameters {
// Text
&& preferredTextLanguages.equals(other.preferredTextLanguages)
&& preferredTextRoleFlags == other.preferredTextRoleFlags
&& usePreferredTextLanguagesAndRoleFlagsFromCaptioningManager
== other.usePreferredTextLanguagesAndRoleFlagsFromCaptioningManager
&& ignoredTextSelectionFlags == other.ignoredTextSelectionFlags
&& selectUndeterminedTextLanguage == other.selectUndeterminedTextLanguage
// Image
@ -1354,7 +1406,9 @@ public class TrackSelectionParameters {
result = 31 * result + (viewportOrientationMayChange ? 1 : 0);
result = 31 * result + viewportWidth;
result = 31 * result + viewportHeight;
result = 31 * result + (isViewportSizeLimitedByPhysicalDisplaySize ? 1 : 0);
result = 31 * result + preferredVideoMimeTypes.hashCode();
result = 31 * result + preferredVideoLanguages.hashCode();
result = 31 * result + preferredVideoRoleFlags;
// Audio
result = 31 * result + preferredAudioLanguages.hashCode();
@ -1366,6 +1420,7 @@ public class TrackSelectionParameters {
// Text
result = 31 * result + preferredTextLanguages.hashCode();
result = 31 * result + preferredTextRoleFlags;
result = 31 * result + (usePreferredTextLanguagesAndRoleFlagsFromCaptioningManager ? 1 : 0);
result = 31 * result + ignoredTextSelectionFlags;
result = 31 * result + (selectUndeterminedTextLanguage ? 1 : 0);
// Image
@ -1410,6 +1465,12 @@ public class TrackSelectionParameters {
private static final String FIELD_AUDIO_OFFLOAD_PREFERENCES = Util.intToStringMaxRadix(30);
private static final String FIELD_IS_PREFER_IMAGE_OVER_VIDEO_ENABLED =
Util.intToStringMaxRadix(31);
private static final String FIELD_PREFERRED_VIDEO_LANGUAGES = Util.intToStringMaxRadix(32);
private static final String FIELD_IS_VIEWPORT_SIZE_LIMITED_BY_PHYSICAL_DISPLAY_SIZE =
Util.intToStringMaxRadix(33);
private static final String
FIELD_USE_PREFERRED_TEXT_LANGUAGES_AND_ROLE_FLAGS_FROM_CAPTIONING_MANAGER =
Util.intToStringMaxRadix(34);
/**
* Defines a minimum field ID value for subclasses to use when implementing {@link #toBundle()}
@ -1436,9 +1497,14 @@ public class TrackSelectionParameters {
bundle.putInt(FIELD_MIN_VIDEO_BITRATE, minVideoBitrate);
bundle.putInt(FIELD_VIEWPORT_WIDTH, viewportWidth);
bundle.putInt(FIELD_VIEWPORT_HEIGHT, viewportHeight);
bundle.putBoolean(
FIELD_IS_VIEWPORT_SIZE_LIMITED_BY_PHYSICAL_DISPLAY_SIZE,
isViewportSizeLimitedByPhysicalDisplaySize);
bundle.putBoolean(FIELD_VIEWPORT_ORIENTATION_MAY_CHANGE, viewportOrientationMayChange);
bundle.putStringArray(
FIELD_PREFERRED_VIDEO_MIMETYPES, preferredVideoMimeTypes.toArray(new String[0]));
bundle.putStringArray(
FIELD_PREFERRED_VIDEO_LANGUAGES, preferredVideoLanguages.toArray(new String[0]));
bundle.putInt(FIELD_PREFERRED_VIDEO_ROLE_FLAGS, preferredVideoRoleFlags);
// Audio
bundle.putStringArray(
@ -1452,6 +1518,9 @@ public class TrackSelectionParameters {
bundle.putStringArray(
FIELD_PREFERRED_TEXT_LANGUAGES, preferredTextLanguages.toArray(new String[0]));
bundle.putInt(FIELD_PREFERRED_TEXT_ROLE_FLAGS, preferredTextRoleFlags);
bundle.putBoolean(
FIELD_USE_PREFERRED_TEXT_LANGUAGES_AND_ROLE_FLAGS_FROM_CAPTIONING_MANAGER,
usePreferredTextLanguagesAndRoleFlagsFromCaptioningManager);
bundle.putInt(FIELD_IGNORED_TEXT_SELECTION_FLAGS, ignoredTextSelectionFlags);
bundle.putBoolean(FIELD_SELECT_UNDETERMINED_TEXT_LANGUAGE, selectUndeterminedTextLanguage);
bundle.putInt(FIELD_AUDIO_OFFLOAD_MODE_PREFERENCE, audioOffloadPreferences.audioOffloadMode);

View File

@ -13,13 +13,13 @@
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.effect;
package androidx.media3.common;
import androidx.media3.common.util.Size;
import androidx.media3.common.util.UnstableApi;
import java.util.List;
/** Settings for the {@link VideoCompositor}. */
/** Settings for the {@code VideoCompositor}. */
@UnstableApi
public interface VideoCompositorSettings {
// TODO: b/262694346 - Consider adding more features, like selecting a:
@ -45,7 +45,7 @@ public interface VideoCompositorSettings {
*/
@Override
public OverlaySettings getOverlaySettings(int inputId, long presentationTimeUs) {
return new OverlaySettings.Builder().build();
return new OverlaySettings() {};
}
};

View File

@ -84,8 +84,8 @@ public interface VideoFrameProcessor {
* Input frames come from the {@linkplain #getInputSurface input surface} and don't need to be
* {@linkplain #registerInputFrame registered} (unlike with {@link #INPUT_TYPE_SURFACE}).
*
* <p>Every frame must use the {@linkplain #registerInputStream(int, List, FrameInfo) input
* stream's registered} frame info. Also sets the surface's {@linkplain
* <p>Every frame must use the {@linkplain #registerInputStream input stream's registered} frame
* format. Also sets the surface's {@linkplain
* android.graphics.SurfaceTexture#setDefaultBufferSize(int, int) default buffer size}.
*/
int INPUT_TYPE_SURFACE_AUTOMATIC_FRAME_REGISTRATION = 4;
@ -131,8 +131,8 @@ public interface VideoFrameProcessor {
interface Listener {
/**
* Called when the {@link VideoFrameProcessor} finishes {@linkplain #registerInputStream(int,
* List, FrameInfo) registering an input stream}.
* Called when the {@link VideoFrameProcessor} finishes {@linkplain #registerInputStream
* registering an input stream}.
*
* <p>The {@link VideoFrameProcessor} is now ready to accept new input {@linkplain
* VideoFrameProcessor#registerInputFrame frames}, {@linkplain
@ -140,11 +140,11 @@ public interface VideoFrameProcessor {
* VideoFrameProcessor#queueInputTexture(int, long) textures}.
*
* @param inputType The {@link InputType} of the new input stream.
* @param format The {@link Format} of the new input stream.
* @param effects The list of {@link Effect effects} to apply to the new input stream.
* @param frameInfo The {@link FrameInfo} of the new input stream.
*/
default void onInputStreamRegistered(
@InputType int inputType, List<Effect> effects, FrameInfo frameInfo) {}
@InputType int inputType, Format format, List<Effect> effects) {}
/**
* Called when the output size changes.
@ -157,6 +157,14 @@ public interface VideoFrameProcessor {
*/
default void onOutputSizeChanged(int width, int height) {}
/**
* Called when the output frame rate changes.
*
* @param frameRate The output frame rate in frames per second, or {@link Format#NO_VALUE} if
* unknown.
*/
default void onOutputFrameRateChanged(float frameRate) {}
/**
* Called when an output frame with the given {@code presentationTimeUs} becomes available for
* rendering.
@ -196,8 +204,8 @@ public interface VideoFrameProcessor {
/**
* Provides an input {@link Bitmap} to the {@link VideoFrameProcessor}.
*
* <p>Can be called many times after {@link #registerInputStream(int, List, FrameInfo) registering
* the input stream} to put multiple frames in the same input stream.
* <p>Can be called many times after {@link #registerInputStream registering the input stream} to
* put multiple frames in the same input stream.
*
* @param inputBitmap The {@link Bitmap} queued to the {@code VideoFrameProcessor}.
* @param timestampIterator A {@link TimestampIterator} generating the exact timestamps that the
@ -271,14 +279,20 @@ public interface VideoFrameProcessor {
* #queueInputTexture queued}.
*
* <p>This method blocks the calling thread until the previous calls to this method finish, that
* is when {@link Listener#onInputStreamRegistered(int, List, FrameInfo)} is called after the
* is when {@link Listener#onInputStreamRegistered(int, Format, List)} is called after the
* underlying processing pipeline has been adapted to the registered input stream.
*
* @param inputType The {@link InputType} of the new input stream.
* @param format The {@link Format} of the new input stream. The {@link Format#colorInfo}, the
* {@link Format#width}, the {@link Format#height} and the {@link
* Format#pixelWidthHeightRatio} must be set.
* @param effects The list of {@link Effect effects} to apply to the new input stream.
* @param frameInfo The {@link FrameInfo} of the new input stream.
* @param offsetToAddUs The offset that must be added to the frame presentation timestamps, in
* microseconds. This offset is not part of the input timestamps. It is added to the frame
* timestamps before processing, and is retained in the output timestamps.
*/
void registerInputStream(@InputType int inputType, List<Effect> effects, FrameInfo frameInfo);
void registerInputStream(
@InputType int inputType, Format format, List<Effect> effects, long offsetToAddUs);
/**
* Informs the {@code VideoFrameProcessor} that a frame will be queued to its {@linkplain
@ -287,11 +301,10 @@ public interface VideoFrameProcessor {
* <p>Must be called before rendering a frame to the input surface. The caller must not render
* frames to the {@linkplain #getInputSurface input surface} when {@code false} is returned.
*
* @return Whether the input frame was successfully registered. If {@link
* #registerInputStream(int, List, FrameInfo)} is called, this method returns {@code false}
* until {@link Listener#onInputStreamRegistered(int, List, FrameInfo)} is called. Otherwise,
* a return value of {@code false} indicates the {@code VideoFrameProcessor} is not ready to
* accept input.
* @return Whether the input frame was successfully registered. If {@link #registerInputStream} is
* called, this method returns {@code false} until {@link
* Listener#onInputStreamRegistered(int, Format, List)} is called. Otherwise, a return value
* of {@code false} indicates the {@code VideoFrameProcessor} is not ready to accept input.
* @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

View File

@ -35,6 +35,14 @@ public interface VideoGraph {
*/
default void onOutputSizeChanged(int width, int height) {}
/**
* Called when the output frame rate changes.
*
* @param frameRate The output frame rate in frames per second, or {@link Format#NO_VALUE} if
* unknown.
*/
default void onOutputFrameRateChanged(float frameRate) {}
/**
* Called when an output frame with the given {@code framePresentationTimeUs} becomes available
* for rendering.

View File

@ -0,0 +1,344 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.common.audio;
import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkNotNull;
import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.media3.common.AudioAttributes;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.Objects;
/**
* Compatibility version of an {@link AudioFocusRequest} with fallbacks for older Android versions.
*/
@UnstableApi
public final class AudioFocusRequestCompat {
private final @AudioManagerCompat.AudioFocusGain int focusGain;
private final AudioManager.OnAudioFocusChangeListener onAudioFocusChangeListener;
private final Handler focusChangeHandler;
private final AudioAttributes audioAttributes;
private final boolean pauseOnDuck;
@Nullable private final Object frameworkAudioFocusRequest;
/* package */ AudioFocusRequestCompat(
int focusGain,
AudioManager.OnAudioFocusChangeListener onAudioFocusChangeListener,
Handler focusChangeHandler,
AudioAttributes audioFocusRequestCompat,
boolean pauseOnDuck) {
this.focusGain = focusGain;
this.focusChangeHandler = focusChangeHandler;
this.audioAttributes = audioFocusRequestCompat;
this.pauseOnDuck = pauseOnDuck;
if (Util.SDK_INT < 26) {
this.onAudioFocusChangeListener =
new OnAudioFocusChangeListenerHandlerCompat(
onAudioFocusChangeListener, focusChangeHandler);
} else {
this.onAudioFocusChangeListener = onAudioFocusChangeListener;
}
if (Util.SDK_INT >= 26) {
this.frameworkAudioFocusRequest =
new AudioFocusRequest.Builder(focusGain)
.setAudioAttributes(audioAttributes.getAudioAttributesV21().audioAttributes)
.setWillPauseWhenDucked(pauseOnDuck)
.setOnAudioFocusChangeListener(onAudioFocusChangeListener, focusChangeHandler)
.build();
} else {
this.frameworkAudioFocusRequest = null;
}
}
/**
* Returns the type of {@link AudioManagerCompat.AudioFocusGain} configured for this {@code
* AudioFocusRequestCompat}.
*/
public @AudioManagerCompat.AudioFocusGain int getFocusGain() {
return focusGain;
}
/**
* Returns the {@link AudioAttributes} set for this {@code AudioFocusRequestCompat}, or the
* default attributes if none were set.
*/
public AudioAttributes getAudioAttributes() {
return audioAttributes;
}
/**
* Returns whether the application that would use this {@code AudioFocusRequestCompat} would pause
* when it is requested to duck. This value is only applicable on {@link
* android.os.Build.VERSION_CODES#O} and later.
*/
public boolean willPauseWhenDucked() {
return pauseOnDuck;
}
/**
* Returns the {@link AudioManager.OnAudioFocusChangeListener} set for this {@code
* AudioFocusRequestCompat}.
*
* @return The {@link AudioManager.OnAudioFocusChangeListener} that was set.
*/
public AudioManager.OnAudioFocusChangeListener getOnAudioFocusChangeListener() {
return onAudioFocusChangeListener;
}
/**
* Returns the {@link Handler} to be used for the {@link AudioManager.OnAudioFocusChangeListener}.
*/
public Handler getFocusChangeHandler() {
return focusChangeHandler;
}
/** Returns new {@link Builder} with all values of this instance pre-populated. */
public Builder buildUpon() {
return new Builder(this);
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
return true;
}
if (!(o instanceof AudioFocusRequestCompat)) {
return false;
}
AudioFocusRequestCompat that = (AudioFocusRequestCompat) o;
return focusGain == that.focusGain
&& pauseOnDuck == that.pauseOnDuck
&& Objects.equals(onAudioFocusChangeListener, that.onAudioFocusChangeListener)
&& Objects.equals(focusChangeHandler, that.focusChangeHandler)
&& Objects.equals(audioAttributes, that.audioAttributes);
}
@Override
public int hashCode() {
return Objects.hash(
focusGain, onAudioFocusChangeListener, focusChangeHandler, audioAttributes, pauseOnDuck);
}
@RequiresApi(26)
/* package */ AudioFocusRequest getAudioFocusRequest() {
return (AudioFocusRequest) checkNotNull(frameworkAudioFocusRequest);
}
/**
* Builder class for {@link AudioFocusRequestCompat} objects.
*
* <p>The default values are:
*
* <ul>
* <li>focus listener and handler: none
* <li>audio attributes: {@link AudioAttributes#DEFAULT}
* <li>pauses on duck: false
* <li>supports delayed focus grant: false
* </ul>
*
* <p>In contrast to a {@link AudioFocusRequest}, attempting to {@link #build()} an {@link
* AudioFocusRequestCompat} without an {@link AudioManager.OnAudioFocusChangeListener} will throw
* an {@link IllegalArgumentException}, because the listener is required for all API levels up to
* API 26.
*/
public static final class Builder {
private @AudioManagerCompat.AudioFocusGain int focusGain;
@Nullable private AudioManager.OnAudioFocusChangeListener onAudioFocusChangeListener;
@Nullable private Handler focusChangeHandler;
private AudioAttributes audioAttributes;
private boolean pauseOnDuck;
/**
* Constructs a new {@code Builder}, and specifies how audio focus will be requested.
*
* <p>By default there is no focus change listener, delayed focus is not supported, ducking is
* suitable for the application, and the {@link AudioAttributes} are set to {@link
* AudioAttributes#DEFAULT}.
*
* @param focusGain The type of {@link AudioManagerCompat.AudioFocusGain} that will be
* requested.
*/
public Builder(@AudioManagerCompat.AudioFocusGain int focusGain) {
this.audioAttributes = AudioAttributes.DEFAULT;
this.focusGain = focusGain;
}
private Builder(AudioFocusRequestCompat other) {
focusGain = other.getFocusGain();
onAudioFocusChangeListener = other.getOnAudioFocusChangeListener();
focusChangeHandler = other.getFocusChangeHandler();
audioAttributes = other.getAudioAttributes();
pauseOnDuck = other.willPauseWhenDucked();
}
/**
* Sets the type of {@link AudioManagerCompat.AudioFocusGain} that will be requested.
*
* @param focusGain The type of {@link AudioManagerCompat.AudioFocusGain} that will be
* requested.
* @return This {@code Builder} instance.
*/
@CanIgnoreReturnValue
public Builder setFocusGain(@AudioManagerCompat.AudioFocusGain int focusGain) {
checkArgument(isValidFocusGain(focusGain));
this.focusGain = focusGain;
return this;
}
/**
* Sets the listener called when audio focus changes after being requested with {@link
* AudioManagerCompat#requestAudioFocus(AudioManager, AudioFocusRequestCompat)}, and until being
* abandoned with {@link AudioManagerCompat#abandonAudioFocusRequest(AudioManager,
* AudioFocusRequestCompat)}. Note that only focus changes (gains and losses) affecting the
* focus owner are reported, not gains and losses of other focus requesters in the system. <br>
* Notifications are delivered on the main thread.
*
* @param listener The {@link AudioManager.OnAudioFocusChangeListener} receiving the focus
* change notifications.
* @return This {@code Builder} instance.
*/
@CanIgnoreReturnValue
public Builder setOnAudioFocusChangeListener(AudioManager.OnAudioFocusChangeListener listener) {
return setOnAudioFocusChangeListener(listener, new Handler(Looper.getMainLooper()));
}
/**
* Sets the listener called when audio focus changes after being requested with {@link
* AudioManagerCompat#requestAudioFocus(AudioManager, AudioFocusRequestCompat)}, and until being
* abandoned with {@link AudioManagerCompat#abandonAudioFocusRequest(AudioManager,
* AudioFocusRequestCompat)}. Note that only focus changes (gains and losses) affecting the
* focus owner are reported, not gains and losses of other focus requesters in the system.
*
* @param listener The {@link AudioManager.OnAudioFocusChangeListener} receiving the focus
* change notifications.
* @param handler The {@link Handler} for the thread on which to execute the notifications.
* @return This {@code Builder} instance.
*/
@CanIgnoreReturnValue
public Builder setOnAudioFocusChangeListener(
AudioManager.OnAudioFocusChangeListener listener, Handler handler) {
checkNotNull(listener);
checkNotNull(handler);
onAudioFocusChangeListener = listener;
focusChangeHandler = handler;
return this;
}
/**
* Sets the {@link AudioAttributes} to be associated with the focus request, and which describe
* the use case for which focus is requested. As the focus requests typically precede audio
* playback, this information is used on certain platforms to declare the subsequent playback
* use case. It is therefore good practice to use in this method the same {@code
* AudioAttributes} as used for playback, see for example {@code
* ExoPlayer.Builder.setAudioAttributes()}.
*
* @param attributes The {@link AudioAttributes} for the focus request.
* @return This {@code Builder} instance.
*/
@CanIgnoreReturnValue
public Builder setAudioAttributes(AudioAttributes attributes) {
checkNotNull(attributes);
audioAttributes = attributes;
return this;
}
/**
* Declares the intended behavior of the application with regards to audio ducking. See more
* details in the {@link AudioFocusRequest} class documentation. Setting {@code pauseOnDuck} to
* true will only have an effect on {@link android.os.Build.VERSION_CODES#O} and later.
*
* @param pauseOnDuck Use {@code true} if the application intends to pause audio playback when
* losing focus with {@link AudioManager#AUDIOFOCUS_LOSS_TRANSIENT_CAN_DUCK}.
* @return This {@code Builder} instance.
*/
@CanIgnoreReturnValue
public Builder setWillPauseWhenDucked(boolean pauseOnDuck) {
this.pauseOnDuck = pauseOnDuck;
return this;
}
/**
* Builds a new {@code AudioFocusRequestCompat} instance combining all the information gathered
* by this builder's configuration methods.
*
* @return The {@code AudioFocusRequestCompat}.
*/
public AudioFocusRequestCompat build() {
if (onAudioFocusChangeListener == null) {
throw new IllegalStateException(
"Can't build an AudioFocusRequestCompat instance without a listener");
}
return new AudioFocusRequestCompat(
focusGain,
onAudioFocusChangeListener,
checkNotNull(focusChangeHandler),
audioAttributes,
pauseOnDuck);
}
/**
* Checks whether a focus gain constant is a valid value for an audio focus request.
*
* @param focusGain value to check
* @return true if focusGain is a valid value for an audio focus request.
*/
private static boolean isValidFocusGain(@AudioManagerCompat.AudioFocusGain int focusGain) {
switch (focusGain) {
case AudioManagerCompat.AUDIOFOCUS_GAIN:
case AudioManagerCompat.AUDIOFOCUS_GAIN_TRANSIENT:
case AudioManagerCompat.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK:
case AudioManagerCompat.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE:
return true;
default:
return false;
}
}
}
/**
* Class to allow {@link AudioManager.OnAudioFocusChangeListener#onAudioFocusChange(int)} calls on
* a specific thread prior to API 26.
*/
private static class OnAudioFocusChangeListenerHandlerCompat
implements AudioManager.OnAudioFocusChangeListener {
private final Handler handler;
private final AudioManager.OnAudioFocusChangeListener listener;
/* package */ OnAudioFocusChangeListenerHandlerCompat(
AudioManager.OnAudioFocusChangeListener listener, Handler handler) {
this.listener = listener;
this.handler = Util.createHandler(handler.getLooper(), /* callback= */ null);
}
@Override
public void onAudioFocusChange(int focusChange) {
Util.postOrRun(handler, () -> listener.onAudioFocusChange(focusChange));
}
}
}

View File

@ -0,0 +1,247 @@
/*
* Copyright 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.common.audio;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static java.lang.annotation.ElementType.TYPE_USE;
import android.content.Context;
import android.media.AudioManager;
import android.os.Looper;
import androidx.annotation.IntDef;
import androidx.annotation.IntRange;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.util.BackgroundExecutor;
import androidx.media3.common.util.ConditionVariable;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** Compatibility layer for {@link AudioManager} with fallbacks for older Android versions. */
@UnstableApi
public final class AudioManagerCompat {
private static final String TAG = "AudioManagerCompat";
/**
* Audio focus gain types. One of {@link #AUDIOFOCUS_NONE}, {@link #AUDIOFOCUS_GAIN}, {@link
* #AUDIOFOCUS_GAIN_TRANSIENT}, {@link #AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK} or {@link
* #AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE)
@IntDef({
AUDIOFOCUS_NONE,
AUDIOFOCUS_GAIN,
AUDIOFOCUS_GAIN_TRANSIENT,
AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK,
AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE
})
public @interface AudioFocusGain {}
/** Used to indicate no audio focus has been gained or lost, or requested. */
@SuppressWarnings("InlinedApi")
public static final int AUDIOFOCUS_NONE = AudioManager.AUDIOFOCUS_NONE;
/** Used to indicate a gain of audio focus, or a request of audio focus, of unknown duration. */
public static final int AUDIOFOCUS_GAIN = AudioManager.AUDIOFOCUS_GAIN;
/**
* Used to indicate a temporary gain or request of audio focus, anticipated to last a short amount
* of time. Examples of temporary changes are the playback of driving directions, or an event
* notification.
*/
public static final int AUDIOFOCUS_GAIN_TRANSIENT = AudioManager.AUDIOFOCUS_GAIN_TRANSIENT;
/**
* Used to indicate a temporary request of audio focus, anticipated to last a short amount of
* time, and where it is acceptable for other audio applications to keep playing after having
* lowered their output level (also referred to as "ducking"). Examples of temporary changes are
* the playback of driving directions where playback of music in the background is acceptable.
*/
public static final int AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK =
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK;
/**
* Used to indicate a temporary request of audio focus, anticipated to last a short amount of
* time, during which no other applications, or system components, should play anything. Examples
* of exclusive and transient audio focus requests are voice memo recording and speech
* recognition, during which the system shouldn't play any notifications, and media playback
* should have paused.
*/
public static final int AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE =
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE;
@SuppressWarnings("NonFinalStaticField") // Lazily initialized under class lock
@Nullable
private static AudioManager audioManager;
@SuppressWarnings("NonFinalStaticField") // Lazily initialized under class lock
private static @MonotonicNonNull Context applicationContext;
/**
* Returns the {@link AudioManager}.
*
* <p>This method avoids potential threading issues where AudioManager keeps access to the thread
* it was created on until after this thread is stopped.
*
* <p>It is recommended to use this method from a background thread.
*
* @param context A {@link Context}.
* @return The {@link AudioManager}.
*/
public static synchronized AudioManager getAudioManager(Context context) {
Context applicationContext = context.getApplicationContext();
if (AudioManagerCompat.applicationContext != applicationContext) {
// Reset cached instance if the application context changed. This should only happen in tests.
audioManager = null;
}
if (audioManager != null) {
return audioManager;
}
@Nullable Looper myLooper = Looper.myLooper();
if (myLooper == null || myLooper == Looper.getMainLooper()) {
// The AudioManager will assume the main looper as default callback anyway, so create the
// instance here without using BackgroundExecutor.
audioManager = (AudioManager) applicationContext.getSystemService(Context.AUDIO_SERVICE);
return checkNotNull(audioManager);
}
// Create the audio manager on the BackgroundExecutor to avoid running the potentially blocking
// command on the main thread but still use a thread that is guaranteed to exist for the
// lifetime of the app.
ConditionVariable audioManagerSetCondition = new ConditionVariable();
BackgroundExecutor.get()
.execute(
() -> {
audioManager =
(AudioManager) applicationContext.getSystemService(Context.AUDIO_SERVICE);
audioManagerSetCondition.open();
});
audioManagerSetCondition.blockUninterruptible();
return checkNotNull(audioManager);
}
/**
* Requests audio focus. See the {@link AudioFocusRequestCompat} for information about the options
* available to configure your request, and notification of focus gain and loss.
*
* @param audioManager The {@link AudioManager}.
* @param focusRequest An {@link AudioFocusRequestCompat} instance used to configure how focus is
* requested.
* @return {@link AudioManager#AUDIOFOCUS_REQUEST_FAILED} or {@link
* AudioManager#AUDIOFOCUS_REQUEST_GRANTED}.
*/
@SuppressWarnings("deprecation")
public static int requestAudioFocus(
AudioManager audioManager, AudioFocusRequestCompat focusRequest) {
if (Util.SDK_INT >= 26) {
return audioManager.requestAudioFocus(focusRequest.getAudioFocusRequest());
} else {
return audioManager.requestAudioFocus(
focusRequest.getOnAudioFocusChangeListener(),
focusRequest.getAudioAttributes().getStreamType(),
focusRequest.getFocusGain());
}
}
/**
* Abandon audio focus. Causes the previous focus owner, if any, to receive focus.
*
* @param audioManager The {@link AudioManager}.
* @param focusRequest The {@link AudioFocusRequestCompat} that was used when requesting focus
* with {@link #requestAudioFocus(AudioManager, AudioFocusRequestCompat)}.
* @return {@link AudioManager#AUDIOFOCUS_REQUEST_FAILED} or {@link
* AudioManager#AUDIOFOCUS_REQUEST_GRANTED}
*/
@SuppressWarnings("deprecation")
public static int abandonAudioFocusRequest(
AudioManager audioManager, AudioFocusRequestCompat focusRequest) {
if (Util.SDK_INT >= 26) {
return audioManager.abandonAudioFocusRequest(focusRequest.getAudioFocusRequest());
} else {
return audioManager.abandonAudioFocus(focusRequest.getOnAudioFocusChangeListener());
}
}
/**
* Returns the maximum volume index for a particular stream.
*
* @param audioManager The {@link AudioManager}.
* @param streamType The {@link C.StreamType} whose maximum volume index is returned.
* @return The maximum valid volume index for the stream.
*/
@IntRange(from = 0)
public static int getStreamMaxVolume(AudioManager audioManager, @C.StreamType int streamType) {
return audioManager.getStreamMaxVolume(streamType);
}
/**
* Returns the minimum volume index for a particular stream.
*
* @param audioManager The {@link AudioManager}.
* @param streamType The {@link C.StreamType} whose minimum volume index is returned.
* @return The minimum valid volume index for the stream.
*/
@IntRange(from = 0)
public static int getStreamMinVolume(AudioManager audioManager, @C.StreamType int streamType) {
return Util.SDK_INT >= 28 ? audioManager.getStreamMinVolume(streamType) : 0;
}
/**
* Returns the current volume for a particular stream.
*
* @param audioManager The {@link AudioManager}.
* @param streamType The {@link C.StreamType} whose volume is returned.
* @return The current volume of the stream.
*/
public static int getStreamVolume(AudioManager audioManager, @C.StreamType int streamType) {
// AudioManager#getStreamVolume(int) throws an exception on some devices. See
// https://github.com/google/ExoPlayer/issues/8191.
try {
return audioManager.getStreamVolume(streamType);
} catch (RuntimeException e) {
Log.w(
"AudioManagerCompat",
"Could not retrieve stream volume for stream type " + streamType,
e);
return audioManager.getStreamMaxVolume(streamType);
}
}
/**
* Returns whether the given stream is muted.
*
* @param audioManager The {@link AudioManager}.
* @param streamType The {@link C.StreamType} to check.
* @return Whether the stream is muted.
*/
public static boolean isStreamMute(AudioManager audioManager, @C.StreamType int streamType) {
if (Util.SDK_INT >= 23) {
return audioManager.isStreamMute(streamType);
} else {
return getStreamVolume(audioManager, streamType) == 0;
}
}
private AudioManagerCompat() {}
}

View File

@ -20,9 +20,9 @@ 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.common.base.Objects;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Objects;
/**
* Interface for audio processors, which take audio data as input and transform it, potentially
@ -107,7 +107,7 @@ public interface AudioProcessor {
@Override
public int hashCode() {
return Objects.hashCode(sampleRate, channelCount, encoding);
return Objects.hash(sampleRate, channelCount, encoding);
}
}

Some files were not shown because too many files have changed in this diff Show More