mirror of
https://github.com/androidx/media.git
synced 2025-05-18 04:59:54 +08:00
Compare commits
No commits in common. "release" and "1.6.0-beta01" have entirely different histories.
release
...
1.6.0-beta01
5
.github/ISSUE_TEMPLATE/bug.yml
vendored
5
.github/ISSUE_TEMPLATE/bug.yml
vendored
@ -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:
|
||||
|
542
RELEASENOTES.md
542
RELEASENOTES.md
@ -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
|
||||
|
@ -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
|
||||
|
@ -30,7 +30,6 @@ internal fun ExtraControls(player: Player, modifier: Modifier = Modifier) {
|
||||
horizontalArrangement = Arrangement.Center,
|
||||
verticalAlignment = Alignment.CenterVertically,
|
||||
) {
|
||||
PlaybackSpeedPopUpButton(player)
|
||||
ShuffleButton(player)
|
||||
RepeatButton(player)
|
||||
}
|
||||
|
@ -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)
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
@ -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
|
||||
|
@ -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"
|
||||
}
|
||||
]
|
||||
},
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
@ -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();
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
}
|
||||
;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
|
||||
/**
|
||||
|
@ -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} < {@code toIndex} <= {@link
|
||||
* #getMediaItemCount()}.
|
||||
* @param newIndex The new index of the first moved item. The index is in the range {@code 0}
|
||||
* <= {@code newIndex} <= {@link #getMediaItemCount() - (toIndex - fromIndex)}.
|
||||
* <= {@code newIndex} < {@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 <=
|
||||
* {@code fromIndex} <= {@link #getMediaItemCount()}.
|
||||
* {@code fromIndex} < {@link #getMediaItemCount()}.
|
||||
* @param toIndex The index of the first item not to be replaced (exclusive). The index is in the
|
||||
* range {@code fromIndex} <= {@code toIndex} <= {@link #getMediaItemCount()}.
|
||||
* range {@code fromIndex} < {@code toIndex} <= {@link #getMediaItemCount()}.
|
||||
* @param mediaItems The media items to replace the specified range with.
|
||||
* @return A {@link ListenableFuture} indicating the completion of all immediate {@link State}
|
||||
* changes caused by this call.
|
||||
@ -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));
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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
|
||||
|
@ -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);
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
}
|
||||
}
|
||||
|
@ -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,
|
||||
|
@ -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.
|
||||
*/
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
@ -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;
|
||||
|
||||
|
@ -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 {
|
||||
|
||||
|
@ -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();
|
||||
}
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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() {
|
||||
|
@ -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;
|
||||
|
@ -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,
|
||||
|
@ -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(
|
||||
|
@ -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(
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
}
|
||||
|
||||
|
@ -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
|
||||
|
@ -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(
|
||||
|
@ -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 {
|
||||
|
@ -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()
|
||||
|
@ -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;
|
||||
});
|
||||
|
||||
|
@ -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()
|
||||
|
@ -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(
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
|
||||
|
@ -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.
|
||||
|
@ -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.
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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);
|
||||
}
|
||||
}
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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,
|
||||
|
@ -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 {
|
||||
|
@ -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) {
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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() {}
|
||||
|
@ -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
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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();
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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)
|
||||
|
@ -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();
|
||||
|
@ -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,
|
||||
|
@ -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;
|
||||
|
@ -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;
|
||||
}
|
||||
|
@ -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);
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
|
@ -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");
|
||||
}
|
||||
}
|
||||
|
@ -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")
|
||||
|
@ -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);
|
||||
|
@ -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
|
||||
|
@ -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;
|
||||
|
@ -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) {
|
||||
|
@ -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;
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -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];
|
||||
}
|
||||
|
@ -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
|
||||
|
@ -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
Loading…
x
Reference in New Issue
Block a user