Compare commits

...

900 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
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
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
Tianyi Feng
b01c6ffcb3 Merge branch 'release' into release-1.4.0 2024-07-25 14:42:08 +00:00
tianyifeng
9fb7316696 Fix the release notes for 1.4.0 stable release
#cherrypick

PiperOrigin-RevId: 655558346
(cherry picked from commit d70ff7e4d2)
2024-07-24 14:45:25 +00:00
tianyifeng
4e6a643d88 Update media3 version for 1.4.0 stable release
PiperOrigin-RevId: 653654999
(cherry picked from commit 3c9332bb48)
2024-07-19 13:20:28 +00:00
tianyifeng
18a1582e8c Fix the release notes for 1.4.0 stable release
#cherrypick

PiperOrigin-RevId: 653640574
(cherry picked from commit 1cbcd20851)
2024-07-19 13:15:34 +00:00
tianyifeng
1dfab4f73a Merge release notes for media3 1.4.0 stable release
#cherrypick

PiperOrigin-RevId: 653261278
(cherry picked from commit 68e8d9cb68)
2024-07-19 13:15:34 +00:00
tianyifeng
567204e986 Version bump to 1.4.0-rc01
#cherrypick

PiperOrigin-RevId: 648982615
(cherry picked from commit b531d93b90)
2024-07-03 10:25:39 +00:00
tianyifeng
75dadeaa9e Update release notes for 1.4.0-rc01
PiperOrigin-RevId: 648745388
(cherry picked from commit 9277a34253)
2024-07-02 16:57:58 +00:00
tianyifeng
d97ec132b9 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
(cherry picked from commit afe3826d7c)
2024-07-02 14:12:52 +00:00
tianyifeng
6946f49997 Add OptIn annotation to method declaration in demo app file
#cherrypick

PiperOrigin-RevId: 648641357
(cherry picked from commit 0510370bd2)
2024-07-02 14:12:52 +00:00
ibaker
5b60f6c67d Fix index out of bounds exception when a Subtitle is empty
Issue: androidx/media#1516

#cherrypick

PiperOrigin-RevId: 648416119
(cherry picked from commit 711d18de03)
2024-07-02 14:12:52 +00:00
bachinger
035c943219 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
(cherry picked from commit 70c063905c)
2024-07-02 14:12:52 +00:00
tianyifeng
4a319f19e3 Update translations
#cherrypick

PiperOrigin-RevId: 648385733
(cherry picked from commit 6bf2461f80)
2024-07-02 14:12:52 +00:00
simakova
0df98407d3 Update the composition README file
PiperOrigin-RevId: 648282532
(cherry picked from commit 16ef63cdfc)
2024-07-02 14:12:52 +00:00
michaelkatz
bd1704904e 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
(cherry picked from commit a58e77a5a6)
2024-07-02 14:12:52 +00:00
tonihei
adeef60c14 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
PiperOrigin-RevId: 646968309
(cherry picked from commit 727645179b)
2024-07-02 14:12:47 +00:00
Copybara-Service
708dc6cab9 Merge pull request #1487 from colinkho:main
PiperOrigin-RevId: 646917527
(cherry picked from commit 6244d8605f)
2024-07-02 14:11:38 +00:00
rohks
0b87e176fd 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
(cherry picked from commit 12c42585d2)
2024-07-02 14:11:38 +00:00
ibaker
0f0a20bc1c 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

(cherry picked from commit 5fcc7433a1)
2024-07-02 14:11:38 +00:00
tonihei
a73d32a795 Rename DummyTrackOutput and DummyExtractorOutput
#cherrypick

PiperOrigin-RevId: 646434450
(cherry picked from commit 867410fece)
2024-07-02 14:11:38 +00:00
tonihei
0f18fd87ac 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
(cherry picked from commit 18e631ff79)
2024-07-02 14:11:38 +00:00
Copybara-Service
aa508b1aaf Merge pull request #1479 from dryganets:sdryanets/fix-handler-usage
PiperOrigin-RevId: 646402268
(cherry picked from commit 0466728497)
2024-07-02 14:11:38 +00:00
Copybara-Service
53bb87b7d6 Merge pull request #1416 from khouzam:customFormat
PiperOrigin-RevId: 646121082
(cherry picked from commit b026271c84)
2024-07-02 14:11:38 +00:00
tonihei
f49fa99aab 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
(cherry picked from commit 71ef848ec3)
2024-07-02 14:11:38 +00:00
okunhardt
ddf188552e In DemoUtil, don't set cookie handler when using HttpEngineDataSource.
HttpEngine does not support cookie storage.

#cherrypick

PiperOrigin-RevId: 646084702
(cherry picked from commit bb568b5150)
2024-07-02 14:11:38 +00:00
tonihei
3a5e367b93 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
(cherry picked from commit ada4dc982f)
2024-07-02 14:11:38 +00:00
okunhardt
b433bf928b 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
(cherry picked from commit e591c37b1e)
2024-07-02 14:11:37 +00:00
ibaker
9b713907ce 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
(cherry picked from commit 99803066ea)
2024-07-02 14:11:37 +00:00
ibaker
f645e9ccdc 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
(cherry picked from commit cb8f87e05e)
2024-07-02 14:11:37 +00:00
tonihei
c14110cdbf 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
(cherry picked from commit e84bb0d21c)
2024-07-02 14:11:37 +00:00
tonihei
05571b3e0d 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
(cherry picked from commit 1d26d1891e)
2024-07-02 14:11:37 +00:00
tonihei
2fd6b60daa 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
(cherry picked from commit c0abd6f91e)
2024-07-02 14:11:37 +00:00
ibaker
30cb76269a 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
(cherry picked from commit 968f72fec6)
2024-07-02 14:11:37 +00:00
bachinger
4330d6a952 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
(cherry picked from commit 6cc6444dd9)
2024-07-02 14:11:37 +00:00
bachinger
56ae85fa3c Allow session activity to be set per controller
PiperOrigin-RevId: 644693533
(cherry picked from commit 856d394c28)
2024-07-02 14:11:31 +00:00
tonihei
bd5f99cb09 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
(cherry picked from commit 66c19390e2)
2024-07-02 14:10:12 +00:00
tonihei
d76316a1f7 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
(cherry picked from commit e20e94fde2)
2024-07-02 14:10:12 +00:00
michaelkatz
023e9d1479 Version bump to media3:1.4.0-beta01
PiperOrigin-RevId: 644352776
(cherry picked from commit c07bbd333c)
2024-06-27 09:34:04 +00:00
michaelkatz
cfeee5b34e Update release notes for 1.4.0-beta01
#cherrypick

PiperOrigin-RevId: 644351958
(cherry picked from commit 794731607d)
2024-06-18 13:43:30 +00:00
michaelkatz
32bedfa93e Fix linter errors in DemoMediaLibrarySessionCallback.kt
PiperOrigin-RevId: 644002147
(cherry picked from commit ed07ac5d7d)
2024-06-17 14:57:01 +00:00
Copybara-Service
f36395ae66 Merge pull request #1437 from MGaetan89:add_exoplayer_setMaxSeekToPreviousPosition
PiperOrigin-RevId: 643987403
(cherry picked from commit 67a7b41fa7)
2024-06-17 14:57:01 +00:00
sheenachhabra
d833d59124 Update Media3 version number
PiperOrigin-RevId: 622211426
(cherry picked from commit 2a5b9afb88)
2024-04-05 10:09:43 -07:00
sheenachhabra
1792723be2 Update release notes for 1.3.1 bug fix release
PiperOrigin-RevId: 622189733
(cherry picked from commit 5fc35a6045)
2024-04-08 16:56:58 +00:00
sheenachhabra
1a710b06d0 Import string translations
#minor-release

PiperOrigin-RevId: 621828038
(cherry picked from commit d57229ad15)
2024-04-04 05:34:46 -07:00
ibaker
5fe906f1e9 Rollback of e665e2aee8
PiperOrigin-RevId: 619871653
(cherry picked from commit b60cd2c033)
2024-03-28 04:00:11 -07:00
Copybara-Service
7b5522fc58 Merge pull request #1054 from jekopena:main
PiperOrigin-RevId: 619573181
(cherry picked from commit 8fe70332ee)
2024-03-27 10:24:32 -07:00
ibaker
4caed3cfb2 Add PlayerView.setControllerAnimationEnabled(boolean)
Issue: androidx/media#1227
PiperOrigin-RevId: 619558900
(cherry picked from commit d684cdb330)
2024-03-27 09:36:22 -07:00
ibaker
26ce3f012a Fix javadoc on PlayerView.setControllerVisibilityListener
#minor-release

PiperOrigin-RevId: 619536799
(cherry picked from commit ebfd540817)
2024-03-27 08:17:03 -07:00
tonihei
a5d0cb51bc Don't apply performance point workaround from API 35
The workaround check is now part of CTS and we should be able
to rely on the PerformancePoints values (or at least can be sure
that they cover all CDD requirements)

#minor-release

PiperOrigin-RevId: 619201331
(cherry picked from commit 737bf08314)
2024-03-26 08:38:31 -07:00
ibaker
88ebc89da2 Plumb MP3 average bitrate from metadata frames into Format
Issue: androidx/media#1081

PiperOrigin-RevId: 619185083
(cherry picked from commit d00ca1e343)
2024-03-26 07:36:46 -07:00
ibaker
dca3c61c5b In-line versions only used by exoplayer-media2 extension
Also remove an unused `androidxTestServicesStorageVersion` version.

Issue: androidx/media#1216
PiperOrigin-RevId: 618990983
(cherry picked from commit 6cb25119fb)
2024-03-25 16:18:02 -07:00
tonihei
3657831923 Include nullness of MediaMetadata.extras in equals method
This ensures MediaMetadata with just non-null extras is not
considered equal to MediaMetadata.EMPTY. This makes sure the
contents are bundled when a controller sets the extras in a
new MediaItem

Issue: androidx/media#1176

PiperOrigin-RevId: 618876642
(cherry picked from commit ab0391167c)
2024-03-25 10:02:23 -07:00
ibaker
06a4036539 Remove unecessary re-setting of group from session Gradle files
These TODOs precede <unknown commit> when the group was set in the top-level
`build.gradle` file.

Issue: androidx/media#1215
PiperOrigin-RevId: 618835040
(cherry picked from commit fd268eed46)
2024-03-25 07:30:24 -07:00
tonihei
5fc9ddab0b Partial revert of 3a7d31a599
The original change did not set the color info in the codec
for standard SDR infos. This was meant to help with consistency
to avoid a situation where the information is partially set and
later the bitstream provides slightly different values (all in
standard SDR though).

We can revert this part because the bitstream may change anyway
and the decoder needs to handle this internally. And more
importantly, it also avoids removing this information when encoding
the format again in Transformer.

PiperOrigin-RevId: 617582066
(cherry picked from commit ed505df2ca)
2024-03-20 11:48:23 -07:00
tonihei
b0adf2fe43 Don't set codec color info for default SDR
Some media can read color info values from the bitstream
and may partially set some of the SDR default values in
Format.ColorInfo. Setting these default values for SDR can
confuse some codecs and may also prevent adaptive ABR
switches if not all ColorInfo values are set in exactly the
same way.

We can avoid any influence of HDR color info handling by
disabling setting the color info MediaFormat keys for SDR
video and also avoid codec reset at format changes if both
formats are SDR with slightly different ColorInfo settings.

To identify "SDR" ColorInfo instances, we need to do some
fuzzy matching as many of the default values are assumed to
match the SDR profile even if not set.

Issue: androidx/media#1158
PiperOrigin-RevId: 617473937
(cherry picked from commit 3a7d31a599)
2024-03-20 04:55:06 -07:00
ibaker
961d204c2c Fix proguard rule for DefaultVideoFrameProcess.Factory.Builder.build()
Issue: androidx/media#1187

#minor-release

PiperOrigin-RevId: 616112879
(cherry picked from commit 48cffc849a)
2024-03-15 06:46:53 -07:00
ibaker
d195b094b4 Don't emit a CuesWithTiming for zero-duration Subtitle events
It's a bit arguable whether the `Subtitle` implementation supports
zero-duration events, since `getEventTimeCount` is documented as
effectively "the number of times the cues returns by `getCues(long)`
changes", and zero-duration events violate that. However, the current
`WebvttSubtitle` impl **does** produce zero-duration events, so it
seems safer to handle them gracefully here and then, as a possible
follow-up, fix the `WebvttSubtitle` impl (or remove it completely).

Issue: androidx/media#1177

PiperOrigin-RevId: 616095798
(cherry picked from commit e9ed874e51)
2024-03-15 05:22:47 -07:00
Copybara-Service
8a169d104f Merge pull request #1117 from loliball:dev_wav_align_fix
PiperOrigin-RevId: 615820438
(cherry picked from commit e9a28beb44)
2024-03-14 10:17:49 -07:00
andrewlewis
6f0b70ea78 Add short form content demo to project
#minor-release

PiperOrigin-RevId: 615756117
(cherry picked from commit 9da878956a)
2024-03-14 06:25:23 -07:00
ibaker
860f18f29c Move test-session-xxx modules from core_settings to settings
This ensures these modules are only configured when directly working
on the library, rather than when depending on it locally. These modules
only contain tests, they shouldn't be depended on by any apps (and are
not published via Maven).

PiperOrigin-RevId: 615004801
(cherry picked from commit c41213c273)
2024-03-12 05:30:06 -07:00
michaelkatz
561dafcdea Start playing period-enabled renderers when setting playWhenReady true
Renderers may be enabled for subsequent media items as soon as the current media item's renderer's isEnded() returns true. Currently, when a player is set to pause, it stops all renderers that are `STATE_STARTED`. When a player is set to play, it starts all renderers that are enabled. This would include renderers that were enabled early for the subsequent media item. The solution is to only start renderers that are enabled by the current playing period.

Issue: androidx/media#1017
PiperOrigin-RevId: 614734437
(cherry picked from commit 8b219b0ae6)
2024-03-11 11:27:26 -07:00
tonihei
c8ae6d1142 Always set PARAMETER_KEY_TUNNEL_PEEK when tunneling
This should already be the default, but some devices seem
to not adhere to this contract and assume the default is unset.

Issue: androidx/media#1169
PiperOrigin-RevId: 614697283
(cherry picked from commit cbed80ecf3)
2024-03-11 09:46:07 -07:00
tonihei
3f3d60eb6a Add device-specific opt-ins for async MediaCodecAdapter
Some devices just don't work very well with the synchronous
model, but are currently still excluded from our approximate
API 31 check. This change allows to include additional devices
or device groups by passing in the Context to the default
adapter.

It also adopts the workaround added in ebceee08c6
for Fire TV Smart devices that exhibit this issue.

PiperOrigin-RevId: 614642545
(cherry picked from commit e4a55844d0)
2024-03-11 06:27:15 -07:00
tonihei
fffbf9ac4e Force external surround sound flag if requested by device
Some FireOS6 devices ask to force the external surround global
flag and ignore any signals from the HDMI connection.

This is the equivalent change of e341944d1e

PiperOrigin-RevId: 614634499
(cherry picked from commit 410c0492cc)
2024-03-11 05:52:52 -07:00
tonihei
a85d9e251c Reorder audio capability checks
Using the more accurate check available on later API versions
first is likely better than falling back to a fallback solution
from older API versions.

PiperOrigin-RevId: 614612628
(cherry picked from commit 18cbbf3850)
2024-03-11 04:14:31 -07:00
tonihei
a72a23266b Document MergingMediaSource tag contract
Issue: androidx/media#883

#minor-release

PiperOrigin-RevId: 613970048
(cherry picked from commit eb6f607717)
2024-03-08 10:11:55 -08:00
ibaker
4db96acee6 Add opt-in flag for the MIDI module for apps with a local dependency
PiperOrigin-RevId: 613938538
(cherry picked from commit 4bca1ac47b)
2024-03-08 08:13:55 -08:00
ibaker
e55621496c Add workarounds for NoSuchMethodError from DRM framework exceptions
Issue: androidx/media#1145

PiperOrigin-RevId: 613573868
(cherry picked from commit a604600126)
2024-03-07 07:13:01 -08:00
tofunmi
2dc2e5daf1 Composition: clarify javadoc to setTransmuxAudio\Video
Document that assoicated effects are ignored if these setters are set

PiperOrigin-RevId: 613518167
(cherry picked from commit 5b7774fcaf)
2024-03-07 02:55:30 -08:00
ibaker
41771d7fa3 Set Format.frameRate for single-frame MP4 tracks
Issue: androidx/media#1051
PiperOrigin-RevId: 613516802
(cherry picked from commit afacf2cdb7)
2024-03-07 02:49:59 -08:00
michaelkatz
71bfdd1bce Start early-enabled renderers only after advancing the playing period
Renderers may be enabled for subsequent media items as soon as the current media item's renderer's isEnded() returns true. When a renderer is being enabled and the player is 'playing', that renderer is also started. When playing a mixed playlist of images and content with audio & video, the player may skip some image items because the early-starting of the audio renderer causes a clock update.

A solution is to only start the "early-enabled" renderers at the point of media transition and add a condition on DefaultMediaClock to use the standalone clock when reading-ahead and the renderer clock source is not in a started state.

Issue: androidx/media#1017
PiperOrigin-RevId: 613231227
(cherry picked from commit 638b2a3c86)
2024-04-05 13:05:36 +00:00
andrewlewis
3fdd3bdb7a Fix typo
PiperOrigin-RevId: 613156951
(cherry picked from commit a90a7049e8)
2024-03-06 03:47:22 -08:00
ibaker
b6cb9c1b90 Fix some invalid javadoc references caught by a new JDK version
PiperOrigin-RevId: 612808322
(cherry picked from commit 3a43bd7687)
2024-04-02 18:43:29 +00:00
tonihei
672d149a73 Remove invalid command comparison
The removed check searched for a player command inside a list of
session commands, which is not allowed by the IntDef definition
and only worked because both types map to a Java int.

PiperOrigin-RevId: 612758442
(cherry picked from commit c79ac5ba21)
2024-04-02 18:36:27 +00:00
Copybara-Service
e28d772423 Merge pull request #1025 from v-novaltd:dsparano-exo209
PiperOrigin-RevId: 612485043
(cherry picked from commit bbdaf2b092)
2024-04-02 18:36:27 +00:00
tianyifeng
cf9ff4de45 Avoid position jumping back when controller replaces the current item
When the controller replaces the current item, the masking position will be changed to the default position of the new item for a short while, before the correct position comes from the session. This will interrupt the current position fetched from the controller when the playback doesn't interrupted by the item replacing.

Issue: androidx/media#951

PiperOrigin-RevId: 611417539
(cherry picked from commit 1bdc58de0b)
2024-04-02 18:36:22 +00:00
ibaker
1d2116c53a Add fps-awareness to DefaultTrackSelector
This change aims to prioritise tracks that have a 'smooth enough for
video' frame rate, without always selecting the track with the highest
frame rate.

In particular MP4 files extracted from motion photos sometimes have two
HEVC tracks, with the higher-res one having a very low frame rate (not
intended for use in video playback). Before this change
`DefaultTrackSelector` would pick the low-fps, high-res track.

This change adds a somewhat arbitrary 10fps threshold for "smooth video
playback", meaning any tracks above this threshold are selected in
preference to tracks below it. Within the tracks above the threshold
other attributes are used to select the preferred track. We deliberately
don't pick the highest-fps track (over pixel count and bitrate), because
most users would prefer to see a 30fps 4k track over a 60fps 720p track.

This change also includes a test MP4 file, extracted from the existing
`jpeg/pixel-motion-photo-2-hevc-tracks.jpg` file by logging
`mp4StartPosition` in
[`MotionPhotoDescription.getMotionPhotoMetadata`](b930b40a16/libraries/extractor/src/main/java/androidx/media3/extractor/jpeg/MotionPhotoDescription.java (L123))
and then using `dd`:

```
mp4StartPosition=2603594

$ dd if=jpeg/pixel-motion-photo-2-hevc-tracks.jpg \
    of=mp4/pixel-motion-photo-2-hevc-tracks.mp4 \
    bs=1 \
    skip=2603594
```

----

This solution is in addition to the `JpegMotionPhotoExtractor` change
made specifically for these two-track motion photos in
5266c71b3a.
We will keep both changes, even though that change is not strictly
needed after this one, because adding the role flags helps to
communicate more clearly the intended usage of these tracks. This
change to consider FPS seems like a generally useful improvement to
`DefaultTrackSelector`, since it seems unlikely we would prefer a 5fps
video track over a 30fps one.

Issue: androidx/media#1051
PiperOrigin-RevId: 611015459
(cherry picked from commit c7e00b12b4)
2024-04-02 18:34:46 +00:00
michaelkatz
3521ccda9b Fallback to legacy sizerate check if CDD H264 PerfPoint check fails
Some devices supporting Performance Points for decoder coverage are missing coverage over the CDD requirements for H264. For these cases ExoPlayer should fall back to legacy resolution and frame rate support checks. If there is a stream evaluated as a PerformancePointCoverageResult of COVERAGE_RESULT_NO, then ExoPlayer checks for coverage of the 720p H264 CDD requirement.

Issue: google/ExoPlayer#10898

Issue: androidx/media#693

Issue: androidx/media#966
PiperOrigin-RevId: 609740128
(cherry picked from commit 23a301fc5d)
2024-04-02 18:30:13 +00:00
christosts
3c10b41ffa ExoPlayerImplInternal.releaseInternal(): unblock the app thread
This change makes ExoPlayerImplInternal.releaseInternal() unblock the
app thread if a runtime exception is thrown while releasing components
from the playback thread.

Before this change, if a runtime exception occurred during releasing
components in the playback thread, ExoPlayer.release() would wait for
`releaseTimeoutMs` and then raise a player error. With this change,
the player error is reported only when the playback thread is blocked
but if there is a runtime exception, the application thread is
unblocked.

The impact of this change is potentially fewer ANRs on
ExoPlayer.release() at the expense of less error reporting.

PiperOrigin-RevId: 609702549
(cherry picked from commit 0480eff6a1)
2024-04-02 18:27:25 +00:00
christosts
25e8dc5e8b ForwardingAudioSink: add override for release()
We forgot to add it when we added AudioSink.release(). The commit
includes a test that ensures ForwardingAudioSink overrides all the
methods defined in the AudioSink interface.

PiperOrigin-RevId: 609402258
(cherry picked from commit 440d2ab162)
2024-04-02 18:24:37 +00:00
michaelkatz
81e91c25f1 Use playing period TrackSelectorResult in track reselection update
If the reading period has already advanced and a track reselection procs that only affects the reading period media, then ExoPlayer may try and apply the reading period's track selection incorrectly unto the playing period. ExoPlayer should apply the playing period's track selection to the playing period instead.

PiperOrigin-RevId: 609375077
(cherry picked from commit 4192924622)
2024-04-02 18:24:37 +00:00
michaelkatz
ed71172ade Allow renderer retry for audio track offload initialization failure
If render error occurs due to AudioTrack initialization failure in offload mode, then ExoPlayer should allow retry as subsequent attempt will be with DefaultAudioSink disabling offload.

PiperOrigin-RevId: 609304897
(cherry picked from commit 9046f2edb6)
2024-04-02 18:24:33 +00:00
tianyifeng
2ae79959ef Fix a bug in retaining streams when preload a PreloadMediaPeriod again
The `PreloadMediaPeriod.selectTracksForPreloading` can be called for multiple times at the preloading stage (before the period is being played). For example, when the period resumes preloading. This change fix the assertion failure in `ProgressiveMediaPeriod.selectTracks` caused by the wrong implementation of `PreloadMediaPeriod.selectTracksForPreloading` when it is trying to retain the previously preloaded streams.

Also the `TrackSelectorResult` parameter is changed to a list of `ExoTrackSelection`. We should compare the selections only rather than considering the `RendererConfiguration` in the `TrackSelectorResult` to decide whether to retain the streams, as for preloading case the renderers haven't consumed the samples yet.

PiperOrigin-RevId: 609126868
(cherry picked from commit d952a06214)
2024-04-02 18:21:05 +00:00
michaelkatz
2f47151131 Allow empty information attributes in RTSP Session Description
Issue: androidx/media#1087
PiperOrigin-RevId: 608534659
(cherry picked from commit 52c1d60d39)
2024-04-02 17:08:26 +00:00
Googler
04ce83691e Fix failure to write negative PTS sample
Fixes an issue caused by no support for negative audio PTS and edit lists
in FrameworkMuxer, Android versions before 11

PiperOrigin-RevId: 607690507
(cherry picked from commit e43f96687c)
2024-04-02 16:57:22 +00:00
ibaker
b218d910ad Fix docs on playUntilStartOfMediaItem to match playUntilPosition
The behaviour and docs of `playUntilPosition` were changed in
00c7a9bcbb.

This change also affects `playUntilStartOfMediaItem` (since it delegates
to `playUntilPosition`), so the same doc change should also be made
here.

#minor-release

PiperOrigin-RevId: 607364897
(cherry picked from commit fa3212e73e)
2024-04-02 16:41:12 +00:00
claincly
396ae86fdd Fix forward for 216f3fedb8
The original change missed copying the newly added flag in `buildUpon()`, which
caused some test flakes.

PiperOrigin-RevId: 607232373
(cherry picked from commit ae0da442b1)
2024-04-02 16:41:12 +00:00
ibaker
61a3127468 Demo app: Clarify that only Widevine DRM content can be downloaded
Issue: androidx/media#1085
PiperOrigin-RevId: 606921440
(cherry picked from commit a0727fe4d7)
2024-04-02 16:41:12 +00:00
bachinger
879bcb5d99 Check whether the session activity pending intent is an activity
PiperOrigin-RevId: 606613694
(cherry picked from commit 89571c0a92)
2024-04-02 16:41:11 +00:00
tofunmi
c58adb097a Add release notes for recent transformer features
PiperOrigin-RevId: 606565385
(cherry picked from commit caf584152d)
2024-04-02 16:41:03 +00:00
claincly
5fd1f61c84 Loosen the requirement to register every input frame
VideoFrameProcessor requires every input frame to be registered before they are
rendered to its input Surface. This CL adds the option to register the input
frame only once. This is useful for camera inputs where not all input frames
come with changing FrameInfo.

PiperOrigin-RevId: 606294894
(cherry picked from commit 216f3fedb8)
2024-04-02 16:14:01 +00:00
michaelkatz
c4688fc557 Fallback to including track language name if display name is not found
Issue: androidx/media#988
PiperOrigin-RevId: 606193299
(cherry picked from commit 0b0c419c73)
2024-04-02 16:13:56 +00:00
huangdarwin
2ec19a5da5 HDR: Add color-space conversion for PQ tone-map.
Previously, we missed the BT2020->BT709 color-space conversion.

A user-visible impact of this is that red and green channels used to be
undersaturated, but now are more correctly saturated.

PiperOrigin-RevId: 605411926
(cherry picked from commit a5e47982f4)
2024-04-02 16:12:42 +00:00
tofunmi
39f26e6041 DefaultAssetLoaderFactory: Simplify file extension retrival
Change the file extension retrieval back to how it was before 5488d33da8 to reduce the change of false negatives in `isImage()`

PiperOrigin-RevId: 605281186
(cherry picked from commit 4d29d8f012)
2024-04-02 16:11:10 +00:00
huangdarwin
9825e21a71 Effect: Remove extra checkStateNotNull by reordering logic.
PiperOrigin-RevId: 603660773
(cherry picked from commit ae85ba9ee9)
2024-04-02 16:11:10 +00:00
huangdarwin
ecaff24e46 Effect: Use element instead of peek, when throwing if null.
If nullness should result in an exception, we should throw as soon as possible,
so that stack traces are easier to follow.

Did a quick scan of media3.effect+transformer and this covers all uses of
peek that immediately throw there.

PiperOrigin-RevId: 603393366
(cherry picked from commit dd7846ee1c)
2024-04-02 16:11:10 +00:00
huangdarwin
bfa492bf4f HDR: Don't assume that swapping between BT709 and BT601 needs tone-map.
VideoFrameProcessor treats BT601 and BT709 as roughly equivalent now, so we
shouldn't be making checks that assume BT709 <-> requires tone-mapping.

Also, the color transfer is a better determinant for tone-mapping than color range, so use just the transfer to determine if tone-mapping is required.

PiperOrigin-RevId: 603083100
(cherry picked from commit cebe6d8ba5)
2024-04-02 16:11:10 +00:00
Ian Baker
d13a0f4ec6 Merge branch 'release' into release-1.3.0 2024-03-07 09:25:30 +00:00
sheenachhabra
99e7156561 Update media3 version for 1.3.0 stable release
PiperOrigin-RevId: 609745599
(cherry picked from commit 9be7a7ab7f)
2024-02-26 12:15:21 +00:00
sheenachhabra
c6a6b81d96 Update release notes section for 1.3.0 stable release
PiperOrigin-RevId: 609407248
(cherry picked from commit 82065e699a)
2024-02-26 12:15:05 +00:00
sheenachhabra
ee0eac7e31 Fix nit issues in 1.3.0-rc01 release notes
PiperOrigin-RevId: 608580903
(cherry picked from commit ebcb4e8b21)
2024-02-20 16:31:17 +00:00
sheenachhabra
fbf8ac37c3 Version bump to media3:1.3.0-rc01
PiperOrigin-RevId: 605573991
(cherry picked from commit a31a384393)
2024-02-09 11:39:45 +00:00
sheenachhabra
43ee291172 Update release notes for media3:1.3.0-rc01
#minor-release

PiperOrigin-RevId: 605561427
(cherry picked from commit c4605b3c90)
2024-02-09 11:34:02 +00:00
tianyifeng
38d5635818 Report the skipped silence more deterministically
Issue: androidx/media#1035
#minor-release
PiperOrigin-RevId: 605361126
(cherry picked from commit 9b0cdde7d2)
2024-02-08 19:09:41 +00:00
tonihei
e492e37baa Move release note to the right section
#minor-release

PiperOrigin-RevId: 605310711
(cherry picked from commit e56e27e725)
2024-02-08 17:30:17 +00:00
ibaker
dbb99fef19 Rollback of 406c0a15be
PiperOrigin-RevId: 605015994
(cherry picked from commit 3a7a665d5d)
2024-02-08 17:30:17 +00:00
ibaker
1dc4377a4b Stop double-encoding CMCD query parameters
`Uri.appendQueryParameter` is documented to encode its arguments, so
calling `Uri.encode` beforehand results in double-encoding.

Issue: androidx/media#1075

#minor-release

PiperOrigin-RevId: 604995441
(cherry picked from commit 7ebfed505c)
2024-02-08 17:30:17 +00:00
tonihei
245e6231d9 Set correct track id when skipping empty tracks in Mp4Extractor
The track id must be the index in the list of published tracks
as it's used as such elsewhere. This is currently not true if we
skip an empty track as all subsequent tracks get a wrong or even
invalid id.

#minor-release

PiperOrigin-RevId: 604929178
(cherry picked from commit 5f9c96ab53)
2024-02-08 17:30:17 +00:00
bachinger
74d0f93dd4 Fix the regex used for validating custom CMCD key names
Relax the regex to only check for hyphen which is required by the specification.

