Compare commits

..

No commits in common. "release" and "1.6.0-beta01" have entirely different histories.

896 changed files with 2965 additions and 12956 deletions

View File

@ -19,8 +19,6 @@ body:
options:
- Media3 main branch
- Media3 pre-release (alpha, beta or RC not in this list)
- Media3 1.6.1
- Media3 1.6.0
- Media3 1.5.1
- Media3 1.5.0
- Media3 1.4.1
@ -45,6 +43,9 @@ body:
- ExoPlayer 2.16.0
- ExoPlayer 2.15.1
- ExoPlayer 2.15.0
- ExoPlayer 2.14.2
- ExoPlayer 2.14.1
- ExoPlayer 2.14.0
- ExoPlayer dev-v2 branch
- Older (unsupported)
validations:

View File

@ -2,109 +2,209 @@
## 1.6
### 1.6.1 (2025-04-14)
### 1.6.0-beta01 (2025-02-26)
This release includes the following changes since the
[1.6.0 release](#160-2025-03-26):
[1.6.0-alpha03 release](#160-alpha03-2025-02-06):
* Common Library:
* Add `PlaybackParameters.withPitch(float)` method for easily copying a
`PlaybackParameters` with a new `pitch` value
([#2257](https://github.com/androidx/media/issues/2257)).
* ExoPlayer:
* Fix issue where media item transition fails due to recoverable renderer
error during initialization of the next media item
([#2229](https://github.com/androidx/media/issues/2229)).
* Fix issue where `ProgressiveMediaPeriod` throws an
`IllegalStateException` as `PreloadMediaSource` attempts to call its
`getBufferedDurationUs()` before it is prepared
([#2315](https://github.com/androidx/media/issues/2315)).
* Fix sending `CmcdData` in manifest requests for DASH, HLS, and
SmoothStreaming ([#2253](https://github.com/androidx/media/pull/2253)).
* Ensure `AdPlaybackState.withAdDurationsUs(long[][])` can be used after
ad groups have been removed. The user still needs to pass in an array of
durations for removed ad groups which can be empty or null
([#2267](https://github.com/androidx/media/issues/2267)).
* Extractors:
* MP4: Parse `alternate_group` from the `tkhd` box and expose it as an
`Mp4AlternateGroupData` entry in each track's `Format.metadata`
([#2242](https://github.com/androidx/media/issues/2242)).
* Audio:
* Fix offload issue where the position might get stuck when playing a
playlist of short content
([#1920](https://github.com/androidx/media/issues/1920)).
* Session:
* Lower aggregation timeout for platform `MediaSession` callbacks from 500
to 100 milliseconds and add an experimental setter to allow apps to
configure this value.
* Fix issue where notifications reappear after they have been dismissed by
the user ([#2302](https://github.com/androidx/media/issues/2302)).
* Fix a bug where the session returned a single-item timeline when the
wrapped player is actually empty. This happened when the wrapped player
doesn't have `COMMAND_GET_TIMELINE` available while
`COMMAND_GET_CURRENT_MEDIA_ITEM` is available and the wrapped player is
empty ([#2320](https://github.com/androidx/media/issues/2320)).
* Fix a bug where calling
`MediaSessionService.setMediaNotificationProvider` is silently ignored
after other interactions with the service like
`setForegroundServiceTimeoutMs`
([#2305](https://github.com/androidx/media/issues/2305)).
* UI:
* Enable `PlayerSurface` to work with `ExoPlayer.setVideoEffects` and
`CompositionPlayer`.
* Fix bug where `PlayerSurface` can't be recomposed with a new `Player`.
* HLS extension:
* Fix issue where chunk duration wasn't set in `CmcdData` for HLS media,
causing an assertion failure when processing encrypted media segments
([#2312](https://github.com/androidx/media/issues/2312)).
* RTSP extension:
* Add support for URI with RTSPT scheme as a way to configure the RTSP
session to use TCP
([#1484](https://github.com/androidx/media/issues/1484)).
* Cast extension:
* Add support for playlist metadata
([#2235](https://github.com/androidx/media/pull/2235)).
### 1.6.0 (2025-03-26)
This release includes the following changes since the
[1.5.1 release](#151-2024-12-19):
* Common Library:
* Add `AudioManagerCompat` and `AudioFocusRequestCompat` to replace the
equivalent classes in `androidx.media`.
* Upgrade Kotlin from 1.9.20 to 2.0.20 and use Compose Compiler Gradle
plugin. Upgrade KotlinX Coroutines library from 1.8.1 to 1.9.0.
* Remove `Format.toBundle(boolean excludeMetadata)` method, use
`Format.toBundle()` instead.
* ExoPlayer:
* Initial audio session id is no longer immediately available after
creating the player. You can use
`AnalyticsListener.onAudioSessionIdChanged` to listen to the initial
update if required.
* Transformer:
* Add `MediaProjectionAssetLoader`, which provides media from a
`MediaProjection` for screen recording, and add support for screen
recording to the Transformer demo app.
* Add `#getInputFormat()` to `Codec` interface.
* Shift the responsibility to release the `GlObjectsProvider` onto the
caller in `DefaultVideoFrameProcessor` and `DefaultVideoCompositor` when
possible.
* Video:
* Add experimental `ExoPlayer` API to drop late `MediaCodecVideoRenderer`
decoder input buffers that are not depended on. Enable it with
`DefaultRenderersFactory.experimentalSetLateThresholdToDropDecoderInputUs`.
* Session:
* Keep foreground service state for an additional 10 minutes when playback
pauses, stops or fails. This allows users to resume playback within this
timeout without risking foreground service restrictions on various
devices. Note that simply calling `player.pause()` can no longer be used
to stop the foreground service before `stopSelf()` when overriding
`onTaskRemoved`, use `MediaSessionService.pauseAllPlayersAndStopSelf()`
instead.
* Make `MediaSession.setSessionActivity(PendingIntent)` accept null
([#2109](https://github.com/androidx/media/issues/2109)).
* Keep notification visible when playback enters an error or stopped
state. The notification is only removed if the playlist is cleared or
the player is released.
* Improve handling of Android platform MediaSession actions ACTION_PLAY
and ACTION_PAUSE to only set one of them according to the available
commands and also accept if only one of them is set.
* Remove deprecated symbols:
* Removed the following deprecated `DownloadHelper` methods:
* Constructor `DownloadHelper(MediaItem, @Nullable MediaSource,
TrackSelectionParameters, RendererCapabilities[])`, use
`DownloadHelper(MediaItem, @Nullable MediaSource,
TrackSelectionParameters, RendererCapabilitiesList)` instead.
* `getRendererCapabilities(RenderersFactory)`, equivalent
functionality can be achieved by creating a
`DefaultRendererCapabilitiesList` with a `RenderersFactory`, and
calling `DefaultRendererCapabilitiesList.getRendererCapabilities()`.
* Removed
`PlayerNotificationManager.setMediaSessionToken(MediaSessionCompat)`
method. Use
`PlayerNotificationManager.setMediaSessionToken(MediaSession.Token)` and
pass in `(MediaSession.Token) compatToken.getToken()`instead.
### 1.6.0-alpha03 (2025-02-06)
This release includes the following changes since the
[1.6.0-alpha02 release](#160-alpha02-2025-01-30):
* ExoPlayer:
* Add option to `ClippingMediaSource` to allow clipping in unseekable
media.
* Fix bug where seeking with pre-warming could block following media item
transition.
* Audio:
* Make `androidx.media3.common.audio.SonicAudioProcessor` final.
* Video:
* Change `MediaCodecVideoRenderer.shouldUsePlaceholderSurface` to
protected so that applications can override to block usage of
placeholder surfaces
([#1905](https://github.com/androidx/media/pull/1905)).
* Add experimental `ExoPlayer` AV1 sample dependency parsing to speed up
seeking. Enable it with the new
`DefaultRenderersFactory.experimentalSetParseAv1SampleDependencies` API.
* Muxers:
* Disable `Mp4Muxer` sample batching and copying by default.
* Remove deprecated symbols:
* Removed `androidx.media3.exoplayer.audio.SonicAudioProcessor`.
### 1.6.0-alpha02 (2025-01-30)
This release includes the following changes since the
[1.6.0-alpha01 release](#160-alpha01-2024-12-20):
* Common Library:
* Fix bug in `SimpleBasePlayer` where setting a new
`currentMediaItemIndex` in `State` after `setPlaylist` with `null`
`MediaMetadata` does not reevaluate the metadata
([#1940](https://github.com/androidx/media/issues/1940)).
* Change `SimpleBasePlayer.State` access from protected to public to make
it easier to handle updates in other classes
([#2128](https://github.com/androidx/media/issues/2128)).
* ExoPlayer:
* Add `MediaExtractorCompat`, a new class that provides equivalent
features to platform `MediaExtractor`.
* Add experimental 'ExoPlayer' pre-warming support for playback using
`MediaCodecVideoRenderer`. You can configure `DefaultRenderersFactory`
through `experimentalSetEnableMediaCodecVideoRendererPrewarming` to
provide a secondary `MediaCodecVideoRenderer` to `ExoPlayer`. If
enabled, `ExoPlayer` pre-processes the video of consecutive media items
during playback to reduce media item transition latency.
* Reduce default values for `bufferForPlaybackMs` and
`bufferForPlaybackAfterRebufferMs` in `DefaultLoadControl` to 1000 and
2000 ms respectively.
* Fix issue where additional decode-only frames may be displayed in quick
succession when transitioning to content media after a mid-roll ad.
* Make `DefaultRenderersFactory` add two `MetadataRenderer` instances to
enable apps to receive two different schemes of metadata by default.
* Initialize `DeviceInfo` and device volume asynchronously (if enabled
using `setDeviceVolumeControlEnabled`). These values aren't available
instantly after `ExoPlayer.Builder.build()`, and `Player.Listener`
notifies changes through `onDeviceInfoChanged` and
`onDeviceVolumeChanged`.
* Initial audio session id is no longer immediately available after
creating the player. You can use
`AnalyticsListener.onAudioSessionIdChanged` to listen to the initial
update if required.
* Reevaluate whether the ongoing load of a chunk should be cancelled when
playback is paused
([#1785](https://github.com/androidx/media/pull/1785)).
* Transformer:
* Enable support for Android platform diagnostics using
`MediaMetricsManager`. Transformer forwards editing events and
performance data to the platform, which helps to provide system
performance and debugging information on the device. This data may also
be collected by Google
[if sharing usage and diagnostics data is enabled](https://support.google.com/accounts/answer/6078260)
by the user of the device. Apps can opt-out of contributing to platform
diagnostics for Transformer with
`Transformer.Builder.setUsePlatformDiagnostics(false)`.
* Split `InAppMuxer` into `InAppMp4Muxer` and `InAppFragmentedMp4Muxer`.
You use `InAppMp4Muxer` to produce a non-fragmented MP4 file, while
`InAppFragmentedMp4Muxer` is for producing a fragmented MP4 file.
* Move `Muxer` interface from `media3-muxer` to `media3-transformer`.
* Add support for transcoding and transmuxing Dolby Vision (profile 8)
format.
* Extractors:
* Fix handling of NAL units with lengths expressed in 1 or 2 bytes (rather
than 4).
* Fix `ArrayIndexOutOfBoundsException` in MP4 edit lists when the edit
list starts at a non-sync frame with no preceding sync frame
([#2062](https://github.com/androidx/media/issues/2062)).
* Audio:
* Don't bypass `SonicAudioProcessor` when `SpeedChangingAudioProcessor` is
configured with default parameters.
* Fix underflow in `Sonic#getOutputSize()` that could cause
`DefaultAudioSink` to stall.
* Fix `MediaCodecAudioRenderer.getDurationToProgressUs()` and
`DecoderAudioRenderer.getDurationToProgressUs()` so that seeks correctly
reset the provided durations.
* Text:
* TTML: Add support for referencing `tts:origin` and `tts:extent` using
`style` ([#2953](https://github.com/google/ExoPlayer/issues/2953)).
* Restrict WebVTT and SubRip timestamps to exactly 3 decimal places.
Previously we incorrectly parsed any number of decimal places but always
assumed the value was in milliseconds, leading to incorrect timestamps
([#1997](https://github.com/androidx/media/issues/1997)).
* Add support for VobSub subtitles
([#8260](https://github.com/google/ExoPlayer/issues/8260)).
* Fix playback hanging when a playlist contains clipped items with CEA-608
or CEA-708 captions.
* Fix `IllegalStateException` when an SSA file contains a cue with zero
duration (start and end time equal)
([#2052](https://github.com/androidx/media/issues/2052)).
* Suppress (and log) subtitle parsing errors when subtitles are muxed into
the same container as audio and video
([#2052](https://github.com/androidx/media/issues/2052)).
* Muxers:
* Renamed `setSampleCopyEnabled()` method to `setSampleCopyingEnabled()`
in both `Mp4Muxer.Builder` and `FragmentedMp4Muxer.Builder`.
* `Mp4Muxer.addTrack()` and `FragmentedMp4Muxer.addTrack()` now return an
`int` track ID instead of a `TrackToken`.
* `Mp4Muxer` and `FragmentedMp4Muxer` no longer implement `Muxer`
interface.
* Session:
* Fix bug where calling a `Player` method on a `MediaController` connected
to a legacy session dropped changes from a pending update.
* UI:
* Add `PresentationState` state holder class and the corresponding
`rememberPresentationState` Composable to `media3-ui-compose`.
* HLS Extension:
* Parse `SUPPLEMENTAL-CODECS` tag from HLS playlist to detect Dolby Vision
formats ([#1785](https://github.com/androidx/media/pull/1785)).
* DASH Extension:
* Fix issue when calculating the update interval for ad insertion in
multi-period live streams
([#1698](https://github.com/androidx/media/issues/1698)).
* Parse `scte214:supplementalCodecs` attribute from DASH manifest to
detect Dolby Vision formats
([#1785](https://github.com/androidx/media/pull/1785)).
* Improve handling of period transitions in live streams where the period
contains media samples beyond the declared period duration
([#1698](https://github.com/androidx/media/issues/1698)).
* Demo app:
* Use `PresentationState` to control the aspect ratio of `PlayerSurface`
Composable. This depends on the ContentScale type and covers it with a
shutter-overlay before the first frame is rendered.
* Remove deprecated symbols:
* Removed `ExoPlayer.VideoComponent`, `ExoPlayer.AudioComponent`,
`ExoPlayer.TextComponent` and `ExoPlayer.DeviceComponent`.
### 1.6.0-alpha01 (2024-12-20)
This release includes the following changes since the
[1.5.1 release](#151-2024-12-19):
* Common Library:
* Remove `Format.toBundle(boolean excludeMetadata)` method, use
`Format.toBundle()` instead.
* Add `AudioManagerCompat` and `AudioFocusRequestCompat` to replace the
equivalent classes in `androidx.media`.
* ExoPlayer:
* Consider language when selecting a video track. By default, select a
'main' video track that matches the language of the selected audio
track, if available. Explicit video language preferences can be
@ -118,6 +218,11 @@ This release includes the following changes since the
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)).
* Reduce default values for `bufferForPlaybackMs` and
`bufferForPlaybackAfterRebufferMs` in `DefaultLoadControl` to 1000 and
2000 ms respectively.
* Add `MediaExtractorCompat`, a new class that provides equivalent
features to platform `MediaExtractor`.
* Move `BasePreloadManager.Listener` to a top-level
`PreloadManagerListener`.
* `RenderersFactory.createSecondaryRenderer` can be implemented to provide
@ -132,121 +237,29 @@ This release includes the following changes since the
* Change `AdsMediaSource` to allow the `AdPlaybackStates` to grow by
appending ad groups. Invalid modifications are detected and throw an
exception.
* Fix issue where additional decode-only frames may be displayed in quick
succession when transitioning to content media after a mid-roll ad.
* Make `DefaultRenderersFactory` add two `MetadataRenderer` instances to
enable apps to receive two different schemes of metadata by default.
* Reevaluate whether the ongoing load of a chunk should be cancelled when
playback is paused
([#1785](https://github.com/androidx/media/pull/1785)).
* Add option to `ClippingMediaSource` to allow clipping in unseekable
media.
* Fix bug where seeking with pre-warming could block following media item
transition.
* Fix a bug where `ExoPlayer.isLoading()` remains `true` while it has
transitioned to `STATE_IDLE` or `STATE_ENDED`
([#2133](https://github.com/androidx/media/issues/2133)).
* Add `lastRebufferRealtimeMs` to `LoadControl.Parameter`
([#2113](https://github.com/androidx/media/pull/2113)).
* Transformer:
* Add support for transmuxing into alternative backward compatible
formats.
* Add support for transcoding and transmuxing Dolby Vision (profile 8)
format.
* Update parameters of `VideoFrameProcessor.registerInputStream` and
`VideoFrameProcessor.Listener.onInputStreamRegistered` to use `Format`.
* Add support for transmuxing into alternative backward compatible
formats.
* Generate HDR static metadata when using `DefaultEncoderFactory`.
* Enable support for Android platform diagnostics using
`MediaMetricsManager`. Transformer forwards editing events and
performance data to the platform, which helps to provide system
performance and debugging information on the device. This data may also
be collected by Google
[if sharing usage and diagnostics data is enabled](https://support.google.com/accounts/answer/6078260)
by the user of the device. Apps can opt-out of contributing to platform
diagnostics for Transformer with
`Transformer.Builder.setUsePlatformDiagnostics(false)`.
* Split `InAppMuxer` into `InAppMp4Muxer` and `InAppFragmentedMp4Muxer`.
You use `InAppMp4Muxer` to produce a non-fragmented MP4 file, while
`InAppFragmentedMp4Muxer` is for producing a fragmented MP4 file.
* Move `Muxer` interface from `media3-muxer` to `media3-transformer`.
* Add `MediaProjectionAssetLoader`, which provides media from a
`MediaProjection` for screen recording, and add support for screen
recording to the Transformer demo app.
* Add `#getInputFormat()` to `Codec` interface.
* Shift the responsibility to release the `GlObjectsProvider` onto the
caller in `DefaultVideoFrameProcessor` and `DefaultVideoCompositor` when
possible.
* Extractors:
* AVI: Fix handling of files with constant bitrate compressed audio where
the stream header stores the number of bytes instead of the number of
chunks.
* Fix handling of NAL units with lengths expressed in 1 or 2 bytes (rather
than 4).
* Fix `ArrayIndexOutOfBoundsException` in MP4 edit lists when the edit
list starts at a non-sync frame with no preceding sync frame
([#2062](https://github.com/androidx/media/issues/2062)).
* Fix issue where TS streams can get stuck on some devices
([#2069](https://github.com/androidx/media/issues/2069)).
* FLAC: Add support for 32-bit FLAC files. Previously these would fail to
play with `IllegalStateException: Playback stuck buffering and not
loading` ([#2197](https://github.com/androidx/media/issues/2197)).
* Audio:
* Fix `onAudioPositionAdvancing` to be called when playback resumes
(previously it was called when playback was paused).
* Don't bypass `SonicAudioProcessor` when `SpeedChangingAudioProcessor` is
configured with default parameters.
* Fix underflow in `Sonic#getOutputSize()` that could cause
`DefaultAudioSink` to stall.
* Fix `MediaCodecAudioRenderer.getDurationToProgressUs()` and
`DecoderAudioRenderer.getDurationToProgressUs()` so that seeks correctly
reset the provided durations.
* Make `androidx.media3.common.audio.SonicAudioProcessor` final.
* Add support for float PCM to `ChannelMappingAudioProcessor` and
`TrimmingAudioProcessor`.
* Video:
* Change `MediaCodecVideoRenderer.shouldUsePlaceholderSurface` to
protected so that applications can override to block usage of
placeholder surfaces
([#1905](https://github.com/androidx/media/pull/1905)).
* Add experimental `ExoPlayer` AV1 sample dependency parsing to speed up
seeking. Enable it with the new
`DefaultRenderersFactory.experimentalSetParseAv1SampleDependencies` API.
* Add experimental `ExoPlayer` API to drop late `MediaCodecVideoRenderer`
decoder input buffers that are not depended on. Enable it with
`DefaultRenderersFactory.experimentalSetLateThresholdToDropDecoderInputUs`.
* Fix issue where a player without a surface was ready immediately and
very slow decoding any pending frames
([#1973](https://github.com/androidx/media/issues/1973)).
* Exclude Xiaomi and OPPO devices from detached surface mode to avoid
screen flickering
([#2059](https://github.com/androidx/media/issues/2059)).
* Fix `MediaCodecVideoRenderer` such that when without a `Surface`, the
renderer skips just-early frames only if the
`VideoFrameReleaseControl.getFrameReleaseAction` is not
`FRAME_RELEASE_TRY_AGAIN_LATER`.
* Text:
* Add support for VobSub subtitles
([#8260](https://github.com/google/ExoPlayer/issues/8260)).
* Stop eagerly loading all subtitle files configured with
`MediaItem.Builder.setSubtitleConfigurations`, and instead only load one
if it is selected by track selection
([#1721](https://github.com/androidx/media/issues/1721)).
* TTML: Add support for referencing `tts:origin` and `tts:extent` using
`style` ([#2953](https://github.com/google/ExoPlayer/issues/2953)).
* Restrict WebVTT and SubRip timestamps to exactly 3 decimal places.
Previously we incorrectly parsed any number of decimal places but always
assumed the value was in milliseconds, leading to incorrect timestamps
([#1997](https://github.com/androidx/media/issues/1997)).
* Fix playback hanging when a playlist contains clipped items with CEA-608
or CEA-708 captions.
* Fix `IllegalStateException` when an SSA file contains a cue with zero
duration (start and end time equal)
([#2052](https://github.com/androidx/media/issues/2052)).
* Suppress (and log) subtitle parsing errors when subtitles are muxed into
the same container as audio and video
([#2052](https://github.com/androidx/media/issues/2052)).
* Fix handling of multi-byte UTF-8 characters in WebVTT files using CR
line endings ([#2167](https://github.com/androidx/media/issues/2167)).
* DRM:
* Fix `MediaCodec$CryptoException: Operation not supported in this
configuration` error when playing ClearKey content on API < 27 devices
([#1732](https://github.com/androidx/media/issues/1732)).
* Effect:
* Moved the functionality of `OverlaySettings` into
`StaticOverlaySettings`. `OverlaySettings` can be subclassed to allow
@ -254,49 +267,18 @@ This release includes the following changes since the
* Muxers:
* Moved `MuxerException` out of `Muxer` interface to avoid a very long
fully qualified name.
* Renamed `setSampleCopyEnabled()` method to `setSampleCopyingEnabled()`
in both `Mp4Muxer.Builder` and `FragmentedMp4Muxer.Builder`.
* `Mp4Muxer.addTrack()` and `FragmentedMp4Muxer.addTrack()` now return an
`int` track ID instead of a `TrackToken`.
* `Mp4Muxer` and `FragmentedMp4Muxer` no longer implement `Muxer`
interface.
* Disable `Mp4Muxer` sample batching and copying by default.
* Fix a bug in `FragmentedMp4Muxer` that creates a lot of fragments when
only audio track is written.
* Session:
* Keep foreground service state for an additional 10 minutes when playback
pauses, stops or fails. This allows users to resume playback within this
timeout without risking foreground service restrictions on various
devices. Note that simply calling `player.pause()` can no longer be used
to stop the foreground service before `stopSelf()` when overriding
`onTaskRemoved`, use `MediaSessionService.pauseAllPlayersAndStopSelf()`
instead.
* Keep notification visible when playback enters an error or stopped
state. The notification is only removed if the playlist is cleared or
the player is released.
* Improve handling of Android platform MediaSession actions ACTION_PLAY
and ACTION_PAUSE to only set one of them according to the available
commands and also accept if only one of them is set.
* Add `Context` as a parameter to
`MediaButtonReceiver.shouldStartForegroundService`
([#1887](https://github.com/androidx/media/issues/1887)).
* Fix bug where calling a `Player` method on a `MediaController` connected
to a legacy session dropped changes from a pending update.
* Make `MediaSession.setSessionActivity(PendingIntent)` accept null
([#2109](https://github.com/androidx/media/issues/2109)).
* Fix bug where a stale notification stays visible when the playlist is
cleared ([#2211](https://github.com/androidx/media/issues/2211)).
* UI:
* Add state holders and composables to the `media3-ui-compose` module for
`PlayerSurface`, `PresentationState`, `PlayPauseButtonState`,
`NextButtonState`, `PreviousButtonState`, `RepeatButtonState`,
`ShuffleButtonState` and `PlaybackSpeedState`.
* Downloads:
* Fix bug in `CacheWriter` that leaves data sources open and cache areas
locked in case the data source throws an `Exception` other than
`IOException`
([#9760](https://github.com/google/ExoPlayer/issues/9760)).
* HLS extension:
* Add `PlayerSurface` Composable to `media3-ui-compose` module.
* Add `PlayPauseButtonState`, `NextButtonState`, `PreviousButtonState`,
`RepeatButtonState`, `ShuffleButtonState` classes and the corresponding
`rememberPlayPauseButtonState`, `rememberNextButtonState`,
`rememberPreviousButtonState`, `rememberRepeatButtonState`,
`rememberShuffleButtonState` Composables to `media3-ui-compose` module.
* HLS Extension:
* Add a first version of `HlsInterstitialsAdsLoader`. The ads loader reads
the HLS interstitials of an HLS media playlist and maps them to the
`AdPlaybackState` that is passed to the `AdsMediaSource`. This initial
@ -304,40 +286,19 @@ This release includes the following changes since the
* Add `HlsInterstitialsAdsLoader.AdsMediaSourceFactory`. Apps can use it
to create `AdsMediaSource` instances that use an
`HlsInterstitialsAdsLoader` in a convenient and safe way.
* Parse `SUPPLEMENTAL-CODECS` tag from HLS playlist to detect Dolby Vision
formats ([#1785](https://github.com/androidx/media/pull/1785)).
* Loosen the condition for seeking to sync positions in an HLS stream
([#2209](https://github.com/androidx/media/issues/2209)).
* DASH extension:
* DASH Extension:
* Add AC-4 Level-4 format support for DASH
([#1898](https://github.com/androidx/media/pull/1898)).
* Fix issue when calculating the update interval for ad insertion in
multi-period live streams
([#1698](https://github.com/androidx/media/issues/1698)).
* Parse `scte214:supplementalCodecs` attribute from DASH manifest to
detect Dolby Vision formats
([#1785](https://github.com/androidx/media/pull/1785)).
* Improve handling of period transitions in live streams where the period
contains media samples beyond the declared period duration
([#1698](https://github.com/androidx/media/issues/1698)).
* Fix issue where adaptation sets marked with `adaptation-set-switching`
but different languages or role flags are merged together
([#2222](https://github.com/androidx/media/issues/2222)).
* Decoder extensions (FFmpeg, VP9, AV1, etc.):
* Decoder Extensions (FFmpeg, VP9, AV1, etc.):
* Add the MPEG-H decoder module which uses the native MPEG-H decoder
module to decode MPEG-H audio
([#1826](https://github.com/androidx/media/pull/1826)).
* MIDI extension:
* Plumb custom `AudioSink` and `AudioRendererEventListener` instances into
`MidiRenderer`.
* Cast extension:
* Bump the `play-services-cast-framework` dependency to 21.5.0 to fix a
`FLAG_MUTABLE` crash in apps targeting API 34+ on devices with Google
Play services installed but disabled
([#2178](https://github.com/androidx/media/issues/2178)).
* Demo app:
* Extend `demo-compose` with additional buttons and enhance
`PlayerSurface` integration with scaling and shutter support.
* Add `MinimalControls` (`PlayPauseButton`, `NextButton`,
`PreviousButton`) and `ExtraControls` (`RepeatButton`, `ShuffleButton`)
Composable UI elements to `demo-compose` utilizing
`PlayPauseButtonState`, `NextButtonState`, `PreviousButtonState`,
`RepeatButtonState`, `ShuffleButtonState`.
* Remove deprecated symbols:
* Remove deprecated `AudioMixer.create()` method. Use
`DefaultAudioMixer.Factory().create()` instead.
@ -391,47 +352,6 @@ This release includes the following changes since the
`BaseGlShaderProgram` instead.
* Remove `Transformer.flattenForSlowMotion`. Use
`EditedMediaItem.flattenForSlowMotion` instead.
* Removed `ExoPlayer.VideoComponent`, `ExoPlayer.AudioComponent`,
`ExoPlayer.TextComponent` and `ExoPlayer.DeviceComponent`.
* Removed `androidx.media3.exoplayer.audio.SonicAudioProcessor`.
* Removed the following deprecated `DownloadHelper` methods:
* Constructor `DownloadHelper(MediaItem, @Nullable MediaSource,
TrackSelectionParameters, RendererCapabilities[])`, use
`DownloadHelper(MediaItem, @Nullable MediaSource,
TrackSelectionParameters, RendererCapabilitiesList)` instead.
* `getRendererCapabilities(RenderersFactory)`, equivalent
functionality can be achieved by creating a
`DefaultRendererCapabilitiesList` with a `RenderersFactory`, and
calling `DefaultRendererCapabilitiesList.getRendererCapabilities()`.
* Removed
`PlayerNotificationManager.setMediaSessionToken(MediaSessionCompat)`
method. Use
`PlayerNotificationManager.setMediaSessionToken(MediaSession.Token)` and
pass in `(MediaSession.Token) compatToken.getToken()` instead.
### 1.6.0-rc02 (2025-03-18)
Use the 1.6.0 [stable version](#160-2025-03-26).
### 1.6.0-rc01 (2025-03-12)
Use the 1.6.0 [stable version](#160-2025-03-26).
### 1.6.0-beta01 (2025-02-26)
Use the 1.6.0 [stable version](#160-2025-03-26).
### 1.6.0-alpha03 (2025-02-06)
Use the 1.6.0 [stable version](#160-2025-03-26).
### 1.6.0-alpha02 (2025-01-30)
Use the 1.6.0 [stable version](#160-2025-03-26).
### 1.6.0-alpha01 (2024-12-20)
Use the 1.6.0 [stable version](#160-2025-03-26).
## 1.5
@ -745,19 +665,19 @@ This release includes the following changes since the
[#184](https://github.com/androidx/media/issues/184)).
* Fix bug where the "None" choice in the text selection is not working if
there are app-defined text track selection preferences.
* DASH extension:
* DASH Extension:
* Add support for periods starting in the middle of a segment
([#1440](https://github.com/androidx/media/issues/1440)).
* Smooth Streaming extension:
* Smooth Streaming Extension:
* Fix a `Bad magic number for Bundle` error when playing SmoothStreaming
streams with text tracks
([#1779](https://github.com/androidx/media/issues/1779)).
* RTSP extension:
* RTSP Extension:
* Fix user info removal for URLs that contain encoded @ characters
([#1138](https://github.com/androidx/media/pull/1138)).
* Fix crashing when parsing of RTP packets with header extensions
([#1225](https://github.com/androidx/media/pull/1225)).
* Decoder extensions (FFmpeg, VP9, AV1, etc.):
* Decoder Extensions (FFmpeg, VP9, AV1, etc.):
* Add the IAMF decoder module, which provides support for playback of MP4
files containing IAMF tracks using the libiamf native library to
synthesize audio.
@ -766,7 +686,7 @@ This release includes the following changes since the
binaural playback support is currently not available.
* Add 16 KB page support for decoder extensions on Android 15
([#1685](https://github.com/androidx/media/issues/1685)).
* Cast extension:
* Cast Extension:
* Stop cleaning the timeline after the CastSession disconnects, which
enables the sender app to resume playback locally after a disconnection.
* Populate CastPlayer's `DeviceInfo` when a `Context` is provided. This
@ -847,7 +767,7 @@ This release includes the following changes since the
`MediaButtonReceiver` when deciding whether to ignore it to avoid a
`ForegroundServiceDidNotStartInTimeException`
([#1581](https://github.com/androidx/media/issues/1581)).
* RTSP extension:
* RTSP Extension:
* Skip invalid Media Descriptions in SDP parsing
([#1087](https://github.com/androidx/media/issues/1472)).
@ -1192,12 +1112,12 @@ This release includes the following changes since the
instances, which can eventually result in an app crashing with
`IllegalStateException: Too many receivers, total of 1000, registered
for pid` ([#1224](https://github.com/androidx/media/issues/1224)).
* Cronet extension:
* Cronet Extension:
* Fix `SocketTimeoutException` in `CronetDataSource`. In some versions of
Cronet, the request provided by the callback is not always the same.
This leads to callback not completing and request timing out
(https://issuetracker.google.com/328442628).
* HLS extension:
* HLS Extension:
* Fix bug where pending EMSG samples waiting for a discontinuity were
delegated in `HlsSampleStreamWrapper` with an incorrect offset causing
an `IndexOutOfBoundsException` or an `IllegalArgumentException`
@ -1211,13 +1131,13 @@ This release includes the following changes since the
* Fix bug where enabling CMCD for HLS live streams causes
`ArrayIndexOutOfBoundsException`
([#1395](https://github.com/androidx/media/issues/1395)).
* DASH extension:
* DASH Extension:
* Fix bug where re-preparing a multi-period live stream can throw an
`IndexOutOfBoundsException`
([#1329](https://github.com/androidx/media/issues/1329)).
* Add support for `dashif:Laurl` license urls
([#1345](https://github.com/androidx/media/issues/1345)).
* Cast extension:
* Cast Extension:
* Fix bug that converted the album title of the `MediaQueueItem` to the
artist in the Media3 media item
([#1255](https://github.com/androidx/media/pull/1255)).
@ -1365,13 +1285,13 @@ This release includes the following changes since the
* Fallback to include audio track language name if `Locale` cannot
identify a display name
([#988](https://github.com/androidx/media/issues/988)).
* DASH extension:
* DASH Extension:
* Populate all `Label` elements from the manifest into `Format.labels`
([#1054](https://github.com/androidx/media/pull/1054)).
* RTSP extension:
* RTSP Extension:
* Skip empty session information values (i-tags) in SDP parsing
([#1087](https://github.com/androidx/media/issues/1087)).
* Decoder extensions (FFmpeg, VP9, AV1, MIDI, etc.):
* Decoder Extensions (FFmpeg, VP9, AV1, MIDI, etc.):
* Disable the MIDI extension as a local dependency by default because it
requires an additional Maven repository to be configured. Users who need
this module from a local dependency
@ -1524,12 +1444,12 @@ This release includes the following changes since the
not transmitted between media controllers and sessions.
* Add constructor to `MediaLibrarySession.Builder` that only takes a
`Context` instead of a `MediaLibraryService`.
* HLS extension:
* HLS Extension:
* Reduce `HlsMediaPeriod` to package-private visibility. This type
shouldn't be directly depended on from outside the HLS package.
* Resolve seeks to beginning of a segment more efficiently
([#1031](https://github.com/androidx/media/pull/1031)).
* Decoder extensions (FFmpeg, VP9, AV1, MIDI, etc.):
* Decoder Extensions (FFmpeg, VP9, AV1, MIDI, etc.):
* MIDI decoder: Ignore SysEx event messages
([#710](https://github.com/androidx/media/pull/710)).
* Test Utilities:
@ -1627,16 +1547,16 @@ This release includes the following changes since the
* Fix issue where the numbers in the fast forward button of the
`PlayerControlView` were misaligned
([#547](https://github.com/androidx/media/issues/547)).
* DASH extension:
* DASH Extension:
* Parse "f800" as channel count of 5 for Dolby in DASH manifest
([#688](https://github.com/androidx/media/issues/688)).
* Decoder extensions (FFmpeg, VP9, AV1, MIDI, etc.):
* Decoder Extensions (FFmpeg, VP9, AV1, MIDI, etc.):
* MIDI: Fix issue where seeking forward skips the Program Change events
([#704](https://github.com/androidx/media/issues/704)).
* Migrate to FFmpeg 6.0 and update supported NDK to `r26b`
([#707](https://github.com/androidx/media/pull/707),
[#867](https://github.com/androidx/media/pull/867)).
* Cast extension:
* Cast Extension:
* Sanitize creation of a `Timeline` to not crash the app when loading
media fails on the cast device
([#708](https://github.com/androidx/media/issues/708)).
@ -1874,11 +1794,11 @@ This release includes the following changes since the
add `dataSync` as `foregroundServiceType` in the manifest and add the
`FOREGROUND_SERVICE_DATA_SYNC` permission
([#11239](https://github.com/google/ExoPlayer/issues/11239)).
* HLS extension:
* HLS Extension:
* Refresh the HLS live playlist with an interval calculated from the last
load start time rather than the last load completed time
([#663](https://github.com/androidx/media/issues/663)).
* DASH extension:
* DASH Extension:
* Allow multiple of the same DASH identifier in segment template url.
* Add experimental support for parsing subtitles during extraction. This
has better support for merging overlapping subtitles, including
@ -1886,7 +1806,7 @@ This release includes the following changes since the
can enable this using
`DashMediaSource.Factory.experimentalParseSubtitlesDuringExtraction()`
([#288](https://github.com/androidx/media/issues/288)).
* RTSP extension:
* RTSP Extension:
* Fix a race condition that could lead to `IndexOutOfBoundsException` when
falling back to TCP, or playback hanging in some situations.
* Check state in RTSP setup when returning loading state of
@ -1897,7 +1817,7 @@ This release includes the following changes since the
* Use RTSP Setup Response timeout value in time interval of sending
keep-alive RTSP Options requests
([#662](https://github.com/androidx/media/issues/662)).
* Decoder extensions (FFmpeg, VP9, AV1, MIDI, etc.):
* Decoder Extensions (FFmpeg, VP9, AV1, MIDI, etc.):
* Release the MIDI decoder module, which provides support for playback of
standard MIDI files using the Jsyn library to synthesize audio.
* Add `DecoderOutputBuffer.shouldBeSkipped` to directly mark output
@ -2174,20 +2094,20 @@ This release contains the following changes since the
* Add Util methods `shouldShowPlayButton` and
`handlePlayPauseButtonAction` to write custom UI elements with a
play/pause button.
* RTSP extension:
* RTSP Extension:
* For MPEG4-LATM, use default profile-level-id value if absent in Describe
Response SDP message
([#302](https://github.com/androidx/media/issues/302)).
* Use base Uri for relative path resolution from the RTSP session if
present in DESCRIBE response header
([#11160](https://github.com/google/ExoPlayer/issues/11160)).
* DASH extension:
* DASH Extension:
* Remove the media time offset from `MediaLoadData.startTimeMs` and
`MediaLoadData.endTimeMs` for multi period DASH streams.
* Fix a bug where re-preparing a multi-period live Dash media source
produced a `IndexOutOfBoundsException`
([#10838](https://github.com/google/ExoPlayer/issues/10838)).
* HLS extension:
* HLS Extension:
* Add
`HlsMediaSource.Factory.setTimestampAdjusterInitializationTimeoutMs(long)`
to set a timeout for the loading thread to wait for the

View File

@ -12,8 +12,8 @@
// See the License for the specific language governing permissions and
// limitations under the License.
project.ext {
releaseVersion = '1.6.1'
releaseVersionCode = 1_006_001_3_00
releaseVersion = '1.6.0-beta01'
releaseVersionCode = 1_006_000_1_01
minSdkVersion = 21
// See https://developer.android.com/training/cars/media/automotive-os#automotive-module
automotiveMinSdkVersion = 28

View File

@ -30,7 +30,6 @@ internal fun ExtraControls(player: Player, modifier: Modifier = Modifier) {
horizontalArrangement = Arrangement.Center,
verticalAlignment = Alignment.CenterVertically,
) {
PlaybackSpeedPopUpButton(player)
ShuffleButton(player)
RepeatButton(player)
}

View File

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

View File

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

View File

@ -257,10 +257,6 @@
{
"name": "Apple media playlist (AAC)",
"uri": "https://devstreaming-cdn.apple.com/videos/streaming/examples/bipbop_4x3/gear0/prog_index.m3u8"
},
{
"name": "Bitmovin (FMP4)",
"uri": "https://bitdash-a.akamaihd.net/content/MI201109210084_1/m3u8s-fmp4/f08e80da-bf1d-4e3d-8899-f0f6155f6efa.m3u8"
}
]
},

View File

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

View File

@ -24,7 +24,7 @@ android {
}
dependencies {
api 'com.google.android.gms:play-services-cast-framework:21.5.0'
api 'com.google.android.gms:play-services-cast-framework:21.3.0'
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
api project(modulePrefix + 'lib-common')
compileOnly 'org.checkerframework:checker-qual:' + checkerframeworkVersion

View File

@ -16,7 +16,6 @@
package androidx.media3.cast;
import static androidx.media3.common.util.Assertions.checkArgument;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static androidx.media3.common.util.Util.SDK_INT;
import static androidx.media3.common.util.Util.castNonNull;
import static java.lang.Math.min;
@ -74,7 +73,6 @@ import com.google.android.gms.common.api.PendingResult;
import com.google.android.gms.common.api.ResultCallback;
import com.google.common.collect.ImmutableList;
import java.util.List;
import java.util.Objects;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/**
@ -167,7 +165,6 @@ public final class CastPlayer extends BasePlayer {
private long pendingSeekPositionMs;
@Nullable private PositionInfo pendingMediaItemRemovalPosition;
private MediaMetadata mediaMetadata;
private MediaMetadata playlistMetadata;
private DeviceInfo deviceInfo;
/**
@ -270,7 +267,6 @@ public final class CastPlayer extends BasePlayer {
playbackState = STATE_IDLE;
currentTimeline = CastTimeline.EMPTY_CAST_TIMELINE;
mediaMetadata = MediaMetadata.EMPTY;
playlistMetadata = MediaMetadata.EMPTY;
currentTracks = Tracks.EMPTY;
availableCommands = new Commands.Builder().addAll(PERMANENT_AVAILABLE_COMMANDS).build();
pendingSeekWindowIndex = C.INDEX_UNSET;
@ -659,19 +655,14 @@ public final class CastPlayer extends BasePlayer {
@Override
public MediaMetadata getPlaylistMetadata() {
return playlistMetadata;
// CastPlayer does not currently support metadata.
return MediaMetadata.EMPTY;
}
/** This method is not supported and does nothing. */
@Override
public void setPlaylistMetadata(MediaMetadata playlistMetadata) {
checkNotNull(playlistMetadata);
if (playlistMetadata.equals(this.playlistMetadata)) {
return;
}
this.playlistMetadata = playlistMetadata;
listeners.sendEvent(
EVENT_PLAYLIST_METADATA_CHANGED,
listener -> listener.onPlaylistMetadataChanged(this.playlistMetadata));
public void setPlaylistMetadata(MediaMetadata mediaMetadata) {
// CastPlayer does not currently support metadata.
}
@Override
@ -918,7 +909,7 @@ public final class CastPlayer extends BasePlayer {
? currentTimeline.getPeriod(currentWindowIndex, period, /* setIds= */ true).uid
: null;
if (!playingPeriodChangedByTimelineChange
&& !Objects.equals(oldPeriodUid, currentPeriodUid)
&& !Util.areEqual(oldPeriodUid, currentPeriodUid)
&& pendingSeekCount == 0) {
// Report discontinuity and media item auto transition.
currentTimeline.getPeriod(oldWindowIndex, period, /* setIds= */ true);

View File

@ -1800,7 +1800,7 @@ public class CastPlayerTest {
}
@Test
public void setMediaItems_doesNotifyOnMediaMetadataChanged() {
public void setMediaItems_doesNotifyOnMetadataChanged() {
when(mockRemoteMediaClient.queueJumpToItem(anyInt(), anyLong(), eq(null)))
.thenReturn(mockPendingResult);
ArgumentCaptor<MediaMetadata> metadataCaptor = ArgumentCaptor.forClass(MediaMetadata.class);
@ -1827,7 +1827,7 @@ public class CastPlayerTest {
.build());
castPlayer.addListener(mockListener);
MediaMetadata initialMetadata = castPlayer.getMediaMetadata();
MediaMetadata intitalMetadata = castPlayer.getMediaMetadata();
castPlayer.setMediaItems(firstPlaylist, /* startIndex= */ 0, /* startPositionMs= */ 2000L);
updateTimeLine(firstPlaylist, /* mediaQueueItemIds= */ new int[] {1}, /* currentItemId= */ 1);
MediaMetadata firstMetadata = castPlayer.getMediaMetadata();
@ -1850,7 +1850,7 @@ public class CastPlayerTest {
secondPlaylist.get(1).mediaMetadata,
secondPlaylist.get(0).mediaMetadata)
.inOrder();
assertThat(initialMetadata).isEqualTo(MediaMetadata.EMPTY);
assertThat(intitalMetadata).isEqualTo(MediaMetadata.EMPTY);
assertThat(ImmutableList.of(firstMetadata, secondMetadata, thirdMetadata))
.containsExactly(
firstPlaylist.get(0).mediaMetadata,
@ -1898,35 +1898,6 @@ public class CastPlayerTest {
verify(mockListener, never()).onMediaMetadataChanged(any());
}
@Test
public void setPlaylistMetadata_doesNotifyOnPlaylistMetadataChanged() {
castPlayer.addListener(mockListener);
MediaMetadata metadata = new MediaMetadata.Builder().setArtist("foo").build();
assertThat(castPlayer.getPlaylistMetadata()).isEqualTo(MediaMetadata.EMPTY);
castPlayer.setPlaylistMetadata(metadata);
assertThat(castPlayer.getPlaylistMetadata()).isEqualTo(metadata);
verify(mockListener).onPlaylistMetadataChanged(metadata);
}
@Test
public void setPlaylistMetadata_equalMetadata_doesNotNotifyOnPlaylistMetadataChanged() {
castPlayer.addListener(mockListener);
MediaMetadata metadata = new MediaMetadata.Builder().setArtist("foo").build();
castPlayer.setPlaylistMetadata(metadata);
castPlayer.setPlaylistMetadata(metadata);
assertThat(castPlayer.getPlaylistMetadata()).isEqualTo(metadata);
verify(mockListener, times(1)).onPlaylistMetadataChanged(metadata);
}
@Test
public void getDeviceInfo_returnsCorrectDeviceInfoWithPlaybackTypeRemote() {
DeviceInfo deviceInfo = castPlayer.getDeviceInfo();

View File

@ -1072,23 +1072,14 @@ public final class AdPlaybackState {
/**
* Returns an instance with the specified ad durations, in microseconds.
*
* <p>The number of arrays of durations ({@code adDurations.length}) must always be equal to
* {@link #adGroupCount}. This is required even on an instance created with {@link
* #withRemovedAdGroupCount(int)}. The array of durations at the index of a removed ad group can
* be null or empty.
*
* @throws IllegalArgumentException if {@code adDurations.length != adGroupCount}.
* <p>Must only be used if {@link #removedAdGroupCount} is 0.
*/
@CheckResult
public AdPlaybackState withAdDurationsUs(long[][] adDurationUs) {
checkArgument(adDurationUs.length == adGroupCount);
checkState(removedAdGroupCount == 0);
AdGroup[] adGroups = Util.nullSafeArrayCopy(this.adGroups, this.adGroups.length);
for (int correctedAdGroupIndex = 0;
correctedAdGroupIndex < adGroupCount - removedAdGroupCount;
correctedAdGroupIndex++) {
adGroups[correctedAdGroupIndex] =
adGroups[correctedAdGroupIndex].withAdDurationsUs(
adDurationUs[removedAdGroupCount + correctedAdGroupIndex]);
for (int adGroupIndex = 0; adGroupIndex < adGroupCount; adGroupIndex++) {
adGroups[adGroupIndex] = adGroups[adGroupIndex].withAdDurationsUs(adDurationUs[adGroupIndex]);
}
return new AdPlaybackState(
adsId, adGroups, adResumePositionUs, contentDurationUs, removedAdGroupCount);
@ -1374,7 +1365,7 @@ public final class AdPlaybackState {
return false;
}
AdPlaybackState that = (AdPlaybackState) o;
return Objects.equals(adsId, that.adsId)
return Util.areEqual(adsId, that.adsId)
&& adGroupCount == that.adGroupCount
&& adResumePositionUs == that.adResumePositionUs
&& contentDurationUs == that.contentDurationUs

View File

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

View File

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

View File

@ -1039,10 +1039,7 @@ public final class Format {
/** The audio sampling rate in Hz, or {@link #NO_VALUE} if unknown or not applicable. */
public final int sampleRate;
/**
* The {@link C.PcmEncoding} for PCM or losslessly compressed audio. Set to {@link #NO_VALUE} for
* other media types.
*/
/** The {@link C.PcmEncoding} for PCM audio. Set to {@link #NO_VALUE} for other media types. */
@UnstableApi public final @C.PcmEncoding int pcmEncoding;
/**

View File

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

View File

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

View File

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

View File

@ -29,11 +29,11 @@ public final class MediaLibraryInfo {
/** The version of the library expressed as a string, for example "1.2.3" or "1.2.0-beta01". */
// Intentionally hardcoded. Do not derive from other constants (e.g. VERSION_INT) or vice versa.
public static final String VERSION = "1.6.1";
public static final String VERSION = "1.6.0-beta01";
/** 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.6.1";
public static final String VERSION_SLASHY = "AndroidXMedia3/1.6.0-beta01";
/**
* 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_006_001_3_00;
public static final int VERSION_INT = 1_006_000_1_01;
/** Whether the library was compiled with {@link Assertions} checks enabled. */
public static final boolean ASSERTIONS_ENABLED = true;

View File

@ -29,6 +29,7 @@ import androidx.annotation.IntRange;
import androidx.annotation.Nullable;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.lang.annotation.Documented;
@ -38,7 +39,6 @@ import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
/**
* Metadata of a {@link MediaItem}, playlist, or a combination of multiple sources of {@link
@ -249,8 +249,8 @@ public final class MediaMetadata {
@CanIgnoreReturnValue
public Builder maybeSetArtworkData(byte[] artworkData, @PictureType int artworkDataType) {
if (this.artworkData == null
|| artworkDataType == PICTURE_TYPE_FRONT_COVER
|| !Objects.equals(this.artworkDataType, PICTURE_TYPE_FRONT_COVER)) {
|| Util.areEqual(artworkDataType, PICTURE_TYPE_FRONT_COVER)
|| !Util.areEqual(this.artworkDataType, PICTURE_TYPE_FRONT_COVER)) {
this.artworkData = artworkData.clone();
this.artworkDataType = artworkDataType;
}
@ -1221,47 +1221,47 @@ public final class MediaMetadata {
return false;
}
MediaMetadata that = (MediaMetadata) obj;
return Objects.equals(title, that.title)
&& Objects.equals(artist, that.artist)
&& Objects.equals(albumTitle, that.albumTitle)
&& Objects.equals(albumArtist, that.albumArtist)
&& Objects.equals(displayTitle, that.displayTitle)
&& Objects.equals(subtitle, that.subtitle)
&& Objects.equals(description, that.description)
&& Objects.equals(durationMs, that.durationMs)
&& Objects.equals(userRating, that.userRating)
&& Objects.equals(overallRating, that.overallRating)
return Util.areEqual(title, that.title)
&& Util.areEqual(artist, that.artist)
&& Util.areEqual(albumTitle, that.albumTitle)
&& Util.areEqual(albumArtist, that.albumArtist)
&& Util.areEqual(displayTitle, that.displayTitle)
&& Util.areEqual(subtitle, that.subtitle)
&& Util.areEqual(description, that.description)
&& Util.areEqual(durationMs, that.durationMs)
&& Util.areEqual(userRating, that.userRating)
&& Util.areEqual(overallRating, that.overallRating)
&& Arrays.equals(artworkData, that.artworkData)
&& Objects.equals(artworkDataType, that.artworkDataType)
&& Objects.equals(artworkUri, that.artworkUri)
&& Objects.equals(trackNumber, that.trackNumber)
&& Objects.equals(totalTrackCount, that.totalTrackCount)
&& Objects.equals(folderType, that.folderType)
&& Objects.equals(isBrowsable, that.isBrowsable)
&& Objects.equals(isPlayable, that.isPlayable)
&& Objects.equals(recordingYear, that.recordingYear)
&& Objects.equals(recordingMonth, that.recordingMonth)
&& Objects.equals(recordingDay, that.recordingDay)
&& Objects.equals(releaseYear, that.releaseYear)
&& Objects.equals(releaseMonth, that.releaseMonth)
&& Objects.equals(releaseDay, that.releaseDay)
&& Objects.equals(writer, that.writer)
&& Objects.equals(composer, that.composer)
&& Objects.equals(conductor, that.conductor)
&& Objects.equals(discNumber, that.discNumber)
&& Objects.equals(totalDiscCount, that.totalDiscCount)
&& Objects.equals(genre, that.genre)
&& Objects.equals(compilation, that.compilation)
&& Objects.equals(station, that.station)
&& Objects.equals(mediaType, that.mediaType)
&& Objects.equals(supportedCommands, that.supportedCommands)
&& Util.areEqual(artworkDataType, that.artworkDataType)
&& Util.areEqual(artworkUri, that.artworkUri)
&& Util.areEqual(trackNumber, that.trackNumber)
&& Util.areEqual(totalTrackCount, that.totalTrackCount)
&& Util.areEqual(folderType, that.folderType)
&& Util.areEqual(isBrowsable, that.isBrowsable)
&& Util.areEqual(isPlayable, that.isPlayable)
&& Util.areEqual(recordingYear, that.recordingYear)
&& Util.areEqual(recordingMonth, that.recordingMonth)
&& Util.areEqual(recordingDay, that.recordingDay)
&& Util.areEqual(releaseYear, that.releaseYear)
&& Util.areEqual(releaseMonth, that.releaseMonth)
&& Util.areEqual(releaseDay, that.releaseDay)
&& Util.areEqual(writer, that.writer)
&& Util.areEqual(composer, that.composer)
&& Util.areEqual(conductor, that.conductor)
&& Util.areEqual(discNumber, that.discNumber)
&& Util.areEqual(totalDiscCount, that.totalDiscCount)
&& Util.areEqual(genre, that.genre)
&& Util.areEqual(compilation, that.compilation)
&& Util.areEqual(station, that.station)
&& Util.areEqual(mediaType, that.mediaType)
&& Util.areEqual(supportedCommands, that.supportedCommands)
&& ((extras == null) == (that.extras == null));
}
@SuppressWarnings("deprecation") // Hashing deprecated fields.
@Override
public int hashCode() {
return Objects.hash(
return Objects.hashCode(
title,
artist,
albumTitle,

View File

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

View File

@ -36,7 +36,6 @@ import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Objects;
/** Thrown when a non locally recoverable playback failure occurs. */
public class PlaybackException extends Exception {
@ -554,17 +553,17 @@ public class PlaybackException extends Exception {
@Nullable Throwable thisCause = getCause();
@Nullable Throwable thatCause = other.getCause();
if (thisCause != null && thatCause != null) {
if (!Objects.equals(thisCause.getMessage(), thatCause.getMessage())) {
if (!Util.areEqual(thisCause.getMessage(), thatCause.getMessage())) {
return false;
}
if (!Objects.equals(thisCause.getClass(), thatCause.getClass())) {
if (!Util.areEqual(thisCause.getClass(), thatCause.getClass())) {
return false;
}
} else if (thisCause != null || thatCause != null) {
return false;
}
return errorCode == other.errorCode
&& Objects.equals(getMessage(), other.getMessage())
&& Util.areEqual(getMessage(), other.getMessage())
&& timestampMs == other.timestampMs;
}

View File

@ -88,18 +88,6 @@ public final class PlaybackParameters {
return new PlaybackParameters(speed, pitch);
}
/**
* Returns a copy with the given pitch.
*
* @param pitch The new pitch. Must be greater than zero.
* @return The copied playback parameters.
*/
@UnstableApi
@CheckResult
public PlaybackParameters withPitch(@FloatRange(from = 0, fromInclusive = false) float pitch) {
return new PlaybackParameters(speed, pitch);
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {

View File

@ -37,6 +37,7 @@ import androidx.media3.common.text.CueGroup;
import androidx.media3.common.util.Size;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import com.google.common.base.Objects;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
@ -44,7 +45,6 @@ import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
/**
* A media player interface defining high-level functionality, such as the ability to play, pause,
@ -352,13 +352,13 @@ public interface Player {
}
PositionInfo that = (PositionInfo) o;
return equalsForBundling(that)
&& Objects.equals(windowUid, that.windowUid)
&& Objects.equals(periodUid, that.periodUid);
&& Objects.equal(windowUid, that.windowUid)
&& Objects.equal(periodUid, that.periodUid);
}
@Override
public int hashCode() {
return Objects.hash(
return Objects.hashCode(
windowUid,
mediaItemIndex,
mediaItem,
@ -382,7 +382,7 @@ public interface Player {
&& contentPositionMs == other.contentPositionMs
&& adGroupIndex == other.adGroupIndex
&& adIndexInAdGroup == other.adIndexInAdGroup
&& Objects.equals(mediaItem, other.mediaItem);
&& Objects.equal(mediaItem, other.mediaItem);
}
@VisibleForTesting static final String FIELD_MEDIA_ITEM_INDEX = Util.intToStringMaxRadix(0);
@ -1613,7 +1613,7 @@ public interface Player {
/** {@link #getDeviceInfo()} changed. */
int EVENT_DEVICE_INFO_CHANGED = 29;
/** {@link #getDeviceVolume()} or {@link #isDeviceMuted()} changed. */
/** {@link #getDeviceVolume()} changed. */
int EVENT_DEVICE_VOLUME_CHANGED = 30;
/**

View File

@ -96,7 +96,7 @@ import org.checkerframework.checker.nullness.qual.RequiresNonNull;
public abstract class SimpleBasePlayer extends BasePlayer {
/** An immutable state description of the player. */
public static final class State {
protected static final class State {
/** A builder for {@link State} objects. */
public static final class Builder {
@ -1793,9 +1793,9 @@ public abstract class SimpleBasePlayer extends BasePlayer {
return this.uid.equals(mediaItemData.uid)
&& this.tracks.equals(mediaItemData.tracks)
&& this.mediaItem.equals(mediaItemData.mediaItem)
&& Objects.equals(this.mediaMetadata, mediaItemData.mediaMetadata)
&& Objects.equals(this.manifest, mediaItemData.manifest)
&& Objects.equals(this.liveConfiguration, mediaItemData.liveConfiguration)
&& Util.areEqual(this.mediaMetadata, mediaItemData.mediaMetadata)
&& Util.areEqual(this.manifest, mediaItemData.manifest)
&& Util.areEqual(this.liveConfiguration, mediaItemData.liveConfiguration)
&& this.presentationStartTimeMs == mediaItemData.presentationStartTimeMs
&& this.windowStartTimeMs == mediaItemData.windowStartTimeMs
&& this.elapsedRealtimeEpochOffsetMs == mediaItemData.elapsedRealtimeEpochOffsetMs
@ -3460,7 +3460,7 @@ public abstract class SimpleBasePlayer extends BasePlayer {
* index is in the range {@code fromIndex} &lt; {@code toIndex} &lt;= {@link
* #getMediaItemCount()}.
* @param newIndex The new index of the first moved item. The index is in the range {@code 0}
* &lt;= {@code newIndex} &lt;= {@link #getMediaItemCount() - (toIndex - fromIndex)}.
* &lt;= {@code newIndex} &lt; {@link #getMediaItemCount() - (toIndex - fromIndex)}.
* @return A {@link ListenableFuture} indicating the completion of all immediate {@link State}
* changes caused by this call.
*/
@ -3475,9 +3475,9 @@ public abstract class SimpleBasePlayer extends BasePlayer {
* <p>Will only be called if {@link Player#COMMAND_CHANGE_MEDIA_ITEMS} is available.
*
* @param fromIndex The start index of the items to replace. The index is in the range 0 &lt;=
* {@code fromIndex} &lt;= {@link #getMediaItemCount()}.
* {@code fromIndex} &lt; {@link #getMediaItemCount()}.
* @param toIndex The index of the first item not to be replaced (exclusive). The index is in the
* range {@code fromIndex} &lt;= {@code toIndex} &lt;= {@link #getMediaItemCount()}.
* range {@code fromIndex} &lt; {@code toIndex} &lt;= {@link #getMediaItemCount()}.
* @param mediaItems The media items to replace the specified range with.
* @return A {@link ListenableFuture} indicating the completion of all immediate {@link State}
* changes caused by this call.
@ -3486,9 +3486,6 @@ public abstract class SimpleBasePlayer extends BasePlayer {
protected ListenableFuture<?> handleReplaceMediaItems(
int fromIndex, int toIndex, List<MediaItem> mediaItems) {
ListenableFuture<?> addFuture = handleAddMediaItems(toIndex, mediaItems);
if (fromIndex == toIndex) {
return addFuture;
}
ListenableFuture<?> removeFuture = handleRemoveMediaItems(fromIndex, toIndex);
return Util.transformFutureAsync(addFuture, unused -> removeFuture);
}
@ -3622,7 +3619,7 @@ public abstract class SimpleBasePlayer extends BasePlayer {
Player.EVENT_MEDIA_ITEM_TRANSITION,
listener -> listener.onMediaItemTransition(mediaItem, mediaItemTransitionReason));
}
if (!Objects.equals(previousState.playerError, newState.playerError)) {
if (!Util.areEqual(previousState.playerError, newState.playerError)) {
listeners.queueEvent(
Player.EVENT_PLAYER_ERROR,
listener -> listener.onPlayerErrorChanged(newState.playerError));

View File

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

View File

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

View File

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

View File

@ -161,11 +161,6 @@ public final class TrackGroup {
return id.equals(other.id) && Arrays.equals(formats, other.formats);
}
@Override
public String toString() {
return id + ": " + Arrays.toString(formats);
}
private static final String FIELD_FORMATS = Util.intToStringMaxRadix(0);
private static final String FIELD_ID = Util.intToStringMaxRadix(1);

View File

@ -22,6 +22,7 @@ import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.media3.common.AudioAttributes;
@ -55,7 +56,7 @@ public final class AudioFocusRequestCompat {
this.audioAttributes = audioFocusRequestCompat;
this.pauseOnDuck = pauseOnDuck;
if (Util.SDK_INT < 26) {
if (Util.SDK_INT < 26 && focusChangeHandler.getLooper() != Looper.getMainLooper()) {
this.onAudioFocusChangeListener =
new OnAudioFocusChangeListenerHandlerCompat(
onAudioFocusChangeListener, focusChangeHandler);
@ -325,7 +326,9 @@ public final class AudioFocusRequestCompat {
* a specific thread prior to API 26.
*/
private static class OnAudioFocusChangeListenerHandlerCompat
implements AudioManager.OnAudioFocusChangeListener {
implements Handler.Callback, AudioManager.OnAudioFocusChangeListener {
private static final int FOCUS_CHANGE = 0x002a74b2;
private final Handler handler;
private final AudioManager.OnAudioFocusChangeListener listener;
@ -333,12 +336,21 @@ public final class AudioFocusRequestCompat {
/* package */ OnAudioFocusChangeListenerHandlerCompat(
AudioManager.OnAudioFocusChangeListener listener, Handler handler) {
this.listener = listener;
this.handler = Util.createHandler(handler.getLooper(), /* callback= */ null);
this.handler = Util.createHandler(handler.getLooper(), /* callback= */ this);
}
@Override
public void onAudioFocusChange(int focusChange) {
Util.postOrRun(handler, () -> listener.onAudioFocusChange(focusChange));
handler.sendMessage(Message.obtain(handler, FOCUS_CHANGE, focusChange, 0));
}
@Override
public boolean handleMessage(Message message) {
if (message.what == FOCUS_CHANGE) {
listener.onAudioFocusChange(message.arg1);
return true;
}
return false;
}
}
}

View File

@ -15,18 +15,12 @@
*/
package androidx.media3.common.audio;
import static androidx.media3.common.util.Assertions.checkNotNull;
import static java.lang.annotation.ElementType.TYPE_USE;
import android.content.Context;
import android.media.AudioManager;
import android.os.Looper;
import androidx.annotation.IntDef;
import androidx.annotation.IntRange;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.util.BackgroundExecutor;
import androidx.media3.common.util.ConditionVariable;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
@ -34,14 +28,11 @@ import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** Compatibility layer for {@link AudioManager} with fallbacks for older Android versions. */
@UnstableApi
public final class AudioManagerCompat {
private static final String TAG = "AudioManagerCompat";
/**
* Audio focus gain types. One of {@link #AUDIOFOCUS_NONE}, {@link #AUDIOFOCUS_GAIN}, {@link
* #AUDIOFOCUS_GAIN_TRANSIENT}, {@link #AUDIOFOCUS_GAIN_TRANSIENT_MAY_DUCK} or {@link
@ -92,55 +83,6 @@ public final class AudioManagerCompat {
public static final int AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE =
AudioManager.AUDIOFOCUS_GAIN_TRANSIENT_EXCLUSIVE;
@SuppressWarnings("NonFinalStaticField") // Lazily initialized under class lock
@Nullable
private static AudioManager audioManager;
@SuppressWarnings("NonFinalStaticField") // Lazily initialized under class lock
private static @MonotonicNonNull Context applicationContext;
/**
* Returns the {@link AudioManager}.
*
* <p>This method avoids potential threading issues where AudioManager keeps access to the thread
* it was created on until after this thread is stopped.
*
* <p>It is recommended to use this method from a background thread.
*
* @param context A {@link Context}.
* @return The {@link AudioManager}.
*/
public static synchronized AudioManager getAudioManager(Context context) {
Context applicationContext = context.getApplicationContext();
if (AudioManagerCompat.applicationContext != applicationContext) {
// Reset cached instance if the application context changed. This should only happen in tests.
audioManager = null;
}
if (audioManager != null) {
return audioManager;
}
@Nullable Looper myLooper = Looper.myLooper();
if (myLooper == null || myLooper == Looper.getMainLooper()) {
// The AudioManager will assume the main looper as default callback anyway, so create the
// instance here without using BackgroundExecutor.
audioManager = (AudioManager) applicationContext.getSystemService(Context.AUDIO_SERVICE);
return checkNotNull(audioManager);
}
// Create the audio manager on the BackgroundExecutor to avoid running the potentially blocking
// command on the main thread but still use a thread that is guaranteed to exist for the
// lifetime of the app.
ConditionVariable audioManagerSetCondition = new ConditionVariable();
BackgroundExecutor.get()
.execute(
() -> {
audioManager =
(AudioManager) applicationContext.getSystemService(Context.AUDIO_SERVICE);
audioManagerSetCondition.open();
});
audioManagerSetCondition.blockUninterruptible();
return checkNotNull(audioManager);
}
/**
* Requests audio focus. See the {@link AudioFocusRequestCompat} for information about the options
* available to configure your request, and notification of focus gain and loss.
@ -220,7 +162,10 @@ public final class AudioManagerCompat {
try {
return audioManager.getStreamVolume(streamType);
} catch (RuntimeException e) {
Log.w(TAG, "Could not retrieve stream volume for stream type " + streamType, e);
Log.w(
"AudioManagerCompat",
"Could not retrieve stream volume for stream type " + streamType,
e);
return audioManager.getStreamMaxVolume(streamType);
}
}

View File

@ -20,9 +20,9 @@ import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import com.google.common.base.Objects;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.Objects;
/**
* Interface for audio processors, which take audio data as input and transform it, potentially
@ -107,7 +107,7 @@ public interface AudioProcessor {
@Override
public int hashCode() {
return Objects.hash(sampleRate, channelCount, encoding);
return Objects.hashCode(sampleRate, channelCount, encoding);
}
}

View File

@ -21,24 +21,25 @@ import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.common.util.SpeedProviderUtil.getNextSpeedChangeSamplePosition;
import static androidx.media3.common.util.SpeedProviderUtil.getSampleAlignedSpeed;
import static androidx.media3.common.util.Util.sampleCountToDurationUs;
import static androidx.media3.common.util.Util.scaleLargeValue;
import static java.lang.Math.min;
import static java.lang.Math.round;
import androidx.annotation.GuardedBy;
import androidx.annotation.IntRange;
import androidx.annotation.VisibleForTesting;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.util.LongArray;
import androidx.media3.common.util.LongArrayQueue;
import androidx.media3.common.util.SpeedProviderUtil;
import androidx.media3.common.util.TimestampConsumer;
import androidx.media3.common.util.UnstableApi;
import java.math.RoundingMode;
import androidx.media3.common.util.Util;
import java.nio.ByteBuffer;
import java.util.ArrayDeque;
import java.util.Queue;
import java.util.function.LongConsumer;
import org.checkerframework.checker.initialization.qual.UnknownInitialization;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/**
* An {@link AudioProcessor} that changes the speed of audio samples depending on their timestamp.
@ -66,12 +67,34 @@ public final class SpeedChangingAudioProcessor implements AudioProcessor {
@GuardedBy("lock")
private final Queue<TimestampConsumer> pendingCallbacks;
// Elements in the same positions in the arrays are associated.
@GuardedBy("lock")
private LongArray inputSegmentStartTimesUs;
@GuardedBy("lock")
private LongArray outputSegmentStartTimesUs;
@GuardedBy("lock")
private long lastProcessedInputTimeUs;
@GuardedBy("lock")
private long lastSpeedAdjustedInputTimeUs;
@GuardedBy("lock")
private long lastSpeedAdjustedOutputTimeUs;
@GuardedBy("lock")
private long speedAdjustedTimeAsyncInputTimeUs;
@GuardedBy("lock")
private float currentSpeed;
private long framesRead;
private boolean endOfStreamQueuedToSonic;
/** The current input audio format. */
@GuardedBy("lock")
private AudioFormat inputAudioFormat;
private AudioFormat pendingInputAudioFormat;
@ -89,6 +112,7 @@ public final class SpeedChangingAudioProcessor implements AudioProcessor {
new SynchronizedSonicAudioProcessor(lock, /* keepActiveWithDefaultParameters= */ true);
pendingCallbackInputTimesUs = new LongArrayQueue();
pendingCallbacks = new ArrayDeque<>();
speedAdjustedTimeAsyncInputTimeUs = C.TIME_UNSET;
resetInternalState(/* shouldResetSpeed= */ true);
}
@ -96,10 +120,10 @@ public final class SpeedChangingAudioProcessor implements AudioProcessor {
public static long getSampleCountAfterProcessorApplied(
SpeedProvider speedProvider,
@IntRange(from = 1) int inputSampleRateHz,
@IntRange(from = 0) long inputSamples) {
@IntRange(from = 1) long inputSamples) {
checkArgument(speedProvider != null);
checkArgument(inputSampleRateHz > 0);
checkArgument(inputSamples >= 0);
checkArgument(inputSamples > 0);
long outputSamples = 0;
long positionSamples = 0;
@ -147,22 +171,18 @@ public final class SpeedChangingAudioProcessor implements AudioProcessor {
@Override
public void queueInput(ByteBuffer inputBuffer) {
AudioFormat format;
synchronized (lock) {
format = inputAudioFormat;
}
float newSpeed = getSampleAlignedSpeed(speedProvider, framesRead, format.sampleRate);
long currentTimeUs = sampleCountToDurationUs(framesRead, inputAudioFormat.sampleRate);
float newSpeed = getSampleAlignedSpeed(speedProvider, framesRead, inputAudioFormat.sampleRate);
long nextSpeedChangeSamplePosition =
getNextSpeedChangeSamplePosition(speedProvider, framesRead, format.sampleRate);
getNextSpeedChangeSamplePosition(speedProvider, framesRead, inputAudioFormat.sampleRate);
updateSpeed(newSpeed);
updateSpeed(newSpeed, currentTimeUs);
int inputBufferLimit = inputBuffer.limit();
int bytesToNextSpeedChange;
if (nextSpeedChangeSamplePosition != C.INDEX_UNSET) {
bytesToNextSpeedChange =
(int) ((nextSpeedChangeSamplePosition - framesRead) * format.bytesPerFrame);
(int) ((nextSpeedChangeSamplePosition - framesRead) * inputAudioFormat.bytesPerFrame);
// Update the input buffer limit to make sure that all samples processed have the same speed.
inputBuffer.limit(min(inputBufferLimit, inputBuffer.position() + bytesToNextSpeedChange));
} else {
@ -177,8 +197,10 @@ public final class SpeedChangingAudioProcessor implements AudioProcessor {
endOfStreamQueuedToSonic = true;
}
long bytesRead = inputBuffer.position() - startPosition;
checkState(bytesRead % format.bytesPerFrame == 0, "A frame was not queued completely.");
framesRead += bytesRead / format.bytesPerFrame;
checkState(
bytesRead % inputAudioFormat.bytesPerFrame == 0, "A frame was not queued completely.");
framesRead += bytesRead / inputAudioFormat.bytesPerFrame;
updateLastProcessedInputTime();
inputBuffer.limit(inputBufferLimit);
}
@ -193,7 +215,9 @@ public final class SpeedChangingAudioProcessor implements AudioProcessor {
@Override
public ByteBuffer getOutput() {
return sonicAudioProcessor.getOutput();
ByteBuffer output = sonicAudioProcessor.getOutput();
processPendingCallbacks();
return output;
}
@Override
@ -204,12 +228,9 @@ public final class SpeedChangingAudioProcessor implements AudioProcessor {
@Override
public void flush() {
inputEnded = false;
inputAudioFormat = pendingInputAudioFormat;
resetInternalState(/* shouldResetSpeed= */ false);
synchronized (lock) {
inputAudioFormat = pendingInputAudioFormat;
sonicAudioProcessor.flush();
processPendingCallbacks();
}
sonicAudioProcessor.flush();
}
@Override
@ -217,11 +238,7 @@ public final class SpeedChangingAudioProcessor implements AudioProcessor {
flush();
pendingInputAudioFormat = AudioFormat.NOT_SET;
pendingOutputAudioFormat = AudioFormat.NOT_SET;
synchronized (lock) {
inputAudioFormat = AudioFormat.NOT_SET;
pendingCallbackInputTimesUs.clear();
pendingCallbacks.clear();
}
inputAudioFormat = AudioFormat.NOT_SET;
resetInternalState(/* shouldResetSpeed= */ true);
sonicAudioProcessor.reset();
}
@ -244,125 +261,154 @@ public final class SpeedChangingAudioProcessor implements AudioProcessor {
* @param callback The callback called with the output time. May be called on a different thread
* from the caller of this method.
*/
// TODO(b/381553948): Accept an executor on which to dispatch the callback.
public void getSpeedAdjustedTimeAsync(long inputTimeUs, TimestampConsumer callback) {
int sampleRate;
synchronized (lock) {
sampleRate = inputAudioFormat.sampleRate;
if (sampleRate == Format.NO_VALUE) {
pendingCallbackInputTimesUs.add(inputTimeUs);
pendingCallbacks.add(callback);
checkArgument(speedAdjustedTimeAsyncInputTimeUs < inputTimeUs);
speedAdjustedTimeAsyncInputTimeUs = inputTimeUs;
if ((inputTimeUs <= lastProcessedInputTimeUs && pendingCallbackInputTimesUs.isEmpty())
|| isEnded()) {
callback.onTimestamp(calculateSpeedAdjustedTime(inputTimeUs));
return;
}
pendingCallbackInputTimesUs.add(inputTimeUs);
pendingCallbacks.add(callback);
}
// TODO(b/381553948): Use an executor to invoke callback.
callback.onTimestamp(
getDurationUsAfterProcessorApplied(speedProvider, sampleRate, inputTimeUs));
}
/**
* Returns the input media duration in microseconds for the given playout duration.
* Returns the input media duration for the given playout duration.
*
* <p>This method returns the inverse of {@link #getSpeedAdjustedTimeAsync} when the instance has
* been configured and flushed. Otherwise, it returns {@code playoutDurationUs}.
* <p>Both durations are counted from the last {@link #reset()} or {@link #flush()} of the audio
* processor.
*
* <p>The {@code playoutDurationUs} must be less than last processed buffer output time.
*
* @param playoutDurationUs The playout duration in microseconds.
* @return The corresponding input duration in microseconds.
*/
public long getMediaDurationUs(long playoutDurationUs) {
int sampleRate;
synchronized (lock) {
sampleRate = inputAudioFormat.sampleRate;
int floorIndex = outputSegmentStartTimesUs.size() - 1;
while (floorIndex > 0 && outputSegmentStartTimesUs.get(floorIndex) > playoutDurationUs) {
floorIndex--;
}
long lastSegmentOutputDurationUs =
playoutDurationUs - outputSegmentStartTimesUs.get(floorIndex);
long lastSegmentInputDurationUs;
if (floorIndex == outputSegmentStartTimesUs.size() - 1) {
lastSegmentInputDurationUs = getMediaDurationUsAtCurrentSpeed(lastSegmentOutputDurationUs);
} else {
lastSegmentInputDurationUs =
round(
lastSegmentOutputDurationUs
* divide(
inputSegmentStartTimesUs.get(floorIndex + 1)
- inputSegmentStartTimesUs.get(floorIndex),
outputSegmentStartTimesUs.get(floorIndex + 1)
- outputSegmentStartTimesUs.get(floorIndex)));
}
return inputSegmentStartTimesUs.get(floorIndex) + lastSegmentInputDurationUs;
}
if (sampleRate == Format.NO_VALUE) {
return playoutDurationUs;
}
long outputSamples =
scaleLargeValue(playoutDurationUs, sampleRate, C.MICROS_PER_SECOND, RoundingMode.HALF_EVEN);
long inputSamples = getInputFrameCountForOutput(speedProvider, sampleRate, outputSamples);
return sampleCountToDurationUs(inputSamples, sampleRate);
}
/**
* Returns the number of input frames needed to output a specific number of frames, given a speed
* provider, input sample rate, and number of output frames.
*
* <p>This is the inverse operation of {@link #getSampleCountAfterProcessorApplied}.
* Assuming enough audio has been processed, calculates the time at which the {@code inputTimeUs}
* is outputted at after the speed changes has been applied.
*/
@VisibleForTesting
/* package */ static long getInputFrameCountForOutput(
SpeedProvider speedProvider,
@IntRange(from = 1) int inputSampleRate,
@IntRange(from = 0) long outputFrameCount) {
checkArgument(inputSampleRate > 0);
checkArgument(outputFrameCount >= 0);
long inputSampleCount = 0;
while (outputFrameCount > 0) {
long boundarySamples =
getNextSpeedChangeSamplePosition(speedProvider, inputSampleCount, inputSampleRate);
float speed = getSampleAlignedSpeed(speedProvider, inputSampleCount, inputSampleRate);
long outputSamplesForSection =
Sonic.getExpectedFrameCountAfterProcessorApplied(
/* inputSampleRateHz= */ inputSampleRate,
/* outputSampleRateHz= */ inputSampleRate,
/* speed= */ speed,
/* pitch= */ speed,
/* inputFrameCount= */ boundarySamples - inputSampleCount);
if (boundarySamples == C.INDEX_UNSET || outputSamplesForSection > outputFrameCount) {
inputSampleCount +=
Sonic.getExpectedInputFrameCountForOutputFrameCount(
/* inputSampleRateHz= */ inputSampleRate,
/* outputSampleRateHz= */ inputSampleRate,
/* speed= */ speed,
/* pitch= */ speed,
outputFrameCount);
outputFrameCount = 0;
} else {
outputFrameCount -= outputSamplesForSection;
inputSampleCount = boundarySamples;
}
@SuppressWarnings("GuardedBy") // All call sites are guarded.
private long calculateSpeedAdjustedTime(long inputTimeUs) {
int floorIndex = inputSegmentStartTimesUs.size() - 1;
while (floorIndex > 0 && inputSegmentStartTimesUs.get(floorIndex) > inputTimeUs) {
floorIndex--;
}
return inputSampleCount;
long lastSegmentOutputDurationUs;
if (floorIndex == inputSegmentStartTimesUs.size() - 1) {
if (lastSpeedAdjustedInputTimeUs < inputSegmentStartTimesUs.get(floorIndex)) {
lastSpeedAdjustedInputTimeUs = inputSegmentStartTimesUs.get(floorIndex);
lastSpeedAdjustedOutputTimeUs = outputSegmentStartTimesUs.get(floorIndex);
}
long lastSegmentInputDurationUs = inputTimeUs - lastSpeedAdjustedInputTimeUs;
lastSegmentOutputDurationUs = getPlayoutDurationUsAtCurrentSpeed(lastSegmentInputDurationUs);
} else {
long lastSegmentInputDurationUs = inputTimeUs - lastSpeedAdjustedInputTimeUs;
lastSegmentOutputDurationUs =
round(
lastSegmentInputDurationUs
* divide(
outputSegmentStartTimesUs.get(floorIndex + 1)
- outputSegmentStartTimesUs.get(floorIndex),
inputSegmentStartTimesUs.get(floorIndex + 1)
- inputSegmentStartTimesUs.get(floorIndex)));
}
lastSpeedAdjustedInputTimeUs = inputTimeUs;
lastSpeedAdjustedOutputTimeUs += lastSegmentOutputDurationUs;
return lastSpeedAdjustedOutputTimeUs;
}
private static long getDurationUsAfterProcessorApplied(
SpeedProvider speedProvider, int sampleRate, long inputDurationUs) {
long inputSamples =
scaleLargeValue(inputDurationUs, sampleRate, C.MICROS_PER_SECOND, RoundingMode.HALF_EVEN);
long outputSamples =
getSampleCountAfterProcessorApplied(speedProvider, sampleRate, inputSamples);
return sampleCountToDurationUs(outputSamples, sampleRate);
private static double divide(long dividend, long divisor) {
return ((double) dividend) / divisor;
}
private void processPendingCallbacks() {
synchronized (lock) {
if (inputAudioFormat.sampleRate == Format.NO_VALUE) {
return;
}
while (!pendingCallbacks.isEmpty()) {
long inputTimeUs = pendingCallbackInputTimesUs.remove();
TimestampConsumer consumer = pendingCallbacks.remove();
// TODO(b/381553948): Use an executor to invoke callback.
consumer.onTimestamp(
getDurationUsAfterProcessorApplied(
speedProvider, inputAudioFormat.sampleRate, inputTimeUs));
while (!pendingCallbacks.isEmpty()
&& (pendingCallbackInputTimesUs.element() <= lastProcessedInputTimeUs || isEnded())) {
pendingCallbacks
.remove()
.onTimestamp(calculateSpeedAdjustedTime(pendingCallbackInputTimesUs.remove()));
}
}
}
private void updateSpeed(float newSpeed) {
if (newSpeed != currentSpeed) {
currentSpeed = newSpeed;
sonicAudioProcessor.setSpeed(newSpeed);
sonicAudioProcessor.setPitch(newSpeed);
// Invalidate any previously created buffers in SonicAudioProcessor and the base class.
sonicAudioProcessor.flush();
endOfStreamQueuedToSonic = false;
private void updateSpeed(float newSpeed, long timeUs) {
synchronized (lock) {
if (newSpeed != currentSpeed) {
updateSpeedChangeArrays(timeUs);
currentSpeed = newSpeed;
sonicAudioProcessor.setSpeed(newSpeed);
sonicAudioProcessor.setPitch(newSpeed);
// Invalidate any previously created buffers in SonicAudioProcessor and the base class.
sonicAudioProcessor.flush();
endOfStreamQueuedToSonic = false;
}
}
}
@SuppressWarnings("GuardedBy") // All call sites are guarded.
private void updateSpeedChangeArrays(long currentSpeedChangeInputTimeUs) {
long lastSpeedChangeOutputTimeUs =
outputSegmentStartTimesUs.get(outputSegmentStartTimesUs.size() - 1);
long lastSpeedChangeInputTimeUs =
inputSegmentStartTimesUs.get(inputSegmentStartTimesUs.size() - 1);
long lastSpeedSegmentMediaDurationUs =
currentSpeedChangeInputTimeUs - lastSpeedChangeInputTimeUs;
inputSegmentStartTimesUs.add(currentSpeedChangeInputTimeUs);
outputSegmentStartTimesUs.add(
lastSpeedChangeOutputTimeUs
+ getPlayoutDurationUsAtCurrentSpeed(lastSpeedSegmentMediaDurationUs));
}
private long getPlayoutDurationUsAtCurrentSpeed(long mediaDurationUs) {
return sonicAudioProcessor.getPlayoutDuration(mediaDurationUs);
}
private long getMediaDurationUsAtCurrentSpeed(long playoutDurationUs) {
return sonicAudioProcessor.getMediaDuration(playoutDurationUs);
}
private void updateLastProcessedInputTime() {
synchronized (lock) {
// TODO - b/320242819: Investigate whether bytesRead can be used here rather than
// sonicAudioProcessor.getProcessedInputBytes().
long currentProcessedInputDurationUs =
Util.scaleLargeTimestamp(
/* timestamp= */ sonicAudioProcessor.getProcessedInputBytes(),
/* multiplier= */ C.MICROS_PER_SECOND,
/* divisor= */ (long) inputAudioFormat.sampleRate * inputAudioFormat.bytesPerFrame);
lastProcessedInputTimeUs =
inputSegmentStartTimesUs.get(inputSegmentStartTimesUs.size() - 1)
+ currentProcessedInputDurationUs;
}
}
@ -374,12 +420,28 @@ public final class SpeedChangingAudioProcessor implements AudioProcessor {
*
* @param shouldResetSpeed Whether {@link #currentSpeed} should be reset to its default value.
*/
@EnsuresNonNull({"inputSegmentStartTimesUs", "outputSegmentStartTimesUs"})
@RequiresNonNull("lock")
private void resetInternalState(
@UnknownInitialization SpeedChangingAudioProcessor this, boolean shouldResetSpeed) {
if (shouldResetSpeed) {
currentSpeed = 1f;
synchronized (lock) {
inputSegmentStartTimesUs = new LongArray();
outputSegmentStartTimesUs = new LongArray();
inputSegmentStartTimesUs.add(0);
outputSegmentStartTimesUs.add(0);
lastProcessedInputTimeUs = 0;
lastSpeedAdjustedInputTimeUs = 0;
lastSpeedAdjustedOutputTimeUs = 0;
if (shouldResetSpeed) {
currentSpeed = 1f;
}
}
framesRead = 0;
endOfStreamQueuedToSonic = false;
// TODO: b/339842724 - This should ideally also reset speedAdjustedTimeAsyncInputTimeUs and
// clear pendingCallbacks and pendingCallbacksInputTimes. We can't do this at the moment
// because some clients register callbacks with getSpeedAdjustedTimeAsync before this audio
// processor is flushed.
}
}

View File

@ -40,6 +40,7 @@ import androidx.annotation.Nullable;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import com.google.common.base.Objects;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.ByteArrayOutputStream;
import java.lang.annotation.Documented;
@ -47,7 +48,6 @@ import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.ArrayList;
import java.util.Objects;
import org.checkerframework.dataflow.qual.Pure;
/** Contains information about a specific cue, including textual content and formatting data. */
@ -396,7 +396,7 @@ public final class Cue {
@Override
public int hashCode() {
return Objects.hash(
return Objects.hashCode(
text,
textAlignment,
multiRowAlignment,

View File

@ -31,8 +31,6 @@ public final class BackgroundExecutor {
*
* <p>Must only be used for quick, high-priority tasks to ensure other background tasks are not
* blocked.
*
* <p>The thread is guaranteed to be alive for the lifetime of the application.
*/
public static synchronized Executor get() {
if (staticInstance == null) {
@ -44,9 +42,6 @@ public final class BackgroundExecutor {
/**
* Sets the {@link Executor} to be returned from {@link #get()}.
*
* <p>Note that the thread of the provided {@link Executor} must stay alive for the lifetime of
* the application.
*
* @param executor An {@link Executor} that runs tasks on background threads and should only be
* used for quick, high-priority tasks to ensure other background tasks are not blocked.
*/

View File

@ -43,7 +43,6 @@ import java.nio.ByteOrder;
import java.nio.FloatBuffer;
import java.util.Arrays;
import java.util.List;
import java.util.Objects;
import javax.microedition.khronos.egl.EGL10;
/** OpenGL ES utilities. */
@ -210,7 +209,7 @@ public final class GlUtil {
*/
public static boolean isYuvTargetExtensionSupported() {
@Nullable String glExtensions;
if (Objects.equals(EGL14.eglGetCurrentContext(), EGL14.EGL_NO_CONTEXT)) {
if (Util.areEqual(EGL14.eglGetCurrentContext(), EGL14.EGL_NO_CONTEXT)) {
// Create a placeholder context and make it current to allow calling GLES20.glGetString().
try {
EGLDisplay eglDisplay = getDefaultEglDisplay();

View File

@ -128,10 +128,6 @@ public final class MediaFormatUtil {
formatBuilder.setInitializationData(csdBuffers.build());
if (mediaFormat.containsKey(MediaFormat.KEY_TRACK_ID)) {
formatBuilder.setId(mediaFormat.getInteger(MediaFormat.KEY_TRACK_ID));
}
return formatBuilder.build();
}
@ -179,10 +175,6 @@ public final class MediaFormatUtil {
result.setInteger(MediaFormat.KEY_ENCODER_PADDING, format.encoderPadding);
maybeSetPixelAspectRatio(result, format.pixelWidthHeightRatio);
if (format.id != null) {
result.setInteger(MediaFormat.KEY_TRACK_ID, Integer.parseInt(format.id));
}
return result;
}

View File

@ -19,6 +19,7 @@ import androidx.annotation.Nullable;
import com.google.common.collect.ImmutableSet;
import com.google.common.primitives.Chars;
import com.google.common.primitives.Ints;
import com.google.common.primitives.UnsignedBytes;
import com.google.errorprone.annotations.CheckReturnValue;
import java.nio.ByteBuffer;
import java.nio.charset.Charset;
@ -720,8 +721,8 @@ public final class ParsableByteArray {
/**
* Peeks at the character at {@link #position} (as decoded by {@code charset}), returns it and the
* number of bytes the character takes up within the array packed into an int. First two bytes are
* the character and the second two is the size in bytes it takes. Returns 0 if {@link
* number of bytes the character takes up within the array packed into an int. First four bytes
* are the character and the second four is the size in bytes it takes. Returns 0 if {@link
* #bytesLeft()} doesn't allow reading a whole character in {@code charset} or if the {@code
* charset} is not one of US_ASCII, UTF-8, UTF-16, UTF-16BE, or UTF-16LE.
*
@ -729,27 +730,23 @@ public final class ParsableByteArray {
* bytes for UTF-16).
*/
private int peekCharacterAndSize(Charset charset) {
byte charByte1;
byte charByte2;
byte characterSize;
byte character;
short characterSize;
if ((charset.equals(StandardCharsets.UTF_8) || charset.equals(StandardCharsets.US_ASCII))
&& bytesLeft() >= 1) {
charByte1 = 0;
charByte2 = data[position];
character = (byte) Chars.checkedCast(UnsignedBytes.toInt(data[position]));
characterSize = 1;
} else if ((charset.equals(StandardCharsets.UTF_16)
|| charset.equals(StandardCharsets.UTF_16BE))
&& bytesLeft() >= 2) {
charByte1 = data[position];
charByte2 = data[position + 1];
character = (byte) Chars.fromBytes(data[position], data[position + 1]);
characterSize = 2;
} else if (charset.equals(StandardCharsets.UTF_16LE) && bytesLeft() >= 2) {
charByte1 = data[position + 1];
charByte2 = data[position];
character = (byte) Chars.fromBytes(data[position + 1], data[position]);
characterSize = 2;
} else {
return 0;
}
return Ints.fromBytes(charByte1, charByte2, (byte) 0, characterSize);
return (Chars.checkedCast(character) << Short.SIZE) + characterSize;
}
}

View File

@ -22,7 +22,6 @@ import android.text.TextUtils;
import androidx.annotation.Nullable;
import com.google.common.base.Ascii;
import java.util.List;
import java.util.Objects;
/** Utility methods for manipulating URIs. */
@UnstableApi
@ -307,7 +306,7 @@ public final class UriUtil {
baseUriScheme == null
? targetUriScheme == null
: targetUriScheme != null && Ascii.equalsIgnoreCase(baseUriScheme, targetUriScheme);
if (!isSameScheme || !Objects.equals(baseUri.getAuthority(), targetUri.getAuthority())) {
if (!isSameScheme || !Util.areEqual(baseUri.getAuthority(), targetUri.getAuthority())) {
// Different schemes or authorities, cannot find relative path, return targetUri.
return targetUri.toString();
}

View File

@ -95,7 +95,6 @@ import androidx.media3.common.ParserException;
import androidx.media3.common.PlaybackException;
import androidx.media3.common.Player;
import androidx.media3.common.Player.Commands;
import androidx.media3.common.audio.AudioManagerCompat;
import androidx.media3.common.audio.AudioProcessor;
import com.google.common.base.Ascii;
import com.google.common.io.ByteStreams;
@ -564,7 +563,7 @@ public final class Util {
@UnstableApi
public static boolean contains(@NullableType Object[] items, @Nullable Object item) {
for (Object arrayItem : items) {
if (Objects.equals(arrayItem, item)) {
if (areEqual(arrayItem, item)) {
return true;
}
}
@ -796,7 +795,7 @@ public final class Util {
if (!looper.getThread().isAlive()) {
return false;
}
if (looper == Looper.myLooper()) {
if (handler.getLooper() == Looper.myLooper()) {
runnable.run();
return true;
} else {
@ -2486,7 +2485,9 @@ public final class Util {
*/
@UnstableApi
public static int generateAudioSessionIdV21(Context context) {
return AudioManagerCompat.getAudioManager(context).generateAudioSessionId();
@Nullable
AudioManager audioManager = ((AudioManager) context.getSystemService(Context.AUDIO_SERVICE));
return audioManager == null ? AudioManager.ERROR : audioManager.generateAudioSessionId();
}
/**
@ -2570,8 +2571,7 @@ public final class Util {
*/
public static @ContentType int inferContentType(Uri uri) {
@Nullable String scheme = uri.getScheme();
if (scheme != null
&& (Ascii.equalsIgnoreCase("rtsp", scheme) || Ascii.equalsIgnoreCase("rtspt", scheme))) {
if (scheme != null && Ascii.equalsIgnoreCase("rtsp", scheme)) {
return C.CONTENT_TYPE_RTSP;
}

View File

@ -1036,110 +1036,4 @@ public class AdPlaybackStateTest {
assertThat(AdPlaybackState.AdGroup.fromBundle(adGroup.toBundle()).ids[1]).isNull();
assertThat(AdPlaybackState.AdGroup.fromBundle(adGroup.toBundle())).isEqualTo(adGroup);
}
@Test
public void setDurationsUs_withRemovedAdGroups_updatedCorrectlyAndSafely() {
AdPlaybackState adPlaybackState =
new AdPlaybackState("adsId")
.withLivePostrollPlaceholderAppended(false)
.withNewAdGroup(/* adGroupIndex= */ 0, 10_000)
.withAdCount(/* adGroupIndex= */ 0, 1)
.withAvailableAdMediaItem(
/* adGroupIndex= */ 0,
/* adIndexInAdGroup= */ 0,
MediaItem.fromUri("http://example.com/0-0"))
.withNewAdGroup(/* adGroupIndex= */ 1, 11_000)
.withAdCount(/* adGroupIndex= */ 1, 2)
.withAvailableAdMediaItem(
/* adGroupIndex= */ 1,
/* adIndexInAdGroup= */ 0,
MediaItem.fromUri("http://example.com/1-0"))
.withAvailableAdMediaItem(
/* adGroupIndex= */ 1,
/* adIndexInAdGroup= */ 1,
MediaItem.fromUri("http://example.com/1-1"))
.withNewAdGroup(/* adGroupIndex= */ 2, 12_000)
.withAdCount(/* adGroupIndex= */ 2, 1)
.withAvailableAdMediaItem(
/* adGroupIndex= */ 2,
/* adIndexInAdGroup= */ 0,
MediaItem.fromUri("http://example.com/2-0"));
long[][] adDurationsUs = {
new long[] {10L}, new long[] {20L, 21L}, new long[] {30L}, new long[] {C.TIME_END_OF_SOURCE}
};
adPlaybackState =
adPlaybackState
.withAdDurationsUs(adDurationsUs)
.withRemovedAdGroupCount(/* removedAdGroupCount= */ 1);
assertThat(adPlaybackState.adGroupCount).isEqualTo(4);
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).durationsUs).hasLength(0);
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).count).isEqualTo(0);
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).states).hasLength(0);
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).isPlaceholder).isFalse();
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).mediaItems).hasLength(0);
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 0).ids).hasLength(0);
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 1).durationsUs)
.asList()
.containsExactly(20L, 21L)
.inOrder();
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 2).durationsUs)
.asList()
.containsExactly(30L);
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 3).durationsUs)
.asList()
.containsExactly(C.TIME_END_OF_SOURCE);
adDurationsUs[1][0] = 120L;
adDurationsUs[1][1] = 121L;
adPlaybackState = adPlaybackState.withAdDurationsUs(adDurationsUs);
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 1).durationsUs)
.asList()
.containsExactly(120L, 121L)
.inOrder();
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 2).durationsUs)
.asList()
.containsExactly(30L);
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 3).durationsUs)
.asList()
.containsExactly(C.TIME_END_OF_SOURCE);
adDurationsUs[0] = null;
adDurationsUs[1] = null;
adDurationsUs[2][0] = C.TIME_UNSET;
adPlaybackState =
adPlaybackState
.withRemovedAdGroupCount(/* removedAdGroupCount= */ 2)
.withAdDurationsUs(adDurationsUs);
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 1).durationsUs).hasLength(0);
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 2).durationsUs)
.asList()
.containsExactly(C.TIME_UNSET);
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 3).durationsUs)
.asList()
.containsExactly(C.TIME_END_OF_SOURCE);
adDurationsUs[2] = null;
adDurationsUs[3][0] = 0L;
adPlaybackState =
adPlaybackState
.withRemovedAdGroupCount(/* removedAdGroupCount= */ 3)
.withAdDurationsUs(adDurationsUs);
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 2).durationsUs).hasLength(0);
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 3).durationsUs)
.asList()
.containsExactly(0L);
adDurationsUs[3] = null;
adPlaybackState =
adPlaybackState
.withRemovedAdGroupCount(/* removedAdGroupCount= */ 4)
.withAdDurationsUs(adDurationsUs);
assertThat(adPlaybackState.getAdGroup(/* adGroupIndex= */ 3).durationsUs).hasLength(0);
}
}

View File

@ -16,7 +16,7 @@
package androidx.media3.common.audio;
import static androidx.media3.common.audio.AudioProcessor.EMPTY_BUFFER;
import static androidx.media3.common.audio.SpeedChangingAudioProcessor.getInputFrameCountForOutput;
import static androidx.media3.common.util.Assertions.checkState;
import static androidx.media3.test.utils.TestUtil.getNonRandomByteBuffer;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assert.assertThrows;
@ -36,59 +36,53 @@ import org.junit.runner.RunWith;
@RunWith(AndroidJUnit4.class)
public class SpeedChangingAudioProcessorTest {
private static final AudioFormat AUDIO_FORMAT_44_100HZ =
private static final AudioFormat AUDIO_FORMAT =
new AudioFormat(
/* sampleRate= */ 44_100, /* channelCount= */ 2, /* encoding= */ C.ENCODING_PCM_16BIT);
private static final AudioFormat AUDIO_FORMAT_50_000HZ =
new AudioFormat(
/* sampleRate= */ 50_000, /* channelCount= */ 2, /* encoding= */ C.ENCODING_PCM_16BIT);
/* sampleRate= */ 44100, /* channelCount= */ 2, /* encoding= */ C.ENCODING_PCM_16BIT);
@Test
public void queueInput_noSpeedChange_doesNotOverwriteInput() throws Exception {
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_44_100HZ, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1});
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1});
SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
speedChangingAudioProcessor.queueInput(inputBuffer);
inputBuffer.rewind();
assertThat(inputBuffer)
.isEqualTo(
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame));
.isEqualTo(getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame));
}
@Test
public void queueInput_speedChange_doesNotOverwriteInput() throws Exception {
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_44_100HZ, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
speedChangingAudioProcessor.queueInput(inputBuffer);
inputBuffer.rewind();
assertThat(inputBuffer)
.isEqualTo(
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame));
.isEqualTo(getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame));
}
@Test
public void queueInput_noSpeedChange_copiesSamples() throws Exception {
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_44_100HZ, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1});
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1});
SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
speedChangingAudioProcessor.queueInput(inputBuffer);
speedChangingAudioProcessor.queueEndOfStream();
@ -102,11 +96,11 @@ public class SpeedChangingAudioProcessorTest {
public void queueInput_speedChange_modifiesSamples() throws Exception {
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_44_100HZ, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
speedChangingAudioProcessor.queueInput(inputBuffer);
speedChangingAudioProcessor.queueEndOfStream();
@ -121,13 +115,11 @@ public class SpeedChangingAudioProcessorTest {
public void queueInput_noSpeedChangeAfterSpeedChange_copiesSamples() throws Exception {
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_44_100HZ,
/* frameCounts= */ new int[] {5, 5},
/* speeds= */ new float[] {2, 1});
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 1});
SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
speedChangingAudioProcessor.queueInput(inputBuffer);
inputBuffer.rewind();
@ -144,13 +136,11 @@ public class SpeedChangingAudioProcessorTest {
throws Exception {
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_44_100HZ,
/* frameCounts= */ new int[] {5, 5},
/* speeds= */ new float[] {1, 2});
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {1, 2});
SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
speedChangingAudioProcessor.queueInput(inputBuffer);
inputBuffer.rewind();
@ -160,7 +150,7 @@ public class SpeedChangingAudioProcessorTest {
speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_44_100HZ, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider);
inputBuffer.rewind();
speedChangingAudioProcessor.queueInput(inputBuffer);
@ -175,13 +165,11 @@ public class SpeedChangingAudioProcessorTest {
throws Exception {
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_44_100HZ,
/* frameCounts= */ new int[] {5, 5},
/* speeds= */ new float[] {3, 2});
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {3, 2});
SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
speedChangingAudioProcessor.queueInput(inputBuffer);
inputBuffer.rewind();
@ -191,7 +179,7 @@ public class SpeedChangingAudioProcessorTest {
speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_44_100HZ, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider);
inputBuffer.rewind();
speedChangingAudioProcessor.queueInput(inputBuffer);
@ -206,20 +194,18 @@ public class SpeedChangingAudioProcessorTest {
throws Exception {
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_44_100HZ,
/* frameCounts= */ new int[] {5, 5},
/* speeds= */ new float[] {2, 3});
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 3});
SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
speedChangingAudioProcessor.queueInput(inputBuffer);
ByteBuffer outputBuffer = getAudioProcessorOutput(speedChangingAudioProcessor);
speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_44_100HZ, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
speedChangingAudioProcessor = getConfiguredSpeedChangingAudioProcessor(speedProvider);
inputBuffer.rewind();
speedChangingAudioProcessor.queueInput(inputBuffer);
@ -232,7 +218,7 @@ public class SpeedChangingAudioProcessorTest {
@Test
public void queueInput_multipleSpeedsInBufferWithLimitAtFrameBoundary_readsDataUntilSpeedLimit()
throws Exception {
long speedChangeTimeUs = 4 * C.MICROS_PER_SECOND / AUDIO_FORMAT_44_100HZ.sampleRate;
long speedChangeTimeUs = 4 * C.MICROS_PER_SECOND / AUDIO_FORMAT.sampleRate;
SpeedProvider speedProvider =
TestSpeedProvider.createWithStartTimes(
/* startTimesUs= */ new long[] {0L, speedChangeTimeUs},
@ -240,19 +226,19 @@ public class SpeedChangingAudioProcessorTest {
SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
int inputBufferLimit = inputBuffer.limit();
speedChangingAudioProcessor.queueInput(inputBuffer);
assertThat(inputBuffer.position()).isEqualTo(4 * AUDIO_FORMAT_44_100HZ.bytesPerFrame);
assertThat(inputBuffer.position()).isEqualTo(4 * AUDIO_FORMAT.bytesPerFrame);
assertThat(inputBuffer.limit()).isEqualTo(inputBufferLimit);
}
@Test
public void queueInput_multipleSpeedsInBufferWithLimitInsideFrame_readsDataUntilSpeedLimit()
throws Exception {
long speedChangeTimeUs = (long) (3.5 * C.MICROS_PER_SECOND / AUDIO_FORMAT_44_100HZ.sampleRate);
long speedChangeTimeUs = (long) (3.5 * C.MICROS_PER_SECOND / AUDIO_FORMAT.sampleRate);
SpeedProvider speedProvider =
TestSpeedProvider.createWithStartTimes(
/* startTimesUs= */ new long[] {0L, speedChangeTimeUs},
@ -260,12 +246,12 @@ public class SpeedChangingAudioProcessorTest {
SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
int inputBufferLimit = inputBuffer.limit();
speedChangingAudioProcessor.queueInput(inputBuffer);
assertThat(inputBuffer.position()).isEqualTo(4 * AUDIO_FORMAT_44_100HZ.bytesPerFrame);
assertThat(inputBuffer.position()).isEqualTo(4 * AUDIO_FORMAT.bytesPerFrame);
assertThat(inputBuffer.limit()).isEqualTo(inputBufferLimit);
}
@ -280,18 +266,18 @@ public class SpeedChangingAudioProcessorTest {
SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
// SpeedChangingAudioProcessor only queues samples until the next speed change.
while (inputBuffer.hasRemaining()) {
speedChangingAudioProcessor.queueInput(inputBuffer);
outputFrames +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT_44_100HZ.bytesPerFrame;
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame;
}
speedChangingAudioProcessor.queueEndOfStream();
outputFrames +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT_44_100HZ.bytesPerFrame;
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame;
// We allow 1 sample of tolerance per speed change.
assertThat(outputFrames).isWithin(1).of(3);
}
@ -301,13 +287,11 @@ public class SpeedChangingAudioProcessorTest {
throws Exception {
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_44_100HZ,
/* frameCounts= */ new int[] {5, 5},
/* speeds= */ new float[] {2, 1});
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 1});
SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
speedChangingAudioProcessor.queueInput(inputBuffer);
inputBuffer.rewind();
@ -323,13 +307,11 @@ public class SpeedChangingAudioProcessorTest {
throws Exception {
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_44_100HZ,
/* frameCounts= */ new int[] {5, 5},
/* speeds= */ new float[] {1, 2});
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {1, 2});
SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
speedChangingAudioProcessor.queueInput(inputBuffer);
inputBuffer.rewind();
@ -345,11 +327,11 @@ public class SpeedChangingAudioProcessorTest {
throws Exception {
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_44_100HZ, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1});
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1});
SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
speedChangingAudioProcessor.queueInput(inputBuffer);
speedChangingAudioProcessor.queueEndOfStream();
@ -362,11 +344,11 @@ public class SpeedChangingAudioProcessorTest {
throws Exception {
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_44_100HZ, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
speedChangingAudioProcessor.queueInput(inputBuffer);
speedChangingAudioProcessor.queueEndOfStream();
@ -378,7 +360,7 @@ public class SpeedChangingAudioProcessorTest {
public void queueEndOfStream_noInputQueued_endsProcessor() throws Exception {
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_44_100HZ, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider);
@ -391,11 +373,11 @@ public class SpeedChangingAudioProcessorTest {
public void isEnded_afterNoSpeedChangeAndOutputRetrieved_isFalse() throws Exception {
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_44_100HZ, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1});
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {1});
SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
speedChangingAudioProcessor.queueInput(inputBuffer);
getAudioProcessorOutput(speedChangingAudioProcessor);
@ -407,11 +389,11 @@ public class SpeedChangingAudioProcessorTest {
public void isEnded_afterSpeedChangeAndOutputRetrieved_isFalse() throws Exception {
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_44_100HZ, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
AUDIO_FORMAT, /* frameCounts= */ new int[] {5}, /* speeds= */ new float[] {2});
SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
speedChangingAudioProcessor.queueInput(inputBuffer);
getAudioProcessorOutput(speedChangingAudioProcessor);
@ -420,89 +402,147 @@ public class SpeedChangingAudioProcessorTest {
}
@Test
public void getSpeedAdjustedTimeAsync_beforeFlush_callbacksCalledWithCorrectParametersAfterFlush()
throws Exception {
public void getSpeedAdjustedTimeAsync_callbacksCalledWithCorrectParameters() throws Exception {
ArrayList<Long> outputTimesUs = new ArrayList<>();
// Sample period = 20us.
// The speed change is at 113Us (5*MICROS_PER_SECOND/sampleRate).
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_50_000HZ,
/* frameCounts= */ new int[] {6, 6},
/* speeds= */ new float[] {2, 1});
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 1});
SpeedChangingAudioProcessor speedChangingAudioProcessor =
new SpeedChangingAudioProcessor(speedProvider);
speedChangingAudioProcessor.configure(AUDIO_FORMAT_50_000HZ);
getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
speedChangingAudioProcessor.getSpeedAdjustedTimeAsync(
/* inputTimeUs= */ 40L, outputTimesUs::add);
/* inputTimeUs= */ 50L, outputTimesUs::add);
speedChangingAudioProcessor.queueInput(inputBuffer);
getAudioProcessorOutput(speedChangingAudioProcessor);
inputBuffer.rewind();
speedChangingAudioProcessor.queueInput(inputBuffer);
getAudioProcessorOutput(speedChangingAudioProcessor);
speedChangingAudioProcessor.getSpeedAdjustedTimeAsync(
/* inputTimeUs= */ 80L, outputTimesUs::add);
/* inputTimeUs= */ 100L, outputTimesUs::add);
speedChangingAudioProcessor.getSpeedAdjustedTimeAsync(
/* inputTimeUs= */ 160L, outputTimesUs::add);
/* inputTimeUs= */ 150L, outputTimesUs::add);
assertThat(outputTimesUs).isEmpty();
speedChangingAudioProcessor.flush();
assertThat(outputTimesUs).containsExactly(20L, 40L, 100L);
// 150 is after the speed change so floor(113 / 2 + (150 - 113)*1) -> 93
assertThat(outputTimesUs).containsExactly(25L, 50L, 93L);
}
@Test
public void getSpeedAdjustedTimeAsync_afterCallToFlush_callbacksCalledWithCorrectParameters()
public void getSpeedAdjustedTimeAsync_afterFlush_callbacksCalledWithCorrectParameters()
throws Exception {
ArrayList<Long> outputTimesUs = new ArrayList<>();
// Sample period = 20us.
// The speed change is at 113Us (5*MICROS_PER_SECOND/sampleRate). Also add another speed change
// to 3x at a later point that should not be used if the flush is handled correctly.
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_50_000HZ,
/* frameCounts= */ new int[] {6, 6},
/* speeds= */ new float[] {2, 1});
AUDIO_FORMAT,
/* frameCounts= */ new int[] {5, 5, 5},
/* speeds= */ new float[] {2, 1, 3});
SpeedChangingAudioProcessor speedChangingAudioProcessor =
new SpeedChangingAudioProcessor(speedProvider);
speedChangingAudioProcessor.configure(AUDIO_FORMAT_50_000HZ);
getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
// Use the audio processor before a flush
speedChangingAudioProcessor.queueInput(inputBuffer);
getAudioProcessorOutput(speedChangingAudioProcessor);
inputBuffer.rewind();
speedChangingAudioProcessor.queueInput(inputBuffer);
getAudioProcessorOutput(speedChangingAudioProcessor);
inputBuffer.rewind();
// Flush and use it again.
speedChangingAudioProcessor.flush();
speedChangingAudioProcessor.getSpeedAdjustedTimeAsync(
/* inputTimeUs= */ 50L, outputTimesUs::add);
speedChangingAudioProcessor.queueInput(inputBuffer);
getAudioProcessorOutput(speedChangingAudioProcessor);
inputBuffer.rewind();
speedChangingAudioProcessor.queueInput(inputBuffer);
getAudioProcessorOutput(speedChangingAudioProcessor);
speedChangingAudioProcessor.getSpeedAdjustedTimeAsync(
/* inputTimeUs= */ 100L, outputTimesUs::add);
speedChangingAudioProcessor.getSpeedAdjustedTimeAsync(
/* inputTimeUs= */ 150L, outputTimesUs::add);
speedChangingAudioProcessor.getSpeedAdjustedTimeAsync(
/* inputTimeUs= */ 40L, outputTimesUs::add);
speedChangingAudioProcessor.getSpeedAdjustedTimeAsync(
/* inputTimeUs= */ 80L, outputTimesUs::add);
speedChangingAudioProcessor.getSpeedAdjustedTimeAsync(
/* inputTimeUs= */ 160L, outputTimesUs::add);
assertThat(outputTimesUs).containsExactly(20L, 40L, 100L);
// 150 is after the speed change so floor(113 / 2 + (150 - 113)*1) -> 93
assertThat(outputTimesUs).containsExactly(25L, 50L, 93L);
}
@Test
public void getSpeedAdjustedTimeAsync_timeAfterEndTime_callbacksCalledWithCorrectParameters()
throws Exception {
ArrayList<Long> outputTimesUs = new ArrayList<>();
// The speed change is at 120Us (6*MICROS_PER_SECOND/sampleRate).
// The speed change is at 113Us (5*MICROS_PER_SECOND/sampleRate).
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_50_000HZ,
/* frameCounts= */ new int[] {6, 6},
/* speeds= */ new float[] {2, 1});
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 1});
SpeedChangingAudioProcessor speedChangingAudioProcessor =
new SpeedChangingAudioProcessor(speedProvider);
speedChangingAudioProcessor.configure(AUDIO_FORMAT_50_000HZ);
speedChangingAudioProcessor.flush();
getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 3, AUDIO_FORMAT.bytesPerFrame);
speedChangingAudioProcessor.getSpeedAdjustedTimeAsync(
/* inputTimeUs= */ 300L, outputTimesUs::add);
speedChangingAudioProcessor.queueInput(inputBuffer);
getAudioProcessorOutput(speedChangingAudioProcessor);
inputBuffer.rewind();
speedChangingAudioProcessor.queueInput(inputBuffer);
getAudioProcessorOutput(speedChangingAudioProcessor);
inputBuffer.rewind();
speedChangingAudioProcessor.queueInput(inputBuffer);
speedChangingAudioProcessor.queueEndOfStream();
getAudioProcessorOutput(speedChangingAudioProcessor);
// 150 is after the speed change so floor(113 / 2 + (300 - 113)*1) -> 243
assertThat(outputTimesUs).containsExactly(243L);
}
@Test
public void
getSpeedAdjustedTimeAsync_timeAfterEndTimeAfterProcessorEnded_callbacksCalledWithCorrectParameters()
throws Exception {
ArrayList<Long> outputTimesUs = new ArrayList<>();
// The speed change is at 113Us (5*MICROS_PER_SECOND/sampleRate).
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT, /* frameCounts= */ new int[] {5, 5}, /* speeds= */ new float[] {2, 1});
SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 5, AUDIO_FORMAT.bytesPerFrame);
speedChangingAudioProcessor.queueInput(inputBuffer);
getAudioProcessorOutput(speedChangingAudioProcessor);
inputBuffer.rewind();
speedChangingAudioProcessor.queueInput(inputBuffer);
speedChangingAudioProcessor.queueEndOfStream();
getAudioProcessorOutput(speedChangingAudioProcessor);
checkState(speedChangingAudioProcessor.isEnded());
speedChangingAudioProcessor.getSpeedAdjustedTimeAsync(
/* inputTimeUs= */ 300L, outputTimesUs::add);
assertThat(outputTimesUs).containsExactly(240L);
// 150 is after the speed change so floor(113 / 2 + (300 - 113)*1) -> 243
assertThat(outputTimesUs).containsExactly(243L);
}
@Test
public void getMediaDurationUs_returnsCorrectValues() throws Exception {
// The speed changes happen every 10ms (500 samples @ 50.KHz)
// The speed changes happen every 10ms (441 samples @ 441.KHz)
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_50_000HZ,
/* frameCounts= */ new int[] {500, 500, 500, 500},
AUDIO_FORMAT,
/* frameCounts= */ new int[] {441, 441, 441, 441},
/* speeds= */ new float[] {2, 1, 5, 2});
SpeedChangingAudioProcessor speedChangingAudioProcessor =
new SpeedChangingAudioProcessor(speedProvider);
speedChangingAudioProcessor.configure(AUDIO_FORMAT_50_000HZ);
speedChangingAudioProcessor.flush();
getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer inputBuffer =
getNonRandomByteBuffer(/* frameCount= */ 441 * 4, AUDIO_FORMAT.bytesPerFrame);
while (inputBuffer.position() < inputBuffer.limit()) {
speedChangingAudioProcessor.queueInput(inputBuffer);
}
getAudioProcessorOutput(speedChangingAudioProcessor);
// input (in ms) (0, 10, 20, 30, 40) ->
// output (in ms) (0, 10/2, 10/2 + 10, 10/2 + 10 + 10/5, 10/2 + 10 + 10/5 + 10/2)
@ -532,30 +572,30 @@ public class SpeedChangingAudioProcessorTest {
int outputFrameCount = 0;
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_44_100HZ,
AUDIO_FORMAT,
/* frameCounts= */ new int[] {1000, 1000, 1000},
/* speeds= */ new float[] {2, 4, 2}); // 500, 250, 500 = 1250
SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer input = getNonRandomByteBuffer(1000, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
ByteBuffer input = getNonRandomByteBuffer(1000, AUDIO_FORMAT.bytesPerFrame);
speedChangingAudioProcessor.queueInput(input);
outputFrameCount +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT_44_100HZ.bytesPerFrame;
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame;
input.rewind();
speedChangingAudioProcessor.queueInput(input);
outputFrameCount +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT_44_100HZ.bytesPerFrame;
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame;
input.rewind();
speedChangingAudioProcessor.queueInput(input);
outputFrameCount +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT_44_100HZ.bytesPerFrame;
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame;
speedChangingAudioProcessor.queueEndOfStream();
outputFrameCount +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT_44_100HZ.bytesPerFrame;
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame;
assertThat(outputFrameCount).isWithin(2).of(1250);
}
@ -572,17 +612,17 @@ public class SpeedChangingAudioProcessorTest {
/* speeds= */ new float[] {2, 3, 8, 4});
SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider);
ByteBuffer input = getNonRandomByteBuffer(12, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
ByteBuffer input = getNonRandomByteBuffer(12, AUDIO_FORMAT.bytesPerFrame);
while (input.hasRemaining()) {
speedChangingAudioProcessor.queueInput(input);
outputFrameCount +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT_44_100HZ.bytesPerFrame;
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame;
}
speedChangingAudioProcessor.queueEndOfStream();
outputFrameCount +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT_44_100HZ.bytesPerFrame;
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame;
// Allow one sample of tolerance per effectively applied speed change.
assertThat(outputFrameCount).isWithin(1).of(4);
@ -593,23 +633,23 @@ public class SpeedChangingAudioProcessorTest {
throws AudioProcessor.UnhandledAudioFormatException {
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_44_100HZ,
AUDIO_FORMAT,
/* frameCounts= */ new int[] {1000, 1000},
/* speeds= */ new float[] {1, 2}); // 1000, 500.
SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider);
// 1500 input frames falls in the middle of the 2x region.
ByteBuffer input = getNonRandomByteBuffer(1500, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
ByteBuffer input = getNonRandomByteBuffer(1500, AUDIO_FORMAT.bytesPerFrame);
int outputFrameCount = 0;
while (input.hasRemaining()) {
speedChangingAudioProcessor.queueInput(input);
outputFrameCount +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT_44_100HZ.bytesPerFrame;
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame;
}
speedChangingAudioProcessor.flush();
outputFrameCount +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT_44_100HZ.bytesPerFrame;
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame;
assertThat(outputFrameCount).isEqualTo(1250);
input.rewind();
@ -619,11 +659,11 @@ public class SpeedChangingAudioProcessorTest {
while (input.hasRemaining()) {
speedChangingAudioProcessor.queueInput(input);
outputFrameCount +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT_44_100HZ.bytesPerFrame;
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame;
}
speedChangingAudioProcessor.queueEndOfStream();
outputFrameCount +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT_44_100HZ.bytesPerFrame;
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame;
assertThat(outputFrameCount).isWithin(1).of(2500); // 1250 * 2.
}
@ -632,23 +672,23 @@ public class SpeedChangingAudioProcessorTest {
throws AudioProcessor.UnhandledAudioFormatException {
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_44_100HZ,
AUDIO_FORMAT,
/* frameCounts= */ new int[] {1000, 1000},
/* speeds= */ new float[] {2, 4}); // 500, 250.
SpeedChangingAudioProcessor speedChangingAudioProcessor =
getConfiguredSpeedChangingAudioProcessor(speedProvider);
// 1500 input frames falls in the middle of the 2x region.
ByteBuffer input = getNonRandomByteBuffer(1500, AUDIO_FORMAT_44_100HZ.bytesPerFrame);
ByteBuffer input = getNonRandomByteBuffer(1500, AUDIO_FORMAT.bytesPerFrame);
int outputFrameCount = 0;
while (input.hasRemaining()) {
speedChangingAudioProcessor.queueInput(input);
outputFrameCount +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT_44_100HZ.bytesPerFrame;
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame;
}
speedChangingAudioProcessor.flush();
outputFrameCount +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT_44_100HZ.bytesPerFrame;
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame;
assertThat(outputFrameCount).isWithin(1).of(625);
input.rewind();
@ -658,11 +698,11 @@ public class SpeedChangingAudioProcessorTest {
while (input.hasRemaining()) {
speedChangingAudioProcessor.queueInput(input);
outputFrameCount +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT_44_100HZ.bytesPerFrame;
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame;
}
speedChangingAudioProcessor.queueEndOfStream();
outputFrameCount +=
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT_44_100HZ.bytesPerFrame;
speedChangingAudioProcessor.getOutput().remaining() / AUDIO_FORMAT.bytesPerFrame;
assertThat(outputFrameCount).isWithin(2).of(1250); // 625 * 2.
}
@ -676,7 +716,7 @@ public class SpeedChangingAudioProcessorTest {
long sampleCountAfterProcessorApplied =
SpeedChangingAudioProcessor.getSampleCountAfterProcessorApplied(
speedProvider, AUDIO_FORMAT_44_100HZ.sampleRate, /* inputSamples= */ 100);
speedProvider, AUDIO_FORMAT.sampleRate, /* inputSamples= */ 100);
assertThat(sampleCountAfterProcessorApplied).isEqualTo(50);
}
@ -684,13 +724,13 @@ public class SpeedChangingAudioProcessorTest {
public void getSampleCountAfterProcessorApplied_withMultipleSpeeds_outputsExpectedSamples() {
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_44_100HZ,
AUDIO_FORMAT,
/* frameCounts= */ new int[] {100, 400, 50},
/* speeds= */ new float[] {2.f, 4f, 0.5f});
long sampleCountAfterProcessorApplied =
SpeedChangingAudioProcessor.getSampleCountAfterProcessorApplied(
speedProvider, AUDIO_FORMAT_44_100HZ.sampleRate, /* inputSamples= */ 550);
speedProvider, AUDIO_FORMAT.sampleRate, /* inputSamples= */ 550);
assertThat(sampleCountAfterProcessorApplied).isEqualTo(250);
}
@ -699,13 +739,13 @@ public class SpeedChangingAudioProcessorTest {
getSampleCountAfterProcessorApplied_beyondLastSpeedRegion_stillAppliesLastSpeedValue() {
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_44_100HZ,
AUDIO_FORMAT,
/* frameCounts= */ new int[] {100, 400, 50},
/* speeds= */ new float[] {2.f, 4f, 0.5f});
long sampleCountAfterProcessorApplied =
SpeedChangingAudioProcessor.getSampleCountAfterProcessorApplied(
speedProvider, AUDIO_FORMAT_44_100HZ.sampleRate, /* inputSamples= */ 3000);
speedProvider, AUDIO_FORMAT.sampleRate, /* inputSamples= */ 3000);
assertThat(sampleCountAfterProcessorApplied).isEqualTo(5150);
}
@ -714,38 +754,38 @@ public class SpeedChangingAudioProcessorTest {
getSampleCountAfterProcessorApplied_withInputCountBeyondIntRange_outputsExpectedSamples() {
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_44_100HZ,
AUDIO_FORMAT,
/* frameCounts= */ new int[] {1000, 10000, 8200},
/* speeds= */ new float[] {0.2f, 8f, 0.5f});
long sampleCountAfterProcessorApplied =
SpeedChangingAudioProcessor.getSampleCountAfterProcessorApplied(
speedProvider, AUDIO_FORMAT_44_100HZ.sampleRate, /* inputSamples= */ 3_000_000_000L);
assertThat(sampleCountAfterProcessorApplied).isEqualTo(5_999_984_250L);
speedProvider, AUDIO_FORMAT.sampleRate, /* inputSamples= */ 3_000_000_000L);
assertThat(sampleCountAfterProcessorApplied).isEqualTo(5999984250L);
}
// Testing range validation.
@SuppressLint("Range")
@Test
public void getSampleCountAfterProcessorApplied_withNegativeFrameCount_throws() {
public void getSampleCountAfterProcessorApplied_withNegativeSampleCount_throws() {
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_44_100HZ,
AUDIO_FORMAT,
/* frameCounts= */ new int[] {1000, 10000, 8200},
/* speeds= */ new float[] {0.2f, 8f, 0.5f});
assertThrows(
IllegalArgumentException.class,
() ->
SpeedChangingAudioProcessor.getSampleCountAfterProcessorApplied(
speedProvider, AUDIO_FORMAT_44_100HZ.sampleRate, /* inputSamples= */ -2L));
speedProvider, AUDIO_FORMAT.sampleRate, /* inputSamples= */ -2L));
}
// Testing range validation.
@SuppressLint("Range")
@Test
public void getSampleCountAfterProcessorApplied_withZeroFrameRate_throws() {
public void getSampleCountAfterProcessorApplied_withZeroSampleRate_throws() {
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_44_100HZ,
AUDIO_FORMAT,
/* frameCounts= */ new int[] {1000, 10000, 8200},
/* speeds= */ new float[] {0.2f, 8f, 0.5f});
assertThrows(
@ -761,32 +801,14 @@ public class SpeedChangingAudioProcessorTest {
IllegalArgumentException.class,
() ->
SpeedChangingAudioProcessor.getSampleCountAfterProcessorApplied(
/* speedProvider= */ null,
AUDIO_FORMAT_44_100HZ.sampleRate,
/* inputSamples= */ 1000L));
}
@Test
public void getSampleCountAfterProcessorApplied_withZeroInputFrames_returnsZero() {
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_44_100HZ,
/* frameCounts= */ new int[] {1000, 10000, 8200},
/* speeds= */ new float[] {0.2f, 8f, 0.5f});
long sampleCountAfterProcessorApplied =
SpeedChangingAudioProcessor.getSampleCountAfterProcessorApplied(
speedProvider, AUDIO_FORMAT_44_100HZ.sampleRate, /* inputSamples= */ 0L);
assertThat(sampleCountAfterProcessorApplied).isEqualTo(0L);
/* speedProvider= */ null, AUDIO_FORMAT.sampleRate, /* inputSamples= */ 1000L));
}
@Test
public void isActive_beforeConfigure_returnsFalse() {
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_44_100HZ,
/* frameCounts= */ new int[] {1000},
/* speeds= */ new float[] {2f});
AUDIO_FORMAT, /* frameCounts= */ new int[] {1000}, /* speeds= */ new float[] {2f});
SpeedChangingAudioProcessor processor = new SpeedChangingAudioProcessor(speedProvider);
assertThat(processor.isActive()).isFalse();
@ -797,34 +819,18 @@ public class SpeedChangingAudioProcessorTest {
throws AudioProcessor.UnhandledAudioFormatException {
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_44_100HZ,
/* frameCounts= */ new int[] {1000},
/* speeds= */ new float[] {2f});
AUDIO_FORMAT, /* frameCounts= */ new int[] {1000}, /* speeds= */ new float[] {2f});
SpeedChangingAudioProcessor processor = new SpeedChangingAudioProcessor(speedProvider);
processor.configure(AUDIO_FORMAT_44_100HZ);
processor.configure(AUDIO_FORMAT);
assertThat(processor.isActive()).isTrue();
}
@Test
public void getInputFrameCountForOutput_withZeroOutputFrames_returnsZero() {
SpeedProvider speedProvider =
TestSpeedProvider.createWithFrameCounts(
AUDIO_FORMAT_44_100HZ,
/* frameCounts= */ new int[] {1000, 10000, 8200},
/* speeds= */ new float[] {0.2f, 8f, 0.5f});
long inputFrames =
getInputFrameCountForOutput(
speedProvider, AUDIO_FORMAT_44_100HZ.sampleRate, /* outputFrameCount= */ 0L);
assertThat(inputFrames).isEqualTo(0L);
}
private static SpeedChangingAudioProcessor getConfiguredSpeedChangingAudioProcessor(
SpeedProvider speedProvider) throws AudioProcessor.UnhandledAudioFormatException {
SpeedChangingAudioProcessor speedChangingAudioProcessor =
new SpeedChangingAudioProcessor(speedProvider);
speedChangingAudioProcessor.configure(AUDIO_FORMAT_44_100HZ);
speedChangingAudioProcessor.configure(AUDIO_FORMAT);
speedChangingAudioProcessor.flush();
return speedChangingAudioProcessor;
}

View File

@ -679,31 +679,6 @@ public final class ParsableByteArrayTest {
assertThat(parser.readLine()).isNull();
}
@Test
public void readTwoLinesWithCr_utf8() {
byte[] bytes = "foo\rbar".getBytes(StandardCharsets.UTF_8);
ParsableByteArray parser = new ParsableByteArray(bytes);
assertThat(parser.readLine()).isEqualTo("foo");
assertThat(parser.getPosition()).isEqualTo(4);
assertThat(parser.readLine()).isEqualTo("bar");
assertThat(parser.getPosition()).isEqualTo(7);
assertThat(parser.readLine()).isNull();
}
// https://github.com/androidx/media/issues/2167
@Test
public void readTwoLinesWithCrAndWideChar_utf8() {
byte[] bytes = "foo\r\uD83D\uDE1B".getBytes(StandardCharsets.UTF_8);
ParsableByteArray parser = new ParsableByteArray(bytes);
assertThat(parser.readLine()).isEqualTo("foo");
assertThat(parser.getPosition()).isEqualTo(4);
assertThat(parser.readLine()).isEqualTo("\uD83D\uDE1B");
assertThat(parser.getPosition()).isEqualTo(8);
assertThat(parser.readLine()).isNull();
}
@Test
public void readTwoLinesWithCrFollowedByLf_utf8() {
byte[] bytes = "foo\r\nbar".getBytes(StandardCharsets.UTF_8);

View File

@ -1,54 +0,0 @@
/*
* Copyright 2025 The Android Open Source Project
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* 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.container;
import androidx.annotation.Nullable;
import androidx.media3.common.Metadata;
import androidx.media3.common.util.UnstableApi;
/** Stores MP4 {@code alternate_group} info parsed from a {@code tkhd} box. */
@UnstableApi
public final class Mp4AlternateGroupData implements Metadata.Entry {
public final int alternateGroup;
public Mp4AlternateGroupData(int alternateGroup) {
this.alternateGroup = alternateGroup;
}
@Override
public boolean equals(@Nullable Object obj) {
if (this == obj) {
return true;
}
if (!(obj instanceof Mp4AlternateGroupData)) {
return false;
}
Mp4AlternateGroupData other = (Mp4AlternateGroupData) obj;
return alternateGroup == other.alternateGroup;
}
@Override
public int hashCode() {
return alternateGroup;
}
@Override
public String toString() {
return "Mp4AlternateGroup: " + alternateGroup;
}
}

View File

@ -245,9 +245,6 @@ public abstract class Mp4Box {
@SuppressWarnings("ConstantCaseForConstants")
public static final int TYPE_esds = 0x65736473;
@SuppressWarnings("ConstantCaseForConstants")
public static final int TYPE_btrt = 0x62747274;
@SuppressWarnings("ConstantCaseForConstants")
public static final int TYPE_moof = 0x6d6f6f66;

View File

@ -106,9 +106,6 @@ public final class NalUnitUtil {
*/
@Deprecated public static final int NAL_UNIT_TYPE_PREFIX = H264_NAL_UNIT_TYPE_PREFIX;
/** H.264 unspecified NAL unit. */
public static final int H264_NAL_UNIT_TYPE_UNSPECIFIED = 24;
/** H.265 coded slice segment of a random access skipped leading picture (RASL_R). */
public static final int H265_NAL_UNIT_TYPE_RASL_R = 9;
@ -136,9 +133,6 @@ public final class NalUnitUtil {
/** H.265 suffixed supplemental enhancement information (SUFFIX_SEI_NUT). */
public static final int H265_NAL_UNIT_TYPE_SUFFIX_SEI = 40;
/** H.265 unspecified NAL unit. */
public static final int H265_NAL_UNIT_TYPE_UNSPECIFIED = 48;
/** Holds data parsed from a H.264 sequence parameter set NAL unit. */
public static final class SpsData {

View File

@ -36,21 +36,16 @@ import androidx.media3.common.PlaybackException;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.Clock;
import androidx.media3.common.util.ConditionVariable;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import com.google.common.base.Ascii;
import com.google.common.base.Predicate;
import com.google.common.collect.ImmutableMap;
import com.google.common.net.HttpHeaders;
import com.google.common.primitives.Longs;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.Arrays;
@ -326,8 +321,6 @@ public final class HttpEngineDataSource extends BaseDataSource implements HttpDa
// The size of read buffer passed to cronet UrlRequest.read().
private static final int READ_BUFFER_SIZE_BYTES = 32 * 1024;
private static final String TAG = "HttpEngineDataSource";
private final HttpEngine httpEngine;
private final Executor executor;
private final int requestPriority;
@ -716,7 +709,7 @@ public final class HttpEngineDataSource extends BaseDataSource implements HttpDa
@UnstableApi
@VisibleForTesting
@Nullable
UrlRequestCallback getCurrentUrlRequestCallback() {
UrlRequest.Callback getCurrentUrlRequestCallback() {
return currentUrlRequestWrapper == null
? null
: currentUrlRequestWrapper.getUrlRequestCallback();
@ -939,6 +932,14 @@ public final class HttpEngineDataSource extends BaseDataSource implements HttpDa
return false;
}
@Nullable
private static String parseCookies(@Nullable List<String> setCookieHeaders) {
if (setCookieHeaders == null || setCookieHeaders.isEmpty()) {
return null;
}
return TextUtils.join(";", setCookieHeaders);
}
@Nullable
private static String getFirstHeader(Map<String, List<String>> allHeaders, String headerName) {
@Nullable List<String> headers = allHeaders.get(headerName);
@ -956,61 +957,6 @@ public final class HttpEngineDataSource extends BaseDataSource implements HttpDa
return remaining;
}
// Stores the cookie headers from the response in the default {@link CookieHandler}.
private static void storeCookiesFromHeaders(UrlResponseInfo info) {
storeCookiesFromHeaders(info, CookieHandler.getDefault());
}
// Stores the cookie headers from the response in the provided {@link CookieHandler}.
private static void storeCookiesFromHeaders(
UrlResponseInfo info, @Nullable CookieHandler cookieHandler) {
if (cookieHandler == null) {
return;
}
try {
cookieHandler.put(new URI(info.getUrl()), info.getHeaders().getAsMap());
} catch (Exception e) {
Log.w(TAG, "Failed to store cookies in CookieHandler", e);
}
}
@VisibleForTesting
/* private */ static String getCookieHeader(String url) {
return getCookieHeader(url, ImmutableMap.of(), CookieHandler.getDefault());
}
@VisibleForTesting
/* private */ static String getCookieHeader(String url, @Nullable CookieHandler cookieHandler) {
return getCookieHeader(url, ImmutableMap.of(), cookieHandler);
}
// getCookieHeader maps Set-Cookie2 (RFC 2965) to Cookie just like CookieManager does.
private static String getCookieHeader(
String url, Map<String, List<String>> headers, @Nullable CookieHandler cookieHandler) {
if (cookieHandler == null) {
return "";
}
Map<String, List<String>> cookieHeaders = ImmutableMap.of();
try {
cookieHeaders = cookieHandler.get(new URI(url), headers);
} catch (Exception e) {
Log.w(TAG, "Failed to read cookies from CookieHandler", e);
}
StringBuilder cookies = new StringBuilder();
if (cookieHeaders.containsKey(HttpHeaders.COOKIE)) {
List<String> cookiesList = cookieHeaders.get(HttpHeaders.COOKIE);
if (cookiesList != null) {
for (String cookie : cookiesList) {
cookies.append(cookie).append("; ");
}
}
}
return cookies.toString().stripTrailing();
}
/**
* A wrapper class that manages a {@link UrlRequest} and the {@link UrlRequestCallback} associated
* with that request.
@ -1038,7 +984,7 @@ public final class HttpEngineDataSource extends BaseDataSource implements HttpDa
urlRequest.cancel();
}
public UrlRequestCallback getUrlRequestCallback() {
public UrlRequest.Callback getUrlRequestCallback() {
return urlRequestCallback;
}
@ -1058,7 +1004,8 @@ public final class HttpEngineDataSource extends BaseDataSource implements HttpDa
}
}
final class UrlRequestCallback implements UrlRequest.Callback {
private final class UrlRequestCallback implements UrlRequest.Callback {
private volatile boolean isClosed = false;
public void close() {
@ -1093,18 +1040,6 @@ public final class HttpEngineDataSource extends BaseDataSource implements HttpDa
resetConnectTimeout();
}
CookieHandler cookieHandler = CookieHandler.getDefault();
if (cookieHandler == null && handleSetCookieRequests) {
// a temporary CookieManager is created for the duration of this request - this guarantees
// redirects preserve the cookies correctly.
cookieHandler = new CookieManager();
}
storeCookiesFromHeaders(info, cookieHandler);
String cookieHeaders =
getCookieHeader(info.getUrl(), info.getHeaders().getAsMap(), cookieHandler);
boolean shouldKeepPost =
keepPostFor302Redirects
&& dataSpec.httpMethod == DataSpec.HTTP_METHOD_POST
@ -1112,12 +1047,17 @@ public final class HttpEngineDataSource extends BaseDataSource implements HttpDa
// request.followRedirect() transforms a POST request into a GET request, so if we want to
// keep it as a POST we need to fall through to the manual redirect logic below.
if (!shouldKeepPost) {
// No cookies, or we're not handling them - so just follow the redirect.
if (!handleSetCookieRequests || TextUtils.isEmpty(cookieHeaders)) {
request.followRedirect();
return;
}
if (!shouldKeepPost && !handleSetCookieRequests) {
request.followRedirect();
return;
}
@Nullable
String cookieHeadersValue =
parseCookies(info.getHeaders().getAsMap().get(HttpHeaders.SET_COOKIE));
if (!shouldKeepPost && TextUtils.isEmpty(cookieHeadersValue)) {
request.followRedirect();
return;
}
request.cancel();
@ -1135,15 +1075,13 @@ public final class HttpEngineDataSource extends BaseDataSource implements HttpDa
} else {
redirectUrlDataSpec = dataSpec.withUri(Uri.parse(newLocationUrl));
}
if (!TextUtils.isEmpty(cookieHeaders)) {
if (!TextUtils.isEmpty(cookieHeadersValue)) {
Map<String, String> requestHeaders = new HashMap<>();
requestHeaders.putAll(dataSpec.httpRequestHeaders);
requestHeaders.put(HttpHeaders.COOKIE, cookieHeaders);
requestHeaders.put(HttpHeaders.COOKIE, cookieHeadersValue);
redirectUrlDataSpec =
redirectUrlDataSpec.buildUpon().setHttpRequestHeaders(requestHeaders).build();
}
UrlRequestWrapper redirectUrlRequestWrapper;
try {
redirectUrlRequestWrapper = buildRequestWrapper(redirectUrlDataSpec);
@ -1163,7 +1101,6 @@ public final class HttpEngineDataSource extends BaseDataSource implements HttpDa
if (isClosed) {
return;
}
storeCookiesFromHeaders(info);
responseInfo = info;
operation.open();
}

View File

@ -159,7 +159,7 @@ public final class CacheWriter {
try {
resolvedLength = dataSource.open(boundedDataSpec);
isDataSourceOpen = true;
} catch (Exception e) {
} catch (IOException e) {
DataSourceUtil.closeQuietly(dataSource);
}
}
@ -172,7 +172,7 @@ public final class CacheWriter {
dataSpec.buildUpon().setPosition(position).setLength(C.LENGTH_UNSET).build();
try {
resolvedLength = dataSource.open(unboundedDataSpec);
} catch (Exception e) {
} catch (IOException e) {
DataSourceUtil.closeQuietly(dataSource);
throw e;
}
@ -195,7 +195,7 @@ public final class CacheWriter {
if (isLastBlock) {
onRequestEndPosition(position + totalBytesRead);
}
} catch (Exception e) {
} catch (IOException e) {
DataSourceUtil.closeQuietly(dataSource);
throw e;
}

View File

@ -45,14 +45,9 @@ import androidx.media3.common.util.Util;
import androidx.media3.datasource.HttpDataSource.HttpDataSourceException;
import androidx.media3.datasource.HttpDataSource.InvalidResponseCodeException;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import java.io.IOException;
import java.io.InterruptedIOException;
import java.net.CookieHandler;
import java.net.CookieManager;
import java.net.SocketTimeoutException;
import java.net.URI;
import java.net.UnknownHostException;
import java.nio.ByteBuffer;
import java.util.ArrayList;
@ -85,14 +80,8 @@ public final class HttpEngineDataSourceTest {
private static final int TEST_CONNECT_TIMEOUT_MS = 100;
private static final int TEST_READ_TIMEOUT_MS = 100;
private static final String TEST_URL = "http://google.com/video/";
private static final String TEST_URL = "http://google.com";
private static final String TEST_CONTENT_TYPE = "test/test";
private static final String TEST_REQUEST_COOKIE = "foo=bar";
private static final String TEST_REQUEST_COOKIE_2 = "baz=qux";
private static final String TEST_RESPONSE_SET_COOKIE =
TEST_REQUEST_COOKIE + ";path=/video; expires 31-12-2099 23:59:59 GMT";
private static final String TEST_RESPONSE_SET_COOKIE_2 =
TEST_REQUEST_COOKIE_2 + ";path=/; expires 31-12-2099 23:59:59 GMT";
private static final byte[] TEST_POST_BODY = Util.getUtf8Bytes("test post body");
private static final long TEST_CONTENT_LENGTH = 16000L;
@ -152,8 +141,6 @@ public final class HttpEngineDataSourceTest {
// This value can be anything since the DataSpec is unset.
testResponseHeader.put("Content-Length", Long.toString(TEST_CONTENT_LENGTH));
testUrlResponseInfo = createUrlResponseInfo(/* statusCode= */ 200);
CookieHandler.setDefault(null);
}
@After
@ -285,15 +272,15 @@ public final class HttpEngineDataSourceTest {
@Test
public void requestHeadersSet() throws HttpDataSourceException {
Map<String, String> headersSet = new HashMap<>();
when(mockUrlRequestBuilder.addHeader(
ArgumentMatchers.anyString(), ArgumentMatchers.anyString()))
.thenAnswer(
doAnswer(
(invocation) -> {
String key = invocation.getArgument(0);
String value = invocation.getArgument(1);
headersSet.put(key, value);
return null;
});
})
.when(mockUrlRequestBuilder)
.addHeader(ArgumentMatchers.anyString(), ArgumentMatchers.anyString());
dataSourceUnderTest.setRequestProperty("defaultHeader2", "dataSourceOverridesDefault");
dataSourceUnderTest.setRequestProperty("dataSourceHeader1", "dataSourceValue1");
@ -460,7 +447,8 @@ public final class HttpEngineDataSourceTest {
assertThat(e).isInstanceOf(HttpDataSource.InvalidContentTypeException.class);
// Check for connection not automatically closed.
verify(mockUrlRequest, never()).cancel();
assertThat(testedContentTypes).containsExactly(TEST_CONTENT_TYPE);
assertThat(testedContentTypes).hasSize(1);
assertThat(testedContentTypes.get(0)).isEqualTo(TEST_CONTENT_TYPE);
}
}
@ -1289,7 +1277,7 @@ public final class HttpEngineDataSourceTest {
.createDataSource();
mockSingleRedirectSuccess(/* responseCode= */ 302);
dataSourceUnderTest.setRequestProperty("Content-Type", TEST_CONTENT_TYPE);
testResponseHeader.put("Set-Cookie", TEST_RESPONSE_SET_COOKIE);
testResponseHeader.put("Set-Cookie", "testcookie=testcookie; Path=/video");
dataSourceUnderTest.open(testPostDataSpec);
@ -1461,64 +1449,6 @@ public final class HttpEngineDataSourceTest {
verify(mockUrlRequestBuilder).setDirectExecutorAllowed(true);
}
@Test
public void getCookieHeader_noCookieHandler() {
assertThat(HttpEngineDataSource.getCookieHeader(TEST_URL)).isEmpty();
assertThat(CookieHandler.getDefault()).isNull();
}
@Test
public void getCookieHeader_emptyCookieHandler() {
CookieHandler.setDefault(new CookieManager());
assertThat(HttpEngineDataSource.getCookieHeader(TEST_URL)).isEmpty();
}
@Test
public void getCookieHeader_cookieHandler() throws Exception {
CookieManager cm = new CookieManager();
cm.put(
new URI(TEST_URL),
ImmutableMap.of(
"Set-Cookie", ImmutableList.of(TEST_RESPONSE_SET_COOKIE, TEST_RESPONSE_SET_COOKIE_2)));
CookieHandler.setDefault(cm);
assertThat(HttpEngineDataSource.getCookieHeader(TEST_URL))
.isEqualTo(TEST_REQUEST_COOKIE + "; " + TEST_REQUEST_COOKIE_2 + ";");
}
@Test
public void getCookieHeader_cookieHandlerCustomHandler() throws Exception {
CookieManager cm = new CookieManager();
cm.put(
new URI(TEST_URL),
ImmutableMap.of(
"Set-Cookie", ImmutableList.of(TEST_RESPONSE_SET_COOKIE, TEST_RESPONSE_SET_COOKIE_2)));
assertThat(HttpEngineDataSource.getCookieHeader(TEST_URL, cm))
.isEqualTo(TEST_REQUEST_COOKIE + "; " + TEST_REQUEST_COOKIE_2 + ";");
}
@Test
public void getCookieHeader_cookieHandlerCookie2() throws Exception {
CookieManager cm = new CookieManager();
cm.put(
new URI(TEST_URL),
ImmutableMap.of(
"Set-Cookie2", ImmutableList.of(TEST_RESPONSE_SET_COOKIE, TEST_RESPONSE_SET_COOKIE_2)));
CookieHandler.setDefault(cm);
// This asserts the surprising behavior of CookieManager - Set-Cookie2 is translated to Cookie,
// not Cookie2.
assertThat(cm.get(new URI(TEST_URL), ImmutableMap.of("", ImmutableList.of()))).isNotEmpty();
assertThat(cm.get(new URI(TEST_URL), ImmutableMap.of("", ImmutableList.of())).get("Cookie"))
.containsExactly(TEST_REQUEST_COOKIE, TEST_REQUEST_COOKIE_2);
assertThat(cm.get(new URI(TEST_URL), ImmutableMap.of("", ImmutableList.of())))
.doesNotContainKey("Cookie2");
assertThat(HttpEngineDataSource.getCookieHeader(TEST_URL))
.isEqualTo(TEST_REQUEST_COOKIE + "; " + TEST_REQUEST_COOKIE_2 + ";");
}
// Helper methods.
private void mockStatusResponse() {

View File

@ -22,10 +22,8 @@ import static org.junit.Assert.assertThrows;
import android.net.Uri;
import androidx.media3.common.C;
import androidx.media3.common.util.Util;
import androidx.media3.datasource.DataSource;
import androidx.media3.datasource.DataSpec;
import androidx.media3.datasource.FileDataSource;
import androidx.media3.datasource.TransferListener;
import androidx.media3.test.utils.FailOnCloseDataSink;
import androidx.media3.test.utils.FakeDataSet;
import androidx.media3.test.utils.FakeDataSource;
@ -265,119 +263,6 @@ public final class CacheWriterTest {
assertCachedData(cache, fakeDataSet);
}
@Test
public void cache_ioExceptionDuringOpen_closesDataSource() {
FakeDataSet fakeDataSet = new FakeDataSet().newData("test_data").appendReadData(1).endData();
FakeDataSource dataSource = new FakeDataSource(fakeDataSet);
dataSource.addTransferListener(
new TransferListener() {
@Override
public void onTransferInitializing(
DataSource source, DataSpec dataSpec, boolean isNetwork) {
Util.sneakyThrow(new IOException());
}
@Override
public void onTransferStart(DataSource source, DataSpec dataSpec, boolean isNetwork) {}
@Override
public void onBytesTransferred(
DataSource source, DataSpec dataSpec, boolean isNetwork, int bytesTransferred) {}
@Override
public void onTransferEnd(DataSource source, DataSpec dataSpec, boolean isNetwork) {}
});
CacheWriter cacheWriter =
new CacheWriter(
new CacheDataSource(cache, dataSource),
new DataSpec(Uri.parse("test_data")),
/* temporaryBuffer= */ null,
new CachingCounters());
assertThrows(IOException.class, cacheWriter::cache);
assertThat(dataSource.isOpened()).isFalse();
}
@Test
public void cache_ioExceptionDuringRead_closesDataSource() {
FakeDataSet fakeDataSet =
new FakeDataSet()
.newData("test_data")
.appendReadError(new IOException())
.appendReadData(1)
.endData();
FakeDataSource dataSource = new FakeDataSource(fakeDataSet);
CacheWriter cacheWriter =
new CacheWriter(
new CacheDataSource(cache, dataSource),
new DataSpec(Uri.parse("test_data")),
/* temporaryBuffer= */ null,
new CachingCounters());
assertThrows(IOException.class, cacheWriter::cache);
assertThat(dataSource.isOpened()).isFalse();
}
@Test
public void cache_nonIoExceptionDuringOpen_closesDataSource() {
FakeDataSet fakeDataSet = new FakeDataSet().newData("test_data").appendReadData(1).endData();
FakeDataSource dataSource = new FakeDataSource(fakeDataSet);
dataSource.addTransferListener(
new TransferListener() {
@Override
public void onTransferInitializing(
DataSource source, DataSpec dataSpec, boolean isNetwork) {
throw new IllegalStateException();
}
@Override
public void onTransferStart(DataSource source, DataSpec dataSpec, boolean isNetwork) {}
@Override
public void onBytesTransferred(
DataSource source, DataSpec dataSpec, boolean isNetwork, int bytesTransferred) {}
@Override
public void onTransferEnd(DataSource source, DataSpec dataSpec, boolean isNetwork) {}
});
CacheWriter cacheWriter =
new CacheWriter(
new CacheDataSource(cache, dataSource),
new DataSpec(Uri.parse("test_data")),
/* temporaryBuffer= */ null,
new CachingCounters());
assertThrows(IllegalStateException.class, cacheWriter::cache);
assertThat(dataSource.isOpened()).isFalse();
}
@Test
public void cache_nonIoExceptionDuringRead_closesDataSource() {
FakeDataSet fakeDataSet =
new FakeDataSet()
.newData("test_data")
.appendReadAction(
() -> {
throw new IllegalStateException();
})
.appendReadData(1)
.endData();
FakeDataSource dataSource = new FakeDataSource(fakeDataSet);
CacheWriter cacheWriter =
new CacheWriter(
new CacheDataSource(cache, dataSource),
new DataSpec(Uri.parse("test_data")),
/* temporaryBuffer= */ null,
new CachingCounters());
assertThrows(IllegalStateException.class, cacheWriter::cache);
assertThat(dataSource.isOpened()).isFalse();
}
private static final class CachingCounters implements CacheWriter.ProgressListener {
private long contentLength = C.LENGTH_UNSET;

View File

@ -26,6 +26,7 @@ import androidx.media3.common.C;
import androidx.media3.common.Format;
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.Decoder;
import androidx.media3.decoder.DecoderInputBuffer;
@ -34,7 +35,6 @@ import androidx.media3.exoplayer.DecoderReuseEvaluation;
import androidx.media3.exoplayer.RendererCapabilities;
import androidx.media3.exoplayer.video.DecoderVideoRenderer;
import androidx.media3.exoplayer.video.VideoRendererEventListener;
import java.util.Objects;
// TODO: Merge actual implementation in https://github.com/google/ExoPlayer/pull/7132.
/**
@ -124,7 +124,7 @@ public final class ExperimentalFfmpegVideoRenderer extends DecoderVideoRenderer
@Override
protected DecoderReuseEvaluation canReuseDecoder(
String decoderName, Format oldFormat, Format newFormat) {
boolean sameMimeType = Objects.equals(oldFormat.sampleMimeType, newFormat.sampleMimeType);
boolean sameMimeType = Util.areEqual(oldFormat.sampleMimeType, newFormat.sampleMimeType);
// TODO: Ability to reuse the decoder may be MIME type dependent.
return new DecoderReuseEvaluation(
decoderName,

View File

@ -44,14 +44,6 @@ public class FlacExtractorTest {
/* dumpFilesPrefix= */ "extractordumps/flac/bear_raw");
}
@Test
public void sample32bit() throws Exception {
ExtractorAsserts.assertAllBehaviors(
FlacExtractor::new,
/* file= */ "media/flac/bear_32bit.flac",
/* dumpFilesPrefix= */ "extractordumps/flac/bear_32bit_raw");
}
@Test
public void sampleWithId3HeaderAndId3Enabled() throws Exception {
ExtractorAsserts.assertAllBehaviors(

View File

@ -48,7 +48,6 @@ public class FlacPlaybackTest {
private static final String BEAR_FLAC_16BIT = "mka/bear-flac-16bit.mka";
private static final String BEAR_FLAC_24BIT = "mka/bear-flac-24bit.mka";
private static final String BEAR_FLAC_32BIT = "mka/bear-flac-32bit.mka";
@Before
public void setUp() {
@ -67,11 +66,6 @@ public class FlacPlaybackTest {
playAndAssertAudioSinkInput(BEAR_FLAC_24BIT);
}
@Test
public void test32BitPlayback() throws Exception {
playAndAssertAudioSinkInput(BEAR_FLAC_32BIT);
}
private static void playAndAssertAudioSinkInput(String fileName) throws Exception {
CapturingAudioSink audioSink =
new CapturingAudioSink(

View File

@ -26,7 +26,6 @@ import androidx.media3.common.AudioAttributes;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.audio.AudioManagerCompat;
import androidx.media3.common.util.TraceUtil;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
@ -96,7 +95,10 @@ public class LibiamfAudioRenderer extends DecoderAudioRenderer<IamfDecoder> {
return false;
}
AudioManager audioManager = AudioManagerCompat.getAudioManager(context);
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
if (audioManager == null) {
return false;
}
AudioFormat audioFormat =
new AudioFormat.Builder()
.setEncoding(IamfDecoder.OUTPUT_PCM_ENCODING)

View File

@ -16,15 +16,12 @@
package androidx.media3.decoder.midi;
import android.content.Context;
import android.os.Handler;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.decoder.CryptoConfig;
import androidx.media3.exoplayer.audio.AudioRendererEventListener;
import androidx.media3.exoplayer.audio.AudioSink;
import androidx.media3.exoplayer.audio.DecoderAudioRenderer;
/** Decodes and renders MIDI audio. */
@ -33,22 +30,8 @@ public final class MidiRenderer extends DecoderAudioRenderer<MidiDecoder> {
private final Context context;
/**
* @deprecated Use {@link #MidiRenderer(Context, Handler, AudioRendererEventListener, AudioSink)}
* instead.
*/
@Deprecated
public MidiRenderer(Context context) {
this.context = context.getApplicationContext();
}
/** Creates the renderer instance. */
public MidiRenderer(
Context context,
@Nullable Handler eventHandler,
@Nullable AudioRendererEventListener eventListener,
AudioSink audioSink) {
super(eventHandler, eventListener, audioSink);
public MidiRenderer(Context context) {
this.context = context.getApplicationContext();
}

View File

@ -68,8 +68,6 @@ public final class DebugViewShaderProgram implements GlShaderProgram {
private Executor errorListenerExecutor;
private @MonotonicNonNull EGLDisplay eglDisplay;
private int outputWidth = C.LENGTH_UNSET;
private int outputHeight = C.LENGTH_UNSET;
public DebugViewShaderProgram(
Context context, DebugViewProvider debugViewProvider, ColorInfo outputColorInfo) {
@ -156,13 +154,9 @@ public final class DebugViewShaderProgram implements GlShaderProgram {
eglDisplay = getDefaultEglDisplay();
}
EGLContext eglContext = GlUtil.getCurrentContext();
if (outputWidth == C.LENGTH_UNSET || outputHeight == C.LENGTH_UNSET) {
outputWidth = inputWidth;
outputHeight = inputHeight;
}
@Nullable
SurfaceView debugSurfaceView =
debugViewProvider.getDebugPreviewSurfaceView(outputWidth, outputHeight);
debugViewProvider.getDebugPreviewSurfaceView(inputWidth, inputHeight);
if (debugSurfaceView != null && !Objects.equals(this.debugSurfaceView, debugSurfaceView)) {
debugSurfaceViewWrapper =
new SurfaceViewWrapper(
@ -170,16 +164,10 @@ public final class DebugViewShaderProgram implements GlShaderProgram {
}
this.debugSurfaceView = debugSurfaceView;
if (defaultShaderProgram == null) {
ImmutableList.Builder<GlMatrixTransformation> matrixTransformationListBuilder =
new ImmutableList.Builder<>();
matrixTransformationListBuilder.add(
Presentation.createForWidthAndHeight(
outputWidth, outputHeight, Presentation.LAYOUT_SCALE_TO_FIT));
defaultShaderProgram =
DefaultShaderProgram.createApplyingOetf(
context,
/* matrixTransformations= */ matrixTransformationListBuilder.build(),
/* matrixTransformations= */ ImmutableList.of(),
/* rgbMatrices= */ ImmutableList.of(),
outputColorInfo,
outputColorInfo.colorTransfer == C.COLOR_TRANSFER_LINEAR

View File

@ -42,11 +42,11 @@ import androidx.media3.common.util.GlUtil;
import androidx.media3.common.util.Log;
import androidx.media3.common.util.LongArrayQueue;
import androidx.media3.common.util.Size;
import androidx.media3.common.util.Util;
import androidx.media3.effect.DefaultVideoFrameProcessor.WorkingColorSpace;
import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.List;
import java.util.Objects;
import java.util.Queue;
import java.util.concurrent.ConcurrentLinkedQueue;
import java.util.concurrent.Executor;
@ -340,7 +340,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
if (textureOutputListener != null) {
return;
}
if (Objects.equals(this.outputSurfaceInfo, outputSurfaceInfo)) {
if (Util.areEqual(this.outputSurfaceInfo, outputSurfaceInfo)) {
return;
}
@ -479,7 +479,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
this.inputHeight = inputHeight;
Size outputSizeBeforeSurfaceTransformation =
MatrixUtils.configureAndGetOutputSize(inputWidth, inputHeight, matrixTransformations);
if (!Objects.equals(
if (!Util.areEqual(
this.outputSizeBeforeSurfaceTransformation, outputSizeBeforeSurfaceTransformation)) {
this.outputSizeBeforeSurfaceTransformation = outputSizeBeforeSurfaceTransformation;
videoFrameProcessorListenerExecutor.execute(

View File

@ -31,7 +31,7 @@
}
-dontnote androidx.media3.decoder.midi.MidiRenderer
-keepclassmembers class androidx.media3.decoder.midi.MidiRenderer {
<init>(android.content.Context, android.os.Handler, androidx.media3.exoplayer.audio.AudioRendererEventListener, androidx.media3.exoplayer.audio.AudioSink);
<init>(android.content.Context);
}
-dontnote androidx.media3.decoder.mpegh.MpeghAudioRenderer
-keepclassmembers class androidx.media3.decoder.mpegh.MpeghAudioRenderer {

View File

@ -17,11 +17,9 @@ package androidx.media3.exoplayer;
import static androidx.test.platform.app.InstrumentationRegistry.getInstrumentation;
import static com.google.common.truth.Truth.assertThat;
import static org.junit.Assume.assumeTrue;
import android.content.Context;
import android.net.Uri;
import android.os.Build;
import android.os.Looper;
import androidx.media3.common.C;
import androidx.media3.common.MediaItem;
@ -36,7 +34,6 @@ import androidx.media3.exoplayer.source.ClippingMediaSource;
import androidx.media3.exoplayer.source.DefaultMediaSourceFactory;
import androidx.media3.exoplayer.text.TextOutput;
import androidx.media3.exoplayer.text.TextRenderer;
import androidx.test.filters.SdkSuppress;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.Iterables;
import java.util.ArrayList;
@ -109,12 +106,7 @@ public final class ClippedPlaybackTest {
}
@Test
// TODO: b/399815346 - This test is flaky on API 21 even when parsing subtitles during extraction.
@SdkSuppress(minSdkVersion = 22)
public void subtitlesRespectClipping_multiplePeriods() throws Exception {
// Parsing subtitles during rendering is flaky (see comment above), so restrict that
// configuration to a single API level to reduce the chance of seeing a flaky failure.
assumeTrue(parseSubtitlesDuringExtraction || Build.VERSION.SDK_INT >= 34);
ImmutableList<MediaItem> mediaItems =
ImmutableList.of(
new MediaItem.Builder()

View File

@ -701,13 +701,11 @@ public class MediaExtractorCompatTest {
public void getTrackFormat_withMultipleTracks_returnsCorrectTrackId() throws IOException {
fakeExtractor.addReadAction(
(input, seekPosition) -> {
TrackOutput output1 = extractorOutput.track(/* id= */ 0, C.TRACK_TYPE_VIDEO);
TrackOutput output2 = extractorOutput.track(/* id= */ 1, C.TRACK_TYPE_AUDIO);
TrackOutput output1 = extractorOutput.track(/* id= */ 1, C.TRACK_TYPE_VIDEO);
TrackOutput output2 = extractorOutput.track(/* id= */ 2, C.TRACK_TYPE_AUDIO);
extractorOutput.endTracks();
output1.format(
new Format.Builder().setId(1).setSampleMimeType(MimeTypes.VIDEO_H264).build());
output2.format(
new Format.Builder().setId(2).setSampleMimeType(MimeTypes.AUDIO_AAC).build());
output1.format(PLACEHOLDER_FORMAT_VIDEO);
output2.format(PLACEHOLDER_FORMAT_AUDIO);
return Extractor.RESULT_CONTINUE;
});

View File

@ -24,7 +24,6 @@ import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes;
import androidx.test.core.app.ApplicationProvider;
import androidx.test.ext.junit.runners.AndroidJUnit4;
import androidx.test.filters.SdkSuppress;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayList;
@ -36,7 +35,6 @@ import org.junit.runner.RunWith;
public class DefaultAudioSinkTest {
@Test
@SdkSuppress(minSdkVersion = 22) // TODO: b/399130330 - Debug why this fails on API 21.
public void audioTrackExceedsSharedMemory_retriesUntilOngoingReleasesAreDone() throws Exception {
Context context = ApplicationProvider.getApplicationContext();
getInstrumentation()

View File

@ -27,7 +27,6 @@ import android.content.Context;
import android.media.AudioFocusRequest;
import android.media.AudioManager;
import android.os.Handler;
import android.os.Looper;
import androidx.annotation.IntDef;
import androidx.annotation.Nullable;
import androidx.annotation.VisibleForTesting;
@ -38,13 +37,13 @@ import androidx.media3.common.audio.AudioFocusRequestCompat;
import androidx.media3.common.audio.AudioManagerCompat;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.Log;
import com.google.common.base.Objects;
import com.google.common.base.Supplier;
import com.google.common.base.Suppliers;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Objects;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** Manages requesting and responding to changes in audio focus. */
@ -138,13 +137,18 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* Constructs an AudioFocusManager to automatically handle audio focus for a player.
*
* @param context The current context.
* @param eventLooper A {@link Looper} for the thread on which the audio focus manager is used.
* @param eventHandler A {@link Handler} to for the thread on which the player is used.
* @param playerControl A {@link PlayerControl} to handle commands from this instance.
*/
public AudioFocusManager(Context context, Looper eventLooper, PlayerControl playerControl) {
this.audioManager = Suppliers.memoize(() -> AudioManagerCompat.getAudioManager(context));
public AudioFocusManager(Context context, Handler eventHandler, PlayerControl playerControl) {
this.audioManager =
Suppliers.memoize(
() ->
checkNotNull(
(AudioManager)
context.getApplicationContext().getSystemService(Context.AUDIO_SERVICE)));
this.playerControl = playerControl;
this.eventHandler = new Handler(eventLooper);
this.eventHandler = eventHandler;
this.audioFocusState = AUDIO_FOCUS_STATE_NOT_REQUESTED;
}
@ -163,7 +167,7 @@ import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
* managed automatically.
*/
public void setAudioAttributes(@Nullable AudioAttributes audioAttributes) {
if (!Objects.equals(this.audioAttributes, audioAttributes)) {
if (!Objects.equal(this.audioAttributes, audioAttributes)) {
this.audioAttributes = audioAttributes;
focusGainToRequest = convertAudioAttributesToFocusGain(audioAttributes);
Assertions.checkArgument(

View File

@ -27,6 +27,7 @@ import androidx.media3.common.Timeline;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.Clock;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.decoder.DecoderInputBuffer;
import androidx.media3.decoder.DecoderInputBuffer.InsufficientCapacityException;
import androidx.media3.exoplayer.analytics.PlayerId;
@ -36,7 +37,6 @@ import androidx.media3.exoplayer.source.SampleStream;
import androidx.media3.exoplayer.source.SampleStream.ReadDataResult;
import androidx.media3.exoplayer.source.SampleStream.ReadFlags;
import java.io.IOException;
import java.util.Objects;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/**
@ -208,7 +208,7 @@ public abstract class BaseRenderer implements Renderer, RendererCapabilities {
@Override
public final void setTimeline(Timeline timeline) {
if (!Objects.equals(this.timeline, timeline)) {
if (!Util.areEqual(this.timeline, timeline)) {
this.timeline = timeline;
onTimelineChanged(this.timeline);
}

View File

@ -557,15 +557,9 @@ public class DefaultRenderersFactory implements RenderersFactory {
// Full class names used for constructor args so the LINT rule triggers if any of them move.
// LINT.IfChange
Class<?> clazz = Class.forName("androidx.media3.decoder.midi.MidiRenderer");
Constructor<?> constructor =
clazz.getConstructor(
Context.class,
android.os.Handler.class,
androidx.media3.exoplayer.audio.AudioRendererEventListener.class,
androidx.media3.exoplayer.audio.AudioSink.class);
Constructor<?> constructor = clazz.getConstructor(Context.class);
// LINT.ThenChange(../../../../../../proguard-rules.txt)
Renderer renderer =
(Renderer) constructor.newInstance(context, eventHandler, eventListener, audioSink);
Renderer renderer = (Renderer) constructor.newInstance(context);
out.add(extensionRendererIndex++, renderer);
Log.i(TAG, "Loaded MidiRenderer.");
} catch (ClassNotFoundException e) {

View File

@ -41,7 +41,6 @@ import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.Objects;
/** Thrown when a non locally recoverable playback failure occurs. */
public final class ExoPlaybackException extends PlaybackException {
@ -321,11 +320,11 @@ public final class ExoPlaybackException extends PlaybackException {
// true.
ExoPlaybackException other = (ExoPlaybackException) Util.castNonNull(that);
return type == other.type
&& Objects.equals(rendererName, other.rendererName)
&& Util.areEqual(rendererName, other.rendererName)
&& rendererIndex == other.rendererIndex
&& Objects.equals(rendererFormat, other.rendererFormat)
&& Util.areEqual(rendererFormat, other.rendererFormat)
&& rendererFormatSupport == other.rendererFormatSupport
&& Objects.equals(mediaPeriodId, other.mediaPeriodId)
&& Util.areEqual(mediaPeriodId, other.mediaPeriodId)
&& isRecoverable == other.isRecoverable;
}

View File

@ -485,9 +485,6 @@ public interface ExoPlayer extends Player {
* <p>If enabled, ExoPlayer's playback loop will run as rarely as possible by scheduling work
* for when {@link Renderer} progress can be made.
*
* <p>If a custom {@link AudioSink} is used then it must correctly implement {@link
* AudioSink#getAudioTrackBufferSizeUs()} to enable dynamic scheduling for audio playback.
*
* <p>This method is experimental, and will be renamed or removed in a future release.
*
* @param dynamicSchedulingEnabled Whether to enable dynamic scheduling.

View File

@ -120,7 +120,6 @@ import com.google.common.collect.ImmutableList;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.CopyOnWriteArraySet;
/** The default implementation of {@link ExoPlayer}. */
@ -169,6 +168,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
private final ComponentListener componentListener;
private final FrameMetadataListener frameMetadataListener;
private final AudioBecomingNoisyManager audioBecomingNoisyManager;
private final AudioFocusManager audioFocusManager;
@Nullable private final StreamVolumeManager streamVolumeManager;
private final WakeLockManager wakeLockManager;
private final WifiLockManager wifiLockManager;
@ -350,7 +350,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
PlayerId playerId = new PlayerId(builder.playerName);
internalPlayer =
new ExoPlayerImplInternal(
applicationContext,
renderers,
secondaryRenderers,
trackSelector,
@ -408,6 +407,8 @@ import java.util.concurrent.CopyOnWriteArraySet;
new AudioBecomingNoisyManager(
builder.context, playbackLooper, builder.looper, componentListener, clock);
audioBecomingNoisyManager.setEnabled(builder.handleAudioBecomingNoisy);
audioFocusManager = new AudioFocusManager(builder.context, eventHandler, componentListener);
audioFocusManager.setAudioAttributes(builder.handleAudioFocus ? audioAttributes : null);
if (builder.suppressPlaybackOnUnsuitableOutput) {
suitableOutputChecker = builder.suitableOutputChecker;
@ -441,7 +442,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
videoSize = VideoSize.UNKNOWN;
surfaceSize = Size.UNKNOWN;
internalPlayer.setAudioAttributes(audioAttributes, builder.handleAudioFocus);
internalPlayer.setAudioAttributes(audioAttributes);
sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_ATTRIBUTES, audioAttributes);
sendRendererMessage(TRACK_TYPE_VIDEO, MSG_SET_SCALING_MODE, videoScalingMode);
sendRendererMessage(
@ -521,13 +522,17 @@ import java.util.concurrent.CopyOnWriteArraySet;
@Override
public void prepare() {
verifyApplicationThread();
boolean playWhenReady = getPlayWhenReady();
@AudioFocusManager.PlayerCommand
int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, Player.STATE_BUFFERING);
updatePlayWhenReady(playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playerCommand));
if (playbackInfo.playbackState != Player.STATE_IDLE) {
return;
}
PlaybackInfo playbackInfo = this.playbackInfo.copyWithPlaybackError(null);
playbackInfo =
maskPlaybackState(
playbackInfo, playbackInfo.timeline.isEmpty() ? STATE_ENDED : STATE_BUFFERING);
playbackInfo.copyWithPlaybackState(
playbackInfo.timeline.isEmpty() ? STATE_ENDED : STATE_BUFFERING);
// Trigger internal prepare first before updating the playback info and notifying external
// listeners to ensure that new operations issued in the listener notifications reach the
// player after this prepare. The internal player can't change the playback info immediately
@ -798,7 +803,9 @@ import java.util.concurrent.CopyOnWriteArraySet;
@Override
public void setPlayWhenReady(boolean playWhenReady) {
verifyApplicationThread();
updatePlayWhenReady(playWhenReady, PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST);
@AudioFocusManager.PlayerCommand
int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, getPlaybackState());
updatePlayWhenReady(playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playerCommand));
}
@Override
@ -898,7 +905,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
PlaybackInfo newPlaybackInfo = playbackInfo;
if (playbackInfo.playbackState == Player.STATE_READY
|| (playbackInfo.playbackState == Player.STATE_ENDED && !timeline.isEmpty())) {
newPlaybackInfo = maskPlaybackState(playbackInfo, Player.STATE_BUFFERING);
newPlaybackInfo = playbackInfo.copyWithPlaybackState(Player.STATE_BUFFERING);
}
int oldMaskingMediaItemIndex = getCurrentMediaItemIndex();
newPlaybackInfo =
@ -999,6 +1006,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
@Override
public void stop() {
verifyApplicationThread();
audioFocusManager.updateAudioFocus(getPlayWhenReady(), Player.STATE_IDLE);
stopInternal(/* error= */ null);
currentCueGroup = new CueGroup(ImmutableList.of(), playbackInfo.positionUs);
}
@ -1023,6 +1031,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
}
wakeLockManager.setStayAwake(false);
wifiLockManager.setStayAwake(false);
audioFocusManager.release();
if (suitableOutputChecker != null) {
suitableOutputChecker.disable();
}
@ -1042,7 +1051,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
if (playbackInfo.sleepingForOffload) {
playbackInfo = playbackInfo.copyWithEstimatedPosition();
}
playbackInfo = maskPlaybackState(playbackInfo, Player.STATE_IDLE);
playbackInfo = playbackInfo.copyWithPlaybackState(Player.STATE_IDLE);
playbackInfo = playbackInfo.copyWithLoadingMediaPeriodId(playbackInfo.periodId);
playbackInfo.bufferedPositionUs = playbackInfo.positionUs;
playbackInfo.totalBufferedDurationUs = 0;
@ -1452,7 +1461,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
if (playerReleased) {
return;
}
if (!Objects.equals(this.audioAttributes, newAudioAttributes)) {
if (!Util.areEqual(this.audioAttributes, newAudioAttributes)) {
this.audioAttributes = newAudioAttributes;
sendRendererMessage(TRACK_TYPE_AUDIO, MSG_SET_AUDIO_ATTRIBUTES, newAudioAttributes);
if (streamVolumeManager != null) {
@ -1464,8 +1473,13 @@ import java.util.concurrent.CopyOnWriteArraySet;
listener -> listener.onAudioAttributesChanged(newAudioAttributes));
}
internalPlayer.setAudioAttributes(audioAttributes, handleAudioFocus);
internalPlayer.setAudioAttributes(audioAttributes);
audioFocusManager.setAudioAttributes(handleAudioFocus ? newAudioAttributes : null);
boolean playWhenReady = getPlayWhenReady();
@AudioFocusManager.PlayerCommand
int playerCommand = audioFocusManager.updateAudioFocus(playWhenReady, getPlaybackState());
updatePlayWhenReady(playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playerCommand));
listeners.flushEvents();
}
@ -1523,7 +1537,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
return;
}
this.volume = volume;
internalPlayer.setVolume(volume);
sendVolumeToInternalPlayer();
float finalVolume = volume;
listeners.sendEvent(EVENT_VOLUME_CHANGED, listener -> listener.onVolumeChanged(finalVolume));
}
@ -1598,7 +1612,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
@Override
public void setPriorityTaskManager(@Nullable PriorityTaskManager priorityTaskManager) {
verifyApplicationThread();
if (Objects.equals(this.priorityTaskManager, priorityTaskManager)) {
if (Util.areEqual(this.priorityTaskManager, priorityTaskManager)) {
return;
}
if (isPriorityTaskManagerRegistered) {
@ -1868,7 +1882,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
this.playbackInfo.copyWithLoadingMediaPeriodId(this.playbackInfo.periodId);
playbackInfo.bufferedPositionUs = playbackInfo.positionUs;
playbackInfo.totalBufferedDurationUs = 0;
playbackInfo = maskPlaybackState(playbackInfo, Player.STATE_IDLE);
playbackInfo = playbackInfo.copyWithPlaybackState(Player.STATE_IDLE);
if (error != null) {
playbackInfo = playbackInfo.copyWithPlaybackError(error);
}
@ -2346,7 +2360,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
maskingPlaybackState = STATE_BUFFERING;
}
}
newPlaybackInfo = maskPlaybackState(newPlaybackInfo, maskingPlaybackState);
newPlaybackInfo = newPlaybackInfo.copyWithPlaybackState(maskingPlaybackState);
internalPlayer.setMediaSources(
holders, startWindowIndex, Util.msToUs(startPositionMs), shuffleOrder);
boolean positionDiscontinuity =
@ -2420,7 +2434,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
&& toIndex == currentMediaSourceCount
&& currentIndex >= newPlaybackInfo.timeline.getWindowCount();
if (transitionsToEnded) {
newPlaybackInfo = maskPlaybackState(newPlaybackInfo, STATE_ENDED);
newPlaybackInfo = newPlaybackInfo.copyWithPlaybackState(STATE_ENDED);
}
internalPlayer.removeMediaSources(fromIndex, toIndex, shuffleOrder);
return newPlaybackInfo;
@ -2544,14 +2558,6 @@ import java.util.concurrent.CopyOnWriteArraySet;
return playbackInfo;
}
private static PlaybackInfo maskPlaybackState(PlaybackInfo playbackInfo, int playbackState) {
playbackInfo = playbackInfo.copyWithPlaybackState(playbackState);
if (playbackState == STATE_IDLE || playbackState == STATE_ENDED) {
playbackInfo = playbackInfo.copyWithIsLoading(false);
}
return playbackInfo;
}
@Nullable
private Pair<Object, Long> getPeriodPositionUsAfterTimelineChanged(
Timeline oldTimeline,
@ -2730,15 +2736,31 @@ import java.util.concurrent.CopyOnWriteArraySet;
}
}
private void sendVolumeToInternalPlayer() {
float scaledVolume = volume * audioFocusManager.getVolumeMultiplier();
internalPlayer.setVolume(scaledVolume);
}
private void updatePlayWhenReady(
boolean playWhenReady, @Player.PlayWhenReadyChangeReason int playWhenReadyChangeReason) {
boolean playWhenReady,
@AudioFocusManager.PlayerCommand int playerCommand,
@Player.PlayWhenReadyChangeReason int playWhenReadyChangeReason) {
playWhenReady = playWhenReady && playerCommand != AudioFocusManager.PLAYER_COMMAND_DO_NOT_PLAY;
@PlaybackSuppressionReason
int playbackSuppressionReason = computePlaybackSuppressionReason(playWhenReady);
int playbackSuppressionReason = computePlaybackSuppressionReason(playWhenReady, playerCommand);
if (playbackInfo.playWhenReady == playWhenReady
&& playbackInfo.playbackSuppressionReason == playbackSuppressionReason
&& playbackInfo.playWhenReadyChangeReason == playWhenReadyChangeReason) {
return;
}
updatePlaybackInfoForPlayWhenReadyAndSuppressionReasonStates(
playWhenReady, playWhenReadyChangeReason, playbackSuppressionReason);
}
private void updatePlaybackInfoForPlayWhenReadyAndSuppressionReasonStates(
boolean playWhenReady,
@Player.PlayWhenReadyChangeReason int playWhenReadyChangeReason,
@PlaybackSuppressionReason int playbackSuppressionReason) {
pendingOperationAcks++;
// Position estimation and copy must occur before changing/masking playback state.
PlaybackInfo newPlaybackInfo =
@ -2760,16 +2782,22 @@ import java.util.concurrent.CopyOnWriteArraySet;
/* repeatCurrentMediaItem= */ false);
}
private @PlaybackSuppressionReason int computePlaybackSuppressionReason(boolean playWhenReady) {
if (suitableOutputChecker != null
&& !suitableOutputChecker.isSelectedOutputSuitableForPlayback()) {
return Player.PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT;
}
if (playbackInfo.playbackSuppressionReason
== Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS
&& !playWhenReady) {
@PlaybackSuppressionReason
private int computePlaybackSuppressionReason(
boolean playWhenReady, @AudioFocusManager.PlayerCommand int playerCommand) {
if (playerCommand == AudioFocusManager.PLAYER_COMMAND_WAIT_FOR_CALLBACK) {
return Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS;
}
if (suitableOutputChecker != null) {
if (playWhenReady && !suitableOutputChecker.isSelectedOutputSuitableForPlayback()) {
return Player.PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT;
}
if (!playWhenReady
&& playbackInfo.playbackSuppressionReason
== PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT) {
return Player.PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT;
}
}
return Player.PLAYBACK_SUPPRESSION_REASON_NONE;
}
@ -2888,10 +2916,16 @@ import java.util.concurrent.CopyOnWriteArraySet;
if (isSelectedOutputSuitableForPlayback) {
if (playbackInfo.playbackSuppressionReason
== Player.PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT) {
updatePlayWhenReady(playbackInfo.playWhenReady, PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST);
updatePlaybackInfoForPlayWhenReadyAndSuppressionReasonStates(
playbackInfo.playWhenReady,
PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
Player.PLAYBACK_SUPPRESSION_REASON_NONE);
}
} else {
updatePlayWhenReady(playbackInfo.playWhenReady, PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST);
updatePlaybackInfoForPlayWhenReadyAndSuppressionReasonStates(
playbackInfo.playWhenReady,
PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST,
Player.PLAYBACK_SUPPRESSION_REASON_UNSUITABLE_AUDIO_OUTPUT);
}
}
@ -2910,6 +2944,12 @@ import java.util.concurrent.CopyOnWriteArraySet;
.build();
}
private static int getPlayWhenReadyChangeReason(int playerCommand) {
return playerCommand == AudioFocusManager.PLAYER_COMMAND_DO_NOT_PLAY
? PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS
: PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST;
}
private static final class MediaSourceHolderSnapshot implements MediaSourceInfoHolder {
private final Object uid;
@ -2946,6 +2986,7 @@ import java.util.concurrent.CopyOnWriteArraySet;
SurfaceHolder.Callback,
TextureView.SurfaceTextureListener,
SphericalGLSurfaceView.VideoSurfaceListener,
AudioFocusManager.PlayerControl,
AudioBecomingNoisyManager.EventListener,
StreamVolumeManager.Listener,
AudioOffloadListener {
@ -3178,12 +3219,28 @@ import java.util.concurrent.CopyOnWriteArraySet;
setVideoOutputInternal(/* videoOutput= */ null);
}
// AudioFocusManager.PlayerControl implementation
@Override
public void setVolumeMultiplier(float volumeMultiplier) {
sendVolumeToInternalPlayer();
}
@Override
public void executePlayerCommand(@AudioFocusManager.PlayerCommand int playerCommand) {
boolean playWhenReady = getPlayWhenReady();
updatePlayWhenReady(
playWhenReady, playerCommand, getPlayWhenReadyChangeReason(playerCommand));
}
// AudioBecomingNoisyManager.EventListener implementation.
@Override
public void onAudioBecomingNoisy() {
updatePlayWhenReady(
/* playWhenReady= */ false, Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY);
/* playWhenReady= */ false,
AudioFocusManager.PLAYER_COMMAND_DO_NOT_PLAY,
Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_BECOMING_NOISY);
}
// StreamVolumeManager.Listener implementation.

View File

@ -27,7 +27,6 @@ import static androidx.media3.exoplayer.audio.AudioSink.OFFLOAD_MODE_DISABLED;
import static java.lang.Math.max;
import static java.lang.Math.min;
import android.content.Context;
import android.os.Handler;
import android.os.Looper;
import android.os.Message;
@ -78,7 +77,6 @@ import java.io.IOException;
import java.util.ArrayList;
import java.util.Collections;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.atomic.AtomicBoolean;
/** Implements the internal behavior of {@link ExoPlayerImpl}. */
@ -88,8 +86,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
TrackSelector.InvalidationListener,
MediaSourceList.MediaSourceListInfoRefreshListener,
PlaybackParametersListener,
PlayerMessage.Sender,
AudioFocusManager.PlayerControl {
PlayerMessage.Sender {
private static final String TAG = "ExoPlayerImplInternal";
@ -166,8 +163,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
private static final int MSG_SET_VIDEO_OUTPUT = 30;
private static final int MSG_SET_AUDIO_ATTRIBUTES = 31;
private static final int MSG_SET_VOLUME = 32;
private static final int MSG_AUDIO_FOCUS_PLAYER_COMMAND = 33;
private static final int MSG_AUDIO_FOCUS_VOLUME_MULTIPLIER = 34;
private static final long BUFFERING_MAXIMUM_INTERVAL_MS =
Util.usToMs(Renderer.DEFAULT_DURATION_TO_PROGRESS_US);
@ -214,7 +209,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
private final AnalyticsCollector analyticsCollector;
private final HandlerWrapper applicationLooperHandler;
private final boolean hasSecondaryRenderers;
private final AudioFocusManager audioFocusManager;
@SuppressWarnings("unused")
private SeekParameters seekParameters;
@ -245,10 +239,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
private Timeline lastPreloadPoolInvalidationTimeline;
private long prewarmingMediaPeriodDiscontinuity = C.TIME_UNSET;
private boolean isPrewarmingDisabledUntilNextTransition;
private float volume;
public ExoPlayerImplInternal(
Context context,
Renderer[] renderers,
Renderer[] secondaryRenderers,
TrackSelector trackSelector,
@ -286,7 +278,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
this.playerId = playerId;
this.preloadConfiguration = preloadConfiguration;
this.analyticsCollector = analyticsCollector;
this.volume = 1f;
playbackMaybeBecameStuckAtMs = C.TIME_UNSET;
lastRebufferRealtimeMs = C.TIME_UNSET;
@ -341,8 +332,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
(playbackLooperProvider == null) ? new PlaybackLooperProvider() : playbackLooperProvider;
this.playbackLooper = this.playbackLooperProvider.obtainLooper();
handler = clock.createHandler(this.playbackLooper, this);
audioFocusManager = new AudioFocusManager(context, playbackLooper, /* playerControl= */ this);
}
private MediaPeriodHolder createMediaPeriodHolder(
@ -463,29 +452,14 @@ import java.util.concurrent.atomic.AtomicBoolean;
.sendToTarget();
}
public void setAudioAttributes(AudioAttributes audioAttributes, boolean handleAudioFocus) {
handler
.obtainMessage(MSG_SET_AUDIO_ATTRIBUTES, handleAudioFocus ? 1 : 0, 0, audioAttributes)
.sendToTarget();
public void setAudioAttributes(AudioAttributes audioAttributes) {
handler.obtainMessage(MSG_SET_AUDIO_ATTRIBUTES, audioAttributes).sendToTarget();
}
public void setVolume(float volume) {
handler.obtainMessage(MSG_SET_VOLUME, volume).sendToTarget();
}
private void handleAudioFocusPlayerCommandInternal(
@AudioFocusManager.PlayerCommand int playerCommand) throws ExoPlaybackException {
updatePlayWhenReadyWithAudioFocus(
playbackInfo.playWhenReady,
playerCommand,
playbackInfo.playbackSuppressionReason,
playbackInfo.playWhenReadyChangeReason);
}
private void handleAudioFocusVolumeMultiplierChange() throws ExoPlaybackException {
setVolumeInternal(volume);
}
@Override
public synchronized void sendMessage(PlayerMessage message) {
if (released || !playbackLooper.getThread().isAlive()) {
@ -604,18 +578,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
.sendToTarget();
}
// AudioFocusManager.PlayerControl implementation
@Override
public void setVolumeMultiplier(float volumeMultiplier) {
handler.sendEmptyMessage(MSG_AUDIO_FOCUS_VOLUME_MULTIPLIER);
}
@Override
public void executePlayerCommand(@AudioFocusManager.PlayerCommand int playerCommand) {
handler.obtainMessage(MSG_AUDIO_FOCUS_PLAYER_COMMAND, playerCommand, 0).sendToTarget();
}
// Handler.Callback implementation.
@SuppressWarnings({"unchecked", "WrongConstant"}) // Casting message payload types and IntDef.
@ -716,18 +678,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
updateMediaSourcesWithMediaItemsInternal(msg.arg1, msg.arg2, (List<MediaItem>) msg.obj);
break;
case MSG_SET_AUDIO_ATTRIBUTES:
setAudioAttributesInternal(
(AudioAttributes) msg.obj, /* handleAudioFocus= */ msg.arg1 != 0);
setAudioAttributesInternal((AudioAttributes) msg.obj);
break;
case MSG_SET_VOLUME:
setVolumeInternal((Float) msg.obj);
break;
case MSG_AUDIO_FOCUS_PLAYER_COMMAND:
handleAudioFocusPlayerCommandInternal(/* playerCommand= */ msg.arg1);
break;
case MSG_AUDIO_FOCUS_VOLUME_MULTIPLIER:
handleAudioFocusVolumeMultiplierChange();
break;
case MSG_RELEASE:
releaseInternal();
// Return immediately to not send playback info updates after release.
@ -750,7 +705,25 @@ import java.util.concurrent.atomic.AtomicBoolean;
: readingPeriod.info.id);
}
}
if (e.type == ExoPlaybackException.TYPE_RENDERER
if (e.isRecoverable
&& (pendingRecoverableRendererError == null
|| e.errorCode == PlaybackException.ERROR_CODE_AUDIO_TRACK_OFFLOAD_INIT_FAILED
|| e.errorCode == PlaybackException.ERROR_CODE_AUDIO_TRACK_OFFLOAD_WRITE_FAILED)) {
// If pendingRecoverableRendererError != null and error was
// ERROR_CODE_AUDIO_TRACK_OFFLOAD_WRITE_FAILED then upon retry, renderer will attempt with
// offload disabled.
Log.w(TAG, "Recoverable renderer error", e);
if (pendingRecoverableRendererError != null) {
pendingRecoverableRendererError.addSuppressed(e);
e = pendingRecoverableRendererError;
} else {
pendingRecoverableRendererError = e;
}
// Given that the player is now in an unhandled exception state, the error needs to be
// recovered or the player stopped before any other message is handled.
handler.sendMessageAtFrontOfQueue(
handler.obtainMessage(MSG_ATTEMPT_RENDERER_ERROR_RECOVERY, e));
} else if (e.type == ExoPlaybackException.TYPE_RENDERER
&& renderers[e.rendererIndex % renderers.length].isRendererPrewarming(
/* id= */ e.rendererIndex)) {
// TODO(b/380273486): Investigate recovery for pre-warming renderer errors
@ -774,12 +747,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
pendingRecoverableRendererError.addSuppressed(e);
e = pendingRecoverableRendererError;
}
Log.e(TAG, "Playback error", e);
if (e.type == ExoPlaybackException.TYPE_RENDERER
&& queue.getPlayingPeriod() != queue.getReadingPeriod()) {
// We encountered a renderer error while reading ahead. Force-update the playback position
// to the failing item to ensure correct retry or that the user-visible error is reported
// after the transition.
// to the failing item to ensure the user-visible error is reported after the transition.
while (queue.getPlayingPeriod() != queue.getReadingPeriod()) {
queue.advancePlayingPeriod();
}
@ -795,24 +767,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
/* reportDiscontinuity= */ true,
Player.DISCONTINUITY_REASON_AUTO_TRANSITION);
}
if (e.isRecoverable
&& (pendingRecoverableRendererError == null
|| e.errorCode == PlaybackException.ERROR_CODE_AUDIO_TRACK_OFFLOAD_INIT_FAILED
|| e.errorCode == PlaybackException.ERROR_CODE_AUDIO_TRACK_OFFLOAD_WRITE_FAILED)) {
// Given that the player is now in an unhandled exception state, the error needs to be
// recovered or the player stopped before any other message is handled.
Log.w(TAG, "Recoverable renderer error", e);
if (pendingRecoverableRendererError == null) {
pendingRecoverableRendererError = e;
}
handler.sendMessageAtFrontOfQueue(
handler.obtainMessage(MSG_ATTEMPT_RENDERER_ERROR_RECOVERY, e));
} else {
Log.e(TAG, "Playback error", e);
stopInternal(/* forceResetRenderers= */ true, /* acknowledgeStop= */ false);
playbackInfo = playbackInfo.copyWithPlaybackError(e);
}
stopInternal(/* forceResetRenderers= */ true, /* acknowledgeStop= */ false);
playbackInfo = playbackInfo.copyWithPlaybackError(e);
}
} catch (DrmSession.DrmSessionException e) {
handleIoException(e, e.errorCode);
@ -915,7 +871,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
}
}
private void prepareInternal() throws ExoPlaybackException {
private void prepareInternal() {
playbackInfoUpdate.incrementPendingOperationAcks(/* operationAcks= */ 1);
resetInternal(
/* resetRenderers= */ false,
@ -924,7 +880,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
/* resetError= */ true);
loadControl.onPrepared(playerId);
setState(playbackInfo.timeline.isEmpty() ? Player.STATE_ENDED : Player.STATE_BUFFERING);
updatePlayWhenReadyWithAudioFocus();
mediaSourceList.prepare(bandwidthMeter.getTransferListener());
handler.sendEmptyMessage(MSG_DO_SOME_WORK);
}
@ -997,18 +952,13 @@ import java.util.concurrent.atomic.AtomicBoolean;
handleMediaSourceListInfoRefreshed(timeline, /* isSourceRefresh= */ false);
}
private void setAudioAttributesInternal(AudioAttributes audioAttributes, boolean handleAudioFocus)
throws ExoPlaybackException {
private void setAudioAttributesInternal(AudioAttributes audioAttributes) {
trackSelector.setAudioAttributes(audioAttributes);
audioFocusManager.setAudioAttributes(handleAudioFocus ? audioAttributes : null);
updatePlayWhenReadyWithAudioFocus();
}
private void setVolumeInternal(float volume) throws ExoPlaybackException {
this.volume = volume;
float scaledVolume = volume * audioFocusManager.getVolumeMultiplier();
for (RendererHolder renderer : renderers) {
renderer.setVolume(scaledVolume);
renderer.setVolume(volume);
}
}
@ -1031,47 +981,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
@Player.PlayWhenReadyChangeReason int reason)
throws ExoPlaybackException {
playbackInfoUpdate.incrementPendingOperationAcks(operationAck ? 1 : 0);
updatePlayWhenReadyWithAudioFocus(playWhenReady, playbackSuppressionReason, reason);
}
private void updatePlayWhenReadyWithAudioFocus() throws ExoPlaybackException {
updatePlayWhenReadyWithAudioFocus(
playbackInfo.playWhenReady,
playbackInfo.playbackSuppressionReason,
playbackInfo.playWhenReadyChangeReason);
}
private void updatePlayWhenReadyWithAudioFocus(
boolean playWhenReady,
@PlaybackSuppressionReason int playbackSuppressionReason,
@Player.PlayWhenReadyChangeReason int playWhenReadyChangeReason)
throws ExoPlaybackException {
@AudioFocusManager.PlayerCommand
int playerCommand =
audioFocusManager.updateAudioFocus(playWhenReady, playbackInfo.playbackState);
updatePlayWhenReadyWithAudioFocus(
playWhenReady, playerCommand, playbackSuppressionReason, playWhenReadyChangeReason);
}
private void updatePlayWhenReadyWithAudioFocus(
boolean playWhenReady,
@AudioFocusManager.PlayerCommand int playerCommand,
@PlaybackSuppressionReason int playbackSuppressionReason,
@Player.PlayWhenReadyChangeReason int playWhenReadyChangeReason)
throws ExoPlaybackException {
playWhenReady = playWhenReady && playerCommand != AudioFocusManager.PLAYER_COMMAND_DO_NOT_PLAY;
playWhenReadyChangeReason =
updatePlayWhenReadyChangeReason(playerCommand, playWhenReadyChangeReason);
playbackSuppressionReason =
updatePlaybackSuppressionReason(playerCommand, playbackSuppressionReason);
if (playbackInfo.playWhenReady == playWhenReady
&& playbackInfo.playbackSuppressionReason == playbackSuppressionReason
&& playbackInfo.playWhenReadyChangeReason == playWhenReadyChangeReason) {
return;
}
playbackInfo =
playbackInfo.copyWithPlayWhenReady(
playWhenReady, playWhenReadyChangeReason, playbackSuppressionReason);
playbackInfo.copyWithPlayWhenReady(playWhenReady, reason, playbackSuppressionReason);
updateRebufferingState(/* isRebuffering= */ false, /* resetLastRebufferRealtimeMs= */ false);
notifyTrackSelectionPlayWhenReadyChanged(playWhenReady);
if (!shouldPlayWhenReady()) {
@ -1751,7 +1662,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
/* resetError= */ false);
playbackInfoUpdate.incrementPendingOperationAcks(acknowledgeStop ? 1 : 0);
loadControl.onStopped(playerId);
audioFocusManager.updateAudioFocus(playbackInfo.playWhenReady, Player.STATE_IDLE);
setState(Player.STATE_IDLE);
}
@ -1764,7 +1674,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
/* resetError= */ false);
releaseRenderers();
loadControl.onReleased(playerId);
audioFocusManager.release();
trackSelector.release();
setState(Player.STATE_IDLE);
} finally {
@ -2259,8 +2168,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
mediaClock.getPlaybackParameters().speed,
playbackInfo.playWhenReady,
isRebuffering,
targetLiveOffsetUs,
lastRebufferRealtimeMs));
targetLiveOffsetUs));
}
private boolean isTimelineReady() {
@ -2419,7 +2327,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
int oldWindowIndex = oldTimeline.getPeriodByUid(oldPeriodId.periodUid, period).windowIndex;
oldWindowUid = oldTimeline.getWindow(oldWindowIndex, window).uid;
}
if (!Objects.equals(oldWindowUid, windowUid) || forceSetTargetOffsetOverride) {
if (!Util.areEqual(oldWindowUid, windowUid) || forceSetTargetOffsetOverride) {
// Reset overridden target live offset to media values if window changes or if seekTo
// default live position.
livePlaybackSpeedControl.setTargetLiveOffsetOverrideUs(C.TIME_UNSET);
@ -2782,10 +2690,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
private void maybeUpdateOffloadScheduling() {
// If playing period is audio-only with offload mode preference to enable, then offload
// scheduling should be enabled.
if (queue.getPlayingPeriod() != queue.getReadingPeriod()) {
// Do not enable offload scheduling when starting to process the next media item.
return;
}
@Nullable MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod();
if (playingPeriodHolder != null) {
TrackSelectorResult trackSelectorResult = playingPeriodHolder.getTrackSelectorResult();
@ -2984,8 +2888,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
mediaClock.getPlaybackParameters().speed,
playbackInfo.playWhenReady,
isRebuffering,
targetLiveOffsetUs,
lastRebufferRealtimeMs);
targetLiveOffsetUs);
boolean shouldContinueLoading = loadControl.shouldContinueLoading(loadParameters);
MediaPeriodHolder playingPeriodHolder = queue.getPlayingPeriod();
if (!shouldContinueLoading
@ -3243,8 +3146,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
mediaClock.getPlaybackParameters().speed,
playbackInfo.playWhenReady,
isRebuffering,
targetLiveOffsetUs,
lastRebufferRealtimeMs),
targetLiveOffsetUs),
trackGroups,
trackSelectorResult.selections);
}
@ -3752,31 +3654,6 @@ import java.util.concurrent.atomic.AtomicBoolean;
: newTimeline.getPeriod(newPeriodIndex, period).windowIndex;
}
private static @Player.PlayWhenReadyChangeReason int updatePlayWhenReadyChangeReason(
@AudioFocusManager.PlayerCommand int playerCommand,
@Player.PlayWhenReadyChangeReason int playWhenReadyChangeReason) {
if (playerCommand == AudioFocusManager.PLAYER_COMMAND_DO_NOT_PLAY) {
return Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS;
}
if (playWhenReadyChangeReason == Player.PLAY_WHEN_READY_CHANGE_REASON_AUDIO_FOCUS_LOSS) {
return Player.PLAY_WHEN_READY_CHANGE_REASON_USER_REQUEST;
}
return playWhenReadyChangeReason;
}
private static @Player.PlaybackSuppressionReason int updatePlaybackSuppressionReason(
@AudioFocusManager.PlayerCommand int playerCommand,
@Player.PlaybackSuppressionReason int playbackSuppressionReason) {
if (playerCommand == AudioFocusManager.PLAYER_COMMAND_WAIT_FOR_CALLBACK) {
return Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS;
}
if (playbackSuppressionReason
== Player.PLAYBACK_SUPPRESSION_REASON_TRANSIENT_AUDIO_FOCUS_LOSS) {
return Player.PLAYBACK_SUPPRESSION_REASON_NONE;
}
return playbackSuppressionReason;
}
private static final class SeekPosition {
public final Timeline timeline;

View File

@ -15,7 +15,6 @@
*/
package androidx.media3.exoplayer;
import android.os.SystemClock;
import androidx.media3.common.C;
import androidx.media3.common.Player;
import androidx.media3.common.Timeline;
@ -81,18 +80,6 @@ public interface LoadControl {
*/
public final long targetLiveOffsetUs;
/**
* Sets the time at which the last rebuffering occurred, in milliseconds since boot including
* time spent in sleep.
*
* <p>The time base used is the same as that measured by {@link SystemClock#elapsedRealtime}.
*
* <p><b>Note:</b> If rebuffer events are not known when the load is started or continued, or if
* no rebuffering has occurred, or if there have been any user interactions such as seeking or
* stopping the player, the value will be set to {@link C#TIME_UNSET}.
*/
public final long lastRebufferRealtimeMs;
/**
* Creates parameters for {@link LoadControl} methods.
*
@ -105,7 +92,6 @@ public interface LoadControl {
* @param playWhenReady See {@link #playWhenReady}.
* @param rebuffering See {@link #rebuffering}.
* @param targetLiveOffsetUs See {@link #targetLiveOffsetUs}.
* @param lastRebufferRealtimeMs see {@link #lastRebufferRealtimeMs}
*/
public Parameters(
PlayerId playerId,
@ -116,8 +102,7 @@ public interface LoadControl {
float playbackSpeed,
boolean playWhenReady,
boolean rebuffering,
long targetLiveOffsetUs,
long lastRebufferRealtimeMs) {
long targetLiveOffsetUs) {
this.playerId = playerId;
this.timeline = timeline;
this.mediaPeriodId = mediaPeriodId;
@ -127,7 +112,6 @@ public interface LoadControl {
this.playWhenReady = playWhenReady;
this.rebuffering = rebuffering;
this.targetLiveOffsetUs = targetLiveOffsetUs;
this.lastRebufferRealtimeMs = lastRebufferRealtimeMs;
}
}

View File

@ -21,8 +21,8 @@ import android.os.SystemClock;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.util.UnstableApi;
import com.google.common.base.Objects;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.Objects;
/** Information about the player state when loading is started or continued. */
@UnstableApi
@ -152,6 +152,6 @@ public final class LoadingInfo {
@Override
public int hashCode() {
return Objects.hash(playbackPositionUs, playbackSpeed, lastRebufferRealtimeMs);
return Objects.hashCode(playbackPositionUs, playbackSpeed, lastRebufferRealtimeMs);
}
}

View File

@ -1002,6 +1002,7 @@ public final class MediaExtractorCompat {
FormatHolder scratchFormatHolder, DecoderInputBuffer scratchNoDataDecoderInputBuffer) {
Format format = getFormat(scratchFormatHolder, scratchNoDataDecoderInputBuffer);
MediaFormat mediaFormatResult = MediaFormatUtil.createMediaFormatFromFormat(format);
mediaFormatResult.setInteger(MediaFormat.KEY_TRACK_ID, getIdOfBackingTrack());
if (compatibilityTrackMimeType != null) {
if (Util.SDK_INT >= 29) {
mediaFormatResult.removeKey(MediaFormat.KEY_CODECS_STRING);

View File

@ -18,9 +18,9 @@ package androidx.media3.exoplayer;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.source.MediaPeriod;
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
import java.util.Objects;
/** Stores the information required to load and play a {@link MediaPeriod}. */
/* package */ final class MediaPeriodInfo {
@ -170,7 +170,7 @@ import java.util.Objects;
&& isLastInTimelinePeriod == that.isLastInTimelinePeriod
&& isLastInTimelineWindow == that.isLastInTimelineWindow
&& isFinal == that.isFinal
&& Objects.equals(id, that.id);
&& Util.areEqual(id, that.id);
}
@Override

View File

@ -63,13 +63,13 @@ import androidx.media3.exoplayer.source.MediaLoadData;
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
import androidx.media3.exoplayer.trackselection.TrackSelection;
import androidx.media3.exoplayer.video.VideoDecoderOutputBufferRenderer;
import com.google.common.base.Objects;
import java.io.IOException;
import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.util.List;
import java.util.Objects;
/**
* A listener for analytics events.
@ -572,15 +572,15 @@ public interface AnalyticsListener {
&& currentWindowIndex == eventTime.currentWindowIndex
&& currentPlaybackPositionMs == eventTime.currentPlaybackPositionMs
&& totalBufferedDurationMs == eventTime.totalBufferedDurationMs
&& Objects.equals(timeline, eventTime.timeline)
&& Objects.equals(mediaPeriodId, eventTime.mediaPeriodId)
&& Objects.equals(currentTimeline, eventTime.currentTimeline)
&& Objects.equals(currentMediaPeriodId, eventTime.currentMediaPeriodId);
&& Objects.equal(timeline, eventTime.timeline)
&& Objects.equal(mediaPeriodId, eventTime.mediaPeriodId)
&& Objects.equal(currentTimeline, eventTime.currentTimeline)
&& Objects.equal(currentMediaPeriodId, eventTime.currentMediaPeriodId);
}
@Override
public int hashCode() {
return Objects.hash(
return Objects.hashCode(
realtimeMs,
timeline,
windowIndex,

View File

@ -57,12 +57,12 @@ import androidx.media3.exoplayer.drm.DrmSession;
import androidx.media3.exoplayer.source.LoadEventInfo;
import androidx.media3.exoplayer.source.MediaLoadData;
import androidx.media3.exoplayer.source.MediaSource.MediaPeriodId;
import com.google.common.base.Objects;
import com.google.common.collect.ImmutableList;
import com.google.common.collect.ImmutableMap;
import com.google.common.collect.Iterables;
import java.io.IOException;
import java.util.List;
import java.util.Objects;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@ -1123,11 +1123,11 @@ public class DefaultAnalyticsCollector implements AnalyticsCollector {
ImmutableMap.Builder<MediaPeriodId, Timeline> builder = ImmutableMap.builder();
if (mediaPeriodQueue.isEmpty()) {
addTimelineForMediaPeriodId(builder, playingMediaPeriod, preferredTimeline);
if (!Objects.equals(readingMediaPeriod, playingMediaPeriod)) {
if (!Objects.equal(readingMediaPeriod, playingMediaPeriod)) {
addTimelineForMediaPeriodId(builder, readingMediaPeriod, preferredTimeline);
}
if (!Objects.equals(currentPlayerMediaPeriod, playingMediaPeriod)
&& !Objects.equals(currentPlayerMediaPeriod, readingMediaPeriod)) {
if (!Objects.equal(currentPlayerMediaPeriod, playingMediaPeriod)
&& !Objects.equal(currentPlayerMediaPeriod, readingMediaPeriod)) {
addTimelineForMediaPeriodId(builder, currentPlayerMediaPeriod, preferredTimeline);
}
} else {

View File

@ -76,7 +76,6 @@ import java.io.IOException;
import java.net.SocketTimeoutException;
import java.net.UnknownHostException;
import java.util.HashMap;
import java.util.Objects;
import java.util.UUID;
import java.util.concurrent.Executor;
import org.checkerframework.checker.nullness.qual.EnsuresNonNullIf;
@ -492,7 +491,7 @@ public final class MediaMetricsListener
private void maybeUpdateVideoFormat(
long realtimeMs, @Nullable Format videoFormat, @C.SelectionReason int trackSelectionReason) {
if (Objects.equals(currentVideoFormat, videoFormat)) {
if (Util.areEqual(currentVideoFormat, videoFormat)) {
return;
}
if (currentVideoFormat == null && trackSelectionReason == C.SELECTION_REASON_UNKNOWN) {
@ -505,7 +504,7 @@ public final class MediaMetricsListener
private void maybeUpdateAudioFormat(
long realtimeMs, @Nullable Format audioFormat, @C.SelectionReason int trackSelectionReason) {
if (Objects.equals(currentAudioFormat, audioFormat)) {
if (Util.areEqual(currentAudioFormat, audioFormat)) {
return;
}
if (currentAudioFormat == null && trackSelectionReason == C.SELECTION_REASON_UNKNOWN) {
@ -518,7 +517,7 @@ public final class MediaMetricsListener
private void maybeUpdateTextFormat(
long realtimeMs, @Nullable Format textFormat, @C.SelectionReason int trackSelectionReason) {
if (Objects.equals(currentTextFormat, textFormat)) {
if (Util.areEqual(currentTextFormat, textFormat)) {
return;
}
if (currentTextFormat == null && trackSelectionReason == C.SELECTION_REASON_UNKNOWN) {

View File

@ -46,7 +46,6 @@ import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Map;
import java.util.Objects;
/**
* {@link AnalyticsListener} to gather {@link PlaybackStats} from the player.
@ -779,7 +778,7 @@ public final class PlaybackStatsListener
}
private void maybeUpdateVideoFormat(EventTime eventTime, @Nullable Format newFormat) {
if (Objects.equals(currentVideoFormat, newFormat)) {
if (Util.areEqual(currentVideoFormat, newFormat)) {
return;
}
maybeRecordVideoFormatTime(eventTime.realtimeMs);
@ -798,7 +797,7 @@ public final class PlaybackStatsListener
}
private void maybeUpdateAudioFormat(EventTime eventTime, @Nullable Format newFormat) {
if (Objects.equals(currentAudioFormat, newFormat)) {
if (Util.areEqual(currentAudioFormat, newFormat)) {
return;
}
maybeRecordAudioFormatTime(eventTime.realtimeMs);

View File

@ -40,7 +40,6 @@ import androidx.media3.common.AudioAttributes;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.audio.AudioManagerCompat;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import com.google.common.collect.ImmutableList;
@ -51,7 +50,6 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
/** Represents the set of audio formats that a device is capable of playing. */
@ -143,7 +141,8 @@ public final class AudioCapabilities {
@Nullable Intent intent,
AudioAttributes audioAttributes,
@Nullable AudioDeviceInfoApi23 routedDevice) {
AudioManager audioManager = AudioManagerCompat.getAudioManager(context);
AudioManager audioManager =
(AudioManager) checkNotNull(context.getSystemService(Context.AUDIO_SERVICE));
AudioDeviceInfoApi23 currentDevice =
routedDevice != null
? routedDevice
@ -518,7 +517,7 @@ public final class AudioCapabilities {
AudioProfile audioProfile = (AudioProfile) other;
return encoding == audioProfile.encoding
&& maxChannelCount == audioProfile.maxChannelCount
&& Objects.equals(channelMasks, audioProfile.channelMasks);
&& Util.areEqual(channelMasks, audioProfile.channelMasks);
}
@Override

View File

@ -31,10 +31,8 @@ import android.os.Handler;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.media3.common.AudioAttributes;
import androidx.media3.common.audio.AudioManagerCompat;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import java.util.Objects;
/**
* Receives broadcast events indicating changes to the device's audio capabilities, notifying a
@ -134,7 +132,7 @@ public final class AudioCapabilitiesReceiver {
*/
@RequiresApi(23)
public void setRoutedDevice(@Nullable AudioDeviceInfo routedDevice) {
if (Objects.equals(
if (Util.areEqual(
routedDevice, this.routedDevice == null ? null : this.routedDevice.audioDeviceInfo)) {
return;
}
@ -262,12 +260,14 @@ public final class AudioCapabilitiesReceiver {
public static void registerAudioDeviceCallback(
Context context, AudioDeviceCallback callback, Handler handler) {
AudioManagerCompat.getAudioManager(context).registerAudioDeviceCallback(callback, handler);
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
checkNotNull(audioManager).registerAudioDeviceCallback(callback, handler);
}
public static void unregisterAudioDeviceCallback(
Context context, AudioDeviceCallback callback) {
AudioManagerCompat.getAudioManager(context).unregisterAudioDeviceCallback(callback);
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
checkNotNull(audioManager).unregisterAudioDeviceCallback(callback);
}
private Api23() {}

View File

@ -591,15 +591,6 @@ public interface AudioSink {
*/
default void setOutputStreamOffsetUs(long outputStreamOffsetUs) {}
/**
* Returns the size of the underlying {@link AudioTrack} buffer in microseconds. If unsupported or
* the {@link AudioTrack} is not initialized then return {@link C#TIME_UNSET}.
*
* <p>If the {@link AudioTrack} is configured with a compressed encoding, then the returned
* duration is an estimated minimum based on the encoding's maximum encoded byte rate.
*/
long getAudioTrackBufferSizeUs();
/**
* Enables tunneling, if possible. The sink is reset if tunneling was previously disabled.
* Enabling tunneling is only possible if the sink is based on a platform {@link AudioTrack}, and

View File

@ -284,7 +284,7 @@ import java.lang.reflect.Method;
resetSyncParams();
}
public long getCurrentPositionUs() {
public long getCurrentPositionUs(boolean sourceEnded) {
AudioTrack audioTrack = checkNotNull(this.audioTrack);
if (audioTrack.getPlayState() == PLAYSTATE_PLAYING) {
maybeSampleSyncParams();
@ -307,11 +307,7 @@ import java.lang.reflect.Method;
} else {
if (playheadOffsetCount == 0) {
// The AudioTrack has started, but we don't have any samples to compute a smoothed position.
positionUs =
stopTimestampUs != C.TIME_UNSET
? sampleCountToDurationUs(
getSimulatedPlaybackHeadPositionAfterStop(), outputSampleRate)
: getPlaybackHeadPositionUs();
positionUs = getPlaybackHeadPositionUs();
} else {
// getPlaybackHeadPositionUs() only has a granularity of ~20 ms, so we base the position off
// the system clock (and a smoothed offset between it and the playhead position) so as to
@ -320,11 +316,8 @@ import java.lang.reflect.Method;
Util.getMediaDurationForPlayoutDuration(
systemTimeUs + smoothedPlayheadOffsetUs, audioTrackPlaybackSpeed);
}
positionUs = max(0, positionUs - latencyUs);
if (stopTimestampUs != C.TIME_UNSET) {
positionUs =
min(sampleCountToDurationUs(endPlaybackHeadPosition, outputSampleRate), positionUs);
if (!sourceEnded) {
positionUs = max(0, positionUs - latencyUs);
}
}
@ -457,8 +450,13 @@ import java.lang.reflect.Method;
* @return Whether the audio track has any pending data to play out.
*/
public boolean hasPendingData(long writtenFrames) {
return writtenFrames > durationUsToSampleCount(getCurrentPositionUs(), outputSampleRate)
|| forceHasPendingData();
if (stopTimestampUs != C.TIME_UNSET) {
return writtenFrames > getPlaybackHeadPosition() || forceHasPendingData();
} else {
long currentPositionUs = getCurrentPositionUs(/* sourceEnded= */ false);
return writtenFrames > durationUsToSampleCount(currentPositionUs, outputSampleRate)
|| forceHasPendingData();
}
}
/**
@ -635,11 +633,19 @@ import java.lang.reflect.Method;
* @return The playback head position, in frames.
*/
private long getPlaybackHeadPosition() {
if (stopTimestampUs != C.TIME_UNSET) {
long simulatedPlaybackHeadPositionAfterStop = getSimulatedPlaybackHeadPositionAfterStop();
return min(endPlaybackHeadPosition, simulatedPlaybackHeadPositionAfterStop);
}
long currentTimeMs = clock.elapsedRealtime();
if (stopTimestampUs != C.TIME_UNSET) {
if (checkNotNull(this.audioTrack).getPlayState() == AudioTrack.PLAYSTATE_PAUSED) {
// If AudioTrack is paused while stopping, then return cached playback head position.
return stopPlaybackHeadPosition;
}
// Simulate the playback head position up to the total number of frames submitted.
long elapsedTimeSinceStopUs = msToUs(currentTimeMs) - stopTimestampUs;
long mediaTimeSinceStopUs =
Util.getMediaDurationForPlayoutDuration(elapsedTimeSinceStopUs, audioTrackPlaybackSpeed);
long framesSinceStop = durationUsToSampleCount(mediaTimeSinceStopUs, outputSampleRate);
return min(endPlaybackHeadPosition, stopPlaybackHeadPosition + framesSinceStop);
}
if (currentTimeMs - lastRawPlaybackHeadPositionSampleTimeMs
>= RAW_PLAYBACK_HEAD_POSITION_UPDATE_INTERVAL_MS) {
updateRawPlaybackHeadPosition(currentTimeMs);
@ -648,19 +654,6 @@ import java.lang.reflect.Method;
return rawPlaybackHeadPosition + sumRawPlaybackHeadPosition + (rawPlaybackHeadWrapCount << 32);
}
private long getSimulatedPlaybackHeadPositionAfterStop() {
if (checkNotNull(this.audioTrack).getPlayState() == AudioTrack.PLAYSTATE_PAUSED) {
// If AudioTrack is paused while stopping, then return cached playback head position.
return stopPlaybackHeadPosition;
}
// Simulate the playback head position up to the total number of frames submitted.
long elapsedTimeSinceStopUs = msToUs(clock.elapsedRealtime()) - stopTimestampUs;
long mediaTimeSinceStopUs =
Util.getMediaDurationForPlayoutDuration(elapsedTimeSinceStopUs, audioTrackPlaybackSpeed);
long framesSinceStop = durationUsToSampleCount(mediaTimeSinceStopUs, outputSampleRate);
return stopPlaybackHeadPosition + framesSinceStop;
}
private void updateRawPlaybackHeadPosition(long currentTimeMs) {
AudioTrack audioTrack = checkNotNull(this.audioTrack);
int state = audioTrack.getPlayState();

View File

@ -15,8 +15,6 @@
*/
package androidx.media3.exoplayer.audio;
import static androidx.media3.common.util.Util.getByteDepth;
import androidx.annotation.Nullable;
import androidx.media3.common.C;
import androidx.media3.common.Format;
@ -24,7 +22,6 @@ import androidx.media3.common.audio.AudioProcessor;
import androidx.media3.common.audio.BaseAudioProcessor;
import androidx.media3.common.util.Assertions;
import java.nio.ByteBuffer;
import java.util.Arrays;
/**
* An {@link AudioProcessor} that applies a mapping from input channels onto specified output
@ -56,8 +53,7 @@ import java.util.Arrays;
return AudioFormat.NOT_SET;
}
if (inputAudioFormat.encoding != C.ENCODING_PCM_16BIT
&& inputAudioFormat.encoding != C.ENCODING_PCM_FLOAT) {
if (inputAudioFormat.encoding != C.ENCODING_PCM_16BIT) {
throw new UnhandledAudioFormatException(inputAudioFormat);
}
@ -65,17 +61,12 @@ import java.util.Arrays;
for (int i = 0; i < outputChannels.length; i++) {
int channelIndex = outputChannels[i];
if (channelIndex >= inputAudioFormat.channelCount) {
throw new UnhandledAudioFormatException(
"Channel map ("
+ Arrays.toString(outputChannels)
+ ") trying to access non-existent input channel.",
inputAudioFormat);
throw new UnhandledAudioFormatException(inputAudioFormat);
}
active |= (channelIndex != i);
}
return active
? new AudioFormat(
inputAudioFormat.sampleRate, outputChannels.length, inputAudioFormat.encoding)
? new AudioFormat(inputAudioFormat.sampleRate, outputChannels.length, C.ENCODING_PCM_16BIT)
: AudioFormat.NOT_SET;
}
@ -89,17 +80,7 @@ import java.util.Arrays;
ByteBuffer buffer = replaceOutputBuffer(outputSize);
while (position < limit) {
for (int channelIndex : outputChannels) {
int inputIndex = position + getByteDepth(inputAudioFormat.encoding) * channelIndex;
switch (inputAudioFormat.encoding) {
case C.ENCODING_PCM_16BIT:
buffer.putShort(inputBuffer.getShort(inputIndex));
break;
case C.ENCODING_PCM_FLOAT:
buffer.putFloat(inputBuffer.getFloat(inputIndex));
break;
default:
throw new IllegalStateException("Unexpected encoding: " + inputAudioFormat.encoding);
}
buffer.putShort(inputBuffer.getShort(position + 2 * channelIndex));
}
position += inputAudioFormat.bytesPerFrame;
}

View File

@ -22,7 +22,6 @@ import static androidx.media3.exoplayer.DecoderReuseEvaluation.REUSE_RESULT_NO;
import static androidx.media3.exoplayer.source.SampleStream.FLAG_REQUIRE_FORMAT;
import static com.google.common.base.MoreObjects.firstNonNull;
import static java.lang.Math.max;
import static java.lang.Math.min;
import static java.lang.annotation.ElementType.TYPE_USE;
import android.media.AudioDeviceInfo;
@ -170,7 +169,6 @@ public abstract class DecoderAudioRenderer<
private long largestQueuedPresentationTimeUs;
private long lastBufferInStreamPresentationTimeUs;
private long nextBufferToWritePresentationTimeUs;
private boolean isRendereringToEndOfStream;
public DecoderAudioRenderer() {
this(/* eventHandler= */ null, /* eventListener= */ null);
@ -248,28 +246,16 @@ public abstract class DecoderAudioRenderer<
if (nextBufferToWritePresentationTimeUs == C.TIME_UNSET) {
return super.getDurationToProgressUs(positionUs, elapsedRealtimeUs);
}
long audioTrackBufferDurationUs = audioSink.getAudioTrackBufferSizeUs();
// Return default if getAudioTrackBufferSizeUs is unsupported and not in the midst of rendering
// to end of stream.
if (!isRendereringToEndOfStream && audioTrackBufferDurationUs == C.TIME_UNSET) {
return super.getDurationToProgressUs(positionUs, elapsedRealtimeUs);
}
// Compare written, yet-to-play content duration against the audio track buffer size.
long writtenDurationUs = (nextBufferToWritePresentationTimeUs - positionUs);
long bufferedDurationUs =
audioTrackBufferDurationUs != C.TIME_UNSET
? min(audioTrackBufferDurationUs, writtenDurationUs)
: writtenDurationUs;
bufferedDurationUs =
long durationUs =
(long)
(bufferedDurationUs
((nextBufferToWritePresentationTimeUs - positionUs)
/ (getPlaybackParameters() != null ? getPlaybackParameters().speed : 1.0f)
/ 2);
if (isStarted) {
// Account for the elapsed time since the start of this iteration of the rendering loop.
bufferedDurationUs -= Util.msToUs(getClock().elapsedRealtime()) - elapsedRealtimeUs;
durationUs -= Util.msToUs(getClock().elapsedRealtime()) - elapsedRealtimeUs;
}
return max(DEFAULT_DURATION_TO_PROGRESS_US, bufferedDurationUs);
return max(DEFAULT_DURATION_TO_PROGRESS_US, durationUs);
}
@Override
@ -318,7 +304,6 @@ public abstract class DecoderAudioRenderer<
try {
audioSink.playToEndOfStream();
nextBufferToWritePresentationTimeUs = lastBufferInStreamPresentationTimeUs;
isRendereringToEndOfStream = true;
} catch (AudioSink.WriteException e) {
throw createRendererException(
e, e.format, e.isRecoverable, PlaybackException.ERROR_CODE_AUDIO_TRACK_WRITE_FAILED);
@ -600,7 +585,6 @@ public abstract class DecoderAudioRenderer<
outputStreamEnded = true;
audioSink.playToEndOfStream();
nextBufferToWritePresentationTimeUs = lastBufferInStreamPresentationTimeUs;
isRendereringToEndOfStream = true;
}
private void flushDecoder() throws ExoPlaybackException {
@ -676,7 +660,6 @@ public abstract class DecoderAudioRenderer<
currentPositionUs = positionUs;
nextBufferToWritePresentationTimeUs = C.TIME_UNSET;
isRendereringToEndOfStream = false;
hasPendingReportedSkippedSilence = false;
allowPositionDiscontinuity = true;
inputStreamEnded = false;
@ -706,7 +689,6 @@ public abstract class DecoderAudioRenderer<
setOutputStreamOffsetUs(C.TIME_UNSET);
hasPendingReportedSkippedSilence = false;
nextBufferToWritePresentationTimeUs = C.TIME_UNSET;
isRendereringToEndOfStream = false;
try {
setSourceDrmSession(null);
releaseDecoder();

View File

@ -26,7 +26,6 @@ import androidx.media3.common.AudioAttributes;
import androidx.media3.common.C;
import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes;
import androidx.media3.common.audio.AudioManagerCompat;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
@ -117,13 +116,17 @@ public final class DefaultAudioOffloadSupportProvider
}
if (context != null) {
AudioManager audioManager = AudioManagerCompat.getAudioManager(context);
String offloadVariableRateSupportedKeyValue =
audioManager.getParameters(/* keys= */ OFFLOAD_VARIABLE_RATE_SUPPORTED_KEY);
isOffloadVariableRateSupported =
offloadVariableRateSupportedKeyValue != null
&& offloadVariableRateSupportedKeyValue.equals(
OFFLOAD_VARIABLE_RATE_SUPPORTED_KEY + "=1");
AudioManager audioManager = (AudioManager) context.getSystemService(Context.AUDIO_SERVICE);
if (audioManager != null) {
String offloadVariableRateSupportedKeyValue =
audioManager.getParameters(/* keys= */ OFFLOAD_VARIABLE_RATE_SUPPORTED_KEY);
isOffloadVariableRateSupported =
offloadVariableRateSupportedKeyValue != null
&& offloadVariableRateSupportedKeyValue.equals(
OFFLOAD_VARIABLE_RATE_SUPPORTED_KEY + "=1");
} else {
isOffloadVariableRateSupported = false;
}
} else {
isOffloadVariableRateSupported = false;
}

View File

@ -71,7 +71,6 @@ import java.lang.annotation.Documented;
import java.lang.annotation.Retention;
import java.lang.annotation.RetentionPolicy;
import java.lang.annotation.Target;
import java.math.RoundingMode;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.util.ArrayDeque;
@ -602,9 +601,7 @@ public final class DefaultAudioSink implements AudioSink {
toIntPcmAvailableAudioProcessors =
ImmutableList.of(
new ToInt16PcmAudioProcessor(), channelMappingAudioProcessor, trimmingAudioProcessor);
toFloatPcmAvailableAudioProcessors =
ImmutableList.of(
new ToFloatPcmAudioProcessor(), channelMappingAudioProcessor, trimmingAudioProcessor);
toFloatPcmAvailableAudioProcessors = ImmutableList.of(new ToFloatPcmAudioProcessor());
volume = 1f;
audioSessionId = C.AUDIO_SESSION_ID_UNSET;
auxEffectInfo = new AuxEffectInfo(AuxEffectInfo.NO_AUX_EFFECT_ID, 0f);
@ -677,7 +674,7 @@ public final class DefaultAudioSink implements AudioSink {
if (!isAudioTrackInitialized() || startMediaTimeUsNeedsInit) {
return CURRENT_POSITION_NOT_SET;
}
long positionUs = audioTrackPositionTracker.getCurrentPositionUs();
long positionUs = audioTrackPositionTracker.getCurrentPositionUs(sourceEnded);
positionUs = min(positionUs, configuration.framesToDurationUs(getWrittenFrames()));
return applySkipping(applyMediaPositionParameters(positionUs));
}
@ -1278,9 +1275,8 @@ public final class DefaultAudioSink implements AudioSink {
if (listener != null) {
listener.onAudioSinkError(e);
}
if (e.isRecoverable && context != null) {
if (e.isRecoverable) {
// Change to the audio capabilities supported by all the devices during the error recovery.
// Only do this if we have a context and the capabilities can automatically adjust back.
audioCapabilities = DEFAULT_AUDIO_CAPABILITIES;
throw e; // Do not delay the exception if it can be recovered at higher level.
}
@ -1455,23 +1451,6 @@ public final class DefaultAudioSink implements AudioSink {
}
}
@Override
public long getAudioTrackBufferSizeUs() {
if (!isAudioTrackInitialized()) {
return C.TIME_UNSET;
}
if (Util.SDK_INT >= 23) {
return Api23.getAudioTrackBufferSizeUs(audioTrack, configuration);
}
long byteRate =
configuration.outputMode == OUTPUT_MODE_PCM
? (long) configuration.outputSampleRate * configuration.outputPcmFrameSize
: DefaultAudioTrackBufferSizeProvider.getMaximumEncodedRateBytesPerSecond(
configuration.outputEncoding);
return Util.scaleLargeValue(
configuration.bufferSize, C.MICROS_PER_SECOND, byteRate, RoundingMode.DOWN);
}
@Override
public void enableTunnelingV21() {
Assertions.checkState(externalAudioSessionIdProvided);
@ -2036,7 +2015,7 @@ public final class DefaultAudioSink implements AudioSink {
}
@Nullable AudioDeviceInfo routedDevice = router.getRoutedDevice();
if (routedDevice != null) {
capabilitiesReceiver.setRoutedDevice(routedDevice);
capabilitiesReceiver.setRoutedDevice(router.getRoutedDevice());
}
}
}
@ -2383,18 +2362,6 @@ public final class DefaultAudioSink implements AudioSink {
audioTrack.setPreferredDevice(
audioDeviceInfo == null ? null : audioDeviceInfo.audioDeviceInfo);
}
public static long getAudioTrackBufferSizeUs(
AudioTrack audioTrack, Configuration configuration) {
return configuration.outputMode == OUTPUT_MODE_PCM
? configuration.framesToDurationUs(audioTrack.getBufferSizeInFrames())
: Util.scaleLargeValue(
audioTrack.getBufferSizeInFrames(),
C.MICROS_PER_SECOND,
DefaultAudioTrackBufferSizeProvider.getMaximumEncodedRateBytesPerSecond(
configuration.outputEncoding),
RoundingMode.DOWN);
}
}
@RequiresApi(31)

View File

@ -162,11 +162,6 @@ public class ForwardingAudioSink implements AudioSink {
sink.setOutputStreamOffsetUs(outputStreamOffsetUs);
}
@Override
public long getAudioTrackBufferSizeUs() {
return sink.getAudioTrackBufferSizeUs();
}
@Override
public void enableTunnelingV21() {
sink.enableTunnelingV21();

View File

@ -20,7 +20,6 @@ import static androidx.media3.exoplayer.DecoderReuseEvaluation.DISCARD_REASON_MA
import static androidx.media3.exoplayer.DecoderReuseEvaluation.REUSE_RESULT_NO;
import static com.google.common.base.MoreObjects.firstNonNull;
import static java.lang.Math.max;
import static java.lang.Math.min;
import android.annotation.SuppressLint;
import android.content.Context;
@ -126,7 +125,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
private int rendererPriority;
private boolean isStarted;
private long nextBufferToWritePresentationTimeUs;
private boolean isRendereringToEndOfStream;
/**
* @param context A context.
@ -520,33 +518,20 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
@Override
protected long getDurationToProgressUs(
long positionUs, long elapsedRealtimeUs, boolean isOnBufferAvailableListenerRegistered) {
if (nextBufferToWritePresentationTimeUs == C.TIME_UNSET) {
return super.getDurationToProgressUs(
positionUs, elapsedRealtimeUs, isOnBufferAvailableListenerRegistered);
if (nextBufferToWritePresentationTimeUs != C.TIME_UNSET) {
long durationUs =
(long)
((nextBufferToWritePresentationTimeUs - positionUs)
/ (getPlaybackParameters() != null ? getPlaybackParameters().speed : 1.0f)
/ 2);
if (isStarted) {
// Account for the elapsed time since the start of this iteration of the rendering loop.
durationUs -= Util.msToUs(getClock().elapsedRealtime()) - elapsedRealtimeUs;
}
return max(DEFAULT_DURATION_TO_PROGRESS_US, durationUs);
}
long audioTrackBufferDurationUs = audioSink.getAudioTrackBufferSizeUs();
// Return default if getAudioTrackBufferSizeUs is unsupported and not in the midst of rendering
// to end of stream.
if (!isRendereringToEndOfStream && audioTrackBufferDurationUs == C.TIME_UNSET) {
return super.getDurationToProgressUs(
positionUs, elapsedRealtimeUs, isOnBufferAvailableListenerRegistered);
}
// Compare written, yet-to-play content duration against the audio track buffer size.
long writtenDurationUs = (nextBufferToWritePresentationTimeUs - positionUs);
long bufferedDurationUs =
audioTrackBufferDurationUs != C.TIME_UNSET
? min(audioTrackBufferDurationUs, writtenDurationUs)
: writtenDurationUs;
bufferedDurationUs =
(long)
(bufferedDurationUs
/ (getPlaybackParameters() != null ? getPlaybackParameters().speed : 1.0f)
/ 2);
if (isStarted) {
// Account for the elapsed time since the start of this iteration of the rendering loop.
bufferedDurationUs -= Util.msToUs(getClock().elapsedRealtime()) - elapsedRealtimeUs;
}
return max(DEFAULT_DURATION_TO_PROGRESS_US, bufferedDurationUs);
return super.getDurationToProgressUs(
positionUs, elapsedRealtimeUs, isOnBufferAvailableListenerRegistered);
}
@Override
@ -694,7 +679,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
currentPositionUs = positionUs;
nextBufferToWritePresentationTimeUs = C.TIME_UNSET;
isRendereringToEndOfStream = false;
hasPendingReportedSkippedSilence = false;
allowPositionDiscontinuity = true;
}
@ -719,7 +703,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
audioSinkNeedsReset = true;
inputFormat = null;
nextBufferToWritePresentationTimeUs = C.TIME_UNSET;
isRendereringToEndOfStream = false;
try {
audioSink.flush();
} finally {
@ -735,7 +718,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
protected void onReset() {
hasPendingReportedSkippedSilence = false;
nextBufferToWritePresentationTimeUs = C.TIME_UNSET;
isRendereringToEndOfStream = false;
try {
super.onReset();
} finally {
@ -875,7 +857,6 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
if (getLastBufferInStreamPresentationTimeUs() != C.TIME_UNSET) {
nextBufferToWritePresentationTimeUs = getLastBufferInStreamPresentationTimeUs();
}
isRendereringToEndOfStream = true;
} catch (AudioSink.WriteException e) {
throw createRendererException(
e,

View File

@ -26,6 +26,8 @@ import java.nio.ByteBuffer;
/** Audio processor for trimming samples from the start/end of data. */
/* package */ final class TrimmingAudioProcessor extends BaseAudioProcessor {
private static final @C.PcmEncoding int OUTPUT_ENCODING = C.ENCODING_PCM_16BIT;
private int trimStartFrames;
private int trimEndFrames;
private boolean reconfigurationPending;
@ -78,8 +80,7 @@ import java.nio.ByteBuffer;
@Override
public AudioFormat onConfigure(AudioFormat inputAudioFormat)
throws UnhandledAudioFormatException {
if (inputAudioFormat.encoding != C.ENCODING_PCM_16BIT
&& inputAudioFormat.encoding != C.ENCODING_PCM_FLOAT) {
if (inputAudioFormat.encoding != OUTPUT_ENCODING) {
throw new UnhandledAudioFormatException(inputAudioFormat);
}
reconfigurationPending = true;

View File

@ -56,7 +56,6 @@ import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Objects;
import java.util.Set;
import java.util.UUID;
import org.checkerframework.checker.nullness.qual.EnsuresNonNull;
@ -500,7 +499,7 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
// Only use an existing session if it has matching init data.
session = null;
for (DefaultDrmSession existingSession : sessions) {
if (Objects.equals(existingSession.schemeDatas, schemeDatas)) {
if (Util.areEqual(existingSession.schemeDatas, schemeDatas)) {
session = existingSession;
break;
}

View File

@ -22,12 +22,12 @@ import androidx.annotation.GuardedBy;
import androidx.annotation.Nullable;
import androidx.media3.common.MediaItem;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.datasource.DataSource;
import androidx.media3.datasource.DefaultHttpDataSource;
import androidx.media3.exoplayer.upstream.LoadErrorHandlingPolicy;
import com.google.common.primitives.Ints;
import java.util.Map;
import java.util.Objects;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
/** Default implementation of {@link DrmSessionManagerProvider}. */
@ -93,7 +93,7 @@ public final class DefaultDrmSessionManagerProvider implements DrmSessionManager
}
synchronized (lock) {
if (!Objects.equals(drmConfiguration, this.drmConfiguration)) {
if (!Util.areEqual(drmConfiguration, this.drmConfiguration)) {
this.drmConfiguration = drmConfiguration;
this.manager = createManager(drmConfiguration);
}

View File

@ -42,7 +42,6 @@ import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.analytics.PlayerId;
import androidx.media3.extractor.mp4.PsshAtomUtil;
import androidx.media3.extractor.mp4.PsshAtomUtil.PsshAtom;
import java.nio.ByteBuffer;
import java.nio.ByteOrder;
import java.nio.charset.StandardCharsets;
@ -299,14 +298,11 @@ public final class FrameworkMediaDrm implements ExoMediaDrm {
} else {
MediaCrypto mediaCrypto = null;
try {
mediaCrypto = new MediaCrypto(adjustUuid(uuid), sessionId);
mediaCrypto = new MediaCrypto(uuid, sessionId);
result = mediaCrypto.requiresSecureDecoderComponent(mimeType);
} catch (MediaCryptoException e) {
// This shouldn't happen, but if it does then assume that most DRM schemes need a secure
// decoder but ClearKey doesn't (because ClearKey never uses secure decryption). Requesting
// a secure decoder when it's not supported leads to playback failures:
// https://github.com/androidx/media/issues/1732
result = !uuid.equals(C.CLEARKEY_UUID);
// This shouldn't happen, but if it does then assume that a secure decoder may be required.
result = true;
} finally {
if (mediaCrypto != null) {
mediaCrypto.release();
@ -437,8 +433,8 @@ public final class FrameworkMediaDrm implements ExoMediaDrm {
for (int i = 0; i < schemeDatas.size(); i++) {
SchemeData schemeData = schemeDatas.get(i);
byte[] schemeDataData = Assertions.checkNotNull(schemeData.data);
if (Objects.equals(schemeData.mimeType, firstSchemeData.mimeType)
&& Objects.equals(schemeData.licenseServerUrl, firstSchemeData.licenseServerUrl)
if (Util.areEqual(schemeData.mimeType, firstSchemeData.mimeType)
&& Util.areEqual(schemeData.licenseServerUrl, firstSchemeData.licenseServerUrl)
&& PsshAtomUtil.isPsshAtom(schemeDataData)) {
concatenatedDataLength += schemeDataData.length;
} else {
@ -478,7 +474,8 @@ public final class FrameworkMediaDrm implements ExoMediaDrm {
}
private static UUID adjustUuid(UUID uuid) {
return cdmRequiresCommonPsshUuid(uuid) ? C.COMMON_PSSH_UUID : uuid;
// ClearKey had to be accessed using the Common PSSH UUID prior to API level 27.
return Util.SDK_INT < 27 && C.CLEARKEY_UUID.equals(uuid) ? C.COMMON_PSSH_UUID : uuid;
}
private static byte[] adjustRequestInitData(UUID uuid, byte[] initData) {
@ -493,13 +490,6 @@ public final class FrameworkMediaDrm implements ExoMediaDrm {
PsshAtomUtil.buildPsshAtom(
C.PLAYREADY_UUID, addLaUrlAttributeIfMissing(schemeSpecificData));
}
if (cdmRequiresCommonPsshUuid(uuid)) {
PsshAtom psshAtom = PsshAtomUtil.parsePsshAtom(initData);
if (psshAtom != null) {
initData =
PsshAtomUtil.buildPsshAtom(C.COMMON_PSSH_UUID, psshAtom.keyIds, psshAtom.schemeData);
}
}
// Prior to API level 21, the Widevine CDM required scheme specific data to be extracted from
// the PSSH atom. We also extract the data on API levels 21 and 22 because these API levels
@ -541,11 +531,6 @@ public final class FrameworkMediaDrm implements ExoMediaDrm {
return requestData;
}
private static boolean cdmRequiresCommonPsshUuid(UUID uuid) {
// ClearKey had to be accessed using the Common PSSH UUID prior to API level 27.
return Util.SDK_INT < 27 && Objects.equals(uuid, C.CLEARKEY_UUID);
}
private static void forceWidevineL3(MediaDrm mediaDrm) {
mediaDrm.setPropertyString("securityLevel", "L3");
}

View File

@ -42,7 +42,6 @@ import android.util.Pair;
import androidx.annotation.Nullable;
import androidx.annotation.RequiresApi;
import androidx.annotation.VisibleForTesting;
import androidx.media3.common.C;
import androidx.media3.common.ColorInfo;
import androidx.media3.common.Format;
import androidx.media3.common.MimeTypes;
@ -52,7 +51,6 @@ import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.DecoderReuseEvaluation;
import androidx.media3.exoplayer.DecoderReuseEvaluation.DecoderDiscardReasons;
import java.util.Objects;
/** Information about a {@link MediaCodec} for a given MIME type. */
@SuppressWarnings("InlinedApi")
@ -268,10 +266,6 @@ public final class MediaCodecInfo {
return false;
}
if (!isCompressedAudioBitDepthSupported(format)) {
return false;
}
if (isVideo) {
if (format.width <= 0 || format.height <= 0) {
return true;
@ -293,8 +287,7 @@ public final class MediaCodecInfo {
*/
public boolean isFormatFunctionallySupported(Format format) {
return isSampleMimeTypeSupported(format)
&& isCodecProfileAndLevelSupported(format, /* checkPerformanceCapabilities= */ false)
&& isCompressedAudioBitDepthSupported(format);
&& isCodecProfileAndLevelSupported(format, /* checkPerformanceCapabilities= */ false);
}
private boolean isSampleMimeTypeSupported(Format format) {
@ -370,17 +363,6 @@ public final class MediaCodecInfo {
return false;
}
private boolean isCompressedAudioBitDepthSupported(Format format) {
// MediaCodec doesn't have a way to query FLAC decoder bit-depth support.
// c2.android.flac.decoder is known not to support 32-bit until API 34. We optimistically assume
// that another (unrecognized) FLAC decoder does support 32-bit on all API levels where it
// exists.
return !Objects.equals(format.sampleMimeType, MimeTypes.AUDIO_FLAC)
|| format.pcmEncoding != C.ENCODING_PCM_32BIT
|| Util.SDK_INT >= 34
|| !name.equals("c2.android.flac.decoder");
}
/** Whether the codec handles HDR10+ out-of-band metadata. */
public boolean isHdr10PlusOutOfBandMetadataSupported() {
if (Util.SDK_INT >= 29 && MimeTypes.VIDEO_VP9.equals(mimeType)) {
@ -425,7 +407,7 @@ public final class MediaCodecInfo {
*/
public DecoderReuseEvaluation canReuseCodec(Format oldFormat, Format newFormat) {
@DecoderDiscardReasons int discardReasons = 0;
if (!Objects.equals(oldFormat.sampleMimeType, newFormat.sampleMimeType)) {
if (!Util.areEqual(oldFormat.sampleMimeType, newFormat.sampleMimeType)) {
discardReasons |= DISCARD_REASON_MIME_TYPE_CHANGED;
}
@ -439,7 +421,7 @@ public final class MediaCodecInfo {
}
if ((!ColorInfo.isEquivalentToAssumedSdrDefault(oldFormat.colorInfo)
|| !ColorInfo.isEquivalentToAssumedSdrDefault(newFormat.colorInfo))
&& !Objects.equals(oldFormat.colorInfo, newFormat.colorInfo)) {
&& !Util.areEqual(oldFormat.colorInfo, newFormat.colorInfo)) {
// Don't perform detailed checks if both ColorInfos fall within the default SDR assumption.
discardReasons |= DISCARD_REASON_VIDEO_COLOR_INFO_CHANGED;
}
@ -710,8 +692,7 @@ public final class MediaCodecInfo {
private static boolean isDetachedSurfaceSupported(@Nullable CodecCapabilities capabilities) {
return Util.SDK_INT >= 35
&& capabilities != null
&& capabilities.isFeatureSupported(CodecCapabilities.FEATURE_DetachedSurface)
&& !needsDetachedSurfaceUnsupportedWorkaround();
&& capabilities.isFeatureSupported(CodecCapabilities.FEATURE_DetachedSurface);
}
private static boolean areSizeAndRateSupported(
@ -859,8 +840,8 @@ public final class MediaCodecInfo {
}
/**
* Returns whether a profile is excluded from the list of supported profiles. This may happen when
* a device declares support for a profile it doesn't actually support.
* Whether a profile is excluded from the list of supported profiles. This may happen when a
* device declares support for a profile it doesn't actually support.
*/
private static boolean needsProfileExcludedWorkaround(String mimeType, int profile) {
// See https://github.com/google/ExoPlayer/issues/3537
@ -868,9 +849,4 @@ public final class MediaCodecInfo {
&& CodecProfileLevel.HEVCProfileMain10 == profile
&& ("sailfish".equals(Build.DEVICE) || "marlin".equals(Build.DEVICE));
}
/** Returns whether the device is known to have issues with the detached surface mode. */
private static boolean needsDetachedSurfaceUnsupportedWorkaround() {
return Build.MANUFACTURER.equals("Xiaomi") || Build.MANUFACTURER.equals("OPPO");
}
}

View File

@ -39,7 +39,6 @@ import androidx.media3.exoplayer.scheduler.Requirements.RequirementFlags;
import androidx.media3.exoplayer.scheduler.Scheduler;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
@ -1097,7 +1096,7 @@ public abstract class DownloadService extends Service {
// Internal methods.
private boolean schedulerNeedsUpdate(Requirements requirements) {
return !Objects.equals(scheduledRequirements, requirements);
return !Util.areEqual(scheduledRequirements, requirements);
}
@RequiresNonNull("scheduler")

View File

@ -43,7 +43,6 @@ import java.util.ArrayList;
import java.util.Collections;
import java.util.HashMap;
import java.util.List;
import java.util.Objects;
import java.util.concurrent.ExecutionException;
import java.util.concurrent.Executor;
@ -476,7 +475,7 @@ public abstract class SegmentDownloader<M extends FilterableManifest<M>> impleme
return dataSpec1.uri.equals(dataSpec2.uri)
&& dataSpec1.length != C.LENGTH_UNSET
&& (dataSpec1.position + dataSpec1.length == dataSpec2.position)
&& Objects.equals(dataSpec1.key, dataSpec2.key)
&& Util.areEqual(dataSpec1.key, dataSpec2.key)
&& dataSpec1.flags == dataSpec2.flags
&& dataSpec1.httpMethod == dataSpec2.httpMethod
&& dataSpec1.httpRequestHeaders.equals(dataSpec2.httpRequestHeaders);

View File

@ -456,10 +456,6 @@ public final class ClippingMediaSource extends WrappingMediaSource {
Timeline timeline, long startUs, long endUs, boolean allowUnseekableMedia)
throws IllegalClippingException {
super(timeline);
if (endUs != C.TIME_END_OF_SOURCE && endUs < startUs) {
throw new IllegalClippingException(
IllegalClippingException.REASON_START_EXCEEDS_END, startUs, endUs);
}
if (timeline.getPeriodCount() != 1) {
throw new IllegalClippingException(IllegalClippingException.REASON_INVALID_PERIOD_COUNT);
}
@ -468,22 +464,25 @@ public final class ClippingMediaSource extends WrappingMediaSource {
if (!allowUnseekableMedia && !window.isPlaceholder && startUs != 0 && !window.isSeekable) {
throw new IllegalClippingException(IllegalClippingException.REASON_NOT_SEEKABLE_TO_START);
}
endUs = endUs == C.TIME_END_OF_SOURCE ? window.durationUs : max(0, endUs);
long resolvedEndUs = endUs == C.TIME_END_OF_SOURCE ? window.durationUs : max(0, endUs);
if (window.durationUs != C.TIME_UNSET) {
if (endUs > window.durationUs) {
endUs = window.durationUs;
if (resolvedEndUs > window.durationUs) {
resolvedEndUs = window.durationUs;
}
if (startUs > endUs) {
startUs = endUs;
if (startUs > resolvedEndUs) {
throw new IllegalClippingException(
IllegalClippingException.REASON_START_EXCEEDS_END,
startUs,
/* endUs= */ resolvedEndUs);
}
}
this.startUs = startUs;
this.endUs = endUs;
durationUs = endUs == C.TIME_UNSET ? C.TIME_UNSET : (endUs - startUs);
this.endUs = resolvedEndUs;
durationUs = resolvedEndUs == C.TIME_UNSET ? C.TIME_UNSET : (resolvedEndUs - startUs);
isDynamic =
window.isDynamic
&& (endUs == C.TIME_UNSET
|| (window.durationUs != C.TIME_UNSET && endUs == window.durationUs));
&& (resolvedEndUs == C.TIME_UNSET
|| (window.durationUs != C.TIME_UNSET && resolvedEndUs == window.durationUs));
}
@Override

View File

@ -28,7 +28,6 @@ import androidx.media3.exoplayer.drm.DrmSession;
import androidx.media3.exoplayer.drm.DrmSessionEventListener;
import java.io.IOException;
import java.util.HashMap;
import java.util.Objects;
/**
* Composite {@link MediaSource} consisting of multiple child sources.
@ -366,11 +365,11 @@ public abstract class CompositeMediaSource<T> extends BaseMediaSource {
}
int windowIndex = getWindowIndexForChildWindowIndex(id, childWindowIndex);
if (mediaSourceEventDispatcher.windowIndex != windowIndex
|| !Objects.equals(mediaSourceEventDispatcher.mediaPeriodId, mediaPeriodId)) {
|| !Util.areEqual(mediaSourceEventDispatcher.mediaPeriodId, mediaPeriodId)) {
mediaSourceEventDispatcher = createEventDispatcher(windowIndex, mediaPeriodId);
}
if (drmEventDispatcher.windowIndex != windowIndex
|| !Objects.equals(drmEventDispatcher.mediaPeriodId, mediaPeriodId)) {
|| !Util.areEqual(drmEventDispatcher.mediaPeriodId, mediaPeriodId)) {
drmEventDispatcher = createDrmEventDispatcher(windowIndex, mediaPeriodId);
}
return true;

View File

@ -40,7 +40,6 @@ import com.google.common.collect.ImmutableList;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.HashMap;
import java.util.IdentityHashMap;
import java.util.Objects;
/**
* Concatenates multiple {@link MediaSource MediaSources}, combining everything in one single {@link
@ -433,7 +432,7 @@ public final class ConcatenatingMediaSource2 extends CompositeMediaSource<Intege
hasInitialManifest = true;
}
manifestsAreIdentical =
manifestsAreIdentical && Objects.equals(initialManifest, window.manifest);
manifestsAreIdentical && Util.areEqual(initialManifest, window.manifest);
long windowDurationUs = window.durationUs;
if (windowDurationUs == C.TIME_UNSET) {

View File

@ -27,8 +27,8 @@ import androidx.media3.common.Timeline;
import androidx.media3.common.Timeline.Window;
import androidx.media3.common.util.Assertions;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.exoplayer.upstream.Allocator;
import java.util.Objects;
import org.checkerframework.checker.nullness.qual.RequiresNonNull;
/**
@ -317,7 +317,7 @@ public final class MaskingMediaSource extends WrappingMediaSource {
@Override
public Window getWindow(int windowIndex, Window window, long defaultPositionProjectionUs) {
timeline.getWindow(windowIndex, window, defaultPositionProjectionUs);
if (Objects.equals(window.uid, replacedInternalWindowUid)) {
if (Util.areEqual(window.uid, replacedInternalWindowUid)) {
window.uid = Window.SINGLE_WINDOW_UID;
}
return window;
@ -326,7 +326,7 @@ public final class MaskingMediaSource extends WrappingMediaSource {
@Override
public Period getPeriod(int periodIndex, Period period, boolean setIds) {
timeline.getPeriod(periodIndex, period, setIds);
if (Objects.equals(period.uid, replacedInternalPeriodUid) && setIds) {
if (Util.areEqual(period.uid, replacedInternalPeriodUid) && setIds) {
period.uid = MASKING_EXTERNAL_PERIOD_UID;
}
return period;
@ -343,7 +343,7 @@ public final class MaskingMediaSource extends WrappingMediaSource {
@Override
public Object getUidOfPeriod(int periodIndex) {
Object uid = timeline.getUidOfPeriod(periodIndex);
return Objects.equals(uid, replacedInternalPeriodUid) ? MASKING_EXTERNAL_PERIOD_UID : uid;
return Util.areEqual(uid, replacedInternalPeriodUid) ? MASKING_EXTERNAL_PERIOD_UID : uid;
}
}

View File

@ -41,7 +41,6 @@ import java.util.List;
/* package */ final class MergingMediaPeriod implements MediaPeriod, MediaPeriod.Callback {
private final MediaPeriod[] periods;
private final boolean[] periodsWithTimeOffsets;
private final IdentityHashMap<SampleStream, Integer> streamPeriodIndices;
private final CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
private final ArrayList<MediaPeriod> childrenPendingPreparation;
@ -63,10 +62,8 @@ import java.util.List;
compositeSequenceableLoader = compositeSequenceableLoaderFactory.empty();
streamPeriodIndices = new IdentityHashMap<>();
enabledPeriods = new MediaPeriod[0];
periodsWithTimeOffsets = new boolean[periods.length];
for (int i = 0; i < periods.length; i++) {
if (periodTimeOffsetsUs[i] != 0) {
periodsWithTimeOffsets[i] = true;
this.periods[i] = new TimeOffsetMediaPeriod(periods[i], periodTimeOffsetsUs[i]);
}
}
@ -78,7 +75,7 @@ import java.util.List;
* specified index.
*/
public MediaPeriod getChildPeriod(int index) {
return periodsWithTimeOffsets[index]
return periods[index] instanceof TimeOffsetMediaPeriod
? ((TimeOffsetMediaPeriod) periods[index]).getWrappedMediaPeriod()
: periods[index];
}

View File

@ -46,7 +46,6 @@ import androidx.media3.extractor.SeekMap;
import androidx.media3.extractor.TrackOutput;
import com.google.common.base.Supplier;
import com.google.errorprone.annotations.CanIgnoreReturnValue;
import java.util.Objects;
import java.util.concurrent.Executor;
/**
@ -364,7 +363,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource
return newConfiguration != null
&& newConfiguration.uri.equals(existingConfiguration.uri)
&& newConfiguration.imageDurationMs == existingConfiguration.imageDurationMs
&& Objects.equals(newConfiguration.customCacheKey, existingConfiguration.customCacheKey);
&& Util.areEqual(newConfiguration.customCacheKey, existingConfiguration.customCacheKey);
}
@Override

View File

@ -37,6 +37,7 @@ import androidx.media3.common.util.Log;
import androidx.media3.common.util.NullableType;
import androidx.media3.common.util.ParsableByteArray;
import androidx.media3.common.util.UnstableApi;
import androidx.media3.common.util.Util;
import androidx.media3.decoder.DecoderInputBuffer;
import androidx.media3.decoder.DecoderInputBuffer.InsufficientCapacityException;
import androidx.media3.exoplayer.FormatHolder;
@ -50,7 +51,6 @@ import androidx.media3.exoplayer.source.SampleStream.ReadFlags;
import androidx.media3.exoplayer.upstream.Allocator;
import androidx.media3.extractor.TrackOutput;
import java.io.IOException;
import java.util.Objects;
/** A queue of media samples. */
@UnstableApi
@ -740,7 +740,7 @@ public class SampleQueue implements TrackOutput {
private synchronized boolean setUpstreamFormat(Format format) {
upstreamFormatRequired = false;
if (Objects.equals(format, upstreamFormat)) {
if (Util.areEqual(format, upstreamFormat)) {
// The format is unchanged. If format and upstreamFormat are different objects, we keep the
// current upstreamFormat so we can detect format changes on the read side using cheap
// referential quality.
@ -930,7 +930,7 @@ public class SampleQueue implements TrackOutput {
// This sample queue is not expected to handle DRM. Nothing to do.
return;
}
if (!isFirstFormat && Objects.equals(oldDrmInitData, newDrmInitData)) {
if (!isFirstFormat && Util.areEqual(oldDrmInitData, newDrmInitData)) {
// Nothing to do.
return;
}

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