Compare commits

...

789 commits

Author SHA1 Message Date
shahddaghash
76088cd6af Bump Media3 version to 1.5.1
PiperOrigin-RevId: 707576152
(cherry picked from commit c3b58f2434)
2024-12-18 09:26:54 -08:00
shahddaghash
7ae9ddf166 Update release notes for Media3 1.5.1 release
PiperOrigin-RevId: 707558817
(cherry picked from commit 896bd0d330)
2024-12-19 11:21:58 +00:00
tonihei
e4e59cd929 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
PiperOrigin-RevId: 707025950
(cherry picked from commit 71f82df57f)
2024-12-17 03:22:51 -08:00
tonihei
508a4258a3 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
(cherry picked from commit 3bce3af1a3)
2024-12-13 01:41:20 -08:00
Googler
52f9761796 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
(cherry picked from commit 3936c27b6d)
2024-12-11 10:56:06 -08:00
ibaker
de91ebc6ae 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
(cherry picked from commit 12546070ee)
2024-12-09 09:09:53 -08:00
rohks
5d9badcb50 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

PiperOrigin-RevId: 702707611
(cherry picked from commit e927d7b986)
2024-12-04 06:48:43 -08:00
Copybara-Service
121b79ae96 Merge pull request #1943 from DolbyLaboratories:dlb/ac4-ajoc/dev
PiperOrigin-RevId: 702281314
(cherry picked from commit e4993779db)
2024-12-03 04:04:52 -08:00
ibaker
fa9689ef9a Clarify CommandButton javadoc around iconResId and ICON_UNDEFINED
PiperOrigin-RevId: 701898620
(cherry picked from commit 77d33645cc)
2024-12-02 02:56:34 -08:00
Copybara-Service
f5bbb39e90 Merge pull request #1823 from MGaetan89:remove_outdated_sdk_check
PiperOrigin-RevId: 700706152
(cherry picked from commit bff5523bb6)
2024-11-27 08:16:02 -08:00
ibaker
fd0c04b3a0 Recommend ForwardingSimpleBasePlayer in ForwardingPlayer javadoc
Also add an explicit warning about how fiddly `ForwardingPlayer` can be
to use correctly.

PiperOrigin-RevId: 700698032
(cherry picked from commit 60133b0c7e)
2024-11-27 07:40:13 -08:00
ibaker
8a1e6e59b0 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
(cherry picked from commit 6cf3004d62)
2024-11-27 04:53:38 -08:00
ibaker
f91d5208b5 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
(cherry picked from commit 46578ee0a6)
2024-12-18 14:19:41 +00:00
ibaker
989c8f4fdb 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

PiperOrigin-RevId: 700269811
(cherry picked from commit f257e5511f)
2024-11-26 02:32:09 -08:00
ibaker
7abfa764e1 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
(cherry picked from commit 3eb36d67bd)
2024-11-26 01:29:40 -08:00
ibaker
f34abbb29f Add pixel aspect ratio to Format.toLogString
#cherrypick

PiperOrigin-RevId: 698770714
(cherry picked from commit 827966b7a4)
2024-12-17 18:24:31 +00:00
michaelkatz
8d791fd836 Rollback of 854566dbfe
PiperOrigin-RevId: 698730105
(cherry picked from commit 5282fe3125)
2024-12-17 18:24:29 +00:00
sheenachhabra
05921514b7 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
(cherry picked from commit 407bc4fec9)
2024-12-17 18:24:14 +00:00
Copybara-Service
6fbd3be9c5 Merge pull request #1277 from consp1racy:patch-1
PiperOrigin-RevId: 694095488
(cherry picked from commit 19d71266ec)
2024-12-17 18:24:14 +00:00
sheenachhabra
92166828e1 Make error messages unique in Boxes.java
PiperOrigin-RevId: 692920946
(cherry picked from commit c3e72a87e5)
2024-12-17 18:24:14 +00:00
sheenachhabra
0ceaa17bf5 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
(cherry picked from commit f181855c5e)
2024-12-17 18:24:14 +00:00
Shahd AbuDaghash
df887a9422 Merge branch 'release' into release-1.5.0 2024-11-25 14:47:51 +00:00
shahddaghash
cc8439db93 Bump Media3 version to 1.5.0
PiperOrigin-RevId: 698761734
(cherry picked from commit 73c4bb6e1f)
2024-11-21 06:44:25 -08:00
shahddaghash
46a5f0f9b2 Merge release notes for media3 1.5.0 stable release
PiperOrigin-RevId: 698713460
(cherry picked from commit e5110e6442)
2024-11-21 15:35:44 +00:00
shahddaghash
f63069e266 Move misplaced release note to Unreleased changes section
PiperOrigin-RevId: 698426838
(cherry picked from commit cf4488aa1f)
2024-11-21 15:20:23 +00:00
ibaker
8bab42334e Bump media3 version to 1.5.0-rc02
PiperOrigin-RevId: 696912494
(cherry picked from commit cbb8e2f1e6)
2024-11-19 11:11:51 +00:00
tianyifeng
737fdd8693 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
(cherry picked from commit 0143884cd7)
2024-11-19 11:11:51 +00:00
ibaker
fd02ee182c Release notes for 1.5.0-rc01
PiperOrigin-RevId: 696879276
(cherry picked from commit c50867c81d)
2024-11-19 11:11:48 +00:00
ibaker
ef90f501bf 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.

PiperOrigin-RevId: 696583702
(cherry picked from commit e6448f3498)
2024-11-19 11:04:53 +00:00
Copybara-Service
47f3aab231 Merge pull request #1265 from DolbyLaboratories:dlb/ac4-level4/dev_new2
PiperOrigin-RevId: 696157037
(cherry picked from commit 74611bbdc0)
2024-11-19 11:04:53 +00:00
tianyifeng
57d0721fd6 Resolve the memory leaks in demo short-form app
Issue: androidx/media#1839
PiperOrigin-RevId: 696080063
(cherry picked from commit c3d4722197)
2024-11-19 11:04:53 +00:00
ibaker
a46716c0e9 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.

Issue: androidx/media#1863
PiperOrigin-RevId: 695767247
(cherry picked from commit 19b38c83b6)
2024-11-19 11:04:52 +00:00
bachinger
f109a8167b Fix supportedCommands in MediaMetadata
#cherrypick

PiperOrigin-RevId: 695304782
(cherry picked from commit fa790bd73c)
2024-11-19 11:04:52 +00:00
ibaker
0e37bd08be 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
(cherry picked from commit 53953dd377)
2024-11-19 11:04:52 +00:00
tianyifeng
dba31108a3 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
(cherry picked from commit 2b54b1ebbe)
2024-11-19 11:04:52 +00:00
rohks
461a1fa037 Fix wrong class name in error message of MediaExtractorCompatTest
PiperOrigin-RevId: 693685232
(cherry picked from commit 3d51b36e99)
2024-11-19 11:04:52 +00:00
rohks
09be7b0b25 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
(cherry picked from commit 261ca326c5)
2024-11-19 11:04:52 +00:00
sheenachhabra
761cf4a001 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
(cherry picked from commit 31ece8cbd2)
2024-11-19 11:04:52 +00:00
ivanbuper
ca010231a8 Fix incorrect Media3 1.5.0-rc01 release notes
PiperOrigin-RevId: 697626185
(cherry picked from commit fff6e2e169)
2024-11-18 16:12:23 +00:00
tonihei
caf7c2b7f1 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
(cherry picked from commit 06718c5df3)
2024-11-05 13:10:08 +00:00
Copybara-Service
7839f420ab Merge pull request #1225 from Kekelic:support-for-parsing-rtsp-packets-with-header-extension
PiperOrigin-RevId: 692156233
(cherry picked from commit 4910b2cdc0)
2024-11-05 13:10:08 +00:00
tonihei
aad746b05c Annotate parameters in RepeatModeUtil
PiperOrigin-RevId: 692129684
(cherry picked from commit 544d7aa2dc)
2024-11-05 13:10:08 +00:00
rohks
664dc6e482 Add missing DefaultRenderersFactoryTest for decoder extensions
Added `DefaultRenderersFactoryTest` for `IAMF`, `AV1`, and `MIDI` decoder extensions.

PiperOrigin-RevId: 691381816
(cherry picked from commit 27de9f02e0)
2024-11-05 13:10:08 +00:00
tonihei
4b6e886ad2 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
(cherry picked from commit 7c0cffdca8)
2024-11-05 13:10:06 +00:00
rohks
7ec61f13ce 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
(cherry picked from commit b1f2efd218)
2024-11-05 13:09:10 +00:00
ibaker
c44d509ea8 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
(cherry picked from commit 757f223d8a)
2024-11-05 13:09:10 +00:00
ibaker
8ca80a6b71 Remove some un-needed proguard-rules.txt symlinks
PiperOrigin-RevId: 689344803
(cherry picked from commit b36de302f7)
2024-11-05 13:09:10 +00:00
ibaker
26cbf9444d 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
(cherry picked from commit 4a406be1bf)
2024-11-05 13:09:10 +00:00
Copybara-Service
08e55d81ef Merge pull request #1794 from stevemayhew:p-fix-ntp-time-update-main
PiperOrigin-RevId: 689121191
(cherry picked from commit b5615d5e91)
2024-11-05 13:08:52 +00:00
tonihei
a44079b516 Removed unused constructor
PiperOrigin-RevId: 688960856
(cherry picked from commit 21526588be)
2024-11-05 12:13:17 +00:00
tonihei
bc7c901969 Suppress not-applicable lint warning
PiperOrigin-RevId: 688948857
(cherry picked from commit dfb7636138)
2024-11-05 12:13:17 +00:00
ibaker
fbbe48cd47 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
(cherry picked from commit 7b66209bca)
2024-11-05 12:13:17 +00:00
rohks
a03bd8248c 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
(cherry picked from commit e677c8dccd)
2024-11-05 12:13:17 +00:00
bachinger
57f0c0d368 Fix flakiness of MediaBrowserListenerWithMediaBrowserServiceCompatTest
PiperOrigin-RevId: 688870397
(cherry picked from commit 0038dda3c3)
2024-11-05 12:13:17 +00:00
ibaker
70c7ee2e0c Use Guava's HttpHeaders consistently in HTTP testing machinery
PiperOrigin-RevId: 688576776
(cherry picked from commit cabc541a6f)
2024-11-05 12:13:17 +00:00
jbibik
e6849e082c 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
(cherry picked from commit ee4f0c40bc)
2024-11-05 12:13:16 +00:00
ibaker
8fdb233a7d DataSourceContractTest: Add expected response headers
PiperOrigin-RevId: 688556440
(cherry picked from commit 219565c15e)
2024-11-05 12:13:16 +00:00
tonihei
b68d455b5e 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
(cherry picked from commit d9ca3c734a)
2024-11-05 12:13:16 +00:00
rohks
8304c26e08 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
(cherry picked from commit 0ecd35e24c)
2024-11-05 12:13:16 +00:00
ibaker
6611187316 Test ResolvingDataSource resolveReportedUri functionality
PiperOrigin-RevId: 688102934
(cherry picked from commit 40cd64ab19)
2024-11-05 12:13:16 +00:00
ibaker
ab4dff7530 DataSourceContractTest: Add tests for resolved vs original URI
PiperOrigin-RevId: 688076205
(cherry picked from commit 74bbd7727d)
2024-11-05 12:13:04 +00:00
Copybara-Service
6f42f36c05 Merge pull request #1742 from colinkho:trackselection-playwhenready
PiperOrigin-RevId: 688050467
(cherry picked from commit f2ecca3b6a)
2024-11-05 12:07:18 +00:00
ibaker
eb2ef4d14b Fix some markdown-in-javadoc
PiperOrigin-RevId: 687354846
(cherry picked from commit 49337d9667)
2024-11-05 12:07:18 +00:00
tonihei
2bf0138590 Let FakeTrackSelection extend BaseTrackSelection
This fixes a bug in getIndexInTrackGroup

PiperOrigin-RevId: 687336621
(cherry picked from commit ceac959c29)
2024-11-05 12:07:18 +00:00
ibaker
b2b30249c6 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
(cherry picked from commit b64bf88272)
2024-11-05 12:07:18 +00:00
ibaker
6537a63f2b Release the Surface at the end of every playback test
Without this an error is logged which obfuscates real test failures.

PiperOrigin-RevId: 687302953
(cherry picked from commit 2f01900e83)
2024-11-05 12:07:17 +00:00
tonihei
e6c24087f8 Remove unneeded @Nullable from PlayerWrapper.legacyExtras
All values passed in via the constructor or setLegacyExtras
are guaranteed to be non-null.

PiperOrigin-RevId: 687245475
(cherry picked from commit 5fffe03312)
2024-11-05 12:07:17 +00:00
bachinger
88a78ade04 Accept resource URIs for command buttons
PiperOrigin-RevId: 687239119
(cherry picked from commit 3e556a4b53)
2024-11-05 12:07:17 +00:00
ibaker
ed6b822ff5 Remove unused Surface from DashPlaybackTest
Follow-up to cb90bb38ee

PiperOrigin-RevId: 687235908
(cherry picked from commit 13d6f37a84)
2024-11-05 12:07:17 +00:00
ibaker
9c15ea5436 Add @ForOverride annotation to DataSourceContractTest
PiperOrigin-RevId: 687223500
(cherry picked from commit 3ba2fa6c07)
2024-11-05 12:07:17 +00:00
bachinger
332aa7d34b Look for METADATA_KEY_ART_URI for legacy media items
PiperOrigin-RevId: 687222830
(cherry picked from commit 08e6f30b68)
2024-11-05 12:07:17 +00:00
ivanbuper
0f739bc5fa Bump Media3 to 1.5.0-rc01
#cherrypick

PiperOrigin-RevId: 692221696
(cherry picked from commit 0b1695124b)
2024-11-01 17:48:21 +00:00
ivanbuper
a8c34ca164 Prepare RELEASENOTES.md for Media3 1.5.0-rc01 release
This change also fixes two notes added incorrectly onto the previous
beta01 release section.

PiperOrigin-RevId: 692169335
(cherry picked from commit 38e1efafc2)
2024-11-01 17:47:49 +00:00
ivanbuper
86c9e0f9f7 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
(cherry picked from commit f991e1f023)
2024-11-01 12:10:54 +00:00
rohks
fc32b7f281 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
(cherry picked from commit 2b27e33784)
2024-10-31 14:41:24 +00:00
ibaker
b6baeb6cb0 Support CEA-608 subtitles in Dolby Vision
Issue: androidx/media#1820

#cherrypick

PiperOrigin-RevId: 691378476
(cherry picked from commit 27371db225)
2024-10-31 14:41:24 +00:00
ibaker
bf15b93b60 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
(cherry picked from commit 08a141328d)
2024-10-31 14:41:24 +00:00
rohks
358a3c62fa 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
(cherry picked from commit 129cf8ea72)
2024-10-31 14:41:24 +00:00
rohks
ad09a02810 Fix .gitignore paths for extensions
#cherrypick

PiperOrigin-RevId: 690534708
(cherry picked from commit 84ab67cca3)
2024-10-31 14:41:23 +00:00
ibaker
8d8a5211dd 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
(cherry picked from commit 39c734963f)
2024-10-31 14:41:23 +00:00
rohks
895c69c08f 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
(cherry picked from commit 5f99955f31)
2024-10-31 14:41:23 +00:00
rohks
6d0ef8bfe7 Add support for identifying h263 box in MP4 files for H.263 video
Issue: androidx/media#1821

#cherrypick

PiperOrigin-RevId: 688570141
(cherry picked from commit 7545a8929b)
2024-10-31 14:41:23 +00:00
rohks
cc947dc690 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

PiperOrigin-RevId: 688103949
(cherry picked from commit 457bc55a4d)
2024-10-31 14:40:55 +00:00
shahddaghash
7da2161a7b Bump Media3 version to 1.5.0-beta01
PiperOrigin-RevId: 688079507
(cherry picked from commit 5088e87195)
2024-10-21 11:30:12 +00:00
shahddaghash
baadadc07a Fix dropped full stop in release notes
PiperOrigin-RevId: 687252101
(cherry picked from commit 709246ac6a)
2024-10-18 13:59:05 +00:00
shahddaghash
7fee7eab03 Update release notes for Media3 1.5.0-beta01 release
PiperOrigin-RevId: 687243739
(cherry picked from commit 4d711050bb)
2024-10-18 13:58:43 +00:00
tonihei
627b7a3e56 Add media button preferences
This adds the API surface for media button preferences in MediaSession
and MediaController. It closely mimics the existing custom layout
infrastructure (which it will replace eventually).

Compat logic:
 - Session:
     - When converting to platform custom actions, prefer to use
       media button preferences if both are set.
     - When connecting to an older Media3 controller, send the
       media button preferences as custom layout instead.
 - Controller:
     - Maintain a single resolved media button preferences field.
     - For Media3 controller receiving both values, prefer media
       button preferences over custom layouts.

Missing functionality:
 - The conversion from/to custom layout and platform custom actions
   does not take the slot preferences into account yet.

PiperOrigin-RevId: 686950100
2024-10-17 09:54:36 -07:00
tonihei
e851a1419d Add slots to CommandButton
These allow to define preferences for where a button should be
displayed.

PiperOrigin-RevId: 686938126
2024-10-17 09:16:44 -07:00
ibaker
8cb558e875 Add HALF_UP rounding TODO to scaleLargeTimestamp (and its usages)
PiperOrigin-RevId: 686921743
2024-10-17 08:25:03 -07:00
ibaker
64e0397811 De-flake new test for ProgressiveMediaSource.suppressPrepareError
This test was added in b3290eff10

#cherrypick

PiperOrigin-RevId: 686918104
2024-10-17 08:11:01 -07:00
ibaker
49dec5db8b Ignore renderer errors from text/metadata tracks
Before this change:

* With legacy subtitle decoding (at render time), load errors (e.g. HTTP
  404) would result playback completely failing, while parse errors
  (e.g. invalid  WebVTT data) would be silently ignored, so playback
  would continue without subtitles.
* With new subtitle decoding (at extraction time), both load and parse
  errors would result in playback completely failing.

This change means that now neither load nor parse errors in text or
metadata tracks stop playback from continuing. Instead the error'd track
is disabled until the end of the current period.

With new subtitle decoding, both load and parse errors happen during
loading/extraction, and so are emitted to the app via
`MediaSourceEventListener.onLoadError` and
`AnalyticsListener.onLoadError`. With legacy subtitle decoding, only
load errors are emitted via these listeners and parsing errors continue
to be silently ignored.

Issue: androidx/media#1722
PiperOrigin-RevId: 686902979
2024-10-17 07:15:22 -07:00
ibaker
191bc094a5 Propagate events from secondary children in MergingMediaSource
These events are always reported with the primary child period ID,
because this is the same ID used in the parent `MergingMediaSource`'s
Timeline.

This ensures that e.g. loading errors from sideloaded subtitles (which
uses `MergingMediaSource`) are now reported via
`AnalyticsListener.onLoadError`.

It results in non-error events being reported from these children too,
which will result in more `onLoadStarted` and `onLoadCompleted` events
being reported (one for each child).

Issue: androidx/media#1722
PiperOrigin-RevId: 686901439
2024-10-17 07:10:59 -07:00
ibaker
b3290eff10 Allow ProgressiveMediaSource to optionally suppress prepare errors
Use this for sideloaded subtitles, so preparation can still complete
despite an error from e.g. `DataSource.open`. In this case, no subtitle
tracks will be emitted.

Issue: androidx/media#1722
PiperOrigin-RevId: 686888588
2024-10-17 06:20:11 -07:00
ivanbuper
b78395b325 Extract method for calculating expected accumulated truncation error
This is prework for implementing
`RandomParameterizedSpeedChangingAudioProcessorTest`, which depends on
Sonic's resampling algorithm's behaviour.

This is a non-functional refactor.

PiperOrigin-RevId: 686874593
2024-10-17 05:24:04 -07:00
michaelkatz
2a1e71b203 Reduce needless loading period resets in clipping mediasource playlists
When a `SampleQueue` is prepared prior to playback, the start position may be less than the timestamp of the first sample in the queue and still be valid. This scenario can come about with specific clipping values and if all samples are sync samples. Currently, with `ClippingMediaPeriods` around `ProgressiveMediaPeriods`, if the `SampleQueue` has already been reset through the seekTo operation in `onPrepared`, then in the aforementioned scenario the seekTo operation in `handleDiscontinuity` will remove all samples and reset the loading periods unnecessarily.

The solution is that if the `ProgressiveMediaPeriod` has already handled a seekTo operation for the same position and the sample queue has not been read yet, then loading does not need to be reset.

The tests in `MergingPlaylistPlaybackTest` were specifically causing this behavior through its setup of `MergingMediaSources` around clipped `FilteringMediaSources`. Since the video content was not 'all sync samples', there would always be a discontinuity to handle and the audio content being 'all sync samples' would start with samples post start time.

These changes also remove the flakiness from the `MergingPlaylistPlaybackTest`.

PiperOrigin-RevId: 686858444
2024-10-17 04:20:50 -07:00
bachinger
8681109d1f Use SessionError error codes to make lint happy
#cherrypick

PiperOrigin-RevId: 686849343
2024-10-17 03:43:59 -07:00
kimvde
38c27d45f1 Make VideoGraph and VideoFrameProcessor listener methods optional
PiperOrigin-RevId: 686833280
2024-10-17 02:38:03 -07:00
ibaker
d2ccace75c Remove allocator param from MediaSourceTestRunner constructor
Currently every test in the library passes `null` here, which seems to
end up being passed into non-null places in the library. This change
removes the parameter and instead initializes the field to a
`DefaultAllocator` instance in the same way that `DefaultLoadControl`
creates one.

PiperOrigin-RevId: 686823273
2024-10-17 02:00:08 -07:00
shahddaghash
2e61c93dba Remove deprecated DefaultEncoderFactory constructors.
Use `DefaultEncoderFactory.Builder` instead.

PiperOrigin-RevId: 686821388
2024-10-17 01:53:23 -07:00
kimvde
31ef7ff088 Clarify Javadoc of EditedMediaItem.Builder.setDurationUs
PiperOrigin-RevId: 686521901
2024-10-16 08:48:10 -07:00
sheenachhabra
d3b7f7e114 Add VP9 test to Mp4MuxerEndToEndNonParameterizedAndroidTest
The test is currently disabled because the produced dump file
is different on different SDK versions.

PiperOrigin-RevId: 686518799
2024-10-16 08:37:41 -07:00
kimvde
363f71357b Handle output size changes inside DefaultVideoSink
PiperOrigin-RevId: 686507112
2024-10-16 07:59:35 -07:00
ibaker
6afebf4c7d Fix typo in release notes
#cherrypick

PiperOrigin-RevId: 686477355
2024-10-16 05:59:00 -07:00
ibaker
e3f813cf0b Attach a Surface in HlsPlaybackTest.cmcdEnabled_withInitSegment
This ensures that the buffers are correctly marked as `rendered = true`
(and therefore `renderered = false` is meaningful when it's present).

This was accidentally missed in 387153fcf2

PiperOrigin-RevId: 686474869
2024-10-16 05:46:22 -07:00
rohks
692f1c78b3 Add ForwardingTrackOutput implementation
This allows users to extend and customize specific methods of the `TrackOutput` implementation while inheriting default behaviors for others.

PiperOrigin-RevId: 686454360
2024-10-16 04:20:33 -07:00
bachinger
075f311200 Cache children when subscribing
Childrens returned by the legacy service when a Media3
browser connects are cached and returned with the first
`getChildren` call in case the same `paranetid` is
requested.

In any other case the cache is immediately cleared.

#cherrypick

PiperOrigin-RevId: 686157511
2024-10-15 10:40:29 -07:00
rohks
91c56335ef Handle out-of-order frames in endIndices for MP4 with edit list
Updated logic to walk forward in the timestamps array to include all frames within the valid edit duration, accounting for out-of-order frames. This ensures that no frames with timestamps less than `editMediaTime` + `editDuration` are incorrectly excluded.

Issue: androidx/media#1797
PiperOrigin-RevId: 686075680
2024-10-15 06:09:15 -07:00
kimvde
9adb3aaf41 Transformer: add an entry point to disable automatic rotation
PiperOrigin-RevId: 686067527
2024-10-15 05:40:41 -07:00
sheenachhabra
643e16ca8f Handle invalid language codes in Boxes.java
When an invalid language code is give, write default value
instead of throwing.

This behaviour aligns with `MediaMuxer`.

PiperOrigin-RevId: 686066088
2024-10-15 05:36:04 -07:00
bachinger
5a827829b0 Call onChildrenChanged to close the legacy subscription error path
When receiving an error from a legacy `MediaBrowserService` after
having successfully subscribed to a given `parentId`, the callback
needs to be asked to load the children again to receive an error
from the service.

Before this change such an error was dropped as a no-op by Media3
without the `MediaBrowser` giving a chance to react on such an error
being sent by the legacy service.

#cherrypick

PiperOrigin-RevId: 686052969
2024-10-15 04:38:34 -07:00
sheenachhabra
407bd49ed5 Skip audio encoding bitrate setting test on API <= 23
The encoder output format on API 23 does not seem to contain bitrate,
hence the test fails.

PiperOrigin-RevId: 686047480
2024-10-15 04:15:38 -07:00
ibaker
cb90bb38ee Remove unused CapturingRenderersFactory from DASH playback test
The `CapturingRenderersFactory` is only needed if the output is
asserted on, e.g. by using `PlaybackOutput` and
`DumpFileAsserts.assertOutput()`.

PiperOrigin-RevId: 686046545
2024-10-15 04:11:42 -07:00
sheenachhabra
0b47e93df5 Update FragmentedMp4Muxer and Mp4Muxerdocumentation to include VP9
PiperOrigin-RevId: 686041467
2024-10-15 03:53:08 -07:00
Googler
0100f1d902 Boxes: Add edit list box.
PiperOrigin-RevId: 685974308
2024-10-14 23:26:08 -07:00
Copybara-Service
4df9d4e146 Merge pull request #1792 from DolbyLaboratories:dlb/elst-handling/dev
PiperOrigin-RevId: 685851466
2024-10-14 15:20:17 -07:00
kimvde
1084c9ea98 Implement DefaultVideoSink.isReady
PiperOrigin-RevId: 685720088
2024-10-14 08:44:51 -07:00
claincly
adb35ee7c4 Reword javadoc
The old javadoc is, IMO, quite hard to understand, so I simplified it a bit,
and added one example.

PiperOrigin-RevId: 685701916
2024-10-14 07:39:53 -07:00
ibaker
9a23d9a611 Deprecate HlsExtractorFactory.DEFAULT
`HlsExtractorFactory` instances are mutable, so storing one in a static
field is not safe, and can lead to state accidentally/surprisingly being
shared between different player instances.

PiperOrigin-RevId: 685701062
2024-10-14 07:36:10 -07:00
ibaker
4a40fa6451 Update StatsDataSource.lastOpenedUri & responseHeader in finally
This ensures these values are still updated even if the delegate
`DataSource.open()` throws an exception (e.g. an HTTP 404).

PiperOrigin-RevId: 685687810
2024-10-14 06:38:56 -07:00
kimvde
37cd008c01 Remove unnecessary method in VideoFrameRenderControl
PiperOrigin-RevId: 685681192
2024-10-14 06:10:33 -07:00
michaelkatz
1c4ee06ad6 Remove Renderer[] from LoadControl.onTracksSelected
The `DefaultLoadControl` implementation of onTracksSelected only utilizes the `Renderer[]` parameter for use in stream type, of which it can collect from the `ExoTrackSelection[]` parameter.

PiperOrigin-RevId: 685677726
2024-10-14 05:56:24 -07:00
claincly
17c0ff8ba8 Log warnings when Transformer sees unsupported track type
PiperOrigin-RevId: 685649866
2024-10-14 03:54:52 -07:00
shahddaghash
5acb483222 Add export settings to demo-composition
Added UI and logic implementation for the following export settings:
* Output audio MIME type
* Output video MIME type
* Enable debug tracing
* Use Media3 Muxer
* Produce fragmented MP4

The settings are shown in a dialog when `Export` button is clicked.

PiperOrigin-RevId: 685648147
2024-10-14 03:46:05 -07:00
shahddaghash
45d2bc39ae Remove SDK checks for H265 and AV1 in Transformer Demo
For simplicity, the following SDK checks when adding supported video codecs were removed from Transformer demo.
1. Adding H265 for API >= 24.
2. Adding AV1 for API >= 34.

PiperOrigin-RevId: 685634851
2024-10-14 02:45:42 -07:00
kimvde
638eae44ab Remove unnecessary method in VideoFrameRenderControl
PiperOrigin-RevId: 685632001
2024-10-14 02:34:49 -07:00
kimvde
5eeedeacc6 Implement DefaultVideoSink.flush()
This is part of the effort to delegate the rendering of the VideoGraph
output frames to a DefaultVideoSink.

PiperOrigin-RevId: 685622019
2024-10-14 01:53:52 -07:00
kimvde
7dbacdb011 PlaybackVideoGraphWrapper: simplify flushing logic
PiperOrigin-RevId: 685610127
2024-10-14 01:10:07 -07:00
ivanbuper
7e023f915b Use BigDecimal as speed parameter for RandomParameterizedSonicTest
This change simplifies conversions to `BigDecimal` and rounding to a set
number of decimal places.

PiperOrigin-RevId: 684890287
2024-10-11 11:00:44 -07:00
tianyifeng
98dc7f2def Add DefaultPreloadManager.Builder
The `DefaultPreloadManager.Builder` is able to build the `DefaultPreloadManager` and `ExoPlayer` instances with the consistently shared configurations. Apps can:

* Simply setup the `DefaultPreloadManager` and `ExoPlayer` with all default configurations via `build()` and `buildExoPlayer()`;
* Or customize the shared configurations by the setters on `DefaultPreloadManager.Builder` and setup via `build()` and `buildExoPlayer()`;
* Or customize the player-only configurations for `ExoPlayer` via `buildExoPlayer(ExoPlayer.Builder)`.

PiperOrigin-RevId: 684852808
2024-10-11 08:55:25 -07:00
ivanbuper
337e59e733 Move util methods in SpeedChangingAudioProcessorto Util
This CL is prework for implementing
`RandomParameterizedSpeedChangingAudioProcessorTest`, which will build
on logic present in `RandomParameterizedSonicTest`.

This is a non-functional change.

PiperOrigin-RevId: 684838017
2024-10-11 08:02:40 -07:00
ivanbuper
984b0bb31a Avoid dropped output frames on SpeedChangingAudioProcessor
Inconsistent rounding modes between `currentTimeUs` and
`bytesUntilNextSpeedChange` would cause `SpeedChangingAudioProcessor`
to miss calling `queueEndOfStream()` on `SonicAudioProcessor` on a speed
change, and thus the final output samples of that `SonicAudioProcessor`
"configuration" would be missed.

This change is also a partial revert of 971486f5f9, which fixed a hang
of `SpeedChangingAudioProcessor`, but introduced the dropped output
frames issue fixed in this CL (see b/372203420). To avoid reintroducing
the hang, we are now ignoring any mid-sample speed changes and will only
apply speed changes that are effective at a whole sample position.

PiperOrigin-RevId: 684824218
2024-10-11 07:01:59 -07:00
tonihei
73f97c0371 Allow AudioTrack to be provided by a customizable provider
PiperOrigin-RevId: 684800579
2024-10-11 05:23:07 -07:00
rohks
ad0493b90f Re-enable disabled tests in FlacExtractorSeekTest
PiperOrigin-RevId: 684790556
2024-10-11 04:38:25 -07:00
sheenachhabra
a0ccd46653 Skip AACObjectHE encoding on faulty devices
These devices claim to have the AACObjectHE profile but the profile never gets applied.

PiperOrigin-RevId: 684786157
2024-10-11 04:18:52 -07:00
Copybara-Service
019fe0589f Merge pull request #1754 from colinkho:loader-plumbing
PiperOrigin-RevId: 684781854
2024-10-11 04:03:09 -07:00
rohks
c78abaac3f Simplify Flac extension build process
Removed `Android.mk` and `Application.mk`, allowing `CMake` to run directly from the build.gradle file. Users no longer need to check out `NDK` or depend on it, simplifying the usage of the Flac extension.

Also fixed a copy-pasted comment in `CMakeLists.txt` of Opus and IAMF.

PiperOrigin-RevId: 684769561
2024-10-11 03:09:32 -07:00
tonihei
b27cbe60b9 Add release callback and generic class to handle type casting 2024-10-11 11:00:45 +01:00
rohks
a2eda3348b Simplify Opus extension build process
Removed `Android.mk` and `Application.mk`, allowing `CMake` to run directly from the build.gradle file. Users no longer need to check out `NDK` or depend on it, simplifying the usage of the Opus extension.

PiperOrigin-RevId: 684489927
2024-10-10 10:23:26 -07:00
rohks
2640ebd58f Add 16 KB page support for decoder extensions on Android 15
We need to rebuild any native components of the app to prevent crashes on devices with 16 KB page support.

Tested on a device that supports 16 KB pages and runs Android 15, as well as on older Android devices.

Issue: androidx/media#1685
PiperOrigin-RevId: 684488244
2024-10-10 10:18:20 -07:00
rohks
6acddfeee6 Simplify IAMF extension build process
Removed `Android.mk` and `Application.mk`, allowing `CMake` to run directly from the `build.gradle` file. Users no longer need to check out `NDK` or depend on it, simplifying the usage of the IAMF extension.

PiperOrigin-RevId: 684471874
2024-10-10 09:32:25 -07:00
sheenachhabra
1729e11159 Fix version and flags in the ctts box
The version and flags are stored in a single integer,
with the version in the higher 8 bits and the flags in
the lower 24 bits. The version should be 1 and the
flags should be 0.

Surprisingly the incorrect value was ignored by many
players and hence the bug was never caught.
With the bug, the video does not play on
`Samsung Galaxy S22 Ultra` and works well
after fixing the bug.

PiperOrigin-RevId: 684433371
2024-10-10 07:19:24 -07:00
ivanbuper
3818e103e6 Rename timeUs to currentTimeUs
This is a non-functional refactor.

PiperOrigin-RevId: 684408479
2024-10-10 05:36:44 -07:00
bachinger
cbc0ee369f Use connection hints when connecting to MediaBrowserService
Minor improvement to allow an Media3 browser to pass extras
when connecting the initial browser in `MediaControllerImplLegacy`.
Before this change an empty bundle was sent. After this change
the connection hints of the `Media3 browser is used as root hints
of the initial browser that connects when the Media3 browser is
built in `MediaBrowser.buildAsync`.

#cherrypick

PiperOrigin-RevId: 684372552
2024-10-10 03:11:52 -07:00
ibaker
b6d0540059 Use scaleLargeTimestamp in TimestampAdjuster
This helps avoid overflows in intermediate calculations.

Verified the value in the test using `BigInteger`:

```
jshell> BigInteger.valueOf(1L << 52).multiply(BigInteger.valueOf(90000)).divide(BigInteger.valueOf(1000000))
$3 ==> 405323966463344
```

Issue: androidx/media#1763

#cherrypick

PiperOrigin-RevId: 684028178
2024-10-09 07:27:53 -07:00
ibaker
2c46cea088 Use RoundingMode.DOWN in Util.scaleLargeTimestamp and friends
The implementation of these methods was updated from direct java integer
arithmetic in 885ddb167e.
In this change, `RoundingMode.FLOOR` was used to try and maintain
compatibility with java integer division. This was incorrect, because
java integer division uses `DOWN` (i.e. towards zero), rather than
`FLOOR` (i.e. towards negative infinity) semantics.

This change fixes the compatibility.

The dump file changes in this CL relate to tests that exercise edit
list behaviour. This involves manipulating negative timestamps, which
explains why they are impacted by this change.

PiperOrigin-RevId: 684013175
2024-10-09 06:24:26 -07:00
shahddaghash
d5baa4ce59 Add HDR & Resolution Height settings to demo-composition
Added UI and logic implementation for HDR mode and Resolution Height to be used as settings for both previewing and exporting.

PiperOrigin-RevId: 684011852
2024-10-09 06:19:28 -07:00
rohks
c744fe9f8f Recognize IAMF format and enhance channel count constraints
- Updated `DefaultTrackSelector.SpatializerWrapperV32.canBeSpatialized` to handle IAMF format.
- Modified `isAudioFormatWithinAudioChannelCountConstraints` to check for `NO_VALUE` of `channelCount` to improve readability.

Note: `DefaultTrackSelector.SpatializerWrapperV32.canBeSpatialized` is not triggered for the IAMF format due to the unset channel count (`Format.NO_VALUE`). The update ensures completeness.

#cherrypick

PiperOrigin-RevId: 684003980
2024-10-09 05:50:58 -07:00
tonihei
15a6906877 Formatting and javadoc 2024-10-09 13:50:30 +01:00
Colin Kho
2f6d8bf5ba Remove unused import 2024-10-09 13:50:29 +01:00
Colin Kho
10bb2e1501 Allow Injection of custom Executor in ProgressiveMediaSource 2024-10-09 13:50:29 +01:00
Colin Kho
ea837e494b Remove unused Executors import 2024-10-09 13:50:29 +01:00
Colin Kho
f7a1b19001 Allow custom Executor to be supplied to ChunkSampleStream 2024-10-09 13:50:29 +01:00
rohks
4df7216bc0 Prioritize object-based audio in DefaultTrackSelector
Object-based audio is more efficient and flexible than channel-based audio, supporting a broader range of devices. This update makes `DefaultTrackSelector` prefer object-based audio when other factors are equal, ensuring its use whenever possible.

#cherrypick

PiperOrigin-RevId: 683990051
2024-10-09 04:55:36 -07:00
Copybara-Service
5e5d486ef1 Merge pull request #1618 from khouzam:main
PiperOrigin-RevId: 683973733
2024-10-09 03:51:10 -07:00
microkatz
3f44f9a898 Cosmetic changes 2024-10-09 09:41:54 +00:00
shahddaghash
12f34c337e Connect produceFragmentedMp4CheckBox to useMedia3Muxer
Connected `produceFragmentedMp4CheckBox` to `useMedia3Muxer` checkbox since producing fragmented MP4 is contingent on using Media3 Muxer.

1. If both were unchecked, `useMedia3Muxer` gets checked when `produceFragmentedMp4CheckBox` is checked.
2. If both were checked, `produceFragmentedMp4CheckBox` gets unchecked when `useMedia3Muxer` is unchecked.

PiperOrigin-RevId: 683948863
2024-10-09 02:15:03 -07:00
ybai001
3f4a16555d Bugfix: segment_duration and media_time use different unit 2024-10-09 16:54:59 +08:00
ybai001
d73115a927
Merge pull request #12 from androidx/main
Merge from androidx/media main branch
2024-10-09 16:51:13 +08:00
kimvde
e234076fdc DefaultVideoSink: implement set/clearOutputSurfaceInfo
This is part of the effort to handle rendering of the frames output by
the VideoGraph in a DefaultVideoSink

PiperOrigin-RevId: 683939818
2024-10-09 01:47:38 -07:00
bachinger
c4ff07e229 Don't advertise commands that are not available
When calling `MediaController.getCommandButtonForMediaItem(MediaItem)`
command buttons with custom commands that are not available
shouldn't be advertised to the controller when connected to
a Media3 session.

In contrast, when connected to a legacy session, available commands
are not enforced when advertising commands. Similarly, when sending
a custom commands that is referenced by a command button for media
items, sending is permitted without the command being available.

This is required because available commands match to custom actions
in `PlaybackStateCompat` of the legacy session. Adding commands for
media items to custom action of the `PlaybackStateCompat` would
interfere with other use cases.

Issue: androidx/media#1474
#cherrypick
PiperOrigin-RevId: 683717723
2024-10-08 12:14:34 -07:00
ibaker
546d7da2f2 Fix Fmp4Extractor.init to use text transcoding ExtractorOutput
This was missed in da724c8cc4

I tried to write a test for this, but got stuck crafting valid test
data. I was able to create a new fragmented MP4 file containing only a
TTML track:

```shell
$ MP4Box -add simple.ttml -frag 2000 sample_fragmented_ttml.mp4
```

Then I tried naively removing the `ftyp` and `moov` boxes with a hex
editor, but using this in `FragmentedMp4ExtractorNoSniffingTest` gave
me an `EOFException` that I didn't get to the root cause of.

Issue: androidx/media#1779

#cherrypick

PiperOrigin-RevId: 683667850
2024-10-08 10:11:18 -07:00
rohks
72ab282c0d Ignore channelCount and sampleRate values read from iamf box
As per the IAMF spec (https://aomediacodec.github.io/iamf/#iasampleentry-section), `channelCount` and `sampleRate` SHALL be set to `0` and ignored.

#cherrypick

PiperOrigin-RevId: 683648991
2024-10-08 09:18:44 -07:00
microkatz
ae363671b5 Moved supportsFormat functional code to private method 2024-10-08 15:55:39 +00:00
rohks
8f82a15e48 Return early when audioManager is null
Also declare and use `AudioFormat` directly instead of building it later.

PiperOrigin-RevId: 683637116
2024-10-08 08:42:56 -07:00
microkatz
34f50adcd2 Format with google-java-format 2024-10-08 14:43:55 +00:00
Gilles Khouzam
a772e1525c Add a static rendererSupportsFormat method for MCVR
This refactors the `supportsFormat` method on the MediaCodecVideoRenderer to be a static method that can be called from external components to determine if a video format is supported by the device renderers. Since `context` is the only component that is part of the MCVR, it can easily be obtained externally and passed to the method.

This can help optimize format support decisions ahead of time of a player having been instantiated, such as removing unsupported representations from a dash manifest.

This could also be done for the MCAR but would also require passing in the AudioSink and isn't required at this time.
2024-10-08 14:43:55 +00:00
tianyifeng
fd48dd9ce8 Add PlaybackLooperProvider and make it injectable for ExoPlayer
PiperOrigin-RevId: 683607682
2024-10-08 07:07:13 -07:00
ibaker
abfeea518e Ensure consistent ExtractorOutput usage in WebvttExtractor
This change is a no-op, because
`SubtitleTranscodingExtractorOutput.seekMap` forwards directly to the
delegate implementation, but it seems clearer to always use the
wrapper.

Also remove a no-op assignment in `MatroskaExtractor`.

This is a follow-up to Issue: androidx/media#1779 where I manually checked every
implementation of `Extractor.init` for a similar mistake.

#cherrypick

PiperOrigin-RevId: 683607090
2024-10-08 07:04:50 -07:00
kimvde
5f935ef22e DefaultVideoSink: implement initialize() and isInitialized()
The implementation is straightforward as there is nothing to init.

PiperOrigin-RevId: 683596706
2024-10-08 06:25:47 -07:00
Copybara-Service
62864d5475 Merge pull request #1651 from colinkho:mp-cl
PiperOrigin-RevId: 683548885
2024-10-08 03:28:59 -07:00
kimvde
52f08d46c2 Add motion photo support to Transformer
PiperOrigin-RevId: 683540867
2024-10-08 02:57:52 -07:00
tonihei
bd192c17ca Restrict CommandButton.iconUri to content Uris
These Uris are not widely supported yet and were only meant to be
used with content Uris. Restricting this more tightly allows
controllers to use these Uris more easily as they have a stricter
guarentee on what it's needed to load these Uris. Media session
apps with different types of Uris can convert them by setting up
a ContentProvider if needed.

Issue: androidx/media#1783
PiperOrigin-RevId: 683539747
2024-10-08 02:53:49 -07:00
kimvde
7c9fede3ad Add DefaultVideoSink and implement straightforward methods
Other methods will be implemented in follow-up CLs

PiperOrigin-RevId: 683538245
2024-10-08 02:49:44 -07:00
tonihei
bf88128383 Formatting and additional call for preloading period 2024-10-08 10:15:04 +01:00
Colin Kho
7b0f83690c Add LoadingInfo as a parameter to continueLoading on MediaPeriodHolder 2024-10-08 10:15:04 +01:00
kimvde
8d0b82dfc3 PlaybackVideoGraphWrapper: rename VideoSinkImpl to InputVideoSink
PlaybackVideoGraphWrapper will soon contain an input and an output video
sink, as the rendering of the VideoGraph output frames will be handled
by a DefaultVideoSink instance.

PiperOrigin-RevId: 683167795
2024-10-07 07:02:12 -07:00
ivanbuper
af922fbcb0 Fix truncation error accumulation on Sonic's time stretching algorithm
This CL also fixes EOS handling to account for not-yet-copied samples in
`remainingInputToCopyFrameCount`, which would throw off the final output
sample count calculation.

For testing, we allow a tolerance of 0.000017% drift between expected
and actual number of output samples. The value was obtained from running
100 iterations of `timeStretching_returnsExpectedNumberOfSamples()` and
calculating the average delta percentage between expected and actual
number of output samples. Roughly, this means a tolerance of 40 samples
on a 90 min mono stream @48KHz.

PiperOrigin-RevId: 683133461
2024-10-07 04:59:54 -07:00
tonihei
f7af58951d Allow signed TTML region origins
The origin may be negative if the subtitle starts off-screen
but extends into the screen for example.

TTML1 defines `tts:origin` in terms of
[`length`](https://www.w3.org/TR/2018/REC-ttml1-20181108/#style-value-length),
which allows a prefix of `+`, `-` or nothing, and it's the
[same in TTML2](https://www.w3.org/TR/2018/REC-ttml2-20181108/#style-value-length).

PiperOrigin-RevId: 682379845
2024-10-04 10:58:42 -07:00
tonihei
af6ad43ca0 Disable the language/role flag preferences when selecting "none"
Just clearing the overrides only helps if a text override was
previously set. If the text is shown because of app defined track
selection parameters for language or role flags, the "none" button is
currently not working and we need to clear these flags explicitly.

PiperOrigin-RevId: 682373821
2024-10-04 10:42:27 -07:00
tonihei
47021c8777 Account for missing preroll when converting adPodIndex to adGroupIndex
IMA always starts midrolls at index 1. So if there is no preroll ad,
the ad group index in AdPlaybackState is off by 1 all the time, and
may also lead to ArrayIndexOutOfBoundsExceptions when trying to access
the last midroll ad

Issue: androidx/media#1741
PiperOrigin-RevId: 682324368
2024-10-04 08:11:04 -07:00
claincly
b5680e8a65 Make OverlayEffect take a List
Otherwise, it cannot be used with Kotlin `listOf()`.

PiperOrigin-RevId: 682255423
2024-10-04 03:46:41 -07:00
dancho
7b08bedf2c Do not force EOS when decoder has produced all frames
The workaround in ExternalTextureManager.forceSignalEndOfStream
was being applied even when the decoder did output all frames, and only
the GL pipeline was slow.

This change ensures that workaround is not applied when the decoder
has already produced all output frames.

PiperOrigin-RevId: 680471587
2024-09-30 01:50:10 -07:00
sheenachhabra
b0b54ca018 Calculate min timestamp across tracks in the Boxes.moov method
The Boxes.moov method can do the calculation instead of caller doing
it.

PiperOrigin-RevId: 679653033
2024-09-27 11:00:52 -07:00
tonihei
4481b3567e Add workaround for codecs not propagating EOS signal.
If a codec received the EOS signal and already returned the last
output buffer, we should expect the output EOS very quickly. If
it doesn't arrive within 100ms, we can proceed to end the stream
manually without waiting any further to prevent cases where the
codec is completely stuck otherwise.

PiperOrigin-RevId: 679633116
2024-09-27 10:07:10 -07:00
bachinger
287f353c87 Move tests relying on unreleased robolectric changes
#cherrypick

PiperOrigin-RevId: 679589647
2024-09-27 07:57:39 -07:00
bachinger
c6434a8276 Add @CanIgnoreReturnValue to MediaSession.BuilderBase
PiperOrigin-RevId: 679536456
2024-09-27 04:32:55 -07:00
claincly
e0e9f5b057 Increase the maxImage on ImageReader, hoping less flaky tests
PiperOrigin-RevId: 679528425
2024-09-27 04:03:15 -07:00
dancho
b9aed0a937 Have AndroidTestUtil.canEncode mirror VideoSampleExporter
Some devices can encode portrait 720x1080 but not landscape
1080x720. But VideoSampleExporter always prefers encoding
landscape. Have `assumeFormatsSupported` mirror sample exporter
logic more closely

PiperOrigin-RevId: 679495210
2024-09-27 02:01:05 -07:00
tonihei
23e02cce81 Upgrade Guava to 33.3.1
This bugfix release contains a fix for Issue: androidx/media#1700.

PiperOrigin-RevId: 679493263
2024-09-27 01:53:14 -07:00
tonihei
138a8d65ca Get updated buffered position when calling shouldStartPlayback
The buffered position was last updated before the beginning of
the renderer loop in doSomeWork. As the loading happens on a
background thread, it may have progressed further already
depending on how long it took to run the renderer loop.

It's slightly more correct to pass in an updated value to
shouldStartPlayback so that playback can start quicker if the
buffering is particularly fast.

PiperOrigin-RevId: 679203465
2024-09-26 10:33:40 -07:00
tianyifeng
2dde824bde Fix the flakiness in DefaultPreloadManagerTest
We began to use a different preload thread than the main thread in the `DefaultPreloadManagerTest`, then in the test execution, we should also ensure that the events queued on the preload looper have been executed.

Also, there is an edge case also causing flakiness. Assume that `DefaultPreloadManager` is preloading source B, then the app invalidates and triggers another sequence of preloading, say they are A, B and C. There is possibility that source B finishes preloading (started before `invalidate`) when A starting to preload, then `onPreloadSkipped` will be triggered for source B instead of `onPreloadCompleted`. However, the functional block inside of `onPreloadSkipped` is dispatched asynchronously, and by then it's possible that B starts to preload again at the consequence of A being completed, then the functional block just mentioned may think that the current source matches at B, and will advance the current preloading source to C, without informing the `Listener.onPreloadCompleted` for it. As the result, the `Listener.onPreloadCompleted` can never be triggered for B for the second sequence. To fix this, we should prevent the functional block in `onPreloadCompleted`, `onPreloadError`, `onPreloadSkipped` to be even dispatched, when the source doesn't match the current preloading one.

PiperOrigin-RevId: 679145353
2024-09-26 07:47:35 -07:00
samrobinson
45c400c7b5 Clarify compositionPlayer audio test naming.
PiperOrigin-RevId: 679142528
2024-09-26 07:37:52 -07:00
sheenachhabra
5e57734346 Update the comment of WRITE_TO_DEVICE enum
The previous comment was not super clear on how to
replace the dump files in the project directory.

PiperOrigin-RevId: 679136624
2024-09-26 07:17:43 -07:00
michaelkatz
09a5ef505b Assign the C.TRACK_TYPE_METADATA type to icy or vnd.dvb.ait tracks
The MetadataRenderer by default supports icy and vnd.dvb.ait content. Those tracks should therefore be set with the `C.TrackType` `TRACK_TYPE_METADATA` rather than `TRACK_TYPE_UNKNOWN`.

PiperOrigin-RevId: 679132680
2024-09-26 07:02:53 -07:00
sheenachhabra
b6192f7a39 Add AudioEncoderSettings to Transformer
It allows clients to specify the audio encoding profile and bitrate.
This is similar to VideoEncoderSettings.

PiperOrigin-RevId: 679131963
2024-09-26 06:59:53 -07:00
tonihei
f4eef88089 Ensure Media3 play calls get FGS exemption
When a Media3 controller calls play on a Media3 session, the call
is currently not routed through the platform session at all.
This means the usual exemption to start a FGS for media controller
interactions is not triggered.

We can manually ensure this exemption is given by sending a custom
platform command to the session. The Media3 session will never
receive this command as it's not a known Media3 custom command.
Sessions will see a single onConnect call with a platform controller
from the sender app though. We can prevent this on newer versions of
the code by dropping the onCommand call early.

PiperOrigin-RevId: 679115247
2024-09-26 05:57:40 -07:00
claincly
50c879ee21 Remove stale comment
PiperOrigin-RevId: 679082395
2024-09-26 03:55:24 -07:00
bachinger
65962dcb37 Add Builder.setMaxCommandsForMediaItems for browser and controller
The max number of commands for media items of a browser or controller
can be configured with `setMaxCommandsForMediaItems(int)` on
`MediaController.Builder` and `MediaBrowser.Builder`. An app that has
only limited space for displaying commands can hint this limit to the
session.

A session can receive the value of a connected browser or controller
through `getMaxCommandsForMediaItems()` of a `ControllerInfo` that
is passed into every callback method. The session can then pick the most
sensible commands instead of making the browser app truncating the commands
rather randomly.

When a `MediaBrowser` is connected against a legacy `MediaBrowserServiceCompat`,
the max number of commands is automatically added to the root hints. Conversely,
the value passed in with the root hints to `MediaLibraryService` by a legacy
`MediaBrowserCompat`, is read into `ControllerInfo` like for a Media3 browser.

Issue: androidx/media#1474

#cherrypick

PiperOrigin-RevId: 679076506
2024-09-26 03:33:01 -07:00
Googler
020ce7765c Reduce rounding error and stts table entries.
To avoid rounding errors, set the `Rounding mode` of the `uvFromVu` and `vuFromUs` results to `HALF_UP`. This `Rounding mode` rounds numbers towards the "nearest neighbor" unless both neighbors are equidistant, in which case round up.

PiperOrigin-RevId: 679003943
2024-09-25 23:19:20 -07:00
ibaker
2520dd12f9 Fix EMSG typo in HlsChunkSource and ChunkSampleStream
PiperOrigin-RevId: 678870309
2024-09-25 15:33:22 -07:00
bachinger
b8ec6b836b Add interoperability for media item commands
See https://developer.android.com/training/cars/media#custom_browse_actions

- `MediaMetadata.supportedCommands` is converted to an array list of
  strings into the extras of `MediaDescriptionCompat` with `DESCRIPTION_EXTRAS_KEY_CUSTOM_BROWSER_ACTION_ID_LIST` and vice versa.

- The set of media item command buttons of a session is passed in the
root hints to a legacy browser that connects. A Media3 browser connected
to a legacy service, gets the set of all commands after calling
`getLibraryRoot()`.

#cherrypick

PiperOrigin-RevId: 678807473
2024-09-25 12:40:22 -07:00
bachinger
686c3fe7f5 Add media item command buttons for Media3 controllers
Note that unlike the legacy implementation, custom media items
commands can be used for any media items with Media3 API. This
includes `MediaItem` instances that are received from sources
different to `MediaLibraryService` methods.

Hence when connected against a Media3 session these custom commands
can be used with a `MediaController` as well as with a `MediaBrowser`.

Interoperability with `MediaBrowserServiceCompat` will
be added in a follow up CL.

Issue: androidx/media#1474
#cherrypick
PiperOrigin-RevId: 678782860
2024-09-25 11:36:11 -07:00
claincly
0ea63e3fa6 Disable video only on the sequence that has items that disable video
This means we need a custom track selector for each SequencePlayer.

PiperOrigin-RevId: 678779216
2024-09-25 11:28:26 -07:00
tonihei
5879426c07 Check surface validity before configuring it on a codec
This check is also done when setting a surface on ExoPlayerImpl,
but by the time we configure the codec the surface may have become
invalid (e.g. when it is destroyed). Even though we immediately remove
a destroyed surface, we could still accidentally use it before the
removal is processed. To avoid these edge cases, we can simply not
configure the codec with an invalid surface.

PiperOrigin-RevId: 678741425
2024-09-25 09:58:11 -07:00
kimvde
3ec9c99644 Document that the MediaItem's image duration should be set for images
PiperOrigin-RevId: 678702334
2024-09-25 08:04:31 -07:00
kimvde
2dc32360d6 Remove usages of deprecated DefaultDecoderFactory constructor
The constructor was deprecated but usages were not migrated.

PiperOrigin-RevId: 678683678
2024-09-25 07:01:14 -07:00
samrobinson
fa04386863 Enable multi sequence audio preview test.
PiperOrigin-RevId: 678667829
2024-09-25 06:03:23 -07:00
samrobinson
5ab72a3938 Use constants in dump file names for playback test.
PiperOrigin-RevId: 678664730
2024-09-25 05:51:06 -07:00
kimvde
eaec7a4a61 Set MediaItem's image duration for image export
This field will become mandatory

PiperOrigin-RevId: 678656879
2024-09-25 05:23:27 -07:00
kimvde
f83d2b1392 Stop calling setDurationUs when image duration already set
The EditedMediaItem's duration is set to the MediaItem's image duration
by default.

PiperOrigin-RevId: 678597324
2024-09-25 02:02:30 -07:00
samrobinson
fc07ce056a Add safer gap based checks to Transformer API boundary points.
PiperOrigin-RevId: 678278666
2024-09-24 09:23:01 -07:00
tonihei
8b7c8ffb86 Misc cleanup for session token
Improved string representation for legacy token and
import for unambigious class name.

PiperOrigin-RevId: 678256188
2024-09-24 08:16:08 -07:00
samrobinson
076eea283e Adding API and internal export support for audio gaps in sequences.
PiperOrigin-RevId: 678235289
2024-09-24 07:14:59 -07:00
tonihei
43765b7567 Add platform token to Media3 SessionToken
Access is package-private and it will allow the media controller logic
to interact with the underlying platform session directly if needed.

Interop: When a MediaController connects to an older session (before this
change), it won't get the platform token from the session directly.
Many controllers will be set up with a platform or compat token though
and we can simply keep the already known token and use it. The only
cases where we still don't have a platform token in the MediaController
are the cases where the controller is created with a SessionToken based
on a ComponentName.
PiperOrigin-RevId: 678230977
2024-09-24 07:01:39 -07:00
kimvde
d8dc513431 Apply video composition effects in preview
PiperOrigin-RevId: 678207818
2024-09-24 05:46:33 -07:00
kimvde
c19d910f6b Fix EffectPlaybackPixelTest
The tests using createTimestampOverlay() were passing even if the effect
was removed, because the overlay was too small.

PiperOrigin-RevId: 678169395
2024-09-24 03:24:49 -07:00
Copybara-Service
424ecadb66 Merge pull request #1736 from colinkho:msource_list
PiperOrigin-RevId: 677994281
2024-09-23 17:04:39 -07:00
Tianyi Feng
b303498834 Fix the typo in javadoc 2024-09-23 20:53:53 +00:00
Tianyi Feng
508a1d800d Add release note 2024-09-23 20:53:53 +00:00
Colin Kho
31a540953c Add method to MediaSourceEventListener.EventDispatcher to submit events through a lambda function. This allows clients that implement this interface to submit customized event dispatching logic to the EventDispatcher's listeners 2024-09-23 20:53:52 +00:00
bachinger
b884d7ee9b Handle IllegalArgumentException when setting broadcast receiver
Some devices seem to throw an `IllegalArgumentException` when
attempting to set a valid media button broadcast receiver
for playback resumption. This change handles this exception
as a no-op to avoid crashing the app. As a result, playback
resumption with media keys isn't going to work on these
devices.

This change needs to be reverted once the root cause on these
devices has been fixed (see internal bug ref in source).

Issue: androidx/media#1730
PiperOrigin-RevId: 677904243
2024-09-23 12:53:37 -07:00
ibaker
869a91bba8 Remove DemoUtil.ALLOW_CRONET_FOR_NETWORKING
This boolean only exists to be changed in source, but it is now used
as part of a 3-way fallback logic (since adding `HttpEngine`
integration), so it's not really more convenient or clearer to change
this constant than just hack the code of `getHttpDataSourceFactory`
directly.

PiperOrigin-RevId: 677834348
2024-09-23 09:55:55 -07:00
samrobinson
b4436c523c Migrate Media3 EditedMediaItemSequence usages to the Builder.
Removed the unnecessary wrapping of items in an ImmutableList.

PiperOrigin-RevId: 677796662
2024-09-23 08:06:55 -07:00
bachinger
ba1cdba403 Preload first period of next window
Allow apps to preload the first period of the next window in
the playlist of `ExoPlayer`. By default playlist preloading is
disabled. To enable preloading,
`ExoPlayer.setPreloadConfiguration(PreloadConfiguration)` can be
called.

`LoadControl` determines when to preload with its implemenation of `shouldContinuePreloading(timeline, mediaPeriodId, bufferedDurationUs)`.
The implementation in `DefaultLoadControl` allows preloading only when
the player isn't currently loading for playback. Apps can override this
behaviour.

Issue: androidx/media#468
PiperOrigin-RevId: 677786017
2024-09-23 07:32:48 -07:00
ivanbuper
3d3ec85c12 Setup basic testing for Sonic and assert expected sample count drift
This CL adds `SonicTest` and `RandomParameterizedSonicTest` as
initial basic unit testing for `Sonic.java`. The tested scenarios
do not necessarily verify a correct implementation of Sonic, but rather
hope to catch any behaviour change from the current implementation.

The change includes a small fix for a lossy simplification and also
checks whether the output sample count matches the expected drift from
the truncation accumulation error present in Sonic's resampler. This is
important as pre-work for fixing issues with unexpected durations within
`SonicAudioProcessor` and `SpeedChangingAudioProcessor` that cause AV
sync issues for speed changing effects.

This is a partial roll forward of e88d6fe459, which was rolled back in
873d485056.

PiperOrigin-RevId: 677756854
2024-09-23 05:53:58 -07:00
dancho
17e1d37112 Fix GL filtering algorithm used when experimental fix is disabled
When working on SurfaceTexture crop fix, we accidentally switched
to GL_NEAREST resampling.

PiperOrigin-RevId: 677751819
2024-09-23 05:34:30 -07:00
kimvde
be4d31ba87 Prevent ExoPlayer dropping frames in EffectPlaybackPixelTest
Some tests check all the output frames and fail if there are frames
missing.

PiperOrigin-RevId: 677676143
2024-09-23 01:12:40 -07:00
tonihei
5e3dcea1bf Ramp up volume after AudioTrack flush to avoid pop sound
AudioTrack doesn't automatically ramp up the volume after a flush
(only when resuming with play after a pause), which causes audible
pop sounds in most cases. The issue can be avoided by manually
applying a short 20ms volume ramp, the same duration used by the
platform for the automatic volume ramping where available.

Together with the already submitted 6147050b90, this fixes the
unwanted pop sounds for most cases in the desired way. It only
leaves two cases that are not handled perfectly:
 - If the media file itself contains a volume ramp at the beginning,
   we wouldn't need this additional ramping. Given the extremely
   short duration, this seems ignorable and we can treat it as a
   future feature request to mark the beginning of media in a special
   way that can then disable the volume ramping.
 - For seamless period transitions where we keep using the same
   AudioTrack, we may still get a pop sound at the transition. To
   solve this, we'd need a dedicated audio processor to either ramp
   the end of media down and the beginning of the next item up, or
   apply a very short cross-fade. Either way, we need new signalling
   to identify cases where the media originates from the same source
   and this effect should not be applied (e.g. when re-concatenating
   clipped audio snippets from the same file).

PiperOrigin-RevId: 676860234
2024-09-20 08:55:29 -07:00
ibaker
e887614246 Move release note from 1.5.0-alpha01 to 'unreleased'
This was accidentally added in the wrong place in 6bda0da6be

PiperOrigin-RevId: 676841659
2024-09-20 07:48:26 -07:00
Copybara-Service
6d6724db94 Merge pull request #1740 from MGaetan89:fix_shortform_demo_input_type
PiperOrigin-RevId: 676581325
2024-09-19 15:17:07 -07:00
bachinger
6bda0da6be Fix sending custom commands with a media browser
When sending a custom command with `browser.sendCustomCommand` when
connected to a legacy browser service, the custom command was delivered to `MediaSessionCompat.Callback.onCustomAction` instead of the service method
`onCustomAction`. The difference is that the service version can return an
async response with a bundle, while the session callback version doesn't
have a return value.

Hence, the service method was never called and it wasn't possible to send
a reponse or signal an error back to the browser. The resulting
`ListanableFuture` simply always immediately resolved to a success.

This change overrides `ListenableFuture<SessionResult> sendCustomCommand(SessionCommand command, Bundle args)` in
`MediaBrowserImplLegacy` to use the `MediaBrowserCompat` method to send
instead of the `MediaControlleCompat` method that was used by the subclass
`MediaControllerImplLegacy`. This involves the service callback instead of the
session callback and enables `MediaBrowser` to get the actual return value
from the legacy service.

Issue: androidx/media#1474
#cherrypick
PiperOrigin-RevId: 676519314
2024-09-19 12:35:42 -07:00
samrobinson
3c5e764b86 Fix CapturingMuxer writing of final PCM audio sample to dump file.
Last buffer was not flipped, so was writing the garbage data between
limit and capacity, rather than the actual data between position and
limit.

As a result, all PCM audio dump files need updating.

PiperOrigin-RevId: 676452990
2024-09-19 09:52:12 -07:00
samrobinson
75c7ee79d5 Add EditedMediaItemSequence.Builder.
PiperOrigin-RevId: 676422122
2024-09-19 08:23:37 -07:00
Gaëtan Muller
76e3fc06dd
Update activity_main.xml 2024-09-19 11:08:06 +02:00
kimvde
980f24d906 Avoid ImageReader frame drops in EffectPlaybackPixelTest
If this doesn't work, we could make this test resilient to frame drops
by only comparing the frames that weren't dropped.

PiperOrigin-RevId: 676294944
2024-09-19 00:18:46 -07:00
rohks
ecb0024a0b Improve frame rate calculation by using media duration from mdhd box
- Added logic to parse media duration from the `mdhd` box for accurate frame rate calculation.
- Fallbacks to track duration from `tkhd` when `mdhd` contains invalid or missing data.
- Avoids incorrect frame rate calculations in MP4 files with an edit list (`elst`) box.
- Adds frame rate calculations for partially fragmented MP4 files.
- Verified accuracy with tools like `mediainfo` and `ffprobe`.

Issue: androidx/media#1531

**Note**: The slight difference in frame rate values in dump files that aren’t MP4s with an edit list or fragmented MP4s isn’t due to differences in `tkhd` and `mdhd` duration values (which should be identical for non-edited or non-fragmented files). Rather, it’s because they are calculated using different timescales. The `mvhd` box defines a global movie timescale, which is used for the track's `tkhd` duration. Meanwhile, each track’s `mdhd` box defines its own timescale specific to its content type, which we now use for more accurate frame rate calculation.

PiperOrigin-RevId: 676046744
2024-09-18 10:42:11 -07:00
rohks
8799bf4bfe Format Format.frameRate to two decimal places before dumping
PiperOrigin-RevId: 675996979
2024-09-18 08:14:52 -07:00
ibaker
0b86f89498 Guard DrmSession.requiresSecureDecoder calls with state checks
This method is documented that it may only be called in `STATE_OPENED`
or `STATE_OPENED_WITH_KEYS`. It's possible for it to be called in other
states (like `STATE_ERROR`) without this guard.

Previously this didn't cause issues, but since 9d62845c45
we assume that the `sessionId` is non-null in this method, which results
in an `IllegalStateException` when the documented state restriction is
ignored.

PiperOrigin-RevId: 675969256
2024-09-18 06:41:32 -07:00
kimvde
caf70e54db Fix outdated Transformer Javadoc
PiperOrigin-RevId: 675942348
2024-09-18 04:56:51 -07:00
ibaker
3e8ecbf564 Remove @DoNotInline annotations
This is no longer needed now our `compileSdk` implies a new-enough AGP
which does this out-lining automatically via R8. See also
https://issuetracker.google.com/345472586#comment7

There's no plan to remove the `ApiXXX` classes, but no new ones need
to be added.

PiperOrigin-RevId: 675940634
2024-09-18 04:47:39 -07:00
kimvde
2951a2599c Apply all video Composition effects to single sequence exports
PiperOrigin-RevId: 675933601
2024-09-18 04:18:55 -07:00
claincly
fd3d8e1782 Add RATE_UNSET option to encoder performance setting
This is to allow not setting the MediaFormat OPERATING_RATE and PRIORITY
altogether. The current behvaiour, if left the value `UNSET`, it'll apply the
our optimizations, but apps might want to disable this optimization.

PiperOrigin-RevId: 675923909
2024-09-18 03:40:13 -07:00
michaelkatz
f0fb386224 Add workaround for Galaxy Tab S7 FE device PerformancePoint issue
The Galaxy Tab S7 FE has a device issue that causes 60fps secure H264 streams to be marked as unsupported. This CL adds a workaround for this issue by checking the CDD required support for secure H264 in addition to the current check on standard H264. If the provided performance points do not cover the CDD requirement of support 720p H264 at 60fps, then it falls back to using legacy methods for checking frame rate and resolution support.

Issue: androidx/media#1619
PiperOrigin-RevId: 675920968
2024-09-18 03:29:10 -07:00
claincly
69da26935e Fix logic that detects the last media item in Sequence
After the change in a879bc2154, the Sequence won't have repeated
EditedMediaItems. Thus if the sequence is looping, the last EditedMediaItems
in the Sequence object might not corresponds to the last item in the "logical"
sequence.

PiperOrigin-RevId: 675912197
2024-09-18 02:54:11 -07:00
ibaker
3facfbf542 Assert baseDataSourceFactory non-null in DefaultDataSource.Factory
This gives a faster/clearer failure for Issue: androidx/media#1718.

PiperOrigin-RevId: 675896262
2024-09-18 02:00:03 -07:00
hoisie
dc66c9160c Update some APIs to Use real types in ShadowNotificationManager
NotificationChannel and NotificationChannelGroup are public APIs that were
added in Android O, so at this point it is okay to have them appear in API
signatures.

PiperOrigin-RevId: 675756005
2024-09-17 16:47:36 -07:00
Googler
087e75850e Add suitable output checker tests relevant for API 35+ in media3-ui
PiperOrigin-RevId: 675678257
2024-09-17 13:05:43 -07:00
sheenachhabra
acb8e71c6e Set hasMuxedTimestampZero to true only when its muxed
The muxer might not have accepted the first sample, if it
is waiting for audio track.

This bug causes issue when

1. VideoSampleExporter gives first sample (timestamp = 0) to the muxer.
2. Muxer does not write it because its waiting for audio track.
3. The video pipleline has processed all the sample and they are ready
to be consumed.
4. VideoSampleExporter fetches the next available sample from encoder (which is still with timestamp = 0) but it changes its timestamp to last timestamp because VideoSampleExporter thinks it has muxed the sample at timestamp zero, but in reality it hasn't. This is because the flag `hasMuxedTimestampZero` is set when queueing the input, rather than actually muxing the input.

This scenario can happen when video is processed much faster than
the audio.

PiperOrigin-RevId: 675565603
2024-09-17 07:53:22 -07:00
Googler
e1c4ecf2d3 Add suitable output checker tests relevant for API 35+ in ExoPlayerTest
PiperOrigin-RevId: 675547156
2024-09-17 06:45:15 -07:00
rohks
9bc89ae989 Ensure track indices are only added when samples are committed
Previously, track IDs were added to `trackIndicesPerSampleInQueuedOrder`
even when the sample was not committed. This caused issues where attempts
to read samples from the `SampleQueue` returned `C.RESULT_READ_NOTHING`,
which led to an exception being thrown due to the assumption that samples
were available to read.

This fix updates the logic to track sample commits by comparing the write index before and after calling `SampleQueue.sampleMetadata`. Track indices are only added if the sample was committed, ensuring accurate sample handling and avoiding exceptions.

PiperOrigin-RevId: 675526115
2024-09-17 05:27:51 -07:00
ivanbuper
873d485056 Rollback of e88d6fe459
PiperOrigin-RevId: 675525508
2024-09-17 05:25:07 -07:00
kimvde
0ea229d795 Remove limit of 2 sequences in CompositionPlayer
The player supports more than 2 audio sequences

PiperOrigin-RevId: 675493637
2024-09-17 03:20:28 -07:00
Copybara-Service
6632e64007 Merge pull request #1653 from theskyblockman:main
PiperOrigin-RevId: 675277275
2024-09-16 13:52:55 -07:00
oceanjules
25bb8e411b Make setFullscreenButtonState UnstableApi 2024-09-16 20:19:55 +01:00
oceanjules
9b75523fd9 Add and format RELEASENOTES 2024-09-16 20:05:02 +01:00
oceanjules
5536b73a08 Format with google-java-format 2024-09-16 20:05:02 +01:00
theskyblockman
370a4c0035 Deleted redundant check 2024-09-16 20:05:02 +01:00
theskyblockman
c42f53fcc9 Edited condition to exit updateIsFullscreen quickly 2024-09-16 20:05:02 +01:00
theskyblockman
4a4b3a3bc0 - Rephrased/Expanded javadocs for fullscreen methods
- Replaced all occurrences of "FullScreen" to "Fullscreen"
2024-09-16 20:05:02 +01:00
theskyblockman
49af9228db Added public-facing calls to set whether to show [fullScreenButton] or [minimalFullScreenButton] in PlayerView calling PlayerControlView 2024-09-16 20:05:02 +01:00
sheenachhabra
47d45a82ca Change the default value of lastSampleDurationBehavior
to
LAST_SAMPLE_DURATION_BEHAVIOR_SET_FROM_END_OF_STREAM_BUFFER_OR_DUPLICATE_PREVIOUS

This CL also combines LAST_SAMPLE_DURATION_BEHAVIOR_SET_FROM_END_OF_STREAM_BUFFER
and LAST_SAMPLE_DURATION_BEHAVIOR_DUPLICATE_PREVIOUS.

The reason for combining the two enums is that, when the option
to use END_OF_STREAM_BUFFER is selected and if the EOS buffer is
not provided then the muxer anyways fallbacks to duplicate
duration behavior.

The last sample with 0 durations seems less useful so
change the default behavior to non-zero duration.
This will also match the behavior with MediaMuxer.

PiperOrigin-RevId: 675189932
2024-09-16 10:02:10 -07:00
Copybara-Service
0ce6d9620e Merge pull request #1703 from colinkho:fwd-renderer
PiperOrigin-RevId: 675122802
2024-09-16 06:25:27 -07:00
claincly
a879bc2154 Rewrite sequence duration matching on MediaSource level
This simplifies the later handling of speed adjusted media.

PiperOrigin-RevId: 675114587
2024-09-16 05:55:00 -07:00
Rohit Singh
c8aa122e8a Change from internal review 2024-09-16 13:34:21 +01:00
Rohit Singh
11aea9b34b Add RELEASENOTES 2024-09-16 13:15:40 +01:00
Rohit Singh
9975175700 Forward default methods 2024-09-16 13:07:36 +01:00
Rohit Singh
3c6f1f1e77 Add copyright text and move test to correct package 2024-09-16 12:36:03 +01:00
Rohit Singh
8ca12338f6 Format with google-java-format 2024-09-16 12:31:48 +01:00
Colin Kho
62aef96b7d Format code using google java format 2024-09-16 12:31:48 +01:00
Colin Kho
72e39c91c4 Denote ForwardingRenderer as subject to change 2024-09-16 12:31:48 +01:00
Colin Kho
72f26d79f6 Added a forwarding class for Renderer to support composing custom behavior on Metadata & Text Renderers 2024-09-16 12:31:48 +01:00
michaelkatz
e938d27846 Fix typo in release notes
PiperOrigin-RevId: 675070722
2024-09-16 02:54:20 -07:00
Googler
011659b326 Add profile and level for H263 codec.
To support for 3gpp h263 codec in Mp4Muxer currently profile and level is hardcoded and provided to h263 box. Parse profile and level from MediaFormat and use those value to write h263 box.

PiperOrigin-RevId: 675004590
2024-09-15 22:09:38 -07:00
claincly
6c92402fbb Re-enable audio tests
The tests were flaky because CompositionPlayer registers audio sequences'
formats to the audio pipeline in first-come-first-serve order. With the change
in bc8d82355f, the audio format is deterministic.

The test is turned off in 060356ea00

PiperOrigin-RevId: 674261801
2024-09-13 05:44:28 -07:00
tianyifeng
72ae454f67 Use buffered duration from start position to control preload progress
`PreloadMediaSource` allows to have a `startPositionUs` passed when `preload` is called, then in `PreloadControl.onContinueLoadingRequested`, it can be more intuitive to see the buffered duration rather than the absolute buffered position as the preload progress. Similar in `DefaultPreloadManager`, we haven't allowed the apps to set a custom start position for individual sources though, once we add this support, using the "duration from the start position" than the absolute position will be less error-prone, otherwise, it can run into a case that the position that the apps set is smaller than the start position.

PiperOrigin-RevId: 674251362
2024-09-13 05:05:39 -07:00
Copybara-Service
023fd32cb1 Merge pull request #1138 from Lavamancer:bugfix/rtsp_message_util_encoded_authority
PiperOrigin-RevId: 674239756
2024-09-13 04:16:05 -07:00
Googler
ff656012a8 Update the unisoc blocklist
PiperOrigin-RevId: 674101970
2024-09-12 19:33:13 -07:00
claincly
bc8d82355f Block secondary audio processing until primary format is configured
PiperOrigin-RevId: 673961235
2024-09-12 12:42:09 -07:00
ivanbuper
e88d6fe459 Fix truncation error acumulation for Sonic's resampling algorithm
Sonic would accumulate truncation errors on float to int conversions
that caused the final output sample count to drift noticeably, by
hundreds of samples on streams of a few minutes of length. The fix now
keeps track of the truncation error and compensates for it.

Other small fixes include eliminating lossy operations (e.g. int
division) and using doubles instead of floats for resampling where
helpful.

This CL also introduces `SonicParameterizedTest`, which helps test
resampling on an arbitrary number of randomly generated parameters,
with random sample data. `SonicParameterizedTest` uses `BigDecimal`s
for calculating sample count values, as to avoid precision issues with
large sample counts.

PiperOrigin-RevId: 673852768
2024-09-12 08:14:25 -07:00
Googler
3caebbf5ad Fix erroneous use of "cpu_features" in AV1 JNI exports.
`CPU_FEATURES_COMPILED_ANY_ARM_NEON` is always defined when `CPU_FEATURES_ARCH_ARM` is but it can be defined as `0` or `1` ([cpu_features source](8e60d3f9be/include/cpu_features_macros.h (L237-L245))). This patch makes sure to use the value of `CPU_FEATURES_COMPILED_ANY_ARM_NEON` instead of whether it is defined or not.

PiperOrigin-RevId: 673837522
2024-09-12 07:30:47 -07:00
kimvde
ce98b7d379 Rename SequencePlayerRenderersWrapper
Also remove reference to SequencePlayerRenderersWrapper in inner
classes to make them effectively static.

PiperOrigin-RevId: 673720251
2024-09-12 00:52:17 -07:00
microkatz
661f3de325 Added note in release notes 2024-09-12 04:56:57 +00:00
microkatz
61343cd75f Format with google-java-format 2024-09-12 04:47:05 +00:00
Lavamancer
17e0fd22b1 Fixed removal of user info for URLs that contain encoded @ characters 2024-09-12 04:47:05 +00:00
rohks
f133e8d1f2 Fix preroll sample handling for non-keyframe media start positions
When processing edit lists in MP4 files, the media start position may be a non-keyframe. To ensure proper playback, the decoder must preroll to the preceding keyframe, as these preroll samples are essential for decoding but are not rendered.

Issue: google/ExoPlayer#1659

#cherrypick

PiperOrigin-RevId: 673457615
2024-09-11 11:02:39 -07:00
rohks
d9a678483b Refine sample presentation time validation for negative PTS workaround
In case of negative PTS workaround, instead of disallowing all cases where samples are not in presentation order, we now validate that the first sample's presentation time is the smallest. This adjustment allows for correctly applying an offset to ensure all samples have a presentation time >= 0.

#cherrypick

PiperOrigin-RevId: 673434793
2024-09-11 10:04:16 -07:00
rohks
bb3d055191 Do not drop negative timestamp video buffers during transmuxing
Prevents discarding video buffers with key frame which are required for decoding.

#cherrypick

PiperOrigin-RevId: 673375261
2024-09-11 07:00:02 -07:00
kimvde
8271a5f920 Rename CompositingVideoSinkProvider and PreviewAudioPipeline
The components are mirror components for video and audio so they should
have a matching name

PiperOrigin-RevId: 673357081
2024-09-11 05:49:35 -07:00
sheenachhabra
4be5b74366 Read NAL unit data as 4 byte integer
When converting NAL units from AnnexB to Avcc format,
one byte at a time was read. In fact many bytes were read
multiple times due to suboptimal logic.

Changed the logic to read 4 bytes at once and also to avoid
reading same bytes again.

This improved the time taken for writing a batch of 30
samples from 40ms to 20ms.

PiperOrigin-RevId: 673025781
2024-09-10 10:57:22 -07:00
sheenachhabra
35dc10aac8 Fix a bug to read last 3 bytes in AnnexBUtils
In the current implementation due to missing "="
operator, last three bytes were not checked for
000 or 001 sequence.

In sample_no_bframes file, few samples has extra 0 at the end.
It was working fine with the bug because muxer was writing some
harmless 0 at the end.

PiperOrigin-RevId: 672994054
2024-09-10 09:32:56 -07:00
jbibik
8bfa7e2de1 Make StreamVolumeManager take streamType in constructor
Avoids the dispatch of listener notifications of stream type/device volume change during construction of ExoPlayer. That was problematic because we end up blocking on `constructorFinished.blockUninterruptible()`

Issue: androidx/media#1692
PiperOrigin-RevId: 672965552
2024-09-10 08:05:21 -07:00
kimvde
a53ea621bb Stop rejecting frames later in ExternalTextureManager
If the task executor handles an available frame (task submitted in the
SurfaceTexture listener) between the call to registerInputFrame() and
the execution of the task submitted in the method (in this CL), it
should be rejected.

PiperOrigin-RevId: 672903756
2024-09-10 04:29:48 -07:00
sheenachhabra
327b1c8ad8 Move getCodecProfileAndLevel to CodecSpecificDataUtil
`Muxer` module needs to use this method, hence moved to common.

This CL also makes `getHevcProfileAndLevel` public because this is used
in `MediaCodecUtil`.

PiperOrigin-RevId: 671739166
2024-09-06 06:52:29 -07:00
ivanbuper
a1357befff Add Kotlin dependencies to missing_aar_type_workaround.gradle
#cherrypick

PiperOrigin-RevId: 671736852
2024-09-06 06:40:41 -07:00
aquilescanta
4ea58a133e Populate DeviceInfo in CastPlayer using MediaRouter2 info
This enables linking the media session to a routing session.

Issue: androidx/media#1056
PiperOrigin-RevId: 671425490
2024-09-05 10:38:08 -07:00
kimvde
a1d2310170 Fix stuck player after seek
Seeking was causing the player to hang in the following scenario:
1. The surfaceTexture's onFrameAvailableListener is called in
   ExternalTextureManager to notify that a new frame is available.
2. This call submits a task on the GL thread.
3. A seek is performed and DefaultVideoFrameProcessor.flush() is called
   before the task submitted in 2 is executed.
4. DefaultVideoFrameProcessor.flush() flushes the task executor, so that
   the task submitted in 2 never gets executed.
5. Once the seek is over, the first frame is registered and rendered on
   the surface texture.
6. Playback hangs because the onFrameAvailableListener is never called
   for this new frame. This is because surfaceTexture.updateTexImage()
   was never called on the frame that became available in 1.

This fix is making sure that the task submitted in 2 always gets executed.

Issue: androidx/media#1535
PiperOrigin-RevId: 671389215
2024-09-05 08:54:55 -07:00
ibaker
6822818549 Fix Format.toLogString handling of new Format.labels field
Before this, because `Label.toString()` isn't implemented, the logged info
wasn't that useful:

```
labels=[androidx.media3.common.Label@6caac039]
```

With this change it's more useful:

```
labels=[en: english]
```
PiperOrigin-RevId: 671029474
2024-09-04 11:02:36 -07:00
aquilescanta
a00c446529 Do not clear the timeline after the Cast receiver disconnects
The goal is to enable the app to fetch the timeline after a
disconnection in order to prepare and resume local playback.

PiperOrigin-RevId: 671022044
2024-09-04 10:42:53 -07:00
ibaker
87bd9ba585 Make PlayerView Compose workaround opt-in
The workaround causes issues with XML-based shared transitions, so we
can't apply it unilaterally.

Issue: androidx/media#1594

Issue: androidx/media#1237
PiperOrigin-RevId: 670960693
2024-09-04 07:24:27 -07:00
jbibik
c851464063 Add a note on not using PlayerControlView as a standalone component
We have a tracking bug Issue: androidx/media#514 for supporting it, but issues like Issue: androidx/media#1542 still show that users stumble over it.

PiperOrigin-RevId: 670955189
2024-09-04 07:02:17 -07:00
ibaker
0933f561b7 Move MCR CryptoException handling to top-level render() method
Handling for `MediaCodec.CryptoException` was originally added only
around calls to `MediaCodec.queueSecureInputBuffer` and
`queueInputBuffer` (because these are the only methods that can throw
this exception). When asynchronous interaction with `MediaCodec` was
added in <unknown commit>, exceptions from `MediaCodec` started being stored
and bubbled out of **later** interactions with `MediaCodecAdapter`. This
means that `MediaCodecRenderer` can now see `CryptoException` thrown
from a different method, like
`MediaCodecAdapter.dequeueInputBufferIndex()`, and this ends up missing
the `catch (CryptoException)` code in `MediaCodecRenderer`. This results
in an "unexpected runtime error" stack trace like [A].

This change fixes the stack trace to:
1. Make it a "renderer exception" instead of "unexpected runtime error"
2. Include the correct DRM error code -> `@PlaybackException.ErrorCode`
   mapping.

You can see the corrected stack trace below [B].

-----

[A] (synthesized from manually throwing a `CryptoException` from
`AsynchronousMediaCodecBufferEnqueuer#doQueueSecureInputBuffer`)

```
playerFailed [eventTime=11.56, mediaPos=10.35, window=0, period=0, errorCode=ERROR_CODE_UNSPECIFIED
  androidx.media3.exoplayer.ExoPlaybackException: Unexpected runtime error
      at androidx.media3.exoplayer.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:729)
      at android.os.Handler.dispatchMessage(Handler.java:103)
      at android.os.Looper.loopOnce(Looper.java:232)
      at android.os.Looper.loop(Looper.java:317)
      at android.os.HandlerThread.run(HandlerThread.java:85)
  Caused by: android.media.MediaCodec$CryptoException: Test error message
      at androidx.media3.exoplayer.mediacodec.AsynchronousMediaCodecBufferEnqueuer.doQueueSecureInputBuffer(AsynchronousMediaCodecBufferEnqueuer.java:232)
      at androidx.media3.exoplayer.mediacodec.AsynchronousMediaCodecBufferEnqueuer.doHandleMessage(AsynchronousMediaCodecBufferEnqueuer.java:196)
      at androidx.media3.exoplayer.mediacodec.AsynchronousMediaCodecBufferEnqueuer.access$000(AsynchronousMediaCodecBufferEnqueuer.java:47)
      at androidx.media3.exoplayer.mediacodec.AsynchronousMediaCodecBufferEnqueuer$1.handleMessage(AsynchronousMediaCodecBufferEnqueuer.java:93)
      at android.os.Handler.dispatchMessage(Handler.java:107)
      at android.os.Looper.loopOnce(Looper.java:232) 
      at android.os.Looper.loop(Looper.java:317) 
      at android.os.HandlerThread.run(HandlerThread.java:85) 
```

[B]

```
Playback error
  androidx.media3.exoplayer.ExoPlaybackException: MediaCodecAudioRenderer error, index=1, format=Format(0, null, null, audio/mp4a-latm, mp4a.40.2, 134359, en, [-1, -1, -1.0, null], [2, 44100]), format_supported=YES
      at androidx.media3.exoplayer.ExoPlayerImplInternal.handleMessage(ExoPlayerImplInternal.java:649)
      at android.os.Handler.dispatchMessage(Handler.java:103)
      at android.os.Looper.loopOnce(Looper.java:232)
      at android.os.Looper.loop(Looper.java:317)
      at android.os.HandlerThread.run(HandlerThread.java:85)
  Caused by: android.media.MediaCodec$CryptoException: Test error message
      at androidx.media3.exoplayer.mediacodec.AsynchronousMediaCodecBufferEnqueuer.doQueueSecureInputBuffer(AsynchronousMediaCodecBufferEnqueuer.java:232)
      at androidx.media3.exoplayer.mediacodec.AsynchronousMediaCodecBufferEnqueuer.doHandleMessage(AsynchronousMediaCodecBufferEnqueuer.java:196)
      at androidx.media3.exoplayer.mediacodec.AsynchronousMediaCodecBufferEnqueuer.access$000(AsynchronousMediaCodecBufferEnqueuer.java:47)
      at androidx.media3.exoplayer.mediacodec.AsynchronousMediaCodecBufferEnqueuer$1.handleMessage(AsynchronousMediaCodecBufferEnqueuer.java:93)
      at android.os.Handler.dispatchMessage(Handler.java:107)
      at android.os.Looper.loopOnce(Looper.java:232) 
      at android.os.Looper.loop(Looper.java:317) 
      at android.os.HandlerThread.run(HandlerThread.java:85) 
```

PiperOrigin-RevId: 670951229
2024-09-04 06:44:21 -07:00
sheenachhabra
e27c7d5d45 Use simpler timestamps in tests for readability
PiperOrigin-RevId: 670907537
2024-09-04 03:35:54 -07:00
rohks
6e1bab03bd Add Mp4PlaybackTest for sample with edit list (edts box)
PiperOrigin-RevId: 670572646
2024-09-03 08:43:33 -07:00
sheenachhabra
a7788e0d60 Rename last sample duration behaviour enums
This is to improve readability.

PiperOrigin-RevId: 670563611
2024-09-03 08:15:23 -07:00
dancho
1c61fbadf7 Downscale bitmaps during decoding in Transformer
Limit input image size in Transformer to be less than 4096x4096.
For very large images, this can reduce memory usage substantially,
and stays away from `GL_MAX_TEXTURE_SIZE` - often 4096

PiperOrigin-RevId: 670555939
2024-09-03 07:53:13 -07:00
ivanbuper
9562c976a9 Bump Media3 version to 1.5.0-alpha01
PiperOrigin-RevId: 670535221
2024-09-03 06:40:01 -07:00
ivanbuper
e16b4fff8d Update release notes for Media3 1.5.0-alpha01 release
PiperOrigin-RevId: 670523759
2024-09-03 06:02:37 -07:00
kimvde
af61c03e09 Move ExoPlayerEffectPlaybackSeekTest out of performance directory
These tests do not test performance. Moving them out of this directory
ensures they are run on emulators and on all physical devices.

PiperOrigin-RevId: 670505992
2024-09-03 04:54:12 -07:00
kimvde
af2f9cb37f Increase ExoplayerEffectPlaybackSeekTest time out
This test will be moved so that it is run on emulators, which are
generally slower to run.

PiperOrigin-RevId: 670495551
2024-09-03 04:13:24 -07:00
sheenachhabra
95f69b649d Use InAppMuxer in tests for API < 25
MediaMuxer doesn't support B-frame before API 25.

The fix is added only in those test which appeared in triage failure.
It can be added to other tests as they are discovered.

PiperOrigin-RevId: 670480966
2024-09-03 03:18:01 -07:00
ivanbuper
ebb550a9f1 Fix WrongConstant lint failure for IamfDecoder#OUTPUT_PCM_ENCODING
To avoid `WrongConstant` failures with methods that expect
`C.@PcmEncoding`, `OUTPUT_PCM_ENCODING` now points to the constant in
`C` instead of `AudioFormat`.

#cherrypick

PiperOrigin-RevId: 670251463
2024-09-02 10:20:55 -07:00
ibaker
4562c781ed Update minSdk values in UtilTest
These were missed when upgrading to both SDK 19 and 21, but they now
cause failures like:

```
Caused by: java.lang.RuntimeException: Failed to parse package buildout/intermediates/apk_for_local_test/debugUnitTest/packageDebugUnitTestForUnitTest/apk-for-local-test.ap_: buildout/intermediates/apk_for_local_test/debugUnitTest/packageDebugUnitTestForUnitTest/apk-for-local-test.ap_ (at Binary XML file line #20): Requires newer sdk version #21 (current version is #19)
    at org.robolectric.shadows.ShadowPackageParser.callParsePackage(ShadowPackageParser.java:61)
    ... 20 more
```

#cherrypick

PiperOrigin-RevId: 670241471
2024-09-02 09:34:15 -07:00
samrobinson
1d9276863c Ensure force silence SampleConsumerWrapper has the right track type.
PiperOrigin-RevId: 670226122
2024-09-02 08:30:46 -07:00
dancho
207684ca66 Prototype frame extraction based on analyzer mode
Make ByteBufferGlEffect public.
Build a speed test, and end to end test that verify
frames can be copied to CPU-accessible ByteBuffer

PiperOrigin-RevId: 670213343
2024-09-02 07:34:33 -07:00
sheenachhabra
0843444a34 Fix order of elements in FrameworkMuxer.java
PiperOrigin-RevId: 670205944
2024-09-02 07:04:00 -07:00
claincly
060356ea00 Temporarily disable multi-sequence audio playback test
Disable assertions and make sure playback passes.

The flake is caused by having different sequences starting with MediaItems of
different audio format, and it's undefined behaviour as to which one CompositionPlayer chooses to use.

PiperOrigin-RevId: 670195113
2024-09-02 06:16:43 -07:00
sheenachhabra
f0fa7640ca Add support for setting video duration in InAppMuxer
Similar support was previously added in FrameworkMuxer.

PiperOrigin-RevId: 670193986
2024-09-02 06:11:32 -07:00
kimvde
748e4e5230 Fix hanging ExoPlayerPlaybackSeekTest
The test uses a video renderer to count the number of frames and could
time out if some frames were dropped.

PiperOrigin-RevId: 670181693
2024-09-02 05:21:01 -07:00
dancho
b3b4c80641 Release the analyzer mode placeholder surface
PiperOrigin-RevId: 670179138
2024-09-02 05:09:30 -07:00
ktrajkovski
b0213c870b Update release notes to contain IAMF support.
PiperOrigin-RevId: 670160408
2024-09-02 03:57:39 -07:00
ibaker
551cbadafc Gracefully handle unexpected non-MP3 trailing data
The `bear-cbr-no-seek-table-trailing-garbage.mp3` test file was generated by appending 150kB of `0xDEADBEEF` onto the end of `bear-cbr-variable-frame-size-no-seek-table.mp3`.

Issue: androidx/media#1563

#cherrypick

PiperOrigin-RevId: 670131828
2024-09-02 02:10:13 -07:00
Copybara-Service
854566dbfe Merge pull request #1371 from v-novaltd:dsparano-exo207
PiperOrigin-RevId: 669352191
2024-08-30 09:35:32 -07:00
samrobinson
5eba716410 Add a composition export dump test with a shorter second sequence.
PiperOrigin-RevId: 669348374
2024-08-30 09:20:58 -07:00
sheenachhabra
4e858f7260 Add support for setting last sample duration in Mp4Muxer
PiperOrigin-RevId: 669340763
2024-08-30 08:53:36 -07:00
microkatz
f1654732a6 Added release note for change 2024-08-30 13:47:43 +00:00
sheenachhabra
791483f2d3 Remove TrackMetadataProvider interface
This interface was used by Boxes.moov. This CL removes the interface and just uses the Track object directly.

Since Track is package-private it seems fine to use it directly.
The drawback with interface is that, with every new field addition in the
Track class, we need to update the interface as well (if we need to access that field for moov box).

PiperOrigin-RevId: 669295399
2024-08-30 05:47:07 -07:00
sheenachhabra
613c7a6aa7 Change adjustLastSampleDuration() to getLastSampleDurationVu()
This is a no-op change.

PiperOrigin-RevId: 669283958
2024-08-30 04:55:40 -07:00
dancho
dc9854cc5b Add GlRect helper class
Adds a class that represents an image rectangle
in OpenGL coordinate convention.

android.graphics.Rect puts (0, 0) as the top-left corner:
it enforces `Rect.top <= Rect.bottom` and this matches
`android.graphics.Bitmap` coordinates: docs https://developer.android.com/reference/android/graphics/Rect

This is different from OpenGL coordinates where (0, 0) is
at the bottom-left corner. I.e. GlRect.bottom <= GlRect.top: docs https://registry.khronos.org/OpenGL-Refpages/es3.0/html/glReadPixels.xhtml

The reason for this change is to allow a public API GlRect
getScaledRegion() which selects a region of pixels of a GL texture
to be copied to CPU memory.

PiperOrigin-RevId: 669231826
2024-08-30 01:13:22 -07:00
sheenachhabra
388d1f17b9 Clean up comments in Boxes.java
The CL aims to

1. Shorten unnecessary lengthy chatty comments.
2. Remove dead TODOs.
3. nit fixes for comment style consistency.
4. Remove usage of "we" in the comments.
5. Media3 muxer does not need to mention the behaviour of framework muxer
unless its required for some purpose, so remove them.

PiperOrigin-RevId: 668985875
2024-08-29 10:37:38 -07:00
Copybara-Service
39ed9cf88d Merge pull request #1652 from MiSikora:ms/vtt-speaker
PiperOrigin-RevId: 668976037
2024-08-29 10:08:13 -07:00
Ian Baker
c78ac6e784 Remove intdef value list from javadoc 2024-08-29 16:45:10 +01:00
Copybara-Service
68f55461c0 Merge pull request #1659 from colinkho:drm-util
PiperOrigin-RevId: 668919756
2024-08-29 06:51:13 -07:00
kimvde
37561c829f Fix and refactor logic to remove all frames in ExternalTextureManager
The existing logic was not working sometimes because:

1. The repeated scheduling in releaseAllFramesFromMediaCodec was
starving the thread on which the SurfaceTexture frameAvailableListener
was called.

2. The case where a pending frame arrives on the surface after flush
finishes executing was not handled.

The consequence of both problems is that availableFrameCount ended up
being > pendingFrames.size().

PiperOrigin-RevId: 668916256
2024-08-29 06:37:20 -07:00
Copybara-Service
e30161656e Merge pull request #1667 from JaroslavHerber:patch-1
PiperOrigin-RevId: 668905162
2024-08-29 05:57:59 -07:00
tonihei
41a56daab7 Code formatting 2024-08-29 13:48:41 +01:00
Colin Kho
a509033a5c Correct grammar and format javadoc on DrmUtil.executePost 2024-08-29 13:19:27 +01:00
Colin Kho
4ee34cc00e Refactor HttpMediaDrmCallback's executePost into a shared utility method within DrmUtil 2024-08-29 13:19:27 +01:00
Jaro
02fa4b0f9c Remove obsolete semicolon 2024-08-29 12:25:19 +01:00
ibaker
05cbbffbd7 Add a space in the ParserException message
PiperOrigin-RevId: 668870991
2024-08-29 03:51:40 -07:00
kimvde
3587afc9d7 Move CompositionPlayerSeekTest out of performance directory
These tests do not test performance. Moving them out of this directory
ensures they are run on emulators and on more than 1 physical device.

PiperOrigin-RevId: 668859017
2024-08-29 03:14:12 -07:00
Ian Baker
4d9ad5a6e0 Add release note 2024-08-29 10:27:10 +01:00
Ian Baker
a4aa975a26 Reformat and tweak javadoc 2024-08-29 10:25:06 +01:00
Michał Sikora
cd47e2a134 Remove redundant test 2024-08-29 10:25:06 +01:00
Michał Sikora
24bbe6d921 Rename VTT voice span speakerName to name 2024-08-29 10:25:06 +01:00
Michał Sikora
ce52fc77ec Remove VTT voice span classes 2024-08-29 10:25:06 +01:00
Michał Sikora
108a5ca2f5 Remove language span marker interface 2024-08-29 10:25:06 +01:00
Michał Sikora
d6f08a6237 Add VTT voice spans to cues 2024-08-29 10:25:06 +01:00
tonihei
e8664dbc8e Report initial discontinuity for DASH periods that require preroll
DASH periods don't have to start at the beginning of a segment. In
these cases, they should report an initial discontinuity to let the
player know it needs to expect preroll data (e.g. to flush renderers)

This information is only available in the ChunkSampleStream after
loading the initialization data, so we need to check the sample
streams and tell them to only report discontinuities at the very
beginning of playback. All other position resets are triggered by
the player itself and don't need this method.

Issue: androidx/media#1440
PiperOrigin-RevId: 668831563
2024-08-29 01:57:15 -07:00
kimvde
8367e420ad Import correct nullable annotation
PiperOrigin-RevId: 668521379
2024-08-28 10:54:29 -07:00
dancho
5c2dc7ed4e Add ByteBufferGlEffect.Image for easier format conversion
PiperOrigin-RevId: 668506831
2024-08-28 10:22:01 -07:00
kimvde
070e8217ac Report swallowed exceptions in ExternalTextureManager
The error was swallowed if
ExternalTextureManager.removeAllSurfaceTextureFrames was throwing.

PiperOrigin-RevId: 668480565
2024-08-28 09:14:53 -07:00
tianyifeng
c2e81052e8 Fix flakiness in ExoPlayerTest
In the flaky test `ExoPlayerTest.loading_withLargeAllocationCausingOom_playsRemainingMediaAndThenThrows`, it is indeterministic that when the message `MSG_IO_EXCEPTION` from the loader thread will arrive on the playback looper. If it arrives after `MSG_PERIOD_PREPARED`, then the period can continue loading and get the three samples written to the `SampleQueue` before the intentional OOM surfacing to the `ExoPlayerImplInternal`, otherwise, the OOM will be detected by `ExoPlayerImplInternal` very early and the player disallows to load three samples, which will cause the assertion to fail.

As we are expecting the three samples to play until the playback fails, we should assume that the `Loader` encounters the OOM after those samples loaded, thus we need to put this trigger a bit later until the `SampleStreamItem`s are handled by the `FakeSampleStream`. This could be checked by the return value of `FakeMediaPeriod.continueLoading` (super class implementation). However, `FakeMediaPeriod.continueLoading` originally always returns `true`, which is not aligned with the javadoc of `MediaPeriod.continueLoading`:

"return `true` if progress was made, meaning that `getNextLoadPositionUs()` will return a different value than prior to the call, `false` otherwise."

then we should also modify that logic.

PiperOrigin-RevId: 668438316
2024-08-28 07:16:29 -07:00
microkatz
0ac26c4004 Format with google-java-format 2024-08-28 12:41:49 +00:00
Daniele Sparano
7473156853 Fix output pixel aspect ratio and add test for it 2024-08-28 12:41:49 +00:00
Daniele Sparano
f0463bff8a Set pixel aspect ratio from output media format if set 2024-08-28 12:41:49 +00:00
samrobinson
9c5ea4f1ba Assert silent bytes are 0 in AudioGraphInputTest
PiperOrigin-RevId: 668403467
2024-08-28 05:18:37 -07:00
samrobinson
84f4c7bbcc Create and assert against dump files in ParameterizedAudioExportTest.
PiperOrigin-RevId: 668002146
2024-08-27 08:43:27 -07:00
sheenachhabra
ebd60acc95 Rename LAST_FRAME_DURATION_BEHAVIOR to LAST_SAMPLE_DURATION_BEHAVIOR
The original idea was to set duration for the last video frame, but this behavior actually applies to all tracks.

PiperOrigin-RevId: 667990099
2024-08-27 08:08:25 -07:00
tonihei
d0676245b5 Add AnalyticsListener.onRendererReadyChanged
This callback allows listeners to track when individual renderers
allow or prevent playback from being ready. For example, this is useful
to figure out which renderer blocked the playback the longest.

PiperOrigin-RevId: 667970933
2024-08-27 07:08:42 -07:00
claincly
36d61000fd Reorder the assetions in tests
So it's more clear what's actually broke in the error log.

PiperOrigin-RevId: 667964963
2024-08-27 06:43:58 -07:00
kimvde
a4d0735d4c Fix player hanging when seeking at MediaItem transition
Before this CL, the video sink was stuck if a flush was executed:
- after VideoSink.onInputStreamChanged, which is setting
  pendingInputStreamBufferPresentationTimeUs and
- before the previous stream was fully rendered.

This is because pendingInputStreamBufferPresentationTimeUs was not reset
to TIME_UNSET when flushing the sink, so that the sink was still waiting
for the last frame of the previous stream to be rendered in
handleInputFrame/Bitmap.

PiperOrigin-RevId: 667924517
2024-08-27 04:00:46 -07:00
dancho
c4930c4bb6 Default to OpenGL 3.0 context in DVFP
OpenGL ES 3.0 likely can be used since Android 18.
Moving to a higher version context more often can make
sharing GL context easier for apps

PiperOrigin-RevId: 667915331
2024-08-27 03:23:54 -07:00
dancho
563eb963fd Make ByteBufferConcurrentEffect asynchronous
* Changes to GlUtil to manage Pixel Buffer Objects and asynchronous
GPU -> CPU glReadPixels
* Make ByteBufferConcurrentEffect non-blocking

PiperOrigin-RevId: 667908805
2024-08-27 02:58:51 -07:00
sheenachhabra
52aeffbb3b Remove TODO for empty samples in muxer
There is no reason to write the empty samples so keep
the behaviour same as MediaMuxer

PiperOrigin-RevId: 667495861
2024-08-26 01:28:13 -07:00
claincly
2b031484fe Allow looping sequences in CompositionPlayer
PiperOrigin-RevId: 666793658
2024-08-23 07:54:13 -07:00
dancho
ee93a9832e Add ByteBufferGlEffect for easier non-GPU video effects
* new ByteBufferGlEffect GlEffect that enables API users to
implement an effect that accesses video frame data via CPU-mapped ByteBuffer
* ByteBufferConcurrentEffect responsible for reading
video frame data in CPU-accessible ByteBuffer

PiperOrigin-RevId: 666375594
2024-08-22 09:18:32 -07:00
tianyifeng
c35a9d62ba Bump media3 version to 1.4.1
PiperOrigin-RevId: 666347191
(cherry picked from commit 829cad6912)
2024-08-22 15:16:35 +00:00
tianyifeng
517762c087 Update release notes for 1.4.1 bug fix release
PiperOrigin-RevId: 666328660
(cherry picked from commit 1994ccdea8)
2024-08-22 15:13:14 +00:00
tianyifeng
829cad6912 Bump media3 version to 1.4.1
#cherrypick

PiperOrigin-RevId: 666347191
2024-08-22 07:49:33 -07:00
kak
0060ff2028 Automated Code Change
PiperOrigin-RevId: 666338281
2024-08-22 07:19:57 -07:00
tianyifeng
1994ccdea8 Update release notes for 1.4.1 bug fix release
#cherrypick

PiperOrigin-RevId: 666328660
2024-08-22 06:45:59 -07:00
dancho
6e0e2d0cee Add QueuingGlShaderProgram for effects that run outside GL context
Implement a QueuingGlShaderProgram which queues up OpenGL frames and allows
asynchronous execution of effects that operate on video frames without a
performance penalty.

PiperOrigin-RevId: 666326611
2024-08-22 06:38:28 -07:00
tonihei
6f0cb539d0 Add util method BaseRenderer.getStreamOffsetUs
This simplifies the common pattern of saving this offset in
onStreamChanged to use it at a later point.

PiperOrigin-RevId: 666226263
2024-08-22 00:53:04 -07:00
kimvde
4ba9f59492 Improve logic to wait for a seek in CompositionPlayerSeekTest
The logic was assuming that the shader program was only flushed when
seeking. This is true if a single renderer is used but, with multiple
renderers, the shader program can be flushed at the transition.

This change is necessary to make the test pass for prewarming because 2
video renderers will be used in that case.

PiperOrigin-RevId: 666215967
2024-08-22 00:15:30 -07:00
Googler
6b56f12f15 AtomParser: Update initialization data to CodecPrivate format for Vp9
PiperOrigin-RevId: 666188937
2024-08-21 22:33:28 -07:00
tianyifeng
410b26fba1 Detect SampleStream error after PreloadMediaPeriod has prepared
Per the javadoc, the method `MediaPeriod.maybeThrowPrepareError` is only allowed to be called before the period has completed preparation. For later errors in loading the streams, `SampleStream.maybeThrowError` will be called instead.

PiperOrigin-RevId: 665831430
2024-08-21 05:16:42 -07:00
kimvde
059ad62377 Add timeout to CountDownLatch awaits
This is to avoid tests hanging forever

PiperOrigin-RevId: 665826507
2024-08-21 05:02:18 -07:00
tianyifeng
88b640136a Allow playback regardless buffered duration when loading fails
It is possible for playback to be stuck when there is failure in loading further data, while the player is required to load more due to the buffered duration being under `DefaultLoadControl.bufferForPlayback`. Therefore, we check if there is any loading error in `isLoadingPossible`, so that the player will allow the playback of the existing data rather than waiting forever for the data that can never be loaded.

Issue: androidx/media#1571
PiperOrigin-RevId: 665801674
(cherry picked from commit 351593a250)
2024-08-21 11:38:36 +00:00
tianyifeng
9b39e3514f Update translations
#cherrypick

PiperOrigin-RevId: 663705597
(cherry picked from commit 1ffc962fde)
2024-08-21 11:35:55 +00:00
ibaker
b184677b7b Check WV version before relying on MediaDrm.requiresSecureDecoder
This method was added in API 31 (S) but it's non-functional
(incorrectly, silently, returns `false`) on the Widevine plugin version
(`16.0`) from R (API 30), which some devices up to at least API 34 are
still using.

This results in ExoPlayer incorrectly selecting an insecure decoder for
L1 secure content, and subsequently calling
`MediaCodec.queueInputBuffer` instead of `queueSecureInputBuffer`,
which is not supported and generates the following error:
> Operation not supported in this configuration: ERROR_DRM_CANNOT_HANDLE

Issue: androidx/media#1603

#cherrypick

PiperOrigin-RevId: 662852176
(cherry picked from commit ca455ee858)
2024-08-21 11:35:55 +00:00
tianyifeng
f139d709c7 Handle preload callbacks asynchronously in PreloadMediaSource
When there is an exception thrown from the `LoadTask`, the `Loader` will call `Loader.Callback.onLoadError`. Some implementations of `onLoadError` method may call `MediaPeriod.onContinueLoadingRequested`, and in the `PreloadMediaSource`, its `PreloadMediaPeriodCallback` will be triggered and then it can further call `continueLoading` if it finds needed. However the above process is currently done synchronously, which will cause problem. By calling `continueLoading`, the `Loader` is set with a `currentTask`, and when that long sync logic in `Loader.Callback.onLoadError` ends, the `Loader` will immediately retry, and then a non-null `currentTask` will cause the `IllegalStateException`.

Issue: androidx/media#1568

PiperOrigin-RevId: 662550622
(cherry picked from commit cd532c5fb2)
2024-08-21 11:35:51 +00:00
ibaker
07e9c659d7 Handle HEADSETHOOK as 'play' in MediaButtonReceiver.onReceive
Issue: androidx/media#1581

PiperOrigin-RevId: 662515428
(cherry picked from commit c48c051ce2)
2024-08-21 11:30:14 +00:00
ibaker
eebf081528 Pass missing length into SubtitleParser from SubtitleExtractor
If the length of the `ExtractorInput` is not known then the
`subtitleData` field is re-sized by 1kB each time
(`SubtitleExtractor.DEFAULT_BUFFER_SIZE`), so the end of the array is
often not populated. This change ensures that `length` is propagated to
`SubtitleParser`, so that implementations don't try and parse the
garbage/zero bytes at the end of the array.

Discovered while investigating Issue: androidx/media#1516

#cherrypick

PiperOrigin-RevId: 661195634
(cherry picked from commit f37f9690f4)
2024-08-21 11:23:53 +00:00
michaelkatz
c773789edb Skip invalid media description in SessionDescriptionParser
Some RTSP servers may provide media descriptions for custom streams that are not supported. ExoPlayer should skip the invalid media description and continues parsing the following media descriptions.

To start, ExoPlayer will still error on malformed SDP lines for media descriptions, but will now skip media descriptions with "non-parsable" formats as described by [RFC 8866 Section 5.14](https://datatracker.ietf.org/doc/html/rfc8866#section-5.14).

Issue: androidx/media#1472
PiperOrigin-RevId: 660826116
(cherry picked from commit 8b33ad5811)
2024-08-21 11:23:53 +00:00
ibaker
bf934495df Fix IndexOutOfBoundsException in LegacySubtitleUtil
This is caused when the requested "output start time" is equal to or
larger than the last event time in a `Subtitle` object.

This resolves the error in Issue: androidx/media#1516, but subtitles are still not
renderered (probably because the timestamps aren't what we expect
somewhere, but I need to investigate this part further).

#cherrypick

PiperOrigin-RevId: 660462720
(cherry picked from commit 3763e5bc1d)
2024-08-21 11:23:53 +00:00
claincly
cd2a36f705 Add a method to disallow VFP destroying shared eglContext
Previously in MultiInputVideoGraph, each VFP would destroy the shared
eglContext, such that the same eglContext object is destroyed multiple times.

Adding a flag to disallow this.

The alternative being we could add a flag on the VFP constructor, but I think
that is too subscriptive (meaning if we later might want to add another boolean
to control another GL behaviour, multiple booleans would make the class less
reason-able), and would incur a lot of code changes at places.

PiperOrigin-RevId: 660354367
(cherry picked from commit 8f8e48731e)
2024-08-21 11:23:49 +00:00
Googler
efb79472ff Fixes README instructions for depending on modules locally
#cherrypick

PiperOrigin-RevId: 659504142
(cherry picked from commit e7eef0ce34)
2024-08-21 11:22:24 +00:00
ibaker
eb19aefa57 Implement MP3 ConstantBitrateSeeker.getDataEndPosition()
This is needed to correctly handle files with trailing non-MP3 data
(which is indicated by the length in the `Info` frame being shorter than
the overall length of the file).

The test file was generated by appending 150kB of `DEADBEEF` onto the
end of `test-cbr-info-header.mp3`, and the test asserts that the
extracted samples are identical.

Issue: androidx/media#1480

PiperOrigin-RevId: 658727595
(cherry picked from commit b09cea9e3a)
2024-08-21 11:22:20 +00:00
Copybara-Service
3dfe43b498 Merge pull request #1548 from kikoso:chore/fixed_links
PiperOrigin-RevId: 657138513
(cherry picked from commit f1ed195c10)
2024-08-21 11:21:02 +00:00
Googler
692ab33640 Add support for Audio Vorbis codec in Mp4Muxer.
Update esdsBox to support muxing of files encoded with Vorbis audio codec .

PiperOrigin-RevId: 655159074
(cherry picked from commit b77f1d0f99)
2024-08-21 11:21:02 +00:00
dancho
c2d7417ec8 Destroy EGLSurface immediately by focusing a placeholder surface
eglDestroySurface only destroys the surface when it's made not current.
Pass the placeholder surface in FinalShaderProgramWrapper and use it
when destroying eglSurface.

PiperOrigin-RevId: 655139661
(cherry picked from commit 1797359950)
2024-08-21 11:21:02 +00:00
dancho
b6e78f0b2f Ensure EGLSurface is released on GL thread
Add VideoFrameProcessingTaskExecutor.invoke() method that blocks until
Task has executed on GL thread.
Use that for FinalShaderProgramWrapper.setOutputSurfaceInfo

PiperOrigin-RevId: 655119768
(cherry picked from commit 9c075b692e)
2024-08-21 11:21:01 +00:00
bachinger
d44500e0fb Add EXTRAS_KEY_DOWNLOAD_STATUS to MediaContants
This was used in media1 `MediaItemDescription` to indicate the download
status of a media item. When connected to a legacy
`MediaBrowserServiceCompat` the Media3 browsers converts the legacy
media item to a Media3 `MediaItem` and converts the extras of
`MediaDescriptionCompat.extras` to `MediaMetadata.extras`.

#cherrypick

PiperOrigin-RevId: 654625502
(cherry picked from commit 225ad482b1)
2024-08-21 11:21:01 +00:00
Googler
818e015e05 Add support for Opus audio codec.
Implement dOpsBox to provide support for Opus audio codec

PiperOrigin-RevId: 653288049
(cherry picked from commit 01dda6d3e5)
2024-08-21 11:21:01 +00:00
Googler
66e977a810 Add support for 3gpp h263 codec in Mp4Muxer.
Implement d263Box to provide support for muxing video encoded with the h263 codec.

PiperOrigin-RevId: 653188633
(cherry picked from commit 951f296851)
2024-08-21 11:21:01 +00:00
Googler
a153d26d8f Add support for 3gpp amr-nb audio codec.
To support AMR audio codec(audio/3gpp) add `0x81FF` mode to create damrBox.

Add unit test and an Android end to end test.

PiperOrigin-RevId: 652438693
(cherry picked from commit 11ca78761e)
2024-08-21 11:21:01 +00:00
ibaker
4070535ba9 Remove stray parenthesis from MediaSession.ControllerInfo.toString()
#cherrypick

PiperOrigin-RevId: 651760391
(cherry picked from commit 2c7f2686b7)
2024-08-21 11:21:01 +00:00
ibaker
09b6e8fd04 Transform double-tap of HEADSETHOOK to skip-to-next
As reported in Issue: androidx/media#1493 we already use `HEADSETHOOK` to start
waiting for a double-tap, but we don't consider it for the second tap.

Similarly, we previously considered `PLAY` for the second tap, but not the first.
Only `HEADSETHOOK` and `PLAY_PAUSE` are
[documented](https://developer.android.com/reference/androidx/media3/session/MediaSession#media-key-events-mapping)
to transform double-tap to `seekToNext`. So this change removes the
`PLAY` detection from the second tap.

PiperOrigin-RevId: 651017522
(cherry picked from commit c64dacf3df)
2024-08-21 11:20:56 +00:00
Googler
0e75a0a5e1 Add support to MPEG4 codec in Mp4Muxer.
Add support for MPEG4 codec to enable muxing video encoded with the mp4v-es codec. Use esdsBox method to generate esds box required for Mp4v box.

PiperOrigin-RevId: 651000744
(cherry picked from commit 34a802ef38)
2024-08-21 11:20:10 +00:00
Googler
70e8e1bf45 Support for Large CodecSpecificData in ESDS box
Some external media files have CodecSpecificData greater than 128 bytes. Currently, that size
isn't fitting in one byte. Hence, added support to store large CodecSpecificDataSize, as per
ISO standard, by extending to more than one byte as required.

PiperOrigin-RevId: 650972472
(cherry picked from commit cf90d2624d)
2024-08-21 11:20:10 +00:00
dancho
0b6249b8ae Destroy eglSurface as soon as Surface changes
eglDestroySurface now unbinds surface from the GL
thread to ensure quick release of resources.

PiperOrigin-RevId: 650938712
(cherry picked from commit 70a6b5d50d)
2024-08-21 11:20:10 +00:00
Googler
c4bb43517d Add support for amr-wb audio codec.
Implement damrBox to provide support for amr-wb audio codec.

Add unit test and an Android end to end test.

PiperOrigin-RevId: 650210732
(cherry picked from commit 6e18cb0053)
2024-08-21 11:20:10 +00:00
Googler
f2d3072d1a Refactor audioEsdsBox to esdsBox
Since the muxer supported only AAC audio codec, the esdsBox was unconditionally created within the audioSampleEntry. This CL refactors the box creation logic by moving it to the codecSpecificBox method. This is to make adding support for new audio codecs easier.

PiperOrigin-RevId: 650130935
(cherry picked from commit a269355369)
2024-08-21 11:20:10 +00:00
ibaker
ae5a7e54ae Fix TTML handling of inherited percentage tts:fontSize values
The percentage should be interpreted as relative to the size of a parent
node.

This change makes this inheritance work correctly for percentages in
both the parent and child. It does not fix the case of a non-percentage
parent size with a percentage child size.

PiperOrigin-RevId: 649631055
(cherry picked from commit bb2fd002ae)
2024-08-21 11:20:07 +00:00
bachinger
4dc21fd743 Count down three playback states to match the assertion
PiperOrigin-RevId: 648629427
(cherry picked from commit ec3a58f8db)
2024-08-21 11:18:57 +00:00
tianyifeng
351593a250 Allow playback regardless buffered duration when loading fails
It is possible for playback to be stuck when there is failure in loading further data, while the player is required to load more due to the buffered duration being under `DefaultLoadControl.bufferForPlayback`. Therefore, we check if there is any loading error in `isLoadingPossible`, so that the player will allow the playback of the existing data rather than waiting forever for the data that can never be loaded.

Issue: androidx/media#1571
#cherrypick
PiperOrigin-RevId: 665801674
2024-08-21 03:37:42 -07:00
sheenachhabra
875953f971 Add Mp4Extractor test for interleaved editable video tracks file
PiperOrigin-RevId: 665800463
2024-08-21 03:34:28 -07:00
Copybara-Service
1bb48b1c47 Merge pull request #1616 from colinkho:chmain
PiperOrigin-RevId: 665779059
2024-08-21 02:32:16 -07:00
kimvde
51622b6d80 Add BufferingVideoSink
PiperOrigin-RevId: 665733825
2024-08-21 00:15:25 -07:00
Copybara-Service
5304f227a7 Merge pull request #1624 from colinkho:comain
PiperOrigin-RevId: 665430514
2024-08-20 10:47:43 -07:00
Ian Baker
bed1dadcbf Add release note 2024-08-20 17:50:58 +01:00
Ian Baker
9c25845cd7 Restore a couple of blank lines 2024-08-20 17:47:48 +01:00
Ian Baker
a6f5d3daf5 Format with google-java-format 2024-08-20 17:47:48 +01:00
Colin Kho
0a0444b9a5 Use Suppliers.memoize for lazy instantiation of AudioManager 2024-08-20 17:47:47 +01:00
Colin Kho
6af92b0af3 Differ AudioManager retrieval to whenever AudioFocusManagement is required 2024-08-20 17:47:47 +01:00
sheenachhabra
4b7c5100f1 Move moov box generation to Boxes.java
This is a no-op change.
Like all other boxes, moov box creation can also be in
Boxes.java class.

PiperOrigin-RevId: 665359529
2024-08-20 07:47:58 -07:00
rohks
f52ca07446 Add option to enable index-based seeking in AmrExtractor
This allows seek operations in files that were previously unseekable, particularly those with variable bitrate (VBR) or constant bitrate (CBR) with silence frames.

New samples for tests were created by adding silence frames to existing narrowband and wideband versions.

PiperOrigin-RevId: 665349241
2024-08-20 07:17:26 -07:00
Copybara-Service
24f3856ea4 Merge pull request #1580 from colinkho:main
PiperOrigin-RevId: 665330328
2024-08-20 06:17:20 -07:00
Ian Baker
41430aaa0c Fix annotation position and clarify javadoc 2024-08-20 13:42:00 +01:00
sheenachhabra
bb3948aa98 Implement interleaving of editable video tracks
The CL adds another way of writing editable video
tracks where the samples will be interleaved with the
primary track samples in the "mdat" box.

PiperOrigin-RevId: 665313751
2024-08-20 05:28:29 -07:00
Copybara-Service
643ec73e58 Merge pull request #1607 from colinkho:atom
PiperOrigin-RevId: 665285219
2024-08-20 04:03:45 -07:00
ibaker
b04b270c2b Use SubtitleExtractor from MediaParserChunkExtractor
This uses a 'bundled' extractor (`SubtitleExtractor`) because
`MediaParser` doesn't support transcoding subtitles to
`application/x-media3-cues`. This transcoding is required to support
non-legacy subtitle handling where they are parsed during extraction,
instead of during rendering.

PiperOrigin-RevId: 665282298
2024-08-20 03:54:17 -07:00
Ian Baker
af5df6e700 Add retryCount parameter to make it clear onLoadStarted is called for retries too 2024-08-20 11:28:18 +01:00
Colin Kho
33d9b78e21 Fix javadoc of onLoadStarted & remove error information as fields 2024-08-20 11:13:41 +01:00
Colin Kho
1cc1bf02ef Added onLoadStarted callback to Loader's callback 2024-08-20 11:13:41 +01:00
Ian Baker
2e49e91c84 Rename to singular BoxParser for consistency with other types like ColorParser 2024-08-20 11:11:14 +01:00
Ian Baker
77f3ef9b25 Add @UnstableApi annotation 2024-08-20 11:09:50 +01:00
Ian Baker
2dd6794ba0 More atom -> box updates in public API surface 2024-08-20 11:09:50 +01:00
Colin Kho
3a95b24afc Rename BoxParsers tag from AtomParsers to BoxParsers 2024-08-20 11:09:50 +01:00
Colin Kho
6ba9c9ff9e Rename AtomParsers to BoxParsers 2024-08-20 11:09:50 +01:00
Colin Kho
0b5443c450 Make Extractor/Mp4 Atom Parsing code reusable by external custom mp4 extractors 2024-08-20 11:09:50 +01:00
kimvde
266f16823f Remove VideoSink.isFrameDropAllowedOnInput
This is an unnecessary layer

PiperOrigin-RevId: 665267029
2024-08-20 03:09:13 -07:00
kimvde
9b69690856 Move some tests from CompositionPlaybackTest out of performance
directory

PiperOrigin-RevId: 665202382
2024-08-20 00:13:21 -07:00
Colin Kho
f9f3e67ee2 Include initialization chunk wording in getChunkIndex method 2024-08-19 10:31:34 -07:00
Colin Kho
4a6c5c7598 Cache ChunkIndex once load is done 2024-08-19 10:28:14 -07:00
ktrajkovski
ab366e2626 Fix licensing link from http to https in IAMF related files.
PiperOrigin-RevId: 664845636
2024-08-19 09:38:49 -07:00
claincly
677f8ad9f4 Fix edge case: no frame between trim position and the next sync sample
PiperOrigin-RevId: 664841893
2024-08-19 09:32:32 -07:00
ktrajkovski
63b45b7503 Build IAMF libraries with gradle and ndk-build.
In order to support building with `gradle` a new `build.gradle` file was added together with `Android.mk`, `Application.mk`, and `libiamf.mk` necessary for local builds with NDK.

After this change, IAMF files may also be played in the ExoPlayer demo built locally (without blaze).

PiperOrigin-RevId: 664841684
2024-08-19 09:29:47 -07:00
tonihei
ba00798451 Make NalUnitTargetBuffer public
This class is a useful utility for custom TS parsers that operate
on NAL units.

PiperOrigin-RevId: 664795988
2024-08-19 07:23:05 -07:00
kimvde
cd0a7e1143 Document why playback tests are in the performance package
PiperOrigin-RevId: 664792272
2024-08-19 07:14:32 -07:00
kimvde
b95b534f68 Fix inverted colors in call to Color.rgb
PiperOrigin-RevId: 664716252
2024-08-19 03:00:45 -07:00
sheenachhabra
ed905a6498 Refactor pending sample writing logic in Mp4Writer
The refactored method can be reused when implementing depth/editing format
interleave mode.

PiperOrigin-RevId: 663774290
2024-08-16 10:29:32 -07:00
ibaker
3600d04719 Deprecate SingleSampleMediaChunk
This is only used as part of legacy subtitle decoding. If any apps are
using it directly (rather than via `DashMediaSource` or `SsMediaSource`)
they probably need to enable legacy decoding in `TextRenderer` to avoid
playback failures.

PiperOrigin-RevId: 663740343
2024-08-16 08:36:38 -07:00
ibaker
91a95e23c9 Remove CueDecoder.decode overload without length & offset params
This method is only used in tests, so let's remove it and just test the
one that takes `length` and `offset`.

PiperOrigin-RevId: 663731139
2024-08-16 07:59:12 -07:00
tianyifeng
1ffc962fde Update translations
#cherrypick

PiperOrigin-RevId: 663705597
2024-08-16 06:06:40 -07:00
Googler
afac17bef0 Mp4Muxer: Add support for Vp9 codec
Implement vpcCBox to provide support for muxing video encoded using the VP9 codec.

PiperOrigin-RevId: 663603654
2024-08-15 22:34:56 -07:00
jbibik
c3313c0049 Add jvmTarget and kotlin-android plugin to common_ktx module
According to https://kotlinlang.org/docs/gradle-configure-project.html#check-for-jvm-target-compatibility-of-related-compile-tasks and https://kotlinlang.org/docs/gradle-configure-project.html#what-can-go-wrong-if-targets-are-incompatible, we require explicit jvmTarget setting in order not to run into:

`'compileDebugJavaWithJavac' task (current target is 1.8) and 'compileDebugKotlin' task (current target is 17) jvm target compatibility should be set to the same Java version.`

Missing the plugin and the jvmTarget resulted in a failed `compileDebugKotlin` task for any application code, which was trying to import common_ktx functions (e.g. `Player.listen` with `Unresolved reference: listen` error)

PiperOrigin-RevId: 663358677
2024-08-15 10:36:10 -07:00
Colin Kho
1c1fd4d8b8 Add getter for ChunkIndex on InitializationChunk 2024-08-15 10:00:56 -07:00
rohks
50708f8c37 Add AmrExtractor tests with FLAG_ENABLE_CONSTANT_BITRATE_SEEKING_ALWAYS
PiperOrigin-RevId: 663320349
2024-08-15 08:53:28 -07:00
tonihei
b58a1d16f7 Split DefaultAudioSink.writeBuffer in two seperate steps
The method currently has two ways of operating with a complicated
contract:
 1. outputBuffer == null -> sets a new outputBuffer and starts
    draining it as far as possible.
 2. outputBuffer != null -> continues draining a previous buffer,
    requires that the input parameter is the same as outputBuffer.

This contract can be made cleaner by splitting the logic into
(1) setting a new output buffer and (2) draining the output buffer.

Only one parameter is needed for each method and there is no
requirement to pass in the same argument as before because we already
know what we are draining. This also means the caller of the method
doesn't have to keep track of the actual outputBuffer, allowing
further processing steps in the future that change the drained
instance.

PiperOrigin-RevId: 663316251
2024-08-15 08:33:31 -07:00
sheenachhabra
7a1f3629ff Extract method to find minimum presentation timestamp across tracks
This is a ground work of implementing depth/editing format interleave mode.

PiperOrigin-RevId: 663301670
2024-08-15 07:40:27 -07:00
ibaker
873ec1544e Remove nested import of Player.Events from PlayerExtensions.listen
This makes it clearer that the part inside and outside the parentheses
are clearly different, and not "just" a reference to `Player.Events`.
Specifically this syntax is showing that the function has a `Player`
"receiver", **and** is a function from `Player.Events` to `Unit`.

https://kotlinlang.org/docs/lambdas.html#function-types

PiperOrigin-RevId: 663293405
2024-08-15 07:07:48 -07:00
ibaker
ee27334f06 Rename Atom to Mp4Box, and move it to container module
This makes the class available to custom MP4-parsing implementations,
while also allowing it to be used by `muxer` in future.

'Box' is the term used throughout the ISO 14496-12 spec, while the
'Atom' nomenclature was used in an earlier form of the spec
(Quicktime).

This change moves it from `extractor.mp4.Atom` to `container.Mp4Box`,
to be consistent with existing MP4-specific types in the `container`
module like `Mp4TimestampData`.

PiperOrigin-RevId: 663274752
2024-08-15 05:44:32 -07:00
sheenachhabra
4df5ecb045 Rename flushPending() to writePendingTrackSamples()
The new name is more readable

PiperOrigin-RevId: 663264188
2024-08-15 04:54:19 -07:00
ktrajkovski
fc18ad3135 Add LibiamfAudioRenderer's constructor to proguard rules.
Making a change to the `proguard-rules.txt` also requires a test added as `IamfModuleProguard`.

PiperOrigin-RevId: 663239150
2024-08-15 02:55:38 -07:00
sheenachhabra
68eaa061e0 Add support for depth/editing file format in Mp4Extractor
PiperOrigin-RevId: 662956209
2024-08-14 09:55:01 -07:00
claincly
92635342fb Move the test of removeAudio to robolectric
The audio mixing is not deterministic on real device testing because of
threading, but runs deterministically on robolectric tests

PiperOrigin-RevId: 662925912
2024-08-14 08:23:22 -07:00
dancho
879771ded2 Add an experimental flag for renderer dynamic scheduling
Use ExoPlayer dynamic scheduling to reduce the render() interval for
older API devices where `DefaultCodec.getMaxPendingFrameCount()` is set
to 1 in order to prevent frame drops.

Controlled via API on DefaultDecoderFactory.

Add TransformerForegroundSpeedTest that mimics transcoding while the app
is in foreground.

PiperOrigin-RevId: 662925764
2024-08-14 08:20:21 -07:00
sheenachhabra
dbc9f5e0d1 Add EditablevideoParameter class
The parameter class will allow addition of more
parameters (link shouldInterleaveSamples), which are specific to
editable video file format.

PiperOrigin-RevId: 662923844
2024-08-14 08:13:05 -07:00
rohks
74cfd2ad79 Deduplicate AMR samples and use dump file prefix for unique names
AMR samples with identical data but different names, previously used to generate uniquely named dump files, have been deleted. Instead, `AssertionConfig` is now used to set the dump file prefix, ensuring files are generated with unique names.

PiperOrigin-RevId: 662883541
2024-08-14 05:36:26 -07:00
ibaker
9d62845c45 Pass explicit securityLevel into MediaDrm.requiresSecureDecoder
Previous to this change, `FrameworkMediaDrm.requiresSecureDecoder`
ignores its `sessionId` parameter on API 31+, and uses only the
`mimeType` parameter. This means the result [assumes the session is
opened at the 'default security
level'](https://developer.android.com/reference/android/media/MediaDrm#requiresSecureDecoder(java.lang.String)):
> The default security level is defined as the highest security level
> supported on the device.

This change is a no-op in all (?) cases, because the `ExoMediaDrm`
interface only exposes the zero-arg `openSession()` method, which in the
framework case **also** assumes the highest security level is preferred:
> By default, sessions are opened at the native security level of the
> device.

However, it seems more obviously correct to only make this
"highest/native security level" assumption in one place
(`openSession()`), and check the session's **actual** security level
everywhere else.

Issue: androidx/media#1603
PiperOrigin-RevId: 662872860
2024-08-14 04:52:33 -07:00
rohks
e9cfd72083 Enable CBR seeking for files with unknown length in AmrExtractor
Constant bit rate (CBR) seeking can be enabled even when the length of the file is not known.

Additionally, dump files for these files have been updated to accurately log the `position` when `timeUs` is set to `0`.

PiperOrigin-RevId: 662868607
2024-08-14 04:32:38 -07:00
ibaker
ca455ee858 Check WV version before relying on MediaDrm.requiresSecureDecoder
This method was added in API 31 (S) but it's non-functional
(incorrectly, silently, returns `false`) on the Widevine plugin version
(`16.0`) from R (API 30), which some devices up to at least API 34 are
still using.

This results in ExoPlayer incorrectly selecting an insecure decoder for
L1 secure content, and subsequently calling
`MediaCodec.queueInputBuffer` instead of `queueSecureInputBuffer`,
which is not supported and generates the following error:
> Operation not supported in this configuration: ERROR_DRM_CANNOT_HANDLE

Issue: androidx/media#1603

#cherrypick

PiperOrigin-RevId: 662852176
2024-08-14 03:27:50 -07:00
kak
e7f037bff5 Automated Code Change
PiperOrigin-RevId: 662793513
2024-08-13 23:32:49 -07:00
sheenachhabra
68393832b4 Use Track instead of TrackToken internally
The TrackToken is primarily for public API.
Using Track object internally will remove unnecessary
type casting at various places.

PiperOrigin-RevId: 662564224
2024-08-13 10:24:04 -07:00
tianyifeng
cd532c5fb2 Handle preload callbacks asynchronously in PreloadMediaSource
When there is an exception thrown from the `LoadTask`, the `Loader` will call `Loader.Callback.onLoadError`. Some implementations of `onLoadError` method may call `MediaPeriod.onContinueLoadingRequested`, and in the `PreloadMediaSource`, its `PreloadMediaPeriodCallback` will be triggered and then it can further call `continueLoading` if it finds needed. However the above process is currently done synchronously, which will cause problem. By calling `continueLoading`, the `Loader` is set with a `currentTask`, and when that long sync logic in `Loader.Callback.onLoadError` ends, the `Loader` will immediately retry, and then a non-null `currentTask` will cause the `IllegalStateException`.

Issue: androidx/media#1568

#cherrypick

PiperOrigin-RevId: 662550622
2024-08-13 09:45:35 -07:00
ktrajkovski
0b23285bae Add nativeDecoderPointer field to IamfDecoder.
Moving this field to `IamfDecoder` instead of `iamf_jni` allows multiple instances of the IAMF decoder with possibly different configurations at the same time.

PiperOrigin-RevId: 662548068
2024-08-13 09:38:44 -07:00
ktrajkovski
92cff64321 Add spatial effects to IAMF support in Exoplayer.
Check if the output device supports spatialization for the requested output format. If so, return a stream decoded for 6 channels in a 5.1 layout. Otherwise, return a stream decoded for 2 channels in a binaural layout.

PiperOrigin-RevId: 662546818
2024-08-13 09:34:50 -07:00
ibaker
c48c051ce2 Handle HEADSETHOOK as 'play' in MediaButtonReceiver.onReceive
Issue: androidx/media#1581

#cherrypick

PiperOrigin-RevId: 662515428
2024-08-13 07:53:04 -07:00
simakova
2d527b08c3 Toggle video effects in the composition demo.
Add an option to enable or disable video effects in preview or export.

PiperOrigin-RevId: 662488658
2024-08-13 06:14:22 -07:00
ibaker
9d008da356 Remove unused Atom.ContainerAtom.getChildAtomOfTypeCount(int) method
This is in preparation for making this class public.

PiperOrigin-RevId: 662466043
2024-08-13 04:41:05 -07:00
ibaker
9dfd72b6c6 Move atom parsing logic from Atom to AtomParsers
This is groundwork to moving `Atom` to the `container` library, which
we want to do before making it public (so it can be used by `muxer` in
future).

PiperOrigin-RevId: 662453520
2024-08-13 03:50:40 -07:00
sheenachhabra
96f2c7ece7 Refactor editable video track related logic
This is to reuse same logic in depth/edit file format `interleave` mode.

PiperOrigin-RevId: 662117528
2024-08-12 09:32:37 -07:00
claincly
62da288caf Support removing video in previewing
Video playback will be disabled if *any* `EditedMediaItem` removes video. This
is consistent with Transformer.

PiperOrigin-RevId: 662093484
2024-08-12 08:28:16 -07:00
claincly
46eeabb877 Support setRemoveAudio in CompositionPlayer
PiperOrigin-RevId: 662063725
2024-08-12 06:34:44 -07:00
ibaker
ef6cb5d913 Use runCatching instead of try/catch in PlayerExtensionsTest
PiperOrigin-RevId: 661992546
2024-08-12 01:44:53 -07:00
Googler
a76ff16179 Manage wakelock when playback suppression is being handled.
After this change, a WakeLock of PowerManager#PARTIAL_WAKE_LOCK level would be acquired when the media is paused due to playback attempt without suitable output.
This WakeLock will be release either when the suitable media output has been connected or the set timeout to do so has expired.

PiperOrigin-RevId: 661570346
2024-08-10 02:28:12 -07:00
Googler
65a471e3db Automated Code Change
PiperOrigin-RevId: 661516063
2024-08-09 21:18:31 -07:00
sheenachhabra
117ac2e3f4 Create a common constant for large box size header
PiperOrigin-RevId: 661323230
2024-08-09 10:48:46 -07:00
ibaker
0411e1937b Deprecate SingleSampleMediaSource
This is an additional signal that legacy subtitle support needs to be
explicitly enabled, and is going away at some point.

PiperOrigin-RevId: 661305694
2024-08-09 10:07:14 -07:00
sheenachhabra
94abb9515b Write same timestamp in edit data MP4 as in outer MP4
The exiting code ensured that the timestamp is same only when it is set by the app.

PiperOrigin-RevId: 661283124
2024-08-09 09:05:33 -07:00
dancho
931b0e25f1 Add a DefaultDecoderFactory option to configure operating rate
This has the largest impact during operations with no encoder, such as
frame extraction. Add a matching performance test.

PiperOrigin-RevId: 661220044
2024-08-09 05:03:02 -07:00
ibaker
f37f9690f4 Pass missing length into SubtitleParser from SubtitleExtractor
If the length of the `ExtractorInput` is not known then the
`subtitleData` field is re-sized by 1kB each time
(`SubtitleExtractor.DEFAULT_BUFFER_SIZE`), so the end of the array is
often not populated. This change ensures that `length` is propagated to
`SubtitleParser`, so that implementations don't try and parse the
garbage/zero bytes at the end of the array.

Discovered while investigating Issue: androidx/media#1516

#cherrypick

PiperOrigin-RevId: 661195634
2024-08-09 03:09:09 -07:00
michaelkatz
8b33ad5811 Skip invalid media description in SessionDescriptionParser
Some RTSP servers may provide media descriptions for custom streams that are not supported. ExoPlayer should skip the invalid media description and continues parsing the following media descriptions.

To start, ExoPlayer will still error on malformed SDP lines for media descriptions, but will now skip media descriptions with "non-parsable" formats as described by [RFC 8866 Section 5.14](https://datatracker.ietf.org/doc/html/rfc8866#section-5.14).

Issue: androidx/media#1472
PiperOrigin-RevId: 660826116
2024-08-08 07:22:22 -07:00
dancho
c1078e3cfa Do not checkState based on input data
App users can choose arbitrary data that might not be
anticipated by developers. Transformer shouldn't `checkState` based on
media data or file type -- report an error for unsupported data instead.

Public API change `ImageAssetLoader` needs to parse MIME type and now accepts
`Context` as parameter.

PiperOrigin-RevId: 660762459
2024-08-08 03:18:27 -07:00
kak
2202397758 Automated Code Change
PiperOrigin-RevId: 660491742
2024-08-07 12:28:25 -07:00
ibaker
3763e5bc1d Fix IndexOutOfBoundsException in LegacySubtitleUtil
This is caused when the requested "output start time" is equal to or
larger than the last event time in a `Subtitle` object.

This resolves the error in Issue: androidx/media#1516, but subtitles are still not
renderered (probably because the timestamps aren't what we expect
somewhere, but I need to investigate this part further).

#cherrypick

PiperOrigin-RevId: 660462720
2024-08-07 11:16:30 -07:00
sheenachhabra
0530d663bc Rename muxer/Mp4Utils.java to muxer/Mp4MuxerUtil.java
PiperOrigin-RevId: 660417092
2024-08-07 09:23:53 -07:00
rohks
bef134a093 Fix IndexSeekMap.getTimeUs() javadoc
PiperOrigin-RevId: 660376007
2024-08-07 07:11:43 -07:00
rohks
9bc199f107 Refactor IndexSeeker to utilize IndexSeekMap, removing redundant code
This is a no-op change.

PiperOrigin-RevId: 660370824
2024-08-07 06:51:42 -07:00
claincly
8f8e48731e Add a method to disallow VFP destroying shared eglContext
Previously in MultiInputVideoGraph, each VFP would destroy the shared
eglContext, such that the same eglContext object is destroyed multiple times.

Adding a flag to disallow this.

The alternative being we could add a flag on the VFP constructor, but I think
that is too subscriptive (meaning if we later might want to add another boolean
to control another GL behaviour, multiple booleans would make the class less
reason-able), and would incur a lot of code changes at places.

PiperOrigin-RevId: 660354367
2024-08-07 05:50:32 -07:00
rohks
a087f82fa8 Make IndexSeekMap dynamic, allowing seek points to be added later
PiperOrigin-RevId: 660324829
2024-08-07 03:57:51 -07:00
sheenachhabra
5dac58995a Make parsing editable track map method non static
The method is not supposed to work with any input byte[]
so its best to make it non static and add appropriate validations.

PiperOrigin-RevId: 659906543
2024-08-06 04:44:40 -07:00
samrobinson
a23f655cf4 Check AssetLoader supports output type required by TransformerInternal.
PiperOrigin-RevId: 659557063
2024-08-05 08:10:23 -07:00
claincly
5165d7df68 Rollback of ffc45820b9
PiperOrigin-RevId: 659520675
2024-08-05 05:31:46 -07:00
tonihei
b00e018697 Add ImageDecoder for external image loading libraries
The integration with external libraries like Glide or Coil is
currently extremely complicated. Providing the boilerplate code
to handle the ImageDecoder interface and a better hook for custom
image decoders in DefaultRenderersFactory allows apps to easily
inject their own logic for external image loading libraries.

PiperOrigin-RevId: 659508914
2024-08-05 04:36:16 -07:00
Googler
e7eef0ce34 Fixes README instructions for depending on modules locally
#cherrypick

PiperOrigin-RevId: 659504142
2024-08-05 04:14:22 -07:00
kak
399f48ab42 Automated Code Change
PiperOrigin-RevId: 658829974
2024-08-02 10:29:47 -07:00
sheenachhabra
6a7e9132fd Write sample location key when muxing editable tracks
There are two ways to write editable tracks samples.
1. In the embedded edit data MP4.
2. Interleaved with primary tracks samples.

Initial plan was to support only option 1 but then the
decision is to support both ways. To identify between these two
an additional key will be required.

Option 2 is yet to be implemented in Mp4Muxer.

PiperOrigin-RevId: 658791214
2024-08-02 08:03:35 -07:00
rohks
8aab324fb4 Add the getSampleSize() API to MediaExtractorCompat
This API allows users to retrieve the sample size, ensuring they can allocate sufficient buffer capacity before utilizing the `readSampleData(ByteBuffer buffer, int offset)` method to read data.

PiperOrigin-RevId: 658772408
2024-08-02 06:33:06 -07:00
ktrajkovski
20df8b282a Collect configuration values in IamfDecoder.
Instead of hard-coding values in multiple files, all default values are declared in `IamfDecoder`. Additionally, the max number of frames used for output buffer initialisation is fetched from `libiamf` native functions.

PiperOrigin-RevId: 658772175
2024-08-02 06:30:34 -07:00
jbibik
9ace81bf2f Move PlayerExtensions into common-ktx module
For now, the only extension function on the `Player` has been used in a Compose demo. It can be promoted to a proper module where in the future other extension functions will reside. Given that `Player` is in `androidx.media3.common`, the corresponding KTX library for it is `androidx.media3.common-ktx`

To start using the new `suspend fun listen`, one must add `androidx.media3:media3-common-ktx` as a Gradle dependency and `import androidx.media3.common.listen`

PiperOrigin-RevId: 658771029
2024-08-02 06:24:10 -07:00
sheenachhabra
86bd1df632 Move editable track type constants to container module
This is to share the constants with the extractor.

PiperOrigin-RevId: 658755661
2024-08-02 05:10:16 -07:00
dancho
a79b80fcee Support setting H.264 level
Try to follow recommendations in
https://developer.android.com/media/optimize/sharing
more closely

For maximum compatibility the H.264 level should be less than or equal to 4.1

PiperOrigin-RevId: 658755139
2024-08-02 05:06:53 -07:00
ibaker
b09cea9e3a Implement MP3 ConstantBitrateSeeker.getDataEndPosition()
This is needed to correctly handle files with trailing non-MP3 data
(which is indicated by the length in the `Info` frame being shorter than
the overall length of the file).

The test file was generated by appending 150kB of `DEADBEEF` onto the
end of `test-cbr-info-header.mp3`, and the test asserts that the
extracted samples are identical.

Issue: androidx/media#1480

#cherrypick

PiperOrigin-RevId: 658727595
2024-08-02 02:51:49 -07:00
jbibik
018d0488e1 Create a new media3-common-ktx module
It will be used for Kotlin-specific functionality like extension functions on the classes from the `media3-common` module. To import it, add the following to your build.gradle file:

`implementation("androidx.media3:media3-common-ktx:1.X.Y")`

PiperOrigin-RevId: 658492256
2024-08-01 12:14:16 -07:00
jbibik
61e68d3f24 Add Kotlin extension function on Player
Given that `Player` interface is written in Java and is has a callback-based Listener interface, we need an adapter for the Kotlin-native world.

This change introduces a suspending function `listen` that creates a coroutine, in order to capture `Player.Events`.

PiperOrigin-RevId: 658478608
2024-08-01 11:41:11 -07:00
tonihei
56c419c1b3 Run CronetDataSource contract test for all Cronet providers
The data source may behave differently, depending on the provider,
so we can extend the contract test to check all available providers.

PiperOrigin-RevId: 658457650
2024-08-01 10:49:37 -07:00
dancho
a98a37aa75 Initial Frame Extractor Transformer Factory
Package-private until API is more useable.

Similar to frame analyzer mode: uses ImageReader instead of an encoder,
and no muxer.

PiperOrigin-RevId: 658446675
2024-08-01 10:21:10 -07:00
dancho
ddc86686b7 Experimental flag to limit the number of frames in encoder
Transformer.experimentalSetMaxFramesInEncoder controls max number
of frames in encoder.

VideoFrameProcessor now allows delayed releasing of frames to Surface,
while still using the original presentation time.

VideoSampleExporter can now configure video graphs to not render frames
automatically to output surface.

VideoSampleExporter.VideoGraphWrapper tracks how many frames are ready
to be rendered to Surface, and how many frames are already in-use by encoder.

PiperOrigin-RevId: 658429969
2024-08-01 09:33:16 -07:00
tonihei
766902634e Define Glide version
PiperOrigin-RevId: 658378243
2024-08-01 06:18:28 -07:00
sheenachhabra
9d14b91d94 Dump auxiliaryTrackType in a string format
PiperOrigin-RevId: 658372776
2024-08-01 05:59:54 -07:00
sheenachhabra
bd399eb601 Refactor get editable track types from map logic
The new method will be reused when Mp4Extractor need to
parse this metadata.

PiperOrigin-RevId: 658372567
2024-08-01 05:56:24 -07:00
samrobinson
34d3dc926d Use getDecoderNames method, rather than duplicating logic.
PiperOrigin-RevId: 658368479
2024-08-01 05:38:30 -07:00
Googler
b951833aec Update list of supported video and audio mime type.
Add parameterized test for codecs supported by InAppMuxer.
Split TransformerWithInAppMuxerEndToEndParameterizedTest tests
between parameterized and non parameterized

This will be helpful for adding more tests which need not to
be parameterized.

PiperOrigin-RevId: 658353532
2024-08-01 04:31:04 -07:00
ktrajkovski
01593a9c1f Replace RuntimeException with IllegalStateException.
Refactor to replace instances of `RuntimeException` with `IllegalStateException`.
This change ensures exceptions can be handled more specifically without
unintended catches of unrelated exceptions.

PiperOrigin-RevId: 658337459
2024-08-01 03:20:54 -07:00
claincly
f3bf4ad5fe Disable frame dropping on sm-x200@API34
From debug trace when decoding a 30fps video, the decoder output

```
"0us",
"33366us",
"66733us",
"100100us",
"133466us",
"166833us",
"200200us",
```

But the frame processor only received, despite setting `ALLOW_FRAME_DROP`:

```
"0us",
"166833us",
"200200us",
```

PiperOrigin-RevId: 658079749
2024-07-31 11:30:18 -07:00
sheenachhabra
dc3a9cea3e Validate data before creating MdtaMetadataEntry object
For some predefined keys the type of value is already defined.
Early validation will help avoiding error when processing this data later.

PiperOrigin-RevId: 658060844
2024-07-31 10:42:11 -07:00
kimvde
ffc45820b9 Move playback tests outside of performance directory
PiperOrigin-RevId: 658055853
2024-07-31 10:29:39 -07:00
Copybara-Service
40de898b22 Merge pull request #1576 from colinkho:main
PiperOrigin-RevId: 657990422
2024-07-31 06:55:45 -07:00
dancho
6e678e511b Enable experimentalRepeatInputBitmapWithoutResampling
Speed up image to video export by default.

PiperOrigin-RevId: 657958037
2024-07-31 04:40:29 -07:00
Ian Baker
c637774cc2 Take Executor instead of ExecutorService
This also avoids shutting down the externally-provided ExecutorService,
which we shouldn't do (since we don't really own it, and didn't create
it).
2024-07-31 10:37:14 +01:00
Googler
4a99dc4c94 Reserve space for the skip buttons in the media3 demo
This is to avoid the shuffle custom action jumping around when skipping to the first or last item of a playlist.

PiperOrigin-RevId: 657925696
2024-07-31 02:31:03 -07:00
Ian Baker
ff22838c0d Add javadoc summary fragment 2024-07-31 10:00:01 +01:00
Colin Kho
68e65ec884 Allow custom ExecutorService to be supplied to the Loader 2024-07-30 13:20:30 -07:00
dancho
3f49f5c157 Check for EGL_NO_SURFACE and similar in GlUtil
`== null` does not check for equality with
EGL_NO_SURFACE, EGL_NO_CONTEXT, or EGL_NO_DISPLAY.

PiperOrigin-RevId: 657651835
2024-07-30 10:49:06 -07:00
ktrajkovski
04bfeec751 Add decoding functions to IamfDecoder and LibiamfAudioRender.
PiperOrigin-RevId: 657621223
2024-07-30 09:33:31 -07:00
Copybara-Service
e9787c4196 Merge pull request #1566 from colinkho:main
PiperOrigin-RevId: 657571792
2024-07-30 06:42:22 -07:00
rohks
8b7b1b51a9 Add MediaDataSourceAdapter
Added a new data source which acts an adapter to read media data from platform `MediaDataSource`. This enables adding the `setDataSource(MediaDataSource)` API to `MediaExtractorCompat`.

PiperOrigin-RevId: 657564901
2024-07-30 06:16:30 -07:00
rohks
867e9ea2da Add APIs to set data source using content URI, file path or HTTP URL
Added three `setDataSource` APIs in `MediaExtractorCompat`:
- `setDataSource(Context context, Uri uri, @Nullable Map<String, String> headers)` to set data source with a content URI and optional headers.
- `setDataSource(String path)` to set data source using a file path or HTTP URL.
- `setDataSource(String path, @Nullable Map<String, String> headers)` to set data source using a file path or HTTP URL with optional headers.

PiperOrigin-RevId: 657563973
2024-07-30 06:12:56 -07:00
kimvde
ca5a26a409 Add frame count tests for preview
This is to ensure prewarming doesn't introduce any regression

PiperOrigin-RevId: 657559693
2024-07-30 06:04:41 -07:00
rohks
004b9d69fd Handle case where length is unset in FileDescriptorDataSource
- Modified the logic of `open()` and `read()` methods to handle scenarios where length is unset for the `FileDescriptor` provided.
- Added unit test and contract test to handle this case.

Also used `getDeclaredLength()` instead of `getLength()` to set the length of `AssetFileDescriptor` in unit tests and contract tests.

PiperOrigin-RevId: 657551343
2024-07-30 05:29:05 -07:00
ibaker
8360e44e07 Remove multidex config from iamf_decoder library's
`AndroidManifest.xml`. This doesn't have Gradle wiring yet, so was
probably missed as part of https://github.com/androidx/media/pull/1549.

PiperOrigin-RevId: 657549817
2024-07-30 05:22:49 -07:00
Ian Baker
335d5e7a1d Add some tests to TrackSelectionUtilTest 2024-07-29 15:12:23 +01:00
Colin Kho
0ead7bb221 Refactor getMaxVideoSizeInViewport into TrackSelectionUtil so it can be reused in external code 2024-07-29 15:12:23 +01:00
tofunmi
7d784d4067 Use FEATURE_HlgEditing to determine HDR support
PiperOrigin-RevId: 657174992
2024-07-29 06:11:48 -07:00
Copybara-Service
f1ed195c10 Merge pull request #1548 from kikoso:chore/fixed_links
PiperOrigin-RevId: 657138513
2024-07-29 03:36:26 -07:00
Googler
f6dc02fa6a Unsuppress/suppress playback on suitable media output updates
PiperOrigin-RevId: 657111555
2024-07-29 01:43:46 -07:00
Enrique López Mañas
debea15bdf chore: fixed links 2024-07-26 12:21:31 +00:00
Enrique López Mañas
b93cc68a67 chore: updated docs and broken links 2024-07-26 12:21:31 +00:00
rohks
32c9d62d39 Add DataSource contract tests to verify offset and position
These tests addresses two identified gaps in the contract:
 - Ensures that the output buffer offset passed to the `DataSource.read` method is correctly applied.
 - Verifies that the position within the input stream is properly incremented when reading in two parts.

PiperOrigin-RevId: 656358935
2024-07-26 05:14:51 -07:00
Copybara-Service
ccf704b30b Merge pull request #1549 from MGaetan89:min_sdk_21
PiperOrigin-RevId: 656358426
2024-07-26 05:11:46 -07:00
dancho
940e28e4db Refactor threading in FinalShaderProgramWrapper
Public methods either assert they're running GL thread, or
submit a task to run on GL thread.

Move methods to keep interface implementations together.

Add javadoc to VideoFrameProcessingTaskExecutor to clarify which
thread can call each public method.

PiperOrigin-RevId: 655978796
2024-07-25 09:23:41 -07:00
Googler
300453820c Selectable builtin speaker support for Wear OS
The builtin speaker is to be supported as a suitable output when that is deliberately selected for the media playback by the user in Wear OS.

PiperOrigin-RevId: 655950824
2024-07-25 07:42:31 -07:00
tofunmi
685ea1e616 create and use SpeedProviderMediaPeriod in CompositionPlayer
PiperOrigin-RevId: 655945332
2024-07-25 07:21:31 -07:00
Ian Baker
4af220a2ac Clarify javadoc in controller test app 2024-07-25 15:12:07 +01:00
tofunmi
043de45763 Store the speed provider in timestamp adjustment
PiperOrigin-RevId: 655928480
2024-07-25 06:09:02 -07:00
Ian Baker
8c79a8fed2 Remove tools:replace="android:name" from manifests where multidex config has been removed 2024-07-25 13:12:10 +01:00
Googler
aaa6561aa9 Rename tests in Boxestest
Replace the box name with Codec name in the test.

PiperOrigin-RevId: 655915063
2024-07-25 05:09:43 -07:00
kimvde
3211f38ebc Add tests for image seeking in CompositionPlayer
This is to make sure prewarming won't introduce any regression when it
will be implemented.

PiperOrigin-RevId: 655879558
2024-07-25 02:27:45 -07:00
rohks
80202bc9f2 Use getDeclaredLength() for setting length of AssetFileDescriptor
Should have been part of the change: 0ac90855b4.

PiperOrigin-RevId: 655873821
2024-07-25 02:05:14 -07:00
Googler
d160aa2520 Modify tests in BoxesTest
Replace the string with the field from MimeType class

PiperOrigin-RevId: 655783812
2024-07-24 19:37:43 -07:00
tianyifeng
edd3a3f349 Fix bug where BasePreloadManager.Listener invokes from incorrect thread
The DefaultPreloadManagerTest didn't to catch this because we use main looper as the preload looper in the tests. This CL also improves the tests by assigning the preload looper with one that corresponds to a different thread.

PiperOrigin-RevId: 655664189
2024-07-24 12:41:21 -07:00
Gaëtan Muller
ed15ab012f Revert changes to androidx.media3.session.legacy 2024-07-24 16:17:02 +01:00
Gaëtan Muller
b90f00c774 Revert erroneous changes 2024-07-24 16:17:02 +01:00
Gaëtan Muller
eefb37a0ba Simplify VolumeProviderCompat.getVolumeProvider() 2024-07-24 16:17:01 +01:00
Gaëtan Muller
0593b36dad Remove MediaSessionImplBase, MediaSessionImplApi18, and MediaSessionImplApi19 2024-07-24 16:17:01 +01:00
Gaëtan Muller
0c564004c4 Remove unnecessary @SdkSuppress annotation 2024-07-24 16:17:01 +01:00
Gaëtan Muller
b84a63d318 Fix deprecation message 2024-07-24 16:17:01 +01:00
Gaëtan Muller
7406b78fbc Update javadoc 2024-07-24 16:17:01 +01:00
Gaëtan Muller
28edfcbb69 Remove unnecessary SDK_INT checks 2024-07-24 16:17:01 +01:00
Gaëtan Muller
7289764a65 Remove unnecessary @RequiresApi 2024-07-24 16:17:00 +01:00
Gaëtan Muller
71b8c32a6f Remove Multidex 2024-07-24 16:17:00 +01:00
Gaëtan Muller
e1633ff03b Remove unnecessary targetApi 2024-07-24 16:17:00 +01:00
Gaëtan Muller
64eedceb8c Use project.ext.minSdkVersion everywhere 2024-07-24 16:17:00 +01:00
ibaker
4fc66f42d6 Move OpusDecoderTest from /test/ (Robolectric) to /androidTest/
The dependency on the native `opusV2JNI` library doesn't work from
Robolectric, so the `assumeTrue` statements in this test always fail,
and the tests are always skipped. Moving it to an instrumentation test
allows the native library to be successfully loaded, and the test to be
run.

PiperOrigin-RevId: 655570129
2024-07-24 08:11:13 -07:00
tianyifeng
d70ff7e4d2 Fix the release notes for 1.4.0 stable release
#cherrypick

PiperOrigin-RevId: 655558346
2024-07-24 07:31:39 -07:00
kimvde
da4c962e09 Rename VideoSink methods
This is following a renaming of registerInputFrame to handleInputFrame.
- queueInputBitmap is renamed to handleInputBitmap for consistency with
  handleInputFrame.
- registerInputStream is renamed to onInputStreamChanged for consistency
  with media3 method names.

PiperOrigin-RevId: 655529699
2024-07-24 05:56:17 -07:00
ktrajkovski
a1f20de3a9 Import a library for a Java clarity fix.
PiperOrigin-RevId: 655488843
2024-07-24 02:38:00 -07:00
dancho
7103f21da9 Use WORKING_COLOR_SPACE_DEFAULT in multi-sequence compositions
Build upon Transformer.videoFrameProcessorFactory in MultipleInputVideoGraph
Check that Transformer.videoFrameProcessorFactory is DefaultVideoFrameProcessor
for multi-input video

Fixes https://github.com/androidx/media/issues/1509

PiperOrigin-RevId: 655232381
2024-07-23 11:14:12 -07:00
dancho
9a42d03466 Enable experimentalAdjustSurfaceTextureTransformationMatrix
PiperOrigin-RevId: 655231134
2024-07-23 11:11:14 -07:00
ktrajkovski
2793ba845e Implement IamfDecoder and LibiamfAudioRenderer configuration.
PiperOrigin-RevId: 655212678
2024-07-23 10:23:48 -07:00
Googler
b77f1d0f99 Add support for Audio Vorbis codec in Mp4Muxer.
Update esdsBox to support muxing of files encoded with Vorbis audio codec .

PiperOrigin-RevId: 655159074
2024-07-23 07:39:26 -07:00
rohks
0ac90855b4 Use getDeclaredLength() in setDataSource for AssetFileDescriptor
Aligns `MediaExtractorCompat` with platform behavior by using `getDeclaredLength()`
instead of `getLength()` when setting the data source. This ensures compatibility
with asset files where the length is not predefined, even though it may result
in reading across multiple logical files when backed by the same physical file.

PiperOrigin-RevId: 655153390
2024-07-23 07:17:52 -07:00
dancho
1797359950 Destroy EGLSurface immediately by focusing a placeholder surface
eglDestroySurface only destroys the surface when it's made not current.
Pass the placeholder surface in FinalShaderProgramWrapper and use it
when destroying eglSurface.

PiperOrigin-RevId: 655139661
2024-07-23 06:22:08 -07:00
dancho
9c075b692e Ensure EGLSurface is released on GL thread
Add VideoFrameProcessingTaskExecutor.invoke() method that blocks until
Task has executed on GL thread.
Use that for FinalShaderProgramWrapper.setOutputSurfaceInfo

PiperOrigin-RevId: 655119768
2024-07-23 04:59:59 -07:00
Googler
3c5c81fc3e BoxesTest: Add default format builders
Move the common initialization code for format builder into separate methods to reduce code duplication.

PiperOrigin-RevId: 655118764
2024-07-23 04:54:32 -07:00
kimvde
225d336713 Remove VideoSinkProvider parameter from renderers
PiperOrigin-RevId: 655073481
2024-07-23 01:41:30 -07:00
tonihei
6147050b90 Delay flush after AudioTrack pause to ramp down volume
AudioTrack automatically ramps down volume when pausing. However,
when this happens as part of a AudioSink.flush() operation, we
need to postpone the actual flush() until the ramp down finished
in the audio system. Otherwise audio is just cut off, creating pop
sounds.

Delaying the release is fine now, because DefaultAudioSink starts
creating a new track immediately without waiting for the previous
track to be released.

Also using the opportunity to add more comments about related quirks
of the AudioTrack flush/release handling for more context.

PiperOrigin-RevId: 654794818
2024-07-22 09:58:53 -07:00
bachinger
225ad482b1 Add EXTRAS_KEY_DOWNLOAD_STATUS to MediaContants
This was used in media1 `MediaItemDescription` to indicate the download
status of a media item. When connected to a legacy
`MediaBrowserServiceCompat` the Media3 browsers converts the legacy
media item to a Media3 `MediaItem` and converts the extras of
`MediaDescriptionCompat.extras` to `MediaMetadata.extras`.

#cherrypick

PiperOrigin-RevId: 654625502
2024-07-21 23:41:37 -07:00
tonihei
0a8ca18305 Create new AudioTrack without waiting for previous released tracks
We currently wait until a previous AudioTrack from the same
DefaultAudioSink is released on a background thread before attempting
to initialize a new AudioTrack. This is done to avoid issues where
the releasing track blocks some shared audio memory, preventing a new
track from being created.

The current solution has two main shortcomings:
 - In most cases, the system can easily handle multiple AudioTracks
   and waiting for the release just causes unnecessary delays (e.g.
   when seeking).
 - It only waits for a previous track from the same DefaultAudioSink,
   not accounting for any other tracks that may be in the process of
   being released from other players.

To improve on both shortcomings, we can
 (1) move the check for "is releasing tracks and thus may block shared
 memory" to the static release infrastructure to be shared across all
 player instances.
 (2) optimistically create a new AudioTrack immediately without waiting
 for the previous one to be fully released.
 (3) extend the existing retry logic that already retries failed
 attempts for 100ms to only start the timer when ongoing releases are
 done. This ensures we really waited until we have all shared resources
 we can get before giving up completely. This also acts as a replacement
 for change (2) to handle situations where creating a second track is
 genuinely not possible. Also increase threshold to 200ms as the new
 unit test is falky on a real device with just 100ms (highlighting that
 the device needed more than 100ms to clean up internal resources).

PiperOrigin-RevId: 654053123
2024-07-19 10:41:17 -07:00
bachinger
076bc451f2 Update Kotlin Gradle plugin to version 1.9.10.
According to b/292763081 the build failure
`unresolved reference: addLast` in the session-demo is caused
by a bug in the Kotlin compiler 1.9.0 to which we depend.

Bumping the Kotlin plugin version to 1.9.10 which is the next
above 1.9.0 as listed in [1], the build problem is resolved.

Further, the Kotlin extension compiler version is set to 1.5.3
to make compose work with Kotlin 1.9.10 (see [2] for the
Kotlin/Compose compatibility map).

[1] https://kotlinlang.org/docs/releases.html#release-details
[2] https://developer.android.com/jetpack/androidx/releases/compose-kotlin

PiperOrigin-RevId: 654030537
2024-07-19 09:33:41 -07:00
tonihei
50f9f35353 Guard timeline access in ImaSSAIMS against empty timelines
All methods check if the player is currently handling the ad source
by calling isCurrentAdPlaying(). This method was missing a check
for empty timelines that throws an exception when trying to access
a non-existent period.

Also add this check to two methods that assume the current item
is the ads source, but didn't check it yet.

PiperOrigin-RevId: 653963557
2024-07-19 04:27:36 -07:00
rohks
0def3b215c Add APIs to set data source using AssetFileDescriptor & FileDescriptor
Introduced three `setDataSource` APIs in `MediaExtractorCompat`, enabling the use of `AssetFileDescriptor` and `FileDescriptor` to set the data source.

PiperOrigin-RevId: 653957035
2024-07-19 04:00:44 -07:00
tofunmi
1e28755b4a CompositionPlayer: clip silence with media source
PiperOrigin-RevId: 653667116
2024-07-18 10:17:29 -07:00
tianyifeng
3c9332bb48 Update media3 version for 1.4.0 stable release
#cherrypick

PiperOrigin-RevId: 653654999
2024-07-18 09:43:16 -07:00
dancho
f8bdb7e59f Fix division by zero in MuxerWrapper
PiperOrigin-RevId: 653644998
2024-07-18 09:18:34 -07:00
tianyifeng
1cbcd20851 Fix the release notes for 1.4.0 stable release
#cherrypick

PiperOrigin-RevId: 653640574
2024-07-18 09:06:22 -07:00
jbibik
4ba7fd0ace Fix javadoc formatting from backticks to tags
PiperOrigin-RevId: 653629763
2024-07-18 08:34:56 -07:00
tonihei
f7a726bb11 Add MediaCodec loudness controller for API35+
This controller connects the audio output to the MediaCodec so
that it can automatically propagate CTA-2075 loudness metadata.

PiperOrigin-RevId: 653628503
2024-07-18 08:31:14 -07:00
sheenachhabra
a52df6d29e Make Mp4MoovStructure.moov() method static
Creating a moov box is same as creating any other box so
there is no particular need to have a separate class for this.
In a follow up CL the method will be moved into Boxes.java along with
other box creation methods.

Made nit changes in the final fields ordering to match with
constructor parameter ordering.

PiperOrigin-RevId: 653602558
2024-07-18 06:50:42 -07:00
samrobinson
570be3680c Reduce test flakes by adding effects to multi-sequence playback test.
This test is flaky at p4head, because CompositionPlayer has no logic to
ensure a specific input is used to configure the audio graph. Depending
on which sequence registers input first, it changes the output format
of the media.

By setting effects on the 2nd sequence, both inputs are the same format
and this flakiness is avoided in the test.

PiperOrigin-RevId: 653582441
2024-07-18 05:40:49 -07:00
samrobinson
e28270b4cb Add preview tests for duration wrt clipping and speed adjustment.
PiperOrigin-RevId: 653569415
2024-07-18 04:43:41 -07:00
tonihei
54f5e0729e Support detached surface mode from API 35
This replaces the existing PlaceholderSurface mode with a more
efficient solution that doesn't require a GL texture or a new
thread.

PiperOrigin-RevId: 653537596
2024-07-18 02:29:57 -07:00
sheenachhabra
51d27d7575 Merge Boxes.moov() into Mp4MoovStucture.moov()
Boxes.moov() simply wraps the subboxes and this logic can be
put into main method which has all the logic of creating moov box.
This is to eventually move Mp4MoovStucture.moov() into
Boxes.java where all other box creation methods are already present.

PiperOrigin-RevId: 653319292
2024-07-17 12:15:44 -07:00
Googler
01dda6d3e5 Add support for Opus audio codec.
Implement dOpsBox to provide support for Opus audio codec

PiperOrigin-RevId: 653288049
2024-07-17 10:51:43 -07:00
tianyifeng
68e8d9cb68 Merge release notes for media3 1.4.0 stable release
#cherrypick

PiperOrigin-RevId: 653261278
2024-07-17 09:38:23 -07:00
tonihei
a86b5f065f Move release note to right section
The change is purely video releated.

PiperOrigin-RevId: 653229546
2024-07-17 07:41:15 -07:00
samrobinson
d0afb96c40 Implement repeat mode for CompositionPlayer.
-----

Context:
* Each sequence is wrapped as a single MediaSource, each being played
by an underlying ExoPlayer.
* Repeat mode is typically implemented in Players as a seek to the next
item in the playlist.

-----

This CL:

Repeat mode is triggered by listening for when the main input player
sees a play when ready change due to the end of the media item.

There is a slight delay at the end of the playback, before it repeats.
Setting repeat mode on the underlying players addresses this, but means
that the players will seek without waiting for the CompositionPlayer,
and as such previewAudioPipeline does not get the correct signals
around blocking/flushing.

PreviewAudioPipeline - The seek position can validly be C.TIME_UNSET,
however preview pipeline did not handle this case.

CompositionPlayer getContentPosition is given (through a lambda) as a
supplier to the State object, which means any comparisons between
previous/new state for this value does not work. In SimpleBasePlayer,
there is logic to use the positionDiscontinuityPositionUs for the
position change (see getPositionInfo called from
updateStateAndInformListeners), however this logic is not considered in
getMediaItemTransitionReason, so a condition needed to be added for
this case.

-----

Tests:
* Dump files clearly show the position and data is repeated.
* Assertions on the reasons for transitions or position
discontinuities.
PiperOrigin-RevId: 653210278
2024-07-17 06:22:02 -07:00
tofunmi
29a2486ce3 implement getDurationAfterEffectApplied in TimestampAdjustment Effect
PiperOrigin-RevId: 653206245
2024-07-17 06:07:27 -07:00
dancho
c05a5c6237 Rollback of 9151dbf9e6
PiperOrigin-RevId: 653192624
2024-07-17 05:08:52 -07:00
Googler
951f296851 Add support for 3gpp h263 codec in Mp4Muxer.
Implement d263Box to provide support for muxing video encoded with the h263 codec.

PiperOrigin-RevId: 653188633
2024-07-17 04:53:28 -07:00
ibaker
76db936d68 Enable lint errors in session tests
Also resolve some failures.

Lint checks [aren't enabled in tests by default](http://groups.google.com/g/lint-dev/c/rtjVpqHmY0Y).

This change suppresses `NewApi` failures in Robolectric tests because
these tests only run at 'target SDK' by default (currently 30), but the
lint doesn't understand this and so flags spurious issues with API
usages below this.

PiperOrigin-RevId: 653172059
2024-07-17 03:50:05 -07:00
ibaker
3b8ea4a412 Allow negative presentation time in ReorderingSeiMessageQueue
PiperOrigin-RevId: 653170404
2024-07-17 03:43:34 -07:00
simakova
e78802d0d8 Update recommendation on setting frame rate for images
PiperOrigin-RevId: 653167049
2024-07-17 03:31:57 -07:00
dancho
e94ee03cd0 Add 1920 and 1088 as guesses for surface texture crop fix
Some devices always allocate 1920x1088 pixel buffers,
regardless of video resolution

PiperOrigin-RevId: 653148028
2024-07-17 02:16:46 -07:00
tofunmi
1e43404468 Correct typo
PiperOrigin-RevId: 653137432
2024-07-17 01:35:01 -07:00
rohks
ded66debc3 Retry alternative addresses on timeout in SNTP client
Changed the default timeout for SNTP requests to 1 second.

Issue: androidx/media#1540
PiperOrigin-RevId: 652897579
2024-07-16 10:38:10 -07:00
ibaker
7f304092ae Explicitly set the group ID
This avoids it being implicitly set from the `rootProject.name`

PiperOrigin-RevId: 652879619
2024-07-16 09:47:17 -07:00
tonihei
b4975a1b49 Remove unused field in MediaCodecAdapter
The field is always 0 as the only publicly accessible creator
methods set it to zero in all cases.

PiperOrigin-RevId: 652858810
2024-07-16 08:42:48 -07:00
dancho
f68cf30791 Change ExportTest assertion to allow mismatching level
Due to differences in MediaCodec behavior between minor Android
versions, this test was flaky.

PiperOrigin-RevId: 652841512
2024-07-16 07:55:34 -07:00
samrobinson
1c3fe20826 Handle no supported encoder & muxer mime types in the Encoder factory.
PiperOrigin-RevId: 652825117
2024-07-16 07:03:25 -07:00
ibaker
99679645fc Add a test for MediaCodecRenderer handling of IllegalStateException
This is a regression test for the bug introduced in bb9ff30c3a
which was manually spotted and fixed in 0d2bf49d6a.

Reverting the fix causes this test to fail.

This test is a bit hacky because we have to munge the stack trace of
the `IllegalStateException` to make it look like it was thrown from
inside `MediaCodec`. We deliberately do this 'badly' (e.g. using
`fakeMethod`) to avoid a future reader being confused by a
fake-but-plausible stack trace.

PiperOrigin-RevId: 652820878
2024-07-16 06:47:31 -07:00
tonihei
d4c6e39dfb Further unapplied rotation clean-up
Now the value is guaranteed to be zero (see bb9ff30c3a), we can
remove the rotation handling for it in the UI module. We can also
enforce the documentation more clearly by not even setting the
value to anything other than zero.

PiperOrigin-RevId: 652772091
2024-07-16 03:35:46 -07:00
ktrajkovski
104fcc1c76 Add skeleton of the IAMF JNI wrapper for the native decoder in libiamf.
PiperOrigin-RevId: 652761237
2024-07-16 02:54:21 -07:00
rohks
c60baabb1c Allow changing SNTP client timeout
Added a method to set the timeout for the SNTP request.

Also changed the default timeout to 5s instead of 10s as it seemed quite high.

Issue: androidx/media#1540
PiperOrigin-RevId: 652566008
2024-07-15 12:36:30 -07:00
tonihei
0d2bf49d6a Partially revert MediaCodecException detection
The change in bb9ff30c3a removed the detection util completely,
but even on API21+, the codec exceptions can be thrown as
IllegalStateException and we should reinstate this check to
correctly classify the exceptions.

PiperOrigin-RevId: 652557091
2024-07-15 12:06:22 -07:00
tianyifeng
ec1954c1d5 Release all ListenerHolders when clearing ListenerSet
When removing one listener from the `ListenerSet`, we release the corresponding `ListenerHolder`, which prevents the event queued or sent later than the removal from being invoked. We should also do this in the method `ListenerSet.clear()` where every listener is removed.

PiperOrigin-RevId: 652535216
2024-07-15 10:59:13 -07:00
sheenachhabra
d747f38f59 Skip TransformerPauseResumeTest on vivo 1901
The process crashes unexpectedly on vivo devices.
When test is run individually it completes successfully.

PiperOrigin-RevId: 652496852
2024-07-15 08:56:37 -07:00
ibaker
bb9ff30c3a Remove dead code related to MediaCodec now minSdk is 21
This removes several workarounds that are no longer needed, including
`codecNeedsMonoChannelCountWorkaround` which has been permanently
disabled since the (incomplete) minSdk 19 clean-up in fb7438378d.

PiperOrigin-RevId: 652495578
2024-07-15 08:51:35 -07:00
Googler
1bb8d5f956 Update internal reference.
PiperOrigin-RevId: 652448337
2024-07-15 05:34:32 -07:00
dancho
bfe4824bfd Rollback of 9151dbf9e6
PiperOrigin-RevId: 652443920
2024-07-15 05:14:58 -07:00
Googler
11ca78761e Add support for 3gpp amr-nb audio codec.
To support AMR audio codec(audio/3gpp) add `0x81FF` mode to create damrBox.

Add unit test and an Android end to end test.

PiperOrigin-RevId: 652438693
2024-07-15 04:48:28 -07:00
tofunmi
4da1e26206 Take effects in account when calculating presentationDurationUs
PiperOrigin-RevId: 652425099
2024-07-15 03:40:59 -07:00
andrewlewis
4b7cc80593 Handle muxing with timestamps offset from zero in wrapper
Sources (for example media projection) can populate the `Surface` from
`SurfaceAssetLoader` with timestamps that don't start from zero. But
`MuxerWrapper` assumes the latest sample timestamp can be used as the duration
when it calculates average bitrate and notifies its listener.

This can cause a crash because the calculated average bitrate can be zero if
the denominator duration is large enough.

Use the max minus first sample timestamp across tracks instead to get the
duration.

Side note: the large timestamps from the surface texture when using media
projection arrive unchanged (apart from conversion from ns to us) in effect
implementations and in the muxer wrapper (and are passed to the underlying
muxer). The outputs of media3 muxer and the framework muxer are similar.

PiperOrigin-RevId: 652422674
2024-07-15 03:30:26 -07:00
tonihei
c510ab81bb Update compileSdk to 35
This should have no influence on app behavior and other policies
and just allows code to depend on new API 35 platform symbols.

PiperOrigin-RevId: 652414026
2024-07-15 02:45:55 -07:00
ibaker
268c8cf6a2 Remove dead code from MediaStyleNotificationHelper now minSdk is 21
Tested using session demo app on API 21 emulator, and checked
notification layout looked correct.

PiperOrigin-RevId: 652403407
2024-07-15 01:56:37 -07:00
tonihei
e96ca5a242 Decouple displaySurface from placeholderSurface
We currently use displaySurface == placeholderSurface IF codec != null
as a signal that the codec is configured with a placeholder. In the
future, this placeholder might not be needed and we can decouple this
state a bit better by leaving displaySurface == null in this case and
only using placeholderSurface instead of null when setting a Surface
to the codec.

PiperOrigin-RevId: 652391729
2024-07-15 01:04:48 -07:00
ibaker
d035b745cd Remove dead code from ExoPlayerImpl now minSdk is 21
PiperOrigin-RevId: 651815091
2024-07-12 10:27:49 -07:00
ibaker
6a9ff95bf0 Bump minSdk to 21 and remove resulting simple dead code
All other AndroidX libraries have already increased their min SDK to
21.

This change renames private symbols to remove `V21` suffixes and
similar, but doesn't change public or protected symbols with similar
names, to avoid needless breakages/churn on users of the library.

Some of the dead code removal is more complex, so I've split it out
into follow-up changes to make it easier to review.

PiperOrigin-RevId: 651776556
2024-07-12 08:11:01 -07:00
ibaker
5fa9985ce6 Add H264_ prefix to NalUnitUtil.NAL_UNIT_TYPE_* constants
Also promote all H.265 constants to be public in `NalUnitUtil` with
`H265_` prefixes, for consistency.

A lot of these names are used in h.265 too (and `NalUnitUtil` handles
both), but with different values, so this rename aims to avoid
accidentally using an h.264 value in an h.265 context.

PiperOrigin-RevId: 651774188
2024-07-12 08:02:24 -07:00
ibaker
2c7f2686b7 Remove stray parenthesis from MediaSession.ControllerInfo.toString()
#cherrypick

PiperOrigin-RevId: 651760391
2024-07-12 06:58:33 -07:00
dancho
9151dbf9e6 Avoid decoder input skipping close to media end duration
MediaCodecRenderer might not be able to determine whether a sample
is last in time. Do not use decoder input skipping optimization
for buffers close to media end duration in order to ensure
last frame is rendered.

PiperOrigin-RevId: 651757814
2024-07-12 06:44:51 -07:00
dancho
09239a8a55 Add tests for releasing DefaultVideoFrameProcessor output surface
Add parametrized tests for DefaultVideoFrameProcessor that ensure
output EGL surface can be released, and deallocated.

PiperOrigin-RevId: 651712624
2024-07-12 03:07:20 -07:00
Googler
4c4e24db60 Recognize QC's MV-HEVC decoder.
PiperOrigin-RevId: 651539516
2024-07-11 14:40:39 -07:00
tianyifeng
7aa70a5f2f Add Listener to BasePreloadManager to propagate preload events to apps
PiperOrigin-RevId: 651421044
2024-07-11 08:36:41 -07:00
Googler
735e0cf8a1 Add an MV-HEVC test to Mp4ExtractorParameterizedTest.
PiperOrigin-RevId: 651392787
2024-07-11 06:52:15 -07:00
claincly
1ba2d98fce Remove extra comment line
PiperOrigin-RevId: 651367551
2024-07-11 05:06:17 -07:00
claincly
81f5a5f5f3 Set a longer muxer timeout on emulators
This is because the force EOS workaround for videos is longer than the original
muxer timeout. This way the apps depending on Transformer won't have to manually
set the muxer timeout on emulators.

PiperOrigin-RevId: 651355755
2024-07-11 04:12:59 -07:00
ktrajkovski
b7f141ad2a Replace sample mp4 files related to IAMF and update the dump files.
PiperOrigin-RevId: 651347968
2024-07-11 03:41:56 -07:00
ibaker
0ea555dae0 Stop using SubtitleTranscodingExtractor and deprecate it
The integration with `SubtitleTranscodingExtractorOutput` has been
moved inside the relevant `Extractor` implementations instead.

PiperOrigin-RevId: 651213564
2024-07-10 18:09:53 -07:00
ibaker
240b6fd606 Fix 'duplicate class' error caused by incomplete kt stdlib metadata
When testing locally it seemed that upgrading KGP to `1.9.20` resolved
this, but testing again confirms this was incorrect, so the problem
was reintroduced in 00d1e70a34.

This change reverts back to KGP `1.9.0` (since upgrading didn't help)
and instead implements the workaround suggested in
https://issuetracker.google.com/278545487. The workaround is now in
the `lib-common` `build.gradle` file, which is transitively depended
on by all modules (and therefore all apps that use this library), so
there's no need for any developer action, and so the release note is
removed.

Issue: androidx/media#1262
PiperOrigin-RevId: 651066418
2024-07-10 10:20:12 -07:00
kimvde
21992bff33 Call getFrameReleaseAction from VideoSink when enabled
VideoSink.registerInputFrame is now called for every input frame (not
only the ones that should be rendered to the input surface) because it's
the VideoSink that decides whether it wants the frame to be rendered.

PiperOrigin-RevId: 651049851
2024-07-10 09:33:11 -07:00
dancho
0ff9e0723d Expose fMP4 FLAG_READ_WITHIN_GOP_SAMPLE_DEPENDENCIES to DASH
In order for DASH playback to benefit from
FLAG_READ_WITHIN_GOP_SAMPLE_DEPENDENCIES, the fMP4 extractor flag
must be set. The smallest API change that allows this is to add an
experimental method to BundledChunkExtractor.

Add a dash end-to-end test to verify that video frames are skipped at
decoder input.

PiperOrigin-RevId: 651046676
2024-07-10 09:23:35 -07:00
ibaker
c64dacf3df Transform double-tap of HEADSETHOOK to skip-to-next
As reported in Issue: androidx/media#1493 we already use `HEADSETHOOK` to start
waiting for a double-tap, but we don't consider it for the second tap.

Similarly, we previously considered `PLAY` for the second tap, but not the first.
Only `HEADSETHOOK` and `PLAY_PAUSE` are
[documented](https://developer.android.com/reference/androidx/media3/session/MediaSession#media-key-events-mapping)
to transform double-tap to `seekToNext`. So this change removes the
`PLAY` detection from the second tap.

#cherrypick

PiperOrigin-RevId: 651017522
2024-07-10 07:52:18 -07:00
tonihei
e8778d77fa Document Renderer and BaseRenderer in more detail
PiperOrigin-RevId: 651008509
2024-07-10 07:24:02 -07:00
Googler
f673ef43b4 Add support for SEI and vexu box parsing.
Stereo view information is stored in the 3D reference displays information SEI and the optional vexu box.  Parsing of the SEI and vexu box is added, and based on the parsed info, proper mapping of primary/secondary view to left/right eye is determined.

PiperOrigin-RevId: 651002190
2024-07-10 07:00:29 -07:00
Googler
34a802ef38 Add support to MPEG4 codec in Mp4Muxer.
Add support for MPEG4 codec to enable muxing video encoded with the mp4v-es codec. Use esdsBox method to generate esds box required for Mp4v box.

PiperOrigin-RevId: 651000744
2024-07-10 06:55:00 -07:00
Googler
cf90d2624d Support for Large CodecSpecificData in ESDS box
Some external media files have CodecSpecificData greater than 128 bytes. Currently, that size
isn't fitting in one byte. Hence, added support to store large CodecSpecificDataSize, as per
ISO standard, by extending to more than one byte as required.

PiperOrigin-RevId: 650972472
2024-07-10 05:16:01 -07:00
tonihei
9d4e43cf55 Support multiple DataSource configurations in DataSourceContractTest
PiperOrigin-RevId: 650967939
2024-07-10 05:01:33 -07:00
ibaker
a202fd0c9c Add parameter comments in ReorderingSeiMessageQueueTest
PiperOrigin-RevId: 650959556
2024-07-10 04:34:24 -07:00
dancho
70a6b5d50d Destroy eglSurface as soon as Surface changes
eglDestroySurface now unbinds surface from the GL
thread to ensure quick release of resources.

PiperOrigin-RevId: 650938712
2024-07-10 03:21:48 -07:00
andrewlewis
dcc3e439e2 Handle AVI containers with no keyframes in index
If there is no keyframe in the index assume that the first chunk has a
keyframe.

PiperOrigin-RevId: 650915467
2024-07-10 01:51:06 -07:00
tonihei
b145a9b35e Limit maximum number of parallel metadata retrievals.
The operation almost always acquires resources straight-away,
for example a new thread or file access. This means starting
many metadata retrievals in parallel easily causes resource
contention. This problem can be alleviated by limiting the
maximum number of parallel retrievals.

Local testing showed a 20% speedup for local file retrievals
when limited to 5 in parallel. Any more parallel retrievals
did not show any improvement or started getting slower again
the more operations are allowed.

PiperOrigin-RevId: 650674685
2024-07-09 10:25:20 -07:00
tonihei
5777e30979 Reuse thread in MetadataRetriever
MetadataRetriever is often used in a loop to retrieve data about many
items in a directory for example. In such cases, it's wasteful to
start a new 'playback' thread for each retrieval, in particular since
this thread is doing almost nothing (just triggering loads and handling
events). This change re-uses an existing thread if one exists already.

PiperOrigin-RevId: 650633343
2024-07-09 08:15:45 -07:00
ibaker
00d1e70a34 Rollback of 91633e6ae3
PiperOrigin-RevId: 650620012
2024-07-09 07:29:57 -07:00
claincly
60359c16da Add a new debug trace log to log the device name / sdk
PiperOrigin-RevId: 650567544
2024-07-09 03:48:52 -07:00
dancho
6650270a4e MediaCodecVideoRenderer skips decoder inputs unused as reference
During a seek, or when playing a media with clipped start,
MCVR encounters preroll decode-only buffers that are not rendered.
Use C.BUFFER_FLAG_NO_OTHER_SAMPLE_DEPENDS_ON_THIS to determine
whether a decode-only buffer is unused as reference.
These buffers can be dropped before the decoder.
When this optimization is triggered, increment
decoderCounters.skippedInputBufferCount.

Tested in ExoPlayer demo app on "One hour frame counter (MP4)"
after enabling extractorsFactory.setMp4ExtractorFlags(
    FLAG_READ_WITHIN_GOP_SAMPLE_DEPENDENCIES);
Observe: "sib" increases on each seek.
PiperOrigin-RevId: 650566216
2024-07-09 03:43:06 -07:00
dancho
7d4f623b00 Fix RELEASENOTES merge conflict
PiperOrigin-RevId: 650551156
2024-07-09 02:43:03 -07:00
dancho
439536480b Parse the H264 bitstream of fMP4 files to identify sample dependencies
Changes to FragmentedMp4Extractor to parse additional sample dependency
information and mark output samples as "no other samples depend on this".
Only applies to H.264 tracks.
Controlled by new fMP4 flag: FLAG_READ_WITHIN_GOP_SAMPLE_DEPENDENCIES

PiperOrigin-RevId: 650538377
2024-07-09 01:53:51 -07:00
tonihei
2bb719fd54 Remove obsolete TODO
The idea was to not even write any samples to SampleQueues as
they are not needed during the metadata extraction process.
However, this is not easily possible as long as we use our
existing Extractors and MediaSource/Periods for loading given
how deeply ingrained the extraction of samples is in these
classes. For most common formats like MP4, no samples will be
extracted anyway as they can finish the prepare step without
reading any samples.

PiperOrigin-RevId: 650529371
2024-07-09 01:18:01 -07:00
andrewlewis
74c06dc2f4 Add SurfaceAssetLoader
This supports queueing input to Transformer via a `Surface`.

PiperOrigin-RevId: 650318396
2024-07-08 11:33:05 -07:00
ibaker
5dd377fb7b Rollback of 91633e6ae3
PiperOrigin-RevId: 650312000
2024-07-08 11:15:18 -07:00
claincly
ecd8a33f01 Check EOS is not signalled when queueing bitmap and texture
This is consistent with Surface input

PiperOrigin-RevId: 650298944
2024-07-08 10:39:39 -07:00
ktrajkovski
972007abef Add readLeb128 and readLeb128ToInt to ParsableByteArray.
Leb128 is a little-endian long of variable byte length. The format is used during the extraction of the size of the OBU configuration for the iacb configuration box.

PiperOrigin-RevId: 650295002
2024-07-08 10:29:42 -07:00
rohks
007c258ceb Fix maxInputSize for AMR-NB
The `maxInputSize` for AMR-NB should be 32, not 61. The `maxInputSize` for AMR-WB is 61.

PiperOrigin-RevId: 650282516
2024-07-08 09:52:54 -07:00
bachinger
06d61ffaaa Allow an app to decide to not start the service
Once a service is started as a foreground service, it must
be started into the foreground. This means an app can not
suppress a play command arriving from the `MediaButtonReceiver`
once the receiver has started the service.

This change adds a method to the `MediaButtonReceiver` that
allows app to suppress starting the service to not get into
this situation of wanting to suppress the play command after
the service is already started.

Issue: androidx/media#1528
PiperOrigin-RevId: 650280025
2024-07-08 09:44:30 -07:00
ibaker
2377d7556f Remove warning about depending on Kotlin std lib
We now depend on Kotlin from both `annotation` and
`annotation-experimental`, so it's not really possible to avoid
depending on it from any module now.

PiperOrigin-RevId: 650251094
2024-07-08 08:04:04 -07:00
okunhardt
91633e6ae3 Update HttpEngineDataSource to require at least S extension 7.
This earlier version supports [HttpEngine](https://developer.android.com/reference/android/net/http/HttpEngine) and thus this change allows more devices to use HttpEngine.

This fixes Issue: androidx/media#1262, which suggests to do this.

PiperOrigin-RevId: 650224711
2024-07-08 06:13:24 -07:00
Googler
6e18cb0053 Add support for amr-wb audio codec.
Implement damrBox to provide support for amr-wb audio codec.

Add unit test and an Android end to end test.

PiperOrigin-RevId: 650210732
2024-07-08 05:13:18 -07:00
claincly
d0a29400ea Fix test failuer on real-device
In the previous CL, muxer timeout is set to the surface timeout, which is
incorrect.

PiperOrigin-RevId: 650203505
2024-07-08 04:40:22 -07:00
tianyifeng
58ff8fa3c2 Add onPreloadError method to PreloadMediaSource.PreloadControl
Upon the call of `PreloadMediaSource.preload`, the source will periodically check the source refresh or period loading error, and trigger `PreloadMediaSource.PreloadControl.onPreloadError`. For now, the `DefaultPreloadManager` will skip the problematic source and continue to preload the next source. The checking of the error will be terminated when the source stops preloading or releases.

PiperOrigin-RevId: 650195817
2024-07-08 04:06:32 -07:00
claincly
b4722ef1ea Make timeout longer for emulators
Some test is flaky because frame processing is quite slow and triggered this
time out.

PiperOrigin-RevId: 650191068
2024-07-08 03:47:15 -07:00
tonihei
f55c09cfe2 Add ForwardingSimpleBasePlayer
This utility helps apps to forward to another Player while overriding
selected behavior or state values. The advantage to a ForwardingPlayer
is that the SimpleBasePlayer base class keeps ensuring correctness,
listener handling etc.

The default forwarding logic tries to stay as close as possible to the
original method calls, even if not strictly required by the Player
interface (e.g. calling single item addMediaItem instead of
addMediaItems if only one item is added).

Issue: androidx/media#1183
PiperOrigin-RevId: 650155924
2024-07-08 01:15:16 -07:00
Googler
a269355369 Refactor audioEsdsBox to esdsBox
Since the muxer supported only AAC audio codec, the esdsBox was unconditionally created within the audioSampleEntry. This CL refactors the box creation logic by moving it to the codecSpecificBox method. This is to make adding support for new audio codecs easier.

PiperOrigin-RevId: 650130935
2024-07-07 23:13:29 -07:00
tonihei
b2585aad0f Allow externally provided Timeline in SimpleBasePlayer.State
The Timeline, Tracks and MediaMetadata are currently provided
with a list of MediaItemData objects, that are a declarative
version of these classes. This works well for cases where
SimpleBasePlayer is used for external systems or custom players
that don't have a Timeline object available already. However,
this makes it really hard to provide the data if the app already
has a Timeline, currently requiring to convert it back and forth
to a list of MediaItemData.

This change adds an override for `State.Builder.setPlaylist`
that allows to set these 3 objects directly without going
through MediaItemData. The conversion only happens when needed
(e.g. when modifying the playlist).

PiperOrigin-RevId: 649667983
2024-07-05 09:39:49 -07:00
tonihei
fafd927702 Replace SimpleBasePlayer.State.playlist by getter
The value is basically a duplicate of the information stored
in the timeline field. Reducing the source of truth to the
single Timeline also allows acceptance of other Timelines in the
future that don't necessarily have the helper structure of
the playlist.

To allow apps to retrieve the current playlist as it is, we
add a getter instead.

PiperOrigin-RevId: 649667281
2024-07-05 09:35:44 -07:00
ktrajkovski
35a43d5c43 Add support for IAMF audio in MP4 Extractors.
A new IAMF type can now be recognized as an audio sample entry. A new mime type was created.

PiperOrigin-RevId: 649658865
2024-07-05 08:40:18 -07:00
dancho
40a5d31753 Parse the H264 bitstream of mp4 files to identify sample dependencies
Changes to Mp4Extractor to parse additional sample dependency information
and mark output samples as "no other sample depend on this".
Only applies to H.264 tracks.
Controlled by new mp4 flag: FLAG_READ_WITHIN_GOP_SAMPLE_DEPENDENCIES

PiperOrigin-RevId: 649640184
2024-07-05 06:45:40 -07:00
ibaker
bb2fd002ae Fix TTML handling of inherited percentage tts:fontSize values
The percentage should be interpreted as relative to the size of a parent
node.

This change makes this inheritance work correctly for percentages in
both the parent and child. It does not fix the case of a non-percentage
parent size with a percentage child size.

PiperOrigin-RevId: 649631055
2024-07-05 05:57:54 -07:00
andrewlewis
8f72054f2b Remove unused tag
PiperOrigin-RevId: 649448617
2024-07-04 10:55:15 -07:00
rohks
adf1c7915d Add FileDescriptorDataSource
This is a new `DataSource` that can be used to read from a `FileDescriptor`.

Limitations:
- The provided file descriptor must be seekable via lseek.
- There's no way to duplicate a file descriptor with an independent position (it
  would be necessary instead for the app to provide a new FD). Therefore this
  implementation will only work if there's one open data source for a given file
  descriptor at a time.
PiperOrigin-RevId: 649443584
2024-07-04 10:24:25 -07:00
ibaker
b7f317e650 Remove deprecation note from release notes
This was added in 0b96f4372f
but isn't needed. The deprecation annotation is self-documenting.

PiperOrigin-RevId: 649419778
2024-07-04 08:19:59 -07:00
Googler
0d4a785b61 Add support for parsing LHEVCConfigurationBox.
Parse LHEVCDecoderConfigurationRecord with the ‘lhvC’ type and set the corresponding sample mime type to video/mv-hevc.  With no MV-HEVC decoder available, fallback to single-layer HEVC decoding.

PiperOrigin-RevId: 649119173
2024-07-03 10:24:36 -07:00
claincly
8632c3add6 Fix not setting videoSinkNeedsRegisterInputStream when seeking
PiperOrigin-RevId: 649093092
2024-07-03 09:04:58 -07:00
andrewlewis
4c1807781f Mark flaky test as ignored
PiperOrigin-RevId: 649091256
2024-07-03 08:58:58 -07:00
claincly
ccdc0ffc27 Use us (microsecond) API.
PiperOrigin-RevId: 649058648
2024-07-03 07:00:18 -07:00
sheenachhabra
a1fc4e766f Reduce Exoplayer load control buffer durations for Transformer demo app
The default value is 50 seconds.
Changed it to 5 seconds.

This prevents the player from buffering too much data and causing the app to crash due to OOM.

This was reported in https://github.com/androidx/media/issues/1506

PiperOrigin-RevId: 649054885
2024-07-03 06:41:52 -07:00
kimvde
c832cf5a57 Clarify Javadoc of VideoSink
The VideoSink has an input and an output surface. Clarify which surface
the Javadoc is referring to. Also document that the VideoSink can be fed
with images, and that multiple renderers can feed the same sink.

PiperOrigin-RevId: 649052551
2024-07-03 06:31:40 -07:00
claincly
ed3a741601 Fix MCVR crash when seeking in HDR10 videos
MCVR crashed because MCVR registers a new input stream to VideoSink on every
`onOutputFormatChanged()`, assuming that `onOutputFormatChanged()` is only
invoked on media item transition. However, it can be called multiple times for
one media item.

PiperOrigin-RevId: 649050576
2024-07-03 06:22:44 -07:00
claincly
ce8ab84b7c Add test that covers clipping all videos in a sequence
PiperOrigin-RevId: 649048322
2024-07-03 06:13:08 -07:00
claincly
b9d101f090 Use a resolution that should be encode-able on all devices
The image in the test has a resolution of 1x1 which some device will reject.

PiperOrigin-RevId: 649039791
2024-07-03 05:32:56 -07:00
andrewlewis
0b96f4372f Rollback of 95260e28a5
PiperOrigin-RevId: 649003095
2024-07-03 02:52:00 -07:00
tianyifeng
b531d93b90 Version bump to 1.4.0-rc01
#cherrypick

PiperOrigin-RevId: 648982615
2024-07-03 01:27:39 -07:00
claincly
73da1c09bd Call onProcessedStreamChange() for every media item change
Added `MCR.experimentalEnableProcessedStreamChangedAtStart()` to guard this new
feature.

PiperOrigin-RevId: 648886533
2024-07-02 17:04:08 -07:00
sheenachhabra
3793a06bdd Add support for file format for depth/editing in Mp4Muxer
PiperOrigin-RevId: 648747038
2024-07-02 09:42:36 -07:00
tianyifeng
9277a34253 Update release notes for 1.4.0-rc01
#cherrypick

PiperOrigin-RevId: 648745388
2024-07-02 09:37:05 -07:00
dancho
91bf3d1da1 Use software decoder in VideoDecodingWrapper
Hardware decoder on some devices fails to write 1920x1080 YUV_420_888 buffers into an ImageReader. This change allows us to remove skipCalculateSsim device workaround in ExportTest.java.

VideoDecodingWrapper now uses media3.MediaExtractorCompat: necessary for parsing of MediaFormat#KEY_CODECS_STRING and decoder capabilities check

PiperOrigin-RevId: 648726721
2024-07-02 08:29:36 -07:00
andrewlewis
3b7d59ef4f Rollback of 95260e28a5
PiperOrigin-RevId: 648708459
2024-07-02 07:16:27 -07:00
tianyifeng
afe3826d7c Suppress the lint "WrongConstant" error
Lint somehow complains that the integer resulting from the bit-manipulation shouldn't be passed as an @IntDef parameter.

#cherrypick

PiperOrigin-RevId: 648687698
2024-07-02 05:49:23 -07:00
andrewlewis
95260e28a5 Add input type for automatic frame registration
Deprecate `setInputDefaultBufferSize` and `setRequireRegisteringAllInputFrames`
as the new input stream type replaces these (as far as we know they are always
used together).

This is in preparation for supporting asset loaders signaling that they require
these features, specifically for recording from a surface.

PiperOrigin-RevId: 648686087
2024-07-02 05:41:23 -07:00
kimvde
a2087ba5bb Improve Javadoc of VideoSink.registerInputFrame/queueBitmap
Document that the stream must be registered before

PiperOrigin-RevId: 648648650
2024-07-02 02:50:02 -07:00
ibaker
03a205f220 Re-order CEA-6/708 samples during extraction instead of rendering
This is required before we can move CEA-6/708 parsing from the rendering
side of the sample queue to the extraction side.

This re-ordering is needed for video encodings with different decoder
and presentation orders, because the CEA-6/708 data is attached to each
frame and needs to be processed in presentation order instead of decode
order. This change re-orders frames within a group-of-pictures, but also
takes advantage of `maxNumReorderFrames/Pics` values to cap the size of
the re-ordering queue, allowing caption data to be released 'earlier'
than the end of a GoP.

Annex D of the CEA-708 spec (which also applies for CEA-608 embedded in
SEI messages), makes the need to re-order from decode to presentation
order clear.

PiperOrigin-RevId: 648648002
2024-07-02 02:47:45 -07:00
tianyifeng
0510370bd2 Add OptIn annotation to method declaration in demo app file
#cherrypick

PiperOrigin-RevId: 648641357
2024-07-02 02:19:18 -07:00
bachinger
ec3a58f8db Count down three playback states to match the assertion
PiperOrigin-RevId: 648629427
2024-07-02 01:30:38 -07:00
tianyifeng
7fb6eee920 Add PreloadException with initial subset of error codes
As the first version, the error codes only include the miscellaneous and IO errors, but are likely to be extended.

PiperOrigin-RevId: 648466385
2024-07-01 13:24:34 -07:00
ibaker
711d18de03 Fix index out of bounds exception when a Subtitle is empty
Issue: androidx/media#1516

#cherrypick

PiperOrigin-RevId: 648416119
2024-07-01 10:40:15 -07:00
bachinger
70c063905c Improve automatic error replication for legacy browsers
This change extends the error replication to a given set of
error codes (not only authentication error), but only
replicates an error if the caller of the service `Callback`
is a legacy controller. It also makes error replication
configurable so that apps can opt-out and report errors
manually instead, or define the error codes for which
replication is enabled.

The change also removes the restriction of `sendError` only
being available for Media3 controllers. Instead, sending an
error to a legacy controller updates the platform playback
state in the same way as sending the error to the media
notification controller.

#cherrypick

PiperOrigin-RevId: 648399237
2024-07-01 09:47:25 -07:00
tianyifeng
6bf2461f80 Update translations
#cherrypick

PiperOrigin-RevId: 648385733
2024-07-01 09:02:21 -07:00
simakova
16ef63cdfc Update the composition README file
PiperOrigin-RevId: 648282532
2024-07-01 01:34:17 -07:00
dancho
9939d77d14 Change test to output 16x width and height
PiperOrigin-RevId: 647716870
2024-06-28 10:03:48 -07:00
claincly
174d5ea530 Update test golden images
Now VFP receives actual (un-offsetted) frame presentation time, updates the
test goldens to match.

PiperOrigin-RevId: 647665323
2024-06-28 07:04:47 -07:00
ktrajkovski
e4d4a776c3 Remove deprecated hasPreviousWindow() and hasPrevious() methods.
Use Player.hasPreviousMediaItem() and Player.seekToPreviousMediaItem() instead.

PiperOrigin-RevId: 647664991
2024-06-28 07:02:33 -07:00
dancho
18b28cd625 Skip seahawk device for 8K encoding
PiperOrigin-RevId: 647647264
2024-06-28 05:40:50 -07:00
ktrajkovski
e392084c4d Remove deprecated DrmSessionEventListener.onDrmSessionAcquired method.
#cherrypick

PiperOrigin-RevId: 647620751
2024-06-28 03:36:51 -07:00
ktrajkovski
47b1ca18ed Remove deprecated Player#hasPrevious() method.
Use Player#hasPreviousMediaItem() instead.

#cherrypick

PiperOrigin-RevId: 647336042
2024-06-27 09:04:21 -07:00
Googler
3da63eeaa7 Add NAL unit parsing needed for stereo MV-HEVC playback.
Add the following NAL unit parsing utility functions that will be needed for the MV-HEVC support as proposed in Apple's HEVC stereo video interoperability profile:
- NAL unit header parsing to get the layer information needed for MV-HEVC support.
- VPS parsing, including vps_extension() needed for MV-HEVC support.
- SPS parsing modifications to support MV-HEVC.

PiperOrigin-RevId: 647329211
2024-06-27 08:39:12 -07:00
michaelkatz
304bcfc852 Fix spelling typo in 1.4.0-beta01 release notes
PiperOrigin-RevId: 647327810
2024-06-27 08:35:06 -07:00
michaelkatz
a58e77a5a6 Cache audio timestamp frame position across track transition reset
Upon track transition of offloaded playback of gapless tracks, the framework will reset the audiotrack frame position. The `AudioTrackPositionTracker`'s `AudioTimestampPoller` must be made to expect the reset and cache accumulated sum of `AudioTimestamp.framePosition`.

#cherrypick

PiperOrigin-RevId: 647294360
2024-06-27 06:27:32 -07:00
andrewlewis
dcbded0fa9 Rename cancel to pause in Transformer demo
The user might expect this button to back out to the configuration activity,
but actually it toggles pause/resume (though `cancel` is the method called).

PiperOrigin-RevId: 647273416
2024-06-27 04:59:13 -07:00
tonihei
727645179b Send pending updates before adding discontinuity for error
When handling a playback error that originates from a future item in
the playlist, we added support for jumping to that item first,
ensuring the errors 'happen' for the right 'current item'.
See 79b688ef30.

However, when we add this new position discontinuity to the
playback state, there may already be other position discontinuities
pending from other parts of the code that executed before the
error. As we can't control that in this case (because it's part
of a generic try/catch block), we need to send any pending
updates first before handling the new change.

Issue: androidx/media#1483
#cherrypick
PiperOrigin-RevId: 646968309
2024-06-26 09:09:01 -07:00
kimvde
25b8385cff Fix Javadoc of VideoSink.setStreamOffsetAndAdjustmentUs
PiperOrigin-RevId: 646942034
2024-06-26 07:40:26 -07:00
Copybara-Service
6244d8605f Merge pull request #1487 from colinkho:main
PiperOrigin-RevId: 646917527
2024-06-26 06:05:07 -07:00
sheenachhabra
6fc0243106 Use Set instead of a List for metadata collection
All metadata entries are supposed to be unique, so Set is more appropriate.

PiperOrigin-RevId: 646907916
2024-06-26 05:29:47 -07:00
claincly
efbd522df9 Fix test with TimestampWrapper
The renderer offset is not needed as the pipeline now takes un-offset time,
from 73bf852405

PiperOrigin-RevId: 646808594
2024-06-26 03:48:43 -07:00
kimvde
3694487285 Correct documented Transformer HDR limitation
PiperOrigin-RevId: 646798618
2024-06-26 03:08:08 -07:00
claincly
9e7318e3b4 Clarify the HDR10 video in the demos is actually HDR10+
It's captured on a Samsung and has dynamic metadata.

PiperOrigin-RevId: 646796836
2024-06-26 03:02:00 -07:00
Ian Baker
a15710ba44 Reformat and add/remove some final keywords 2024-06-25 15:51:03 +01:00
Colin Kho
2ee69cb1f9 Avoid unnecessary getSystemService call on WifiLockManager and WakeLockManager if not enabled 2024-06-25 15:51:02 +01:00
sheenachhabra
be2d68c2b3 Improve method names in Mp4Writer
PiperOrigin-RevId: 646465516
2024-06-25 07:09:12 -07:00
rohks
12c42585d2 Use removeKey method instead of setting null for KEY_CODECS_STRING
Setting a `null` value doesn't remove the key as expected per the `MediaFormat` API documentation, using the `removeKey` method instead which is only available starting API level 29.

PiperOrigin-RevId: 646462402
2024-06-25 06:57:19 -07:00
ibaker
5fcc7433a1 Use MediaCodec.stop() before release() for surface switching bug
ExoPlayer used to call `stop()` before `release()`. This was removed in
<unknown commit>.

A framework bug introduced in Android 11 (API 30) resulted in some
DRM -> clear transitions failing during `MediaCodec.configure()`. An
investigation in Issue: google/ExoPlayer#8696 and b/191966399 identified that this was
due to `release()` returning 'too early' and the subsequent
`configure()` call was then trying to re-use a `Surface` that hadn't
been fully detached from the previous codec. This was fixed in
Android 13 (API 33) with http://r.android.com/2094347.

ExoPlayer worked around the framework bug by adding an arbitrary 50ms
sleep after a failed codec initialization, followed by retrying. This
was enough to resolve the problem in the test scenario on a OnePlus
AC2003.

Issue: androidx/media#1497 points out that 50ms might not be the appropriate delay
for all devices, so it's an incomplete fix. They suggested re-adding the
`MediaCodec.stop()` call instead. This also reliably resolves the issue
on the OnePlus AC2003 (with neither workaround in place, the problem
repros almost immediately).
PiperOrigin-RevId: 646461943
2024-06-25 06:54:45 -07:00
claincly
73bf852405 Make ExoPlayer.setVideoEffects() timestamp start from 0
This is consistent with `Transformer` and `CompositionPlayer`

Issue: androidx/media#1098
PiperOrigin-RevId: 646446824
2024-06-25 05:59:15 -07:00
tonihei
867410fece Rename DummyTrackOutput and DummyExtractorOutput
#cherrypick

PiperOrigin-RevId: 646434450
2024-06-25 05:07:56 -07:00
tonihei
18e631ff79 Add guard against additional tracks reported by Extractors
Extractors should not report additional tracks once they called
ExtractorOutput.endTracks. This causes thread safety issues in
ProgressiveMediaPeriod where the array of sample queues is
extended while the playback thread accesses the arrays.

Detecting this problem early is beneficial to avoid unexplained
exceptions later one. In most cases where this may happen (namely
TS extractors finding new tracks), it's better to ignore the new
tracks instead of failing completely. So this change adds a
warning log message and assigns a placeholder output.

Note: The same workaround already exists in HlsSampleStreamWrapper
and MediaExtractorCompat.

Issue: androidx/media#1476
#cherrypick
PiperOrigin-RevId: 646427213
2024-06-25 04:38:22 -07:00
Copybara-Service
0466728497 Merge pull request #1479 from dryganets:sdryanets/fix-handler-usage
PiperOrigin-RevId: 646402268
2024-06-25 02:56:09 -07:00
kimvde
304c4e41f8 Call VideoFrameReleaseControl.isReady from VideoSink when enabled
PiperOrigin-RevId: 646385384
2024-06-25 01:51:14 -07:00
sheenachhabra
babc9c69c6 Remove unnecessary FileChannel from Mp4Muxer
As per the documentation, closing a `FileOutputStream`
automatically closes the associated `FileChannel`.

PiperOrigin-RevId: 646152280
2024-06-24 10:58:30 -07:00
sheenachhabra
327f728010 Move muxer closing logic from Mp4Writer to Mp4Muxer
PiperOrigin-RevId: 646134522
2024-06-24 10:11:52 -07:00
Copybara-Service
b026271c84 Merge pull request #1416 from khouzam:customFormat
PiperOrigin-RevId: 646121082
2024-06-24 09:30:28 -07:00
tonihei
58864a4bb9 Additional variable changes + argument checks in HandlerWrapper 2024-06-24 16:42:02 +01:00
Sergei Dryganets
457deec4eb Stop using what == 0 for messages. 2024-06-24 16:26:27 +01:00
tonihei
b6070a5299 Formatting fixes 2024-06-24 16:10:13 +01:00
tonihei
71ef848ec3 Add fail-early checks for TrackSelectorResult correctness
The two arrays need to have the same length and the selection
must match in their nullness (unless for TYPE_NONE
renderers). Clarify this more clearly in the docs and add
new asssertions for it. This avoids that the player is failing
in obscure ways much later.

Issue: androidx/media#1473
#cherrypick
PiperOrigin-RevId: 646086833
2024-06-24 07:30:25 -07:00
okunhardt
bb568b5150 In DemoUtil, don't set cookie handler when using HttpEngineDataSource.
HttpEngine does not support cookie storage.

#cherrypick

PiperOrigin-RevId: 646084702
2024-06-24 07:21:08 -07:00
samrobinson
938fac4161 Update CompositionPlayer state for volume.
PiperOrigin-RevId: 646071591
2024-06-24 06:25:28 -07:00
tonihei
ada4dc982f Fix flakiness in MediaCodecVideoRendererTest
The test is flaky because the decoding process in the renderer
depends on some timing from MediaCodec beyond our control and
the new keyframe added in the test is sometimes 'dropped' when
it arrives too late.

We can fix this by controlling the test progress a bit more
tightly: first rendering with the same current time until the
key frame is processed and then start increasing the time
until we've reached the end.

#cherrypick

PiperOrigin-RevId: 646064352
2024-06-24 05:53:48 -07:00
okunhardt
e591c37b1e Use HttpEngineDataSource when supported in demo app.
HttpEngineDataSource is the recommended HttpDataSource when it is available. It uses the [HttpEngine](https://developer.android.com/reference/android/net/http/HttpEngine).

#cherrypick

PiperOrigin-RevId: 646051562
2024-06-24 04:52:15 -07:00
Gilles Khouzam
d717a0c5d5 Add a CustomData field to the Format class
Summary:
    This change aims to add a generic `CustomData` field to the `Format` class.

    The intent is to allow ExoPlayer customers to add extra data to the Format class without forcing
    specific data to be included, impacting customers that do not need it and would allow for the data
    to be changed without requiring changes to the `Media3` codebase.
2024-06-24 09:43:00 +01:00
dancho
476ec607f2 Implement isNoOp for LanczosResample
This allows for transmuxing whenever resolutions match,
and LanczosResample is in the video effects chain.

PiperOrigin-RevId: 645433104
2024-06-21 10:37:19 -07:00
simakova
d16004781e Remove AspectRatioFrameLayout usage from transformer demo
Removing unnecessary usage from transformer demo

PiperOrigin-RevId: 645426871
2024-06-21 10:18:30 -07:00
samrobinson
f1fadccef5 Add software decoder workaround for redmi 7a.
PiperOrigin-RevId: 645383707
2024-06-21 07:31:18 -07:00
claincly
89fbd0d27a Add Accept header to DESCRIBE request
The Accept header is required by some servers, and we only support SDP.

Issue: google/ExoPlayer#10919
PiperOrigin-RevId: 645361576
2024-06-21 05:47:52 -07:00
simakova
52bd9a2815 Add an option to include background audio sequence in Composition.
PiperOrigin-RevId: 645100835
2024-06-20 11:29:28 -07:00
ibaker
99803066ea Add null-check to PlayerView to avoid NPE in edit mode
Previously we assumed that `surfaceSyncGroupV34` was always non-null on
API 34, but this isn't true in edit mode. Instead we add an explicit
null-check before accessing it. We don't need to null-check it at the
other usage site because we are already null-checking `surfaceView` (via
`instanceof` check).

Issue: androidx/media#1237

#cherrypick

PiperOrigin-RevId: 645008049
2024-06-20 06:30:42 -07:00
sheenachhabra
307655f6d5 Move component initialization from Mp4Muxer.Builder to Mp4Muxer
PiperOrigin-RevId: 645004885
2024-06-20 06:18:13 -07:00
ibaker
cb8f87e05e Remove direct AspectRatioFrameLayout usage from session demo
This class is a lower-level UI component that isn't directly needed if
apps are using `PlayerView` to handle their video output (it is used as
an implementation detail of `PlayerView`).

Removing its unecessary usages from this demo avoids developers copying
this as an example when building their own apps.

#cherrypick

PiperOrigin-RevId: 644972454
2024-06-20 04:06:23 -07:00
tonihei
e84bb0d21c Fix audio focus handling in ExoPlayerImpl
Some cases are not handled correctly at the moment:
 - Pausing during suppressed playback should not clear the
   suppression state.
 - Transient focus loss while paused should be reported as
   a playback suppression.

Issue: androidx/media#1436
#cherrypick
PiperOrigin-RevId: 644971218
2024-06-20 04:00:57 -07:00
tonihei
1d26d1891e Clarify that onPlayWhenReadyChanged can be called again with new reason
Sometimes the reason for the current state may change. If we don't
report this again, users have no way of knowing that the reason
changed.

Also adjust ExoPlayerImpl and MediaControllerImplBase accordingly.
SimpleBasePlayer already adheres to this logic.

#cherrypick

PiperOrigin-RevId: 644970236
2024-06-20 03:58:05 -07:00
tonihei
c0abd6f91e Move playWhenReadyChangeReason inside PlaybackInfo
This helps to keep the reason always together with the state it
is referring to, avoiding any side channels and making sure there
are no accidental inconsistencies.

#cherrypick

PiperOrigin-RevId: 644969317
2024-06-20 03:54:04 -07:00
samrobinson
f2305cc05c Add a parameterized android test for single asset exports.
PiperOrigin-RevId: 644740900
2024-06-19 07:23:01 -07:00
kimvde
4751b80703 Call VideoFrameReleaseControl.join from VideoSink when enabled
PiperOrigin-RevId: 644738200
2024-06-19 07:09:38 -07:00
samrobinson
ff4feed0eb Add case to parameterized sequence test for no composition effects.
PiperOrigin-RevId: 644734136
2024-06-19 06:48:39 -07:00
ibaker
968f72fec6 Use SurfaceSyncGroup to ensure resize transaction isn't dropped
This workaround is only applied on API 34, because the problem isn't
present on API 33 and it is fixed in the platform for API 35 onwards.

Issue: androidx/media#1237

#cherrypick

PiperOrigin-RevId: 644729909
2024-06-19 06:27:13 -07:00
bachinger
6cc6444dd9 Use a localized fallback message for known error codes
In the case of a legacy session app providing an error
code different to `ERROR_UNKNOWN` without an error
message, a localized fallback message is provided
instead of ignoring the error.

#cherrypick

PiperOrigin-RevId: 644704680
2024-06-19 04:28:20 -07:00
bachinger
856d394c28 Allow session activity to be set per controller
#cherrypick

PiperOrigin-RevId: 644693533
2024-06-19 03:37:02 -07:00
kimvde
30b9c976ea Fix Javadoc of EditedMediaItem.Builder.setDurationUs
The Javadoc was indicating that the duration should always be set,
but it doesn't need to be set in most cases for export.

PiperOrigin-RevId: 644685827
2024-06-19 03:03:15 -07:00
samrobinson
afa7935553 Split parameterized android test utils into utility class.
Includes adjusting how effects are defined, to make it clearer in a
test that a given ItemConfig has effects associated with it.

PiperOrigin-RevId: 644684308
2024-06-19 02:56:34 -07:00
kimvde
d27549d29a Skip 4K export test on Pixel 3a
PiperOrigin-RevId: 644659699
2024-06-19 01:18:05 -07:00
tonihei
66c19390e2 Audio focus player command clean up
The 'player commands' returned to ExoPlayerImpl instruct the
player on how to treat the current audio focus state.

The current return value when playWhenReady==false is misleading
because it implies we are definitely not allowed to play as if
we've lost focus. Instead, we should return the actual player
command corresponding to the focus state we are in.

This has no practical effect in ExoPlayerImpl as we already
ignore the 'player command' completely  when playWhenReady=false.

To facilitate this change, we also introduce a new internal
state for FOCUS_NOT_REQUESTED to distinguish it from the state
in which we lost focus.

#cherrypick

PiperOrigin-RevId: 644416586
2024-06-18 09:42:26 -07:00
kimvde
048d71e392 Change muxer video duration unit to microseconds
PiperOrigin-RevId: 644402109
2024-06-18 08:56:26 -07:00
kimvde
ada7271974 Add a method to change the frame rate strategy from the VideoSink
PiperOrigin-RevId: 644373231
2024-06-18 07:11:58 -07:00
samrobinson
1d8b2e3f43 Migrate ParameterizedInputSequenceExportTest to AssetInfo for images.
PiperOrigin-RevId: 644363298
2024-06-18 06:31:56 -07:00
michaelkatz
c07bbd333c Version bump to media3:1.4.0-beta01
#cherrypick

PiperOrigin-RevId: 644352776
2024-06-18 05:52:03 -07:00
michaelkatz
794731607d Update release notes for 1.4.0-beta01
#cherrypick

PiperOrigin-RevId: 644351958
2024-06-18 05:48:00 -07:00
tonihei
e20e94fde2 Improve audio focus handling tests with ExoPlayer
There are a lot of tests for AudioFocusManager in isolation,
but almost none for the handling in ExoPlayer.

Add test coverage for all the common cases, including some
currently broken behavior that is indicated by TODOs.

PiperOrigin-RevId: 644319251
2024-06-18 03:24:21 -07:00
samrobinson
21ad768628 Add AndroidTestUtil missing asset Format info.
Also adds basic support for canDecode receiving an image Format.

PiperOrigin-RevId: 644300521
2024-06-18 02:06:59 -07:00
kimvde
f0aa30555a Remove calls to VideoFrameReleaseControl.join in CompositionPlayer
These calls have no effect because the VideoFrameReleaseControl of
CompositionPlayer is created with allowedJoiningTimeMs set to 0.

PiperOrigin-RevId: 644274524
2024-06-18 00:22:25 -07:00
samrobinson
2698f3ffc2 Migrate performance tests to use AndroidTestUtil constants.
PiperOrigin-RevId: 644058273
2024-06-17 10:38:23 -07:00
samrobinson
2b55a5bc2d Create an AssetInfo class for AndroidTestUtil test asset information.
This is an internal refactor with no logic changed.

There is a duplication in information and a lack of consistency around
the use of test assets in transformer androidTest. This change
refactors each asset to be defined as its own AssetInfo, with the other
relevant parts stored alongside the uri.

Once this is in place, we can consider other useful functionality, such
as having boolean flags for what tracks an asset has, helper methods
for whether an asset is local or remote, and more. This will reduce the
manual overhead necessary to use more assets in tests, and in
particular leads towards easily using new & existing assets in
parameterized tests.

PiperOrigin-RevId: 644040595
2024-06-17 09:47:56 -07:00
ibaker
d0815d3f7b Deprecate Util.areEqual in favour of Objects.equals
`Objects.equals` has been available since API 19.

PiperOrigin-RevId: 644026884
2024-06-17 09:04:48 -07:00
michaelkatz
ed07ac5d7d Fix linter errors in DemoMediaLibrarySessionCallback.kt
PiperOrigin-RevId: 644002147
2024-06-17 07:37:00 -07:00
Copybara-Service
67a7b41fa7 Merge pull request #1437 from MGaetan89:add_exoplayer_setMaxSeekToPreviousPosition
PiperOrigin-RevId: 643987403
2024-06-17 06:38:02 -07:00
samrobinson
6dcbafad21 Use AndroidTestUtil constants so test asset information is consistent.
PiperOrigin-RevId: 643984158
2024-06-17 06:23:22 -07:00
sheenachhabra
ded1adc092 Change sample duration type from Long to Integer
In muxer only 32-bit sample duration is supported. Earlier the duration was type
casted at different places.

PiperOrigin-RevId: 643954226
2024-06-17 04:13:00 -07:00
tonihei
cd2250b5fa Formatting fixes and additional plumbin in legacy controller 2024-06-14 17:47:03 +01:00
Gaëtan Muller
63103978be Make MediaControllerImplLegacy.getMaxSeekToPreviousPosition() return the correct value 2024-06-14 16:21:31 +01:00
Gaëtan Muller
424d2a52fe Support setting the max seek to previous position on CastPlayer 2024-06-14 16:21:30 +01:00
Gaëtan Muller
6153b6d740 Add the ExoPlayer.Builder.setMaxSeekToPreviousPosition(long) method
This method allows customizing the maximum position when using `Player.seekToPrevious()`.

This commit also adds two new methods to `TestExoPlayerBuilder`:
- `setMaxSeekToPreviousPosition(long)`
- `getMaxSeekToPreviousPosition()`
2024-06-14 16:21:30 +01:00
1441 changed files with 269465 additions and 15652 deletions

View file

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

41
.gitignore vendored
View file

@ -52,30 +52,31 @@ tmp
# External native builds
.externalNativeBuild
.cxx
# VP9 extension
extensions/vp9/src/main/jni/libvpx
extensions/vp9/src/main/jni/libvpx_android_configs
extensions/vp9/src/main/jni/libyuv
# VP9 decoder extension
libraries/decoder_vp9/src/main/jni/libvpx
libraries/decoder_vp9/src/main/jni/libvpx_android_configs
libraries/decoder_vp9/src/main/jni/libyuv
# AV1 extension
extensions/av1/src/main/jni/cpu_features
extensions/av1/src/main/jni/libgav1
# AV1 decoder extension
libraries/decoder_av1/src/main/jni/cpu_features
libraries/decoder_av1/src/main/jni/libgav1
# Opus extension
extensions/opus/src/main/jni/libopus
# Opus decoder extension
libraries/decoder_opus/src/main/jni/libopus
# FLAC extension
extensions/flac/src/main/jni/flac
# FLAC decoder extension
libraries/decoder_flac/src/main/jni/flac
# FFmpeg extension
extensions/ffmpeg/src/main/jni/ffmpeg
# FFmpeg decoder extension
libraries/decoder_ffmpeg/src/main/jni/ffmpeg
# Cronet extension
extensions/cronet/jniLibs/*
!extensions/cronet/jniLibs/README.md
extensions/cronet/libs/*
!extensions/cronet/libs/README.md
# Cronet datasource extension
libraries/datasource_cronet/jniLibs/*
!libraries/datasource_cronet/jniLibs/README.md
libraries/datasource_cronet/libs/*
!libraries/datasource_cronet/libs/README.md
# MIDI extension
extensions/midi/lib
# MIDI decoder extension
libraries/decoder_midi/lib

View file

@ -100,12 +100,6 @@ compileOptions {
}
```
#### 3. Enable multidex
If your Gradle `minSdkVersion` is 20 or lower, you should
[enable multidex](https://developer.android.com/studio/build/multidex) in order
to prevent build errors.
### Locally
Cloning the repository and depending on the modules locally is required when
@ -116,7 +110,6 @@ First, clone the repository into a local directory:
```sh
git clone https://github.com/androidx/media.git
cd media
```
Next, add the following to your project's `settings.gradle.kts` file, replacing
@ -130,7 +123,7 @@ apply(from = file("path/to/media/core_settings.gradle"))
Or in Gradle Groovy DSL `settings.gradle`:
```groovy
gradle.ext.androidxMediaModulePrefix = 'media-'
gradle.ext.androidxMediaModulePrefix = 'media3-'
apply from: file("path/to/media/core_settings.gradle")
```
@ -139,17 +132,17 @@ You can depend on them from `build.gradle.kts` as you would on any other local
module, for example:
```kotlin
implementation(project(":media-lib-exoplayer"))
implementation(project(":media-lib-exoplayer-dash"))
implementation(project(":media-lib-ui"))
implementation(project(":media3-lib-exoplayer"))
implementation(project(":media3-lib-exoplayer-dash"))
implementation(project(":media3-lib-ui"))
```
Or in Gradle Groovy DSL `build.gradle`:
```groovy
implementation project(':media-lib-exoplayer')
implementation project(':media-lib-exoplayer-dash')
implementation project(':media-lib-ui')
implementation project(':media3-lib-exoplayer')
implementation project(':media3-lib-exoplayer-dash')
implementation project(':media3-lib-ui')
```
#### MIDI module

View file

@ -1,7 +1,423 @@
# Release notes
## 1.5
### 1.5.1 (2024-12-19)
This release includes the following changes since the
[1.5.0 release](#150-2024-11-27):
* ExoPlayer:
* Disable use of asynchronous decryption in MediaCodec to avoid reported
codec timeout issues with this platform API
([#1641](https://github.com/androidx/media/issues/1641)).
* Extractors:
* MP3: Don't stop playback early when a `VBRI` frame's table of contents
doesn't cover all the MP3 data in a file
([#1904](https://github.com/androidx/media/issues/1904)).
* Video:
* Rollback of using `MediaCodecAdapter` supplied pixel aspect ratio values
when provided while processing `onOutputFormatChanged`
([#1371](https://github.com/androidx/media/pull/1371)).
* Text:
* Fix bug in `ReplacingCuesResolver.discardCuesBeforeTimeUs` where the cue
active at `timeUs` (started before but not yet ended) was incorrectly
discarded ([#1939](https://github.com/androidx/media/issues/1939)).
* Metadata:
* Extract disc/track numbering and genre from Vorbis comments into
`MediaMetadata`
([#1958](https://github.com/androidx/media/issues/1958)).
### 1.5.0 (2024-11-27)
This release includes the following changes since the
[1.4.1 release](#141-2024-08-23):
* Common Library:
* Add `ForwardingSimpleBasePlayer` that allows forwarding to another
player with small adjustments while ensuring full consistency and
listener handling
([#1183](https://github.com/androidx/media/issues/1183)).
* Replace `SimpleBasePlayer.State.playlist` by `getPlaylist()` method.
* Add override for `SimpleBasePlayer.State.Builder.setPlaylist()` to
directly specify a `Timeline` and current `Tracks` and `Metadata`
instead of building a playlist structure.
* Increase `minSdk` to 21 (Android Lollipop). This is aligned with all
other AndroidX libraries.
* Add `androidx.media3:media3-common-ktx` artifact which provides
Kotlin-specific functionality built on top of the Common library
* Add `Player.listen` suspending extension function to spin a coroutine to
listen to `Player.Events` to the `media3-common-ktx` library.
* Remove `@DoNotInline` annotations from manually out-of-lined inner
classes designed to avoid
[runtime class verification failures](https://chromium.googlesource.com/chromium/src/+/HEAD/build/android/docs/class_verification_failures.md).
Recent versions of [R8](https://developer.android.com/build/shrink-code)
now automatically out-of-line calls like these to avoid the runtime
failures (so the manual out-of-lining is no longer required). All Gradle
users of the library must already be a using a version of the Android
Gradle Plugin that uses a version of R8 which does this,
[due to `compileSdk = 35`](https://issuetracker.google.com/345472586#comment7).
Users of the library with non-Gradle build systems will need to ensure
their R8-equivalent shrinking/obfuscating step does a similar automatic
out-of-lining process in order to avoid runtime class verification
failures. This change has
[already been done in other AndroidX libraries](http://r.android.com/3156141).
* ExoPlayer:
* `MediaCodecRenderer.onProcessedStreamChange()` can now be called for
every media item. Previously it was not called for the first one. Use
`MediaCodecRenderer.experimentalEnableProcessedStreamChangedAtStart()`
to enable this.
* Add `PreloadMediaSource.PreloadControl.onPreloadError` to allow
`PreloadMediaSource.PreloadControl` implementations to take actions when
error occurs.
* Add `BasePreloadManager.Listener` to propagate preload events to apps.
* Allow changing SNTP client timeout and retry alternative addresses on
timeout ([#1540](https://github.com/androidx/media/issues/1540)).
* Remove `MediaCodecAdapter.Configuration.flags` as the field was always
zero.
* Allow the user to select the built-in speaker for playback on Wear OS
API 35+ (where the device advertises support for this).
* Defer the blocking call to
`Context.getSystemService(Context.AUDIO_SERVICE)` until audio focus
handling is enabled. This ensures the blocking call isn't done if audio
focus handling is not enabled
([#1616](https://github.com/androidx/media/pull/1616)).
* Allow playback regardless of buffered duration when loading fails
([#1571](https://github.com/androidx/media/issues/1571)).
* Add `AnalyticsListener.onRendererReadyChanged()` to signal when
individual renderers allow playback to be ready.
* Fix `MediaCodec.CryptoException` sometimes being reported as an
"unexpected runtime error" when `MediaCodec` is operated in asynchronous
mode (default behaviour on API 31+).
* Pass `bufferedDurationUs` instead of `bufferedPositionUs` with
`PreloadMediaSource.PreloadControl.onContinueLoadingRequested()`. Also
changes `DefaultPreloadManager.Status.STAGE_LOADED_TO_POSITION_MS` to
`DefaultPreloadManager.Status.STAGE_LOADED_FOR_DURATION_MS`, apps then
need to pass a value representing a specific duration from the default
start position for which the corresponding media source has to be
preloaded with this IntDef, instead of a position.
* Add `ForwardingRenderer` implementation that forwards all method calls
to another renderer
([1703](https://github.com/androidx/media/pull/1703)).
* Add playlist preloading for the next item in the playlist. Apps can
enable preloading by calling
`ExoPlayer.setPreloadConfiguration(PreloadConfiguration)` accordingly.
By default preloading is disabled. When opted-in and to not interfere
with playback, `DefaultLoadControl` restricts preloading to start and
continue only when the player is not loading for playback. Apps can
change this behaviour by implementing
`LoadControl.shouldContinuePreloading()` accordingly (like when
overriding this method in `DefaultLoadControl`). The default
implementation of `LoadControl` disables preloading in case an app is
using a custom implementation of `LoadControl`.
* Add method `MediaSourceEventListener.EventDispatcher.dispatchEvent()` to
allow invoking events of subclass listeners
([1736](https://github.com/androidx/media/pull/1736)).
* Add `DefaultPreloadManager.Builder` that builds the
`DefaultPreloadManager` and `ExoPlayer` instances with consistently
shared configurations.
* Remove `Renderer[]` parameter from `LoadControl.onTracksSelected()` as
`DefaultLoadControl` implementation can retrieve the stream types from
`ExoTrackSelection[]`.
* Deprecated `DefaultLoadControl.calculateTargetBufferBytes(Renderer[],
ExoTrackSelection[])` and marked method as final to prevent overrides.
The new
`DefaultLoadControl.calculateTargetBufferBytes(ExoTrackSelection[])`
should be used instead.
* Report `MediaSourceEventListener` events from secondary sources in
`MergingMediaSource`. This will result in load
start/error/cancelled/completed events being reported for sideloaded
subtitles (those added with
`MediaItem.LocalConfiguration.subtitleConfigurations`), which may appear
as duplicate load events emitted from `AnalyticsListener`.
* Prevent subtitle & metadata errors from completely stopping playback.
Instead the problematic track is disabled and playback of the remaining
tracks continues
([#1722](https://github.com/google/ExoPlayer/issues/1722)).
* In new subtitle handling (during extraction), associated parse (e.g.
invalid subtitle data) and load errors (e.g. HTTP 404) are emitted
via `onLoadError` callbacks.
* In legacy subtitle handling (during rendering), only associated load
errors are emitted via `onLoadError` callbacks while parse errors
are silently ignored (this is pre-existing behaviour).
* 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)).
* Add a setter to `SntpClient` to set the max elapsed time since the last
update after which the client is re-initialized
([#1794](https://github.com/androidx/media/pull/1794)).
* Transformer:
* Add `SurfaceAssetLoader`, which supports queueing video data to
Transformer via a `Surface`.
* `ImageAssetLoader` reports unsupported input via `AssetLoader.onError`
instead of throwing an `IllegalStateException`.
* Make setting the image duration using
`MediaItem.Builder.setImageDurationMs` mandatory for image export.
* Add export support for gaps in sequences of audio EditedMediaItems.
* Track Selection:
* `DefaultTrackSelector`: Prefer object-based audio over channel-based
audio when other factors are equal.
* Extractors:
* Allow `Mp4Extractor` and `FragmentedMp4Extractor` to identify H264
samples that are not used as reference by subsequent samples.
* Add option to enable index-based seeking in `AmrExtractor`.
* Treat MP3 files with more than 128kB between valid frames as truncated
(instead of invalid). This means files with non-MP3 data at the end,
with no other metadata to indicate the length of the MP3 bytes, now stop
playback at the end of the MP3 data instead of failing with
`ParserException: Searched too many bytes.{contentIsMalformed=true,
dataType=1}` ([#1563](https://github.com/androidx/media/issues/1563)).
* Fix preroll sample handling for non-keyframe media start positions when
processing edit lists in MP4 files
([#1659](https://github.com/google/ExoPlayer/issues/1659)).
* Improved frame rate calculation by using media duration from the `mdhd`
box in `Mp4Extractor` and `FragmentedMp4Extractor`
([#1531](https://github.com/androidx/media/issues/1531)).
* Fix incorrect scaling of `media_time` in MP4 edit lists. While
`segment_duration` was already correctly scaled using the movie
timescale, `media_time` is now properly scaled using the track
timescale, as specified by the MP4 format standard
([#1792](https://github.com/androidx/media/issues/1792)).
* Handle out-of-order frames in `endIndices` calculation for MP4 with edit
list ([#1797](https://github.com/androidx/media/issues/1797)).
* Fix media duration parsing in `mdhd` box of MP4 files to handle `-1`
values ([#1819](https://github.com/androidx/media/issues/1819)).
* Add support for identifying `h263` box in MP4 files for H.263 video
([#1821](https://github.com/androidx/media/issues/1821)).
* Add AC-4 Level-4 ISO base media file format support
([#1265](https://github.com/androidx/media/pull/1265)).
* DataSource:
* Update `HttpEngineDataSource` to allow use starting at version S
extension 7 instead of API level 34
([#1262](https://github.com/androidx/media/issues/1262)).
* `DataSourceContractTest`: Assert that `DataSource.getUri()` returns the
resolved URI (as documented). Where this is different to the requested
URI, tests can indicate this using the new
`DataSourceContractTest.TestResource.Builder.setResolvedUri()` method.
* `DataSourceContractTest`: Assert that `DataSource.getUri()` and
`getResponseHeaders()` return their 'open' value after a failed call to
`open()` (due to a 'not found' resource) and before a subsequent
`close()` call.
* Overriding `DataSourceContractTest.getNotFoundResources()` allows
test sub-classes to provide multiple 'not found' resources, and to
provide any expected headers too. This allows to distinguish between
HTTP 404 (with headers) and "server not found" (no headers).
* Audio:
* Automatically configure CTA-2075 loudness metadata on the codec if
present in the media.
* Ensure smooth volume ramp down when seeking.
* Fix pop sounds that may occur during seeks.
* Fix truncation error accumulation for Sonic's
time-stretching/pitch-shifting algorithm.
* Fix bug in `SpeedChangingAudioProcessor` that causes dropped output
frames.
* Video:
* `MediaCodecVideoRenderer` avoids decoding samples that are neither
rendered nor used as reference by other samples.
* On API 35 and above, `MediaCodecAdapter` may now receive a `null`
`Surface` in `configure` and calls to a new method `detachOutputSurface`
to remove a previously set `Surface` if the codec supports this
(`MediaCodecInfo.detachedSurfaceSupported`).
* Use `MediaCodecAdapter` supplied pixel aspect ratio values if provided
when processing `onOutputFormatChanged`
([#1371](https://github.com/androidx/media/pull/1371)).
* Add workaround for a device issue on Galaxy Tab S7 FE that causes 60fps
secure H264 streams to be marked as unsupported
([#1619](https://github.com/androidx/media/issues/1619)).
* Add workaround for codecs that get stuck after the last sample without
returning an end-of-stream signal.
* Text:
* Add a custom `VoiceSpan` and populate it for
[WebVTT voice spans](https://www.w3.org/TR/webvtt1/#webvtt-cue-voice-span)
([#1632](https://github.com/androidx/media/issues/1632)).
* Ensure WebVTT in HLS with very large subtitle timestamps (which overflow
a 64-bit `long` when represented as microseconds and multiplied by the
`90,000` MPEG timebase) are displayed
([#1763](https://github.com/androidx/media/issues/1763)).
* Support CEA-608 subtitles in Dolby Vision content
([#1820](https://github.com/androidx/media/issues/1820)).
* Fix playback hanging on DASH multi-period streams when CEA-608 subtitles
are enabled ([#1863](https://github.com/androidx/media/issues/1863)).
* Metadata:
* Assign the `C.TRACK_TYPE_METADATA` type to tracks containing icy or
vnd.dvb.ait content.
* Image:
* Add `ExternallyLoadedImageDecoder` for simplified integration with
external image loading libraries like Glide or Coil.
* DataSource:
* Add `FileDescriptorDataSource`, a new `DataSource` that can be used to
read from a `FileDescriptor`
([#3757](https://github.com/google/ExoPlayer/issues/3757)).
* Effect:
* Add `DefaultVideoFrameProcessor` workaround for minor `SurfaceTexture`
scaling. `SurfaceTexture` may include a small scaling that cuts off a
1-texel border around the edge of a cropped buffer. This is now handled
such that output is closer to expected.
* Speed up `DefaultVideoFrameProcessor.queueInputBitmap()`. As a result,
exporting images to videos with `Transformer` is faster.
* IMA extension:
* Fix bug where clearing the playlist may cause an
`ArrayIndexOutOfBoundsException` in
`ImaServerSideAdInsertionMediaSource`.
* Fix bug where server-side inserted DAI streams without a preroll can
result in an `ArrayIndexOutOfBoundsException` when playing past the last
midroll ([#1741](https://github.com/androidx/media/issues/1741)).
* Session:
* Add `MediaButtonReceiver.shouldStartForegroundService(Intent)` to allow
apps to suppress a play command coming in for playback resumption by
overriding this method. By default, the service is always started and
playback can't be suppressed without the system crashing the service
with a `ForegroundServiceDidNotStartInTimeException`
([#1528](https://github.com/google/ExoPlayer/issues/1528)).
* Fix bug that caused custom commands sent from a `MediaBrowser` being
dispatched to the `MediaSessionCompat.Callback` instead of the
`MediaBrowserServiceCompat` variant of the method when connected to a
legacy service. This prevented the `MediaBrowser` to receive the actual
return value sent back by the legacy service
([#1474](https://github.com/androidx/media/issues/1474)).
* Handle `IllegalArgumentException` thrown by devices of certain
manufacturers when setting the broadcast receiver for media button
intents ([#1730](https://github.com/androidx/media/issues/1730)).
* Add command buttons for media items. This adds the Media3 API for what
was known as `Custom browse actions` with the legacy library with
`MediaBrowserCompat`. Note that with Media3 command buttons for media
items are available for both, `MediaBrowser` and `MediaController`. See
[Custom Browse actions of AAOS](https://developer.android.com/training/cars/media#custom_browse_actions).
* Fix bug where a Media3 controller was sometimes unable to let a session
app start a foreground service after requesting `play()`.
* Restrict `CommandButton.Builder.setIconUri` to only accept content Uris.
* Pass connection hints of a Media3 browser to the initial
`MediaBrowserCompat` when connecting to a legacy `MediaBrowserCompat`.
The service can receive the connection hints passed in as root hints
with the first call to `onGetRoot()`.
* Fix bug where a `MediaBrowser` connected to a legacy browser service,
didn't receive an error sent by the service after the browser has
subscribed to a `parentid`.
* Improve interoperability behavior, so that a Media3 browser that is
connected to a legacy `MediaBrowserService` doesn't request the children
of a `parentId` twice when subscribing to a parent.
* UI:
* Make the stretched/cropped video in
`PlayerView`-in-Compose-`AndroidView` workaround opt-in, due to issues
with XML-based shared transitions. Apps using `PlayerView` inside
`AndroidView` need to call
`PlayerView.setEnableComposeSurfaceSyncWorkaround` in order to opt-in
([#1237](https://github.com/androidx/media/issues/1237),
[#1594](https://github.com/androidx/media/issues/1594)).
* Add `setFullscreenButtonState` to `PlayerView` to allow updates of
fullscreen button's icon on demand, i.e. out-of-band and not reactively
to a click interaction
([#1590](https://github.com/androidx/media/issues/1590),
[#184](https://github.com/androidx/media/issues/184)).
* Fix bug where the "None" choice in the text selection is not working if
there are app-defined text track selection preferences.
* DASH Extension:
* Add support for periods starting in the middle of a segment
([#1440](https://github.com/androidx/media/issues/1440)).
* 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:
* 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.):
* Add the IAMF decoder module, which provides support for playback of MP4
files containing IAMF tracks using the libiamf native library to
synthesize audio.
* Playback is enabled with a stereo layout as well as 5.1 with
spatialization together with optional head tracking enabled, but
binaural playback support is currently not available.
* Add 16 KB page support for decoder extensions on Android 15
([#1685](https://github.com/androidx/media/issues/1685)).
* Cast Extension:
* Stop cleaning the timeline after the CastSession disconnects, which
enables the sender app to resume playback locally after a disconnection.
* Populate CastPlayer's `DeviceInfo` when a `Context` is provided. This
enables linking the `MediaSession` to a `RoutingSession`, which is
necessary for integrating Output Switcher
([#1056](https://github.com/androidx/media/issues/1056)).
* Test Utilities:
* `DataSourceContractTest` now includes tests to verify:
* Input stream `read position` is updated.
* Output buffer `offset` is applied correctly.
* Demo app
* Resolve the memory leaks in demo short-form app
([#1839](https://github.com/androidx/media/issues/1839)).
* Remove deprecated symbols:
* Remove deprecated `Player.hasPrevious`, `Player.hasPreviousWindow()`.
Use `Player.hasPreviousMediaItem()` instead.
* Remove deprecated `Player.previous()`method. Use
`Player.seekToPreviousMediaItem()` instead.
* Remove deprecated `DrmSessionEventListener.onDrmSessionAcquired` method.
* Remove deprecated `DefaultEncoderFactory` constructors. Use
`DefaultEncoderFactory.Builder` instead.
### 1.5.0-rc02 (2024-11-19)
Use the 1.5.0 [stable version](#150-2024-11-27).
### 1.5.0-rc01 (2024-11-13)
Use the 1.5.0 [stable version](#150-2024-11-27).
### 1.5.0-beta01 (2024-10-30)
Use the 1.5.0 [stable version](#150-2024-11-27).
### 1.5.0-alpha01 (2024-09-06)
Use the 1.5.0 [stable version](#150-2024-11-27).
## 1.4
### 1.4.1 (2024-08-23)
This release includes the following changes since the
[1.4.0 release](#140-2024-07-24):
* ExoPlayer:
* Handle preload callbacks asynchronously in `PreloadMediaSource`
([#1568](https://github.com/androidx/media/issues/1568)).
* Allow playback regardless of buffered duration when loading fails
([#1571](https://github.com/androidx/media/issues/1571)).
* Extractors:
* MP3: Fix `Searched too many bytes` error by correctly ignoring trailing
non-MP3 data based on the length field in an `Info` frame
([#1480](https://github.com/androidx/media/issues/1480)).
* Text:
* TTML: Fix handling of percentage `tts:fontSize` values to ensure they
are correctly inherited from parent nodes with percentage `tts:fontSize`
values.
* Fix `IndexOutOfBoundsException` in `LegacySubtitleUtil` due to
incorrectly handling the case of the requested output start time being
greater than or equal to the final event time in the `Subtitle`
([#1516](https://github.com/androidx/media/issues/1516)).
* DRM:
* Fix `android.media.MediaCodec$CryptoException: Operation not supported
in this configuration: ERROR_DRM_CANNOT_HANDLE` error on API 31+ devices
playing L1 Widevine content. This error is caused by an incomplete
implementation of the framework
[`MediaDrm.requiresSecureDecoder`](https://developer.android.com/reference/android/media/MediaDrm#requiresSecureDecoder\(java.lang.String\))
method ([#1603](https://github.com/androidx/media/issues/1603)).
* Effect:
* Add a `release()` method to `GlObjectsProvider`.
* Session:
* Transform a double-tap of `KEYCODE_HEADSETHOOK` into a 'seek to next'
action, as
[documented](https://developer.android.com/reference/androidx/media3/session/MediaSession#media-key-events-mapping)
([#1493](https://github.com/androidx/media/issues/1493)).
* Handle `KEYCODE_HEADSETHOOK` as a 'play' command in
`MediaButtonReceiver` when deciding whether to ignore it to avoid a
`ForegroundServiceDidNotStartInTimeException`
([#1581](https://github.com/androidx/media/issues/1581)).
* RTSP Extension:
* Skip invalid Media Descriptions in SDP parsing
([#1087](https://github.com/androidx/media/issues/1472)).
### 1.4.0 (2024-07-24)
This release includes the following changes since the

10
api.txt
View file

@ -26,7 +26,7 @@ package androidx.media3.common {
}
public final class AudioAttributes {
method @RequiresApi(21) public androidx.media3.common.AudioAttributes.AudioAttributesV21 getAudioAttributesV21();
method public androidx.media3.common.AudioAttributes.AudioAttributesV21 getAudioAttributesV21();
field public static final androidx.media3.common.AudioAttributes DEFAULT;
field @androidx.media3.common.C.AudioAllowedCapturePolicy public final int allowedCapturePolicy;
field @androidx.media3.common.C.AudioContentType public final int contentType;
@ -35,7 +35,7 @@ package androidx.media3.common {
field @androidx.media3.common.C.AudioUsage public final int usage;
}
@RequiresApi(21) public static final class AudioAttributes.AudioAttributesV21 {
public static final class AudioAttributes.AudioAttributesV21 {
field public final android.media.AudioAttributes audioAttributes;
}
@ -79,6 +79,7 @@ package androidx.media3.common {
field public static final java.util.UUID PLAYREADY_UUID;
field public static final float RATE_UNSET = -3.4028235E38f;
field public static final int ROLE_FLAG_ALTERNATE = 2; // 0x2
field public static final int ROLE_FLAG_AUXILIARY = 32768; // 0x8000
field public static final int ROLE_FLAG_CAPTION = 64; // 0x40
field public static final int ROLE_FLAG_COMMENTARY = 8; // 0x8
field public static final int ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND = 1024; // 0x400
@ -156,7 +157,7 @@ package androidx.media3.common {
@IntDef(open=true, value={androidx.media3.common.C.CRYPTO_TYPE_UNSUPPORTED, androidx.media3.common.C.CRYPTO_TYPE_NONE, androidx.media3.common.C.CRYPTO_TYPE_FRAMEWORK}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target(java.lang.annotation.ElementType.TYPE_USE) public static @interface C.CryptoType {
}
@IntDef(flag=true, value={androidx.media3.common.C.ROLE_FLAG_MAIN, androidx.media3.common.C.ROLE_FLAG_ALTERNATE, androidx.media3.common.C.ROLE_FLAG_SUPPLEMENTARY, androidx.media3.common.C.ROLE_FLAG_COMMENTARY, androidx.media3.common.C.ROLE_FLAG_DUB, androidx.media3.common.C.ROLE_FLAG_EMERGENCY, androidx.media3.common.C.ROLE_FLAG_CAPTION, androidx.media3.common.C.ROLE_FLAG_SUBTITLE, androidx.media3.common.C.ROLE_FLAG_SIGN, androidx.media3.common.C.ROLE_FLAG_DESCRIBES_VIDEO, androidx.media3.common.C.ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND, androidx.media3.common.C.ROLE_FLAG_ENHANCED_DIALOG_INTELLIGIBILITY, androidx.media3.common.C.ROLE_FLAG_TRANSCRIBES_DIALOG, androidx.media3.common.C.ROLE_FLAG_EASY_TO_READ, androidx.media3.common.C.ROLE_FLAG_TRICK_PLAY}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface C.RoleFlags {
@IntDef(flag=true, value={androidx.media3.common.C.ROLE_FLAG_MAIN, androidx.media3.common.C.ROLE_FLAG_ALTERNATE, androidx.media3.common.C.ROLE_FLAG_SUPPLEMENTARY, androidx.media3.common.C.ROLE_FLAG_COMMENTARY, androidx.media3.common.C.ROLE_FLAG_DUB, androidx.media3.common.C.ROLE_FLAG_EMERGENCY, androidx.media3.common.C.ROLE_FLAG_CAPTION, androidx.media3.common.C.ROLE_FLAG_SUBTITLE, androidx.media3.common.C.ROLE_FLAG_SIGN, androidx.media3.common.C.ROLE_FLAG_DESCRIBES_VIDEO, androidx.media3.common.C.ROLE_FLAG_DESCRIBES_MUSIC_AND_SOUND, androidx.media3.common.C.ROLE_FLAG_ENHANCED_DIALOG_INTELLIGIBILITY, androidx.media3.common.C.ROLE_FLAG_TRANSCRIBES_DIALOG, androidx.media3.common.C.ROLE_FLAG_EASY_TO_READ, androidx.media3.common.C.ROLE_FLAG_TRICK_PLAY, androidx.media3.common.C.ROLE_FLAG_AUXILIARY}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface C.RoleFlags {
}
@IntDef(flag=true, value={androidx.media3.common.C.SELECTION_FLAG_DEFAULT, androidx.media3.common.C.SELECTION_FLAG_FORCED, androidx.media3.common.C.SELECTION_FLAG_AUTOSELECT}) @java.lang.annotation.Documented @java.lang.annotation.Retention(java.lang.annotation.RetentionPolicy.SOURCE) @java.lang.annotation.Target({java.lang.annotation.ElementType.FIELD, java.lang.annotation.ElementType.METHOD, java.lang.annotation.ElementType.PARAMETER, java.lang.annotation.ElementType.LOCAL_VARIABLE, java.lang.annotation.ElementType.TYPE_USE}) public static @interface C.SelectionFlags {
@ -548,6 +549,7 @@ package androidx.media3.common {
field public static final String APPLICATION_PGS = "application/pgs";
field @Deprecated public static final String APPLICATION_RAWCC = "application/x-rawcc";
field public static final String APPLICATION_RTSP = "application/x-rtsp";
field public static final String APPLICATION_SDP = "application/sdp";
field public static final String APPLICATION_SS = "application/vnd.ms-sstr+xml";
field public static final String APPLICATION_SUBRIP = "application/x-subrip";
field public static final String APPLICATION_TTML = "application/ttml+xml";
@ -1190,7 +1192,7 @@ package androidx.media3.common {
field public static final androidx.media3.common.VideoSize UNKNOWN;
field @IntRange(from=0) public final int height;
field @FloatRange(from=0, fromInclusive=false) public final float pixelWidthHeightRatio;
field @IntRange(from=0, to=359) public final int unappliedRotationDegrees;
field @Deprecated @IntRange(from=0, to=359) public final int unappliedRotationDegrees;
field @IntRange(from=0) public final int width;
}

View file

@ -19,7 +19,7 @@ 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.0'
classpath 'org.jetbrains.kotlin:kotlin-gradle-plugin:1.9.10'
}
}
allprojects {

View file

@ -14,6 +14,8 @@
apply from: "$gradle.ext.androidxMediaSettingsDir/constants.gradle"
apply plugin: 'com.android.library'
group = 'androidx.media3'
android {
compileSdkVersion project.ext.compileSdkVersion
@ -25,7 +27,6 @@ android {
aarMetadata {
minCompileSdk = project.ext.compileSdkVersion
}
multiDexEnabled true
}
compileOptions {
@ -40,7 +41,3 @@ android {
unitTests.includeAndroidResources true
}
}
dependencies {
androidTestImplementation 'androidx.multidex:multidex:' + androidxMultidexVersion
}

View file

@ -12,23 +12,24 @@
// See the License for the specific language governing permissions and
// limitations under the License.
project.ext {
releaseVersion = '1.4.0'
releaseVersionCode = 1_004_000_3_00
minSdkVersion = 19
releaseVersion = '1.5.1'
releaseVersionCode = 1_005_001_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
compileSdkVersion = 34
compileSdkVersion = 35
dexmakerVersion = '2.28.3'
// Use the same JUnit version as the Android repo:
// https://cs.android.com/android/platform/superproject/main/+/main:external/junit/METADATA
junitVersion = '4.13.2'
// Use the same Guava version as the Android repo:
// https://cs.android.com/android/platform/superproject/main/+/main:external/guava/METADATA
guavaVersion = '33.0.0-android'
guavaVersion = '33.3.1-android'
glideVersion = '4.14.2'
kotlinxCoroutinesVersion = '1.8.1'
leakCanaryVersion = '2.10'
mockitoVersion = '3.12.4'
@ -38,18 +39,15 @@ project.ext {
errorProneVersion = '2.18.0'
jsr305Version = '3.0.2'
kotlinAnnotationsVersion = '1.9.0'
// Updating this to 1.4.0+ will import Kotlin stdlib [internal ref: b/277891049].
androidxAnnotationVersion = '1.3.0'
androidxAnnotationVersion = '1.6.0'
androidxAnnotationExperimentalVersion = '1.3.1'
androidxAppCompatVersion = '1.6.1'
androidxCollectionVersion = '1.2.0'
androidxConstraintLayoutVersion = '2.1.4'
// Updating this to 1.9.0+ will import Kotlin stdlib [internal ref: b/277891049].
androidxCoreVersion = '1.8.0'
androidxExifInterfaceVersion = '1.3.6'
androidxLifecycleVersion = '2.6.0'
androidxMediaVersion = '1.7.0'
androidxMultidexVersion = '2.0.1'
androidxRecyclerViewVersion = '1.3.0'
androidxMaterialVersion = '1.8.0'
androidxTestCoreVersion = '1.5.0'

View file

@ -24,6 +24,9 @@ if (gradle.ext.has('androidxMediaModulePrefix')) {
include modulePrefix + 'lib-common'
project(modulePrefix + 'lib-common').projectDir = new File(rootDir, 'libraries/common')
include modulePrefix + 'lib-common-ktx'
project(modulePrefix + 'lib-common-ktx').projectDir = new File(rootDir, 'libraries/common_ktx')
include modulePrefix + 'lib-container'
project(modulePrefix + 'lib-container').projectDir = new File(rootDir, 'libraries/container')
@ -72,6 +75,8 @@ include modulePrefix + 'lib-decoder-ffmpeg'
project(modulePrefix + 'lib-decoder-ffmpeg').projectDir = new File(rootDir, 'libraries/decoder_ffmpeg')
include modulePrefix + 'lib-decoder-flac'
project(modulePrefix + 'lib-decoder-flac').projectDir = new File(rootDir, 'libraries/decoder_flac')
include modulePrefix + 'lib-decoder-iamf'
project(modulePrefix + 'lib-decoder-iamf').projectDir = new File(rootDir, 'libraries/decoder_iamf')
if (gradle.ext.has('androidxMediaEnableMidiModule') && gradle.ext.androidxMediaEnableMidiModule) {
include modulePrefix + 'lib-decoder-midi'
project(modulePrefix + 'lib-decoder-midi').projectDir = new File(rootDir, 'libraries/decoder_midi')

View file

@ -29,7 +29,6 @@ android {
versionCode project.ext.releaseVersionCode
minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.appTargetSdkVersion
multiDexEnabled true
}
buildTypes {
@ -62,7 +61,6 @@ dependencies {
implementation project(modulePrefix + 'lib-ui')
implementation project(modulePrefix + 'lib-cast')
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
implementation 'androidx.recyclerview:recyclerview:' + androidxRecyclerViewVersion
implementation 'com.google.android.material:material:' + androidxMaterialVersion
}

View file

@ -23,7 +23,6 @@
<uses-sdk/>
<application
android:name="androidx.multidex.MultiDexApplication"
android:label="@string/application_name"
android:icon="@mipmap/ic_launcher"
android:largeHeap="true"

View file

@ -32,7 +32,7 @@ android {
defaultConfig {
versionName project.ext.releaseVersion
versionCode project.ext.releaseVersionCode
minSdkVersion 21
minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.appTargetSdkVersion
}
@ -56,7 +56,13 @@ android {
compose true
}
composeOptions {
kotlinCompilerExtensionVersion = "1.5.0"
kotlinCompilerExtensionVersion = "1.5.3"
}
testOptions {
unitTests {
includeAndroidResources = true
}
}
}
@ -73,4 +79,9 @@ dependencies {
// 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')
}

View file

@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources xmlns:tools="http://schemas.android.com/tools">
<resources>
<!-- Base application theme. -->
<style name="Theme.Media3ComposeDemo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
@ -25,9 +25,7 @@
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">
?attr/colorPrimaryVariant
</item>
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View file

@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources xmlns:tools="http://schemas.android.com/tools">
<resources>
<!-- Base application theme. -->
<style name="Theme.Media3ComposeDemo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
@ -25,9 +25,7 @@
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">
?attr/colorPrimaryVariant
</item>
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View file

@ -29,9 +29,8 @@ android {
defaultConfig {
versionName project.ext.releaseVersion
versionCode project.ext.releaseVersionCode
minSdkVersion 21
minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.appTargetSdkVersion
multiDexEnabled true
}
buildTypes {
@ -55,9 +54,9 @@ dependencies {
implementation project(modulePrefix + 'lib-effect')
implementation project(modulePrefix + 'lib-exoplayer')
implementation project(modulePrefix + 'lib-exoplayer-dash')
implementation project(modulePrefix + 'lib-muxer')
implementation project(modulePrefix + 'lib-transformer')
implementation project(modulePrefix + 'lib-ui')
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
}

View file

@ -15,23 +15,38 @@
*/
package androidx.media3.demo.composition;
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;
import static androidx.media3.transformer.Composition.HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL;
import android.app.Activity;
import android.content.DialogInterface;
import android.os.Bundle;
import android.view.LayoutInflater;
import android.view.View;
import android.widget.ArrayAdapter;
import android.widget.CheckBox;
import android.widget.Spinner;
import android.widget.Toast;
import androidx.annotation.Nullable;
import androidx.appcompat.app.AlertDialog;
import androidx.appcompat.app.AppCompatActivity;
import androidx.appcompat.widget.AppCompatButton;
import androidx.appcompat.widget.AppCompatCheckBox;
import androidx.appcompat.widget.AppCompatTextView;
import androidx.media3.common.Effect;
import androidx.media3.common.MediaItem;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.PlaybackException;
import androidx.media3.common.Player;
import androidx.media3.common.audio.SonicAudioProcessor;
import androidx.media3.common.util.Clock;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.Util;
import androidx.media3.effect.DebugTraceUtil;
import androidx.media3.effect.LanczosResample;
import androidx.media3.effect.Presentation;
import androidx.media3.effect.RgbFilter;
import androidx.media3.transformer.Composition;
import androidx.media3.transformer.CompositionPlayer;
@ -40,6 +55,7 @@ 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.JsonUtil;
import androidx.media3.transformer.Transformer;
import androidx.media3.ui.PlayerView;
@ -49,6 +65,7 @@ import androidx.recyclerview.widget.RecyclerView;
import com.google.common.base.Stopwatch;
import com.google.common.base.Ticker;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.io.File;
import java.io.IOException;
import java.util.ArrayList;
@ -63,6 +80,19 @@ import org.json.JSONObject;
*/
public final class CompositionPreviewActivity extends AppCompatActivity {
private static final String TAG = "CompPreviewActivity";
private static final String AUDIO_URI =
"https://storage.googleapis.com/exoplayer-test-media-0/play.mp3";
private static final String SAME_AS_INPUT_OPTION = "same as input";
private static final ImmutableMap<String, @Composition.HdrMode Integer> HDR_MODE_DESCRIPTIONS =
new ImmutableMap.Builder<String, @Composition.HdrMode Integer>()
.put("Keep HDR", HDR_MODE_KEEP_HDR)
.put("MediaCodec tone-map HDR to SDR", HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_MEDIACODEC)
.put("OpenGL tone-map HDR to SDR", HDR_MODE_TONE_MAP_HDR_TO_SDR_USING_OPEN_GL)
.put("Force Interpret HDR as SDR", HDR_MODE_EXPERIMENTAL_FORCE_INTERPRET_HDR_AS_SDR)
.build();
private static final ImmutableList<String> RESOLUTION_HEIGHTS =
ImmutableList.of(
SAME_AS_INPUT_OPTION, "144", "240", "360", "480", "720", "1080", "1440", "2160");
private ArrayList<String> sequenceAssetTitles;
private boolean[] selectedMediaItems;
@ -75,6 +105,8 @@ public final class CompositionPreviewActivity extends AppCompatActivity {
private AppCompatButton exportButton;
private AppCompatTextView exportInformationTextView;
private Stopwatch exportStopwatch;
private boolean includeBackgroundAudioTrack;
private boolean appliesVideoEffects;
@Override
protected void onCreate(@Nullable Bundle savedInstanceState) {
@ -92,7 +124,28 @@ public final class CompositionPreviewActivity extends AppCompatActivity {
exportInformationTextView = findViewById(R.id.export_information_text);
exportButton = findViewById(R.id.composition_export_button);
exportButton.setOnClickListener(view -> exportComposition());
exportButton.setOnClickListener(view -> showExportSettings());
AppCompatCheckBox backgroundAudioCheckBox = findViewById(R.id.background_audio_checkbox);
backgroundAudioCheckBox.setOnCheckedChangeListener(
(compoundButton, checked) -> includeBackgroundAudioTrack = checked);
ArrayAdapter<String> resolutionHeightAdapter =
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
resolutionHeightAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
Spinner resolutionHeightSpinner = findViewById(R.id.resolution_height_spinner);
resolutionHeightSpinner.setAdapter(resolutionHeightAdapter);
resolutionHeightAdapter.addAll(RESOLUTION_HEIGHTS);
ArrayAdapter<String> hdrModeAdapter = new ArrayAdapter<>(this, R.layout.spinner_item);
hdrModeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
Spinner hdrModeSpinner = findViewById(R.id.hdr_mode_spinner);
hdrModeSpinner.setAdapter(hdrModeAdapter);
hdrModeAdapter.addAll(HDR_MODE_DESCRIPTIONS.keySet());
AppCompatCheckBox applyVideoEffectsCheckBox = findViewById(R.id.apply_video_effects_checkbox);
applyVideoEffectsCheckBox.setOnCheckedChangeListener(
((compoundButton, checked) -> appliesVideoEffects = checked));
presetDescriptions = getResources().getStringArray(R.array.preset_descriptions);
// Select two media items by default.
@ -137,9 +190,22 @@ public final class CompositionPreviewActivity extends AppCompatActivity {
String[] presetUris = getResources().getStringArray(/* id= */ R.array.preset_uris);
int[] presetDurationsUs = getResources().getIntArray(/* id= */ R.array.preset_durations);
List<EditedMediaItem> mediaItems = new ArrayList<>();
ImmutableList<Effect> effects =
ImmutableList.of(
MatrixTransformationFactory.createDizzyCropEffect(), RgbFilter.createGrayscaleFilter());
ImmutableList.Builder<Effect> videoEffectsBuilder = new ImmutableList.Builder<>();
if (appliesVideoEffects) {
videoEffectsBuilder.add(MatrixTransformationFactory.createDizzyCropEffect());
videoEffectsBuilder.add(RgbFilter.createGrayscaleFilter());
}
Spinner resolutionHeightSpinner = findViewById(R.id.resolution_height_spinner);
String selectedResolutionHeight = String.valueOf(resolutionHeightSpinner.getSelectedItem());
if (!SAME_AS_INPUT_OPTION.equals(selectedResolutionHeight)) {
int resolutionHeight = Integer.parseInt(selectedResolutionHeight);
videoEffectsBuilder.add(LanczosResample.scaleToFit(10000, resolutionHeight));
videoEffectsBuilder.add(Presentation.createForHeight(resolutionHeight));
}
ImmutableList<Effect> videoEffects = videoEffectsBuilder.build();
// Preview requires all sequences to be the same duration, so calculate main sequence duration
// and limit background sequence duration to match.
long videoSequenceDurationUs = 0;
for (int i = 0; i < selectedMediaItems.length; i++) {
if (selectedMediaItems[i]) {
SonicAudioProcessor pitchChanger = new SonicAudioProcessor();
@ -154,22 +220,47 @@ public final class CompositionPreviewActivity extends AppCompatActivity {
.setEffects(
new Effects(
/* audioProcessors= */ ImmutableList.of(pitchChanger),
/* videoEffects= */ effects))
/* videoEffects= */ videoEffects))
.setDurationUs(presetDurationsUs[i]);
videoSequenceDurationUs += presetDurationsUs[i];
mediaItems.add(itemBuilder.build());
}
}
EditedMediaItemSequence videoSequence = new EditedMediaItemSequence(mediaItems);
EditedMediaItemSequence videoSequence = new EditedMediaItemSequence.Builder(mediaItems).build();
List<EditedMediaItemSequence> compositionSequences = new ArrayList<>();
compositionSequences.add(videoSequence);
if (includeBackgroundAudioTrack) {
compositionSequences.add(getAudioBackgroundSequence(Util.usToMs(videoSequenceDurationUs)));
}
SonicAudioProcessor sampleRateChanger = new SonicAudioProcessor();
sampleRateChanger.setOutputSampleRateHz(8_000);
return new Composition.Builder(/* sequences= */ ImmutableList.of(videoSequence))
Spinner hdrModeSpinner = findViewById(R.id.hdr_mode_spinner);
int selectedHdrMode =
HDR_MODE_DESCRIPTIONS.get(String.valueOf(hdrModeSpinner.getSelectedItem()));
return new Composition.Builder(compositionSequences)
.setEffects(
new Effects(
/* audioProcessors= */ ImmutableList.of(sampleRateChanger),
/* videoEffects= */ ImmutableList.of()))
.setHdrMode(selectedHdrMode)
.build();
}
private EditedMediaItemSequence getAudioBackgroundSequence(long durationMs) {
MediaItem audioMediaItem =
new MediaItem.Builder()
.setUri(AUDIO_URI)
.setClippingConfiguration(
new MediaItem.ClippingConfiguration.Builder()
.setStartPositionMs(0)
.setEndPositionMs(durationMs)
.build())
.build();
EditedMediaItem audioItem =
new EditedMediaItem.Builder(audioMediaItem).setDurationUs(59_000_000).build();
return new EditedMediaItemSequence.Builder(audioItem).build();
}
private void previewComposition() {
releasePlayer();
Composition composition = prepareComposition();
@ -188,6 +279,7 @@ 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();
@ -197,7 +289,7 @@ public final class CompositionPreviewActivity extends AppCompatActivity {
new AlertDialog.Builder(/* context= */ this)
.setTitle(R.string.select_preset_title)
.setMultiChoiceItems(presetDescriptions, selectedMediaItems, this::selectPresetInDialog)
.setPositiveButton(android.R.string.ok, /* listener= */ null)
.setPositiveButton(R.string.ok, /* listener= */ null)
.setCancelable(false)
.create()
.show();
@ -216,7 +308,67 @@ public final class CompositionPreviewActivity extends AppCompatActivity {
}
}
private void exportComposition() {
private void showExportSettings() {
AlertDialog.Builder alertDialogBuilder = new AlertDialog.Builder(this);
LayoutInflater inflater = this.getLayoutInflater();
View exportSettingsDialogView = inflater.inflate(R.layout.export_settings, null);
alertDialogBuilder
.setView(exportSettingsDialogView)
.setTitle(R.string.export_settings)
.setPositiveButton(
R.string.export, (dialog, id) -> exportComposition(exportSettingsDialogView))
.setNegativeButton(R.string.cancel, (dialog, id) -> dialog.dismiss());
ArrayAdapter<String> audioMimeAdapter =
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
audioMimeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
Spinner audioMimeSpinner = exportSettingsDialogView.findViewById(R.id.audio_mime_spinner);
audioMimeSpinner.setAdapter(audioMimeAdapter);
audioMimeAdapter.addAll(
SAME_AS_INPUT_OPTION, MimeTypes.AUDIO_AAC, MimeTypes.AUDIO_AMR_NB, MimeTypes.AUDIO_AMR_WB);
ArrayAdapter<String> videoMimeAdapter =
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
videoMimeAdapter.setDropDownViewResource(android.R.layout.simple_spinner_dropdown_item);
Spinner videoMimeSpinner = exportSettingsDialogView.findViewById(R.id.video_mime_spinner);
videoMimeSpinner.setAdapter(videoMimeAdapter);
videoMimeAdapter.addAll(
SAME_AS_INPUT_OPTION,
MimeTypes.VIDEO_H263,
MimeTypes.VIDEO_H264,
MimeTypes.VIDEO_H265,
MimeTypes.VIDEO_MP4V,
MimeTypes.VIDEO_AV1);
CheckBox enableDebugTracingCheckBox =
exportSettingsDialogView.findViewById(R.id.enable_debug_tracing_checkbox);
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(
(buttonView, isChecked) -> {
if (isChecked) {
useMedia3MuxerCheckBox.setChecked(true);
}
});
AlertDialog dialog = alertDialogBuilder.create();
dialog.show();
}
private void exportComposition(View exportSettingsDialogView) {
// Cancel and clean up files from any ongoing export.
cancelExport();
@ -237,8 +389,33 @@ public final class CompositionPreviewActivity extends AppCompatActivity {
}
String filePath = outputFile.getAbsolutePath();
Transformer.Builder transformerBuilder = new Transformer.Builder(/* context= */ this);
Spinner audioMimeTypeSpinner = exportSettingsDialogView.findViewById(R.id.audio_mime_spinner);
String selectedAudioMimeType = String.valueOf(audioMimeTypeSpinner.getSelectedItem());
if (!SAME_AS_INPUT_OPTION.equals(selectedAudioMimeType)) {
transformerBuilder.setAudioMimeType(selectedAudioMimeType);
}
Spinner videoMimeTypeSpinner = exportSettingsDialogView.findViewById(R.id.video_mime_spinner);
String selectedVideoMimeType = String.valueOf(videoMimeTypeSpinner.getSelectedItem());
if (!SAME_AS_INPUT_OPTION.equals(selectedVideoMimeType)) {
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());
}
transformer =
new Transformer.Builder(/* context= */ this)
transformerBuilder
.addListener(
new Transformer.Listener() {
@Override
@ -247,6 +424,7 @@ public final class CompositionPreviewActivity extends AppCompatActivity {
long elapsedTimeMs = exportStopwatch.elapsed(TimeUnit.MILLISECONDS);
String details =
getString(R.string.export_completed, elapsedTimeMs / 1000.f, filePath);
Log.d(TAG, DebugTraceUtil.generateTraceSummary());
Log.i(TAG, details);
exportInformationTextView.setText(details);
@ -275,6 +453,7 @@ public final class CompositionPreviewActivity extends AppCompatActivity {
Toast.LENGTH_LONG)
.show();
Log.e(TAG, "Export error", exportException);
Log.d(TAG, DebugTraceUtil.generateTraceSummary());
exportInformationTextView.setText(R.string.export_error);
}
})

View file

@ -43,7 +43,7 @@
android:layout_marginBottom="8dp"
android:padding="8dp"
android:textAppearance="@style/TextAppearance.AppCompat.Medium"
android:text="@string/preview_single_sequence" />
android:text="@string/preview_composition" />
<FrameLayout
android:layout_width="match_parent"
@ -64,7 +64,7 @@
android:id="@+id/sequence_header_text"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/single_sequence_items"
android:text="@string/video_sequence_items"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintTop_toBottomOf="@id/composition_preview_card_view"
app:layout_constraintBottom_toTopOf="@id/composition_preset_list"/>
@ -75,8 +75,18 @@
android:layout_height="wrap_content"
android:textAppearance="@style/TextAppearance.AppCompat.Small"
android:text="@string/edit"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintTop_toBottomOf="@id/composition_preview_card_view"/>
app:layout_constraintStart_toEndOf="@id/sequence_header_text"
app:layout_constraintTop_toTopOf="@id/sequence_header_text"
app:layout_constraintBottom_toBottomOf="@id/sequence_header_text"/>
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/apply_video_effects_checkbox"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
android:text="@string/add_effects"
app:layout_constraintStart_toEndOf="@id/edit_sequence_button"
app:layout_constraintTop_toTopOf="@id/sequence_header_text"
app:layout_constraintBottom_toBottomOf="@id/sequence_header_text" />
<androidx.recyclerview.widget.RecyclerView
android:id="@+id/composition_preset_list"
@ -92,7 +102,58 @@
android:layout_width="match_parent"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintBottom_toTopOf="@id/composition_export_button"/>
app:layout_constraintBottom_toTopOf="@id/background_audio_checkbox"/>
<androidx.appcompat.widget.AppCompatCheckBox
android:id="@+id/background_audio_checkbox"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:text="@string/add_background_audio"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/resolution_height_setting" />
<LinearLayout
android:id="@+id/resolution_height_setting"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="8dp"
app:layout_constraintBottom_toTopOf="@id/hdr_mode_setting">
<TextView
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="1"
android:text="@string/output_video_resolution"/>
<Spinner
android:id="@+id/resolution_height_spinner"
android:layout_gravity="end|center_vertical"
android:gravity="end"
android:layout_height="wrap_content"
android:layout_width="wrap_content"/>
</LinearLayout>
<LinearLayout
android:id="@+id/hdr_mode_setting"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="12dp"
app:layout_constraintBottom_toTopOf="@id/preview_button">
<TextView
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="1"
android:text="@string/hdr_mode" />
<Spinner
android:id="@+id/hdr_mode_spinner"
android:layout_gravity="end|center_vertical"
android:gravity="end"
android:layout_height="wrap_content"
android:layout_width="wrap_content"/>
</LinearLayout>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/composition_export_button"
@ -100,9 +161,9 @@
android:layout_marginTop="16dp"
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintStart_toEndOf="@id/preview_button"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/preview_button"/>
app:layout_constraintBottom_toBottomOf="parent"/>
<androidx.appcompat.widget.AppCompatButton
android:id="@+id/preview_button"
@ -111,7 +172,7 @@
android:layout_width="wrap_content"
android:layout_height="wrap_content"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintEnd_toStartOf="@id/composition_export_button"
app:layout_constraintBottom_toBottomOf="parent"/>
</androidx.constraintlayout.widget.ConstraintLayout>

View file

@ -0,0 +1,110 @@
<?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
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.
-->
<LinearLayout xmlns:android="http://schemas.android.com/apk/res/android"
android:id="@+id/export_settings_list"
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="vertical"
android:padding="8dp">
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="12dp"
android:layout_marginTop="12dp">
<TextView
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="1"
android:text="@string/output_audio_mime_type"/>
<Spinner
android:id="@+id/audio_mime_spinner"
android:layout_gravity="end|center_vertical"
android:gravity="end"
android:layout_height="wrap_content"
android:layout_width="wrap_content"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical"
android:layout_marginBottom="12dp">
<TextView
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="1"
android:text="@string/output_video_mime_type"/>
<Spinner
android:id="@+id/video_mime_spinner"
android:layout_gravity="end|center_vertical"
android:gravity="end"
android:layout_height="wrap_content"
android:layout_width="wrap_content"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="1"
android:text="@string/enable_debug_tracing"/>
<CheckBox
android:id="@+id/enable_debug_tracing_checkbox"
android:layout_gravity="end"
android:checked="false"
android:layout_height="wrap_content"
android:layout_width="wrap_content"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:text="@string/use_media3_muxer"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="1" />
<CheckBox
android:id="@+id/use_media3_muxer_checkbox"
android:layout_gravity="end"
android:checked="false"
android:layout_height="wrap_content"
android:layout_width="wrap_content"/>
</LinearLayout>
<LinearLayout
android:layout_width="match_parent"
android:layout_height="wrap_content"
android:orientation="horizontal"
android:gravity="center_vertical">
<TextView
android:text="@string/produce_fragmented_mp4"
android:layout_height="wrap_content"
android:layout_width="0dp"
android:layout_weight="1" />
<CheckBox
android:id="@+id/produce_fragmented_mp4_checkbox"
android:layout_gravity="end"
android:checked="false"
android:layout_height="wrap_content"
android:layout_width="wrap_content"/>
</LinearLayout>
</LinearLayout>

View file

@ -0,0 +1,25 @@
<?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
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.
-->
<TextView
xmlns:android="http://schemas.android.com/apk/res/android"
android:layout_width="wrap_content"
android:layout_height="32dp"
android:gravity="start|center_vertical"
android:paddingLeft="4dp"
android:paddingRight="4dp"
android:layout_marginLeft="4dp"
android:layout_marginRight="4dp"
android:textIsSelectable="false" />

View file

@ -27,7 +27,7 @@
<item>H264 video and AAC audio (portrait, H &lt; W, 90°)</item>
<item>SEF slow motion with 240 fps</item>
<item>480p DASH (non-square pixels)</item>
<item>HDR (HDR10) H265 limited range video (encoding may fail)</item>
<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</item>
<item>London JPG image (plays for 5 secs at 30 fps)</item>

View file

@ -16,12 +16,24 @@
<resources>
<string name="app_name">Composition Demo</string>
<string name="edit">Edit</string>
<string name="add_effects">Add effects</string>
<string name="preview" translatable="false">Preview</string>
<string name="preview_single_sequence" translatable="false">Single sequence preview</string>
<string name="single_sequence_items" translatable="false">Single sequence items:</string>
<string name="preview_composition" translatable="false">Composition preview</string>
<string name="video_sequence_items" translatable="false">Video sequence items:</string>
<string name="select_preset_title" translatable="false">Choose preset input</string>
<string name="export" translatable="false">Export</string>
<string name="export_completed" translatable="false">Export completed in %.3f seconds.\nOutput: %s</string>
<string name="export_error" translatable="false">Export error</string>
<string name="export_started" translatable="false">Export started</string>
<string name="add_background_audio" translatable="false">Add background audio</string>
<string name="output_video_resolution" translatable="false">Output video resolution</string>
<string name="hdr_mode" translatable="false">HDR mode</string>
<string name="ok" translatable="false">OK</string>
<string name="cancel" translatable="false">Cancel</string>
<string name="export_settings" translatable="false">Export Settings</string>
<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>
</resources>

View file

@ -29,7 +29,6 @@ android {
versionCode project.ext.releaseVersionCode
minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.appTargetSdkVersion
multiDexEnabled true
}
buildTypes {
@ -55,6 +54,5 @@ dependencies {
implementation project(modulePrefix + 'lib-exoplayer-smoothstreaming')
implementation project(modulePrefix + 'lib-ui')
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
}

View file

@ -22,7 +22,6 @@
<uses-sdk/>
<application
android:name="androidx.multidex.MultiDexApplication"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/application_name">

View file

@ -31,7 +31,6 @@ android {
versionCode project.ext.releaseVersionCode
minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.appTargetSdkVersion
multiDexEnabled true
}
buildTypes {
@ -75,7 +74,6 @@ dependencies {
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
implementation 'com.google.android.material:material:' + androidxMaterialVersion
implementation project(modulePrefix + 'lib-exoplayer')
implementation project(modulePrefix + 'lib-exoplayer-dash')
@ -89,6 +87,7 @@ dependencies {
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-ffmpeg')
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-flac')
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-opus')
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-iamf')
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-vp9')
withDecoderExtensionsImplementation project(modulePrefix + 'lib-decoder-midi')
withDecoderExtensionsImplementation project(modulePrefix + 'lib-datasource-rtmp')

View file

@ -40,7 +40,6 @@
android:largeHeap="true"
android:allowBackup="false"
android:supportsRtl="true"
android:name="androidx.multidex.MultiDexApplication"
tools:targetApi="29">
<activity android:name=".SampleChooserActivity"

View file

@ -758,6 +758,10 @@
{
"name": "One hour frame counter (MP4)",
"uri": "https://storage.googleapis.com/exoplayer-test-media-1/mp4/frame-counter-one-hour.mp4"
},
{
"name": "Immersive Audio Format Sample (MP4, IAMF)",
"uri": "https://github.com/AOMediaCodec/libiamf/raw/main/tests/test_000036_s.mp4"
}
]
},

View file

@ -63,7 +63,7 @@ public class DemoDownloadService extends DownloadService {
@Override
protected Scheduler getScheduler() {
return Util.SDK_INT >= 21 ? new PlatformScheduler(this, JOB_ID) : null;
return new PlatformScheduler(this, JOB_ID);
}
@Override

View file

@ -18,6 +18,7 @@ package androidx.media3.demo.main;
import android.content.Context;
import android.net.http.HttpEngine;
import android.os.Build;
import android.os.ext.SdkExtensions;
import androidx.annotation.OptIn;
import androidx.media3.database.DatabaseProvider;
import androidx.media3.database.StandaloneDatabaseProvider;
@ -49,16 +50,6 @@ public final class DemoUtil {
public static final String DOWNLOAD_NOTIFICATION_CHANNEL_ID = "download_channel";
/**
* Whether the demo application uses Cronet for networking when {@link HttpEngine} is not
* supported. Note that Cronet does not provide automatic support for cookies
* (https://github.com/google/ExoPlayer/issues/5975).
*
* <p>If set to false, the {@link DefaultHttpDataSource} is used with a {@link CookieManager}
* configured in {@link #getHttpDataSourceFactory} when {@link HttpEngine} is not supported.
*/
private static final boolean ALLOW_CRONET_FOR_NETWORKING = true;
private static final String TAG = "DemoUtil";
private static final String DOWNLOAD_CONTENT_DIRECTORY = "downloads";
@ -106,22 +97,20 @@ public final class DemoUtil {
return httpDataSourceFactory;
}
context = context.getApplicationContext();
if (Build.VERSION.SDK_INT >= 34) {
if (Build.VERSION.SDK_INT >= 30
&& SdkExtensions.getExtensionVersion(Build.VERSION_CODES.S) >= 7) {
HttpEngine httpEngine = new HttpEngine.Builder(context).build();
httpDataSourceFactory =
new HttpEngineDataSource.Factory(httpEngine, Executors.newSingleThreadExecutor());
return httpDataSourceFactory;
}
if (ALLOW_CRONET_FOR_NETWORKING) {
@Nullable CronetEngine cronetEngine = CronetUtil.buildCronetEngine(context);
if (cronetEngine != null) {
httpDataSourceFactory =
new CronetDataSource.Factory(cronetEngine, Executors.newSingleThreadExecutor());
return httpDataSourceFactory;
}
@Nullable CronetEngine cronetEngine = CronetUtil.buildCronetEngine(context);
if (cronetEngine != null) {
httpDataSourceFactory =
new CronetDataSource.Factory(cronetEngine, Executors.newSingleThreadExecutor());
return httpDataSourceFactory;
}
// The device doesn't support HttpEngine or we don't want to allow Cronet, or we failed to
// instantiate a CronetEngine.
// The device doesn't support HttpEngine and we failed to instantiate a CronetEngine.
CookieManager cookieManager = new CookieManager();
cookieManager.setCookiePolicy(CookiePolicy.ACCEPT_ORIGINAL_SERVER);
CookieHandler.setDefault(cookieManager);

View file

@ -45,7 +45,6 @@ import android.widget.ExpandableListView.OnChildClickListener;
import android.widget.ImageButton;
import android.widget.TextView;
import android.widget.Toast;
import androidx.annotation.DoNotInline;
import androidx.annotation.Nullable;
import androidx.annotation.OptIn;
import androidx.annotation.RequiresApi;
@ -667,7 +666,6 @@ public class SampleChooserActivity extends AppCompatActivity
@RequiresApi(33)
private static class Api33 {
@DoNotInline
public static String getPostNotificationPermissionString() {
return Manifest.permission.POST_NOTIFICATIONS;
}

View file

@ -34,7 +34,6 @@ android {
versionCode project.ext.releaseVersionCode
minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.appTargetSdkVersion
multiDexEnabled true
}
buildTypes {
@ -65,7 +64,6 @@ dependencies {
implementation 'androidx.lifecycle:lifecycle-common:' + androidxLifecycleVersion
implementation 'androidx.lifecycle:lifecycle-runtime-ktx:' + androidxLifecycleVersion
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
implementation 'com.google.android.material:material:' + androidxMaterialVersion
implementation 'org.jetbrains.kotlinx:kotlinx-coroutines-guava:' + kotlinxCoroutinesVersion
implementation project(modulePrefix + 'lib-ui')

View file

@ -14,7 +14,6 @@
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.session">
<uses-sdk/>
@ -23,12 +22,10 @@
<uses-permission android:name="android.permission.FOREGROUND_SERVICE_MEDIA_PLAYBACK" />
<application
android:name="androidx.multidex.MultiDexApplication"
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:theme="@style/Theme.Media3Demo"
tools:replace="android:name">
android:theme="@style/Theme.Media3Demo">
<!-- Declare that this session demo supports Android Auto. -->
<meta-data

View file

@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources xmlns:tools="http://schemas.android.com/tools">
<resources>
<!-- Base application theme. -->
<style name="Theme.Media3Demo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
@ -25,9 +25,7 @@
<item name="colorSecondaryVariant">@color/teal_200</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">
?attr/colorPrimaryVariant
</item>
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View file

@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources xmlns:tools="http://schemas.android.com/tools">
<resources>
<!-- Base application theme. -->
<style name="Theme.Media3Demo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
@ -25,9 +25,7 @@
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">
?attr/colorPrimaryVariant
</item>
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View file

@ -34,7 +34,6 @@ android {
versionCode project.ext.releaseVersionCode
minSdkVersion project.ext.automotiveMinSdkVersion
targetSdkVersion project.ext.appTargetSdkVersion
multiDexEnabled true
}
buildTypes {
@ -60,7 +59,6 @@ android {
dependencies {
implementation 'androidx.core:core-ktx:' + androidxCoreVersion
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
implementation 'com.google.android.material:material:' + androidxMaterialVersion
implementation project(modulePrefix + 'lib-session')
implementation project(modulePrefix + 'demo-session-service')

View file

@ -14,7 +14,6 @@
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.session.automotive">
<uses-sdk/>
@ -39,13 +38,11 @@
android:resource="@xml/automotive_app_desc"/>
<application
android:name="androidx.multidex.MultiDexApplication"
android:allowBackup="false"
android:taskAffinity=""
android:appCategory="audio"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
tools:replace="android:name">
android:label="@string/app_name">
<meta-data
android:name="androidx.car.app.TintableAttributionIcon"

View file

@ -33,7 +33,6 @@ android {
versionCode project.ext.releaseVersionCode
minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.appTargetSdkVersion
multiDexEnabled true
}
buildTypes {
@ -54,7 +53,6 @@ android {
dependencies {
implementation 'androidx.core:core-ktx:' + androidxCoreVersion
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
implementation project(modulePrefix + 'lib-exoplayer')
implementation project(modulePrefix + 'lib-exoplayer-dash')
implementation project(modulePrefix + 'lib-exoplayer-hls')

View file

@ -24,11 +24,13 @@ import android.os.Build
import androidx.annotation.OptIn
import androidx.core.app.NotificationCompat
import androidx.core.app.NotificationManagerCompat
import androidx.core.os.bundleOf
import androidx.media3.common.AudioAttributes
import androidx.media3.common.util.UnstableApi
import androidx.media3.demo.session.service.R
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.util.EventLogger
import androidx.media3.session.MediaConstants
import androidx.media3.session.MediaLibraryService
import androidx.media3.session.MediaSession
import androidx.media3.session.MediaSession.ControllerInfo
@ -111,6 +113,17 @@ open class DemoPlaybackService : MediaLibraryService() {
MediaLibrarySession.Builder(this, player, createLibrarySessionCallback())
.also { builder -> getSingleTopActivity()?.let { builder.setSessionActivity(it) } }
.build()
.also { mediaLibrarySession ->
// The media session always supports skip, except at the start and end of the playlist.
// Reserve the space for the skip action in these cases to avoid custom actions jumping
// around when the user skips.
mediaLibrarySession.setSessionExtras(
bundleOf(
MediaConstants.EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_PREV to true,
MediaConstants.EXTRAS_KEY_SLOT_RESERVATION_SEEK_TO_NEXT to true,
)
)
}
}
@OptIn(UnstableApi::class) // MediaSessionService.Listener

View file

@ -34,7 +34,6 @@ android {
versionCode project.ext.releaseVersionCode
minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.appTargetSdkVersion
multiDexEnabled true
}
buildTypes {
@ -80,7 +79,6 @@ dependencies {
implementation 'androidx.core:core-ktx:' + androidxCoreVersion
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
implementation 'com.google.android.material:material:' + androidxMaterialVersion
implementation project(modulePrefix + 'lib-exoplayer')
implementation project(modulePrefix + 'lib-exoplayer-dash')

View file

@ -14,17 +14,14 @@
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.shortform">
<application
android:allowBackup="false"
android:icon="@mipmap/ic_launcher"
android:label="@string/app_name"
android:name="androidx.multidex.MultiDexApplication"
android:theme="@style/Theme.MaterialComponents.DayNight.NoActionBar"
android:taskAffinity=""
tools:replace="android:name">
android:taskAffinity="">
<activity
android:exported="true"
android:name=".MainActivity">

View file

@ -15,16 +15,13 @@
*/
package androidx.media3.demo.shortform
import android.content.Context
import android.os.Handler
import android.os.Looper
import androidx.annotation.OptIn
import androidx.media3.common.Player
import androidx.media3.common.util.UnstableApi
import androidx.media3.exoplayer.ExoPlayer
import androidx.media3.exoplayer.LoadControl
import androidx.media3.exoplayer.RenderersFactory
import androidx.media3.exoplayer.upstream.BandwidthMeter
import androidx.media3.exoplayer.source.preload.DefaultPreloadManager.Builder
import androidx.media3.exoplayer.util.EventLogger
import com.google.common.collect.BiMap
import com.google.common.collect.HashBiMap
@ -34,14 +31,7 @@ import java.util.LinkedList
import java.util.Queue
@OptIn(UnstableApi::class)
class PlayerPool(
private val numberOfPlayers: Int,
context: Context,
playbackLooper: Looper,
loadControl: LoadControl,
renderersFactory: RenderersFactory,
bandwidthMeter: BandwidthMeter,
) {
class PlayerPool(private val numberOfPlayers: Int, preloadManagerBuilder: Builder) {
/** Creates a player instance to be used by the pool. */
interface PlayerFactory {
@ -52,8 +42,7 @@ class PlayerPool(
private val availablePlayerQueue: Queue<Int> = LinkedList()
private val playerMap: BiMap<Int, ExoPlayer> = Maps.synchronizedBiMap(HashBiMap.create())
private val playerRequestTokenSet: MutableSet<Int> = Collections.synchronizedSet(HashSet<Int>())
private val playerFactory: PlayerFactory =
DefaultPlayerFactory(context, playbackLooper, loadControl, renderersFactory, bandwidthMeter)
private val playerFactory: PlayerFactory = DefaultPlayerFactory(preloadManagerBuilder)
fun acquirePlayer(token: Int, callback: (ExoPlayer) -> Unit) {
synchronized(playerMap) {
@ -126,23 +115,11 @@ class PlayerPool(
}
@OptIn(UnstableApi::class)
private class DefaultPlayerFactory(
private val context: Context,
private val playbackLooper: Looper,
private val loadControl: LoadControl,
private val renderersFactory: RenderersFactory,
private val bandwidthMeter: BandwidthMeter,
) : PlayerFactory {
private class DefaultPlayerFactory(private val preloadManagerBuilder: Builder) : PlayerFactory {
private var playerCounter = 0
override fun createPlayer(): ExoPlayer {
val player =
ExoPlayer.Builder(context)
.setPlaybackLooper(playbackLooper)
.setLoadControl(loadControl)
.setRenderersFactory(renderersFactory)
.setBandwidthMeter(bandwidthMeter)
.build()
val player = preloadManagerBuilder.buildExoPlayer()
player.addAnalyticsListener(EventLogger("player-$playerCounter"))
playerCounter++
player.repeatMode = ExoPlayer.REPEAT_MODE_ONE

View file

@ -25,7 +25,7 @@ import androidx.viewpager2.widget.ViewPager2
class ViewPagerActivity : AppCompatActivity() {
private lateinit var viewPagerView: ViewPager2
private lateinit var adapter: ViewPagerMediaAdapter
private lateinit var onPageChangeCallback: ViewPager2.OnPageChangeCallback
private var numberOfPlayers = 3
private var mediaItemDatabase = MediaItemDatabase()
@ -40,23 +40,24 @@ class ViewPagerActivity : AppCompatActivity() {
Log.d(TAG, "Using a pool of $numberOfPlayers players")
viewPagerView = findViewById(R.id.viewPager)
viewPagerView.offscreenPageLimit = 1
viewPagerView.registerOnPageChangeCallback(
}
override fun onStart() {
super.onStart()
val adapter = ViewPagerMediaAdapter(mediaItemDatabase, numberOfPlayers, applicationContext)
viewPagerView.adapter = adapter
onPageChangeCallback =
object : ViewPager2.OnPageChangeCallback() {
override fun onPageSelected(position: Int) {
adapter.onPageSelected(position)
}
}
)
}
override fun onStart() {
super.onStart()
adapter = ViewPagerMediaAdapter(mediaItemDatabase, numberOfPlayers, this)
viewPagerView.adapter = adapter
viewPagerView.registerOnPageChangeCallback(onPageChangeCallback)
}
override fun onStop() {
adapter.onDestroy()
viewPagerView.unregisterOnPageChangeCallback(onPageChangeCallback)
viewPagerView.adapter = null
super.onStop()
}
}

View file

@ -16,8 +16,6 @@
package androidx.media3.demo.shortform.viewpager
import android.content.Context
import android.os.HandlerThread
import android.os.Process
import android.view.LayoutInflater
import android.view.ViewGroup
import androidx.annotation.OptIn
@ -29,14 +27,9 @@ import androidx.media3.demo.shortform.MediaItemDatabase
import androidx.media3.demo.shortform.PlayerPool
import androidx.media3.demo.shortform.R
import androidx.media3.exoplayer.DefaultLoadControl
import androidx.media3.exoplayer.DefaultRendererCapabilitiesList
import androidx.media3.exoplayer.DefaultRenderersFactory
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory
import androidx.media3.exoplayer.source.preload.DefaultPreloadManager
import androidx.media3.exoplayer.source.preload.DefaultPreloadManager.Status.STAGE_LOADED_TO_POSITION_MS
import androidx.media3.exoplayer.source.preload.DefaultPreloadManager.Status.STAGE_LOADED_FOR_DURATION_MS
import androidx.media3.exoplayer.source.preload.TargetPreloadStatusControl
import androidx.media3.exoplayer.trackselection.DefaultTrackSelector
import androidx.media3.exoplayer.upstream.DefaultBandwidthMeter
import androidx.recyclerview.widget.RecyclerView
import kotlin.math.abs
@ -46,13 +39,11 @@ class ViewPagerMediaAdapter(
numberOfPlayers: Int,
context: Context,
) : RecyclerView.Adapter<ViewPagerMediaHolder>() {
private val playbackThread: HandlerThread =
HandlerThread("playback-thread", Process.THREAD_PRIORITY_AUDIO)
private val preloadManager: DefaultPreloadManager
private val currentMediaItemsAndIndexes: ArrayDeque<Pair<MediaItem, Int>> = ArrayDeque()
private var playerPool: PlayerPool
private val holderMap: MutableMap<Int, ViewPagerMediaHolder>
private var currentPlayingIndex: Int = C.INDEX_UNSET
private val preloadControl: DefaultPreloadControl
companion object {
private const val TAG = "ViewPagerMediaAdapter"
@ -64,7 +55,6 @@ class ViewPagerMediaAdapter(
}
init {
playbackThread.start()
val loadControl =
DefaultLoadControl.Builder()
.setBufferDurationsMs(
@ -75,35 +65,26 @@ class ViewPagerMediaAdapter(
)
.setPrioritizeTimeOverSizeThresholds(true)
.build()
val renderersFactory = DefaultRenderersFactory(context)
playerPool =
PlayerPool(
numberOfPlayers,
context,
playbackThread.looper,
loadControl,
renderersFactory,
DefaultBandwidthMeter.getSingletonInstance(context),
)
preloadControl = DefaultPreloadControl()
val preloadManagerBuilder =
DefaultPreloadManager.Builder(context.applicationContext, preloadControl)
.setLoadControl(loadControl)
playerPool = PlayerPool(numberOfPlayers, preloadManagerBuilder)
holderMap = mutableMapOf()
val trackSelector = DefaultTrackSelector(context)
trackSelector.init({}, DefaultBandwidthMeter.getSingletonInstance(context))
preloadManager =
DefaultPreloadManager(
DefaultPreloadControl(),
DefaultMediaSourceFactory(context),
trackSelector,
DefaultBandwidthMeter.getSingletonInstance(context),
DefaultRendererCapabilitiesList.Factory(renderersFactory),
loadControl.allocator,
playbackThread.looper,
)
preloadManager = preloadManagerBuilder.build()
for (i in 0 until MANAGED_ITEM_COUNT) {
addMediaItem(index = i, isAddingToRightEnd = true)
}
preloadManager.invalidate()
}
override fun onDetachedFromRecyclerView(recyclerView: RecyclerView) {
playerPool.destroyPlayers()
preloadManager.release()
holderMap.clear()
super.onDetachedFromRecyclerView(recyclerView)
}
override fun onCreateViewHolder(parent: ViewGroup, viewType: Int): ViewPagerMediaHolder {
val view =
LayoutInflater.from(parent.context).inflate(R.layout.media_item_view_pager, parent, false)
@ -156,15 +137,9 @@ class ViewPagerMediaAdapter(
return Int.MAX_VALUE
}
fun onDestroy() {
preloadManager.release()
playerPool.destroyPlayers()
playbackThread.quit()
}
fun onPageSelected(position: Int) {
currentPlayingIndex = position
holderMap[position]?.playIfPossible()
preloadControl.currentPlayingIndex = position
preloadManager.setCurrentPlayingIndex(position)
preloadManager.invalidate()
}
@ -197,12 +172,14 @@ class ViewPagerMediaAdapter(
preloadManager.remove(itemAndIndex.first)
}
inner class DefaultPreloadControl : TargetPreloadStatusControl<Int> {
inner class DefaultPreloadControl(var currentPlayingIndex: Int = C.INDEX_UNSET) :
TargetPreloadStatusControl<Int> {
override fun getTargetPreloadStatus(rankingData: Int): DefaultPreloadManager.Status? {
if (abs(rankingData - currentPlayingIndex) == 2) {
return DefaultPreloadManager.Status(STAGE_LOADED_TO_POSITION_MS, 500L)
return DefaultPreloadManager.Status(STAGE_LOADED_FOR_DURATION_MS, 500L)
} else if (abs(rankingData - currentPlayingIndex) == 1) {
return DefaultPreloadManager.Status(STAGE_LOADED_TO_POSITION_MS, 1000L)
return DefaultPreloadManager.Status(STAGE_LOADED_FOR_DURATION_MS, 1000L)
}
return null
}

View file

@ -1 +0,0 @@
../../proguard-rules.txt

View file

@ -42,7 +42,7 @@
android:background="@color/purple_700"
android:gravity="center"
android:hint="@string/num_of_players"
android:inputType="numberDecimal"
android:inputType="number"
android:textColorHint="@color/grey" />
</LinearLayout>

View file

@ -13,7 +13,7 @@
See the License for the specific language governing permissions and
limitations under the License.
-->
<resources xmlns:tools="http://schemas.android.com/tools">
<resources>
<!-- Base application theme. -->
<style name="Theme.Media3Demo" parent="Theme.MaterialComponents.DayNight.DarkActionBar">
<!-- Primary brand color. -->
@ -25,9 +25,7 @@
<item name="colorSecondaryVariant">@color/teal_700</item>
<item name="colorOnSecondary">@color/black</item>
<!-- Status bar color. -->
<item name="android:statusBarColor" tools:targetApi="l">
?attr/colorPrimaryVariant
</item>
<item name="android:statusBarColor">?attr/colorPrimaryVariant</item>
<!-- Customize your theme here. -->
</style>
</resources>

View file

@ -62,8 +62,8 @@ public final class MainActivity extends Activity {
private boolean isOwner;
@Nullable private LegacyPlayerControlView playerControlView;
@Nullable private SurfaceView fullScreenView;
@Nullable private SurfaceView nonFullScreenView;
@Nullable private SurfaceView fullscreenView;
@Nullable private SurfaceView nonFullscreenView;
@Nullable private SurfaceView currentOutputView;
@Nullable private static ExoPlayer player;
@ -75,13 +75,13 @@ public final class MainActivity extends Activity {
super.onCreate(savedInstanceState);
setContentView(R.layout.main_activity);
playerControlView = findViewById(R.id.player_control_view);
fullScreenView = findViewById(R.id.full_screen_view);
fullScreenView.setOnClickListener(
fullscreenView = findViewById(R.id.full_screen_view);
fullscreenView.setOnClickListener(
v -> {
setCurrentOutputView(nonFullScreenView);
Assertions.checkNotNull(fullScreenView).setVisibility(View.GONE);
setCurrentOutputView(nonFullscreenView);
Assertions.checkNotNull(fullscreenView).setVisibility(View.GONE);
});
attachSurfaceListener(fullScreenView);
attachSurfaceListener(fullscreenView);
isOwner = getIntent().getBooleanExtra(OWNER_EXTRA, /* defaultValue= */ true);
GridLayout gridLayout = findViewById(R.id.grid_layout);
for (int i = 0; i < 9; i++) {
@ -97,8 +97,8 @@ public final class MainActivity extends Activity {
button.setText(getString(R.string.full_screen_label));
button.setOnClickListener(
v -> {
setCurrentOutputView(fullScreenView);
Assertions.checkNotNull(fullScreenView).setVisibility(View.VISIBLE);
setCurrentOutputView(fullscreenView);
Assertions.checkNotNull(fullscreenView).setVisibility(View.VISIBLE);
});
} else if (i == 2) {
Button button = new Button(/* context= */ this);
@ -116,10 +116,10 @@ public final class MainActivity extends Activity {
surfaceView.setOnClickListener(
v -> {
setCurrentOutputView(surfaceView);
nonFullScreenView = surfaceView;
nonFullscreenView = surfaceView;
});
if (nonFullScreenView == null) {
nonFullScreenView = surfaceView;
if (nonFullscreenView == null) {
nonFullscreenView = surfaceView;
}
}
gridLayout.addView(view);
@ -144,7 +144,7 @@ public final class MainActivity extends Activity {
initializePlayer();
}
setCurrentOutputView(nonFullScreenView);
setCurrentOutputView(nonFullscreenView);
LegacyPlayerControlView playerControlView = Assertions.checkNotNull(this.playerControlView);
playerControlView.setPlayer(player);

View file

@ -12,8 +12,8 @@ Building the demo app with [MediaPipe][] integration enabled requires some extra
manual steps.
1. Follow the
[instructions](https://google.github.io/mediapipe/getting_started/install.html)
to install MediaPipe.
[instructions](https://ai.google.dev/edge/mediapipe/solutions/guide#get_started)
to get started with MediaPipe.
1. Copy the Transformer demo's build configuration and MediaPipe graph text
protocol buffer under the MediaPipe source tree. This makes it easy to
[build an AAR][] with bazel by reusing MediaPipe's workspace.
@ -62,5 +62,5 @@ manual steps.
app and select a MediaPipe-based effect.
[Transformer]: https://developer.android.com/media/media3/transformer
[MediaPipe]: https://google.github.io/mediapipe/
[build an AAR]: https://google.github.io/mediapipe/getting_started/android_archive_library.html
[MediaPipe]: https://ai.google.dev/edge/mediapipe/solutions/guide
[build an AAR]: https://ai.google.dev/edge/mediapipe/framework/getting_started/android_archive_library

View file

@ -29,9 +29,8 @@ android {
defaultConfig {
versionName project.ext.releaseVersion
versionCode project.ext.releaseVersionCode
minSdkVersion 21
minSdkVersion project.ext.minSdkVersion
targetSdkVersion project.ext.appTargetSdkVersion
multiDexEnabled true
}
buildTypes {
@ -77,7 +76,6 @@ dependencies {
implementation 'androidx.appcompat:appcompat:' + androidxAppCompatVersion
implementation 'androidx.constraintlayout:constraintlayout:' + androidxConstraintLayoutVersion
implementation 'androidx.recyclerview:recyclerview:' + androidxRecyclerViewVersion
implementation 'androidx.multidex:multidex:' + androidxMultidexVersion
implementation 'com.google.android.material:material:' + androidxMaterialVersion
implementation project(modulePrefix + 'lib-effect')
implementation project(modulePrefix + 'lib-exoplayer')

View file

@ -257,13 +257,12 @@ public final class ConfigurationActivity extends AppCompatActivity {
videoMimeSpinner = findViewById(R.id.video_mime_spinner);
videoMimeSpinner.setAdapter(videoMimeAdapter);
videoMimeAdapter.addAll(
SAME_AS_INPUT_OPTION, MimeTypes.VIDEO_H263, MimeTypes.VIDEO_H264, MimeTypes.VIDEO_MP4V);
if (SDK_INT >= 24) {
videoMimeAdapter.add(MimeTypes.VIDEO_H265);
}
if (SDK_INT >= 34) {
videoMimeAdapter.add(MimeTypes.VIDEO_AV1);
}
SAME_AS_INPUT_OPTION,
MimeTypes.VIDEO_H263,
MimeTypes.VIDEO_H264,
MimeTypes.VIDEO_H265,
MimeTypes.VIDEO_MP4V,
MimeTypes.VIDEO_AV1);
ArrayAdapter<String> resolutionHeightAdapter =
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);
@ -302,6 +301,18 @@ public final class ConfigurationActivity extends AppCompatActivity {
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(
(buttonView, isChecked) -> {
if (isChecked) {
useMedia3Muxer.setChecked(true);
}
});
ArrayAdapter<String> hdrModeAdapter =
new ArrayAdapter<>(/* context= */ this, R.layout.spinner_item);

View file

@ -20,6 +20,8 @@ import static android.Manifest.permission.READ_MEDIA_VIDEO;
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;
@ -78,13 +80,12 @@ import androidx.media3.effect.ScaleAndRotateTransformation;
import androidx.media3.effect.SingleColorLut;
import androidx.media3.effect.TextOverlay;
import androidx.media3.effect.TextureOverlay;
import androidx.media3.exoplayer.DefaultLoadControl;
import androidx.media3.exoplayer.ExoPlayer;
import androidx.media3.exoplayer.audio.SilenceSkippingAudioProcessor;
import androidx.media3.exoplayer.util.DebugTextViewHelper;
import androidx.media3.muxer.Muxer;
import androidx.media3.transformer.Composition;
import androidx.media3.transformer.DefaultEncoderFactory;
import androidx.media3.transformer.DefaultMuxer;
import androidx.media3.transformer.EditedMediaItem;
import androidx.media3.transformer.EditedMediaItemSequence;
import androidx.media3.transformer.Effects;
@ -118,6 +119,10 @@ import org.json.JSONObject;
/** An {@link Activity} that exports and plays media using {@link Transformer}. */
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 int LOAD_CONTROL_MIN_BUFFER_MS = 5_000;
private static int LOAD_CONTROL_MAX_BUFFER_MS = 5_000;
private Button displayInputButton;
private MaterialCardView inputCardView;
@ -130,7 +135,7 @@ public final class TransformerActivity extends AppCompatActivity {
private TextView informationTextView;
private ViewGroup progressViewGroup;
private LinearProgressIndicator progressIndicator;
private Button cancelButton;
private Button pauseButton;
private Button resumeButton;
private Stopwatch exportStopwatch;
private AspectRatioFrameLayout debugFrame;
@ -157,8 +162,8 @@ public final class TransformerActivity extends AppCompatActivity {
informationTextView = findViewById(R.id.information_text_view);
progressViewGroup = findViewById(R.id.progress_view_group);
progressIndicator = findViewById(R.id.progress_indicator);
cancelButton = findViewById(R.id.cancel_button);
cancelButton.setOnClickListener(view -> cancelExport());
pauseButton = findViewById(R.id.pause_button);
pauseButton.setOnClickListener(view -> pauseExport());
resumeButton = findViewById(R.id.resume_button);
resumeButton.setOnClickListener(view -> startExport());
debugFrame = findViewById(R.id.debug_aspect_ratio_frame_layout);
@ -241,7 +246,7 @@ public final class TransformerActivity extends AppCompatActivity {
debugTextView.setVisibility(View.GONE);
informationTextView.setText(R.string.export_started);
progressViewGroup.setVisibility(View.VISIBLE);
cancelButton.setVisibility(View.VISIBLE);
pauseButton.setVisibility(View.VISIBLE);
resumeButton.setVisibility(View.GONE);
progressIndicator.setProgress(0);
Handler mainHandler = new Handler(getMainLooper());
@ -262,7 +267,8 @@ public final class TransformerActivity extends AppCompatActivity {
}
private MediaItem createMediaItem(@Nullable Bundle bundle, Uri uri) {
MediaItem.Builder mediaItemBuilder = new MediaItem.Builder().setUri(uri);
MediaItem.Builder mediaItemBuilder =
new MediaItem.Builder().setUri(uri).setImageDurationMs(IMAGE_DURATION_MS);
if (bundle != null) {
long trimStartMs =
bundle.getLong(ConfigurationActivity.TRIM_START_MS, /* defaultValue= */ C.TIME_UNSET);
@ -317,14 +323,13 @@ public final class TransformerActivity extends AppCompatActivity {
transformerBuilder.setMaxDelayBetweenMuxerSamplesMs(C.TIME_UNSET);
}
Muxer.Factory muxerFactory = new DefaultMuxer.Factory();
if (bundle.getBoolean(ConfigurationActivity.USE_MEDIA3_MUXER)) {
muxerFactory = new InAppMuxer.Factory.Builder().build();
transformerBuilder.setMuxerFactory(
new InAppMuxer.Factory.Builder()
.setOutputFragmentedMp4(
bundle.getBoolean(ConfigurationActivity.PRODUCE_FRAGMENTED_MP4))
.build());
}
if (bundle.getBoolean(ConfigurationActivity.PRODUCE_FRAGMENTED_MP4)) {
muxerFactory = new InAppMuxer.Factory.Builder().setOutputFragmentedMp4(true).build();
}
transformerBuilder.setMuxerFactory(muxerFactory);
if (bundle.getBoolean(ConfigurationActivity.ENABLE_DEBUG_PREVIEW)) {
transformerBuilder.setDebugViewProvider(new DemoDebugViewProvider());
@ -354,7 +359,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.setDurationUs(5_000_000).setFrameRate(30);
editedMediaItemBuilder.setFrameRate(IMAGE_FRAME_RATE_FPS);
if (bundle != null) {
ImmutableList<AudioProcessor> audioProcessors = createAudioProcessorsFromBundle(bundle);
ImmutableList<Effect> videoEffects = createVideoEffectsFromBundle(bundle);
@ -366,7 +371,8 @@ public final class TransformerActivity extends AppCompatActivity {
.setEffects(new Effects(audioProcessors, videoEffects));
}
Composition.Builder compositionBuilder =
new Composition.Builder(new EditedMediaItemSequence(editedMediaItemBuilder.build()));
new Composition.Builder(
new EditedMediaItemSequence.Builder(editedMediaItemBuilder.build()).build());
if (bundle != null) {
compositionBuilder
.setHdrMode(bundle.getInt(ConfigurationActivity.HDR_MODE))
@ -698,7 +704,17 @@ public final class TransformerActivity extends AppCompatActivity {
releasePlayer();
Uri uri = checkNotNull(inputMediaItem.localConfiguration).uri;
ExoPlayer outputPlayer = new ExoPlayer.Builder(/* context= */ this).build();
ExoPlayer outputPlayer =
new ExoPlayer.Builder(/* context= */ this)
.setLoadControl(
new DefaultLoadControl.Builder()
.setBufferDurationsMs(
LOAD_CONTROL_MIN_BUFFER_MS,
LOAD_CONTROL_MAX_BUFFER_MS,
DEFAULT_BUFFER_FOR_PLAYBACK_MS,
DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS)
.build())
.build();
outputPlayerView.setPlayer(outputPlayer);
outputPlayerView.setControllerAutoShow(false);
outputPlayer.setMediaItem(outputMediaItem);
@ -724,7 +740,17 @@ public final class TransformerActivity extends AppCompatActivity {
inputImageView.setVisibility(View.GONE);
inputTextView.setText(getString(R.string.input_video_no_sound));
ExoPlayer inputPlayer = new ExoPlayer.Builder(/* context= */ this).build();
ExoPlayer inputPlayer =
new ExoPlayer.Builder(/* context= */ this)
.setLoadControl(
new DefaultLoadControl.Builder()
.setBufferDurationsMs(
LOAD_CONTROL_MIN_BUFFER_MS,
LOAD_CONTROL_MAX_BUFFER_MS,
DEFAULT_BUFFER_FOR_PLAYBACK_MS,
DEFAULT_BUFFER_FOR_PLAYBACK_AFTER_REBUFFER_MS)
.build())
.build();
inputPlayerView.setPlayer(inputPlayer);
inputPlayerView.setControllerAutoShow(false);
inputPlayerView.setOnClickListener(this::handlePlayerViewClick);
@ -799,11 +825,11 @@ public final class TransformerActivity extends AppCompatActivity {
}
}
private void cancelExport() {
private void pauseExport() {
transformer.cancel();
transformer = null;
exportStopwatch.stop();
cancelButton.setVisibility(View.GONE);
pauseButton.setVisibility(View.GONE);
resumeButton.setVisibility(View.VISIBLE);
if (oldOutputFile != null) {
oldOutputFile.delete();

View file

@ -49,7 +49,6 @@
android:text="@string/hide_input_video"
android:layout_margin="8dp" />
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
@ -76,28 +75,23 @@
android:padding="8dp"
android:text="@string/input_video_no_sound" />
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" >
<ImageView
android:id="@+id/input_image_view"
<FrameLayout
android:layout_width="match_parent"
android:layout_height="wrap_content" />
android:layout_height="wrap_content" >
<androidx.media3.ui.PlayerView
android:id="@+id/input_player_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
<androidx.media3.ui.AspectRatioFrameLayout
android:id="@+id/input_debug_aspect_ratio_frame_layout"
<ImageView
android:id="@+id/input_image_view"
android:layout_width="match_parent"
android:layout_height="match_parent" />
android:layout_height="wrap_content" />
<androidx.media3.ui.PlayerView
android:id="@+id/input_player_view"
android:layout_width="match_parent"
android:layout_height="wrap_content" />
</FrameLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>
<com.google.android.material.card.MaterialCardView
@ -160,10 +154,10 @@
android:text="@string/debug_preview" />
<Button
android:id="@+id/cancel_button"
android:id="@+id/pause_button"
android:layout_height="wrap_content"
android:layout_width="match_parent"
android:text="@string/cancel"/>
android:text="@string/pause"/>
<Button
android:id="@+id/resume_button"
@ -187,7 +181,6 @@
</FrameLayout>
</LinearLayout>
</com.google.android.material.card.MaterialCardView>

View file

@ -51,7 +51,7 @@
<item>Tokyo JPG image (portrait, plays for 5 secs at 30 fps)</item>
<item>SEF slow motion with 240 fps</item>
<item>480p DASH (non-square pixels)</item>
<item>HDR (HDR10) H265 limited range video (encoding may fail)</item>
<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>
</string-array>

View file

@ -42,7 +42,7 @@
<string name="no_media_pipe_error" translatable="false">Failed to load MediaPipeShaderProgram. Check the README for instructions.</string>
<string name="export" translatable="false">Export</string>
<string name="debug_preview" translatable="false">Debug preview:</string>
<string name="cancel" translatable="false">Cancel</string>
<string name="pause" translatable="false">Pause</string>
<string name="resume" translatable="false">Resume</string>
<string name="debug_preview_not_available" translatable="false">No debug preview available.</string>
<string name="export_started" translatable="false">Export started</string>

View file

@ -17,9 +17,17 @@ 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;
import static java.lang.Math.min;
import android.content.Context;
import android.media.MediaRouter2;
import android.media.MediaRouter2.RouteCallback;
import android.media.MediaRouter2.RoutingController;
import android.media.MediaRouter2.TransferCallback;
import android.media.RouteDiscoveryPreference;
import android.os.Handler;
import android.os.Looper;
import android.view.Surface;
import android.view.SurfaceHolder;
@ -27,6 +35,7 @@ import android.view.SurfaceView;
import android.view.TextureView;
import androidx.annotation.IntRange;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
import androidx.media3.common.AudioAttributes;
import androidx.media3.common.BasePlayer;
@ -83,8 +92,11 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@UnstableApi
public final class CastPlayer extends BasePlayer {
/** The {@link DeviceInfo} returned by {@link #getDeviceInfo() this player}. */
public static final DeviceInfo DEVICE_INFO =
/**
* A {@link DeviceInfo#PLAYBACK_TYPE_REMOTE remote} {@link DeviceInfo} with a null {@link
* DeviceInfo#routingControllerId}.
*/
public static final DeviceInfo DEVICE_INFO_REMOTE_EMPTY =
new DeviceInfo.Builder(DeviceInfo.PLAYBACK_TYPE_REMOTE).build();
static {
@ -128,6 +140,7 @@ public final class CastPlayer extends BasePlayer {
// TODO: Allow custom implementations of CastTimelineTracker.
private final CastTimelineTracker timelineTracker;
private final Timeline.Period period;
@Nullable private final Api30Impl api30Impl;
// Result callbacks.
private final StatusListener statusListener;
@ -153,6 +166,7 @@ public final class CastPlayer extends BasePlayer {
private long pendingSeekPositionMs;
@Nullable private PositionInfo pendingMediaItemRemovalPosition;
private MediaMetadata mediaMetadata;
private DeviceInfo deviceInfo;
/**
* Creates a new cast player.
@ -202,6 +216,7 @@ public final class CastPlayer extends BasePlayer {
@IntRange(from = 1) long seekBackIncrementMs,
@IntRange(from = 1) long seekForwardIncrementMs) {
this(
/* context= */ null,
castContext,
mediaItemConverter,
seekBackIncrementMs,
@ -212,6 +227,8 @@ public final class CastPlayer extends BasePlayer {
/**
* Creates a new cast player.
*
* @param context A {@link Context} used to populate {@link #getDeviceInfo()}. If null, {@link
* #getDeviceInfo()} will always return {@link #DEVICE_INFO_REMOTE_EMPTY}.
* @param castContext The context from which the cast session is obtained.
* @param mediaItemConverter The {@link MediaItemConverter} to use.
* @param seekBackIncrementMs The {@link #seekBack()} increment, in milliseconds.
@ -223,6 +240,7 @@ public final class CastPlayer extends BasePlayer {
* negative.
*/
public CastPlayer(
@Nullable Context context,
CastContext castContext,
MediaItemConverter mediaItemConverter,
@IntRange(from = 1) long seekBackIncrementMs,
@ -260,6 +278,14 @@ public final class CastPlayer extends BasePlayer {
CastSession session = sessionManager.getCurrentCastSession();
setRemoteMediaClient(session != null ? session.getRemoteMediaClient() : null);
updateInternalStateAndNotifyIfChanged();
if (SDK_INT >= 30 && context != null) {
api30Impl = new Api30Impl(context);
api30Impl.initialize();
deviceInfo = api30Impl.fetchDeviceInfo();
} else {
api30Impl = null;
deviceInfo = DEVICE_INFO_REMOTE_EMPTY;
}
}
/**
@ -530,6 +556,10 @@ public final class CastPlayer extends BasePlayer {
@Override
public void release() {
// The SDK_INT check is not necessary, but it prevents a lint error for the release call.
if (SDK_INT >= 30 && api30Impl != null) {
api30Impl.release();
}
SessionManager sessionManager = castContext.getSessionManager();
sessionManager.removeSessionManagerListener(statusListener, CastSession.class);
sessionManager.endCurrentSession(false);
@ -782,10 +812,14 @@ public final class CastPlayer extends BasePlayer {
return CueGroup.EMPTY_TIME_ZERO;
}
/** This method always returns {@link CastPlayer#DEVICE_INFO}. */
/**
* Returns a {@link DeviceInfo} describing the receiver device. Returns {@link
* #DEVICE_INFO_REMOTE_EMPTY} if no {@link Context} was provided at construction, or if the Cast
* {@link RoutingController} could not be identified.
*/
@Override
public DeviceInfo getDeviceInfo() {
return DEVICE_INFO;
return deviceInfo;
}
/** This method is not supported and always returns {@code 0}. */
@ -1283,11 +1317,8 @@ public final class CastPlayer extends BasePlayer {
remoteMediaClient.registerCallback(statusListener);
remoteMediaClient.addProgressListener(statusListener, PROGRESS_REPORT_PERIOD_MS);
updateInternalStateAndNotifyIfChanged();
} else {
updateTimelineAndNotifyIfChanged();
if (sessionAvailabilityListener != null) {
sessionAvailabilityListener.onCastSessionUnavailable();
}
} else if (sessionAvailabilityListener != null) {
sessionAvailabilityListener.onCastSessionUnavailable();
}
}
@ -1537,4 +1568,105 @@ public final class CastPlayer extends BasePlayer {
return pendingResultCallback == resultCallback;
}
}
@RequiresApi(30)
private final class Api30Impl {
private final MediaRouter2 mediaRouter2;
private final TransferCallback transferCallback;
private final RouteCallback emptyRouteCallback;
private final Handler handler;
public Api30Impl(Context context) {
mediaRouter2 = MediaRouter2.getInstance(context);
transferCallback = new MediaRouter2TransferCallbackImpl();
emptyRouteCallback = new MediaRouter2RouteCallbackImpl();
handler = new Handler(Looper.getMainLooper());
}
/** Acquires necessary resources and registers callbacks. */
public void initialize() {
mediaRouter2.registerTransferCallback(handler::post, transferCallback);
// We need at least one route callback registered in order to get transfer callback updates.
mediaRouter2.registerRouteCallback(
handler::post,
emptyRouteCallback,
new RouteDiscoveryPreference.Builder(ImmutableList.of(), /* activeScan= */ false)
.build());
}
/**
* Releases any resources acquired in {@link #initialize()} and unregisters any registered
* callbacks.
*/
public void release() {
mediaRouter2.unregisterTransferCallback(transferCallback);
mediaRouter2.unregisterRouteCallback(emptyRouteCallback);
handler.removeCallbacksAndMessages(/* token= */ null);
}
/** Updates the device info with an up-to-date value and notifies the listeners. */
private void updateDeviceInfo() {
DeviceInfo oldDeviceInfo = deviceInfo;
DeviceInfo newDeviceInfo = fetchDeviceInfo();
deviceInfo = newDeviceInfo;
if (!deviceInfo.equals(oldDeviceInfo)) {
listeners.sendEvent(
EVENT_DEVICE_INFO_CHANGED, listener -> listener.onDeviceInfoChanged(newDeviceInfo));
}
}
/**
* Returns a {@link DeviceInfo} with the {@link RoutingController#getId() id} that corresponds
* to the Cast session, or {@link #DEVICE_INFO_REMOTE_EMPTY} if not available.
*/
public DeviceInfo fetchDeviceInfo() {
// TODO: b/364833997 - Fetch this information from the AndroidX MediaRouter selected route
// once the selected route id matches the controller id.
List<RoutingController> controllers = mediaRouter2.getControllers();
// The controller at position zero is always the system controller (local playback). All other
// controllers are for remote playback, and could be the Cast one.
if (controllers.size() != 2) {
// There's either no remote routing controller, or there's more than one. In either case we
// don't populate the device info because either there's no Cast routing controller, or we
// cannot safely identify the Cast routing controller.
return DEVICE_INFO_REMOTE_EMPTY;
} else {
// There's only one remote routing controller. It's safe to assume it's the Cast routing
// controller.
RoutingController remoteController = controllers.get(1);
// TODO b/364580007 - Populate volume information, and implement Player volume-related
// methods.
return new DeviceInfo.Builder(DeviceInfo.PLAYBACK_TYPE_REMOTE)
.setRoutingControllerId(remoteController.getId())
.build();
}
}
/**
* Empty {@link RouteCallback} implementation necessary for registering the {@link MediaRouter2}
* instance with the system_server.
*
* <p>This callback must be registered so that the media router service notifies the {@link
* MediaRouter2TransferCallbackImpl} of transfer events.
*/
private final class MediaRouter2RouteCallbackImpl extends RouteCallback {}
/**
* {@link TransferCallback} implementation to listen for {@link RoutingController} creation and
* releases.
*/
private final class MediaRouter2TransferCallbackImpl extends TransferCallback {
@Override
public void onTransfer(RoutingController oldController, RoutingController newController) {
updateDeviceInfo();
}
@Override
public void onStop(RoutingController controller) {
updateDeviceInfo();
}
}
}
}

View file

@ -1902,7 +1902,7 @@ public class CastPlayerTest {
public void getDeviceInfo_returnsCorrectDeviceInfoWithPlaybackTypeRemote() {
DeviceInfo deviceInfo = castPlayer.getDeviceInfo();
assertThat(deviceInfo).isEqualTo(CastPlayer.DEVICE_INFO);
assertThat(deviceInfo).isEqualTo(CastPlayer.DEVICE_INFO_REMOTE_EMPTY);
assertThat(deviceInfo.playbackType).isEqualTo(DeviceInfo.PLAYBACK_TYPE_REMOTE);
}

View file

@ -65,6 +65,11 @@ dependencies {
}
api 'androidx.annotation:annotation-experimental:' + androidxAnnotationExperimentalVersion
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
// Workaround for 'duplicate class' error caused by incomplete version
// metadata in Kotlin std lib (https://issuetracker.google.com/278545487).
// This can be removed when one of the other deps here (probably
// androidx.annotation) depends on kotlin-stdlib:1.9.20.
implementation platform('org.jetbrains.kotlin:kotlin-bom:1.8.0')
compileOnly 'com.google.code.findbugs:jsr305:' + jsr305Version
compileOnly 'com.google.errorprone:error_prone_annotations:' + errorProneVersion
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion

View file

@ -16,7 +16,6 @@
package androidx.media3.common;
import android.os.Bundle;
import androidx.annotation.DoNotInline;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.media3.common.util.UnstableApi;
@ -37,7 +36,6 @@ import com.google.errorprone.annotations.CanIgnoreReturnValue;
public final class AudioAttributes {
/** A direct wrapper around {@link android.media.AudioAttributes}. */
@RequiresApi(21)
public static final class AudioAttributesV21 {
public final android.media.AudioAttributes audioAttributes;
@ -165,7 +163,6 @@ public final class AudioAttributes {
* <p>Some fields are ignored if the corresponding {@link android.media.AudioAttributes.Builder}
* setter is not available on the current API level.
*/
@RequiresApi(21)
public AudioAttributesV21 getAudioAttributesV21() {
if (audioAttributesV21 == null) {
audioAttributesV21 = new AudioAttributesV21(this);
@ -242,7 +239,6 @@ public final class AudioAttributes {
@RequiresApi(29)
private static final class Api29 {
@DoNotInline
public static void setAllowedCapturePolicy(
android.media.AudioAttributes.Builder builder,
@C.AudioAllowedCapturePolicy int allowedCapturePolicy) {
@ -252,7 +248,6 @@ public final class AudioAttributes {
@RequiresApi(32)
private static final class Api32 {
@DoNotInline
public static void setSpatializationBehavior(
android.media.AudioAttributes.Builder builder,
@C.SpatializationBehavior int spatializationBehavior) {

View file

@ -147,38 +147,11 @@ public abstract class BasePlayer implements Player {
seekToOffset(getSeekForwardIncrement(), Player.COMMAND_SEEK_FORWARD);
}
/**
* @deprecated Use {@link #hasPreviousMediaItem()} instead.
*/
@Deprecated
@Override
public final boolean hasPrevious() {
return hasPreviousMediaItem();
}
/**
* @deprecated Use {@link #hasPreviousMediaItem()} instead.
*/
@Deprecated
@Override
public final boolean hasPreviousWindow() {
return hasPreviousMediaItem();
}
@Override
public final boolean hasPreviousMediaItem() {
return getPreviousMediaItemIndex() != C.INDEX_UNSET;
}
/**
* @deprecated Use {@link #seekToPreviousMediaItem()} instead.
*/
@Deprecated
@Override
public final void previous() {
seekToPreviousMediaItem();
}
/**
* @deprecated Use {@link #seekToPreviousMediaItem()} instead.
*/

View file

@ -32,7 +32,6 @@ import android.media.MediaFormat;
import android.net.Uri;
import android.view.Surface;
import androidx.annotation.IntDef;
import androidx.annotation.RequiresApi;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import com.google.errorprone.annotations.InlineMe;
@ -620,6 +619,7 @@ public final class C {
* <ul>
* <li>{@link #BUFFER_FLAG_KEY_FRAME}
* <li>{@link #BUFFER_FLAG_END_OF_STREAM}
* <li>{@link #BUFFER_FLAG_NOT_DEPENDED_ON}
* <li>{@link #BUFFER_FLAG_FIRST_SAMPLE}
* <li>{@link #BUFFER_FLAG_LAST_SAMPLE}
* <li>{@link #BUFFER_FLAG_ENCRYPTED}
@ -634,6 +634,7 @@ public final class C {
value = {
BUFFER_FLAG_KEY_FRAME,
BUFFER_FLAG_END_OF_STREAM,
BUFFER_FLAG_NOT_DEPENDED_ON,
BUFFER_FLAG_FIRST_SAMPLE,
BUFFER_FLAG_HAS_SUPPLEMENTAL_DATA,
BUFFER_FLAG_LAST_SAMPLE,
@ -648,6 +649,9 @@ public final class C {
@UnstableApi
public static final int BUFFER_FLAG_END_OF_STREAM = MediaCodec.BUFFER_FLAG_END_OF_STREAM;
/** Indicates that no other buffers depend on the data in this buffer. */
@UnstableApi public static final int BUFFER_FLAG_NOT_DEPENDED_ON = 1 << 26; // 0x04000000
/** Indicates that a buffer is known to contain the first media sample of the stream. */
@UnstableApi public static final int BUFFER_FLAG_FIRST_SAMPLE = 1 << 27; // 0x08000000
@ -1092,7 +1096,8 @@ public final class C {
/**
* The stereo mode for 360/3D/VR videos. One of {@link Format#NO_VALUE}, {@link
* #STEREO_MODE_MONO}, {@link #STEREO_MODE_TOP_BOTTOM}, {@link #STEREO_MODE_LEFT_RIGHT} or {@link
* #STEREO_MODE_STEREO_MESH}.
* #STEREO_MODE_STEREO_MESH}, {@link #STEREO_MODE_INTERLEAVED_LEFT_PRIMARY}, {@link
* #STEREO_MODE_INTERLEAVED_RIGHT_PRIMARY}.
*/
@UnstableApi
@Documented
@ -1103,7 +1108,9 @@ public final class C {
STEREO_MODE_MONO,
STEREO_MODE_TOP_BOTTOM,
STEREO_MODE_LEFT_RIGHT,
STEREO_MODE_STEREO_MESH
STEREO_MODE_STEREO_MESH,
STEREO_MODE_INTERLEAVED_LEFT_PRIMARY,
STEREO_MODE_INTERLEAVED_RIGHT_PRIMARY
})
public @interface StereoMode {}
@ -1122,6 +1129,18 @@ public final class C {
*/
@UnstableApi public static final int STEREO_MODE_STEREO_MESH = 3;
/**
* Indicates interleaved stereo layout with the left view being the primary view, used with
* 360/3D/VR videos.
*/
@UnstableApi public static final int STEREO_MODE_INTERLEAVED_LEFT_PRIMARY = 4;
/**
* Indicates interleaved stereo layout with the right view being the primary view, used with
* 360/3D/VR videos.
*/
@UnstableApi public static final int STEREO_MODE_INTERLEAVED_RIGHT_PRIMARY = 5;
// LINT.IfChange(color_space)
/**
* Video color spaces, also referred to as color standards. One of {@link Format#NO_VALUE}, {@link
@ -1428,7 +1447,8 @@ public final class C {
ROLE_FLAG_ENHANCED_DIALOG_INTELLIGIBILITY,
ROLE_FLAG_TRANSCRIBES_DIALOG,
ROLE_FLAG_EASY_TO_READ,
ROLE_FLAG_TRICK_PLAY
ROLE_FLAG_TRICK_PLAY,
ROLE_FLAG_AUXILIARY
})
public @interface RoleFlags {}
@ -1493,6 +1513,58 @@ public final class C {
/** Indicates the track is intended for trick play. */
public static final int ROLE_FLAG_TRICK_PLAY = 1 << 14;
/**
* Indicates an auxiliary track. An auxiliary track provides additional information about other
* tracks and is generally not meant for stand-alone playback, but rather for further processing
* in conjunction with other tracks (for example, a track with depth information).
*/
public static final int ROLE_FLAG_AUXILIARY = 1 << 15;
/**
* {@linkplain #ROLE_FLAG_AUXILIARY Auxiliary track types}. One of {@link
* #AUXILIARY_TRACK_TYPE_UNDEFINED}, {@link #AUXILIARY_TRACK_TYPE_ORIGINAL}, {@link
* #AUXILIARY_TRACK_TYPE_DEPTH_LINEAR}, {@link #AUXILIARY_TRACK_TYPE_DEPTH_INVERSE}, {@link
* #AUXILIARY_TRACK_TYPE_DEPTH_METADATA}.
*/
@UnstableApi
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target({FIELD, METHOD, PARAMETER, LOCAL_VARIABLE, TYPE_USE})
@IntDef({
AUXILIARY_TRACK_TYPE_UNDEFINED,
AUXILIARY_TRACK_TYPE_ORIGINAL,
AUXILIARY_TRACK_TYPE_DEPTH_LINEAR,
AUXILIARY_TRACK_TYPE_DEPTH_INVERSE,
AUXILIARY_TRACK_TYPE_DEPTH_METADATA
})
public @interface AuxiliaryTrackType {}
// LINT.IfChange(auxiliary_track_type)
/** Not an auxiliary track or an auxiliary track with an undefined type. */
@UnstableApi public static final int AUXILIARY_TRACK_TYPE_UNDEFINED = 0;
/** The original video track without any depth based effects applied. */
@UnstableApi public static final int AUXILIARY_TRACK_TYPE_ORIGINAL = 1;
/**
* A linear encoded depth video track.
*
* <p>See https://developer.android.com/static/media/camera/camera2/Dynamic-depth-v1.0.pdf for
* linear depth encoding.
*/
@UnstableApi public static final int AUXILIARY_TRACK_TYPE_DEPTH_LINEAR = 2;
/**
* An inverse encoded depth video track.
*
* <p>See https://developer.android.com/static/media/camera/camera2/Dynamic-depth-v1.0.pdf for
* inverse depth encoding.
*/
@UnstableApi public static final int AUXILIARY_TRACK_TYPE_DEPTH_INVERSE = 3;
/** A timed metadata of depth video track. */
@UnstableApi public static final int AUXILIARY_TRACK_TYPE_DEPTH_METADATA = 4;
/**
* Level of support for a format. One of {@link #FORMAT_HANDLED}, {@link
* #FORMAT_EXCEEDS_CAPABILITIES}, {@link #FORMAT_UNSUPPORTED_DRM}, {@link
@ -1626,7 +1698,6 @@ public final class C {
replacement = "Util.generateAudioSessionIdV21(context)",
imports = {"androidx.media3.common.util.Util"})
@Deprecated
@RequiresApi(21)
public static int generateAudioSessionIdV21(Context context) {
return Util.generateAudioSessionIdV21(context);
}

View file

@ -203,7 +203,7 @@ public final class ColorInfo {
/**
* Returns the {@link C.ColorSpace} corresponding to the given ISO color primary code, as per
* table A.7.21.1 in Rec. ITU-T T.832 (03/2009), or {@link Format#NO_VALUE} if no mapping can be
* table A.7.21.1 in Rec. ITU-T T.832 (06/2019), or {@link Format#NO_VALUE} if no mapping can be
* made.
*/
@Pure
@ -219,13 +219,52 @@ public final class ColorInfo {
case 9:
return C.COLOR_SPACE_BT2020;
default:
// Remaining color primaries are either reserved or unspecified.
return Format.NO_VALUE;
}
}
/**
* Returns the ISO color primary code corresponding to the given {@link C.ColorSpace}, as per
* table A.7.21.1 in Rec. ITU-T T.832 (06/2019). made.
*/
public static int colorSpaceToIsoColorPrimaries(@C.ColorSpace int colorSpace) {
switch (colorSpace) {
// Default to BT.709 SDR as per the <a
// href="https://www.webmproject.org/vp9/mp4/#optional-fields">recommendation</a>.
case Format.NO_VALUE:
case C.COLOR_SPACE_BT709:
return 1;
case C.COLOR_SPACE_BT601:
return 5;
case C.COLOR_SPACE_BT2020:
return 9;
}
return 1;
}
/**
* Returns the ISO matrix coefficients code corresponding to the given {@link C.ColorSpace}, as
* per table A.7.21.3 in Rec. ITU-T T.832 (06/2019).
*/
public static int colorSpaceToIsoMatrixCoefficients(@C.ColorSpace int colorSpace) {
switch (colorSpace) {
// Default to BT.709 SDR as per the <a
// href="https://www.webmproject.org/vp9/mp4/#optional-fields">recommendation</a>.
case Format.NO_VALUE:
case C.COLOR_SPACE_BT709:
return 1;
case C.COLOR_SPACE_BT601:
return 6;
case C.COLOR_SPACE_BT2020:
return 9;
}
return 1;
}
/**
* Returns the {@link C.ColorTransfer} corresponding to the given ISO transfer characteristics
* code, as per table A.7.21.2 in Rec. ITU-T T.832 (03/2009), or {@link Format#NO_VALUE} if no
* code, as per table A.7.21.2 in Rec. ITU-T T.832 (06/2019), or {@link Format#NO_VALUE} if no
* mapping can be made.
*/
@Pure
@ -249,6 +288,31 @@ public final class ColorInfo {
}
}
/**
* Returns the ISO transfer characteristics code corresponding to the given {@link
* C.ColorTransfer}, as per table A.7.21.2 in Rec. ITU-T T.832 (06/2019).
*/
public static int colorTransferToIsoTransferCharacteristics(@C.ColorTransfer int colorTransfer) {
switch (colorTransfer) {
// Default to BT.709 SDR as per the <a
// href="https://www.webmproject.org/vp9/mp4/#optional-fields">recommendation</a>.
case C.COLOR_TRANSFER_LINEAR:
return 8;
case C.COLOR_TRANSFER_SRGB:
return 13;
case Format.NO_VALUE:
case C.COLOR_TRANSFER_SDR:
return 1;
case C.COLOR_TRANSFER_ST2084:
return 16;
case C.COLOR_TRANSFER_HLG:
return 18;
case C.COLOR_TRANSFER_GAMMA_2_2:
return 4;
}
return 1;
}
/**
* Returns whether the {@code ColorInfo} uses an HDR {@link C.ColorTransfer}.
*

View file

@ -16,6 +16,7 @@
package androidx.media3.common;
import static androidx.media3.common.util.Assertions.checkState;
import static com.google.common.math.DoubleMath.fuzzyEquals;
import static java.lang.annotation.ElementType.TYPE_USE;
import android.os.Bundle;
@ -27,6 +28,7 @@ import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import com.google.common.base.Joiner;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Lists;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
@ -146,6 +148,7 @@ public final class Format {
@Nullable private String language;
private @C.SelectionFlags int selectionFlags;
private @C.RoleFlags int roleFlags;
private @C.AuxiliaryTrackType int auxiliaryTrackType;
private int averageBitrate;
private int peakBitrate;
@Nullable private String codecs;
@ -164,6 +167,7 @@ public final class Format {
@Nullable private List<byte[]> initializationData;
@Nullable private DrmInitData drmInitData;
private long subsampleOffsetUs;
private boolean hasPrerollSamples;
// Video specific.
@ -225,6 +229,7 @@ public final class Format {
tileCountVertical = NO_VALUE;
// Provided by the source.
cryptoType = C.CRYPTO_TYPE_NONE;
auxiliaryTrackType = C.AUXILIARY_TRACK_TYPE_UNDEFINED;
}
/**
@ -253,6 +258,7 @@ public final class Format {
this.initializationData = format.initializationData;
this.drmInitData = format.drmInitData;
this.subsampleOffsetUs = format.subsampleOffsetUs;
this.hasPrerollSamples = format.hasPrerollSamples;
// Video specific.
this.width = format.width;
this.height = format.height;
@ -360,6 +366,9 @@ public final class Format {
/**
* Sets {@link Format#roleFlags}. The default value is 0.
*
* <p>When {@code roleFlags} includes {@link C#ROLE_FLAG_AUXILIARY}, then the specific {@link
* C.AuxiliaryTrackType} can also be {@linkplain #setAuxiliaryTrackType(int) set}.
*
* @param roleFlags The {@link Format#roleFlags}.
* @return The builder.
*/
@ -369,6 +378,22 @@ public final class Format {
return this;
}
/**
* Sets {@link Format#auxiliaryTrackType}. The default value is {@link
* C#AUXILIARY_TRACK_TYPE_UNDEFINED}.
*
* <p>This must be set to a value other than {@link C#AUXILIARY_TRACK_TYPE_UNDEFINED} only when
* {@linkplain #setRoleFlags(int) role flags} contains {@link C#ROLE_FLAG_AUXILIARY}.
*
* @param auxiliaryTrackType The {@link Format#auxiliaryTrackType}.
* @return The builder.
*/
@CanIgnoreReturnValue
public Builder setAuxiliaryTrackType(@C.AuxiliaryTrackType int auxiliaryTrackType) {
this.auxiliaryTrackType = auxiliaryTrackType;
return this;
}
/**
* Sets {@link Format#averageBitrate}. The default value is {@link #NO_VALUE}.
*
@ -521,6 +546,18 @@ public final class Format {
return this;
}
/**
* Sets {@link Format#hasPrerollSamples}. The default value is {@code false}.
*
* @param hasPrerollSamples The {@link Format#hasPrerollSamples}.
* @return The builder.
*/
@CanIgnoreReturnValue
public Builder setHasPrerollSamples(boolean hasPrerollSamples) {
this.hasPrerollSamples = hasPrerollSamples;
return this;
}
// Video specific.
/**
@ -713,7 +750,7 @@ public final class Format {
/**
* Sets {@link Format#tileCountHorizontal}. The default value is {@link #NO_VALUE}.
*
* @param tileCountHorizontal The {@link Format#accessibilityChannel}.
* @param tileCountHorizontal The {@link Format#tileCountHorizontal}.
* @return The builder.
*/
@CanIgnoreReturnValue
@ -725,7 +762,7 @@ public final class Format {
/**
* Sets {@link Format#tileCountVertical}. The default value is {@link #NO_VALUE}.
*
* @param tileCountVertical The {@link Format#accessibilityChannel}.
* @param tileCountVertical The {@link Format#tileCountVertical}.
* @return The builder.
*/
@CanIgnoreReturnValue
@ -824,6 +861,9 @@ public final class Format {
/** Track role flags. */
public final @C.RoleFlags int roleFlags;
/** The auxiliary track type. */
@UnstableApi public final @C.AuxiliaryTrackType int auxiliaryTrackType;
/**
* The average bitrate in bits per second, or {@link #NO_VALUE} if unknown or not applicable. The
* way in which this field is populated depends on the type of media to which the format
@ -927,6 +967,15 @@ public final class Format {
*/
@UnstableApi public final long subsampleOffsetUs;
/**
* Indicates whether the stream contains preroll samples.
*
* <p>When this field is set to {@code true}, it means that the stream includes decode-only
* samples that occur before the intended playback start position. These samples are necessary for
* decoding but are not meant to be rendered and should be skipped after decoding.
*/
@UnstableApi public final boolean hasPrerollSamples;
// Video specific.
/** The width of the video in pixels, or {@link #NO_VALUE} if unknown or not applicable. */
@ -1043,7 +1092,14 @@ public final class Format {
label = builder.label;
}
selectionFlags = builder.selectionFlags;
checkState(
builder.auxiliaryTrackType == C.AUXILIARY_TRACK_TYPE_UNDEFINED
|| (builder.roleFlags & C.ROLE_FLAG_AUXILIARY) != 0,
"Auxiliary track type must only be set to a value other than AUXILIARY_TRACK_TYPE_UNDEFINED"
+ " only when ROLE_FLAG_AUXILIARY is set");
roleFlags = builder.roleFlags;
auxiliaryTrackType = builder.auxiliaryTrackType;
averageBitrate = builder.averageBitrate;
peakBitrate = builder.peakBitrate;
bitrate = peakBitrate != NO_VALUE ? peakBitrate : averageBitrate;
@ -1060,6 +1116,7 @@ public final class Format {
builder.initializationData == null ? Collections.emptyList() : builder.initializationData;
drmInitData = builder.drmInitData;
subsampleOffsetUs = builder.subsampleOffsetUs;
hasPrerollSamples = builder.hasPrerollSamples;
// Video specific.
width = builder.width;
height = builder.height;
@ -1229,6 +1286,7 @@ public final class Format {
result = 31 * result + (language == null ? 0 : language.hashCode());
result = 31 * result + selectionFlags;
result = 31 * result + roleFlags;
result = 31 * result + auxiliaryTrackType;
result = 31 * result + averageBitrate;
result = 31 * result + peakBitrate;
result = 31 * result + (codecs == null ? 0 : codecs.hashCode());
@ -1284,6 +1342,7 @@ public final class Format {
// Field equality checks ordered by type, with the cheapest checks first.
return selectionFlags == other.selectionFlags
&& roleFlags == other.roleFlags
&& auxiliaryTrackType == other.auxiliaryTrackType
&& averageBitrate == other.averageBitrate
&& peakBitrate == other.peakBitrate
&& maxInputSize == other.maxInputSize
@ -1347,6 +1406,7 @@ public final class Format {
if (format == null) {
return "null";
}
Joiner commaJoiner = Joiner.on(',');
StringBuilder builder = new StringBuilder();
builder.append("id=").append(format.id).append(", mimeType=").append(format.sampleMimeType);
if (format.containerMimeType != null) {
@ -1377,12 +1437,15 @@ public final class Format {
}
}
builder.append(", drm=[");
Joiner.on(',').appendTo(builder, schemes);
commaJoiner.appendTo(builder, schemes);
builder.append(']');
}
if (format.width != NO_VALUE && format.height != NO_VALUE) {
builder.append(", res=").append(format.width).append("x").append(format.height);
}
if (!fuzzyEquals(format.pixelWidthHeightRatio, 1, 0.001)) {
builder.append(", par=").append(Util.formatInvariant("%.3f", format.pixelWidthHeightRatio));
}
if (format.colorInfo != null && format.colorInfo.isValid()) {
builder.append(", color=").append(format.colorInfo.toLogString());
}
@ -1400,22 +1463,28 @@ public final class Format {
}
if (!format.labels.isEmpty()) {
builder.append(", labels=[");
Joiner.on(',').appendTo(builder, format.labels);
commaJoiner.appendTo(
builder, Lists.transform(format.labels, l -> l.language + ": " + l.value));
builder.append("]");
}
if (format.selectionFlags != 0) {
builder.append(", selectionFlags=[");
Joiner.on(',').appendTo(builder, Util.getSelectionFlagStrings(format.selectionFlags));
commaJoiner.appendTo(builder, Util.getSelectionFlagStrings(format.selectionFlags));
builder.append("]");
}
if (format.roleFlags != 0) {
builder.append(", roleFlags=[");
Joiner.on(',').appendTo(builder, Util.getRoleFlagStrings(format.roleFlags));
commaJoiner.appendTo(builder, Util.getRoleFlagStrings(format.roleFlags));
builder.append("]");
}
if (format.customData != null) {
builder.append(", customData=").append(format.customData);
}
if ((format.roleFlags & C.ROLE_FLAG_AUXILIARY) != 0) {
builder
.append(", auxiliaryTrackType=")
.append(Util.getAuxiliaryTrackTypeString(format.auxiliaryTrackType));
}
return builder.toString();
}
@ -1452,6 +1521,7 @@ public final class Format {
private static final String FIELD_TILE_COUNT_HORIZONTAL = Util.intToStringMaxRadix(30);
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.
@ -1476,6 +1546,9 @@ public final class Format {
bundle.putString(FIELD_LANGUAGE, language);
bundle.putInt(FIELD_SELECTION_FLAGS, selectionFlags);
bundle.putInt(FIELD_ROLE_FLAGS, roleFlags);
if (auxiliaryTrackType != DEFAULT.auxiliaryTrackType) {
bundle.putInt(FIELD_AUXILIARY_TRACK_TYPE, auxiliaryTrackType);
}
bundle.putInt(FIELD_AVERAGE_BITRATE, averageBitrate);
bundle.putInt(FIELD_PEAK_BITRATE, peakBitrate);
bundle.putString(FIELD_CODECS, codecs);
@ -1540,6 +1613,8 @@ public final class Format {
.setLanguage(defaultIfNull(bundle.getString(FIELD_LANGUAGE), DEFAULT.language))
.setSelectionFlags(bundle.getInt(FIELD_SELECTION_FLAGS, DEFAULT.selectionFlags))
.setRoleFlags(bundle.getInt(FIELD_ROLE_FLAGS, DEFAULT.roleFlags))
.setAuxiliaryTrackType(
bundle.getInt(FIELD_AUXILIARY_TRACK_TYPE, DEFAULT.auxiliaryTrackType))
.setAverageBitrate(bundle.getInt(FIELD_AVERAGE_BITRATE, DEFAULT.averageBitrate))
.setPeakBitrate(bundle.getInt(FIELD_PEAK_BITRATE, DEFAULT.peakBitrate))
.setCodecs(defaultIfNull(bundle.getString(FIELD_CODECS), DEFAULT.codecs))

View file

@ -30,6 +30,25 @@ import java.util.List;
/**
* A {@link Player} that forwards method calls to another {@link Player}. Applications can use this
* class to suppress or modify specific operations, by overriding the respective methods.
*
* <p>Subclasses must ensure they maintain consistency with the {@link Player} interface, including
* interactions with {@link Player.Listener}, which can be quite fiddly. For example, if removing an
* available {@link Player.Command} and disabling the corresponding method, subclasses need to:
*
* <ul>
* <li>Override {@link #isCommandAvailable(int)} and {@link #getAvailableCommands()}
* <li>Override and no-op the method itself
* <li>Override {@link #addListener(Listener)} and wrap the provided {@link Player.Listener} with
* an implementation that drops calls to {@link
* Player.Listener#onAvailableCommandsChanged(Commands)} and {@link
* Player.Listener#onEvents(Player, Events)} if they were only triggered by a change in
* command availability that is 'invisible' after the command removal.
* </ul>
*
* <p>Many customization use-cases are instead better served by {@link ForwardingSimpleBasePlayer},
* which allows subclasses to more concisely modify the behavior of an operation, or disallow a
* {@link Player.Command}. In many cases {@link ForwardingSimpleBasePlayer} should be used in
* preference to {@code ForwardingPlayer}.
*/
@UnstableApi
public class ForwardingPlayer implements Player {
@ -327,48 +346,12 @@ public class ForwardingPlayer implements Player {
player.seekForward();
}
/**
* Calls {@link Player#hasPrevious()} on the delegate and returns the result.
*
* @deprecated Use {@link #hasPreviousMediaItem()} instead.
*/
@SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated
@Override
public boolean hasPrevious() {
return player.hasPrevious();
}
/**
* Calls {@link Player#hasPreviousWindow()} on the delegate and returns the result.
*
* @deprecated Use {@link #hasPreviousMediaItem()} instead.
*/
@SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated
@Override
public boolean hasPreviousWindow() {
return player.hasPreviousWindow();
}
/** Calls {@link Player#hasPreviousMediaItem()} on the delegate and returns the result. */
@Override
public boolean hasPreviousMediaItem() {
return player.hasPreviousMediaItem();
}
/**
* Calls {@link Player#previous()} on the delegate.
*
* @deprecated Use {@link #seekToPreviousMediaItem()} instead.
*/
@SuppressWarnings("deprecation") // Forwarding to deprecated method
@Deprecated
@Override
public void previous() {
player.previous();
}
/**
* Calls {@link Player#seekToPreviousWindow()} on the delegate.
*

View file

@ -0,0 +1,499 @@
/*
* 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.view.Surface;
import android.view.SurfaceHolder;
import android.view.SurfaceView;
import android.view.TextureView;
import androidx.annotation.Nullable;
import androidx.media3.common.util.UnstableApi;
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.
*
* <p>The class can be used to selectively override {@link #getState()} or {@code handle{Action}}
* methods:
*
* <pre>{@code
* new ForwardingSimpleBasePlayer(player) {
* @Override
* protected State getState() {
* State state = super.getState();
* // Modify current state as required:
* return state.buildUpon().setAvailableCommands(filteredCommands).build();
* }
*
* @Override
* protected ListenableFuture<?> handleSetRepeatMode(int repeatMode) {
* // Modify actions by directly calling the underlying player as needed:
* getPlayer().setShuffleModeEnabled(true);
* // ..or forward to the default handling with modified parameters:
* return super.handleSetRepeatMode(Player.REPEAT_MODE_ALL);
* }
* }
* }</pre>
*
* This base class handles many aspect of the player implementation to simplify the subclass, for
* example listener handling. See the documentation of {@link SimpleBasePlayer} for a more detailed
* description.
*/
@UnstableApi
public class ForwardingSimpleBasePlayer extends SimpleBasePlayer {
private final Player player;
private ForwardingPositionSupplier currentPositionSupplier;
private Metadata lastTimedMetadata;
private @Player.PlayWhenReadyChangeReason int playWhenReadyChangeReason;
private @Player.DiscontinuityReason int pendingDiscontinuityReason;
private long pendingPositionDiscontinuityNewPositionMs;
private boolean pendingFirstFrameRendered;
/**
* Creates the forwarding player.
*
* @param player The {@link Player} to forward to.
*/
public ForwardingSimpleBasePlayer(Player player) {
super(player.getApplicationLooper());
this.player = player;
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);
player.addListener(
new Listener() {
@Override
public void onMetadata(Metadata metadata) {
lastTimedMetadata = metadata;
}
@Override
public void onPlayWhenReadyChanged(
boolean playWhenReady, @Player.PlayWhenReadyChangeReason int reason) {
playWhenReadyChangeReason = reason;
}
@Override
public void onPositionDiscontinuity(
PositionInfo oldPosition,
PositionInfo newPosition,
@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);
}
@Override
public void onRenderedFirstFrame() {
pendingFirstFrameRendered = true;
}
@SuppressWarnings("method.invocation.invalid") // Calling method from constructor.
@Override
public void onEvents(Player player, Events events) {
invalidateState();
}
});
}
/** Returns the wrapped player. */
protected final Player getPlayer() {
return player;
}
@Override
protected State getState() {
// Ordered alphabetically by State.Builder setters.
State.Builder state = new State.Builder();
ForwardingPositionSupplier positionSupplier = currentPositionSupplier;
if (player.isCommandAvailable(Player.COMMAND_GET_CURRENT_MEDIA_ITEM)) {
state.setAdBufferedPositionMs(positionSupplier::getBufferedPositionMs);
state.setAdPositionMs(positionSupplier::getCurrentPositionMs);
}
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);
if (player.isCommandAvailable(Player.COMMAND_GET_TIMELINE)) {
state.setCurrentAd(player.getCurrentAdGroupIndex(), player.getCurrentAdIndexInAdGroup());
}
}
if (player.isCommandAvailable(Player.COMMAND_GET_TEXT)) {
state.setCurrentCues(player.getCurrentCues());
}
if (player.isCommandAvailable(Player.COMMAND_GET_TIMELINE)) {
state.setCurrentMediaItemIndex(player.getCurrentMediaItemIndex());
}
state.setDeviceInfo(player.getDeviceInfo());
if (player.isCommandAvailable(Player.COMMAND_GET_DEVICE_VOLUME)) {
state.setDeviceVolume(player.getDeviceVolume());
state.setIsDeviceMuted(player.isDeviceMuted());
}
state.setIsLoading(player.isLoading());
state.setMaxSeekToPreviousPositionMs(player.getMaxSeekToPreviousPosition());
if (pendingFirstFrameRendered) {
state.setNewlyRenderedFirstFrame(true);
pendingFirstFrameRendered = false;
}
state.setPlaybackParameters(player.getPlaybackParameters());
state.setPlaybackState(player.getPlaybackState());
state.setPlaybackSuppressionReason(player.getPlaybackSuppressionReason());
state.setPlayerError(player.getPlayerError());
if (player.isCommandAvailable(Player.COMMAND_GET_TIMELINE)) {
Tracks tracks =
player.isCommandAvailable(Player.COMMAND_GET_TRACKS)
? player.getCurrentTracks()
: Tracks.EMPTY;
MediaMetadata mediaMetadata =
player.isCommandAvailable(Player.COMMAND_GET_METADATA) ? player.getMediaMetadata() : null;
state.setPlaylist(player.getCurrentTimeline(), tracks, mediaMetadata);
}
if (player.isCommandAvailable(Player.COMMAND_GET_METADATA)) {
state.setPlaylistMetadata(player.getPlaylistMetadata());
}
state.setPlayWhenReady(player.getPlayWhenReady(), playWhenReadyChangeReason);
if (pendingPositionDiscontinuityNewPositionMs != C.TIME_UNSET) {
state.setPositionDiscontinuity(
pendingDiscontinuityReason, pendingPositionDiscontinuityNewPositionMs);
pendingPositionDiscontinuityNewPositionMs = C.TIME_UNSET;
}
state.setRepeatMode(player.getRepeatMode());
state.setSeekBackIncrementMs(player.getSeekBackIncrement());
state.setSeekForwardIncrementMs(player.getSeekForwardIncrement());
state.setShuffleModeEnabled(player.getShuffleModeEnabled());
state.setSurfaceSize(player.getSurfaceSize());
state.setTimedMetadata(lastTimedMetadata);
if (player.isCommandAvailable(Player.COMMAND_GET_CURRENT_MEDIA_ITEM)) {
state.setTotalBufferedDurationMs(positionSupplier::getTotalBufferedDurationMs);
}
state.setTrackSelectionParameters(player.getTrackSelectionParameters());
state.setVideoSize(player.getVideoSize());
if (player.isCommandAvailable(Player.COMMAND_GET_VOLUME)) {
state.setVolume(player.getVolume());
}
return state.build();
}
@Override
protected ListenableFuture<?> handleSetPlayWhenReady(boolean playWhenReady) {
player.setPlayWhenReady(playWhenReady);
return Futures.immediateVoidFuture();
}
@Override
protected ListenableFuture<?> handlePrepare() {
player.prepare();
return Futures.immediateVoidFuture();
}
@Override
protected ListenableFuture<?> handleStop() {
player.stop();
return Futures.immediateVoidFuture();
}
@Override
protected ListenableFuture<?> handleRelease() {
player.release();
return Futures.immediateVoidFuture();
}
@Override
protected ListenableFuture<?> handleSetRepeatMode(@Player.RepeatMode int repeatMode) {
player.setRepeatMode(repeatMode);
return Futures.immediateVoidFuture();
}
@Override
protected ListenableFuture<?> handleSetShuffleModeEnabled(boolean shuffleModeEnabled) {
player.setShuffleModeEnabled(shuffleModeEnabled);
return Futures.immediateVoidFuture();
}
@Override
protected ListenableFuture<?> handleSetPlaybackParameters(PlaybackParameters playbackParameters) {
player.setPlaybackParameters(playbackParameters);
return Futures.immediateVoidFuture();
}
@Override
protected ListenableFuture<?> handleSetTrackSelectionParameters(
TrackSelectionParameters trackSelectionParameters) {
player.setTrackSelectionParameters(trackSelectionParameters);
return Futures.immediateVoidFuture();
}
@Override
protected ListenableFuture<?> handleSetPlaylistMetadata(MediaMetadata playlistMetadata) {
player.setPlaylistMetadata(playlistMetadata);
return Futures.immediateVoidFuture();
}
@Override
protected ListenableFuture<?> handleSetVolume(float volume) {
player.setVolume(volume);
return Futures.immediateVoidFuture();
}
@SuppressWarnings("deprecation") // Calling deprecated method if updated command not available.
@Override
protected ListenableFuture<?> handleSetDeviceVolume(int deviceVolume, int flags) {
if (player.isCommandAvailable(Player.COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS)) {
player.setDeviceVolume(deviceVolume, flags);
} else {
player.setDeviceVolume(deviceVolume);
}
return Futures.immediateVoidFuture();
}
@SuppressWarnings("deprecation") // Calling deprecated method if updated command not available.
@Override
protected ListenableFuture<?> handleIncreaseDeviceVolume(@C.VolumeFlags int flags) {
if (player.isCommandAvailable(Player.COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS)) {
player.increaseDeviceVolume(flags);
} else {
player.increaseDeviceVolume();
}
return Futures.immediateVoidFuture();
}
@SuppressWarnings("deprecation") // Calling deprecated method if updated command not available.
@Override
protected ListenableFuture<?> handleDecreaseDeviceVolume(@C.VolumeFlags int flags) {
if (player.isCommandAvailable(Player.COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS)) {
player.decreaseDeviceVolume(flags);
} else {
player.decreaseDeviceVolume();
}
return Futures.immediateVoidFuture();
}
@SuppressWarnings("deprecation") // Calling deprecated method if updated command not available.
@Override
protected ListenableFuture<?> handleSetDeviceMuted(boolean muted, @C.VolumeFlags int flags) {
if (player.isCommandAvailable(Player.COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS)) {
player.setDeviceMuted(muted, flags);
} else {
player.setDeviceMuted(muted);
}
return Futures.immediateVoidFuture();
}
@Override
protected ListenableFuture<?> handleSetAudioAttributes(
AudioAttributes audioAttributes, boolean handleAudioFocus) {
player.setAudioAttributes(audioAttributes, handleAudioFocus);
return Futures.immediateVoidFuture();
}
@Override
protected ListenableFuture<?> handleSetVideoOutput(Object videoOutput) {
if (videoOutput instanceof SurfaceView) {
player.setVideoSurfaceView((SurfaceView) videoOutput);
} else if (videoOutput instanceof TextureView) {
player.setVideoTextureView((TextureView) videoOutput);
} else if (videoOutput instanceof SurfaceHolder) {
player.setVideoSurfaceHolder((SurfaceHolder) videoOutput);
} else if (videoOutput instanceof Surface) {
player.setVideoSurface((Surface) videoOutput);
} else {
throw new IllegalStateException();
}
return Futures.immediateVoidFuture();
}
@Override
protected ListenableFuture<?> handleClearVideoOutput(@Nullable Object videoOutput) {
if (videoOutput instanceof SurfaceView) {
player.clearVideoSurfaceView((SurfaceView) videoOutput);
} else if (videoOutput instanceof TextureView) {
player.clearVideoTextureView((TextureView) videoOutput);
} else if (videoOutput instanceof SurfaceHolder) {
player.clearVideoSurfaceHolder((SurfaceHolder) videoOutput);
} else if (videoOutput instanceof Surface) {
player.clearVideoSurface((Surface) videoOutput);
} else if (videoOutput == null) {
player.clearVideoSurface();
} else {
throw new IllegalStateException();
}
return Futures.immediateVoidFuture();
}
@Override
protected ListenableFuture<?> handleSetMediaItems(
List<MediaItem> mediaItems, int startIndex, long startPositionMs) {
boolean useSingleItemCall =
mediaItems.size() == 1 && player.isCommandAvailable(Player.COMMAND_SET_MEDIA_ITEM);
if (startIndex == C.INDEX_UNSET) {
if (useSingleItemCall) {
player.setMediaItem(mediaItems.get(0));
} else {
player.setMediaItems(mediaItems);
}
} else {
if (useSingleItemCall) {
player.setMediaItem(mediaItems.get(0), startPositionMs);
} else {
player.setMediaItems(mediaItems, startIndex, startPositionMs);
}
}
return Futures.immediateVoidFuture();
}
@Override
protected ListenableFuture<?> handleAddMediaItems(int index, List<MediaItem> mediaItems) {
if (mediaItems.size() == 1) {
player.addMediaItem(index, mediaItems.get(0));
} else {
player.addMediaItems(index, mediaItems);
}
return Futures.immediateVoidFuture();
}
@Override
protected ListenableFuture<?> handleMoveMediaItems(int fromIndex, int toIndex, int newIndex) {
if (toIndex == fromIndex + 1) {
player.moveMediaItem(fromIndex, newIndex);
} else {
player.moveMediaItems(fromIndex, toIndex, newIndex);
}
return Futures.immediateVoidFuture();
}
@Override
protected ListenableFuture<?> handleReplaceMediaItems(
int fromIndex, int toIndex, List<MediaItem> mediaItems) {
if (toIndex == fromIndex + 1 && mediaItems.size() == 1) {
player.replaceMediaItem(fromIndex, mediaItems.get(0));
} else {
player.replaceMediaItems(fromIndex, toIndex, mediaItems);
}
return Futures.immediateVoidFuture();
}
@Override
protected ListenableFuture<?> handleRemoveMediaItems(int fromIndex, int toIndex) {
if (toIndex == fromIndex + 1) {
player.removeMediaItem(fromIndex);
} else {
player.removeMediaItems(fromIndex, toIndex);
}
return Futures.immediateVoidFuture();
}
@Override
protected ListenableFuture<?> handleSeek(
int mediaItemIndex, long positionMs, @Command int seekCommand) {
switch (seekCommand) {
case Player.COMMAND_SEEK_BACK:
player.seekBack();
break;
case Player.COMMAND_SEEK_FORWARD:
player.seekForward();
break;
case Player.COMMAND_SEEK_IN_CURRENT_MEDIA_ITEM:
player.seekTo(positionMs);
break;
case Player.COMMAND_SEEK_TO_DEFAULT_POSITION:
player.seekToDefaultPosition();
break;
case Player.COMMAND_SEEK_TO_MEDIA_ITEM:
if (mediaItemIndex != C.INDEX_UNSET) {
player.seekTo(mediaItemIndex, positionMs);
}
break;
case Player.COMMAND_SEEK_TO_NEXT:
player.seekToNext();
break;
case Player.COMMAND_SEEK_TO_NEXT_MEDIA_ITEM:
player.seekToNextMediaItem();
break;
case Player.COMMAND_SEEK_TO_PREVIOUS:
player.seekToPrevious();
break;
case Player.COMMAND_SEEK_TO_PREVIOUS_MEDIA_ITEM:
player.seekToPreviousMediaItem();
break;
default:
throw new IllegalStateException();
}
return Futures.immediateVoidFuture();
}
/**
* Forwards to the changing position values of the wrapped player until the forwarding is
* deactivated with constant values.
*/
private static final class ForwardingPositionSupplier {
private final Player player;
private long positionsMs;
private long contentPositionMs;
public ForwardingPositionSupplier(Player player) {
this.player = player;
this.positionsMs = C.TIME_UNSET;
this.contentPositionMs = C.TIME_UNSET;
}
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;
}
}
}

View file

@ -81,4 +81,11 @@ public interface GlObjectsProvider {
* @throws GlException If an error occurs during creation.
*/
GlTextureInfo createBuffersForTexture(int texId, int width, int height) throws GlException;
/**
* Releases the created objects.
*
* @param eglDisplay The {@link EGLDisplay} to release the objects for.
*/
void release(EGLDisplay eglDisplay) throws GlException;
}

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.4.0";
public static final String VERSION = "1.5.1";
/** 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.4.0";
public static final String VERSION_SLASHY = "AndroidXMedia3/1.5.1";
/**
* 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_004_000_3_00;
public static final int VERSION_INT = 1_005_001_3_00;
/** Whether the library was compiled with {@link Assertions} checks enabled. */
public static final boolean ASSERTIONS_ENABLED = true;

View file

@ -30,11 +30,13 @@ 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;
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.List;
@ -85,8 +87,11 @@ public final class MediaMetadata {
@Nullable private CharSequence station;
@Nullable private @MediaType Integer mediaType;
@Nullable private Bundle extras;
private ImmutableList<String> supportedCommands;
public Builder() {}
public Builder() {
supportedCommands = ImmutableList.of();
}
@SuppressWarnings("deprecation") // Assigning from deprecated fields.
private Builder(MediaMetadata mediaMetadata) {
@ -123,6 +128,7 @@ public final class MediaMetadata {
this.compilation = mediaMetadata.compilation;
this.station = mediaMetadata.station;
this.mediaType = mediaMetadata.mediaType;
this.supportedCommands = mediaMetadata.supportedCommands;
this.extras = mediaMetadata.extras;
}
@ -440,6 +446,17 @@ public final class MediaMetadata {
return this;
}
/**
* Sets the IDs of the supported commands (see for instance {@code
* CommandButton.sessionCommand.customAction} of the Media3 session module).
*/
@CanIgnoreReturnValue
@UnstableApi
public Builder setSupportedCommands(List<String> supportedCommands) {
this.supportedCommands = ImmutableList.copyOf(supportedCommands);
return this;
}
/**
* Sets all fields supported by the {@link Metadata.Entry entries} within the {@link Metadata}.
*
@ -596,6 +613,10 @@ public final class MediaMetadata {
setExtras(mediaMetadata.extras);
}
if (!mediaMetadata.supportedCommands.isEmpty()) {
setSupportedCommands(mediaMetadata.supportedCommands);
}
return this;
}
@ -1123,6 +1144,12 @@ public final class MediaMetadata {
*/
@Nullable public final Bundle extras;
/**
* The IDs of the supported commands of this media item (see for instance {@code
* CommandButton.sessionCommand.customAction} of the Media3 session module).
*/
@UnstableApi public final ImmutableList<String> supportedCommands;
@SuppressWarnings("deprecation") // Assigning deprecated fields.
private MediaMetadata(Builder builder) {
// Handle compatibility for deprecated fields.
@ -1175,6 +1202,7 @@ public final class MediaMetadata {
this.compilation = builder.compilation;
this.station = builder.station;
this.mediaType = mediaType;
this.supportedCommands = builder.supportedCommands;
this.extras = builder.extras;
}
@ -1227,6 +1255,7 @@ public final class MediaMetadata {
&& Util.areEqual(compilation, that.compilation)
&& Util.areEqual(station, that.station)
&& Util.areEqual(mediaType, that.mediaType)
&& Util.areEqual(supportedCommands, that.supportedCommands)
&& ((extras == null) == (that.extras == null));
}
@ -1267,7 +1296,8 @@ public final class MediaMetadata {
compilation,
station,
mediaType,
extras == null);
extras == null,
supportedCommands);
}
private static final String FIELD_TITLE = Util.intToStringMaxRadix(0);
@ -1304,6 +1334,7 @@ public final class MediaMetadata {
private static final String FIELD_MEDIA_TYPE = Util.intToStringMaxRadix(31);
private static final String FIELD_IS_BROWSABLE = Util.intToStringMaxRadix(32);
private static final String FIELD_DURATION_MS = Util.intToStringMaxRadix(33);
private static final String FIELD_SUPPORTED_COMMANDS = Util.intToStringMaxRadix(34);
private static final String FIELD_EXTRAS = Util.intToStringMaxRadix(1000);
@SuppressWarnings("deprecation") // Bundling deprecated fields.
@ -1409,6 +1440,9 @@ public final class MediaMetadata {
if (mediaType != null) {
bundle.putInt(FIELD_MEDIA_TYPE, mediaType);
}
if (!supportedCommands.isEmpty()) {
bundle.putStringArrayList(FIELD_SUPPORTED_COMMANDS, new ArrayList<>(supportedCommands));
}
if (extras != null) {
bundle.putBundle(FIELD_EXTRAS, extras);
}
@ -1499,6 +1533,11 @@ public final class MediaMetadata {
if (bundle.containsKey(FIELD_MEDIA_TYPE)) {
builder.setMediaType(bundle.getInt(FIELD_MEDIA_TYPE));
}
@Nullable
ArrayList<String> supportedCommands = bundle.getStringArrayList(FIELD_SUPPORTED_COMMANDS);
if (supportedCommands != null) {
builder.setSupportedCommands(supportedCommands);
}
return builder.build();
}

View file

@ -62,6 +62,7 @@ public final class MimeTypes {
public static final String VIDEO_MJPEG = BASE_TYPE_VIDEO + "/mjpeg";
public static final String VIDEO_MP42 = BASE_TYPE_VIDEO + "/mp42";
public static final String VIDEO_MP43 = BASE_TYPE_VIDEO + "/mp43";
@UnstableApi public static final String VIDEO_MV_HEVC = BASE_TYPE_VIDEO + "/mv-hevc";
@UnstableApi public static final String VIDEO_RAW = BASE_TYPE_VIDEO + "/raw";
@UnstableApi public static final String VIDEO_UNKNOWN = BASE_TYPE_VIDEO + "/x-unknown";
@ -99,6 +100,7 @@ public final class MimeTypes {
public static final String AUDIO_OGG = BASE_TYPE_AUDIO + "/ogg";
public static final String AUDIO_WAV = BASE_TYPE_AUDIO + "/wav";
public static final String AUDIO_MIDI = BASE_TYPE_AUDIO + "/midi";
@UnstableApi public static final String AUDIO_IAMF = BASE_TYPE_AUDIO + "/iamf";
@UnstableApi
public static final String AUDIO_EXOPLAYER_MIDI = BASE_TYPE_AUDIO + "/x-exoplayer-midi";
@ -139,10 +141,15 @@ public final class MimeTypes {
public static final String APPLICATION_VOBSUB = BASE_TYPE_APPLICATION + "/vobsub";
public static final String APPLICATION_PGS = BASE_TYPE_APPLICATION + "/pgs";
@UnstableApi public static final String APPLICATION_SCTE35 = BASE_TYPE_APPLICATION + "/x-scte35";
public static final String APPLICATION_SDP = BASE_TYPE_APPLICATION + "/sdp";
@UnstableApi
public static final String APPLICATION_CAMERA_MOTION = BASE_TYPE_APPLICATION + "/x-camera-motion";
@UnstableApi
public static final String APPLICATION_DEPTH_METADATA =
BASE_TYPE_APPLICATION + "/x-depth-metadata";
@UnstableApi public static final String APPLICATION_EMSG = BASE_TYPE_APPLICATION + "/x-emsg";
public static final String APPLICATION_DVBSUBS = BASE_TYPE_APPLICATION + "/dvbsubs";
@UnstableApi public static final String APPLICATION_EXIF = BASE_TYPE_APPLICATION + "/x-exif";
@ -488,6 +495,29 @@ public final class MimeTypes {
}
}
/**
* Returns the MP4 object type identifier corresponding to a MIME type, as defined in RFC 6381 and
* <a href="https://mp4ra.org/registered-types/object-types">MPEG-4 Object Types</a>.
*
* @param sampleMimeType The MIME type of the track.
* @return The corresponding MP4 object type identifier, or {@code null} if it could not be
* determined.
*/
@UnstableApi
@Nullable
public static Byte getMp4ObjectTypeFromMimeType(String sampleMimeType) {
switch (sampleMimeType) {
case MimeTypes.AUDIO_AAC:
return (byte) 0x40;
case MimeTypes.AUDIO_VORBIS:
return (byte) 0xDD;
case MimeTypes.VIDEO_MP4V:
return (byte) 0x20;
default:
return null;
}
}
/**
* Returns the MIME type corresponding to an MP4 object type identifier, as defined in RFC 6381
* and https://mp4ra.org/#/object_types.
@ -571,7 +601,9 @@ public final class MimeTypes {
return C.TRACK_TYPE_IMAGE;
} else if (APPLICATION_ID3.equals(mimeType)
|| APPLICATION_EMSG.equals(mimeType)
|| APPLICATION_SCTE35.equals(mimeType)) {
|| APPLICATION_SCTE35.equals(mimeType)
|| APPLICATION_ICY.equals(mimeType)
|| APPLICATION_AIT.equals(mimeType)) {
return C.TRACK_TYPE_METADATA;
} else if (APPLICATION_CAMERA_MOTION.equals(mimeType)) {
return C.TRACK_TYPE_CAMERA_MOTION;
@ -653,14 +685,17 @@ public final class MimeTypes {
}
mimeType = Ascii.toLowerCase(mimeType);
switch (mimeType) {
// Normalize uncommon versions of some audio MIME types to their standard equivalent.
// Normalize uncommon versions of some video MIME types to their standard equivalent.
case BASE_TYPE_VIDEO + "/x-mvhevc":
return VIDEO_MV_HEVC;
// Normalize uncommon versions of some audio MIME types to their standard equivalent.
case BASE_TYPE_AUDIO + "/x-flac":
return AUDIO_FLAC;
case BASE_TYPE_AUDIO + "/mp3":
return AUDIO_MPEG;
case BASE_TYPE_AUDIO + "/x-wav":
return AUDIO_WAV;
// Normalize MIME types that are often written with upper-case letters to their common form.
// Normalize MIME types that are often written with upper-case letters to their common form.
case "application/x-mpegurl":
return APPLICATION_M3U8;
case "audio/mpeg-l1":

View file

@ -113,7 +113,7 @@ public class ParserException extends IOException {
@Override
public String getMessage() {
return super.getMessage()
+ "{contentIsMalformed="
+ " {contentIsMalformed="
+ contentIsMalformed
+ ", dataType="
+ dataType

View file

@ -2635,20 +2635,6 @@ public interface Player {
*/
void seekForward();
/**
* @deprecated Use {@link #hasPreviousMediaItem()} instead.
*/
@UnstableApi
@Deprecated
boolean hasPrevious();
/**
* @deprecated Use {@link #hasPreviousMediaItem()} instead.
*/
@UnstableApi
@Deprecated
boolean hasPreviousWindow();
/**
* Returns whether a previous media item exists, which may depend on the current repeat mode and
* whether shuffle mode is enabled.
@ -2662,13 +2648,6 @@ public interface Player {
*/
boolean hasPreviousMediaItem();
/**
* @deprecated Use {@link #seekToPreviousMediaItem()} instead.
*/
@UnstableApi
@Deprecated
void previous();
/**
* @deprecated Use {@link #seekToPreviousMediaItem()} instead.
*/

View file

@ -53,6 +53,7 @@ import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Objects;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@ -126,8 +127,10 @@ public abstract class SimpleBasePlayer extends BasePlayer {
private Size surfaceSize;
private boolean newlyRenderedFirstFrame;
private Metadata timedMetadata;
private ImmutableList<MediaItemData> playlist;
@Nullable private ImmutableList<MediaItemData> playlist;
private Timeline timeline;
@Nullable private Tracks currentTracks;
@Nullable private MediaMetadata currentMetadata;
private MediaMetadata playlistMetadata;
private int currentMediaItemIndex;
private int currentAdGroupIndex;
@ -171,6 +174,8 @@ public abstract class SimpleBasePlayer extends BasePlayer {
timedMetadata = new Metadata(/* presentationTimeUs= */ C.TIME_UNSET);
playlist = ImmutableList.of();
timeline = Timeline.EMPTY;
currentTracks = null;
currentMetadata = null;
playlistMetadata = MediaMetadata.EMPTY;
currentMediaItemIndex = C.INDEX_UNSET;
currentAdGroupIndex = C.INDEX_UNSET;
@ -212,8 +217,13 @@ public abstract class SimpleBasePlayer extends BasePlayer {
this.surfaceSize = state.surfaceSize;
this.newlyRenderedFirstFrame = state.newlyRenderedFirstFrame;
this.timedMetadata = state.timedMetadata;
this.playlist = state.playlist;
this.timeline = state.timeline;
if (state.timeline instanceof PlaylistTimeline) {
this.playlist = ((PlaylistTimeline) state.timeline).playlist;
} else {
this.currentTracks = state.currentTracks;
this.currentMetadata = state.currentMetadata;
}
this.playlistMetadata = state.playlistMetadata;
this.currentMediaItemIndex = state.currentMediaItemIndex;
this.currentAdGroupIndex = state.currentAdGroupIndex;
@ -538,10 +548,13 @@ public abstract class SimpleBasePlayer extends BasePlayer {
}
/**
* Sets the list of {@link MediaItemData media items} in the playlist.
* Sets the playlist as a list of {@link MediaItemData media items}.
*
* <p>All items must have unique {@linkplain MediaItemData.Builder#setUid UIDs}.
*
* <p>This call replaces any previous playlist set via {@link #setPlaylist(Timeline, Tracks,
* MediaMetadata)}.
*
* @param playlist The list of {@link MediaItemData media items} in the playlist.
* @return This builder.
*/
@ -553,6 +566,33 @@ public abstract class SimpleBasePlayer extends BasePlayer {
}
this.playlist = ImmutableList.copyOf(playlist);
this.timeline = new PlaylistTimeline(this.playlist);
this.currentTracks = null;
this.currentMetadata = null;
return this;
}
/**
* Sets the playlist as a {@link Timeline} with information about the current {@link Tracks}
* and {@link MediaMetadata}.
*
* <p>This call replaces any previous playlist set via {@link #setPlaylist(List)}.
*
* @param timeline The {@link Timeline} containing the playlist data.
* @param currentTracks The {@link Tracks} of the {@linkplain #setCurrentMediaItemIndex
* current media item}.
* @param currentMetadata The combined {@link MediaMetadata} of the {@linkplain
* #setCurrentMediaItemIndex current media item}. If null, the current metadata is assumed
* to be the combination of the {@link MediaItem#mediaMetadata MediaItem} metadata and the
* metadata of the selected {@link Format#metadata Formats}.
* @return This builder.
*/
@CanIgnoreReturnValue
public Builder setPlaylist(
Timeline timeline, Tracks currentTracks, @Nullable MediaMetadata currentMetadata) {
this.playlist = null;
this.timeline = timeline;
this.currentTracks = currentTracks;
this.currentMetadata = currentMetadata;
return this;
}
@ -850,12 +890,15 @@ public abstract class SimpleBasePlayer extends BasePlayer {
/** The most recent timed metadata. */
public final Metadata timedMetadata;
/** The media items in the playlist. */
public final ImmutableList<MediaItemData> playlist;
/** The {@link Timeline} derived from the {@link #playlist}. */
/** The {@link Timeline}. */
public final Timeline timeline;
/** The current {@link Tracks}. */
public final Tracks currentTracks;
/** The current combined {@link MediaMetadata}. */
public final MediaMetadata currentMetadata;
/** The playlist {@link MediaMetadata}. */
public final MediaMetadata playlistMetadata;
@ -916,6 +959,8 @@ public abstract class SimpleBasePlayer extends BasePlayer {
public final long discontinuityPositionMs;
private State(Builder builder) {
Tracks currentTracks = builder.currentTracks;
MediaMetadata currentMetadata = builder.currentMetadata;
if (builder.timeline.isEmpty()) {
checkArgument(
builder.playbackState == Player.STATE_IDLE
@ -925,6 +970,12 @@ public abstract class SimpleBasePlayer extends BasePlayer {
builder.currentAdGroupIndex == C.INDEX_UNSET
&& builder.currentAdIndexInAdGroup == C.INDEX_UNSET,
"Ads not allowed if playlist is empty");
if (currentTracks == null) {
currentTracks = Tracks.EMPTY;
}
if (currentMetadata == null) {
currentMetadata = MediaMetadata.EMPTY;
}
} else {
int mediaItemIndex = builder.currentMediaItemIndex;
if (mediaItemIndex == C.INDEX_UNSET) {
@ -955,6 +1006,17 @@ public abstract class SimpleBasePlayer extends BasePlayer {
"Ad group has less ads than adIndexInGroupIndex");
}
}
if (builder.playlist != null) {
MediaItemData mediaItemData = builder.playlist.get(mediaItemIndex);
currentTracks = mediaItemData.tracks;
currentMetadata = mediaItemData.mediaMetadata;
}
if (currentMetadata == null) {
currentMetadata =
getCombinedMediaMetadata(
builder.timeline.getWindow(mediaItemIndex, new Timeline.Window()).mediaItem,
checkNotNull(currentTracks));
}
}
if (builder.playerError != null) {
checkArgument(
@ -1015,8 +1077,9 @@ public abstract class SimpleBasePlayer extends BasePlayer {
this.surfaceSize = builder.surfaceSize;
this.newlyRenderedFirstFrame = builder.newlyRenderedFirstFrame;
this.timedMetadata = builder.timedMetadata;
this.playlist = builder.playlist;
this.timeline = builder.timeline;
this.currentTracks = checkNotNull(currentTracks);
this.currentMetadata = currentMetadata;
this.playlistMetadata = builder.playlistMetadata;
this.currentMediaItemIndex = builder.currentMediaItemIndex;
this.currentAdGroupIndex = builder.currentAdGroupIndex;
@ -1036,6 +1099,27 @@ public abstract class SimpleBasePlayer extends BasePlayer {
return new Builder(this);
}
/**
* Returns the list of {@link MediaItemData} for the current playlist.
*
* @see Builder#setPlaylist(List)
*/
public ImmutableList<MediaItemData> getPlaylist() {
if (timeline instanceof PlaylistTimeline) {
return ((PlaylistTimeline) timeline).playlist;
}
Timeline.Window window = new Timeline.Window();
Timeline.Period period = new Timeline.Period();
ImmutableList.Builder<MediaItemData> items =
ImmutableList.builderWithExpectedSize(timeline.getWindowCount());
for (int i = 0; i < timeline.getWindowCount(); i++) {
items.add(
MediaItemData.buildFromState(
/* state= */ this, /* mediaItemIndex= */ i, period, window));
}
return items.build();
}
@Override
public boolean equals(@Nullable Object o) {
if (this == o) {
@ -1050,7 +1134,7 @@ public abstract class SimpleBasePlayer extends BasePlayer {
&& availableCommands.equals(state.availableCommands)
&& playbackState == state.playbackState
&& playbackSuppressionReason == state.playbackSuppressionReason
&& Util.areEqual(playerError, state.playerError)
&& Objects.equals(playerError, state.playerError)
&& repeatMode == state.repeatMode
&& shuffleModeEnabled == state.shuffleModeEnabled
&& isLoading == state.isLoading
@ -1069,7 +1153,9 @@ public abstract class SimpleBasePlayer extends BasePlayer {
&& surfaceSize.equals(state.surfaceSize)
&& newlyRenderedFirstFrame == state.newlyRenderedFirstFrame
&& timedMetadata.equals(state.timedMetadata)
&& playlist.equals(state.playlist)
&& timeline.equals(state.timeline)
&& currentTracks.equals(state.currentTracks)
&& currentMetadata.equals(state.currentMetadata)
&& playlistMetadata.equals(state.playlistMetadata)
&& currentMediaItemIndex == state.currentMediaItemIndex
&& currentAdGroupIndex == state.currentAdGroupIndex
@ -1112,7 +1198,9 @@ public abstract class SimpleBasePlayer extends BasePlayer {
result = 31 * result + surfaceSize.hashCode();
result = 31 * result + (newlyRenderedFirstFrame ? 1 : 0);
result = 31 * result + timedMetadata.hashCode();
result = 31 * result + playlist.hashCode();
result = 31 * result + timeline.hashCode();
result = 31 * result + currentTracks.hashCode();
result = 31 * result + currentMetadata.hashCode();
result = 31 * result + playlistMetadata.hashCode();
result = 31 * result + currentMediaItemIndex;
result = 31 * result + currentAdGroupIndex;
@ -1136,9 +1224,9 @@ public abstract class SimpleBasePlayer extends BasePlayer {
private final int[] windowIndexByPeriodIndex;
private final HashMap<Object, Integer> periodIndexByUid;
public PlaylistTimeline(ImmutableList<MediaItemData> playlist) {
public PlaylistTimeline(List<MediaItemData> playlist) {
int mediaItemCount = playlist.size();
this.playlist = playlist;
this.playlist = ImmutableList.copyOf(playlist);
this.firstPeriodIndexByWindowIndex = new int[mediaItemCount];
int periodCount = 0;
for (int i = 0; i < mediaItemCount; i++) {
@ -1636,7 +1724,6 @@ public abstract class SimpleBasePlayer extends BasePlayer {
public final ImmutableList<PeriodData> periods;
private final long[] periodPositionInWindowUs;
private final MediaMetadata combinedMediaMetadata;
private MediaItemData(Builder builder) {
if (builder.liveConfiguration == null) {
@ -1684,8 +1771,6 @@ public abstract class SimpleBasePlayer extends BasePlayer {
periodPositionInWindowUs[i + 1] = periodPositionInWindowUs[i] + periods.get(i).durationUs;
}
}
combinedMediaMetadata =
mediaMetadata != null ? mediaMetadata : getCombinedMediaMetadata(mediaItem, tracks);
}
/** Returns a {@link Builder} pre-populated with the current values. */
@ -1744,6 +1829,39 @@ public abstract class SimpleBasePlayer extends BasePlayer {
return result;
}
private static MediaItemData buildFromState(
State state, int mediaItemIndex, Timeline.Period period, Timeline.Window window) {
boolean isCurrentItem = getCurrentMediaItemIndexInternal(state) == mediaItemIndex;
state.timeline.getWindow(mediaItemIndex, window);
ImmutableList.Builder<PeriodData> periods = ImmutableList.builder();
for (int i = window.firstPeriodIndex; i <= window.lastPeriodIndex; i++) {
state.timeline.getPeriod(/* periodIndex= */ i, period, /* setIds= */ true);
periods.add(
new PeriodData.Builder(checkNotNull(period.uid))
.setAdPlaybackState(period.adPlaybackState)
.setDurationUs(period.durationUs)
.setIsPlaceholder(period.isPlaceholder)
.build());
}
return new MediaItemData.Builder(window.uid)
.setDefaultPositionUs(window.defaultPositionUs)
.setDurationUs(window.durationUs)
.setElapsedRealtimeEpochOffsetMs(window.elapsedRealtimeEpochOffsetMs)
.setIsDynamic(window.isDynamic)
.setIsPlaceholder(window.isPlaceholder)
.setIsSeekable(window.isSeekable)
.setLiveConfiguration(window.liveConfiguration)
.setManifest(window.manifest)
.setMediaItem(window.mediaItem)
.setMediaMetadata(isCurrentItem ? state.currentMetadata : null)
.setPeriods(periods.build())
.setPositionInFirstPeriodUs(window.positionInFirstPeriodUs)
.setPresentationStartTimeMs(window.presentationStartTimeMs)
.setTracks(isCurrentItem ? state.currentTracks : Tracks.EMPTY)
.setWindowStartTimeMs(window.windowStartTimeMs)
.build();
}
private Timeline.Window getWindow(int firstPeriodIndex, Timeline.Window window) {
int periodCount = periods.isEmpty() ? 1 : periods.size();
window.set(
@ -1799,25 +1917,6 @@ public abstract class SimpleBasePlayer extends BasePlayer {
Object periodId = periods.get(periodIndexInMediaItem).uid;
return Pair.create(uid, periodId);
}
private static MediaMetadata getCombinedMediaMetadata(MediaItem mediaItem, Tracks tracks) {
MediaMetadata.Builder metadataBuilder = new MediaMetadata.Builder();
int trackGroupCount = tracks.getGroups().size();
for (int i = 0; i < trackGroupCount; i++) {
Tracks.Group group = tracks.getGroups().get(i);
for (int j = 0; j < group.length; j++) {
if (group.isTrackSelected(j)) {
Format format = group.getTrackFormat(j);
if (format.metadata != null) {
for (int k = 0; k < format.metadata.length(); k++) {
format.metadata.get(k).populateMediaMetadata(metadataBuilder);
}
}
}
}
}
return metadataBuilder.populate(mediaItem.mediaMetadata).build();
}
}
/** Data describing the properties of a period inside a {@link MediaItemData}. */
@ -2133,7 +2232,7 @@ public abstract class SimpleBasePlayer extends BasePlayer {
placeholderPlaylist.add(getPlaceholderMediaItemData(mediaItems.get(i)));
}
return getStateWithNewPlaylistAndPosition(
state, placeholderPlaylist, startIndex, startPositionMs);
state, placeholderPlaylist, startIndex, startPositionMs, window);
});
}
@ -2143,7 +2242,7 @@ public abstract class SimpleBasePlayer extends BasePlayer {
checkArgument(index >= 0);
// Use a local copy to ensure the lambda below uses the current state value.
State state = this.state;
int playlistSize = state.playlist.size();
int playlistSize = state.timeline.getWindowCount();
if (!shouldHandleCommand(Player.COMMAND_CHANGE_MEDIA_ITEMS) || mediaItems.isEmpty()) {
return;
}
@ -2151,20 +2250,22 @@ public abstract class SimpleBasePlayer extends BasePlayer {
updateStateForPendingOperation(
/* pendingOperation= */ handleAddMediaItems(correctedIndex, mediaItems),
/* placeholderStateSupplier= */ () -> {
ArrayList<MediaItemData> placeholderPlaylist = new ArrayList<>(state.playlist);
List<MediaItemData> placeholderPlaylist =
buildMutablePlaylistFromState(state, period, window);
for (int i = 0; i < mediaItems.size(); i++) {
placeholderPlaylist.add(
i + correctedIndex, getPlaceholderMediaItemData(mediaItems.get(i)));
}
if (!state.playlist.isEmpty()) {
return getStateWithNewPlaylist(state, placeholderPlaylist, period);
if (!state.timeline.isEmpty()) {
return getStateWithNewPlaylist(state, placeholderPlaylist, period, window);
} else {
// Handle initial position update when these are the first items added to the playlist.
return getStateWithNewPlaylistAndPosition(
state,
placeholderPlaylist,
state.currentMediaItemIndex,
state.contentPositionMsSupplier.get());
state.contentPositionMsSupplier.get(),
window);
}
});
}
@ -2175,14 +2276,14 @@ public abstract class SimpleBasePlayer extends BasePlayer {
checkArgument(fromIndex >= 0 && toIndex >= fromIndex && newIndex >= 0);
// Use a local copy to ensure the lambda below uses the current state value.
State state = this.state;
int playlistSize = state.playlist.size();
int playlistSize = state.timeline.getWindowCount();
if (!shouldHandleCommand(Player.COMMAND_CHANGE_MEDIA_ITEMS)
|| playlistSize == 0
|| fromIndex >= playlistSize) {
return;
}
int correctedToIndex = min(toIndex, playlistSize);
int correctedNewIndex = min(newIndex, state.playlist.size() - (correctedToIndex - fromIndex));
int correctedNewIndex = min(newIndex, playlistSize - (correctedToIndex - fromIndex));
if (fromIndex == correctedToIndex || correctedNewIndex == fromIndex) {
return;
}
@ -2190,9 +2291,10 @@ public abstract class SimpleBasePlayer extends BasePlayer {
/* pendingOperation= */ handleMoveMediaItems(
fromIndex, correctedToIndex, correctedNewIndex),
/* placeholderStateSupplier= */ () -> {
ArrayList<MediaItemData> placeholderPlaylist = new ArrayList<>(state.playlist);
List<MediaItemData> placeholderPlaylist =
buildMutablePlaylistFromState(state, period, window);
Util.moveItems(placeholderPlaylist, fromIndex, correctedToIndex, correctedNewIndex);
return getStateWithNewPlaylist(state, placeholderPlaylist, period);
return getStateWithNewPlaylist(state, placeholderPlaylist, period, window);
});
}
@ -2201,7 +2303,7 @@ public abstract class SimpleBasePlayer extends BasePlayer {
verifyApplicationThreadAndInitState();
checkArgument(fromIndex >= 0 && fromIndex <= toIndex);
State state = this.state;
int playlistSize = state.playlist.size();
int playlistSize = state.timeline.getWindowCount();
if (!shouldHandleCommand(Player.COMMAND_CHANGE_MEDIA_ITEMS) || fromIndex > playlistSize) {
return;
}
@ -2209,14 +2311,15 @@ public abstract class SimpleBasePlayer extends BasePlayer {
updateStateForPendingOperation(
/* pendingOperation= */ handleReplaceMediaItems(fromIndex, correctedToIndex, mediaItems),
/* placeholderStateSupplier= */ () -> {
ArrayList<MediaItemData> placeholderPlaylist = new ArrayList<>(state.playlist);
List<MediaItemData> placeholderPlaylist =
buildMutablePlaylistFromState(state, period, window);
for (int i = 0; i < mediaItems.size(); i++) {
placeholderPlaylist.add(
i + correctedToIndex, getPlaceholderMediaItemData(mediaItems.get(i)));
}
State updatedState;
if (!state.playlist.isEmpty()) {
updatedState = getStateWithNewPlaylist(state, placeholderPlaylist, period);
if (!state.timeline.isEmpty()) {
updatedState = getStateWithNewPlaylist(state, placeholderPlaylist, period, window);
} else {
// Handle initial position update when these are the first items added to the playlist.
updatedState =
@ -2224,11 +2327,12 @@ public abstract class SimpleBasePlayer extends BasePlayer {
state,
placeholderPlaylist,
state.currentMediaItemIndex,
state.contentPositionMsSupplier.get());
state.contentPositionMsSupplier.get(),
window);
}
if (fromIndex < correctedToIndex) {
Util.removeRange(placeholderPlaylist, fromIndex, correctedToIndex);
return getStateWithNewPlaylist(updatedState, placeholderPlaylist, period);
return getStateWithNewPlaylist(updatedState, placeholderPlaylist, period, window);
} else {
return updatedState;
}
@ -2241,7 +2345,7 @@ public abstract class SimpleBasePlayer extends BasePlayer {
checkArgument(fromIndex >= 0 && toIndex >= fromIndex);
// Use a local copy to ensure the lambda below uses the current state value.
State state = this.state;
int playlistSize = state.playlist.size();
int playlistSize = state.timeline.getWindowCount();
if (!shouldHandleCommand(Player.COMMAND_CHANGE_MEDIA_ITEMS)
|| playlistSize == 0
|| fromIndex >= playlistSize) {
@ -2254,9 +2358,10 @@ public abstract class SimpleBasePlayer extends BasePlayer {
updateStateForPendingOperation(
/* pendingOperation= */ handleRemoveMediaItems(fromIndex, correctedToIndex),
/* placeholderStateSupplier= */ () -> {
ArrayList<MediaItemData> placeholderPlaylist = new ArrayList<>(state.playlist);
List<MediaItemData> placeholderPlaylist =
buildMutablePlaylistFromState(state, period, window);
Util.removeRange(placeholderPlaylist, fromIndex, correctedToIndex);
return getStateWithNewPlaylist(state, placeholderPlaylist, period);
return getStateWithNewPlaylist(state, placeholderPlaylist, period, window);
});
}
@ -2361,14 +2466,14 @@ public abstract class SimpleBasePlayer extends BasePlayer {
boolean ignoreSeekForPlaceholderState =
mediaItemIndex == C.INDEX_UNSET
|| isPlayingAd()
|| (!state.playlist.isEmpty() && mediaItemIndex >= state.playlist.size());
|| (!state.timeline.isEmpty() && mediaItemIndex >= state.timeline.getWindowCount());
updateStateForPendingOperation(
/* pendingOperation= */ handleSeek(mediaItemIndex, positionMs, seekCommand),
/* placeholderStateSupplier= */ () ->
ignoreSeekForPlaceholderState
? state
: getStateWithNewPlaylistAndPosition(
state, state.playlist, mediaItemIndex, positionMs),
state, /* newPlaylist= */ null, mediaItemIndex, positionMs, window),
/* forceSeekDiscontinuity= */ !ignoreSeekForPlaceholderState,
isRepeatingCurrentItem);
}
@ -2427,7 +2532,7 @@ public abstract class SimpleBasePlayer extends BasePlayer {
.setPlaybackState(Player.STATE_IDLE)
.setTotalBufferedDurationMs(PositionSupplier.ZERO)
.setContentBufferedPositionMs(
PositionSupplier.getConstant(getContentPositionMsInternal(state)))
PositionSupplier.getConstant(getContentPositionMsInternal(state, window)))
.setAdBufferedPositionMs(state.adPositionMsSupplier)
.setIsLoading(false)
.build());
@ -2452,7 +2557,7 @@ public abstract class SimpleBasePlayer extends BasePlayer {
.setPlaybackState(Player.STATE_IDLE)
.setTotalBufferedDurationMs(PositionSupplier.ZERO)
.setContentBufferedPositionMs(
PositionSupplier.getConstant(getContentPositionMsInternal(state)))
PositionSupplier.getConstant(getContentPositionMsInternal(state, window)))
.setAdBufferedPositionMs(state.adPositionMsSupplier)
.setIsLoading(false)
.build();
@ -2461,7 +2566,7 @@ public abstract class SimpleBasePlayer extends BasePlayer {
@Override
public final Tracks getCurrentTracks() {
verifyApplicationThreadAndInitState();
return getCurrentTracksInternal(state);
return state.currentTracks;
}
@Override
@ -2487,7 +2592,7 @@ public abstract class SimpleBasePlayer extends BasePlayer {
@Override
public final MediaMetadata getMediaMetadata() {
verifyApplicationThreadAndInitState();
return getMediaMetadataInternal(state);
return state.currentMetadata;
}
@Override
@ -2581,13 +2686,15 @@ public abstract class SimpleBasePlayer extends BasePlayer {
@Override
public final long getContentPosition() {
verifyApplicationThreadAndInitState();
return getContentPositionMsInternal(state);
return getContentPositionMsInternal(state, window);
}
@Override
public final long getContentBufferedPosition() {
verifyApplicationThreadAndInitState();
return max(getContentBufferedPositionMsInternal(state), getContentPositionMsInternal(state));
return max(
getContentBufferedPositionMsInternal(state, window),
getContentPositionMsInternal(state, window));
}
@Override
@ -3184,7 +3291,7 @@ public abstract class SimpleBasePlayer extends BasePlayer {
* Player#setDeviceMuted(boolean, int)}.
*
* <p>Will only be called if {@link Player#COMMAND_ADJUST_DEVICE_VOLUME} or {@link
* Player#COMMAND_ADJUST_DEVICE_VOLUME} is available.
* Player#COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS} is available.
*
* @param muted Whether the device was requested to be muted.
* @param flags Either 0 or a bitwise combination of one or more {@link C.VolumeFlags}.
@ -3410,10 +3517,6 @@ public abstract class SimpleBasePlayer extends BasePlayer {
boolean playWhenReadyChanged = previousState.playWhenReady != newState.playWhenReady;
boolean playbackStateChanged = previousState.playbackState != newState.playbackState;
Tracks previousTracks = getCurrentTracksInternal(previousState);
Tracks newTracks = getCurrentTracksInternal(newState);
MediaMetadata previousMediaMetadata = getMediaMetadataInternal(previousState);
MediaMetadata newMediaMetadata = getMediaMetadataInternal(newState);
int positionDiscontinuityReason =
getPositionDiscontinuityReason(
previousState, newState, forceSeekDiscontinuity, window, period);
@ -3424,7 +3527,8 @@ public abstract class SimpleBasePlayer extends BasePlayer {
if (timelineChanged) {
@Player.TimelineChangeReason
int timelineChangeReason = getTimelineChangeReason(previousState.playlist, newState.playlist);
int timelineChangeReason =
getTimelineChangeReason(previousState.timeline, newState.timeline, window);
listeners.queueEvent(
Player.EVENT_TIMELINE_CHANGED,
listener -> listener.onTimelineChanged(newState.timeline, timelineChangeReason));
@ -3451,7 +3555,8 @@ public abstract class SimpleBasePlayer extends BasePlayer {
MediaItem mediaItem =
newState.timeline.isEmpty()
? null
: newState.playlist.get(getCurrentMediaItemIndexInternal(newState)).mediaItem;
: newState.timeline.getWindow(getCurrentMediaItemIndexInternal(newState), window)
.mediaItem;
listeners.queueEvent(
Player.EVENT_MEDIA_ITEM_TRANSITION,
listener -> listener.onMediaItemTransition(mediaItem, mediaItemTransitionReason));
@ -3472,14 +3577,15 @@ public abstract class SimpleBasePlayer extends BasePlayer {
listener ->
listener.onTrackSelectionParametersChanged(newState.trackSelectionParameters));
}
if (!previousTracks.equals(newTracks)) {
if (!previousState.currentTracks.equals(newState.currentTracks)) {
listeners.queueEvent(
Player.EVENT_TRACKS_CHANGED, listener -> listener.onTracksChanged(newTracks));
Player.EVENT_TRACKS_CHANGED,
listener -> listener.onTracksChanged(newState.currentTracks));
}
if (!previousMediaMetadata.equals(newMediaMetadata)) {
if (!previousState.currentMetadata.equals(newState.currentMetadata)) {
listeners.queueEvent(
EVENT_MEDIA_METADATA_CHANGED,
listener -> listener.onMediaMetadataChanged(newMediaMetadata));
listener -> listener.onMediaMetadataChanged(newState.currentMetadata));
}
if (previousState.isLoading != newState.isLoading) {
listeners.queueEvent(
@ -3675,18 +3781,6 @@ public abstract class SimpleBasePlayer extends BasePlayer {
&& state.playbackSuppressionReason == PLAYBACK_SUPPRESSION_REASON_NONE;
}
private static Tracks getCurrentTracksInternal(State state) {
return state.playlist.isEmpty()
? Tracks.EMPTY
: state.playlist.get(getCurrentMediaItemIndexInternal(state)).tracks;
}
private static MediaMetadata getMediaMetadataInternal(State state) {
return state.playlist.isEmpty()
? MediaMetadata.EMPTY
: state.playlist.get(getCurrentMediaItemIndexInternal(state)).combinedMediaMetadata;
}
private static int getCurrentMediaItemIndexInternal(State state) {
if (state.currentMediaItemIndex != C.INDEX_UNSET) {
return state.currentMediaItemIndex;
@ -3694,22 +3788,27 @@ public abstract class SimpleBasePlayer extends BasePlayer {
return 0; // TODO: Use shuffle order to get first item if playlist is not empty.
}
private static long getContentPositionMsInternal(State state) {
return getPositionOrDefaultInMediaItem(state.contentPositionMsSupplier.get(), state);
private static long getContentPositionMsInternal(State state, Timeline.Window window) {
return getPositionOrDefaultInMediaItem(state.contentPositionMsSupplier.get(), state, window);
}
private static long getContentBufferedPositionMsInternal(State state) {
return getPositionOrDefaultInMediaItem(state.contentBufferedPositionMsSupplier.get(), state);
private static long getContentBufferedPositionMsInternal(State state, Timeline.Window window) {
return getPositionOrDefaultInMediaItem(
state.contentBufferedPositionMsSupplier.get(), state, window);
}
private static long getPositionOrDefaultInMediaItem(long positionMs, State state) {
private static long getPositionOrDefaultInMediaItem(
long positionMs, State state, Timeline.Window window) {
if (positionMs != C.TIME_UNSET) {
return positionMs;
}
if (state.playlist.isEmpty()) {
if (state.timeline.isEmpty()) {
return 0;
}
return usToMs(state.playlist.get(getCurrentMediaItemIndexInternal(state)).defaultPositionUs);
return state
.timeline
.getWindow(getCurrentMediaItemIndexInternal(state), window)
.getDefaultPositionMs();
}
private static int getCurrentPeriodIndexInternal(
@ -3719,7 +3818,11 @@ public abstract class SimpleBasePlayer extends BasePlayer {
return currentMediaItemIndex;
}
return getPeriodIndexFromWindowPosition(
state.timeline, currentMediaItemIndex, getContentPositionMsInternal(state), window, period);
state.timeline,
currentMediaItemIndex,
getContentPositionMsInternal(state, window),
window,
period);
}
private static int getPeriodIndexFromWindowPosition(
@ -3734,13 +3837,13 @@ public abstract class SimpleBasePlayer extends BasePlayer {
}
private static @Player.TimelineChangeReason int getTimelineChangeReason(
List<MediaItemData> previousPlaylist, List<MediaItemData> newPlaylist) {
if (previousPlaylist.size() != newPlaylist.size()) {
Timeline previousTimeline, Timeline newTimeline, Timeline.Window window) {
if (previousTimeline.getWindowCount() != newTimeline.getWindowCount()) {
return Player.TIMELINE_CHANGE_REASON_PLAYLIST_CHANGED;
}
for (int i = 0; i < previousPlaylist.size(); i++) {
Object previousUid = previousPlaylist.get(i).uid;
Object newUid = newPlaylist.get(i).uid;
for (int i = 0; i < previousTimeline.getWindowCount(); i++) {
Object previousUid = previousTimeline.getWindow(/* windowIndex= */ i, window).uid;
Object newUid = newTimeline.getWindow(/* windowIndex= */ i, window).uid;
boolean resolvedAutoGeneratedPlaceholder =
previousUid instanceof PlaceholderUid && !(newUid instanceof PlaceholderUid);
if (!previousUid.equals(newUid) && !resolvedAutoGeneratedPlaceholder) {
@ -3763,11 +3866,11 @@ public abstract class SimpleBasePlayer extends BasePlayer {
if (forceSeekDiscontinuity) {
return Player.DISCONTINUITY_REASON_SEEK;
}
if (previousState.playlist.isEmpty()) {
if (previousState.timeline.isEmpty()) {
// First change from an empty playlist is not reported as a discontinuity.
return C.INDEX_UNSET;
}
if (newState.playlist.isEmpty()) {
if (newState.timeline.isEmpty()) {
// The playlist became empty.
return Player.DISCONTINUITY_REASON_REMOVE;
}
@ -3790,7 +3893,7 @@ public abstract class SimpleBasePlayer extends BasePlayer {
}
// Check if reached the previous period's or ad's duration to assume an auto-transition.
long previousPositionMs =
getCurrentPeriodOrAdPositionMs(previousState, previousPeriodUid, period);
getCurrentPeriodOrAdPositionMs(previousState, previousPeriodUid, period, window);
long previousDurationMs = getPeriodOrAdDurationMs(previousState, previousPeriodUid, period);
return previousDurationMs != C.TIME_UNSET && previousPositionMs >= previousDurationMs
? Player.DISCONTINUITY_REASON_AUTO_TRANSITION
@ -3799,8 +3902,8 @@ public abstract class SimpleBasePlayer extends BasePlayer {
// We are in the same content period or ad. Check if the position deviates more than a
// reasonable threshold from the previous one.
long previousPositionMs =
getCurrentPeriodOrAdPositionMs(previousState, previousPeriodUid, period);
long newPositionMs = getCurrentPeriodOrAdPositionMs(newState, newPeriodUid, period);
getCurrentPeriodOrAdPositionMs(previousState, previousPeriodUid, period, window);
long newPositionMs = getCurrentPeriodOrAdPositionMs(newState, newPeriodUid, period, window);
if (Math.abs(previousPositionMs - newPositionMs) < POSITION_DISCONTINUITY_THRESHOLD_MS) {
return C.INDEX_UNSET;
}
@ -3812,10 +3915,10 @@ public abstract class SimpleBasePlayer extends BasePlayer {
}
private static long getCurrentPeriodOrAdPositionMs(
State state, Object currentPeriodUid, Timeline.Period period) {
State state, Object currentPeriodUid, Timeline.Period period, Timeline.Window window) {
return state.currentAdGroupIndex != C.INDEX_UNSET
? state.adPositionMsSupplier.get()
: getContentPositionMsInternal(state)
: getContentPositionMsInternal(state, window)
- state.timeline.getPeriodByUid(currentPeriodUid, period).getPositionInWindowMs();
}
@ -3852,9 +3955,9 @@ public abstract class SimpleBasePlayer extends BasePlayer {
contentPositionMs =
state.currentAdGroupIndex == C.INDEX_UNSET
? positionMs
: getContentPositionMsInternal(state);
: getContentPositionMsInternal(state, window);
} else {
contentPositionMs = getContentPositionMsInternal(state);
contentPositionMs = getContentPositionMsInternal(state, window);
positionMs =
state.currentAdGroupIndex != C.INDEX_UNSET
? state.adPositionMsSupplier.get()
@ -3905,9 +4008,14 @@ 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
&& getContentPositionMsInternal(previousState) > getContentPositionMsInternal(newState)) {
return MEDIA_ITEM_TRANSITION_REASON_REPEAT;
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_SEEK && isRepeatingCurrentItem) {
return MEDIA_ITEM_TRANSITION_REASON_SEEK;
@ -3924,38 +4032,42 @@ public abstract class SimpleBasePlayer extends BasePlayer {
}
private static int getMediaItemIndexInNewPlaylist(
List<MediaItemData> oldPlaylist,
Timeline newPlaylistTimeline,
Timeline oldTimeline,
Timeline newTimeline,
int oldMediaItemIndex,
Timeline.Period period) {
if (oldPlaylist.isEmpty()) {
return oldMediaItemIndex < newPlaylistTimeline.getWindowCount()
? oldMediaItemIndex
: C.INDEX_UNSET;
Timeline.Period period,
Timeline.Window window) {
if (oldTimeline.isEmpty()) {
return oldMediaItemIndex < newTimeline.getWindowCount() ? oldMediaItemIndex : C.INDEX_UNSET;
}
int oldFirstPeriodIndex = oldTimeline.getWindow(oldMediaItemIndex, window).firstPeriodIndex;
Object oldFirstPeriodUid =
oldPlaylist.get(oldMediaItemIndex).getPeriodUid(/* periodIndexInMediaItem= */ 0);
if (newPlaylistTimeline.getIndexOfPeriod(oldFirstPeriodUid) == C.INDEX_UNSET) {
checkNotNull(oldTimeline.getPeriod(oldFirstPeriodIndex, period, /* setIds= */ true).uid);
if (newTimeline.getIndexOfPeriod(oldFirstPeriodUid) == C.INDEX_UNSET) {
return C.INDEX_UNSET;
}
return newPlaylistTimeline.getPeriodByUid(oldFirstPeriodUid, period).windowIndex;
return newTimeline.getPeriodByUid(oldFirstPeriodUid, period).windowIndex;
}
private static State getStateWithNewPlaylist(
State oldState, List<MediaItemData> newPlaylist, Timeline.Period period) {
State oldState,
List<MediaItemData> newPlaylist,
Timeline.Period period,
Timeline.Window window) {
State.Builder stateBuilder = oldState.buildUpon();
stateBuilder.setPlaylist(newPlaylist);
Timeline newTimeline = stateBuilder.timeline;
Timeline newTimeline = new PlaylistTimeline(newPlaylist);
Timeline oldTimeline = oldState.timeline;
long oldPositionMs = oldState.contentPositionMsSupplier.get();
int oldIndex = getCurrentMediaItemIndexInternal(oldState);
int newIndex = getMediaItemIndexInNewPlaylist(oldState.playlist, newTimeline, oldIndex, period);
int newIndex =
getMediaItemIndexInNewPlaylist(oldTimeline, newTimeline, oldIndex, period, window);
long newPositionMs = newIndex == C.INDEX_UNSET ? C.TIME_UNSET : oldPositionMs;
// If the current item no longer exists, try to find a matching subsequent item.
for (int i = oldIndex + 1; newIndex == C.INDEX_UNSET && i < oldState.playlist.size(); i++) {
for (int i = oldIndex + 1; newIndex == C.INDEX_UNSET && i < oldTimeline.getWindowCount(); i++) {
// TODO: Use shuffle order to iterate.
newIndex =
getMediaItemIndexInNewPlaylist(
oldState.playlist, newTimeline, /* oldMediaItemIndex= */ i, period);
oldTimeline, newTimeline, /* oldMediaItemIndex= */ i, period, window);
}
// If this fails, transition to ENDED state.
if (oldState.playbackState != Player.STATE_IDLE && newIndex == C.INDEX_UNSET) {
@ -3965,18 +4077,25 @@ public abstract class SimpleBasePlayer extends BasePlayer {
stateBuilder,
oldState,
oldPositionMs,
newPlaylist,
newTimeline,
newIndex,
newPositionMs,
/* keepAds= */ true);
/* keepAds= */ true,
window);
}
private static State getStateWithNewPlaylistAndPosition(
State oldState, List<MediaItemData> newPlaylist, int newIndex, long newPositionMs) {
State oldState,
@Nullable List<MediaItemData> newPlaylist,
int newIndex,
long newPositionMs,
Timeline.Window window) {
State.Builder stateBuilder = oldState.buildUpon();
stateBuilder.setPlaylist(newPlaylist);
Timeline newTimeline =
newPlaylist == null ? oldState.timeline : new PlaylistTimeline(newPlaylist);
if (oldState.playbackState != Player.STATE_IDLE) {
if (newPlaylist.isEmpty() || (newIndex != C.INDEX_UNSET && newIndex >= newPlaylist.size())) {
if (newTimeline.isEmpty()
|| (newIndex != C.INDEX_UNSET && newIndex >= newTimeline.getWindowCount())) {
stateBuilder.setPlaybackState(Player.STATE_ENDED).setIsLoading(false);
} else {
stateBuilder.setPlaybackState(Player.STATE_BUFFERING);
@ -3987,37 +4106,53 @@ public abstract class SimpleBasePlayer extends BasePlayer {
stateBuilder,
oldState,
oldPositionMs,
newPlaylist,
newTimeline,
newIndex,
newPositionMs,
/* keepAds= */ false);
/* keepAds= */ false,
window);
}
private static State buildStateForNewPosition(
State.Builder stateBuilder,
State oldState,
long oldPositionMs,
List<MediaItemData> newPlaylist,
Timeline newTimeline,
int newIndex,
long newPositionMs,
boolean keepAds) {
boolean keepAds,
Timeline.Window window) {
// Resolve unset or invalid index and position.
oldPositionMs = getPositionOrDefaultInMediaItem(oldPositionMs, oldState);
if (!newPlaylist.isEmpty() && (newIndex == C.INDEX_UNSET || newIndex >= newPlaylist.size())) {
oldPositionMs = getPositionOrDefaultInMediaItem(oldPositionMs, oldState, window);
if (!newTimeline.isEmpty()
&& (newIndex == C.INDEX_UNSET || newIndex >= newTimeline.getWindowCount())) {
newIndex = 0; // TODO: Use shuffle order to get first index.
newPositionMs = C.TIME_UNSET;
}
if (!newPlaylist.isEmpty() && newPositionMs == C.TIME_UNSET) {
newPositionMs = usToMs(newPlaylist.get(newIndex).defaultPositionUs);
if (!newTimeline.isEmpty() && newPositionMs == C.TIME_UNSET) {
newPositionMs = newTimeline.getWindow(newIndex, window).getDefaultPositionMs();
}
boolean oldOrNewPlaylistEmpty = oldState.playlist.isEmpty() || newPlaylist.isEmpty();
boolean oldOrNewPlaylistEmpty = oldState.timeline.isEmpty() || newTimeline.isEmpty();
boolean mediaItemChanged =
!oldOrNewPlaylistEmpty
&& !oldState
.playlist
.get(getCurrentMediaItemIndexInternal(oldState))
.timeline
.getWindow(getCurrentMediaItemIndexInternal(oldState), window)
.uid
.equals(newPlaylist.get(newIndex).uid);
.equals(newTimeline.getWindow(newIndex, window).uid);
// Set timeline, resolving tracks and metadata to the new index.
if (newTimeline.isEmpty()) {
stateBuilder.setPlaylist(newTimeline, Tracks.EMPTY, /* currentMetadata= */ null);
} else if (newTimeline instanceof PlaylistTimeline) {
MediaItemData mediaItemData = ((PlaylistTimeline) newTimeline).playlist.get(newIndex);
stateBuilder.setPlaylist(newTimeline, mediaItemData.tracks, mediaItemData.mediaMetadata);
} else {
boolean keepTracksAndMetadata = !oldOrNewPlaylistEmpty && !mediaItemChanged;
stateBuilder.setPlaylist(
newTimeline,
keepTracksAndMetadata ? oldState.currentTracks : Tracks.EMPTY,
keepTracksAndMetadata ? oldState.currentMetadata : null);
}
if (oldOrNewPlaylistEmpty || mediaItemChanged || newPositionMs < oldPositionMs) {
// New item or seeking back. Assume no buffer and no ad playback persists.
stateBuilder
@ -4038,12 +4173,12 @@ public abstract class SimpleBasePlayer extends BasePlayer {
.setCurrentAd(C.INDEX_UNSET, C.INDEX_UNSET)
.setTotalBufferedDurationMs(
PositionSupplier.getConstant(
getContentBufferedPositionMsInternal(oldState) - oldPositionMs));
getContentBufferedPositionMsInternal(oldState, window) - oldPositionMs));
}
} else {
// Seeking forward. Assume remaining buffer in current item persist, but no ad playback.
long contentBufferedDurationMs =
max(getContentBufferedPositionMsInternal(oldState), newPositionMs);
max(getContentBufferedPositionMsInternal(oldState, window), newPositionMs);
long totalBufferedDurationMs =
max(0, oldState.totalBufferedDurationMsSupplier.get() - (newPositionMs - oldPositionMs));
stateBuilder
@ -4056,5 +4191,36 @@ public abstract class SimpleBasePlayer extends BasePlayer {
return stateBuilder.build();
}
private static MediaMetadata getCombinedMediaMetadata(MediaItem mediaItem, Tracks tracks) {
MediaMetadata.Builder metadataBuilder = new MediaMetadata.Builder();
int trackGroupCount = tracks.getGroups().size();
for (int i = 0; i < trackGroupCount; i++) {
Tracks.Group group = tracks.getGroups().get(i);
for (int j = 0; j < group.length; j++) {
if (group.isTrackSelected(j)) {
Format format = group.getTrackFormat(j);
if (format.metadata != null) {
for (int k = 0; k < format.metadata.length(); k++) {
format.metadata.get(k).populateMediaMetadata(metadataBuilder);
}
}
}
}
}
return metadataBuilder.populate(mediaItem.mediaMetadata).build();
}
private static List<MediaItemData> buildMutablePlaylistFromState(
State state, Timeline.Period period, Timeline.Window window) {
if (state.timeline instanceof PlaylistTimeline) {
return new ArrayList<>(((PlaylistTimeline) state.timeline).playlist);
}
ArrayList<MediaItemData> items = new ArrayList<>(state.timeline.getWindowCount());
for (int i = 0; i < state.timeline.getWindowCount(); i++) {
items.add(MediaItemData.buildFromState(state, /* mediaItemIndex= */ i, period, window));
}
return items;
}
private static final class PlaceholderUid {}
}

View file

@ -41,6 +41,9 @@ public final class SurfaceInfo {
*/
public final int orientationDegrees;
/** Whether the {@link #surface} is an encoder input surface. */
public final boolean isEncoderInputSurface;
/** Creates a new instance. */
public SurfaceInfo(Surface surface, int width, int height) {
this(surface, width, height, /* orientationDegrees= */ 0);
@ -48,6 +51,16 @@ public final class SurfaceInfo {
/** Creates a new instance. */
public SurfaceInfo(Surface surface, int width, int height, int orientationDegrees) {
this(surface, width, height, orientationDegrees, /* isEncoderInputSurface= */ false);
}
/** Creates a new instance. */
public SurfaceInfo(
Surface surface,
int width,
int height,
int orientationDegrees,
boolean isEncoderInputSurface) {
checkArgument(
orientationDegrees == 0
|| orientationDegrees == 90
@ -58,6 +71,7 @@ public final class SurfaceInfo {
this.width = width;
this.height = height;
this.orientationDegrees = orientationDegrees;
this.isEncoderInputSurface = isEncoderInputSurface;
}
@Override
@ -72,6 +86,7 @@ public final class SurfaceInfo {
return width == that.width
&& height == that.height
&& orientationDegrees == that.orientationDegrees
&& isEncoderInputSurface == that.isEncoderInputSurface
&& surface.equals(that.surface);
}
@ -81,6 +96,7 @@ public final class SurfaceInfo {
result = 31 * result + width;
result = 31 * result + height;
result = 31 * result + orientationDegrees;
result = 31 * result + (isEncoderInputSurface ? 1 : 0);
return result;
}
}

View file

@ -575,7 +575,8 @@ public abstract class Timeline {
*/
public boolean isPlaceholder;
private AdPlaybackState adPlaybackState;
/** The {@link AdPlaybackState} for all ads in this period. */
@UnstableApi public AdPlaybackState adPlaybackState;
/** Creates a new instance with no ad playback state. */
public Period() {

View file

@ -390,5 +390,4 @@ public final class Tracks {
: BundleCollectionUtil.fromBundleList(Group::fromBundle, groupBundles);
return new Tracks(groups);
}
;
}

View file

@ -19,6 +19,7 @@ import static java.lang.annotation.ElementType.TYPE_USE;
import android.content.Context;
import android.graphics.Bitmap;
import android.graphics.SurfaceTexture;
import android.opengl.EGLExt;
import android.view.Surface;
import androidx.annotation.IntDef;
@ -48,12 +49,18 @@ import java.util.concurrent.Executor;
public interface VideoFrameProcessor {
/**
* Specifies how the input frames are made available to the {@link VideoFrameProcessor}. One of
* {@link #INPUT_TYPE_SURFACE}, {@link #INPUT_TYPE_BITMAP} or {@link #INPUT_TYPE_TEXTURE_ID}.
* {@link #INPUT_TYPE_SURFACE}, {@link #INPUT_TYPE_BITMAP}, {@link #INPUT_TYPE_TEXTURE_ID} or
* {@link #INPUT_TYPE_SURFACE_AUTOMATIC_FRAME_REGISTRATION}.
*/
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target(TYPE_USE)
@IntDef({INPUT_TYPE_SURFACE, INPUT_TYPE_BITMAP, INPUT_TYPE_TEXTURE_ID})
@IntDef({
INPUT_TYPE_SURFACE,
INPUT_TYPE_BITMAP,
INPUT_TYPE_TEXTURE_ID,
INPUT_TYPE_SURFACE_AUTOMATIC_FRAME_REGISTRATION,
})
@interface InputType {}
/**
@ -73,6 +80,16 @@ public interface VideoFrameProcessor {
*/
int INPUT_TYPE_TEXTURE_ID = 3;
/**
* 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
* android.graphics.SurfaceTexture#setDefaultBufferSize(int, int) default buffer size}.
*/
int INPUT_TYPE_SURFACE_AUTOMATIC_FRAME_REGISTRATION = 4;
/** A factory for {@link VideoFrameProcessor} instances. */
interface Factory {
@ -126,8 +143,8 @@ public interface VideoFrameProcessor {
* @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.
*/
void onInputStreamRegistered(
@InputType int inputType, List<Effect> effects, FrameInfo frameInfo);
default void onInputStreamRegistered(
@InputType int inputType, List<Effect> effects, FrameInfo frameInfo) {}
/**
* Called when the output size changes.
@ -138,7 +155,7 @@ public interface VideoFrameProcessor {
* <p>The output size may differ from the size specified using {@link
* #setOutputSurfaceInfo(SurfaceInfo)}.
*/
void onOutputSizeChanged(int width, int height);
default void onOutputSizeChanged(int width, int height) {}
/**
* Called when an output frame with the given {@code presentationTimeUs} becomes available for
@ -146,7 +163,7 @@ public interface VideoFrameProcessor {
*
* @param presentationTimeUs The presentation time of the frame, in microseconds.
*/
void onOutputFrameAvailableForRendering(long presentationTimeUs);
default void onOutputFrameAvailableForRendering(long presentationTimeUs) {}
/**
* Called when an exception occurs during asynchronous video frame processing.
@ -154,10 +171,10 @@ public interface VideoFrameProcessor {
* <p>If this is called, the calling {@link VideoFrameProcessor} must immediately be {@linkplain
* VideoFrameProcessor#release() released}.
*/
void onError(VideoFrameProcessingException exception);
default void onError(VideoFrameProcessingException exception) {}
/** Called after the {@link VideoFrameProcessor} has rendered its final output frame. */
void onEnded();
default void onEnded() {}
}
/**
@ -169,6 +186,13 @@ public interface VideoFrameProcessor {
/** Indicates the frame should be dropped after {@link #renderOutputFrame(long)} is invoked. */
long DROP_OUTPUT_FRAME = -2;
/**
* Indicates the frame should preserve the input presentation time when {@link
* #renderOutputFrame(long)} is invoked.
*/
@SuppressWarnings("GoodTime-ApiWithNumericTimeUnit") // This is a named constant, not a time unit.
long RENDER_OUTPUT_FRAME_WITH_PRESENTATION_TIME = -3;
/**
* Provides an input {@link Bitmap} to the {@link VideoFrameProcessor}.
*
@ -206,6 +230,14 @@ public interface VideoFrameProcessor {
*/
void setOnInputFrameProcessedListener(OnInputFrameProcessedListener listener);
/**
* Sets a listener that's called when the {@linkplain #getInputSurface() input surface} is ready
* to use.
*/
void setOnInputSurfaceReadyListener(Runnable listener);
// TODO: b/351776002 - Call setDefaultBufferSize on the INPUT_TYPE_SURFACE path too and remove
// mentions of the method (which leak an implementation detail) throughout this file.
/**
* Returns the input {@link Surface}, where {@link VideoFrameProcessor} consumes input frames
* from.
@ -214,6 +246,16 @@ public interface VideoFrameProcessor {
* VideoFrameProcessor} until {@link #registerInputStream} is called with {@link
* #INPUT_TYPE_SURFACE}.
*
* <p>For streams with {@link #INPUT_TYPE_SURFACE}, the returned surface is ready to use
* immediately and will not have a {@linkplain SurfaceTexture#setDefaultBufferSize(int, int)
* default buffer size} set on it. This is suitable for configuring a {@link
* android.media.MediaCodec} decoder.
*
* <p>For streams with {@link #INPUT_TYPE_SURFACE_AUTOMATIC_FRAME_REGISTRATION}, set a listener
* for the surface becoming ready via {@link #setOnInputSurfaceReadyListener(Runnable)} and wait
* for the event before using the returned surface. This is suitable for use with non-decoder
* producers like media projection.
*
* @throws UnsupportedOperationException If the {@code VideoFrameProcessor} does not accept
* {@linkplain #INPUT_TYPE_SURFACE surface input}.
*/
@ -298,7 +340,10 @@ public interface VideoFrameProcessor {
*
* @param renderTimeNs The render time to use for the frame, in nanoseconds. The render time can
* be before or after the current system time. Use {@link #DROP_OUTPUT_FRAME} to drop the
* frame, or {@link #RENDER_OUTPUT_FRAME_IMMEDIATELY} to render the frame immediately.
* frame, or {@link #RENDER_OUTPUT_FRAME_IMMEDIATELY} to render the frame immediately, or
* {@link #RENDER_OUTPUT_FRAME_WITH_PRESENTATION_TIME} to render the frame to the {@linkplain
* #setOutputSurfaceInfo output surface} with the presentation timestamp seen in {@link
* Listener#onOutputFrameAvailableForRendering(long)}.
*/
void renderOutputFrame(long renderTimeNs);

View file

@ -20,7 +20,7 @@ import androidx.annotation.IntRange;
import androidx.annotation.Nullable;
import androidx.media3.common.util.UnstableApi;
/** Represents a graph for processing decoded video frames. */
/** Represents a graph for processing raw video frames. */
@UnstableApi
public interface VideoGraph {
@ -33,7 +33,7 @@ public interface VideoGraph {
* @param width The new output width in pixels.
* @param height The new output width in pixels.
*/
void onOutputSizeChanged(int width, int height);
default void onOutputSizeChanged(int width, int height) {}
/**
* Called when an output frame with the given {@code framePresentationTimeUs} becomes available
@ -41,14 +41,14 @@ public interface VideoGraph {
*
* @param framePresentationTimeUs The presentation time of the frame, in microseconds.
*/
void onOutputFrameAvailableForRendering(long framePresentationTimeUs);
default void onOutputFrameAvailableForRendering(long framePresentationTimeUs) {}
/**
* Called after the {@link VideoGraph} has rendered its final output frame.
*
* @param finalFramePresentationTimeUs The timestamp of the last output frame, in microseconds.
*/
void onEnded(long finalFramePresentationTimeUs);
default void onEnded(long finalFramePresentationTimeUs) {}
/**
* Called when an exception occurs during video frame processing.
@ -56,7 +56,7 @@ public interface VideoGraph {
* <p>If this is called, the calling {@link VideoGraph} must immediately be {@linkplain
* #release() released}.
*/
void onError(VideoFrameProcessingException exception);
default void onError(VideoFrameProcessingException exception) {}
}
/**

View file

@ -27,7 +27,6 @@ public final class VideoSize {
private static final int DEFAULT_WIDTH = 0;
private static final int DEFAULT_HEIGHT = 0;
private static final int DEFAULT_UNAPPLIED_ROTATION_DEGREES = 0;
private static final float DEFAULT_PIXEL_WIDTH_HEIGHT_RATIO = 1F;
public static final VideoSize UNKNOWN = new VideoSize(DEFAULT_WIDTH, DEFAULT_HEIGHT);
@ -41,19 +40,10 @@ public final class VideoSize {
public final int height;
/**
* Clockwise rotation in degrees that the application should apply for the video for it to be
* rendered in the correct orientation.
*
* <p>Is 0 if unknown or if no rotation is needed.
*
* <p>Player should apply video rotation internally, in which case unappliedRotationDegrees is 0.
* But when a player can't apply the rotation, for example before API level 21, the unapplied
* rotation is reported by this field for application to handle.
*
* <p>Applications that use {@link android.view.TextureView} can apply the rotation by calling
* {@link android.view.TextureView#setTransform}.
* @deprecated Rotation is handled internally by the player, so this is always zero.
*/
@IntRange(from = 0, to = 359)
@Deprecated
public final int unappliedRotationDegrees;
/**
@ -73,7 +63,7 @@ public final class VideoSize {
*/
@UnstableApi
public VideoSize(@IntRange(from = 0) int width, @IntRange(from = 0) int height) {
this(width, height, DEFAULT_UNAPPLIED_ROTATION_DEGREES, DEFAULT_PIXEL_WIDTH_HEIGHT_RATIO);
this(width, height, DEFAULT_PIXEL_WIDTH_HEIGHT_RATIO);
}
/**
@ -81,23 +71,34 @@ public final class VideoSize {
*
* @param width The video width in pixels.
* @param height The video height in pixels.
* @param unappliedRotationDegrees Clockwise rotation in degrees that the application should apply
* for the video for it to be rendered in the correct orientation. See {@link
* #unappliedRotationDegrees}.
* @param pixelWidthHeightRatio The width to height ratio of each pixel. For the normal case of
* square pixels this will be equal to 1.0. Different values are indicative of anamorphic
* content.
*/
@SuppressWarnings("deprecation") // Setting deprecated field
@UnstableApi
public VideoSize(
@IntRange(from = 0) int width,
@IntRange(from = 0) int height,
@FloatRange(from = 0, fromInclusive = false) float pixelWidthHeightRatio) {
this.width = width;
this.height = height;
this.unappliedRotationDegrees = 0;
this.pixelWidthHeightRatio = pixelWidthHeightRatio;
}
/**
* @deprecated Use {@link VideoSize#VideoSize(int, int, float)} instead. {@code
* unappliedRotationDegrees} is not needed on API 21+ and is always zero.
*/
@Deprecated
@UnstableApi
public VideoSize(
@IntRange(from = 0) int width,
@IntRange(from = 0) int height,
@IntRange(from = 0, to = 359) int unappliedRotationDegrees,
@FloatRange(from = 0, fromInclusive = false) float pixelWidthHeightRatio) {
this.width = width;
this.height = height;
this.unappliedRotationDegrees = unappliedRotationDegrees;
this.pixelWidthHeightRatio = pixelWidthHeightRatio;
this(width, height, pixelWidthHeightRatio);
}
@Override
@ -109,7 +110,6 @@ public final class VideoSize {
VideoSize other = (VideoSize) obj;
return width == other.width
&& height == other.height
&& unappliedRotationDegrees == other.unappliedRotationDegrees
&& pixelWidthHeightRatio == other.pixelWidthHeightRatio;
}
return false;
@ -120,23 +120,27 @@ public final class VideoSize {
int result = 7;
result = 31 * result + width;
result = 31 * result + height;
result = 31 * result + unappliedRotationDegrees;
result = 31 * result + Float.floatToRawIntBits(pixelWidthHeightRatio);
return result;
}
private static final String FIELD_WIDTH = Util.intToStringMaxRadix(0);
private static final String FIELD_HEIGHT = Util.intToStringMaxRadix(1);
private static final String FIELD_UNAPPLIED_ROTATION_DEGREES = Util.intToStringMaxRadix(2);
// 2 reserved for deprecated 'unappliedRotationDegrees'.
private static final String FIELD_PIXEL_WIDTH_HEIGHT_RATIO = Util.intToStringMaxRadix(3);
@UnstableApi
public Bundle toBundle() {
Bundle bundle = new Bundle();
bundle.putInt(FIELD_WIDTH, width);
bundle.putInt(FIELD_HEIGHT, height);
bundle.putInt(FIELD_UNAPPLIED_ROTATION_DEGREES, unappliedRotationDegrees);
bundle.putFloat(FIELD_PIXEL_WIDTH_HEIGHT_RATIO, pixelWidthHeightRatio);
if (width != 0) {
bundle.putInt(FIELD_WIDTH, width);
}
if (height != 0) {
bundle.putInt(FIELD_HEIGHT, height);
}
if (pixelWidthHeightRatio != 1f) {
bundle.putFloat(FIELD_PIXEL_WIDTH_HEIGHT_RATIO, pixelWidthHeightRatio);
}
return bundle;
}
@ -145,11 +149,8 @@ public final class VideoSize {
public static VideoSize fromBundle(Bundle bundle) {
int width = bundle.getInt(FIELD_WIDTH, DEFAULT_WIDTH);
int height = bundle.getInt(FIELD_HEIGHT, DEFAULT_HEIGHT);
int unappliedRotationDegrees =
bundle.getInt(FIELD_UNAPPLIED_ROTATION_DEGREES, DEFAULT_UNAPPLIED_ROTATION_DEGREES);
float pixelWidthHeightRatio =
bundle.getFloat(FIELD_PIXEL_WIDTH_HEIGHT_RATIO, DEFAULT_PIXEL_WIDTH_HEIGHT_RATIO);
return new VideoSize(width, height, unappliedRotationDegrees, pixelWidthHeightRatio);
return new VideoSize(width, height, pixelWidthHeightRatio);
}
;
}

View file

@ -16,9 +16,9 @@
*/
package androidx.media3.common.audio;
import static androidx.media3.common.util.Assertions.checkState;
import static java.lang.Math.min;
import androidx.media3.common.util.Assertions;
import java.nio.ShortBuffer;
import java.util.Arrays;
@ -52,11 +52,23 @@ import java.util.Arrays;
private int pitchFrameCount;
private int oldRatePosition;
private int newRatePosition;
/**
* Number of frames pending to be copied from {@link #inputBuffer} directly to {@link
* #outputBuffer}.
*
* <p>This field is only relevant to time-stretching or pitch-shifting in {@link
* #changeSpeed(double)}, particularly when more frames need to be copied to the {@link
* #outputBuffer} than are available in {@link #inputBuffer} and Sonic must wait until the next
* buffer (or EOS) is queued.
*/
private int remainingInputToCopyFrameCount;
private int prevPeriod;
private int prevMinDiff;
private int minDiff;
private int maxDiff;
private double accumulatedSpeedAdjustmentError;
/**
* Creates a new Sonic audio stream processor.
@ -130,10 +142,26 @@ import java.util.Arrays;
*/
public void queueEndOfStream() {
int remainingFrameCount = inputFrameCount;
float s = speed / pitch;
float r = rate * pitch;
double s = speed / pitch;
double r = rate * pitch;
// If there are frames to be copied directly onto the output buffer, we should not count those
// as "input frames" because Sonic is not applying any processing on them.
int adjustedRemainingFrames = remainingFrameCount - remainingInputToCopyFrameCount;
// We add directly to the output the number of frames in remainingInputToCopyFrameCount.
// Otherwise, expectedOutputFrames will be off and will make Sonic output an incorrect number of
// frames.
int expectedOutputFrames =
outputFrameCount + (int) ((remainingFrameCount / s + pitchFrameCount) / r + 0.5f);
outputFrameCount
+ (int)
((adjustedRemainingFrames / s
+ remainingInputToCopyFrameCount
+ accumulatedSpeedAdjustmentError
+ pitchFrameCount)
/ r
+ 0.5);
accumulatedSpeedAdjustmentError = 0;
// Add enough silence to flush both input and pitch buffers.
inputBuffer =
@ -166,6 +194,7 @@ import java.util.Arrays;
prevMinDiff = 0;
minDiff = 0;
maxDiff = 0;
accumulatedSpeedAdjustmentError = 0;
}
/** Returns the size of output that can be read with {@link #getOutput(ShortBuffer)}, in bytes. */
@ -355,14 +384,14 @@ import java.util.Arrays;
pitchFrameCount -= frameCount;
}
private short interpolate(short[] in, int inPos, int oldSampleRate, int newSampleRate) {
private short interpolate(short[] in, int inPos, long oldSampleRate, long newSampleRate) {
short left = in[inPos];
short right = in[inPos + channelCount];
int position = newRatePosition * oldSampleRate;
int leftPosition = oldRatePosition * newSampleRate;
int rightPosition = (oldRatePosition + 1) * newSampleRate;
int ratio = rightPosition - position;
int width = rightPosition - leftPosition;
long position = newRatePosition * oldSampleRate;
long leftPosition = oldRatePosition * newSampleRate;
long rightPosition = (oldRatePosition + 1) * newSampleRate;
long ratio = rightPosition - position;
long width = rightPosition - leftPosition;
return (short) ((ratio * left + (width - ratio) * right) / width);
}
@ -370,16 +399,23 @@ import java.util.Arrays;
if (outputFrameCount == originalOutputFrameCount) {
return;
}
int newSampleRate = (int) (inputSampleRateHz / rate);
int oldSampleRate = inputSampleRateHz;
// Use long to avoid overflows int-int multiplications. The actual value of newSampleRate and
// oldSampleRate should always be comfortably within the int range.
long newSampleRate = (long) (inputSampleRateHz / rate);
long oldSampleRate = inputSampleRateHz;
// Set these values to help with the integer math.
while (newSampleRate > (1 << 14) || oldSampleRate > (1 << 14)) {
while (newSampleRate != 0
&& oldSampleRate != 0
&& newSampleRate % 2 == 0
&& oldSampleRate % 2 == 0) {
newSampleRate /= 2;
oldSampleRate /= 2;
}
moveNewSamplesToPitchBuffer(originalOutputFrameCount);
// Leave at least one pitch sample in the buffer.
for (int position = 0; position < pitchFrameCount - 1; position++) {
// Cast to long to avoid overflow.
while ((oldRatePosition + 1) * newSampleRate > newRatePosition * oldSampleRate) {
outputBuffer =
ensureSpaceForAdditionalFrames(
@ -394,21 +430,26 @@ import java.util.Arrays;
oldRatePosition++;
if (oldRatePosition == oldSampleRate) {
oldRatePosition = 0;
Assertions.checkState(newRatePosition == newSampleRate);
checkState(newRatePosition == newSampleRate);
newRatePosition = 0;
}
}
removePitchFrames(pitchFrameCount - 1);
}
private int skipPitchPeriod(short[] samples, int position, float speed, int period) {
private int skipPitchPeriod(short[] samples, int position, double speed, int period) {
// Skip over a pitch period, and copy period/speed samples to the output.
int newFrameCount;
if (speed >= 2.0f) {
newFrameCount = (int) (period / (speed - 1.0f));
double expectedFrameCount = period / (speed - 1.0) + accumulatedSpeedAdjustmentError;
newFrameCount = (int) Math.round(expectedFrameCount);
accumulatedSpeedAdjustmentError = expectedFrameCount - newFrameCount;
} else {
newFrameCount = period;
remainingInputToCopyFrameCount = (int) (period * (2.0f - speed) / (speed - 1.0f));
double expectedInputToCopy =
period * (2.0f - speed) / (speed - 1.0f) + accumulatedSpeedAdjustmentError;
remainingInputToCopyFrameCount = (int) Math.round(expectedInputToCopy);
accumulatedSpeedAdjustmentError = expectedInputToCopy - remainingInputToCopyFrameCount;
}
outputBuffer = ensureSpaceForAdditionalFrames(outputBuffer, outputFrameCount, newFrameCount);
overlapAdd(
@ -424,14 +465,19 @@ import java.util.Arrays;
return newFrameCount;
}
private int insertPitchPeriod(short[] samples, int position, float speed, int period) {
private int insertPitchPeriod(short[] samples, int position, double speed, int period) {
// Insert a pitch period, and determine how much input to copy directly.
int newFrameCount;
if (speed < 0.5f) {
newFrameCount = (int) (period * speed / (1.0f - speed));
double expectedFrameCount = period * speed / (1.0f - speed) + accumulatedSpeedAdjustmentError;
newFrameCount = (int) Math.round(expectedFrameCount);
accumulatedSpeedAdjustmentError = expectedFrameCount - newFrameCount;
} else {
newFrameCount = period;
remainingInputToCopyFrameCount = (int) (period * (2.0f * speed - 1.0f) / (1.0f - speed));
double expectedInputToCopy =
period * (2.0f * speed - 1.0f) / (1.0f - speed) + accumulatedSpeedAdjustmentError;
remainingInputToCopyFrameCount = (int) Math.round(expectedInputToCopy);
accumulatedSpeedAdjustmentError = expectedInputToCopy - remainingInputToCopyFrameCount;
}
outputBuffer =
ensureSpaceForAdditionalFrames(outputBuffer, outputFrameCount, period + newFrameCount);
@ -454,7 +500,7 @@ import java.util.Arrays;
return newFrameCount;
}
private void changeSpeed(float speed) {
private void changeSpeed(double speed) {
if (inputFrameCount < maxRequiredFrameCount) {
return;
}
@ -478,7 +524,7 @@ import java.util.Arrays;
private void processStreamInput() {
// Resample as many pitch periods as we have buffered on the input.
int originalOutputFrameCount = outputFrameCount;
float s = speed / pitch;
double s = speed / pitch;
float r = rate * pitch;
if (s > 1.00001 || s < 0.99999) {
changeSpeed(s);

View file

@ -28,7 +28,6 @@ import androidx.media3.common.util.SpeedProviderUtil;
import androidx.media3.common.util.TimestampConsumer;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import java.math.RoundingMode;
import java.nio.ByteBuffer;
import java.util.ArrayDeque;
import java.util.Queue;
@ -115,27 +114,39 @@ public final class SpeedChangingAudioProcessor extends BaseAudioProcessor {
@Override
public void queueInput(ByteBuffer inputBuffer) {
long timeUs =
long currentTimeUs =
Util.scaleLargeTimestamp(
/* timestamp= */ bytesRead,
/* multiplier= */ C.MICROS_PER_SECOND,
/* divisor= */ (long) inputAudioFormat.sampleRate * inputAudioFormat.bytesPerFrame);
float newSpeed = speedProvider.getSpeed(timeUs);
float newSpeed = speedProvider.getSpeed(currentTimeUs);
long nextSpeedChangeTimeUs = speedProvider.getNextSpeedChangeTimeUs(currentTimeUs);
long sampleRateAlignedNextSpeedChangeTimeUs =
getSampleRateAlignedTimestamp(nextSpeedChangeTimeUs, inputAudioFormat.sampleRate);
updateSpeed(newSpeed, timeUs);
// If next speed change falls between the current sample position and the next sample, then get
// the next speed and next speed change from the following sample. If needed, this will ignore
// one or more mid-sample speed changes.
if (sampleRateAlignedNextSpeedChangeTimeUs == currentTimeUs) {
long sampleDuration =
Util.sampleCountToDurationUs(/* sampleCount= */ 1, inputAudioFormat.sampleRate);
newSpeed = speedProvider.getSpeed(currentTimeUs + sampleDuration);
nextSpeedChangeTimeUs =
speedProvider.getNextSpeedChangeTimeUs(currentTimeUs + sampleDuration);
}
updateSpeed(newSpeed, currentTimeUs);
int inputBufferLimit = inputBuffer.limit();
long nextSpeedChangeTimeUs = speedProvider.getNextSpeedChangeTimeUs(timeUs);
int bytesToNextSpeedChange;
if (nextSpeedChangeTimeUs != C.TIME_UNSET) {
bytesToNextSpeedChange =
(int)
Util.scaleLargeValue(
/* timestamp= */ nextSpeedChangeTimeUs - timeUs,
Util.scaleLargeTimestamp(
/* timestamp= */ nextSpeedChangeTimeUs - currentTimeUs,
/* multiplier= */ (long) inputAudioFormat.sampleRate
* inputAudioFormat.bytesPerFrame,
/* divisor= */ C.MICROS_PER_SECOND,
RoundingMode.CEILING);
/* divisor= */ C.MICROS_PER_SECOND);
int bytesToNextFrame =
inputAudioFormat.bytesPerFrame - bytesToNextSpeedChange % inputAudioFormat.bytesPerFrame;
if (bytesToNextFrame != inputAudioFormat.bytesPerFrame) {
@ -410,4 +421,15 @@ public final class SpeedChangingAudioProcessor extends BaseAudioProcessor {
// because some clients register callbacks with getSpeedAdjustedTimeAsync before this audio
// processor is flushed.
}
/**
* Returns the timestamp in microseconds of the sample defined by {@code sampleRate} that is
* closest to {@code timestampUs}, using the rounding mode specified in {@link
* Util#scaleLargeTimestamp}.
*/
private static long getSampleRateAlignedTimestamp(long timestampUs, int sampleRate) {
long exactSamplePosition =
Util.scaleLargeTimestamp(timestampUs, sampleRate, C.MICROS_PER_SECOND);
return Util.scaleLargeTimestamp(exactSamplePosition, C.MICROS_PER_SECOND, sampleRate);
}
}

View file

@ -45,20 +45,11 @@ import java.util.ArrayList;
*/
/* package */ final class CustomSpanBundler {
/**
* Media3 custom span implementations. One of the following:
*
* <ul>
* <li>{@link #UNKNOWN}
* <li>{@link #RUBY}
* <li>{@link #TEXT_EMPHASIS}
* <li>{@link #HORIZONTAL_TEXT_IN_VERTICAL_CONTEXT}
* </ul>
*/
/** Media3 custom span implementations. */
@Documented
@Retention(RetentionPolicy.SOURCE)
@Target({TYPE_USE})
@IntDef({UNKNOWN, RUBY, TEXT_EMPHASIS, HORIZONTAL_TEXT_IN_VERTICAL_CONTEXT})
@IntDef({UNKNOWN, RUBY, TEXT_EMPHASIS, HORIZONTAL_TEXT_IN_VERTICAL_CONTEXT, VOICE})
private @interface CustomSpanType {}
private static final int UNKNOWN = -1;
@ -69,6 +60,8 @@ import java.util.ArrayList;
private static final int HORIZONTAL_TEXT_IN_VERTICAL_CONTEXT = 3;
private static final int VOICE = 4;
private static final String FIELD_START_INDEX = Util.intToStringMaxRadix(0);
private static final String FIELD_END_INDEX = Util.intToStringMaxRadix(1);
private static final String FIELD_FLAGS = Util.intToStringMaxRadix(2);
@ -94,6 +87,11 @@ import java.util.ArrayList;
text, span, /* spanType= */ HORIZONTAL_TEXT_IN_VERTICAL_CONTEXT, /* params= */ null);
bundledCustomSpans.add(bundle);
}
for (VoiceSpan span : text.getSpans(0, text.length(), VoiceSpan.class)) {
Bundle bundle =
spanToBundle(text, span, /* spanType= */ VOICE, /* params= */ span.toBundle());
bundledCustomSpans.add(bundle);
}
return bundledCustomSpans;
}
@ -113,6 +111,9 @@ import java.util.ArrayList;
case HORIZONTAL_TEXT_IN_VERTICAL_CONTEXT:
text.setSpan(new HorizontalTextInVerticalContextSpan(), start, end, flags);
break;
case VOICE:
text.setSpan(VoiceSpan.fromBundle(checkNotNull(span)), start, end, flags);
break;
default:
break;
}

View file

@ -17,6 +17,7 @@ package androidx.media3.common.text;
import android.text.Spannable;
import android.text.style.ForegroundColorSpan;
import android.text.style.RelativeSizeSpan;
import androidx.media3.common.util.UnstableApi;
/**
@ -44,14 +45,52 @@ public final class SpanUtil {
Spannable spannable, Object span, int start, int end, int spanFlags) {
Object[] existingSpans = spannable.getSpans(start, end, span.getClass());
for (Object existingSpan : existingSpans) {
if (spannable.getSpanStart(existingSpan) == start
&& spannable.getSpanEnd(existingSpan) == end
&& spannable.getSpanFlags(existingSpan) == spanFlags) {
spannable.removeSpan(existingSpan);
}
removeIfStartEndAndFlagsMatch(spannable, existingSpan, start, end, spanFlags);
}
spannable.setSpan(span, start, end, spanFlags);
}
/**
* Modifies the size of the text between {@code start} and {@code end} relative to any existing
* {@link RelativeSizeSpan} instances which cover <b>at least the same range</b>.
*
* <p>{@link RelativeSizeSpan} instances which only cover a part of the text between {@code start}
* and {@code end} are ignored.
*
* <p>A new {@link RelativeSizeSpan} instance is added between {@code start} and {@code end} with
* its {@code sizeChange} value computed by modifying the {@code size} parameter by the {@code
* sizeChange} of {@link RelativeSizeSpan} instances covering between {@code start} and {@code
* end}.
*
* <p>{@link RelativeSizeSpan} instances with the same {@code start}, {@code end}, and {@code
* spanFlags} are removed.
*
* @param spannable The {@link Spannable} to add the {@link RelativeSizeSpan} to.
* @param size The fraction to modify the text size by.
* @param start The start index to add the new span at.
* @param end The end index to add the new span at.
* @param spanFlags The flags to pass to {@link Spannable#setSpan(Object, int, int, int)}.
*/
public static void addInheritedRelativeSizeSpan(
Spannable spannable, float size, int start, int end, int spanFlags) {
for (RelativeSizeSpan existingSpan : spannable.getSpans(start, end, RelativeSizeSpan.class)) {
if (spannable.getSpanStart(existingSpan) <= start
&& spannable.getSpanEnd(existingSpan) >= end) {
size *= existingSpan.getSizeChange();
}
removeIfStartEndAndFlagsMatch(spannable, existingSpan, start, end, spanFlags);
}
spannable.setSpan(new RelativeSizeSpan(size), start, end, spanFlags);
}
private static void removeIfStartEndAndFlagsMatch(
Spannable spannable, Object span, int start, int end, int spanFlags) {
if (spannable.getSpanStart(span) == start
&& spannable.getSpanEnd(span) == end
&& spannable.getSpanFlags(span) == spanFlags) {
spannable.removeSpan(span);
}
}
private SpanUtil() {}
}

View file

@ -0,0 +1,52 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*
*/
package androidx.media3.common.text;
import static androidx.media3.common.util.Assertions.checkNotNull;
import android.os.Bundle;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
/**
* A span representing the speaker of the spanned text.
*
* <p>For example a <a href="https://www.w3.org/TR/webvtt1/#webvtt-cue-voice-span">WebVTT voice
* span</a>.
*/
@UnstableApi
public final class VoiceSpan {
/** The voice name. */
public final String name;
private static final String FIELD_NAME = Util.intToStringMaxRadix(0);
public VoiceSpan(String name) {
this.name = name;
}
public Bundle toBundle() {
Bundle bundle = new Bundle();
bundle.putString(FIELD_NAME, name);
return bundle;
}
public static VoiceSpan fromBundle(Bundle bundle) {
return new VoiceSpan(checkNotNull(bundle.getString(FIELD_NAME)));
}
}

View file

@ -17,15 +17,23 @@ package androidx.media3.common.util;
import static androidx.media3.common.util.Assertions.checkArgument;
import android.annotation.SuppressLint;
import android.media.MediaCodecInfo;
import android.util.Pair;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.ColorInfo;
import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.regex.Matcher;
import java.util.regex.Pattern;
/** Provides utilities for handling various types of codec-specific data. */
@SuppressLint("InlinedApi")
@UnstableApi
public final class CodecSpecificDataUtil {
@ -39,6 +47,26 @@ public final class CodecSpecificDataUtil {
private static final int EXTENDED_PAR = 0x0F;
private static final int RECTANGULAR = 0x00;
// Codecs to constant mappings.
// H263
private static final String CODEC_ID_H263 = "s263";
// AVC.
private static final String CODEC_ID_AVC1 = "avc1";
private static final String CODEC_ID_AVC2 = "avc2";
// VP9
private static final String CODEC_ID_VP09 = "vp09";
// HEVC.
private static final String CODEC_ID_HEV1 = "hev1";
private static final String CODEC_ID_HVC1 = "hvc1";
// AV1.
private static final String CODEC_ID_AV01 = "av01";
// MP4A AAC.
private static final String CODEC_ID_MP4A = "mp4a";
private static final Pattern PROFILE_PATTERN = Pattern.compile("^\\D?(\\d+)$");
private static final String TAG = "CodecSpecificDataUtil";
/**
* Parses an ALAC AudioSpecificConfig (i.e. an <a
* href="https://github.com/macosforge/alac/blob/master/ALACMagicCookieDescription.txt">ALACSpecificConfig</a>).
@ -80,6 +108,35 @@ public final class CodecSpecificDataUtil {
&& initializationData.get(0)[0] == 1;
}
/**
* Returns initialization data in CodecPrivate format of VP9.
*
* <p>Each feature of VP9 CodecPrivate is defined by the binary format of ID (1 byte), length (1
* byte), and data (1 byte). See <a>
* href="https://www.webmproject.org/docs/container/#vp9-codec-feature-metadata-codecprivate">CodecPrivate
* format of VP9</a> for more details.
*
* @param profile The VP9 codec profile.
* @param level The VP9 codec level.
* @param bitDepth The bit depth of the luma and color components.
* @param chromaSubsampling The chroma subsampling.
*/
public static ImmutableList<byte[]> buildVp9CodecPrivateInitializationData(
byte profile, byte level, byte bitDepth, byte chromaSubsampling) {
byte profileId = 0x01;
byte levelId = 0x02;
byte bitDepthId = 0x03;
byte chromaSubsamplingId = 0x04;
byte length = 0x01;
return ImmutableList.of(
new byte[] {
profileId, length, profile,
levelId, length, level,
bitDepthId, length, bitDepth,
chromaSubsamplingId, length, chromaSubsampling
});
}
/**
* Parses an MPEG-4 Visual configuration information, as defined in ISO/IEC14496-2.
*
@ -204,6 +261,103 @@ public final class CodecSpecificDataUtil {
return builder.toString();
}
/** Builds an RFC 6381 H263 codec string using profile and level. */
public static String buildH263CodecString(int profile, int level) {
return Util.formatInvariant("s263.%d.%d", profile, level);
}
/**
* Returns profile and level (as defined by {@link MediaCodecInfo.CodecProfileLevel})
* corresponding to the codec description string (as defined by RFC 6381) of the given format.
*
* @param format Media format with a codec description string, as defined by RFC 6381.
* @return A pair (profile constant, level constant) if the codec of the {@code format} is
* well-formed and recognized, or null otherwise.
*/
@Nullable
public static Pair<Integer, Integer> getCodecProfileAndLevel(Format format) {
if (format.codecs == null) {
return null;
}
String[] parts = format.codecs.split("\\.");
// Dolby Vision can use DV, AVC or HEVC codec IDs, so check the MIME type first.
if (MimeTypes.VIDEO_DOLBY_VISION.equals(format.sampleMimeType)) {
return getDolbyVisionProfileAndLevel(format.codecs, parts);
}
switch (parts[0]) {
case CODEC_ID_H263:
return getH263ProfileAndLevel(format.codecs, parts);
case CODEC_ID_AVC1:
case CODEC_ID_AVC2:
return getAvcProfileAndLevel(format.codecs, parts);
case CODEC_ID_VP09:
return getVp9ProfileAndLevel(format.codecs, parts);
case CODEC_ID_HEV1:
case CODEC_ID_HVC1:
return getHevcProfileAndLevel(format.codecs, parts, format.colorInfo);
case CODEC_ID_AV01:
return getAv1ProfileAndLevel(format.codecs, parts, format.colorInfo);
case CODEC_ID_MP4A:
return getAacCodecProfileAndLevel(format.codecs, parts);
default:
return null;
}
}
/**
* Returns Hevc profile and level corresponding to the codec description string (as defined by RFC
* 6381) and it {@link ColorInfo}.
*
* @param codec The codec description string (as defined by RFC 6381).
* @param parts The codec string split by ".".
* @param colorInfo The {@link ColorInfo}.
* @return A pair (profile constant, level constant) if profile and level are recognized, or
* {@code null} otherwise.
*/
@Nullable
public static Pair<Integer, Integer> getHevcProfileAndLevel(
String codec, String[] parts, @Nullable ColorInfo colorInfo) {
if (parts.length < 4) {
// The codec has fewer parts than required by the HEVC codec string format.
Log.w(TAG, "Ignoring malformed HEVC codec string: " + codec);
return null;
}
// The profile_space gets ignored.
Matcher matcher = PROFILE_PATTERN.matcher(parts[1]);
if (!matcher.matches()) {
Log.w(TAG, "Ignoring malformed HEVC codec string: " + codec);
return null;
}
@Nullable String profileString = matcher.group(1);
int profile;
if ("1".equals(profileString)) {
profile = MediaCodecInfo.CodecProfileLevel.HEVCProfileMain;
} else if ("2".equals(profileString)) {
if (colorInfo != null && colorInfo.colorTransfer == C.COLOR_TRANSFER_ST2084) {
profile = MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10HDR10;
} else {
// For all other cases, we map to the Main10 profile. Note that this includes HLG
// HDR. On Android 13+, the platform guarantees that a decoder that advertises
// HEVCProfileMain10 will be able to decode HLG. This is not guaranteed for older
// Android versions, but we still map to Main10 for backwards compatibility.
profile = MediaCodecInfo.CodecProfileLevel.HEVCProfileMain10;
}
} else if ("6".equals(profileString)) {
// Framework does not have profileLevel.HEVCProfileMultiviewMain defined.
profile = 6;
} else {
Log.w(TAG, "Unknown HEVC profile string: " + profileString);
return null;
}
@Nullable String levelString = parts[3];
@Nullable Integer level = hevcCodecStringToProfileLevel(levelString);
if (level == null) {
Log.w(TAG, "Unknown HEVC level string: " + levelString);
return null;
}
return new Pair<>(profile, level);
}
/**
* Constructs a NAL unit consisting of the NAL start code followed by the specified data.
*
@ -289,5 +443,528 @@ public final class CodecSpecificDataUtil {
return true;
}
@Nullable
private static Pair<Integer, Integer> getDolbyVisionProfileAndLevel(
String codec, String[] parts) {
if (parts.length < 3) {
// The codec has fewer parts than required by the Dolby Vision codec string format.
Log.w(TAG, "Ignoring malformed Dolby Vision codec string: " + codec);
return null;
}
// The profile_space gets ignored.
Matcher matcher = PROFILE_PATTERN.matcher(parts[1]);
if (!matcher.matches()) {
Log.w(TAG, "Ignoring malformed Dolby Vision codec string: " + codec);
return null;
}
@Nullable String profileString = matcher.group(1);
@Nullable Integer profile = dolbyVisionStringToProfile(profileString);
if (profile == null) {
Log.w(TAG, "Unknown Dolby Vision profile string: " + profileString);
return null;
}
String levelString = parts[2];
@Nullable Integer level = dolbyVisionStringToLevel(levelString);
if (level == null) {
Log.w(TAG, "Unknown Dolby Vision level string: " + levelString);
return null;
}
return new Pair<>(profile, level);
}
/** Returns H263 profile and level from codec string. */
private static Pair<Integer, Integer> getH263ProfileAndLevel(String codec, String[] parts) {
Pair<Integer, Integer> defaultProfileAndLevel =
new Pair<>(
MediaCodecInfo.CodecProfileLevel.H263ProfileBaseline,
MediaCodecInfo.CodecProfileLevel.H263Level10);
if (parts.length < 3) {
Log.w(TAG, "Ignoring malformed H263 codec string: " + codec);
return defaultProfileAndLevel;
}
try {
int profile = Integer.parseInt(parts[1]);
int level = Integer.parseInt(parts[2]);
return new Pair<>(profile, level);
} catch (NumberFormatException e) {
Log.w(TAG, "Ignoring malformed H263 codec string: " + codec);
return defaultProfileAndLevel;
}
}
@Nullable
private static Pair<Integer, Integer> getAvcProfileAndLevel(String codec, String[] parts) {
if (parts.length < 2) {
// The codec has fewer parts than required by the AVC codec string format.
Log.w(TAG, "Ignoring malformed AVC codec string: " + codec);
return null;
}
int profileInteger;
int levelInteger;
try {
if (parts[1].length() == 6) {
// Format: avc1.xxccyy, where xx is profile and yy level, both hexadecimal.
profileInteger = Integer.parseInt(parts[1].substring(0, 2), 16);
levelInteger = Integer.parseInt(parts[1].substring(4), 16);
} else if (parts.length >= 3) {
// Format: avc1.xx.[y]yy where xx is profile and [y]yy level, both decimal.
profileInteger = Integer.parseInt(parts[1]);
levelInteger = Integer.parseInt(parts[2]);
} else {
// We don't recognize the format.
Log.w(TAG, "Ignoring malformed AVC codec string: " + codec);
return null;
}
} catch (NumberFormatException e) {
Log.w(TAG, "Ignoring malformed AVC codec string: " + codec);
return null;
}
int profile = avcProfileNumberToConst(profileInteger);
if (profile == -1) {
Log.w(TAG, "Unknown AVC profile: " + profileInteger);
return null;
}
int level = avcLevelNumberToConst(levelInteger);
if (level == -1) {
Log.w(TAG, "Unknown AVC level: " + levelInteger);
return null;
}
return new Pair<>(profile, level);
}
@Nullable
private static Pair<Integer, Integer> getVp9ProfileAndLevel(String codec, String[] parts) {
if (parts.length < 3) {
Log.w(TAG, "Ignoring malformed VP9 codec string: " + codec);
return null;
}
int profileInteger;
int levelInteger;
try {
profileInteger = Integer.parseInt(parts[1]);
levelInteger = Integer.parseInt(parts[2]);
} catch (NumberFormatException e) {
Log.w(TAG, "Ignoring malformed VP9 codec string: " + codec);
return null;
}
int profile = vp9ProfileNumberToConst(profileInteger);
if (profile == -1) {
Log.w(TAG, "Unknown VP9 profile: " + profileInteger);
return null;
}
int level = vp9LevelNumberToConst(levelInteger);
if (level == -1) {
Log.w(TAG, "Unknown VP9 level: " + levelInteger);
return null;
}
return new Pair<>(profile, level);
}
@Nullable
private static Pair<Integer, Integer> getAv1ProfileAndLevel(
String codec, String[] parts, @Nullable ColorInfo colorInfo) {
if (parts.length < 4) {
Log.w(TAG, "Ignoring malformed AV1 codec string: " + codec);
return null;
}
int profileInteger;
int levelInteger;
int bitDepthInteger;
try {
profileInteger = Integer.parseInt(parts[1]);
levelInteger = Integer.parseInt(parts[2].substring(0, 2));
bitDepthInteger = Integer.parseInt(parts[3]);
} catch (NumberFormatException e) {
Log.w(TAG, "Ignoring malformed AV1 codec string: " + codec);
return null;
}
if (profileInteger != 0) {
Log.w(TAG, "Unknown AV1 profile: " + profileInteger);
return null;
}
if (bitDepthInteger != 8 && bitDepthInteger != 10) {
Log.w(TAG, "Unknown AV1 bit depth: " + bitDepthInteger);
return null;
}
int profile;
if (bitDepthInteger == 8) {
profile = MediaCodecInfo.CodecProfileLevel.AV1ProfileMain8;
} else if (colorInfo != null
&& (colorInfo.hdrStaticInfo != null
|| colorInfo.colorTransfer == C.COLOR_TRANSFER_HLG
|| colorInfo.colorTransfer == C.COLOR_TRANSFER_ST2084)) {
profile = MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10HDR10;
} else {
profile = MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10;
}
int level = av1LevelNumberToConst(levelInteger);
if (level == -1) {
Log.w(TAG, "Unknown AV1 level: " + levelInteger);
return null;
}
return new Pair<>(profile, level);
}
@Nullable
private static Pair<Integer, Integer> getAacCodecProfileAndLevel(String codec, String[] parts) {
if (parts.length != 3) {
Log.w(TAG, "Ignoring malformed MP4A codec string: " + codec);
return null;
}
try {
// Get the object type indication, which is a hexadecimal value (see RFC 6381/ISO 14496-1).
int objectTypeIndication = Integer.parseInt(parts[1], 16);
@Nullable String mimeType = MimeTypes.getMimeTypeFromMp4ObjectType(objectTypeIndication);
if (MimeTypes.AUDIO_AAC.equals(mimeType)) {
// For MPEG-4 audio this is followed by an audio object type indication as a decimal number.
int audioObjectTypeIndication = Integer.parseInt(parts[2]);
int profile = mp4aAudioObjectTypeToProfile(audioObjectTypeIndication);
if (profile != -1) {
// Level is set to zero in AAC decoder CodecProfileLevels.
return new Pair<>(profile, 0);
}
}
} catch (NumberFormatException e) {
Log.w(TAG, "Ignoring malformed MP4A codec string: " + codec);
}
return null;
}
private static int avcProfileNumberToConst(int profileNumber) {
switch (profileNumber) {
case 66:
return MediaCodecInfo.CodecProfileLevel.AVCProfileBaseline;
case 77:
return MediaCodecInfo.CodecProfileLevel.AVCProfileMain;
case 88:
return MediaCodecInfo.CodecProfileLevel.AVCProfileExtended;
case 100:
return MediaCodecInfo.CodecProfileLevel.AVCProfileHigh;
case 110:
return MediaCodecInfo.CodecProfileLevel.AVCProfileHigh10;
case 122:
return MediaCodecInfo.CodecProfileLevel.AVCProfileHigh422;
case 244:
return MediaCodecInfo.CodecProfileLevel.AVCProfileHigh444;
default:
return -1;
}
}
private static int avcLevelNumberToConst(int levelNumber) {
// TODO: Find int for CodecProfileLevel.AVCLevel1b.
switch (levelNumber) {
case 10:
return MediaCodecInfo.CodecProfileLevel.AVCLevel1;
case 11:
return MediaCodecInfo.CodecProfileLevel.AVCLevel11;
case 12:
return MediaCodecInfo.CodecProfileLevel.AVCLevel12;
case 13:
return MediaCodecInfo.CodecProfileLevel.AVCLevel13;
case 20:
return MediaCodecInfo.CodecProfileLevel.AVCLevel2;
case 21:
return MediaCodecInfo.CodecProfileLevel.AVCLevel21;
case 22:
return MediaCodecInfo.CodecProfileLevel.AVCLevel22;
case 30:
return MediaCodecInfo.CodecProfileLevel.AVCLevel3;
case 31:
return MediaCodecInfo.CodecProfileLevel.AVCLevel31;
case 32:
return MediaCodecInfo.CodecProfileLevel.AVCLevel32;
case 40:
return MediaCodecInfo.CodecProfileLevel.AVCLevel4;
case 41:
return MediaCodecInfo.CodecProfileLevel.AVCLevel41;
case 42:
return MediaCodecInfo.CodecProfileLevel.AVCLevel42;
case 50:
return MediaCodecInfo.CodecProfileLevel.AVCLevel5;
case 51:
return MediaCodecInfo.CodecProfileLevel.AVCLevel51;
case 52:
return MediaCodecInfo.CodecProfileLevel.AVCLevel52;
default:
return -1;
}
}
private static int vp9ProfileNumberToConst(int profileNumber) {
switch (profileNumber) {
case 0:
return MediaCodecInfo.CodecProfileLevel.VP9Profile0;
case 1:
return MediaCodecInfo.CodecProfileLevel.VP9Profile1;
case 2:
return MediaCodecInfo.CodecProfileLevel.VP9Profile2;
case 3:
return MediaCodecInfo.CodecProfileLevel.VP9Profile3;
default:
return -1;
}
}
private static int vp9LevelNumberToConst(int levelNumber) {
switch (levelNumber) {
case 10:
return MediaCodecInfo.CodecProfileLevel.VP9Level1;
case 11:
return MediaCodecInfo.CodecProfileLevel.VP9Level11;
case 20:
return MediaCodecInfo.CodecProfileLevel.VP9Level2;
case 21:
return MediaCodecInfo.CodecProfileLevel.VP9Level21;
case 30:
return MediaCodecInfo.CodecProfileLevel.VP9Level3;
case 31:
return MediaCodecInfo.CodecProfileLevel.VP9Level31;
case 40:
return MediaCodecInfo.CodecProfileLevel.VP9Level4;
case 41:
return MediaCodecInfo.CodecProfileLevel.VP9Level41;
case 50:
return MediaCodecInfo.CodecProfileLevel.VP9Level5;
case 51:
return MediaCodecInfo.CodecProfileLevel.VP9Level51;
case 60:
return MediaCodecInfo.CodecProfileLevel.VP9Level6;
case 61:
return MediaCodecInfo.CodecProfileLevel.VP9Level61;
case 62:
return MediaCodecInfo.CodecProfileLevel.VP9Level62;
default:
return -1;
}
}
@Nullable
private static Integer hevcCodecStringToProfileLevel(@Nullable String codecString) {
if (codecString == null) {
return null;
}
switch (codecString) {
case "L30":
return MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel1;
case "L60":
return MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel2;
case "L63":
return MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel21;
case "L90":
return MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel3;
case "L93":
return MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel31;
case "L120":
return MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel4;
case "L123":
return MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel41;
case "L150":
return MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel5;
case "L153":
return MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel51;
case "L156":
return MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel52;
case "L180":
return MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel6;
case "L183":
return MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel61;
case "L186":
return MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel62;
case "H30":
return MediaCodecInfo.CodecProfileLevel.HEVCHighTierLevel1;
case "H60":
return MediaCodecInfo.CodecProfileLevel.HEVCHighTierLevel2;
case "H63":
return MediaCodecInfo.CodecProfileLevel.HEVCHighTierLevel21;
case "H90":
return MediaCodecInfo.CodecProfileLevel.HEVCHighTierLevel3;
case "H93":
return MediaCodecInfo.CodecProfileLevel.HEVCHighTierLevel31;
case "H120":
return MediaCodecInfo.CodecProfileLevel.HEVCHighTierLevel4;
case "H123":
return MediaCodecInfo.CodecProfileLevel.HEVCHighTierLevel41;
case "H150":
return MediaCodecInfo.CodecProfileLevel.HEVCHighTierLevel5;
case "H153":
return MediaCodecInfo.CodecProfileLevel.HEVCHighTierLevel51;
case "H156":
return MediaCodecInfo.CodecProfileLevel.HEVCHighTierLevel52;
case "H180":
return MediaCodecInfo.CodecProfileLevel.HEVCHighTierLevel6;
case "H183":
return MediaCodecInfo.CodecProfileLevel.HEVCHighTierLevel61;
case "H186":
return MediaCodecInfo.CodecProfileLevel.HEVCHighTierLevel62;
default:
return null;
}
}
@Nullable
private static Integer dolbyVisionStringToProfile(@Nullable String profileString) {
if (profileString == null) {
return null;
}
switch (profileString) {
case "00":
return MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvavPer;
case "01":
return MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvavPen;
case "02":
return MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvheDer;
case "03":
return MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvheDen;
case "04":
return MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvheDtr;
case "05":
return MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvheStn;
case "06":
return MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvheDth;
case "07":
return MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvheDtb;
case "08":
return MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvheSt;
case "09":
return MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvavSe;
case "10":
return MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvav110;
default:
return null;
}
}
@Nullable
private static Integer dolbyVisionStringToLevel(@Nullable String levelString) {
if (levelString == null) {
return null;
}
// TODO (Internal: b/179261323): use framework constant for level 13.
switch (levelString) {
case "01":
return MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelHd24;
case "02":
return MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelHd30;
case "03":
return MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelFhd24;
case "04":
return MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelFhd30;
case "05":
return MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelFhd60;
case "06":
return MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelUhd24;
case "07":
return MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelUhd30;
case "08":
return MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelUhd48;
case "09":
return MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelUhd60;
case "10":
return MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelUhd120;
case "11":
return MediaCodecInfo.CodecProfileLevel.DolbyVisionLevel8k30;
case "12":
return MediaCodecInfo.CodecProfileLevel.DolbyVisionLevel8k60;
case "13":
return 0x1000;
default:
return null;
}
}
private static int av1LevelNumberToConst(int levelNumber) {
// See https://aomediacodec.github.io/av1-spec/av1-spec.pdf Annex A: Profiles and levels for
// more information on mapping AV1 codec strings to levels.
switch (levelNumber) {
case 0:
return MediaCodecInfo.CodecProfileLevel.AV1Level2;
case 1:
return MediaCodecInfo.CodecProfileLevel.AV1Level21;
case 2:
return MediaCodecInfo.CodecProfileLevel.AV1Level22;
case 3:
return MediaCodecInfo.CodecProfileLevel.AV1Level23;
case 4:
return MediaCodecInfo.CodecProfileLevel.AV1Level3;
case 5:
return MediaCodecInfo.CodecProfileLevel.AV1Level31;
case 6:
return MediaCodecInfo.CodecProfileLevel.AV1Level32;
case 7:
return MediaCodecInfo.CodecProfileLevel.AV1Level33;
case 8:
return MediaCodecInfo.CodecProfileLevel.AV1Level4;
case 9:
return MediaCodecInfo.CodecProfileLevel.AV1Level41;
case 10:
return MediaCodecInfo.CodecProfileLevel.AV1Level42;
case 11:
return MediaCodecInfo.CodecProfileLevel.AV1Level43;
case 12:
return MediaCodecInfo.CodecProfileLevel.AV1Level5;
case 13:
return MediaCodecInfo.CodecProfileLevel.AV1Level51;
case 14:
return MediaCodecInfo.CodecProfileLevel.AV1Level52;
case 15:
return MediaCodecInfo.CodecProfileLevel.AV1Level53;
case 16:
return MediaCodecInfo.CodecProfileLevel.AV1Level6;
case 17:
return MediaCodecInfo.CodecProfileLevel.AV1Level61;
case 18:
return MediaCodecInfo.CodecProfileLevel.AV1Level62;
case 19:
return MediaCodecInfo.CodecProfileLevel.AV1Level63;
case 20:
return MediaCodecInfo.CodecProfileLevel.AV1Level7;
case 21:
return MediaCodecInfo.CodecProfileLevel.AV1Level71;
case 22:
return MediaCodecInfo.CodecProfileLevel.AV1Level72;
case 23:
return MediaCodecInfo.CodecProfileLevel.AV1Level73;
default:
return -1;
}
}
private static int mp4aAudioObjectTypeToProfile(int profileNumber) {
switch (profileNumber) {
case 1:
return MediaCodecInfo.CodecProfileLevel.AACObjectMain;
case 2:
return MediaCodecInfo.CodecProfileLevel.AACObjectLC;
case 3:
return MediaCodecInfo.CodecProfileLevel.AACObjectSSR;
case 4:
return MediaCodecInfo.CodecProfileLevel.AACObjectLTP;
case 5:
return MediaCodecInfo.CodecProfileLevel.AACObjectHE;
case 6:
return MediaCodecInfo.CodecProfileLevel.AACObjectScalable;
case 17:
return MediaCodecInfo.CodecProfileLevel.AACObjectERLC;
case 20:
return MediaCodecInfo.CodecProfileLevel.AACObjectERScalable;
case 23:
return MediaCodecInfo.CodecProfileLevel.AACObjectLD;
case 29:
return MediaCodecInfo.CodecProfileLevel.AACObjectHE_PS;
case 39:
return MediaCodecInfo.CodecProfileLevel.AACObjectELD;
case 42:
return MediaCodecInfo.CodecProfileLevel.AACObjectXHE;
default:
return -1;
}
}
private CodecSpecificDataUtil() {}
}

View file

@ -473,7 +473,7 @@ public final class GlProgram {
? GLES20.GL_TEXTURE_2D
: GLES11Ext.GL_TEXTURE_EXTERNAL_OES,
texIdValue,
type == GLES20.GL_SAMPLER_2D && !externalTexturesRequireNearestSampling
type == GLES20.GL_SAMPLER_2D || !externalTexturesRequireNearestSampling
? GLES20.GL_LINEAR
: GLES20.GL_NEAREST);
GLES20.glUniform1i(location, texUnitIndex);

View file

@ -0,0 +1,48 @@
/*
* 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.common.util;
import static androidx.media3.common.util.Assertions.checkArgument;
/**
* Represents a rectangle by the coordinates of its 4 edges (left, bottom, right, top).
*
* <p>Note that the right and top coordinates are exclusive.
*
* <p>This class represents coordinates in the OpenGL coordinate convention: {@code left <= right}
* and {@code bottom <= top}.
*/
@UnstableApi
public final class GlRect {
public int left;
public int bottom;
public int right;
public int top;
/** Creates an instance from (0, 0) to the specified width and height. */
public GlRect(int width, int height) {
this(/* left= */ 0, /* bottom= */ 0, width, height);
}
/** Creates an instance. */
public GlRect(int left, int bottom, int right, int top) {
checkArgument(left <= right && bottom <= top);
this.left = left;
this.bottom = bottom;
this.right = right;
this.top = top;
}
}

View file

@ -16,7 +16,6 @@
package androidx.media3.common.util;
import static android.opengl.EGL14.EGL_CONTEXT_CLIENT_VERSION;
import static android.opengl.EGL14.EGL_NO_SURFACE;
import static android.opengl.GLU.gluErrorString;
import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkState;
@ -36,6 +35,7 @@ import android.opengl.GLUtils;
import android.opengl.Matrix;
import androidx.annotation.IntRange;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.media3.common.C;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
@ -294,7 +294,7 @@ public final class GlUtil {
sharedContext,
contextAttributes,
/* offset= */ 0);
if (eglContext == null) {
if (eglContext == null || eglContext.equals(EGL14.EGL_NO_CONTEXT)) {
EGL14.eglTerminate(eglDisplay);
throw new GlException(
"eglCreateContext() failed to create a valid context. The device may not support EGL"
@ -779,13 +779,13 @@ public final class GlUtil {
*/
public static void destroyEglContext(
@Nullable EGLDisplay eglDisplay, @Nullable EGLContext eglContext) throws GlException {
if (eglDisplay == null) {
if (eglDisplay == null || eglDisplay.equals(EGL14.EGL_NO_DISPLAY)) {
return;
}
EGL14.eglMakeCurrent(
eglDisplay, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_SURFACE, EGL14.EGL_NO_CONTEXT);
checkEglException("Error releasing context");
if (eglContext != null) {
if (eglContext != null && !eglContext.equals(EGL14.EGL_NO_CONTEXT)) {
EGL14.eglDestroyContext(eglDisplay, eglContext);
checkEglException("Error destroying context");
}
@ -801,10 +801,10 @@ public final class GlUtil {
*/
public static void destroyEglSurface(
@Nullable EGLDisplay eglDisplay, @Nullable EGLSurface eglSurface) throws GlException {
if (eglDisplay == null || eglSurface == null) {
if (eglDisplay == null || eglDisplay.equals(EGL14.EGL_NO_DISPLAY)) {
return;
}
if (EGL14.eglGetCurrentSurface(EGL14.EGL_DRAW) == EGL_NO_SURFACE) {
if (eglSurface == null || eglSurface.equals(EGL14.EGL_NO_SURFACE)) {
return;
}
@ -825,6 +825,181 @@ public final class GlUtil {
checkGlError();
}
/**
* Copies the pixels from {@code readFboId} into {@code drawFboId}. Requires OpenGL ES 3.0.
*
* <p>When the input pixel region (given by {@code readRect}) doesn't have the same size as the
* output region (given by {@code drawRect}), this method uses {@link GLES20#GL_LINEAR} filtering
* to scale the image contents.
*
* @param readFboId The framebuffer object to read from.
* @param readRect The rectangular region of {@code readFboId} to read from.
* @param drawFboId The framebuffer object to draw into.
* @param drawRect The rectangular region of {@code drawFboId} to draw into.
*/
public static void blitFrameBuffer(int readFboId, GlRect readRect, int drawFboId, GlRect drawRect)
throws GlException {
int[] boundFramebuffer = new int[1];
GLES20.glGetIntegerv(GLES20.GL_FRAMEBUFFER_BINDING, boundFramebuffer, /* offset= */ 0);
checkGlError();
GLES30.glBindFramebuffer(GLES30.GL_READ_FRAMEBUFFER, readFboId);
checkGlError();
GLES30.glBindFramebuffer(GLES30.GL_DRAW_FRAMEBUFFER, drawFboId);
checkGlError();
GLES30.glBlitFramebuffer(
readRect.left,
readRect.bottom,
readRect.right,
readRect.top,
drawRect.left,
drawRect.bottom,
drawRect.right,
drawRect.top,
GLES30.GL_COLOR_BUFFER_BIT,
GLES30.GL_LINEAR);
checkGlError();
GLES20.glBindFramebuffer(GLES20.GL_FRAMEBUFFER, /* framebuffer= */ boundFramebuffer[0]);
checkGlError();
}
/**
* Creates a pixel buffer object with a data store of the given size and usage {@link
* GLES30#GL_DYNAMIC_READ}.
*
* <p>The buffer is suitable for repeated modification by OpenGL and reads by the application.
*
* @param size The size of the buffer object's data store.
* @return The pixel buffer object.
*/
public static int createPixelBufferObject(int size) throws GlException {
int[] ids = new int[1];
GLES30.glGenBuffers(/* n= */ 1, ids, /* offset= */ 0);
GlUtil.checkGlError();
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, ids[0]);
GlUtil.checkGlError();
GLES30.glBufferData(
GLES30.GL_PIXEL_PACK_BUFFER, /* size= */ size, /* data= */ null, GLES30.GL_DYNAMIC_READ);
GlUtil.checkGlError();
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, /* buffer= */ 0);
GlUtil.checkGlError();
return ids[0];
}
/**
* Reads pixel data from the {@link GLES30#GL_COLOR_ATTACHMENT0} attachment of a framebuffer into
* the data store of a pixel buffer object.
*
* <p>The texture backing the color attachment of {@code readFboId} and the buffer store of {@code
* bufferId} must hold an image of the given {@code width} and {@code height} with format {@link
* GLES30#GL_RGBA} and type {@link GLES30#GL_UNSIGNED_BYTE}.
*
* <p>This a non-blocking call which reads the data asynchronously.
*
* <p>Requires API 24: This method must call the version of {@link GLES30#glReadPixels(int, int,
* int, int, int, int, int)} which accepts an integer offset as the last parameter. This version
* of glReadPixels is not available in the Java {@link GLES30} wrapper until API 24.
*
* <p>HDR support is not yet implemented.
*
* @param readFboId The framebuffer that holds pixel data.
* @param width The image width.
* @param height The image height.
* @param bufferId The pixel buffer object to read into.
*/
@RequiresApi(24)
public static void schedulePixelBufferRead(int readFboId, int width, int height, int bufferId)
throws GlException {
focusFramebufferUsingCurrentContext(readFboId, width, height);
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, bufferId);
GlUtil.checkGlError();
GLES30.glReadBuffer(GLES30.GL_COLOR_ATTACHMENT0);
GLES30.glReadPixels(
/* x= */ 0,
/* y= */ 0,
width,
height,
GLES30.GL_RGBA,
GLES30.GL_UNSIGNED_BYTE,
/* offset= */ 0);
GlUtil.checkGlError();
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, /* buffer= */ 0);
GlUtil.checkGlError();
}
/**
* Maps the pixel buffer object's data store of a given size and returns a {@link ByteBuffer} of
* OpenGL managed memory.
*
* <p>The application must not write into the returned {@link ByteBuffer}.
*
* <p>The pixel buffer object should have a {@linkplain #schedulePixelBufferRead previously
* scheduled pixel buffer read}.
*
* <p>When the application no longer needs to access the returned buffer, call {@link
* #unmapPixelBufferObject}.
*
* <p>This call blocks until the pixel buffer data from the last {@link #schedulePixelBufferRead}
* call is available.
*
* <p>Requires API 24: see {@link #schedulePixelBufferRead}.
*
* @param bufferId The pixel buffer object.
* @param size The size of the pixel buffer object's data store to be mapped.
* @return The {@link ByteBuffer} that holds pixel data.
*/
@RequiresApi(24)
public static ByteBuffer mapPixelBufferObject(int bufferId, int size) throws GlException {
GLES20.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, bufferId);
checkGlError();
ByteBuffer mappedPixelBuffer =
(ByteBuffer)
GLES30.glMapBufferRange(
GLES30.GL_PIXEL_PACK_BUFFER,
/* offset= */ 0,
/* length= */ size,
GLES30.GL_MAP_READ_BIT);
GlUtil.checkGlError();
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, /* buffer= */ 0);
GlUtil.checkGlError();
return mappedPixelBuffer;
}
/**
* Unmaps the pixel buffer object {@code bufferId}'s data store.
*
* <p>The pixel buffer object should be previously {@linkplain #mapPixelBufferObject mapped}.
*
* <p>After this method returns, accessing data inside a previously {@linkplain
* #mapPixelBufferObject mapped} {@link ByteBuffer} results in undefined behaviour.
*
* <p>When this method returns, the pixel buffer object {@code bufferId} can be reused by {@link
* #schedulePixelBufferRead}.
*
* <p>Requires API 24: see {@link #schedulePixelBufferRead}.
*
* @param bufferId The pixel buffer object.
*/
@RequiresApi(24)
public static void unmapPixelBufferObject(int bufferId) throws GlException {
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, bufferId);
GlUtil.checkGlError();
GLES30.glUnmapBuffer(GLES30.GL_PIXEL_PACK_BUFFER);
GlUtil.checkGlError();
GLES30.glBindBuffer(GLES30.GL_PIXEL_PACK_BUFFER, /* buffer= */ 0);
GlUtil.checkGlError();
}
/** Deletes a buffer object, or silently ignores the method call if {@code bufferId} is unused. */
public static void deleteBuffer(int bufferId) throws GlException {
GLES20.glDeleteBuffers(/* n= */ 1, new int[] {bufferId}, /* offset= */ 0);
checkGlError();
}
/**
* Throws a {@link GlException} with the given message if {@code expression} evaluates to {@code
* false}.

View file

@ -198,6 +198,9 @@ public final class ListenerSet<T extends @NonNull Object> {
/** Removes all listeners from the set. */
public void clear() {
verifyCurrentThread();
for (ListenerHolder<T> listenerHolder : listeners) {
listenerHolder.release(iterationFinishedEvent);
}
listeners.clear();
}

View file

@ -15,6 +15,8 @@
*/
package androidx.media3.common.util;
import static java.lang.Math.max;
import java.util.Arrays;
/** An append-only, auto-growing {@code long[]}. */
@ -49,6 +51,20 @@ public final class LongArray {
values[size++] = value;
}
/**
* Appends all elements of the specified array.
*
* @param values The array whose elements are to be added.
*/
public void addAll(long[] values) {
int newSize = size + values.length;
if (newSize > this.values.length) {
this.values = Arrays.copyOf(this.values, max(this.values.length * 2, newSize));
}
System.arraycopy(values, 0, this.values, size, values.length);
size = newSize;
}
/**
* Returns the value at a specified index.
*

View file

@ -28,6 +28,7 @@ import androidx.media3.common.MimeTypes;
import com.google.common.collect.ImmutableList;
import java.nio.ByteBuffer;
import java.util.List;
import java.util.Objects;
/** Helper class containing utility methods for managing {@link MediaFormat} instances. */
@UnstableApi
@ -79,7 +80,7 @@ public final class MediaFormatUtil {
.setAverageBitrate(
getInteger(
mediaFormat, MediaFormat.KEY_BIT_RATE, /* defaultValue= */ Format.NO_VALUE))
.setCodecs(mediaFormat.getString(MediaFormat.KEY_CODECS_STRING))
.setCodecs(getCodecString(mediaFormat))
.setFrameRate(getFrameRate(mediaFormat, /* defaultValue= */ Format.NO_VALUE))
.setWidth(
getInteger(mediaFormat, MediaFormat.KEY_WIDTH, /* defaultValue= */ Format.NO_VALUE))
@ -95,8 +96,7 @@ public final class MediaFormatUtil {
/* defaultValue= */ Format.NO_VALUE))
.setRotationDegrees(
getInteger(mediaFormat, MediaFormat.KEY_ROTATION, /* defaultValue= */ 0))
// TODO(b/278101856): Disallow invalid values after confirming.
.setColorInfo(getColorInfo(mediaFormat, /* allowInvalidValues= */ true))
.setColorInfo(getColorInfo(mediaFormat))
.setSampleRate(
getInteger(
mediaFormat, MediaFormat.KEY_SAMPLE_RATE, /* defaultValue= */ Format.NO_VALUE))
@ -269,13 +269,6 @@ public final class MediaFormatUtil {
*/
@Nullable
public static ColorInfo getColorInfo(MediaFormat mediaFormat) {
return getColorInfo(mediaFormat, /* allowInvalidValues= */ false);
}
// Internal methods.
@Nullable
private static ColorInfo getColorInfo(MediaFormat mediaFormat, boolean allowInvalidValues) {
if (SDK_INT < 24) {
// MediaFormat KEY_COLOR_TRANSFER and other KEY_COLOR values available from API 24.
return null;
@ -293,21 +286,17 @@ public final class MediaFormatUtil {
@Nullable
byte[] hdrStaticInfo =
hdrStaticInfoByteBuffer != null ? getArray(hdrStaticInfoByteBuffer) : null;
if (!allowInvalidValues) {
// Some devices may produce invalid values from MediaFormat#getInteger.
// See b/239435670 for more information.
if (!isValidColorSpace(colorSpace)) {
colorSpace = Format.NO_VALUE;
}
if (!isValidColorRange(colorRange)) {
colorRange = Format.NO_VALUE;
}
if (!isValidColorTransfer(colorTransfer)) {
colorTransfer = Format.NO_VALUE;
}
// Some devices may produce invalid values from MediaFormat#getInteger.
// See b/239435670 for more information.
if (!isValidColorSpace(colorSpace)) {
colorSpace = Format.NO_VALUE;
}
if (!isValidColorRange(colorRange)) {
colorRange = Format.NO_VALUE;
}
if (!isValidColorTransfer(colorTransfer)) {
colorTransfer = Format.NO_VALUE;
}
if (colorSpace != Format.NO_VALUE
|| colorRange != Format.NO_VALUE
|| colorTransfer != Format.NO_VALUE
@ -332,6 +321,32 @@ public final class MediaFormatUtil {
return mediaFormat.containsKey(name) ? mediaFormat.getFloat(name) : defaultValue;
}
/** Supports {@link MediaFormat#getString(String, String)} for {@code API < 29}. */
@Nullable
public static String getString(
MediaFormat mediaFormat, String name, @Nullable String defaultValue) {
return mediaFormat.containsKey(name) ? mediaFormat.getString(name) : defaultValue;
}
/**
* Returns a {@code Codecs string} of {@link MediaFormat}. In case of an H263 codec string, builds
* and returns an RFC 6381 H263 codec string using profile and level.
*/
@Nullable
@SuppressLint("InlinedApi") // Inlined MediaFormat keys.
private static String getCodecString(MediaFormat mediaFormat) {
// Add H263 profile and level to codec string as per RFC 6381.
if (Objects.equals(mediaFormat.getString(MediaFormat.KEY_MIME), MimeTypes.VIDEO_H263)
&& mediaFormat.containsKey(MediaFormat.KEY_PROFILE)
&& mediaFormat.containsKey(MediaFormat.KEY_LEVEL)) {
return CodecSpecificDataUtil.buildH263CodecString(
mediaFormat.getInteger(MediaFormat.KEY_PROFILE),
mediaFormat.getInteger(MediaFormat.KEY_LEVEL));
} else {
return getString(mediaFormat, MediaFormat.KEY_CODECS_STRING, /* defaultValue= */ null);
}
}
/**
* Returns the frame rate from a {@link MediaFormat}.
*

View file

@ -17,9 +17,9 @@ package androidx.media3.common.util;
import static java.lang.Math.min;
import com.google.common.base.Charsets;
import com.google.errorprone.annotations.CheckReturnValue;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
/** Wraps a byte array, providing methods that allow it to be read as a bitstream. */
@UnstableApi
@ -285,7 +285,7 @@ public final class ParsableBitArray {
* @return The string encoded by the bytes in UTF-8.
*/
public String readBytesAsString(int length) {
return readBytesAsString(length, Charsets.UTF_8);
return readBytesAsString(length, StandardCharsets.UTF_8);
}
/**

View file

@ -16,13 +16,14 @@
package androidx.media3.common.util;
import androidx.annotation.Nullable;
import com.google.common.base.Charsets;
import com.google.common.collect.ImmutableSet;
import com.google.common.primitives.Chars;
import com.google.common.primitives.Ints;
import com.google.common.primitives.UnsignedBytes;
import com.google.errorprone.annotations.CheckReturnValue;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
import java.nio.charset.StandardCharsets;
import java.util.Arrays;
/**
@ -37,7 +38,11 @@ public final class ParsableByteArray {
private static final char[] LF = {'\n'};
private static final ImmutableSet<Charset> SUPPORTED_CHARSETS_FOR_READLINE =
ImmutableSet.of(
Charsets.US_ASCII, Charsets.UTF_8, Charsets.UTF_16, Charsets.UTF_16BE, Charsets.UTF_16LE);
StandardCharsets.US_ASCII,
StandardCharsets.UTF_8,
StandardCharsets.UTF_16,
StandardCharsets.UTF_16BE,
StandardCharsets.UTF_16LE);
private byte[] data;
private int position;
@ -238,8 +243,8 @@ public final class ParsableByteArray {
/**
* Peeks at the next char.
*
* <p>Equivalent to passing {@link Charsets#UTF_16} or {@link Charsets#UTF_16BE} to {@link
* #peekChar(Charset)}.
* <p>Equivalent to passing {@link StandardCharsets#UTF_16} or {@link StandardCharsets#UTF_16BE}
* to {@link #peekChar(Charset)}.
*/
public char peekChar() {
return (char) ((data[position] & 0xFF) << 8 | (data[position + 1] & 0xFF));
@ -446,7 +451,7 @@ public final class ParsableByteArray {
* @return The string encoded by the bytes.
*/
public String readString(int length) {
return readString(length, Charsets.UTF_8);
return readString(length, StandardCharsets.UTF_8);
}
/**
@ -520,11 +525,11 @@ public final class ParsableByteArray {
/**
* Reads a line of text in UTF-8.
*
* <p>Equivalent to passing {@link Charsets#UTF_8} to {@link #readLine(Charset)}.
* <p>Equivalent to passing {@link StandardCharsets#UTF_8} to {@link #readLine(Charset)}.
*/
@Nullable
public String readLine() {
return readLine(Charsets.UTF_8);
return readLine(StandardCharsets.UTF_8);
}
/**
@ -550,7 +555,7 @@ public final class ParsableByteArray {
if (bytesLeft() == 0) {
return null;
}
if (!charset.equals(Charsets.US_ASCII)) {
if (!charset.equals(StandardCharsets.US_ASCII)) {
Charset unused = readUtfCharsetFromBom(); // Skip BOM if present
}
int lineLimit = findNextLineTerminator(charset);
@ -597,6 +602,41 @@ public final class ParsableByteArray {
return value;
}
/**
* Reads a little endian long of variable length.
*
* @throws IllegalStateException if the byte to be read is over the limit of the parsable byte
* array
* @return long value
*/
public long readUnsignedLeb128ToLong() {
long value = 0;
// At most, 63 bits of unsigned data can be stored in a long, which corresponds to 63/7=9 bytes
// in LEB128.
for (int i = 0; i < 9; i++) {
if (this.position == limit) {
throw new IllegalStateException("Attempting to read a byte over the limit.");
}
long currentByte = this.readUnsignedByte();
value |= (currentByte & 0x7F) << (i * 7);
if ((currentByte & 0x80) == 0) {
break;
}
}
return value;
}
/**
* Reads a little endian integer of variable length.
*
* @throws IllegalArgumentException if the read value is greater than {@link Integer#MAX_VALUE} or
* less than {@link Integer#MIN_VALUE}
* @return integer value
*/
public int readUnsignedLeb128ToInt() {
return Ints.checkedCast(readUnsignedLeb128ToLong());
}
/**
* Reads a UTF byte order mark (BOM) and returns the UTF {@link Charset} it represents. Returns
* {@code null} without advancing {@link #getPosition() position} if no BOM is found.
@ -608,14 +648,14 @@ public final class ParsableByteArray {
&& data[position + 1] == (byte) 0xBB
&& data[position + 2] == (byte) 0xBF) {
position += 3;
return Charsets.UTF_8;
return StandardCharsets.UTF_8;
} else if (bytesLeft() >= 2) {
if (data[position] == (byte) 0xFE && data[position + 1] == (byte) 0xFF) {
position += 2;
return Charsets.UTF_16BE;
return StandardCharsets.UTF_16BE;
} else if (data[position] == (byte) 0xFF && data[position + 1] == (byte) 0xFE) {
position += 2;
return Charsets.UTF_16LE;
return StandardCharsets.UTF_16LE;
}
}
return null;
@ -626,24 +666,25 @@ public final class ParsableByteArray {
*/
private int findNextLineTerminator(Charset charset) {
int stride;
if (charset.equals(Charsets.UTF_8) || charset.equals(Charsets.US_ASCII)) {
if (charset.equals(StandardCharsets.UTF_8) || charset.equals(StandardCharsets.US_ASCII)) {
stride = 1;
} else if (charset.equals(Charsets.UTF_16)
|| charset.equals(Charsets.UTF_16LE)
|| charset.equals(Charsets.UTF_16BE)) {
} else if (charset.equals(StandardCharsets.UTF_16)
|| charset.equals(StandardCharsets.UTF_16LE)
|| charset.equals(StandardCharsets.UTF_16BE)) {
stride = 2;
} else {
throw new IllegalArgumentException("Unsupported charset: " + charset);
}
for (int i = position; i < limit - (stride - 1); i += stride) {
if ((charset.equals(Charsets.UTF_8) || charset.equals(Charsets.US_ASCII))
if ((charset.equals(StandardCharsets.UTF_8) || charset.equals(StandardCharsets.US_ASCII))
&& Util.isLinebreak(data[i])) {
return i;
} else if ((charset.equals(Charsets.UTF_16) || charset.equals(Charsets.UTF_16BE))
} else if ((charset.equals(StandardCharsets.UTF_16)
|| charset.equals(StandardCharsets.UTF_16BE))
&& data[i] == 0x00
&& Util.isLinebreak(data[i + 1])) {
return i;
} else if (charset.equals(Charsets.UTF_16LE)
} else if (charset.equals(StandardCharsets.UTF_16LE)
&& data[i + 1] == 0x00
&& Util.isLinebreak(data[i])) {
return i;
@ -691,14 +732,16 @@ public final class ParsableByteArray {
private int peekCharacterAndSize(Charset charset) {
byte character;
short characterSize;
if ((charset.equals(Charsets.UTF_8) || charset.equals(Charsets.US_ASCII)) && bytesLeft() >= 1) {
if ((charset.equals(StandardCharsets.UTF_8) || charset.equals(StandardCharsets.US_ASCII))
&& bytesLeft() >= 1) {
character = (byte) Chars.checkedCast(UnsignedBytes.toInt(data[position]));
characterSize = 1;
} else if ((charset.equals(Charsets.UTF_16) || charset.equals(Charsets.UTF_16BE))
} else if ((charset.equals(StandardCharsets.UTF_16)
|| charset.equals(StandardCharsets.UTF_16BE))
&& bytesLeft() >= 2) {
character = (byte) Chars.fromBytes(data[position], data[position + 1]);
characterSize = 2;
} else if (charset.equals(Charsets.UTF_16LE) && bytesLeft() >= 2) {
} else if (charset.equals(StandardCharsets.UTF_16LE) && bytesLeft() >= 2) {
character = (byte) Chars.fromBytes(data[position + 1], data[position]);
characterSize = 2;
} else {

View file

@ -63,12 +63,12 @@ public final class RepeatModeUtil {
/**
* Gets the next repeat mode out of {@code enabledModes} starting from {@code currentMode}.
*
* @param currentMode The current repeat mode.
* @param enabledModes Bitmask of enabled modes.
* @param currentMode The current {@link Player.RepeatMode}.
* @param enabledModes The bitmask of enabled {@link RepeatToggleModes}.
* @return The next repeat mode.
*/
public static @Player.RepeatMode int getNextRepeatMode(
@Player.RepeatMode int currentMode, int enabledModes) {
@Player.RepeatMode int currentMode, @RepeatToggleModes int enabledModes) {
for (int offset = 1; offset <= 2; offset++) {
@Player.RepeatMode int proposedMode = (currentMode + offset) % 3;
if (isRepeatModeEnabled(proposedMode, enabledModes)) {
@ -79,13 +79,15 @@ public final class RepeatModeUtil {
}
/**
* Verifies whether a given {@code repeatMode} is enabled in the bitmask {@code enabledModes}.
* Verifies whether a given {@link Player.RepeatMode} is enabled in the bitmask of {@link
* RepeatToggleModes}.
*
* @param repeatMode The mode to check.
* @param enabledModes The bitmask representing the enabled modes.
* @param repeatMode The {@link Player.RepeatMode} to check.
* @param enabledModes The bitmask of enabled {@link RepeatToggleModes}.
* @return {@code true} if enabled.
*/
public static boolean isRepeatModeEnabled(@Player.RepeatMode int repeatMode, int enabledModes) {
public static boolean isRepeatModeEnabled(
@Player.RepeatMode int repeatMode, @RepeatToggleModes int enabledModes) {
switch (repeatMode) {
case Player.REPEAT_MODE_OFF:
return true;

View file

@ -271,7 +271,7 @@ public final class TimestampAdjuster {
* @return The corresponding value in microseconds.
*/
public static long ptsToUs(long pts) {
return (pts * C.MICROS_PER_SECOND) / 90000;
return Util.scaleLargeTimestamp(pts, C.MICROS_PER_SECOND, 90000);
}
/**
@ -295,6 +295,6 @@ public final class TimestampAdjuster {
* @return The corresponding value as a 90 kHz clock timestamp.
*/
public static long usToNonWrappedPts(long us) {
return (us * 90000) / C.MICROS_PER_SECOND;
return Util.scaleLargeTimestamp(us, 90000, C.MICROS_PER_SECOND);
}
}

View file

@ -16,6 +16,11 @@
package androidx.media3.common.util;
import static android.content.Context.UI_MODE_SERVICE;
import static androidx.media3.common.C.AUXILIARY_TRACK_TYPE_DEPTH_INVERSE;
import static androidx.media3.common.C.AUXILIARY_TRACK_TYPE_DEPTH_LINEAR;
import static androidx.media3.common.C.AUXILIARY_TRACK_TYPE_DEPTH_METADATA;
import static androidx.media3.common.C.AUXILIARY_TRACK_TYPE_ORIGINAL;
import static androidx.media3.common.C.AUXILIARY_TRACK_TYPE_UNDEFINED;
import static androidx.media3.common.Player.COMMAND_PLAY_PAUSE;
import static androidx.media3.common.Player.COMMAND_PREPARE;
import static androidx.media3.common.Player.COMMAND_SEEK_BACK;
@ -75,7 +80,6 @@ import android.view.Display;
import android.view.SurfaceView;
import android.view.WindowManager;
import androidx.annotation.ChecksSdkIntAtLeast;
import androidx.annotation.DoNotInline;
import androidx.annotation.DrawableRes;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
@ -91,7 +95,6 @@ import androidx.media3.common.Player;
import androidx.media3.common.Player.Commands;
import androidx.media3.common.audio.AudioProcessor;
import com.google.common.base.Ascii;
import com.google.common.base.Charsets;
import com.google.common.io.ByteStreams;
import com.google.common.math.DoubleMath;
import com.google.common.math.LongMath;
@ -102,6 +105,7 @@ import com.google.common.util.concurrent.Futures;
import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import com.google.common.util.concurrent.SettableFuture;
import com.google.errorprone.annotations.InlineMe;
import java.io.ByteArrayOutputStream;
import java.io.Closeable;
import java.io.File;
@ -112,6 +116,7 @@ import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
import java.util.ArrayDeque;
import java.util.ArrayList;
import java.util.Arrays;
@ -475,16 +480,15 @@ public final class Util {
}
/**
* Tests two objects for {@link Object#equals(Object)} equality, handling the case where one or
* both may be {@code null}.
*
* @param o1 The first object.
* @param o2 The second object.
* @return {@code o1 == null ? o2 == null : o1.equals(o2)}.
* @deprecated Use {@link Objects#equals(Object, Object)} instead.
*/
@UnstableApi
@Deprecated
@InlineMe(
replacement = "Objects.equals(o1, o2)",
imports = {"java.util.Objects"})
public static boolean areEqual(@Nullable Object o1, @Nullable Object o2) {
return o1 == null ? o2 == null : o1.equals(o2);
return Objects.equals(o1, o2);
}
/**
@ -962,16 +966,14 @@ public final class Util {
/**
* Returns the language tag for a {@link Locale}.
*
* <p>For API levels &ge; 21, this tag is IETF BCP 47 compliant. Use {@link
* #normalizeLanguageCode(String)} to retrieve a normalized IETF BCP 47 language tag for all API
* levels if needed.
* <p>This tag is IETF BCP 47 compliant.
*
* @param locale A {@link Locale}.
* @return The language tag.
*/
@UnstableApi
public static String getLocaleLanguageTag(Locale locale) {
return SDK_INT >= 21 ? getLocaleLanguageTagV21(locale) : locale.toString();
return locale.toLanguageTag();
}
/**
@ -1043,7 +1045,7 @@ public final class Util {
*/
@UnstableApi
public static String fromUtf8Bytes(byte[] bytes) {
return new String(bytes, Charsets.UTF_8);
return new String(bytes, StandardCharsets.UTF_8);
}
/**
@ -1056,7 +1058,7 @@ public final class Util {
*/
@UnstableApi
public static String fromUtf8Bytes(byte[] bytes, int offset, int length) {
return new String(bytes, offset, length, Charsets.UTF_8);
return new String(bytes, offset, length, StandardCharsets.UTF_8);
}
/**
@ -1067,7 +1069,7 @@ public final class Util {
*/
@UnstableApi
public static byte[] getUtf8Bytes(String value) {
return value.getBytes(Charsets.UTF_8);
return value.getBytes(StandardCharsets.UTF_8);
}
/**
@ -1598,7 +1600,7 @@ public final class Util {
*/
@UnstableApi
public static long sampleCountToDurationUs(long sampleCount, int sampleRate) {
return scaleLargeValue(sampleCount, C.MICROS_PER_SECOND, sampleRate, RoundingMode.FLOOR);
return scaleLargeValue(sampleCount, C.MICROS_PER_SECOND, sampleRate, RoundingMode.DOWN);
}
/**
@ -1615,7 +1617,7 @@ public final class Util {
*/
@UnstableApi
public static long durationUsToSampleCount(long durationUs, int sampleRate) {
return scaleLargeValue(durationUs, sampleRate, C.MICROS_PER_SECOND, RoundingMode.CEILING);
return scaleLargeValue(durationUs, sampleRate, C.MICROS_PER_SECOND, RoundingMode.UP);
}
/**
@ -1900,16 +1902,18 @@ public final class Util {
* Scales a large timestamp.
*
* <p>Equivalent to {@link #scaleLargeValue(long, long, long, RoundingMode)} with {@link
* RoundingMode#FLOOR}.
* RoundingMode#DOWN}.
*
* @param timestamp The timestamp to scale.
* @param multiplier The multiplier.
* @param divisor The divisor.
* @return The scaled timestamp.
*/
// TODO: b/372204124 - Consider switching this (and impls below) to HALF_UP rounding to reduce
// round-trip errors when switching between time bases with different resolutions.
@UnstableApi
public static long scaleLargeTimestamp(long timestamp, long multiplier, long divisor) {
return scaleLargeValue(timestamp, multiplier, divisor, RoundingMode.FLOOR);
return scaleLargeValue(timestamp, multiplier, divisor, RoundingMode.DOWN);
}
/**
@ -1922,7 +1926,7 @@ public final class Util {
*/
@UnstableApi
public static long[] scaleLargeTimestamps(List<Long> timestamps, long multiplier, long divisor) {
return scaleLargeValues(timestamps, multiplier, divisor, RoundingMode.FLOOR);
return scaleLargeValues(timestamps, multiplier, divisor, RoundingMode.DOWN);
}
/**
@ -1934,7 +1938,7 @@ public final class Util {
*/
@UnstableApi
public static void scaleLargeTimestampsInPlace(long[] timestamps, long multiplier, long divisor) {
scaleLargeValuesInPlace(timestamps, multiplier, divisor, RoundingMode.FLOOR);
scaleLargeValuesInPlace(timestamps, multiplier, divisor, RoundingMode.DOWN);
}
/**
@ -2246,6 +2250,24 @@ public final class Util {
}
case 12:
return AudioFormat.CHANNEL_OUT_7POINT1POINT4;
case 24:
if (Util.SDK_INT >= 32) {
return AudioFormat.CHANNEL_OUT_7POINT1POINT4
| AudioFormat.CHANNEL_OUT_FRONT_LEFT_OF_CENTER
| AudioFormat.CHANNEL_OUT_FRONT_RIGHT_OF_CENTER
| AudioFormat.CHANNEL_OUT_BACK_CENTER
| AudioFormat.CHANNEL_OUT_TOP_CENTER
| AudioFormat.CHANNEL_OUT_TOP_FRONT_CENTER
| AudioFormat.CHANNEL_OUT_TOP_BACK_CENTER
| AudioFormat.CHANNEL_OUT_TOP_SIDE_LEFT
| AudioFormat.CHANNEL_OUT_TOP_SIDE_RIGHT
| AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_LEFT
| AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_RIGHT
| AudioFormat.CHANNEL_OUT_BOTTOM_FRONT_CENTER
| AudioFormat.CHANNEL_OUT_LOW_FREQUENCY_2;
} else {
return AudioFormat.CHANNEL_INVALID;
}
default:
return AudioFormat.CHANNEL_INVALID;
}
@ -2253,7 +2275,6 @@ public final class Util {
/** Creates {@link AudioFormat} with given sampleRate, channelConfig, and encoding. */
@UnstableApi
@RequiresApi(21)
public static AudioFormat getAudioFormat(int sampleRate, int channelConfig, int encoding) {
return new AudioFormat.Builder()
.setSampleRate(sampleRate)
@ -2313,19 +2334,30 @@ public final class Util {
*/
@UnstableApi
public static int getPcmFrameSize(@C.PcmEncoding int pcmEncoding, int channelCount) {
return getByteDepth(pcmEncoding) * channelCount;
}
/**
* Returns the byte depth for audio with the specified encoding.
*
* @param pcmEncoding The encoding of the audio data.
* @return The byte depth of the audio.
*/
@UnstableApi
public static int getByteDepth(@C.PcmEncoding int pcmEncoding) {
switch (pcmEncoding) {
case C.ENCODING_PCM_8BIT:
return channelCount;
return 1;
case C.ENCODING_PCM_16BIT:
case C.ENCODING_PCM_16BIT_BIG_ENDIAN:
return channelCount * 2;
return 2;
case C.ENCODING_PCM_24BIT:
case C.ENCODING_PCM_24BIT_BIG_ENDIAN:
return channelCount * 3;
return 3;
case C.ENCODING_PCM_32BIT:
case C.ENCODING_PCM_32BIT_BIG_ENDIAN:
case C.ENCODING_PCM_FLOAT:
return channelCount * 4;
return 4;
case C.ENCODING_INVALID:
case Format.NO_VALUE:
default:
@ -2417,7 +2449,6 @@ public final class Util {
* @see AudioManager#generateAudioSessionId()
*/
@UnstableApi
@RequiresApi(21)
public static int generateAudioSessionIdV21(Context context) {
@Nullable
AudioManager audioManager = ((AudioManager) context.getSystemService(Context.AUDIO_SERVICE));
@ -3065,8 +3096,7 @@ public final class Util {
*/
@UnstableApi
public static boolean isWear(Context context) {
return SDK_INT >= 20
&& context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
return context.getPackageManager().hasSystemFeature(PackageManager.FEATURE_WATCH);
}
/**
@ -3294,9 +3324,32 @@ public final class Util {
if ((roleFlags & C.ROLE_FLAG_TRICK_PLAY) != 0) {
result.add("trick-play");
}
if ((roleFlags & C.ROLE_FLAG_AUXILIARY) != 0) {
result.add("auxiliary");
}
return result;
}
/** Returns a string representation of the {@link C.AuxiliaryTrackType}. */
@UnstableApi
public static String getAuxiliaryTrackTypeString(@C.AuxiliaryTrackType int auxiliaryTrackType) {
// LINT.IfChange(auxiliary_track_type)
switch (auxiliaryTrackType) {
case AUXILIARY_TRACK_TYPE_UNDEFINED:
return "undefined";
case AUXILIARY_TRACK_TYPE_ORIGINAL:
return "original";
case AUXILIARY_TRACK_TYPE_DEPTH_LINEAR:
return "depth-linear";
case AUXILIARY_TRACK_TYPE_DEPTH_INVERSE:
return "depth-inverse";
case AUXILIARY_TRACK_TYPE_DEPTH_METADATA:
return "depth metadata";
default:
throw new IllegalStateException("Unsupported auxiliary track type");
}
}
/**
* Returns the current time in milliseconds since the epoch.
*
@ -3373,13 +3426,14 @@ public final class Util {
// bounds. From API 29, if the app targets API 29 or later, the {@link
// MediaFormat#KEY_ALLOW_FRAME_DROP} key prevents frame dropping even when the surface is
// full.
// Some API 30 devices might drop frames despite setting {@link
// MediaFormat#KEY_ALLOW_FRAME_DROP} to 0. See b/307518793 and b/289983935.
// Some devices might drop frames despite setting {@link
// MediaFormat#KEY_ALLOW_FRAME_DROP} to 0. See b/307518793, b/289983935 and b/353487886.
return SDK_INT < 29
|| context.getApplicationInfo().targetSdkVersion < 29
|| (SDK_INT == 30
&& (Ascii.equalsIgnoreCase(MODEL, "moto g(20)")
|| Ascii.equalsIgnoreCase(MODEL, "rmx3231")));
|| ((SDK_INT == 30
&& (Ascii.equalsIgnoreCase(MODEL, "moto g(20)")
|| Ascii.equalsIgnoreCase(MODEL, "rmx3231")))
|| (SDK_INT == 34 && Ascii.equalsIgnoreCase(MODEL, "sm-x200")));
}
/**
@ -3499,9 +3553,7 @@ public final class Util {
@UnstableApi
public static Drawable getDrawable(
Context context, Resources resources, @DrawableRes int drawableRes) {
return SDK_INT >= 21
? Api21.getDrawable(context, resources, drawableRes)
: resources.getDrawable(drawableRes);
return resources.getDrawable(drawableRes, context.getTheme());
}
/**
@ -3663,11 +3715,6 @@ public final class Util {
return split(config.getLocales().toLanguageTags(), ",");
}
@RequiresApi(21)
private static String getLocaleLanguageTagV21(Locale locale) {
return locale.toLanguageTag();
}
private static HashMap<String, String> createIsoLanguageReplacementMap() {
String[] iso2Languages = Locale.getISOLanguages();
HashMap<String, String> replacedLanguages =
@ -3887,18 +3934,9 @@ public final class Util {
0xF3
};
@RequiresApi(21)
private static final class Api21 {
@DoNotInline
public static Drawable getDrawable(Context context, Resources resources, @DrawableRes int res) {
return resources.getDrawable(res, context.getTheme());
}
}
@RequiresApi(29)
private static class Api29 {
@DoNotInline
public static void startForeground(
Service mediaSessionService,
int notificationId,

View file

@ -20,6 +20,7 @@ import static com.google.common.truth.Truth.assertThat;
import android.net.Uri;
import android.os.Bundle;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.collect.ImmutableList;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -68,6 +69,7 @@ public class MediaMetadataTest {
assertThat(mediaMetadata.compilation).isNull();
assertThat(mediaMetadata.station).isNull();
assertThat(mediaMetadata.mediaType).isNull();
assertThat(mediaMetadata.supportedCommands).isEmpty();
assertThat(mediaMetadata.extras).isNull();
}
@ -278,6 +280,7 @@ public class MediaMetadataTest {
.setCompilation("Amazing songs.")
.setStation("radio station")
.setMediaType(MediaMetadata.MEDIA_TYPE_MIXED)
.setSupportedCommands(ImmutableList.of("command1", "command2"))
.setExtras(extras)
.build();
}

View file

@ -148,6 +148,7 @@ public final class MimeTypesTest {
assertThat(MimeTypes.getTrackType(MimeTypes.APPLICATION_CEA608)).isEqualTo(C.TRACK_TYPE_TEXT);
assertThat(MimeTypes.getTrackType(MimeTypes.APPLICATION_EMSG)).isEqualTo(C.TRACK_TYPE_METADATA);
assertThat(MimeTypes.getTrackType(MimeTypes.APPLICATION_AIT)).isEqualTo(C.TRACK_TYPE_METADATA);
assertThat(MimeTypes.getTrackType(MimeTypes.APPLICATION_CAMERA_MOTION))
.isEqualTo(C.TRACK_TYPE_CAMERA_MOTION);
assertThat(MimeTypes.getTrackType("application/custom")).isEqualTo(C.TRACK_TYPE_UNKNOWN);

View file

@ -40,7 +40,9 @@ import androidx.media3.common.SimpleBasePlayer.State;
import androidx.media3.common.text.Cue;
import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.Size;
import androidx.media3.extractor.metadata.icy.IcyInfo;
import androidx.media3.test.utils.FakeMetadataEntry;
import androidx.media3.test.utils.FakeTimeline;
import androidx.media3.test.utils.TestUtil;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@ -225,6 +227,15 @@ public class SimpleBasePlayerTest {
Size surfaceSize = new Size(480, 360);
DeviceInfo deviceInfo =
new DeviceInfo.Builder(DeviceInfo.PLAYBACK_TYPE_LOCAL).setMaxVolume(7).build();
MediaMetadata mediaMetadata = new MediaMetadata.Builder().setTitle("title").build();
Tracks tracks =
new Tracks(
ImmutableList.of(
new Tracks.Group(
new TrackGroup(new Format.Builder().build()),
/* adaptiveSupported= */ true,
/* trackSupport= */ new int[] {C.FORMAT_HANDLED},
/* trackSelected= */ new boolean[] {true})));
ImmutableList<SimpleBasePlayer.MediaItemData> playlist =
ImmutableList.of(
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ new Object()).build(),
@ -236,6 +247,8 @@ public class SimpleBasePlayerTest {
new AdPlaybackState(
/* adsId= */ new Object(), /* adGroupTimesUs...= */ 555, 666))
.build()))
.setMediaMetadata(mediaMetadata)
.setTracks(tracks)
.build());
MediaMetadata playlistMetadata = new MediaMetadata.Builder().setArtist("artist").build();
SimpleBasePlayer.PositionSupplier contentPositionSupplier = () -> 456;
@ -312,7 +325,10 @@ public class SimpleBasePlayerTest {
assertThat(state.surfaceSize).isEqualTo(surfaceSize);
assertThat(state.newlyRenderedFirstFrame).isTrue();
assertThat(state.timedMetadata).isEqualTo(timedMetadata);
assertThat(state.playlist).isEqualTo(playlist);
assertThat(state.getPlaylist()).isEqualTo(playlist);
assertThat(state.timeline.getWindowCount()).isEqualTo(2);
assertThat(state.currentTracks).isEqualTo(tracks);
assertThat(state.currentMetadata).isEqualTo(mediaMetadata);
assertThat(state.playlistMetadata).isEqualTo(playlistMetadata);
assertThat(state.currentMediaItemIndex).isEqualTo(1);
assertThat(state.currentAdGroupIndex).isEqualTo(1);
@ -327,6 +343,69 @@ public class SimpleBasePlayerTest {
assertThat(state.discontinuityPositionMs).isEqualTo(400);
}
@Test
public void stateBuilderBuild_withExplicitTimeline_setsCorrectValues() {
MediaMetadata mediaMetadata = new MediaMetadata.Builder().setTitle("title").build();
Tracks tracks =
new Tracks(
ImmutableList.of(
new Tracks.Group(
new TrackGroup(new Format.Builder().build()),
/* adaptiveSupported= */ true,
/* trackSupport= */ new int[] {C.FORMAT_HANDLED},
/* trackSelected= */ new boolean[] {true})));
Timeline timeline = new FakeTimeline(/* windowCount= */ 2);
State state = new State.Builder().setPlaylist(timeline, tracks, mediaMetadata).build();
assertThat(state.timeline).isEqualTo(timeline);
assertThat(state.currentTracks).isEqualTo(tracks);
assertThat(state.currentMetadata).isEqualTo(mediaMetadata);
}
@Test
public void
stateBuilderBuild_withUndefinedMediaMetadataAndExplicitTimeline_derivesMediaMetadataFromTracksAndMediaItem()
throws Exception {
Timeline timeline =
new FakeTimeline(
new FakeTimeline.TimelineWindowDefinition(
/* periodCount= */ 1,
/* id= */ 0,
/* isSeekable= */ true,
/* isDynamic= */ true,
/* isLive= */ true,
/* isPlaceholder= */ false,
/* durationUs= */ 1000,
/* defaultPositionUs= */ 0,
/* windowOffsetInFirstPeriodUs= */ 0,
ImmutableList.of(AdPlaybackState.NONE),
new MediaItem.Builder()
.setMediaId("1")
.setMediaMetadata(new MediaMetadata.Builder().setArtist("artist").build())
.build()));
Tracks tracks =
new Tracks(
ImmutableList.of(
new Tracks.Group(
new TrackGroup(
new Format.Builder()
.setMetadata(
new Metadata(
new IcyInfo(
/* rawMetadata= */ new byte[0], "title", /* url= */ null)))
.build()),
/* adaptiveSupported= */ true,
/* trackSupport= */ new int[] {C.FORMAT_HANDLED},
/* trackSelected= */ new boolean[] {true})));
State state =
new State.Builder().setPlaylist(timeline, tracks, /* currentMetadata= */ null).build();
assertThat(state.currentMetadata)
.isEqualTo(new MediaMetadata.Builder().setArtist("artist").setTitle("title").build());
}
@Test
public void stateBuilderBuild_emptyTimelineWithReadyState_throwsException() {
assertThrows(
@ -8069,6 +8148,211 @@ public class SimpleBasePlayerTest {
verifyNoMoreInteractions(listener);
}
@SuppressWarnings("deprecation") // Verifying deprecated listener calls.
@Test
public void seekTo_asyncHandlingToNewItem_usesPlaceholderStateWithUpdatedTracksAndMetadata() {
MediaItem newMediaItem = new MediaItem.Builder().setMediaId("2").build();
Tracks newTracks =
new Tracks(
ImmutableList.of(
new Tracks.Group(
new TrackGroup(new Format.Builder().build()),
/* adaptiveSupported= */ true,
/* trackSupport= */ new int[] {C.FORMAT_HANDLED},
/* trackSelected= */ new boolean[] {true})));
MediaMetadata newMediaMetadata = new MediaMetadata.Builder().setTitle("title").build();
State state =
new State.Builder()
.setAvailableCommands(new Commands.Builder().addAllCommands().build())
.setPlaylist(
ImmutableList.of(
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1).build(),
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 2)
.setMediaItem(newMediaItem)
.setTracks(newTracks)
.setMediaMetadata(newMediaMetadata)
.build()))
.build();
SettableFuture<?> future = SettableFuture.create();
SimpleBasePlayer player =
new SimpleBasePlayer(Looper.myLooper()) {
@Override
protected State getState() {
return state;
}
@Override
protected ListenableFuture<?> handleSeek(
int mediaItemIndex, long positionMs, @Player.Command int seekCommand) {
return future;
}
};
Listener listener = mock(Listener.class);
player.addListener(listener);
player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 3000);
// Verify placeholder state and listener calls.
assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1);
assertThat(player.getCurrentTracks()).isEqualTo(newTracks);
assertThat(player.getMediaMetadata()).isEqualTo(newMediaMetadata);
verify(listener).onMediaItemTransition(newMediaItem, Player.MEDIA_ITEM_TRANSITION_REASON_SEEK);
verify(listener).onTracksChanged(newTracks);
verify(listener).onMediaMetadataChanged(newMediaMetadata);
verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK);
verify(listener).onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_SEEK));
verifyNoMoreInteractions(listener);
}
@SuppressWarnings("deprecation") // Verifying deprecated listener calls.
@Test
public void
seekTo_asyncHandlingToNewItemWithExplicitTimeline_usesPlaceholderStateWithEmptyTracksAndMetadata() {
Tracks tracks =
new Tracks(
ImmutableList.of(
new Tracks.Group(
new TrackGroup(new Format.Builder().build()),
/* adaptiveSupported= */ true,
/* trackSupport= */ new int[] {C.FORMAT_HANDLED},
/* trackSelected= */ new boolean[] {true})));
MediaMetadata mediaMetadata = new MediaMetadata.Builder().setTitle("title").build();
Timeline timeline = new FakeTimeline(/* windowCount= */ 2);
State state =
new State.Builder()
.setAvailableCommands(new Commands.Builder().addAllCommands().build())
.setPlaylist(timeline, tracks, mediaMetadata)
.build();
SettableFuture<?> future = SettableFuture.create();
SimpleBasePlayer player =
new SimpleBasePlayer(Looper.myLooper()) {
@Override
protected State getState() {
return state;
}
@Override
protected ListenableFuture<?> handleSeek(
int mediaItemIndex, long positionMs, @Player.Command int seekCommand) {
return future;
}
};
Listener listener = mock(Listener.class);
player.addListener(listener);
player.seekTo(/* mediaItemIndex= */ 1, /* positionMs= */ 3000);
// Verify placeholder state and listener calls.
assertThat(player.getCurrentMediaItemIndex()).isEqualTo(1);
assertThat(player.getCurrentTracks()).isEqualTo(Tracks.EMPTY);
assertThat(player.getMediaMetadata()).isEqualTo(MediaMetadata.EMPTY);
verify(listener)
.onMediaItemTransition(
timeline.getWindow(/* windowIndex= */ 1, new Timeline.Window()).mediaItem,
Player.MEDIA_ITEM_TRANSITION_REASON_SEEK);
verify(listener).onTracksChanged(Tracks.EMPTY);
verify(listener).onMediaMetadataChanged(MediaMetadata.EMPTY);
verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK);
verify(listener).onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_SEEK));
verifyNoMoreInteractions(listener);
}
@SuppressWarnings("deprecation") // Verifying deprecated listener calls.
@Test
public void
seekTo_asyncHandlingToSameItem_usesPlaceholderStateWithoutChangingTracksAndMetadata() {
Tracks tracks =
new Tracks(
ImmutableList.of(
new Tracks.Group(
new TrackGroup(new Format.Builder().build()),
/* adaptiveSupported= */ true,
/* trackSupport= */ new int[] {C.FORMAT_HANDLED},
/* trackSelected= */ new boolean[] {true})));
MediaMetadata mediaMetadata = new MediaMetadata.Builder().setTitle("title").build();
State state =
new State.Builder()
.setAvailableCommands(new Commands.Builder().addAllCommands().build())
.setPlaylist(
ImmutableList.of(
new SimpleBasePlayer.MediaItemData.Builder(/* uid= */ 1)
.setTracks(tracks)
.setMediaMetadata(mediaMetadata)
.build()))
.build();
SettableFuture<?> future = SettableFuture.create();
SimpleBasePlayer player =
new SimpleBasePlayer(Looper.myLooper()) {
@Override
protected State getState() {
return state;
}
@Override
protected ListenableFuture<?> handleSeek(
int mediaItemIndex, long positionMs, @Player.Command int seekCommand) {
return future;
}
};
Listener listener = mock(Listener.class);
player.addListener(listener);
player.seekTo(/* positionMs= */ 3000);
// Verify placeholder state and listener calls.
assertThat(player.getCurrentTracks()).isEqualTo(tracks);
assertThat(player.getMediaMetadata()).isEqualTo(mediaMetadata);
verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK);
verify(listener).onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_SEEK));
verifyNoMoreInteractions(listener);
}
@SuppressWarnings("deprecation") // Verifying deprecated listener calls.
@Test
public void
seekTo_asyncHandlingToSameItemWithExplicitTimeline_usesPlaceholderStateWithoutChangingTracksAndMetadata() {
Tracks tracks =
new Tracks(
ImmutableList.of(
new Tracks.Group(
new TrackGroup(new Format.Builder().build()),
/* adaptiveSupported= */ true,
/* trackSupport= */ new int[] {C.FORMAT_HANDLED},
/* trackSelected= */ new boolean[] {true})));
MediaMetadata mediaMetadata = new MediaMetadata.Builder().setTitle("title").build();
Timeline timeline = new FakeTimeline(/* windowCount= */ 2);
State state =
new State.Builder()
.setAvailableCommands(new Commands.Builder().addAllCommands().build())
.setPlaylist(timeline, tracks, mediaMetadata)
.build();
SettableFuture<?> future = SettableFuture.create();
SimpleBasePlayer player =
new SimpleBasePlayer(Looper.myLooper()) {
@Override
protected State getState() {
return state;
}
@Override
protected ListenableFuture<?> handleSeek(
int mediaItemIndex, long positionMs, @Player.Command int seekCommand) {
return future;
}
};
Listener listener = mock(Listener.class);
player.addListener(listener);
player.seekTo(/* positionMs= */ 3000);
// Verify placeholder state and listener calls.
assertThat(player.getCurrentTracks()).isEqualTo(tracks);
assertThat(player.getMediaMetadata()).isEqualTo(mediaMetadata);
verify(listener).onPositionDiscontinuity(Player.DISCONTINUITY_REASON_SEEK);
verify(listener).onPositionDiscontinuity(any(), any(), eq(Player.DISCONTINUITY_REASON_SEEK));
verifyNoMoreInteractions(listener);
}
@Test
public void seekTo_withoutAvailableCommandForSeekToMediaItem_isNotForwarded() {
State state =

View file

@ -34,11 +34,7 @@ public final class VideoSizeTest {
@Test
public void roundTripViaBundle_ofArbitraryVideoSize_yieldsEqualInstance() {
VideoSize videoSize =
new VideoSize(
/* width= */ 9,
/* height= */ 8,
/* unappliedRotationDegrees= */ 7,
/* pixelWidthHeightRatio= */ 6);
new VideoSize(/* width= */ 9, /* height= */ 8, /* pixelWidthHeightRatio= */ 6);
assertThat(roundTripViaBundle(videoSize)).isEqualTo(videoSize);
}

View file

@ -0,0 +1,227 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.common.audio;
import static androidx.media3.common.audio.SonicTestingUtils.calculateAccumulatedTruncationErrorForResampling;
import static androidx.media3.test.utils.TestUtil.generateFloatInRange;
import static com.google.common.truth.Truth.assertThat;
import static java.lang.Math.max;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableSet;
import com.google.common.collect.Range;
import java.math.BigDecimal;
import java.math.RoundingMode;
import java.nio.ByteBuffer;
import java.nio.ShortBuffer;
import java.util.Random;
import org.junit.Test;
import org.junit.runner.RunWith;
import org.robolectric.ParameterizedRobolectricTestRunner;
import org.robolectric.ParameterizedRobolectricTestRunner.Parameter;
import org.robolectric.ParameterizedRobolectricTestRunner.Parameters;
/** Parameterized robolectric test for {@link Sonic}. */
@RunWith(ParameterizedRobolectricTestRunner.class)
public final class RandomParameterizedSonicTest {
private static final int BLOCK_SIZE = 4096;
private static final int BYTES_PER_SAMPLE = 2;
private static final int SAMPLE_RATE = 48000;
// Max 10 min streams.
private static final long MAX_LENGTH_SAMPLES = 10 * 60 * SAMPLE_RATE;
/** Defines how many random instances of each parameter the test runner should generate. */
private static final int PARAM_COUNT = 5;
private static final int SPEED_DECIMAL_PRECISION = 2;
/**
* Allowed error tolerance ratio for number of output samples for Sonic's time stretching
* algorithm.
*
* <p>The actual tolerance is calculated as {@code expectedOutputSampleCount /
* TIME_STRETCHING_SAMPLE_DRIFT_TOLERANCE}, rounded to the nearest integer value. However, we
* always allow a minimum tolerance of ±1 samples.
*
* <p>This tolerance is roughly equal to an error of 900us/~44 samples/0.000017% for a 90 min mono
* stream @48KHz. To obtain the value, we ran 100 iterations of {@link
* #timeStretching_returnsExpectedNumberOfSamples()} (by setting {@link #PARAM_COUNT} to 10) and
* we calculated the average delta percentage between expected number of samples and actual number
* of samples (b/366169590).
*/
private static final BigDecimal TIME_STRETCHING_SAMPLE_DRIFT_TOLERANCE =
new BigDecimal("0.00000017");
private static final ImmutableList<Range<Float>> SPEED_RANGES =
ImmutableList.of(
Range.closedOpen(0f, 0.5f),
Range.closedOpen(0.5f, 1f),
Range.closedOpen(1f, 2f),
Range.closedOpen(2f, 20f));
private static final Random random = new Random(/* seed */ 0);
private static final ImmutableList<Object[]> sParams = initParams();
@Parameters(name = "speed={0}, streamLength={1}")
public static ImmutableList<Object[]> params() {
// params() is called multiple times, so return cached parameters to avoid regenerating
// different random parameter values.
return sParams;
}
/**
* Returns a list of random parameter combinations with which to run the tests in this class.
*
* <p>Each list item contains a value for {{@link #speed}, {@link #streamLength}} stored within an
* Object array.
*
* <p>The method generates {@link #PARAM_COUNT} random {@link #speed} values and {@link
* #PARAM_COUNT} random {@link #streamLength} values. These generated values are then grouped into
* all possible combinations, and every group passed as parameters for each test.
*/
private static ImmutableList<Object[]> initParams() {
ImmutableSet.Builder<Object[]> paramsBuilder = new ImmutableSet.Builder<>();
ImmutableSet.Builder<BigDecimal> speedsBuilder = new ImmutableSet.Builder<>();
for (int i = 0; i < PARAM_COUNT; i++) {
Range<Float> range = SPEED_RANGES.get(i % SPEED_RANGES.size());
BigDecimal speed =
BigDecimal.valueOf(generateFloatInRange(random, range))
.setScale(SPEED_DECIMAL_PRECISION, RoundingMode.HALF_EVEN);
speedsBuilder.add(speed);
}
ImmutableSet<BigDecimal> speeds = speedsBuilder.build();
ImmutableSet<Long> lengths =
new ImmutableSet.Builder<Long>()
.addAll(
random
.longs(/* min */ 0, MAX_LENGTH_SAMPLES)
.distinct()
.limit(PARAM_COUNT)
.iterator())
.build();
for (long length : lengths) {
for (BigDecimal speed : speeds) {
paramsBuilder.add(new Object[] {speed, length});
}
}
return paramsBuilder.build().asList();
}
@Parameter(0)
public BigDecimal speed;
@Parameter(1)
public long streamLength;
@Test
public void resampling_returnsExpectedNumberOfSamples() {
byte[] inputBuffer = new byte[BLOCK_SIZE * BYTES_PER_SAMPLE];
ShortBuffer outBuffer = ShortBuffer.allocate(BLOCK_SIZE);
// Use same speed and pitch values for Sonic to resample stream.
Sonic sonic =
new Sonic(
/* inputSampleRateHz= */ SAMPLE_RATE,
/* channelCount= */ 1,
/* speed= */ speed.floatValue(),
/* pitch= */ speed.floatValue(),
/* outputSampleRateHz= */ SAMPLE_RATE);
long readSampleCount = 0;
for (long samplesLeft = streamLength; samplesLeft > 0; samplesLeft -= BLOCK_SIZE) {
random.nextBytes(inputBuffer);
if (samplesLeft >= BLOCK_SIZE) {
sonic.queueInput(ByteBuffer.wrap(inputBuffer).asShortBuffer());
} else {
// The last buffer to queue might have less samples than BLOCK_SIZE, so we should only queue
// the remaining number of samples (samplesLeft).
sonic.queueInput(
ByteBuffer.wrap(inputBuffer, 0, (int) (samplesLeft * BYTES_PER_SAMPLE))
.asShortBuffer());
sonic.queueEndOfStream();
}
while (sonic.getOutputSize() > 0) {
sonic.getOutput(outBuffer);
readSampleCount += outBuffer.position();
outBuffer.clear();
}
}
sonic.flush();
BigDecimal bigLength = new BigDecimal(String.valueOf(streamLength));
// The scale of expectedSize will be bigLength.scale() - speed.scale(). Thus, the result should
// always yield an integer.
BigDecimal expectedSize = bigLength.divide(speed, RoundingMode.HALF_EVEN);
long accumulatedTruncationError =
calculateAccumulatedTruncationErrorForResampling(
bigLength, new BigDecimal(SAMPLE_RATE), speed);
assertThat(readSampleCount)
.isWithin(1)
.of(expectedSize.longValueExact() - accumulatedTruncationError);
}
@Test
public void timeStretching_returnsExpectedNumberOfSamples() {
byte[] buf = new byte[BLOCK_SIZE * BYTES_PER_SAMPLE];
ShortBuffer outBuffer = ShortBuffer.allocate(BLOCK_SIZE);
Sonic sonic =
new Sonic(
/* inputSampleRateHz= */ SAMPLE_RATE,
/* channelCount= */ 1,
speed.floatValue(),
/* pitch= */ 1,
/* outputSampleRateHz= */ SAMPLE_RATE);
long readSampleCount = 0;
for (long samplesLeft = streamLength; samplesLeft > 0; samplesLeft -= BLOCK_SIZE) {
random.nextBytes(buf);
if (samplesLeft >= BLOCK_SIZE) {
sonic.queueInput(ByteBuffer.wrap(buf).asShortBuffer());
} else {
sonic.queueInput(
ByteBuffer.wrap(buf, 0, (int) (samplesLeft * BYTES_PER_SAMPLE)).asShortBuffer());
sonic.queueEndOfStream();
}
while (sonic.getOutputSize() > 0) {
sonic.getOutput(outBuffer);
readSampleCount += outBuffer.position();
outBuffer.clear();
}
}
sonic.flush();
BigDecimal bigLength = new BigDecimal(String.valueOf(streamLength));
// The scale of expectedSampleCount will be bigLength.scale() - speed.scale(). Thus, the result
// should always yield an integer.
BigDecimal expectedSampleCount = bigLength.divide(speed, RoundingMode.HALF_EVEN);
// Calculate allowed tolerance and round to nearest integer.
BigDecimal allowedTolerance =
TIME_STRETCHING_SAMPLE_DRIFT_TOLERANCE
.multiply(expectedSampleCount)
.setScale(/* newScale= */ 0, RoundingMode.HALF_EVEN);
// Always allow at least 1 sample of tolerance.
long tolerance = max(allowedTolerance.longValue(), 1);
assertThat(readSampleCount).isWithin(tolerance).of(expectedSampleCount.longValueExact());
}
}

View file

@ -0,0 +1,110 @@
/*
* Copyright (C) 2024 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package androidx.media3.common.audio;
import static com.google.common.truth.Truth.assertThat;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.nio.ShortBuffer;
import org.junit.Rule;
import org.junit.Test;
import org.junit.rules.Timeout;
import org.junit.runner.RunWith;
/** Unit test for {@link Sonic}. */
@RunWith(AndroidJUnit4.class)
public class SonicTest {
@Rule public final Timeout globalTimeout = Timeout.millis(1000);
@Test
public void resample_toDoubleRate_linearlyInterpolatesSamples() {
ShortBuffer inputBuffer = ShortBuffer.wrap(new short[] {0, 10, 20, 30, 40, 50});
Sonic sonic =
new Sonic(
/* inputSampleRateHz= */ 44100,
/* channelCount= */ 1,
/* speed= */ 1,
/* pitch= */ 1,
/* outputSampleRateHz= */ 88200);
sonic.queueInput(inputBuffer);
sonic.queueEndOfStream();
ShortBuffer outputBuffer = ShortBuffer.allocate(sonic.getOutputSize() / 2);
sonic.getOutput(outputBuffer);
// End of stream is padded with silence, so last sample will be interpolated between (50; 0).
assertThat(outputBuffer.array())
.isEqualTo(new short[] {0, 5, 10, 15, 20, 25, 30, 35, 40, 45, 50, 25});
}
@Test
public void resample_toHalfRate_linearlyInterpolatesSamples() {
ShortBuffer inputBuffer =
ShortBuffer.wrap(new short[] {-40, -30, -20, -10, 0, 10, 20, 30, 40, 50});
Sonic sonic =
new Sonic(
/* inputSampleRateHz= */ 44100,
/* channelCount= */ 1,
/* speed= */ 1,
/* pitch= */ 1,
/* outputSampleRateHz= */ 22050);
sonic.queueInput(inputBuffer);
sonic.queueEndOfStream();
ShortBuffer outputBuffer = ShortBuffer.allocate(sonic.getOutputSize() / 2);
sonic.getOutput(outputBuffer);
// TODO (b/361768785): Remove this unexpected last sample when Sonic's resampler returns the
// right number of samples.
assertThat(outputBuffer.array()).isEqualTo(new short[] {-40, -20, 0, 20, 40, 0});
}
@Test
public void resample_withOneSample_doesNotHang() {
ShortBuffer inputBuffer = ShortBuffer.wrap(new short[] {10});
Sonic sonic =
new Sonic(
/* inputSampleRateHz= */ 44100,
/* channelCount= */ 1,
/* speed= */ 1,
/* pitch= */ 1,
/* outputSampleRateHz= */ 88200);
sonic.queueInput(inputBuffer);
sonic.queueEndOfStream();
ShortBuffer outputBuffer = ShortBuffer.allocate(sonic.getOutputSize() / 2);
sonic.getOutput(outputBuffer);
// End of stream is padded with silence, so last sample will be interpolated between (10; 0).
assertThat(outputBuffer.array()).isEqualTo(new short[] {10, 5});
}
@Test
public void resample_withFractionalOutputSampleCount_roundsNumberOfOutputSamples() {
ShortBuffer inputBuffer = ShortBuffer.wrap(new short[] {0, 2, 4, 6, 8});
Sonic sonic =
new Sonic(
/* inputSampleRateHz= */ 44100,
/* channelCount= */ 1,
/* speed= */ 1,
/* pitch= */ 1,
/* outputSampleRateHz= */ 22050);
sonic.queueInput(inputBuffer);
sonic.queueEndOfStream();
ShortBuffer outputBuffer = ShortBuffer.allocate(sonic.getOutputSize() / 2);
sonic.getOutput(outputBuffer);
assertThat(outputBuffer.array()).isEqualTo(new short[] {0, 4, 8});
}
}

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