Issue: androidx/media#1028
#minor-release
PiperOrigin-RevId: 604719300
(cherry picked from commit 138532e3fd)
2024-02-08 17:30:17 +00:00
tianyifeng
9f28e7d81b Improve AudioCapabilities with AudioManager API in Android 13
PiperOrigin-RevId: 604700601
(cherry picked from commit ccd603acb0)
2024-02-08 17:30:17 +00:00
ibaker
bf8b8749fe JpegMotionPhotoExtractor: Don't emit an image track with no metadata
The current implementation of `JpegMotionPhotoExtractor.sniff` returns
`true` for any image with Exif data (not just motion photos). Improving
this is tracked by b/324033919. In the meantime, when we 'extract' a
non-motion photo with `JpegMotionPhotoExtractor`, the result is
currently a single empty image track and no video track (since there's
no video, since this isn't a motion photo). This 'empty' image track
is usually used to transmit metadata about the video parts of the
image file (in the form of `MotionPhotoMetadata`), but this metadata is
also (understandably) absent for non-motion photos. Therefore there's
no need to emit this image track at all, and it's clearer to emit no
tracks at all when extracting a non-motion photo using
`JpegMotionPhotoExtractor`.

This change also removes a `TODO` that is misplaced, since there's no
image bytes being emitted here (and never was).

PiperOrigin-RevId: 604688053
(cherry picked from commit 7ae3d69e00)
2024-02-08 17:30:16 +00:00
ibaker
7721f387ca Check sampleMimeType rather than containerMimeType for images
These are often the same for image tracks, since we usually drop the
whole image file (both the container and actual encoded image bytes)
into a single sample, but there are cases where we emit a track with
`containerMimeType=image/jpeg` but **no** samples (from
`JpegMotionPhotoExtractor`, to carry some metadata about the image +
video byte offsets).

It's therefore more correct to implement the `supportsFormat` check
based on `sampleMimeType`, so that these 'empty' image tracks are not
considered 'supported' by `ImageRenderer`.

#minor-release

PiperOrigin-RevId: 604672331
(cherry picked from commit eabba49610)
2024-02-08 17:30:16 +00:00
bachinger
dc8d3eaf30 Add constructor that takes only a Context
#minor-release

PiperOrigin-RevId: 604659845
(cherry picked from commit c2273345c5)
2024-02-08 17:30:16 +00:00
tonihei
ca2779e774 Add source prefix to MergingMediaPeriod Format ids
This was already done for the TrackGroup ids in <unknown commit>,
but in some scenarios only the Format instances are known and
it's helpful to be able to identify where they came from.

Issue: androidx/media#883

#minor-release

PiperOrigin-RevId: 604644039
(cherry picked from commit f8f6d80477)
2024-02-08 17:30:16 +00:00
tonihei
a294dc9bc5 Include nullness of RequestMetadata.extras in equals method
This ensures RequestMetadata with just non-null extras is not
considered equal to RequestMetadata.EMPTY. This makes sure the
contents are bundled when a controller sets the extras in a
new MediaItem.

PiperOrigin-RevId: 604632788
(cherry picked from commit 766a15a51e)
2024-02-08 17:30:16 +00:00
ibaker
ad57b41da1 Clearly define the consistency requirements for SequenceableLoader
Add a test for this consistency in `CompositeSequenceableLoaderTest`,
and also make the
`CompositeSequenceableLoaderTest.FakeSequenceableLoader` implementation
more realistic.

#minor-release

PiperOrigin-RevId: 604604103
(cherry picked from commit db74bb9609)
2024-02-08 17:30:16 +00:00
Googler
4a952170a2 Fix Kotlin 2.0 compilation error in session demo app
Since Kotlin 2.0 the compiler became better at detecting incorrect
annotation applications. The @OptIn annotation can't be applied to an
expression, so when compiling with Kotlin 2.0 it results in an error.

PiperOrigin-RevId: 604596657
(cherry picked from commit 718cf1299b)
2024-02-08 17:30:16 +00:00
ibaker
4eea69dd46 Merge Cea608Parser back into Cea608Decoder
This reverses 27caeb8038

Due to the re-ordering of packets done in `CeaDecoder`, there's no way
to use the current implementation to correctly parse these subtitle
formats during extraction (the `SubtitleParser` interface), so we have
to keep the `SubtitleDecoder` implementations.

#minor-release

PiperOrigin-RevId: 604594837
(cherry picked from commit 25498b151b)
2024-02-08 17:30:16 +00:00
ibaker
dd2e4a5978 Merge Cea708Parser back into Cea708Decoder
This reverses 94e45eb4ad

Due to the re-ordering of packets done in `CeaDecoder`, there's no way
to use the current implementation to correctly parse these subtitle
formats during extraction (the `SubtitleParser` interface), so we have
to keep the `SubtitleDecoder` implementations.

#minor-release

PiperOrigin-RevId: 604350951
(cherry picked from commit 51b4fa2cc8)
2024-02-08 17:30:16 +00:00
tonihei
548c4ce799 Fix HlsPlaybackTest flakiness
The new test introduced in 45bd5c6f0a is flaky because we only
wait until the media is fully buffered. However, we can't fully
control how much of this data is initially read by the Robolectric
codec and thus the output dump files (containing these codec
interactions) are flaky.

This can be fixed by fully playing the media once and then seeking
back instead.

#minor-release

PiperOrigin-RevId: 603324068
(cherry picked from commit e3e57c9b99)
2024-02-08 17:30:16 +00:00
michaelkatz
7758e422aa Render last image despite not receiving EoS
If seeking between last image sample and end of the file where the current stream is not final, then EoS sample will not be provided to `ImageRenderer`. ImageRenderer must still produce the last image sample.

PiperOrigin-RevId: 603312090
(cherry picked from commit 62c7ee0fb0)
2024-02-08 17:30:16 +00:00
Copybara-Service
127d0b5f3c Merge pull request #1011 from cedricxperi:dts-lbr-hls-bitrate-unknown-fix
PiperOrigin-RevId: 603302863
(cherry picked from commit f85860c041)
2024-02-08 17:30:16 +00:00
Copybara-Service
ad519e2e7e Merge pull request #1015 from kamaroyl:feat/PsshVersion1
PiperOrigin-RevId: 603016920
(cherry picked from commit dfcc2cb41d)
2024-02-08 17:30:16 +00:00
Copybara-Service
3d87578779 Merge pull request #1031 from garethfenn:hlschunkseek
PiperOrigin-RevId: 603008793
(cherry picked from commit 45bd5c6f0a)
2024-02-08 17:30:04 +00:00
claincly
a18d96c320 Fix blank video when switching on/off screen
This happens when using `ExoPlayer.setVideoEffects()`.

This CL also fixes the first frame not rendered problem, originally solved in
7e65cce967, but rolled back in 5056dfaa2b because the solution introduces
the flash that is observed in b/292111083.

Before media3 1.1 release, the output size of `VideoFrameProcessor` is not
reported to the app. This was changed later after introducing
`CompositingVideoSinkProvider`, where the video size after processing **is**
reported to the app. After this CL, the size is again, not reported.

PiperOrigin-RevId: 602345087
(cherry picked from commit dcae49a561)
2024-02-08 17:29:13 +00:00
tonihei
dfe47219f9 Update skip silence algorithm
The updated algorithm has two main improvements:
 - The silence padding is not constant but a ratio of the original
   silence (up to a defined max) to more naturally represent the
   original gaps.
 - The silence is not instantly going to zero, but uses a ramp down
   and up for a smooth volume transition and also retains a small
   percentage of the original noise for more natural "silences" that
   still contain some background noise.

#minor-release

Issue: google/ExoPlayer#7423
PiperOrigin-RevId: 602322442
(cherry picked from commit bb533332f4)
2024-02-08 17:29:12 +00:00
jbibik
826f20dbbd Remove a redundant TODO in Util
PiperOrigin-RevId: 601820851
(cherry picked from commit eb3173aa90)
2024-02-08 17:29:12 +00:00
tonihei
4560611bd0 Remove misleading @CanIgnoreReturnValue
The return value of onConfigure must not be ignored as it specifies
the output format of the processor, which may be different from the
input format.

#minor-release

PiperOrigin-RevId: 601799440
(cherry picked from commit 2fc5590e7a)
2024-02-08 17:29:12 +00:00
tonihei
4a7442ef3a Replace or suppress deprecated usages
Many usages are needed to support other deprecations and some
can be replaced by the recommended direct alternative.

Also replace links to deprecated/redirected dev site

PiperOrigin-RevId: 601795998
(cherry picked from commit ed5b7004b4)
2024-02-08 17:29:12 +00:00
ibaker
0886da450d Add a comment to suggest keeping JUnit version in sync with the Android
source tree - to make importing GTS tests easier.

PiperOrigin-RevId: 601725811
(cherry picked from commit 373822ce45)
2024-02-08 17:29:12 +00:00
Googler
ddca0116b2 Update IMA README to include link to supported platforms
PiperOrigin-RevId: 601720412
(cherry picked from commit 807d98a3d6)
2024-02-08 17:29:12 +00:00
sheenachhabra
4570ee9275 Fix link to 1.3.0-alpha01 release
PiperOrigin-RevId: 601469906
(cherry picked from commit 605689a020)
2024-01-26 11:15:09 +00:00
sheenachhabra
7822613cc6 Version bump to media3:1.3.0-beta01
PiperOrigin-RevId: 601441910
(cherry picked from commit 21ab474260)
2024-01-25 16:23:46 +00:00
ibaker
4382aa0b34 Make Cea608Parser and Cea708Parser package-private
It's likely that we will merge these back into their `XXXDecoder`
implementations, but this smaller change allows us to avoid including
these public symbols in the upcoming release.

#minor-release

PiperOrigin-RevId: 601432629
(cherry picked from commit 12157a6b1a)
2024-01-25 16:20:26 +00:00
sheenachhabra
e29e775732 Update release notes for Media3 1.3.0-beta01 release
#minor-release

PiperOrigin-RevId: 601408404
(cherry picked from commit 79b0b8090c)
2024-01-25 16:20:26 +00:00
ibaker
8770286a8e Throw immediately from ExoPlayer.setVideoEffects() if dep not found
This method works by reflectively loading classes from the `lib-effect`
module, in order to avoid a hard dependency on this module for ExoPlayer
users that don't want video effect functionality. This change ensures
that a failure to load the necessary classes fails immediately, instead
of on a later thread inside `MediaCodecVideoRenderer` when the
reflection currently happens.

Also update the javadoc to make the dependency requirement clear.

#minor-release

PiperOrigin-RevId: 601387957
(cherry picked from commit a6812156e6)
2024-01-25 11:42:44 +00:00
jbibik
748fec1ac6 Add a setter of SubtitleParser.Factory to MediaSource.Factory
DASH: `DashMediaSource.Factory` would only propagate it to `DashChunkSource.Factory` -> `BundledChunkExtractor.Factory`

SS: `SSMediaSource.Factory` -> `SsChunkSource.Factory`

HLS: `HlsMediaSource.Factory` -> `HlsExtractorFactory`

Remove nullability of SubtitleParser.Factory across the stack

#minor-release

PiperOrigin-RevId: 601250013
(cherry picked from commit f103a2dcf5)
2024-01-25 11:42:44 +00:00
christosts
55e3dd77c6 Rollback of e364510937
PiperOrigin-RevId: 601187997
(cherry picked from commit 85db94782a)
2024-01-25 11:42:44 +00:00
jbibik
0713d56efc Add setters of SubtitleParser.Factory and experimental toggle
The `SubtitleParser.Factory` is no longer @Nullable and the experimenting toggle is used to enable/disable the use of this factory for subtitle parsing during extraction.

The three places that will hold the "truth" for the `SubtitleParser.Factory` are: BundledChunkExtractor.Factory, SsChunkSource.Factory, DefaultHlsExtractorFactory

DASH: `DashMediaSource.Factory` would only propagate it to `DashChunkSource.Factory` -> `BundledChunkExtractor.Factory`

SS: `SSMediaSource.Factory` -> `SsChunkSource.Factory`

HLS: `HlsMediaSource.Factory` -> `HlsExtractorFactory`

#minor-release

PiperOrigin-RevId: 601151615
(cherry picked from commit 4d7b23f0d1)
2024-01-25 11:42:44 +00:00
sheenachhabra
9581af110a Write "stco" box instead of "co64" for fragmented MP4
As per MP4 spec ISO 14496-12: 8.7.5 Chunk Offset Box, Both "stco" and
"co64" can be used to store chunk offsets. While "stco" supports 32-bit
offsets, "co64" supports 64-bit offsets.
In non fragmented MP4, the mdat box can be extremely large, hence muxer
uses "co64" box.
But for fragmented MP4, muxer does not write any data in this chunk offset
box (present in "moov" box) because all sample related info is present in
"moof" box.
Technically, "co64" box should also work in fragmented MP4because
its empty only but QuickTime player fails to play video if "co64"
box is present in fragmented MP4 output file.

Testing: Verified that QuickTime player does not play video when "co64"
box is present but is able to play when "stco" box is present.

#minor-release

PiperOrigin-RevId: 601147046
(cherry picked from commit 0acf6902e5)
2024-01-25 11:42:44 +00:00
jbibik
db420350f8 Remove SubtitleParser.Factory references from Hls/Ss/DashMediaPeriod
Those classes only needed to have access to a `SubtitleParser.Factory` to get a potentially updated `Format` for TrackGroups. The `SubtitleParser.Factory` was only used to check the support for the `mimeType` and getting some cue-related behaviour.

This introduced complexity in a way that both Periods and Extractors needed to have the same `SubtitleParser.Factory` in their individual stacks. To ensure that the sample queue would get the same transcoded/original format.

Instead, now we expose `getOutputTextFormat` methods on `ChunkExtractor.Factory`, `SsChunkSource.Factory` and `HlsExtractorFactory`. Those are the dependencies that Hls/Ss/DashMediaPeriod can make use of to delegate the format-updating logic to.

#minor-release

PiperOrigin-RevId: 601130714
(cherry picked from commit 966b710897)
2024-01-25 11:42:44 +00:00
rohks
cfb676adaf Add Mp4ExtractorTest for sample with edit list (edts box)
The sample has multiple sync frames for video track.

PiperOrigin-RevId: 601129350
(cherry picked from commit f8dbbc82e2)
2024-01-25 11:42:44 +00:00
sheenachhabra
539a8f9a24 Write sample data offset related fields in tfhd and trun box
This fix makes output playable on VLC player.
The output does not play on QuickTime player which is being fixed in
a separate CL.

#minor-release

PiperOrigin-RevId: 601118813
(cherry picked from commit 806f90922b)
2024-01-25 11:42:44 +00:00
michaelkatz
db0262efdb Keep stream offset alive in ImageRenderer until stream transition
Fix modeled after OutputStreamInfo usage for stream offset in `MediaCodecRenderer`

PiperOrigin-RevId: 601109900
(cherry picked from commit 688622eb47)
2024-01-25 11:42:44 +00:00
tofunmi
a6756c6d40 MuxerWrapper rotation degree fix
Allow setAdditionalRotationDegrees to be called with same rotation after tracks added. This is needed for processes that mux files partially trim optimization so they don't error out after hitting the check state

Manually tested to ensure trim optimization succeeds, automated test added here as well

PiperOrigin-RevId: 601081778
(cherry picked from commit b94c7d08c1)
2024-01-25 11:42:44 +00:00
ibaker
97b8f1add9 Add proguard keep rules for ExoPlayer.setVideoEffects
These symbols in `lib-effect` are referenced via reflection from
`CompositingVideoSinkProvider` in `lib-exoplayer` in order to avoid
a hard dependency from `lib-exoplayer` to `lib-effect`. Without this
keep rule, the symbols can get renamed by R8 resulting in the
invocations failing.

#minor-release

PiperOrigin-RevId: 601074636
(cherry picked from commit e5621cc709)
2024-01-25 11:42:44 +00:00
timpeut
be0d531857 Suppress nullability warnings
PiperOrigin-RevId: 600891113
(cherry picked from commit 9c1aaa7c43)
2024-01-25 11:42:44 +00:00
tianyifeng
733c982a21 Change the behaviour in MediaMetadata.Builder.populate
Populate both `artworkUri` and `artworkData` in
`MediaMetadata.Builder.populate(MediaMetadata)` when at least one of them is non-null.

Issue: androidx/media#964
PiperOrigin-RevId: 600826103
(cherry picked from commit 35ac46b92e)
2024-01-25 11:42:44 +00:00
sheenachhabra
426dd77ac0 Suppress lint warning about missing POST_NOTIFICATIONS permission
For media session related notifications, permission is not required.
https://developer.android.com/develop/ui/views/notifications/notification-permission#exemptions-media-sessions

#minor-release

PiperOrigin-RevId: 600819251
(cherry picked from commit b98e5ac0d4)
2024-01-25 11:42:44 +00:00
sheenachhabra
081baa03b9 Process all tracks before writing fragment in fragmented MP4
Earlier implementation processed each track (pending sample's buffer info)
individually when writing their corresponding "traf" box in a fragment.
The change involves processing all tracks before start writing "traf" boxes.

#minor-release

PiperOrigin-RevId: 600811093
(cherry picked from commit 4c1581a175)
2024-01-25 11:42:44 +00:00
tonihei
a1280b1a23 Add setRemotePlaybackInfo to MediaStyle
This method is needed for some system apps to override the
output switcher when MediaRouter2 can't be used.

PiperOrigin-RevId: 600807119
(cherry picked from commit b64d754670)
2024-01-25 11:42:44 +00:00
siroberts
193cb143ff Change type of setCustomLayout in MediaSession.resultBuilder to List
PiperOrigin-RevId: 600801528
(cherry picked from commit 1f78aa5b2a)
2024-01-25 11:42:44 +00:00
tonihei
236287a513 Use Media3 MediaStyle instead of legacy one
The default notification provider was still using the legacy
compat MediaStyle instead of our own Media3 one. They are fully
equivalent in their implementation and API and can be swapped out
easily.

PiperOrigin-RevId: 600797920
(cherry picked from commit 9448f939f4)
2024-01-25 11:42:43 +00:00
tianyifeng
3a222473e9 Internal change
PiperOrigin-RevId: 600784733
(cherry picked from commit 0c0b19e26e)
2024-01-25 11:42:43 +00:00
ibaker
8e97895e9f Test parsing-during-extraction in ClippedPlaybackTest
We keep the previous parsing-during-rendering tests, even though they
can be a bit flaky, because this is an important regression test. The
regression risk is lower for this instrumentation test compared to
robolectric tests with different `ShadowLooper` behaviour.

#minor-release

PiperOrigin-RevId: 600781035
(cherry picked from commit a53f3451dd)
2024-01-25 11:42:43 +00:00
tofunmi
b8ff9cd715 Update METADATA to add clang-format requirement for glsl files
also ran clang-format for good measure

PiperOrigin-RevId: 600759938
(cherry picked from commit acc78125d2)
2024-01-25 11:42:43 +00:00
tofunmi
eda2080279 BaseGlShaderProgram: Remove redundant NoSuchElementException catching
PiperOrigin-RevId: 600738198
(cherry picked from commit cc62f0556c)
2024-01-25 11:42:43 +00:00
ibaker
dd9f61005e Release MediaSession and ExoPlayer in MediaControllerStubTest
Due to poor isolation between the session tests, in particular the
static state in `MediaSession.SESSION_ID_TO_SESSION_MAP`, an unreleased
session at the end of one test can cause subsequent tests to fail with
obscure errors like `Session ID must be unique`.

#minor-release

PiperOrigin-RevId: 600737697
(cherry picked from commit ca61ac6ca3)
2024-01-25 11:42:43 +00:00
tofunmi
21e6c1c43e Transformer demo: support selecting all media MIME types
It's useful for development and debugging to select a local image
or audio (only) file as well as a video file in the transformer demo
app.

I tested manually that you can select a local video, audio and image
but not e.g. a pdf with the main "choose local file" picker and only
an image with the choose local image picker for the bitmap overlay
demo.

PiperOrigin-RevId: 600722622
(cherry picked from commit 94ce356bc1)
2024-01-25 11:42:43 +00:00
tofunmi
fce23d9d4e Trim optimization fix: check if audio is removed when comparing formats
#minor-release

PiperOrigin-RevId: 600493390
(cherry picked from commit 5863ce7dd5)
2024-01-25 11:42:43 +00:00
tonihei
5ac0c8beee Fix cleared metadata when repeating the same item
Issue: androidx/media#1007

#minor-release

PiperOrigin-RevId: 600477540
(cherry picked from commit c6bf380d50)
2024-01-25 11:42:43 +00:00
sheenachhabra
50e0072dac Do not write empty track (with no samples) in a non fragmented MP4
PiperOrigin-RevId: 600453680
(cherry picked from commit 94bf9fa81d)
2024-01-25 11:42:43 +00:00
tonihei
6632531c4a Catch exceptions when retrieving current device from audio manager
We can just continue to assume that we don't know the current device.
This case happens on the latest Robolectric release where this method
call isn't implemented yet. As we not generally assume that the
method can throw, this workaround can be removed once Robolectric
is updated again.

#minor-release

PiperOrigin-RevId: 600426851
(cherry picked from commit 81615dd5b5)
2024-01-25 11:42:43 +00:00
christosts
05b8e633e3 SimpleBasePlayer: add protected method for thread verification
Add protected method in SimpleBasePlayer for thread verification to help
subclasses verify thread access for additional methods they define and
still report the same message to the user.

Also, remove the DAC link pointing to the ExoPlayer-specific
documentation from the exception message. Users who extend
SimpleBasePlayer have access to the class' javadoc.

PiperOrigin-RevId: 600426692
(cherry picked from commit 9e9c3cbe5e)
2024-01-25 11:42:43 +00:00
andrewlewis
18617b8a60 Prevent using high profile on problematic devices
[Android best
practices](https://developer.android.com/media/optimize/sharing#android_8_81_and_9)
recommend disabling B-frames on API 27, but some devices output B-frames anyway
when H.264/AVC High profile is selected. Add a workaround to force these
devices not to use high profile, to prevent B-frames being output.

`MediaMuxer` likely handles B-frames on these specific devices, but this change
allows the migration to default to in-app muxing to take place without
introducing errors, and it's a temporary workaround until B-frames are properly
supported in the in-app muxer.

PiperOrigin-RevId: 600422238
(cherry picked from commit 6029521898)
2024-01-25 11:42:43 +00:00
tonihei
3e005e62dc Check int and float parameters from external apps for validity
Some player method calls sent from MediaControllers accept int
or float values with a very clear API contract that disallows
some values. Filtering by these values early avoids calling a
Player implementation with invalid values.

PiperOrigin-RevId: 600413993
(cherry picked from commit c64b271f07)
2024-01-25 11:42:43 +00:00
ibaker
da5e5db3d2 Disable 'skip buffers with identical release times' in GTS tests
This optimization always reports buffers as 'skipped' (i.e. deliberately
not shown), which makes sense for the target case of high FPS content on
a lower refresh rate screen, when lots of the frames will **never** be
shown.

However the optimization also results in reporting buffers as 'skipped'
when decoding is a bit slow, resulting in a frame being released one
vsync late, which then means we have two frames to release in the same
vsync (when the previous vsync was empty). In this case, it would be
more correct to report this as a 'dropped' frame (since it was due to
slow decoding).

Until we can change the logic to distinguish these cases and report them
separately, this CL disables the optimization completely in GTS tests.
This is needed because we often assert there were zero skipped frames,
so slight decoding slowness can cause spurious/flaky test failures (our
threshold for dropped frames is non-zero).

#minor-release

PiperOrigin-RevId: 600406443
(cherry picked from commit 999e154b2a)
2024-01-25 11:42:43 +00:00
tonihei
5b7b19cf3c Add TODO about known feature gap in ImageRenderer
PiperOrigin-RevId: 600404546
(cherry picked from commit b84104e7a1)
2024-01-25 11:42:43 +00:00
andrewlewis
515d2e2578 Skip 1080p test on Nexus 7, API 21
This device doesn't seem to be capable of simultaneous encode/decode at this
resolution. We don't have a good way to check the capability (we are already
checking separate decode/encode capability) so just skip this test to save time
triaging its failures.

PiperOrigin-RevId: 600399564
(cherry picked from commit e82393ed41)
2024-01-25 11:42:43 +00:00
christosts
1dc880cb3b Rollback of e364510937
PiperOrigin-RevId: 600393114
(cherry picked from commit d1d03189eb)
2024-01-25 11:42:43 +00:00
tonihei
432cde6051 Use routed device in AudioCapabilities
From API 23, we may have a preferred device that is most likely used
as the output device.
From API 24, the AudioTrack tells us the actual routed device that is
used for output and we can listen to changes happening mid-playback.
From API 33, we can directly query the default device that will
be used for audio output for the current attributes.

If the routed device is known by any of the methods above, we can add
more targeted checks in methods like isBluetoothConnected to avoid
iterating over all devices that are not relevant.

The knowledge about the routed device will also be useful to check
advanced output capabilities in the future (e.g. for lossless
playback)

PiperOrigin-RevId: 600384923
(cherry picked from commit b1c954fa84)
2024-01-25 11:42:43 +00:00
huangdarwin
dcfa8c60ce Previewing: set VideoFrameReleaseControl after CVSP is created.
This allows us to inject a videoFrameProcessorFactory into
MediaCodecVideoRenderer, without issues about creating the
VideoFrameReleaseControl in the MediaCodecVideoRenderer.

Unfortunately, this does result in more complex CVSP state, where
VideoFrameReleaseControl is no longer final, may be null, and may potentially
change. However, this tries to be careful with assertions to guarantee good
state, and is cleaner than modifying the long-standing MediaCodecVideoRenderer
interface.

Tested that this works on the ExoPlayer demo with setVideoEffects applied, and
using a playlist with SDR->HDR and HDR->SDR items.

PiperOrigin-RevId: 599823412
(cherry picked from commit cb0f5a7fff)
2024-01-25 11:42:43 +00:00
samrobinson
4ac9d449d3 Remove thread blocking for TransformerInternal#getProgress.
Thread-safe interactions with state and value.

PiperOrigin-RevId: 599810672
(cherry picked from commit e364510937)
2024-01-25 11:42:43 +00:00
Michael Katz
b930b40a16
Fix merge error with ffmpeg_jni.cc
Fix merge error with ffmpeg_jni.cc where cherry-pick process included code from nonselected commit.
2024-01-17 17:04:10 +00:00
michaelkatz
0ae7a6bd60 Fix typo of missing closing parentheses in RELEASENOTES
PiperOrigin-RevId: 597496311
2024-01-11 10:48:31 +00:00
michaelkatz
1f79a0ad93 Bump media3 version to 1.2.1 and update RELEASENOTES
PiperOrigin-RevId: 596916027
(cherry picked from commit 324e1beef2)
2024-01-09 14:46:23 +00:00
microkatz
f0d24cbf58 Fix BitmapOverlay to have public constructor 2024-01-09 12:26:11 +00:00
ibaker
d55b33474e Clarify docs on Player.setMediaItem(s) and replaceMediaItem(s)
These methods sound similar, but have different behaviour. This change
tries to make the distinction clearer, and sign-post from one to the
other.

#minor-release

Issue: androidx/media#910
PiperOrigin-RevId: 595701540
(cherry picked from commit 95e742948c)
2024-01-09 12:17:49 +00:00
bachinger
141f9b760d Return null if media notification controller Future is not done
When the media notification controller is requested for a session
with `getConnectedControllerForSession` and the `Future` is not null
but not yet completed, the `Future` was returned either way. This was
reported as creating a race condition between the notification
being requested for update the very first time, and the media
notification controller having completed connecting to the session.

Returning null from `getConnectedControllerForSession` when the
`Future` is available but not yet done fixes the problem. This is
safe because for the case when a notification update is dropped,
the media notification controller will trigger the update as soon
as the connection completes.

Issue: androidx/media#917
#minor-release
PiperOrigin-RevId: 595699929
(cherry picked from commit 5c50b27e8f)
2024-01-09 12:17:49 +00:00
Copybara-Service
77d220c507 Merge pull request #369 from Tolriq:fix_invalid_frames
PiperOrigin-RevId: 595650068
(cherry picked from commit 8eda9f2ed2)
2024-01-09 12:17:48 +00:00
bachinger
d1b882ae56 Don't set negative values to setWhen()
When the 'when' timer of the notification is disabled
`DefaultMediaNotificationProvider` may set `C.TIME_UNSET`
as the time. Users reported problems on some devices with
this and the docs ask for an event time that probably
shouldn't be a negative number.

This change sets `0L` instead of `C.TIME_UNSET` when the
timer is disabled.

Issue: androidx/media#903

#minor-release

PiperOrigin-RevId: 594451074
(cherry picked from commit 426bc94090)
2024-01-09 12:17:48 +00:00
andrewlewis
b9d205ba0f Expand operating rate workaround to T612 chipset
PiperOrigin-RevId: 592916187
(cherry picked from commit 1845a4ae69)
2024-01-09 12:17:48 +00:00
Copybara-Service
b68173aae4 Merge pull request #914 from cemrich:366-forward-time-wrong
PiperOrigin-RevId: 592871532
(cherry picked from commit 966b5178b6)
2024-01-09 12:17:48 +00:00
ibaker
1da24b2875 Add container to Format.toLogString
While investigating Issue: androidx/media#887 I naively assumed the CEA-608
captions were in a TS file, but they're actually in an MP4 (which is
possibly obvious given DASH only supports MP4). This change includes
container info in the `EventLogger` `tracks` output.

PiperOrigin-RevId: 592192752
(cherry picked from commit 6853ffccae)
2024-01-09 12:17:48 +00:00
ibaker
5d7c9142ba Add Widevine license renewal example to demo app
PiperOrigin-RevId: 592182371
(cherry picked from commit 0b8a9a2ca4)
2024-01-09 12:17:48 +00:00
rohks
6236fd38d0 Fix sending negative bufferedDurationUs to CmcdData.Factory
When track is changed during playback, `playbackPositionUs` may be in middle of a chunk and `loadPositionUs` should be the start of that chunk. In this situation `loadPositionUs` can be less than the current `playbackPositionUs`, resulting into negative `bufferedDurationUs`. It translates to having no buffer and hence we should send `0` for `bufferedDurationUs` when creating new instances of `CmcdData.Factory`.

Issue: androidx/media#888

#minor-release

PiperOrigin-RevId: 591099785
(cherry picked from commit 7f6596bab2)
2024-01-09 12:17:48 +00:00
tonihei
4231a1d183 Use different wraparound assumptions for duration readers
The timestamp adjuster also estimates the number of wraparounds
of the 90Khz TS timestamp. It does that by assuming that a new
timestamp is always close to the previous one (in either direction).

This logic doesn't always work for duration estimates because the
timestamp at the end of the media is not close to the one at the
beginning and it may also never be less than the one at the beginning.

This can be fixed by introducing a new estimation model that assumes
the new timestamp is strictly greater than the previous one without
making the assumption that it has to be close to it.

Issue: androidx/media#855

#minor-release

PiperOrigin-RevId: 590936953
(cherry picked from commit 01578780a6)
2024-01-09 12:17:48 +00:00
bachinger
09d30a160e Fix broadcasting notifyChildrenChanged for legacy controllers
When broadcasting a notifyChildrenChanged event, the task for legacy
controllers was sent to the broadcasting callback. This would
technically work, but because the subscription list is maintained
with specific controllers, the broadcast controller isn't subscribed
and hence the call wasn't executed.

This change calls the overloaded method for a specific controller
for each connected controller. Making sure (only) subscribed
controllers are notified.

Issue: androidx/media#644
PiperOrigin-RevId: 590904037
(cherry picked from commit 4974f960e7)
2024-01-09 12:17:48 +00:00
Copybara-Service
c8a403edcf Merge pull request #753 from stevemayhew:p-fix-issue-9347
PiperOrigin-RevId: 590862514
(cherry picked from commit ab296ef686)
2024-01-09 12:17:39 +00:00
ibaker
33a51906f9 Combine 'matching' versions in media3 bug template
#minor-release

PiperOrigin-RevId: 590586491
(cherry picked from commit 9a766161ee)
2024-01-09 12:11:49 +00:00
Copybara-Service
7d564f8571 Merge pull request #864 from v-novaltd:dsparano-exo129_2
PiperOrigin-RevId: 590234505
(cherry picked from commit f465efeefd)
2024-01-09 12:11:49 +00:00
ibaker
5001f70b3d Fix VorbisComment.populateMediaMetadata key comparison to ignore case
Issue: androidx/media#876

#minor-release

PiperOrigin-RevId: 590215918
(cherry picked from commit 5580b78b13)
2024-01-09 12:11:49 +00:00
Copybara-Service
504bcd804f Merge pull request #867 from equeim:ndk-r26
PiperOrigin-RevId: 590142275
(cherry picked from commit 27f437b65a)
2024-01-09 12:11:49 +00:00
samrobinson
89baa96e32 Fix handling of repeated EOS in SilenceSkippingAudioProcessor.
Issue: androidx/media#712
PiperOrigin-RevId: 589882412
(cherry picked from commit 90a0cbdf3d)
2024-01-09 12:11:49 +00:00
ibaker
e509db273d Fix handling of multiple HEVC tracks in JPEG motion photos
The MP4 data in JPEG motion photos can contain multiple `video/hevc` tracks, but only the first is at a playable frame rate while the others are low-fps, high-res tracks designed for specific use-cases (not direct video playback).

ExoPlayer currently selects the unplayable track by default, because it
has a higher resolution. This change introduces a flag to
`Mp4Extractor` that results in the first video track being marked as
`ROLE_FLAG_MAIN`, and all subsequent video tracks `ROLE_FLAG_ALTERNATE`
- this then results in the playable lower-res track being selected by
default.

PiperOrigin-RevId: 589832072
(cherry picked from commit 5266c71b3a)
2024-01-09 12:11:46 +00:00
tofunmi
7df03f381d TransformerEndToEndTest: replace /*testId=*/ with variable
PiperOrigin-RevId: 589765715
(cherry picked from commit 00d5b6ec99)
2024-01-09 12:08:20 +00:00
ibaker
482d5d69d5 Add extractor and playback tests for Pixel JPEG motion photo
This image has two video tracks in the MP4 data, one is a 'real' video
which we want to play by default, and the other is a low-fps video track
which isn't intended to be directly played, it's encoded in HEVC for
compression and decoding efficiency.

This test demonstrates ExoPlayer's current behaviour default extraction
and playback, which results in selecting the high-res, low-fps track
(actually single sample in this example), instead of playing the actual
video.

PiperOrigin-RevId: 588068908
(cherry picked from commit 6360082b87)
2024-01-09 11:03:33 +00:00
ibaker
ea72856ac8 Add Robolectric e2e test support for HEVC content
PiperOrigin-RevId: 588055594
(cherry picked from commit d4fe3fe318)
2024-01-09 11:03:33 +00:00
Googler
c314123298 Fix nullability issue in MediaControllerImplLegacy
PiperOrigin-RevId: 588035411
(cherry picked from commit e0f1783a54)
2024-01-09 11:03:33 +00:00
michaelkatz
03eae4f9ac Limit processing Opus decode-only frames by seek-preroll in offload
As Opus decoders skip some bytes prior to playback during a seek, the renderer for bypass playback should send samples to the decoder even if they would be decode-only. However, the renderer should not send samples with time preceding that range. This change adds that constraint.

#minor-release

PiperOrigin-RevId: 588014983
(cherry picked from commit d1e38abf93)
2024-01-09 10:48:33 +00:00
tonihei
57187aa899 Map VORBIS channel layout to Android layout
Both the extension OPUS decoder and the OMX/C2 MediaCodec
implementations for OPUS and VORBIS decode into the channel
layout defined by VORBIS. See
https://www.xiph.org/vorbis/doc/Vorbis_I_spec.html#x1-140001.2.3

While this is technically correct for a stand-alone OPUS or VORBIS
decoder, it doesn't match the channel layout expected by Android.
See https://developer.android.com/reference/android/media/AudioFormat#channelMask

The fix is to apply the channel mapping after decoding if needed.
Also add e2e tests with audio dumps for the extension renderer,
including a new 5.1 channel test file.

Issue: google/ExoPlayer#8396

PiperOrigin-RevId: 588004832
(cherry picked from commit b1541b096f)
2024-01-09 10:48:31 +00:00
ibaker
483426ad7b MCR: Ensure mediaCrypto and codec are atomically non-null
`mediaCrypto` is initialized before `codec` in
`maybeInitCodecOrBypass`. Before this change, it was possible for
`maybeInitCodecOrBypass` to complete with `mediaCrypto != null` and
`codec == null`, in particular if it was run as part of clearing the
player surface (since in that case, no video codec is initialized).
This inconsistent state then causes issues during a later invocation of
`maybeInitCodecOrBypass`, when `mediaCrypto` is still non-null, and
`mediaCryptoRequiresSecureDecoder = true`, but the
content has been changed to unencrypted with no associated DRM session.
This results in a playback error, because a secure decoder is
initialized but there's no DRM session available to work with it.

This change ensures that when `maybeInitCodecOrBypass` completes,
either both `mediaCrypto != null` and `codec != null` (i.e. codec
initialization was completed) or `mediaCrypto == null` and
`codec == null` (i.e. codec initialization was not completed). We also
ensure that when nulling out `mediaCrypto` we also set
`maybeInitCodecOrBypass = false`. A later change should be able to
demote `maybeInitCodecOrBypass` from a field to a local in order to
remove any risk of that part of state becoming out of sync. This
resolves the issue, because during the second invocation of
`maybeInitCodecOrBypass` an insecure decoder is now (correctly)
initialized and the unencrypted content is successfully played.

#minor-release

PiperOrigin-RevId: 587713911
(cherry picked from commit 913f6da083)
2024-01-08 16:40:28 +00:00
ibaker
fa268cf84d Don't include null text or bitmaps in Cue.toBundle()
`fromBundle` doesn't distinguish between `FIELD_BITMAP` and `FIELD_TEXT`
being present with a null value, or being absent, so we might as well
avoid including them when the value is null.

I've separated this from a later change to add
`Cue.toSerializableBundle` which will also skip setting a bitmap value
into the `Bundle` if `this.bitmap == null`. This is partly because it
results in changes to a lot of extractor test dump files, and it's
easier to review that as a separate change.

PiperOrigin-RevId: 586626141
(cherry picked from commit 28c210686f)
2024-01-08 16:16:21 +00:00
christosts
3f3287aba3 Exit early progressive loads if the load task is canceled
Add an check when loading progressive media in case the load
is canceled. If the player is released very early, the progressive
media period may carry on with the initial loading unnecessarily.

PiperOrigin-RevId: 586288385
(cherry picked from commit 3d1d8f4439)
2024-01-08 15:55:36 +00:00
tofunmi
d68c36e082 Update emulator device names
PiperOrigin-RevId: 585682881
(cherry picked from commit b598c96c2f)
2024-01-08 15:55:36 +00:00
Copybara-Service
db648316ed Merge pull request #837 from superjohan:fix/android-14-clearkey
PiperOrigin-RevId: 585639025
(cherry picked from commit 5f27b18210)
2024-01-08 15:33:26 +00:00
andrewlewis
19ee78729c Restrict operating rate workaround to SM8550
PiperOrigin-RevId: 585613041
(cherry picked from commit e84a13fb54)
2024-01-08 15:33:26 +00:00
andrewlewis
710c1b9053 Work around codec frame rate issues in Redmi Note 9 Pro
The decoder and encoder won't accept high values for frame rate, so avoid
setting the key when configuring the decoder, and set a default value for the
encoder (where the key is required).

Also skip SSIM calculation for 4k, where the device lacks concurrent decoding
support.

PiperOrigin-RevId: 585604976
(cherry picked from commit 8b38b34b9f)
2024-01-08 15:33:26 +00:00
andrewlewis
97c9e234d2 Avoid value close to overflow for KEY_OPERATING_RATE
Using `Integer.MAX_VALUE` risks causing arithmetic overflow in the codec
implementation.

Issue: androidx/media#810

PiperOrigin-RevId: 585104621
(cherry picked from commit ad40db4489)
2024-01-08 15:33:24 +00:00
ibaker
36f634d8d1 Fix typo in DashManifestParser
PiperOrigin-RevId: 585017285
(cherry picked from commit 479344d74e)
2024-01-08 15:31:29 +00:00
tonihei
4234bc7091 Remove redundant ) in Javadoc
PiperOrigin-RevId: 584910697
(cherry picked from commit 85a54e2e19)
2024-01-08 15:31:29 +00:00
Copybara-Service
33484b0239 Merge pull request #707 from equeim:ffmpeg-6.0
PiperOrigin-RevId: 584893190
(cherry picked from commit 45b51d8c97)
2024-01-08 15:31:27 +00:00
bachinger
5e86c4c20b Add session extras to the state of the controller
This change adds `MediaController.getSessionExtras()` through
which a controller can access the session extras.

The session extras can be set for the entire session when
building the session. This can be overridden for specific
controllers in `MediaSession.Callback.onConnect`.

PiperOrigin-RevId: 584430419
(cherry picked from commit a063d137b4)
2024-01-08 15:26:08 +00:00
christosts
124eb31fd2 MidiExtractor: mark only the first sample as key-frame
This change fixes a bug with seeking forward in MIDI. When seeking forward,
the progressive media period attempts to seek within the sample queue, if a
key-frame exists before the seeking position. With MIDI, however, we can
only skip Note-On and Note-Off samples and all other samples must be sent
to the MIDI decoder.

When seeking outside the sample queue, the MidiExtractor already
instructs the player to start from the beginning of the MIDI input. With
this change, only the first output sample is a key-frame, thus the
progressive media period can no longer seek within the sample queue and
is forced to seek from the MIDI input start always.

Issue: androidx/media#704

PiperOrigin-RevId: 584321443
(cherry picked from commit ec08db458e)
2024-01-08 15:26:03 +00:00
tonihei
c9f6ad0398 Avoid clipping live offset override to min/max offsets
The live offset override is used to replace the media-defined
live offset after user seeks to ensure the live adjustment adjusts
to the new user-provided live offset and doesn't go back to the
original one.

However, the code currently clips the override to the min/max
live offsets defined in LiveConfiguration. This is useful to
clip the default value (in case of inconsistent values in the media),
but the clipping shouldn't be applied to user overrides as
the player will then adjust the position back to the min/max
and doesn't stay at the desired user position.

See 2416d99857 (r132871601)

PiperOrigin-RevId: 584311004
(cherry picked from commit af0282b9db)
2024-01-08 15:21:28 +00:00
bachinger
ea642a41e9 Add test case to test position conversion when POSITION_UNKNOWN
PiperOrigin-RevId: 584261559
(cherry picked from commit ec478138ba)
2024-01-08 15:18:39 +00:00
bachinger
5752a09a40 Return empty timeline when media info is null
Issue: androidx/media#708
PiperOrigin-RevId: 584054624
(cherry picked from commit 167f50a9ca)
2024-01-08 15:18:39 +00:00
rohks
0038355590 Use .test top level domain for test URI
PiperOrigin-RevId: 583951327
(cherry picked from commit ffbaa090aa)
2024-01-08 15:18:38 +00:00
rohks
aab1c9ada1 Populate MediaMetadata.extras to MediaMetadataCompat
Ensures backward compatibility.

Issue: androidx/media#802
PiperOrigin-RevId: 583425114
(cherry picked from commit 6df240877c)
2024-01-08 15:18:38 +00:00
tonihei
69884e764e Workaround layout problems with Material Design
In some contexts (e.g. BottomSheetDialogFrament), Material Design
themes will override the default of singleLine=false to true. This
causes layout problems because the forward/rewind buttons are no
longer visible with singleLine=true.

This problem can be avoided by explicitly requesting the default
value of false in our layout files.

Issue: androidx/media#511

#minor-release

PiperOrigin-RevId: 582604131
(cherry picked from commit 310e2edcca)
2024-01-08 15:18:38 +00:00
ibaker
b9722764ac Update recommended way to suppress @UnstableApi errors in lint.xml
#minor-release

PiperOrigin-RevId: 582599098
(cherry picked from commit bd7615c0b8)
2024-01-08 15:18:38 +00:00
andrewlewis
7901d1cc0a Expand frame drop workaround to Realme C11
Based on on-device testing, this device seems to have the same issue as Moto G (20) where frames are dropped despite configuring the decoder not to drop frames.

PiperOrigin-RevId: 581943805
(cherry picked from commit 330713f687)
2024-01-08 15:18:36 +00:00
tianyifeng
21461abc1c Parse "f800" as channel count of 5 for Dolby in DASH manifest
Issue: androidx/media#688
PiperOrigin-RevId: 581908905
(cherry picked from commit 79711ebd3f)
2024-01-08 15:08:53 +00:00
Googler
2db9da5c1d Don't crash when receiving a bad playback state
PiperOrigin-RevId: 580942377
(cherry picked from commit e79809616c)
2024-01-08 14:54:21 +00:00
jbibik
f189af5632 Remove old pre-releases from the github bug template
PiperOrigin-RevId: 580554963
(cherry picked from commit 508582d56c)
2024-01-08 14:54:21 +00:00
tonihei
d8d552af59 Fix proguard rule to also keep referenced class name
PiperOrigin-RevId: 579234050
(cherry picked from commit bce82bdc75)
2024-01-08 14:54:17 +00:00
tonihei
40094273ea Fix access to stale ByteBuffer in FfmpegAudioDecoder
The native code can now reallocate the buffer if it needs to grow
its size, so we have to reacquire a reference in the Java code to
avoid accessing a stale instance.

This fixes a bug introduced by 8750ed8de6.

PiperOrigin-RevId: 578799862
(cherry picked from commit ae6f83d298)
2024-01-08 14:51:05 +00:00
ibaker
2f4d475b5c Split media1/media3 conversion methods out of MediaUtils
Android Studio removed some nested imports, but I think the extra
qualification at the usage site is actually mostly helpful, so I'm
leaving it as-is.

PiperOrigin-RevId: 578518880
(cherry picked from commit 72b7019578)
2024-01-08 14:51:05 +00:00
ibaker
07258aa373 Add warning log if DASH manifest contains incomplete ClearKey info
Unfortunately we can't fail any more obviously at this point, because
manifests often contain config for multiple DRM schemes, and when
parsing the manifest we don't know which scheme is going to be used for
playback. It would be unreasonable to fail playback due to incomplete
ClearKey config if playback was otherwise going to succeed using e.g.
Widevine.

* Issue: androidx/media#777
* Issue: androidx/media#563
* Issue: google/ExoPlayer#9169

#minor-release

PiperOrigin-RevId: 578491484
(cherry picked from commit d42c23706b)
2024-01-08 14:51:05 +00:00
tianyifeng
2237f4902d Put the custom keys in MediaMetadataCompat to MediaMetadata.extras
PiperOrigin-RevId: 578473874
(cherry picked from commit 84022eacc5)
2024-01-08 14:51:05 +00:00
ibaker
fe8bbcaa12 Remove stray parentheses from release notes
PiperOrigin-RevId: 577809964
(cherry picked from commit db1ab1dcf3)
2024-01-08 14:50:26 +00:00
ibaker
a017080353 Bump okhttp dependency to 4.12
Issue: androidx/media#768
PiperOrigin-RevId: 577208115
(cherry picked from commit e8cca688ad)
2024-01-08 13:56:59 +00:00
michaelkatz
79ce4f1c73 Fallback to legacy sizerate check for H264 if CDD PerfPoint check fails
Some devices supporting Performance Points for decoder coverage are missing coverage over the CDD requirements for H264. For these cases ExoPlayer should fall back to legacy resolution and frame rate support checks. If there is an H264 stream evaluated as a `PerformancePointCoverageResult` of `COVERAGE_RESULT_NO`, then ExoPlayer checks for coverage of the [720p CDD requirement](https://source.android.com/docs/compatibility/10/android-10-cdd#5_3_4_h_264).

Issue: google/ExoPlayer#10898

Issue: androidx/media#693
PiperOrigin-RevId: 575768836
(cherry picked from commit 4515a0c3f2)
2024-01-08 13:56:42 +00:00
ibaker
f6fe90f30b Clean-up multi-line strings in YAML issue templates
* If we don't want any newlines in the result, it's better to use `>`
* If we want newlines (e.g. for markdown) then we should ensure the
  string **only** contains the newlines we want, because GitHub (unlike
  other markdown renderers) preserves single newlines in the output,
  leading to ugly newlines dictated by the source.

Also remove a markdown-style link that isn't renderered as markdown.

PiperOrigin-RevId: 590309749
(cherry picked from commit 6aeaad26ad)
2023-12-13 09:49:13 +00:00
Ian Baker
4f5038bc07 Merge branch 'release' into release-1.2.0 2023-11-17 16:13:40 +00:00
ibaker
2cc8afecc6 Remove recommendation to pin annotation-experimental to version 1.2.0
This was intended to avoid bringing in a transitive dependency on the
Kotlin standard library, but Gradle no longer flags lint errors on
`@RequiresOptIn` violations with `annotation-experimental:1.2.0` (1.3.0
is needed), making this recommendation dangerous. See also
https://issuetracker.google.com/310651921.

PiperOrigin-RevId: 582276430
(cherry picked from commit aa1ec981a3)
2023-11-17 10:07:23 +00:00
ibaker
1b83c59759 Add @OptIn to fields in demo PlayerActivity now this is supported
This is possible now we use `annotation-experimental:1.3.1`.

#minor-release

PiperOrigin-RevId: 582315579
(cherry picked from commit 8d83d491f1)
2023-11-17 10:07:23 +00:00
jbibik
f90726a78d Merge release notes for media3 1.2.0 stable release
PiperOrigin-RevId: 580923121
(cherry picked from commit 7ee07a5ff5)
2023-11-17 10:07:20 +00:00
jbibik
a2679fba54 Bump media3 versions to 1.2.0 (stable)
#minor-release

PiperOrigin-RevId: 580856330
(cherry picked from commit 3918d36200)
2023-11-12 20:52:00 +00:00
ibaker
7991838326 Rollback of a19f577976
PiperOrigin-RevId: 577139027
(cherry picked from commit dd6306e1ba)
2023-11-06 16:51:08 +00:00
rohks
3d6ac7f6f4 Add com.github.philburk:jsyn to JAR list
#minor-release

PiperOrigin-RevId: 576148893
(cherry picked from commit 00943a0a73)
2023-10-24 17:07:47 +01:00
rohks
1c54c393a0 Reorder RELEASENOTES to move unreleased changes to correct section
PiperOrigin-RevId: 575807109
(cherry picked from commit 105bdf57d8)
2023-10-23 07:03:35 -07:00
rohks
7a2d80d87e Bump Media3 version numbers for 1.2.0-rc01
#minor-release

PiperOrigin-RevId: 575805495
(cherry picked from commit 9d3d7abdc6)
2023-10-23 06:55:40 -07:00
rohks
c997c71258 Update RELEASENOTES for 1.2.0-rc01 release
PiperOrigin-RevId: 575795800
(cherry picked from commit 7202f5d4de)
2023-10-23 06:07:35 -07:00
bachinger
f2cf43ccd5 Use MediaSessionImpl.onMediaButtonEvent() to dispatch key events
This change moves the handling of any media button event into
`MediaSessionImpl.onMediaButtonEvent(intent)`. This includes
the double click handling from `MediaSessionLegacyStub`.

The advantage is that everything is in one place which allows
to offer `MediaSession.Callback.onMediaButtonEvent` with which
an app can override the default implementation and handle media
buttons in a custom way.

Media button events can originate from various places:

- Delivered to `MediaSessionService.onStartCommand(Intent)`
  - A `PendingIntent` from the notification below API 33
  - An `Intent` sent to the `MediaButtonReceiver` by the system dispatched
    to the service
- Delivered to `MediaSessionCompat.Callback.onMediaButtonEvent(Intent)`
  implemented by `MediaSessionLegacyStub` during the session is active
  - Bluetooth (headset/remote control)
  - Apps/system using `AudioManager.dispatchKeyEvent(KeyEvent)`
  - Apps/system using `MediaControllerCompat.dispatchKeyEvent(keyEvent)`

Issue: androidx/media#12
Issue: androidx/media#159
Issue: androidx/media#216
Issue: androidx/media#249

#minor-release

PiperOrigin-RevId: 575231251
(cherry picked from commit a79d44edc5)
2023-10-20 08:52:55 -07:00
rohks
47a451abf7 Update translations in the ui module
#minor-release

PiperOrigin-RevId: 575161190
(cherry picked from commit a8ab9e2c70)
2023-10-20 02:49:23 -07:00
simakova
9ac6f89fd7 Remove CompositionPlayer activity from the transformer demo app
The CompositionPlayer is not ready yet.

Issue: androidx/media#741
PiperOrigin-RevId: 574859927
(cherry picked from commit beb1711d4c)
2023-10-19 07:17:02 -07:00
tonihei
e1ad106453 Add missing command checks to playback resumption flow
Player methods shouldn't be called if they are not available and the
entry point to the playback resumption flow only checks
COMMAND_PLAY_PAUSE.

#minor-release

PiperOrigin-RevId: 574834148
(cherry picked from commit bfd1a2724c)
2023-10-19 05:36:49 -07:00
Copybara-Service
cb67b16cac Merge pull request #728 from lawadr:audio-capabilities-fix
PiperOrigin-RevId: 574829263
(cherry picked from commit 5f80a47081)
2023-10-19 05:18:21 -07:00
bachinger
a8b0bd712a Do not hide System UI when app rejects connection
If an app rejects the connection of the internal media notification manager
the session should behave like without the the media notification controller.
The legacy System UI controller should not be hidden or even rejected to
connect in such a case.

#minor-release

PiperOrigin-RevId: 574807901
(cherry picked from commit 54d5810fc3)
2023-10-19 03:48:16 -07:00
tonihei
2ae6b1e832 Rollback of 64bd3bcad3
PiperOrigin-RevId: 574766164
(cherry picked from commit f0cab4d03e)
2023-10-19 01:03:49 -07:00
Googler
4c778edd4d Rollback of eafe2e35f0
PiperOrigin-RevId: 574755143
(cherry picked from commit cf3733765c)
2023-10-19 00:18:56 -07:00
tonihei
21cb3a6bea Rollback of 4ebe630a80
PiperOrigin-RevId: 574530273
(cherry picked from commit c0759a4e62)
2023-10-23 13:15:17 +01:00
ibaker
5d6fefe410 Add formatting to scheme list in DefaultDataSource javadoc
The current formatting makes the 'scheme' part of the list blend into
the definition, especially when the definition is multi-line.

https://developer.android.com/reference/androidx/media3/datasource/DefaultDataSource

I considered adding another level of nesting, but I think bold will
help distinguish the structure of the list without adding too much HTML
or visual whitespace.

#minor-release

PiperOrigin-RevId: 574514208
(cherry picked from commit aec6db77fa)
2023-10-23 13:15:17 +01:00
michaelkatz
111ac63695 Send decode-only Opus samples in bypass mode for seekPreRoll skip
As Opus decoders skip some bytes prior to playback during a seek, the renderer for bypass playback should send samples to the decoder even if they would be decode-only.

#minor-release

PiperOrigin-RevId: 574494666
(cherry picked from commit 00193e0304)
2023-10-23 13:15:17 +01:00
tonihei
fe60d0d7b4 Use DataSourceBitmapLoader by default
This replaces the SimpleBitmapLoader that can now be deprecated
as it's fully unused and doesn't provide any additional functionality.

#minor-release

PiperOrigin-RevId: 574454636
(cherry picked from commit db86932781)
2023-10-23 13:15:17 +01:00
christosts
5a4adc9b25 Fix MIDI decoder build.gradle
Issue: androidx/media#734

#minor-release

PiperOrigin-RevId: 574425269
(cherry picked from commit ff4ff76990)
2023-10-23 13:15:17 +01:00
Googler
9ace4855e0 Rollback of 4ebe630a80
PiperOrigin-RevId: 574308136
(cherry picked from commit ff330bd8e9)
2023-10-23 13:15:17 +01:00
Googler
fcb3315edc Rollback of 64bd3bcad3
PiperOrigin-RevId: 574290408
(cherry picked from commit 1a43aa3602)
2023-10-23 13:15:17 +01:00
christosts
ee83baf92e Publish MIDI decoder module on Maven repository
Issue: androidx/media#734

#minor-release

PiperOrigin-RevId: 574182702
(cherry picked from commit 61770f8a61)
2023-10-23 13:15:17 +01:00
Copybara-Service
29846d0167 Merge pull request #491 from v-novaltd:dsparano-exo128
PiperOrigin-RevId: 574129451
(cherry picked from commit 009d48a75e)
2023-10-23 13:15:17 +01:00
ibaker
f290d0ef86 Move DASH subtitle parsing release note to correct section
#minor-release

PiperOrigin-RevId: 574090381
(cherry picked from commit df19097e22)
2023-10-23 13:15:17 +01:00
bachinger
8e48433be3 Send media button events from service directly using MediaSessionImpl
Media button event coming from the `MediaSessionService` are delegated
to the `MediaSessionImpl` and then sent to the session by using the
`MediaSessionStub` directly instead of using the `MediaController`
API.

Splitting the `MediaController.Listener` and `Player.Listener` in
`MediaNotificationManager` got reverted, and both listener are set to the
controller as before. This reverts the change that introduced a
different timing behaviour. It still holds, that a listener
registered on a `MediaController` that calls a method like `play()` is
called immediately and before the call has arrived at the player. This
change works around this behaviour from the library side by calling
`MediaSessionStub` directly with a `ControllerInfo`.

#minor-release

PiperOrigin-RevId: 573918850
(cherry picked from commit 64bd3bcad3)
2023-10-23 13:15:17 +01:00
bachinger
85167f175a Send ConnectionState as in-process bundle if possible
#minor-release

PiperOrigin-RevId: 573849858
(cherry picked from commit d5f093f43c)
2023-10-23 13:15:17 +01:00
bachinger
386d2239eb Only set the queue when COMMAND_GET_TIMELINE is available
Android Auto shows a queue button when the queue is not empty.
Apps were able to remove this queue button with the legacy API
by not setting the queue of the session.

After this change, removing `COMMAND_GET_TIMELINE` from the commands
of the media notification controller or the session player sets the
queue in the platform session to null.

#minor-release
Issue: androidx/media#339
PiperOrigin-RevId: 573813558
(cherry picked from commit f53e1bc6f6)
2023-10-23 13:15:17 +01:00
tonihei
e46ee01c00 Expand MediaItems in session demo instead of just replacing them
When MediaItems are added from the controller, we currently completely
replace the item with the one from our database, overriding any
potential additional information the controller may have set.

Also forward the onAddMediaItems/onSetMediaItems callbacks to common
helper methods instead of redirecting them through super methods

#minor-release
Issue: androidx/media#706
PiperOrigin-RevId: 573799351
(cherry picked from commit 00425dbe80)
2023-10-23 13:15:17 +01:00
tianyifeng
2a7d85e065 Calculate HLS live playlist refresh interval accurately
Previously, we calculated the next playlist reload time by adding the target duration (or half of it, depending on whether there is a real update in the new playlist snapshot) from the last load completion time, which makes the reload interval as long as `targetDuration(or half of it) + lastLoadDuration`. While still complying to the standard that "the client MUST wait for at least the target duration before attempting to reload the Playlist file again", this could cause buffering when the playback position is close to the end of live window. This change is to calculate the reload interval accurately by not adding the term `lastLoadDuration`.

Issue: androidx/media#663

#minor-release

PiperOrigin-RevId: 573300009
(cherry picked from commit 58a63c82aa)
2023-10-23 13:15:16 +01:00
Googler
b65136e292 Remove deprecated DownloadNotificationHelper.buildProgressNotification
Use a non deprecated method that takes a `notMetRequirements` parameter
instead.

PiperOrigin-RevId: 573252909
(cherry picked from commit 8b7ebc7032)
2023-10-23 13:15:12 +01:00
ibaker
3244318d28 Test more URI forms in RawResourceDataSourceContractTest
PiperOrigin-RevId: 573220915
(cherry picked from commit 40459f7212)
2023-10-23 13:14:42 +01:00
ibaker
f77f3dfa1a Migrate SubtitleParser implementations to incremental parse()
All production and test callers of the non-incremental methods are
already migrated, so we can remove them in this change too.

#minor-release

PiperOrigin-RevId: 573207318
(cherry picked from commit ecd24646cb)
2023-10-23 13:14:42 +01:00
ibaker
74277b14c8 Remove the 'super speed' SmoothStreaming PlayReady stream from demo
This content is no longer available, the manifest is returning a 404.

Issue: google/ExoPlayer#11309

#minor-release

PiperOrigin-RevId: 573202175
(cherry picked from commit a19f577976)
2023-10-23 13:14:42 +01:00
ibaker
b77a2530af Change LegacySubtitleUtil handling of SubtitleParser.OutputOptions
If the `Subtitle` has 'active' cues at `OutputOptions.startTimeUs`, this
change ensures these are emitted in a `CuesWithTiming` with
`CuesWithTiming.startTimeUs = OutputOptions.startTimeUs`. If
`OutputOptions.outputAllCues` is also set, then another `CuesWithTiming`
is emitted at the end that covers the 'first part' of the active cues,
and  ends at `OutputOptions.startTimeUs`.

As well as adding some more tests to `LegacySubtitleUtilWebvttTest`,
this change also adds more tests for `TtmlParser` handling of
`OutputOptions`, which transitively tests the behaviour of
`LegacySubtitleUtil`.

#minor-release

PiperOrigin-RevId: 573151016
(cherry picked from commit f9ece88a25)
2023-10-23 13:14:42 +01:00
Copybara-Service
c7186c9400 Merge pull request #650 from cedricxperi:dts-lbr-buffer-underflow-fix
PiperOrigin-RevId: 572864175
(cherry picked from commit 2421ba4d8f)
2023-10-23 13:14:42 +01:00
ibaker
24c39b6a78 Migrate SubtitleParser tests to incremental parse() methods
All the production code is already calling these new incremental
methods, migrating the tests allows us to remove the old
`List`-returning methods in a follow-up change.

#minor-release

PiperOrigin-RevId: 572822828
(cherry picked from commit a12bde4f57)
2023-10-23 13:14:42 +01:00
tonihei
d7044c7b83 Avoid bundling PlayerInfo for in-process calls
PlayerInfo bundling is costly and we can add a shortcut for
in-process binder calls where we store the direct object
reference in a live Binder object that can be written to the
Bundle instead of the individual data fields.

#minor-release

PiperOrigin-RevId: 572816784
(cherry picked from commit d1fc15f207)
2023-10-23 13:14:42 +01:00
tonihei
7d6146170f Split available command filtering and bundling
A few methods in PlayerInfo and related classes combine filtering
information with bundling in one method. This makes it impossible
to use just the filtering for example and it's also easier to reason
about than two dedicated methods. This change splits these methods
into two parts accordingly.

PiperOrigin-RevId: 572592458
(cherry picked from commit 4ebe630a80)
2023-10-23 13:14:42 +01:00
bachinger
0255f3be16 Check whether a session is still managed before removing
When the controller of the `MediaNotificationManager` is disconnected,
the session is removed from the service without checking whether the
session hasn't already been removed. This caused flakiness in `MediaSessionServiceTest.addSession()`.

Because there is a public API `MediaSessionService.removeSession()`,
the controller can't make an assumption whether the session is still
contained in the service when being disconnected.

#minor-release

PiperOrigin-RevId: 572568350
(cherry picked from commit 7fdc5b22ba)
2023-10-23 13:14:42 +01:00
michaelkatz
ec21b9e634 Update release notes to mention AudioOffloadPreference class changes
Issue: androidx/media#721
PiperOrigin-RevId: 572565009
(cherry picked from commit cef85be40f)
2023-10-23 13:14:42 +01:00
ibaker
de3eb672a5 Request notification permission when starting session demo app
#minor-release

PiperOrigin-RevId: 572556101
(cherry picked from commit c7a091a973)
2023-10-23 13:14:41 +01:00
bachinger
4b85207621 Add MediaSession.Builder().setPeriodicPositionUpdateEnabled()
This allows to disable periodic position updates when building
the session.

#minor-release

PiperOrigin-RevId: 572531837
(cherry picked from commit 4dc3db4da3)
2023-10-23 13:14:41 +01:00
ibaker
03a3f77340 Add experimental opt-in to parse DASH subtitles during extraction
This currently only applies to subtitles muxed into mp4 segments, and
not standalone text files linked directly from the manifest.

Issue: androidx/media#288

#minor-release

PiperOrigin-RevId: 572263764
(cherry picked from commit 66fa591959)
2023-10-23 13:14:41 +01:00
tonihei
7254f5aca5 Do not interrupt controller thread without a good reason
Interrupting the main thread in particular may be dangerous
as the flag is not cleared after handling the current message.

#minor-release

PiperOrigin-RevId: 572259422
(cherry picked from commit 846117399f)
2023-10-23 13:14:41 +01:00
ibaker
2ec24ac55f Update @UnstableApi docs to include a package-info.java example
#minor-release

PiperOrigin-RevId: 572229092
(cherry picked from commit 7009c53c79)
2023-10-23 13:14:41 +01:00
ibaker
292701ba55 Add SubtitleParser.Factory.getCueReplacementBehavior()
This gives access to the replacement behavior for a particular subtitle
format without needing to instantiate a `SubtitleParser`.

#minor-release

PiperOrigin-RevId: 572226084
(cherry picked from commit e366c3d419)
2023-10-23 13:14:41 +01:00
tonihei
d078baf435 Remove unneccessary method parameter
The value already exists as a class field.

#minor-release

PiperOrigin-RevId: 572200771
(cherry picked from commit e5fa0c2ce9)
2023-10-23 13:14:41 +01:00
tonihei
69749c59cc Align audio adaptive support checks with video
In particular:
 - Add allowAudioNonSeamlessAdaptiveness parameter (default true, same
   as video and as already implemented by default)
 - Forward mixedMimeTypeAdaptation support to AudioTrackScore
   (as for VideoTrackScore) and adapt mixed MIME type adaptive
   support accordingly
 - Check adaptive support when deciding whether a track is allowed for
   adaptation (also same check as for video). This takes the new
   parameter into account.

PiperOrigin-RevId: 572191308
(cherry picked from commit f20d18e6ca)
2023-10-23 13:14:39 +01:00
ibaker
bd24e788b3 Use package-level @OptIn for demo apps
This demonstrates that `@OptIn` can now be used at the package-level
(since [`androidx.annotation:annotation-experimental:1.3.0`](https://developer.android.com/jetpack/androidx/releases/annotation#annotation-experimental-1.3.0)).

PiperOrigin-RevId: 572187729
(cherry picked from commit d60596cfca)
2023-10-23 13:13:46 +01:00
tonihei
77bccf0dd1 Make BundleListRetriever local Binder aware
When used within the same process, we don't have to go via the
onTransact method (which includes marshalling and unmarhsalling
the data), but can directly return the list.

#minor-release

PiperOrigin-RevId: 572179846
(cherry picked from commit 0bddd06938)
2023-10-23 13:13:46 +01:00
tonihei
7d063e9aab Add missing Future cancellation checks
Future.isDone and getDone doesn't imply the Future was successful
and it may have been cancelled or failed.

In case where we handle failure, we should also handle cancellation
to avoid CancellationException to bubble up unchecked.

In demo app code where we use isDone for field initialization, we
want to crash in the failure case (usually security exception where
the connection is disallowed), but we want to gracefully handle
cancellation. Cancellation of these variables usually happens in
Activity.onDestroy/onStop, but methods may be called after this point.

#minor-release

PiperOrigin-RevId: 572178018
(cherry picked from commit fe7c62afe0)
2023-10-23 13:13:46 +01:00
Googler
c913237e92 Fix the resumption of playback when suitable device is connected.
With this change the playback will resume as soon as the suitable device is connected and suppression reason is cleared (within set time out).

#minor-release

PiperOrigin-RevId: 572140309
(cherry picked from commit dc859eae82)
2023-10-23 13:13:46 +01:00
Googler
f799259a14 ...Update metalava library and Reformat api.txt...
PiperOrigin-RevId: 572013340
(cherry picked from commit da49a02b44)
2023-10-23 13:13:46 +01:00
tonihei
fdaf9b47df Use more targeted listening in session PlayerActivity
The current metadata updates are triggered by item transitions,
but depending on the speed of loading the playlist, the first
metadata may only be known later via metadata-change callbacks.

Slow playlist loading also means the UI stays empty and it's
beneficial to show a placeholder to avoid the impressions the
UI hangs.

Finally, clean-up by removing unused string constants and merging
all listeners into onEvents

#minor-release

PiperOrigin-RevId: 571951529
(cherry picked from commit fd81c904e1)
2023-10-23 13:13:46 +01:00
ibaker
99ea703455 Fix the asset and dump file names for the standalone TTML DASH test
#minor-release

PiperOrigin-RevId: 571941997
(cherry picked from commit 33c151eb5b)
2023-10-23 13:13:46 +01:00
christosts
3ba8459624 Report dropped frames from the VideoSink
After 4fad529433, MediaCodecVideoRenderer does not report if frames
are dropped from the VideoSink. This commit fixes this.

#minor-release

PiperOrigin-RevId: 571905721
(cherry picked from commit 05b17b5430)
2023-10-23 13:13:46 +01:00
ibaker
1818d46077 Return true from CuesResolver.addCues if the output changed
This belongs in the resolver, because it depends on the resolution
algorithm (and therefore the logic can't live in `TextRenderer`).

This also fixes a bug in `TextRenderer` where we were doing arithmetic
with `cues.durationUs` without checking if it was `TIME_UNSET` first.

#minor-release

PiperOrigin-RevId: 571332750
(cherry picked from commit 272428734b)
2023-10-23 13:13:46 +01:00
ibaker
6ad54d72ad Update TextRenderer to handle CuesWithTiming instances directly
The existing `Subtitle` handling code is left intact to support the
legacy post-`SampleQueue` decoding path for now.

This also includes full support for merging overlapping `CuesWithTiming`
instances, which explains the test dump file changes, and which should
resolve the following issues (if used with the
decoder-before-`SampleQueue` subtitle logic added in
5d453fcf37):

* Issue: google/ExoPlayer#10295
* Issue: google/ExoPlayer#4794

It should also help resolve Issue: androidx/media#288, but that will also require
some changes in the DASH module to enable pre-`SampleQueue` subtitle
parsing (which should happen soon).

PiperOrigin-RevId: 571021417
(cherry picked from commit 002ee0555d)
2023-10-23 13:13:42 +01:00
jbibik
c16accb816 Update RELEASENOTES.md for 1.2.0-beta01 release
PiperOrigin-RevId: 571941830
(cherry picked from commit 62ad1dfdf5)
2023-10-17 15:57:14 +01:00
jbibik
ef013c04ab Bump Media3 version numbers for 1.2.0-beta01 release
#minor-release

PiperOrigin-RevId: 572003628
(cherry picked from commit 97645a200d)
2023-10-09 22:47:37 +01:00
michaelkatz
f776021dcc Deflake RTSP keep-alive monitor test
Alters RTSP KeepAlive monitor test to just make sure that keep-alive message is sent.

The test was added in 42c1846984

#minor-release

PiperOrigin-RevId: 571349013
(cherry picked from commit 417970f713)
2023-10-06 19:20:13 +01:00
jbibik
efb8c70a4b Add multidex Gradle dependency to test-session-current
#minor-release

PiperOrigin-RevId: 571347856
(cherry picked from commit e63be0317f)
2023-10-06 19:20:13 +01:00
michaelkatz
f43d0f187a Update cached playbackHeadPosition when pausing after AudioTrack.stop()
In some streaming scenarios, like offload, the sink may finish writing buffers a bit before playback reaches the end of the track. In this case a player may pause while in this 'stopping' state.

The AudioTrackPositionTracker needs to update the cached values it uses to calculate position in the `PLAYSTATE_STOPPED`/`PLAYSTATE_STOPPING` states if pause/play are called during this period.

PiperOrigin-RevId: 571345914
(cherry picked from commit a789db5b41)
2023-10-06 19:20:13 +01:00
michaelkatz
5d0d30a280 Allow pause if in offload mode after writing all buffers
In offload mode, `AudioTrack#stop()` will put the track in `PLAYSTATE_STOPPING` rather than `PLAYSTATE_STOPPED`. The difference in state means that `AudioTrack` can be paused and played during this 'stopping' period.

Currently, if `AudioTrackPositionTracker#handleEndOfStream()` has been called then `DefaultAudioSink` in `pause()` won't call `AudioTrack#pause()`. `AudioTrack#pause()` should be called in this case if in offload mode.

#minor-release

PiperOrigin-RevId: 571335108
(cherry picked from commit ab42d64d6d)
2023-10-06 19:20:12 +01:00
bachinger
da5a743175 Add MEDIA_PLAY_FROM_SEARCH to manifest of session demo app
This is required to make GMS send voice commands to the app.

#minor-release

PiperOrigin-RevId: 571326122
(cherry picked from commit 78f403aa7b)
2023-10-06 15:57:25 +01:00
tonihei
7e9ca0bf74 Deprecate decode-only flag.
The flag is no longer used by our components and only set and checked
in a few places to guarantee compatiblity with existing renderers and
decoders that still use it.

The flag will be removed in the future due to its design limitations.

#minor-release

PiperOrigin-RevId: 571291168
(cherry picked from commit 89d01981bc)
2023-10-06 15:57:25 +01:00
tonihei
03008b966d Remove release notes lines added by merge conflict
#minor-release

PiperOrigin-RevId: 571005643
(cherry picked from commit 49b1e0bbc2)
2023-10-06 15:57:25 +01:00
ibaker
dad362b414 Add tests for CuesWithTiming.endTimeUs
#minor-release

PiperOrigin-RevId: 570988195
(cherry picked from commit 6057b59723)
2023-10-06 15:57:25 +01:00
tofunmi
a4582be2c1 Change equalTo check in ImagePlaybackTest to atLeast
The aim of this test is to make sure the image is onscreen for the right amount of time, so to drive down flakes from the decoder taking too long, change this to an atLeast check

#minor-release

PiperOrigin-RevId: 570988044
(cherry picked from commit 9cc75ca52e)
2023-10-06 15:57:25 +01:00
tonihei
3c414fcde4 Update documentation wrongly referencing the decode-only flag
#minor-release

PiperOrigin-RevId: 570973457
(cherry picked from commit 87f1b4252e)
2023-10-06 15:57:25 +01:00
samrobinson
fbb64e2db2 Remove experimental keepAudioTrackOnSeek.
PiperOrigin-RevId: 570966027
(cherry picked from commit 068d420ba2)
2023-10-06 15:57:25 +01:00
michaelkatz
21c714d8d4 Use RTSP Setup response timeout value in KeepAliveMonitor intervalMs
Set KeepAliveMonitor to send a keep-alive message at half the timeout value, if provided, by the RTSP Setup response.

Issue: androidx/media#662
PiperOrigin-RevId: 570946237
(cherry picked from commit 42c1846984)
2023-10-06 15:57:25 +01:00
ibaker
1a0000db1d Add CuesWithTiming.endTimeUs
In most cases this is more useful than `durationUs`.

We will keep `durationUs`, and the constructor will continue to take
`startTimeUs` and `durationUs`, to allow for use-cases where we don't
know the start time but still want to indicate a duration (this will be
used to implement CEA-608 timeout).

#minor-release

PiperOrigin-RevId: 570944449
(cherry picked from commit bf7b91e57e)
2023-10-06 15:57:25 +01:00
tonihei
df85a996ba Add Decoder.setOutputStartTimeUs and use it in extension decoders
This gets rid of the reliance on the decode only flag that is still
set on input buffers to the decoder if they are less than the start
time.

We still need to set and check the decode-only flag in SimpleDecoder
to ensure compatbility with custom decoders that use the flag while
it's not fully removed.

PiperOrigin-RevId: 570736692
(cherry picked from commit a03e20fe6c)
2023-10-06 15:57:25 +01:00
tonihei
7bfddf9558 Remove wrong Javadoc
The corresponding logic has been removed in 796781d4c3

#minor-release

PiperOrigin-RevId: 570729509
(cherry picked from commit 64fe863f31)
2023-10-06 15:57:25 +01:00
tonihei
889739d33e Replace ENCODING_DTS_UHD_P2 value by reference to platform constant
#minor-release

PiperOrigin-RevId: 570696505
(cherry picked from commit 9ef1c20e7a)
2023-10-06 15:57:25 +01:00
tofunmi
c99c71b5a4 Update getName of BitmapFactoryImageDecoder
cleanup from 8f5835c51c

#minor-release

PiperOrigin-RevId: 570663437
(cherry picked from commit 572fb4676c)
2023-10-06 15:57:25 +01:00
tonihei
6d2bf513fb Add onAudioTrackInitialized/Released events
This is useful for analytics and understanding player behavior
during transitions.

#minor-release

PiperOrigin-RevId: 570623227
(cherry picked from commit 8e2bf21011)
2023-10-06 15:57:25 +01:00
rohks
55633658c6 Add nullness annotations to DecoderVideoRenderer
Also fixed a bug where format queue was polled with wrong timestamp value.

#fixit

PiperOrigin-RevId: 570420304
(cherry picked from commit a879bae1ee)
2023-10-06 15:57:25 +01:00
rohks
7957554442 Add nullness annotations to MediaCodecRenderer
#fixit

PiperOrigin-RevId: 570403923
(cherry picked from commit 7a91474af9)
2023-10-06 15:57:25 +01:00
tonihei
0480b70f36 Move decode-only handling out of MetadataDecoder interface logic
The interface requires the implementation to return null if the
decode-only flag is set. So instead of setting the flag and returning
null, we can simply not call the method and assume it's null.

The only reason where this wouldn't work is if the metadata format
has keyframe-like logic and requires previous metadata to decode
the next one. This is not something we came across before and it seems
ignorable. If that feature is needed in the future, we should instead
add a method to MetadataDecoder to set the first output timestamp.

#minor-release

PiperOrigin-RevId: 570399838
(cherry picked from commit 796781d4c3)
2023-10-06 15:57:25 +01:00
michaelkatz
2564c11834 Disable offload scheduling at set up for track transition
While sleeping for offload, position is estimated based on time playing. If asleep and AudioTrack is reused, then the position will keep incrementing as the subsequent item plays. That is until wakeup when playing position is updated to the timestamp of the second item. Offload scheduling should be disabled until track transitions fully.

PiperOrigin-RevId: 570397140
(cherry picked from commit da06bf057a)
2023-10-06 15:57:24 +01:00
jbibik
e4637d82b7 Add DashPlayback test with sideloaded TTML subtitles
The test is hidden behind the Ignore annotation due to some flakiness just like `webvttInMp4`. However, it will be removed when the subtitle parsing is moved to a pre-sample-queue architecture.

#minor-release

PiperOrigin-RevId: 570376275
(cherry picked from commit bd5a3920b8)
2023-10-06 15:57:24 +01:00
jbibik
484339e3e6 Add Dumper support for outputting multiline strings
PiperOrigin-RevId: 570348425
(cherry picked from commit b83f12c4ba)
2023-10-06 15:57:24 +01:00
tonihei
bc3dded868 Explicitly mark DecoderOutputBuffer as shouldBeSkipped if needed
In some cases, SimpleDecoder output needs to be skipped for rendering
because the decoder produces no data. This is one of the remaining
usages of BUFFER_FLAG_DECODE_ONLY at the moment and can be more
directly solved without using the flag. SimpleDecoder still needs to
check the flag though for backwards compatbility with custom decoders
while the flag is not completely removed.

PiperOrigin-RevId: 570345233
(cherry picked from commit c8aac24ffd)
2023-10-06 15:57:24 +01:00
samrobinson
27d6a9bcbd Deprecate experimental keepAudioTrackOnSeek methods.
#minor-release

PiperOrigin-RevId: 570340714
(cherry picked from commit 1bb501ab50)
2023-10-06 15:57:24 +01:00
ibaker
ffe65b358f Fix Util.scaleLargeValue/Timestamp to handle negative numbers
#minor-release

PiperOrigin-RevId: 570337535
(cherry picked from commit 9edbfa974a)
2023-10-06 15:57:24 +01:00
Copybara-Service
948491f665 Merge pull request #574 from hugohlln:main
PiperOrigin-RevId: 570037211
(cherry picked from commit b06d823238)
2023-10-06 15:57:24 +01:00
christosts
ecfddb9aeb MediaCodeVideoRenderer: flush video sink before codec
When seeking, we must first flush the video sink so it stops
using any SurfaceTextures before flushing MediaCodec.

#minor-release

PiperOrigin-RevId: 570015998
(cherry picked from commit 144bd72236)
2023-10-06 15:57:24 +01:00
ibaker
1c7b426ccb Mark test_session_current support app as MultiDexApplication
PiperOrigin-RevId: 570015354
(cherry picked from commit e9bf41ca04)
2023-10-06 15:57:24 +01:00
bachinger
4acce9bcae Update playlist UI when playlist is updated
When changing the playlist on Android Auto the UI of the
activity needs to be kept in sync.

PiperOrigin-RevId: 569528785
(cherry picked from commit 52d9fbff73)
2023-10-06 15:57:24 +01:00
oceanjules
12332a4afc Remove FfmpegVideoRenderer from Media3 1.2.0 release 2023-10-04 13:13:58 +01:00
tianyifeng
3f366109b7 Fix Media3 1.1.1 release notes
PiperOrigin-RevId: 556735572
(cherry picked from commit dca4f1a9db)
2023-08-14 12:05:48 +00:00
tianyifeng
9c7cf224e5 Bump version numbers to Media3 1.1.1 and ExoPlayer 2.19.1
PiperOrigin-RevId: 555987303
(cherry picked from commit 1e2a5cd06a)
2023-08-14 10:22:43 +00:00
tianyifeng
a842e62991 Update release notes for Media3 1.1.1
PiperOrigin-RevId: 555951054
(cherry picked from commit b24f7678bb)
2023-08-14 10:22:38 +00:00
jbibik
2fb3a45b6c Fix Gradle Lint with @RequiresApi and SDK version
Gradle Lint doesn't recognise `checkState` assertion and TargetApi should only ever be used for suppressing a bug in Android Lint. Hence, we keep @RequiresApi and add an if-statement explicitly. Also, fixes >26 to >=26 for the version check.

PiperOrigin-RevId: 555144577
(cherry picked from commit f7c31bd3ef)
2023-08-09 13:31:48 +00:00
rohks
4af6fcb838 Bump IMA SDK version to 3.30.3
PiperOrigin-RevId: 555130308
(cherry picked from commit 38eabdc9fc)
2023-08-09 12:16:14 +00:00
tonihei
bab04ebf2c Verify source is not released before updating ad playback state
Updates to the ad playback state are posted on the main handler,
so they may arrive after the source has already been released
(=the internal MediaSource is null). This can cause NPEs.

PiperOrigin-RevId: 555102426
(cherry picked from commit 20d2ce7ce8)
2023-08-09 09:51:56 +00:00
jbibik
136f0876d5 Fix PlayerWrapper's creation of VolumeProviderCompat
When hardware buttons are used to control the volume of the remote device, the call propagates to `MediaSessionCompat.setPlaybackToRemote(volumeProviderCompat)`. However, `volumeProviderCompat` was created incorrectly when the new device volume commands were present (COMMAND_SET_DEVICE_VOLUME_WITH_FLAGS and COMMAND_ADJUST_DEVICE_VOLUME_WITH_FLAGS), i.e. with volumeControlType = `VOLUME_CONTROL_FIXED`. This resulted in `VolumeProviderCompat` which doesn't call `onSetVolumeTo` or `onAdjustVolume` and hence doesn't propagate the calls to the `Player`. Instead, it only worked with the deprecated commands which ensured the volumeControlType was `VOLUME_CONTROL_ABSOLUTE`.

This bug was introduced in c71e4bf1ff (1.0 media 3 release) when `PlayerWrapper`'s call to `createVolumeProviderCompat` was mostly rewritten to handle the new commands, but the two if-statements were not amended. Note: this change fixes the bug only for Android 11 and below. For 12 and above, there is a tracking bug for the regression that was introduced: https://issuetracker.google.com/issues/201546605

http://Issue: androidx/media#554

PiperOrigin-RevId: 554966361
(cherry picked from commit dedccc596e)
2023-08-08 22:51:53 +00:00
Tianyi Feng
294fa261b7 Merge pull request #528 from zgzong:patch-2
PiperOrigin-RevId: 554869426
(cherry picked from commit ef54364478)
2023-08-10 12:09:43 +00:00
rohks
57c73d51da Add field object type (ot)
Added this CMCD-Object field to Common Media Client Data (CMCD) logging.

PiperOrigin-RevId: 554843305
(cherry picked from commit 3ec462d1cf)
2023-08-08 16:09:07 +00:00
ibaker
7e58fde18e Use ceiling divide logic in AudioTrackPositionTracker.hasPendingData
This fixes a bug with playing very short audio files, introduced by
fe710871aa

The existing code using floor integer division results in playback never
transitioning to `STATE_ENDED` because at the end of playback for the
short sample clip provided `currentPositionUs=189937`,
`outputSampleRate=16000` and `(189937 * 16000) / 1000000 = 3038.992`,
while `writtenFrames=3039`. This is fixed by using `Util.ceilDivide`
so we return `3039`, which means
`AudioTrackPositionTracker.hasPendingData()` returns `false` (since
`writtenFrames ==
durationUsToFrames(getCurrentPositionUs(/* sourceEnded= */ false))`).

Issue: androidx/media#538
PiperOrigin-RevId: 554481782
(cherry picked from commit 6e91f0d4c5)
2023-08-07 15:17:31 +00:00
kimvde
4b3a4b3395 Check command availability before getting tracks in PlayerView
#minor-release

PiperOrigin-RevId: 554451569
(cherry picked from commit 117b18f54c)
2023-08-07 13:01:49 +00:00
tianyifeng
c0c67dd5e6 Update translations
PiperOrigin-RevId: 554439196
(cherry picked from commit ff3902debb)
2023-08-07 11:54:19 +00:00
rohks
46c757b008 Simplify and accurately compute chunk duration
Refactored `CmcdLog` to `CmcdHeadersFactory` for improved representation of its purpose and updated implementations.

#minor-change

PiperOrigin-RevId: 552831995
2023-08-01 16:48:19 +00:00
rohks
e8a4894a31 Fix wrong documentation and add missing annotations
#minor-release

PiperOrigin-RevId: 552749407
(cherry picked from commit 267d4164a9)
2023-08-01 11:21:33 +01:00
andrewlewis
50db5207cb Fix parsing of H.265 sequence parameter sets
Fix short term reference picture list parsing. Before this change, `deltaPocS0`
was derived by adding one to the value of the syntax element
`delta_poc_s0_minus1`, but (maybe surprising) the specification actually says
that `DeltaPocS0[stRpsIdx][i]` should be assigned the negation
`-(delta_poc_s0_minus1[i] + 1)` on the first iteration, then that value added
to the previous value on previous iterations. See equations (7-67) to (7-70) in
the 2021-08 version of the H.265/HEVC specification.

Also read the number of long term reference pictures once rather than on every
loop iteration (subsection 7.3.2.2.1).

PiperOrigin-RevId: 551852999
(cherry picked from commit ddb0f86604)
2023-07-28 16:00:13 +01:00
bachinger
ab2505d1a9 Only use result.sendError where supported by legacy media library
`MediaLibraryServiceLegacyStub` handles various edge cases by calling
`result.sendError(null)` with the intention to send back an error to
the legacy browser [1].

`MediaBrowserServiceCompat` of the legacy media1 Compat library has an
inner base class `Result` that has a default implementation of
`onErrorSent` that throws an `UnsupportedOperationException` [2].
However, most anonymous inner classes for `Result` created in
`MediaBrowserServiceCompat` do not override `onErrorSent` [3].

Hence Media3 must not call `sendError` in these cases. Instead we call
`sendResult(null)` according to what the default implementation of
the callbacks in `MediaBrowserServiceCompat` do ([4] as an example).

Issue: androidx/media#78
Issue: androidx/media#334

[1] https://github.com/androidx/media/blob/release/libraries/session/src/main/java/androidx/media3/session/MediaLibraryServiceLegacyStub.java#L200
[2] https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:media/media/src/main/java/androidx/media/MediaBrowserServiceCompat.java;l=872
[3] https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:media/media/src/main/java/androidx/media/MediaBrowserServiceCompat.java;l=578-604?q=MediaBrowserServiceCompat&ss=androidx
[4] https://cs.android.com/androidx/platform/frameworks/support/+/androidx-main:media/media/src/main/java/androidx/media/MediaBrowserServiceCompat.java;l=1395

PiperOrigin-RevId: 551210137
(cherry picked from commit 17ee5277d5)
2023-07-26 16:14:39 +01:00
tonihei
ac175a9c37 Fix ms to us conversion bug in DecoderVideoRenderer
The current code multiplies the value by 1000 twice,
effectively converting to nanoseconds.

#minor-release

PiperOrigin-RevId: 551129750
(cherry picked from commit f766936140)
2023-07-26 09:43:03 +01:00
ibaker
db336d5c74 Add comment about out-of-order timestamps in AtomParsers.parseStbl
PiperOrigin-RevId: 550596173
(cherry picked from commit 449cf55523)
2023-07-24 18:05:04 +01:00
ibaker
eaf4a2b448 Tighten the demo app's handling of DrmInitData for downloads
This code is Widevine specific. `OfflineLicenseHelper.downloadLicense`
requires the passed `Format` to have a `DrmInitData.SchemeData` with
Widevine UUID and non-null `data` field. The demo app tries to check
this in advance (to avoid an exception later), but its checks are
looser than those made by `OfflineLicenseHelper`. This change tightens
the checks to match.

Issue: androidx/media#512
PiperOrigin-RevId: 549587506
(cherry picked from commit 1ccedf8414)
2023-07-20 12:03:36 +01:00
rohks
7352a1be0a Add field measured throughput (mtp)
Updated `ExoTrackSelection` to provide the most recent bitrate estimate, enabling the inclusion of measured throughput (mtp) as a CMCD-Request field in Common Media Client Data (CMCD) logging.

Additionally, made changes to the `checkArgument` methods in `CmcdLog` to prevent the use of default values in certain cases.

PiperOrigin-RevId: 549369529
(cherry picked from commit cdb174c91a)
2023-07-19 19:10:56 +01:00
rohks
6443614020 Add fields top bitrate(tb) and object duration(d)
Added these CMCD-Object fields to Common Media Client Data (CMCD) logging.

PiperOrigin-RevId: 548950296
(cherry picked from commit 1b2a2fcde0)
2023-07-18 11:24:34 +01:00
tonihei
3931c28697 Fix race condition in clipped sample streams
The streams return end-of-input if they read no samples, but know that
they are fully buffered to at least the clipped end time. This helps to
detect the end of stream even if there are no new buffers after the end
of the clip (e.g. for sparse metadata tracks).

The race condition occurs because the buffered position is evaluated
after reading the sample. So between reading "no sample" and checking
the buffered position, the source may have loaded arbitrary amounts
of data. This may lead to a situation where the source has not read
all samples, reads NOTHING_READ (because the queue is empty) and then
immediately returns end-of-stream (because the buffered position
jumped forward), causing all remaining samples in the stream to be
skipped. This can fixed by moving the buffered position check to
before reading the sample, so that it never exceeds the buffered
position at the time of reading "no sample".

#minor-release

PiperOrigin-RevId: 548646464
(cherry picked from commit c64d9fd6da)
2023-07-17 12:17:17 +01:00
tonihei
d4c66e549a Remove duplicated release note entry
Issue: androidx/media#515

#minor-release

PiperOrigin-RevId: 548111465
(cherry picked from commit 42998d6400)
2023-07-14 14:36:33 +01:00
michaelkatz
09835006f4 Prepend Ogg ID and Comment Header Pages to offloaded Opus stream
Add Ogg ID Header and Comment Header Pages to the Ogg encapsulated Opus for offload playback. This further matches the RFC 7845 spec and provides initialization data to decoders.

PiperOrigin-RevId: 548080222
(cherry picked from commit 847f6f24d3)
2023-07-14 11:41:56 +01:00
rohks
57d0c197cc Add tests for DefaultSsChunkSource
Added tests for Common Media Client Data (CMCD) logging in SmoothStreaming(SS)

PiperOrigin-RevId: 548072725
2023-07-14 11:03:05 +01:00
rohks
88e34017a3 Fix dependencies for UI module
PiperOrigin-RevId: 548063325
(cherry picked from commit 4d40f2e7dd)
2023-07-14 10:12:32 +01:00
rohks
77fb6525b1 Replace Dummy with Placeholder
`Dummy` is a non inclusive language.

PiperOrigin-RevId: 547815680
(cherry picked from commit ca10204b2d)
2023-07-13 16:55:49 +01:00
rohks
272c844abd Add fields streaming format(sf), stream type(st) and version(v)
Added these CMCD-Session fields to Common Media Client Data (CMCD) logging.

PiperOrigin-RevId: 547435498
(cherry picked from commit 0412a36564)
2023-07-12 10:25:50 +01:00
bachinger
7d35f18732 Add custom layout to the state of the MediaController
This change also marks the buttons of the custom layout as
enabled/disabled according to available commands in the controller.
Accordingly, `CommandButton.Builder.setEnabled(boolean)` is deprecated
because the value is overridden by the library.

Issue: androidx/media#38

PiperOrigin-RevId: 547272588
(cherry picked from commit ea21d27a69)
2023-07-11 20:59:01 +01:00
ibaker
996755c22b Expand @UnstableApi javadoc with how to request stable API additions
Issue: androidx/media#503

#minor-release

PiperOrigin-RevId: 547143885
(cherry picked from commit 2afcf99591)
2023-07-11 11:57:13 +01:00
ibaker
c1261aa266 Remove javadoc @link from non-javadoc comments
Also update type names to match the current names for these types.

PiperOrigin-RevId: 546884049
(cherry picked from commit eed0e42ff8)
2023-07-10 16:46:18 +01:00
ibaker
bae387651b Clarify that new ConditionVariable instances are closed
#minor-release

PiperOrigin-RevId: 546803592
(cherry picked from commit a8520bdee6)
2023-07-10 10:10:03 +01:00
andrewlewis
405e5a6e3e Fix incorrect class name for MediaPipe demo
Issue: androidx/media#500

#minor-release

PiperOrigin-RevId: 545942450
(cherry picked from commit bdbf3e29a1)
2023-07-06 12:07:21 +01:00
tonihei
9de5684d6e Change multidex dependency type to androidTestImplementation
This is only needed for instrumentation tests and should not
be included in regular builds.

Issue: androidx/media#499
PiperOrigin-RevId: 545913113
(cherry picked from commit 2250ffe6c8)
2023-07-06 09:45:11 +01:00
rohks
0c6cfea048 Replace BitArray.skipBytes() with BitArray.skipBits()
Based on the spec, ETSI TS 102 366 V1.4.1 Annex F, 6 bits should have skipped instead of 6 bytes.

This correction was pointed out in Issue: androidx/media#474.

PiperOrigin-RevId: 545658365
(cherry picked from commit 07d4e5986b)
2023-07-05 15:04:50 +01:00
ibaker
bcde7e03c2 Include timing info in some SCTE-35 toString implementations
This was helpful in investigating Issue: androidx/media#471

PiperOrigin-RevId: 545393217
(cherry picked from commit 3456382ae7)
2023-07-04 10:09:27 +00:00
michaelkatz
cb7b3862c4 Add nanoTime method to Clock to support overriding System.nanoTime()
PiperOrigin-RevId: 545237925
(cherry picked from commit de4575da28)
2023-07-03 16:29:13 +00:00
tonihei
26ee4c35f3 Replace exoplayer.dev@gmail.com with android-media-github@google.com
#minor-release

PiperOrigin-RevId: 545165879
(cherry picked from commit 5e19a33095)
2023-07-03 09:34:55 +00:00
ibaker
aa34db41f7 CEA-608: Only truncate to 32 visible characters
We introduced truncation to 32 chars in <unknown commit>
and included indent and offset in the calculation. I think this is
technically correct, but it causes problems with the content in
Issue: google/ExoPlayer#11019 and it doesn't seem a problem to only truncate actual
cue text (i.e. ignore offset and indent).

PiperOrigin-RevId: 544677965
(cherry picked from commit e8fdd83558)
2023-06-30 16:35:46 +00:00
tonihei
aefba8a565 Remove dead code in ProgressiveMediaSource.Builder
These fields cannot be set and the logic to handle them can be removed.

PiperOrigin-RevId: 544646460
(cherry picked from commit 2f113c8b82)
2023-06-30 14:05:39 +00:00
microkatz
3a66617b9e Merge pull request #487 from vishnuchilakala:allow_unsigned_int_for_adaptation_set_id
PiperOrigin-RevId: 544601945
(cherry picked from commit 9513f2c551)
2023-07-05 08:53:09 +00:00
tianyifeng
52706b2eb4 Ensure that ShuffleOrder has the same length as the current playlist
Add a fail-fast check in `ExoPlayerImpl` to ensure the equality of the lengths of `ShuffleOrder` and the current playlist. Also improve the documentation of `setShuffleOrder(ShuffleOrder)` with explicit instruction on this.

Issue: androidx/media#480

#minor-release

PiperOrigin-RevId: 544009359
(cherry picked from commit d895a46b28)
2023-06-28 10:41:49 +00:00
tonihei
f768fedaee Clarify MediaPeriod.readDiscontinuity Javadoc
It currently wrongly documents that it is only called before reading
streams (that has never been the case and all MediaPeriods already need
to handle calls after reading samples from the streams).

It was also a bit unclear what a discontinuity implies and the new
Javadoc calls out the main use case for discontinuties and the intended
meaning of returning a discontinuity.

#minor-release

PiperOrigin-RevId: 543989124
(cherry picked from commit b324b8aa72)
2023-06-28 08:55:56 +00:00
tianyifeng
7683ee254a Use different package names in DiagnosticInfo for media3 and exoplayer
Issue: androidx/media#476

#minor-release

PiperOrigin-RevId: 543460075
(cherry picked from commit 140c83ce7e)
2023-06-26 16:28:47 +00:00
tonihei
ec13c42800 Do not trim audio samples by changing their timestamp
MP4 edit lists sometimes ask to start playback between two samples.
If this happens, we currently change the timestamp of the first
sample to zero to trim it (e.g. to display the first frame for a
slightly shorter period of time). However, we can't do this to audio
samples are they have an inherent duration and trimming them this
way is not possible.

PiperOrigin-RevId: 543420218
(cherry picked from commit 2322462404)
2023-06-26 13:26:35 +00:00
Googler
bf4561c606 Removing @CallSuper from [add,remove]Listener of ForwardingListener.
PiperOrigin-RevId: 543373503
(cherry picked from commit 1fc49ce288)
2023-06-26 09:11:27 +00:00
jbibik
b220dfe73c Cleaner unified PlayerInfo update method in MediaControllerImplBase
`MediaControllerImplBase` has 2 methods for updating listeners about `PlayerInfo` changes - `updatePlayerInfo` (for masking the state) and `onPlayerInfoChanged` (when communicating with the session). There is a set number of listener callbacks related to `PlayerInfo` updates and both methods should go through the same control flow (whether we know that masking will ignore most of them or not).

A unified method `notifyPlayerInfoListenersWithReasons` encapsulates only the shared logic of 2 methods - listeners' callbacks. This ensures that both methods call them in the same order and none are missed out.

PiperOrigin-RevId: 542587879
(cherry picked from commit c2d8051662)
2023-06-22 16:49:36 +00:00
jbibik
e008e21b0f Fix missing equals sign in inline-comment parameter names
PiperOrigin-RevId: 542577676
(cherry picked from commit ea0f564c1e)
2023-06-22 16:09:57 +00:00
jbibik
b4c7e6cb86 Order MediaControllerImplBase listener callbacks as in ExoPlayerImpl
The callbacks for `PlayerInfo` changes are currently in both `MediaControllerImplBase.updatePlayerInfo` (masking) and `MediaControllerImplBase.onPlayerInfoChanged`. But the order was different between them both and `ExoPlayerImpl.updatePlaybackInfo` which they are trying to mimic.

#minor-release

PiperOrigin-RevId: 542519070
(cherry picked from commit b8ac5b4210)
2023-06-22 11:19:48 +00:00
ibaker
4f5d59b58f Switch to SVG assets hosted on developer.android.com for reference docs
#minor-release

PiperOrigin-RevId: 541970884
(cherry picked from commit 34f23451e6)
2023-06-20 17:23:37 +00:00
bachinger
f6a30f6645 Fix ArrayIndexOutOfBoundIndex when re-preparing after exception
When an app tried to re-prepare a live streeam with server side inserted
ad after a playback exception, the player tried to find the ad group by
its index in the ad playback state of the next timeline when creating
the first period.

If a source that supports server side ad, has removed the ad playback
state when the source has been removed, this causes a crash. For live
streams this is a reasonable thing to do given the exception could be
caused by an invalid ad playback state.

This change removes the ad metadata from the current period for live
streams and the timeline. In case the ad playback state is not reset
by the source, the first timeline refresh would ad the metadata again.

PiperOrigin-RevId: 541959628
(cherry picked from commit 4604f0cde6)
2023-06-20 16:55:22 +00:00
jbibik
4710b8f03a Fixed spelling across various PlayerInfo *ChangeReason fields
PiperOrigin-RevId: 541892788
(cherry picked from commit b9cc70d9e2)
2023-06-20 13:06:34 +00:00
tonihei
3561258949 Fix spurious sessions created for events after the playlist is cleared
Some events may arrive after the playlist is cleared (e.g. load
cancellation). In this case, the DefaultPlaybackSessionManager may
create a new session for the already removed item.

We already have checks in place that ignore events with old
windowSequenceNumbers, but these checks only work if the current
session is set (i.e. the playlist is non-empty). The fix is to add
the same check for empty playlists by keeping note of the last
removed window sequence number.

PiperOrigin-RevId: 541870812
(cherry picked from commit e0191ddded)
2023-06-20 12:27:23 +01:00
Marc Baechinger
7e0296fa7c Merge pull request #436 from jaeho-lee104:feature/improve_condition
PiperOrigin-RevId: 540875285
(cherry picked from commit af69d5822a)
2023-06-19 16:25:40 +01:00
rohks
f01ca9d9f1 Refactor method CmcdLog.createInstance to accept bufferedDurationUs
Instead of providing `playbackDurationUs` and `loadPositionUs` individually, which are used to calculate the buffer duration for CMCD logging, we can directly pass the pre-calculated `bufferedDurationUs` available in the `getNextChunk` method of the chunk source classes.

Issue: google/ExoPlayer#8699

#minor-release

PiperOrigin-RevId: 540630112
(cherry picked from commit be9b057dda)
2023-06-15 18:55:30 +01:00
rohks
0aede89586 Add CMCD logging when requesting initialization chunk for DASH and HLS
Additionally, two existing methods to `buildDataSpec` in `DashUtil` have been deprecated, while a new method has been added that allows the inclusion of `httpRequestHeaders`.

Issue: google/ExoPlayer#8699

#minor-release

PiperOrigin-RevId: 540594444
(cherry picked from commit 52878b2aca)
2023-06-15 16:48:55 +01:00
jbibik
2367e7a9e8 Default RepeatMode for conversion is NONE/OFF
Current behaviour causes an app to crash if it receives an unrecognized repeat mode send over the wire. In order to avoid the crash, a sensible default had to be chosen.

For `Player.RepeatMode`, it is `Player.REPEAT_MODE_OFF`, which is the same value we use as default when unbundling `PlayerInfo`.

For `PlaybackStateCompat.RepeatMode`, it is `PlaybackStateCompat.REPEAT_MODE_NONE`, which is what we use in the no-arg `LegacyPlayerInfo` constructor.

Issue: androidx/media#448

#minor-release

PiperOrigin-RevId: 540563792
(cherry picked from commit 501da109ce)
2023-06-15 14:31:59 +01:00
ibaker
3c9831fcb4 Remove = from parameter args in call to Constructor.newInstance
These comments reflect the parameter names of the constructor that
we're reflectively calling, but errorprone complains that they don't
match the parameter names of `Constructor.newInstance`.

PiperOrigin-RevId: 540348118
(cherry picked from commit 567890da9e)
2023-06-14 20:32:01 +01:00
siroberts
648e9943e5 Add missing checkNotNull to bitmapLoader.
#minor-release

PiperOrigin-RevId: 540309118
(cherry picked from commit cf21add916)
2023-06-14 18:22:13 +01:00
bachinger
3cfdd4fdc5 Implement equals/hashCode for CommandButton
#minor-release

PiperOrigin-RevId: 540274932
(cherry picked from commit 3d674fa2f3)
2023-06-14 16:13:48 +01:00
ibaker
983d9d57d7 Stop suppressing exceptions in MediaCodec.Callback during flush
Previously `AsynchronousMediaCodecCallback.mediaCodecException` was
cleared when flushing completed. This behaviour was changed in
aeff51c507
so now the exception is not cleared.

The result after that commit was that we would **only** suppress/ignore
the expression if a flush was currently pending, and we would throw it
both before and after the flush. This doesn't really make sense, so this
commit changes the behaviour to also throw the exception during the
flush.

This commit also corrects the assertion in
`flush_withPendingError_resetsError` and deflakes it so that it
consistently passes. The previous version of this test, although the
assertion was incorrect, would often pass because the
`dequeueInputBuffer` call would happen while the `flush` was still
pending, so the exception was suppressed.

#minor-release

PiperOrigin-RevId: 540237228
(cherry picked from commit 248d1d99ec)
2023-06-14 13:07:39 +01:00
tonihei
a9b78318ca Add missing @Override
PiperOrigin-RevId: 540220141
(cherry picked from commit c76680a65c)
2023-06-14 11:26:38 +01:00
jbibik
9dec0d308b Notify listeners of error changes when masking in MediaControllerImplBase
Currently, the implementation of `MediaControllerImplBase` differs from `ExoPlayerImpl`. The listeners of the former are notified of player error changes only in `onPlayerInfoChanged` and not `updatePlayerInfo` (masking method). Whereas `ExoPlayerImpl` has one unified method - `updatePlaybackInfo` - which sends the events to all the available listeners.

This change fixes the lack of 2 particular callbacks - `onPlayerErrorChanged` and `onPlayerError`, however, there might be more differences. Ideally, there should be a unified method for oldPlayerInfo/newPlayerInfo comparison-update-notify-listeners flow.

Issue: androidx/media#449

#minor-release

PiperOrigin-RevId: 539961618
(cherry picked from commit 4b5a457790)
2023-06-13 15:46:46 +01:00
ibaker
6615399861 Release ExoPlayer instances in ExoPlayerTest
This was recommended in https://github.com/robolectric/robolectric/issues/8187#issuecomment-1552060094

PiperOrigin-RevId: 539691757
(cherry picked from commit 959e974138)
2023-06-12 18:19:56 +01:00
bachinger
c3a96b2e08 Clean up HLS sample for cast demo
Issue: androidx/media#452
#minor-release
PiperOrigin-RevId: 539613535
(cherry picked from commit c2f78db87e)
2023-06-12 11:40:30 +00:00
claincly
0cf3ab31bf Factor out video decoding and fix two minor issues
1. Not treating 0 as valid buffer index
2. Not handling the case the last frame is a comparison frame

PiperOrigin-RevId: 539607482
(cherry picked from commit 4b1ac2f172)
2023-06-12 11:05:08 +00:00
ibaker
8ff61a1430 Stop setting -no-module-directories in ExoPlayer javadoc generation
This flag was introduced to fix links in javadoc search when generating
it with Java 11: <unknown commit>

The flag is no longer supported with Java 17 (which is required for
Gradle 8.0+), and seems to no longer be needed: I generated the javadoc
with it removed and the search links work OK.

PiperOrigin-RevId: 536738686
2023-08-11 15:52:04 +00:00
Tofunmi Adigun-Hameed
5328d6464a
Merge pull request #498 from androidx/release-1.1.0
Release 1.1.0
2023-07-05 08:56:12 +00:00
ibaker
00005cb09d Replace exoplayer.dev@gmail.com with android-media-github@google.com
#minor-release

PiperOrigin-RevId: 544596686
(cherry picked from commit f2c0eab468)
2023-06-30 10:57:51 +01:00
tofunmi
1d73c68465 Fix version bump for media3:1.1.0
#minor-release

PiperOrigin-RevId: 543376501
(cherry picked from commit 10cf06386d)
2023-06-26 10:04:55 +00:00
tofunmi
5fdeabbed8 Version bump for exoplayer 2.19.0 continued
#minor-release

PiperOrigin-RevId: 543369900
(cherry picked from commit 81dfe9d4b4)
2023-06-26 10:04:55 +00:00
tofunmi
ece76c4b61 Version bump to media3-1.1.0 and exoplayer 2.19.0
#minor-release

PiperOrigin-RevId: 542881427
(cherry picked from commit 114364897b)
2023-06-23 18:11:57 +00:00
tofunmi
69c6038e32 Merge release notes for media3 1.1.0 stable release
Merged during cherry-pick

PiperOrigin-RevId: 542539300
(cherry picked from commit 7e475146dd)
2023-06-23 12:20:40 +00:00
ibaker
b1522b4069 Add a section to CONTRIBUTING.md about push access to PR forks
#minor-release

PiperOrigin-RevId: 542294607
(cherry picked from commit 3f7599e9c3)
2023-06-23 12:13:32 +00:00
ibaker
bcdadbfdf5 Fix two more unresolved SVGs in Timeline.Window and Timeline.Period
These were missed in 10342507f7

#minor-release

PiperOrigin-RevId: 541860649
(cherry picked from commit 71f73229dd)
2023-06-23 12:13:31 +00:00
bachinger
1ae8ddd0fd Document how to use a custom receiver app with the cast demo
#minor-release
Issue: androidx/media#452
PiperOrigin-RevId: 539915277
(cherry picked from commit 5afe7552fe)
2023-06-23 12:13:31 +00:00
bachinger
e53796fc25 Fix bug where PlayerView distorts video when video size is unknown
PiperOrigin-RevId: 541640959
(cherry picked from commit 8d8c514d12)
2023-06-19 17:03:25 +01:00
tofunmi
3631e1c6f3 Version bump to media3:1.1.0-rc01
#minor-release

PiperOrigin-RevId: 539632164
(cherry picked from commit 4bceb64dee)
2023-06-12 13:23:56 +00:00
tofunmi
66b59ec3a7 Update RELEASENOTES.md for media3-1.1.0-rc01 release
Removed unreleased changes in this cherry-pick.

PiperOrigin-RevId: 539606230
(cherry picked from commit 49b893f6f3)
2023-06-12 16:15:31 +00:00
Googler
2ee0900659 Rollback of a66f08ba97
*** Original commit ***

Add a timer to end a video stream prematurely in ExtTexMgr

***

This has been submitting for more than 1.5hrs. "This presubmit is running slowly because you have been throttled by Build Queue due to using too much of your Product Area's quota."

adding NO_SQ as this is a pure rollback

PiperOrigin-RevId: 539135970
(cherry picked from commit 5c29abbbf4)
2023-06-12 09:15:55 +00:00
tianyifeng
23e92805a1 Throw exception when TimestampAdjuster initialization hits timeout
Add `HlsMediaSource.Factory.setTimestampAdjusterInitializationTimeoutMs(long)` to set the timeout for the loading thread to wait for the `TimestampAdjuster` to initialize. If the initialization doesn't complete before the timeout, a `PlaybackException` is thrown to avoid the playback endless stalling. The timeout is set to zero by default.

This can avoid HLS playback endlessly stalls when manifest has missing discontinuities. According to the HLS spec, all variants and renditions have discontinuities at the same points in time. If not, the one with discontinuities will have a new `TimestampAdjuster` not shared by the others. When the loading thread of that variant is waiting for the other threads to initialize the timestamp and hits the timeout, the playback will stall.

Issue: androidx/media#323

#minor-release

PiperOrigin-RevId: 539108886
(cherry picked from commit db3e662bdc)
2023-06-12 09:15:54 +00:00
bachinger
21fb8c9942 Fix splitting ad playback state for partial ad group when joining
This change addresses the case when the user joins the live stream
on an ad period but the metadata for the ad period is not emitted.
This results in inserting a partial ad group.

In this case the ad group duration is longer than the partial ad
group. If now the partial ad group ends at the period before the
last period of the window (unknown duration), the splitting algorithm
didn't recognize that the ad group already ended and made the last
period wrongly an ad period.

This change handles this edge case by counting the mapped ads in
the partial ad group to detect this situation and stops splitting.

#minor-release

PiperOrigin-RevId: 539102785
(cherry picked from commit cd604e7ead)
2023-06-12 09:15:54 +00:00
claincly
4dae1a1679 Add missing empty lines
PiperOrigin-RevId: 539100987
(cherry picked from commit edf30433b6)
2023-06-12 09:15:54 +00:00
bachinger
56c62d1ab1 Make current period a placeholder when a live stream is reset
In case the player is reset while a live stream is playing, the current
period needs to be a placeholder. This makes sure that the default start
position is used when the first live timeline arrives after re-preparing.

#minor-release

PiperOrigin-RevId: 539044360
(cherry picked from commit 71153a43a8)
2023-06-12 09:15:54 +00:00
claincly
50f4caacd6 Add a timer to end a video stream prematurely in ExtTexMgr
PiperOrigin-RevId: 539036285
(cherry picked from commit a66f08ba97)
2023-06-12 09:15:54 +00:00
bachinger
1c8c563263 Do not reset period uid when DashMediaSource is released
When the source is prepared again after stop, the period uid
is calculated by subtracting the `firstPeriodId` from the
period uid that is passed in to `createPeriod`. When this
happens after stop, the uid from the old period uid that
is still stored and has the value of the last played uid.

Hence the `firstPeriodId` must not be reset when released.

Issue: google/ExoPlayer#10838
PiperOrigin-RevId: 539028570
(cherry picked from commit 319854d624)
2023-06-12 09:15:54 +00:00
rohks
f1529d63a1 Implement logging support for Common Media Client Data (CMCD)
Add support for including Common Media Client Data (CMCD) in the outgoing requests of adaptive streaming formats DASH, HLS, and SmoothStreaming.

API structure and API methods:
   *   CMCD logging is disabled by default, use `MediaSource.Factory.setCmcdConfigurationFactory(CmcdConfiguration.Factory cmcdConfigurationFactory)` to enable it.
   *   All keys are enabled by default, override `CmcdConfiguration.RequestConfig.isKeyAllowed(String key)` to filter out which keys are logged.
   *  Override `CmcdConfiguration.RequestConfig.getCustomData()` to enable custom key logging.

NOTE: Only the following fields have been implemented: `br`, `bl`, `cid`, `rtp`, and `sid`.

Issue: google/ExoPlayer#8699

#minor-release

PiperOrigin-RevId: 539021056
(cherry picked from commit b55ddf12b4)
2023-06-12 09:15:54 +00:00
bachinger
ec3446fe8b Enable re-preparing the ImaSSAIMediaSource
#minor-release

PiperOrigin-RevId: 538927855
(cherry picked from commit a67ce066df)
2023-06-12 09:15:53 +00:00
jbibik
f18ec2e0c5 Make StreamKey Bundleable and remove deprecated trackIndex
#minor-release

PiperOrigin-RevId: 538809105
(cherry picked from commit 28b8fb706a)
2023-06-12 09:15:53 +00:00
tofunmi
39b98fe5ad Add 'Keep every nth frame' frame dropping strategy.
#minor-release

PiperOrigin-RevId: 538804347
(cherry picked from commit 276f2f1fe6)
2023-06-12 09:15:53 +00:00
tianyifeng
78f23c0c9b Add seekPrev and seekNext buttons on the default compact notification
This change is for Android 12 and below, where the buttons are derived from the actions added with the notification. From Android 13 (https://developer.android.com/about/versions/13/behavior-changes-13#playback-controls), the system derives media controls from `PlaybackState` actions.

When adding the actions onto the notification, the logic will iterate all the command buttons. The `COMMAND_KEY_CONPACT_VIEW_INDEX` extra will be checked for each button. If that extra is set for the three buttons on the compact view, then the customized buttons and their order will be used. Otherwise, the compact view will be "seekPrev" (if any), "play/pause" (if any), "seekNext" (if any) buttons (in such order).

Issue: androidx/media#410
PiperOrigin-RevId: 538797874
(cherry picked from commit 2e2f19351f)
2023-06-12 09:15:53 +00:00
claincly
f1b942150c Fix calling extra registerInputStream and not handling EOS
PiperOrigin-RevId: 538796466
(cherry picked from commit a7cff4e0e3)
2023-06-12 09:15:53 +00:00
sheenachhabra
ebfb9cf402 Add support for passing custom metadata via transformer
Changes included:
1. Enable MP4 extractor to read all types of metadata.
2. Allow passing String and Float metadata via Transformer.

Reference to QuickTime spec: https://developer.apple.com/library/archive/documentation/QuickTime/QTFF/Metadata/Metadata.html#//apple_ref/doc/uid/TP40000939-CH1-SW21

PiperOrigin-RevId: 538783982
(cherry picked from commit 53c174f047)
2023-06-12 09:15:53 +00:00
sheenachhabra
f2c0d7e13c Enable nullness checker for Muxer module androidTest library
PiperOrigin-RevId: 538742957
(cherry picked from commit 2753fb0e3e)
2023-06-12 09:15:53 +00:00
huangdarwin
6dcfe44b89 Test: Add no-op effect test for GL tone mapping.
To ensure no regressions for the potentially confusing pipeline of:
* HDR electrical -> SDR linear EOTF+OOTF, and
* SDR linear -> SDR electrical OETF

PiperOrigin-RevId: 538741079
(cherry picked from commit 0c924fcb40)
2023-06-12 09:15:53 +00:00
tofunmi
5afc256eba DefaultVideoFrameProcessorMultipleTextureOutputPixelTest fixes & cleanup
PiperOrigin-RevId: 538495675
(cherry picked from commit ce203ccfed)
2023-06-12 09:15:52 +00:00
huangdarwin
797ee99baf Test: Move duplicated GL tone mapping logic into helper methods.
PiperOrigin-RevId: 538491957
(cherry picked from commit 9dad207603)
2023-06-12 09:15:52 +00:00
kimvde
71c2d7ba92 Remove unnecessary thread in AssetLoader tests
Also queue textures from a different thread in TextureAssetLoader, to
have a behaviour closer to reality.

PiperOrigin-RevId: 538473089
(cherry picked from commit 3ba8f6dd8f)
2023-06-12 09:15:52 +00:00
jbibik
ade2990503 Fix 1 ErrorProneStyle finding
PiperOrigin-RevId: 538469993
(cherry picked from commit e7f7e86f3e)
2023-06-12 09:15:52 +00:00
tofunmi
f7245a5ff2 Tests for disabled color transfers
#minor-release

PiperOrigin-RevId: 538466615
(cherry picked from commit c3b9328d74)
2023-06-12 09:15:52 +00:00
huangdarwin
295eb06145 Effect: Add multiple texture output test.
Confirms that multiple textures can be output, and that timestamps and pixels
are as expected.

PiperOrigin-RevId: 538459296
(cherry picked from commit adf53b4d50)
2023-06-12 09:15:52 +00:00
andrewlewis
752a49c8d0 Log additional information on test runner timeout
PiperOrigin-RevId: 538437142
(cherry picked from commit 828a05e0fa)
2023-06-12 09:15:52 +00:00
Tofunmi Adigun-Hameed
9b136d3368 Merge pull request #371 from tatsuyafujisaki:simplify-loadJSONFromAsset
PiperOrigin-RevId: 538423581
(cherry picked from commit 9aa519f1ee)
2023-06-12 09:15:52 +00:00
Tofunmi Adigun-Hameed
9db3769fac Merge pull request #439 from kaidokert:pick_workaround
PiperOrigin-RevId: 538209925
(cherry picked from commit 886717e3fe)
2023-06-12 09:15:51 +00:00
sheenachhabra
f1d285ed6f Add support for passing creation time via InAppMuxer
PiperOrigin-RevId: 538175466
(cherry picked from commit 7e14811e25)
2023-06-12 09:15:51 +00:00
Tofunmi Adigun-Hameed
d4e0a8d13d Merge pull request #425 from vishnuchilakala:set_min_live_position
PiperOrigin-RevId: 538173603
(cherry picked from commit 9ca6e5d90d)
2023-06-12 09:15:51 +00:00
tianyifeng
1c1ca3edaf Defer outputting the metadata sample when TimestampAdjuster isn't initialized
The sample timestamp carried by the emsg box can have a significant delta when comparing to the earliest presentation timestamp of the segment. Using this timestamp to intialize the timestamp offset in TimestampAdjuster will cause the media sample to have a wrong adjusted timestamp. So we should defer adjusting the metadata sample timestamp until the TimestampAdjuster is initialized with a real media sample.

PiperOrigin-RevId: 538172841
(cherry picked from commit f4bf376e89)
2023-06-12 09:15:51 +00:00
claincly
70a25b01bf Add utility to create ScheduledExecutorService
PiperOrigin-RevId: 538129792
(cherry picked from commit 08e7158be5)
2023-06-12 09:15:51 +00:00
jbibik
a5949d8806 Allow playback of MediaItems with LocalConfiguration
When initiated by MediaController, it should be possible for `MediaSession` to pass `MediaItems` to the `Player` if they have `LocalConfiguration`. In such case, it is not required to override `MediaSession.Callback.onAddMediaItems`, because the new current default implementation will handle it.

However, in other cases, MediaItem.toBundle() will continue to strip the LocalConfiguration information.

Issue: androidx/media#282

RELEASENOTES.md modified in cherrypick

PiperOrigin-RevId: 537993460
(cherry picked from commit d9764c18ad)
2023-06-12 09:15:01 +00:00
Googler
4a90efaf10 Override ParserException#getMessage
PiperOrigin-RevId: 537908595
(cherry picked from commit 83e9080b71)
2023-06-12 09:13:53 +00:00
ibaker
5792b9299e Add a 'more version details' box to the GitHub bug issue template
#minor-release

PiperOrigin-RevId: 537893360
(cherry picked from commit d328e62f69)
2023-06-12 09:13:53 +00:00
claincly
39ec69fe66 Fix recreation of DefaultShaderProgram in FinalWrapper
`outputSurfaceInfoChanged` is not reset when `defaultShaderProgram` is null.
That is, on the first time `ensureConfigured()` is called with output size
changed, `outputSurfaceInfoChanged` is not set to false after creating the
`defaultShaderProgram`, and `defaultShaderProgram` will be created again on the
second time `ensureConfigured()` is called.

PiperOrigin-RevId: 537870404
(cherry picked from commit 133943a635)
2023-06-12 09:13:53 +00:00
tofunmi
a8dbea74ce Revert AndroidTestUtil.canDecode to use EncoderUtil.findCodecForFormat
208eefc0fd introduced using `DefaultDecoderFactory.getDecoderInfo(format) != null` caused certain tests not to be skipped when they were expected to be, creating more mh failures.

PiperOrigin-RevId: 537820370
(cherry picked from commit 2af5752785)
2023-06-12 09:13:53 +00:00
sheenachhabra
83957eb67f Ignore ByteBuffer position when writing samples in Mp4Muxer
PiperOrigin-RevId: 537814319
(cherry picked from commit 997f2be5e5)
2023-06-12 09:13:53 +00:00
andrewlewis
3ef7bc291c Tidy color info checking tests
ExoPlayer extractors (backing `MetadataRetriever`) now parse the color format
from the bitstream so using `MetadataRetriever` should be an equivalent but
more lightweight way to verify the color info.

Also remove try/catch blocks in test code calling into these methods, and add
skipping based on decoder capabilities in the cases where it was missing.

PiperOrigin-RevId: 537789483
(cherry picked from commit 74478f2478)
2023-06-12 09:13:53 +00:00
huangdarwin
4bb00d5e2e Test: Add special effects for forcing transcode.
Previously, we would apply a general effect to signal wanting to transcode.

PiperOrigin-RevId: 537034455
(cherry picked from commit c52130a212)
2023-06-12 09:13:52 +00:00
Tofunmi Adigun-Hameed
568327ff2e Merge pull request #420 from changxiangzhong:fix/cp-missing
PiperOrigin-RevId: 537014587
(cherry picked from commit a739f6062e)
2023-06-12 09:13:52 +00:00
claincly
1238a5f328 Use the designated UNSET value for aspect ratio
PiperOrigin-RevId: 536770380
(cherry picked from commit f6dbe99c79)
2023-06-12 09:13:52 +00:00
kimvde
55a2e7ff9e Add TransformationRequest toString method
PiperOrigin-RevId: 536727079
(cherry picked from commit 108000834b)
2023-06-12 09:13:52 +00:00
ibaker
89c5e16d2f Soften MediaCodecRenderer's assumptions about using framework DRM
#minor-release

PiperOrigin-RevId: 536724725
(cherry picked from commit 0ddc024c69)
2023-06-12 09:13:52 +00:00
claincly
ba84de02f6 Fix codec's MIME type is not used
In some cases the codec selected for decoding has a different MIME type than
the media. In thoses cases Transformer continued to use the media's MIME type
and that caused codec configuration failures.

Removed `EncoderUtil.findCodecForFormat()` as we stopped using the method it
uses for finding a codec. Plus, the method is only used in the test.

See also `MediaCodecUtil.getALternativeCodecMimeType()`.

PiperOrigin-RevId: 536683663
(cherry picked from commit 208eefc0fd)
2023-06-12 09:13:52 +00:00
huangdarwin
db1b5f148d Exoplayer: Suppress check to allow video to run ahead of Audio.
Otherwise, texture output errors out if video decoding decodes faster than audio,
hitting the end of the file, while audio is still in the middle of the file.

PiperOrigin-RevId: 536679568
(cherry picked from commit e2821f10f5)
2023-06-12 09:13:52 +00:00
huangdarwin
2713f81fd0 Effect: Remove extra wait on taskExecutor release()
This future.get() duplicates the wait done in
singleThreadExecutorService.awaitTermination(). If awaitTermination times out, this future.get() would also result in unnecessary blocking.

PiperOrigin-RevId: 536442153
(cherry picked from commit 1c172e0bed)
2023-06-12 09:13:51 +00:00
sheenachhabra
6e74b1770c Add only supported MdtaMetadataEntry
PiperOrigin-RevId: 536351494
(cherry picked from commit 99d2cf6713)
2023-06-12 09:13:51 +00:00
jbibik
629ab59b5f Remove previously deprecated MediaItem.PlaybackProperties in favour of LocalConfiguration.
Deprecated field `MediaItem.playbackProperties` remains for backwards compatibility, but its type is changed from `MediaItem.PlaybackProperties` to `MediaItem.LocalConfiguration`. The private `MediaItem` constructor will now also take in a `LocalConfiguration` argument instead.

PiperOrigin-RevId: 535648420
(cherry picked from commit 25bf0c6738)
2023-06-12 09:13:51 +00:00
tofunmi
75ad65f14c Image transcoding: Add support for bmp image format.
With this change we will now support loading bitmaps from all the formats documented [here](https://developer.android.com/guide/topics/media/media-formats#image-formats) except for gifs (because they are animated). Java doc is added to express this.

PiperOrigin-RevId: 535610152
(cherry picked from commit 94d29f35fc)
2023-06-12 09:13:51 +00:00
kimvde
2ed784f156 Transmux video if rotation is only effect applied
PiperOrigin-RevId: 535554628
(cherry picked from commit f4d1a6c453)
2023-06-12 09:13:51 +00:00
Tofunmi Adigun-Hameed
b79ee36661 Removing unreleased Audio Offload changes from git 2023-06-07 09:05:51 +00:00
tofunmi
723424a852 Version bump to media3:1.1.0-beta01
#minor-release

PiperOrigin-RevId: 536464412
(cherry picked from commit 49ea280bb8)
2023-05-30 19:42:29 +00:00
Tofunmi Adigun-Hameed
6e0156766b Merge branch 'release' into release-1.1.0-beta01 2023-05-30 16:12:11 +00:00
huangdarwin
4d4f61b772 Rollback of 438ae0ed6a
*** Original commit ***

ExoPlayer: Add setVideoFrameProcessorFactory().

This allows apps to use a custom VideoFrameProcessor implementation for video
playback. This may be useful, for example, when outputting to a texture.
***

PiperOrigin-RevId: 536391597
(cherry picked from commit 06908e1a86)
2023-05-30 14:57:40 +00:00
tofunmi
a442919246 Update release notes for 1.1.0-beta01 release
PiperOrigin-RevId: 536383299
(cherry picked from commit ebbbcf9ac9)
Merged in cherry-pick
2023-05-30 14:57:05 +00:00
Tofunmi Adigun-Hameed
bd29a3553d Merge pull request #335 from cedricxperi:dts-direct-passthrough-support
Resolved Merge Conflict during cherrypicking

PiperOrigin-RevId: 535255453
(cherry picked from commit c3dd88d715)
2023-05-26 13:58:36 +00:00
ibaker
75a9de6c21 Add sections for each minor release to RELEASENOTES.md
#minor-release

PiperOrigin-RevId: 535240816
(cherry picked from commit 41b19df5fa)
2023-05-26 13:34:52 +00:00
Googler
d7e5c1bb0b Show the file path of the exported output video.
PiperOrigin-RevId: 535233266
(cherry picked from commit 86515f8683)
2023-05-26 13:34:52 +00:00
tofunmi
7047f44564 Move OnInputFrameProcessedListener into it's own file
PiperOrigin-RevId: 535183521
(cherry picked from commit a19e07c4d2)
2023-05-26 13:34:52 +00:00
tonihei
f2edc0bed1 Implement Player.replaceMediaItem(s)
This change moves the default logic into the actual Player
implementations, but does not introduce any behavior changes compared
to addMediaItems+removeMediaItems except to make the updates "atomic"
in ExoPlayerImpl, SimpleBasePlayer and MediaController. It also
provides backwards compatbility for cases where Players don't support
the operation.

Issue: google/ExoPlayer#8046

#minor-release

PiperOrigin-RevId: 534945089
(cherry picked from commit 2c07468908)
2023-05-26 13:25:10 +00:00
tonihei
638dee44a9 Merge pull request #386 from yschimke:icon
PiperOrigin-RevId: 534927167
(cherry picked from commit cf0334d793)
2023-05-26 13:25:10 +00:00
michaelkatz
df4803028f Use base Uri from the RTSP DESCRIBE response header for relative paths
Issue: google/ExoPlayer#11160

#minor-release

PiperOrigin-RevId: 534896789
(cherry picked from commit 85f83b1208)
2023-05-26 13:25:10 +00:00
huangdarwin
74a032d462 Effects: Have VFP texture output disable surface output.
Also, document that texture output disables manual frame release.

In the past, texture output would lead to surface output methods throwing. Now,
they're simply no-ops instead.

PiperOrigin-RevId: 534894168
(cherry picked from commit abf649cdfa)
2023-05-26 13:25:10 +00:00
andrewlewis
f59dac9d7d Clarify color transfers for internal textures
PiperOrigin-RevId: 534869452
(cherry picked from commit b988cce62c)
2023-05-26 13:25:10 +00:00
bachinger
5c498c3eca Keep aspect ratio of PlayerView when IDLE
When the video renderer is disabled, the video size is set to 0/0
and sent to listeners. The `PlayerView` potentially still has the last frame
displayed when the player is stopped or an error occurs. This may have the
effect that the frame is displayed distorted.

Not changing the aspect ratio when the video size arrives when the player is IDLE
avoids the problem. In the case when playback starts again and the renderes is
enabled, another video size is sent to the listener.

#minor-release

PiperOrigin-RevId: 534860889
(cherry picked from commit 6469fffd8f)
2023-05-26 13:25:10 +00:00
andrewlewis
a7501f4aaf Tidy TextureManager javadoc
Fix `@param`s and clarify wording.

PiperOrigin-RevId: 534857204
(cherry picked from commit 749c64c74f)
2023-05-26 13:25:10 +00:00
jbibik
171f48fccc Make MediaItem.AdsConfiguration bundleable
PiperOrigin-RevId: 534848363
(cherry picked from commit 3cc4e44fe3)
2023-05-26 13:25:09 +00:00
tonihei
8573a4ba85 Add clarifiying note to Player.replaceMediaItem
This helps to highlight that the replaced range doesn't need to have
the same size as before.

#minor-release

PiperOrigin-RevId: 534834917
(cherry picked from commit acb567d5a7)
2023-05-26 13:25:09 +00:00
sheenachhabra
c38f094b32 Add support for adding capture FPS via transformer
PiperOrigin-RevId: 534814892
(cherry picked from commit a944ffecb9)
2023-05-26 13:25:09 +00:00
sheenachhabra
fb952145fc Add support for adding XMP data via transformer
PiperOrigin-RevId: 534801202
(cherry picked from commit 71facd825e)
2023-05-26 13:25:09 +00:00
ibaker
6b19e5781c Clean up release notes (again)
I undid my last clean-up (b762ca993e) in 5a5c3ce3bd

#minor-release

PiperOrigin-RevId: 534763770
(cherry picked from commit 41492b6e29)
2023-05-26 13:25:09 +00:00
ibaker
c2d6c27d0b Remove deprecated onSeekProcessed
This change removes it from `Player.Listener` and `AnalyticsListener`,
use `onPositionDiscontinuity` with `DISCONTINUITY_REASON_SEEK` instead.

#minor-release

PiperOrigin-RevId: 534757426
(cherry picked from commit 5c713feb60)
2023-05-26 13:25:09 +00:00
huangdarwin
9e19c60907 Codec: Reduce limit for max decoder pending output frames.
Tentative/experimental value to reduce codec timeouts. We will reconsider using a larger limit after seeing whether this really does reduce error rate.

PiperOrigin-RevId: 534491615
(cherry picked from commit 66554b9b68)
2023-05-26 13:25:09 +00:00
claincly
38a341a458 Add logging for ExtTexMgr
- Number of frames from SurfaceTexture that is sent downstream
- Times ExtTexMgr signaled end of current input stream

PiperOrigin-RevId: 534487842
(cherry picked from commit d584a772e3)
2023-05-26 13:25:09 +00:00
sheenachhabra
d3f88a434b Add support for updating Metadata entries via InAppMuxer
Mp4Muxer already supports writing Mp4LocationData so added that
as supported Metadata entry.
Support for more Metadata entries will be added in upcoming CLs.

PiperOrigin-RevId: 534473866
(cherry picked from commit 7c477589e5)
2023-05-26 13:25:09 +00:00
ibaker
4d17a05b2b Ensure rootProject.name is only set from settings.gradle
I moved this assignment in 0888dfbd05
in order to provide a single source-of-truth for `publish.gradle`,
but as pointed out in Issue: androidx/media#416 this breaks apps that are depending
on our project locally using the instructions we publish. Instead we can
remove the `rootProject.name` check from `publish.gradle`, and check an
explicit boolean value instead to indicate if the root project is 'ours'
(with this boolean only set from `settings.gradle`, so it doesn't get
picked up by apps depending on us locally).

#minor-release

PiperOrigin-RevId: 534459085
(cherry picked from commit 9a79571284)
2023-05-26 13:25:09 +00:00
tonihei
88cdbe52d7 Improve MediaSource threading constraints documentation
And fix violation of this in AdsMediaSource.

#minor-release

PiperOrigin-RevId: 534441648
(cherry picked from commit c73955a4cd)
2023-05-26 13:25:09 +00:00
ibaker
6621287c86 Remove four deprecated AnalyticsListener decoder methods
Use the audio or video specific variants instead.

#minor-release

PiperOrigin-RevId: 534436644
(cherry picked from commit 5a5c3ce3bd)
2023-05-26 13:25:09 +00:00
ibaker
48c58cefc2 Remove deprecated OfflineLicenseHelper constructor
Use the non-deprecated constructor instead.

#minor-release

PiperOrigin-RevId: 534426655
(cherry picked from commit 48348df58a)
2023-05-26 13:25:09 +00:00
ibaker
c650a10256 Remove deprecated Cue constructors, use Cue.Builder instead
#minor-release

PiperOrigin-RevId: 534412494
(cherry picked from commit e6f5f58e47)
2023-05-26 13:25:09 +00:00
jbibik
16b09f6d63 Make MediaItem.SubtitleConfiguration bundleable
PiperOrigin-RevId: 534390168
(cherry picked from commit b7edc9e416)
2023-05-26 13:25:09 +00:00
ibaker
22ac971bbc Remove deprecated DownloadManager constructor
Use the constructor that takes an `Executor` instead.

#minor-release

PiperOrigin-RevId: 534370613
(cherry picked from commit ff0f1c4e9c)
2023-05-26 13:25:08 +00:00
bachinger
f8ef386065 Use artwork display mode in demo app
- Use artwork display mode `fill` to improve visual apperance
- Some minor cleanup

#minor-release

PiperOrigin-RevId: 534366246
(cherry picked from commit 230921e4ab)
2023-05-26 13:25:08 +00:00
ibaker
e8bcbd5249 Clean up release notes
This change merges some duplicate sections, moves some items to more
appropriate sections and removes unnecessary items (deprecations are
self-documenting, so don't need to be included here).

#minor-release

PiperOrigin-RevId: 534363065
(cherry picked from commit b762ca993e)
2023-05-26 13:25:08 +00:00
ibaker
ead2c0682a Remove deprecated DefaultLoadControl.Builder.createDefaultLoadControl()
Use `build()` instead.

#minor-release

PiperOrigin-RevId: 534348979
(cherry picked from commit 594e9ac50d)
2023-05-26 13:25:08 +00:00
ibaker
12ca8903ce Remove deprecated ExoPlayer.setHandleWakeLock(boolean)
Use `setWakeMode(int)` instead.

#minor-release

PiperOrigin-RevId: 534344010
(cherry picked from commit cad1ac2eb5)
2023-05-26 13:25:08 +00:00
bachinger
f372440882 Add artwork display mode to PlayerView
This change deprecates `PlayerView.setUseArtwork(boolean)` and
introduces `setArtworkDisplayMode(mode)` and
`artworkDisplayMode="off|fit|fill"` instead.

- off: no artwork is displayed (like deprecated useArtwork=false)
- fit: letterbox like media (like deprecated useArtwork=true)
- fill: scales the artwork to fill the entire width/weight of the player view

#minor-release

PiperOrigin-RevId: 534167226
(cherry picked from commit 46fb454b3f)
2023-05-26 13:25:08 +00:00
bachinger
5f3cb861fc Minor session demo app improvements
Basically this change removes a bug that makes video playback stuck when
a video is playing and the user taps the UMO notification to get to
the player activity.

- Use `launchMode="singleTop"` for `PlayerActivity`
- Change session activity to a back stacked activity on service `onDestroy`.

Using a back stacked activity `onDestroy()` will be useful once this demo
app implements playback resumption.

The rest of the changes are aesthetic:

- clean up and optimize screen space usage in UI of `PlayerActivity`
- changed some colors, paddings and spacings
- adds a default artwork for the `PlayerView`

PiperOrigin-RevId: 534152052
(cherry picked from commit 96a4ae7e40)
2023-05-26 13:25:08 +00:00
christosts
fbd0bae265 Fix seeking bug in opus
Fix a bug when seeking in an opus container. The calculations inside
DefaultOggSeeker may overflow a long primitive.

Issue: androidx/media#391

#minor-release

PiperOrigin-RevId: 534128513
(cherry picked from commit b9a4e614f7)
2023-05-26 13:25:08 +00:00
sheenachhabra
cebdb17134 Write metadata to Mp4Muxer in the release() method
Earlier metadata was written multiple times as it came.
With new changes, all the distinct metadata entries will
get collected and will be written at once in the end.

PiperOrigin-RevId: 534088401
(cherry picked from commit a9e3f5def4)
2023-05-26 13:25:08 +00:00
huangdarwin
9d04b11d49 Effect: Add GlTextureInfo release() and accessor methods.
This allows us to disallow access after release.

PiperOrigin-RevId: 534046475
(cherry picked from commit a6897aedaa)
2023-05-26 13:25:08 +00:00
huangdarwin
3d231cce05 ExoPlayer: Add setVideoFrameProcessorFactory().
This allows apps to use a custom VideoFrameProcessor implementation for video
playback. This may be useful, for example, when outputting to a texture.

PiperOrigin-RevId: 534044831
(cherry picked from commit 438ae0ed6a)
2023-05-26 13:25:08 +00:00
tonihei
5cdbd59756 Improve handling of adding items to empty playlist in MediaController
This is a follow-up to 99dac0be0f where we made the same change in
ExoPlayerImpl and SimpleBasePlayer, but for consistency it makes
sense to also update the masking code in MediaControllerImplBase to
assume the same logic.

Note: MediaControllerImplLegacy already handles this case via
setMediaItems and doesn't need to be updated further.

#minor-release

PiperOrigin-RevId: 534038759
(cherry picked from commit 33af245465)
2023-05-26 13:25:08 +00:00
tonihei
1b007deca0 Untangle PlayerInfo/PlaybackInfo updates
The methods in ExoPlayerImpl and MediaControllerImplBase that determine
the new PlayerInfo/PlaybackInfo currently have a hard-to-reason-about
setup where the method generating the new info accesses other methods
that rely on the existing class field instead of working with the
passed in PlayerInfo/PlaybackInfo. This prevents reuse of the util
methods (e.g. for replaceMediaItems) because they access potentially
stale state.

This change untangles these methods a bit by making the util methods
either static or at least ensure that they don't rely on existing
class fields of PlayerInfo/PlaybackInfo. Overall, the change is a
complete no-op.

#minor-release

PiperOrigin-RevId: 534036633
(cherry picked from commit 1fa790348e)
2023-05-26 13:25:08 +00:00
tonihei
44ffea2477 Keep pending initial position when setting empty playlist
MediaControllerImplBase currently drops the pending initial seek
position when a user sets an empty playlist.

When seeking in empty playlists and setting new empty playlists,
the class also drops the the period index (and wrongly assigns zero
instead of the windowIndex).

#minor-release

PiperOrigin-RevId: 534035046
(cherry picked from commit caf1c77af1)
2023-05-26 13:25:08 +00:00
tonihei
3704c7fc8d Extend main Player Javadoc
The main interface documentation hasn't been updated substantially
since 2017 and is missing notes for many of its current features and
requirements.

Also change the recommendation for implementors from BasePlayer to
SimpleBasePlayer to ensure new classes are more likely to cover all
of the interface requirements.

#minor-release

PiperOrigin-RevId: 534027117
(cherry picked from commit 6de6bd9c4f)
2023-05-26 13:25:08 +00:00
andrewlewis
8df047108c Remove unnecessary volatile
This field is always accessed with the mutex held.

PiperOrigin-RevId: 534027096
(cherry picked from commit ea0b94c0eb)
2023-05-26 13:25:07 +00:00
andrewlewis
8a9503c01a Use Ascii for conversion to lower case
PiperOrigin-RevId: 534016337
(cherry picked from commit b0418f1a2a)
2023-05-26 13:25:07 +00:00
andrewlewis
aa4f84d89a Run clang-format on GLSL
PiperOrigin-RevId: 534015933
(cherry picked from commit 2cbc2c6176)
2023-05-26 13:25:07 +00:00
ibaker
5c42c25ad3 Remove deprecated zero-arg DefaultTrackSelector constructor
Use `DefaultTrackSelector(Context)` instead.

#minor-release

PiperOrigin-RevId: 533985937
(cherry picked from commit 2b409da881)
2023-05-26 13:25:07 +00:00
jbibik
992c9aa470 Make DrmConfiguration Bundleable
PiperOrigin-RevId: 533721679
(cherry picked from commit 5008417c8c)
2023-05-26 13:25:07 +00:00
sheenachhabra
d40f37158a Move MdtaMetadataEntry class into container module
This class is to be shared between extractor, transformer
and muxer module.

PiperOrigin-RevId: 533490888
(cherry picked from commit b382a7c2da)
2023-05-26 13:25:07 +00:00
bachinger
b066a0912e Set video size to 0/0 when video render is disabled
In terms of MCVR with a `VideoRendererEventListener`, the video size is set to
0/0 right after `onVideoDisabled()` is called and is set to the actual size as
soon as the video size is known after 'onVideoEnabled()`.

For ExoPlayer and in terms of the `Player` interface, `Player.getVideoSize()`
returns a video size of 0/0 when `Player.getCurrentTracks()` does not support
`C.TRACK_TYPE_VIDEO`. This is ensured by the masking behavior of
`ExoPlayerImpl` that sets an empty track selection result when the playing
period changes due to a seek or timeline removal.

When transitioning playback from a video media item to the next, or when
seeking within the same video media item, the renderer is not disabled.

#minor-release

PiperOrigin-RevId: 533479600
(cherry picked from commit 2a6f893fba)
2023-05-26 13:25:07 +00:00
bachinger
ef6772b946 Return full media item when SystemUI calls service on device boot time
#minor-release

PiperOrigin-RevId: 533465595
(cherry picked from commit 3fa666c687)
2023-05-25 10:44:07 +00:00
ibaker
41de418c3d Remove deprecated ExoPlayer.retry(), use prepare() instead.
#minor-release

PiperOrigin-RevId: 533463348
(cherry picked from commit 50112c685b)
2023-05-25 10:44:07 +00:00
ibaker
7934eaf882 Add logging where we were swallowing failed futures in media3.session
This may not be completely exhaustive, but it's better than before!

#minor-release

PiperOrigin-RevId: 533457167
(cherry picked from commit fe19dc421d)
2023-05-25 10:44:07 +00:00
ibaker
c26659184c Add empty headings to the 'unreleased changes' section of release notes
PiperOrigin-RevId: 533403520
(cherry picked from commit 0e09c0041f)
2023-05-25 10:44:07 +00:00
claincly
1f8caa508f Add a getter method for texture manager
PiperOrigin-RevId: 533402277
(cherry picked from commit d7ad431cfc)
2023-05-25 10:44:07 +00:00
tonihei
5e98c938c0 Add FilteringMediaSource that only provides tracks of given types
This is useful for cases where only certain types (e.g. only video)
from a source are needed and other tracks should be filtered out
completely to avoid later track selection issues.

#minor-release

PiperOrigin-RevId: 533394658
(cherry picked from commit c44b3828ca)
2023-05-25 10:44:07 +00:00
huangdarwin
3ac01d43dd Effect: Use callback to release texture
This allows us to avoid needing a reference to the VideoFrameProcessor, which
can be especially difficult if an App only has a reference to the
VideoFrameProcessor.Factory it passes into Transformer/ExoPlayer.

PiperOrigin-RevId: 533205983
(cherry picked from commit 25fa2df2de)
2023-05-25 10:44:07 +00:00
ibaker
2b53139c5f Allow mock(Random.class) to work with Java 17
https://stackoverflow.com/questions/70993863/mockito-can-not-mock-random-in-java-17

#minor-release

PiperOrigin-RevId: 533161221
(cherry picked from commit 04106da932)
2023-05-25 10:44:07 +00:00
jbibik
9c1a80082f Use TestUtil.getPublicMethods instead of getDeclaredMethods
JaCoCo introduces private synthetic methods (even on interfaces) which
have to be skipped when checking that a 'forwarding' implementation does
forward everything. Instead we can use the existing `getPublicMethods()`
method which implicitly skips these (since they're private).

PiperOrigin-RevId: 533130932
(cherry picked from commit 620b9e1540)
2023-05-25 10:44:07 +00:00
sheenachhabra
400218c018 Add argument validation in Mp4LocationData
PiperOrigin-RevId: 533121451
(cherry picked from commit 209783bdf2)
2023-05-25 10:44:07 +00:00
claincly
dd425d53c9 Always use sRGB/BT.709 for bitmap inputs
PiperOrigin-RevId: 533117700
(cherry picked from commit 24a4396990)
2023-05-25 10:44:07 +00:00
Tofunmi Adigun-Hameed
cd9ff24086 Remove FfmpegVideoRenderer from 1.1.0 release 2023-05-18 18:04:45 +00:00
tofunmi
431b985a04 HDR texture asset loading
PiperOrigin-RevId: 532846248
(cherry picked from commit 5fe10d7652)
2023-05-18 16:07:52 +00:00
rohks
633c339dc1 Remove deprecated methods Format.copyWithXXX
Use `Format.buildUpon()` and `setXXX` instead.

#minor-release

PiperOrigin-RevId: 532840625
(cherry picked from commit 538524e579)
2023-05-18 16:07:52 +00:00
sheenachhabra
b97ec5edfc Add default constructor for InAppMuxer.Factory
PiperOrigin-RevId: 532838813
(cherry picked from commit 410840c9e1)
2023-05-18 16:07:52 +00:00
rohks
61a5dd76e3 Remove deprecated methods that create an instance of Format
Use `Format.Builder` instead.

#minor-release

PiperOrigin-RevId: 532808478
(cherry picked from commit 18aa664cb8)
2023-05-18 16:07:52 +00:00
huangdarwin
55c9d10022 HDR: Blocklist Galaxy Z Fold 4 for HLG tone mapping.
This device failed on HdrEditingTest's exportAndTranscode_hlg10File_whenHdrEditingUnsupported_toneMapsOrThrows
before this CL, and succeeds on that test after this CL.

PiperOrigin-RevId: 532796897
(cherry picked from commit 83190a0fe9)
2023-05-18 16:07:52 +00:00
sheenachhabra
7adca46e4b Move Mp4LocationData from extractor module to container module
This class will be shared between extractor and muxer module.

PiperOrigin-RevId: 532784415
(cherry picked from commit 7b62b33127)
2023-05-18 16:07:52 +00:00
ibaker
514f032afc Add main/dev-v2 branch options to bug.yml template
#minor-release

PiperOrigin-RevId: 532766676
(cherry picked from commit 84d0206c76)
2023-05-18 16:07:51 +00:00
ibaker
962499c9d8 Add Media3 1.0.2 and ExoPlayer 2.18.7 to bug.yml template
#minor-release

PiperOrigin-RevId: 532765549
(cherry picked from commit 4ede3d6007)
2023-05-18 16:07:51 +00:00
huangdarwin
ce5b57e03c Effect: Make TexturePool and use in FinalWrapper.
Have the FinalShaderProgramWrapper / VideoFrameProcessor texture
output access textures provided through a texture pool, that
recycles used textures.

Also, add the TexturePool interface to generally re-use textures.

PiperOrigin-RevId: 532754377
(cherry picked from commit 94efcd7917)
2023-05-18 16:07:51 +00:00
sheenachhabra
f5ec1bb6f9 Move NAL unit related methods from common to container module
PiperOrigin-RevId: 532750099
(cherry picked from commit e0d6f67dd9)
2023-05-18 16:07:51 +00:00
kimvde
6abc1a7155 Simplify FrameConsumptionManager onReadyToAcceptInputFrame logic
Propagate the "end of current stream" signal directly after queueing the
last frame, instead of waiting for the next onReadyToAcceptInputFrame()
call.

PiperOrigin-RevId: 532739462
(cherry picked from commit 028b3a7312)
2023-05-18 16:07:51 +00:00
tianyifeng
a5031a688b Avoid using @see in the summary fragment in C.java
PiperOrigin-RevId: 532536037
(cherry picked from commit 5d4c73eee5)
2023-05-18 16:07:51 +00:00
rohks
7773c88404 Remove deprecated WorkManagerScheduler constructor
Use a non deprecated constructor that includes the option to provide a `Context` parameter instead.

#minor-release

PiperOrigin-RevId: 532535770
(cherry picked from commit df52864420)
2023-05-18 16:07:51 +00:00
rohks
fa152ddc80 Remove deprecated String constants for Charsets
Use Kotlin Charsets from the `kotlin.text` package, the `java.nio.charset.StandardCharsets` or the `com.google.common.base.Charsets` instead.

#minor-release

PiperOrigin-RevId: 532469103
(cherry picked from commit 1061135cfd)
2023-05-18 16:07:51 +00:00
tofunmi
7fdae1ad0d Adjust image input ForPixelWidthHeightRatio
PiperOrigin-RevId: 532463400
(cherry picked from commit 63ee5ccb28)
2023-05-18 16:07:51 +00:00
rohks
5a72333554 Remove deprecated DownloadService constructor
Use a non deprecated constructor that includes the option to provide a `channelDescriptionResourceId` parameter.

#minor-release

PiperOrigin-RevId: 532450975
(cherry picked from commit 022a05c376)
2023-05-18 16:07:51 +00:00
ibaker
06c8433ce8 Remove deprecated DownloadHelper format-specific methods
Use `forMediaItem` instead.

PiperOrigin-RevId: 532414060
(cherry picked from commit 57479dd397)
2023-05-18 16:07:51 +00:00
bachinger
1510710c46 Enable live DASH stream for IMA DAI
This change enables the IMA extension to play live DASH streams
with DAI support. Samples streams can be found and played in the
main demo app.

Issue: google/ExoPlayer#10912
#minor-release
PiperOrigin-RevId: 532407708
(cherry picked from commit dab1353aad)
2023-05-17 16:59:56 +00:00
ibaker
3633ad15cc Update release notes for Media3 1.0.2
#minor-release

PiperOrigin-RevId: 532404001
(cherry picked from commit 1a38a0c41e)
2023-05-17 16:59:56 +00:00
tofunmi
43f8d080df Update mixed input test to include images of different aspect ratios
Makes this test a little more thorough.

PiperOrigin-RevId: 532386515
(cherry picked from commit 88642587ac)
2023-05-17 16:59:55 +00:00
huangdarwin
04a00c7ac4 Test: Suppress SSIM timeouts on galaxy fold/flip.
This is a failure only in SSIM, so it seems unlikely we'll prioritize this over
other work or bugs soon. Suppress test failures to reduce triage burden.

PiperOrigin-RevId: 532200729
(cherry picked from commit 62afbe87bb)
2023-05-17 16:59:55 +00:00
rohks
fc6429b2d4 Remove two deprecated InvalidResponseCodeException constructors
Use a non-deprecated constructor that accepts additional fields(`cause`, `responseBody`) to enhance error logging.

#minor-release

PiperOrigin-RevId: 532190896
(cherry picked from commit b120ef65ed)
2023-05-17 16:59:55 +00:00
tonihei
a8c56f2c60 Add Player.replaceMediaItem(s)
This methods allows to replace single items or a range of items directly
without using separate operations for add and remove. The advantage is
more readable code for apps and the potential for player
implementations to optimize this process (e.g. only replace values
without interrupting playback).

The current change just introduces the API with its default behavior.
The default logic will be removed again in the future in favor of
better logic in the Player implementations.

Issue: google/ExoPlayer#8046
PiperOrigin-RevId: 532151471
(cherry picked from commit b1cfeb04a0)
2023-05-17 16:59:55 +00:00
jbibik
27becc028d Allow ExoPlayer to opt into volume device control, forbidden by default
PiperOrigin-RevId: 532136692
(cherry picked from commit 1c6b894e88)
2023-05-17 16:59:55 +00:00
tonihei
7f1c1185e7 Remove unnecessary warning suppression
PiperOrigin-RevId: 532114601
(cherry picked from commit 79657692e4)
2023-05-17 16:59:55 +00:00
ibaker
7ac9cf0ec7 Remove deprecated DefaultDrmSessionManager constructors
Use `DefaultDrmSessionManager.Builder` instead.

#minor-release

PiperOrigin-RevId: 532102375
(cherry picked from commit 8a5cebb54d)
2023-05-17 16:59:55 +00:00
huangdarwin
a1fbb12ade Effect: Multiple Texture output
Allow the VideoFrameProcessor to output multiple textures at a time, so that
lifetime of textures is up to the consumer calling VFP.releaseOutputFrame.

The FinalShaderProgramWrapper also has a new maxCapacity limit added, to ensure
the a reasonable amount of textures is used and avoid using up memory.

PiperOrigin-RevId: 532094256
(cherry picked from commit 07ec1eaa48)
2023-05-17 16:59:55 +00:00
tonihei
c7004d43af Add consistency check to sending and receiving position updates
The periodic updates are only meant to happen while we are in the
same period or ad. This was already guaranteed except for two cases:
1. The Player in a session has updated its state without yet calling
   its listeners
2. The session scheduled a PlayerInfo update that hasn't been sent yet

... and in both cases, the following happened:
 - The change updated the mediaItemIndex to an index that didn't exist
   in a previous Timeline known to the Controller
 - One of the period position updates happened to be sent at exactly
   this time

This problem can be avoided by only scheduling the update if we are
still in the same period/ad and haven't scheduled a normal PlayerInfo
update already.

Since new MediaControllers may still connect to old sessons with this
bug, we need an equivalent change on the controller side to ignore such
buggy updates.

PiperOrigin-RevId: 532089328
(cherry picked from commit 96dd0ae583)
2023-05-17 16:59:55 +00:00
andrewlewis
5722e6472a Fix lost shader input capacity after end-of-stream
When exporting compositions with multiple images in a row, transformation could
get stuck if a shader was ready to accept input when end-of-stream was already
signaled and queued from upstream. Fix accounting for the downstream capacity.

Manually tested on concatenations with several images and several videos in a
row, by adding logging and verifying the capacity updates as expected across
edited media item transitions.

PiperOrigin-RevId: 532088793
(cherry picked from commit 6850391e45)
2023-05-17 16:59:55 +00:00
ibaker
1ebf5c2a6a Delete deprecated zero-arg DefaultBandwidthMeter constructor
Use `DefaultBandwidthMeter.Builder` instead.

#minor-release

PiperOrigin-RevId: 532069549
(cherry picked from commit 8f29a5eba9)
2023-05-17 16:59:55 +00:00
kimvde
5e926cdce5 Refactor SequenceAssetLoader release
In the past, the SequenceAssetLoader was released in TransformerInternal
when the export ended.

fc539da061 was made to release the SequenceAssetLoader earlier, when
loading ended. This was causing player release timeouts because the last
AssetLoader in the sequence was released before the SamplePipelines (see
0b40bc37ab for more information).

The code that was releasing the SequenceAssetLoader was first commented
out because we didn't have an understanding of what was happening.

This change removes the early SequenceAssetLoader release all together.
It doesn't have any effect as this code was already commented out.

PiperOrigin-RevId: 532065673
(cherry picked from commit 6dfb387117)
2023-05-17 16:59:55 +00:00
andrewlewis
b0f8a8f316 Signal end-of-stream after creating latch
The output end-of-stream notification from the last shader could theoretically
arrive before the latch for detecting it is created, which might cause waiting
on the latch indefinitely. Create the latch before signaling end of stream so
that it's guaranteed to be set before the end-of-stream signal arrives.

PiperOrigin-RevId: 532056472
(cherry picked from commit 857e6ebee8)
2023-05-17 16:59:55 +00:00
ibaker
7e30091196 Remove two deprecated SimpleCache constructors
Use a non-deprecated constructor that takes a `DatabaseProvider`
instead for better performance.

#minor-release

PiperOrigin-RevId: 532046598
(cherry picked from commit 0a86790be2)
2023-05-17 16:59:55 +00:00
ibaker
07ca741eb1 Rollback of 509830f38f
*** Original commit ***

Rollback of 221a56da38

*** Original commit ***

Rollback of 749d77b1d9

*** Original commit ***

PiperOrigin-RevId: 531530885
(cherry picked from commit 9366b4e50a)
2023-05-17 16:59:54 +00:00
huangdarwin
2ab9c0c556 Transformer: Add latest input format to DebugTraceUtil
This can provide more information to help debug muxer errors

PiperOrigin-RevId: 531521974
(cherry picked from commit 2736b11845)
2023-05-17 16:59:54 +00:00
bachinger
932bed1f79 Implement SystemUI contract for media resumption
When a `MediaButtonReceiver` is found in the manifest, the library
can implement the contract of SystemUI to signal that the app wants
a playback resumption notification to be displayed.

And, vice versa, if no `MediaButtonReceiver` is in the manifest, the
library will signal to not show the notification after the app has been
terminated.

#minor-release

PiperOrigin-RevId: 531516023
(cherry picked from commit 9bf6b7ea20)
2023-05-17 16:59:54 +00:00
sheenachhabra
f15e5733ce Add container module
This module will contain functionalities common to extractor and muxer.

PiperOrigin-RevId: 531501602
(cherry picked from commit eb8ec87a5c)
2023-05-17 16:59:54 +00:00
sheenachhabra
80aac22cf9 Add test file with metadata track
Mp4Muxer supports adding Metadata track.
Added test file to cover this scenario.

PiperOrigin-RevId: 531496409
(cherry picked from commit b3fd202e11)
2023-05-17 16:59:54 +00:00
sheenachhabra
2478b48997 Rollback of 925aa34e13
*** Original commit ***

Rollback of 65d5132f76

*** Original commit ***

Create InAppMuxer in transformer

To use the InAppMuxer, the client needs to pass InAppMuxer Factory.

***

***

PiperOrigin-RevId: 531470081
(cherry picked from commit 867355fdc5)
2023-05-17 16:59:54 +00:00
866 changed files with 43529 additions and 11678 deletions

View file

@ -19,6 +19,8 @@ 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

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

@ -1,6 +1,37 @@
# Release notes
### Unreleased changes
## 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
@ -17,6 +48,20 @@
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
@ -41,12 +86,78 @@
([#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.
@ -57,14 +168,50 @@
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.
@ -75,11 +222,26 @@
* 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.
@ -87,7 +249,6 @@
* Add `FileDescriptorDataSource`, a new `DataSource` that can be used to
read from a `FileDescriptor`
([#3757](https://github.com/google/ExoPlayer/issues/3757)).
* DRM:
* Effect:
* Add `DefaultVideoFrameProcessor` workaround for minor `SurfaceTexture`
scaling. `SurfaceTexture` may include a small scaling that cuts off a
@ -95,11 +256,13 @@
such that output is closer to expected.
* Speed up `DefaultVideoFrameProcessor.queueInputBitmap()`. As a result,
exporting images to videos with `Transformer` is faster.
* Muxers:
* 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
@ -107,17 +270,60 @@
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:
* Downloads:
* OkHttp Extension:
* Cronet Extension:
* RTMP Extension:
* HLS Extension:
* 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
@ -125,20 +331,46 @@
* 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.
* MIDI extension:
* Leanback extension:
* 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:
* 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

View file

@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
project.ext {
releaseVersion = '1.4.1'
releaseVersionCode = 1_004_001_3_00
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
@ -28,7 +28,7 @@ project.ext {
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'

View file

@ -54,6 +54,7 @@ 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

View file

@ -15,9 +15,19 @@
*/
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;
@ -27,12 +37,16 @@ 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;
@ -41,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;
@ -50,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;
@ -66,6 +82,17 @@ 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;
@ -97,12 +124,25 @@ 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));
@ -150,12 +190,19 @@ 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> videoEffects =
appliesVideoEffects
? ImmutableList.of(
MatrixTransformationFactory.createDizzyCropEffect(),
RgbFilter.createGrayscaleFilter())
: ImmutableList.of();
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;
@ -179,7 +226,7 @@ public final class CompositionPreviewActivity extends AppCompatActivity {
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) {
@ -187,11 +234,15 @@ public final class CompositionPreviewActivity extends AppCompatActivity {
}
SonicAudioProcessor sampleRateChanger = new SonicAudioProcessor();
sampleRateChanger.setOutputSampleRateHz(8_000);
return new Composition.Builder(/* sequences= */ compositionSequences)
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();
}
@ -207,7 +258,7 @@ public final class CompositionPreviewActivity extends AppCompatActivity {
.build();
EditedMediaItem audioItem =
new EditedMediaItem.Builder(audioMediaItem).setDurationUs(59_000_000).build();
return new EditedMediaItemSequence(audioItem);
return new EditedMediaItemSequence.Builder(audioItem).build();
}
private void previewComposition() {
@ -238,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();
@ -257,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();
@ -278,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
@ -288,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);
@ -316,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

@ -111,7 +111,49 @@
android:text="@string/add_background_audio"
app:layout_constraintStart_toStartOf="parent"
app:layout_constraintEnd_toEndOf="parent"
app:layout_constraintBottom_toTopOf="@id/preview_button" />
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"

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

@ -26,4 +26,14 @@
<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

@ -50,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";
@ -114,16 +104,13 @@ public final class DemoUtil {
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

@ -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

@ -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

@ -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

@ -84,10 +84,8 @@ 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;
@ -121,6 +119,8 @@ 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;
@ -267,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);
@ -322,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());
@ -359,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);
@ -371,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))

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

@ -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;
@ -240,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) {
@ -250,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

@ -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;
@ -165,6 +167,7 @@ public final class Format {
@Nullable private List<byte[]> initializationData;
@Nullable private DrmInitData drmInitData;
private long subsampleOffsetUs;
private boolean hasPrerollSamples;
// Video specific.
@ -255,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;
@ -542,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.
/**
@ -734,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
@ -746,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
@ -951,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. */
@ -1091,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;
@ -1380,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) {
@ -1410,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());
}
@ -1433,17 +1463,18 @@ 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) {

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 {

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.1";
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.1";
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_001_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

@ -601,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;
@ -683,6 +685,9 @@ public final class MimeTypes {
}
mimeType = Ascii.toLowerCase(mimeType);
switch (mimeType) {
// 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;

View file

@ -143,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.
@ -155,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
@ -163,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.
@ -171,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() {}
}
/**

View file

@ -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

@ -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

@ -17,16 +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 {
@ -40,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>).
@ -234,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.
*
@ -319,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

@ -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

@ -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

@ -80,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;
@ -1601,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);
}
/**
@ -1618,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);
}
/**
@ -1903,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);
}
/**
@ -1925,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);
}
/**
@ -1937,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);
}
/**
@ -2249,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;
}
@ -3918,7 +3937,6 @@ public final class Util {
@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

@ -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});
}
}

View file

@ -0,0 +1,66 @@
/*
* 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 java.math.BigDecimal;
import java.math.RoundingMode;
/** Testing utils class related to {@link Sonic} */
/* package */ final class SonicTestingUtils {
/**
* Returns expected accumulated truncation error for {@link Sonic}'s resampling algorithm, given
* an input length, input sample rate, and resampling rate.
*
* <p><b>Note:</b> This method is only necessary until we address b/361768785 and fix the
* underlying truncation issue.
*
* <p>The accumulated truncation error is calculated as follows:
*
* <ol>
* <li>Individual truncation error: Divide sample rate by resampling rate, and calculate delta
* between floating point result and truncated int representation.
* <li>Truncation accumulation count: Divide length by sample rate to obtain number of times
* that truncation error accumulates.
* <li>Accumulated truncation error: Multiply results of 1 and 2.
* </ol>
*
* @param length Length of input in frames.
* @param sampleRate Input sample rate of {@link Sonic} instance.
* @param resamplingRate Resampling rate given by {@code pitch * (inputSampleRate /
* outputSampleRate)}.
*/
public static long calculateAccumulatedTruncationErrorForResampling(
BigDecimal length, BigDecimal sampleRate, BigDecimal resamplingRate) {
// Calculate number of times that Sonic accumulates truncation error. Set scale to 20 decimal
// places, so that division doesn't return an integer.
BigDecimal errorCount = length.divide(sampleRate, /* scale= */ 20, RoundingMode.HALF_EVEN);
// Calculate what truncation error Sonic is accumulating, calculated as:
// inputSampleRate / resamplingRate - (int) inputSampleRate / resamplingRate. Set scale to 20
// decimal places, so that division doesn't return an integer.
BigDecimal individualError =
sampleRate.divide(resamplingRate, /* scale */ 20, RoundingMode.HALF_EVEN);
individualError =
individualError.subtract(individualError.setScale(/* newScale= */ 0, RoundingMode.FLOOR));
// Calculate total accumulated error = (int) floor(errorCount * individualError).
BigDecimal accumulatedError =
errorCount.multiply(individualError).setScale(/* newScale= */ 0, RoundingMode.FLOOR);
return accumulatedError.longValueExact();
}
private SonicTestingUtils() {}
}

View file

@ -240,9 +240,9 @@ public class SpeedChangingAudioProcessorTest {
}
@Test
public void queueInput_multipleSpeedsInBufferWithLimitVeryClose_readsDataUntilSpeedLimit()
throws Exception {
public void queueInput_multipleSpeedsInBufferWithLimitVeryClose_doesNotHang() throws Exception {
long speedChangeTimeUs = 1; // Change speed very close to current position at 1us.
int outputFrames = 0;
SpeedProvider speedProvider =
TestSpeedProvider.createWithStartTimes(
/* startTimesUs= */ new long[] {0L, speedChangeTimeUs},
@ -250,12 +250,14 @@ public class SpeedChangingAudioProcessorTest {
SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer = getInputBuffer(/* frameCount= */ 5);
int inputBufferLimit = inputBuffer.limit();
speedChangingAudioProcessor.queueInput(inputBuffer);
assertThat(inputBuffer.position()).isEqualTo(AUDIO_FORMAT.bytesPerFrame);
assertThat(inputBuffer.limit()).isEqualTo(inputBufferLimit);
outputFrames +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame;
speedChangingAudioProcessor.queueEndOfStream();
outputFrames +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame;
assertThat(outputFrames).isEqualTo(3);
}
@Test
@ -531,6 +533,68 @@ public class SpeedChangingAudioProcessorTest {
.isEqualTo(40_000);
}
@Test
public void queueInput_exactlyUpToSpeedBoundary_outputsExpectedNumberOfSamples()
throws AudioProcessor.UnhandledAudioFormatException {
int outputFrameCount = 0;
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT,
/* frameCounts= */ new int[] {1000, 1000, 1000},
/* speeds= */ new float[] {2, 4, 2}); // 500, 250, 500 = 1250
SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer input = getInputBuffer(1000);
speedChangingAudioProcessor.queueInput(input);
outputFrameCount +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame;
input.rewind();
speedChangingAudioProcessor.queueInput(input);
outputFrameCount +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame;
input.rewind();
speedChangingAudioProcessor.queueInput(input);
outputFrameCount +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame;
speedChangingAudioProcessor.queueEndOfStream();
outputFrameCount +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame;
assertThat(outputFrameCount).isWithin(2).of(1250);
}
@Test
public void queueInput_withUnalignedSpeedStartTimes_skipsMidSampleSpeedChanges()
throws AudioProcessor.UnhandledAudioFormatException {
int outputFrameCount = 0;
// Sample duration @44.1KHz is 22.67573696145125us. The last three speed changes fall between
// samples 4 and 5, so only the speed change at 105us should be used. We expect an output of
// 4 / 2 + 8 / 4 = 4 samples.
SpeedProvider speedProvider =
TestSpeedProvider.createWithStartTimes(
/* startTimesUs= */ new long[] {0, 95, 100, 105},
/* speeds= */ new float[] {2, 3, 8, 4});
SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer input = getInputBuffer(12);
while (input.hasRemaining()) {
speedChangingAudioProcessor.queueInput(input);
outputFrameCount +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame;
}
speedChangingAudioProcessor.queueEndOfStream();
outputFrameCount +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame;
// Allow one sample of tolerance per effectively applied speed change.
assertThat(outputFrameCount).isWithin(1).of(4);
}
private static SpeedChangingAudioProcessor getConfiguredSpeedChangingAudioProcessor(
SpeedProvider speedProvider) throws AudioProcessor.UnhandledAudioFormatException {
SpeedChangingAudioProcessor speedChangingAudioProcessor =

View file

@ -15,9 +15,16 @@
*/
package androidx.media3.common.util;
import static androidx.media3.common.util.CodecSpecificDataUtil.getCodecProfileAndLevel;
import static com.google.common.truth.Truth.assertThat;
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 androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -45,4 +52,163 @@ public class CodecSpecificDataUtilTest {
assertThat(sampleRateAndChannelCount.first).isEqualTo(96000);
assertThat(sampleRateAndChannelCount.second).isEqualTo(2);
}
@Test
public void getCodecProfileAndLevel_handlesH263CodecString() {
assertCodecProfileAndLevelForCodecsString(
MimeTypes.VIDEO_H263,
"s263.1.1",
MediaCodecInfo.CodecProfileLevel.H263ProfileBaseline,
MediaCodecInfo.CodecProfileLevel.H263Level10);
}
@Test
public void getCodecProfileAndLevel_handlesVp9Profile1CodecString() {
assertCodecProfileAndLevelForCodecsString(
MimeTypes.VIDEO_VP9,
"vp09.01.51",
MediaCodecInfo.CodecProfileLevel.VP9Profile1,
MediaCodecInfo.CodecProfileLevel.VP9Level51);
}
@Test
public void getCodecProfileAndLevel_handlesVp9Profile2CodecString() {
assertCodecProfileAndLevelForCodecsString(
MimeTypes.VIDEO_VP9,
"vp09.02.10",
MediaCodecInfo.CodecProfileLevel.VP9Profile2,
MediaCodecInfo.CodecProfileLevel.VP9Level1);
}
@Test
public void getCodecProfileAndLevel_handlesFullVp9CodecString() {
// Example from https://www.webmproject.org/vp9/mp4/#codecs-parameter-string.
assertCodecProfileAndLevelForCodecsString(
MimeTypes.VIDEO_VP9,
"vp09.02.10.10.01.09.16.09.01",
MediaCodecInfo.CodecProfileLevel.VP9Profile2,
MediaCodecInfo.CodecProfileLevel.VP9Level1);
}
@Test
public void getCodecProfileAndLevel_handlesDolbyVisionCodecString() {
assertCodecProfileAndLevelForCodecsString(
MimeTypes.VIDEO_DOLBY_VISION,
"dvh1.05.05",
MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvheStn,
MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelFhd60);
}
@Test
public void getCodecProfileAndLevel_handlesDolbyVisionProfile10CodecString() {
assertCodecProfileAndLevelForCodecsString(
MimeTypes.VIDEO_DOLBY_VISION,
"dav1.10.09",
MediaCodecInfo.CodecProfileLevel.DolbyVisionProfileDvav110,
MediaCodecInfo.CodecProfileLevel.DolbyVisionLevelUhd60);
}
@Test
public void getCodecProfileAndLevel_handlesAv1ProfileMain8CodecString() {
assertCodecProfileAndLevelForCodecsString(
MimeTypes.VIDEO_AV1,
"av01.0.10M.08",
MediaCodecInfo.CodecProfileLevel.AV1ProfileMain8,
MediaCodecInfo.CodecProfileLevel.AV1Level42);
}
@Test
public void getCodecProfileAndLevel_handlesAv1ProfileMain10CodecString() {
assertCodecProfileAndLevelForCodecsString(
MimeTypes.VIDEO_AV1,
"av01.0.20M.10",
MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10,
MediaCodecInfo.CodecProfileLevel.AV1Level7);
}
@Test
public void getCodecProfileAndLevel_handlesAv1ProfileMain10HDRWithHdrInfoSet() {
ColorInfo colorInfo =
new ColorInfo.Builder()
.setColorSpace(C.COLOR_SPACE_BT709)
.setColorRange(C.COLOR_RANGE_LIMITED)
.setColorTransfer(C.COLOR_TRANSFER_SDR)
.setHdrStaticInfo(new byte[] {1, 2, 3, 4, 5, 6, 7})
.build();
Format format =
new Format.Builder()
.setSampleMimeType(MimeTypes.VIDEO_AV1)
.setCodecs("av01.0.21M.10")
.setColorInfo(colorInfo)
.build();
assertCodecProfileAndLevelForFormat(
format,
MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10HDR10,
MediaCodecInfo.CodecProfileLevel.AV1Level71);
}
@Test
public void getCodecProfileAndLevel_handlesAv1ProfileMain10HDRWithoutHdrInfoSet() {
ColorInfo colorInfo =
new ColorInfo.Builder()
.setColorSpace(C.COLOR_SPACE_BT709)
.setColorRange(C.COLOR_RANGE_LIMITED)
.setColorTransfer(C.COLOR_TRANSFER_HLG)
.build();
Format format =
new Format.Builder()
.setSampleMimeType(MimeTypes.VIDEO_AV1)
.setCodecs("av01.0.21M.10")
.setColorInfo(colorInfo)
.build();
assertCodecProfileAndLevelForFormat(
format,
MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10HDR10,
MediaCodecInfo.CodecProfileLevel.AV1Level71);
}
@Test
public void getCodecProfileAndLevel_handlesFullAv1CodecString() {
// Example from https://aomediacodec.github.io/av1-isobmff/#codecsparam.
assertCodecProfileAndLevelForCodecsString(
MimeTypes.VIDEO_AV1,
"av01.0.04M.10.0.112.09.16.09.0",
MediaCodecInfo.CodecProfileLevel.AV1ProfileMain10,
MediaCodecInfo.CodecProfileLevel.AV1Level3);
}
@Test
public void getCodecProfileAndLevel_rejectsNullCodecString() {
Format format = new Format.Builder().setCodecs(null).build();
assertThat(getCodecProfileAndLevel(format)).isNull();
}
@Test
public void getCodecProfileAndLevel_rejectsEmptyCodecString() {
Format format = new Format.Builder().setCodecs("").build();
assertThat(getCodecProfileAndLevel(format)).isNull();
}
@Test
public void getCodecProfileAndLevel_handlesMvHevcCodecString() {
assertCodecProfileAndLevelForCodecsString(
MimeTypes.VIDEO_MV_HEVC,
"hvc1.6.40.L120.BF.80",
/* profile= */ 6,
MediaCodecInfo.CodecProfileLevel.HEVCMainTierLevel4);
}
private static void assertCodecProfileAndLevelForCodecsString(
String sampleMimeType, String codecs, int profile, int level) {
Format format =
new Format.Builder().setSampleMimeType(sampleMimeType).setCodecs(codecs).build();
assertCodecProfileAndLevelForFormat(format, profile, level);
}
private static void assertCodecProfileAndLevelForFormat(Format format, int profile, int level) {
@Nullable Pair<Integer, Integer> codecProfileAndLevel = getCodecProfileAndLevel(format);
assertThat(codecProfileAndLevel).isNotNull();
assertThat(codecProfileAndLevel.first).isEqualTo(profile);
assertThat(codecProfileAndLevel.second).isEqualTo(level);
}
}

View file

@ -240,4 +240,10 @@ public class TimestampAdjusterTest {
assertThat(secondAdjustedTimestampUs - firstAdjustedTimestampUs).isGreaterThan(0x100000000L);
}
// https://github.com/androidx/media/issues/1763
@Test
public void usToWrappedPts_usTimestampCloseToOverflow_doesntOverflow() {
assertThat(TimestampAdjuster.usToNonWrappedPts(1L << 52)).isEqualTo(405323966463344L);
}
}

View file

@ -116,6 +116,9 @@ public abstract class Mp4Box {
public static final int TYPE_H263 = 0x48323633;
@SuppressWarnings("ConstantCaseForConstants")
public static final int TYPE_h263 = 0x68323633;
@SuppressWarnings("ConstantCaseForConstants")
public static final int TYPE_d263 = 0x64323633;

View file

@ -15,6 +15,7 @@
*/
package androidx.media3.container;
import static androidx.media3.common.MimeTypes.containsCodecsCorrespondingToMimeType;
import static com.google.common.math.DoubleMath.log2;
import static java.lang.Math.max;
import static java.lang.Math.min;
@ -33,6 +34,7 @@ import java.math.RoundingMode;
import java.nio.ByteBuffer;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/** Utility methods for handling H.264/AVC and H.265/HEVC NAL units. */
@UnstableApi
@ -614,6 +616,24 @@ public final class NalUnitUtil {
&& ((nalUnitHeaderFirstByte & 0x7E) >> 1) == H265_NAL_UNIT_TYPE_PREFIX_SEI);
}
/**
* Returns whether the NAL unit with the specified header contains supplemental enhancement
* information.
*
* @param format The sample {@link Format}.
* @param nalUnitHeaderFirstByte The first byte of nal_unit().
* @return Whether the NAL unit with the specified header is an SEI NAL unit. False is returned if
* the {@code MimeType} is {@code null}.
*/
public static boolean isNalUnitSei(Format format, byte nalUnitHeaderFirstByte) {
return ((Objects.equals(format.sampleMimeType, MimeTypes.VIDEO_H264)
|| containsCodecsCorrespondingToMimeType(format.codecs, MimeTypes.VIDEO_H264))
&& (nalUnitHeaderFirstByte & 0x1F) == H264_NAL_UNIT_TYPE_SEI)
|| ((Objects.equals(format.sampleMimeType, MimeTypes.VIDEO_H265)
|| containsCodecsCorrespondingToMimeType(format.codecs, MimeTypes.VIDEO_H265))
&& ((nalUnitHeaderFirstByte & 0x7E) >> 1) == H265_NAL_UNIT_TYPE_PREFIX_SEI);
}
/**
* Returns the type of the NAL unit in {@code data} that starts at {@code offset}.
*

View file

@ -16,17 +16,19 @@
package androidx.media3.container;
import static androidx.annotation.RestrictTo.Scope.LIBRARY_GROUP;
import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.common.util.Util.castNonNull;
import androidx.annotation.Nullable;
import androidx.annotation.RestrictTo;
import androidx.media3.common.C;
import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.UnstableApi;
import java.util.ArrayDeque;
import java.util.Deque;
import java.util.ArrayList;
import java.util.List;
import java.util.PriorityQueue;
import java.util.concurrent.atomic.AtomicLong;
/** A queue of SEI messages, ordered by presentation timestamp. */
@UnstableApi
@ -40,18 +42,17 @@ public final class ReorderingSeiMessageQueue {
}
private final SeiConsumer seiConsumer;
private final AtomicLong tieBreakGenerator = new AtomicLong();
/**
* Pool of re-usable {@link SeiMessage} objects to avoid repeated allocations. Elements should be
* added and removed from the 'tail' of the queue (with {@link Deque#push(Object)} and {@link
* Deque#pop()}), to avoid unnecessary array copying.
*/
private final ArrayDeque<SeiMessage> unusedSeiMessages;
/** Pool of re-usable {@link ParsableByteArray} objects to avoid repeated allocations. */
private final ArrayDeque<ParsableByteArray> unusedParsableByteArrays;
private final PriorityQueue<SeiMessage> pendingSeiMessages;
/** Pool of re-usable {@link SampleSeiMessages} objects to avoid repeated allocations. */
private final ArrayDeque<SampleSeiMessages> unusedSampleSeiMessages;
private final PriorityQueue<SampleSeiMessages> pendingSeiMessages;
private int reorderingQueueSize;
@Nullable private SampleSeiMessages lastQueuedMessage;
/**
* Creates an instance, initially with no max size.
@ -62,7 +63,8 @@ public final class ReorderingSeiMessageQueue {
*/
public ReorderingSeiMessageQueue(SeiConsumer seiConsumer) {
this.seiConsumer = seiConsumer;
unusedSeiMessages = new ArrayDeque<>();
unusedParsableByteArrays = new ArrayDeque<>();
unusedSampleSeiMessages = new ArrayDeque<>();
pendingSeiMessages = new PriorityQueue<>();
reorderingQueueSize = C.LENGTH_UNSET;
}
@ -70,8 +72,15 @@ public final class ReorderingSeiMessageQueue {
/**
* Sets the max size of the re-ordering queue.
*
* <p>The size is defined in terms of the number of unique presentation timestamps, rather than
* the number of messages. This ensures that properties like H.264's {@code
* max_number_reorder_frames} can be used to set this max size in the case of multiple SEI
* messages per sample (where multiple SEI messages therefore have the same presentation
* timestamp).
*
* <p>When the queue exceeds this size during a call to {@link #add(long, ParsableByteArray)}, the
* least message is passed to the {@link SeiConsumer} provided during construction.
* messages associated with the least timestamp are passed to the {@link SeiConsumer} provided
* during construction.
*
* <p>If the new size is larger than the number of elements currently in the queue, items are
* removed from the head of the queue (least first) and passed to the {@link SeiConsumer} provided
@ -86,7 +95,7 @@ public final class ReorderingSeiMessageQueue {
/**
* Returns the maximum size of this queue, or {@link C#LENGTH_UNSET} if it is unbounded.
*
* <p>See {@link #setMaxSize(int)}.
* <p>See {@link #setMaxSize(int)} for details on how size is defined.
*/
public int getMaxSize() {
return reorderingQueueSize;
@ -95,12 +104,16 @@ public final class ReorderingSeiMessageQueue {
/**
* Adds a message to the queue.
*
* <p>If this causes the queue to exceed its {@linkplain #setMaxSize(int) max size}, the least
* message (which may be the one passed to this method) is passed to the {@link SeiConsumer}
* provided during construction.
* <p>If this causes the queue to exceed its {@linkplain #setMaxSize(int) max size}, messages
* associated with the least timestamp (which may be the message passed to this method) are passed
* to the {@link SeiConsumer} provided during construction.
*
* <p>Messages with matching timestamps must be added consecutively (this will naturally happen
* when parsing messages from a container).
*
* @param presentationTimeUs The presentation time of the SEI message.
* @param seiBuffer The SEI data. The data will be copied, so the provided object can be re-used.
* @param seiBuffer The SEI data. The data will be copied, so the provided object can be re-used
* after this method returns.
*/
public void add(long presentationTimeUs, ParsableByteArray seiBuffer) {
if (reorderingQueueSize == 0
@ -110,15 +123,42 @@ public final class ReorderingSeiMessageQueue {
seiConsumer.consume(presentationTimeUs, seiBuffer);
return;
}
SeiMessage seiMessage =
unusedSeiMessages.isEmpty() ? new SeiMessage() : unusedSeiMessages.poll();
seiMessage.reset(presentationTimeUs, tieBreakGenerator.getAndIncrement(), seiBuffer);
pendingSeiMessages.add(seiMessage);
// Make a local copy of the SEI data so we can store it in the queue and allow the seiBuffer
// parameter to be safely re-used after this add() method returns.
ParsableByteArray seiBufferCopy = copy(seiBuffer);
if (lastQueuedMessage != null && presentationTimeUs == lastQueuedMessage.presentationTimeUs) {
lastQueuedMessage.nalBuffers.add(seiBufferCopy);
return;
}
SampleSeiMessages sampleSeiMessages =
unusedSampleSeiMessages.isEmpty() ? new SampleSeiMessages() : unusedSampleSeiMessages.pop();
sampleSeiMessages.init(presentationTimeUs, seiBufferCopy);
pendingSeiMessages.add(sampleSeiMessages);
lastQueuedMessage = sampleSeiMessages;
if (reorderingQueueSize != C.LENGTH_UNSET) {
flushQueueDownToSize(reorderingQueueSize);
}
}
/**
* Copies {@code input} into a {@link ParsableByteArray} instance from {@link
* #unusedParsableByteArrays}, or a new instance if that is empty.
*/
private ParsableByteArray copy(ParsableByteArray input) {
ParsableByteArray result =
unusedParsableByteArrays.isEmpty()
? new ParsableByteArray()
: unusedParsableByteArrays.pop();
result.reset(input.bytesLeft());
System.arraycopy(
/* src= */ input.getData(),
/* srcPos= */ input.getPosition(),
/* dest= */ result.getData(),
/* destPos= */ 0,
/* length= */ result.bytesLeft());
return result;
}
/**
* Empties the queue, passing all messages (least first) to the {@link SeiConsumer} provided
* during construction.
@ -129,47 +169,42 @@ public final class ReorderingSeiMessageQueue {
private void flushQueueDownToSize(int targetSize) {
while (pendingSeiMessages.size() > targetSize) {
SeiMessage seiMessage = castNonNull(pendingSeiMessages.poll());
seiConsumer.consume(seiMessage.presentationTimeUs, seiMessage.data);
unusedSeiMessages.push(seiMessage);
SampleSeiMessages sampleSeiMessages = castNonNull(pendingSeiMessages.poll());
for (int i = 0; i < sampleSeiMessages.nalBuffers.size(); i++) {
seiConsumer.consume(
sampleSeiMessages.presentationTimeUs, sampleSeiMessages.nalBuffers.get(i));
unusedParsableByteArrays.push(sampleSeiMessages.nalBuffers.get(i));
}
sampleSeiMessages.nalBuffers.clear();
if (lastQueuedMessage != null
&& lastQueuedMessage.presentationTimeUs == sampleSeiMessages.presentationTimeUs) {
lastQueuedMessage = null;
}
unusedSampleSeiMessages.push(sampleSeiMessages);
}
}
/** Holds data from a SEI sample with its presentation timestamp. */
private static final class SeiMessage implements Comparable<SeiMessage> {
/** Holds the presentation timestamp of a sample and the data from associated SEI messages. */
private static final class SampleSeiMessages implements Comparable<SampleSeiMessages> {
private final ParsableByteArray data;
public final List<ParsableByteArray> nalBuffers;
public long presentationTimeUs;
private long presentationTimeUs;
/**
* {@link PriorityQueue} breaks ties arbitrarily. This field ensures that insertion order is
* preserved when messages have the same {@link #presentationTimeUs}.
*/
private long tieBreak;
public SeiMessage() {
public SampleSeiMessages() {
presentationTimeUs = C.TIME_UNSET;
data = new ParsableByteArray();
nalBuffers = new ArrayList<>();
}
public void reset(long presentationTimeUs, long tieBreak, ParsableByteArray nalBuffer) {
checkState(presentationTimeUs != C.TIME_UNSET);
public void init(long presentationTimeUs, ParsableByteArray nalBuffer) {
checkArgument(presentationTimeUs != C.TIME_UNSET);
checkState(this.nalBuffers.isEmpty());
this.presentationTimeUs = presentationTimeUs;
this.tieBreak = tieBreak;
this.data.reset(nalBuffer.bytesLeft());
System.arraycopy(
/* src= */ nalBuffer.getData(),
/* srcPos= */ nalBuffer.getPosition(),
/* dest= */ data.getData(),
/* destPos= */ 0,
/* length= */ nalBuffer.bytesLeft());
this.nalBuffers.add(nalBuffer);
}
@Override
public int compareTo(SeiMessage other) {
int timeComparison = Long.compare(this.presentationTimeUs, other.presentationTimeUs);
return timeComparison != 0 ? timeComparison : Long.compare(this.tieBreak, other.tieBreak);
public int compareTo(SampleSeiMessages other) {
return Long.compare(this.presentationTimeUs, other.presentationTimeUs);
}
}
}

View file

@ -115,6 +115,41 @@ public final class ReorderingSeiMessageQueueTest {
.containsExactly(new SeiMessage(/* presentationTimeUs= */ -123, data2));
}
@Test
public void withMaxSize_addEmitsWhenQueueIsFull_handlesDuplicateTimestamps() {
ArrayList<SeiMessage> emittedMessages = new ArrayList<>();
ReorderingSeiMessageQueue reorderingQueue =
new ReorderingSeiMessageQueue(
(presentationTimeUs, seiBuffer) ->
emittedMessages.add(new SeiMessage(presentationTimeUs, seiBuffer)));
reorderingQueue.setMaxSize(1);
// Deliberately re-use a single ParsableByteArray instance to ensure the implementation is
// copying as required.
ParsableByteArray scratchData = new ParsableByteArray();
byte[] data1 = TestUtil.buildTestData(20);
scratchData.reset(data1);
reorderingQueue.add(/* presentationTimeUs= */ 345, scratchData);
// Add a message with a repeated timestamp which should not trigger the max size.
byte[] data2 = TestUtil.buildTestData(15);
scratchData.reset(data2);
reorderingQueue.add(/* presentationTimeUs= */ 345, scratchData);
byte[] data3 = TestUtil.buildTestData(10);
scratchData.reset(data3);
reorderingQueue.add(/* presentationTimeUs= */ -123, scratchData);
// Add another message to flush out the two t=345 messages.
byte[] data4 = TestUtil.buildTestData(5);
scratchData.reset(data4);
reorderingQueue.add(/* presentationTimeUs= */ 456, scratchData);
assertThat(emittedMessages)
.containsExactly(
new SeiMessage(/* presentationTimeUs= */ -123, data3),
new SeiMessage(/* presentationTimeUs= */ 345, data1),
new SeiMessage(/* presentationTimeUs= */ 345, data2))
.inOrder();
}
/**
* Tests that if a message smaller than all current queue items is added when the queue is full,
* the same {@link ParsableByteArray} instance is passed straight to the output to avoid

View file

@ -31,6 +31,7 @@ import com.google.common.util.concurrent.ListenableFuture;
import com.google.common.util.concurrent.MoreExecutors;
import java.io.File;
import java.nio.file.Files;
import java.nio.file.Path;
import java.nio.file.Paths;
import java.util.concurrent.ExecutionException;
import okhttp3.mockwebserver.MockResponse;
@ -215,6 +216,28 @@ public class DataSourceBitmapLoaderTest {
assertThat(bitmap.isMutable()).isTrue();
}
@Test
public void loadBitmap_withFileUriAndMaxOutputDimension_loadsDataWithSmallerSize()
throws Exception {
byte[] imageData =
TestUtil.getByteArray(ApplicationProvider.getApplicationContext(), TEST_IMAGE_PATH);
File file = tempFolder.newFile();
Files.write(Path.of(file.getAbsolutePath()), imageData);
Uri uri = Uri.fromFile(file);
int maximumOutputDimension = 2000;
DataSourceBitmapLoader bitmapLoader =
new DataSourceBitmapLoader(
MoreExecutors.newDirectExecutorService(),
dataSourceFactory,
/* options= */ null,
maximumOutputDimension);
Bitmap bitmap = bitmapLoader.loadBitmap(uri).get();
assertThat(bitmap.getWidth()).isAtMost(maximumOutputDimension);
assertThat(bitmap.getHeight()).isAtMost(maximumOutputDimension);
}
@Test
public void loadBitmap_fileUriWithFileNotExisting_throws() {
DataSourceBitmapLoader bitmapLoader =

View file

@ -15,11 +15,11 @@
*/
package androidx.media3.datasource;
import android.net.Uri;
import androidx.media3.test.utils.DataSourceContractTest;
import androidx.media3.test.utils.HttpDataSourceTestEnv;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.collect.ImmutableList;
import java.util.List;
import org.junit.Rule;
import org.junit.runner.RunWith;
@ -40,7 +40,7 @@ public class DefaultHttpDataSourceContractTest extends DataSourceContractTest {
}
@Override
protected Uri getNotFoundUri() {
return Uri.parse(httpDataSourceTestEnv.getNonexistentUrl());
protected List<TestResource> getNotFoundResources() {
return httpDataSourceTestEnv.getNotFoundResources();
}
}

View file

@ -15,13 +15,13 @@
*/
package androidx.media3.datasource;
import android.net.Uri;
import android.net.http.HttpEngine;
import androidx.media3.test.utils.DataSourceContractTest;
import androidx.media3.test.utils.HttpDataSourceTestEnv;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.collect.ImmutableList;
import java.util.List;
import java.util.concurrent.ExecutorService;
import java.util.concurrent.Executors;
import org.junit.After;
@ -53,7 +53,7 @@ public class HttpEngineDataSourceContractTest extends DataSourceContractTest {
}
@Override
protected Uri getNotFoundUri() {
return Uri.parse(httpDataSourceTestEnv.getNonexistentUrl());
protected List<TestResource> getNotFoundResources() {
return httpDataSourceTestEnv.getNotFoundResources();
}
}

View file

@ -15,11 +15,14 @@
*/
package androidx.media3.datasource;
import static java.lang.Math.max;
import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.graphics.Matrix;
import androidx.annotation.Nullable;
import androidx.exifinterface.media.ExifInterface;
import androidx.media3.common.C;
import androidx.media3.common.ParserException;
import androidx.media3.common.util.UnstableApi;
import java.io.ByteArrayInputStream;
@ -38,14 +41,37 @@ public final class BitmapUtil {
*
* @param data Byte array of compressed image data.
* @param length The number of bytes to parse.
* @param options the {@link BitmapFactory.Options} to decode the {@code data} with.
* @param options The {@link BitmapFactory.Options} to decode the {@code data} with.
* @param maximumOutputDimension The largest output Bitmap dimension that can be returned by this
* method, or {@link C#LENGTH_UNSET} if no limits are enforced.
* @throws ParserException if the {@code data} could not be decoded.
*/
// BitmapFactory's options parameter is null-ok.
@SuppressWarnings("nullness:argument.type.incompatible")
public static Bitmap decode(byte[] data, int length, @Nullable BitmapFactory.Options options)
public static Bitmap decode(
byte[] data, int length, @Nullable BitmapFactory.Options options, int maximumOutputDimension)
throws IOException {
if (maximumOutputDimension != C.LENGTH_UNSET) {
if (options == null) {
options = new BitmapFactory.Options();
}
options.inJustDecodeBounds = true;
BitmapFactory.decodeByteArray(data, /* offset= */ 0, length, options);
int largerDimensions = max(options.outWidth, options.outHeight);
options.inJustDecodeBounds = false;
options.inSampleSize = 1;
// Only scaling by 2x is supported.
while (largerDimensions > maximumOutputDimension) {
options.inSampleSize *= 2;
largerDimensions /= 2;
}
}
@Nullable Bitmap bitmap = BitmapFactory.decodeByteArray(data, /* offset= */ 0, length, options);
if (options != null) {
options.inSampleSize = 1;
}
if (bitmap == null) {
throw ParserException.createForMalformedContainer(
"Could not decode image data", new IllegalStateException());

View file

@ -23,6 +23,7 @@ import android.graphics.Bitmap;
import android.graphics.BitmapFactory;
import android.net.Uri;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.util.BitmapLoader;
import androidx.media3.common.util.UnstableApi;
import com.google.common.base.Supplier;
@ -51,6 +52,7 @@ public final class DataSourceBitmapLoader implements BitmapLoader {
private final ListeningExecutorService listeningExecutorService;
private final DataSource.Factory dataSourceFactory;
@Nullable private final BitmapFactory.Options options;
private final int maximumOutputDimension;
/**
* Creates an instance that uses a {@link DefaultHttpDataSource} for image loading and delegates
@ -84,9 +86,29 @@ public final class DataSourceBitmapLoader implements BitmapLoader {
ListeningExecutorService listeningExecutorService,
DataSource.Factory dataSourceFactory,
@Nullable BitmapFactory.Options options) {
this(listeningExecutorService, dataSourceFactory, options, C.LENGTH_UNSET);
}
/**
* Creates an instance that delegates loading tasks to the {@link ListeningExecutorService}.
*
* <p>Use {@code maximumOutputDimension} to limit memory usage when loading large Bitmaps.
*
* @param listeningExecutorService The {@link ListeningExecutorService}.
* @param dataSourceFactory The {@link DataSource.Factory} that creates the {@link DataSource}
* used to load the image.
* @param options The {@link BitmapFactory.Options} the image should be loaded with.
* @param maximumOutputDimension The maximum dimension of the output Bitmap.
*/
public DataSourceBitmapLoader(
ListeningExecutorService listeningExecutorService,
DataSource.Factory dataSourceFactory,
@Nullable BitmapFactory.Options options,
int maximumOutputDimension) {
this.listeningExecutorService = listeningExecutorService;
this.dataSourceFactory = dataSourceFactory;
this.options = options;
this.maximumOutputDimension = maximumOutputDimension;
}
@Override
@ -96,22 +118,27 @@ public final class DataSourceBitmapLoader implements BitmapLoader {
@Override
public ListenableFuture<Bitmap> decodeBitmap(byte[] data) {
return listeningExecutorService.submit(() -> BitmapUtil.decode(data, data.length, options));
return listeningExecutorService.submit(
() -> BitmapUtil.decode(data, data.length, options, maximumOutputDimension));
}
@Override
public ListenableFuture<Bitmap> loadBitmap(Uri uri) {
return listeningExecutorService.submit(
() -> load(dataSourceFactory.createDataSource(), uri, options));
() -> load(dataSourceFactory.createDataSource(), uri, options, maximumOutputDimension));
}
private static Bitmap load(
DataSource dataSource, Uri uri, @Nullable BitmapFactory.Options options) throws IOException {
DataSource dataSource,
Uri uri,
@Nullable BitmapFactory.Options options,
int maximumOutputDimension)
throws IOException {
try {
DataSpec dataSpec = new DataSpec(uri);
dataSource.open(dataSpec);
byte[] readData = DataSourceUtil.readToEnd(dataSource);
return BitmapUtil.decode(readData, readData.length, options);
return BitmapUtil.decode(readData, readData.length, options, maximumOutputDimension);
} finally {
dataSource.close();
}

View file

@ -87,7 +87,7 @@ public final class DefaultDataSource implements DataSource {
*/
public Factory(Context context, DataSource.Factory baseDataSourceFactory) {
this.context = context.getApplicationContext();
this.baseDataSourceFactory = baseDataSourceFactory;
this.baseDataSourceFactory = Assertions.checkNotNull(baseDataSourceFactory);
}
/**

View file

@ -261,7 +261,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
@Nullable private DataSpec dataSpec;
@Nullable private HttpURLConnection connection;
@Nullable private InputStream inputStream;
private boolean opened;
private boolean transferStarted;
private int responseCode;
private long bytesToRead;
private long bytesRead;
@ -296,7 +296,13 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
@Override
@Nullable
public Uri getUri() {
return connection == null ? null : Uri.parse(connection.getURL().toString());
if (connection != null) {
return Uri.parse(connection.getURL().toString());
} else if (dataSpec != null) {
return dataSpec.uri;
} else {
return null;
}
}
@UnstableApi
@ -372,7 +378,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
long documentSize =
HttpUtil.getDocumentSize(connection.getHeaderField(HttpHeaders.CONTENT_RANGE));
if (dataSpec.position == documentSize) {
opened = true;
transferStarted = true;
transferStarted(dataSpec);
return dataSpec.length != C.LENGTH_UNSET ? dataSpec.length : 0;
}
@ -442,7 +448,7 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
HttpDataSourceException.TYPE_OPEN);
}
opened = true;
transferStarted = true;
transferStarted(dataSpec);
try {
@ -493,10 +499,12 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
} finally {
inputStream = null;
closeConnectionQuietly();
if (opened) {
opened = false;
if (transferStarted) {
transferStarted = false;
transferEnded();
}
connection = null;
dataSpec = null;
}
}
@ -787,7 +795,6 @@ public class DefaultHttpDataSource extends BaseDataSource implements HttpDataSou
} catch (Exception e) {
Log.e(TAG, "Unexpected error while disconnecting", e);
}
connection = null;
}
}

View file

@ -38,9 +38,6 @@ import androidx.media3.common.util.Clock;
import androidx.media3.common.util.ConditionVariable;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.datasource.HttpDataSource.CleartextNotPermittedException;
import androidx.media3.datasource.HttpDataSource.HttpDataSourceException;
import androidx.media3.datasource.HttpDataSource.InvalidResponseCodeException;
import com.google.common.base.Ascii;
import com.google.common.base.Predicate;
import com.google.common.net.HttpHeaders;
@ -341,7 +338,7 @@ public final class HttpEngineDataSource extends BaseDataSource implements HttpDa
private final boolean keepPostFor302Redirects;
// Accessed by the calling thread only.
private boolean opened;
private boolean transferStarted;
private long bytesRemaining;
@Nullable private DataSpec currentDataSpec;
@ -430,14 +427,20 @@ public final class HttpEngineDataSource extends BaseDataSource implements HttpDa
@Override
@Nullable
public Uri getUri() {
return responseInfo == null ? null : Uri.parse(responseInfo.getUrl());
if (responseInfo != null) {
return Uri.parse(responseInfo.getUrl());
} else if (currentDataSpec != null) {
return currentDataSpec.uri;
} else {
return null;
}
}
@UnstableApi
@Override
public long open(DataSpec dataSpec) throws HttpDataSourceException {
Assertions.checkNotNull(dataSpec);
Assertions.checkState(!opened);
Assertions.checkState(!transferStarted);
operation.close();
resetConnectTimeout();
@ -499,7 +502,7 @@ public final class HttpEngineDataSource extends BaseDataSource implements HttpDa
long documentSize =
HttpUtil.getDocumentSize(getFirstHeader(responseHeaders, HttpHeaders.CONTENT_RANGE));
if (dataSpec.position == documentSize) {
opened = true;
transferStarted = true;
transferStarted(dataSpec);
return dataSpec.length != C.LENGTH_UNSET ? dataSpec.length : 0;
}
@ -558,7 +561,7 @@ public final class HttpEngineDataSource extends BaseDataSource implements HttpDa
bytesRemaining = dataSpec.length;
}
opened = true;
transferStarted = true;
transferStarted(dataSpec);
skipFully(bytesToSkip, dataSpec);
@ -568,7 +571,7 @@ public final class HttpEngineDataSource extends BaseDataSource implements HttpDa
@UnstableApi
@Override
public int read(byte[] buffer, int offset, int length) throws HttpDataSourceException {
Assertions.checkState(opened);
Assertions.checkState(transferStarted);
if (length == 0) {
return 0;
@ -639,7 +642,7 @@ public final class HttpEngineDataSource extends BaseDataSource implements HttpDa
*/
@UnstableApi
public int read(ByteBuffer buffer) throws HttpDataSourceException {
Assertions.checkState(opened);
Assertions.checkState(transferStarted);
if (!buffer.isDirect()) {
throw new IllegalArgumentException("Passed buffer is not a direct ByteBuffer");
@ -696,8 +699,8 @@ public final class HttpEngineDataSource extends BaseDataSource implements HttpDa
responseInfo = null;
exception = null;
finished = false;
if (opened) {
opened = false;
if (transferStarted) {
transferStarted = false;
transferEnded();
}
}

View file

@ -83,10 +83,18 @@ public final class StatsDataSource implements DataSource {
// Reassign defaults in case dataSource.open throws an exception.
lastOpenedUri = dataSpec.uri;
lastResponseHeaders = Collections.emptyMap();
long availableBytes = dataSource.open(dataSpec);
lastOpenedUri = Assertions.checkNotNull(getUri());
lastResponseHeaders = getResponseHeaders();
return availableBytes;
try {
return dataSource.open(dataSpec);
} finally {
// TODO: b/373321956 - Remove this null-tolerance when we've fixed all DataSource
// implementations to return a non-null URI after a failed open() call and before close()
// (and updated the DataSourceContractTest to enforce this).
Uri upstreamUri = getUri();
if (upstreamUri != null) {
lastOpenedUri = upstreamUri;
}
lastResponseHeaders = getResponseHeaders();
}
}
@Override

View file

@ -32,17 +32,28 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class ResolvingDataSourceContractTest extends DataSourceContractTest {
private static final String URI = "test://simple.test";
private static final String REQUESTED_URI = "test://simple.test";
private static final String RESOLVED_URI = "resolved://simple.resolved";
private static final String REQUESTED_URI_WITH_DIFFERENT_REPORTED =
"test://different.report.test";
private static final String RESOLVED_URI_WITH_DIFFERENT_REPORTED =
"resolved://different.report.test";
private static final String REPORTED_URI = "reported://reported.test";
private byte[] simpleData;
private byte[] differentReportedData;
private FakeDataSet fakeDataSet;
private FakeDataSource fakeDataSource;
@Before
public void setUp() {
simpleData = TestUtil.buildTestData(/* length= */ 20);
fakeDataSet = new FakeDataSet().newData(RESOLVED_URI).appendReadData(simpleData).endData();
differentReportedData = TestUtil.buildTestData(/* length= */ 15);
fakeDataSet =
new FakeDataSet()
.setData(RESOLVED_URI, simpleData)
.setData(RESOLVED_URI_WITH_DIFFERENT_REPORTED, differentReportedData);
}
@Override
@ -50,8 +61,15 @@ public class ResolvingDataSourceContractTest extends DataSourceContractTest {
return ImmutableList.of(
new TestResource.Builder()
.setName("simple")
.setUri(URI)
.setUri(REQUESTED_URI)
.setResolvedUri(RESOLVED_URI)
.setExpectedBytes(simpleData)
.build(),
new TestResource.Builder()
.setName("different-reported")
.setUri(REQUESTED_URI_WITH_DIFFERENT_REPORTED)
.setResolvedUri(REPORTED_URI)
.setExpectedBytes(differentReportedData)
.build());
}
@ -68,9 +86,21 @@ public class ResolvingDataSourceContractTest extends DataSourceContractTest {
new Resolver() {
@Override
public DataSpec resolveDataSpec(DataSpec dataSpec) throws IOException {
return URI.equals(dataSpec.uri.normalizeScheme().toString())
? dataSpec.buildUpon().setUri(RESOLVED_URI).build()
: dataSpec;
switch (dataSpec.uri.normalizeScheme().toString()) {
case REQUESTED_URI:
return dataSpec.buildUpon().setUri(RESOLVED_URI).build();
case REQUESTED_URI_WITH_DIFFERENT_REPORTED:
return dataSpec.buildUpon().setUri(RESOLVED_URI_WITH_DIFFERENT_REPORTED).build();
default:
return dataSpec;
}
}
@Override
public Uri resolveReportedUri(Uri uri) {
return uri.normalizeScheme().toString().equals(RESOLVED_URI_WITH_DIFFERENT_REPORTED)
? Uri.parse(REPORTED_URI)
: uri;
}
});
}

View file

@ -0,0 +1,63 @@
/*
* 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.datasource;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
import android.net.Uri;
import androidx.media3.test.utils.FakeDataSet;
import androidx.media3.test.utils.FakeDataSource;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.io.IOException;
import org.junit.Test;
import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public final class StatsDataSourceTest {
@Test
public void getLastOpenedUri_openSucceeds_returnsRedirectedUriAfterClosure() throws Exception {
Uri redirectedUri = Uri.parse("bar");
FakeDataSet fakeDataSet = new FakeDataSet();
fakeDataSet.setRandomData(redirectedUri, /* length= */ 10);
StatsDataSource statsDataSource =
new StatsDataSource(
new ResolvingDataSource(
new FakeDataSource(fakeDataSet),
dataSpec -> dataSpec.buildUpon().setUri(redirectedUri).build()));
statsDataSource.open(new DataSpec(Uri.parse("foo")));
statsDataSource.close();
assertThat(statsDataSource.getLastOpenedUri()).isEqualTo(redirectedUri);
}
@Test
public void getLastOpenedUri_openFails_returnsRedirectedUriAfterClosure() throws Exception {
Uri redirectedUri = Uri.parse("bar");
StatsDataSource statsDataSource =
new StatsDataSource(
new ResolvingDataSource(
new FakeDataSource(),
dataSpec -> dataSpec.buildUpon().setUri(redirectedUri).build()));
assertThrows(IOException.class, () -> statsDataSource.open(new DataSpec(Uri.parse("foo"))));
statsDataSource.close();
assertThat(statsDataSource.getLastOpenedUri()).isEqualTo(redirectedUri);
}
}

View file

@ -15,7 +15,6 @@
*/
package androidx.media3.datasource.cronet;
import android.net.Uri;
import androidx.media3.datasource.DataSource;
import androidx.media3.test.utils.DataSourceContractTest;
import androidx.media3.test.utils.HttpDataSourceTestEnv;
@ -66,7 +65,7 @@ public class CronetDataSourceContractTest extends DataSourceContractTest {
}
@Override
protected Uri getNotFoundUri() {
return Uri.parse(httpDataSourceTestEnv.getNonexistentUrl());
protected List<TestResource> getNotFoundResources() {
return httpDataSourceTestEnv.getNotFoundResources();
}
}

View file

@ -463,7 +463,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
private final boolean keepPostFor302Redirects;
// Accessed by the calling thread only.
private boolean opened;
private boolean transferStarted;
private long bytesRemaining;
// Written from the calling thread only. currentUrlRequest.start() calls ensure writes are visible
@ -555,14 +555,20 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
@Override
@Nullable
public Uri getUri() {
return responseInfo == null ? null : Uri.parse(responseInfo.getUrl());
if (responseInfo != null) {
return Uri.parse(responseInfo.getUrl());
} else if (currentDataSpec != null) {
return currentDataSpec.uri;
} else {
return null;
}
}
@UnstableApi
@Override
public long open(DataSpec dataSpec) throws HttpDataSourceException {
Assertions.checkNotNull(dataSpec);
Assertions.checkState(!opened);
Assertions.checkState(!transferStarted);
operation.close();
resetConnectTimeout();
@ -624,7 +630,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
long documentSize =
HttpUtil.getDocumentSize(getFirstHeader(responseHeaders, HttpHeaders.CONTENT_RANGE));
if (dataSpec.position == documentSize) {
opened = true;
transferStarted = true;
transferStarted(dataSpec);
return dataSpec.length != C.LENGTH_UNSET ? dataSpec.length : 0;
}
@ -683,7 +689,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
bytesRemaining = dataSpec.length;
}
opened = true;
transferStarted = true;
transferStarted(dataSpec);
skipFully(bytesToSkip, dataSpec);
@ -693,7 +699,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
@UnstableApi
@Override
public int read(byte[] buffer, int offset, int length) throws HttpDataSourceException {
Assertions.checkState(opened);
Assertions.checkState(transferStarted);
if (length == 0) {
return 0;
@ -764,7 +770,7 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
*/
@UnstableApi
public int read(ByteBuffer buffer) throws HttpDataSourceException {
Assertions.checkState(opened);
Assertions.checkState(transferStarted);
if (!buffer.isDirect()) {
throw new IllegalArgumentException("Passed buffer is not a direct ByteBuffer");
@ -818,8 +824,8 @@ public class CronetDataSource extends BaseDataSource implements HttpDataSource {
responseInfo = null;
exception = null;
finished = false;
if (opened) {
opened = false;
if (transferStarted) {
transferStarted = false;
transferEnded();
}
}

View file

@ -192,7 +192,7 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
@Nullable private DataSpec dataSpec;
@Nullable private Response response;
@Nullable private InputStream responseByteStream;
private boolean opened;
private boolean connectionEstablished;
private long bytesToRead;
private long bytesRead;
@ -215,7 +215,13 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
@Override
@Nullable
public Uri getUri() {
return response == null ? null : Uri.parse(response.request().url().toString());
if (response != null) {
return Uri.parse(response.request().url().toString());
} else if (dataSpec != null) {
return dataSpec.uri;
} else {
return null;
}
}
@UnstableApi
@ -281,7 +287,7 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
long documentSize =
HttpUtil.getDocumentSize(response.headers().get(HttpHeaders.CONTENT_RANGE));
if (dataSpec.position == documentSize) {
opened = true;
connectionEstablished = true;
transferStarted(dataSpec);
return dataSpec.length != C.LENGTH_UNSET ? dataSpec.length : 0;
}
@ -325,7 +331,7 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
bytesToRead = contentLength != -1 ? (contentLength - bytesToSkip) : C.LENGTH_UNSET;
}
opened = true;
connectionEstablished = true;
transferStarted(dataSpec);
try {
@ -352,11 +358,13 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
@UnstableApi
@Override
public void close() {
if (opened) {
opened = false;
if (connectionEstablished) {
connectionEstablished = false;
transferEnded();
closeConnectionQuietly();
}
response = null;
dataSpec = null;
}
/** Establishes a connection. */
@ -524,7 +532,6 @@ public class OkHttpDataSource extends BaseDataSource implements HttpDataSource {
private void closeConnectionQuietly() {
if (response != null) {
Assertions.checkNotNull(response.body()).close();
response = null;
}
responseByteStream = null;
}

View file

@ -15,12 +15,12 @@
*/
package androidx.media3.datasource.okhttp;
import android.net.Uri;
import androidx.media3.datasource.DataSource;
import androidx.media3.test.utils.DataSourceContractTest;
import androidx.media3.test.utils.HttpDataSourceTestEnv;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.collect.ImmutableList;
import java.util.List;
import okhttp3.OkHttpClient;
import org.junit.Rule;
import org.junit.runner.RunWith;
@ -42,7 +42,7 @@ public class OkHttpDataSourceContractTest extends DataSourceContractTest {
}
@Override
protected Uri getNotFoundUri() {
return Uri.parse(httpDataSourceTestEnv.getNonexistentUrl());
protected List<TestResource> getNotFoundResources() {
return httpDataSourceTestEnv.getNotFoundResources();
}
}

View file

@ -27,6 +27,14 @@ android {
}
}
}
// TODO(Internal: b/372449691): Remove packagingOptions once AGP is updated
// to version 8.5.1 or higher.
packagingOptions {
jniLibs {
useLegacyPackaging true
}
}
}
// Configure the native build only if libgav1 is present to avoid gradle sync
@ -51,4 +59,9 @@ dependencies {
implementation project(modulePrefix + 'lib-exoplayer')
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
testImplementation project(modulePrefix + 'test-utils')
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
androidTestImplementation project(modulePrefix + 'test-utils')
androidTestImplementation 'androidx.test:runner:' + androidxTestRunnerVersion
androidTestImplementation 'androidx.test.ext:junit:' + androidxTestJUnitVersion
}

View file

@ -61,3 +61,7 @@ target_link_libraries(gav1JNI
PRIVATE libgav1_static
PRIVATE ${android_log_lib})
# Enable 16 KB ELF alignment.
target_link_options(gav1JNI
PRIVATE "-Wl,-z,max-page-size=16384")

View file

@ -19,12 +19,23 @@
#include <android/native_window_jni.h>
#include "cpu_features_macros.h" // NOLINT
// For ARMv7, we use `cpu_feature` to detect availability of NEON at runtime.
#ifdef CPU_FEATURES_ARCH_ARM
#include "cpuinfo_arm.h" // NOLINT
#endif // CPU_FEATURES_ARCH_ARM
#ifdef CPU_FEATURES_COMPILED_ANY_ARM_NEON
#include <arm_neon.h>
// For ARM in general (v7/v8) we detect compile time availability of NEON.
#ifdef CPU_FEATURES_ARCH_ANY_ARM
#if CPU_FEATURES_COMPILED_ANY_ARM_NEON // always defined to 0 or 1.
#define HAS_COMPILE_TIME_NEON_SUPPORT
#endif // CPU_FEATURES_COMPILED_ANY_ARM_NEON
#endif // CPU_FEATURES_ARCH_ANY_ARM
#ifdef HAS_COMPILE_TIME_NEON_SUPPORT
#include <arm_neon.h>
#endif
#include <jni.h>
#include <cstdint>
@ -400,7 +411,7 @@ void Convert10BitFrameTo8BitDataBuffer(
}
}
#ifdef CPU_FEATURES_COMPILED_ANY_ARM_NEON
#ifdef HAS_COMPILE_TIME_NEON_SUPPORT
void Convert10BitFrameTo8BitDataBufferNeon(
const libgav1::DecoderBuffer* decoder_buffer, jbyte* data) {
uint32x2_t lcg_value = vdup_n_u32(random());
@ -497,7 +508,7 @@ void Convert10BitFrameTo8BitDataBufferNeon(
}
}
}
#endif // CPU_FEATURES_COMPILED_ANY_ARM_NEON
#endif // HAS_COMPILE_TIME_NEON_SUPPORT
} // namespace
@ -507,20 +518,19 @@ DECODER_FUNC(jlong, gav1Init, jint threads) {
return kStatusError;
}
#ifdef CPU_FEATURES_ARCH_ARM
// Libgav1 requires NEON with arm ABIs.
#ifdef CPU_FEATURES_COMPILED_ANY_ARM_NEON
const cpu_features::ArmFeatures arm_features =
cpu_features::GetArmInfo().features;
if (!arm_features.neon) {
#ifdef CPU_FEATURES_ARCH_ANY_ARM // Arm v7/v8
#ifndef HAS_COMPILE_TIME_NEON_SUPPORT // no compile time NEON support
#ifdef CPU_FEATURES_ARCH_ARM // check runtime support for ARMv7
if (cpu_features::GetArmInfo().features.neon == false) {
context->jni_status_code = kJniStatusNeonNotSupported;
return reinterpret_cast<jlong>(context);
}
#else
#else // Unexpected case of an ARMv8 with no NEON support.
context->jni_status_code = kJniStatusNeonNotSupported;
return reinterpret_cast<jlong>(context);
#endif // CPU_FEATURES_COMPILED_ANY_ARM_NEON
#endif // CPU_FEATURES_ARCH_ARM
#endif // HAS_COMPILE_TIME_NEON_SUPPORT
#endif // CPU_FEATURES_ARCH_ANY_ARM
libgav1::DecoderSettings settings;
settings.threads = threads;
@ -613,11 +623,11 @@ DECODER_FUNC(jint, gav1GetFrame, jlong jContext, jobject jOutputBuffer,
CopyFrameToDataBuffer(decoder_buffer, data);
break;
case 10:
#ifdef CPU_FEATURES_COMPILED_ANY_ARM_NEON
#ifdef HAS_COMPILE_TIME_NEON_SUPPORT
Convert10BitFrameTo8BitDataBufferNeon(decoder_buffer, data);
#else
Convert10BitFrameTo8BitDataBuffer(decoder_buffer, data);
#endif // CPU_FEATURES_COMPILED_ANY_ARM_NEON
#endif // HAS_COMPILE_TIME_NEON_SUPPORT
break;
default:
context->jni_status_code = kJniStatusBitDepth12NotSupportedWithYuv;

View file

@ -0,0 +1,19 @@
<?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.
-->
<manifest package="androidx.media3.decoder.av1.test">
<uses-sdk/>
</manifest>

View file

@ -0,0 +1,33 @@
/*
* 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.decoder.av1;
import androidx.media3.common.C;
import androidx.media3.test.utils.DefaultRenderersFactoryAsserts;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Unit test for {@link DefaultRenderersFactoryTest} with {@link Libgav1VideoRenderer}. */
@RunWith(AndroidJUnit4.class)
public final class DefaultRenderersFactoryTest {
@Test
public void createRenderers_instantiatesAv1Renderer() {
DefaultRenderersFactoryAsserts.assertExtensionRendererCreated(
Libgav1VideoRenderer.class, C.TRACK_TYPE_VIDEO);
}
}

View file

@ -13,7 +13,17 @@
// limitations under the License.
apply from: "$gradle.ext.androidxMediaSettingsDir/common_library_config.gradle"
android.namespace = 'androidx.media3.decoder.ffmpeg'
android {
namespace 'androidx.media3.decoder.ffmpeg'
// TODO(Internal: b/372449691): Remove packagingOptions once AGP is updated
// to version 8.5.1 or higher.
packagingOptions {
jniLibs {
useLegacyPackaging true
}
}
}
// Configure the native build only if ffmpeg is present to avoid gradle sync
// failures if ffmpeg hasn't been built according to the README instructions.

View file

@ -56,3 +56,7 @@ target_link_libraries(ffmpegJNI
if(ANDROID_ABI STREQUAL "arm64-v8a")
target_link_options(ffmpegJNI PRIVATE "-Wl,-Bsymbolic")
endif()
# Enable 16 KB ELF alignment.
target_link_options(ffmpegJNI
PRIVATE "-Wl,-z,max-page-size=16384")

View file

@ -17,7 +17,7 @@ To use the module you need to clone this GitHub project and depend on its
modules locally. Instructions for doing this can be found in the
[top level README][].
In addition, it's necessary to build the module's native components as follows:
In addition, it's necessary to fetch libflac as follows:
* Set the following environment variables:
@ -26,30 +26,24 @@ cd "<path to project checkout>"
FLAC_MODULE_PATH="$(pwd)/libraries/decoder_flac/src/main"
```
* Download the [Android NDK][] and set its location in an environment variable.
This build configuration has been tested on NDK r21.
```
NDK_PATH="<path to Android NDK>"
```
* Download and extract flac-1.3.2 as "${FLAC_MODULE_PATH}/jni/flac" folder:
* Fetch libflac:
```
cd "${FLAC_MODULE_PATH}/jni" && \
curl https://ftp.osuosl.org/pub/xiph/releases/flac/flac-1.3.2.tar.xz | tar xJ && \
mv flac-1.3.2 flac
git clone https://github.com/xiph/flac.git libflac
```
* Build the JNI native libraries from the command line:
* [Install CMake][]
```
cd "${FLAC_MODULE_PATH}"/jni && \
${NDK_PATH}/ndk-build APP_ABI=all -j4
```
Having followed these steps, gradle will build the module automatically when run
on the command line or via Android Studio, using [CMake][] and [Ninja][] to
configure and build libflac and the module's [JNI wrapper library][].
[top level README]: ../../README.md
[Android NDK]: https://developer.android.com/tools/sdk/ndk/index.html
[Install CMake]: https://developer.android.com/studio/projects/install-ndk
[CMake]: https://cmake.org/
[Ninja]: https://ninja-build.org
[JNI wrapper library]: src/main/jni/flac_jni.cc
## Build instructions (Windows)

View file

@ -17,12 +17,42 @@ android {
namespace 'androidx.media3.decoder.flac'
sourceSets {
main {
jniLibs.srcDir 'src/main/libs'
jni.srcDirs = [] // Disable the automatic ndk-build call by Android Studio.
}
androidTest.assets.srcDir '../test_data/src/test/assets'
}
defaultConfig {
externalNativeBuild {
cmake {
arguments "-DWITH_OGG=OFF"
arguments "-DINSTALL_MANPAGES=OFF"
targets "flacJNI"
}
}
}
// TODO(Internal: b/372449691): Remove packagingOptions once AGP is updated
// to version 8.5.1 or higher.
packagingOptions {
jniLibs {
useLegacyPackaging true
}
}
}
// Configure the native build only if libflac is present to avoid gradle sync
// failures if libflac hasn't been built according to the README instructions.
if (project.file('src/main/jni/libflac').exists()) {
android.externalNativeBuild.cmake {
path = 'src/main/jni/CMakeLists.txt'
version = '3.21.0+'
if (project.hasProperty('externalNativeBuildDir')) {
if (!new File(externalNativeBuildDir).isAbsolute()) {
ext.externalNativeBuildDir =
new File(rootDir, it.externalNativeBuildDir)
}
buildStagingDirectory = "${externalNativeBuildDir}/${project.name}"
}
}
}
dependencies {

View file

@ -29,7 +29,6 @@ import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import java.io.IOException;
import java.util.List;
import org.junit.Ignore;
import org.junit.Test;
import org.junit.runner.RunWith;
@ -76,7 +75,6 @@ public final class FlacExtractorSeekTest {
fileName, trackOutput, targetSeekTimeUs, extractedFrameIndex);
}
@Ignore("Fix [internal: b/249505968] before enabling this.")
@Test
public void seeking_seekTable_handlesSeekToEoF() throws IOException {
String fileName = TEST_FILE_SEEK_TABLE;
@ -94,7 +92,6 @@ public final class FlacExtractorSeekTest {
fileName, trackOutput, targetSeekTimeUs, extractedFrameIndex);
}
@Ignore("Fix [internal: b/249505968] before enabling this.")
@Test
public void seeking_seekTable_handlesSeekingBackward() throws IOException {
String fileName = TEST_FILE_SEEK_TABLE;
@ -114,7 +111,6 @@ public final class FlacExtractorSeekTest {
fileName, trackOutput, targetSeekTimeUs, extractedFrameIndex);
}
@Ignore("Fix [internal: b/249505968] before enabling this.")
@Test
public void seeking_seekTable_handlesSeekingForward() throws IOException {
String fileName = TEST_FILE_SEEK_TABLE;
@ -162,7 +158,6 @@ public final class FlacExtractorSeekTest {
fileName, trackOutput, targetSeekTimeUs, extractedFrameIndex);
}
@Ignore("Fix [internal: b/249505968] before enabling this.")
@Test
public void seeking_binarySearch_handlesSeekToEoF() throws IOException {
String fileName = TEST_FILE_BINARY_SEARCH;
@ -180,7 +175,6 @@ public final class FlacExtractorSeekTest {
fileName, trackOutput, targetSeekTimeUs, extractedFrameIndex);
}
@Ignore("Fix [internal: b/249505968] before enabling this.")
@Test
public void seeking_binarySearch_handlesSeekingBackward() throws IOException {
String fileName = TEST_FILE_BINARY_SEARCH;
@ -200,7 +194,6 @@ public final class FlacExtractorSeekTest {
fileName, trackOutput, targetSeekTimeUs, extractedFrameIndex);
}
@Ignore("Fix [internal: b/249505968] before enabling this.")
@Test
public void seeking_binarySearch_handlesSeekingForward() throws IOException {
String fileName = TEST_FILE_BINARY_SEARCH;

View file

@ -1,38 +0,0 @@
#
# Copyright (C) 2016 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.
#
WORKING_DIR := $(call my-dir)
# build libflacJNI.so
include $(CLEAR_VARS)
include $(WORKING_DIR)/flac_sources.mk
LOCAL_PATH := $(WORKING_DIR)
LOCAL_MODULE := libflacJNI
LOCAL_ARM_MODE := arm
LOCAL_CPP_EXTENSION := .cc
LOCAL_C_INCLUDES := \
$(LOCAL_PATH)/flac/include \
$(LOCAL_PATH)/flac/src/libFLAC/include
LOCAL_SRC_FILES := $(FLAC_SOURCES)
LOCAL_CFLAGS += '-DPACKAGE_VERSION="1.3.2"' -DFLAC__NO_MD5 -DFLAC__INTEGER_ONLY_LIBRARY
LOCAL_CFLAGS += -D_REENTRANT -DPIC -DU_COMMON_IMPLEMENTATION -fPIC -DHAVE_SYS_PARAM_H
LOCAL_CFLAGS += -O3 -funroll-loops -finline-functions -DFLAC__NO_ASM '-DFLAC__HAS_OGG=0'
LOCAL_LDLIBS := -llog -lz -lm
include $(BUILD_SHARED_LIBRARY)

View file

@ -1,20 +0,0 @@
#
# Copyright (C) 2016 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.
#
APP_OPTIM := release
APP_STL := c++_static
APP_CPPFLAGS := -frtti
APP_PLATFORM := android-14

View file

@ -0,0 +1,51 @@
#
# Copyright 2024 The Android Open Source Project
#
# Licensed under the Apache License, Version 2.0 (the "License");
# you may not use this file except in compliance with the License.
# You may obtain a copy of the License at
#
# 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.
#
cmake_minimum_required(VERSION 3.21.0 FATAL_ERROR)
# Enable C++11 features.
set(CMAKE_CXX_STANDARD 11)
# Define project name for your JNI module
project(libflacJNI C CXX)
set(libflac_jni_root "${CMAKE_CURRENT_SOURCE_DIR}")
# Build libflac.
add_subdirectory("${libflac_jni_root}/libflac"
EXCLUDE_FROM_ALL)
# Build libflacJNI.
add_library(flacJNI
SHARED
flac_jni.cc
flac_parser.cc)
# Add the include directory from libflac.
include_directories("${libflac_jni_root}/libflac/include")
# Locate NDK log library.
find_library(android_log_lib log)
# Link libflacJNI against used libraries.
target_link_libraries(flacJNI
PRIVATE android
PRIVATE FLAC
PRIVATE ${android_log_lib})
# Enable 16 KB ELF alignment.
target_link_options(flacJNI
PRIVATE "-Wl,-z,max-page-size=16384")

View file

@ -1,45 +0,0 @@
#
# Copyright (C) 2016 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.
#
FLAC_SOURCES = \
flac_jni.cc \
flac_parser.cc \
flac/src/libFLAC/bitmath.c \
flac/src/libFLAC/bitreader.c \
flac/src/libFLAC/bitwriter.c \
flac/src/libFLAC/cpu.c \
flac/src/libFLAC/crc.c \
flac/src/libFLAC/fixed.c \
flac/src/libFLAC/fixed_intrin_sse2.c \
flac/src/libFLAC/fixed_intrin_ssse3.c \
flac/src/libFLAC/float.c \
flac/src/libFLAC/format.c \
flac/src/libFLAC/lpc.c \
flac/src/libFLAC/lpc_intrin_avx2.c \
flac/src/libFLAC/lpc_intrin_sse2.c \
flac/src/libFLAC/lpc_intrin_sse41.c \
flac/src/libFLAC/lpc_intrin_sse.c \
flac/src/libFLAC/md5.c \
flac/src/libFLAC/memory.c \
flac/src/libFLAC/metadata_iterators.c \
flac/src/libFLAC/metadata_object.c \
flac/src/libFLAC/stream_decoder.c \
flac/src/libFLAC/stream_encoder.c \
flac/src/libFLAC/stream_encoder_framing.c \
flac/src/libFLAC/stream_encoder_intrin_avx2.c \
flac/src/libFLAC/stream_encoder_intrin_sse2.c \
flac/src/libFLAC/stream_encoder_intrin_ssse3.c \
flac/src/libFLAC/window.c

View file

@ -19,15 +19,15 @@
#include <stdint.h>
// libFLAC parser
#include <FLAC/stream_decoder.h>
#include <array>
#include <cstdlib>
#include <string>
#include <vector>
// libFLAC parser
#include "FLAC/stream_decoder.h"
#include "include/data_source.h"
#include "../include/data_source.h"
typedef int status_t;

View file

@ -1,7 +1,7 @@
# IAMF decoder module
The IAMF module provides `LibiamfAudioRenderer`, which uses libiamf (the IAMF
decoding library) to decode IAMF audio.
The IAMF module provides `LibiamfAudioRenderer`, which uses the libiamf native
library to decode IAMF audio.
## License note
@ -17,58 +17,33 @@ To use the module you need to clone this GitHub project and depend on its
modules locally. Instructions for doing this can be found in the
[top level README][].
In addition, it's necessary to build the module's native components as follows:
In addition, it's necessary to fetch libiamf as follows:
* Set the following environment variables:
```
cd "<path to project checkout>"
IAMF_MODULE_PATH="$(pwd)/libraries/decoder_iamf/src/main"
```
* Download the [Android NDK][] and set its location in an environment
variable. This build configuration has been tested on NDK r27.
* Fetch libiamf:
```
NDK_PATH="<path to Android NDK>"
cd "${IAMF_MODULE_PATH}/jni" && \
git clone https://github.com/AOMediaCodec/libiamf.git
```
* Fetch libiamf:
* [Install CMake][]
Clone the repository containing libiamf to a local folder of choice - preferably
outside of the project checkout. Link it to the project's jni folder through
symlink.
```
cd <preferred location for libiamf>
git clone https://github.com/AOMediaCodec/libiamf.git libiamf && \
cd libiamf && \
LIBIAMF_PATH=$(pwd)
```
* Symlink the folder containing libiamf to the project's JNI folder and run
the script to convert libiamf code to NDK compatible format:
```
cd "${IAMF_MODULE_PATH}"/jni && \
ln -s $LIBIAMF_PATH libiamf && \
cd libiamf/code &&\
cmake . && \
make
```
* Build the JNI native libraries from the command line:
```
cd "${IAMF_MODULE_PATH}"/jni && \
${NDK_PATH}/ndk-build APP_ABI=all -j4
```
Having followed these steps, gradle will build the module automatically when run
on the command line or via Android Studio, using [CMake][] and [Ninja][] to
configure and build libiamf and the module's [JNI wrapper library][].
[top level README]: ../../README.md
[Android NDK]: https://developer.android.com/tools/sdk/ndk/index.html
[Install CMake]: https://developer.android.com/studio/projects/install-ndk
[CMake]: https://cmake.org/
[Ninja]: https://ninja-build.org
[JNI wrapper library]: src/main/jni/iamf_jni.cc
## Build instructions (Windows)
@ -77,13 +52,6 @@ be possible to follow the Linux instructions in [Windows PowerShell][].
[Windows PowerShell]: https://docs.microsoft.com/en-us/powershell/scripting/getting-started/getting-started-with-windows-powershell
## Notes
* Every time there is a change to the libiamf checkout clean and re-build the
project.
* If you want to use your own version of libiamf, place it in
`${IAMF_MODULE_PATH}/jni/libiamf`.
## Using the module with ExoPlayer
Once you've followed the instructions above to check out, build and depend on

View file

@ -1,4 +1,4 @@
// Copyright (C) 2024 The Android Open Source Project
// 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.
@ -17,12 +17,40 @@ android {
namespace 'androidx.media3.decoder.iamf'
sourceSets {
main {
jniLibs.srcDir 'src/main/libs'
jni.srcDirs = [] // Disable the automatic ndk-build call by Android Studio.
}
androidTest.assets.srcDir '../test_data/src/test/assets'
}
defaultConfig {
externalNativeBuild {
cmake {
targets "iamfJNI"
}
}
}
// TODO(Internal: b/372449691): Remove packagingOptions once AGP is updated
// to version 8.5.1 or higher.
packagingOptions {
jniLibs {
useLegacyPackaging true
}
}
}
// Configure the native build only if libiamf is present to avoid gradle sync
// failures if libiamf hasn't been built according to the README instructions.
if (project.file('src/main/jni/libiamf').exists()) {
android.externalNativeBuild.cmake {
path = 'src/main/jni/CMakeLists.txt'
version = '3.21.0+'
if (project.hasProperty('externalNativeBuildDir')) {
if (!new File(externalNativeBuildDir).isAbsolute()) {
ext.externalNativeBuildDir =
new File(rootDir, it.externalNativeBuildDir)
}
buildStagingDirectory = "${externalNativeBuildDir}/${project.name}"
}
}
}
dependencies {

View file

@ -16,6 +16,7 @@
package androidx.media3.decoder.iamf;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assume.assumeTrue;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.collect.ImmutableList;
@ -40,7 +41,7 @@ public final class IamfDecoderTest {
@Before
public void setUp() {
assertThat(IamfLibrary.isAvailable()).isTrue();
assumeTrue(IamfLibrary.isAvailable());
}
@Test

View file

@ -56,6 +56,9 @@ public final class IamfDecoder
public IamfDecoder(List<byte[]> initializationData, boolean spatializationSupported)
throws IamfDecoderException {
super(new DecoderInputBuffer[1], new SimpleDecoderOutputBuffer[1]);
if (!IamfLibrary.isAvailable()) {
throw new IamfDecoderException("Failed to load decoder native libraries.");
}
if (initializationData.size() != 1) {
throw new IamfDecoderException("Initialization data must contain a single element.");
}

View file

@ -15,10 +15,12 @@
*/
package androidx.media3.decoder.iamf;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.decoder.DecoderException;
/** Thrown when an IAMF decoder error occurs. */
final class IamfDecoderException extends DecoderException {
@UnstableApi
public final class IamfDecoderException extends DecoderException {
/* package */ IamfDecoderException(String message) {
super(message);

View file

@ -15,7 +15,6 @@
*/
package androidx.media3.decoder.iamf;
import androidx.media3.common.C;
import androidx.media3.common.MediaLibraryInfo;
import androidx.media3.common.util.LibraryLoader;
import androidx.media3.common.util.UnstableApi;
@ -43,12 +42,9 @@ public final class IamfLibrary {
* it must do so before calling any other method defined by this class, and before instantiating a
* {@link LibiamfAudioRenderer} instance.
*
* @param cryptoType The {@link C.CryptoType} for which the decoder library supports decrypting
* protected content, or {@link C#CRYPTO_TYPE_UNSUPPORTED} if the library does not support
* decryption.
* @param libraries The names of the IAMF native libraries.
*/
public static void setLibraries(@C.CryptoType int cryptoType, String... libraries) {
public static void setLibraries(String... libraries) {
LOADER.setLibraries(libraries);
}

View file

@ -27,6 +27,7 @@ import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.TraceUtil;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.decoder.CryptoConfig;
import androidx.media3.decoder.DecoderException;
@ -36,6 +37,7 @@ import androidx.media3.exoplayer.audio.DecoderAudioRenderer;
import java.util.Objects;
/** Decodes and renders audio using the native IAMF decoder. */
@UnstableApi
public class LibiamfAudioRenderer extends DecoderAudioRenderer<IamfDecoder> {
private final Context context;
@ -94,18 +96,19 @@ public class LibiamfAudioRenderer extends DecoderAudioRenderer<IamfDecoder> {
}
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
AudioFormat.Builder audioFormat =
new AudioFormat.Builder()
.setEncoding(IamfDecoder.OUTPUT_PCM_ENCODING)
.setChannelMask(IamfDecoder.SPATIALIZED_OUTPUT_LAYOUT);
if (audioManager == null) {
return false;
}
AudioFormat audioFormat =
new AudioFormat.Builder()
.setEncoding(IamfDecoder.OUTPUT_PCM_ENCODING)
.setChannelMask(IamfDecoder.SPATIALIZED_OUTPUT_LAYOUT)
.build();
Spatializer spatializer = audioManager.getSpatializer();
return spatializer.getImmersiveAudioLevel() != Spatializer.SPATIALIZER_IMMERSIVE_LEVEL_NONE
&& spatializer.isAvailable()
&& spatializer.isEnabled()
&& spatializer.canBeSpatialized(
AudioAttributes.DEFAULT.getAudioAttributesV21().audioAttributes, audioFormat.build());
AudioAttributes.DEFAULT.getAudioAttributesV21().audioAttributes, audioFormat);
}
}

View file

@ -1,32 +0,0 @@
#
# 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
#
# 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.
#
WORKING_DIR := $(call my-dir)
include $(CLEAR_VARS)
# build libiamf.a
LOCAL_PATH := $(WORKING_DIR)
include libiamf.mk
# build libiamfJNI.so
include $(CLEAR_VARS)
LOCAL_PATH := $(WORKING_DIR)
LOCAL_MODULE := libiamfJNI
LOCAL_ARM_MODE := arm
LOCAL_CPP_EXTENSION := .cc
LOCAL_SRC_FILES := iamf_jni.cc
LOCAL_LDLIBS := -llog -lz -lm
LOCAL_STATIC_LIBRARIES := libiamf
include $(BUILD_SHARED_LIBRARY)

View file

@ -1,18 +0,0 @@
#
# 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
#
# 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.
#
APP_ABI := all
APP_PLATFORM := android-21

View file

@ -0,0 +1,50 @@
#
# 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.
#
cmake_minimum_required(VERSION 3.21.0 FATAL_ERROR)
# Enable C++11 features.
set(CMAKE_CXX_STANDARD 11)
# Define project name for your JNI module
project(libiamfJNI C CXX)
set(libiamf_jni_root "${CMAKE_CURRENT_SOURCE_DIR}")
# Build libiamf.
add_subdirectory("${libiamf_jni_root}/libiamf/code"
EXCLUDE_FROM_ALL)
# Add the include directory from libiamf.
include_directories ("${libiamf_jni_root}/libiamf/code/include")
# Build libiamfJNI.
add_library(iamfJNI
SHARED
iamf_jni.cc)
# Locate NDK log library.
find_library(android_log_lib log)
# Link libiamfJNI against used libraries.
target_link_libraries(iamfJNI
PRIVATE android
PRIVATE iamf
PRIVATE ${android_log_lib})
# Enable 16 KB ELF alignment.
target_link_options(iamfJNI
PRIVATE "-Wl,-z,max-page-size=16384")

View file

@ -1,35 +0,0 @@
#
# 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
#
# 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.
#
LOCAL_PATH := $(WORKING_DIR)/libiamf
include $(CLEAR_VARS)
LOCAL_MODULE := libiamf
LOCAL_ARM_MODE := arm
LOCAL_C_INCLUDES := $(LOCAL_PATH)/code/include \
$(LOCAL_PATH)/code/src/iamf_dec \
$(LOCAL_PATH)/code/src/common \
$(LOCAL_PATH)/code/dep_codecs/include \
$(LOCAL_PATH)/code/dep_external/include
LOCAL_SRC_FILES := $(shell find $(LOCAL_PATH)/code/src -name "*.c")
LOCAL_EXPORT_C_INCLUDES := $(LOCAL_PATH)/code/include \
$(LOCAL_PATH)/code/src/iamf_dec \
$(LOCAL_PATH)/code/src/common \
$(LOCAL_PATH)/code/dep_codecs/include \
$(LOCAL_PATH)/code/dep_external/include
include $(BUILD_STATIC_LIBRARY)

View file

@ -0,0 +1,19 @@
<?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.
-->
<manifest package="androidx.media3.decoder.iamf.test">
<uses-sdk/>
</manifest>

View file

@ -0,0 +1,33 @@
/*
* 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.decoder.iamf;
import androidx.media3.common.C;
import androidx.media3.test.utils.DefaultRenderersFactoryAsserts;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Unit test for {@link DefaultRenderersFactoryTest} with {@link LibiamfAudioRenderer}. */
@RunWith(AndroidJUnit4.class)
public final class DefaultRenderersFactoryTest {
@Test
public void createRenderers_instantiatesIamfRenderer() {
DefaultRenderersFactoryAsserts.assertExtensionRendererCreated(
LibiamfAudioRenderer.class, C.TRACK_TYPE_AUDIO);
}
}

View file

@ -0,0 +1,33 @@
/*
* 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.decoder.midi;
import androidx.media3.common.C;
import androidx.media3.test.utils.DefaultRenderersFactoryAsserts;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import org.junit.Test;
import org.junit.runner.RunWith;
/** Unit test for {@link DefaultRenderersFactoryTest} with {@link MidiRenderer}. */
@RunWith(AndroidJUnit4.class)
public final class DefaultRenderersFactoryTest {
@Test
public void createRenderers_instantiatesMidiRenderer() {
DefaultRenderersFactoryAsserts.assertExtensionRendererCreated(
MidiRenderer.class, C.TRACK_TYPE_AUDIO);
}
}

View file

@ -1,7 +1,7 @@
# Opus decoder module
The Opus module provides `LibopusAudioRenderer`, which uses libopus (the Opus
decoding library) to decode Opus audio.
The Opus module provides `LibopusAudioRenderer`, which uses the libopus native
library to decode Opus audio.
## License note
@ -17,7 +17,7 @@ To use the module you need to clone this GitHub project and depend on its
modules locally. Instructions for doing this can be found in the
[top level README][].
In addition, it's necessary to build the module's native components as follows:
In addition, it's necessary to fetch libopus as follows:
* Set the following environment variables:
@ -26,13 +26,6 @@ cd "<path to project checkout>"
OPUS_MODULE_PATH="$(pwd)/libraries/decoder_opus/src/main"
```
* Download the [Android NDK][] and set its location in an environment variable.
This build configuration has been tested on NDK r21.
```
NDK_PATH="<path to Android NDK>"
```
* Fetch libopus:
```
@ -40,21 +33,17 @@ cd "${OPUS_MODULE_PATH}/jni" && \
git clone https://gitlab.xiph.org/xiph/opus.git libopus
```
* Run the script to convert arm assembly to NDK compatible format:
* [Install CMake][]
```
cd ${OPUS_MODULE_PATH}/jni && ./convert_android_asm.sh
```
* Build the JNI native libraries from the command line:
```
cd "${OPUS_MODULE_PATH}"/jni && \
${NDK_PATH}/ndk-build APP_ABI=all -j4
```
Having followed these steps, gradle will build the module automatically when run
on the command line or via Android Studio, using [CMake][] and [Ninja][] to
configure and build libopus and the module's [JNI wrapper library][].
[top level README]: ../../README.md
[Android NDK]: https://developer.android.com/tools/sdk/ndk/index.html
[Install CMake]: https://developer.android.com/studio/projects/install-ndk
[CMake]: https://cmake.org/
[Ninja]: https://ninja-build.org
[JNI wrapper library]: src/main/jni/opus_jni.cc
## Build instructions (Windows)
@ -63,14 +52,6 @@ be possible to follow the Linux instructions in [Windows PowerShell][].
[Windows PowerShell]: https://docs.microsoft.com/en-us/powershell/scripting/getting-started/getting-started-with-windows-powershell
## Notes
* Every time there is a change to the libopus checkout:
* Arm assembly should be converted by running `convert_android_asm.sh`
* Clean and re-build the project.
* If you want to use your own version of libopus, place it in
`${OPUS_MODULE_PATH}/jni/libopus`.
## Using the module with ExoPlayer
Once you've followed the instructions above to check out, build and depend on

View file

@ -17,12 +17,40 @@ android {
namespace 'androidx.media3.decoder.opus'
sourceSets {
main {
jniLibs.srcDir 'src/main/libs'
jni.srcDirs = [] // Disable the automatic ndk-build call by Android Studio.
}
androidTest.assets.srcDir '../test_data/src/test/assets'
}
defaultConfig {
externalNativeBuild {
cmake {
targets "opusV2JNI"
}
}
}
// TODO(Internal: b/372449691): Remove packagingOptions once AGP is updated
// to version 8.5.1 or higher.
packagingOptions {
jniLibs {
useLegacyPackaging true
}
}
}
// Configure the native build only if libopus is present to avoid gradle sync
// failures if libopus hasn't been built according to the README instructions.
if (project.file('src/main/jni/libopus').exists()) {
android.externalNativeBuild.cmake {
path = 'src/main/jni/CMakeLists.txt'
version = '3.21.0+'
if (project.hasProperty('externalNativeBuildDir')) {
if (!new File(externalNativeBuildDir).isAbsolute()) {
ext.externalNativeBuildDir =
new File(rootDir, it.externalNativeBuildDir)
}
buildStagingDirectory = "${externalNativeBuildDir}/${project.name}"
}
}
}
dependencies {

View file

@ -20,7 +20,6 @@ import static org.junit.Assume.assumeTrue;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.util.LibraryLoader;
import androidx.media3.decoder.DecoderInputBuffer;
import androidx.media3.decoder.SimpleDecoderOutputBuffer;
import androidx.test.ext.junit.runners.AndroidJUnit4;
@ -34,14 +33,6 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public final class OpusDecoderTest {
private static final LibraryLoader LOADER =
new LibraryLoader("opusV2JNI") {
@Override
protected void loadLibrary(String name) {
System.loadLibrary(name);
}
};
private static final byte[] HEADER =
new byte[] {79, 112, 117, 115, 72, 101, 97, 100, 0, 2, 1, 56, 0, 0, -69, -128, 0, 0, 0};
@ -115,7 +106,7 @@ public final class OpusDecoderTest {
@Test
public void decode_removesPreSkipFromOutput() throws OpusDecoderException {
assumeTrue(LOADER.isAvailable());
assumeTrue(OpusLibrary.isAvailable());
OpusDecoder decoder =
new OpusDecoder(
/* numInputBuffers= */ 0,
@ -135,7 +126,7 @@ public final class OpusDecoderTest {
@Test
public void decode_whenDiscardPaddingDisabled_returnsDiscardPadding()
throws OpusDecoderException {
assumeTrue(LOADER.isAvailable());
assumeTrue(OpusLibrary.isAvailable());
OpusDecoder decoder =
new OpusDecoder(
/* numInputBuffers= */ 0,
@ -156,7 +147,7 @@ public final class OpusDecoderTest {
@Test
public void decode_whenDiscardPaddingEnabled_removesDiscardPadding() throws OpusDecoderException {
assumeTrue(LOADER.isAvailable());
assumeTrue(OpusLibrary.isAvailable());
OpusDecoder decoder =
new OpusDecoder(
/* numInputBuffers= */ 0,

View file

@ -1,33 +0,0 @@
#
# Copyright (C) 2016 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.
#
WORKING_DIR := $(call my-dir)
include $(CLEAR_VARS)
# build libopus.a
LOCAL_PATH := $(WORKING_DIR)
include libopus.mk
# build libopusV2JNI.so
include $(CLEAR_VARS)
LOCAL_PATH := $(WORKING_DIR)
LOCAL_MODULE := libopusV2JNI
LOCAL_ARM_MODE := arm
LOCAL_CPP_EXTENSION := .cc
LOCAL_SRC_FILES := opus_jni.cc
LOCAL_LDLIBS := -llog -lz -lm
LOCAL_STATIC_LIBRARIES := libopus
include $(BUILD_SHARED_LIBRARY)

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