Merge branch 'dev-v2' into trackSelectionView
This commit is contained in:
commit
869981a360
@ -1,4 +1,4 @@
|
|||||||
# ExoPlayer #
|
# ExoPlayer <img src="https://img.shields.io/github/v/release/google/ExoPlayer.svg?label=latest"/> #
|
||||||
|
|
||||||
ExoPlayer is an application level media player for Android. It provides an
|
ExoPlayer is an application level media player for Android. It provides an
|
||||||
alternative to Android’s MediaPlayer API for playing audio and video both
|
alternative to Android’s MediaPlayer API for playing audio and video both
|
||||||
|
314
RELEASENOTES.md
314
RELEASENOTES.md
@ -2,138 +2,137 @@
|
|||||||
|
|
||||||
### dev-v2 (not yet released)
|
### dev-v2 (not yet released)
|
||||||
|
|
||||||
|
* New release notes go here!
|
||||||
|
|
||||||
### 2.12.0 (not yet released - targeted for 2020-08-TBD) ###
|
### 2.12.0 (not yet released - targeted for 2020-09-03) ###
|
||||||
|
|
||||||
* Core library:
|
* Core library:
|
||||||
* Implement getTag for SilenceMediaSource.
|
* `Player`:
|
||||||
* Added `TextComponent.getCurrentCues` because the current cues are no
|
* Add a top level playlist API based on a new `MediaItem` class
|
||||||
longer forwarded to a new `TextOutput` in `SimpleExoPlayer`
|
([#6161](https://github.com/google/ExoPlayer/issues/6161)). The
|
||||||
automatically.
|
new methods for playlist manipulation are `setMediaItem(s)`,
|
||||||
* Add additional options to `SimpleExoPlayer.Builder` that were previously
|
`addMediaItem(s)`, `moveMediaItem(s)`, `removeMediaItem(s)` and
|
||||||
only accessible via setters.
|
`clearMediaItems`. This API should be used instead of
|
||||||
* Add opt-in to verify correct thread usage with
|
`ConcatenatingMediaSource` in most cases.
|
||||||
`SimpleExoPlayer.setThrowsWhenUsingWrongThread(true)`
|
* Add `getCurrentMediaItem` for getting the currently playing item
|
||||||
([#4463](https://github.com/google/ExoPlayer/issues/4463)).
|
in the playlist.
|
||||||
* Add playbackPositionUs parameter to 'LoadControl.shouldContinueLoading'.
|
* Add `EventListener.onMediaItemTransition` to report when
|
||||||
* The `DefaultLoadControl` default minimum buffer is set to 50 seconds,
|
playback transitions from one item to another in the playlist.
|
||||||
equal to the default maximum buffer. `DefaultLoadControl` applies the
|
* Add `play` and `pause` convenience methods. They are equivalent to
|
||||||
same behavior for audio and video.
|
`setPlayWhenReady(true)` and `setPlayWhenReady(false)` respectively.
|
||||||
* Add API in `AnalyticsListener` to report video frame processing offset.
|
* Add `getCurrentLiveOffset` for getting the offset of the current
|
||||||
`MediaCodecVideoRenderer` reports the event.
|
playback position from the live edge of a live stream.
|
||||||
* Add fields `videoFrameProcessingOffsetUsSum` and
|
* Add `getTrackSelector` for getting the `TrackSelector` used by the
|
||||||
`videoFrameProcessingOffsetUsCount` in `DecoderCounters` to compute the
|
player.
|
||||||
average video frame processing offset.
|
* Add `AudioComponent.setAudioSessionId` to set the audio session ID.
|
||||||
* Add playlist API
|
This method is also available on `SimpleExoPlayer`.
|
||||||
([#6161](https://github.com/google/ExoPlayer/issues/6161)).
|
* Add `TextComponent.getCurrentCues` to get the current cues. This
|
||||||
* Attach an identifier and extra information to load error events passed
|
method is also available on `SimpleExoPlayer`. The current cues are
|
||||||
to `LoadErrorHandlingPolicy`. `LoadErrorHandlingPolicy` implementations
|
no longer automatically forwarded to a `TextOutput` when it's added
|
||||||
must migrate to overriding the non-deprecated methods of the interface
|
to a `SimpleExoPlayer`.
|
||||||
in preparation for deprecated methods' removal in a future ExoPlayer
|
* Add `Player.DeviceComponent` to query and control the device volume.
|
||||||
version ([#7309](https://github.com/google/ExoPlayer/issues/7309)).
|
`SimpleExoPlayer` implements this interface.
|
||||||
* Add `play` and `pause` methods to `Player`.
|
|
||||||
* Add `Player.getCurrentLiveOffset` to conveniently return the live
|
|
||||||
offset.
|
|
||||||
* Add `Player.EventListener.onPlayWhenReadyChanged` with reasons.
|
|
||||||
* Add `Player.EventListener.onPlaybackStateChanged` and deprecate
|
|
||||||
`Player.EventListener.onPlayerStateChanged`.
|
|
||||||
* Add `Player.EventListener.onMediaItemTransition` with reasons.
|
|
||||||
* Add `Player.setAudioSessionId` to set the session ID attached to the
|
|
||||||
`AudioTrack`.
|
|
||||||
* Add `Player.getTrackSelector`.
|
|
||||||
* Deprecate and rename `getPlaybackError` to `getPlayerError` for
|
* Deprecate and rename `getPlaybackError` to `getPlayerError` for
|
||||||
consistency.
|
consistency.
|
||||||
* Deprecate and rename `onLoadingChanged` to `onIsLoadingChanged` for
|
* Deprecate and rename `onLoadingChanged` to `onIsLoadingChanged` for
|
||||||
consistency.
|
consistency.
|
||||||
* Deprecate `onSeekProcessed` because all seek changes happen instantly
|
* Deprecate `EventListener.onPlayerStateChanged`, replacing it with
|
||||||
now and listening to `onPositionDiscontinuity` is sufficient.
|
`EventListener.onPlayWhenReadyChanged` and
|
||||||
* Add `ExoPlayer.setPauseAtEndOfMediaItems` to let the player pause at the
|
`EventListener.onPlaybackStateChanged`.
|
||||||
end of each media item
|
* Deprecate `EventListener.onSeekProcessed` because seek changes now
|
||||||
|
happen instantly and listening to `onPositionDiscontinuity` is
|
||||||
|
sufficient.
|
||||||
|
* `ExoPlayer`:
|
||||||
|
* Add `setMediaSource(s)` and `addMediaSource(s)` to `ExoPlayer`, for
|
||||||
|
adding `MediaSource` instances directly to the playlist.
|
||||||
|
* Add `ExoPlayer.setPauseAtEndOfMediaItems` to let the player pause at
|
||||||
|
the end of each media item
|
||||||
([#5660](https://github.com/google/ExoPlayer/issues/5660)).
|
([#5660](https://github.com/google/ExoPlayer/issues/5660)).
|
||||||
* Split `setPlaybackParameter` into `setPlaybackSpeed` and
|
* Allow passing `C.TIME_END_OF_SOURCE` to `PlayerMessage.setPosition`
|
||||||
`AudioComponent.setSkipSilenceEnabled` with callbacks
|
to send a `PlayerMessage` at the end of a stream.
|
||||||
`onPlaybackSpeedChanged` and
|
* `SimpleExoPlayer`:
|
||||||
`AudioListener.onSkipSilenceEnabledChanged`.
|
* `SimpleExoPlayer` implements the new `MediaItem` based playlist API,
|
||||||
* Make `MediaSourceEventListener.LoadEventInfo` and
|
using a `MediaSourceFactory` to convert `MediaItem` instances to
|
||||||
`MediaSourceEventListener.MediaLoadData` top-level classes.
|
playable `MediaSource` instances. A `DefaultMediaSourceFactory` is
|
||||||
* Rename `MediaCodecRenderer.onOutputFormatChanged` to
|
used by default. `Builder.setMediaSourceFactory` allows setting a
|
||||||
`MediaCodecRenderer.onOutputMediaFormatChanged`, further clarifying the
|
custom factory.
|
||||||
distinction between `Format` and `MediaFormat`.
|
* Add additional options to `Builder` that were previously only
|
||||||
* Improve `Format` propagation within the media codec renderer
|
accessible via setters.
|
||||||
([#6646](https://github.com/google/ExoPlayer/issues/6646)).
|
* Add opt-in to verify correct thread usage with
|
||||||
* Move player message-related constants from `C` to `Renderer`, to avoid
|
`setThrowsWhenUsingWrongThread(true)`
|
||||||
having the constants class depend on player/renderer classes.
|
([#4463](https://github.com/google/ExoPlayer/issues/4463)).
|
||||||
* Split out `common` and `extractor` submodules.
|
* `Format`:
|
||||||
* Allow to explicitly send `PlayerMessage`s at the end of a stream.
|
* Add a `Builder` and deprecate all `create` methods and most
|
||||||
* Add `DataSpec.Builder` and deprecate most `DataSpec` constructors.
|
`Format.copyWith` methods.
|
||||||
* Add `DataSpec.customData` to allow applications to pass custom data
|
* Split `bitrate` into `averageBitrate` and `peakBitrate`
|
||||||
through `DataSource` chains.
|
|
||||||
* Add a `Format.Builder` and deprecate all `Format.create*` methods and
|
|
||||||
most `Format.copyWith*` methods.
|
|
||||||
* Split `Format.bitrate` into `Format.averageBitrate` and
|
|
||||||
`Format.peakBitrate`
|
|
||||||
([#2863](https://github.com/google/ExoPlayer/issues/2863)).
|
([#2863](https://github.com/google/ExoPlayer/issues/2863)).
|
||||||
* Add option to `MergingMediaSource` to adjust the time offsets between
|
* `LoadControl`:
|
||||||
the merged sources
|
* Add a `playbackPositionUs` parameter to `shouldContinueLoading`.
|
||||||
|
* Set the default minimum buffer duration in `DefaultLoadControl` to
|
||||||
|
50 seconds (equal to the default maximum buffer), and treat audio
|
||||||
|
and video the same.
|
||||||
|
* Add a `MetadataRetriever` API for retrieving track information and
|
||||||
|
static metadata for a media item
|
||||||
|
([#3609](https://github.com/google/ExoPlayer/issues/3609)).
|
||||||
|
* Attach an identifier and extra information to load error events passed
|
||||||
|
to `LoadErrorHandlingPolicy`
|
||||||
|
([#7309](https://github.com/google/ExoPlayer/issues/7309)).
|
||||||
|
`LoadErrorHandlingPolicy` implementations should migrate to implementing
|
||||||
|
the non-deprecated methods of the interface.
|
||||||
|
* Add an option to `MergingMediaSource` to adjust the time offsets
|
||||||
|
between the merged sources
|
||||||
([#6103](https://github.com/google/ExoPlayer/issues/6103)).
|
([#6103](https://github.com/google/ExoPlayer/issues/6103)).
|
||||||
* `SimpleDecoderVideoRenderer` and `SimpleDecoderAudioRenderer` renamed to
|
* Move `MediaSourceEventListener.LoadEventInfo` and
|
||||||
|
`MediaSourceEventListener.MediaLoadData` to be top-level classes in
|
||||||
|
`com.google.android.exoplayer2.source`.
|
||||||
|
* Move `SimpleDecoderVideoRenderer` and `SimpleDecoderAudioRenderer` to
|
||||||
`DecoderVideoRenderer` and `DecoderAudioRenderer` respectively, and
|
`DecoderVideoRenderer` and `DecoderAudioRenderer` respectively, and
|
||||||
generalized to work with `Decoder` rather than `SimpleDecoder`.
|
generalize them to work with `Decoder` rather than `SimpleDecoder`.
|
||||||
* Add media item based playlist API to `Player`.
|
* Deprecate `C.MSG_*` constants, replacing them with constants in
|
||||||
* Add `getCurrentMediaItem` to `Player`.
|
`Renderer`.
|
||||||
* Remove deprecated members in `DefaultTrackSelector`.
|
* Split the `library-core` module into `library-core`,
|
||||||
* Add `DefaultTrackSelector` constraints for minimum video resolution,
|
`library-common` and `library-extractor`. The `library-core` module
|
||||||
bitrate and frame rate
|
has an API dependency on both of the new modules, so this change
|
||||||
([#4511](https://github.com/google/ExoPlayer/issues/4511)).
|
should be transparent to developers including ExoPlayer using Gradle
|
||||||
* Add `Player.DeviceComponent` and implement it for `SimpleExoPlayer` so
|
dependencies.
|
||||||
that the device volume can be controlled by player.
|
* Add a dependency on Guava.
|
||||||
* Parse track titles from Matroska files
|
* Video:
|
||||||
([#7247](https://github.com/google/ExoPlayer/pull/7247)).
|
* Pass frame rate hint to `Surface.setFrameRate` on Android 11.
|
||||||
* Replace `CacheDataSinkFactory` and `CacheDataSourceFactory` with
|
* Fix incorrect aspect ratio when transitioning from one video to another
|
||||||
`CacheDataSink.Factory` and `CacheDataSource.Factory` respectively.
|
with the same resolution, but a different pixel aspect ratio
|
||||||
* Extend `EventTime` with more details about the current player state for
|
([#6646](https://github.com/google/ExoPlayer/issues/6646)).
|
||||||
easier access
|
|
||||||
([#7332](https://github.com/google/ExoPlayer/issues/7332)).
|
|
||||||
* Add `HttpDataSource.InvalidResponseCodeException#responseBody` field
|
|
||||||
([#6853](https://github.com/google/ExoPlayer/issues/6853)).
|
|
||||||
* Add `TrackSelection.shouldCancelMediaChunkLoad` to check whether an
|
|
||||||
ongoing load should be canceled. Only supported by HLS streams so far.
|
|
||||||
([#2848](https://github.com/google/ExoPlayer/issues/2848)).
|
|
||||||
* Remove throws clause from Renderer.stop.
|
|
||||||
* Don't clear `exception` in `SimpleDecoder#flush()`
|
|
||||||
([#7590](https://github.com/google/ExoPlayer/issues/7590)).
|
|
||||||
* Remove `AdaptiveTrackSelection.minTimeBetweenBufferReevaluationMs`
|
|
||||||
parameter ([#7582](https://github.com/google/ExoPlayer/issues/7582)).
|
|
||||||
* Fix wrong `MediaPeriodId` for some renderer errors reported by
|
|
||||||
`AnalyticsListener.onPlayerError`.
|
|
||||||
* Remove onMediaPeriodCreated/Released/ReadingStarted from
|
|
||||||
`MediaSourceEventListener` and `AnalyticsListener`.
|
|
||||||
* Dispatch previous, next, fast forward and rewind actions through
|
|
||||||
`ControlDispatcher`
|
|
||||||
([#6926](https://github.com/google/ExoPlayer/issues/6926)).
|
|
||||||
* Add Guava dependency.
|
|
||||||
* Add MetadataRetriever API to retrieve the static metadata of a media
|
|
||||||
item ([#3609](https://github.com/google/ExoPlayer/issues/3609)).
|
|
||||||
* Video: Pass frame rate hint to `Surface.setFrameRate` on Android R devices.
|
|
||||||
* Audio:
|
* Audio:
|
||||||
* Add a sample count parameter to `MediaCodecRenderer.processOutputBuffer`
|
* Add experimental support for power efficient playback using audio
|
||||||
and `AudioSink.handleBuffer` to allow batching multiple encoded frames
|
offload.
|
||||||
in one buffer.
|
* Add support for using framework audio speed adjustment instead of
|
||||||
* No longer use a `MediaCodec` in audio passthrough mode.
|
ExoPlayer's implementation
|
||||||
|
([#7502](https://github.com/google/ExoPlayer/issues/7502)). This option
|
||||||
|
can be set using
|
||||||
|
`DefaultRenderersFactory.setEnableAudioTrackPlaybackParams`.
|
||||||
|
* Add an event for the audio position starting to advance, to make it
|
||||||
|
easier for apps to determine when audio playout started
|
||||||
|
([#7577](https://github.com/google/ExoPlayer/issues/7577)).
|
||||||
|
* Generalize support for floating point audio.
|
||||||
|
* Add an option to `DefaultAudioSink` for enabling floating point
|
||||||
|
output. This option can also be set using
|
||||||
|
`DefaultRenderersFactory.setEnableAudioFloatOutput`.
|
||||||
|
* Add floating point output capability to `MediaCodecAudioRenderer`
|
||||||
|
and `LibopusAudioRenderer`, which is enabled automatically if the
|
||||||
|
audio sink supports floating point output and if it makes sense for
|
||||||
|
the content being played.
|
||||||
|
* Enable the floating point output capability of `FfmpegAudioRenderer`
|
||||||
|
automatically if the audio sink supports floating point output and
|
||||||
|
if it makes sense for the content being played. The option to
|
||||||
|
manually enable floating point output has been removed, since this
|
||||||
|
now done with the generalized option on `DefaultAudioSink`.
|
||||||
|
* In `MediaCodecAudioRenderer`, stop passing audio samples through
|
||||||
|
`MediaCodec` when playing PCM audio or encoded audio using passthrough
|
||||||
|
mode.
|
||||||
|
* Reuse audio decoders when transitioning through playlists of gapless
|
||||||
|
audio, rather than reinstantiating them.
|
||||||
* Check `DefaultAudioSink` supports passthrough, in addition to checking
|
* Check `DefaultAudioSink` supports passthrough, in addition to checking
|
||||||
the `AudioCapabilities`
|
the `AudioCapabilities`
|
||||||
* Add an experimental scheduling mode to save power in offload.
|
|
||||||
([#7404](https://github.com/google/ExoPlayer/issues/7404)).
|
([#7404](https://github.com/google/ExoPlayer/issues/7404)).
|
||||||
* Adjust input timestamps in `MediaCodecRenderer` to account for the
|
|
||||||
Codec2 MP3 decoder having lower timestamps on the output side.
|
|
||||||
* Propagate gapless audio metadata without the need to recreate the audio
|
|
||||||
decoders.
|
|
||||||
* Add floating point PCM output capability in `MediaCodecAudioRenderer`,
|
|
||||||
and `LibopusAudioRenderer`.
|
|
||||||
* Do not use a MediaCodec for PCM formats if AudioTrack supports it.
|
|
||||||
* Add optional support for using framework audio speed adjustment instead
|
|
||||||
of application-level audio speed adjustment
|
|
||||||
([#7502](https://github.com/google/ExoPlayer/issues/7502)).
|
|
||||||
* Text:
|
* Text:
|
||||||
* Add a WebView-based output option to `SubtitleView`. This can display
|
* Add a WebView-based output option to `SubtitleView`. This can display
|
||||||
some features not supported by the existing Canvas-based output such as
|
some features not supported by the existing Canvas-based output such as
|
||||||
@ -209,21 +208,19 @@
|
|||||||
`Mp3Extractor`. A significant portion of the file may need to be scanned
|
`Mp3Extractor`. A significant portion of the file may need to be scanned
|
||||||
when a seek is performed, which may be costly for large files.
|
when a seek is performed, which may be costly for large files.
|
||||||
* MP4: Fix playback of MP4 streams that contain Opus audio.
|
* MP4: Fix playback of MP4 streams that contain Opus audio.
|
||||||
* FMP4:
|
* FMP4: Add support for partially fragmented MP4s
|
||||||
* Add support for partially fragmented MP4s
|
|
||||||
([#7308](https://github.com/google/ExoPlayer/issues/7308)).
|
([#7308](https://github.com/google/ExoPlayer/issues/7308)).
|
||||||
* Fix handling of `traf` boxes containing multiple `sbgp` or `sgpd`
|
* Matroska:
|
||||||
boxes ([#7716](https://github.com/google/ExoPlayer/issues/7716)).
|
* Support Dolby Vision
|
||||||
* Matroska: Remove support for the `Invisible` block header flag.
|
([#7267](https://github.com/google/ExoPlayer/issues/7267).
|
||||||
|
* Populate `Format.label` with track titles.
|
||||||
|
* Remove support for the `Invisible` block header flag.
|
||||||
* MPEG-TS: Add support for MPEG-4 Part 2 and H.263
|
* MPEG-TS: Add support for MPEG-4 Part 2 and H.263
|
||||||
([#1603](https://github.com/google/ExoPlayer/issues/1603),
|
([#1603](https://github.com/google/ExoPlayer/issues/1603),
|
||||||
[#5107](https://github.com/google/ExoPlayer/issues/5107)).
|
[#5107](https://github.com/google/ExoPlayer/issues/5107)).
|
||||||
* Ogg: Fix handling of non-contiguous pages
|
* Ogg: Fix handling of non-contiguous pages
|
||||||
([#7230](https://github.com/google/ExoPlayer/issues/7230)).
|
([#7230](https://github.com/google/ExoPlayer/issues/7230)).
|
||||||
* FLV: Ignore `SCRIPTDATA` segments with invalid name types, rather than
|
* UI:
|
||||||
failing playback
|
|
||||||
([#7675](https://github.com/google/ExoPlayer/issues/7675)).
|
|
||||||
* UI
|
|
||||||
* Add `StyledPlayerView` and `StyledPlayerControlView`, which provide a
|
* Add `StyledPlayerView` and `StyledPlayerControlView`, which provide a
|
||||||
more polished user experience than `PlayerView` and `PlayerControlView`
|
more polished user experience than `PlayerView` and `PlayerControlView`
|
||||||
at the cost of decreased customizability.
|
at the cost of decreased customizability.
|
||||||
@ -238,6 +235,9 @@
|
|||||||
* Update `TrackSelectionDialogBuilder` to use the AndroidX app compat
|
* Update `TrackSelectionDialogBuilder` to use the AndroidX app compat
|
||||||
`AlertDialog` rather than the platform version, if available
|
`AlertDialog` rather than the platform version, if available
|
||||||
([#7357](https://github.com/google/ExoPlayer/issues/7357)).
|
([#7357](https://github.com/google/ExoPlayer/issues/7357)).
|
||||||
|
* Make UI components dispatch previous, next, fast forward and rewind
|
||||||
|
actions via their `ControlDispatcher`
|
||||||
|
([#6926](https://github.com/google/ExoPlayer/issues/6926)).
|
||||||
* Downloads and caching:
|
* Downloads and caching:
|
||||||
* Add `DownloadRequest.Builder`.
|
* Add `DownloadRequest.Builder`.
|
||||||
* Add `DownloadRequest.keySetId` to make it easier to store an offline
|
* Add `DownloadRequest.keySetId` to make it easier to store an offline
|
||||||
@ -269,7 +269,39 @@
|
|||||||
([#7011](https://github.com/google/ExoPlayer/issues/7011),
|
([#7011](https://github.com/google/ExoPlayer/issues/7011),
|
||||||
[#6725](https://github.com/google/ExoPlayer/issues/6725),
|
[#6725](https://github.com/google/ExoPlayer/issues/6725),
|
||||||
[#7066](https://github.com/google/ExoPlayer/issues/7066)).
|
[#7066](https://github.com/google/ExoPlayer/issues/7066)).
|
||||||
|
* Remove support for `cbc1` and `cens` encrytion schemes. Support for
|
||||||
|
these schemes was removed from the Android platform from API level 30,
|
||||||
|
and the range of API levels for which they are supported is too small to
|
||||||
|
be useful.
|
||||||
* Remove generic types from DRM components.
|
* Remove generic types from DRM components.
|
||||||
|
* Track selection:
|
||||||
|
* Add `TrackSelection.shouldCancelMediaChunkLoad` to check whether an
|
||||||
|
ongoing load should be canceled
|
||||||
|
([#2848](https://github.com/google/ExoPlayer/issues/2848)).
|
||||||
|
* Add `DefaultTrackSelector` constraints for minimum video resolution,
|
||||||
|
bitrate and frame rate
|
||||||
|
([#4511](https://github.com/google/ExoPlayer/issues/4511)).
|
||||||
|
* Remove previously deprecated `DefaultTrackSelector` members.
|
||||||
|
* Data sources:
|
||||||
|
* Add `HttpDataSource.InvalidResponseCodeException#responseBody` field
|
||||||
|
([#6853](https://github.com/google/ExoPlayer/issues/6853)).
|
||||||
|
* Add `DataSpec.Builder` and deprecate most `DataSpec` constructors.
|
||||||
|
* Add `DataSpec.customData` to allow applications to pass custom data
|
||||||
|
through `DataSource` chains.
|
||||||
|
* Deprecate `CacheDataSinkFactory` and `CacheDataSourceFactory`, which are
|
||||||
|
replaced by `CacheDataSink.Factory` and `CacheDataSource.Factory`
|
||||||
|
respectively.
|
||||||
|
* Analytics:
|
||||||
|
* Extend `EventTime` with more details about the current player state
|
||||||
|
([#7332](https://github.com/google/ExoPlayer/issues/7332)).
|
||||||
|
* Add `AnalyticsListener.onVideoFrameProcessingOffset` to report how
|
||||||
|
early or late video frames are processed relative to them needing to be
|
||||||
|
presented. Video frame processing offset fields are also added to
|
||||||
|
`DecoderCounters`.
|
||||||
|
* Fix incorrect `MediaPeriodId` for some renderer errors reported by
|
||||||
|
`AnalyticsListener.onPlayerError`.
|
||||||
|
* Remove `onMediaPeriodCreated`, `onMediaPeriodReleased` and
|
||||||
|
`onReadingStarted` from `AnalyticsListener`.
|
||||||
* Test utils: Add `TestExoPlayer`, a utility class with APIs to create
|
* Test utils: Add `TestExoPlayer`, a utility class with APIs to create
|
||||||
`SimpleExoPlayer` instances with fake components for testing.
|
`SimpleExoPlayer` instances with fake components for testing.
|
||||||
* Media2 extension: This is a new extension that makes it easy to use
|
* Media2 extension: This is a new extension that makes it easy to use
|
||||||
@ -277,8 +309,6 @@
|
|||||||
* Cast extension: Implement playlist API and deprecate the old queue
|
* Cast extension: Implement playlist API and deprecate the old queue
|
||||||
manipulation API.
|
manipulation API.
|
||||||
* IMA extension:
|
* IMA extension:
|
||||||
* Upgrade to IMA SDK 3.19.4, bringing in a fix for setting the media load
|
|
||||||
timeout ([#7170](https://github.com/google/ExoPlayer/issues/7170)).
|
|
||||||
* Migrate to new 'friendly obstruction' IMA SDK APIs, and allow apps to
|
* Migrate to new 'friendly obstruction' IMA SDK APIs, and allow apps to
|
||||||
register a purpose and detail reason for overlay views via
|
register a purpose and detail reason for overlay views via
|
||||||
`AdsLoader.AdViewProvider`.
|
`AdsLoader.AdViewProvider`.
|
||||||
@ -304,8 +334,32 @@
|
|||||||
* Remove support for media tunneling, random ABR and playback of
|
* Remove support for media tunneling, random ABR and playback of
|
||||||
spherical video. Developers wishing to experiment with these features
|
spherical video. Developers wishing to experiment with these features
|
||||||
can enable them by modifying the demo app source code.
|
can enable them by modifying the demo app source code.
|
||||||
* Fix playback of ClearKey protected content on API level 26 and earlier
|
|
||||||
([#7735](https://github.com/google/ExoPlayer/issues/7735)).
|
### 2.11.8 (2020-08-25) ###
|
||||||
|
|
||||||
|
* Fix distorted playback of floating point audio when samples exceed the
|
||||||
|
`[-1, 1]` nominal range.
|
||||||
|
* MP4:
|
||||||
|
* Add support for `piff` and `isml` brands
|
||||||
|
([#7584](https://github.com/google/ExoPlayer/issues/7584)).
|
||||||
|
* Fix playback of very short MP4 files.
|
||||||
|
* FMP4:
|
||||||
|
* Fix `saiz` and `senc` sample count checks, resolving a "length
|
||||||
|
mismatch" `ParserException` when playing certain protected FMP4 streams
|
||||||
|
([#7592](https://github.com/google/ExoPlayer/issues/7592)).
|
||||||
|
* Fix handling of `traf` boxes containing multiple `sbgp` or `sgpd`
|
||||||
|
boxes.
|
||||||
|
* FLV: Ignore `SCRIPTDATA` segments with invalid name types, rather than
|
||||||
|
failing playback ([#7675](https://github.com/google/ExoPlayer/issues/7675)).
|
||||||
|
* Better infer the content type of `.ism` and `.isml` streaming URLs.
|
||||||
|
* Workaround an issue on Broadcom based devices where playbacks would not
|
||||||
|
transition to `STATE_ENDED` when using video tunneling mode
|
||||||
|
([#7647](https://github.com/google/ExoPlayer/issues/7647)).
|
||||||
|
* IMA extension: Upgrade to IMA SDK 3.19.4, bringing in a fix for setting the
|
||||||
|
media load timeout
|
||||||
|
([#7170](https://github.com/google/ExoPlayer/issues/7170)).
|
||||||
|
* Demo app: Fix playback of ClearKey protected content on API level 26 and
|
||||||
|
earlier ([#7735](https://github.com/google/ExoPlayer/issues/7735)).
|
||||||
|
|
||||||
### 2.11.7 (2020-06-29) ###
|
### 2.11.7 (2020-06-29) ###
|
||||||
|
|
||||||
|
@ -124,18 +124,6 @@
|
|||||||
"drm_scheme": "widevine",
|
"drm_scheme": "widevine",
|
||||||
"drm_license_uri": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
|
"drm_license_uri": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"name": "Secure (cbc1)",
|
|
||||||
"uri": "https://storage.googleapis.com/wvmedia/cbc1/h264/tears/tears_aes_cbc1.mpd",
|
|
||||||
"drm_scheme": "widevine",
|
|
||||||
"drm_license_uri": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
|
|
||||||
},
|
|
||||||
{
|
|
||||||
"name": "Secure UHD (cbc1)",
|
|
||||||
"uri": "https://storage.googleapis.com/wvmedia/cbc1/h264/tears/tears_aes_cbc1_uhd.mpd",
|
|
||||||
"drm_scheme": "widevine",
|
|
||||||
"drm_license_uri": "https://proxy.uat.widevine.com/proxy?provider=widevine_test"
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"name": "Secure (cbcs)",
|
"name": "Secure (cbcs)",
|
||||||
"uri": "https://storage.googleapis.com/wvmedia/cbcs/h264/tears/tears_aes_cbcs.mpd",
|
"uri": "https://storage.googleapis.com/wvmedia/cbcs/h264/tears/tears_aes_cbcs.mpd",
|
||||||
|
@ -458,32 +458,16 @@ public final class CastPlayer extends BasePlayer {
|
|||||||
flushNotifications();
|
flushNotifications();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Use {@link #setPlaybackSpeed(float)} instead. */
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
@Deprecated
|
|
||||||
@Override
|
@Override
|
||||||
public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) {
|
public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) {
|
||||||
// Unsupported by the RemoteMediaClient API. Do nothing.
|
// Unsupported by the RemoteMediaClient API. Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Use {@link #getPlaybackSpeed()} instead. */
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
@Deprecated
|
|
||||||
@Override
|
@Override
|
||||||
public PlaybackParameters getPlaybackParameters() {
|
public PlaybackParameters getPlaybackParameters() {
|
||||||
return PlaybackParameters.DEFAULT;
|
return PlaybackParameters.DEFAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setPlaybackSpeed(float playbackSpeed) {
|
|
||||||
// Unsupported by the RemoteMediaClient API. Do nothing.
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public float getPlaybackSpeed() {
|
|
||||||
return Player.DEFAULT_PLAYBACK_SPEED;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void stop(boolean reset) {
|
public void stop(boolean reset) {
|
||||||
playbackState = STATE_IDLE;
|
playbackState = STATE_IDLE;
|
||||||
|
@ -19,7 +19,6 @@ import static com.google.common.truth.Truth.assertThat;
|
|||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.os.Looper;
|
|
||||||
import android.support.v4.media.session.MediaControllerCompat;
|
import android.support.v4.media.session.MediaControllerCompat;
|
||||||
import android.support.v4.media.session.MediaSessionCompat;
|
import android.support.v4.media.session.MediaSessionCompat;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
@ -46,13 +45,6 @@ public class MediaSessionUtilTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void getSessionCompatToken_withMediaControllerCompat_returnsValidToken() throws Exception {
|
public void getSessionCompatToken_withMediaControllerCompat_returnsValidToken() throws Exception {
|
||||||
// Workaround to instantiate MediaSession with public androidx.media dependency.
|
|
||||||
// TODO(b/146536708): Remove this workaround when the relevant change is released via
|
|
||||||
// androidx.media 1.2.0.
|
|
||||||
if (Looper.myLooper() == null) {
|
|
||||||
Looper.prepare();
|
|
||||||
}
|
|
||||||
|
|
||||||
Context context = ApplicationProvider.getApplicationContext();
|
Context context = ApplicationProvider.getApplicationContext();
|
||||||
|
|
||||||
SessionPlayerConnector sessionPlayerConnector = playerTestRule.getSessionPlayerConnector();
|
SessionPlayerConnector sessionPlayerConnector = playerTestRule.getSessionPlayerConnector();
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.google.android.exoplayer2.ext.media2;
|
package com.google.android.exoplayer2.ext.media2;
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
@ -55,15 +54,23 @@ import org.junit.rules.ExternalResource;
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void before() {
|
protected void before() {
|
||||||
|
// Workaround limitation in androidx.media2.session:1.0.3 which session can only be instantiated
|
||||||
|
// on thread with prepared Looper.
|
||||||
|
// TODO: Remove when androidx.media2.session:1.1.0 is released without the limitation
|
||||||
|
// [Internal: b/146536708]
|
||||||
|
if (Looper.myLooper() == null) {
|
||||||
|
Looper.prepare();
|
||||||
|
}
|
||||||
|
|
||||||
context = ApplicationProvider.getApplicationContext();
|
context = ApplicationProvider.getApplicationContext();
|
||||||
executor = Executors.newFixedThreadPool(1);
|
executor = Executors.newFixedThreadPool(1);
|
||||||
|
|
||||||
InstrumentationRegistry.getInstrumentation()
|
InstrumentationRegistry.getInstrumentation()
|
||||||
.runOnMainSync(
|
.runOnMainSync(
|
||||||
() -> {
|
() -> {
|
||||||
// Initialize AudioManager on the main thread to workaround b/78617702 that
|
// Initialize AudioManager on the main thread to workaround that
|
||||||
// audio focus listener is called on the thread where the AudioManager was
|
// audio focus listener is called on the thread where the AudioManager was
|
||||||
// originally initialized.
|
// originally initialized. [Internal: b/78617702]
|
||||||
// Without posting this, audio focus listeners wouldn't be called because the
|
// Without posting this, audio focus listeners wouldn't be called because the
|
||||||
// listeners would be posted to the test thread (here) where it waits until the
|
// listeners would be posted to the test thread (here) where it waits until the
|
||||||
// tests are finished.
|
// tests are finished.
|
||||||
@ -75,8 +82,7 @@ import org.junit.rules.ExternalResource;
|
|||||||
.setLooper(Looper.myLooper())
|
.setLooper(Looper.myLooper())
|
||||||
.setMediaSourceFactory(new DefaultMediaSourceFactory(dataSourceFactory, null))
|
.setMediaSourceFactory(new DefaultMediaSourceFactory(dataSourceFactory, null))
|
||||||
.build();
|
.build();
|
||||||
sessionPlayerConnector =
|
sessionPlayerConnector = new SessionPlayerConnector(exoPlayer);
|
||||||
new SessionPlayerConnector(exoPlayer, new DefaultMediaItemConverter());
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.google.android.exoplayer2.ext.media2;
|
package com.google.android.exoplayer2.ext.media2;
|
||||||
|
|
||||||
import static com.google.android.exoplayer2.ext.media2.TestUtils.assertPlayerResultSuccess;
|
import static com.google.android.exoplayer2.ext.media2.TestUtils.assertPlayerResultSuccess;
|
||||||
@ -29,6 +28,7 @@ import androidx.annotation.NonNull;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.core.content.ContextCompat;
|
import androidx.core.content.ContextCompat;
|
||||||
import androidx.media2.common.MediaItem;
|
import androidx.media2.common.MediaItem;
|
||||||
|
import androidx.media2.common.MediaMetadata;
|
||||||
import androidx.media2.common.Rating;
|
import androidx.media2.common.Rating;
|
||||||
import androidx.media2.common.SessionPlayer;
|
import androidx.media2.common.SessionPlayer;
|
||||||
import androidx.media2.common.UriMediaItem;
|
import androidx.media2.common.UriMediaItem;
|
||||||
@ -136,7 +136,7 @@ public class SessionCallbackBuilderTest {
|
|||||||
SessionResult.RESULT_ERROR_BAD_VALUE)
|
SessionResult.RESULT_ERROR_BAD_VALUE)
|
||||||
.setRewindIncrementMs(testRewindIncrementMs)
|
.setRewindIncrementMs(testRewindIncrementMs)
|
||||||
.setFastForwardIncrementMs(testFastForwardIncrementMs)
|
.setFastForwardIncrementMs(testFastForwardIncrementMs)
|
||||||
.setMediaItemProvider(new SessionCallbackBuilder.DefaultMediaItemProvider())
|
.setMediaItemProvider(new SessionCallbackBuilder.MediaIdMediaItemProvider())
|
||||||
.build())) {
|
.build())) {
|
||||||
assertPlayerResultSuccess(sessionPlayerConnector.setMediaItem(TestUtils.createMediaItem()));
|
assertPlayerResultSuccess(sessionPlayerConnector.setMediaItem(TestUtils.createMediaItem()));
|
||||||
assertPlayerResultSuccess(sessionPlayerConnector.prepare());
|
assertPlayerResultSuccess(sessionPlayerConnector.prepare());
|
||||||
@ -179,7 +179,7 @@ public class SessionCallbackBuilderTest {
|
|||||||
SessionResult.RESULT_ERROR_BAD_VALUE)
|
SessionResult.RESULT_ERROR_BAD_VALUE)
|
||||||
.setRewindIncrementMs(testRewindIncrementMs)
|
.setRewindIncrementMs(testRewindIncrementMs)
|
||||||
.setFastForwardIncrementMs(testFastForwardIncrementMs)
|
.setFastForwardIncrementMs(testFastForwardIncrementMs)
|
||||||
.setMediaItemProvider(new SessionCallbackBuilder.DefaultMediaItemProvider())
|
.setMediaItemProvider(new SessionCallbackBuilder.MediaIdMediaItemProvider())
|
||||||
.build())) {
|
.build())) {
|
||||||
|
|
||||||
assertPlayerResultSuccess(sessionPlayerConnector.setPlaylist(testPlaylist, null));
|
assertPlayerResultSuccess(sessionPlayerConnector.setPlaylist(testPlaylist, null));
|
||||||
@ -455,13 +455,13 @@ public class SessionCallbackBuilderTest {
|
|||||||
Uri testMediaUri = RawResourceDataSource.buildRawResourceUri(R.raw.audio);
|
Uri testMediaUri = RawResourceDataSource.buildRawResourceUri(R.raw.audio);
|
||||||
|
|
||||||
CountDownLatch providerLatch = new CountDownLatch(1);
|
CountDownLatch providerLatch = new CountDownLatch(1);
|
||||||
SessionCallbackBuilder.DefaultMediaItemProvider defaultMediaItemProvider =
|
SessionCallbackBuilder.MediaIdMediaItemProvider mediaIdMediaItemProvider =
|
||||||
new SessionCallbackBuilder.DefaultMediaItemProvider();
|
new SessionCallbackBuilder.MediaIdMediaItemProvider();
|
||||||
SessionCallbackBuilder.MediaItemProvider provider =
|
SessionCallbackBuilder.MediaItemProvider provider =
|
||||||
(session, controllerInfo, mediaId) -> {
|
(session, controllerInfo, mediaId) -> {
|
||||||
assertThat(mediaId).isEqualTo(testMediaUri.toString());
|
assertThat(mediaId).isEqualTo(testMediaUri.toString());
|
||||||
providerLatch.countDown();
|
providerLatch.countDown();
|
||||||
return defaultMediaItemProvider.onCreateMediaItem(session, controllerInfo, mediaId);
|
return mediaIdMediaItemProvider.onCreateMediaItem(session, controllerInfo, mediaId);
|
||||||
};
|
};
|
||||||
|
|
||||||
CountDownLatch currentMediaItemChangedLatch = new CountDownLatch(1);
|
CountDownLatch currentMediaItemChangedLatch = new CountDownLatch(1);
|
||||||
@ -471,7 +471,9 @@ public class SessionCallbackBuilderTest {
|
|||||||
@Override
|
@Override
|
||||||
public void onCurrentMediaItemChanged(
|
public void onCurrentMediaItemChanged(
|
||||||
@NonNull SessionPlayer player, @NonNull MediaItem item) {
|
@NonNull SessionPlayer player, @NonNull MediaItem item) {
|
||||||
assertThat(((UriMediaItem) item).getUri()).isEqualTo(testMediaUri);
|
MediaMetadata metadata = item.getMetadata();
|
||||||
|
assertThat(metadata.getString(MediaMetadata.METADATA_KEY_MEDIA_ID))
|
||||||
|
.isEqualTo(testMediaUri.toString());
|
||||||
currentMediaItemChangedLatch.countDown();
|
currentMediaItemChangedLatch.countDown();
|
||||||
}
|
}
|
||||||
});
|
});
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.google.android.exoplayer2.ext.media2;
|
package com.google.android.exoplayer2.ext.media2;
|
||||||
|
|
||||||
import static androidx.media2.common.SessionPlayer.PLAYER_STATE_PAUSED;
|
import static androidx.media2.common.SessionPlayer.PLAYER_STATE_PAUSED;
|
||||||
@ -33,6 +32,7 @@ import android.os.Build.VERSION_CODES;
|
|||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import androidx.annotation.NonNull;
|
import androidx.annotation.NonNull;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
|
import androidx.core.util.ObjectsCompat;
|
||||||
import androidx.media.AudioAttributesCompat;
|
import androidx.media.AudioAttributesCompat;
|
||||||
import androidx.media2.common.MediaItem;
|
import androidx.media2.common.MediaItem;
|
||||||
import androidx.media2.common.MediaMetadata;
|
import androidx.media2.common.MediaMetadata;
|
||||||
@ -61,6 +61,7 @@ import java.util.concurrent.CopyOnWriteArrayList;
|
|||||||
import java.util.concurrent.CountDownLatch;
|
import java.util.concurrent.CountDownLatch;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import java.util.concurrent.atomic.AtomicLong;
|
import java.util.concurrent.atomic.AtomicLong;
|
||||||
|
import java.util.concurrent.atomic.AtomicReference;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Rule;
|
import org.junit.Rule;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
@ -273,17 +274,17 @@ public class SessionPlayerConnectorTest {
|
|||||||
@Test
|
@Test
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT)
|
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT)
|
||||||
public void getCurrentPosition_whenIdleState_returnsUnknownTime() {
|
public void getCurrentPosition_whenIdleState_returnsDefaultPosition() {
|
||||||
assertThat(sessionPlayerConnector.getPlayerState()).isEqualTo(SessionPlayer.PLAYER_STATE_IDLE);
|
assertThat(sessionPlayerConnector.getPlayerState()).isEqualTo(SessionPlayer.PLAYER_STATE_IDLE);
|
||||||
assertThat(sessionPlayerConnector.getCurrentPosition()).isEqualTo(SessionPlayer.UNKNOWN_TIME);
|
assertThat(sessionPlayerConnector.getCurrentPosition()).isEqualTo(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@SmallTest
|
@SmallTest
|
||||||
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT)
|
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT)
|
||||||
public void getBufferedPosition_whenIdleState_returnsUnknownTime() {
|
public void getBufferedPosition_whenIdleState_returnsDefaultPosition() {
|
||||||
assertThat(sessionPlayerConnector.getPlayerState()).isEqualTo(SessionPlayer.PLAYER_STATE_IDLE);
|
assertThat(sessionPlayerConnector.getPlayerState()).isEqualTo(SessionPlayer.PLAYER_STATE_IDLE);
|
||||||
assertThat(sessionPlayerConnector.getBufferedPosition()).isEqualTo(SessionPlayer.UNKNOWN_TIME);
|
assertThat(sessionPlayerConnector.getBufferedPosition()).isEqualTo(0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -795,6 +796,73 @@ public class SessionPlayerConnectorTest {
|
|||||||
assertThat(onPlaylistChangedLatch.getCount()).isEqualTo(1);
|
assertThat(onPlaylistChangedLatch.getCount()).isEqualTo(1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@LargeTest
|
||||||
|
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT)
|
||||||
|
public void setPlaylist_byUnderlyingPlayerBeforePrepare_notifiesOnPlaylistChanged()
|
||||||
|
throws Exception {
|
||||||
|
List<MediaItem> playlistToSessionPlayer = TestUtils.createPlaylist(2);
|
||||||
|
List<MediaItem> playlistToExoPlayer = TestUtils.createPlaylist(4);
|
||||||
|
DefaultMediaItemConverter converter = new DefaultMediaItemConverter();
|
||||||
|
List<com.google.android.exoplayer2.MediaItem> exoMediaItems = new ArrayList<>();
|
||||||
|
for (MediaItem mediaItem : playlistToExoPlayer) {
|
||||||
|
exoMediaItems.add(converter.convertToExoPlayerMediaItem(mediaItem));
|
||||||
|
}
|
||||||
|
|
||||||
|
CountDownLatch onPlaylistChangedLatch = new CountDownLatch(1);
|
||||||
|
sessionPlayerConnector.registerPlayerCallback(
|
||||||
|
executor,
|
||||||
|
new SessionPlayer.PlayerCallback() {
|
||||||
|
@Override
|
||||||
|
public void onPlaylistChanged(
|
||||||
|
@NonNull SessionPlayer player,
|
||||||
|
@Nullable List<MediaItem> list,
|
||||||
|
@Nullable MediaMetadata metadata) {
|
||||||
|
if (ObjectsCompat.equals(list, playlistToExoPlayer)) {
|
||||||
|
onPlaylistChangedLatch.countDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sessionPlayerConnector.setPlaylist(playlistToSessionPlayer, /* metadata= */ null);
|
||||||
|
InstrumentationRegistry.getInstrumentation()
|
||||||
|
.runOnMainSync(() -> playerTestRule.getSimpleExoPlayer().setMediaItems(exoMediaItems));
|
||||||
|
assertThat(onPlaylistChangedLatch.await(PLAYLIST_CHANGE_WAIT_TIME_MS, MILLISECONDS)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@LargeTest
|
||||||
|
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT)
|
||||||
|
public void setPlaylist_byUnderlyingPlayerAfterPrepare_notifiesOnPlaylistChanged()
|
||||||
|
throws Exception {
|
||||||
|
List<MediaItem> playlistToSessionPlayer = TestUtils.createPlaylist(2);
|
||||||
|
List<MediaItem> playlistToExoPlayer = TestUtils.createPlaylist(4);
|
||||||
|
DefaultMediaItemConverter converter = new DefaultMediaItemConverter();
|
||||||
|
List<com.google.android.exoplayer2.MediaItem> exoMediaItems = new ArrayList<>();
|
||||||
|
for (MediaItem mediaItem : playlistToExoPlayer) {
|
||||||
|
exoMediaItems.add(converter.convertToExoPlayerMediaItem(mediaItem));
|
||||||
|
}
|
||||||
|
|
||||||
|
CountDownLatch onPlaylistChangedLatch = new CountDownLatch(1);
|
||||||
|
sessionPlayerConnector.registerPlayerCallback(
|
||||||
|
executor,
|
||||||
|
new SessionPlayer.PlayerCallback() {
|
||||||
|
@Override
|
||||||
|
public void onPlaylistChanged(
|
||||||
|
@NonNull SessionPlayer player,
|
||||||
|
@Nullable List<MediaItem> list,
|
||||||
|
@Nullable MediaMetadata metadata) {
|
||||||
|
if (ObjectsCompat.equals(list, playlistToExoPlayer)) {
|
||||||
|
onPlaylistChangedLatch.countDown();
|
||||||
|
}
|
||||||
|
}
|
||||||
|
});
|
||||||
|
sessionPlayerConnector.prepare();
|
||||||
|
sessionPlayerConnector.setPlaylist(playlistToSessionPlayer, /* metadata= */ null);
|
||||||
|
InstrumentationRegistry.getInstrumentation()
|
||||||
|
.runOnMainSync(() -> playerTestRule.getSimpleExoPlayer().setMediaItems(exoMediaItems));
|
||||||
|
assertThat(onPlaylistChangedLatch.await(PLAYLIST_CHANGE_WAIT_TIME_MS, MILLISECONDS)).isTrue();
|
||||||
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@LargeTest
|
@LargeTest
|
||||||
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT)
|
@SdkSuppress(minSdkVersion = Build.VERSION_CODES.KITKAT)
|
||||||
@ -862,7 +930,7 @@ public class SessionPlayerConnectorTest {
|
|||||||
|
|
||||||
CountDownLatch onPlaylistChangedLatch = new CountDownLatch(2);
|
CountDownLatch onPlaylistChangedLatch = new CountDownLatch(2);
|
||||||
int replaceIndex = 2;
|
int replaceIndex = 2;
|
||||||
MediaItem newMediaItem = TestUtils.createMediaItem();
|
MediaItem newMediaItem = TestUtils.createMediaItem(R.raw.video_big_buck_bunny);
|
||||||
playlist.set(replaceIndex, newMediaItem);
|
playlist.set(replaceIndex, newMediaItem);
|
||||||
sessionPlayerConnector.registerPlayerCallback(
|
sessionPlayerConnector.registerPlayerCallback(
|
||||||
executor,
|
executor,
|
||||||
@ -1185,6 +1253,32 @@ public class SessionPlayerConnectorTest {
|
|||||||
assertThat(sessionPlayerConnector.getPlayerState()).isEqualTo(PLAYER_STATE_PLAYING);
|
assertThat(sessionPlayerConnector.getPlayerState()).isEqualTo(PLAYER_STATE_PLAYING);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
@LargeTest
|
||||||
|
public void getPlaylist_returnsPlaylistInUnderlyingPlayer() {
|
||||||
|
List<MediaItem> playlistToExoPlayer = TestUtils.createPlaylist(4);
|
||||||
|
DefaultMediaItemConverter converter = new DefaultMediaItemConverter();
|
||||||
|
List<com.google.android.exoplayer2.MediaItem> exoMediaItems = new ArrayList<>();
|
||||||
|
for (MediaItem mediaItem : playlistToExoPlayer) {
|
||||||
|
exoMediaItems.add(converter.convertToExoPlayerMediaItem(mediaItem));
|
||||||
|
}
|
||||||
|
|
||||||
|
AtomicReference<List<MediaItem>> playlistFromSessionPlayer = new AtomicReference<>();
|
||||||
|
InstrumentationRegistry.getInstrumentation()
|
||||||
|
.runOnMainSync(
|
||||||
|
() -> {
|
||||||
|
SimpleExoPlayer simpleExoPlayer = playerTestRule.getSimpleExoPlayer();
|
||||||
|
simpleExoPlayer.setMediaItems(exoMediaItems);
|
||||||
|
|
||||||
|
try (SessionPlayerConnector sessionPlayer =
|
||||||
|
new SessionPlayerConnector(simpleExoPlayer)) {
|
||||||
|
List<MediaItem> playlist = sessionPlayer.getPlaylist();
|
||||||
|
playlistFromSessionPlayer.set(playlist);
|
||||||
|
}
|
||||||
|
});
|
||||||
|
assertThat(playlistFromSessionPlayer.get()).isEqualTo(playlistToExoPlayer);
|
||||||
|
}
|
||||||
|
|
||||||
private class PlayerCallbackForPlaylist extends SessionPlayer.PlayerCallback {
|
private class PlayerCallbackForPlaylist extends SessionPlayer.PlayerCallback {
|
||||||
private List<MediaItem> playlist;
|
private List<MediaItem> playlist;
|
||||||
private CountDownLatch onCurrentMediaItemChangedLatch;
|
private CountDownLatch onCurrentMediaItemChangedLatch;
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.google.android.exoplayer2.ext.media2;
|
package com.google.android.exoplayer2.ext.media2;
|
||||||
|
|
||||||
import static androidx.media2.common.SessionPlayer.PlayerResult.RESULT_SUCCESS;
|
import static androidx.media2.common.SessionPlayer.PlayerResult.RESULT_SUCCESS;
|
||||||
|
@ -13,79 +13,125 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.google.android.exoplayer2.ext.media2;
|
package com.google.android.exoplayer2.ext.media2;
|
||||||
|
|
||||||
|
import static androidx.media2.common.MediaMetadata.METADATA_KEY_DISPLAY_TITLE;
|
||||||
|
import static androidx.media2.common.MediaMetadata.METADATA_KEY_MEDIA_ID;
|
||||||
|
import static androidx.media2.common.MediaMetadata.METADATA_KEY_MEDIA_URI;
|
||||||
|
import static androidx.media2.common.MediaMetadata.METADATA_KEY_TITLE;
|
||||||
|
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.media2.common.CallbackMediaItem;
|
import androidx.media2.common.CallbackMediaItem;
|
||||||
import androidx.media2.common.FileMediaItem;
|
import androidx.media2.common.FileMediaItem;
|
||||||
import androidx.media2.common.MediaMetadata;
|
|
||||||
import androidx.media2.common.UriMediaItem;
|
import androidx.media2.common.UriMediaItem;
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.MediaItem;
|
import com.google.android.exoplayer2.MediaItem;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
|
||||||
/** Default implementation of {@link MediaItemConverter}. */
|
/**
|
||||||
public final class DefaultMediaItemConverter implements MediaItemConverter {
|
* Default implementation of {@link MediaItemConverter}.
|
||||||
|
*
|
||||||
|
* <p>Note that {@link #getMetadata} can be overridden to fill in additional metadata when
|
||||||
|
* converting {@link MediaItem ExoPlayer MediaItems} to their AndroidX equivalents.
|
||||||
|
*/
|
||||||
|
public class DefaultMediaItemConverter implements MediaItemConverter {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public MediaItem convertToExoPlayerMediaItem(androidx.media2.common.MediaItem androidXMediaItem) {
|
public MediaItem convertToExoPlayerMediaItem(androidx.media2.common.MediaItem media2MediaItem) {
|
||||||
if (androidXMediaItem instanceof FileMediaItem) {
|
if (media2MediaItem instanceof FileMediaItem) {
|
||||||
throw new IllegalStateException("FileMediaItem isn't supported");
|
throw new IllegalStateException("FileMediaItem isn't supported");
|
||||||
}
|
}
|
||||||
if (androidXMediaItem instanceof CallbackMediaItem) {
|
if (media2MediaItem instanceof CallbackMediaItem) {
|
||||||
throw new IllegalStateException("CallbackMediaItem isn't supported");
|
throw new IllegalStateException("CallbackMediaItem isn't supported");
|
||||||
}
|
}
|
||||||
|
|
||||||
MediaItem.Builder exoplayerMediaItemBuilder = new MediaItem.Builder();
|
|
||||||
|
|
||||||
// Set mediaItem as tag for creating MediaSource via MediaSourceFactory methods.
|
|
||||||
exoplayerMediaItemBuilder.setTag(androidXMediaItem);
|
|
||||||
|
|
||||||
// Media ID or URI must be present. Get it from androidx.MediaItem if possible.
|
|
||||||
@Nullable Uri uri = null;
|
@Nullable Uri uri = null;
|
||||||
@Nullable String mediaId = null;
|
@Nullable String mediaId = null;
|
||||||
if (androidXMediaItem instanceof UriMediaItem) {
|
@Nullable String title = null;
|
||||||
UriMediaItem uriMediaItem = (UriMediaItem) androidXMediaItem;
|
if (media2MediaItem instanceof UriMediaItem) {
|
||||||
|
UriMediaItem uriMediaItem = (UriMediaItem) media2MediaItem;
|
||||||
uri = uriMediaItem.getUri();
|
uri = uriMediaItem.getUri();
|
||||||
}
|
}
|
||||||
@Nullable MediaMetadata metadata = androidXMediaItem.getMetadata();
|
@Nullable androidx.media2.common.MediaMetadata metadata = media2MediaItem.getMetadata();
|
||||||
if (metadata != null) {
|
if (metadata != null) {
|
||||||
mediaId = metadata.getString(MediaMetadata.METADATA_KEY_MEDIA_ID);
|
@Nullable String uriString = metadata.getString(METADATA_KEY_MEDIA_URI);
|
||||||
@Nullable String uriString = metadata.getString(MediaMetadata.METADATA_KEY_MEDIA_URI);
|
mediaId = metadata.getString(METADATA_KEY_MEDIA_ID);
|
||||||
if (uri == null && uriString != null) {
|
if (uri == null) {
|
||||||
|
if (uriString != null) {
|
||||||
uri = Uri.parse(uriString);
|
uri = Uri.parse(uriString);
|
||||||
|
} else if (mediaId != null) {
|
||||||
|
uri = Uri.parse("media2:///" + mediaId);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
title = metadata.getString(METADATA_KEY_DISPLAY_TITLE);
|
||||||
|
if (title == null) {
|
||||||
|
title = metadata.getString(METADATA_KEY_TITLE);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
if (uri == null) {
|
if (uri == null) {
|
||||||
// Generate a Uri to make it non-null. If not, tag will be ignored.
|
// Generate a URI to make it non-null. If not, then the tag passed to setTag will be ignored.
|
||||||
uri = Uri.parse("exoplayer://" + androidXMediaItem.hashCode());
|
uri = Uri.parse("media2:///");
|
||||||
}
|
}
|
||||||
exoplayerMediaItemBuilder.setUri(uri);
|
long startPositionMs = media2MediaItem.getStartPosition();
|
||||||
exoplayerMediaItemBuilder.setMediaId(mediaId);
|
if (startPositionMs == androidx.media2.common.MediaItem.POSITION_UNKNOWN) {
|
||||||
|
startPositionMs = 0;
|
||||||
if (androidXMediaItem.getStartPosition() != androidx.media2.common.MediaItem.POSITION_UNKNOWN) {
|
|
||||||
exoplayerMediaItemBuilder.setClipStartPositionMs(androidXMediaItem.getStartPosition());
|
|
||||||
exoplayerMediaItemBuilder.setClipRelativeToDefaultPosition(true);
|
|
||||||
}
|
}
|
||||||
if (androidXMediaItem.getEndPosition() != androidx.media2.common.MediaItem.POSITION_UNKNOWN) {
|
long endPositionMs = media2MediaItem.getEndPosition();
|
||||||
exoplayerMediaItemBuilder.setClipEndPositionMs(androidXMediaItem.getEndPosition());
|
if (endPositionMs == androidx.media2.common.MediaItem.POSITION_UNKNOWN) {
|
||||||
exoplayerMediaItemBuilder.setClipRelativeToDefaultPosition(true);
|
endPositionMs = C.TIME_END_OF_SOURCE;
|
||||||
}
|
}
|
||||||
|
|
||||||
return exoplayerMediaItemBuilder.build();
|
return new MediaItem.Builder()
|
||||||
|
.setUri(uri)
|
||||||
|
.setMediaId(mediaId)
|
||||||
|
.setMediaMetadata(
|
||||||
|
new com.google.android.exoplayer2.MediaMetadata.Builder().setTitle(title).build())
|
||||||
|
.setTag(media2MediaItem)
|
||||||
|
.setClipStartPositionMs(startPositionMs)
|
||||||
|
.setClipEndPositionMs(endPositionMs)
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public androidx.media2.common.MediaItem convertToAndroidXMediaItem(MediaItem exoplayerMediaItem) {
|
public androidx.media2.common.MediaItem convertToMedia2MediaItem(MediaItem exoPlayerMediaItem) {
|
||||||
Assertions.checkNotNull(exoplayerMediaItem);
|
Assertions.checkNotNull(exoPlayerMediaItem);
|
||||||
MediaItem.PlaybackProperties playbackProperties =
|
MediaItem.PlaybackProperties playbackProperties =
|
||||||
Assertions.checkNotNull(exoplayerMediaItem.playbackProperties);
|
Assertions.checkNotNull(exoPlayerMediaItem.playbackProperties);
|
||||||
|
|
||||||
@Nullable Object tag = playbackProperties.tag;
|
@Nullable Object tag = playbackProperties.tag;
|
||||||
if (!(tag instanceof androidx.media2.common.MediaItem)) {
|
if (tag instanceof androidx.media2.common.MediaItem) {
|
||||||
throw new IllegalStateException(
|
|
||||||
"MediaItem tag must be an instance of androidx.media2.common.MediaItem");
|
|
||||||
}
|
|
||||||
return (androidx.media2.common.MediaItem) tag;
|
return (androidx.media2.common.MediaItem) tag;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
androidx.media2.common.MediaMetadata metadata = getMetadata(exoPlayerMediaItem);
|
||||||
|
long startPositionMs = exoPlayerMediaItem.clippingProperties.startPositionMs;
|
||||||
|
long endPositionMs = exoPlayerMediaItem.clippingProperties.endPositionMs;
|
||||||
|
if (endPositionMs == C.TIME_END_OF_SOURCE) {
|
||||||
|
endPositionMs = androidx.media2.common.MediaItem.POSITION_UNKNOWN;
|
||||||
|
}
|
||||||
|
|
||||||
|
return new androidx.media2.common.MediaItem.Builder()
|
||||||
|
.setMetadata(metadata)
|
||||||
|
.setStartPosition(startPositionMs)
|
||||||
|
.setEndPosition(endPositionMs)
|
||||||
|
.build();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Returns a {@link androidx.media2.common.MediaMetadata} corresponding to the given {@link
|
||||||
|
* MediaItem ExoPlayer MediaItem}.
|
||||||
|
*/
|
||||||
|
protected androidx.media2.common.MediaMetadata getMetadata(MediaItem exoPlayerMediaItem) {
|
||||||
|
@Nullable String title = exoPlayerMediaItem.mediaMetadata.title;
|
||||||
|
|
||||||
|
androidx.media2.common.MediaMetadata.Builder metadataBuilder =
|
||||||
|
new androidx.media2.common.MediaMetadata.Builder()
|
||||||
|
.putString(METADATA_KEY_MEDIA_ID, exoPlayerMediaItem.mediaId);
|
||||||
|
if (title != null) {
|
||||||
|
metadataBuilder.putString(METADATA_KEY_TITLE, title);
|
||||||
|
metadataBuilder.putString(METADATA_KEY_DISPLAY_TITLE, title);
|
||||||
|
}
|
||||||
|
return metadataBuilder.build();
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -13,25 +13,24 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.google.android.exoplayer2.ext.media2;
|
package com.google.android.exoplayer2.ext.media2;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.MediaItem;
|
import com.google.android.exoplayer2.MediaItem;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converter for between {@link MediaItem AndroidX MediaItem} and {@link
|
* Converts between {@link androidx.media2.common.MediaItem Media2 MediaItem} and {@link MediaItem
|
||||||
* com.google.android.exoplayer2.MediaItem ExoPlayer MediaItem}.
|
* ExoPlayer MediaItem}.
|
||||||
*/
|
*/
|
||||||
public interface MediaItemConverter {
|
public interface MediaItemConverter {
|
||||||
/**
|
/**
|
||||||
* Converts {@link androidx.media2.common.MediaItem AndroidX MediaItem} to {@link MediaItem
|
* Converts an {@link androidx.media2.common.MediaItem Media2 MediaItem} to an {@link MediaItem
|
||||||
* ExoPlayer MediaItem}.
|
* ExoPlayer MediaItem}.
|
||||||
*/
|
*/
|
||||||
MediaItem convertToExoPlayerMediaItem(androidx.media2.common.MediaItem androidXMediaItem);
|
MediaItem convertToExoPlayerMediaItem(androidx.media2.common.MediaItem media2MediaItem);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Converts {@link MediaItem ExoPlayer MediaItem} to {@link androidx.media2.common.MediaItem
|
* Converts an {@link MediaItem ExoPlayer MediaItem} to an {@link androidx.media2.common.MediaItem
|
||||||
* AndroidX MediaItem}.
|
* Media2 MediaItem}.
|
||||||
*/
|
*/
|
||||||
androidx.media2.common.MediaItem convertToAndroidXMediaItem(MediaItem exoplayerMediaItem);
|
androidx.media2.common.MediaItem convertToMedia2MediaItem(MediaItem exoPlayerMediaItem);
|
||||||
}
|
}
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.google.android.exoplayer2.ext.media2;
|
package com.google.android.exoplayer2.ext.media2;
|
||||||
|
|
||||||
import androidx.annotation.IntRange;
|
import androidx.annotation.IntRange;
|
||||||
@ -27,6 +26,7 @@ import com.google.android.exoplayer2.C;
|
|||||||
import com.google.android.exoplayer2.ControlDispatcher;
|
import com.google.android.exoplayer2.ControlDispatcher;
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
import com.google.android.exoplayer2.MediaItem;
|
import com.google.android.exoplayer2.MediaItem;
|
||||||
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
import com.google.android.exoplayer2.Player;
|
import com.google.android.exoplayer2.Player;
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.audio.AudioAttributes;
|
import com.google.android.exoplayer2.audio.AudioAttributes;
|
||||||
@ -56,44 +56,44 @@ import java.util.List;
|
|||||||
void onPlayerStateChanged(/* @SessionPlayer.PlayerState */ int playerState);
|
void onPlayerStateChanged(/* @SessionPlayer.PlayerState */ int playerState);
|
||||||
|
|
||||||
/** Called when the player is prepared. */
|
/** Called when the player is prepared. */
|
||||||
void onPrepared(androidx.media2.common.MediaItem androidXMediaItem, int bufferingPercentage);
|
void onPrepared(androidx.media2.common.MediaItem media2MediaItem, int bufferingPercentage);
|
||||||
|
|
||||||
/** Called when a seek request has completed. */
|
/** Called when a seek request has completed. */
|
||||||
void onSeekCompleted();
|
void onSeekCompleted();
|
||||||
|
|
||||||
/** Called when the player rebuffers. */
|
/** Called when the player rebuffers. */
|
||||||
void onBufferingStarted(androidx.media2.common.MediaItem androidXMediaItem);
|
void onBufferingStarted(androidx.media2.common.MediaItem media2MediaItem);
|
||||||
|
|
||||||
/** Called when the player becomes ready again after rebuffering. */
|
/** Called when the player becomes ready again after rebuffering. */
|
||||||
void onBufferingEnded(
|
void onBufferingEnded(
|
||||||
androidx.media2.common.MediaItem androidXMediaItem, int bufferingPercentage);
|
androidx.media2.common.MediaItem media2MediaItem, int bufferingPercentage);
|
||||||
|
|
||||||
/** Called periodically with the player's buffered position as a percentage. */
|
/** Called periodically with the player's buffered position as a percentage. */
|
||||||
void onBufferingUpdate(
|
void onBufferingUpdate(
|
||||||
androidx.media2.common.MediaItem androidXMediaItem, int bufferingPercentage);
|
androidx.media2.common.MediaItem media2MediaItem, int bufferingPercentage);
|
||||||
|
|
||||||
/** Called when current media item is changed. */
|
/** Called when current media item is changed. */
|
||||||
void onCurrentMediaItemChanged(androidx.media2.common.MediaItem androidXMediaItem);
|
void onCurrentMediaItemChanged(androidx.media2.common.MediaItem media2MediaItem);
|
||||||
|
|
||||||
/** Called when playback of the item list has ended. */
|
/** Called when playback of the item list has ended. */
|
||||||
void onPlaybackEnded();
|
void onPlaybackEnded();
|
||||||
|
|
||||||
/** Called when the player encounters an error. */
|
/** Called when the player encounters an error. */
|
||||||
void onError(@Nullable androidx.media2.common.MediaItem androidXMediaItem);
|
void onError(@Nullable androidx.media2.common.MediaItem media2MediaItem);
|
||||||
|
|
||||||
/** Called when the playlist is changed */
|
/** Called when the playlist is changed. */
|
||||||
void onPlaylistChanged();
|
void onPlaylistChanged();
|
||||||
|
|
||||||
/** Called when the shuffle mode is changed */
|
/** Called when the shuffle mode is changed. */
|
||||||
void onShuffleModeChanged(int shuffleMode);
|
void onShuffleModeChanged(int shuffleMode);
|
||||||
|
|
||||||
/** Called when the repeat mode is changed */
|
/** Called when the repeat mode is changed. */
|
||||||
void onRepeatModeChanged(int repeatMode);
|
void onRepeatModeChanged(int repeatMode);
|
||||||
|
|
||||||
/** Called when the audio attributes is changed */
|
/** Called when the audio attributes is changed. */
|
||||||
void onAudioAttributesChanged(AudioAttributesCompat audioAttributes);
|
void onAudioAttributesChanged(AudioAttributesCompat audioAttributes);
|
||||||
|
|
||||||
/** Called when the playback speed is changed */
|
/** Called when the playback speed is changed. */
|
||||||
void onPlaybackSpeedChanged(float playbackSpeed);
|
void onPlaybackSpeedChanged(float playbackSpeed);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -108,14 +108,15 @@ import java.util.List;
|
|||||||
private final ControlDispatcher controlDispatcher;
|
private final ControlDispatcher controlDispatcher;
|
||||||
private final ComponentListener componentListener;
|
private final ComponentListener componentListener;
|
||||||
|
|
||||||
private final List<androidx.media2.common.MediaItem> cachedPlaylist;
|
|
||||||
@Nullable private MediaMetadata playlistMetadata;
|
@Nullable private MediaMetadata playlistMetadata;
|
||||||
private final List<MediaItem> cachedMediaItems;
|
|
||||||
|
// These should be only updated in TimelineChanges.
|
||||||
|
private final List<androidx.media2.common.MediaItem> media2Playlist;
|
||||||
|
private final List<MediaItem> exoPlayerPlaylist;
|
||||||
|
|
||||||
private boolean prepared;
|
private boolean prepared;
|
||||||
private boolean rebuffering;
|
private boolean rebuffering;
|
||||||
private int currentWindowIndex;
|
private int currentWindowIndex;
|
||||||
private boolean loggedUnexpectedTimelineChanges;
|
|
||||||
private boolean ignoreTimelineUpdates;
|
private boolean ignoreTimelineUpdates;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -146,79 +147,69 @@ import java.util.List;
|
|||||||
handler = new PlayerHandler(player.getApplicationLooper());
|
handler = new PlayerHandler(player.getApplicationLooper());
|
||||||
pollBufferRunnable = new PollBufferRunnable();
|
pollBufferRunnable = new PollBufferRunnable();
|
||||||
|
|
||||||
cachedPlaylist = new ArrayList<>();
|
media2Playlist = new ArrayList<>();
|
||||||
cachedMediaItems = new ArrayList<>();
|
exoPlayerPlaylist = new ArrayList<>();
|
||||||
currentWindowIndex = C.INDEX_UNSET;
|
currentWindowIndex = C.INDEX_UNSET;
|
||||||
|
|
||||||
|
prepared = player.getPlaybackState() != Player.STATE_IDLE;
|
||||||
|
rebuffering = player.getPlaybackState() == Player.STATE_BUFFERING;
|
||||||
|
|
||||||
|
updatePlaylist(player.getCurrentTimeline());
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean setMediaItem(androidx.media2.common.MediaItem androidXMediaItem) {
|
public boolean setMediaItem(androidx.media2.common.MediaItem media2MediaItem) {
|
||||||
return setPlaylist(Collections.singletonList(androidXMediaItem), /* metadata= */ null);
|
return setPlaylist(Collections.singletonList(media2MediaItem), /* metadata= */ null);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean setPlaylist(
|
public boolean setPlaylist(
|
||||||
List<androidx.media2.common.MediaItem> playlist, @Nullable MediaMetadata metadata) {
|
List<androidx.media2.common.MediaItem> playlist, @Nullable MediaMetadata metadata) {
|
||||||
// Check for duplication.
|
// Check for duplication.
|
||||||
for (int i = 0; i < playlist.size(); i++) {
|
for (int i = 0; i < playlist.size(); i++) {
|
||||||
androidx.media2.common.MediaItem androidXMediaItem = playlist.get(i);
|
androidx.media2.common.MediaItem media2MediaItem = playlist.get(i);
|
||||||
Assertions.checkArgument(playlist.indexOf(androidXMediaItem) == i);
|
Assertions.checkArgument(playlist.indexOf(media2MediaItem) == i);
|
||||||
}
|
}
|
||||||
|
|
||||||
this.cachedPlaylist.clear();
|
|
||||||
this.cachedPlaylist.addAll(playlist);
|
|
||||||
this.playlistMetadata = metadata;
|
this.playlistMetadata = metadata;
|
||||||
this.cachedMediaItems.clear();
|
List<MediaItem> exoPlayerMediaItems = new ArrayList<>();
|
||||||
List<MediaItem> exoplayerMediaItems = new ArrayList<>();
|
|
||||||
for (int i = 0; i < playlist.size(); i++) {
|
for (int i = 0; i < playlist.size(); i++) {
|
||||||
androidx.media2.common.MediaItem androidXMediaItem = playlist.get(i);
|
androidx.media2.common.MediaItem media2MediaItem = playlist.get(i);
|
||||||
MediaItem exoplayerMediaItem =
|
MediaItem exoPlayerMediaItem =
|
||||||
Assertions.checkNotNull(
|
Assertions.checkNotNull(mediaItemConverter.convertToExoPlayerMediaItem(media2MediaItem));
|
||||||
mediaItemConverter.convertToExoPlayerMediaItem(androidXMediaItem));
|
exoPlayerMediaItems.add(exoPlayerMediaItem);
|
||||||
exoplayerMediaItems.add(exoplayerMediaItem);
|
|
||||||
}
|
}
|
||||||
this.cachedMediaItems.addAll(exoplayerMediaItems);
|
|
||||||
|
|
||||||
player.setMediaItems(exoplayerMediaItems, /* resetPosition= */ true);
|
player.setMediaItems(exoPlayerMediaItems, /* resetPosition= */ true);
|
||||||
|
|
||||||
currentWindowIndex = getCurrentMediaItemIndex();
|
currentWindowIndex = getCurrentMediaItemIndex();
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean addPlaylistItem(int index, androidx.media2.common.MediaItem androidXMediaItem) {
|
public boolean addPlaylistItem(int index, androidx.media2.common.MediaItem media2MediaItem) {
|
||||||
Assertions.checkArgument(!cachedPlaylist.contains(androidXMediaItem));
|
Assertions.checkArgument(!media2Playlist.contains(media2MediaItem));
|
||||||
index = Util.constrainValue(index, 0, cachedPlaylist.size());
|
index = Util.constrainValue(index, 0, media2Playlist.size());
|
||||||
|
|
||||||
cachedPlaylist.add(index, androidXMediaItem);
|
MediaItem exoPlayerMediaItem =
|
||||||
MediaItem exoplayerMediaItem =
|
Assertions.checkNotNull(mediaItemConverter.convertToExoPlayerMediaItem(media2MediaItem));
|
||||||
Assertions.checkNotNull(mediaItemConverter.convertToExoPlayerMediaItem(androidXMediaItem));
|
player.addMediaItem(index, exoPlayerMediaItem);
|
||||||
cachedMediaItems.add(index, exoplayerMediaItem);
|
|
||||||
player.addMediaItem(index, exoplayerMediaItem);
|
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean removePlaylistItem(@IntRange(from = 0) int index) {
|
public boolean removePlaylistItem(@IntRange(from = 0) int index) {
|
||||||
androidx.media2.common.MediaItem androidXMediaItemToRemove = cachedPlaylist.remove(index);
|
|
||||||
releaseMediaItem(androidXMediaItemToRemove);
|
|
||||||
cachedMediaItems.remove(index);
|
|
||||||
player.removeMediaItem(index);
|
player.removeMediaItem(index);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean replacePlaylistItem(
|
public boolean replacePlaylistItem(int index, androidx.media2.common.MediaItem media2MediaItem) {
|
||||||
int index, androidx.media2.common.MediaItem androidXMediaItem) {
|
Assertions.checkArgument(!media2Playlist.contains(media2MediaItem));
|
||||||
Assertions.checkArgument(!cachedPlaylist.contains(androidXMediaItem));
|
index = Util.constrainValue(index, 0, media2Playlist.size());
|
||||||
index = Util.constrainValue(index, 0, cachedPlaylist.size());
|
|
||||||
|
|
||||||
androidx.media2.common.MediaItem androidXMediaItemToRemove = cachedPlaylist.get(index);
|
MediaItem exoPlayerMediaItemToAdd =
|
||||||
cachedPlaylist.set(index, androidXMediaItem);
|
Assertions.checkNotNull(mediaItemConverter.convertToExoPlayerMediaItem(media2MediaItem));
|
||||||
releaseMediaItem(androidXMediaItemToRemove);
|
|
||||||
MediaItem exoplayerMediaItemToAdd =
|
|
||||||
Assertions.checkNotNull(mediaItemConverter.convertToExoPlayerMediaItem(androidXMediaItem));
|
|
||||||
cachedMediaItems.set(index, exoplayerMediaItemToAdd);
|
|
||||||
|
|
||||||
ignoreTimelineUpdates = true;
|
ignoreTimelineUpdates = true;
|
||||||
player.removeMediaItem(index);
|
player.removeMediaItem(index);
|
||||||
ignoreTimelineUpdates = false;
|
ignoreTimelineUpdates = false;
|
||||||
player.addMediaItem(index, exoplayerMediaItemToAdd);
|
player.addMediaItem(index, exoPlayerMediaItemToAdd);
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -272,8 +263,8 @@ import java.util.List;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
public List<androidx.media2.common.MediaItem> getCachedPlaylist() {
|
public List<androidx.media2.common.MediaItem> getPlaylist() {
|
||||||
return new ArrayList<>(cachedPlaylist);
|
return new ArrayList<>(media2Playlist);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable
|
@Nullable
|
||||||
@ -290,7 +281,7 @@ import java.util.List;
|
|||||||
}
|
}
|
||||||
|
|
||||||
public int getCurrentMediaItemIndex() {
|
public int getCurrentMediaItemIndex() {
|
||||||
return cachedPlaylist.isEmpty() ? C.INDEX_UNSET : player.getCurrentWindowIndex();
|
return media2Playlist.isEmpty() ? C.INDEX_UNSET : player.getCurrentWindowIndex();
|
||||||
}
|
}
|
||||||
|
|
||||||
public int getPreviousMediaItemIndex() {
|
public int getPreviousMediaItemIndex() {
|
||||||
@ -304,7 +295,7 @@ import java.util.List;
|
|||||||
@Nullable
|
@Nullable
|
||||||
public androidx.media2.common.MediaItem getCurrentMediaItem() {
|
public androidx.media2.common.MediaItem getCurrentMediaItem() {
|
||||||
int index = getCurrentMediaItemIndex();
|
int index = getCurrentMediaItemIndex();
|
||||||
return (index != C.INDEX_UNSET) ? cachedPlaylist.get(index) : null;
|
return index == C.INDEX_UNSET ? null : media2Playlist.get(index);
|
||||||
}
|
}
|
||||||
|
|
||||||
public boolean prepare() {
|
public boolean prepare() {
|
||||||
@ -317,9 +308,9 @@ import java.util.List;
|
|||||||
|
|
||||||
public boolean play() {
|
public boolean play() {
|
||||||
if (player.getPlaybackState() == Player.STATE_ENDED) {
|
if (player.getPlaybackState() == Player.STATE_ENDED) {
|
||||||
int currentWindowIndex = getCurrentMediaItemIndex();
|
|
||||||
boolean seekHandled =
|
boolean seekHandled =
|
||||||
controlDispatcher.dispatchSeekTo(player, currentWindowIndex, /* positionMs= */ 0);
|
controlDispatcher.dispatchSeekTo(
|
||||||
|
player, player.getCurrentWindowIndex(), /* positionMs= */ 0);
|
||||||
if (!seekHandled) {
|
if (!seekHandled) {
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
@ -342,23 +333,19 @@ import java.util.List;
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean seekTo(long position) {
|
public boolean seekTo(long position) {
|
||||||
int currentWindowIndex = getCurrentMediaItemIndex();
|
return controlDispatcher.dispatchSeekTo(player, player.getCurrentWindowIndex(), position);
|
||||||
return controlDispatcher.dispatchSeekTo(player, currentWindowIndex, position);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getCurrentPosition() {
|
public long getCurrentPosition() {
|
||||||
Assertions.checkState(getState() != SessionPlayer.PLAYER_STATE_IDLE);
|
return player.getCurrentPosition();
|
||||||
return Math.max(0, player.getCurrentPosition());
|
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getDuration() {
|
public long getDuration() {
|
||||||
Assertions.checkState(getState() != SessionPlayer.PLAYER_STATE_IDLE);
|
|
||||||
long duration = player.getDuration();
|
long duration = player.getDuration();
|
||||||
return duration == C.TIME_UNSET ? -1 : duration;
|
return duration == C.TIME_UNSET ? SessionPlayer.UNKNOWN_TIME : duration;
|
||||||
}
|
}
|
||||||
|
|
||||||
public long getBufferedPosition() {
|
public long getBufferedPosition() {
|
||||||
Assertions.checkState(getState() != SessionPlayer.PLAYER_STATE_IDLE);
|
|
||||||
return player.getBufferedPosition();
|
return player.getBufferedPosition();
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -397,11 +384,11 @@ import java.util.List;
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void setPlaybackSpeed(float playbackSpeed) {
|
public void setPlaybackSpeed(float playbackSpeed) {
|
||||||
player.setPlaybackSpeed(playbackSpeed);
|
player.setPlaybackParameters(new PlaybackParameters(playbackSpeed));
|
||||||
}
|
}
|
||||||
|
|
||||||
public float getPlaybackSpeed() {
|
public float getPlaybackSpeed() {
|
||||||
return player.getPlaybackSpeed();
|
return player.getPlaybackParameters().speed;
|
||||||
}
|
}
|
||||||
|
|
||||||
public void reset() {
|
public void reset() {
|
||||||
@ -427,7 +414,7 @@ import java.util.List;
|
|||||||
}
|
}
|
||||||
|
|
||||||
public boolean canSkipToPlaylistItem() {
|
public boolean canSkipToPlaylistItem() {
|
||||||
@Nullable List<androidx.media2.common.MediaItem> playlist = getCachedPlaylist();
|
@Nullable List<androidx.media2.common.MediaItem> playlist = getPlaylist();
|
||||||
return playlist != null && playlist.size() > 1;
|
return playlist != null && playlist.size() > 1;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -497,57 +484,57 @@ import java.util.List;
|
|||||||
listener.onShuffleModeChanged(Utils.getShuffleMode(shuffleModeEnabled));
|
listener.onShuffleModeChanged(Utils.getShuffleMode(shuffleModeEnabled));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handlePlaybackSpeedChanged(float playbackSpeed) {
|
private void handlePlaybackParametersChanged(PlaybackParameters playbackParameters) {
|
||||||
listener.onPlaybackSpeedChanged(playbackSpeed);
|
listener.onPlaybackSpeedChanged(playbackParameters.speed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handleTimelineChanged(Timeline timeline) {
|
private void handleTimelineChanged(Timeline timeline) {
|
||||||
if (ignoreTimelineUpdates) {
|
if (ignoreTimelineUpdates) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
updateCachedPlaylistAndMediaItems(timeline);
|
if (!isExoPlayerMediaItemsChanged(timeline)) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
updatePlaylist(timeline);
|
||||||
listener.onPlaylistChanged();
|
listener.onPlaylistChanged();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Update cached playlist, if the ExoPlayer Player's Timeline is unexpectedly changed without
|
// Check whether Timeline is changed by media item changes or not
|
||||||
// using SessionPlayer interface.
|
private boolean isExoPlayerMediaItemsChanged(Timeline timeline) {
|
||||||
private void updateCachedPlaylistAndMediaItems(Timeline currentTimeline) {
|
if (exoPlayerPlaylist.size() != timeline.getWindowCount()) {
|
||||||
// Check whether ExoPlayer media items are the same as expected.
|
return true;
|
||||||
|
}
|
||||||
Timeline.Window window = new Timeline.Window();
|
Timeline.Window window = new Timeline.Window();
|
||||||
int windowCount = currentTimeline.getWindowCount();
|
int windowCount = timeline.getWindowCount();
|
||||||
for (int i = 0; i < windowCount; i++) {
|
for (int i = 0; i < windowCount; i++) {
|
||||||
currentTimeline.getWindow(i, window);
|
timeline.getWindow(i, window);
|
||||||
if (i >= cachedMediaItems.size()
|
if (!ObjectsCompat.equals(exoPlayerPlaylist.get(i), window.mediaItem)) {
|
||||||
|| !ObjectsCompat.equals(cachedMediaItems.get(i), window.mediaItem)) {
|
return true;
|
||||||
if (!loggedUnexpectedTimelineChanges) {
|
}
|
||||||
Log.w(TAG, "Timeline was unexpectedly changed. Playlist will be rebuilt.");
|
}
|
||||||
loggedUnexpectedTimelineChanges = true;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
androidx.media2.common.MediaItem oldAndroidXMediaItem = cachedPlaylist.get(i);
|
private void updatePlaylist(Timeline timeline) {
|
||||||
releaseMediaItem(oldAndroidXMediaItem);
|
List<androidx.media2.common.MediaItem> media2MediaItemToBeRemoved =
|
||||||
|
new ArrayList<>(media2Playlist);
|
||||||
|
media2Playlist.clear();
|
||||||
|
exoPlayerPlaylist.clear();
|
||||||
|
|
||||||
androidx.media2.common.MediaItem androidXMediaItem =
|
Timeline.Window window = new Timeline.Window();
|
||||||
Assertions.checkNotNull(
|
int windowCount = timeline.getWindowCount();
|
||||||
mediaItemConverter.convertToAndroidXMediaItem(window.mediaItem));
|
for (int i = 0; i < windowCount; i++) {
|
||||||
if (i < cachedMediaItems.size()) {
|
timeline.getWindow(i, window);
|
||||||
cachedMediaItems.set(i, window.mediaItem);
|
MediaItem exoPlayerMediaItem = window.mediaItem;
|
||||||
cachedPlaylist.set(i, androidXMediaItem);
|
androidx.media2.common.MediaItem media2MediaItem =
|
||||||
} else {
|
Assertions.checkNotNull(mediaItemConverter.convertToMedia2MediaItem(exoPlayerMediaItem));
|
||||||
cachedMediaItems.add(window.mediaItem);
|
exoPlayerPlaylist.add(exoPlayerMediaItem);
|
||||||
cachedPlaylist.add(androidXMediaItem);
|
media2Playlist.add(media2MediaItem);
|
||||||
}
|
media2MediaItemToBeRemoved.remove(media2MediaItem);
|
||||||
}
|
|
||||||
}
|
|
||||||
if (cachedMediaItems.size() > windowCount) {
|
|
||||||
if (!loggedUnexpectedTimelineChanges) {
|
|
||||||
Log.w(TAG, "Timeline was unexpectedly changed. Playlist will be rebuilt.");
|
|
||||||
loggedUnexpectedTimelineChanges = true;
|
|
||||||
}
|
|
||||||
while (cachedMediaItems.size() > windowCount) {
|
|
||||||
cachedMediaItems.remove(windowCount);
|
|
||||||
cachedPlaylist.remove(windowCount);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
for (androidx.media2.common.MediaItem item : media2MediaItemToBeRemoved) {
|
||||||
|
releaseMediaItem(item);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -556,35 +543,35 @@ import java.util.List;
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void updateBufferingAndScheduleNextPollBuffer() {
|
private void updateBufferingAndScheduleNextPollBuffer() {
|
||||||
androidx.media2.common.MediaItem androidXMediaItem =
|
androidx.media2.common.MediaItem media2MediaItem =
|
||||||
Assertions.checkNotNull(getCurrentMediaItem());
|
Assertions.checkNotNull(getCurrentMediaItem());
|
||||||
listener.onBufferingUpdate(androidXMediaItem, player.getBufferedPercentage());
|
listener.onBufferingUpdate(media2MediaItem, player.getBufferedPercentage());
|
||||||
handler.removeCallbacks(pollBufferRunnable);
|
handler.removeCallbacks(pollBufferRunnable);
|
||||||
handler.postDelayed(pollBufferRunnable, POLL_BUFFER_INTERVAL_MS);
|
handler.postDelayed(pollBufferRunnable, POLL_BUFFER_INTERVAL_MS);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void maybeNotifyBufferingEvents() {
|
private void maybeNotifyBufferingEvents() {
|
||||||
androidx.media2.common.MediaItem androidXMediaItem =
|
androidx.media2.common.MediaItem media2MediaItem =
|
||||||
Assertions.checkNotNull(getCurrentMediaItem());
|
Assertions.checkNotNull(getCurrentMediaItem());
|
||||||
if (prepared && !rebuffering) {
|
if (prepared && !rebuffering) {
|
||||||
rebuffering = true;
|
rebuffering = true;
|
||||||
listener.onBufferingStarted(androidXMediaItem);
|
listener.onBufferingStarted(media2MediaItem);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void maybeNotifyReadyEvents() {
|
private void maybeNotifyReadyEvents() {
|
||||||
androidx.media2.common.MediaItem androidXMediaItem =
|
androidx.media2.common.MediaItem media2MediaItem =
|
||||||
Assertions.checkNotNull(getCurrentMediaItem());
|
Assertions.checkNotNull(getCurrentMediaItem());
|
||||||
boolean prepareComplete = !prepared;
|
boolean prepareComplete = !prepared;
|
||||||
if (prepareComplete) {
|
if (prepareComplete) {
|
||||||
prepared = true;
|
prepared = true;
|
||||||
handlePositionDiscontinuity(Player.DISCONTINUITY_REASON_PERIOD_TRANSITION);
|
handlePositionDiscontinuity(Player.DISCONTINUITY_REASON_PERIOD_TRANSITION);
|
||||||
listener.onPlayerStateChanged(SessionPlayer.PLAYER_STATE_PAUSED);
|
listener.onPlayerStateChanged(SessionPlayer.PLAYER_STATE_PAUSED);
|
||||||
listener.onPrepared(androidXMediaItem, player.getBufferedPercentage());
|
listener.onPrepared(media2MediaItem, player.getBufferedPercentage());
|
||||||
}
|
}
|
||||||
if (rebuffering) {
|
if (rebuffering) {
|
||||||
rebuffering = false;
|
rebuffering = false;
|
||||||
listener.onBufferingEnded(androidXMediaItem, player.getBufferedPercentage());
|
listener.onBufferingEnded(media2MediaItem, player.getBufferedPercentage());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -596,13 +583,13 @@ import java.util.List;
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private void releaseMediaItem(androidx.media2.common.MediaItem androidXMediaItem) {
|
private void releaseMediaItem(androidx.media2.common.MediaItem media2MediaItem) {
|
||||||
try {
|
try {
|
||||||
if (androidXMediaItem instanceof CallbackMediaItem) {
|
if (media2MediaItem instanceof CallbackMediaItem) {
|
||||||
((CallbackMediaItem) androidXMediaItem).getDataSourceCallback().close();
|
((CallbackMediaItem) media2MediaItem).getDataSourceCallback().close();
|
||||||
}
|
}
|
||||||
} catch (IOException e) {
|
} catch (IOException e) {
|
||||||
Log.w(TAG, "Error releasing media item " + androidXMediaItem, e);
|
Log.w(TAG, "Error releasing media item " + media2MediaItem, e);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -641,8 +628,8 @@ import java.util.List;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlaybackSpeedChanged(float playbackSpeed) {
|
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
|
||||||
handlePlaybackSpeedChanged(playbackSpeed);
|
handlePlaybackParametersChanged(playbackParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.google.android.exoplayer2.ext.media2;
|
package com.google.android.exoplayer2.ext.media2;
|
||||||
|
|
||||||
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
import static java.util.concurrent.TimeUnit.MILLISECONDS;
|
||||||
|
@ -13,14 +13,12 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.google.android.exoplayer2.ext.media2;
|
package com.google.android.exoplayer2.ext.media2;
|
||||||
|
|
||||||
import android.Manifest;
|
import android.Manifest;
|
||||||
import android.content.ComponentName;
|
import android.content.ComponentName;
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.content.pm.PackageManager;
|
import android.content.pm.PackageManager;
|
||||||
import android.net.Uri;
|
|
||||||
import android.os.Bundle;
|
import android.os.Bundle;
|
||||||
import android.provider.Settings;
|
import android.provider.Settings;
|
||||||
import android.text.TextUtils;
|
import android.text.TextUtils;
|
||||||
@ -31,7 +29,6 @@ import androidx.media2.common.MediaItem;
|
|||||||
import androidx.media2.common.MediaMetadata;
|
import androidx.media2.common.MediaMetadata;
|
||||||
import androidx.media2.common.Rating;
|
import androidx.media2.common.Rating;
|
||||||
import androidx.media2.common.SessionPlayer;
|
import androidx.media2.common.SessionPlayer;
|
||||||
import androidx.media2.common.UriMediaItem;
|
|
||||||
import androidx.media2.session.MediaController;
|
import androidx.media2.session.MediaController;
|
||||||
import androidx.media2.session.MediaSession;
|
import androidx.media2.session.MediaSession;
|
||||||
import androidx.media2.session.MediaSession.ControllerInfo;
|
import androidx.media2.session.MediaSession.ControllerInfo;
|
||||||
@ -39,13 +36,11 @@ import androidx.media2.session.SessionCommand;
|
|||||||
import androidx.media2.session.SessionCommandGroup;
|
import androidx.media2.session.SessionCommandGroup;
|
||||||
import androidx.media2.session.SessionResult;
|
import androidx.media2.session.SessionResult;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import java.net.URI;
|
|
||||||
import java.net.URISyntaxException;
|
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Builds {@link MediaSession.SessionCallback} with various collaborators.
|
* Builds a {@link MediaSession.SessionCallback} with various collaborators.
|
||||||
*
|
*
|
||||||
* @see MediaSession.SessionCallback
|
* @see MediaSession.SessionCallback
|
||||||
*/
|
*/
|
||||||
@ -351,10 +346,8 @@ public final class SessionCallbackBuilder {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** A {@link MediaItemProvider} that creates media items containing only a media ID. */
|
||||||
* Default implementation of {@link MediaItemProvider} that assumes the media id is a URI string.
|
public static final class MediaIdMediaItemProvider implements MediaItemProvider {
|
||||||
*/
|
|
||||||
public static final class DefaultMediaItemProvider implements MediaItemProvider {
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public MediaItem onCreateMediaItem(
|
public MediaItem onCreateMediaItem(
|
||||||
@ -362,17 +355,11 @@ public final class SessionCallbackBuilder {
|
|||||||
if (TextUtils.isEmpty(mediaId)) {
|
if (TextUtils.isEmpty(mediaId)) {
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
try {
|
|
||||||
new URI(mediaId);
|
|
||||||
} catch (URISyntaxException e) {
|
|
||||||
// Ignore if mediaId isn't a URI.
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
MediaMetadata metadata =
|
MediaMetadata metadata =
|
||||||
new MediaMetadata.Builder()
|
new MediaMetadata.Builder()
|
||||||
.putString(MediaMetadata.METADATA_KEY_MEDIA_ID, mediaId)
|
.putString(MediaMetadata.METADATA_KEY_MEDIA_ID, mediaId)
|
||||||
.build();
|
.build();
|
||||||
return new UriMediaItem.Builder(Uri.parse(mediaId)).setMetadata(metadata).build();
|
return new MediaItem.Builder().setMetadata(metadata).build();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -13,7 +13,6 @@
|
|||||||
* See the License for the specific language governing permissions and
|
* See the License for the specific language governing permissions and
|
||||||
* limitations under the License.
|
* limitations under the License.
|
||||||
*/
|
*/
|
||||||
|
|
||||||
package com.google.android.exoplayer2.ext.media2;
|
package com.google.android.exoplayer2.ext.media2;
|
||||||
|
|
||||||
import androidx.annotation.FloatRange;
|
import androidx.annotation.FloatRange;
|
||||||
@ -23,6 +22,7 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.core.util.ObjectsCompat;
|
import androidx.core.util.ObjectsCompat;
|
||||||
import androidx.core.util.Pair;
|
import androidx.core.util.Pair;
|
||||||
import androidx.media.AudioAttributesCompat;
|
import androidx.media.AudioAttributesCompat;
|
||||||
|
import androidx.media2.common.CallbackMediaItem;
|
||||||
import androidx.media2.common.FileMediaItem;
|
import androidx.media2.common.FileMediaItem;
|
||||||
import androidx.media2.common.MediaItem;
|
import androidx.media2.common.MediaItem;
|
||||||
import androidx.media2.common.MediaMetadata;
|
import androidx.media2.common.MediaMetadata;
|
||||||
@ -41,25 +41,11 @@ import java.util.Map;
|
|||||||
import java.util.concurrent.Callable;
|
import java.util.concurrent.Callable;
|
||||||
import java.util.concurrent.ExecutionException;
|
import java.util.concurrent.ExecutionException;
|
||||||
import java.util.concurrent.Executor;
|
import java.util.concurrent.Executor;
|
||||||
import org.checkerframework.checker.initialization.qual.Initialized;
|
|
||||||
import org.checkerframework.checker.nullness.compatqual.NullableType;
|
import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* An implementation of {@link SessionPlayer} that wraps a given ExoPlayer {@link Player} instance.
|
* An implementation of {@link SessionPlayer} that wraps a given ExoPlayer {@link Player} instance.
|
||||||
*
|
*
|
||||||
* <h3>Ownership</h3>
|
|
||||||
*
|
|
||||||
* <p>{@code SessionPlayerConnector} takes ownership of the provided ExoPlayer {@link Player}
|
|
||||||
* instance between when it's constructed and when it's {@link #close() closed}. No other components
|
|
||||||
* should interact with the wrapped player (otherwise, unexpected event callbacks from the wrapped
|
|
||||||
* player may put the session player in an inconsistent state).
|
|
||||||
*
|
|
||||||
* <p>Call {@link SessionPlayer#close()} when the {@code SessionPlayerConnector} is no longer needed
|
|
||||||
* to regain ownership of the wrapped player. It is the caller's responsibility to release the
|
|
||||||
* wrapped player via {@link Player#release()}.
|
|
||||||
*
|
|
||||||
* <h3>Threading model</h3>
|
|
||||||
*
|
|
||||||
* <p>Internally this implementation posts operations to and receives callbacks on the thread
|
* <p>Internally this implementation posts operations to and receives callbacks on the thread
|
||||||
* associated with {@link Player#getApplicationLooper()}, so it is important not to block this
|
* associated with {@link Player#getApplicationLooper()}, so it is important not to block this
|
||||||
* thread. In particular, when awaiting the result of an asynchronous session player operation, apps
|
* thread. In particular, when awaiting the result of an asynchronous session player operation, apps
|
||||||
@ -95,16 +81,15 @@ public final class SessionPlayerConnector extends SessionPlayer {
|
|||||||
|
|
||||||
// Should be only accessed on the executor, which is currently single-threaded.
|
// Should be only accessed on the executor, which is currently single-threaded.
|
||||||
@Nullable private MediaItem currentMediaItem;
|
@Nullable private MediaItem currentMediaItem;
|
||||||
@Nullable private List<MediaItem> currentPlaylist;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates an instance using {@link DefaultControlDispatcher} to dispatch player commands.
|
* Creates an instance using {@link DefaultMediaItemConverter} to convert between ExoPlayer and
|
||||||
|
* media2 MediaItems and {@link DefaultControlDispatcher} to dispatch player commands.
|
||||||
*
|
*
|
||||||
* @param player The player to wrap.
|
* @param player The player to wrap.
|
||||||
* @param mediaItemConverter The {@link MediaItemConverter}.
|
|
||||||
*/
|
*/
|
||||||
public SessionPlayerConnector(Player player, MediaItemConverter mediaItemConverter) {
|
public SessionPlayerConnector(Player player) {
|
||||||
this(player, mediaItemConverter, new DefaultControlDispatcher());
|
this(player, new DefaultMediaItemConverter(), new DefaultControlDispatcher());
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -124,19 +109,8 @@ public final class SessionPlayerConnector extends SessionPlayer {
|
|||||||
taskHandler = new PlayerHandler(player.getApplicationLooper());
|
taskHandler = new PlayerHandler(player.getApplicationLooper());
|
||||||
taskHandlerExecutor = taskHandler::postOrRun;
|
taskHandlerExecutor = taskHandler::postOrRun;
|
||||||
ExoPlayerWrapperListener playerListener = new ExoPlayerWrapperListener();
|
ExoPlayerWrapperListener playerListener = new ExoPlayerWrapperListener();
|
||||||
PlayerWrapper playerWrapper =
|
this.player = new PlayerWrapper(playerListener, player, mediaItemConverter, controlDispatcher);
|
||||||
new PlayerWrapper(playerListener, player, mediaItemConverter, controlDispatcher);
|
|
||||||
this.player = playerWrapper;
|
|
||||||
playerCommandQueue = new PlayerCommandQueue(this.player, taskHandler);
|
playerCommandQueue = new PlayerCommandQueue(this.player, taskHandler);
|
||||||
|
|
||||||
@SuppressWarnings("assignment.type.incompatible")
|
|
||||||
@Initialized
|
|
||||||
SessionPlayerConnector initializedThis = this;
|
|
||||||
initializedThis.<Void>runPlayerCallableBlocking(
|
|
||||||
/* callable= */ () -> {
|
|
||||||
playerWrapper.reset();
|
|
||||||
return null;
|
|
||||||
});
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -251,17 +225,27 @@ public final class SessionPlayerConnector extends SessionPlayer {
|
|||||||
return runPlayerCallableBlockingWithNullOnException(/* callable= */ player::getAudioAttributes);
|
return runPlayerCallableBlockingWithNullOnException(/* callable= */ player::getAudioAttributes);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* <p>{@link FileMediaItem} and {@link CallbackMediaItem} are not supported.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public ListenableFuture<PlayerResult> setMediaItem(MediaItem item) {
|
public ListenableFuture<PlayerResult> setMediaItem(MediaItem item) {
|
||||||
Assertions.checkNotNull(item);
|
Assertions.checkNotNull(item);
|
||||||
Assertions.checkArgument(!(item instanceof FileMediaItem));
|
Assertions.checkArgument(!(item instanceof FileMediaItem));
|
||||||
|
Assertions.checkArgument(!(item instanceof CallbackMediaItem));
|
||||||
ListenableFuture<PlayerResult> result =
|
ListenableFuture<PlayerResult> result =
|
||||||
playerCommandQueue.addCommand(
|
playerCommandQueue.addCommand(
|
||||||
PlayerCommandQueue.COMMAND_CODE_PLAYER_SET_MEDIA_ITEM, () -> player.setMediaItem(item));
|
PlayerCommandQueue.COMMAND_CODE_PLAYER_SET_MEDIA_ITEM, () -> player.setMediaItem(item));
|
||||||
result.addListener(this::handlePlaylistChangedOnHandler, taskHandlerExecutor);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* <p>{@link FileMediaItem} and {@link CallbackMediaItem} are not supported.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public ListenableFuture<PlayerResult> setPlaylist(
|
public ListenableFuture<PlayerResult> setPlaylist(
|
||||||
final List<MediaItem> playlist, @Nullable MediaMetadata metadata) {
|
final List<MediaItem> playlist, @Nullable MediaMetadata metadata) {
|
||||||
@ -271,6 +255,7 @@ public final class SessionPlayerConnector extends SessionPlayer {
|
|||||||
MediaItem item = playlist.get(i);
|
MediaItem item = playlist.get(i);
|
||||||
Assertions.checkNotNull(item);
|
Assertions.checkNotNull(item);
|
||||||
Assertions.checkArgument(!(item instanceof FileMediaItem));
|
Assertions.checkArgument(!(item instanceof FileMediaItem));
|
||||||
|
Assertions.checkArgument(!(item instanceof CallbackMediaItem));
|
||||||
for (int j = 0; j < i; j++) {
|
for (int j = 0; j < i; j++) {
|
||||||
Assertions.checkArgument(
|
Assertions.checkArgument(
|
||||||
item != playlist.get(j),
|
item != playlist.get(j),
|
||||||
@ -281,20 +266,24 @@ public final class SessionPlayerConnector extends SessionPlayer {
|
|||||||
playerCommandQueue.addCommand(
|
playerCommandQueue.addCommand(
|
||||||
PlayerCommandQueue.COMMAND_CODE_PLAYER_SET_PLAYLIST,
|
PlayerCommandQueue.COMMAND_CODE_PLAYER_SET_PLAYLIST,
|
||||||
/* command= */ () -> player.setPlaylist(playlist, metadata));
|
/* command= */ () -> player.setPlaylist(playlist, metadata));
|
||||||
result.addListener(this::handlePlaylistChangedOnHandler, taskHandlerExecutor);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* <p>{@link FileMediaItem} and {@link CallbackMediaItem} are not supported.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public ListenableFuture<PlayerResult> addPlaylistItem(int index, MediaItem item) {
|
public ListenableFuture<PlayerResult> addPlaylistItem(int index, MediaItem item) {
|
||||||
Assertions.checkArgument(index >= 0);
|
Assertions.checkArgument(index >= 0);
|
||||||
Assertions.checkNotNull(item);
|
Assertions.checkNotNull(item);
|
||||||
Assertions.checkArgument(!(item instanceof FileMediaItem));
|
Assertions.checkArgument(!(item instanceof FileMediaItem));
|
||||||
|
Assertions.checkArgument(!(item instanceof CallbackMediaItem));
|
||||||
ListenableFuture<PlayerResult> result =
|
ListenableFuture<PlayerResult> result =
|
||||||
playerCommandQueue.addCommand(
|
playerCommandQueue.addCommand(
|
||||||
PlayerCommandQueue.COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM,
|
PlayerCommandQueue.COMMAND_CODE_PLAYER_ADD_PLAYLIST_ITEM,
|
||||||
/* command= */ () -> player.addPlaylistItem(index, item));
|
/* command= */ () -> player.addPlaylistItem(index, item));
|
||||||
result.addListener(this::handlePlaylistChangedOnHandler, taskHandlerExecutor);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -305,20 +294,24 @@ public final class SessionPlayerConnector extends SessionPlayer {
|
|||||||
playerCommandQueue.addCommand(
|
playerCommandQueue.addCommand(
|
||||||
PlayerCommandQueue.COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM,
|
PlayerCommandQueue.COMMAND_CODE_PLAYER_REMOVE_PLAYLIST_ITEM,
|
||||||
/* command= */ () -> player.removePlaylistItem(index));
|
/* command= */ () -> player.removePlaylistItem(index));
|
||||||
result.addListener(this::handlePlaylistChangedOnHandler, taskHandlerExecutor);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* {@inheritDoc}
|
||||||
|
*
|
||||||
|
* <p>{@link FileMediaItem} and {@link CallbackMediaItem} are not supported.
|
||||||
|
*/
|
||||||
@Override
|
@Override
|
||||||
public ListenableFuture<PlayerResult> replacePlaylistItem(int index, MediaItem item) {
|
public ListenableFuture<PlayerResult> replacePlaylistItem(int index, MediaItem item) {
|
||||||
Assertions.checkArgument(index >= 0);
|
Assertions.checkArgument(index >= 0);
|
||||||
Assertions.checkNotNull(item);
|
Assertions.checkNotNull(item);
|
||||||
Assertions.checkArgument(!(item instanceof FileMediaItem));
|
Assertions.checkArgument(!(item instanceof FileMediaItem));
|
||||||
|
Assertions.checkArgument(!(item instanceof CallbackMediaItem));
|
||||||
ListenableFuture<PlayerResult> result =
|
ListenableFuture<PlayerResult> result =
|
||||||
playerCommandQueue.addCommand(
|
playerCommandQueue.addCommand(
|
||||||
PlayerCommandQueue.COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM,
|
PlayerCommandQueue.COMMAND_CODE_PLAYER_REPLACE_PLAYLIST_ITEM,
|
||||||
/* command= */ () -> player.replacePlaylistItem(index, item));
|
/* command= */ () -> player.replacePlaylistItem(index, item));
|
||||||
result.addListener(this::handlePlaylistChangedOnHandler, taskHandlerExecutor);
|
|
||||||
return result;
|
return result;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -385,7 +378,7 @@ public final class SessionPlayerConnector extends SessionPlayer {
|
|||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public List<MediaItem> getPlaylist() {
|
public List<MediaItem> getPlaylist() {
|
||||||
return runPlayerCallableBlockingWithNullOnException(/* callable= */ player::getCachedPlaylist);
|
return runPlayerCallableBlockingWithNullOnException(/* callable= */ player::getPlaylist);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -447,7 +440,7 @@ public final class SessionPlayerConnector extends SessionPlayer {
|
|||||||
}
|
}
|
||||||
reset();
|
reset();
|
||||||
|
|
||||||
this.<Void>runPlayerCallableBlockingInternal(
|
this.<Void>runPlayerCallableBlocking(
|
||||||
/* callable= */ () -> {
|
/* callable= */ () -> {
|
||||||
player.close();
|
player.close();
|
||||||
return null;
|
return null;
|
||||||
@ -511,7 +504,7 @@ public final class SessionPlayerConnector extends SessionPlayer {
|
|||||||
state = PLAYER_STATE_IDLE;
|
state = PLAYER_STATE_IDLE;
|
||||||
mediaItemToBuffState.clear();
|
mediaItemToBuffState.clear();
|
||||||
}
|
}
|
||||||
this.<Void>runPlayerCallableBlockingInternal(
|
this.<Void>runPlayerCallableBlocking(
|
||||||
/* callable= */ () -> {
|
/* callable= */ () -> {
|
||||||
player.reset();
|
player.reset();
|
||||||
return null;
|
return null;
|
||||||
@ -558,25 +551,18 @@ public final class SessionPlayerConnector extends SessionPlayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void handlePlaylistChangedOnHandler() {
|
private void handlePlaylistChangedOnHandler() {
|
||||||
List<MediaItem> currentPlaylist = player.getCachedPlaylist();
|
List<MediaItem> currentPlaylist = player.getPlaylist();
|
||||||
boolean notifyCurrentPlaylist = !ObjectsCompat.equals(this.currentPlaylist, currentPlaylist);
|
|
||||||
this.currentPlaylist = currentPlaylist;
|
|
||||||
MediaMetadata playlistMetadata = player.getPlaylistMetadata();
|
MediaMetadata playlistMetadata = player.getPlaylistMetadata();
|
||||||
|
|
||||||
MediaItem currentMediaItem = player.getCurrentMediaItem();
|
MediaItem currentMediaItem = player.getCurrentMediaItem();
|
||||||
boolean notifyCurrentMediaItem = !ObjectsCompat.equals(this.currentMediaItem, currentMediaItem);
|
boolean notifyCurrentMediaItem = !ObjectsCompat.equals(this.currentMediaItem, currentMediaItem);
|
||||||
this.currentMediaItem = currentMediaItem;
|
this.currentMediaItem = currentMediaItem;
|
||||||
|
|
||||||
if (!notifyCurrentMediaItem && !notifyCurrentPlaylist) {
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
long currentPosition = getCurrentPosition();
|
long currentPosition = getCurrentPosition();
|
||||||
notifySessionPlayerCallback(
|
notifySessionPlayerCallback(
|
||||||
callback -> {
|
callback -> {
|
||||||
if (notifyCurrentPlaylist) {
|
|
||||||
callback.onPlaylistChanged(
|
callback.onPlaylistChanged(
|
||||||
SessionPlayerConnector.this, currentPlaylist, playlistMetadata);
|
SessionPlayerConnector.this, currentPlaylist, playlistMetadata);
|
||||||
}
|
|
||||||
if (notifyCurrentMediaItem) {
|
if (notifyCurrentMediaItem) {
|
||||||
Assertions.checkNotNull(
|
Assertions.checkNotNull(
|
||||||
currentMediaItem, "PlaylistManager#currentMediaItem() cannot be changed to null");
|
currentMediaItem, "PlaylistManager#currentMediaItem() cannot be changed to null");
|
||||||
@ -610,13 +596,6 @@ public final class SessionPlayerConnector extends SessionPlayer {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private <T> T runPlayerCallableBlocking(Callable<T> callable) {
|
private <T> T runPlayerCallableBlocking(Callable<T> callable) {
|
||||||
synchronized (stateLock) {
|
|
||||||
Assertions.checkState(!closed);
|
|
||||||
}
|
|
||||||
return runPlayerCallableBlockingInternal(callable);
|
|
||||||
}
|
|
||||||
|
|
||||||
private <T> T runPlayerCallableBlockingInternal(Callable<T> callable) {
|
|
||||||
SettableFuture<T> future = SettableFuture.create();
|
SettableFuture<T> future = SettableFuture.create();
|
||||||
boolean success =
|
boolean success =
|
||||||
taskHandler.postOrRun(
|
taskHandler.postOrRun(
|
||||||
|
@ -38,6 +38,7 @@ import com.google.android.exoplayer2.ControlDispatcher;
|
|||||||
import com.google.android.exoplayer2.DefaultControlDispatcher;
|
import com.google.android.exoplayer2.DefaultControlDispatcher;
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
||||||
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
import com.google.android.exoplayer2.Player;
|
import com.google.android.exoplayer2.Player;
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
@ -127,8 +128,8 @@ public final class MediaSessionConnector {
|
|||||||
@PlaybackActions public static final long DEFAULT_PLAYBACK_ACTIONS = ALL_PLAYBACK_ACTIONS;
|
@PlaybackActions public static final long DEFAULT_PLAYBACK_ACTIONS = ALL_PLAYBACK_ACTIONS;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The name of the {@link PlaybackStateCompat} float extra with the value of {@link
|
* The name of the {@link PlaybackStateCompat} float extra with the value of {@code
|
||||||
* Player#getPlaybackSpeed()}.
|
* Player.getPlaybackParameters().speed}.
|
||||||
*/
|
*/
|
||||||
public static final String EXTRAS_SPEED = "EXO_SPEED";
|
public static final String EXTRAS_SPEED = "EXO_SPEED";
|
||||||
|
|
||||||
@ -765,7 +766,7 @@ public final class MediaSessionConnector {
|
|||||||
queueNavigator != null
|
queueNavigator != null
|
||||||
? queueNavigator.getActiveQueueItemId(player)
|
? queueNavigator.getActiveQueueItemId(player)
|
||||||
: MediaSessionCompat.QueueItem.UNKNOWN_ID;
|
: MediaSessionCompat.QueueItem.UNKNOWN_ID;
|
||||||
float playbackSpeed = player.getPlaybackSpeed();
|
float playbackSpeed = player.getPlaybackParameters().speed;
|
||||||
extras.putFloat(EXTRAS_SPEED, playbackSpeed);
|
extras.putFloat(EXTRAS_SPEED, playbackSpeed);
|
||||||
float sessionPlaybackSpeed = player.isPlaying() ? playbackSpeed : 0f;
|
float sessionPlaybackSpeed = player.isPlaying() ? playbackSpeed : 0f;
|
||||||
builder
|
builder
|
||||||
@ -1134,7 +1135,7 @@ public final class MediaSessionConnector {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlaybackSpeedChanged(float playbackSpeed) {
|
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
|
||||||
invalidateMediaSessionPlaybackState();
|
invalidateMediaSessionPlaybackState();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -592,37 +592,7 @@ public final class Format implements Parcelable {
|
|||||||
// Build.
|
// Build.
|
||||||
|
|
||||||
public Format build() {
|
public Format build() {
|
||||||
return new Format(
|
return new Format(/* builder= */ this);
|
||||||
id,
|
|
||||||
label,
|
|
||||||
language,
|
|
||||||
selectionFlags,
|
|
||||||
roleFlags,
|
|
||||||
averageBitrate,
|
|
||||||
peakBitrate,
|
|
||||||
codecs,
|
|
||||||
metadata,
|
|
||||||
containerMimeType,
|
|
||||||
sampleMimeType,
|
|
||||||
maxInputSize,
|
|
||||||
initializationData,
|
|
||||||
drmInitData,
|
|
||||||
subsampleOffsetUs,
|
|
||||||
width,
|
|
||||||
height,
|
|
||||||
frameRate,
|
|
||||||
rotationDegrees,
|
|
||||||
pixelWidthHeightRatio,
|
|
||||||
projectionData,
|
|
||||||
stereoMode,
|
|
||||||
colorInfo,
|
|
||||||
channelCount,
|
|
||||||
sampleRate,
|
|
||||||
pcmEncoding,
|
|
||||||
encoderDelay,
|
|
||||||
encoderPadding,
|
|
||||||
accessibilityChannel,
|
|
||||||
exoMediaCryptoType);
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1211,86 +1181,51 @@ public final class Format implements Parcelable {
|
|||||||
return new Builder().setId(id).setSampleMimeType(sampleMimeType).build();
|
return new Builder().setId(id).setSampleMimeType(sampleMimeType).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some fields are deprecated but they're still assigned below.
|
private Format(Builder builder) {
|
||||||
/* package */ Format(
|
id = builder.id;
|
||||||
@Nullable String id,
|
label = builder.label;
|
||||||
@Nullable String label,
|
language = Util.normalizeLanguageCode(builder.language);
|
||||||
@Nullable String language,
|
selectionFlags = builder.selectionFlags;
|
||||||
@C.SelectionFlags int selectionFlags,
|
roleFlags = builder.roleFlags;
|
||||||
@C.RoleFlags int roleFlags,
|
averageBitrate = builder.averageBitrate;
|
||||||
int averageBitrate,
|
peakBitrate = builder.peakBitrate;
|
||||||
int peakBitrate,
|
bitrate = peakBitrate != NO_VALUE ? peakBitrate : averageBitrate;
|
||||||
@Nullable String codecs,
|
codecs = builder.codecs;
|
||||||
@Nullable Metadata metadata,
|
metadata = builder.metadata;
|
||||||
// Container specific.
|
// Container specific.
|
||||||
@Nullable String containerMimeType,
|
containerMimeType = builder.containerMimeType;
|
||||||
// Sample specific.
|
// Sample specific.
|
||||||
@Nullable String sampleMimeType,
|
sampleMimeType = builder.sampleMimeType;
|
||||||
int maxInputSize,
|
maxInputSize = builder.maxInputSize;
|
||||||
@Nullable List<byte[]> initializationData,
|
initializationData =
|
||||||
@Nullable DrmInitData drmInitData,
|
builder.initializationData == null ? Collections.emptyList() : builder.initializationData;
|
||||||
long subsampleOffsetUs,
|
drmInitData = builder.drmInitData;
|
||||||
|
subsampleOffsetUs = builder.subsampleOffsetUs;
|
||||||
// Video specific.
|
// Video specific.
|
||||||
int width,
|
width = builder.width;
|
||||||
int height,
|
height = builder.height;
|
||||||
float frameRate,
|
frameRate = builder.frameRate;
|
||||||
int rotationDegrees,
|
rotationDegrees = builder.rotationDegrees == NO_VALUE ? 0 : builder.rotationDegrees;
|
||||||
float pixelWidthHeightRatio,
|
pixelWidthHeightRatio =
|
||||||
@Nullable byte[] projectionData,
|
builder.pixelWidthHeightRatio == NO_VALUE ? 1 : builder.pixelWidthHeightRatio;
|
||||||
@C.StereoMode int stereoMode,
|
projectionData = builder.projectionData;
|
||||||
@Nullable ColorInfo colorInfo,
|
stereoMode = builder.stereoMode;
|
||||||
|
colorInfo = builder.colorInfo;
|
||||||
// Audio specific.
|
// Audio specific.
|
||||||
int channelCount,
|
channelCount = builder.channelCount;
|
||||||
int sampleRate,
|
sampleRate = builder.sampleRate;
|
||||||
@C.PcmEncoding int pcmEncoding,
|
pcmEncoding = builder.pcmEncoding;
|
||||||
int encoderDelay,
|
encoderDelay = builder.encoderDelay == NO_VALUE ? 0 : builder.encoderDelay;
|
||||||
int encoderPadding,
|
encoderPadding = builder.encoderPadding == NO_VALUE ? 0 : builder.encoderPadding;
|
||||||
// Text specific.
|
// Text specific.
|
||||||
int accessibilityChannel,
|
accessibilityChannel = builder.accessibilityChannel;
|
||||||
// Provided by source.
|
// Provided by source.
|
||||||
@Nullable Class<? extends ExoMediaCrypto> exoMediaCryptoType) {
|
if (builder.exoMediaCryptoType == null && drmInitData != null) {
|
||||||
this.id = id;
|
|
||||||
this.label = label;
|
|
||||||
this.language = Util.normalizeLanguageCode(language);
|
|
||||||
this.selectionFlags = selectionFlags;
|
|
||||||
this.roleFlags = roleFlags;
|
|
||||||
this.averageBitrate = averageBitrate;
|
|
||||||
this.peakBitrate = peakBitrate;
|
|
||||||
this.bitrate = peakBitrate != NO_VALUE ? peakBitrate : averageBitrate;
|
|
||||||
this.codecs = codecs;
|
|
||||||
this.metadata = metadata;
|
|
||||||
// Container specific.
|
|
||||||
this.containerMimeType = containerMimeType;
|
|
||||||
// Sample specific.
|
|
||||||
this.sampleMimeType = sampleMimeType;
|
|
||||||
this.maxInputSize = maxInputSize;
|
|
||||||
this.initializationData =
|
|
||||||
initializationData == null ? Collections.emptyList() : initializationData;
|
|
||||||
this.drmInitData = drmInitData;
|
|
||||||
this.subsampleOffsetUs = subsampleOffsetUs;
|
|
||||||
// Video specific.
|
|
||||||
this.width = width;
|
|
||||||
this.height = height;
|
|
||||||
this.frameRate = frameRate;
|
|
||||||
this.rotationDegrees = rotationDegrees == NO_VALUE ? 0 : rotationDegrees;
|
|
||||||
this.pixelWidthHeightRatio = pixelWidthHeightRatio == NO_VALUE ? 1 : pixelWidthHeightRatio;
|
|
||||||
this.projectionData = projectionData;
|
|
||||||
this.stereoMode = stereoMode;
|
|
||||||
this.colorInfo = colorInfo;
|
|
||||||
// Audio specific.
|
|
||||||
this.channelCount = channelCount;
|
|
||||||
this.sampleRate = sampleRate;
|
|
||||||
this.pcmEncoding = pcmEncoding;
|
|
||||||
this.encoderDelay = encoderDelay == NO_VALUE ? 0 : encoderDelay;
|
|
||||||
this.encoderPadding = encoderPadding == NO_VALUE ? 0 : encoderPadding;
|
|
||||||
// Text specific.
|
|
||||||
this.accessibilityChannel = accessibilityChannel;
|
|
||||||
// Provided by source.
|
|
||||||
if (exoMediaCryptoType == null && drmInitData != null) {
|
|
||||||
// Encrypted content must always have a non-null exoMediaCryptoType.
|
// Encrypted content must always have a non-null exoMediaCryptoType.
|
||||||
exoMediaCryptoType = UnsupportedMediaCrypto.class;
|
exoMediaCryptoType = UnsupportedMediaCrypto.class;
|
||||||
|
} else {
|
||||||
|
exoMediaCryptoType = builder.exoMediaCryptoType;
|
||||||
}
|
}
|
||||||
this.exoMediaCryptoType = exoMediaCryptoType;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
// Some fields are deprecated but they're still assigned below.
|
// Some fields are deprecated but they're still assigned below.
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.util;
|
package com.google.android.exoplayer2.util;
|
||||||
|
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
|
|
||||||
@ -219,11 +220,12 @@ public final class NalUnitUtil {
|
|||||||
* Returns whether the NAL unit with the specified header contains supplemental enhancement
|
* Returns whether the NAL unit with the specified header contains supplemental enhancement
|
||||||
* information.
|
* information.
|
||||||
*
|
*
|
||||||
* @param mimeType The sample MIME type.
|
* @param mimeType The sample MIME type, or {@code null} if unknown.
|
||||||
* @param nalUnitHeaderFirstByte The first byte of nal_unit().
|
* @param nalUnitHeaderFirstByte The first byte of nal_unit().
|
||||||
* @return Whether the NAL unit with the specified header is an SEI NAL unit.
|
* @return Whether the NAL unit with the specified header is an SEI NAL unit. False is returned if
|
||||||
|
* the {@code MimeType} is {@code null}.
|
||||||
*/
|
*/
|
||||||
public static boolean isNalUnitSei(String mimeType, byte nalUnitHeaderFirstByte) {
|
public static boolean isNalUnitSei(@Nullable String mimeType, byte nalUnitHeaderFirstByte) {
|
||||||
return (MimeTypes.VIDEO_H264.equals(mimeType)
|
return (MimeTypes.VIDEO_H264.equals(mimeType)
|
||||||
&& (nalUnitHeaderFirstByte & 0x1F) == H264_NAL_UNIT_TYPE_SEI)
|
&& (nalUnitHeaderFirstByte & 0x1F) == H264_NAL_UNIT_TYPE_SEI)
|
||||||
|| (MimeTypes.VIDEO_H265.equals(mimeType)
|
|| (MimeTypes.VIDEO_H265.equals(mimeType)
|
||||||
|
@ -405,6 +405,21 @@ public final class Util {
|
|||||||
return concatenation;
|
return concatenation;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Copies the contents of {@code list} into {@code array}.
|
||||||
|
*
|
||||||
|
* <p>{@code list.size()} must be the same as {@code array.length} to ensure the contents can be
|
||||||
|
* copied into {@code array} without leaving any nulls at the end.
|
||||||
|
*
|
||||||
|
* @param list The list to copy items from.
|
||||||
|
* @param array The array to copy items to.
|
||||||
|
*/
|
||||||
|
@SuppressWarnings("nullness:toArray.nullable.elements.not.newarray")
|
||||||
|
public static <T> void nullSafeListToArray(List<T> list, T[] array) {
|
||||||
|
Assertions.checkState(list.size() == array.length);
|
||||||
|
list.toArray(array);
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a {@link Handler} on the current {@link Looper} thread.
|
* Creates a {@link Handler} on the current {@link Looper} thread.
|
||||||
*
|
*
|
||||||
@ -2362,7 +2377,7 @@ public final class Util {
|
|||||||
case TelephonyManager.NETWORK_TYPE_LTE:
|
case TelephonyManager.NETWORK_TYPE_LTE:
|
||||||
return C.NETWORK_TYPE_4G;
|
return C.NETWORK_TYPE_4G;
|
||||||
case TelephonyManager.NETWORK_TYPE_NR:
|
case TelephonyManager.NETWORK_TYPE_NR:
|
||||||
return C.NETWORK_TYPE_5G;
|
return SDK_INT >= 29 ? C.NETWORK_TYPE_5G : C.NETWORK_TYPE_UNKNOWN;
|
||||||
case TelephonyManager.NETWORK_TYPE_IWLAN:
|
case TelephonyManager.NETWORK_TYPE_IWLAN:
|
||||||
return C.NETWORK_TYPE_WIFI;
|
return C.NETWORK_TYPE_WIFI;
|
||||||
case TelephonyManager.NETWORK_TYPE_GSM:
|
case TelephonyManager.NETWORK_TYPE_GSM:
|
||||||
|
@ -90,37 +90,38 @@ public final class FormatTest {
|
|||||||
C.COLOR_TRANSFER_SDR,
|
C.COLOR_TRANSFER_SDR,
|
||||||
new byte[] {1, 2, 3, 4, 5, 6, 7});
|
new byte[] {1, 2, 3, 4, 5, 6, 7});
|
||||||
|
|
||||||
return new Format(
|
return new Format.Builder()
|
||||||
"id",
|
.setId("id")
|
||||||
"label",
|
.setLabel("label")
|
||||||
"language",
|
.setLanguage("language")
|
||||||
C.SELECTION_FLAG_DEFAULT,
|
.setSelectionFlags(C.SELECTION_FLAG_DEFAULT)
|
||||||
C.ROLE_FLAG_MAIN,
|
.setRoleFlags(C.ROLE_FLAG_MAIN)
|
||||||
/* averageBitrate= */ 1024,
|
.setAverageBitrate(1024)
|
||||||
/* peakBitrate= */ 2048,
|
.setPeakBitrate(2048)
|
||||||
"codec",
|
.setCodecs("codec")
|
||||||
metadata,
|
.setMetadata(metadata)
|
||||||
/* containerMimeType= */ MimeTypes.VIDEO_MP4,
|
.setContainerMimeType(MimeTypes.VIDEO_MP4)
|
||||||
/* sampleMimeType= */ MimeTypes.VIDEO_H264,
|
.setSampleMimeType(MimeTypes.VIDEO_H264)
|
||||||
/* maxInputSize= */ 5000,
|
.setMaxInputSize(5000)
|
||||||
initializationData,
|
.setInitializationData(initializationData)
|
||||||
drmInitData,
|
.setDrmInitData(drmInitData)
|
||||||
Format.OFFSET_SAMPLE_RELATIVE,
|
.setSubsampleOffsetUs(Format.OFFSET_SAMPLE_RELATIVE)
|
||||||
/* width= */ 1920,
|
.setWidth(1920)
|
||||||
/* height= */ 1080,
|
.setHeight(1080)
|
||||||
/* frameRate= */ 24,
|
.setFrameRate(24)
|
||||||
/* rotationDegrees= */ 90,
|
.setRotationDegrees(90)
|
||||||
/* pixelWidthHeightRatio= */ 4,
|
.setPixelWidthHeightRatio(4)
|
||||||
projectionData,
|
.setProjectionData(projectionData)
|
||||||
C.STEREO_MODE_TOP_BOTTOM,
|
.setStereoMode(C.STEREO_MODE_TOP_BOTTOM)
|
||||||
colorInfo,
|
.setColorInfo(colorInfo)
|
||||||
/* channelCount= */ 6,
|
.setChannelCount(6)
|
||||||
/* sampleRate= */ 44100,
|
.setSampleRate(44100)
|
||||||
C.ENCODING_PCM_24BIT,
|
.setPcmEncoding(C.ENCODING_PCM_24BIT)
|
||||||
/* encoderDelay= */ 1001,
|
.setEncoderDelay(1001)
|
||||||
/* encoderPadding= */ 1002,
|
.setEncoderPadding(1002)
|
||||||
/* accessibilityChannel= */ 2,
|
.setAccessibilityChannel(2)
|
||||||
/* exoMediaCryptoType= */ ExoMediaCrypto.class);
|
.setExoMediaCryptoType(ExoMediaCrypto.class)
|
||||||
|
.build();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Generates an array of random bytes with the specified length. */
|
/** Generates an array of random bytes with the specified length. */
|
||||||
|
@ -27,20 +27,20 @@ import com.google.android.exoplayer2.util.StandaloneMediaClock;
|
|||||||
*/
|
*/
|
||||||
/* package */ final class DefaultMediaClock implements MediaClock {
|
/* package */ final class DefaultMediaClock implements MediaClock {
|
||||||
|
|
||||||
/** Listener interface to be notified of changes to the active playback speed. */
|
/** Listener interface to be notified of changes to the active playback parameters. */
|
||||||
public interface PlaybackSpeedListener {
|
public interface PlaybackParametersListener {
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the active playback speed changed. Will not be called for {@link
|
* Called when the active playback parameters changed. Will not be called for {@link
|
||||||
* #setPlaybackSpeed(float)}.
|
* #setPlaybackParameters(PlaybackParameters)}.
|
||||||
*
|
*
|
||||||
* @param newPlaybackSpeed The newly active playback speed.
|
* @param newPlaybackParameters The newly active playback parameters.
|
||||||
*/
|
*/
|
||||||
void onPlaybackSpeedChanged(float newPlaybackSpeed);
|
void onPlaybackParametersChanged(PlaybackParameters newPlaybackParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
private final StandaloneMediaClock standaloneClock;
|
private final StandaloneMediaClock standaloneClock;
|
||||||
private final PlaybackSpeedListener listener;
|
private final PlaybackParametersListener listener;
|
||||||
|
|
||||||
@Nullable private Renderer rendererClockSource;
|
@Nullable private Renderer rendererClockSource;
|
||||||
@Nullable private MediaClock rendererClock;
|
@Nullable private MediaClock rendererClock;
|
||||||
@ -48,13 +48,13 @@ import com.google.android.exoplayer2.util.StandaloneMediaClock;
|
|||||||
private boolean standaloneClockIsStarted;
|
private boolean standaloneClockIsStarted;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new instance with listener for playback speed changes and a {@link Clock} to use for
|
* Creates a new instance with a listener for playback parameters changes and a {@link Clock} to
|
||||||
* the standalone clock implementation.
|
* use for the standalone clock implementation.
|
||||||
*
|
*
|
||||||
* @param listener A {@link PlaybackSpeedListener} to listen for playback speed changes.
|
* @param listener A {@link PlaybackParametersListener} to listen for playback parameters changes.
|
||||||
* @param clock A {@link Clock}.
|
* @param clock A {@link Clock}.
|
||||||
*/
|
*/
|
||||||
public DefaultMediaClock(PlaybackSpeedListener listener, Clock clock) {
|
public DefaultMediaClock(PlaybackParametersListener listener, Clock clock) {
|
||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
this.standaloneClock = new StandaloneMediaClock(clock);
|
this.standaloneClock = new StandaloneMediaClock(clock);
|
||||||
isUsingStandaloneClock = true;
|
isUsingStandaloneClock = true;
|
||||||
@ -102,7 +102,7 @@ import com.google.android.exoplayer2.util.StandaloneMediaClock;
|
|||||||
}
|
}
|
||||||
this.rendererClock = rendererMediaClock;
|
this.rendererClock = rendererMediaClock;
|
||||||
this.rendererClockSource = renderer;
|
this.rendererClockSource = renderer;
|
||||||
rendererClock.setPlaybackSpeed(standaloneClock.getPlaybackSpeed());
|
rendererClock.setPlaybackParameters(standaloneClock.getPlaybackParameters());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,19 +140,19 @@ import com.google.android.exoplayer2.util.StandaloneMediaClock;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setPlaybackSpeed(float playbackSpeed) {
|
public void setPlaybackParameters(PlaybackParameters playbackParameters) {
|
||||||
if (rendererClock != null) {
|
if (rendererClock != null) {
|
||||||
rendererClock.setPlaybackSpeed(playbackSpeed);
|
rendererClock.setPlaybackParameters(playbackParameters);
|
||||||
playbackSpeed = rendererClock.getPlaybackSpeed();
|
playbackParameters = rendererClock.getPlaybackParameters();
|
||||||
}
|
}
|
||||||
standaloneClock.setPlaybackSpeed(playbackSpeed);
|
standaloneClock.setPlaybackParameters(playbackParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getPlaybackSpeed() {
|
public PlaybackParameters getPlaybackParameters() {
|
||||||
return rendererClock != null
|
return rendererClock != null
|
||||||
? rendererClock.getPlaybackSpeed()
|
? rendererClock.getPlaybackParameters()
|
||||||
: standaloneClock.getPlaybackSpeed();
|
: standaloneClock.getPlaybackParameters();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void syncClocks(boolean isReadingAhead) {
|
private void syncClocks(boolean isReadingAhead) {
|
||||||
@ -180,10 +180,10 @@ import com.google.android.exoplayer2.util.StandaloneMediaClock;
|
|||||||
}
|
}
|
||||||
// Continuously sync stand-alone clock to renderer clock so that it can take over if needed.
|
// Continuously sync stand-alone clock to renderer clock so that it can take over if needed.
|
||||||
standaloneClock.resetPosition(rendererClockPositionUs);
|
standaloneClock.resetPosition(rendererClockPositionUs);
|
||||||
float playbackSpeed = rendererClock.getPlaybackSpeed();
|
PlaybackParameters playbackParameters = rendererClock.getPlaybackParameters();
|
||||||
if (playbackSpeed != standaloneClock.getPlaybackSpeed()) {
|
if (!playbackParameters.equals(standaloneClock.getPlaybackParameters())) {
|
||||||
standaloneClock.setPlaybackSpeed(playbackSpeed);
|
standaloneClock.setPlaybackParameters(playbackParameters);
|
||||||
listener.onPlaybackSpeedChanged(playbackSpeed);
|
listener.onPlaybackParametersChanged(playbackParameters);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -619,32 +619,17 @@ import java.util.concurrent.TimeoutException;
|
|||||||
/* seekProcessed= */ true);
|
/* seekProcessed= */ true);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Use {@link #setPlaybackSpeed(float)} instead. */
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
@Deprecated
|
|
||||||
@Override
|
@Override
|
||||||
public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) {
|
public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) {
|
||||||
setPlaybackSpeed(
|
if (playbackParameters == null) {
|
||||||
playbackParameters != null ? playbackParameters.speed : Player.DEFAULT_PLAYBACK_SPEED);
|
playbackParameters = PlaybackParameters.DEFAULT;
|
||||||
}
|
}
|
||||||
|
if (playbackInfo.playbackParameters.equals(playbackParameters)) {
|
||||||
/** @deprecated Use {@link #getPlaybackSpeed()} instead. */
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
@Deprecated
|
|
||||||
@Override
|
|
||||||
public PlaybackParameters getPlaybackParameters() {
|
|
||||||
return new PlaybackParameters(playbackInfo.playbackSpeed);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setPlaybackSpeed(float playbackSpeed) {
|
|
||||||
checkState(playbackSpeed > 0);
|
|
||||||
if (playbackInfo.playbackSpeed == playbackSpeed) {
|
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
PlaybackInfo newPlaybackInfo = playbackInfo.copyWithPlaybackSpeed(playbackSpeed);
|
PlaybackInfo newPlaybackInfo = playbackInfo.copyWithPlaybackParameters(playbackParameters);
|
||||||
pendingOperationAcks++;
|
pendingOperationAcks++;
|
||||||
internalPlayer.setPlaybackSpeed(playbackSpeed);
|
internalPlayer.setPlaybackParameters(playbackParameters);
|
||||||
updatePlaybackInfo(
|
updatePlaybackInfo(
|
||||||
newPlaybackInfo,
|
newPlaybackInfo,
|
||||||
/* positionDiscontinuity= */ false,
|
/* positionDiscontinuity= */ false,
|
||||||
@ -655,8 +640,8 @@ import java.util.concurrent.TimeoutException;
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getPlaybackSpeed() {
|
public PlaybackParameters getPlaybackParameters() {
|
||||||
return playbackInfo.playbackSpeed;
|
return playbackInfo.playbackParameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -1366,7 +1351,7 @@ import java.util.concurrent.TimeoutException;
|
|||||||
private final boolean playWhenReadyChanged;
|
private final boolean playWhenReadyChanged;
|
||||||
private final boolean playbackSuppressionReasonChanged;
|
private final boolean playbackSuppressionReasonChanged;
|
||||||
private final boolean isPlayingChanged;
|
private final boolean isPlayingChanged;
|
||||||
private final boolean playbackSpeedChanged;
|
private final boolean playbackParametersChanged;
|
||||||
private final boolean offloadSchedulingEnabledChanged;
|
private final boolean offloadSchedulingEnabledChanged;
|
||||||
|
|
||||||
public PlaybackInfoUpdate(
|
public PlaybackInfoUpdate(
|
||||||
@ -1405,7 +1390,8 @@ import java.util.concurrent.TimeoutException;
|
|||||||
playbackSuppressionReasonChanged =
|
playbackSuppressionReasonChanged =
|
||||||
previousPlaybackInfo.playbackSuppressionReason != playbackInfo.playbackSuppressionReason;
|
previousPlaybackInfo.playbackSuppressionReason != playbackInfo.playbackSuppressionReason;
|
||||||
isPlayingChanged = isPlaying(previousPlaybackInfo) != isPlaying(playbackInfo);
|
isPlayingChanged = isPlaying(previousPlaybackInfo) != isPlaying(playbackInfo);
|
||||||
playbackSpeedChanged = previousPlaybackInfo.playbackSpeed != playbackInfo.playbackSpeed;
|
playbackParametersChanged =
|
||||||
|
!previousPlaybackInfo.playbackParameters.equals(playbackInfo.playbackParameters);
|
||||||
offloadSchedulingEnabledChanged =
|
offloadSchedulingEnabledChanged =
|
||||||
previousPlaybackInfo.offloadSchedulingEnabled != playbackInfo.offloadSchedulingEnabled;
|
previousPlaybackInfo.offloadSchedulingEnabled != playbackInfo.offloadSchedulingEnabled;
|
||||||
}
|
}
|
||||||
@ -1473,13 +1459,11 @@ import java.util.concurrent.TimeoutException;
|
|||||||
invokeAll(
|
invokeAll(
|
||||||
listenerSnapshot, listener -> listener.onIsPlayingChanged(isPlaying(playbackInfo)));
|
listenerSnapshot, listener -> listener.onIsPlayingChanged(isPlaying(playbackInfo)));
|
||||||
}
|
}
|
||||||
if (playbackSpeedChanged) {
|
if (playbackParametersChanged) {
|
||||||
PlaybackParameters playbackParameters = new PlaybackParameters(playbackInfo.playbackSpeed);
|
|
||||||
invokeAll(
|
invokeAll(
|
||||||
listenerSnapshot,
|
listenerSnapshot,
|
||||||
listener -> {
|
listener -> {
|
||||||
listener.onPlaybackSpeedChanged(playbackInfo.playbackSpeed);
|
listener.onPlaybackParametersChanged(playbackInfo.playbackParameters);
|
||||||
listener.onPlaybackParametersChanged(playbackParameters);
|
|
||||||
});
|
});
|
||||||
}
|
}
|
||||||
if (seekProcessed) {
|
if (seekProcessed) {
|
||||||
|
@ -27,7 +27,7 @@ import android.os.SystemClock;
|
|||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
import androidx.annotation.CheckResult;
|
import androidx.annotation.CheckResult;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.DefaultMediaClock.PlaybackSpeedListener;
|
import com.google.android.exoplayer2.DefaultMediaClock.PlaybackParametersListener;
|
||||||
import com.google.android.exoplayer2.Player.DiscontinuityReason;
|
import com.google.android.exoplayer2.Player.DiscontinuityReason;
|
||||||
import com.google.android.exoplayer2.Player.PlayWhenReadyChangeReason;
|
import com.google.android.exoplayer2.Player.PlayWhenReadyChangeReason;
|
||||||
import com.google.android.exoplayer2.Player.PlaybackSuppressionReason;
|
import com.google.android.exoplayer2.Player.PlaybackSuppressionReason;
|
||||||
@ -61,7 +61,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
MediaPeriod.Callback,
|
MediaPeriod.Callback,
|
||||||
TrackSelector.InvalidationListener,
|
TrackSelector.InvalidationListener,
|
||||||
MediaSourceList.MediaSourceListInfoRefreshListener,
|
MediaSourceList.MediaSourceListInfoRefreshListener,
|
||||||
PlaybackSpeedListener,
|
PlaybackParametersListener,
|
||||||
PlayerMessage.Sender {
|
PlayerMessage.Sender {
|
||||||
|
|
||||||
private static final String TAG = "ExoPlayerImplInternal";
|
private static final String TAG = "ExoPlayerImplInternal";
|
||||||
@ -121,7 +121,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
private static final int MSG_SET_PLAY_WHEN_READY = 1;
|
private static final int MSG_SET_PLAY_WHEN_READY = 1;
|
||||||
private static final int MSG_DO_SOME_WORK = 2;
|
private static final int MSG_DO_SOME_WORK = 2;
|
||||||
private static final int MSG_SEEK_TO = 3;
|
private static final int MSG_SEEK_TO = 3;
|
||||||
private static final int MSG_SET_PLAYBACK_SPEED = 4;
|
private static final int MSG_SET_PLAYBACK_PARAMETERS = 4;
|
||||||
private static final int MSG_SET_SEEK_PARAMETERS = 5;
|
private static final int MSG_SET_SEEK_PARAMETERS = 5;
|
||||||
private static final int MSG_STOP = 6;
|
private static final int MSG_STOP = 6;
|
||||||
private static final int MSG_RELEASE = 7;
|
private static final int MSG_RELEASE = 7;
|
||||||
@ -133,7 +133,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
private static final int MSG_SET_FOREGROUND_MODE = 13;
|
private static final int MSG_SET_FOREGROUND_MODE = 13;
|
||||||
private static final int MSG_SEND_MESSAGE = 14;
|
private static final int MSG_SEND_MESSAGE = 14;
|
||||||
private static final int MSG_SEND_MESSAGE_TO_TARGET_THREAD = 15;
|
private static final int MSG_SEND_MESSAGE_TO_TARGET_THREAD = 15;
|
||||||
private static final int MSG_PLAYBACK_SPEED_CHANGED_INTERNAL = 16;
|
private static final int MSG_PLAYBACK_PARAMETERS_CHANGED_INTERNAL = 16;
|
||||||
private static final int MSG_SET_MEDIA_SOURCES = 17;
|
private static final int MSG_SET_MEDIA_SOURCES = 17;
|
||||||
private static final int MSG_ADD_MEDIA_SOURCES = 18;
|
private static final int MSG_ADD_MEDIA_SOURCES = 18;
|
||||||
private static final int MSG_MOVE_MEDIA_SOURCES = 19;
|
private static final int MSG_MOVE_MEDIA_SOURCES = 19;
|
||||||
@ -163,6 +163,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
private final BandwidthMeter bandwidthMeter;
|
private final BandwidthMeter bandwidthMeter;
|
||||||
private final HandlerWrapper handler;
|
private final HandlerWrapper handler;
|
||||||
private final HandlerThread internalPlaybackThread;
|
private final HandlerThread internalPlaybackThread;
|
||||||
|
private final Looper playbackLooper;
|
||||||
private final Timeline.Window window;
|
private final Timeline.Window window;
|
||||||
private final Timeline.Period period;
|
private final Timeline.Period period;
|
||||||
private final long backBufferDurationUs;
|
private final long backBufferDurationUs;
|
||||||
@ -252,7 +253,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
// not normally change to this priority" is incorrect.
|
// not normally change to this priority" is incorrect.
|
||||||
internalPlaybackThread = new HandlerThread("ExoPlayer:Playback", Process.THREAD_PRIORITY_AUDIO);
|
internalPlaybackThread = new HandlerThread("ExoPlayer:Playback", Process.THREAD_PRIORITY_AUDIO);
|
||||||
internalPlaybackThread.start();
|
internalPlaybackThread.start();
|
||||||
handler = clock.createHandler(internalPlaybackThread.getLooper(), this);
|
playbackLooper = internalPlaybackThread.getLooper();
|
||||||
|
handler = clock.createHandler(playbackLooper, this);
|
||||||
}
|
}
|
||||||
|
|
||||||
public void experimentalSetReleaseTimeoutMs(long releaseTimeoutMs) {
|
public void experimentalSetReleaseTimeoutMs(long releaseTimeoutMs) {
|
||||||
@ -301,8 +303,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
.sendToTarget();
|
.sendToTarget();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setPlaybackSpeed(float playbackSpeed) {
|
public void setPlaybackParameters(PlaybackParameters playbackParameters) {
|
||||||
handler.obtainMessage(MSG_SET_PLAYBACK_SPEED, playbackSpeed).sendToTarget();
|
handler.obtainMessage(MSG_SET_PLAYBACK_PARAMETERS, playbackParameters).sendToTarget();
|
||||||
}
|
}
|
||||||
|
|
||||||
public void setSeekParameters(SeekParameters seekParameters) {
|
public void setSeekParameters(SeekParameters seekParameters) {
|
||||||
@ -403,7 +405,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
}
|
}
|
||||||
|
|
||||||
public Looper getPlaybackLooper() {
|
public Looper getPlaybackLooper() {
|
||||||
return internalPlaybackThread.getLooper();
|
return playbackLooper;
|
||||||
}
|
}
|
||||||
|
|
||||||
// Playlist.PlaylistInfoRefreshListener implementation.
|
// Playlist.PlaylistInfoRefreshListener implementation.
|
||||||
@ -432,11 +434,11 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
handler.sendEmptyMessage(MSG_TRACK_SELECTION_INVALIDATED);
|
handler.sendEmptyMessage(MSG_TRACK_SELECTION_INVALIDATED);
|
||||||
}
|
}
|
||||||
|
|
||||||
// DefaultMediaClock.PlaybackSpeedListener implementation.
|
// DefaultMediaClock.PlaybackParametersListener implementation.
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlaybackSpeedChanged(float playbackSpeed) {
|
public void onPlaybackParametersChanged(PlaybackParameters newPlaybackParameters) {
|
||||||
sendPlaybackSpeedChangedInternal(playbackSpeed, /* acknowledgeCommand= */ false);
|
sendPlaybackParametersChangedInternal(newPlaybackParameters, /* acknowledgeCommand= */ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
// Handler.Callback implementation.
|
// Handler.Callback implementation.
|
||||||
@ -467,8 +469,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
case MSG_SEEK_TO:
|
case MSG_SEEK_TO:
|
||||||
seekToInternal((SeekPosition) msg.obj);
|
seekToInternal((SeekPosition) msg.obj);
|
||||||
break;
|
break;
|
||||||
case MSG_SET_PLAYBACK_SPEED:
|
case MSG_SET_PLAYBACK_PARAMETERS:
|
||||||
setPlaybackSpeedInternal((Float) msg.obj);
|
setPlaybackParametersInternal((PlaybackParameters) msg.obj);
|
||||||
break;
|
break;
|
||||||
case MSG_SET_SEEK_PARAMETERS:
|
case MSG_SET_SEEK_PARAMETERS:
|
||||||
setSeekParametersInternal((SeekParameters) msg.obj);
|
setSeekParametersInternal((SeekParameters) msg.obj);
|
||||||
@ -489,8 +491,9 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
case MSG_TRACK_SELECTION_INVALIDATED:
|
case MSG_TRACK_SELECTION_INVALIDATED:
|
||||||
reselectTracksInternal();
|
reselectTracksInternal();
|
||||||
break;
|
break;
|
||||||
case MSG_PLAYBACK_SPEED_CHANGED_INTERNAL:
|
case MSG_PLAYBACK_PARAMETERS_CHANGED_INTERNAL:
|
||||||
handlePlaybackSpeed((Float) msg.obj, /* acknowledgeCommand= */ msg.arg1 != 0);
|
handlePlaybackParameters(
|
||||||
|
(PlaybackParameters) msg.obj, /* acknowledgeCommand= */ msg.arg1 != 0);
|
||||||
break;
|
break;
|
||||||
case MSG_SEND_MESSAGE:
|
case MSG_SEND_MESSAGE:
|
||||||
sendMessageInternal((PlayerMessage) msg.obj);
|
sendMessageInternal((PlayerMessage) msg.obj);
|
||||||
@ -733,12 +736,14 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
private void setPauseAtEndOfWindowInternal(boolean pauseAtEndOfWindow)
|
private void setPauseAtEndOfWindowInternal(boolean pauseAtEndOfWindow)
|
||||||
throws ExoPlaybackException {
|
throws ExoPlaybackException {
|
||||||
this.pauseAtEndOfWindow = pauseAtEndOfWindow;
|
this.pauseAtEndOfWindow = pauseAtEndOfWindow;
|
||||||
if (queue.getReadingPeriod() != queue.getPlayingPeriod()) {
|
|
||||||
seekToCurrentPosition(/* sendDiscontinuity= */ true);
|
|
||||||
}
|
|
||||||
resetPendingPauseAtEndOfPeriod();
|
resetPendingPauseAtEndOfPeriod();
|
||||||
|
if (pendingPauseAtEndOfPeriod && queue.getReadingPeriod() != queue.getPlayingPeriod()) {
|
||||||
|
// When pausing is required, we need to set the streams of the playing period final. If we
|
||||||
|
// already started reading the next period, we need to flush the renderers.
|
||||||
|
seekToCurrentPosition(/* sendDiscontinuity= */ true);
|
||||||
handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false);
|
handleLoadingMediaPeriodChanged(/* loadingTrackSelectionChanged= */ false);
|
||||||
}
|
}
|
||||||
|
}
|
||||||
|
|
||||||
private void setOffloadSchedulingEnabledInternal(boolean offloadSchedulingEnabled) {
|
private void setOffloadSchedulingEnabledInternal(boolean offloadSchedulingEnabled) {
|
||||||
if (offloadSchedulingEnabled == this.offloadSchedulingEnabled) {
|
if (offloadSchedulingEnabled == this.offloadSchedulingEnabled) {
|
||||||
@ -1182,9 +1187,10 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
notifyTrackSelectionDiscontinuity();
|
notifyTrackSelectionDiscontinuity();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setPlaybackSpeedInternal(float playbackSpeed) {
|
private void setPlaybackParametersInternal(PlaybackParameters playbackParameters) {
|
||||||
mediaClock.setPlaybackSpeed(playbackSpeed);
|
mediaClock.setPlaybackParameters(playbackParameters);
|
||||||
sendPlaybackSpeedChangedInternal(mediaClock.getPlaybackSpeed(), /* acknowledgeCommand= */ true);
|
sendPlaybackParametersChangedInternal(
|
||||||
|
mediaClock.getPlaybackParameters(), /* acknowledgeCommand= */ true);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setSeekParametersInternal(SeekParameters seekParameters) {
|
private void setSeekParametersInternal(SeekParameters seekParameters) {
|
||||||
@ -1301,7 +1307,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
mediaPeriodId,
|
mediaPeriodId,
|
||||||
playbackInfo.playWhenReady,
|
playbackInfo.playWhenReady,
|
||||||
playbackInfo.playbackSuppressionReason,
|
playbackInfo.playbackSuppressionReason,
|
||||||
playbackInfo.playbackSpeed,
|
playbackInfo.playbackParameters,
|
||||||
startPositionUs,
|
startPositionUs,
|
||||||
/* totalBufferedDurationUs= */ 0,
|
/* totalBufferedDurationUs= */ 0,
|
||||||
startPositionUs,
|
startPositionUs,
|
||||||
@ -1361,7 +1367,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void sendMessageToTarget(PlayerMessage message) throws ExoPlaybackException {
|
private void sendMessageToTarget(PlayerMessage message) throws ExoPlaybackException {
|
||||||
if (message.getHandler().getLooper() == handler.getLooper()) {
|
if (message.getHandler().getLooper() == playbackLooper) {
|
||||||
deliverMessage(message);
|
deliverMessage(message);
|
||||||
if (playbackInfo.playbackState == Player.STATE_READY
|
if (playbackInfo.playbackState == Player.STATE_READY
|
||||||
|| playbackInfo.playbackState == Player.STATE_BUFFERING) {
|
|| playbackInfo.playbackState == Player.STATE_BUFFERING) {
|
||||||
@ -1506,7 +1512,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void reselectTracksInternal() throws ExoPlaybackException {
|
private void reselectTracksInternal() throws ExoPlaybackException {
|
||||||
float playbackSpeed = mediaClock.getPlaybackSpeed();
|
float playbackSpeed = mediaClock.getPlaybackParameters().speed;
|
||||||
// Reselect tracks on each period in turn, until the selection changes.
|
// Reselect tracks on each period in turn, until the selection changes.
|
||||||
MediaPeriodHolder periodHolder = queue.getPlayingPeriod();
|
MediaPeriodHolder periodHolder = queue.getPlayingPeriod();
|
||||||
MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod();
|
MediaPeriodHolder readingPeriodHolder = queue.getReadingPeriod();
|
||||||
@ -1624,7 +1630,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
boolean bufferedToEnd = loadingHolder.isFullyBuffered() && loadingHolder.info.isFinal;
|
boolean bufferedToEnd = loadingHolder.isFullyBuffered() && loadingHolder.info.isFinal;
|
||||||
return bufferedToEnd
|
return bufferedToEnd
|
||||||
|| loadControl.shouldStartPlayback(
|
|| loadControl.shouldStartPlayback(
|
||||||
getTotalBufferedDurationUs(), mediaClock.getPlaybackSpeed(), rebuffering);
|
getTotalBufferedDurationUs(), mediaClock.getPlaybackParameters().speed, rebuffering);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isTimelineReady() {
|
private boolean isTimelineReady() {
|
||||||
@ -1960,7 +1966,8 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
MediaPeriodHolder loadingPeriodHolder = queue.getLoadingPeriod();
|
MediaPeriodHolder loadingPeriodHolder = queue.getLoadingPeriod();
|
||||||
loadingPeriodHolder.handlePrepared(mediaClock.getPlaybackSpeed(), playbackInfo.timeline);
|
loadingPeriodHolder.handlePrepared(
|
||||||
|
mediaClock.getPlaybackParameters().speed, playbackInfo.timeline);
|
||||||
updateLoadControlTrackSelection(
|
updateLoadControlTrackSelection(
|
||||||
loadingPeriodHolder.getTrackGroups(), loadingPeriodHolder.getTrackSelectorResult());
|
loadingPeriodHolder.getTrackGroups(), loadingPeriodHolder.getTrackSelectorResult());
|
||||||
if (loadingPeriodHolder == queue.getPlayingPeriod()) {
|
if (loadingPeriodHolder == queue.getPlayingPeriod()) {
|
||||||
@ -1985,14 +1992,15 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
maybeContinueLoading();
|
maybeContinueLoading();
|
||||||
}
|
}
|
||||||
|
|
||||||
private void handlePlaybackSpeed(float playbackSpeed, boolean acknowledgeCommand)
|
private void handlePlaybackParameters(
|
||||||
|
PlaybackParameters playbackParameters, boolean acknowledgeCommand)
|
||||||
throws ExoPlaybackException {
|
throws ExoPlaybackException {
|
||||||
playbackInfoUpdate.incrementPendingOperationAcks(acknowledgeCommand ? 1 : 0);
|
playbackInfoUpdate.incrementPendingOperationAcks(acknowledgeCommand ? 1 : 0);
|
||||||
playbackInfo = playbackInfo.copyWithPlaybackSpeed(playbackSpeed);
|
playbackInfo = playbackInfo.copyWithPlaybackParameters(playbackParameters);
|
||||||
updateTrackSelectionPlaybackSpeed(playbackSpeed);
|
updateTrackSelectionPlaybackSpeed(playbackParameters.speed);
|
||||||
for (Renderer renderer : renderers) {
|
for (Renderer renderer : renderers) {
|
||||||
if (renderer != null) {
|
if (renderer != null) {
|
||||||
renderer.setOperatingRate(playbackSpeed);
|
renderer.setOperatingRate(playbackParameters.speed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -2018,7 +2026,7 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
: loadingPeriodHolder.toPeriodTime(rendererPositionUs)
|
: loadingPeriodHolder.toPeriodTime(rendererPositionUs)
|
||||||
- loadingPeriodHolder.info.startPositionUs;
|
- loadingPeriodHolder.info.startPositionUs;
|
||||||
return loadControl.shouldContinueLoading(
|
return loadControl.shouldContinueLoading(
|
||||||
playbackPositionUs, bufferedDurationUs, mediaClock.getPlaybackSpeed());
|
playbackPositionUs, bufferedDurationUs, mediaClock.getPlaybackParameters().speed);
|
||||||
}
|
}
|
||||||
|
|
||||||
private boolean isLoadingPossible() {
|
private boolean isLoadingPossible() {
|
||||||
@ -2194,10 +2202,14 @@ import java.util.concurrent.atomic.AtomicBoolean;
|
|||||||
loadControl.onTracksSelected(renderers, trackGroups, trackSelectorResult.selections);
|
loadControl.onTracksSelected(renderers, trackGroups, trackSelectorResult.selections);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void sendPlaybackSpeedChangedInternal(float playbackSpeed, boolean acknowledgeCommand) {
|
private void sendPlaybackParametersChangedInternal(
|
||||||
|
PlaybackParameters playbackParameters, boolean acknowledgeCommand) {
|
||||||
handler
|
handler
|
||||||
.obtainMessage(
|
.obtainMessage(
|
||||||
MSG_PLAYBACK_SPEED_CHANGED_INTERNAL, acknowledgeCommand ? 1 : 0, 0, playbackSpeed)
|
MSG_PLAYBACK_PARAMETERS_CHANGED_INTERNAL,
|
||||||
|
acknowledgeCommand ? 1 : 0,
|
||||||
|
0,
|
||||||
|
playbackParameters)
|
||||||
.sendToTarget();
|
.sendToTarget();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -63,8 +63,8 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
|||||||
public final boolean playWhenReady;
|
public final boolean playWhenReady;
|
||||||
/** Reason why playback is suppressed even though {@link #playWhenReady} is {@code true}. */
|
/** Reason why playback is suppressed even though {@link #playWhenReady} is {@code true}. */
|
||||||
@PlaybackSuppressionReason public final int playbackSuppressionReason;
|
@PlaybackSuppressionReason public final int playbackSuppressionReason;
|
||||||
/** The playback speed. */
|
/** The playback parameters. */
|
||||||
public final float playbackSpeed;
|
public final PlaybackParameters playbackParameters;
|
||||||
/** Whether offload scheduling is enabled for the main player loop. */
|
/** Whether offload scheduling is enabled for the main player loop. */
|
||||||
public final boolean offloadSchedulingEnabled;
|
public final boolean offloadSchedulingEnabled;
|
||||||
|
|
||||||
@ -105,7 +105,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
|||||||
PLACEHOLDER_MEDIA_PERIOD_ID,
|
PLACEHOLDER_MEDIA_PERIOD_ID,
|
||||||
/* playWhenReady= */ false,
|
/* playWhenReady= */ false,
|
||||||
Player.PLAYBACK_SUPPRESSION_REASON_NONE,
|
Player.PLAYBACK_SUPPRESSION_REASON_NONE,
|
||||||
Player.DEFAULT_PLAYBACK_SPEED,
|
PlaybackParameters.DEFAULT,
|
||||||
/* bufferedPositionUs= */ 0,
|
/* bufferedPositionUs= */ 0,
|
||||||
/* totalBufferedDurationUs= */ 0,
|
/* totalBufferedDurationUs= */ 0,
|
||||||
/* positionUs= */ 0,
|
/* positionUs= */ 0,
|
||||||
@ -119,10 +119,14 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
|||||||
* @param periodId See {@link #periodId}.
|
* @param periodId See {@link #periodId}.
|
||||||
* @param requestedContentPositionUs See {@link #requestedContentPositionUs}.
|
* @param requestedContentPositionUs See {@link #requestedContentPositionUs}.
|
||||||
* @param playbackState See {@link #playbackState}.
|
* @param playbackState See {@link #playbackState}.
|
||||||
|
* @param playbackError See {@link #playbackError}.
|
||||||
* @param isLoading See {@link #isLoading}.
|
* @param isLoading See {@link #isLoading}.
|
||||||
* @param trackGroups See {@link #trackGroups}.
|
* @param trackGroups See {@link #trackGroups}.
|
||||||
* @param trackSelectorResult See {@link #trackSelectorResult}.
|
* @param trackSelectorResult See {@link #trackSelectorResult}.
|
||||||
* @param loadingMediaPeriodId See {@link #loadingMediaPeriodId}.
|
* @param loadingMediaPeriodId See {@link #loadingMediaPeriodId}.
|
||||||
|
* @param playWhenReady See {@link #playWhenReady}.
|
||||||
|
* @param playbackSuppressionReason See {@link #playbackSuppressionReason}.
|
||||||
|
* @param playbackParameters See {@link #playbackParameters}.
|
||||||
* @param bufferedPositionUs See {@link #bufferedPositionUs}.
|
* @param bufferedPositionUs See {@link #bufferedPositionUs}.
|
||||||
* @param totalBufferedDurationUs See {@link #totalBufferedDurationUs}.
|
* @param totalBufferedDurationUs See {@link #totalBufferedDurationUs}.
|
||||||
* @param positionUs See {@link #positionUs}.
|
* @param positionUs See {@link #positionUs}.
|
||||||
@ -140,7 +144,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
|||||||
MediaPeriodId loadingMediaPeriodId,
|
MediaPeriodId loadingMediaPeriodId,
|
||||||
boolean playWhenReady,
|
boolean playWhenReady,
|
||||||
@PlaybackSuppressionReason int playbackSuppressionReason,
|
@PlaybackSuppressionReason int playbackSuppressionReason,
|
||||||
float playbackSpeed,
|
PlaybackParameters playbackParameters,
|
||||||
long bufferedPositionUs,
|
long bufferedPositionUs,
|
||||||
long totalBufferedDurationUs,
|
long totalBufferedDurationUs,
|
||||||
long positionUs,
|
long positionUs,
|
||||||
@ -156,7 +160,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
|||||||
this.loadingMediaPeriodId = loadingMediaPeriodId;
|
this.loadingMediaPeriodId = loadingMediaPeriodId;
|
||||||
this.playWhenReady = playWhenReady;
|
this.playWhenReady = playWhenReady;
|
||||||
this.playbackSuppressionReason = playbackSuppressionReason;
|
this.playbackSuppressionReason = playbackSuppressionReason;
|
||||||
this.playbackSpeed = playbackSpeed;
|
this.playbackParameters = playbackParameters;
|
||||||
this.bufferedPositionUs = bufferedPositionUs;
|
this.bufferedPositionUs = bufferedPositionUs;
|
||||||
this.totalBufferedDurationUs = totalBufferedDurationUs;
|
this.totalBufferedDurationUs = totalBufferedDurationUs;
|
||||||
this.positionUs = positionUs;
|
this.positionUs = positionUs;
|
||||||
@ -201,7 +205,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
|||||||
loadingMediaPeriodId,
|
loadingMediaPeriodId,
|
||||||
playWhenReady,
|
playWhenReady,
|
||||||
playbackSuppressionReason,
|
playbackSuppressionReason,
|
||||||
playbackSpeed,
|
playbackParameters,
|
||||||
bufferedPositionUs,
|
bufferedPositionUs,
|
||||||
totalBufferedDurationUs,
|
totalBufferedDurationUs,
|
||||||
positionUs,
|
positionUs,
|
||||||
@ -228,7 +232,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
|||||||
loadingMediaPeriodId,
|
loadingMediaPeriodId,
|
||||||
playWhenReady,
|
playWhenReady,
|
||||||
playbackSuppressionReason,
|
playbackSuppressionReason,
|
||||||
playbackSpeed,
|
playbackParameters,
|
||||||
bufferedPositionUs,
|
bufferedPositionUs,
|
||||||
totalBufferedDurationUs,
|
totalBufferedDurationUs,
|
||||||
positionUs,
|
positionUs,
|
||||||
@ -255,7 +259,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
|||||||
loadingMediaPeriodId,
|
loadingMediaPeriodId,
|
||||||
playWhenReady,
|
playWhenReady,
|
||||||
playbackSuppressionReason,
|
playbackSuppressionReason,
|
||||||
playbackSpeed,
|
playbackParameters,
|
||||||
bufferedPositionUs,
|
bufferedPositionUs,
|
||||||
totalBufferedDurationUs,
|
totalBufferedDurationUs,
|
||||||
positionUs,
|
positionUs,
|
||||||
@ -282,7 +286,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
|||||||
loadingMediaPeriodId,
|
loadingMediaPeriodId,
|
||||||
playWhenReady,
|
playWhenReady,
|
||||||
playbackSuppressionReason,
|
playbackSuppressionReason,
|
||||||
playbackSpeed,
|
playbackParameters,
|
||||||
bufferedPositionUs,
|
bufferedPositionUs,
|
||||||
totalBufferedDurationUs,
|
totalBufferedDurationUs,
|
||||||
positionUs,
|
positionUs,
|
||||||
@ -309,7 +313,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
|||||||
loadingMediaPeriodId,
|
loadingMediaPeriodId,
|
||||||
playWhenReady,
|
playWhenReady,
|
||||||
playbackSuppressionReason,
|
playbackSuppressionReason,
|
||||||
playbackSpeed,
|
playbackParameters,
|
||||||
bufferedPositionUs,
|
bufferedPositionUs,
|
||||||
totalBufferedDurationUs,
|
totalBufferedDurationUs,
|
||||||
positionUs,
|
positionUs,
|
||||||
@ -336,7 +340,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
|||||||
loadingMediaPeriodId,
|
loadingMediaPeriodId,
|
||||||
playWhenReady,
|
playWhenReady,
|
||||||
playbackSuppressionReason,
|
playbackSuppressionReason,
|
||||||
playbackSpeed,
|
playbackParameters,
|
||||||
bufferedPositionUs,
|
bufferedPositionUs,
|
||||||
totalBufferedDurationUs,
|
totalBufferedDurationUs,
|
||||||
positionUs,
|
positionUs,
|
||||||
@ -367,7 +371,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
|||||||
loadingMediaPeriodId,
|
loadingMediaPeriodId,
|
||||||
playWhenReady,
|
playWhenReady,
|
||||||
playbackSuppressionReason,
|
playbackSuppressionReason,
|
||||||
playbackSpeed,
|
playbackParameters,
|
||||||
bufferedPositionUs,
|
bufferedPositionUs,
|
||||||
totalBufferedDurationUs,
|
totalBufferedDurationUs,
|
||||||
positionUs,
|
positionUs,
|
||||||
@ -375,13 +379,13 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Copies playback info with new playback speed.
|
* Copies playback info with new playback parameters.
|
||||||
*
|
*
|
||||||
* @param playbackSpeed New playback speed. See {@link #playbackSpeed}.
|
* @param playbackParameters New playback parameters. See {@link #playbackParameters}.
|
||||||
* @return Copied playback info with new playback speed.
|
* @return Copied playback info with new playback parameters.
|
||||||
*/
|
*/
|
||||||
@CheckResult
|
@CheckResult
|
||||||
public PlaybackInfo copyWithPlaybackSpeed(float playbackSpeed) {
|
public PlaybackInfo copyWithPlaybackParameters(PlaybackParameters playbackParameters) {
|
||||||
return new PlaybackInfo(
|
return new PlaybackInfo(
|
||||||
timeline,
|
timeline,
|
||||||
periodId,
|
periodId,
|
||||||
@ -394,7 +398,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
|||||||
loadingMediaPeriodId,
|
loadingMediaPeriodId,
|
||||||
playWhenReady,
|
playWhenReady,
|
||||||
playbackSuppressionReason,
|
playbackSuppressionReason,
|
||||||
playbackSpeed,
|
playbackParameters,
|
||||||
bufferedPositionUs,
|
bufferedPositionUs,
|
||||||
totalBufferedDurationUs,
|
totalBufferedDurationUs,
|
||||||
positionUs,
|
positionUs,
|
||||||
@ -422,7 +426,7 @@ import com.google.android.exoplayer2.trackselection.TrackSelectorResult;
|
|||||||
loadingMediaPeriodId,
|
loadingMediaPeriodId,
|
||||||
playWhenReady,
|
playWhenReady,
|
||||||
playbackSuppressionReason,
|
playbackSuppressionReason,
|
||||||
playbackSpeed,
|
playbackParameters,
|
||||||
bufferedPositionUs,
|
bufferedPositionUs,
|
||||||
totalBufferedDurationUs,
|
totalBufferedDurationUs,
|
||||||
positionUs,
|
positionUs,
|
||||||
|
@ -17,13 +17,9 @@ package com.google.android.exoplayer2;
|
|||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
|
||||||
/**
|
/** Parameters that apply to playback, including speed setting. */
|
||||||
* @deprecated Use {@link Player#setPlaybackSpeed(float)} and {@link
|
|
||||||
* Player.AudioComponent#setSkipSilenceEnabled(boolean)} instead.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
@Deprecated
|
|
||||||
public final class PlaybackParameters {
|
public final class PlaybackParameters {
|
||||||
|
|
||||||
/** The default playback parameters: real-time playback with no silence skipping. */
|
/** The default playback parameters: real-time playback with no silence skipping. */
|
||||||
@ -32,16 +28,34 @@ public final class PlaybackParameters {
|
|||||||
/** The factor by which playback will be sped up. */
|
/** The factor by which playback will be sped up. */
|
||||||
public final float speed;
|
public final float speed;
|
||||||
|
|
||||||
|
/** The factor by which pitch will be shifted. */
|
||||||
|
public final float pitch;
|
||||||
|
|
||||||
private final int scaledUsPerMs;
|
private final int scaledUsPerMs;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates new playback parameters that set the playback speed.
|
* Creates new playback parameters that set the playback speed. The pitch of audio will not be
|
||||||
|
* adjusted, so the effect is to time-stretch the audio.
|
||||||
*
|
*
|
||||||
* @param speed The factor by which playback will be sped up. Must be greater than zero.
|
* @param speed The factor by which playback will be sped up. Must be greater than zero.
|
||||||
*/
|
*/
|
||||||
public PlaybackParameters(float speed) {
|
public PlaybackParameters(float speed) {
|
||||||
|
this(speed, /* pitch= */ 1f);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Creates new playback parameters that set the playback speed/pitch.
|
||||||
|
*
|
||||||
|
* @param speed The factor by which playback will be sped up. Must be greater than zero.
|
||||||
|
* @param pitch The factor by which the pitch of audio will be adjusted. Must be greater than
|
||||||
|
* zero. Useful values are {@code 1} (to time-stretch audio) and the same value as passed in
|
||||||
|
* as the {@code speed} (to resample audio, which is useful for slow-motion videos).
|
||||||
|
*/
|
||||||
|
public PlaybackParameters(float speed, float pitch) {
|
||||||
Assertions.checkArgument(speed > 0);
|
Assertions.checkArgument(speed > 0);
|
||||||
|
Assertions.checkArgument(pitch > 0);
|
||||||
this.speed = speed;
|
this.speed = speed;
|
||||||
|
this.pitch = pitch;
|
||||||
scaledUsPerMs = Math.round(speed * 1000f);
|
scaledUsPerMs = Math.round(speed * 1000f);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -65,11 +79,19 @@ public final class PlaybackParameters {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
PlaybackParameters other = (PlaybackParameters) obj;
|
PlaybackParameters other = (PlaybackParameters) obj;
|
||||||
return this.speed == other.speed;
|
return this.speed == other.speed && this.pitch == other.pitch;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public int hashCode() {
|
public int hashCode() {
|
||||||
return Float.floatToRawIntBits(speed);
|
int result = 17;
|
||||||
|
result = 31 * result + Float.floatToRawIntBits(speed);
|
||||||
|
result = 31 * result + Float.floatToRawIntBits(pitch);
|
||||||
|
return result;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public String toString() {
|
||||||
|
return Util.formatInvariant("PlaybackParameters(speed=%.2f, pitch=%.2f)", speed, pitch);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -583,21 +583,15 @@ public interface Player {
|
|||||||
default void onPositionDiscontinuity(@DiscontinuityReason int reason) {}
|
default void onPositionDiscontinuity(@DiscontinuityReason int reason) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Use {@link #onPlaybackSpeedChanged(float)} and {@link
|
* Called when the current playback parameters change. The playback parameters may change due to
|
||||||
* AudioListener#onSkipSilenceEnabledChanged(boolean)} instead.
|
* a call to {@link #setPlaybackParameters(PlaybackParameters)}, or the player itself may change
|
||||||
|
* them (for example, if audio playback switches to passthrough or offload mode, where speed
|
||||||
|
* adjustment is no longer possible).
|
||||||
|
*
|
||||||
|
* @param playbackParameters The playback parameters.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
@Deprecated
|
|
||||||
default void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {}
|
default void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {}
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the current playback speed changes. The normal playback speed is 1. The speed may
|
|
||||||
* change due to a call to {@link #setPlaybackSpeed(float)}, or the player itself may change it
|
|
||||||
* (for example, if audio playback switches to passthrough mode, where speed adjustment is no
|
|
||||||
* longer possible).
|
|
||||||
*/
|
|
||||||
default void onPlaybackSpeedChanged(float playbackSpeed) {}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Seeks are processed without delay. Listen to {@link
|
* @deprecated Seeks are processed without delay. Listen to {@link
|
||||||
* #onPositionDiscontinuity(int)} with reason {@link #DISCONTINUITY_REASON_SEEK} instead.
|
* #onPositionDiscontinuity(int)} with reason {@link #DISCONTINUITY_REASON_SEEK} instead.
|
||||||
@ -810,9 +804,6 @@ public interface Player {
|
|||||||
*/
|
*/
|
||||||
int MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED = 3;
|
int MEDIA_ITEM_TRANSITION_REASON_PLAYLIST_CHANGED = 3;
|
||||||
|
|
||||||
/** The default playback speed. */
|
|
||||||
float DEFAULT_PLAYBACK_SPEED = 1.0f;
|
|
||||||
|
|
||||||
/** Returns the component of this player for audio output, or null if audio is not supported. */
|
/** Returns the component of this player for audio output, or null if audio is not supported. */
|
||||||
@Nullable
|
@Nullable
|
||||||
AudioComponent getAudioComponent();
|
AudioComponent getAudioComponent();
|
||||||
@ -1161,39 +1152,24 @@ public interface Player {
|
|||||||
void next();
|
void next();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Use {@link #setPlaybackSpeed(float)} or {@link
|
* Attempts to set the playback parameters. Passing {@code null} sets the parameters to the
|
||||||
* AudioComponent#setSkipSilenceEnabled(boolean)} instead.
|
* default, {@link PlaybackParameters#DEFAULT}, which means there is no speed or pitch adjustment.
|
||||||
|
*
|
||||||
|
* <p>Playback parameters changes may cause the player to buffer. {@link
|
||||||
|
* EventListener#onPlaybackParametersChanged(PlaybackParameters)} will be called whenever the
|
||||||
|
* currently active playback parameters change.
|
||||||
|
*
|
||||||
|
* @param playbackParameters The playback parameters, or {@code null} to use the defaults.
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
@Deprecated
|
|
||||||
void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters);
|
void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Use {@link #getPlaybackSpeed()} or {@link AudioComponent#getSkipSilenceEnabled()}
|
* Returns the currently active playback parameters.
|
||||||
* instead.
|
*
|
||||||
|
* @see EventListener#onPlaybackParametersChanged(PlaybackParameters)
|
||||||
*/
|
*/
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
@Deprecated
|
|
||||||
PlaybackParameters getPlaybackParameters();
|
PlaybackParameters getPlaybackParameters();
|
||||||
|
|
||||||
/**
|
|
||||||
* Attempts to set the playback speed.
|
|
||||||
*
|
|
||||||
* <p>Playback speed changes may cause the player to buffer. {@link
|
|
||||||
* EventListener#onPlaybackSpeedChanged(float)} will be called whenever the currently active
|
|
||||||
* playback speed change.
|
|
||||||
*
|
|
||||||
* @param playbackSpeed The playback speed.
|
|
||||||
*/
|
|
||||||
void setPlaybackSpeed(float playbackSpeed);
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns the currently active playback speed.
|
|
||||||
*
|
|
||||||
* @see EventListener#onPlaybackSpeedChanged(float)
|
|
||||||
*/
|
|
||||||
float getPlaybackSpeed();
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Stops playback without resetting the player. Use {@link #pause()} rather than this method if
|
* Stops playback without resetting the player. Use {@link #pause()} rather than this method if
|
||||||
* the intention is to pause playback.
|
* the intention is to pause playback.
|
||||||
|
@ -1030,18 +1030,23 @@ public class SimpleExoPlayer extends BasePlayer
|
|||||||
this.priorityTaskManager = priorityTaskManager;
|
this.priorityTaskManager = priorityTaskManager;
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Use {@link #setPlaybackSpeed(float)} instead. */
|
/**
|
||||||
|
* Sets the {@link PlaybackParams} governing audio playback.
|
||||||
|
*
|
||||||
|
* @param params The {@link PlaybackParams}, or null to clear any previously set parameters.
|
||||||
|
* @deprecated Use {@link #setPlaybackParameters(PlaybackParameters)}.
|
||||||
|
*/
|
||||||
@Deprecated
|
@Deprecated
|
||||||
@RequiresApi(23)
|
@RequiresApi(23)
|
||||||
public void setPlaybackParams(@Nullable PlaybackParams params) {
|
public void setPlaybackParams(@Nullable PlaybackParams params) {
|
||||||
float playbackSpeed;
|
PlaybackParameters playbackParameters;
|
||||||
if (params != null) {
|
if (params != null) {
|
||||||
params.allowDefaults();
|
params.allowDefaults();
|
||||||
playbackSpeed = params.getSpeed();
|
playbackParameters = new PlaybackParameters(params.getSpeed(), params.getPitch());
|
||||||
} else {
|
} else {
|
||||||
playbackSpeed = 1.0f;
|
playbackParameters = null;
|
||||||
}
|
}
|
||||||
setPlaybackSpeed(playbackSpeed);
|
setPlaybackParameters(playbackParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Returns the video format currently being played, or null if no video is being played. */
|
/** Returns the video format currently being played, or null if no video is being played. */
|
||||||
@ -1623,39 +1628,18 @@ public class SimpleExoPlayer extends BasePlayer
|
|||||||
player.seekTo(windowIndex, positionMs);
|
player.seekTo(windowIndex, positionMs);
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use {@link #setPlaybackSpeed(float)} and {@link #setSkipSilenceEnabled(boolean)}
|
|
||||||
* instead.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
@Deprecated
|
|
||||||
@Override
|
@Override
|
||||||
public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) {
|
public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) {
|
||||||
verifyApplicationThread();
|
verifyApplicationThread();
|
||||||
player.setPlaybackParameters(playbackParameters);
|
player.setPlaybackParameters(playbackParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Use {@link #getPlaybackSpeed()} and {@link #getSkipSilenceEnabled()} instead. */
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
@Deprecated
|
|
||||||
@Override
|
@Override
|
||||||
public PlaybackParameters getPlaybackParameters() {
|
public PlaybackParameters getPlaybackParameters() {
|
||||||
verifyApplicationThread();
|
verifyApplicationThread();
|
||||||
return player.getPlaybackParameters();
|
return player.getPlaybackParameters();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setPlaybackSpeed(float playbackSpeed) {
|
|
||||||
verifyApplicationThread();
|
|
||||||
player.setPlaybackSpeed(playbackSpeed);
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public float getPlaybackSpeed() {
|
|
||||||
verifyApplicationThread();
|
|
||||||
return player.getPlaybackSpeed();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setSeekParameters(@Nullable SeekParameters seekParameters) {
|
public void setSeekParameters(@Nullable SeekParameters seekParameters) {
|
||||||
verifyApplicationThread();
|
verifyApplicationThread();
|
||||||
@ -2231,6 +2215,13 @@ public class SimpleExoPlayer extends BasePlayer
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAudioPositionAdvancing(long playoutStartSystemTimeMs) {
|
||||||
|
for (AudioRendererEventListener audioDebugListener : audioDebugListeners) {
|
||||||
|
audioDebugListener.onAudioPositionAdvancing(playoutStartSystemTimeMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAudioUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
|
public void onAudioUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
|
||||||
for (AudioRendererEventListener audioDebugListener : audioDebugListeners) {
|
for (AudioRendererEventListener audioDebugListener : audioDebugListeners) {
|
||||||
|
@ -205,6 +205,14 @@ public class AnalyticsCollector
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public final void onAudioPositionAdvancing(long playoutStartSystemTimeMs) {
|
||||||
|
EventTime eventTime = generateReadingMediaPeriodEventTime();
|
||||||
|
for (AnalyticsListener listener : listeners) {
|
||||||
|
listener.onAudioPositionAdvancing(eventTime, playoutStartSystemTimeMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public final void onAudioUnderrun(
|
public final void onAudioUnderrun(
|
||||||
int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
|
int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
|
||||||
@ -544,12 +552,6 @@ public class AnalyticsCollector
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* @deprecated Use {@link #onPlaybackSpeedChanged(float)} and {@link
|
|
||||||
* #onSkipSilenceEnabledChanged(boolean)} instead.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
@Deprecated
|
|
||||||
@Override
|
@Override
|
||||||
public final void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
|
public final void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
|
||||||
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
|
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
|
||||||
@ -558,14 +560,6 @@ public class AnalyticsCollector
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void onPlaybackSpeedChanged(float playbackSpeed) {
|
|
||||||
EventTime eventTime = generateCurrentPlayerMediaPeriodEventTime();
|
|
||||||
for (AnalyticsListener listener : listeners) {
|
|
||||||
listener.onPlaybackSpeedChanged(eventTime, playbackSpeed);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
@Override
|
@Override
|
||||||
public final void onSeekProcessed() {
|
public final void onSeekProcessed() {
|
||||||
|
@ -279,21 +279,13 @@ public interface AnalyticsListener {
|
|||||||
default void onSeekProcessed(EventTime eventTime) {}
|
default void onSeekProcessed(EventTime eventTime) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* @deprecated Use {@link #onPlaybackSpeedChanged(EventTime, float)} and {@link
|
* Called when the playback parameters changed.
|
||||||
* #onSkipSilenceEnabledChanged(EventTime, boolean)} instead.
|
|
||||||
*/
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
@Deprecated
|
|
||||||
default void onPlaybackParametersChanged(
|
|
||||||
EventTime eventTime, PlaybackParameters playbackParameters) {}
|
|
||||||
|
|
||||||
/**
|
|
||||||
* Called when the playback speed changes.
|
|
||||||
*
|
*
|
||||||
* @param eventTime The event time.
|
* @param eventTime The event time.
|
||||||
* @param playbackSpeed The playback speed.
|
* @param playbackParameters The new playback parameters.
|
||||||
*/
|
*/
|
||||||
default void onPlaybackSpeedChanged(EventTime eventTime, float playbackSpeed) {}
|
default void onPlaybackParametersChanged(
|
||||||
|
EventTime eventTime, PlaybackParameters playbackParameters) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the repeat mode changed.
|
* Called when the repeat mode changed.
|
||||||
@ -479,6 +471,16 @@ public interface AnalyticsListener {
|
|||||||
*/
|
*/
|
||||||
default void onAudioInputFormatChanged(EventTime eventTime, Format format) {}
|
default void onAudioInputFormatChanged(EventTime eventTime, Format format) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the audio position has increased for the first time since the last pause or
|
||||||
|
* position reset.
|
||||||
|
*
|
||||||
|
* @param eventTime The event time.
|
||||||
|
* @param playoutStartSystemTimeMs The approximate derived {@link System#currentTimeMillis()} at
|
||||||
|
* which playout started.
|
||||||
|
*/
|
||||||
|
default void onAudioPositionAdvancing(EventTime eventTime, long playoutStartSystemTimeMs) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when an audio underrun occurs.
|
* Called when an audio underrun occurs.
|
||||||
*
|
*
|
||||||
|
@ -22,6 +22,7 @@ import androidx.annotation.Nullable;
|
|||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
import com.google.android.exoplayer2.Player;
|
import com.google.android.exoplayer2.Player;
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.Timeline.Period;
|
import com.google.android.exoplayer2.Timeline.Period;
|
||||||
@ -334,8 +335,9 @@ public final class PlaybackStatsListener
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlaybackSpeedChanged(EventTime eventTime, float playbackSpeed) {
|
public void onPlaybackParametersChanged(
|
||||||
this.playbackSpeed = playbackSpeed;
|
EventTime eventTime, PlaybackParameters playbackParameters) {
|
||||||
|
playbackSpeed = playbackParameters.speed;
|
||||||
maybeAddSession(eventTime);
|
maybeAddSession(eventTime);
|
||||||
for (PlaybackStatsTracker tracker : playbackStatsTrackers.values()) {
|
for (PlaybackStatsTracker tracker : playbackStatsTrackers.values()) {
|
||||||
tracker.onPlaybackSpeedChanged(eventTime, playbackSpeed);
|
tracker.onPlaybackSpeedChanged(eventTime, playbackSpeed);
|
||||||
|
@ -65,6 +65,15 @@ public interface AudioRendererEventListener {
|
|||||||
*/
|
*/
|
||||||
default void onAudioInputFormatChanged(Format format) {}
|
default void onAudioInputFormatChanged(Format format) {}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the audio position has increased for the first time since the last pause or
|
||||||
|
* position reset.
|
||||||
|
*
|
||||||
|
* @param playoutStartSystemTimeMs The approximate derived {@link System#currentTimeMillis()} at
|
||||||
|
* which playout started.
|
||||||
|
*/
|
||||||
|
default void onAudioPositionAdvancing(long playoutStartSystemTimeMs) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when an audio underrun occurs.
|
* Called when an audio underrun occurs.
|
||||||
*
|
*
|
||||||
@ -89,7 +98,7 @@ public interface AudioRendererEventListener {
|
|||||||
*/
|
*/
|
||||||
default void onSkipSilenceEnabledChanged(boolean skipSilenceEnabled) {}
|
default void onSkipSilenceEnabledChanged(boolean skipSilenceEnabled) {}
|
||||||
|
|
||||||
/** Dispatches events to a {@link AudioRendererEventListener}. */
|
/** Dispatches events to an {@link AudioRendererEventListener}. */
|
||||||
final class EventDispatcher {
|
final class EventDispatcher {
|
||||||
|
|
||||||
@Nullable private final Handler handler;
|
@Nullable private final Handler handler;
|
||||||
@ -106,20 +115,16 @@ public interface AudioRendererEventListener {
|
|||||||
this.listener = listener;
|
this.listener = listener;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Invokes {@link AudioRendererEventListener#onAudioEnabled(DecoderCounters)}. */
|
||||||
* Invokes {@link AudioRendererEventListener#onAudioEnabled(DecoderCounters)}.
|
public void enabled(DecoderCounters decoderCounters) {
|
||||||
*/
|
|
||||||
public void enabled(final DecoderCounters decoderCounters) {
|
|
||||||
if (handler != null) {
|
if (handler != null) {
|
||||||
handler.post(() -> castNonNull(listener).onAudioEnabled(decoderCounters));
|
handler.post(() -> castNonNull(listener).onAudioEnabled(decoderCounters));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Invokes {@link AudioRendererEventListener#onAudioDecoderInitialized(String, long, long)}. */
|
||||||
* Invokes {@link AudioRendererEventListener#onAudioDecoderInitialized(String, long, long)}.
|
public void decoderInitialized(
|
||||||
*/
|
String decoderName, long initializedTimestampMs, long initializationDurationMs) {
|
||||||
public void decoderInitialized(final String decoderName,
|
|
||||||
final long initializedTimestampMs, final long initializationDurationMs) {
|
|
||||||
if (handler != null) {
|
if (handler != null) {
|
||||||
handler.post(
|
handler.post(
|
||||||
() ->
|
() ->
|
||||||
@ -129,18 +134,23 @@ public interface AudioRendererEventListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Invokes {@link AudioRendererEventListener#onAudioInputFormatChanged(Format)}. */
|
||||||
* Invokes {@link AudioRendererEventListener#onAudioInputFormatChanged(Format)}.
|
public void inputFormatChanged(Format format) {
|
||||||
*/
|
|
||||||
public void inputFormatChanged(final Format format) {
|
|
||||||
if (handler != null) {
|
if (handler != null) {
|
||||||
handler.post(() -> castNonNull(listener).onAudioInputFormatChanged(format));
|
handler.post(() -> castNonNull(listener).onAudioInputFormatChanged(format));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/** Invokes {@link AudioRendererEventListener#onAudioPositionAdvancing(long)}. */
|
||||||
|
public void positionAdvancing(long playoutStartSystemTimeMs) {
|
||||||
|
if (handler != null) {
|
||||||
|
handler.post(
|
||||||
|
() -> castNonNull(listener).onAudioPositionAdvancing(playoutStartSystemTimeMs));
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
/** Invokes {@link AudioRendererEventListener#onAudioUnderrun(int, long, long)}. */
|
/** Invokes {@link AudioRendererEventListener#onAudioUnderrun(int, long, long)}. */
|
||||||
public void underrun(
|
public void underrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
|
||||||
final int bufferSize, final long bufferSizeMs, final long elapsedSinceLastFeedMs) {
|
|
||||||
if (handler != null) {
|
if (handler != null) {
|
||||||
handler.post(
|
handler.post(
|
||||||
() ->
|
() ->
|
||||||
@ -149,10 +159,8 @@ public interface AudioRendererEventListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Invokes {@link AudioRendererEventListener#onAudioDisabled(DecoderCounters)}. */
|
||||||
* Invokes {@link AudioRendererEventListener#onAudioDisabled(DecoderCounters)}.
|
public void disabled(DecoderCounters counters) {
|
||||||
*/
|
|
||||||
public void disabled(final DecoderCounters counters) {
|
|
||||||
counters.ensureUpdated();
|
counters.ensureUpdated();
|
||||||
if (handler != null) {
|
if (handler != null) {
|
||||||
handler.post(
|
handler.post(
|
||||||
@ -163,17 +171,15 @@ public interface AudioRendererEventListener {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/** Invokes {@link AudioRendererEventListener#onAudioSessionId(int)}. */
|
||||||
* Invokes {@link AudioRendererEventListener#onAudioSessionId(int)}.
|
public void audioSessionId(int audioSessionId) {
|
||||||
*/
|
|
||||||
public void audioSessionId(final int audioSessionId) {
|
|
||||||
if (handler != null) {
|
if (handler != null) {
|
||||||
handler.post(() -> castNonNull(listener).onAudioSessionId(audioSessionId));
|
handler.post(() -> castNonNull(listener).onAudioSessionId(audioSessionId));
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Invokes {@link AudioRendererEventListener#onSkipSilenceEnabledChanged(boolean)}. */
|
/** Invokes {@link AudioRendererEventListener#onSkipSilenceEnabledChanged(boolean)}. */
|
||||||
public void skipSilenceEnabledChanged(final boolean skipSilenceEnabled) {
|
public void skipSilenceEnabledChanged(boolean skipSilenceEnabled) {
|
||||||
if (handler != null) {
|
if (handler != null) {
|
||||||
handler.post(() -> castNonNull(listener).onSkipSilenceEnabledChanged(skipSilenceEnabled));
|
handler.post(() -> castNonNull(listener).onSkipSilenceEnabledChanged(skipSilenceEnabled));
|
||||||
}
|
}
|
||||||
|
@ -20,6 +20,7 @@ import androidx.annotation.IntDef;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.Documented;
|
||||||
import java.lang.annotation.Retention;
|
import java.lang.annotation.Retention;
|
||||||
import java.lang.annotation.RetentionPolicy;
|
import java.lang.annotation.RetentionPolicy;
|
||||||
@ -72,10 +73,19 @@ public interface AudioSink {
|
|||||||
*/
|
*/
|
||||||
void onPositionDiscontinuity();
|
void onPositionDiscontinuity();
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the audio sink's position has increased for the first time since it was last
|
||||||
|
* paused or flushed.
|
||||||
|
*
|
||||||
|
* @param playoutStartSystemTimeMs The approximate derived {@link System#currentTimeMillis()} at
|
||||||
|
* which playout started. Only valid if the audio track has not underrun.
|
||||||
|
*/
|
||||||
|
default void onPositionAdvancing(long playoutStartSystemTimeMs) {}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the audio sink runs out of data.
|
* Called when the audio sink runs out of data.
|
||||||
* <p>
|
*
|
||||||
* An audio sink implementation may never call this method (for example, if audio data is
|
* <p>An audio sink implementation may never call this method (for example, if audio data is
|
||||||
* consumed in batches rather than based on the sink's own clock).
|
* consumed in batches rather than based on the sink's own clock).
|
||||||
*
|
*
|
||||||
* @param bufferSize The size of the sink's buffer, in bytes.
|
* @param bufferSize The size of the sink's buffer, in bytes.
|
||||||
@ -297,16 +307,21 @@ public interface AudioSink {
|
|||||||
*/
|
*/
|
||||||
boolean hasPendingData();
|
boolean hasPendingData();
|
||||||
|
|
||||||
/** Sets the playback speed. */
|
/**
|
||||||
void setPlaybackSpeed(float playbackSpeed);
|
* Attempts to set the playback parameters. The audio sink may override these parameters if they
|
||||||
|
* are not supported.
|
||||||
|
*
|
||||||
|
* @param playbackParameters The new playback parameters to attempt to set.
|
||||||
|
*/
|
||||||
|
void setPlaybackParameters(PlaybackParameters playbackParameters);
|
||||||
|
|
||||||
/** Gets the playback speed. */
|
/** Returns the active {@link PlaybackParameters}. */
|
||||||
float getPlaybackSpeed();
|
PlaybackParameters getPlaybackParameters();
|
||||||
|
|
||||||
/** Sets whether silences should be skipped in the audio stream. */
|
/** Sets whether silences should be skipped in the audio stream. */
|
||||||
void setSkipSilenceEnabled(boolean skipSilenceEnabled);
|
void setSkipSilenceEnabled(boolean skipSilenceEnabled);
|
||||||
|
|
||||||
/** Gets whether silences are skipped in the audio stream. */
|
/** Returns whether silences are skipped in the audio stream. */
|
||||||
boolean getSkipSilenceEnabled();
|
boolean getSkipSilenceEnabled();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -48,6 +48,15 @@ import java.lang.reflect.Method;
|
|||||||
/** Listener for position tracker events. */
|
/** Listener for position tracker events. */
|
||||||
public interface Listener {
|
public interface Listener {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Called when the position tracker's position has increased for the first time since it was
|
||||||
|
* last paused or reset.
|
||||||
|
*
|
||||||
|
* @param playoutStartSystemTimeMs The approximate derived {@link System#currentTimeMillis()} at
|
||||||
|
* which playout started.
|
||||||
|
*/
|
||||||
|
void onPositionAdvancing(long playoutStartSystemTimeMs);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called when the frame position is too far from the expected frame position.
|
* Called when the frame position is too far from the expected frame position.
|
||||||
*
|
*
|
||||||
@ -145,6 +154,7 @@ import java.lang.reflect.Method;
|
|||||||
private boolean needsPassthroughWorkarounds;
|
private boolean needsPassthroughWorkarounds;
|
||||||
private long bufferSizeUs;
|
private long bufferSizeUs;
|
||||||
private float audioTrackPlaybackSpeed;
|
private float audioTrackPlaybackSpeed;
|
||||||
|
private boolean notifiedPositionIncreasing;
|
||||||
|
|
||||||
private long smoothedPlayheadOffsetUs;
|
private long smoothedPlayheadOffsetUs;
|
||||||
private long lastPlayheadSampleTimeUs;
|
private long lastPlayheadSampleTimeUs;
|
||||||
@ -287,9 +297,21 @@ import java.lang.reflect.Method;
|
|||||||
positionUs /= 1000;
|
positionUs /= 1000;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (!notifiedPositionIncreasing && positionUs > lastPositionUs) {
|
||||||
|
notifiedPositionIncreasing = true;
|
||||||
|
long mediaDurationSinceLastPositionUs = C.usToMs(positionUs - lastPositionUs);
|
||||||
|
long playoutDurationSinceLastPositionUs =
|
||||||
|
Util.getPlayoutDurationForMediaDuration(
|
||||||
|
mediaDurationSinceLastPositionUs, audioTrackPlaybackSpeed);
|
||||||
|
long playoutStartSystemTimeMs =
|
||||||
|
System.currentTimeMillis() - C.usToMs(playoutDurationSinceLastPositionUs);
|
||||||
|
listener.onPositionAdvancing(playoutStartSystemTimeMs);
|
||||||
|
}
|
||||||
|
|
||||||
lastSystemTimeUs = systemTimeUs;
|
lastSystemTimeUs = systemTimeUs;
|
||||||
lastPositionUs = positionUs;
|
lastPositionUs = positionUs;
|
||||||
lastSampleUsedGetTimestampMode = useGetTimestampMode;
|
lastSampleUsedGetTimestampMode = useGetTimestampMode;
|
||||||
|
|
||||||
return positionUs;
|
return positionUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -512,6 +534,7 @@ import java.lang.reflect.Method;
|
|||||||
lastPlayheadSampleTimeUs = 0;
|
lastPlayheadSampleTimeUs = 0;
|
||||||
lastSystemTimeUs = 0;
|
lastSystemTimeUs = 0;
|
||||||
previousModeSystemTimeUs = 0;
|
previousModeSystemTimeUs = 0;
|
||||||
|
notifiedPositionIncreasing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -29,6 +29,7 @@ import com.google.android.exoplayer2.ExoPlaybackException;
|
|||||||
import com.google.android.exoplayer2.ExoPlayer;
|
import com.google.android.exoplayer2.ExoPlayer;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.FormatHolder;
|
import com.google.android.exoplayer2.FormatHolder;
|
||||||
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
import com.google.android.exoplayer2.PlayerMessage.Target;
|
import com.google.android.exoplayer2.PlayerMessage.Target;
|
||||||
import com.google.android.exoplayer2.RendererCapabilities;
|
import com.google.android.exoplayer2.RendererCapabilities;
|
||||||
import com.google.android.exoplayer2.audio.AudioRendererEventListener.EventDispatcher;
|
import com.google.android.exoplayer2.audio.AudioRendererEventListener.EventDispatcher;
|
||||||
@ -498,13 +499,13 @@ public abstract class DecoderAudioRenderer<
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setPlaybackSpeed(float playbackSpeed) {
|
public void setPlaybackParameters(PlaybackParameters playbackParameters) {
|
||||||
audioSink.setPlaybackSpeed(playbackSpeed);
|
audioSink.setPlaybackParameters(playbackParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getPlaybackSpeed() {
|
public PlaybackParameters getPlaybackParameters() {
|
||||||
return audioSink.getPlaybackSpeed();
|
return audioSink.getPlaybackParameters();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -708,6 +709,11 @@ public abstract class DecoderAudioRenderer<
|
|||||||
DecoderAudioRenderer.this.onPositionDiscontinuity();
|
DecoderAudioRenderer.this.onPositionDiscontinuity();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPositionAdvancing(long playoutStartSystemTimeMs) {
|
||||||
|
eventDispatcher.positionAdvancing(playoutStartSystemTimeMs);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
|
public void onUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
|
||||||
eventDispatcher.underrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
|
eventDispatcher.underrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
|
||||||
|
@ -32,6 +32,7 @@ import androidx.annotation.Nullable;
|
|||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
import com.google.android.exoplayer2.audio.AudioProcessor.UnhandledAudioFormatException;
|
import com.google.android.exoplayer2.audio.AudioProcessor.UnhandledAudioFormatException;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.Log;
|
import com.google.android.exoplayer2.util.Log;
|
||||||
@ -92,14 +93,14 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
AudioProcessor[] getAudioProcessors();
|
AudioProcessor[] getAudioProcessors();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures audio processors to apply the specified playback speed immediately, returning the
|
* Configures audio processors to apply the specified playback parameters immediately, returning
|
||||||
* new playback speed, which may differ from the speed passed in. Only called when processors
|
* the new playback parameters, which may differ from those passed in. Only called when
|
||||||
* have no input pending.
|
* processors have no input pending.
|
||||||
*
|
*
|
||||||
* @param playbackSpeed The playback speed to try to apply.
|
* @param playbackParameters The playback parameters to try to apply.
|
||||||
* @return The playback speed that was actually applied.
|
* @return The playback parameters that were actually applied.
|
||||||
*/
|
*/
|
||||||
float applyPlaybackSpeed(float playbackSpeed);
|
PlaybackParameters applyPlaybackParameters(PlaybackParameters playbackParameters);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Configures audio processors to apply whether to skip silences immediately, returning the new
|
* Configures audio processors to apply whether to skip silences immediately, returning the new
|
||||||
@ -170,8 +171,10 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float applyPlaybackSpeed(float playbackSpeed) {
|
public PlaybackParameters applyPlaybackParameters(PlaybackParameters playbackParameters) {
|
||||||
return sonicAudioProcessor.setSpeed(playbackSpeed);
|
float speed = sonicAudioProcessor.setSpeed(playbackParameters.speed);
|
||||||
|
float pitch = sonicAudioProcessor.setPitch(playbackParameters.pitch);
|
||||||
|
return new PlaybackParameters(speed, pitch);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -197,6 +200,10 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
public static final float MIN_PLAYBACK_SPEED = 0.1f;
|
public static final float MIN_PLAYBACK_SPEED = 0.1f;
|
||||||
/** The maximum allowed playback speed. Higher values will be constrained to fall in range. */
|
/** The maximum allowed playback speed. Higher values will be constrained to fall in range. */
|
||||||
public static final float MAX_PLAYBACK_SPEED = 8f;
|
public static final float MAX_PLAYBACK_SPEED = 8f;
|
||||||
|
/** The minimum allowed pitch factor. Lower values will be constrained to fall in range. */
|
||||||
|
public static final float MIN_PITCH = 0.1f;
|
||||||
|
/** The maximum allowed pitch factor. Higher values will be constrained to fall in range. */
|
||||||
|
public static final float MAX_PITCH = 8f;
|
||||||
|
|
||||||
/** The default skip silence flag. */
|
/** The default skip silence flag. */
|
||||||
private static final boolean DEFAULT_SKIP_SILENCE = false;
|
private static final boolean DEFAULT_SKIP_SILENCE = false;
|
||||||
@ -296,7 +303,7 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
private AudioAttributes audioAttributes;
|
private AudioAttributes audioAttributes;
|
||||||
@Nullable private MediaPositionParameters afterDrainParameters;
|
@Nullable private MediaPositionParameters afterDrainParameters;
|
||||||
private MediaPositionParameters mediaPositionParameters;
|
private MediaPositionParameters mediaPositionParameters;
|
||||||
private float audioTrackPlaybackSpeed;
|
private PlaybackParameters audioTrackPlaybackParameters;
|
||||||
|
|
||||||
@Nullable private ByteBuffer avSyncHeader;
|
@Nullable private ByteBuffer avSyncHeader;
|
||||||
private int bytesUntilNextAvSync;
|
private int bytesUntilNextAvSync;
|
||||||
@ -418,11 +425,11 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
auxEffectInfo = new AuxEffectInfo(AuxEffectInfo.NO_AUX_EFFECT_ID, 0f);
|
auxEffectInfo = new AuxEffectInfo(AuxEffectInfo.NO_AUX_EFFECT_ID, 0f);
|
||||||
mediaPositionParameters =
|
mediaPositionParameters =
|
||||||
new MediaPositionParameters(
|
new MediaPositionParameters(
|
||||||
DEFAULT_PLAYBACK_SPEED,
|
PlaybackParameters.DEFAULT,
|
||||||
DEFAULT_SKIP_SILENCE,
|
DEFAULT_SKIP_SILENCE,
|
||||||
/* mediaTimeUs= */ 0,
|
/* mediaTimeUs= */ 0,
|
||||||
/* audioTrackPositionUs= */ 0);
|
/* audioTrackPositionUs= */ 0);
|
||||||
audioTrackPlaybackSpeed = 1f;
|
audioTrackPlaybackParameters = PlaybackParameters.DEFAULT;
|
||||||
drainingAudioProcessorIndex = C.INDEX_UNSET;
|
drainingAudioProcessorIndex = C.INDEX_UNSET;
|
||||||
activeAudioProcessors = new AudioProcessor[0];
|
activeAudioProcessors = new AudioProcessor[0];
|
||||||
outputBuffers = new ByteBuffer[0];
|
outputBuffers = new ByteBuffer[0];
|
||||||
@ -707,7 +714,7 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
// Re-apply playback parameters.
|
// Re-apply playback parameters.
|
||||||
applyAudioProcessorPlaybackSpeedAndSkipSilence(presentationTimeUs);
|
applyAudioProcessorPlaybackParametersAndSkipSilence(presentationTimeUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (!isAudioTrackInitialized()) {
|
if (!isAudioTrackInitialized()) {
|
||||||
@ -720,9 +727,9 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
startMediaTimeUsNeedsInit = false;
|
startMediaTimeUsNeedsInit = false;
|
||||||
|
|
||||||
if (enableAudioTrackPlaybackParams && Util.SDK_INT >= 23) {
|
if (enableAudioTrackPlaybackParams && Util.SDK_INT >= 23) {
|
||||||
setAudioTrackPlaybackSpeedV23(audioTrackPlaybackSpeed);
|
setAudioTrackPlaybackParametersV23(audioTrackPlaybackParameters);
|
||||||
}
|
}
|
||||||
applyAudioProcessorPlaybackSpeedAndSkipSilence(presentationTimeUs);
|
applyAudioProcessorPlaybackParametersAndSkipSilence(presentationTimeUs);
|
||||||
|
|
||||||
if (playing) {
|
if (playing) {
|
||||||
play();
|
play();
|
||||||
@ -758,7 +765,7 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
// Don't process any more input until draining completes.
|
// Don't process any more input until draining completes.
|
||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
applyAudioProcessorPlaybackSpeedAndSkipSilence(presentationTimeUs);
|
applyAudioProcessorPlaybackParametersAndSkipSilence(presentationTimeUs);
|
||||||
afterDrainParameters = null;
|
afterDrainParameters = null;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -789,7 +796,7 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
startMediaTimeUs += adjustmentUs;
|
startMediaTimeUs += adjustmentUs;
|
||||||
startMediaTimeUsNeedsSync = false;
|
startMediaTimeUsNeedsSync = false;
|
||||||
// Re-apply playback parameters because the startMediaTimeUs changed.
|
// Re-apply playback parameters because the startMediaTimeUs changed.
|
||||||
applyAudioProcessorPlaybackSpeedAndSkipSilence(presentationTimeUs);
|
applyAudioProcessorPlaybackParametersAndSkipSilence(presentationTimeUs);
|
||||||
if (listener != null && adjustmentUs != 0) {
|
if (listener != null && adjustmentUs != 0) {
|
||||||
listener.onPositionDiscontinuity();
|
listener.onPositionDiscontinuity();
|
||||||
}
|
}
|
||||||
@ -1011,26 +1018,30 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setPlaybackSpeed(float playbackSpeed) {
|
public void setPlaybackParameters(PlaybackParameters playbackParameters) {
|
||||||
playbackSpeed = Util.constrainValue(playbackSpeed, MIN_PLAYBACK_SPEED, MAX_PLAYBACK_SPEED);
|
playbackParameters =
|
||||||
|
new PlaybackParameters(
|
||||||
|
Util.constrainValue(playbackParameters.speed, MIN_PLAYBACK_SPEED, MAX_PLAYBACK_SPEED),
|
||||||
|
Util.constrainValue(playbackParameters.pitch, MIN_PITCH, MAX_PITCH));
|
||||||
if (enableAudioTrackPlaybackParams && Util.SDK_INT >= 23) {
|
if (enableAudioTrackPlaybackParams && Util.SDK_INT >= 23) {
|
||||||
setAudioTrackPlaybackSpeedV23(playbackSpeed);
|
setAudioTrackPlaybackParametersV23(playbackParameters);
|
||||||
} else {
|
} else {
|
||||||
setAudioProcessorPlaybackSpeedAndSkipSilence(playbackSpeed, getSkipSilenceEnabled());
|
setAudioProcessorPlaybackParametersAndSkipSilence(
|
||||||
|
playbackParameters, getSkipSilenceEnabled());
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getPlaybackSpeed() {
|
public PlaybackParameters getPlaybackParameters() {
|
||||||
// We use either audio processor speed adjustment or AudioTrack playback parameters, so one of
|
return enableAudioTrackPlaybackParams
|
||||||
// the operands is always 1f.
|
? audioTrackPlaybackParameters
|
||||||
return getAudioProcessorPlaybackSpeed() * audioTrackPlaybackSpeed;
|
: getAudioProcessorPlaybackParameters();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setSkipSilenceEnabled(boolean skipSilenceEnabled) {
|
public void setSkipSilenceEnabled(boolean skipSilenceEnabled) {
|
||||||
setAudioProcessorPlaybackSpeedAndSkipSilence(
|
setAudioProcessorPlaybackParametersAndSkipSilence(
|
||||||
getAudioProcessorPlaybackSpeed(), skipSilenceEnabled);
|
getAudioProcessorPlaybackParameters(), skipSilenceEnabled);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -1212,7 +1223,7 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
framesPerEncodedSample = 0;
|
framesPerEncodedSample = 0;
|
||||||
mediaPositionParameters =
|
mediaPositionParameters =
|
||||||
new MediaPositionParameters(
|
new MediaPositionParameters(
|
||||||
getAudioProcessorPlaybackSpeed(),
|
getAudioProcessorPlaybackParameters(),
|
||||||
getSkipSilenceEnabled(),
|
getSkipSilenceEnabled(),
|
||||||
/* mediaTimeUs= */ 0,
|
/* mediaTimeUs= */ 0,
|
||||||
/* audioTrackPositionUs= */ 0);
|
/* audioTrackPositionUs= */ 0);
|
||||||
@ -1249,12 +1260,13 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@RequiresApi(23)
|
@RequiresApi(23)
|
||||||
private void setAudioTrackPlaybackSpeedV23(float audioTrackPlaybackSpeed) {
|
private void setAudioTrackPlaybackParametersV23(PlaybackParameters audioTrackPlaybackParameters) {
|
||||||
if (isAudioTrackInitialized()) {
|
if (isAudioTrackInitialized()) {
|
||||||
PlaybackParams playbackParams =
|
PlaybackParams playbackParams =
|
||||||
new PlaybackParams()
|
new PlaybackParams()
|
||||||
.allowDefaults()
|
.allowDefaults()
|
||||||
.setSpeed(audioTrackPlaybackSpeed)
|
.setSpeed(audioTrackPlaybackParameters.speed)
|
||||||
|
.setPitch(audioTrackPlaybackParameters.pitch)
|
||||||
.setAudioFallbackMode(PlaybackParams.AUDIO_FALLBACK_MODE_FAIL);
|
.setAudioFallbackMode(PlaybackParams.AUDIO_FALLBACK_MODE_FAIL);
|
||||||
try {
|
try {
|
||||||
audioTrack.setPlaybackParams(playbackParams);
|
audioTrack.setPlaybackParams(playbackParams);
|
||||||
@ -1262,20 +1274,22 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
Log.w(TAG, "Failed to set playback params", e);
|
Log.w(TAG, "Failed to set playback params", e);
|
||||||
}
|
}
|
||||||
// Update the speed using the actual effective speed from the audio track.
|
// Update the speed using the actual effective speed from the audio track.
|
||||||
audioTrackPlaybackSpeed = audioTrack.getPlaybackParams().getSpeed();
|
audioTrackPlaybackParameters =
|
||||||
audioTrackPositionTracker.setAudioTrackPlaybackSpeed(audioTrackPlaybackSpeed);
|
new PlaybackParameters(
|
||||||
|
audioTrack.getPlaybackParams().getSpeed(), audioTrack.getPlaybackParams().getPitch());
|
||||||
|
audioTrackPositionTracker.setAudioTrackPlaybackSpeed(audioTrackPlaybackParameters.speed);
|
||||||
}
|
}
|
||||||
this.audioTrackPlaybackSpeed = audioTrackPlaybackSpeed;
|
this.audioTrackPlaybackParameters = audioTrackPlaybackParameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void setAudioProcessorPlaybackSpeedAndSkipSilence(
|
private void setAudioProcessorPlaybackParametersAndSkipSilence(
|
||||||
float playbackSpeed, boolean skipSilence) {
|
PlaybackParameters playbackParameters, boolean skipSilence) {
|
||||||
MediaPositionParameters currentMediaPositionParameters = getMediaPositionParameters();
|
MediaPositionParameters currentMediaPositionParameters = getMediaPositionParameters();
|
||||||
if (playbackSpeed != currentMediaPositionParameters.playbackSpeed
|
if (!playbackParameters.equals(currentMediaPositionParameters.playbackParameters)
|
||||||
|| skipSilence != currentMediaPositionParameters.skipSilence) {
|
|| skipSilence != currentMediaPositionParameters.skipSilence) {
|
||||||
MediaPositionParameters mediaPositionParameters =
|
MediaPositionParameters mediaPositionParameters =
|
||||||
new MediaPositionParameters(
|
new MediaPositionParameters(
|
||||||
playbackSpeed,
|
playbackParameters,
|
||||||
skipSilence,
|
skipSilence,
|
||||||
/* mediaTimeUs= */ C.TIME_UNSET,
|
/* mediaTimeUs= */ C.TIME_UNSET,
|
||||||
/* audioTrackPositionUs= */ C.TIME_UNSET);
|
/* audioTrackPositionUs= */ C.TIME_UNSET);
|
||||||
@ -1291,8 +1305,8 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private float getAudioProcessorPlaybackSpeed() {
|
private PlaybackParameters getAudioProcessorPlaybackParameters() {
|
||||||
return getMediaPositionParameters().playbackSpeed;
|
return getMediaPositionParameters().playbackParameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
private MediaPositionParameters getMediaPositionParameters() {
|
private MediaPositionParameters getMediaPositionParameters() {
|
||||||
@ -1304,18 +1318,18 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
: mediaPositionParameters;
|
: mediaPositionParameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
private void applyAudioProcessorPlaybackSpeedAndSkipSilence(long presentationTimeUs) {
|
private void applyAudioProcessorPlaybackParametersAndSkipSilence(long presentationTimeUs) {
|
||||||
float playbackSpeed =
|
PlaybackParameters playbackParameters =
|
||||||
configuration.canApplyPlaybackParameters
|
configuration.canApplyPlaybackParameters
|
||||||
? audioProcessorChain.applyPlaybackSpeed(getAudioProcessorPlaybackSpeed())
|
? audioProcessorChain.applyPlaybackParameters(getAudioProcessorPlaybackParameters())
|
||||||
: DEFAULT_PLAYBACK_SPEED;
|
: PlaybackParameters.DEFAULT;
|
||||||
boolean skipSilenceEnabled =
|
boolean skipSilenceEnabled =
|
||||||
configuration.canApplyPlaybackParameters
|
configuration.canApplyPlaybackParameters
|
||||||
? audioProcessorChain.applySkipSilenceEnabled(getSkipSilenceEnabled())
|
? audioProcessorChain.applySkipSilenceEnabled(getSkipSilenceEnabled())
|
||||||
: DEFAULT_SKIP_SILENCE;
|
: DEFAULT_SKIP_SILENCE;
|
||||||
mediaPositionParametersCheckpoints.add(
|
mediaPositionParametersCheckpoints.add(
|
||||||
new MediaPositionParameters(
|
new MediaPositionParameters(
|
||||||
playbackSpeed,
|
playbackParameters,
|
||||||
skipSilenceEnabled,
|
skipSilenceEnabled,
|
||||||
/* mediaTimeUs= */ max(0, presentationTimeUs),
|
/* mediaTimeUs= */ max(0, presentationTimeUs),
|
||||||
/* audioTrackPositionUs= */ configuration.framesToDurationUs(getWrittenFrames())));
|
/* audioTrackPositionUs= */ configuration.framesToDurationUs(getWrittenFrames())));
|
||||||
@ -1340,7 +1354,7 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
|
|
||||||
long playoutDurationSinceLastCheckpoint =
|
long playoutDurationSinceLastCheckpoint =
|
||||||
positionUs - mediaPositionParameters.audioTrackPositionUs;
|
positionUs - mediaPositionParameters.audioTrackPositionUs;
|
||||||
if (mediaPositionParameters.playbackSpeed != 1f) {
|
if (!mediaPositionParameters.playbackParameters.equals(PlaybackParameters.DEFAULT)) {
|
||||||
if (mediaPositionParametersCheckpoints.isEmpty()) {
|
if (mediaPositionParametersCheckpoints.isEmpty()) {
|
||||||
playoutDurationSinceLastCheckpoint =
|
playoutDurationSinceLastCheckpoint =
|
||||||
audioProcessorChain.getMediaDuration(playoutDurationSinceLastCheckpoint);
|
audioProcessorChain.getMediaDuration(playoutDurationSinceLastCheckpoint);
|
||||||
@ -1348,7 +1362,8 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
// Playing data at a previous playback speed, so fall back to multiplying by the speed.
|
// Playing data at a previous playback speed, so fall back to multiplying by the speed.
|
||||||
playoutDurationSinceLastCheckpoint =
|
playoutDurationSinceLastCheckpoint =
|
||||||
Util.getMediaDurationForPlayoutDuration(
|
Util.getMediaDurationForPlayoutDuration(
|
||||||
playoutDurationSinceLastCheckpoint, mediaPositionParameters.playbackSpeed);
|
playoutDurationSinceLastCheckpoint,
|
||||||
|
mediaPositionParameters.playbackParameters.speed);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return mediaPositionParameters.mediaTimeUs + playoutDurationSinceLastCheckpoint;
|
return mediaPositionParameters.mediaTimeUs + playoutDurationSinceLastCheckpoint;
|
||||||
@ -1692,8 +1707,8 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
/** Stores parameters used to calculate the current media position. */
|
/** Stores parameters used to calculate the current media position. */
|
||||||
private static final class MediaPositionParameters {
|
private static final class MediaPositionParameters {
|
||||||
|
|
||||||
/** The playback speed. */
|
/** The playback parameters. */
|
||||||
public final float playbackSpeed;
|
public final PlaybackParameters playbackParameters;
|
||||||
/** Whether to skip silences. */
|
/** Whether to skip silences. */
|
||||||
public final boolean skipSilence;
|
public final boolean skipSilence;
|
||||||
/** The media time from which the playback parameters apply, in microseconds. */
|
/** The media time from which the playback parameters apply, in microseconds. */
|
||||||
@ -1702,8 +1717,11 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
public final long audioTrackPositionUs;
|
public final long audioTrackPositionUs;
|
||||||
|
|
||||||
private MediaPositionParameters(
|
private MediaPositionParameters(
|
||||||
float playbackSpeed, boolean skipSilence, long mediaTimeUs, long audioTrackPositionUs) {
|
PlaybackParameters playbackParameters,
|
||||||
this.playbackSpeed = playbackSpeed;
|
boolean skipSilence,
|
||||||
|
long mediaTimeUs,
|
||||||
|
long audioTrackPositionUs) {
|
||||||
|
this.playbackParameters = playbackParameters;
|
||||||
this.skipSilence = skipSilence;
|
this.skipSilence = skipSilence;
|
||||||
this.mediaTimeUs = mediaTimeUs;
|
this.mediaTimeUs = mediaTimeUs;
|
||||||
this.audioTrackPositionUs = audioTrackPositionUs;
|
this.audioTrackPositionUs = audioTrackPositionUs;
|
||||||
@ -1776,6 +1794,13 @@ public final class DefaultAudioSink implements AudioSink {
|
|||||||
Log.w(TAG, "Ignoring impossibly large audio latency: " + latencyUs);
|
Log.w(TAG, "Ignoring impossibly large audio latency: " + latencyUs);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPositionAdvancing(long playoutStartSystemTimeMs) {
|
||||||
|
if (listener != null) {
|
||||||
|
listener.onPositionAdvancing(playoutStartSystemTimeMs);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUnderrun(int bufferSize, long bufferSizeMs) {
|
public void onUnderrun(int bufferSize, long bufferSizeMs) {
|
||||||
if (listener != null) {
|
if (listener != null) {
|
||||||
|
@ -17,6 +17,7 @@ package com.google.android.exoplayer2.audio;
|
|||||||
|
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
|
|
||||||
/** An overridable {@link AudioSink} implementation forwarding all methods to another sink. */
|
/** An overridable {@link AudioSink} implementation forwarding all methods to another sink. */
|
||||||
@ -88,13 +89,13 @@ public class ForwardingAudioSink implements AudioSink {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setPlaybackSpeed(float playbackSpeed) {
|
public void setPlaybackParameters(PlaybackParameters playbackParameters) {
|
||||||
sink.setPlaybackSpeed(playbackSpeed);
|
sink.setPlaybackParameters(playbackParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getPlaybackSpeed() {
|
public PlaybackParameters getPlaybackParameters() {
|
||||||
return sink.getPlaybackSpeed();
|
return sink.getPlaybackParameters();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -33,6 +33,7 @@ import com.google.android.exoplayer2.ExoPlaybackException;
|
|||||||
import com.google.android.exoplayer2.ExoPlayer;
|
import com.google.android.exoplayer2.ExoPlayer;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.FormatHolder;
|
import com.google.android.exoplayer2.FormatHolder;
|
||||||
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
import com.google.android.exoplayer2.PlayerMessage.Target;
|
import com.google.android.exoplayer2.PlayerMessage.Target;
|
||||||
import com.google.android.exoplayer2.RendererCapabilities;
|
import com.google.android.exoplayer2.RendererCapabilities;
|
||||||
import com.google.android.exoplayer2.audio.AudioRendererEventListener.EventDispatcher;
|
import com.google.android.exoplayer2.audio.AudioRendererEventListener.EventDispatcher;
|
||||||
@ -545,13 +546,13 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setPlaybackSpeed(float playbackSpeed) {
|
public void setPlaybackParameters(PlaybackParameters playbackParameters) {
|
||||||
audioSink.setPlaybackSpeed(playbackSpeed);
|
audioSink.setPlaybackParameters(playbackParameters);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getPlaybackSpeed() {
|
public PlaybackParameters getPlaybackParameters() {
|
||||||
return audioSink.getPlaybackSpeed();
|
return audioSink.getPlaybackParameters();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -828,6 +829,11 @@ public class MediaCodecAudioRenderer extends MediaCodecRenderer implements Media
|
|||||||
MediaCodecAudioRenderer.this.onPositionDiscontinuity();
|
MediaCodecAudioRenderer.this.onPositionDiscontinuity();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onPositionAdvancing(long playoutStartSystemTimeMs) {
|
||||||
|
eventDispatcher.positionAdvancing(playoutStartSystemTimeMs);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
|
public void onUnderrun(int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
|
||||||
eventDispatcher.underrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
|
eventDispatcher.underrun(bufferSize, bufferSizeMs, elapsedSinceLastFeedMs);
|
||||||
|
@ -37,6 +37,7 @@ import java.util.Arrays;
|
|||||||
private final int inputSampleRateHz;
|
private final int inputSampleRateHz;
|
||||||
private final int channelCount;
|
private final int channelCount;
|
||||||
private final float speed;
|
private final float speed;
|
||||||
|
private final float pitch;
|
||||||
private final float rate;
|
private final float rate;
|
||||||
private final int minPeriod;
|
private final int minPeriod;
|
||||||
private final int maxPeriod;
|
private final int maxPeriod;
|
||||||
@ -63,12 +64,15 @@ import java.util.Arrays;
|
|||||||
* @param inputSampleRateHz The sample rate of input audio, in hertz.
|
* @param inputSampleRateHz The sample rate of input audio, in hertz.
|
||||||
* @param channelCount The number of channels in the input audio.
|
* @param channelCount The number of channels in the input audio.
|
||||||
* @param speed The speedup factor for output audio.
|
* @param speed The speedup factor for output audio.
|
||||||
|
* @param pitch The pitch factor for output audio.
|
||||||
* @param outputSampleRateHz The sample rate for output audio, in hertz.
|
* @param outputSampleRateHz The sample rate for output audio, in hertz.
|
||||||
*/
|
*/
|
||||||
public Sonic(int inputSampleRateHz, int channelCount, float speed, int outputSampleRateHz) {
|
public Sonic(
|
||||||
|
int inputSampleRateHz, int channelCount, float speed, float pitch, int outputSampleRateHz) {
|
||||||
this.inputSampleRateHz = inputSampleRateHz;
|
this.inputSampleRateHz = inputSampleRateHz;
|
||||||
this.channelCount = channelCount;
|
this.channelCount = channelCount;
|
||||||
this.speed = speed;
|
this.speed = speed;
|
||||||
|
this.pitch = pitch;
|
||||||
rate = (float) inputSampleRateHz / outputSampleRateHz;
|
rate = (float) inputSampleRateHz / outputSampleRateHz;
|
||||||
minPeriod = inputSampleRateHz / MAXIMUM_PITCH;
|
minPeriod = inputSampleRateHz / MAXIMUM_PITCH;
|
||||||
maxPeriod = inputSampleRateHz / MINIMUM_PITCH;
|
maxPeriod = inputSampleRateHz / MINIMUM_PITCH;
|
||||||
@ -118,8 +122,10 @@ import java.util.Arrays;
|
|||||||
*/
|
*/
|
||||||
public void queueEndOfStream() {
|
public void queueEndOfStream() {
|
||||||
int remainingFrameCount = inputFrameCount;
|
int remainingFrameCount = inputFrameCount;
|
||||||
|
float s = speed / pitch;
|
||||||
|
float r = rate * pitch;
|
||||||
int expectedOutputFrames =
|
int expectedOutputFrames =
|
||||||
outputFrameCount + (int) ((remainingFrameCount / speed + pitchFrameCount) / rate + 0.5f);
|
outputFrameCount + (int) ((remainingFrameCount / s + pitchFrameCount) / r + 0.5f);
|
||||||
|
|
||||||
// Add enough silence to flush both input and pitch buffers.
|
// Add enough silence to flush both input and pitch buffers.
|
||||||
inputBuffer =
|
inputBuffer =
|
||||||
@ -464,14 +470,16 @@ import java.util.Arrays;
|
|||||||
private void processStreamInput() {
|
private void processStreamInput() {
|
||||||
// Resample as many pitch periods as we have buffered on the input.
|
// Resample as many pitch periods as we have buffered on the input.
|
||||||
int originalOutputFrameCount = outputFrameCount;
|
int originalOutputFrameCount = outputFrameCount;
|
||||||
if (speed > 1.00001 || speed < 0.99999) {
|
float s = speed / pitch;
|
||||||
changeSpeed(speed);
|
float r = rate * pitch;
|
||||||
|
if (s > 1.00001 || s < 0.99999) {
|
||||||
|
changeSpeed(s);
|
||||||
} else {
|
} else {
|
||||||
copyToOutput(inputBuffer, 0, inputFrameCount);
|
copyToOutput(inputBuffer, 0, inputFrameCount);
|
||||||
inputFrameCount = 0;
|
inputFrameCount = 0;
|
||||||
}
|
}
|
||||||
if (rate != 1.0f) {
|
if (r != 1.0f) {
|
||||||
adjustRate(rate, originalOutputFrameCount);
|
adjustRate(r, originalOutputFrameCount);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -43,6 +43,7 @@ public final class SonicAudioProcessor implements AudioProcessor {
|
|||||||
|
|
||||||
private int pendingOutputSampleRate;
|
private int pendingOutputSampleRate;
|
||||||
private float speed;
|
private float speed;
|
||||||
|
private float pitch;
|
||||||
|
|
||||||
private AudioFormat pendingInputAudioFormat;
|
private AudioFormat pendingInputAudioFormat;
|
||||||
private AudioFormat pendingOutputAudioFormat;
|
private AudioFormat pendingOutputAudioFormat;
|
||||||
@ -61,6 +62,7 @@ public final class SonicAudioProcessor implements AudioProcessor {
|
|||||||
/** Creates a new Sonic audio processor. */
|
/** Creates a new Sonic audio processor. */
|
||||||
public SonicAudioProcessor() {
|
public SonicAudioProcessor() {
|
||||||
speed = 1f;
|
speed = 1f;
|
||||||
|
pitch = 1f;
|
||||||
pendingInputAudioFormat = AudioFormat.NOT_SET;
|
pendingInputAudioFormat = AudioFormat.NOT_SET;
|
||||||
pendingOutputAudioFormat = AudioFormat.NOT_SET;
|
pendingOutputAudioFormat = AudioFormat.NOT_SET;
|
||||||
inputAudioFormat = AudioFormat.NOT_SET;
|
inputAudioFormat = AudioFormat.NOT_SET;
|
||||||
@ -87,6 +89,22 @@ public final class SonicAudioProcessor implements AudioProcessor {
|
|||||||
return speed;
|
return speed;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the playback pitch. This method may only be called after draining data through the
|
||||||
|
* processor. The value returned by {@link #isActive()} may change, and the processor must be
|
||||||
|
* {@link #flush() flushed} before queueing more data.
|
||||||
|
*
|
||||||
|
* @param pitch The requested new pitch.
|
||||||
|
* @return The actual new pitch.
|
||||||
|
*/
|
||||||
|
public float setPitch(float pitch) {
|
||||||
|
if (this.pitch != pitch) {
|
||||||
|
this.pitch = pitch;
|
||||||
|
pendingSonicRecreation = true;
|
||||||
|
}
|
||||||
|
return pitch;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the sample rate for output audio, in Hertz. Pass {@link #SAMPLE_RATE_NO_CHANGE} to output
|
* Sets the sample rate for output audio, in Hertz. Pass {@link #SAMPLE_RATE_NO_CHANGE} to output
|
||||||
* audio at the same sample rate as the input. After calling this method, call {@link
|
* audio at the same sample rate as the input. After calling this method, call {@link
|
||||||
@ -140,6 +158,7 @@ public final class SonicAudioProcessor implements AudioProcessor {
|
|||||||
public boolean isActive() {
|
public boolean isActive() {
|
||||||
return pendingOutputAudioFormat.sampleRate != Format.NO_VALUE
|
return pendingOutputAudioFormat.sampleRate != Format.NO_VALUE
|
||||||
&& (Math.abs(speed - 1f) >= CLOSE_THRESHOLD
|
&& (Math.abs(speed - 1f) >= CLOSE_THRESHOLD
|
||||||
|
|| Math.abs(pitch - 1f) >= CLOSE_THRESHOLD
|
||||||
|| pendingOutputAudioFormat.sampleRate != pendingInputAudioFormat.sampleRate);
|
|| pendingOutputAudioFormat.sampleRate != pendingInputAudioFormat.sampleRate);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -200,6 +219,7 @@ public final class SonicAudioProcessor implements AudioProcessor {
|
|||||||
inputAudioFormat.sampleRate,
|
inputAudioFormat.sampleRate,
|
||||||
inputAudioFormat.channelCount,
|
inputAudioFormat.channelCount,
|
||||||
speed,
|
speed,
|
||||||
|
pitch,
|
||||||
outputAudioFormat.sampleRate);
|
outputAudioFormat.sampleRate);
|
||||||
} else if (sonic != null) {
|
} else if (sonic != null) {
|
||||||
sonic.flush();
|
sonic.flush();
|
||||||
@ -214,6 +234,7 @@ public final class SonicAudioProcessor implements AudioProcessor {
|
|||||||
@Override
|
@Override
|
||||||
public void reset() {
|
public void reset() {
|
||||||
speed = 1f;
|
speed = 1f;
|
||||||
|
pitch = 1f;
|
||||||
pendingInputAudioFormat = AudioFormat.NOT_SET;
|
pendingInputAudioFormat = AudioFormat.NOT_SET;
|
||||||
pendingOutputAudioFormat = AudioFormat.NOT_SET;
|
pendingOutputAudioFormat = AudioFormat.NOT_SET;
|
||||||
inputAudioFormat = AudioFormat.NOT_SET;
|
inputAudioFormat = AudioFormat.NOT_SET;
|
||||||
|
@ -409,7 +409,7 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
|||||||
/**
|
/**
|
||||||
* Sets the mode, which determines the role of sessions acquired from the instance. This must be
|
* Sets the mode, which determines the role of sessions acquired from the instance. This must be
|
||||||
* called before {@link #acquireSession(Looper, DrmSessionEventListener.EventDispatcher, Format)}
|
* called before {@link #acquireSession(Looper, DrmSessionEventListener.EventDispatcher, Format)}
|
||||||
* or {@link #acquirePlaceholderSession} is called.
|
* is called.
|
||||||
*
|
*
|
||||||
* <p>By default, the mode is {@link #MODE_PLAYBACK} and a streaming license is requested when
|
* <p>By default, the mode is {@link #MODE_PLAYBACK} and a streaming license is requested when
|
||||||
* required.
|
* required.
|
||||||
@ -469,34 +469,6 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
|||||||
|
|
||||||
@Override
|
@Override
|
||||||
@Nullable
|
@Nullable
|
||||||
public DrmSession acquirePlaceholderSession(Looper playbackLooper, int trackType) {
|
|
||||||
initPlaybackLooper(playbackLooper);
|
|
||||||
ExoMediaDrm exoMediaDrm = Assertions.checkNotNull(this.exoMediaDrm);
|
|
||||||
boolean avoidPlaceholderDrmSessions =
|
|
||||||
FrameworkMediaCrypto.class.equals(exoMediaDrm.getExoMediaCryptoType())
|
|
||||||
&& FrameworkMediaCrypto.WORKAROUND_DEVICE_NEEDS_KEYS_TO_CONFIGURE_CODEC;
|
|
||||||
// Avoid attaching a session to sparse formats.
|
|
||||||
if (avoidPlaceholderDrmSessions
|
|
||||||
|| Util.linearSearch(useDrmSessionsForClearContentTrackTypes, trackType) == C.INDEX_UNSET
|
|
||||||
|| exoMediaDrm.getExoMediaCryptoType() == null) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
maybeCreateMediaDrmHandler(playbackLooper);
|
|
||||||
if (placeholderDrmSession == null) {
|
|
||||||
DefaultDrmSession placeholderDrmSession =
|
|
||||||
createAndAcquireSessionWithRetry(
|
|
||||||
/* schemeDatas= */ ImmutableList.of(),
|
|
||||||
/* isPlaceholderSession= */ true,
|
|
||||||
/* eventDispatcher= */ null);
|
|
||||||
sessions.add(placeholderDrmSession);
|
|
||||||
this.placeholderDrmSession = placeholderDrmSession;
|
|
||||||
} else {
|
|
||||||
placeholderDrmSession.acquire(/* eventDispatcher= */ null);
|
|
||||||
}
|
|
||||||
return placeholderDrmSession;
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public DrmSession acquireSession(
|
public DrmSession acquireSession(
|
||||||
Looper playbackLooper,
|
Looper playbackLooper,
|
||||||
@Nullable DrmSessionEventListener.EventDispatcher eventDispatcher,
|
@Nullable DrmSessionEventListener.EventDispatcher eventDispatcher,
|
||||||
@ -504,6 +476,11 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
|||||||
initPlaybackLooper(playbackLooper);
|
initPlaybackLooper(playbackLooper);
|
||||||
maybeCreateMediaDrmHandler(playbackLooper);
|
maybeCreateMediaDrmHandler(playbackLooper);
|
||||||
|
|
||||||
|
if (format.drmInitData == null) {
|
||||||
|
// Content is not encrypted.
|
||||||
|
return maybeAcquirePlaceholderSession(MimeTypes.getTrackType(format.sampleMimeType));
|
||||||
|
}
|
||||||
|
|
||||||
@Nullable List<SchemeData> schemeDatas = null;
|
@Nullable List<SchemeData> schemeDatas = null;
|
||||||
if (offlineLicenseKeySetId == null) {
|
if (offlineLicenseKeySetId == null) {
|
||||||
schemeDatas = getSchemeDatas(Assertions.checkNotNull(format.drmInitData), uuid, false);
|
schemeDatas = getSchemeDatas(Assertions.checkNotNull(format.drmInitData), uuid, false);
|
||||||
@ -565,6 +542,32 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
|||||||
|
|
||||||
// Internal methods.
|
// Internal methods.
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
private DrmSession maybeAcquirePlaceholderSession(int trackType) {
|
||||||
|
ExoMediaDrm exoMediaDrm = Assertions.checkNotNull(this.exoMediaDrm);
|
||||||
|
boolean avoidPlaceholderDrmSessions =
|
||||||
|
FrameworkMediaCrypto.class.equals(exoMediaDrm.getExoMediaCryptoType())
|
||||||
|
&& FrameworkMediaCrypto.WORKAROUND_DEVICE_NEEDS_KEYS_TO_CONFIGURE_CODEC;
|
||||||
|
// Avoid attaching a session to sparse formats.
|
||||||
|
if (avoidPlaceholderDrmSessions
|
||||||
|
|| Util.linearSearch(useDrmSessionsForClearContentTrackTypes, trackType) == C.INDEX_UNSET
|
||||||
|
|| UnsupportedMediaCrypto.class.equals(exoMediaDrm.getExoMediaCryptoType())) {
|
||||||
|
return null;
|
||||||
|
}
|
||||||
|
if (placeholderDrmSession == null) {
|
||||||
|
DefaultDrmSession placeholderDrmSession =
|
||||||
|
createAndAcquireSessionWithRetry(
|
||||||
|
/* schemeDatas= */ ImmutableList.of(),
|
||||||
|
/* isPlaceholderSession= */ true,
|
||||||
|
/* eventDispatcher= */ null);
|
||||||
|
sessions.add(placeholderDrmSession);
|
||||||
|
this.placeholderDrmSession = placeholderDrmSession;
|
||||||
|
} else {
|
||||||
|
placeholderDrmSession.acquire(/* eventDispatcher= */ null);
|
||||||
|
}
|
||||||
|
return placeholderDrmSession;
|
||||||
|
}
|
||||||
|
|
||||||
private boolean canAcquireSession(DrmInitData drmInitData) {
|
private boolean canAcquireSession(DrmInitData drmInitData) {
|
||||||
if (offlineLicenseKeySetId != null) {
|
if (offlineLicenseKeySetId != null) {
|
||||||
// An offline license can be restored so a session can always be acquired.
|
// An offline license can be restored so a session can always be acquired.
|
||||||
@ -585,12 +588,16 @@ public class DefaultDrmSessionManager implements DrmSessionManager {
|
|||||||
if (schemeType == null || C.CENC_TYPE_cenc.equals(schemeType)) {
|
if (schemeType == null || C.CENC_TYPE_cenc.equals(schemeType)) {
|
||||||
// If there is no scheme information, assume patternless AES-CTR.
|
// If there is no scheme information, assume patternless AES-CTR.
|
||||||
return true;
|
return true;
|
||||||
} else if (C.CENC_TYPE_cbc1.equals(schemeType)
|
} else if (C.CENC_TYPE_cbcs.equals(schemeType)) {
|
||||||
|| C.CENC_TYPE_cbcs.equals(schemeType)
|
// Support for cbcs (AES-CBC with pattern encryption) was added in API 24. However, the
|
||||||
|| C.CENC_TYPE_cens.equals(schemeType)) {
|
|
||||||
// API support for AES-CBC and pattern encryption was added in API 24. However, the
|
|
||||||
// implementation was not stable until API 25.
|
// implementation was not stable until API 25.
|
||||||
return Util.SDK_INT >= 25;
|
return Util.SDK_INT >= 25;
|
||||||
|
} else if (C.CENC_TYPE_cbc1.equals(schemeType) || C.CENC_TYPE_cens.equals(schemeType)) {
|
||||||
|
// Support for cbc1 (AES-CTR with pattern encryption) and cens (AES-CBC without pattern
|
||||||
|
// encryption) was also added in API 24 and made stable from API 25, however support was
|
||||||
|
// removed from API 30. Since the range of API levels for which these modes are usable is too
|
||||||
|
// small to be useful, we don't indicate support on any API level.
|
||||||
|
return false;
|
||||||
}
|
}
|
||||||
// Unknown schemes, assume one of them is supported.
|
// Unknown schemes, assume one of them is supported.
|
||||||
return true;
|
return true;
|
||||||
|
@ -17,7 +17,6 @@ package com.google.android.exoplayer2.drm;
|
|||||||
|
|
||||||
import android.os.Looper;
|
import android.os.Looper;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
|
|
||||||
/** Manages a DRM session. */
|
/** Manages a DRM session. */
|
||||||
@ -33,13 +32,19 @@ public interface DrmSessionManager {
|
|||||||
new DrmSessionManager() {
|
new DrmSessionManager() {
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@Nullable
|
||||||
public DrmSession acquireSession(
|
public DrmSession acquireSession(
|
||||||
Looper playbackLooper,
|
Looper playbackLooper,
|
||||||
@Nullable DrmSessionEventListener.EventDispatcher eventDispatcher,
|
@Nullable DrmSessionEventListener.EventDispatcher eventDispatcher,
|
||||||
Format format) {
|
Format format) {
|
||||||
|
if (format.drmInitData == null) {
|
||||||
|
return null;
|
||||||
|
} else {
|
||||||
return new ErrorStateDrmSession(
|
return new ErrorStateDrmSession(
|
||||||
new DrmSession.DrmSessionException(
|
new DrmSession.DrmSessionException(
|
||||||
new UnsupportedDrmException(UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME)));
|
new UnsupportedDrmException(
|
||||||
|
UnsupportedDrmException.REASON_UNSUPPORTED_SCHEME)));
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -64,39 +69,27 @@ public interface DrmSessionManager {
|
|||||||
// Do nothing.
|
// Do nothing.
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Returns a {@link DrmSession} that does not execute key requests, with an incremented reference
|
|
||||||
* count. When the caller no longer needs to use the instance, it must call {@link
|
|
||||||
* DrmSession#release(DrmSessionEventListener.EventDispatcher)} to decrement the reference count.
|
|
||||||
*
|
|
||||||
* <p>Placeholder {@link DrmSession DrmSessions} may be used to configure secure decoders for
|
|
||||||
* playback of clear content periods. This can reduce the cost of transitioning between clear and
|
|
||||||
* encrypted content periods.
|
|
||||||
*
|
|
||||||
* @param playbackLooper The looper associated with the media playback thread.
|
|
||||||
* @param trackType The type of the track to acquire a placeholder session for. Must be one of the
|
|
||||||
* {@link C}{@code .TRACK_TYPE_*} constants.
|
|
||||||
* @return The placeholder DRM session, or null if this DRM session manager does not support
|
|
||||||
* placeholder sessions.
|
|
||||||
*/
|
|
||||||
@Nullable
|
|
||||||
default DrmSession acquirePlaceholderSession(Looper playbackLooper, int trackType) {
|
|
||||||
return null;
|
|
||||||
}
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Returns a {@link DrmSession} for the specified {@link Format}, with an incremented reference
|
* Returns a {@link DrmSession} for the specified {@link Format}, with an incremented reference
|
||||||
* count. When the caller no longer needs to use the instance, it must call {@link
|
* count. May return null if the {@link Format#drmInitData} is null and the DRM session manager is
|
||||||
|
* not configured to attach a {@link DrmSession} to clear content. When the caller no longer needs
|
||||||
|
* to use a returned {@link DrmSession}, it must call {@link
|
||||||
* DrmSession#release(DrmSessionEventListener.EventDispatcher)} to decrement the reference count.
|
* DrmSession#release(DrmSessionEventListener.EventDispatcher)} to decrement the reference count.
|
||||||
*
|
*
|
||||||
|
* <p>If the provided {@link Format} contains a null {@link Format#drmInitData}, the returned
|
||||||
|
* {@link DrmSession} (if not null) will be a placeholder session which does not execute key
|
||||||
|
* requests, and cannot be used to handle encrypted content. However, a placeholder session may be
|
||||||
|
* used to configure secure decoders for playback of clear content periods, which can reduce the
|
||||||
|
* cost of transitioning between clear and encrypted content.
|
||||||
|
*
|
||||||
* @param playbackLooper The looper associated with the media playback thread.
|
* @param playbackLooper The looper associated with the media playback thread.
|
||||||
* @param eventDispatcher The {@link DrmSessionEventListener.EventDispatcher} used to distribute
|
* @param eventDispatcher The {@link DrmSessionEventListener.EventDispatcher} used to distribute
|
||||||
* events, and passed on to {@link
|
* events, and passed on to {@link
|
||||||
* DrmSession#acquire(DrmSessionEventListener.EventDispatcher)}.
|
* DrmSession#acquire(DrmSessionEventListener.EventDispatcher)}.
|
||||||
* @param format The {@link Format} for which to acquire a {@link DrmSession}. Must contain a
|
* @param format The {@link Format} for which to acquire a {@link DrmSession}.
|
||||||
* non-null {@link Format#drmInitData}.
|
* @return The DRM session. May be null if the given {@link Format#drmInitData} is null.
|
||||||
* @return The DRM session.
|
|
||||||
*/
|
*/
|
||||||
|
@Nullable
|
||||||
DrmSession acquireSession(
|
DrmSession acquireSession(
|
||||||
Looper playbackLooper,
|
Looper playbackLooper,
|
||||||
@Nullable DrmSessionEventListener.EventDispatcher eventDispatcher,
|
@Nullable DrmSessionEventListener.EventDispatcher eventDispatcher,
|
||||||
@ -105,16 +98,16 @@ public interface DrmSessionManager {
|
|||||||
/**
|
/**
|
||||||
* Returns the {@link ExoMediaCrypto} type associated to sessions acquired for the given {@link
|
* Returns the {@link ExoMediaCrypto} type associated to sessions acquired for the given {@link
|
||||||
* Format}. Returns the {@link UnsupportedMediaCrypto} type if this DRM session manager does not
|
* Format}. Returns the {@link UnsupportedMediaCrypto} type if this DRM session manager does not
|
||||||
* support any of the DRM schemes defined in the given {@link Format}. If the {@link Format}
|
* support any of the DRM schemes defined in the given {@link Format}. Returns null if {@link
|
||||||
* describes unencrypted content, returns an {@link ExoMediaCrypto} type if this DRM session
|
* Format#drmInitData} is null and {@link #acquireSession} would return null for the given {@link
|
||||||
* manager would associate a {@link #acquirePlaceholderSession placeholder session} to the given
|
* Format}.
|
||||||
* {@link Format}, or null otherwise.
|
|
||||||
*
|
*
|
||||||
* @param format The {@link Format} for which to return the {@link ExoMediaCrypto} type.
|
* @param format The {@link Format} for which to return the {@link ExoMediaCrypto} type.
|
||||||
* @return The {@link ExoMediaCrypto} type associated to sessions acquired using the given
|
* @return The {@link ExoMediaCrypto} type associated to sessions acquired using the given {@link
|
||||||
* parameters, or the {@link UnsupportedMediaCrypto} type if the provided {@code drmInitData}
|
* Format}, or {@link UnsupportedMediaCrypto} if this DRM session manager does not support any
|
||||||
* is not supported, or {@code null} if {@code drmInitData} is null and no DRM session will be
|
* of the DRM schemes defined in the given {@link Format}. May be null if {@link
|
||||||
* associated to the given {@code trackType}.
|
* Format#drmInitData} is null and {@link #acquireSession} would return null for the given
|
||||||
|
* {@link Format}.
|
||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
Class<? extends ExoMediaCrypto> getExoMediaCryptoType(Format format);
|
Class<? extends ExoMediaCrypto> getExoMediaCryptoType(Format format);
|
||||||
|
@ -184,7 +184,8 @@ public final class OfflineLicenseHelper {
|
|||||||
/**
|
/**
|
||||||
* Downloads an offline license.
|
* Downloads an offline license.
|
||||||
*
|
*
|
||||||
* @param format The {@link Format} of the content whose license is to be downloaded.
|
* @param format The {@link Format} of the content whose license is to be downloaded. Must contain
|
||||||
|
* a non-null {@link Format#drmInitData}.
|
||||||
* @return The key set id for the downloaded license.
|
* @return The key set id for the downloaded license.
|
||||||
* @throws DrmSessionException Thrown when a DRM session error occurs.
|
* @throws DrmSessionException Thrown when a DRM session error occurs.
|
||||||
*/
|
*/
|
||||||
@ -278,13 +279,14 @@ public final class OfflineLicenseHelper {
|
|||||||
|
|
||||||
private DrmSession openBlockingKeyRequest(
|
private DrmSession openBlockingKeyRequest(
|
||||||
@Mode int licenseMode, @Nullable byte[] offlineLicenseKeySetId, Format format) {
|
@Mode int licenseMode, @Nullable byte[] offlineLicenseKeySetId, Format format) {
|
||||||
|
Assertions.checkNotNull(format.drmInitData);
|
||||||
drmSessionManager.setMode(licenseMode, offlineLicenseKeySetId);
|
drmSessionManager.setMode(licenseMode, offlineLicenseKeySetId);
|
||||||
conditionVariable.close();
|
conditionVariable.close();
|
||||||
DrmSession drmSession =
|
DrmSession drmSession =
|
||||||
drmSessionManager.acquireSession(handlerThread.getLooper(), eventDispatcher, format);
|
drmSessionManager.acquireSession(handlerThread.getLooper(), eventDispatcher, format);
|
||||||
// Block current thread until key loading is finished
|
// Block current thread until key loading is finished
|
||||||
conditionVariable.block();
|
conditionVariable.block();
|
||||||
return drmSession;
|
return Assertions.checkNotNull(drmSession);
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
@ -15,28 +15,19 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.source;
|
package com.google.android.exoplayer2.source;
|
||||||
|
|
||||||
import static com.google.android.exoplayer2.drm.DefaultDrmSessionManager.MODE_PLAYBACK;
|
|
||||||
|
|
||||||
import android.content.Context;
|
import android.content.Context;
|
||||||
import android.net.Uri;
|
import android.net.Uri;
|
||||||
import android.os.Build;
|
|
||||||
import android.util.SparseArray;
|
import android.util.SparseArray;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
||||||
import com.google.android.exoplayer2.MediaItem;
|
import com.google.android.exoplayer2.MediaItem;
|
||||||
import com.google.android.exoplayer2.MediaItem.DrmConfiguration;
|
|
||||||
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
|
|
||||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||||
import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
|
|
||||||
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
|
|
||||||
import com.google.android.exoplayer2.drm.MediaDrmCallback;
|
|
||||||
import com.google.android.exoplayer2.offline.StreamKey;
|
import com.google.android.exoplayer2.offline.StreamKey;
|
||||||
import com.google.android.exoplayer2.source.ads.AdsLoader;
|
import com.google.android.exoplayer2.source.ads.AdsLoader;
|
||||||
import com.google.android.exoplayer2.source.ads.AdsMediaSource;
|
import com.google.android.exoplayer2.source.ads.AdsMediaSource;
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
import com.google.android.exoplayer2.upstream.DefaultDataSourceFactory;
|
||||||
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
|
|
||||||
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
|
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
|
||||||
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
||||||
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
|
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
|
||||||
@ -44,10 +35,8 @@ import com.google.android.exoplayer2.util.Assertions;
|
|||||||
import com.google.android.exoplayer2.util.Log;
|
import com.google.android.exoplayer2.util.Log;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import com.google.common.primitives.Ints;
|
|
||||||
import java.util.Arrays;
|
import java.util.Arrays;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.Map;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* The default {@link MediaSourceFactory} implementation.
|
* The default {@link MediaSourceFactory} implementation.
|
||||||
@ -79,21 +68,6 @@ import java.util.Map;
|
|||||||
* the stream.
|
* the stream.
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
* <h3>DrmSessionManager creation for protected content</h3>
|
|
||||||
*
|
|
||||||
* <p>For a media item with a {@link DrmConfiguration}, a {@link DefaultDrmSessionManager} is
|
|
||||||
* created based on that configuration. The following setter can be used to optionally configure the
|
|
||||||
* creation:
|
|
||||||
*
|
|
||||||
* <ul>
|
|
||||||
* <li>{@link #setDrmHttpDataSourceFactory(HttpDataSource.Factory)}: Sets the data source factory
|
|
||||||
* to be used by the {@link HttpMediaDrmCallback} for network requests (default: {@link
|
|
||||||
* DefaultHttpDataSourceFactory}).
|
|
||||||
* </ul>
|
|
||||||
*
|
|
||||||
* <p>For media items without a {@link DrmConfiguration}, the {@link DrmSessionManager} passed to
|
|
||||||
* {@link #setDrmSessionManager(DrmSessionManager)} will be used.
|
|
||||||
*
|
|
||||||
* <h3>Ad support for media items with ad tag uri</h3>
|
* <h3>Ad support for media items with ad tag uri</h3>
|
||||||
*
|
*
|
||||||
* <p>For a media item with an ad tag uri, an {@link AdSupportProvider} needs to be passed to {@link
|
* <p>For a media item with an ad tag uri, an {@link AdSupportProvider} needs to be passed to {@link
|
||||||
@ -167,21 +141,14 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private static final String TAG = "DefaultMediaSourceFactory";
|
private static final String TAG = "DefaultMediaSourceFactory";
|
||||||
private static final String DEFAULT_USER_AGENT =
|
|
||||||
ExoPlayerLibraryInfo.VERSION_SLASHY
|
|
||||||
+ " (Linux;Android "
|
|
||||||
+ Build.VERSION.RELEASE
|
|
||||||
+ ") "
|
|
||||||
+ ExoPlayerLibraryInfo.VERSION_SLASHY;
|
|
||||||
|
|
||||||
|
private final MediaSourceDrmHelper mediaSourceDrmHelper;
|
||||||
private final DataSource.Factory dataSourceFactory;
|
private final DataSource.Factory dataSourceFactory;
|
||||||
@Nullable private final AdSupportProvider adSupportProvider;
|
@Nullable private final AdSupportProvider adSupportProvider;
|
||||||
private final SparseArray<MediaSourceFactory> mediaSourceFactories;
|
private final SparseArray<MediaSourceFactory> mediaSourceFactories;
|
||||||
@C.ContentType private final int[] supportedTypes;
|
@C.ContentType private final int[] supportedTypes;
|
||||||
|
|
||||||
private DrmSessionManager drmSessionManager;
|
@Nullable private DrmSessionManager drmSessionManager;
|
||||||
@Nullable private HttpDataSource.Factory drmHttpDataSourceFactory;
|
|
||||||
private String userAgent;
|
|
||||||
@Nullable private List<StreamKey> streamKeys;
|
@Nullable private List<StreamKey> streamKeys;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -196,8 +163,7 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
|||||||
DataSource.Factory dataSourceFactory, @Nullable AdSupportProvider adSupportProvider) {
|
DataSource.Factory dataSourceFactory, @Nullable AdSupportProvider adSupportProvider) {
|
||||||
this.dataSourceFactory = dataSourceFactory;
|
this.dataSourceFactory = dataSourceFactory;
|
||||||
this.adSupportProvider = adSupportProvider;
|
this.adSupportProvider = adSupportProvider;
|
||||||
drmSessionManager = DrmSessionManager.getDummyDrmSessionManager();
|
mediaSourceDrmHelper = new MediaSourceDrmHelper();
|
||||||
userAgent = DEFAULT_USER_AGENT;
|
|
||||||
mediaSourceFactories = loadDelegates(dataSourceFactory);
|
mediaSourceFactories = loadDelegates(dataSourceFactory);
|
||||||
supportedTypes = new int[mediaSourceFactories.size()];
|
supportedTypes = new int[mediaSourceFactories.size()];
|
||||||
for (int i = 0; i < mediaSourceFactories.size(); i++) {
|
for (int i = 0; i < mediaSourceFactories.size(); i++) {
|
||||||
@ -205,49 +171,23 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Sets the {@link HttpDataSource.Factory} to be used for creating {@link HttpMediaDrmCallback
|
|
||||||
* HttpMediaDrmCallbacks} which executes key and provisioning requests over HTTP. If {@code null}
|
|
||||||
* is passed the {@link DefaultHttpDataSourceFactory} is used.
|
|
||||||
*
|
|
||||||
* @param drmHttpDataSourceFactory The HTTP data source factory or {@code null} to use {@link
|
|
||||||
* DefaultHttpDataSourceFactory}.
|
|
||||||
* @return This factory, for convenience.
|
|
||||||
*/
|
|
||||||
public DefaultMediaSourceFactory setDrmHttpDataSourceFactory(
|
public DefaultMediaSourceFactory setDrmHttpDataSourceFactory(
|
||||||
@Nullable HttpDataSource.Factory drmHttpDataSourceFactory) {
|
@Nullable HttpDataSource.Factory drmHttpDataSourceFactory) {
|
||||||
this.drmHttpDataSourceFactory = drmHttpDataSourceFactory;
|
mediaSourceDrmHelper.setDrmHttpDataSourceFactory(drmHttpDataSourceFactory);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
@Override
|
||||||
* Sets the optional user agent to be used for DRM requests.
|
|
||||||
*
|
|
||||||
* <p>In case a factory has been set by {@link
|
|
||||||
* #setDrmHttpDataSourceFactory(HttpDataSource.Factory)}, this user agent is ignored.
|
|
||||||
*
|
|
||||||
* @param userAgent The user agent to be used for DRM requests.
|
|
||||||
* @return This factory, for convenience.
|
|
||||||
*/
|
|
||||||
public DefaultMediaSourceFactory setDrmUserAgent(@Nullable String userAgent) {
|
public DefaultMediaSourceFactory setDrmUserAgent(@Nullable String userAgent) {
|
||||||
this.userAgent = userAgent != null ? userAgent : DEFAULT_USER_AGENT;
|
mediaSourceDrmHelper.setDrmUserAgent(userAgent);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the {@link DrmSessionManager} to use for media items that do not specify a {@link
|
|
||||||
* DrmConfiguration}. The default value is {@link DrmSessionManager#DUMMY}.
|
|
||||||
*
|
|
||||||
* @param drmSessionManager The {@link DrmSessionManager}.
|
|
||||||
* @return This factory, for convenience.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public DefaultMediaSourceFactory setDrmSessionManager(
|
public DefaultMediaSourceFactory setDrmSessionManager(
|
||||||
@Nullable DrmSessionManager drmSessionManager) {
|
@Nullable DrmSessionManager drmSessionManager) {
|
||||||
this.drmSessionManager =
|
this.drmSessionManager = drmSessionManager;
|
||||||
drmSessionManager != null
|
|
||||||
? drmSessionManager
|
|
||||||
: DrmSessionManager.getDummyDrmSessionManager();
|
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -292,7 +232,8 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
|||||||
@Nullable MediaSourceFactory mediaSourceFactory = mediaSourceFactories.get(type);
|
@Nullable MediaSourceFactory mediaSourceFactory = mediaSourceFactories.get(type);
|
||||||
Assertions.checkNotNull(
|
Assertions.checkNotNull(
|
||||||
mediaSourceFactory, "No suitable media source factory found for content type: " + type);
|
mediaSourceFactory, "No suitable media source factory found for content type: " + type);
|
||||||
mediaSourceFactory.setDrmSessionManager(createDrmSessionManager(mediaItem));
|
mediaSourceFactory.setDrmSessionManager(
|
||||||
|
drmSessionManager != null ? drmSessionManager : mediaSourceDrmHelper.create(mediaItem));
|
||||||
mediaSourceFactory.setStreamKeys(
|
mediaSourceFactory.setStreamKeys(
|
||||||
!mediaItem.playbackProperties.streamKeys.isEmpty()
|
!mediaItem.playbackProperties.streamKeys.isEmpty()
|
||||||
? mediaItem.playbackProperties.streamKeys
|
? mediaItem.playbackProperties.streamKeys
|
||||||
@ -318,46 +259,6 @@ public final class DefaultMediaSourceFactory implements MediaSourceFactory {
|
|||||||
|
|
||||||
// internal methods
|
// internal methods
|
||||||
|
|
||||||
private DrmSessionManager createDrmSessionManager(MediaItem mediaItem) {
|
|
||||||
Assertions.checkNotNull(mediaItem.playbackProperties);
|
|
||||||
if (mediaItem.playbackProperties.drmConfiguration == null
|
|
||||||
|| mediaItem.playbackProperties.drmConfiguration.licenseUri == null
|
|
||||||
|| Util.SDK_INT < 18) {
|
|
||||||
return drmSessionManager;
|
|
||||||
}
|
|
||||||
DefaultDrmSessionManager drmSessionManager =
|
|
||||||
new DefaultDrmSessionManager.Builder()
|
|
||||||
.setUuidAndExoMediaDrmProvider(
|
|
||||||
mediaItem.playbackProperties.drmConfiguration.uuid,
|
|
||||||
FrameworkMediaDrm.DEFAULT_PROVIDER)
|
|
||||||
.setMultiSession(mediaItem.playbackProperties.drmConfiguration.multiSession)
|
|
||||||
.setPlayClearSamplesWithoutKeys(
|
|
||||||
mediaItem.playbackProperties.drmConfiguration.playClearContentWithoutKey)
|
|
||||||
.setUseDrmSessionsForClearContent(
|
|
||||||
Ints.toArray(mediaItem.playbackProperties.drmConfiguration.sessionForClearTypes))
|
|
||||||
.build(createHttpMediaDrmCallback(mediaItem.playbackProperties.drmConfiguration));
|
|
||||||
|
|
||||||
drmSessionManager.setMode(
|
|
||||||
MODE_PLAYBACK, mediaItem.playbackProperties.drmConfiguration.getKeySetId());
|
|
||||||
|
|
||||||
return drmSessionManager;
|
|
||||||
}
|
|
||||||
|
|
||||||
private MediaDrmCallback createHttpMediaDrmCallback(MediaItem.DrmConfiguration drmConfiguration) {
|
|
||||||
Assertions.checkNotNull(drmConfiguration.licenseUri);
|
|
||||||
HttpMediaDrmCallback drmCallback =
|
|
||||||
new HttpMediaDrmCallback(
|
|
||||||
drmConfiguration.licenseUri.toString(),
|
|
||||||
drmConfiguration.forceDefaultLicenseUri,
|
|
||||||
drmHttpDataSourceFactory != null
|
|
||||||
? drmHttpDataSourceFactory
|
|
||||||
: new DefaultHttpDataSourceFactory(userAgent));
|
|
||||||
for (Map.Entry<String, String> entry : drmConfiguration.requestHeaders.entrySet()) {
|
|
||||||
drmCallback.setKeyRequestProperty(entry.getKey(), entry.getValue());
|
|
||||||
}
|
|
||||||
return drmCallback;
|
|
||||||
}
|
|
||||||
|
|
||||||
private static MediaSource maybeClipMediaSource(MediaItem mediaItem, MediaSource mediaSource) {
|
private static MediaSource maybeClipMediaSource(MediaItem mediaItem, MediaSource mediaSource) {
|
||||||
if (mediaItem.clippingProperties.startPositionMs == 0
|
if (mediaItem.clippingProperties.startPositionMs == 0
|
||||||
&& mediaItem.clippingProperties.endPositionMs == C.TIME_END_OF_SOURCE
|
&& mediaItem.clippingProperties.endPositionMs == C.TIME_END_OF_SOURCE
|
||||||
|
@ -29,6 +29,7 @@ import com.google.android.exoplayer2.extractor.ExtractorsFactory;
|
|||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
import com.google.android.exoplayer2.upstream.Allocator;
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
|
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
|
||||||
|
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
||||||
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
|
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
|
||||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
@ -168,6 +169,23 @@ public final class ExtractorMediaSource extends CompositeMediaSource<Void> {
|
|||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* @deprecated Use {@link ProgressiveMediaSource.Factory#setDrmHttpDataSourceFactory} instead.
|
||||||
|
*/
|
||||||
|
@Deprecated
|
||||||
|
@Override
|
||||||
|
public MediaSourceFactory setDrmHttpDataSourceFactory(
|
||||||
|
@Nullable HttpDataSource.Factory drmHttpDataSourceFactory) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
/** @deprecated Use {@link ProgressiveMediaSource.Factory#setDrmUserAgent} instead. */
|
||||||
|
@Deprecated
|
||||||
|
@Override
|
||||||
|
public MediaSourceFactory setDrmUserAgent(@Nullable String userAgent) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
/** @deprecated Use {@link #createMediaSource(MediaItem)} instead. */
|
/** @deprecated Use {@link #createMediaSource(MediaItem)} instead. */
|
||||||
@SuppressWarnings("deprecation")
|
@SuppressWarnings("deprecation")
|
||||||
@Deprecated
|
@Deprecated
|
||||||
|
@ -0,0 +1,105 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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 com.google.android.exoplayer2.source;
|
||||||
|
|
||||||
|
import static com.google.android.exoplayer2.drm.DefaultDrmSessionManager.MODE_PLAYBACK;
|
||||||
|
import static com.google.android.exoplayer2.util.Util.castNonNull;
|
||||||
|
|
||||||
|
import android.os.Build;
|
||||||
|
import androidx.annotation.Nullable;
|
||||||
|
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
||||||
|
import com.google.android.exoplayer2.MediaItem;
|
||||||
|
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
|
||||||
|
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||||
|
import com.google.android.exoplayer2.drm.FrameworkMediaDrm;
|
||||||
|
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
|
||||||
|
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
|
||||||
|
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
||||||
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
import com.google.android.exoplayer2.util.Util;
|
||||||
|
import com.google.common.primitives.Ints;
|
||||||
|
import java.util.Map;
|
||||||
|
|
||||||
|
/** A helper to create a {@link DrmSessionManager} from a {@link MediaItem}. */
|
||||||
|
public final class MediaSourceDrmHelper {
|
||||||
|
|
||||||
|
private static final String DEFAULT_USER_AGENT =
|
||||||
|
ExoPlayerLibraryInfo.VERSION_SLASHY
|
||||||
|
+ " (Linux;Android "
|
||||||
|
+ Build.VERSION.RELEASE
|
||||||
|
+ ") "
|
||||||
|
+ ExoPlayerLibraryInfo.VERSION_SLASHY;
|
||||||
|
|
||||||
|
@Nullable private HttpDataSource.Factory drmHttpDataSourceFactory;
|
||||||
|
@Nullable private String userAgent;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link HttpDataSource.Factory} to be used for creating {@link HttpMediaDrmCallback
|
||||||
|
* HttpMediaDrmCallbacks} which executes key and provisioning requests over HTTP. If {@code null}
|
||||||
|
* is passed the {@link DefaultHttpDataSourceFactory} is used.
|
||||||
|
*
|
||||||
|
* @param drmHttpDataSourceFactory The HTTP data source factory or {@code null} to use {@link
|
||||||
|
* DefaultHttpDataSourceFactory}.
|
||||||
|
*/
|
||||||
|
public void setDrmHttpDataSourceFactory(
|
||||||
|
@Nullable HttpDataSource.Factory drmHttpDataSourceFactory) {
|
||||||
|
this.drmHttpDataSourceFactory = drmHttpDataSourceFactory;
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the optional user agent to be used for DRM requests.
|
||||||
|
*
|
||||||
|
* <p>In case a factory has been set by {@link
|
||||||
|
* #setDrmHttpDataSourceFactory(HttpDataSource.Factory)}, this user agent is ignored.
|
||||||
|
*
|
||||||
|
* @param userAgent The user agent to be used for DRM requests.
|
||||||
|
*/
|
||||||
|
public void setDrmUserAgent(@Nullable String userAgent) {
|
||||||
|
this.userAgent = userAgent;
|
||||||
|
}
|
||||||
|
|
||||||
|
/** Creates a {@link DrmSessionManager} for the given media item. */
|
||||||
|
public DrmSessionManager create(MediaItem mediaItem) {
|
||||||
|
Assertions.checkNotNull(mediaItem.playbackProperties);
|
||||||
|
@Nullable
|
||||||
|
MediaItem.DrmConfiguration drmConfiguration = mediaItem.playbackProperties.drmConfiguration;
|
||||||
|
if (drmConfiguration == null || drmConfiguration.licenseUri == null || Util.SDK_INT < 18) {
|
||||||
|
return DrmSessionManager.getDummyDrmSessionManager();
|
||||||
|
}
|
||||||
|
HttpDataSource.Factory dataSourceFactory =
|
||||||
|
drmHttpDataSourceFactory != null
|
||||||
|
? drmHttpDataSourceFactory
|
||||||
|
: new DefaultHttpDataSourceFactory(userAgent != null ? userAgent : DEFAULT_USER_AGENT);
|
||||||
|
HttpMediaDrmCallback httpDrmCallback =
|
||||||
|
new HttpMediaDrmCallback(
|
||||||
|
castNonNull(drmConfiguration.licenseUri).toString(),
|
||||||
|
drmConfiguration.forceDefaultLicenseUri,
|
||||||
|
dataSourceFactory);
|
||||||
|
for (Map.Entry<String, String> entry : drmConfiguration.requestHeaders.entrySet()) {
|
||||||
|
httpDrmCallback.setKeyRequestProperty(entry.getKey(), entry.getValue());
|
||||||
|
}
|
||||||
|
DefaultDrmSessionManager drmSessionManager =
|
||||||
|
new DefaultDrmSessionManager.Builder()
|
||||||
|
.setUuidAndExoMediaDrmProvider(
|
||||||
|
drmConfiguration.uuid, FrameworkMediaDrm.DEFAULT_PROVIDER)
|
||||||
|
.setMultiSession(drmConfiguration.multiSession)
|
||||||
|
.setPlayClearSamplesWithoutKeys(drmConfiguration.playClearContentWithoutKey)
|
||||||
|
.setUseDrmSessionsForClearContent(Ints.toArray(drmConfiguration.sessionForClearTypes))
|
||||||
|
.build(httpDrmCallback);
|
||||||
|
drmSessionManager.setMode(MODE_PLAYBACK, drmConfiguration.getKeySetId());
|
||||||
|
return drmSessionManager;
|
||||||
|
}
|
||||||
|
}
|
@ -19,13 +19,34 @@ import android.net.Uri;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.MediaItem;
|
import com.google.android.exoplayer2.MediaItem;
|
||||||
import com.google.android.exoplayer2.drm.DrmSession;
|
import com.google.android.exoplayer2.drm.DefaultDrmSessionManager;
|
||||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||||
|
import com.google.android.exoplayer2.drm.HttpMediaDrmCallback;
|
||||||
import com.google.android.exoplayer2.offline.StreamKey;
|
import com.google.android.exoplayer2.offline.StreamKey;
|
||||||
|
import com.google.android.exoplayer2.upstream.DefaultHttpDataSourceFactory;
|
||||||
|
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
||||||
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
|
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
|
|
||||||
/** Factory for creating {@link MediaSource}s from URIs. */
|
/**
|
||||||
|
* Factory for creating {@link MediaSource}s from URIs.
|
||||||
|
*
|
||||||
|
* <h3>DrmSessionManager creation for protected content</h3>
|
||||||
|
*
|
||||||
|
* <p>In case a {@link DrmSessionManager} is passed to {@link
|
||||||
|
* #setDrmSessionManager(DrmSessionManager)}, it will be used regardless of the drm configuration of
|
||||||
|
* the media item.
|
||||||
|
*
|
||||||
|
* <p>For a media item with a {@link MediaItem.DrmConfiguration}, a {@link DefaultDrmSessionManager}
|
||||||
|
* is created based on that configuration. The following setter can be used to optionally configure
|
||||||
|
* the creation:
|
||||||
|
*
|
||||||
|
* <ul>
|
||||||
|
* <li>{@link #setDrmHttpDataSourceFactory(HttpDataSource.Factory)}: Sets the data source factory
|
||||||
|
* to be used by the {@link HttpMediaDrmCallback} for network requests (default: {@link
|
||||||
|
* DefaultHttpDataSourceFactory}).
|
||||||
|
* </ul>
|
||||||
|
*/
|
||||||
public interface MediaSourceFactory {
|
public interface MediaSourceFactory {
|
||||||
|
|
||||||
/** @deprecated Use {@link MediaItem.PlaybackProperties#streamKeys} instead. */
|
/** @deprecated Use {@link MediaItem.PlaybackProperties#streamKeys} instead. */
|
||||||
@ -35,13 +56,40 @@ public interface MediaSourceFactory {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets the {@link DrmSessionManager} to use for acquiring {@link DrmSession DrmSessions}.
|
* Sets the {@link DrmSessionManager} to use for all media items regardless of their {@link
|
||||||
|
* MediaItem.DrmConfiguration}.
|
||||||
*
|
*
|
||||||
* @param drmSessionManager The {@link DrmSessionManager}.
|
* @param drmSessionManager The {@link DrmSessionManager}.
|
||||||
* @return This factory, for convenience.
|
* @return This factory, for convenience.
|
||||||
*/
|
*/
|
||||||
MediaSourceFactory setDrmSessionManager(@Nullable DrmSessionManager drmSessionManager);
|
MediaSourceFactory setDrmSessionManager(@Nullable DrmSessionManager drmSessionManager);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the {@link HttpDataSource.Factory} to be used for creating {@link HttpMediaDrmCallback
|
||||||
|
* HttpMediaDrmCallbacks} to execute key and provisioning requests over HTTP.
|
||||||
|
*
|
||||||
|
* <p>In case a {@link DrmSessionManager} has been set by {@link
|
||||||
|
* #setDrmSessionManager(DrmSessionManager)}, this data source factory is ignored.
|
||||||
|
*
|
||||||
|
* @param drmHttpDataSourceFactory The HTTP data source factory, or {@code null} to use {@link
|
||||||
|
* DefaultHttpDataSourceFactory}.
|
||||||
|
* @return This factory, for convenience.
|
||||||
|
*/
|
||||||
|
MediaSourceFactory setDrmHttpDataSourceFactory(
|
||||||
|
@Nullable HttpDataSource.Factory drmHttpDataSourceFactory);
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the optional user agent to be used for DRM requests.
|
||||||
|
*
|
||||||
|
* <p>In case a factory has been set by {@link
|
||||||
|
* #setDrmHttpDataSourceFactory(HttpDataSource.Factory)} or a {@link DrmSessionManager} has been
|
||||||
|
* set by {@link #setDrmSessionManager(DrmSessionManager)}, this user agent is ignored.
|
||||||
|
*
|
||||||
|
* @param userAgent The user agent to be used for DRM requests.
|
||||||
|
* @return This factory, for convenience.
|
||||||
|
*/
|
||||||
|
MediaSourceFactory setDrmUserAgent(@Nullable String userAgent);
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets an optional {@link LoadErrorHandlingPolicy}.
|
* Sets an optional {@link LoadErrorHandlingPolicy}.
|
||||||
*
|
*
|
||||||
|
@ -22,7 +22,6 @@ import androidx.annotation.Nullable;
|
|||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.MediaItem;
|
import com.google.android.exoplayer2.MediaItem;
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.drm.DrmSession;
|
|
||||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||||
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
import com.google.android.exoplayer2.extractor.DefaultExtractorsFactory;
|
||||||
import com.google.android.exoplayer2.extractor.Extractor;
|
import com.google.android.exoplayer2.extractor.Extractor;
|
||||||
@ -30,6 +29,7 @@ import com.google.android.exoplayer2.extractor.ExtractorsFactory;
|
|||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
import com.google.android.exoplayer2.upstream.Allocator;
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
|
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
|
||||||
|
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
||||||
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
|
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
|
||||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||||
|
|
||||||
@ -51,9 +51,10 @@ public final class ProgressiveMediaSource extends BaseMediaSource
|
|||||||
public static final class Factory implements MediaSourceFactory {
|
public static final class Factory implements MediaSourceFactory {
|
||||||
|
|
||||||
private final DataSource.Factory dataSourceFactory;
|
private final DataSource.Factory dataSourceFactory;
|
||||||
|
private final MediaSourceDrmHelper mediaSourceDrmHelper;
|
||||||
|
|
||||||
private ExtractorsFactory extractorsFactory;
|
private ExtractorsFactory extractorsFactory;
|
||||||
private DrmSessionManager drmSessionManager;
|
@Nullable private DrmSessionManager drmSessionManager;
|
||||||
private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
|
private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
|
||||||
private int continueLoadingCheckIntervalBytes;
|
private int continueLoadingCheckIntervalBytes;
|
||||||
@Nullable private String customCacheKey;
|
@Nullable private String customCacheKey;
|
||||||
@ -78,7 +79,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource
|
|||||||
public Factory(DataSource.Factory dataSourceFactory, ExtractorsFactory extractorsFactory) {
|
public Factory(DataSource.Factory dataSourceFactory, ExtractorsFactory extractorsFactory) {
|
||||||
this.dataSourceFactory = dataSourceFactory;
|
this.dataSourceFactory = dataSourceFactory;
|
||||||
this.extractorsFactory = extractorsFactory;
|
this.extractorsFactory = extractorsFactory;
|
||||||
drmSessionManager = DrmSessionManager.getDummyDrmSessionManager();
|
mediaSourceDrmHelper = new MediaSourceDrmHelper();
|
||||||
loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy();
|
loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy();
|
||||||
continueLoadingCheckIntervalBytes = DEFAULT_LOADING_CHECK_INTERVAL_BYTES;
|
continueLoadingCheckIntervalBytes = DEFAULT_LOADING_CHECK_INTERVAL_BYTES;
|
||||||
}
|
}
|
||||||
@ -146,19 +147,22 @@ public final class ProgressiveMediaSource extends BaseMediaSource
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the {@link DrmSessionManager} to use for acquiring {@link DrmSession DrmSessions}. The
|
|
||||||
* default value is {@link DrmSessionManager#DUMMY}.
|
|
||||||
*
|
|
||||||
* @param drmSessionManager The {@link DrmSessionManager}.
|
|
||||||
* @return This factory, for convenience.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public Factory setDrmSessionManager(@Nullable DrmSessionManager drmSessionManager) {
|
public Factory setDrmSessionManager(@Nullable DrmSessionManager drmSessionManager) {
|
||||||
this.drmSessionManager =
|
this.drmSessionManager = drmSessionManager;
|
||||||
drmSessionManager != null
|
return this;
|
||||||
? drmSessionManager
|
}
|
||||||
: DrmSessionManager.getDummyDrmSessionManager();
|
|
||||||
|
@Override
|
||||||
|
public Factory setDrmHttpDataSourceFactory(
|
||||||
|
@Nullable HttpDataSource.Factory drmHttpDataSourceFactory) {
|
||||||
|
mediaSourceDrmHelper.setDrmHttpDataSourceFactory(drmHttpDataSourceFactory);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Factory setDrmUserAgent(@Nullable String userAgent) {
|
||||||
|
mediaSourceDrmHelper.setDrmUserAgent(userAgent);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -194,7 +198,7 @@ public final class ProgressiveMediaSource extends BaseMediaSource
|
|||||||
mediaItem,
|
mediaItem,
|
||||||
dataSourceFactory,
|
dataSourceFactory,
|
||||||
extractorsFactory,
|
extractorsFactory,
|
||||||
drmSessionManager,
|
drmSessionManager != null ? drmSessionManager : mediaSourceDrmHelper.create(mediaItem),
|
||||||
loadErrorHandlingPolicy,
|
loadErrorHandlingPolicy,
|
||||||
continueLoadingCheckIntervalBytes);
|
continueLoadingCheckIntervalBytes);
|
||||||
}
|
}
|
||||||
|
@ -327,13 +327,7 @@ public class SampleQueue implements TrackOutput {
|
|||||||
* Attempts to read from the queue.
|
* Attempts to read from the queue.
|
||||||
*
|
*
|
||||||
* <p>{@link Format Formats} read from this method may be associated to a {@link DrmSession}
|
* <p>{@link Format Formats} read from this method may be associated to a {@link DrmSession}
|
||||||
* through {@link FormatHolder#drmSession}, which is populated in two scenarios:
|
* through {@link FormatHolder#drmSession}.
|
||||||
*
|
|
||||||
* <ul>
|
|
||||||
* <li>The {@link Format} has a non-null {@link Format#drmInitData}.
|
|
||||||
* <li>The {@link DrmSessionManager} provides placeholder sessions for this queue's track type.
|
|
||||||
* See {@link DrmSessionManager#acquirePlaceholderSession(Looper, int)}.
|
|
||||||
* </ul>
|
|
||||||
*
|
*
|
||||||
* @param formatHolder A {@link FormatHolder} to populate in the case of reading a format.
|
* @param formatHolder A {@link FormatHolder} to populate in the case of reading a format.
|
||||||
* @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the
|
* @param buffer A {@link DecoderInputBuffer} to populate in the case of reading a sample or the
|
||||||
@ -842,10 +836,7 @@ public class SampleQueue implements TrackOutput {
|
|||||||
// is being used for both DrmInitData.
|
// is being used for both DrmInitData.
|
||||||
@Nullable DrmSession previousSession = currentDrmSession;
|
@Nullable DrmSession previousSession = currentDrmSession;
|
||||||
currentDrmSession =
|
currentDrmSession =
|
||||||
newDrmInitData != null
|
drmSessionManager.acquireSession(playbackLooper, drmEventDispatcher, newFormat);
|
||||||
? drmSessionManager.acquireSession(playbackLooper, drmEventDispatcher, newFormat)
|
|
||||||
: drmSessionManager.acquirePlaceholderSession(
|
|
||||||
playbackLooper, MimeTypes.getTrackType(newFormat.sampleMimeType));
|
|
||||||
outputFormatHolder.drmSession = currentDrmSession;
|
outputFormatHolder.drmSession = currentDrmSession;
|
||||||
|
|
||||||
if (previousSession != null) {
|
if (previousSession != null) {
|
||||||
|
@ -16,6 +16,7 @@
|
|||||||
package com.google.android.exoplayer2.upstream.cache;
|
package com.google.android.exoplayer2.upstream.cache;
|
||||||
|
|
||||||
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
|
import static com.google.android.exoplayer2.util.Assertions.checkArgument;
|
||||||
|
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
|
||||||
import static com.google.android.exoplayer2.util.Assertions.checkState;
|
import static com.google.android.exoplayer2.util.Assertions.checkState;
|
||||||
import static java.lang.Math.max;
|
import static java.lang.Math.max;
|
||||||
import static java.lang.Math.min;
|
import static java.lang.Math.min;
|
||||||
@ -219,9 +220,9 @@ import java.util.TreeSet;
|
|||||||
public SimpleCacheSpan setLastTouchTimestamp(
|
public SimpleCacheSpan setLastTouchTimestamp(
|
||||||
SimpleCacheSpan cacheSpan, long lastTouchTimestamp, boolean updateFile) {
|
SimpleCacheSpan cacheSpan, long lastTouchTimestamp, boolean updateFile) {
|
||||||
checkState(cachedSpans.remove(cacheSpan));
|
checkState(cachedSpans.remove(cacheSpan));
|
||||||
File file = cacheSpan.file;
|
File file = checkNotNull(cacheSpan.file);
|
||||||
if (updateFile) {
|
if (updateFile) {
|
||||||
File directory = file.getParentFile();
|
File directory = checkNotNull(file.getParentFile());
|
||||||
long position = cacheSpan.position;
|
long position = cacheSpan.position;
|
||||||
File newFile = SimpleCacheSpan.getCacheFile(directory, id, position, lastTouchTimestamp);
|
File newFile = SimpleCacheSpan.getCacheFile(directory, id, position, lastTouchTimestamp);
|
||||||
if (file.renameTo(newFile)) {
|
if (file.renameTo(newFile)) {
|
||||||
@ -244,7 +245,9 @@ import java.util.TreeSet;
|
|||||||
/** Removes the given span from cache. */
|
/** Removes the given span from cache. */
|
||||||
public boolean removeSpan(CacheSpan span) {
|
public boolean removeSpan(CacheSpan span) {
|
||||||
if (cachedSpans.remove(span)) {
|
if (cachedSpans.remove(span)) {
|
||||||
|
if (span.file != null) {
|
||||||
span.file.delete();
|
span.file.delete();
|
||||||
|
}
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
return false;
|
return false;
|
||||||
|
@ -15,6 +15,9 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.upstream.cache;
|
package com.google.android.exoplayer2.upstream.cache;
|
||||||
|
|
||||||
|
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
|
||||||
|
import static com.google.android.exoplayer2.util.Assertions.checkState;
|
||||||
|
import static com.google.android.exoplayer2.util.Util.castNonNull;
|
||||||
import static java.lang.Math.min;
|
import static java.lang.Math.min;
|
||||||
|
|
||||||
import android.annotation.SuppressLint;
|
import android.annotation.SuppressLint;
|
||||||
@ -61,6 +64,7 @@ import javax.crypto.NoSuchPaddingException;
|
|||||||
import javax.crypto.spec.IvParameterSpec;
|
import javax.crypto.spec.IvParameterSpec;
|
||||||
import javax.crypto.spec.SecretKeySpec;
|
import javax.crypto.spec.SecretKeySpec;
|
||||||
import org.checkerframework.checker.nullness.compatqual.NullableType;
|
import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||||
|
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
||||||
|
|
||||||
/** Maintains the index of cached content. */
|
/** Maintains the index of cached content. */
|
||||||
/* package */ class CachedContentIndex {
|
/* package */ class CachedContentIndex {
|
||||||
@ -155,13 +159,15 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||||||
@Nullable byte[] legacyStorageSecretKey,
|
@Nullable byte[] legacyStorageSecretKey,
|
||||||
boolean legacyStorageEncrypt,
|
boolean legacyStorageEncrypt,
|
||||||
boolean preferLegacyStorage) {
|
boolean preferLegacyStorage) {
|
||||||
Assertions.checkState(databaseProvider != null || legacyStorageDir != null);
|
checkState(databaseProvider != null || legacyStorageDir != null);
|
||||||
keyToContent = new HashMap<>();
|
keyToContent = new HashMap<>();
|
||||||
idToKey = new SparseArray<>();
|
idToKey = new SparseArray<>();
|
||||||
removedIds = new SparseBooleanArray();
|
removedIds = new SparseBooleanArray();
|
||||||
newIds = new SparseBooleanArray();
|
newIds = new SparseBooleanArray();
|
||||||
|
@Nullable
|
||||||
Storage databaseStorage =
|
Storage databaseStorage =
|
||||||
databaseProvider != null ? new DatabaseStorage(databaseProvider) : null;
|
databaseProvider != null ? new DatabaseStorage(databaseProvider) : null;
|
||||||
|
@Nullable
|
||||||
Storage legacyStorage =
|
Storage legacyStorage =
|
||||||
legacyStorageDir != null
|
legacyStorageDir != null
|
||||||
? new LegacyStorage(
|
? new LegacyStorage(
|
||||||
@ -170,7 +176,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||||||
legacyStorageEncrypt)
|
legacyStorageEncrypt)
|
||||||
: null;
|
: null;
|
||||||
if (databaseStorage == null || (legacyStorage != null && preferLegacyStorage)) {
|
if (databaseStorage == null || (legacyStorage != null && preferLegacyStorage)) {
|
||||||
storage = legacyStorage;
|
storage = castNonNull(legacyStorage);
|
||||||
previousStorage = databaseStorage;
|
previousStorage = databaseStorage;
|
||||||
} else {
|
} else {
|
||||||
storage = databaseStorage;
|
storage = databaseStorage;
|
||||||
@ -325,7 +331,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||||||
|
|
||||||
/** Returns a {@link ContentMetadata} for the given key. */
|
/** Returns a {@link ContentMetadata} for the given key. */
|
||||||
public ContentMetadata getContentMetadata(String key) {
|
public ContentMetadata getContentMetadata(String key) {
|
||||||
CachedContent cachedContent = get(key);
|
@Nullable CachedContent cachedContent = get(key);
|
||||||
return cachedContent != null ? cachedContent.getMetadata() : DefaultContentMetadata.EMPTY;
|
return cachedContent != null ? cachedContent.getMetadata() : DefaultContentMetadata.EMPTY;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -358,7 +364,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||||||
* returns the smallest unused non-negative integer.
|
* returns the smallest unused non-negative integer.
|
||||||
*/
|
*/
|
||||||
@VisibleForTesting
|
@VisibleForTesting
|
||||||
/* package */ static int getNewId(SparseArray<String> idToKey) {
|
/* package */ static int getNewId(SparseArray<@NullableType String> idToKey) {
|
||||||
int size = idToKey.size();
|
int size = idToKey.size();
|
||||||
int id = size == 0 ? 0 : (idToKey.keyAt(size - 1) + 1);
|
int id = size == 0 ? 0 : (idToKey.keyAt(size - 1) + 1);
|
||||||
if (id < 0) { // In case if we pass max int value.
|
if (id < 0) { // In case if we pass max int value.
|
||||||
@ -512,8 +518,9 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||||||
@Nullable private ReusableBufferedOutputStream bufferedOutputStream;
|
@Nullable private ReusableBufferedOutputStream bufferedOutputStream;
|
||||||
|
|
||||||
public LegacyStorage(File file, @Nullable byte[] secretKey, boolean encrypt) {
|
public LegacyStorage(File file, @Nullable byte[] secretKey, boolean encrypt) {
|
||||||
Cipher cipher = null;
|
checkState(secretKey != null || !encrypt);
|
||||||
SecretKeySpec secretKeySpec = null;
|
@Nullable Cipher cipher = null;
|
||||||
|
@Nullable SecretKeySpec secretKeySpec = null;
|
||||||
if (secretKey != null) {
|
if (secretKey != null) {
|
||||||
Assertions.checkArgument(secretKey.length == 16);
|
Assertions.checkArgument(secretKey.length == 16);
|
||||||
try {
|
try {
|
||||||
@ -550,7 +557,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||||||
@Override
|
@Override
|
||||||
public void load(
|
public void load(
|
||||||
HashMap<String, CachedContent> content, SparseArray<@NullableType String> idToKey) {
|
HashMap<String, CachedContent> content, SparseArray<@NullableType String> idToKey) {
|
||||||
Assertions.checkState(!changed);
|
checkState(!changed);
|
||||||
if (!readFile(content, idToKey)) {
|
if (!readFile(content, idToKey)) {
|
||||||
content.clear();
|
content.clear();
|
||||||
idToKey.clear();
|
idToKey.clear();
|
||||||
@ -588,7 +595,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
DataInputStream input = null;
|
@Nullable DataInputStream input = null;
|
||||||
try {
|
try {
|
||||||
InputStream inputStream = new BufferedInputStream(atomicFile.openRead());
|
InputStream inputStream = new BufferedInputStream(atomicFile.openRead());
|
||||||
input = new DataInputStream(inputStream);
|
input = new DataInputStream(inputStream);
|
||||||
@ -606,7 +613,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||||||
input.readFully(initializationVector);
|
input.readFully(initializationVector);
|
||||||
IvParameterSpec ivParameterSpec = new IvParameterSpec(initializationVector);
|
IvParameterSpec ivParameterSpec = new IvParameterSpec(initializationVector);
|
||||||
try {
|
try {
|
||||||
cipher.init(Cipher.DECRYPT_MODE, secretKeySpec, ivParameterSpec);
|
cipher.init(Cipher.DECRYPT_MODE, castNonNull(secretKeySpec), ivParameterSpec);
|
||||||
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
|
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
|
||||||
throw new IllegalStateException(e);
|
throw new IllegalStateException(e);
|
||||||
}
|
}
|
||||||
@ -647,6 +654,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||||||
} else {
|
} else {
|
||||||
bufferedOutputStream.reset(outputStream);
|
bufferedOutputStream.reset(outputStream);
|
||||||
}
|
}
|
||||||
|
ReusableBufferedOutputStream bufferedOutputStream = this.bufferedOutputStream;
|
||||||
output = new DataOutputStream(bufferedOutputStream);
|
output = new DataOutputStream(bufferedOutputStream);
|
||||||
output.writeInt(VERSION);
|
output.writeInt(VERSION);
|
||||||
|
|
||||||
@ -655,11 +663,12 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||||||
|
|
||||||
if (encrypt) {
|
if (encrypt) {
|
||||||
byte[] initializationVector = new byte[16];
|
byte[] initializationVector = new byte[16];
|
||||||
random.nextBytes(initializationVector);
|
castNonNull(random).nextBytes(initializationVector);
|
||||||
output.write(initializationVector);
|
output.write(initializationVector);
|
||||||
IvParameterSpec ivParameterSpec = new IvParameterSpec(initializationVector);
|
IvParameterSpec ivParameterSpec = new IvParameterSpec(initializationVector);
|
||||||
try {
|
try {
|
||||||
cipher.init(Cipher.ENCRYPT_MODE, secretKeySpec, ivParameterSpec);
|
castNonNull(cipher)
|
||||||
|
.init(Cipher.ENCRYPT_MODE, castNonNull(secretKeySpec), ivParameterSpec);
|
||||||
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
|
} catch (InvalidKeyException | InvalidAlgorithmParameterException e) {
|
||||||
throw new IllegalStateException(e); // Should never happen.
|
throw new IllegalStateException(e); // Should never happen.
|
||||||
}
|
}
|
||||||
@ -762,16 +771,17 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||||||
+ " BLOB NOT NULL)";
|
+ " BLOB NOT NULL)";
|
||||||
|
|
||||||
private final DatabaseProvider databaseProvider;
|
private final DatabaseProvider databaseProvider;
|
||||||
private final SparseArray<CachedContent> pendingUpdates;
|
private final SparseArray<@NullableType CachedContent> pendingUpdates;
|
||||||
|
|
||||||
private String hexUid;
|
private @MonotonicNonNull String hexUid;
|
||||||
private String tableName;
|
private @MonotonicNonNull String tableName;
|
||||||
|
|
||||||
public static void delete(DatabaseProvider databaseProvider, long uid)
|
public static void delete(DatabaseProvider databaseProvider, long uid)
|
||||||
throws DatabaseIOException {
|
throws DatabaseIOException {
|
||||||
delete(databaseProvider, Long.toHexString(uid));
|
delete(databaseProvider, Long.toHexString(uid));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@SuppressWarnings("nullness:initialization.fields.uninitialized")
|
||||||
public DatabaseStorage(DatabaseProvider databaseProvider) {
|
public DatabaseStorage(DatabaseProvider databaseProvider) {
|
||||||
this.databaseProvider = databaseProvider;
|
this.databaseProvider = databaseProvider;
|
||||||
pendingUpdates = new SparseArray<>();
|
pendingUpdates = new SparseArray<>();
|
||||||
@ -788,26 +798,26 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||||||
return VersionTable.getVersion(
|
return VersionTable.getVersion(
|
||||||
databaseProvider.getReadableDatabase(),
|
databaseProvider.getReadableDatabase(),
|
||||||
VersionTable.FEATURE_CACHE_CONTENT_METADATA,
|
VersionTable.FEATURE_CACHE_CONTENT_METADATA,
|
||||||
hexUid)
|
checkNotNull(hexUid))
|
||||||
!= VersionTable.VERSION_UNSET;
|
!= VersionTable.VERSION_UNSET;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void delete() throws DatabaseIOException {
|
public void delete() throws DatabaseIOException {
|
||||||
delete(databaseProvider, hexUid);
|
delete(databaseProvider, checkNotNull(hexUid));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void load(
|
public void load(
|
||||||
HashMap<String, CachedContent> content, SparseArray<@NullableType String> idToKey)
|
HashMap<String, CachedContent> content, SparseArray<@NullableType String> idToKey)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
Assertions.checkState(pendingUpdates.size() == 0);
|
checkState(pendingUpdates.size() == 0);
|
||||||
try {
|
try {
|
||||||
int version =
|
int version =
|
||||||
VersionTable.getVersion(
|
VersionTable.getVersion(
|
||||||
databaseProvider.getReadableDatabase(),
|
databaseProvider.getReadableDatabase(),
|
||||||
VersionTable.FEATURE_CACHE_CONTENT_METADATA,
|
VersionTable.FEATURE_CACHE_CONTENT_METADATA,
|
||||||
hexUid);
|
checkNotNull(hexUid));
|
||||||
if (version != TABLE_VERSION) {
|
if (version != TABLE_VERSION) {
|
||||||
SQLiteDatabase writableDatabase = databaseProvider.getWritableDatabase();
|
SQLiteDatabase writableDatabase = databaseProvider.getWritableDatabase();
|
||||||
writableDatabase.beginTransactionNonExclusive();
|
writableDatabase.beginTransactionNonExclusive();
|
||||||
@ -871,7 +881,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||||||
writableDatabase.beginTransactionNonExclusive();
|
writableDatabase.beginTransactionNonExclusive();
|
||||||
try {
|
try {
|
||||||
for (int i = 0; i < pendingUpdates.size(); i++) {
|
for (int i = 0; i < pendingUpdates.size(); i++) {
|
||||||
CachedContent cachedContent = pendingUpdates.valueAt(i);
|
@Nullable CachedContent cachedContent = pendingUpdates.valueAt(i);
|
||||||
if (cachedContent == null) {
|
if (cachedContent == null) {
|
||||||
deleteRow(writableDatabase, pendingUpdates.keyAt(i));
|
deleteRow(writableDatabase, pendingUpdates.keyAt(i));
|
||||||
} else {
|
} else {
|
||||||
@ -906,7 +916,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||||||
return databaseProvider
|
return databaseProvider
|
||||||
.getReadableDatabase()
|
.getReadableDatabase()
|
||||||
.query(
|
.query(
|
||||||
tableName,
|
checkNotNull(tableName),
|
||||||
COLUMNS,
|
COLUMNS,
|
||||||
/* selection= */ null,
|
/* selection= */ null,
|
||||||
/* selectionArgs= */ null,
|
/* selectionArgs= */ null,
|
||||||
@ -917,13 +927,17 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||||||
|
|
||||||
private void initializeTable(SQLiteDatabase writableDatabase) throws DatabaseIOException {
|
private void initializeTable(SQLiteDatabase writableDatabase) throws DatabaseIOException {
|
||||||
VersionTable.setVersion(
|
VersionTable.setVersion(
|
||||||
writableDatabase, VersionTable.FEATURE_CACHE_CONTENT_METADATA, hexUid, TABLE_VERSION);
|
writableDatabase,
|
||||||
dropTable(writableDatabase, tableName);
|
VersionTable.FEATURE_CACHE_CONTENT_METADATA,
|
||||||
|
checkNotNull(hexUid),
|
||||||
|
TABLE_VERSION);
|
||||||
|
dropTable(writableDatabase, checkNotNull(tableName));
|
||||||
writableDatabase.execSQL("CREATE TABLE " + tableName + " " + TABLE_SCHEMA);
|
writableDatabase.execSQL("CREATE TABLE " + tableName + " " + TABLE_SCHEMA);
|
||||||
}
|
}
|
||||||
|
|
||||||
private void deleteRow(SQLiteDatabase writableDatabase, int key) {
|
private void deleteRow(SQLiteDatabase writableDatabase, int key) {
|
||||||
writableDatabase.delete(tableName, WHERE_ID_EQUALS, new String[] {Integer.toString(key)});
|
writableDatabase.delete(
|
||||||
|
checkNotNull(tableName), WHERE_ID_EQUALS, new String[] {Integer.toString(key)});
|
||||||
}
|
}
|
||||||
|
|
||||||
private void addOrUpdateRow(SQLiteDatabase writableDatabase, CachedContent cachedContent)
|
private void addOrUpdateRow(SQLiteDatabase writableDatabase, CachedContent cachedContent)
|
||||||
@ -936,7 +950,7 @@ import org.checkerframework.checker.nullness.compatqual.NullableType;
|
|||||||
values.put(COLUMN_ID, cachedContent.id);
|
values.put(COLUMN_ID, cachedContent.id);
|
||||||
values.put(COLUMN_KEY, cachedContent.key);
|
values.put(COLUMN_KEY, cachedContent.key);
|
||||||
values.put(COLUMN_METADATA, data);
|
values.put(COLUMN_METADATA, data);
|
||||||
writableDatabase.replaceOrThrow(tableName, /* nullColumnHack= */ null, values);
|
writableDatabase.replaceOrThrow(checkNotNull(tableName), /* nullColumnHack= */ null, values);
|
||||||
}
|
}
|
||||||
|
|
||||||
private static void delete(DatabaseProvider databaseProvider, String hexUid)
|
private static void delete(DatabaseProvider databaseProvider, String hexUid)
|
||||||
|
@ -25,6 +25,7 @@ import com.google.android.exoplayer2.C;
|
|||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
import com.google.android.exoplayer2.MediaItem;
|
import com.google.android.exoplayer2.MediaItem;
|
||||||
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
import com.google.android.exoplayer2.Player;
|
import com.google.android.exoplayer2.Player;
|
||||||
import com.google.android.exoplayer2.Player.PlaybackSuppressionReason;
|
import com.google.android.exoplayer2.Player.PlaybackSuppressionReason;
|
||||||
import com.google.android.exoplayer2.RendererCapabilities;
|
import com.google.android.exoplayer2.RendererCapabilities;
|
||||||
@ -147,8 +148,9 @@ public class EventLogger implements AnalyticsListener {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlaybackSpeedChanged(EventTime eventTime, float playbackSpeed) {
|
public void onPlaybackParametersChanged(
|
||||||
logd(eventTime, "playbackSpeed", Float.toString(playbackSpeed));
|
EventTime eventTime, PlaybackParameters playbackParameters) {
|
||||||
|
logd(eventTime, "playbackParameters", playbackParameters.toString());
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -316,13 +318,19 @@ public class EventLogger implements AnalyticsListener {
|
|||||||
logd(eventTime, "audioInputFormat", Format.toLogString(format));
|
logd(eventTime, "audioInputFormat", Format.toLogString(format));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAudioPositionAdvancing(EventTime eventTime, long playoutStartSystemTimeMs) {
|
||||||
|
long timeSincePlayoutStartMs = System.currentTimeMillis() - playoutStartSystemTimeMs;
|
||||||
|
logd(eventTime, "audioPositionAdvancing", "timeSincePlayoutStartMs=" + timeSincePlayoutStartMs);
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAudioUnderrun(
|
public void onAudioUnderrun(
|
||||||
EventTime eventTime, int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
|
EventTime eventTime, int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
|
||||||
loge(
|
loge(
|
||||||
eventTime,
|
eventTime,
|
||||||
"audioTrackUnderrun",
|
"audioTrackUnderrun",
|
||||||
bufferSize + ", " + bufferSizeMs + ", " + elapsedSinceLastFeedMs + "]",
|
bufferSize + ", " + bufferSizeMs + ", " + elapsedSinceLastFeedMs,
|
||||||
/* throwable= */ null);
|
/* throwable= */ null);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.util;
|
package com.google.android.exoplayer2.util;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Tracks the progression of media time.
|
* Tracks the progression of media time.
|
||||||
*/
|
*/
|
||||||
@ -26,13 +28,13 @@ public interface MediaClock {
|
|||||||
long getPositionUs();
|
long getPositionUs();
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Attempts to set the playback speed. The media clock may override the speed if changing the
|
* Attempts to set the playback parameters. The media clock may override the speed if changing the
|
||||||
* speed is not supported.
|
* playback parameters is not supported.
|
||||||
*
|
*
|
||||||
* @param playbackSpeed The playback speed to attempt to set.
|
* @param playbackParameters The playback parameters to attempt to set.
|
||||||
*/
|
*/
|
||||||
void setPlaybackSpeed(float playbackSpeed);
|
void setPlaybackParameters(PlaybackParameters playbackParameters);
|
||||||
|
|
||||||
/** Returns the active playback speed. */
|
/** Returns the active playback parameters. */
|
||||||
float getPlaybackSpeed();
|
PlaybackParameters getPlaybackParameters();
|
||||||
}
|
}
|
||||||
|
@ -16,7 +16,7 @@
|
|||||||
package com.google.android.exoplayer2.util;
|
package com.google.android.exoplayer2.util;
|
||||||
|
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Player;
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* A {@link MediaClock} whose position advances with real time based on the playback parameters when
|
* A {@link MediaClock} whose position advances with real time based on the playback parameters when
|
||||||
@ -29,8 +29,7 @@ public final class StandaloneMediaClock implements MediaClock {
|
|||||||
private boolean started;
|
private boolean started;
|
||||||
private long baseUs;
|
private long baseUs;
|
||||||
private long baseElapsedMs;
|
private long baseElapsedMs;
|
||||||
private float playbackSpeed;
|
private PlaybackParameters playbackParameters;
|
||||||
private int scaledUsPerMs;
|
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a new standalone media clock using the given {@link Clock} implementation.
|
* Creates a new standalone media clock using the given {@link Clock} implementation.
|
||||||
@ -39,8 +38,7 @@ public final class StandaloneMediaClock implements MediaClock {
|
|||||||
*/
|
*/
|
||||||
public StandaloneMediaClock(Clock clock) {
|
public StandaloneMediaClock(Clock clock) {
|
||||||
this.clock = clock;
|
this.clock = clock;
|
||||||
playbackSpeed = Player.DEFAULT_PLAYBACK_SPEED;
|
playbackParameters = PlaybackParameters.DEFAULT;
|
||||||
scaledUsPerMs = getScaledUsPerMs(playbackSpeed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
@ -80,33 +78,29 @@ public final class StandaloneMediaClock implements MediaClock {
|
|||||||
long positionUs = baseUs;
|
long positionUs = baseUs;
|
||||||
if (started) {
|
if (started) {
|
||||||
long elapsedSinceBaseMs = clock.elapsedRealtime() - baseElapsedMs;
|
long elapsedSinceBaseMs = clock.elapsedRealtime() - baseElapsedMs;
|
||||||
if (playbackSpeed == 1f) {
|
if (playbackParameters.speed == 1f) {
|
||||||
positionUs += C.msToUs(elapsedSinceBaseMs);
|
positionUs += C.msToUs(elapsedSinceBaseMs);
|
||||||
} else {
|
} else {
|
||||||
// Add the media time in microseconds that will elapse in elapsedSinceBaseMs milliseconds of
|
// Add the media time in microseconds that will elapse in elapsedSinceBaseMs milliseconds of
|
||||||
// wallclock time
|
// wallclock time
|
||||||
positionUs += elapsedSinceBaseMs * scaledUsPerMs;
|
positionUs += playbackParameters.getMediaTimeUsForPlayoutTimeMs(elapsedSinceBaseMs);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
return positionUs;
|
return positionUs;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setPlaybackSpeed(float playbackSpeed) {
|
public void setPlaybackParameters(PlaybackParameters playbackParameters) {
|
||||||
// Store the current position as the new base, in case the playback speed has changed.
|
// Store the current position as the new base, in case the playback speed has changed.
|
||||||
if (started) {
|
if (started) {
|
||||||
resetPosition(getPositionUs());
|
resetPosition(getPositionUs());
|
||||||
}
|
}
|
||||||
this.playbackSpeed = playbackSpeed;
|
this.playbackParameters = playbackParameters;
|
||||||
scaledUsPerMs = getScaledUsPerMs(playbackSpeed);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getPlaybackSpeed() {
|
public PlaybackParameters getPlaybackParameters() {
|
||||||
return playbackSpeed;
|
return playbackParameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
private static int getScaledUsPerMs(float playbackSpeed) {
|
|
||||||
return Math.round(playbackSpeed * 1000f);
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -1611,6 +1611,8 @@ public class MediaCodecVideoRenderer extends MediaCodecRenderer {
|
|||||||
case "ELUGA_Prim":
|
case "ELUGA_Prim":
|
||||||
case "ELUGA_Ray_X":
|
case "ELUGA_Ray_X":
|
||||||
case "EverStar_S":
|
case "EverStar_S":
|
||||||
|
case "F02H":
|
||||||
|
case "F03H":
|
||||||
case "F3111":
|
case "F3111":
|
||||||
case "F3113":
|
case "F3113":
|
||||||
case "F3116":
|
case "F3116":
|
||||||
|
@ -22,7 +22,7 @@ import static org.mockito.Mockito.verifyNoMoreInteractions;
|
|||||||
import static org.mockito.MockitoAnnotations.initMocks;
|
import static org.mockito.MockitoAnnotations.initMocks;
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import com.google.android.exoplayer2.DefaultMediaClock.PlaybackSpeedListener;
|
import com.google.android.exoplayer2.DefaultMediaClock.PlaybackParametersListener;
|
||||||
import com.google.android.exoplayer2.testutil.FakeClock;
|
import com.google.android.exoplayer2.testutil.FakeClock;
|
||||||
import com.google.android.exoplayer2.testutil.FakeMediaClockRenderer;
|
import com.google.android.exoplayer2.testutil.FakeMediaClockRenderer;
|
||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
@ -36,9 +36,10 @@ public class DefaultMediaClockTest {
|
|||||||
|
|
||||||
private static final long TEST_POSITION_US = 123456789012345678L;
|
private static final long TEST_POSITION_US = 123456789012345678L;
|
||||||
private static final long SLEEP_TIME_MS = 1_000;
|
private static final long SLEEP_TIME_MS = 1_000;
|
||||||
private static final float TEST_PLAYBACK_SPEED = 2f;
|
private static final PlaybackParameters TEST_PLAYBACK_PARAMETERS =
|
||||||
|
new PlaybackParameters(/* speed= */ 2f);
|
||||||
|
|
||||||
@Mock private PlaybackSpeedListener listener;
|
@Mock private PlaybackParametersListener listener;
|
||||||
private FakeClock fakeClock;
|
private FakeClock fakeClock;
|
||||||
private DefaultMediaClock mediaClock;
|
private DefaultMediaClock mediaClock;
|
||||||
|
|
||||||
@ -109,44 +110,44 @@ public class DefaultMediaClockTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void standaloneGetPlaybackSpeed_initializedWithDefaultPlaybackSpeed() {
|
public void standaloneGetPlaybackParameters_initializedWithDefaultPlaybackParameters() {
|
||||||
assertThat(mediaClock.getPlaybackSpeed()).isEqualTo(Player.DEFAULT_PLAYBACK_SPEED);
|
assertThat(mediaClock.getPlaybackParameters()).isEqualTo(PlaybackParameters.DEFAULT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void standaloneSetPlaybackSpeed_getPlaybackSpeedShouldReturnSameValue() {
|
public void standaloneSetPlaybackParameters_getPlaybackParametersShouldReturnSameValue() {
|
||||||
mediaClock.setPlaybackSpeed(TEST_PLAYBACK_SPEED);
|
mediaClock.setPlaybackParameters(TEST_PLAYBACK_PARAMETERS);
|
||||||
assertThat(mediaClock.getPlaybackSpeed()).isEqualTo(TEST_PLAYBACK_SPEED);
|
assertThat(mediaClock.getPlaybackParameters()).isEqualTo(TEST_PLAYBACK_PARAMETERS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void standaloneSetPlaybackSpeed_shouldNotTriggerCallback() {
|
public void standaloneSetPlaybackParameters_shouldNotTriggerCallback() {
|
||||||
mediaClock.setPlaybackSpeed(TEST_PLAYBACK_SPEED);
|
mediaClock.setPlaybackParameters(TEST_PLAYBACK_PARAMETERS);
|
||||||
verifyNoMoreInteractions(listener);
|
verifyNoMoreInteractions(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void standaloneSetPlaybackSpeed_shouldApplyNewPlaybackSpeed() {
|
public void standaloneSetPlaybackParameters_shouldApplyNewPlaybackParameters() {
|
||||||
mediaClock.setPlaybackSpeed(TEST_PLAYBACK_SPEED);
|
mediaClock.setPlaybackParameters(TEST_PLAYBACK_PARAMETERS);
|
||||||
mediaClock.start();
|
mediaClock.start();
|
||||||
// Asserts that clock is running with speed declared in getPlaybackSpeed().
|
// Asserts that clock is running with speed declared in getPlaybackParameters().
|
||||||
assertClockIsRunning(/* isReadingAhead= */ false);
|
assertClockIsRunning(/* isReadingAhead= */ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void standaloneSetOtherPlaybackSpeed_getPlaybackSpeedShouldReturnSameValue() {
|
public void standaloneSetOtherPlaybackParameters_getPlaybackParametersShouldReturnSameValue() {
|
||||||
mediaClock.setPlaybackSpeed(TEST_PLAYBACK_SPEED);
|
mediaClock.setPlaybackParameters(TEST_PLAYBACK_PARAMETERS);
|
||||||
mediaClock.setPlaybackSpeed(Player.DEFAULT_PLAYBACK_SPEED);
|
mediaClock.setPlaybackParameters(PlaybackParameters.DEFAULT);
|
||||||
assertThat(mediaClock.getPlaybackSpeed()).isEqualTo(Player.DEFAULT_PLAYBACK_SPEED);
|
assertThat(mediaClock.getPlaybackParameters()).isEqualTo(PlaybackParameters.DEFAULT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void enableRendererMediaClock_shouldOverwriteRendererPlaybackSpeedIfPossible()
|
public void enableRendererMediaClock_shouldOverwriteRendererPlaybackParametersIfPossible()
|
||||||
throws ExoPlaybackException {
|
throws ExoPlaybackException {
|
||||||
FakeMediaClockRenderer mediaClockRenderer =
|
FakeMediaClockRenderer mediaClockRenderer =
|
||||||
new MediaClockRenderer(TEST_PLAYBACK_SPEED, /* playbackSpeedIsMutable= */ true);
|
new MediaClockRenderer(TEST_PLAYBACK_PARAMETERS, /* playbackParametersAreMutable= */ true);
|
||||||
mediaClock.onRendererEnabled(mediaClockRenderer);
|
mediaClock.onRendererEnabled(mediaClockRenderer);
|
||||||
assertThat(mediaClock.getPlaybackSpeed()).isEqualTo(Player.DEFAULT_PLAYBACK_SPEED);
|
assertThat(mediaClock.getPlaybackParameters()).isEqualTo(PlaybackParameters.DEFAULT);
|
||||||
verifyNoMoreInteractions(listener);
|
verifyNoMoreInteractions(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -154,26 +155,27 @@ public class DefaultMediaClockTest {
|
|||||||
public void enableRendererMediaClockWithFixedPlaybackSpeed_usesRendererPlaybackSpeed()
|
public void enableRendererMediaClockWithFixedPlaybackSpeed_usesRendererPlaybackSpeed()
|
||||||
throws ExoPlaybackException {
|
throws ExoPlaybackException {
|
||||||
FakeMediaClockRenderer mediaClockRenderer =
|
FakeMediaClockRenderer mediaClockRenderer =
|
||||||
new MediaClockRenderer(TEST_PLAYBACK_SPEED, /* playbackSpeedIsMutable= */ false);
|
new MediaClockRenderer(TEST_PLAYBACK_PARAMETERS, /* playbackParametersAreMutable= */ false);
|
||||||
mediaClock.onRendererEnabled(mediaClockRenderer);
|
mediaClock.onRendererEnabled(mediaClockRenderer);
|
||||||
assertThat(mediaClock.getPlaybackSpeed()).isEqualTo(TEST_PLAYBACK_SPEED);
|
assertThat(mediaClock.getPlaybackParameters()).isEqualTo(TEST_PLAYBACK_PARAMETERS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void enableRendererMediaClockWithFixedPlaybackSpeed_shouldTriggerCallback()
|
public void enableRendererMediaClockWithFixedPlaybackSpeed_shouldTriggerCallback()
|
||||||
throws ExoPlaybackException {
|
throws ExoPlaybackException {
|
||||||
FakeMediaClockRenderer mediaClockRenderer =
|
FakeMediaClockRenderer mediaClockRenderer =
|
||||||
new MediaClockRenderer(TEST_PLAYBACK_SPEED, /* playbackSpeedIsMutable= */ false);
|
new MediaClockRenderer(TEST_PLAYBACK_PARAMETERS, /* playbackParametersAreMutable= */ false);
|
||||||
mediaClock.onRendererEnabled(mediaClockRenderer);
|
mediaClock.onRendererEnabled(mediaClockRenderer);
|
||||||
mediaClock.syncAndGetPositionUs(/* isReadingAhead= */ false);
|
mediaClock.syncAndGetPositionUs(/* isReadingAhead= */ false);
|
||||||
verify(listener).onPlaybackSpeedChanged(TEST_PLAYBACK_SPEED);
|
verify(listener).onPlaybackParametersChanged(TEST_PLAYBACK_PARAMETERS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void enableRendererMediaClockWithFixedButSamePlaybackSpeed_shouldNotTriggerCallback()
|
public void enableRendererMediaClockWithFixedButSamePlaybackSpeed_shouldNotTriggerCallback()
|
||||||
throws ExoPlaybackException {
|
throws ExoPlaybackException {
|
||||||
FakeMediaClockRenderer mediaClockRenderer =
|
FakeMediaClockRenderer mediaClockRenderer =
|
||||||
new MediaClockRenderer(Player.DEFAULT_PLAYBACK_SPEED, /* playbackSpeedIsMutable= */ false);
|
new MediaClockRenderer(
|
||||||
|
PlaybackParameters.DEFAULT, /* playbackParametersAreMutable= */ false);
|
||||||
mediaClock.onRendererEnabled(mediaClockRenderer);
|
mediaClock.onRendererEnabled(mediaClockRenderer);
|
||||||
mediaClock.syncAndGetPositionUs(/* isReadingAhead= */ false);
|
mediaClock.syncAndGetPositionUs(/* isReadingAhead= */ false);
|
||||||
verifyNoMoreInteractions(listener);
|
verifyNoMoreInteractions(listener);
|
||||||
@ -182,44 +184,47 @@ public class DefaultMediaClockTest {
|
|||||||
@Test
|
@Test
|
||||||
public void disableRendererMediaClock_shouldKeepPlaybackSpeed() throws ExoPlaybackException {
|
public void disableRendererMediaClock_shouldKeepPlaybackSpeed() throws ExoPlaybackException {
|
||||||
FakeMediaClockRenderer mediaClockRenderer =
|
FakeMediaClockRenderer mediaClockRenderer =
|
||||||
new MediaClockRenderer(TEST_PLAYBACK_SPEED, /* playbackSpeedIsMutable= */ false);
|
new MediaClockRenderer(TEST_PLAYBACK_PARAMETERS, /* playbackParametersAreMutable= */ false);
|
||||||
mediaClock.onRendererEnabled(mediaClockRenderer);
|
mediaClock.onRendererEnabled(mediaClockRenderer);
|
||||||
mediaClock.syncAndGetPositionUs(/* isReadingAhead= */ false);
|
mediaClock.syncAndGetPositionUs(/* isReadingAhead= */ false);
|
||||||
mediaClock.onRendererDisabled(mediaClockRenderer);
|
mediaClock.onRendererDisabled(mediaClockRenderer);
|
||||||
mediaClock.syncAndGetPositionUs(/* isReadingAhead= */ false);
|
mediaClock.syncAndGetPositionUs(/* isReadingAhead= */ false);
|
||||||
assertThat(mediaClock.getPlaybackSpeed()).isEqualTo(TEST_PLAYBACK_SPEED);
|
assertThat(mediaClock.getPlaybackParameters()).isEqualTo(TEST_PLAYBACK_PARAMETERS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void rendererClockSetPlaybackSpeed_getPlaybackSpeedShouldReturnSameValue()
|
public void rendererClockSetPlaybackSpeed_getPlaybackParametersShouldReturnSameValue()
|
||||||
throws ExoPlaybackException {
|
throws ExoPlaybackException {
|
||||||
FakeMediaClockRenderer mediaClockRenderer =
|
FakeMediaClockRenderer mediaClockRenderer =
|
||||||
new MediaClockRenderer(Player.DEFAULT_PLAYBACK_SPEED, /* playbackSpeedIsMutable= */ true);
|
new MediaClockRenderer(
|
||||||
|
PlaybackParameters.DEFAULT, /* playbackParametersAreMutable= */ true);
|
||||||
mediaClock.onRendererEnabled(mediaClockRenderer);
|
mediaClock.onRendererEnabled(mediaClockRenderer);
|
||||||
mediaClock.syncAndGetPositionUs(/* isReadingAhead= */ false);
|
mediaClock.syncAndGetPositionUs(/* isReadingAhead= */ false);
|
||||||
mediaClock.setPlaybackSpeed(TEST_PLAYBACK_SPEED);
|
mediaClock.setPlaybackParameters(TEST_PLAYBACK_PARAMETERS);
|
||||||
assertThat(mediaClock.getPlaybackSpeed()).isEqualTo(TEST_PLAYBACK_SPEED);
|
assertThat(mediaClock.getPlaybackParameters()).isEqualTo(TEST_PLAYBACK_PARAMETERS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void rendererClockSetPlaybackSpeed_shouldNotTriggerCallback() throws ExoPlaybackException {
|
public void rendererClockSetPlaybackSpeed_shouldNotTriggerCallback() throws ExoPlaybackException {
|
||||||
FakeMediaClockRenderer mediaClockRenderer =
|
FakeMediaClockRenderer mediaClockRenderer =
|
||||||
new MediaClockRenderer(Player.DEFAULT_PLAYBACK_SPEED, /* playbackSpeedIsMutable= */ true);
|
new MediaClockRenderer(
|
||||||
|
PlaybackParameters.DEFAULT, /* playbackParametersAreMutable= */ true);
|
||||||
mediaClock.onRendererEnabled(mediaClockRenderer);
|
mediaClock.onRendererEnabled(mediaClockRenderer);
|
||||||
mediaClock.syncAndGetPositionUs(/* isReadingAhead= */ false);
|
mediaClock.syncAndGetPositionUs(/* isReadingAhead= */ false);
|
||||||
mediaClock.setPlaybackSpeed(TEST_PLAYBACK_SPEED);
|
mediaClock.setPlaybackParameters(TEST_PLAYBACK_PARAMETERS);
|
||||||
verifyNoMoreInteractions(listener);
|
verifyNoMoreInteractions(listener);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void rendererClockSetPlaybackSpeedOverwrite_getPlaybackSpeedShouldReturnSameValue()
|
public void rendererClockSetPlaybackSpeedOverwrite_getPlaybackParametersShouldReturnSameValue()
|
||||||
throws ExoPlaybackException {
|
throws ExoPlaybackException {
|
||||||
FakeMediaClockRenderer mediaClockRenderer =
|
FakeMediaClockRenderer mediaClockRenderer =
|
||||||
new MediaClockRenderer(Player.DEFAULT_PLAYBACK_SPEED, /* playbackSpeedIsMutable= */ false);
|
new MediaClockRenderer(
|
||||||
|
PlaybackParameters.DEFAULT, /* playbackParametersAreMutable= */ false);
|
||||||
mediaClock.onRendererEnabled(mediaClockRenderer);
|
mediaClock.onRendererEnabled(mediaClockRenderer);
|
||||||
mediaClock.syncAndGetPositionUs(/* isReadingAhead= */ false);
|
mediaClock.syncAndGetPositionUs(/* isReadingAhead= */ false);
|
||||||
mediaClock.setPlaybackSpeed(TEST_PLAYBACK_SPEED);
|
mediaClock.setPlaybackParameters(TEST_PLAYBACK_PARAMETERS);
|
||||||
assertThat(mediaClock.getPlaybackSpeed()).isEqualTo(Player.DEFAULT_PLAYBACK_SPEED);
|
assertThat(mediaClock.getPlaybackParameters()).isEqualTo(PlaybackParameters.DEFAULT);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -266,12 +271,13 @@ public class DefaultMediaClockTest {
|
|||||||
public void getPositionWithPlaybackSpeedChange_shouldTriggerCallback()
|
public void getPositionWithPlaybackSpeedChange_shouldTriggerCallback()
|
||||||
throws ExoPlaybackException {
|
throws ExoPlaybackException {
|
||||||
MediaClockRenderer mediaClockRenderer =
|
MediaClockRenderer mediaClockRenderer =
|
||||||
new MediaClockRenderer(Player.DEFAULT_PLAYBACK_SPEED, /* playbackSpeedIsMutable= */ true);
|
new MediaClockRenderer(
|
||||||
|
PlaybackParameters.DEFAULT, /* playbackParametersAreMutable= */ true);
|
||||||
mediaClock.onRendererEnabled(mediaClockRenderer);
|
mediaClock.onRendererEnabled(mediaClockRenderer);
|
||||||
// Silently change playback speed of renderer clock.
|
// Silently change playback speed of renderer clock.
|
||||||
mediaClockRenderer.playbackSpeed = TEST_PLAYBACK_SPEED;
|
mediaClockRenderer.playbackParameters = TEST_PLAYBACK_PARAMETERS;
|
||||||
mediaClock.syncAndGetPositionUs(/* isReadingAhead= */ false);
|
mediaClock.syncAndGetPositionUs(/* isReadingAhead= */ false);
|
||||||
verify(listener).onPlaybackSpeedChanged(TEST_PLAYBACK_SPEED);
|
verify(listener).onPlaybackParametersChanged(TEST_PLAYBACK_PARAMETERS);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -356,7 +362,7 @@ public class DefaultMediaClockTest {
|
|||||||
private void assertClockIsRunning(boolean isReadingAhead) {
|
private void assertClockIsRunning(boolean isReadingAhead) {
|
||||||
long clockStartUs = mediaClock.syncAndGetPositionUs(isReadingAhead);
|
long clockStartUs = mediaClock.syncAndGetPositionUs(isReadingAhead);
|
||||||
fakeClock.advanceTime(SLEEP_TIME_MS);
|
fakeClock.advanceTime(SLEEP_TIME_MS);
|
||||||
int scaledUsPerMs = Math.round(mediaClock.getPlaybackSpeed() * 1000f);
|
int scaledUsPerMs = Math.round(mediaClock.getPlaybackParameters().speed * 1000f);
|
||||||
assertThat(mediaClock.syncAndGetPositionUs(isReadingAhead))
|
assertThat(mediaClock.syncAndGetPositionUs(isReadingAhead))
|
||||||
.isEqualTo(clockStartUs + (SLEEP_TIME_MS * scaledUsPerMs));
|
.isEqualTo(clockStartUs + (SLEEP_TIME_MS * scaledUsPerMs));
|
||||||
}
|
}
|
||||||
@ -371,37 +377,53 @@ public class DefaultMediaClockTest {
|
|||||||
@SuppressWarnings("HidingField")
|
@SuppressWarnings("HidingField")
|
||||||
private static class MediaClockRenderer extends FakeMediaClockRenderer {
|
private static class MediaClockRenderer extends FakeMediaClockRenderer {
|
||||||
|
|
||||||
private final boolean playbackSpeedIsMutable;
|
private final boolean playbackParametersAreMutable;
|
||||||
private final boolean isReady;
|
private final boolean isReady;
|
||||||
private final boolean isEnded;
|
private final boolean isEnded;
|
||||||
|
|
||||||
public float playbackSpeed;
|
public PlaybackParameters playbackParameters;
|
||||||
public long positionUs;
|
public long positionUs;
|
||||||
|
|
||||||
public MediaClockRenderer() throws ExoPlaybackException {
|
public MediaClockRenderer() throws ExoPlaybackException {
|
||||||
this(Player.DEFAULT_PLAYBACK_SPEED, false, true, false, false);
|
this(
|
||||||
|
PlaybackParameters.DEFAULT,
|
||||||
|
/* playbackParametersAreMutable= */ false,
|
||||||
|
/* isReady= */ true,
|
||||||
|
/* isEnded= */ false,
|
||||||
|
/* hasReadStreamToEnd= */ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MediaClockRenderer(float playbackSpeed, boolean playbackSpeedIsMutable)
|
public MediaClockRenderer(
|
||||||
|
PlaybackParameters playbackParameters, boolean playbackParametersAreMutable)
|
||||||
throws ExoPlaybackException {
|
throws ExoPlaybackException {
|
||||||
this(playbackSpeed, playbackSpeedIsMutable, true, false, false);
|
this(
|
||||||
|
playbackParameters,
|
||||||
|
playbackParametersAreMutable,
|
||||||
|
/* isReady= */ true,
|
||||||
|
/* isEnded= */ false,
|
||||||
|
/* hasReadStreamToEnd= */ false);
|
||||||
}
|
}
|
||||||
|
|
||||||
public MediaClockRenderer(boolean isReady, boolean isEnded, boolean hasReadStreamToEnd)
|
public MediaClockRenderer(boolean isReady, boolean isEnded, boolean hasReadStreamToEnd)
|
||||||
throws ExoPlaybackException {
|
throws ExoPlaybackException {
|
||||||
this(Player.DEFAULT_PLAYBACK_SPEED, false, isReady, isEnded, hasReadStreamToEnd);
|
this(
|
||||||
|
PlaybackParameters.DEFAULT,
|
||||||
|
/* playbackParametersAreMutable= */ false,
|
||||||
|
isReady,
|
||||||
|
isEnded,
|
||||||
|
hasReadStreamToEnd);
|
||||||
}
|
}
|
||||||
|
|
||||||
private MediaClockRenderer(
|
private MediaClockRenderer(
|
||||||
float playbackSpeed,
|
PlaybackParameters playbackParameters,
|
||||||
boolean playbackSpeedIsMutable,
|
boolean playbackParametersAreMutable,
|
||||||
boolean isReady,
|
boolean isReady,
|
||||||
boolean isEnded,
|
boolean isEnded,
|
||||||
boolean hasReadStreamToEnd)
|
boolean hasReadStreamToEnd)
|
||||||
throws ExoPlaybackException {
|
throws ExoPlaybackException {
|
||||||
super(C.TRACK_TYPE_UNKNOWN);
|
super(C.TRACK_TYPE_UNKNOWN);
|
||||||
this.playbackSpeed = playbackSpeed;
|
this.playbackParameters = playbackParameters;
|
||||||
this.playbackSpeedIsMutable = playbackSpeedIsMutable;
|
this.playbackParametersAreMutable = playbackParametersAreMutable;
|
||||||
this.isReady = isReady;
|
this.isReady = isReady;
|
||||||
this.isEnded = isEnded;
|
this.isEnded = isEnded;
|
||||||
this.positionUs = TEST_POSITION_US;
|
this.positionUs = TEST_POSITION_US;
|
||||||
@ -416,15 +438,15 @@ public class DefaultMediaClockTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setPlaybackSpeed(float playbackSpeed) {
|
public void setPlaybackParameters(PlaybackParameters playbackParameters) {
|
||||||
if (playbackSpeedIsMutable) {
|
if (playbackParametersAreMutable) {
|
||||||
this.playbackSpeed = playbackSpeed;
|
this.playbackParameters = playbackParameters;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getPlaybackSpeed() {
|
public PlaybackParameters getPlaybackParameters() {
|
||||||
return playbackSpeed;
|
return playbackParameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
|
@ -328,11 +328,11 @@ public final class ExoPlayerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setPlaybackSpeed(float playbackSpeed) {}
|
public void setPlaybackParameters(PlaybackParameters playbackParameters) {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getPlaybackSpeed() {
|
public PlaybackParameters getPlaybackParameters() {
|
||||||
return Player.DEFAULT_PLAYBACK_SPEED;
|
return PlaybackParameters.DEFAULT;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -1010,7 +1010,7 @@ public final class ExoPlayerTest {
|
|||||||
}
|
}
|
||||||
})
|
})
|
||||||
// Set playback speed (while the fake media period is not yet prepared).
|
// Set playback speed (while the fake media period is not yet prepared).
|
||||||
.setPlaybackSpeed(2f)
|
.setPlaybackParameters(new PlaybackParameters(/* speed= */ 2f))
|
||||||
// Complete preparation of the fake media period.
|
// Complete preparation of the fake media period.
|
||||||
.executeRunnable(() -> fakeMediaPeriodHolder[0].setPreparationComplete())
|
.executeRunnable(() -> fakeMediaPeriodHolder[0].setPreparationComplete())
|
||||||
.build();
|
.build();
|
||||||
@ -3378,18 +3378,18 @@ public final class ExoPlayerTest {
|
|||||||
SimpleExoPlayer player,
|
SimpleExoPlayer player,
|
||||||
DefaultTrackSelector trackSelector,
|
DefaultTrackSelector trackSelector,
|
||||||
@Nullable Surface surface) {
|
@Nullable Surface surface) {
|
||||||
maskedPlaybackSpeeds.add(player.getPlaybackSpeed());
|
maskedPlaybackSpeeds.add(player.getPlaybackParameters().speed);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
ActionSchedule actionSchedule =
|
ActionSchedule actionSchedule =
|
||||||
new ActionSchedule.Builder(TAG)
|
new ActionSchedule.Builder(TAG)
|
||||||
.pause()
|
.pause()
|
||||||
.waitForPlaybackState(Player.STATE_READY)
|
.waitForPlaybackState(Player.STATE_READY)
|
||||||
.setPlaybackSpeed(1.1f)
|
.setPlaybackParameters(new PlaybackParameters(/* speed= */ 1.1f))
|
||||||
.apply(getPlaybackSpeedAction)
|
.apply(getPlaybackSpeedAction)
|
||||||
.setPlaybackSpeed(1.2f)
|
.setPlaybackParameters(new PlaybackParameters(/* speed= */ 1.2f))
|
||||||
.apply(getPlaybackSpeedAction)
|
.apply(getPlaybackSpeedAction)
|
||||||
.setPlaybackSpeed(1.3f)
|
.setPlaybackParameters(new PlaybackParameters(/* speed= */ 1.3f))
|
||||||
.apply(getPlaybackSpeedAction)
|
.apply(getPlaybackSpeedAction)
|
||||||
.play()
|
.play()
|
||||||
.build();
|
.build();
|
||||||
@ -3397,8 +3397,8 @@ public final class ExoPlayerTest {
|
|||||||
EventListener listener =
|
EventListener listener =
|
||||||
new EventListener() {
|
new EventListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onPlaybackSpeedChanged(float playbackSpeed) {
|
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
|
||||||
reportedPlaybackSpeeds.add(playbackSpeed);
|
reportedPlaybackSpeeds.add(playbackParameters.speed);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
new ExoPlayerTestRunner.Builder(context)
|
new ExoPlayerTestRunner.Builder(context)
|
||||||
@ -3424,28 +3424,28 @@ public final class ExoPlayerTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setPlaybackSpeed(float playbackSpeed) {}
|
public void setPlaybackParameters(PlaybackParameters playbackParameters) {}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public float getPlaybackSpeed() {
|
public PlaybackParameters getPlaybackParameters() {
|
||||||
return Player.DEFAULT_PLAYBACK_SPEED;
|
return PlaybackParameters.DEFAULT;
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
ActionSchedule actionSchedule =
|
ActionSchedule actionSchedule =
|
||||||
new ActionSchedule.Builder(TAG)
|
new ActionSchedule.Builder(TAG)
|
||||||
.pause()
|
.pause()
|
||||||
.waitForPlaybackState(Player.STATE_READY)
|
.waitForPlaybackState(Player.STATE_READY)
|
||||||
.setPlaybackSpeed(1.1f)
|
.setPlaybackParameters(new PlaybackParameters(/* speed= */ 1.1f))
|
||||||
.setPlaybackSpeed(1.2f)
|
.setPlaybackParameters(new PlaybackParameters(/* speed= */ 1.2f))
|
||||||
.setPlaybackSpeed(1.3f)
|
.setPlaybackParameters(new PlaybackParameters(/* speed= */ 1.3f))
|
||||||
.play()
|
.play()
|
||||||
.build();
|
.build();
|
||||||
List<Float> reportedPlaybackParameters = new ArrayList<>();
|
List<PlaybackParameters> reportedPlaybackParameters = new ArrayList<>();
|
||||||
EventListener listener =
|
EventListener listener =
|
||||||
new EventListener() {
|
new EventListener() {
|
||||||
@Override
|
@Override
|
||||||
public void onPlaybackSpeedChanged(float playbackSpeed) {
|
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
|
||||||
reportedPlaybackParameters.add(playbackSpeed);
|
reportedPlaybackParameters.add(playbackParameters);
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
new ExoPlayerTestRunner.Builder(context)
|
new ExoPlayerTestRunner.Builder(context)
|
||||||
@ -3458,7 +3458,11 @@ public final class ExoPlayerTest {
|
|||||||
.blockUntilEnded(TIMEOUT_MS);
|
.blockUntilEnded(TIMEOUT_MS);
|
||||||
|
|
||||||
assertThat(reportedPlaybackParameters)
|
assertThat(reportedPlaybackParameters)
|
||||||
.containsExactly(1.1f, 1.2f, 1.3f, Player.DEFAULT_PLAYBACK_SPEED)
|
.containsExactly(
|
||||||
|
new PlaybackParameters(/* speed= */ 1.1f),
|
||||||
|
new PlaybackParameters(/* speed= */ 1.2f),
|
||||||
|
new PlaybackParameters(/* speed= */ 1.3f),
|
||||||
|
PlaybackParameters.DEFAULT)
|
||||||
.inOrder();
|
.inOrder();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -435,7 +435,7 @@ public final class MediaPeriodQueueTest {
|
|||||||
/* loadingMediaPeriodId= */ null,
|
/* loadingMediaPeriodId= */ null,
|
||||||
/* playWhenReady= */ false,
|
/* playWhenReady= */ false,
|
||||||
Player.PLAYBACK_SUPPRESSION_REASON_NONE,
|
Player.PLAYBACK_SUPPRESSION_REASON_NONE,
|
||||||
/* playbackSpeed= */ Player.DEFAULT_PLAYBACK_SPEED,
|
/* playbackParameters= */ PlaybackParameters.DEFAULT,
|
||||||
/* bufferedPositionUs= */ 0,
|
/* bufferedPositionUs= */ 0,
|
||||||
/* totalBufferedDurationUs= */ 0,
|
/* totalBufferedDurationUs= */ 0,
|
||||||
/* positionUs= */ 0,
|
/* positionUs= */ 0,
|
||||||
|
@ -26,6 +26,7 @@ import androidx.test.ext.junit.runners.AndroidJUnit4;
|
|||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
import com.google.android.exoplayer2.Player;
|
import com.google.android.exoplayer2.Player;
|
||||||
import com.google.android.exoplayer2.Renderer;
|
import com.google.android.exoplayer2.Renderer;
|
||||||
import com.google.android.exoplayer2.RenderersFactory;
|
import com.google.android.exoplayer2.RenderersFactory;
|
||||||
@ -82,7 +83,7 @@ public final class AnalyticsCollectorTest {
|
|||||||
private static final int EVENT_POSITION_DISCONTINUITY = 2;
|
private static final int EVENT_POSITION_DISCONTINUITY = 2;
|
||||||
private static final int EVENT_SEEK_STARTED = 3;
|
private static final int EVENT_SEEK_STARTED = 3;
|
||||||
private static final int EVENT_SEEK_PROCESSED = 4;
|
private static final int EVENT_SEEK_PROCESSED = 4;
|
||||||
private static final int EVENT_PLAYBACK_SPEED_CHANGED = 5;
|
private static final int EVENT_PLAYBACK_PARAMETERS_CHANGED = 5;
|
||||||
private static final int EVENT_REPEAT_MODE_CHANGED = 6;
|
private static final int EVENT_REPEAT_MODE_CHANGED = 6;
|
||||||
private static final int EVENT_SHUFFLE_MODE_CHANGED = 7;
|
private static final int EVENT_SHUFFLE_MODE_CHANGED = 7;
|
||||||
private static final int EVENT_LOADING_CHANGED = 8;
|
private static final int EVENT_LOADING_CHANGED = 8;
|
||||||
@ -106,21 +107,22 @@ public final class AnalyticsCollectorTest {
|
|||||||
private static final int EVENT_AUDIO_INPUT_FORMAT_CHANGED = 26;
|
private static final int EVENT_AUDIO_INPUT_FORMAT_CHANGED = 26;
|
||||||
private static final int EVENT_AUDIO_DISABLED = 27;
|
private static final int EVENT_AUDIO_DISABLED = 27;
|
||||||
private static final int EVENT_AUDIO_SESSION_ID = 28;
|
private static final int EVENT_AUDIO_SESSION_ID = 28;
|
||||||
private static final int EVENT_AUDIO_UNDERRUN = 29;
|
private static final int EVENT_AUDIO_POSITION_ADVANCING = 29;
|
||||||
private static final int EVENT_VIDEO_ENABLED = 30;
|
private static final int EVENT_AUDIO_UNDERRUN = 30;
|
||||||
private static final int EVENT_VIDEO_DECODER_INIT = 31;
|
private static final int EVENT_VIDEO_ENABLED = 31;
|
||||||
private static final int EVENT_VIDEO_INPUT_FORMAT_CHANGED = 32;
|
private static final int EVENT_VIDEO_DECODER_INIT = 32;
|
||||||
private static final int EVENT_DROPPED_FRAMES = 33;
|
private static final int EVENT_VIDEO_INPUT_FORMAT_CHANGED = 33;
|
||||||
private static final int EVENT_VIDEO_DISABLED = 34;
|
private static final int EVENT_DROPPED_FRAMES = 34;
|
||||||
private static final int EVENT_RENDERED_FIRST_FRAME = 35;
|
private static final int EVENT_VIDEO_DISABLED = 35;
|
||||||
private static final int EVENT_VIDEO_FRAME_PROCESSING_OFFSET = 36;
|
private static final int EVENT_RENDERED_FIRST_FRAME = 36;
|
||||||
private static final int EVENT_VIDEO_SIZE_CHANGED = 37;
|
private static final int EVENT_VIDEO_FRAME_PROCESSING_OFFSET = 37;
|
||||||
private static final int EVENT_DRM_KEYS_LOADED = 38;
|
private static final int EVENT_VIDEO_SIZE_CHANGED = 38;
|
||||||
private static final int EVENT_DRM_ERROR = 39;
|
private static final int EVENT_DRM_KEYS_LOADED = 39;
|
||||||
private static final int EVENT_DRM_KEYS_RESTORED = 40;
|
private static final int EVENT_DRM_ERROR = 40;
|
||||||
private static final int EVENT_DRM_KEYS_REMOVED = 41;
|
private static final int EVENT_DRM_KEYS_RESTORED = 41;
|
||||||
private static final int EVENT_DRM_SESSION_ACQUIRED = 42;
|
private static final int EVENT_DRM_KEYS_REMOVED = 42;
|
||||||
private static final int EVENT_DRM_SESSION_RELEASED = 43;
|
private static final int EVENT_DRM_SESSION_ACQUIRED = 43;
|
||||||
|
private static final int EVENT_DRM_SESSION_RELEASED = 44;
|
||||||
|
|
||||||
private static final UUID DRM_SCHEME_UUID =
|
private static final UUID DRM_SCHEME_UUID =
|
||||||
UUID.nameUUIDFromBytes(TestUtil.createByteArray(7, 8, 9));
|
UUID.nameUUIDFromBytes(TestUtil.createByteArray(7, 8, 9));
|
||||||
@ -226,6 +228,7 @@ public final class AnalyticsCollectorTest {
|
|||||||
assertThat(listener.getEvents(EVENT_AUDIO_DECODER_INIT)).containsExactly(period0);
|
assertThat(listener.getEvents(EVENT_AUDIO_DECODER_INIT)).containsExactly(period0);
|
||||||
assertThat(listener.getEvents(EVENT_AUDIO_INPUT_FORMAT_CHANGED)).containsExactly(period0);
|
assertThat(listener.getEvents(EVENT_AUDIO_INPUT_FORMAT_CHANGED)).containsExactly(period0);
|
||||||
assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(period0);
|
assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(period0);
|
||||||
|
assertThat(listener.getEvents(EVENT_AUDIO_POSITION_ADVANCING)).containsExactly(period0);
|
||||||
assertThat(listener.getEvents(EVENT_VIDEO_ENABLED)).containsExactly(period0);
|
assertThat(listener.getEvents(EVENT_VIDEO_ENABLED)).containsExactly(period0);
|
||||||
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INIT)).containsExactly(period0);
|
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INIT)).containsExactly(period0);
|
||||||
assertThat(listener.getEvents(EVENT_VIDEO_INPUT_FORMAT_CHANGED)).containsExactly(period0);
|
assertThat(listener.getEvents(EVENT_VIDEO_INPUT_FORMAT_CHANGED)).containsExactly(period0);
|
||||||
@ -305,6 +308,7 @@ public final class AnalyticsCollectorTest {
|
|||||||
.containsExactly(period0, period1)
|
.containsExactly(period0, period1)
|
||||||
.inOrder();
|
.inOrder();
|
||||||
assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(period0);
|
assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(period0);
|
||||||
|
assertThat(listener.getEvents(EVENT_AUDIO_POSITION_ADVANCING)).containsExactly(period0);
|
||||||
assertThat(listener.getEvents(EVENT_VIDEO_ENABLED)).containsExactly(period0);
|
assertThat(listener.getEvents(EVENT_VIDEO_ENABLED)).containsExactly(period0);
|
||||||
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INIT))
|
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INIT))
|
||||||
.containsExactly(period0, period1)
|
.containsExactly(period0, period1)
|
||||||
@ -380,6 +384,7 @@ public final class AnalyticsCollectorTest {
|
|||||||
assertThat(listener.getEvents(EVENT_AUDIO_DECODER_INIT)).containsExactly(period1);
|
assertThat(listener.getEvents(EVENT_AUDIO_DECODER_INIT)).containsExactly(period1);
|
||||||
assertThat(listener.getEvents(EVENT_AUDIO_INPUT_FORMAT_CHANGED)).containsExactly(period1);
|
assertThat(listener.getEvents(EVENT_AUDIO_INPUT_FORMAT_CHANGED)).containsExactly(period1);
|
||||||
assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(period1);
|
assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID)).containsExactly(period1);
|
||||||
|
assertThat(listener.getEvents(EVENT_AUDIO_POSITION_ADVANCING)).containsExactly(period1);
|
||||||
assertThat(listener.getEvents(EVENT_VIDEO_ENABLED)).containsExactly(period0);
|
assertThat(listener.getEvents(EVENT_VIDEO_ENABLED)).containsExactly(period0);
|
||||||
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INIT)).containsExactly(period0);
|
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INIT)).containsExactly(period0);
|
||||||
assertThat(listener.getEvents(EVENT_VIDEO_INPUT_FORMAT_CHANGED)).containsExactly(period0);
|
assertThat(listener.getEvents(EVENT_VIDEO_INPUT_FORMAT_CHANGED)).containsExactly(period0);
|
||||||
@ -476,6 +481,9 @@ public final class AnalyticsCollectorTest {
|
|||||||
assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID))
|
assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID))
|
||||||
.containsExactly(period0, period1)
|
.containsExactly(period0, period1)
|
||||||
.inOrder();
|
.inOrder();
|
||||||
|
assertThat(listener.getEvents(EVENT_AUDIO_POSITION_ADVANCING))
|
||||||
|
.containsExactly(period0, period1)
|
||||||
|
.inOrder();
|
||||||
assertThat(listener.getEvents(EVENT_AUDIO_DISABLED)).containsExactly(period0);
|
assertThat(listener.getEvents(EVENT_AUDIO_DISABLED)).containsExactly(period0);
|
||||||
assertThat(listener.getEvents(EVENT_VIDEO_ENABLED)).containsExactly(period0);
|
assertThat(listener.getEvents(EVENT_VIDEO_ENABLED)).containsExactly(period0);
|
||||||
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INIT)).containsExactly(period0);
|
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INIT)).containsExactly(period0);
|
||||||
@ -576,6 +584,9 @@ public final class AnalyticsCollectorTest {
|
|||||||
assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID))
|
assertThat(listener.getEvents(EVENT_AUDIO_SESSION_ID))
|
||||||
.containsExactly(period1Seq1, period1Seq2)
|
.containsExactly(period1Seq1, period1Seq2)
|
||||||
.inOrder();
|
.inOrder();
|
||||||
|
assertThat(listener.getEvents(EVENT_AUDIO_POSITION_ADVANCING))
|
||||||
|
.containsExactly(period1Seq1, period1Seq2)
|
||||||
|
.inOrder();
|
||||||
assertThat(listener.getEvents(EVENT_AUDIO_DISABLED)).containsExactly(period0);
|
assertThat(listener.getEvents(EVENT_AUDIO_DISABLED)).containsExactly(period0);
|
||||||
assertThat(listener.getEvents(EVENT_VIDEO_ENABLED)).containsExactly(period0, period0);
|
assertThat(listener.getEvents(EVENT_VIDEO_ENABLED)).containsExactly(period0, period0);
|
||||||
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INIT))
|
assertThat(listener.getEvents(EVENT_VIDEO_DECODER_INIT))
|
||||||
@ -1785,8 +1796,9 @@ public final class AnalyticsCollectorTest {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlaybackSpeedChanged(EventTime eventTime, float playbackSpeed) {
|
public void onPlaybackParametersChanged(
|
||||||
reportedEvents.add(new ReportedEvent(EVENT_PLAYBACK_SPEED_CHANGED, eventTime));
|
EventTime eventTime, PlaybackParameters playbackParameters) {
|
||||||
|
reportedEvents.add(new ReportedEvent(EVENT_PLAYBACK_PARAMETERS_CHANGED, eventTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -1922,6 +1934,11 @@ public final class AnalyticsCollectorTest {
|
|||||||
reportedEvents.add(new ReportedEvent(EVENT_AUDIO_SESSION_ID, eventTime));
|
reportedEvents.add(new ReportedEvent(EVENT_AUDIO_SESSION_ID, eventTime));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void onAudioPositionAdvancing(EventTime eventTime, long playoutStartSystemTimeMs) {
|
||||||
|
reportedEvents.add(new ReportedEvent(EVENT_AUDIO_POSITION_ADVANCING, eventTime));
|
||||||
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onAudioUnderrun(
|
public void onAudioUnderrun(
|
||||||
EventTime eventTime, int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
|
EventTime eventTime, int bufferSize, long bufferSizeMs, long elapsedSinceLastFeedMs) {
|
||||||
|
@ -26,6 +26,7 @@ import static org.mockito.Mockito.verify;
|
|||||||
import android.os.SystemClock;
|
import android.os.SystemClock;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
import com.google.android.exoplayer2.Player;
|
import com.google.android.exoplayer2.Player;
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
@ -75,8 +76,8 @@ public final class PlaybackStatsListenerTest {
|
|||||||
|
|
||||||
playbackStatsListener.onPositionDiscontinuity(
|
playbackStatsListener.onPositionDiscontinuity(
|
||||||
EMPTY_TIMELINE_EVENT_TIME, Player.DISCONTINUITY_REASON_SEEK);
|
EMPTY_TIMELINE_EVENT_TIME, Player.DISCONTINUITY_REASON_SEEK);
|
||||||
playbackStatsListener.onPlaybackSpeedChanged(
|
playbackStatsListener.onPlaybackParametersChanged(
|
||||||
EMPTY_TIMELINE_EVENT_TIME, /* playbackSpeed= */ 2.0f);
|
EMPTY_TIMELINE_EVENT_TIME, new PlaybackParameters(/* speed= */ 2.0f));
|
||||||
playbackStatsListener.onPlayWhenReadyChanged(
|
playbackStatsListener.onPlayWhenReadyChanged(
|
||||||
EMPTY_TIMELINE_EVENT_TIME,
|
EMPTY_TIMELINE_EVENT_TIME,
|
||||||
/* playWhenReady= */ true,
|
/* playWhenReady= */ true,
|
||||||
|
@ -25,6 +25,7 @@ import static org.robolectric.annotation.Config.TARGET_SDK;
|
|||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
import java.nio.ByteBuffer;
|
import java.nio.ByteBuffer;
|
||||||
import java.nio.ByteOrder;
|
import java.nio.ByteOrder;
|
||||||
@ -89,7 +90,7 @@ public final class DefaultAudioSinkTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void handlesBufferAfterReset_withPlaybackSpeed() throws Exception {
|
public void handlesBufferAfterReset_withPlaybackSpeed() throws Exception {
|
||||||
defaultAudioSink.setPlaybackSpeed(/* playbackSpeed= */ 1.5f);
|
defaultAudioSink.setPlaybackParameters(new PlaybackParameters(/* speed= */ 1.5f));
|
||||||
configureDefaultAudioSink(CHANNEL_COUNT_STEREO);
|
configureDefaultAudioSink(CHANNEL_COUNT_STEREO);
|
||||||
defaultAudioSink.handleBuffer(
|
defaultAudioSink.handleBuffer(
|
||||||
createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1);
|
createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1);
|
||||||
@ -99,7 +100,8 @@ public final class DefaultAudioSinkTest {
|
|||||||
configureDefaultAudioSink(CHANNEL_COUNT_STEREO);
|
configureDefaultAudioSink(CHANNEL_COUNT_STEREO);
|
||||||
defaultAudioSink.handleBuffer(
|
defaultAudioSink.handleBuffer(
|
||||||
createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1);
|
createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1);
|
||||||
assertThat(defaultAudioSink.getPlaybackSpeed()).isEqualTo(1.5f);
|
assertThat(defaultAudioSink.getPlaybackParameters())
|
||||||
|
.isEqualTo(new PlaybackParameters(/* speed= */ 1.5f));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
@ -117,7 +119,7 @@ public final class DefaultAudioSinkTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void handlesBufferAfterReset_withFormatChangeAndPlaybackSpeed() throws Exception {
|
public void handlesBufferAfterReset_withFormatChangeAndPlaybackSpeed() throws Exception {
|
||||||
defaultAudioSink.setPlaybackSpeed(/* playbackSpeed= */ 1.5f);
|
defaultAudioSink.setPlaybackParameters(new PlaybackParameters(/* speed= */ 1.5f));
|
||||||
configureDefaultAudioSink(CHANNEL_COUNT_STEREO);
|
configureDefaultAudioSink(CHANNEL_COUNT_STEREO);
|
||||||
defaultAudioSink.handleBuffer(
|
defaultAudioSink.handleBuffer(
|
||||||
createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1);
|
createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1);
|
||||||
@ -127,7 +129,8 @@ public final class DefaultAudioSinkTest {
|
|||||||
configureDefaultAudioSink(CHANNEL_COUNT_MONO);
|
configureDefaultAudioSink(CHANNEL_COUNT_MONO);
|
||||||
defaultAudioSink.handleBuffer(
|
defaultAudioSink.handleBuffer(
|
||||||
createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1);
|
createDefaultSilenceBuffer(), /* presentationTimeUs= */ 0, /* encodedAccessUnitCount= */ 1);
|
||||||
assertThat(defaultAudioSink.getPlaybackSpeed()).isEqualTo(1.5f);
|
assertThat(defaultAudioSink.getPlaybackParameters())
|
||||||
|
.isEqualTo(new PlaybackParameters(/* speed= */ 1.5f));
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
|
@ -49,8 +49,10 @@ import org.junit.runner.RunWith;
|
|||||||
import org.mockito.Mock;
|
import org.mockito.Mock;
|
||||||
import org.mockito.junit.MockitoJUnit;
|
import org.mockito.junit.MockitoJUnit;
|
||||||
import org.mockito.junit.MockitoRule;
|
import org.mockito.junit.MockitoRule;
|
||||||
|
import org.robolectric.annotation.Config;
|
||||||
|
|
||||||
/** Unit tests for {@link MediaCodecAudioRenderer} */
|
/** Unit tests for {@link MediaCodecAudioRenderer} */
|
||||||
|
@Config(sdk = 29)
|
||||||
@RunWith(AndroidJUnit4.class)
|
@RunWith(AndroidJUnit4.class)
|
||||||
public class MediaCodecAudioRendererTest {
|
public class MediaCodecAudioRendererTest {
|
||||||
@Rule public final MockitoRule mockito = MockitoJUnit.rule();
|
@Rule public final MockitoRule mockito = MockitoJUnit.rule();
|
||||||
|
@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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 com.google.android.exoplayer2.e2etest;
|
||||||
|
|
||||||
|
import android.graphics.SurfaceTexture;
|
||||||
|
import android.view.Surface;
|
||||||
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import com.google.android.exoplayer2.MediaItem;
|
||||||
|
import com.google.android.exoplayer2.Player;
|
||||||
|
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||||
|
import com.google.android.exoplayer2.testutil.AutoAdvancingFakeClock;
|
||||||
|
import com.google.android.exoplayer2.testutil.DumpFileAsserts;
|
||||||
|
import com.google.android.exoplayer2.testutil.PlaybackOutput;
|
||||||
|
import com.google.android.exoplayer2.testutil.ShadowMediaCodecConfig;
|
||||||
|
import com.google.android.exoplayer2.testutil.TestExoPlayer;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.robolectric.annotation.Config;
|
||||||
|
|
||||||
|
/** End-to-end tests using MP4 samples. */
|
||||||
|
// TODO(b/143232359): Remove once https://issuetracker.google.com/143232359 is resolved.
|
||||||
|
@Config(sdk = 29)
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class Mp4PlaybackTest {
|
||||||
|
@Rule
|
||||||
|
public ShadowMediaCodecConfig mediaCodecConfig =
|
||||||
|
ShadowMediaCodecConfig.forAllSupportedMimeTypes();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void h264VideoAacAudio() throws Exception {
|
||||||
|
SimpleExoPlayer player =
|
||||||
|
new SimpleExoPlayer.Builder(ApplicationProvider.getApplicationContext())
|
||||||
|
.setClock(new AutoAdvancingFakeClock())
|
||||||
|
.build();
|
||||||
|
player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1)));
|
||||||
|
PlaybackOutput playbackOutput = PlaybackOutput.register(player, mediaCodecConfig);
|
||||||
|
|
||||||
|
player.setMediaItem(MediaItem.fromUri("asset:///media/mp4/sample.mp4"));
|
||||||
|
player.prepare();
|
||||||
|
player.play();
|
||||||
|
TestExoPlayer.runUntilPlaybackState(player, Player.STATE_ENDED);
|
||||||
|
|
||||||
|
DumpFileAsserts.assertOutput(
|
||||||
|
ApplicationProvider.getApplicationContext(),
|
||||||
|
playbackOutput,
|
||||||
|
"playbackdumps/mp4/sample.mp4.dump");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,64 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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 com.google.android.exoplayer2.e2etest;
|
||||||
|
|
||||||
|
import android.graphics.SurfaceTexture;
|
||||||
|
import android.view.Surface;
|
||||||
|
import androidx.test.core.app.ApplicationProvider;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import com.google.android.exoplayer2.MediaItem;
|
||||||
|
import com.google.android.exoplayer2.Player;
|
||||||
|
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||||
|
import com.google.android.exoplayer2.testutil.AutoAdvancingFakeClock;
|
||||||
|
import com.google.android.exoplayer2.testutil.DumpFileAsserts;
|
||||||
|
import com.google.android.exoplayer2.testutil.PlaybackOutput;
|
||||||
|
import com.google.android.exoplayer2.testutil.ShadowMediaCodecConfig;
|
||||||
|
import com.google.android.exoplayer2.testutil.TestExoPlayer;
|
||||||
|
import org.junit.Rule;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
import org.robolectric.annotation.Config;
|
||||||
|
|
||||||
|
/** End-to-end tests using TS samples. */
|
||||||
|
// TODO(b/143232359): Remove once https://issuetracker.google.com/143232359 is resolved.
|
||||||
|
@Config(sdk = 29)
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class TsPlaybackTest {
|
||||||
|
|
||||||
|
@Rule
|
||||||
|
public ShadowMediaCodecConfig mediaCodecConfig =
|
||||||
|
ShadowMediaCodecConfig.forAllSupportedMimeTypes();
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void mpegVideoMpegAudioScte35() throws Exception {
|
||||||
|
SimpleExoPlayer player =
|
||||||
|
new SimpleExoPlayer.Builder(ApplicationProvider.getApplicationContext())
|
||||||
|
.setClock(new AutoAdvancingFakeClock())
|
||||||
|
.build();
|
||||||
|
player.setVideoSurface(new Surface(new SurfaceTexture(/* texName= */ 1)));
|
||||||
|
PlaybackOutput playbackOutput = PlaybackOutput.register(player, mediaCodecConfig);
|
||||||
|
|
||||||
|
player.setMediaItem(MediaItem.fromUri("asset:///media/ts/sample_scte35.ts"));
|
||||||
|
player.prepare();
|
||||||
|
player.play();
|
||||||
|
TestExoPlayer.runUntilPlaybackState(player, Player.STATE_ENDED);
|
||||||
|
|
||||||
|
DumpFileAsserts.assertOutput(
|
||||||
|
ApplicationProvider.getApplicationContext(),
|
||||||
|
playbackOutput,
|
||||||
|
"playbackdumps/ts/sample_scte35.ts.dump");
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,53 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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 com.google.android.exoplayer2.source;
|
||||||
|
|
||||||
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
|
import android.net.Uri;
|
||||||
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
|
import com.google.android.exoplayer2.MediaItem;
|
||||||
|
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||||
|
import org.junit.Test;
|
||||||
|
import org.junit.runner.RunWith;
|
||||||
|
|
||||||
|
/** Unit tests for {@link MediaSourceDrmHelper}. */
|
||||||
|
@RunWith(AndroidJUnit4.class)
|
||||||
|
public class MediaSourceDrmHelperTest {
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void create_noDrmProperties_createsNoopManager() {
|
||||||
|
DrmSessionManager drmSessionManager =
|
||||||
|
new MediaSourceDrmHelper().create(MediaItem.fromUri(Uri.EMPTY));
|
||||||
|
|
||||||
|
assertThat(drmSessionManager).isEqualTo(DrmSessionManager.DUMMY);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Test
|
||||||
|
public void create_createsManager() {
|
||||||
|
MediaItem mediaItem =
|
||||||
|
new MediaItem.Builder()
|
||||||
|
.setUri(Uri.EMPTY)
|
||||||
|
.setDrmLicenseUri(Uri.EMPTY)
|
||||||
|
.setDrmUuid(C.WIDEVINE_UUID)
|
||||||
|
.build();
|
||||||
|
|
||||||
|
DrmSessionManager drmSessionManager = new MediaSourceDrmHelper().create(mediaItem);
|
||||||
|
|
||||||
|
assertThat(drmSessionManager).isNotEqualTo(DrmSessionManager.DUMMY);
|
||||||
|
}
|
||||||
|
}
|
@ -38,6 +38,7 @@ import com.google.android.exoplayer2.drm.DrmInitData;
|
|||||||
import com.google.android.exoplayer2.drm.DrmSession;
|
import com.google.android.exoplayer2.drm.DrmSession;
|
||||||
import com.google.android.exoplayer2.drm.DrmSessionEventListener;
|
import com.google.android.exoplayer2.drm.DrmSessionEventListener;
|
||||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||||
|
import com.google.android.exoplayer2.drm.ExoMediaCrypto;
|
||||||
import com.google.android.exoplayer2.extractor.TrackOutput;
|
import com.google.android.exoplayer2.extractor.TrackOutput;
|
||||||
import com.google.android.exoplayer2.testutil.TestUtil;
|
import com.google.android.exoplayer2.testutil.TestUtil;
|
||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
import com.google.android.exoplayer2.upstream.Allocator;
|
||||||
@ -54,7 +55,6 @@ import org.junit.Assert;
|
|||||||
import org.junit.Before;
|
import org.junit.Before;
|
||||||
import org.junit.Test;
|
import org.junit.Test;
|
||||||
import org.junit.runner.RunWith;
|
import org.junit.runner.RunWith;
|
||||||
import org.mockito.ArgumentMatchers;
|
|
||||||
import org.mockito.Mockito;
|
import org.mockito.Mockito;
|
||||||
|
|
||||||
/** Test for {@link SampleQueue}. */
|
/** Test for {@link SampleQueue}. */
|
||||||
@ -69,6 +69,8 @@ public final class SampleQueueTest {
|
|||||||
private static final Format FORMAT_SPLICED = buildFormat(/* id= */ "spliced");
|
private static final Format FORMAT_SPLICED = buildFormat(/* id= */ "spliced");
|
||||||
private static final Format FORMAT_ENCRYPTED =
|
private static final Format FORMAT_ENCRYPTED =
|
||||||
new Format.Builder().setId(/* id= */ "encrypted").setDrmInitData(new DrmInitData()).build();
|
new Format.Builder().setId(/* id= */ "encrypted").setDrmInitData(new DrmInitData()).build();
|
||||||
|
private static final Format FORMAT_ENCRYPTED_WITH_EXO_MEDIA_CRYPTO_TYPE =
|
||||||
|
FORMAT_ENCRYPTED.copyWithExoMediaCryptoType(MockExoMediaCrypto.class);
|
||||||
private static final byte[] DATA = TestUtil.buildTestData(ALLOCATION_SIZE * 10);
|
private static final byte[] DATA = TestUtil.buildTestData(ALLOCATION_SIZE * 10);
|
||||||
|
|
||||||
/*
|
/*
|
||||||
@ -128,7 +130,7 @@ public final class SampleQueueTest {
|
|||||||
new TrackOutput.CryptoData(C.CRYPTO_MODE_AES_CTR, new byte[16], 0, 0);
|
new TrackOutput.CryptoData(C.CRYPTO_MODE_AES_CTR, new byte[16], 0, 0);
|
||||||
|
|
||||||
private Allocator allocator;
|
private Allocator allocator;
|
||||||
private DrmSessionManager mockDrmSessionManager;
|
private MockDrmSessionManager mockDrmSessionManager;
|
||||||
private DrmSession mockDrmSession;
|
private DrmSession mockDrmSession;
|
||||||
private DrmSessionEventListener.EventDispatcher eventDispatcher;
|
private DrmSessionEventListener.EventDispatcher eventDispatcher;
|
||||||
private SampleQueue sampleQueue;
|
private SampleQueue sampleQueue;
|
||||||
@ -138,11 +140,8 @@ public final class SampleQueueTest {
|
|||||||
@Before
|
@Before
|
||||||
public void setUp() {
|
public void setUp() {
|
||||||
allocator = new DefaultAllocator(false, ALLOCATION_SIZE);
|
allocator = new DefaultAllocator(false, ALLOCATION_SIZE);
|
||||||
mockDrmSessionManager = Mockito.mock(DrmSessionManager.class);
|
|
||||||
mockDrmSession = Mockito.mock(DrmSession.class);
|
mockDrmSession = Mockito.mock(DrmSession.class);
|
||||||
when(mockDrmSessionManager.acquireSession(
|
mockDrmSessionManager = new MockDrmSessionManager(mockDrmSession);
|
||||||
ArgumentMatchers.any(), ArgumentMatchers.any(), ArgumentMatchers.any()))
|
|
||||||
.thenReturn(mockDrmSession);
|
|
||||||
eventDispatcher = new DrmSessionEventListener.EventDispatcher();
|
eventDispatcher = new DrmSessionEventListener.EventDispatcher();
|
||||||
sampleQueue =
|
sampleQueue =
|
||||||
new SampleQueue(
|
new SampleQueue(
|
||||||
@ -399,7 +398,7 @@ public final class SampleQueueTest {
|
|||||||
@Test
|
@Test
|
||||||
public void isReadyReturnsTrueForValidDrmSession() {
|
public void isReadyReturnsTrueForValidDrmSession() {
|
||||||
writeTestDataWithEncryptedSections();
|
writeTestDataWithEncryptedSections();
|
||||||
assertReadFormat(/* formatRequired= */ false, FORMAT_ENCRYPTED);
|
assertReadFormat(/* formatRequired= */ false, FORMAT_ENCRYPTED_WITH_EXO_MEDIA_CRYPTO_TYPE);
|
||||||
assertThat(sampleQueue.isReady(/* loadingFinished= */ false)).isFalse();
|
assertThat(sampleQueue.isReady(/* loadingFinished= */ false)).isFalse();
|
||||||
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
|
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
|
||||||
assertThat(sampleQueue.isReady(/* loadingFinished= */ false)).isTrue();
|
assertThat(sampleQueue.isReady(/* loadingFinished= */ false)).isTrue();
|
||||||
@ -424,7 +423,7 @@ public final class SampleQueueTest {
|
|||||||
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED);
|
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED);
|
||||||
writeTestDataWithEncryptedSections();
|
writeTestDataWithEncryptedSections();
|
||||||
|
|
||||||
assertReadFormat(/* formatRequired= */ false, FORMAT_ENCRYPTED);
|
assertReadFormat(/* formatRequired= */ false, FORMAT_ENCRYPTED_WITH_EXO_MEDIA_CRYPTO_TYPE);
|
||||||
assertReadNothing(/* formatRequired= */ false);
|
assertReadNothing(/* formatRequired= */ false);
|
||||||
assertThat(inputBuffer.waitingForKeys).isTrue();
|
assertThat(inputBuffer.waitingForKeys).isTrue();
|
||||||
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
|
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
|
||||||
@ -464,9 +463,7 @@ public final class SampleQueueTest {
|
|||||||
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
|
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
|
||||||
DrmSession mockPlaceholderDrmSession = Mockito.mock(DrmSession.class);
|
DrmSession mockPlaceholderDrmSession = Mockito.mock(DrmSession.class);
|
||||||
when(mockPlaceholderDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
|
when(mockPlaceholderDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
|
||||||
when(mockDrmSessionManager.acquirePlaceholderSession(
|
mockDrmSessionManager.mockPlaceholderDrmSession = mockPlaceholderDrmSession;
|
||||||
ArgumentMatchers.any(), ArgumentMatchers.anyInt()))
|
|
||||||
.thenReturn(mockPlaceholderDrmSession);
|
|
||||||
writeTestDataWithEncryptedSections();
|
writeTestDataWithEncryptedSections();
|
||||||
|
|
||||||
int result =
|
int result =
|
||||||
@ -497,9 +494,7 @@ public final class SampleQueueTest {
|
|||||||
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
|
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
|
||||||
DrmSession mockPlaceholderDrmSession = Mockito.mock(DrmSession.class);
|
DrmSession mockPlaceholderDrmSession = Mockito.mock(DrmSession.class);
|
||||||
when(mockPlaceholderDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
|
when(mockPlaceholderDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED_WITH_KEYS);
|
||||||
when(mockDrmSessionManager.acquirePlaceholderSession(
|
mockDrmSessionManager.mockPlaceholderDrmSession = mockPlaceholderDrmSession;
|
||||||
ArgumentMatchers.any(), ArgumentMatchers.anyInt()))
|
|
||||||
.thenReturn(mockPlaceholderDrmSession);
|
|
||||||
|
|
||||||
writeFormat(ENCRYPTED_SAMPLE_FORMATS[0]);
|
writeFormat(ENCRYPTED_SAMPLE_FORMATS[0]);
|
||||||
byte[] sampleData = new byte[] {0, 1, 2};
|
byte[] sampleData = new byte[] {0, 1, 2};
|
||||||
@ -540,7 +535,7 @@ public final class SampleQueueTest {
|
|||||||
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED);
|
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED);
|
||||||
writeTestDataWithEncryptedSections();
|
writeTestDataWithEncryptedSections();
|
||||||
|
|
||||||
assertReadFormat(/* formatRequired= */ false, FORMAT_ENCRYPTED);
|
assertReadFormat(/* formatRequired= */ false, FORMAT_ENCRYPTED_WITH_EXO_MEDIA_CRYPTO_TYPE);
|
||||||
assertReadNothing(/* formatRequired= */ false);
|
assertReadNothing(/* formatRequired= */ false);
|
||||||
sampleQueue.maybeThrowError();
|
sampleQueue.maybeThrowError();
|
||||||
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_ERROR);
|
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_ERROR);
|
||||||
@ -569,7 +564,7 @@ public final class SampleQueueTest {
|
|||||||
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED);
|
when(mockDrmSession.getState()).thenReturn(DrmSession.STATE_OPENED);
|
||||||
writeTestDataWithEncryptedSections();
|
writeTestDataWithEncryptedSections();
|
||||||
|
|
||||||
assertReadFormat(/* formatRequired= */ false, FORMAT_ENCRYPTED);
|
assertReadFormat(/* formatRequired= */ false, FORMAT_ENCRYPTED_WITH_EXO_MEDIA_CRYPTO_TYPE);
|
||||||
assertReadEncryptedSample(/* sampleIndex= */ 0);
|
assertReadEncryptedSample(/* sampleIndex= */ 0);
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -1497,4 +1492,33 @@ public final class SampleQueueTest {
|
|||||||
private static Format copyWithLabel(Format format, String label) {
|
private static Format copyWithLabel(Format format, String label) {
|
||||||
return format.buildUpon().setLabel(label).build();
|
return format.buildUpon().setLabel(label).build();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private static final class MockExoMediaCrypto implements ExoMediaCrypto {}
|
||||||
|
|
||||||
|
private static final class MockDrmSessionManager implements DrmSessionManager {
|
||||||
|
|
||||||
|
private final DrmSession mockDrmSession;
|
||||||
|
@Nullable private DrmSession mockPlaceholderDrmSession;
|
||||||
|
|
||||||
|
private MockDrmSessionManager(DrmSession mockDrmSession) {
|
||||||
|
this.mockDrmSession = mockDrmSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public DrmSession acquireSession(
|
||||||
|
Looper playbackLooper,
|
||||||
|
@Nullable DrmSessionEventListener.EventDispatcher eventDispatcher,
|
||||||
|
Format format) {
|
||||||
|
return format.drmInitData != null ? mockDrmSession : mockPlaceholderDrmSession;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Nullable
|
||||||
|
@Override
|
||||||
|
public Class<? extends ExoMediaCrypto> getExoMediaCryptoType(Format format) {
|
||||||
|
return mockPlaceholderDrmSession != null || format.drmInitData != null
|
||||||
|
? MockExoMediaCrypto.class
|
||||||
|
: null;
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,6 @@ import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
|||||||
import com.google.android.exoplayer2.MediaItem;
|
import com.google.android.exoplayer2.MediaItem;
|
||||||
import com.google.android.exoplayer2.ParserException;
|
import com.google.android.exoplayer2.ParserException;
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.drm.DrmSession;
|
|
||||||
import com.google.android.exoplayer2.drm.DrmSessionEventListener;
|
import com.google.android.exoplayer2.drm.DrmSessionEventListener;
|
||||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||||
import com.google.android.exoplayer2.offline.FilteringManifestParser;
|
import com.google.android.exoplayer2.offline.FilteringManifestParser;
|
||||||
@ -42,6 +41,7 @@ import com.google.android.exoplayer2.source.LoadEventInfo;
|
|||||||
import com.google.android.exoplayer2.source.MediaLoadData;
|
import com.google.android.exoplayer2.source.MediaLoadData;
|
||||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
|
import com.google.android.exoplayer2.source.MediaSourceDrmHelper;
|
||||||
import com.google.android.exoplayer2.source.MediaSourceEventListener;
|
import com.google.android.exoplayer2.source.MediaSourceEventListener;
|
||||||
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
|
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
|
||||||
import com.google.android.exoplayer2.source.MediaSourceFactory;
|
import com.google.android.exoplayer2.source.MediaSourceFactory;
|
||||||
@ -54,6 +54,7 @@ import com.google.android.exoplayer2.source.dash.manifest.UtcTimingElement;
|
|||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
import com.google.android.exoplayer2.upstream.Allocator;
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
|
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
|
||||||
|
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
||||||
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
|
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
|
||||||
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy.LoadErrorInfo;
|
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy.LoadErrorInfo;
|
||||||
import com.google.android.exoplayer2.upstream.Loader;
|
import com.google.android.exoplayer2.upstream.Loader;
|
||||||
@ -91,9 +92,10 @@ public final class DashMediaSource extends BaseMediaSource {
|
|||||||
public static final class Factory implements MediaSourceFactory {
|
public static final class Factory implements MediaSourceFactory {
|
||||||
|
|
||||||
private final DashChunkSource.Factory chunkSourceFactory;
|
private final DashChunkSource.Factory chunkSourceFactory;
|
||||||
|
private final MediaSourceDrmHelper mediaSourceDrmHelper;
|
||||||
@Nullable private final DataSource.Factory manifestDataSourceFactory;
|
@Nullable private final DataSource.Factory manifestDataSourceFactory;
|
||||||
|
|
||||||
private DrmSessionManager drmSessionManager;
|
@Nullable private DrmSessionManager drmSessionManager;
|
||||||
private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
|
private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
|
||||||
private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
|
private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
|
||||||
private long livePresentationDelayMs;
|
private long livePresentationDelayMs;
|
||||||
@ -126,7 +128,7 @@ public final class DashMediaSource extends BaseMediaSource {
|
|||||||
@Nullable DataSource.Factory manifestDataSourceFactory) {
|
@Nullable DataSource.Factory manifestDataSourceFactory) {
|
||||||
this.chunkSourceFactory = checkNotNull(chunkSourceFactory);
|
this.chunkSourceFactory = checkNotNull(chunkSourceFactory);
|
||||||
this.manifestDataSourceFactory = manifestDataSourceFactory;
|
this.manifestDataSourceFactory = manifestDataSourceFactory;
|
||||||
drmSessionManager = DrmSessionManager.getDummyDrmSessionManager();
|
mediaSourceDrmHelper = new MediaSourceDrmHelper();
|
||||||
loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy();
|
loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy();
|
||||||
livePresentationDelayMs = DEFAULT_LIVE_PRESENTATION_DELAY_MS;
|
livePresentationDelayMs = DEFAULT_LIVE_PRESENTATION_DELAY_MS;
|
||||||
compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory();
|
compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory();
|
||||||
@ -155,19 +157,22 @@ public final class DashMediaSource extends BaseMediaSource {
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the {@link DrmSessionManager} to use for acquiring {@link DrmSession DrmSessions}. The
|
|
||||||
* default value is {@link DrmSessionManager#DUMMY}.
|
|
||||||
*
|
|
||||||
* @param drmSessionManager The {@link DrmSessionManager}.
|
|
||||||
* @return This factory, for convenience.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public Factory setDrmSessionManager(@Nullable DrmSessionManager drmSessionManager) {
|
public Factory setDrmSessionManager(@Nullable DrmSessionManager drmSessionManager) {
|
||||||
this.drmSessionManager =
|
this.drmSessionManager = drmSessionManager;
|
||||||
drmSessionManager != null
|
return this;
|
||||||
? drmSessionManager
|
}
|
||||||
: DrmSessionManager.getDummyDrmSessionManager();
|
|
||||||
|
@Override
|
||||||
|
public Factory setDrmHttpDataSourceFactory(
|
||||||
|
@Nullable HttpDataSource.Factory drmHttpDataSourceFactory) {
|
||||||
|
mediaSourceDrmHelper.setDrmHttpDataSourceFactory(drmHttpDataSourceFactory);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Factory setDrmUserAgent(@Nullable String userAgent) {
|
||||||
|
mediaSourceDrmHelper.setDrmUserAgent(userAgent);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -312,7 +317,7 @@ public final class DashMediaSource extends BaseMediaSource {
|
|||||||
/* manifestParser= */ null,
|
/* manifestParser= */ null,
|
||||||
chunkSourceFactory,
|
chunkSourceFactory,
|
||||||
compositeSequenceableLoaderFactory,
|
compositeSequenceableLoaderFactory,
|
||||||
drmSessionManager,
|
drmSessionManager != null ? drmSessionManager : mediaSourceDrmHelper.create(mediaItem),
|
||||||
loadErrorHandlingPolicy,
|
loadErrorHandlingPolicy,
|
||||||
livePresentationDelayMs,
|
livePresentationDelayMs,
|
||||||
livePresentationDelayOverridesManifest);
|
livePresentationDelayOverridesManifest);
|
||||||
@ -403,7 +408,7 @@ public final class DashMediaSource extends BaseMediaSource {
|
|||||||
manifestParser,
|
manifestParser,
|
||||||
chunkSourceFactory,
|
chunkSourceFactory,
|
||||||
compositeSequenceableLoaderFactory,
|
compositeSequenceableLoaderFactory,
|
||||||
drmSessionManager,
|
drmSessionManager != null ? drmSessionManager : mediaSourceDrmHelper.create(mediaItem),
|
||||||
loadErrorHandlingPolicy,
|
loadErrorHandlingPolicy,
|
||||||
livePresentationDelayMs,
|
livePresentationDelayMs,
|
||||||
livePresentationDelayOverridesManifest);
|
livePresentationDelayOverridesManifest);
|
||||||
|
@ -20,10 +20,34 @@ package com.google.android.exoplayer2.extractor;
|
|||||||
*/
|
*/
|
||||||
public interface ExtractorOutput {
|
public interface ExtractorOutput {
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Placeholder {@link ExtractorOutput} implementation throwing an {@link
|
||||||
|
* UnsupportedOperationException} in each method.
|
||||||
|
*/
|
||||||
|
ExtractorOutput PLACEHOLDER =
|
||||||
|
new ExtractorOutput() {
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public TrackOutput track(int id, int type) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void endTracks() {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void seekMap(SeekMap seekMap) {
|
||||||
|
throw new UnsupportedOperationException();
|
||||||
|
}
|
||||||
|
};
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Called by the {@link Extractor} to get the {@link TrackOutput} for a specific track.
|
* Called by the {@link Extractor} to get the {@link TrackOutput} for a specific track.
|
||||||
* <p>
|
*
|
||||||
* The same {@link TrackOutput} is returned if multiple calls are made with the same {@code id}.
|
* <p>The same {@link TrackOutput} is returned if multiple calls are made with the same {@code
|
||||||
|
* id}.
|
||||||
*
|
*
|
||||||
* @param id A track identifier.
|
* @param id A track identifier.
|
||||||
* @param type The type of the track. Typically one of the {@link com.google.android.exoplayer2.C}
|
* @param type The type of the track. Typically one of the {@link com.google.android.exoplayer2.C}
|
||||||
|
@ -48,6 +48,7 @@ import com.google.android.exoplayer2.util.ParsableByteArray;
|
|||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import com.google.android.exoplayer2.video.AvcConfig;
|
import com.google.android.exoplayer2.video.AvcConfig;
|
||||||
import com.google.android.exoplayer2.video.ColorInfo;
|
import com.google.android.exoplayer2.video.ColorInfo;
|
||||||
|
import com.google.android.exoplayer2.video.DolbyVisionConfig;
|
||||||
import com.google.android.exoplayer2.video.HevcConfig;
|
import com.google.android.exoplayer2.video.HevcConfig;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
import java.lang.annotation.Documented;
|
import java.lang.annotation.Documented;
|
||||||
@ -170,6 +171,9 @@ public class MatroskaExtractor implements Extractor {
|
|||||||
private static final int ID_FLAG_FORCED = 0x55AA;
|
private static final int ID_FLAG_FORCED = 0x55AA;
|
||||||
private static final int ID_DEFAULT_DURATION = 0x23E383;
|
private static final int ID_DEFAULT_DURATION = 0x23E383;
|
||||||
private static final int ID_MAX_BLOCK_ADDITION_ID = 0x55EE;
|
private static final int ID_MAX_BLOCK_ADDITION_ID = 0x55EE;
|
||||||
|
private static final int ID_BLOCK_ADDITION_MAPPING = 0x41E4;
|
||||||
|
private static final int ID_BLOCK_ADD_ID_TYPE = 0x41E7;
|
||||||
|
private static final int ID_BLOCK_ADD_ID_EXTRA_DATA = 0x41ED;
|
||||||
private static final int ID_NAME = 0x536E;
|
private static final int ID_NAME = 0x536E;
|
||||||
private static final int ID_CODEC_ID = 0x86;
|
private static final int ID_CODEC_ID = 0x86;
|
||||||
private static final int ID_CODEC_PRIVATE = 0x63A2;
|
private static final int ID_CODEC_PRIVATE = 0x63A2;
|
||||||
@ -234,6 +238,17 @@ public class MatroskaExtractor implements Extractor {
|
|||||||
*/
|
*/
|
||||||
private static final int BLOCK_ADDITIONAL_ID_VP9_ITU_T_35 = 4;
|
private static final int BLOCK_ADDITIONAL_ID_VP9_ITU_T_35 = 4;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* BlockAddIdType value for Dolby Vision configuration with profile <= 7. See also
|
||||||
|
* https://www.matroska.org/technical/codec_specs.html.
|
||||||
|
*/
|
||||||
|
private static final int BLOCK_ADD_ID_TYPE_DVCC = 0x64766343;
|
||||||
|
/**
|
||||||
|
* BlockAddIdType value for Dolby Vision configuration with profile > 7. See also
|
||||||
|
* https://www.matroska.org/technical/codec_specs.html.
|
||||||
|
*/
|
||||||
|
private static final int BLOCK_ADD_ID_TYPE_DVVC = 0x64767643;
|
||||||
|
|
||||||
private static final int LACING_NONE = 0;
|
private static final int LACING_NONE = 0;
|
||||||
private static final int LACING_XIPH = 1;
|
private static final int LACING_XIPH = 1;
|
||||||
private static final int LACING_FIXED_SIZE = 2;
|
private static final int LACING_FIXED_SIZE = 2;
|
||||||
@ -501,6 +516,7 @@ public class MatroskaExtractor implements Extractor {
|
|||||||
case ID_CLUSTER:
|
case ID_CLUSTER:
|
||||||
case ID_TRACKS:
|
case ID_TRACKS:
|
||||||
case ID_TRACK_ENTRY:
|
case ID_TRACK_ENTRY:
|
||||||
|
case ID_BLOCK_ADDITION_MAPPING:
|
||||||
case ID_AUDIO:
|
case ID_AUDIO:
|
||||||
case ID_VIDEO:
|
case ID_VIDEO:
|
||||||
case ID_CONTENT_ENCODINGS:
|
case ID_CONTENT_ENCODINGS:
|
||||||
@ -535,6 +551,7 @@ public class MatroskaExtractor implements Extractor {
|
|||||||
case ID_FLAG_FORCED:
|
case ID_FLAG_FORCED:
|
||||||
case ID_DEFAULT_DURATION:
|
case ID_DEFAULT_DURATION:
|
||||||
case ID_MAX_BLOCK_ADDITION_ID:
|
case ID_MAX_BLOCK_ADDITION_ID:
|
||||||
|
case ID_BLOCK_ADD_ID_TYPE:
|
||||||
case ID_CODEC_DELAY:
|
case ID_CODEC_DELAY:
|
||||||
case ID_SEEK_PRE_ROLL:
|
case ID_SEEK_PRE_ROLL:
|
||||||
case ID_CHANNELS:
|
case ID_CHANNELS:
|
||||||
@ -562,6 +579,7 @@ public class MatroskaExtractor implements Extractor {
|
|||||||
case ID_LANGUAGE:
|
case ID_LANGUAGE:
|
||||||
return EbmlProcessor.ELEMENT_TYPE_STRING;
|
return EbmlProcessor.ELEMENT_TYPE_STRING;
|
||||||
case ID_SEEK_ID:
|
case ID_SEEK_ID:
|
||||||
|
case ID_BLOCK_ADD_ID_EXTRA_DATA:
|
||||||
case ID_CONTENT_COMPRESSION_SETTINGS:
|
case ID_CONTENT_COMPRESSION_SETTINGS:
|
||||||
case ID_CONTENT_ENCRYPTION_KEY_ID:
|
case ID_CONTENT_ENCRYPTION_KEY_ID:
|
||||||
case ID_SIMPLE_BLOCK:
|
case ID_SIMPLE_BLOCK:
|
||||||
@ -814,6 +832,9 @@ public class MatroskaExtractor implements Extractor {
|
|||||||
case ID_MAX_BLOCK_ADDITION_ID:
|
case ID_MAX_BLOCK_ADDITION_ID:
|
||||||
currentTrack.maxBlockAdditionId = (int) value;
|
currentTrack.maxBlockAdditionId = (int) value;
|
||||||
break;
|
break;
|
||||||
|
case ID_BLOCK_ADD_ID_TYPE:
|
||||||
|
currentTrack.blockAddIdType = (int) value;
|
||||||
|
break;
|
||||||
case ID_CODEC_DELAY:
|
case ID_CODEC_DELAY:
|
||||||
currentTrack.codecDelayNs = value;
|
currentTrack.codecDelayNs = value;
|
||||||
break;
|
break;
|
||||||
@ -1076,6 +1097,9 @@ public class MatroskaExtractor implements Extractor {
|
|||||||
seekEntryIdBytes.setPosition(0);
|
seekEntryIdBytes.setPosition(0);
|
||||||
seekEntryId = (int) seekEntryIdBytes.readUnsignedInt();
|
seekEntryId = (int) seekEntryIdBytes.readUnsignedInt();
|
||||||
break;
|
break;
|
||||||
|
case ID_BLOCK_ADD_ID_EXTRA_DATA:
|
||||||
|
handleBlockAddIDExtraData(currentTrack, input, contentSize);
|
||||||
|
break;
|
||||||
case ID_CODEC_PRIVATE:
|
case ID_CODEC_PRIVATE:
|
||||||
currentTrack.codecPrivate = new byte[contentSize];
|
currentTrack.codecPrivate = new byte[contentSize];
|
||||||
input.readFully(currentTrack.codecPrivate, 0, contentSize);
|
input.readFully(currentTrack.codecPrivate, 0, contentSize);
|
||||||
@ -1244,6 +1268,18 @@ public class MatroskaExtractor implements Extractor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
protected void handleBlockAddIDExtraData(Track track, ExtractorInput input, int contentSize)
|
||||||
|
throws IOException {
|
||||||
|
if (track.blockAddIdType == BLOCK_ADD_ID_TYPE_DVVC
|
||||||
|
|| track.blockAddIdType == BLOCK_ADD_ID_TYPE_DVCC) {
|
||||||
|
track.dolbyVisionConfigBytes = new byte[contentSize];
|
||||||
|
input.readFully(track.dolbyVisionConfigBytes, 0, contentSize);
|
||||||
|
} else {
|
||||||
|
// Unhandled BlockAddIDExtraData.
|
||||||
|
input.skipFully(contentSize);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
protected void handleBlockAdditionalData(
|
protected void handleBlockAdditionalData(
|
||||||
Track track, int blockAdditionalId, ExtractorInput input, int contentSize)
|
Track track, int blockAdditionalId, ExtractorInput input, int contentSize)
|
||||||
throws IOException {
|
throws IOException {
|
||||||
@ -1883,6 +1919,7 @@ public class MatroskaExtractor implements Extractor {
|
|||||||
public int type;
|
public int type;
|
||||||
public int defaultSampleDurationNs;
|
public int defaultSampleDurationNs;
|
||||||
public int maxBlockAdditionId;
|
public int maxBlockAdditionId;
|
||||||
|
private int blockAddIdType;
|
||||||
public boolean hasContentEncryption;
|
public boolean hasContentEncryption;
|
||||||
public byte[] sampleStrippedBytes;
|
public byte[] sampleStrippedBytes;
|
||||||
public TrackOutput.CryptoData cryptoData;
|
public TrackOutput.CryptoData cryptoData;
|
||||||
@ -1921,6 +1958,7 @@ public class MatroskaExtractor implements Extractor {
|
|||||||
public float whitePointChromaticityY = Format.NO_VALUE;
|
public float whitePointChromaticityY = Format.NO_VALUE;
|
||||||
public float maxMasteringLuminance = Format.NO_VALUE;
|
public float maxMasteringLuminance = Format.NO_VALUE;
|
||||||
public float minMasteringLuminance = Format.NO_VALUE;
|
public float minMasteringLuminance = Format.NO_VALUE;
|
||||||
|
@Nullable public byte[] dolbyVisionConfigBytes;
|
||||||
|
|
||||||
// Audio elements. Initially set to their default values.
|
// Audio elements. Initially set to their default values.
|
||||||
public int channelCount = 1;
|
public int channelCount = 1;
|
||||||
@ -2091,6 +2129,16 @@ public class MatroskaExtractor implements Extractor {
|
|||||||
throw new ParserException("Unrecognized codec identifier.");
|
throw new ParserException("Unrecognized codec identifier.");
|
||||||
}
|
}
|
||||||
|
|
||||||
|
if (dolbyVisionConfigBytes != null) {
|
||||||
|
@Nullable
|
||||||
|
DolbyVisionConfig dolbyVisionConfig =
|
||||||
|
DolbyVisionConfig.parse(new ParsableByteArray(this.dolbyVisionConfigBytes));
|
||||||
|
if (dolbyVisionConfig != null) {
|
||||||
|
codecs = dolbyVisionConfig.codecs;
|
||||||
|
mimeType = MimeTypes.VIDEO_DOLBY_VISION;
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@C.SelectionFlags int selectionFlags = 0;
|
@C.SelectionFlags int selectionFlags = 0;
|
||||||
selectionFlags |= flagDefault ? C.SELECTION_FLAG_DEFAULT : 0;
|
selectionFlags |= flagDefault ? C.SELECTION_FLAG_DEFAULT : 0;
|
||||||
selectionFlags |= flagForced ? C.SELECTION_FLAG_FORCED : 0;
|
selectionFlags |= flagForced ? C.SELECTION_FLAG_FORCED : 0;
|
||||||
|
@ -16,6 +16,10 @@
|
|||||||
package com.google.android.exoplayer2.extractor.mp4;
|
package com.google.android.exoplayer2.extractor.mp4;
|
||||||
|
|
||||||
import static com.google.android.exoplayer2.extractor.mp4.AtomParsers.parseTraks;
|
import static com.google.android.exoplayer2.extractor.mp4.AtomParsers.parseTraks;
|
||||||
|
import static com.google.android.exoplayer2.util.Assertions.checkNotNull;
|
||||||
|
import static com.google.android.exoplayer2.util.Assertions.checkState;
|
||||||
|
import static com.google.android.exoplayer2.util.Util.castNonNull;
|
||||||
|
import static com.google.android.exoplayer2.util.Util.nullSafeArrayCopy;
|
||||||
import static java.lang.Math.max;
|
import static java.lang.Math.max;
|
||||||
|
|
||||||
import android.util.Pair;
|
import android.util.Pair;
|
||||||
@ -42,7 +46,6 @@ import com.google.android.exoplayer2.extractor.mp4.Atom.ContainerAtom;
|
|||||||
import com.google.android.exoplayer2.extractor.mp4.Atom.LeafAtom;
|
import com.google.android.exoplayer2.extractor.mp4.Atom.LeafAtom;
|
||||||
import com.google.android.exoplayer2.metadata.emsg.EventMessage;
|
import com.google.android.exoplayer2.metadata.emsg.EventMessage;
|
||||||
import com.google.android.exoplayer2.metadata.emsg.EventMessageEncoder;
|
import com.google.android.exoplayer2.metadata.emsg.EventMessageEncoder;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
|
||||||
import com.google.android.exoplayer2.util.Log;
|
import com.google.android.exoplayer2.util.Log;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
import com.google.android.exoplayer2.util.NalUnitUtil;
|
import com.google.android.exoplayer2.util.NalUnitUtil;
|
||||||
@ -59,7 +62,6 @@ import java.util.Arrays;
|
|||||||
import java.util.Collections;
|
import java.util.Collections;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import java.util.UUID;
|
import java.util.UUID;
|
||||||
import org.checkerframework.checker.nullness.qual.MonotonicNonNull;
|
|
||||||
|
|
||||||
/** Extracts data from the FMP4 container format. */
|
/** Extracts data from the FMP4 container format. */
|
||||||
@SuppressWarnings("ConstantField")
|
@SuppressWarnings("ConstantField")
|
||||||
@ -175,7 +177,7 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
private boolean processSeiNalUnitPayload;
|
private boolean processSeiNalUnitPayload;
|
||||||
|
|
||||||
// Outputs.
|
// Outputs.
|
||||||
private @MonotonicNonNull ExtractorOutput extractorOutput;
|
private ExtractorOutput extractorOutput;
|
||||||
private TrackOutput[] emsgTrackOutputs;
|
private TrackOutput[] emsgTrackOutputs;
|
||||||
private TrackOutput[] ceaTrackOutputs;
|
private TrackOutput[] ceaTrackOutputs;
|
||||||
|
|
||||||
@ -270,9 +272,9 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
durationUs = C.TIME_UNSET;
|
durationUs = C.TIME_UNSET;
|
||||||
pendingSeekTimeUs = C.TIME_UNSET;
|
pendingSeekTimeUs = C.TIME_UNSET;
|
||||||
segmentIndexEarliestPresentationTimeUs = C.TIME_UNSET;
|
segmentIndexEarliestPresentationTimeUs = C.TIME_UNSET;
|
||||||
|
extractorOutput = ExtractorOutput.PLACEHOLDER;
|
||||||
emsgTrackOutputs = new TrackOutput[0];
|
emsgTrackOutputs = new TrackOutput[0];
|
||||||
ceaTrackOutputs = new TrackOutput[0];
|
ceaTrackOutputs = new TrackOutput[0];
|
||||||
enterReadingAtomHeaderState();
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -283,6 +285,7 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
@Override
|
@Override
|
||||||
public void init(ExtractorOutput output) {
|
public void init(ExtractorOutput output) {
|
||||||
extractorOutput = output;
|
extractorOutput = output;
|
||||||
|
enterReadingAtomHeaderState();
|
||||||
initExtraTracks();
|
initExtraTracks();
|
||||||
if (sideloadedTrack != null) {
|
if (sideloadedTrack != null) {
|
||||||
TrackBundle bundle =
|
TrackBundle bundle =
|
||||||
@ -429,8 +432,9 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
if (atomSize > Integer.MAX_VALUE) {
|
if (atomSize > Integer.MAX_VALUE) {
|
||||||
throw new ParserException("Leaf atom with length > 2147483647 (unsupported).");
|
throw new ParserException("Leaf atom with length > 2147483647 (unsupported).");
|
||||||
}
|
}
|
||||||
atomData = new ParsableByteArray((int) atomSize);
|
ParsableByteArray atomData = new ParsableByteArray((int) atomSize);
|
||||||
System.arraycopy(atomHeader.getData(), 0, atomData.getData(), 0, Atom.HEADER_SIZE);
|
System.arraycopy(atomHeader.getData(), 0, atomData.getData(), 0, Atom.HEADER_SIZE);
|
||||||
|
this.atomData = atomData;
|
||||||
parserState = STATE_READING_ATOM_PAYLOAD;
|
parserState = STATE_READING_ATOM_PAYLOAD;
|
||||||
} else {
|
} else {
|
||||||
if (atomSize > Integer.MAX_VALUE) {
|
if (atomSize > Integer.MAX_VALUE) {
|
||||||
@ -445,6 +449,7 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
|
|
||||||
private void readAtomPayload(ExtractorInput input) throws IOException {
|
private void readAtomPayload(ExtractorInput input) throws IOException {
|
||||||
int atomPayloadSize = (int) atomSize - atomHeaderBytesRead;
|
int atomPayloadSize = (int) atomSize - atomHeaderBytesRead;
|
||||||
|
@Nullable ParsableByteArray atomData = this.atomData;
|
||||||
if (atomData != null) {
|
if (atomData != null) {
|
||||||
input.readFully(atomData.getData(), Atom.HEADER_SIZE, atomPayloadSize);
|
input.readFully(atomData.getData(), Atom.HEADER_SIZE, atomPayloadSize);
|
||||||
onLeafAtomRead(new LeafAtom(atomType, atomData), input.getPosition());
|
onLeafAtomRead(new LeafAtom(atomType, atomData), input.getPosition());
|
||||||
@ -485,12 +490,12 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void onMoovContainerAtomRead(ContainerAtom moov) throws ParserException {
|
private void onMoovContainerAtomRead(ContainerAtom moov) throws ParserException {
|
||||||
Assertions.checkState(sideloadedTrack == null, "Unexpected moov box.");
|
checkState(sideloadedTrack == null, "Unexpected moov box.");
|
||||||
|
|
||||||
@Nullable DrmInitData drmInitData = getDrmInitDataFromAtoms(moov.leafChildren);
|
@Nullable DrmInitData drmInitData = getDrmInitDataFromAtoms(moov.leafChildren);
|
||||||
|
|
||||||
// Read declaration of track fragments in the Moov box.
|
// Read declaration of track fragments in the moov box.
|
||||||
ContainerAtom mvex = moov.getContainerAtomOfType(Atom.TYPE_mvex);
|
ContainerAtom mvex = checkNotNull(moov.getContainerAtomOfType(Atom.TYPE_mvex));
|
||||||
SparseArray<DefaultSampleValues> defaultSampleValuesArray = new SparseArray<>();
|
SparseArray<DefaultSampleValues> defaultSampleValuesArray = new SparseArray<>();
|
||||||
long duration = C.TIME_UNSET;
|
long duration = C.TIME_UNSET;
|
||||||
int mvexChildrenSize = mvex.leafChildren.size();
|
int mvexChildrenSize = mvex.leafChildren.size();
|
||||||
@ -531,7 +536,7 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
}
|
}
|
||||||
extractorOutput.endTracks();
|
extractorOutput.endTracks();
|
||||||
} else {
|
} else {
|
||||||
Assertions.checkState(trackBundles.size() == trackCount);
|
checkState(trackBundles.size() == trackCount);
|
||||||
for (int i = 0; i < trackCount; i++) {
|
for (int i = 0; i < trackCount; i++) {
|
||||||
TrackSampleTable sampleTable = sampleTables.get(i);
|
TrackSampleTable sampleTable = sampleTables.get(i);
|
||||||
Track track = sampleTable.track;
|
Track track = sampleTable.track;
|
||||||
@ -554,7 +559,7 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
// See https://github.com/google/ExoPlayer/issues/4477.
|
// See https://github.com/google/ExoPlayer/issues/4477.
|
||||||
return defaultSampleValuesArray.valueAt(/* index= */ 0);
|
return defaultSampleValuesArray.valueAt(/* index= */ 0);
|
||||||
}
|
}
|
||||||
return Assertions.checkNotNull(defaultSampleValuesArray.get(trackId));
|
return checkNotNull(defaultSampleValuesArray.get(trackId));
|
||||||
}
|
}
|
||||||
|
|
||||||
private void onMoofContainerAtomRead(ContainerAtom moof) throws ParserException {
|
private void onMoofContainerAtomRead(ContainerAtom moof) throws ParserException {
|
||||||
@ -589,7 +594,7 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
emsgTrackOutputs[emsgTrackOutputCount++] =
|
emsgTrackOutputs[emsgTrackOutputCount++] =
|
||||||
extractorOutput.track(nextExtraTrackId++, C.TRACK_TYPE_METADATA);
|
extractorOutput.track(nextExtraTrackId++, C.TRACK_TYPE_METADATA);
|
||||||
}
|
}
|
||||||
emsgTrackOutputs = Arrays.copyOf(emsgTrackOutputs, emsgTrackOutputCount);
|
emsgTrackOutputs = nullSafeArrayCopy(emsgTrackOutputs, emsgTrackOutputCount);
|
||||||
for (TrackOutput eventMessageTrackOutput : emsgTrackOutputs) {
|
for (TrackOutput eventMessageTrackOutput : emsgTrackOutputs) {
|
||||||
eventMessageTrackOutput.format(EMSG_FORMAT);
|
eventMessageTrackOutput.format(EMSG_FORMAT);
|
||||||
}
|
}
|
||||||
@ -604,7 +609,7 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
|
|
||||||
/** Handles an emsg atom (defined in 23009-1). */
|
/** Handles an emsg atom (defined in 23009-1). */
|
||||||
private void onEmsgLeafAtomRead(ParsableByteArray atom) {
|
private void onEmsgLeafAtomRead(ParsableByteArray atom) {
|
||||||
if (emsgTrackOutputs == null || emsgTrackOutputs.length == 0) {
|
if (emsgTrackOutputs.length == 0) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
atom.setPosition(Atom.HEADER_SIZE);
|
atom.setPosition(Atom.HEADER_SIZE);
|
||||||
@ -619,8 +624,8 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
long id;
|
long id;
|
||||||
switch (version) {
|
switch (version) {
|
||||||
case 0:
|
case 0:
|
||||||
schemeIdUri = Assertions.checkNotNull(atom.readNullTerminatedString());
|
schemeIdUri = checkNotNull(atom.readNullTerminatedString());
|
||||||
value = Assertions.checkNotNull(atom.readNullTerminatedString());
|
value = checkNotNull(atom.readNullTerminatedString());
|
||||||
timescale = atom.readUnsignedInt();
|
timescale = atom.readUnsignedInt();
|
||||||
presentationTimeDeltaUs =
|
presentationTimeDeltaUs =
|
||||||
Util.scaleLargeTimestamp(atom.readUnsignedInt(), C.MICROS_PER_SECOND, timescale);
|
Util.scaleLargeTimestamp(atom.readUnsignedInt(), C.MICROS_PER_SECOND, timescale);
|
||||||
@ -638,8 +643,8 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
durationMs =
|
durationMs =
|
||||||
Util.scaleLargeTimestamp(atom.readUnsignedInt(), C.MILLIS_PER_SECOND, timescale);
|
Util.scaleLargeTimestamp(atom.readUnsignedInt(), C.MILLIS_PER_SECOND, timescale);
|
||||||
id = atom.readUnsignedInt();
|
id = atom.readUnsignedInt();
|
||||||
schemeIdUri = Assertions.checkNotNull(atom.readNullTerminatedString());
|
schemeIdUri = checkNotNull(atom.readNullTerminatedString());
|
||||||
value = Assertions.checkNotNull(atom.readNullTerminatedString());
|
value = checkNotNull(atom.readNullTerminatedString());
|
||||||
break;
|
break;
|
||||||
default:
|
default:
|
||||||
Log.w(TAG, "Skipping unsupported emsg version: " + version);
|
Log.w(TAG, "Skipping unsupported emsg version: " + version);
|
||||||
@ -717,7 +722,7 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
*/
|
*/
|
||||||
private static void parseTraf(ContainerAtom traf, SparseArray<TrackBundle> trackBundleArray,
|
private static void parseTraf(ContainerAtom traf, SparseArray<TrackBundle> trackBundleArray,
|
||||||
@Flags int flags, byte[] extendedTypeScratch) throws ParserException {
|
@Flags int flags, byte[] extendedTypeScratch) throws ParserException {
|
||||||
LeafAtom tfhd = traf.getLeafAtomOfType(Atom.TYPE_tfhd);
|
LeafAtom tfhd = checkNotNull(traf.getLeafAtomOfType(Atom.TYPE_tfhd));
|
||||||
@Nullable TrackBundle trackBundle = parseTfhd(tfhd.data, trackBundleArray);
|
@Nullable TrackBundle trackBundle = parseTfhd(tfhd.data, trackBundleArray);
|
||||||
if (trackBundle == null) {
|
if (trackBundle == null) {
|
||||||
return;
|
return;
|
||||||
@ -730,7 +735,7 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
trackBundle.currentlyInFragment = true;
|
trackBundle.currentlyInFragment = true;
|
||||||
@Nullable LeafAtom tfdtAtom = traf.getLeafAtomOfType(Atom.TYPE_tfdt);
|
@Nullable LeafAtom tfdtAtom = traf.getLeafAtomOfType(Atom.TYPE_tfdt);
|
||||||
if (tfdtAtom != null && (flags & FLAG_WORKAROUND_IGNORE_TFDT_BOX) == 0) {
|
if (tfdtAtom != null && (flags & FLAG_WORKAROUND_IGNORE_TFDT_BOX) == 0) {
|
||||||
fragment.nextFragmentDecodeTime = parseTfdt(traf.getLeafAtomOfType(Atom.TYPE_tfdt).data);
|
fragment.nextFragmentDecodeTime = parseTfdt(tfdtAtom.data);
|
||||||
fragment.nextFragmentDecodeTimeIncludesMoov = true;
|
fragment.nextFragmentDecodeTimeIncludesMoov = true;
|
||||||
} else {
|
} else {
|
||||||
fragment.nextFragmentDecodeTime = fragmentDecodeTime;
|
fragment.nextFragmentDecodeTime = fragmentDecodeTime;
|
||||||
@ -742,11 +747,11 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
@Nullable
|
@Nullable
|
||||||
TrackEncryptionBox encryptionBox =
|
TrackEncryptionBox encryptionBox =
|
||||||
trackBundle.moovSampleTable.track.getSampleDescriptionEncryptionBox(
|
trackBundle.moovSampleTable.track.getSampleDescriptionEncryptionBox(
|
||||||
fragment.header.sampleDescriptionIndex);
|
checkNotNull(fragment.header).sampleDescriptionIndex);
|
||||||
|
|
||||||
@Nullable LeafAtom saiz = traf.getLeafAtomOfType(Atom.TYPE_saiz);
|
@Nullable LeafAtom saiz = traf.getLeafAtomOfType(Atom.TYPE_saiz);
|
||||||
if (saiz != null) {
|
if (saiz != null) {
|
||||||
parseSaiz(encryptionBox, saiz.data, fragment);
|
parseSaiz(checkNotNull(encryptionBox), saiz.data, fragment);
|
||||||
}
|
}
|
||||||
|
|
||||||
@Nullable LeafAtom saio = traf.getLeafAtomOfType(Atom.TYPE_saio);
|
@Nullable LeafAtom saio = traf.getLeafAtomOfType(Atom.TYPE_saio);
|
||||||
@ -964,7 +969,7 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
|
|
||||||
Track track = trackBundle.moovSampleTable.track;
|
Track track = trackBundle.moovSampleTable.track;
|
||||||
TrackFragment fragment = trackBundle.fragment;
|
TrackFragment fragment = trackBundle.fragment;
|
||||||
DefaultSampleValues defaultSampleValues = fragment.header;
|
DefaultSampleValues defaultSampleValues = castNonNull(fragment.header);
|
||||||
|
|
||||||
fragment.trunLength[index] = trun.readUnsignedIntToInt();
|
fragment.trunLength[index] = trun.readUnsignedIntToInt();
|
||||||
fragment.trunDataPosition[index] = fragment.dataPosition;
|
fragment.trunDataPosition[index] = fragment.dataPosition;
|
||||||
@ -994,7 +999,7 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
&& track.editListDurations[0] == 0) {
|
&& track.editListDurations[0] == 0) {
|
||||||
edtsOffsetUs =
|
edtsOffsetUs =
|
||||||
Util.scaleLargeTimestamp(
|
Util.scaleLargeTimestamp(
|
||||||
track.editListMediaTimes[0], C.MICROS_PER_SECOND, track.timescale);
|
castNonNull(track.editListMediaTimes)[0], C.MICROS_PER_SECOND, track.timescale);
|
||||||
}
|
}
|
||||||
|
|
||||||
int[] sampleSizeTable = fragment.sampleSizeTable;
|
int[] sampleSizeTable = fragment.sampleSizeTable;
|
||||||
@ -1161,7 +1166,7 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
int perSampleIvSize = sgpd.readUnsignedByte();
|
int perSampleIvSize = sgpd.readUnsignedByte();
|
||||||
byte[] keyId = new byte[16];
|
byte[] keyId = new byte[16];
|
||||||
sgpd.readBytes(keyId, 0, keyId.length);
|
sgpd.readBytes(keyId, 0, keyId.length);
|
||||||
byte[] constantIv = null;
|
@Nullable byte[] constantIv = null;
|
||||||
if (perSampleIvSize == 0) {
|
if (perSampleIvSize == 0) {
|
||||||
int constantIvSize = sgpd.readUnsignedByte();
|
int constantIvSize = sgpd.readUnsignedByte();
|
||||||
constantIv = new byte[constantIvSize];
|
constantIv = new byte[constantIvSize];
|
||||||
@ -1238,7 +1243,7 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private void readEncryptionData(ExtractorInput input) throws IOException {
|
private void readEncryptionData(ExtractorInput input) throws IOException {
|
||||||
TrackBundle nextTrackBundle = null;
|
@Nullable TrackBundle nextTrackBundle = null;
|
||||||
long nextDataOffset = Long.MAX_VALUE;
|
long nextDataOffset = Long.MAX_VALUE;
|
||||||
int trackBundlesSize = trackBundles.size();
|
int trackBundlesSize = trackBundles.size();
|
||||||
for (int i = 0; i < trackBundlesSize; i++) {
|
for (int i = 0; i < trackBundlesSize; i++) {
|
||||||
@ -1277,10 +1282,10 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
* @throws IOException If an error occurs reading from the input.
|
* @throws IOException If an error occurs reading from the input.
|
||||||
*/
|
*/
|
||||||
private boolean readSample(ExtractorInput input) throws IOException {
|
private boolean readSample(ExtractorInput input) throws IOException {
|
||||||
if (parserState == STATE_READING_SAMPLE_START) {
|
@Nullable TrackBundle trackBundle = currentTrackBundle;
|
||||||
if (currentTrackBundle == null) {
|
if (trackBundle == null) {
|
||||||
@Nullable TrackBundle currentTrackBundle = getNextTrackBundle(trackBundles);
|
trackBundle = getNextTrackBundle(trackBundles);
|
||||||
if (currentTrackBundle == null) {
|
if (trackBundle == null) {
|
||||||
// We've run out of samples in the current mdat. Discard any trailing data and prepare to
|
// We've run out of samples in the current mdat. Discard any trailing data and prepare to
|
||||||
// read the header of the next atom.
|
// read the header of the next atom.
|
||||||
int bytesToSkip = (int) (endOfMdatPosition - input.getPosition());
|
int bytesToSkip = (int) (endOfMdatPosition - input.getPosition());
|
||||||
@ -1292,7 +1297,7 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
return false;
|
return false;
|
||||||
}
|
}
|
||||||
|
|
||||||
long nextDataPosition = currentTrackBundle.getCurrentSampleOffset();
|
long nextDataPosition = trackBundle.getCurrentSampleOffset();
|
||||||
// We skip bytes preceding the next sample to read.
|
// We skip bytes preceding the next sample to read.
|
||||||
int bytesToSkip = (int) (nextDataPosition - input.getPosition());
|
int bytesToSkip = (int) (nextDataPosition - input.getPosition());
|
||||||
if (bytesToSkip < 0) {
|
if (bytesToSkip < 0) {
|
||||||
@ -1301,47 +1306,46 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
bytesToSkip = 0;
|
bytesToSkip = 0;
|
||||||
}
|
}
|
||||||
input.skipFully(bytesToSkip);
|
input.skipFully(bytesToSkip);
|
||||||
this.currentTrackBundle = currentTrackBundle;
|
currentTrackBundle = trackBundle;
|
||||||
}
|
}
|
||||||
|
if (parserState == STATE_READING_SAMPLE_START) {
|
||||||
|
sampleSize = trackBundle.getCurrentSampleSize();
|
||||||
|
|
||||||
sampleSize = currentTrackBundle.getCurrentSampleSize();
|
if (trackBundle.currentSampleIndex < trackBundle.firstSampleToOutputIndex) {
|
||||||
|
|
||||||
if (currentTrackBundle.currentSampleIndex < currentTrackBundle.firstSampleToOutputIndex) {
|
|
||||||
input.skipFully(sampleSize);
|
input.skipFully(sampleSize);
|
||||||
currentTrackBundle.skipSampleEncryptionData();
|
trackBundle.skipSampleEncryptionData();
|
||||||
if (!currentTrackBundle.next()) {
|
if (!trackBundle.next()) {
|
||||||
currentTrackBundle = null;
|
currentTrackBundle = null;
|
||||||
}
|
}
|
||||||
parserState = STATE_READING_SAMPLE_START;
|
parserState = STATE_READING_SAMPLE_START;
|
||||||
return true;
|
return true;
|
||||||
}
|
}
|
||||||
|
|
||||||
if (currentTrackBundle.moovSampleTable.track.sampleTransformation
|
if (trackBundle.moovSampleTable.track.sampleTransformation
|
||||||
== Track.TRANSFORMATION_CEA608_CDAT) {
|
== Track.TRANSFORMATION_CEA608_CDAT) {
|
||||||
sampleSize -= Atom.HEADER_SIZE;
|
sampleSize -= Atom.HEADER_SIZE;
|
||||||
input.skipFully(Atom.HEADER_SIZE);
|
input.skipFully(Atom.HEADER_SIZE);
|
||||||
}
|
}
|
||||||
|
|
||||||
if (MimeTypes.AUDIO_AC4.equals(
|
if (MimeTypes.AUDIO_AC4.equals(trackBundle.moovSampleTable.track.format.sampleMimeType)) {
|
||||||
currentTrackBundle.moovSampleTable.track.format.sampleMimeType)) {
|
|
||||||
// AC4 samples need to be prefixed with a clear sample header.
|
// AC4 samples need to be prefixed with a clear sample header.
|
||||||
sampleBytesWritten =
|
sampleBytesWritten =
|
||||||
currentTrackBundle.outputSampleEncryptionData(sampleSize, Ac4Util.SAMPLE_HEADER_SIZE);
|
trackBundle.outputSampleEncryptionData(sampleSize, Ac4Util.SAMPLE_HEADER_SIZE);
|
||||||
Ac4Util.getAc4SampleHeader(sampleSize, scratch);
|
Ac4Util.getAc4SampleHeader(sampleSize, scratch);
|
||||||
currentTrackBundle.output.sampleData(scratch, Ac4Util.SAMPLE_HEADER_SIZE);
|
trackBundle.output.sampleData(scratch, Ac4Util.SAMPLE_HEADER_SIZE);
|
||||||
sampleBytesWritten += Ac4Util.SAMPLE_HEADER_SIZE;
|
sampleBytesWritten += Ac4Util.SAMPLE_HEADER_SIZE;
|
||||||
} else {
|
} else {
|
||||||
sampleBytesWritten =
|
sampleBytesWritten =
|
||||||
currentTrackBundle.outputSampleEncryptionData(sampleSize, /* clearHeaderSize= */ 0);
|
trackBundle.outputSampleEncryptionData(sampleSize, /* clearHeaderSize= */ 0);
|
||||||
}
|
}
|
||||||
sampleSize += sampleBytesWritten;
|
sampleSize += sampleBytesWritten;
|
||||||
parserState = STATE_READING_SAMPLE_CONTINUE;
|
parserState = STATE_READING_SAMPLE_CONTINUE;
|
||||||
sampleCurrentNalBytesRemaining = 0;
|
sampleCurrentNalBytesRemaining = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
Track track = currentTrackBundle.moovSampleTable.track;
|
Track track = trackBundle.moovSampleTable.track;
|
||||||
TrackOutput output = currentTrackBundle.output;
|
TrackOutput output = trackBundle.output;
|
||||||
long sampleTimeUs = currentTrackBundle.getCurrentSamplePresentationTimeUs();
|
long sampleTimeUs = trackBundle.getCurrentSamplePresentationTimeUs();
|
||||||
if (timestampAdjuster != null) {
|
if (timestampAdjuster != null) {
|
||||||
sampleTimeUs = timestampAdjuster.adjustSampleTimestamp(sampleTimeUs);
|
sampleTimeUs = timestampAdjuster.adjustSampleTimestamp(sampleTimeUs);
|
||||||
}
|
}
|
||||||
@ -1407,11 +1411,11 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@C.BufferFlags int sampleFlags = currentTrackBundle.getCurrentSampleFlags();
|
@C.BufferFlags int sampleFlags = trackBundle.getCurrentSampleFlags();
|
||||||
|
|
||||||
// Encryption data.
|
// Encryption data.
|
||||||
TrackOutput.CryptoData cryptoData = null;
|
@Nullable TrackOutput.CryptoData cryptoData = null;
|
||||||
TrackEncryptionBox encryptionBox = currentTrackBundle.getEncryptionBoxIfEncrypted();
|
@Nullable TrackEncryptionBox encryptionBox = trackBundle.getEncryptionBoxIfEncrypted();
|
||||||
if (encryptionBox != null) {
|
if (encryptionBox != null) {
|
||||||
cryptoData = encryptionBox.cryptoData;
|
cryptoData = encryptionBox.cryptoData;
|
||||||
}
|
}
|
||||||
@ -1420,7 +1424,7 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
|
|
||||||
// After we have the sampleTimeUs, we can commit all the pending metadata samples
|
// After we have the sampleTimeUs, we can commit all the pending metadata samples
|
||||||
outputPendingMetadataSamples(sampleTimeUs);
|
outputPendingMetadataSamples(sampleTimeUs);
|
||||||
if (!currentTrackBundle.next()) {
|
if (!trackBundle.next()) {
|
||||||
currentTrackBundle = null;
|
currentTrackBundle = null;
|
||||||
}
|
}
|
||||||
parserState = STATE_READING_SAMPLE_START;
|
parserState = STATE_READING_SAMPLE_START;
|
||||||
@ -1452,7 +1456,7 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
*/
|
*/
|
||||||
@Nullable
|
@Nullable
|
||||||
private static TrackBundle getNextTrackBundle(SparseArray<TrackBundle> trackBundles) {
|
private static TrackBundle getNextTrackBundle(SparseArray<TrackBundle> trackBundles) {
|
||||||
TrackBundle nextTrackBundle = null;
|
@Nullable TrackBundle nextTrackBundle = null;
|
||||||
long nextSampleOffset = Long.MAX_VALUE;
|
long nextSampleOffset = Long.MAX_VALUE;
|
||||||
|
|
||||||
int trackBundlesSize = trackBundles.size();
|
int trackBundlesSize = trackBundles.size();
|
||||||
@ -1579,6 +1583,8 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
TrackSampleTable moovSampleTable,
|
TrackSampleTable moovSampleTable,
|
||||||
DefaultSampleValues defaultSampleValues) {
|
DefaultSampleValues defaultSampleValues) {
|
||||||
this.output = output;
|
this.output = output;
|
||||||
|
this.moovSampleTable = moovSampleTable;
|
||||||
|
this.defaultSampleValues = defaultSampleValues;
|
||||||
fragment = new TrackFragment();
|
fragment = new TrackFragment();
|
||||||
scratch = new ParsableByteArray();
|
scratch = new ParsableByteArray();
|
||||||
encryptionSignalByte = new ParsableByteArray(1);
|
encryptionSignalByte = new ParsableByteArray(1);
|
||||||
@ -1587,9 +1593,8 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
}
|
}
|
||||||
|
|
||||||
public void reset(TrackSampleTable moovSampleTable, DefaultSampleValues defaultSampleValues) {
|
public void reset(TrackSampleTable moovSampleTable, DefaultSampleValues defaultSampleValues) {
|
||||||
Assertions.checkNotNull(moovSampleTable.track);
|
|
||||||
this.moovSampleTable = moovSampleTable;
|
this.moovSampleTable = moovSampleTable;
|
||||||
this.defaultSampleValues = Assertions.checkNotNull(defaultSampleValues);
|
this.defaultSampleValues = defaultSampleValues;
|
||||||
output.format(moovSampleTable.track.format);
|
output.format(moovSampleTable.track.format);
|
||||||
resetFragmentInfo();
|
resetFragmentInfo();
|
||||||
}
|
}
|
||||||
@ -1598,7 +1603,7 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
@Nullable
|
@Nullable
|
||||||
TrackEncryptionBox encryptionBox =
|
TrackEncryptionBox encryptionBox =
|
||||||
moovSampleTable.track.getSampleDescriptionEncryptionBox(
|
moovSampleTable.track.getSampleDescriptionEncryptionBox(
|
||||||
fragment.header.sampleDescriptionIndex);
|
castNonNull(fragment.header).sampleDescriptionIndex);
|
||||||
@Nullable String schemeType = encryptionBox != null ? encryptionBox.schemeType : null;
|
@Nullable String schemeType = encryptionBox != null ? encryptionBox.schemeType : null;
|
||||||
DrmInitData updatedDrmInitData = drmInitData.copyWithSchemeType(schemeType);
|
DrmInitData updatedDrmInitData = drmInitData.copyWithSchemeType(schemeType);
|
||||||
Format format =
|
Format format =
|
||||||
@ -1706,7 +1711,7 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
* @return The number of written bytes.
|
* @return The number of written bytes.
|
||||||
*/
|
*/
|
||||||
public int outputSampleEncryptionData(int sampleSize, int clearHeaderSize) {
|
public int outputSampleEncryptionData(int sampleSize, int clearHeaderSize) {
|
||||||
TrackEncryptionBox encryptionBox = getEncryptionBoxIfEncrypted();
|
@Nullable TrackEncryptionBox encryptionBox = getEncryptionBoxIfEncrypted();
|
||||||
if (encryptionBox == null) {
|
if (encryptionBox == null) {
|
||||||
return 0;
|
return 0;
|
||||||
}
|
}
|
||||||
@ -1718,7 +1723,7 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
vectorSize = encryptionBox.perSampleIvSize;
|
vectorSize = encryptionBox.perSampleIvSize;
|
||||||
} else {
|
} else {
|
||||||
// The default initialization vector should be used.
|
// The default initialization vector should be used.
|
||||||
byte[] initVectorData = encryptionBox.defaultInitializationVector;
|
byte[] initVectorData = castNonNull(encryptionBox.defaultInitializationVector);
|
||||||
defaultInitializationVector.reset(initVectorData, initVectorData.length);
|
defaultInitializationVector.reset(initVectorData, initVectorData.length);
|
||||||
initializationVectorData = defaultInitializationVector;
|
initializationVectorData = defaultInitializationVector;
|
||||||
vectorSize = initVectorData.length;
|
vectorSize = initVectorData.length;
|
||||||
@ -1815,7 +1820,7 @@ public class FragmentedMp4Extractor implements Extractor {
|
|||||||
// Encryption is not supported yet for samples specified in the sample table.
|
// Encryption is not supported yet for samples specified in the sample table.
|
||||||
return null;
|
return null;
|
||||||
}
|
}
|
||||||
int sampleDescriptionIndex = fragment.header.sampleDescriptionIndex;
|
int sampleDescriptionIndex = castNonNull(fragment.header).sampleDescriptionIndex;
|
||||||
@Nullable
|
@Nullable
|
||||||
TrackEncryptionBox encryptionBox =
|
TrackEncryptionBox encryptionBox =
|
||||||
fragment.trackEncryptionBox != null
|
fragment.trackEncryptionBox != null
|
||||||
|
@ -26,7 +26,6 @@ import androidx.annotation.Nullable;
|
|||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
||||||
import com.google.android.exoplayer2.MediaItem;
|
import com.google.android.exoplayer2.MediaItem;
|
||||||
import com.google.android.exoplayer2.drm.DrmSession;
|
|
||||||
import com.google.android.exoplayer2.drm.DrmSessionEventListener;
|
import com.google.android.exoplayer2.drm.DrmSessionEventListener;
|
||||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||||
import com.google.android.exoplayer2.extractor.Extractor;
|
import com.google.android.exoplayer2.extractor.Extractor;
|
||||||
@ -36,6 +35,7 @@ import com.google.android.exoplayer2.source.CompositeSequenceableLoaderFactory;
|
|||||||
import com.google.android.exoplayer2.source.DefaultCompositeSequenceableLoaderFactory;
|
import com.google.android.exoplayer2.source.DefaultCompositeSequenceableLoaderFactory;
|
||||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
|
import com.google.android.exoplayer2.source.MediaSourceDrmHelper;
|
||||||
import com.google.android.exoplayer2.source.MediaSourceEventListener;
|
import com.google.android.exoplayer2.source.MediaSourceEventListener;
|
||||||
import com.google.android.exoplayer2.source.MediaSourceFactory;
|
import com.google.android.exoplayer2.source.MediaSourceFactory;
|
||||||
import com.google.android.exoplayer2.source.SequenceableLoader;
|
import com.google.android.exoplayer2.source.SequenceableLoader;
|
||||||
@ -49,6 +49,7 @@ import com.google.android.exoplayer2.source.hls.playlist.HlsPlaylistTracker;
|
|||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
import com.google.android.exoplayer2.upstream.Allocator;
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
|
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
|
||||||
|
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
||||||
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
|
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
|
||||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
@ -93,12 +94,13 @@ public final class HlsMediaSource extends BaseMediaSource
|
|||||||
public static final class Factory implements MediaSourceFactory {
|
public static final class Factory implements MediaSourceFactory {
|
||||||
|
|
||||||
private final HlsDataSourceFactory hlsDataSourceFactory;
|
private final HlsDataSourceFactory hlsDataSourceFactory;
|
||||||
|
private final MediaSourceDrmHelper mediaSourceDrmHelper;
|
||||||
|
|
||||||
private HlsExtractorFactory extractorFactory;
|
private HlsExtractorFactory extractorFactory;
|
||||||
private HlsPlaylistParserFactory playlistParserFactory;
|
private HlsPlaylistParserFactory playlistParserFactory;
|
||||||
private HlsPlaylistTracker.Factory playlistTrackerFactory;
|
private HlsPlaylistTracker.Factory playlistTrackerFactory;
|
||||||
private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
|
private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
|
||||||
private DrmSessionManager drmSessionManager;
|
@Nullable private DrmSessionManager drmSessionManager;
|
||||||
private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
|
private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
|
||||||
private boolean allowChunklessPreparation;
|
private boolean allowChunklessPreparation;
|
||||||
@MetadataType private int metadataType;
|
@MetadataType private int metadataType;
|
||||||
@ -125,10 +127,10 @@ public final class HlsMediaSource extends BaseMediaSource
|
|||||||
*/
|
*/
|
||||||
public Factory(HlsDataSourceFactory hlsDataSourceFactory) {
|
public Factory(HlsDataSourceFactory hlsDataSourceFactory) {
|
||||||
this.hlsDataSourceFactory = checkNotNull(hlsDataSourceFactory);
|
this.hlsDataSourceFactory = checkNotNull(hlsDataSourceFactory);
|
||||||
|
mediaSourceDrmHelper = new MediaSourceDrmHelper();
|
||||||
playlistParserFactory = new DefaultHlsPlaylistParserFactory();
|
playlistParserFactory = new DefaultHlsPlaylistParserFactory();
|
||||||
playlistTrackerFactory = DefaultHlsPlaylistTracker.FACTORY;
|
playlistTrackerFactory = DefaultHlsPlaylistTracker.FACTORY;
|
||||||
extractorFactory = HlsExtractorFactory.DEFAULT;
|
extractorFactory = HlsExtractorFactory.DEFAULT;
|
||||||
drmSessionManager = DrmSessionManager.getDummyDrmSessionManager();
|
|
||||||
loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy();
|
loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy();
|
||||||
compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory();
|
compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory();
|
||||||
metadataType = METADATA_TYPE_ID3;
|
metadataType = METADATA_TYPE_ID3;
|
||||||
@ -285,19 +287,22 @@ public final class HlsMediaSource extends BaseMediaSource
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the {@link DrmSessionManager} to use for acquiring {@link DrmSession DrmSessions}. The
|
|
||||||
* default value is {@link DrmSessionManager#DUMMY}.
|
|
||||||
*
|
|
||||||
* @param drmSessionManager The {@link DrmSessionManager}.
|
|
||||||
* @return This factory, for convenience.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public Factory setDrmSessionManager(@Nullable DrmSessionManager drmSessionManager) {
|
public Factory setDrmSessionManager(@Nullable DrmSessionManager drmSessionManager) {
|
||||||
this.drmSessionManager =
|
this.drmSessionManager = drmSessionManager;
|
||||||
drmSessionManager != null
|
return this;
|
||||||
? drmSessionManager
|
}
|
||||||
: DrmSessionManager.getDummyDrmSessionManager();
|
|
||||||
|
@Override
|
||||||
|
public Factory setDrmHttpDataSourceFactory(
|
||||||
|
@Nullable HttpDataSource.Factory drmHttpDataSourceFactory) {
|
||||||
|
mediaSourceDrmHelper.setDrmHttpDataSourceFactory(drmHttpDataSourceFactory);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public MediaSourceFactory setDrmUserAgent(@Nullable String userAgent) {
|
||||||
|
mediaSourceDrmHelper.setDrmUserAgent(userAgent);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -374,7 +379,7 @@ public final class HlsMediaSource extends BaseMediaSource
|
|||||||
hlsDataSourceFactory,
|
hlsDataSourceFactory,
|
||||||
extractorFactory,
|
extractorFactory,
|
||||||
compositeSequenceableLoaderFactory,
|
compositeSequenceableLoaderFactory,
|
||||||
drmSessionManager,
|
drmSessionManager != null ? drmSessionManager : mediaSourceDrmHelper.create(mediaItem),
|
||||||
loadErrorHandlingPolicy,
|
loadErrorHandlingPolicy,
|
||||||
playlistTrackerFactory.createTracker(
|
playlistTrackerFactory.createTracker(
|
||||||
hlsDataSourceFactory, loadErrorHandlingPolicy, playlistParserFactory),
|
hlsDataSourceFactory, loadErrorHandlingPolicy, playlistParserFactory),
|
||||||
|
@ -27,7 +27,6 @@ import com.google.android.exoplayer2.C;
|
|||||||
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
||||||
import com.google.android.exoplayer2.MediaItem;
|
import com.google.android.exoplayer2.MediaItem;
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
import com.google.android.exoplayer2.drm.DrmSession;
|
|
||||||
import com.google.android.exoplayer2.drm.DrmSessionEventListener;
|
import com.google.android.exoplayer2.drm.DrmSessionEventListener;
|
||||||
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
import com.google.android.exoplayer2.drm.DrmSessionManager;
|
||||||
import com.google.android.exoplayer2.offline.FilteringManifestParser;
|
import com.google.android.exoplayer2.offline.FilteringManifestParser;
|
||||||
@ -39,6 +38,7 @@ import com.google.android.exoplayer2.source.LoadEventInfo;
|
|||||||
import com.google.android.exoplayer2.source.MediaLoadData;
|
import com.google.android.exoplayer2.source.MediaLoadData;
|
||||||
import com.google.android.exoplayer2.source.MediaPeriod;
|
import com.google.android.exoplayer2.source.MediaPeriod;
|
||||||
import com.google.android.exoplayer2.source.MediaSource;
|
import com.google.android.exoplayer2.source.MediaSource;
|
||||||
|
import com.google.android.exoplayer2.source.MediaSourceDrmHelper;
|
||||||
import com.google.android.exoplayer2.source.MediaSourceEventListener;
|
import com.google.android.exoplayer2.source.MediaSourceEventListener;
|
||||||
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
|
import com.google.android.exoplayer2.source.MediaSourceEventListener.EventDispatcher;
|
||||||
import com.google.android.exoplayer2.source.MediaSourceFactory;
|
import com.google.android.exoplayer2.source.MediaSourceFactory;
|
||||||
@ -50,6 +50,7 @@ import com.google.android.exoplayer2.source.smoothstreaming.manifest.SsManifestP
|
|||||||
import com.google.android.exoplayer2.upstream.Allocator;
|
import com.google.android.exoplayer2.upstream.Allocator;
|
||||||
import com.google.android.exoplayer2.upstream.DataSource;
|
import com.google.android.exoplayer2.upstream.DataSource;
|
||||||
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
|
import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
|
||||||
|
import com.google.android.exoplayer2.upstream.HttpDataSource;
|
||||||
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
|
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy;
|
||||||
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy.LoadErrorInfo;
|
import com.google.android.exoplayer2.upstream.LoadErrorHandlingPolicy.LoadErrorInfo;
|
||||||
import com.google.android.exoplayer2.upstream.Loader;
|
import com.google.android.exoplayer2.upstream.Loader;
|
||||||
@ -77,10 +78,11 @@ public final class SsMediaSource extends BaseMediaSource
|
|||||||
public static final class Factory implements MediaSourceFactory {
|
public static final class Factory implements MediaSourceFactory {
|
||||||
|
|
||||||
private final SsChunkSource.Factory chunkSourceFactory;
|
private final SsChunkSource.Factory chunkSourceFactory;
|
||||||
|
private final MediaSourceDrmHelper mediaSourceDrmHelper;
|
||||||
@Nullable private final DataSource.Factory manifestDataSourceFactory;
|
@Nullable private final DataSource.Factory manifestDataSourceFactory;
|
||||||
|
|
||||||
private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
|
private CompositeSequenceableLoaderFactory compositeSequenceableLoaderFactory;
|
||||||
private DrmSessionManager drmSessionManager;
|
@Nullable private DrmSessionManager drmSessionManager;
|
||||||
private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
|
private LoadErrorHandlingPolicy loadErrorHandlingPolicy;
|
||||||
private long livePresentationDelayMs;
|
private long livePresentationDelayMs;
|
||||||
@Nullable private ParsingLoadable.Parser<? extends SsManifest> manifestParser;
|
@Nullable private ParsingLoadable.Parser<? extends SsManifest> manifestParser;
|
||||||
@ -111,7 +113,7 @@ public final class SsMediaSource extends BaseMediaSource
|
|||||||
@Nullable DataSource.Factory manifestDataSourceFactory) {
|
@Nullable DataSource.Factory manifestDataSourceFactory) {
|
||||||
this.chunkSourceFactory = checkNotNull(chunkSourceFactory);
|
this.chunkSourceFactory = checkNotNull(chunkSourceFactory);
|
||||||
this.manifestDataSourceFactory = manifestDataSourceFactory;
|
this.manifestDataSourceFactory = manifestDataSourceFactory;
|
||||||
drmSessionManager = DrmSessionManager.getDummyDrmSessionManager();
|
mediaSourceDrmHelper = new MediaSourceDrmHelper();
|
||||||
loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy();
|
loadErrorHandlingPolicy = new DefaultLoadErrorHandlingPolicy();
|
||||||
livePresentationDelayMs = DEFAULT_LIVE_PRESENTATION_DELAY_MS;
|
livePresentationDelayMs = DEFAULT_LIVE_PRESENTATION_DELAY_MS;
|
||||||
compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory();
|
compositeSequenceableLoaderFactory = new DefaultCompositeSequenceableLoaderFactory();
|
||||||
@ -197,19 +199,22 @@ public final class SsMediaSource extends BaseMediaSource
|
|||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
|
||||||
* Sets the {@link DrmSessionManager} to use for acquiring {@link DrmSession DrmSessions}. The
|
|
||||||
* default value is {@link DrmSessionManager#DUMMY}.
|
|
||||||
*
|
|
||||||
* @param drmSessionManager The {@link DrmSessionManager}.
|
|
||||||
* @return This factory, for convenience.
|
|
||||||
*/
|
|
||||||
@Override
|
@Override
|
||||||
public Factory setDrmSessionManager(@Nullable DrmSessionManager drmSessionManager) {
|
public Factory setDrmSessionManager(@Nullable DrmSessionManager drmSessionManager) {
|
||||||
this.drmSessionManager =
|
this.drmSessionManager = drmSessionManager;
|
||||||
drmSessionManager != null
|
return this;
|
||||||
? drmSessionManager
|
}
|
||||||
: DrmSessionManager.getDummyDrmSessionManager();
|
|
||||||
|
@Override
|
||||||
|
public Factory setDrmHttpDataSourceFactory(
|
||||||
|
@Nullable HttpDataSource.Factory drmHttpDataSourceFactory) {
|
||||||
|
mediaSourceDrmHelper.setDrmHttpDataSourceFactory(drmHttpDataSourceFactory);
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public Factory setDrmUserAgent(@Nullable String userAgent) {
|
||||||
|
mediaSourceDrmHelper.setDrmUserAgent(userAgent);
|
||||||
return this;
|
return this;
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -280,7 +285,7 @@ public final class SsMediaSource extends BaseMediaSource
|
|||||||
/* manifestParser= */ null,
|
/* manifestParser= */ null,
|
||||||
chunkSourceFactory,
|
chunkSourceFactory,
|
||||||
compositeSequenceableLoaderFactory,
|
compositeSequenceableLoaderFactory,
|
||||||
drmSessionManager,
|
drmSessionManager != null ? drmSessionManager : mediaSourceDrmHelper.create(mediaItem),
|
||||||
loadErrorHandlingPolicy,
|
loadErrorHandlingPolicy,
|
||||||
livePresentationDelayMs);
|
livePresentationDelayMs);
|
||||||
}
|
}
|
||||||
@ -357,7 +362,7 @@ public final class SsMediaSource extends BaseMediaSource
|
|||||||
manifestParser,
|
manifestParser,
|
||||||
chunkSourceFactory,
|
chunkSourceFactory,
|
||||||
compositeSequenceableLoaderFactory,
|
compositeSequenceableLoaderFactory,
|
||||||
drmSessionManager,
|
drmSessionManager != null ? drmSessionManager : mediaSourceDrmHelper.create(mediaItem),
|
||||||
loadErrorHandlingPolicy,
|
loadErrorHandlingPolicy,
|
||||||
livePresentationDelayMs);
|
livePresentationDelayMs);
|
||||||
}
|
}
|
||||||
|
@ -3,7 +3,7 @@
|
|||||||
# Constructor method accessed via reflection in TrackSelectionDialogBuilder
|
# Constructor method accessed via reflection in TrackSelectionDialogBuilder
|
||||||
-dontnote androidx.appcompat.app.AlertDialog.Builder
|
-dontnote androidx.appcompat.app.AlertDialog.Builder
|
||||||
-keepclassmembers class androidx.appcompat.app.AlertDialog$Builder {
|
-keepclassmembers class androidx.appcompat.app.AlertDialog$Builder {
|
||||||
<init>(android.content.Context);
|
<init>(android.content.Context, int);
|
||||||
public android.content.Context getContext();
|
public android.content.Context getContext();
|
||||||
public androidx.appcompat.app.AlertDialog$Builder setTitle(java.lang.CharSequence);
|
public androidx.appcompat.app.AlertDialog$Builder setTitle(java.lang.CharSequence);
|
||||||
public androidx.appcompat.app.AlertDialog$Builder setView(android.view.View);
|
public androidx.appcompat.app.AlertDialog$Builder setView(android.view.View);
|
||||||
|
@ -1085,8 +1085,8 @@ public class PlayerControlView extends FrameLayout {
|
|||||||
long mediaTimeUntilNextFullSecondMs = 1000 - position % 1000;
|
long mediaTimeUntilNextFullSecondMs = 1000 - position % 1000;
|
||||||
mediaTimeDelayMs = Math.min(mediaTimeDelayMs, mediaTimeUntilNextFullSecondMs);
|
mediaTimeDelayMs = Math.min(mediaTimeDelayMs, mediaTimeUntilNextFullSecondMs);
|
||||||
|
|
||||||
// Calculate the delay until the next update in real time, taking playbackSpeed into account.
|
// Calculate the delay until the next update in real time, taking playback speed into account.
|
||||||
float playbackSpeed = player.getPlaybackSpeed();
|
float playbackSpeed = player.getPlaybackParameters().speed;
|
||||||
long delayMs =
|
long delayMs =
|
||||||
playbackSpeed > 0 ? (long) (mediaTimeDelayMs / playbackSpeed) : MAX_UPDATE_INTERVAL_MS;
|
playbackSpeed > 0 ? (long) (mediaTimeDelayMs / playbackSpeed) : MAX_UPDATE_INTERVAL_MS;
|
||||||
|
|
||||||
|
@ -37,6 +37,7 @@ import androidx.media.app.NotificationCompat.MediaStyle;
|
|||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.ControlDispatcher;
|
import com.google.android.exoplayer2.ControlDispatcher;
|
||||||
import com.google.android.exoplayer2.DefaultControlDispatcher;
|
import com.google.android.exoplayer2.DefaultControlDispatcher;
|
||||||
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
import com.google.android.exoplayer2.PlaybackPreparer;
|
import com.google.android.exoplayer2.PlaybackPreparer;
|
||||||
import com.google.android.exoplayer2.Player;
|
import com.google.android.exoplayer2.Player;
|
||||||
import com.google.android.exoplayer2.Timeline;
|
import com.google.android.exoplayer2.Timeline;
|
||||||
@ -923,7 +924,7 @@ public class PlayerNotificationManager {
|
|||||||
* <li>The media is not {@link Player#isCurrentWindowDynamic() dynamically changing its
|
* <li>The media is not {@link Player#isCurrentWindowDynamic() dynamically changing its
|
||||||
* duration} (like for example a live stream).
|
* duration} (like for example a live stream).
|
||||||
* <li>The media is not {@link Player#isPlayingAd() interrupted by an ad}.
|
* <li>The media is not {@link Player#isPlayingAd() interrupted by an ad}.
|
||||||
* <li>The media is played at {@link Player#getPlaybackSpeed() regular speed}.
|
* <li>The media is played at {@link Player#getPlaybackParameters() regular speed}.
|
||||||
* <li>The device is running at least API 21 (Lollipop).
|
* <li>The device is running at least API 21 (Lollipop).
|
||||||
* </ul>
|
* </ul>
|
||||||
*
|
*
|
||||||
@ -1086,7 +1087,7 @@ public class PlayerNotificationManager {
|
|||||||
&& player.isPlaying()
|
&& player.isPlaying()
|
||||||
&& !player.isPlayingAd()
|
&& !player.isPlayingAd()
|
||||||
&& !player.isCurrentWindowDynamic()
|
&& !player.isCurrentWindowDynamic()
|
||||||
&& player.getPlaybackSpeed() == 1f) {
|
&& player.getPlaybackParameters().speed == 1f) {
|
||||||
builder
|
builder
|
||||||
.setWhen(System.currentTimeMillis() - player.getContentPosition())
|
.setWhen(System.currentTimeMillis() - player.getContentPosition())
|
||||||
.setShowWhen(true)
|
.setShowWhen(true)
|
||||||
@ -1336,7 +1337,7 @@ public class PlayerNotificationManager {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlaybackSpeedChanged(float playbackSpeed) {
|
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
|
||||||
postStartOrUpdateNotification();
|
postStartOrUpdateNotification();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -42,6 +42,7 @@ import com.google.android.exoplayer2.ControlDispatcher;
|
|||||||
import com.google.android.exoplayer2.DefaultControlDispatcher;
|
import com.google.android.exoplayer2.DefaultControlDispatcher;
|
||||||
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
import com.google.android.exoplayer2.ExoPlayerLibraryInfo;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
import com.google.android.exoplayer2.PlaybackPreparer;
|
import com.google.android.exoplayer2.PlaybackPreparer;
|
||||||
import com.google.android.exoplayer2.Player;
|
import com.google.android.exoplayer2.Player;
|
||||||
import com.google.android.exoplayer2.RendererCapabilities;
|
import com.google.android.exoplayer2.RendererCapabilities;
|
||||||
@ -1408,8 +1409,8 @@ public class StyledPlayerControlView extends FrameLayout {
|
|||||||
long mediaTimeUntilNextFullSecondMs = 1000 - position % 1000;
|
long mediaTimeUntilNextFullSecondMs = 1000 - position % 1000;
|
||||||
mediaTimeDelayMs = Math.min(mediaTimeDelayMs, mediaTimeUntilNextFullSecondMs);
|
mediaTimeDelayMs = Math.min(mediaTimeDelayMs, mediaTimeUntilNextFullSecondMs);
|
||||||
|
|
||||||
// Calculate the delay until the next update in real time, taking playbackSpeed into account.
|
// Calculate the delay until the next update in real time, taking playback speed into account.
|
||||||
float playbackSpeed = player.getPlaybackSpeed();
|
float playbackSpeed = player.getPlaybackParameters().speed;
|
||||||
long delayMs =
|
long delayMs =
|
||||||
playbackSpeed > 0 ? (long) (mediaTimeDelayMs / playbackSpeed) : MAX_UPDATE_INTERVAL_MS;
|
playbackSpeed > 0 ? (long) (mediaTimeDelayMs / playbackSpeed) : MAX_UPDATE_INTERVAL_MS;
|
||||||
|
|
||||||
@ -1425,7 +1426,7 @@ public class StyledPlayerControlView extends FrameLayout {
|
|||||||
if (player == null) {
|
if (player == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
float speed = player.getPlaybackSpeed();
|
float speed = player.getPlaybackParameters().speed;
|
||||||
int currentSpeedMultBy100 = Math.round(speed * 100);
|
int currentSpeedMultBy100 = Math.round(speed * 100);
|
||||||
int indexForCurrentSpeed = playbackSpeedMultBy100List.indexOf(currentSpeedMultBy100);
|
int indexForCurrentSpeed = playbackSpeedMultBy100List.indexOf(currentSpeedMultBy100);
|
||||||
if (indexForCurrentSpeed == UNDEFINED_POSITION) {
|
if (indexForCurrentSpeed == UNDEFINED_POSITION) {
|
||||||
@ -1481,7 +1482,7 @@ public class StyledPlayerControlView extends FrameLayout {
|
|||||||
if (player == null) {
|
if (player == null) {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
player.setPlaybackSpeed(speed);
|
player.setPlaybackParameters(new PlaybackParameters(speed));
|
||||||
}
|
}
|
||||||
|
|
||||||
/* package */ void requestPlayPauseFocus() {
|
/* package */ void requestPlayPauseFocus() {
|
||||||
@ -1771,7 +1772,7 @@ public class StyledPlayerControlView extends FrameLayout {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void onPlaybackSpeedChanged(float playbackSpeed) {
|
public void onPlaybackParametersChanged(PlaybackParameters playbackParameters) {
|
||||||
updateSettingsPlaybackSpeedLists();
|
updateSettingsPlaybackSpeedLists();
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -25,6 +25,7 @@ import android.view.LayoutInflater;
|
|||||||
import android.view.View;
|
import android.view.View;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
|
import androidx.annotation.StyleRes;
|
||||||
import com.google.android.exoplayer2.source.TrackGroupArray;
|
import com.google.android.exoplayer2.source.TrackGroupArray;
|
||||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector;
|
||||||
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride;
|
import com.google.android.exoplayer2.trackselection.DefaultTrackSelector.SelectionOverride;
|
||||||
@ -51,6 +52,7 @@ public final class TrackSelectionDialogBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private final Context context;
|
private final Context context;
|
||||||
|
@StyleRes private int themeResId;
|
||||||
private final CharSequence title;
|
private final CharSequence title;
|
||||||
private final MappedTrackInfo mappedTrackInfo;
|
private final MappedTrackInfo mappedTrackInfo;
|
||||||
private final int rendererIndex;
|
private final int rendererIndex;
|
||||||
@ -124,6 +126,17 @@ public final class TrackSelectionDialogBuilder {
|
|||||||
newOverrides.isEmpty() ? null : newOverrides.get(0)));
|
newOverrides.isEmpty() ? null : newOverrides.get(0)));
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Sets the resource ID of the theme used to inflate this dialog.
|
||||||
|
*
|
||||||
|
* @param themeResId The resource ID of the theme.
|
||||||
|
* @return This builder, for convenience.
|
||||||
|
*/
|
||||||
|
public TrackSelectionDialogBuilder setTheme(@StyleRes int themeResId) {
|
||||||
|
this.themeResId = themeResId;
|
||||||
|
return this;
|
||||||
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Sets whether the selection is initially shown as disabled.
|
* Sets whether the selection is initially shown as disabled.
|
||||||
*
|
*
|
||||||
@ -221,7 +234,7 @@ public final class TrackSelectionDialogBuilder {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private Dialog buildForPlatform() {
|
private Dialog buildForPlatform() {
|
||||||
AlertDialog.Builder builder = new AlertDialog.Builder(context);
|
AlertDialog.Builder builder = new AlertDialog.Builder(context, themeResId);
|
||||||
|
|
||||||
// Inflate with the builder's context to ensure the correct style is used.
|
// Inflate with the builder's context to ensure the correct style is used.
|
||||||
LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
|
LayoutInflater dialogInflater = LayoutInflater.from(builder.getContext());
|
||||||
@ -245,8 +258,8 @@ public final class TrackSelectionDialogBuilder {
|
|||||||
// the APK size even with shrinking. See https://issuetracker.google.com/161514204.
|
// the APK size even with shrinking. See https://issuetracker.google.com/161514204.
|
||||||
// LINT.IfChange
|
// LINT.IfChange
|
||||||
Class<?> builderClazz = Class.forName("androidx.appcompat.app.AlertDialog$Builder");
|
Class<?> builderClazz = Class.forName("androidx.appcompat.app.AlertDialog$Builder");
|
||||||
Constructor<?> builderConstructor = builderClazz.getConstructor(Context.class);
|
Constructor<?> builderConstructor = builderClazz.getConstructor(Context.class, int.class);
|
||||||
Object builder = builderConstructor.newInstance(context);
|
Object builder = builderConstructor.newInstance(context, themeResId);
|
||||||
|
|
||||||
// Inflate with the builder's context to ensure the correct style is used.
|
// Inflate with the builder's context to ensure the correct style is used.
|
||||||
Context builderContext = (Context) builderClazz.getMethod("getContext").invoke(builder);
|
Context builderContext = (Context) builderClazz.getMethod("getContext").invoke(builder);
|
||||||
|
@ -134,6 +134,7 @@
|
|||||||
android:layout_height="wrap_content"
|
android:layout_height="wrap_content"
|
||||||
android:orientation="vertical"
|
android:orientation="vertical"
|
||||||
android:layout_gravity="bottom|end"
|
android:layout_gravity="bottom|end"
|
||||||
|
android:layout_marginBottom="@dimen/exo_custom_progress_thumb_size"
|
||||||
android:visibility="invisible">
|
android:visibility="invisible">
|
||||||
</LinearLayout>
|
</LinearLayout>
|
||||||
|
|
||||||
|
@ -15,6 +15,8 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.playbacktests.gts;
|
package com.google.android.exoplayer2.playbacktests.gts;
|
||||||
|
|
||||||
|
import static com.google.android.exoplayer2.playbacktests.gts.GtsTestUtil.shouldSkipWidevineTest;
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
import androidx.test.rule.ActivityTestRule;
|
import androidx.test.rule.ActivityTestRule;
|
||||||
import com.google.android.exoplayer2.Player;
|
import com.google.android.exoplayer2.Player;
|
||||||
@ -64,7 +66,7 @@ public final class CommonEncryptionDrmTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void cencSchemeTypeV18() {
|
public void cencSchemeTypeV18() {
|
||||||
if (Util.SDK_INT < 18) {
|
if (Util.SDK_INT < 18 || shouldSkipWidevineTest(testRule.getActivity())) {
|
||||||
// Pass.
|
// Pass.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -74,23 +76,9 @@ public final class CommonEncryptionDrmTest {
|
|||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void cbc1SchemeTypeV25() {
|
|
||||||
if (Util.SDK_INT < 25) {
|
|
||||||
// cbc1 support was added in API 24, but it is stable from API 25 onwards.
|
|
||||||
// See [internal: b/65634809].
|
|
||||||
// Pass.
|
|
||||||
return;
|
|
||||||
}
|
|
||||||
testRunner
|
|
||||||
.setStreamName("test_widevine_h264_scheme_cbc1")
|
|
||||||
.setManifestUrl(DashTestData.WIDEVINE_SCHEME_CBC1)
|
|
||||||
.run();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void cbcsSchemeTypeV25() {
|
public void cbcsSchemeTypeV25() {
|
||||||
if (Util.SDK_INT < 25) {
|
if (Util.SDK_INT < 25 || shouldSkipWidevineTest(testRule.getActivity())) {
|
||||||
// cbcs support was added in API 24, but it is stable from API 25 onwards.
|
// cbcs support was added in API 24, but it is stable from API 25 onwards.
|
||||||
// See [internal: b/65634809].
|
// See [internal: b/65634809].
|
||||||
// Pass.
|
// Pass.
|
||||||
@ -101,9 +89,4 @@ public final class CommonEncryptionDrmTest {
|
|||||||
.setManifestUrl(DashTestData.WIDEVINE_SCHEME_CBCS)
|
.setManifestUrl(DashTestData.WIDEVINE_SCHEME_CBCS)
|
||||||
.run();
|
.run();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Test
|
|
||||||
public void censSchemeTypeV25() {
|
|
||||||
// TODO: Implement once content is available. Track [internal: b/31219813].
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
@ -15,6 +15,7 @@
|
|||||||
*/
|
*/
|
||||||
package com.google.android.exoplayer2.playbacktests.gts;
|
package com.google.android.exoplayer2.playbacktests.gts;
|
||||||
|
|
||||||
|
import static com.google.android.exoplayer2.playbacktests.gts.GtsTestUtil.shouldSkipWidevineTest;
|
||||||
import static com.google.common.truth.Truth.assertThat;
|
import static com.google.common.truth.Truth.assertThat;
|
||||||
|
|
||||||
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
import androidx.test.ext.junit.runners.AndroidJUnit4;
|
||||||
@ -356,7 +357,7 @@ public final class DashStreamingTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void widevineH264FixedV18() throws Exception {
|
public void widevineH264FixedV18() throws Exception {
|
||||||
if (Util.SDK_INT < 18) {
|
if (Util.SDK_INT < 18 || shouldSkipWidevineTest(testRule.getActivity())) {
|
||||||
// Pass.
|
// Pass.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -373,7 +374,9 @@ public final class DashStreamingTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void widevineH264AdaptiveV18() throws Exception {
|
public void widevineH264AdaptiveV18() throws Exception {
|
||||||
if (Util.SDK_INT < 18 || shouldSkipAdaptiveTest(MimeTypes.VIDEO_H264)) {
|
if (Util.SDK_INT < 18
|
||||||
|
|| shouldSkipAdaptiveTest(MimeTypes.VIDEO_H264)
|
||||||
|
|| shouldSkipWidevineTest(testRule.getActivity())) {
|
||||||
// Pass.
|
// Pass.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -390,7 +393,9 @@ public final class DashStreamingTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void widevineH264AdaptiveWithSeekingV18() throws Exception {
|
public void widevineH264AdaptiveWithSeekingV18() throws Exception {
|
||||||
if (Util.SDK_INT < 18 || shouldSkipAdaptiveTest(MimeTypes.VIDEO_H264)) {
|
if (Util.SDK_INT < 18
|
||||||
|
|| shouldSkipAdaptiveTest(MimeTypes.VIDEO_H264)
|
||||||
|
|| shouldSkipWidevineTest(testRule.getActivity())) {
|
||||||
// Pass.
|
// Pass.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -408,7 +413,9 @@ public final class DashStreamingTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void widevineH264AdaptiveWithRendererDisablingV18() throws Exception {
|
public void widevineH264AdaptiveWithRendererDisablingV18() throws Exception {
|
||||||
if (Util.SDK_INT < 18 || shouldSkipAdaptiveTest(MimeTypes.VIDEO_H264)) {
|
if (Util.SDK_INT < 18
|
||||||
|
|| shouldSkipAdaptiveTest(MimeTypes.VIDEO_H264)
|
||||||
|
|| GtsTestUtil.shouldSkipWidevineTest(testRule.getActivity())) {
|
||||||
// Pass.
|
// Pass.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -428,7 +435,7 @@ public final class DashStreamingTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void widevineH265FixedV23() throws Exception {
|
public void widevineH265FixedV23() throws Exception {
|
||||||
if (Util.SDK_INT < 23) {
|
if (Util.SDK_INT < 23 || GtsTestUtil.shouldSkipWidevineTest(testRule.getActivity())) {
|
||||||
// Pass.
|
// Pass.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -445,7 +452,7 @@ public final class DashStreamingTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void widevineH265AdaptiveV24() throws Exception {
|
public void widevineH265AdaptiveV24() throws Exception {
|
||||||
if (Util.SDK_INT < 24) {
|
if (Util.SDK_INT < 24 || GtsTestUtil.shouldSkipWidevineTest(testRule.getActivity())) {
|
||||||
// Pass.
|
// Pass.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -462,7 +469,7 @@ public final class DashStreamingTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void widevineH265AdaptiveWithSeekingV24() throws Exception {
|
public void widevineH265AdaptiveWithSeekingV24() throws Exception {
|
||||||
if (Util.SDK_INT < 24) {
|
if (Util.SDK_INT < 24 || GtsTestUtil.shouldSkipWidevineTest(testRule.getActivity())) {
|
||||||
// Pass.
|
// Pass.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -480,7 +487,7 @@ public final class DashStreamingTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void widevineH265AdaptiveWithRendererDisablingV24() throws Exception {
|
public void widevineH265AdaptiveWithRendererDisablingV24() throws Exception {
|
||||||
if (Util.SDK_INT < 24) {
|
if (Util.SDK_INT < 24 || GtsTestUtil.shouldSkipWidevineTest(testRule.getActivity())) {
|
||||||
// Pass.
|
// Pass.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -500,7 +507,7 @@ public final class DashStreamingTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void widevineVp9Fixed360pV23() throws Exception {
|
public void widevineVp9Fixed360pV23() throws Exception {
|
||||||
if (Util.SDK_INT < 23) {
|
if (Util.SDK_INT < 23 || GtsTestUtil.shouldSkipWidevineTest(testRule.getActivity())) {
|
||||||
// Pass.
|
// Pass.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -517,7 +524,7 @@ public final class DashStreamingTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void widevineVp9AdaptiveV24() throws Exception {
|
public void widevineVp9AdaptiveV24() throws Exception {
|
||||||
if (Util.SDK_INT < 24) {
|
if (Util.SDK_INT < 24 || GtsTestUtil.shouldSkipWidevineTest(testRule.getActivity())) {
|
||||||
// Pass.
|
// Pass.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -534,7 +541,7 @@ public final class DashStreamingTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void widevineVp9AdaptiveWithSeekingV24() throws Exception {
|
public void widevineVp9AdaptiveWithSeekingV24() throws Exception {
|
||||||
if (Util.SDK_INT < 24) {
|
if (Util.SDK_INT < 24 || GtsTestUtil.shouldSkipWidevineTest(testRule.getActivity())) {
|
||||||
// Pass.
|
// Pass.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -552,7 +559,7 @@ public final class DashStreamingTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void widevineVp9AdaptiveWithRendererDisablingV24() throws Exception {
|
public void widevineVp9AdaptiveWithRendererDisablingV24() throws Exception {
|
||||||
if (Util.SDK_INT < 24) {
|
if (Util.SDK_INT < 24 || GtsTestUtil.shouldSkipWidevineTest(testRule.getActivity())) {
|
||||||
// Pass.
|
// Pass.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -573,7 +580,7 @@ public final class DashStreamingTest {
|
|||||||
// 23.976 fps.
|
// 23.976 fps.
|
||||||
@Test
|
@Test
|
||||||
public void widevine23FpsH264FixedV23() throws Exception {
|
public void widevine23FpsH264FixedV23() throws Exception {
|
||||||
if (Util.SDK_INT < 23) {
|
if (Util.SDK_INT < 23 || GtsTestUtil.shouldSkipWidevineTest(testRule.getActivity())) {
|
||||||
// Pass.
|
// Pass.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -591,7 +598,7 @@ public final class DashStreamingTest {
|
|||||||
// 24 fps.
|
// 24 fps.
|
||||||
@Test
|
@Test
|
||||||
public void widevine24FpsH264FixedV23() throws Exception {
|
public void widevine24FpsH264FixedV23() throws Exception {
|
||||||
if (Util.SDK_INT < 23) {
|
if (Util.SDK_INT < 23 || GtsTestUtil.shouldSkipWidevineTest(testRule.getActivity())) {
|
||||||
// Pass.
|
// Pass.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
@ -609,7 +616,7 @@ public final class DashStreamingTest {
|
|||||||
// 29.97 fps.
|
// 29.97 fps.
|
||||||
@Test
|
@Test
|
||||||
public void widevine29FpsH264FixedV23() throws Exception {
|
public void widevine29FpsH264FixedV23() throws Exception {
|
||||||
if (Util.SDK_INT < 23) {
|
if (Util.SDK_INT < 23 || GtsTestUtil.shouldSkipWidevineTest(testRule.getActivity())) {
|
||||||
// Pass.
|
// Pass.
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
|
@ -45,8 +45,6 @@ import com.google.android.exoplayer2.util.Util;
|
|||||||
|
|
||||||
// Widevine encrypted content manifests using different common encryption schemes.
|
// Widevine encrypted content manifests using different common encryption schemes.
|
||||||
public static final String WIDEVINE_SCHEME_CENC = BASE_URL_COMMON_ENCRYPTION + "tears-cenc.mpd";
|
public static final String WIDEVINE_SCHEME_CENC = BASE_URL_COMMON_ENCRYPTION + "tears-cenc.mpd";
|
||||||
public static final String WIDEVINE_SCHEME_CBC1 =
|
|
||||||
BASE_URL_COMMON_ENCRYPTION + "tears-aes-cbc1.mpd";
|
|
||||||
public static final String WIDEVINE_SCHEME_CBCS =
|
public static final String WIDEVINE_SCHEME_CBCS =
|
||||||
BASE_URL_COMMON_ENCRYPTION + "tears-aes-cbcs.mpd";
|
BASE_URL_COMMON_ENCRYPTION + "tears-aes-cbcs.mpd";
|
||||||
|
|
||||||
|
@ -100,7 +100,7 @@ public final class DashWidevineOfflineTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void widevineOfflineLicenseV22() throws Exception {
|
public void widevineOfflineLicenseV22() throws Exception {
|
||||||
if (Util.SDK_INT < 22) {
|
if (Util.SDK_INT < 22 || GtsTestUtil.shouldSkipWidevineTest(testRule.getActivity())) {
|
||||||
return; // Pass.
|
return; // Pass.
|
||||||
}
|
}
|
||||||
downloadLicense();
|
downloadLicense();
|
||||||
@ -113,7 +113,9 @@ public final class DashWidevineOfflineTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void widevineOfflineReleasedLicenseV22() throws Throwable {
|
public void widevineOfflineReleasedLicenseV22() throws Throwable {
|
||||||
if (Util.SDK_INT < 22 || Util.SDK_INT > 28) {
|
if (Util.SDK_INT < 22
|
||||||
|
|| Util.SDK_INT > 28
|
||||||
|
|| GtsTestUtil.shouldSkipWidevineTest(testRule.getActivity())) {
|
||||||
return; // Pass.
|
return; // Pass.
|
||||||
}
|
}
|
||||||
downloadLicense();
|
downloadLicense();
|
||||||
@ -136,7 +138,7 @@ public final class DashWidevineOfflineTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void widevineOfflineReleasedLicenseV29() throws Throwable {
|
public void widevineOfflineReleasedLicenseV29() throws Throwable {
|
||||||
if (Util.SDK_INT < 29) {
|
if (Util.SDK_INT < 29 || GtsTestUtil.shouldSkipWidevineTest(testRule.getActivity())) {
|
||||||
return; // Pass.
|
return; // Pass.
|
||||||
}
|
}
|
||||||
downloadLicense();
|
downloadLicense();
|
||||||
@ -158,7 +160,7 @@ public final class DashWidevineOfflineTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void widevineOfflineExpiredLicenseV22() throws Exception {
|
public void widevineOfflineExpiredLicenseV22() throws Exception {
|
||||||
if (Util.SDK_INT < 22) {
|
if (Util.SDK_INT < 22 || GtsTestUtil.shouldSkipWidevineTest(testRule.getActivity())) {
|
||||||
return; // Pass.
|
return; // Pass.
|
||||||
}
|
}
|
||||||
downloadLicense();
|
downloadLicense();
|
||||||
@ -188,7 +190,7 @@ public final class DashWidevineOfflineTest {
|
|||||||
|
|
||||||
@Test
|
@Test
|
||||||
public void widevineOfflineLicenseExpiresOnPauseV22() throws Exception {
|
public void widevineOfflineLicenseExpiresOnPauseV22() throws Exception {
|
||||||
if (Util.SDK_INT < 22) {
|
if (Util.SDK_INT < 22 || GtsTestUtil.shouldSkipWidevineTest(testRule.getActivity())) {
|
||||||
return; // Pass.
|
return; // Pass.
|
||||||
}
|
}
|
||||||
downloadLicense();
|
downloadLicense();
|
||||||
@ -198,7 +200,7 @@ public final class DashWidevineOfflineTest {
|
|||||||
offlineLicenseHelper.getLicenseDurationRemainingSec(offlineLicenseKeySetId);
|
offlineLicenseHelper.getLicenseDurationRemainingSec(offlineLicenseKeySetId);
|
||||||
long licenseDuration = licenseDurationRemainingSec.first;
|
long licenseDuration = licenseDurationRemainingSec.first;
|
||||||
assertWithMessage(
|
assertWithMessage(
|
||||||
"License duration should be less than 30 sec. " + "Server settings might have changed.")
|
"License duration should be less than 30 sec. Server settings might have changed.")
|
||||||
.that(licenseDuration < 30)
|
.that(licenseDuration < 30)
|
||||||
.isTrue();
|
.isTrue();
|
||||||
ActionSchedule schedule = new ActionSchedule.Builder(TAG)
|
ActionSchedule schedule = new ActionSchedule.Builder(TAG)
|
||||||
|
@ -24,6 +24,7 @@ import android.media.MediaFormat;
|
|||||||
import android.os.Handler;
|
import android.os.Handler;
|
||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import androidx.annotation.RequiresApi;
|
import androidx.annotation.RequiresApi;
|
||||||
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.DefaultRenderersFactory;
|
import com.google.android.exoplayer2.DefaultRenderersFactory;
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
import com.google.android.exoplayer2.Format;
|
import com.google.android.exoplayer2.Format;
|
||||||
@ -84,13 +85,14 @@ import java.util.ArrayList;
|
|||||||
|
|
||||||
private final long[] timestampsList;
|
private final long[] timestampsList;
|
||||||
private final ArrayDeque<Long> inputFormatChangeTimesUs;
|
private final ArrayDeque<Long> inputFormatChangeTimesUs;
|
||||||
private final boolean enableMediaFormatChangeTimeCheck;
|
|
||||||
|
private boolean skipToPositionBeforeRenderingFirstFrame;
|
||||||
|
private boolean shouldMediaFormatChangeTimesBeChecked;
|
||||||
|
|
||||||
private int startIndex;
|
private int startIndex;
|
||||||
private int queueSize;
|
private int queueSize;
|
||||||
private int bufferCount;
|
private int bufferCount;
|
||||||
private int minimumInsertIndex;
|
private int minimumInsertIndex;
|
||||||
private boolean skipToPositionBeforeRenderingFirstFrame;
|
|
||||||
private boolean inputFormatChanged;
|
private boolean inputFormatChanged;
|
||||||
private boolean outputMediaFormatChanged;
|
private boolean outputMediaFormatChanged;
|
||||||
|
|
||||||
@ -112,10 +114,6 @@ import java.util.ArrayList;
|
|||||||
maxDroppedFrameCountToNotify);
|
maxDroppedFrameCountToNotify);
|
||||||
timestampsList = new long[ARRAY_SIZE];
|
timestampsList = new long[ARRAY_SIZE];
|
||||||
inputFormatChangeTimesUs = new ArrayDeque<>();
|
inputFormatChangeTimesUs = new ArrayDeque<>();
|
||||||
|
|
||||||
// As per [Internal ref: b/149818050, b/149751672], MediaFormat changes can occur early for
|
|
||||||
// SDK 29 and 30. Should be fixed for SDK 31 onwards.
|
|
||||||
enableMediaFormatChangeTimeCheck = Util.SDK_INT < 29 || Util.SDK_INT >= 31;
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -137,6 +135,10 @@ import java.util.ArrayList;
|
|||||||
// frames up to the current playback position [Internal: b/66494991].
|
// frames up to the current playback position [Internal: b/66494991].
|
||||||
skipToPositionBeforeRenderingFirstFrame = getState() == Renderer.STATE_STARTED;
|
skipToPositionBeforeRenderingFirstFrame = getState() == Renderer.STATE_STARTED;
|
||||||
super.configureCodec(codecInfo, codecAdapter, format, crypto, operatingRate);
|
super.configureCodec(codecInfo, codecAdapter, format, crypto, operatingRate);
|
||||||
|
|
||||||
|
// Output MediaFormat changes are known to occur too early until API 30 (see [internal:
|
||||||
|
// b/149818050, b/149751672]).
|
||||||
|
shouldMediaFormatChangeTimesBeChecked = Util.SDK_INT > 30;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -247,10 +249,12 @@ import java.util.ArrayList;
|
|||||||
}
|
}
|
||||||
|
|
||||||
if (outputMediaFormatChanged) {
|
if (outputMediaFormatChanged) {
|
||||||
long inputFormatChangeTimeUs = inputFormatChangeTimesUs.remove();
|
long inputFormatChangeTimeUs =
|
||||||
|
inputFormatChangeTimesUs.isEmpty() ? C.TIME_UNSET : inputFormatChangeTimesUs.remove();
|
||||||
outputMediaFormatChanged = false;
|
outputMediaFormatChanged = false;
|
||||||
|
|
||||||
if (enableMediaFormatChangeTimeCheck && presentationTimeUs != inputFormatChangeTimeUs) {
|
if (shouldMediaFormatChangeTimesBeChecked
|
||||||
|
&& presentationTimeUs != inputFormatChangeTimeUs) {
|
||||||
throw new IllegalStateException(
|
throw new IllegalStateException(
|
||||||
"Expected output MediaFormat change timestamp ("
|
"Expected output MediaFormat change timestamp ("
|
||||||
+ presentationTimeUs
|
+ presentationTimeUs
|
||||||
|
@ -0,0 +1,50 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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 com.google.android.exoplayer2.playbacktests.gts;
|
||||||
|
|
||||||
|
import static com.google.android.exoplayer2.C.WIDEVINE_UUID;
|
||||||
|
|
||||||
|
import android.content.Context;
|
||||||
|
import android.content.pm.PackageManager;
|
||||||
|
import android.media.MediaDrm;
|
||||||
|
|
||||||
|
/** Utility methods for GTS tests. */
|
||||||
|
public final class GtsTestUtil {
|
||||||
|
|
||||||
|
private GtsTestUtil() {}
|
||||||
|
|
||||||
|
/** Returns true if the device doesn't support Widevine and this is permitted. */
|
||||||
|
public static boolean shouldSkipWidevineTest(Context context) {
|
||||||
|
if (isGmsInstalled(context)) {
|
||||||
|
// GMS devices are required to support Widevine.
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
// For non-GMS devices Widevine is optional.
|
||||||
|
return !MediaDrm.isCryptoSchemeSupported(WIDEVINE_UUID);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static boolean isGmsInstalled(Context context) {
|
||||||
|
try {
|
||||||
|
context
|
||||||
|
.getPackageManager()
|
||||||
|
.getPackageInfo("com.google.android.gms", PackageManager.GET_SIGNATURES);
|
||||||
|
} catch (PackageManager.NameNotFoundException e) {
|
||||||
|
return false;
|
||||||
|
}
|
||||||
|
return true;
|
||||||
|
}
|
||||||
|
}
|
81
testdata/src/test/assets/playbackdumps/mp4/sample.mp4.dump
vendored
Normal file
81
testdata/src/test/assets/playbackdumps/mp4/sample.mp4.dump
vendored
Normal file
@ -0,0 +1,81 @@
|
|||||||
|
MediaCodec (audio/mp4a-latm):
|
||||||
|
buffers.length = 46
|
||||||
|
buffers[0] = length 23, hash 47DE9131
|
||||||
|
buffers[1] = length 6, hash 31EC5206
|
||||||
|
buffers[2] = length 148, hash 894A176B
|
||||||
|
buffers[3] = length 189, hash CEF235A1
|
||||||
|
buffers[4] = length 205, hash BBF5F7B0
|
||||||
|
buffers[5] = length 210, hash F278B193
|
||||||
|
buffers[6] = length 210, hash 82DA1589
|
||||||
|
buffers[7] = length 207, hash 5BE231DF
|
||||||
|
buffers[8] = length 225, hash 18819EE1
|
||||||
|
buffers[9] = length 215, hash CA7FA67B
|
||||||
|
buffers[10] = length 211, hash 581A1C18
|
||||||
|
buffers[11] = length 216, hash ADB88187
|
||||||
|
buffers[12] = length 229, hash 2E8BA4DC
|
||||||
|
buffers[13] = length 232, hash 22F0C510
|
||||||
|
buffers[14] = length 235, hash 867AD0DC
|
||||||
|
buffers[15] = length 231, hash 84E823A8
|
||||||
|
buffers[16] = length 226, hash 1BEF3A95
|
||||||
|
buffers[17] = length 216, hash EAA345AE
|
||||||
|
buffers[18] = length 229, hash 6957411F
|
||||||
|
buffers[19] = length 219, hash 41275022
|
||||||
|
buffers[20] = length 241, hash 6495DF96
|
||||||
|
buffers[21] = length 228, hash 63D95906
|
||||||
|
buffers[22] = length 238, hash 34F676F9
|
||||||
|
buffers[23] = length 234, hash E5CBC045
|
||||||
|
buffers[24] = length 231, hash 5FC43661
|
||||||
|
buffers[25] = length 217, hash 682708ED
|
||||||
|
buffers[26] = length 239, hash D43780FC
|
||||||
|
buffers[27] = length 243, hash C5E17980
|
||||||
|
buffers[28] = length 231, hash AC5837BA
|
||||||
|
buffers[29] = length 230, hash 169EE895
|
||||||
|
buffers[30] = length 238, hash C48FF3F1
|
||||||
|
buffers[31] = length 225, hash 531E4599
|
||||||
|
buffers[32] = length 232, hash CB3C6B8D
|
||||||
|
buffers[33] = length 243, hash F8C94C7
|
||||||
|
buffers[34] = length 232, hash A646A7D0
|
||||||
|
buffers[35] = length 237, hash E8B787A5
|
||||||
|
buffers[36] = length 228, hash 3FA7A29F
|
||||||
|
buffers[37] = length 235, hash B9B33B0A
|
||||||
|
buffers[38] = length 264, hash 71A4869E
|
||||||
|
buffers[39] = length 257, hash D049B54C
|
||||||
|
buffers[40] = length 227, hash 66757231
|
||||||
|
buffers[41] = length 227, hash BD374F1B
|
||||||
|
buffers[42] = length 235, hash 999477F6
|
||||||
|
buffers[43] = length 229, hash FFF98DF0
|
||||||
|
buffers[44] = length 6, hash 31B22286
|
||||||
|
buffers[45] = length 0, hash 1
|
||||||
|
MediaCodec (video/avc):
|
||||||
|
buffers.length = 31
|
||||||
|
buffers[0] = length 36692, hash D216076E
|
||||||
|
buffers[1] = length 5312, hash D45D3CA0
|
||||||
|
buffers[2] = length 599, hash 1BE7812D
|
||||||
|
buffers[3] = length 7735, hash 4490F110
|
||||||
|
buffers[4] = length 987, hash 560B5036
|
||||||
|
buffers[5] = length 673, hash ED7CD8C7
|
||||||
|
buffers[6] = length 523, hash 3020DF50
|
||||||
|
buffers[7] = length 6061, hash 736C72B2
|
||||||
|
buffers[8] = length 992, hash FE132F23
|
||||||
|
buffers[9] = length 623, hash 5B2C1816
|
||||||
|
buffers[10] = length 421, hash 742E69C1
|
||||||
|
buffers[11] = length 4899, hash F72F86A1
|
||||||
|
buffers[12] = length 568, hash 519A8E50
|
||||||
|
buffers[13] = length 620, hash 3990AA39
|
||||||
|
buffers[14] = length 5450, hash F06EC4AA
|
||||||
|
buffers[15] = length 1051, hash 92DFA63A
|
||||||
|
buffers[16] = length 874, hash 69587FB4
|
||||||
|
buffers[17] = length 781, hash 36BE495B
|
||||||
|
buffers[18] = length 4725, hash AC0C8CD3
|
||||||
|
buffers[19] = length 1022, hash 5D8BFF34
|
||||||
|
buffers[20] = length 790, hash 99413A99
|
||||||
|
buffers[21] = length 610, hash 5E129290
|
||||||
|
buffers[22] = length 2751, hash 769974CB
|
||||||
|
buffers[23] = length 745, hash B78A477A
|
||||||
|
buffers[24] = length 621, hash CF741E7A
|
||||||
|
buffers[25] = length 505, hash 1DB4894E
|
||||||
|
buffers[26] = length 1268, hash C15348DC
|
||||||
|
buffers[27] = length 880, hash C2DE85D0
|
||||||
|
buffers[28] = length 530, hash C98BC6A8
|
||||||
|
buffers[29] = length 568, hash 4FE5C8EA
|
||||||
|
buffers[30] = length 0, hash 1
|
19
testdata/src/test/assets/playbackdumps/ts/sample_scte35.ts.dump
vendored
Normal file
19
testdata/src/test/assets/playbackdumps/ts/sample_scte35.ts.dump
vendored
Normal file
@ -0,0 +1,19 @@
|
|||||||
|
MediaCodec (audio/mpeg-L2):
|
||||||
|
buffers.length = 5
|
||||||
|
buffers[0] = length 1253, hash 727FD1C6
|
||||||
|
buffers[1] = length 1254, hash 73FB07B8
|
||||||
|
buffers[2] = length 1254, hash 73FB07B8
|
||||||
|
buffers[3] = length 1254, hash 73FB07B8
|
||||||
|
buffers[4] = length 0, hash 1
|
||||||
|
MediaCodec (video/mpeg2):
|
||||||
|
buffers.length = 3
|
||||||
|
buffers[0] = length 20711, hash 34341E8
|
||||||
|
buffers[1] = length 18112, hash EC44B35B
|
||||||
|
buffers[2] = length 0, hash 1
|
||||||
|
MetadataOutput:
|
||||||
|
Metadata[0]:
|
||||||
|
entry[0] = SpliceInsertCommand
|
||||||
|
Metadata[1]:
|
||||||
|
entry[0] = SpliceInsertCommand
|
||||||
|
Metadata[2]:
|
||||||
|
entry[0] = SpliceInsertCommand
|
@ -24,6 +24,7 @@ dependencies {
|
|||||||
compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
|
compileOnly 'org.jetbrains.kotlin:kotlin-annotations-jvm:' + kotlinAnnotationsVersion
|
||||||
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
|
implementation 'androidx.annotation:annotation:' + androidxAnnotationVersion
|
||||||
implementation project(modulePrefix + 'library-core')
|
implementation project(modulePrefix + 'library-core')
|
||||||
|
implementation 'org.robolectric:robolectric:' + robolectricVersion
|
||||||
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
|
testImplementation 'org.robolectric:robolectric:' + robolectricVersion
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -22,6 +22,7 @@ import com.google.android.exoplayer2.C;
|
|||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
import com.google.android.exoplayer2.ExoPlayer;
|
import com.google.android.exoplayer2.ExoPlayer;
|
||||||
import com.google.android.exoplayer2.IllegalSeekPositionException;
|
import com.google.android.exoplayer2.IllegalSeekPositionException;
|
||||||
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
import com.google.android.exoplayer2.Player;
|
import com.google.android.exoplayer2.Player;
|
||||||
import com.google.android.exoplayer2.PlayerMessage;
|
import com.google.android.exoplayer2.PlayerMessage;
|
||||||
import com.google.android.exoplayer2.PlayerMessage.Target;
|
import com.google.android.exoplayer2.PlayerMessage.Target;
|
||||||
@ -608,26 +609,26 @@ public abstract class Action {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
/** Calls {@link Player#setPlaybackSpeed(float)}. */
|
/** Calls {@link Player#setPlaybackParameters(PlaybackParameters)}. */
|
||||||
public static final class SetPlaybackSpeed extends Action {
|
public static final class SetPlaybackParameters extends Action {
|
||||||
|
|
||||||
private final float playbackSpeed;
|
private final PlaybackParameters playbackParameters;
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Creates a set playback speed action instance.
|
* Creates a set playback parameters action instance.
|
||||||
*
|
*
|
||||||
* @param tag A tag to use for logging.
|
* @param tag A tag to use for logging.
|
||||||
* @param playbackSpeed The playback speed.
|
* @param playbackParameters The playback parameters.
|
||||||
*/
|
*/
|
||||||
public SetPlaybackSpeed(String tag, float playbackSpeed) {
|
public SetPlaybackParameters(String tag, PlaybackParameters playbackParameters) {
|
||||||
super(tag, "SetPlaybackSpeed:" + playbackSpeed);
|
super(tag, "SetPlaybackParameters:" + playbackParameters);
|
||||||
this.playbackSpeed = playbackSpeed;
|
this.playbackParameters = playbackParameters;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
protected void doActionImpl(
|
protected void doActionImpl(
|
||||||
SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
|
SimpleExoPlayer player, DefaultTrackSelector trackSelector, @Nullable Surface surface) {
|
||||||
player.setPlaybackSpeed(playbackSpeed);
|
player.setPlaybackParameters(playbackParameters);
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -20,6 +20,7 @@ import android.view.Surface;
|
|||||||
import androidx.annotation.Nullable;
|
import androidx.annotation.Nullable;
|
||||||
import com.google.android.exoplayer2.C;
|
import com.google.android.exoplayer2.C;
|
||||||
import com.google.android.exoplayer2.ExoPlaybackException;
|
import com.google.android.exoplayer2.ExoPlaybackException;
|
||||||
|
import com.google.android.exoplayer2.PlaybackParameters;
|
||||||
import com.google.android.exoplayer2.Player;
|
import com.google.android.exoplayer2.Player;
|
||||||
import com.google.android.exoplayer2.PlayerMessage;
|
import com.google.android.exoplayer2.PlayerMessage;
|
||||||
import com.google.android.exoplayer2.PlayerMessage.Target;
|
import com.google.android.exoplayer2.PlayerMessage.Target;
|
||||||
@ -35,7 +36,7 @@ import com.google.android.exoplayer2.testutil.Action.Seek;
|
|||||||
import com.google.android.exoplayer2.testutil.Action.SendMessages;
|
import com.google.android.exoplayer2.testutil.Action.SendMessages;
|
||||||
import com.google.android.exoplayer2.testutil.Action.SetAudioAttributes;
|
import com.google.android.exoplayer2.testutil.Action.SetAudioAttributes;
|
||||||
import com.google.android.exoplayer2.testutil.Action.SetPlayWhenReady;
|
import com.google.android.exoplayer2.testutil.Action.SetPlayWhenReady;
|
||||||
import com.google.android.exoplayer2.testutil.Action.SetPlaybackSpeed;
|
import com.google.android.exoplayer2.testutil.Action.SetPlaybackParameters;
|
||||||
import com.google.android.exoplayer2.testutil.Action.SetRendererDisabled;
|
import com.google.android.exoplayer2.testutil.Action.SetRendererDisabled;
|
||||||
import com.google.android.exoplayer2.testutil.Action.SetRepeatMode;
|
import com.google.android.exoplayer2.testutil.Action.SetRepeatMode;
|
||||||
import com.google.android.exoplayer2.testutil.Action.SetShuffleModeEnabled;
|
import com.google.android.exoplayer2.testutil.Action.SetShuffleModeEnabled;
|
||||||
@ -214,14 +215,14 @@ public final class ActionSchedule {
|
|||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
* Schedules a playback speed setting action.
|
* Schedules a playback parameters setting action.
|
||||||
*
|
*
|
||||||
* @param playbackSpeed The playback speed to set.
|
* @param playbackParameters The playback parameters to set.
|
||||||
* @return The builder, for convenience.
|
* @return The builder, for convenience.
|
||||||
* @see Player#setPlaybackSpeed(float)
|
* @see Player#setPlaybackParameters(PlaybackParameters)
|
||||||
*/
|
*/
|
||||||
public Builder setPlaybackSpeed(float playbackSpeed) {
|
public Builder setPlaybackParameters(PlaybackParameters playbackParameters) {
|
||||||
return apply(new SetPlaybackSpeed(tag, playbackSpeed));
|
return apply(new SetPlaybackParameters(tag, playbackParameters));
|
||||||
}
|
}
|
||||||
|
|
||||||
/**
|
/**
|
||||||
|
@ -52,7 +52,7 @@ public abstract class ExoHostedTest implements AnalyticsListener, HostedTest {
|
|||||||
DefaultAudioSink.failOnSpuriousAudioTimestamp = true;
|
DefaultAudioSink.failOnSpuriousAudioTimestamp = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
public static final long MAX_PLAYING_TIME_DISCREPANCY_MS = 2000;
|
public static final long MAX_PLAYING_TIME_DISCREPANCY_MS = 5000;
|
||||||
public static final long EXPECTED_PLAYING_TIME_MEDIA_DURATION_MS = -2;
|
public static final long EXPECTED_PLAYING_TIME_MEDIA_DURATION_MS = -2;
|
||||||
public static final long EXPECTED_PLAYING_TIME_UNSET = -1;
|
public static final long EXPECTED_PLAYING_TIME_UNSET = -1;
|
||||||
|
|
||||||
|
@ -31,6 +31,7 @@ import com.google.android.exoplayer2.upstream.DefaultLoadErrorHandlingPolicy;
|
|||||||
import com.google.android.exoplayer2.upstream.TransferListener;
|
import com.google.android.exoplayer2.upstream.TransferListener;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import java.util.ArrayList;
|
import java.util.ArrayList;
|
||||||
import java.util.List;
|
import java.util.List;
|
||||||
import org.checkerframework.checker.nullness.compatqual.NullableType;
|
import org.checkerframework.checker.nullness.compatqual.NullableType;
|
||||||
@ -48,7 +49,7 @@ public class FakeAdaptiveMediaPeriod extends FakeMediaPeriod
|
|||||||
@Nullable private final TransferListener transferListener;
|
@Nullable private final TransferListener transferListener;
|
||||||
private final long durationUs;
|
private final long durationUs;
|
||||||
|
|
||||||
@MonotonicNonNull private Callback callback;
|
private @MonotonicNonNull Callback callback;
|
||||||
private ChunkSampleStream<FakeChunkSource>[] sampleStreams;
|
private ChunkSampleStream<FakeChunkSource>[] sampleStreams;
|
||||||
private SequenceableLoader sequenceableLoader;
|
private SequenceableLoader sequenceableLoader;
|
||||||
|
|
||||||
@ -99,7 +100,7 @@ public class FakeAdaptiveMediaPeriod extends FakeMediaPeriod
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
sampleStreams = newSampleStreamArray(validStreams.size());
|
sampleStreams = newSampleStreamArray(validStreams.size());
|
||||||
validStreams.toArray(sampleStreams);
|
Util.nullSafeListToArray(validStreams, sampleStreams);
|
||||||
this.sequenceableLoader = new CompositeSequenceableLoader(sampleStreams);
|
this.sequenceableLoader = new CompositeSequenceableLoader(sampleStreams);
|
||||||
return returnPositionUs;
|
return returnPositionUs;
|
||||||
}
|
}
|
||||||
|
@ -30,6 +30,7 @@ public class FakeAudioRenderer extends FakeRenderer {
|
|||||||
private final AudioRendererEventListener.EventDispatcher eventDispatcher;
|
private final AudioRendererEventListener.EventDispatcher eventDispatcher;
|
||||||
private final DecoderCounters decoderCounters;
|
private final DecoderCounters decoderCounters;
|
||||||
private boolean notifiedAudioSessionId;
|
private boolean notifiedAudioSessionId;
|
||||||
|
private boolean notifiedPositionAdvancing;
|
||||||
|
|
||||||
public FakeAudioRenderer(Handler handler, AudioRendererEventListener eventListener) {
|
public FakeAudioRenderer(Handler handler, AudioRendererEventListener eventListener) {
|
||||||
super(C.TRACK_TYPE_AUDIO);
|
super(C.TRACK_TYPE_AUDIO);
|
||||||
@ -43,6 +44,7 @@ public class FakeAudioRenderer extends FakeRenderer {
|
|||||||
super.onEnabled(joining, mayRenderStartOfStream);
|
super.onEnabled(joining, mayRenderStartOfStream);
|
||||||
eventDispatcher.enabled(decoderCounters);
|
eventDispatcher.enabled(decoderCounters);
|
||||||
notifiedAudioSessionId = false;
|
notifiedAudioSessionId = false;
|
||||||
|
notifiedPositionAdvancing = false;
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
@ -67,6 +69,10 @@ public class FakeAudioRenderer extends FakeRenderer {
|
|||||||
eventDispatcher.audioSessionId(/* audioSessionId= */ 1);
|
eventDispatcher.audioSessionId(/* audioSessionId= */ 1);
|
||||||
notifiedAudioSessionId = true;
|
notifiedAudioSessionId = true;
|
||||||
}
|
}
|
||||||
|
if (shouldProcess && !notifiedPositionAdvancing) {
|
||||||
|
eventDispatcher.positionAdvancing(System.currentTimeMillis());
|
||||||
|
notifiedPositionAdvancing = true;
|
||||||
|
}
|
||||||
return shouldProcess;
|
return shouldProcess;
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -217,8 +217,7 @@ public class FakeDataSource extends BaseDataSource {
|
|||||||
* this method.
|
* this method.
|
||||||
*/
|
*/
|
||||||
public final DataSpec[] getAndClearOpenedDataSpecs() {
|
public final DataSpec[] getAndClearOpenedDataSpecs() {
|
||||||
DataSpec[] dataSpecs = new DataSpec[openedDataSpecs.size()];
|
DataSpec[] dataSpecs = openedDataSpecs.toArray(new DataSpec[0]);
|
||||||
openedDataSpecs.toArray(dataSpecs);
|
|
||||||
openedDataSpecs.clear();
|
openedDataSpecs.clear();
|
||||||
return dataSpecs;
|
return dataSpecs;
|
||||||
}
|
}
|
||||||
|
@ -30,7 +30,6 @@ import com.google.android.exoplayer2.drm.DrmSessionManager;
|
|||||||
import com.google.android.exoplayer2.source.MediaSourceEventListener;
|
import com.google.android.exoplayer2.source.MediaSourceEventListener;
|
||||||
import com.google.android.exoplayer2.source.SampleStream;
|
import com.google.android.exoplayer2.source.SampleStream;
|
||||||
import com.google.android.exoplayer2.util.Assertions;
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
import com.google.android.exoplayer2.util.MimeTypes;
|
|
||||||
import com.google.android.exoplayer2.util.Util;
|
import com.google.android.exoplayer2.util.Util;
|
||||||
import com.google.common.collect.Iterables;
|
import com.google.common.collect.Iterables;
|
||||||
import java.io.IOException;
|
import java.io.IOException;
|
||||||
@ -274,10 +273,7 @@ public class FakeSampleStream implements SampleStream {
|
|||||||
@Nullable DrmSession previousSession = currentDrmSession;
|
@Nullable DrmSession previousSession = currentDrmSession;
|
||||||
Looper playbackLooper = Assertions.checkNotNull(Looper.myLooper());
|
Looper playbackLooper = Assertions.checkNotNull(Looper.myLooper());
|
||||||
currentDrmSession =
|
currentDrmSession =
|
||||||
newDrmInitData != null
|
drmSessionManager.acquireSession(playbackLooper, drmEventDispatcher, newFormat);
|
||||||
? drmSessionManager.acquireSession(playbackLooper, drmEventDispatcher, newFormat)
|
|
||||||
: drmSessionManager.acquirePlaceholderSession(
|
|
||||||
playbackLooper, MimeTypes.getTrackType(newFormat.sampleMimeType));
|
|
||||||
outputFormatHolder.drmSession = currentDrmSession;
|
outputFormatHolder.drmSession = currentDrmSession;
|
||||||
|
|
||||||
if (previousSession != null) {
|
if (previousSession != null) {
|
||||||
|
@ -0,0 +1,93 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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 com.google.android.exoplayer2.testutil;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.SimpleExoPlayer;
|
||||||
|
import com.google.android.exoplayer2.metadata.Metadata;
|
||||||
|
import com.google.android.exoplayer2.util.Assertions;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Class to capture output from a playback test.
|
||||||
|
*
|
||||||
|
* <p>Implements {@link Dumper.Dumpable} so the output can be easily dumped to a string for
|
||||||
|
* comparison against previous test runs.
|
||||||
|
*/
|
||||||
|
public final class PlaybackOutput implements Dumper.Dumpable {
|
||||||
|
|
||||||
|
private final ShadowMediaCodecConfig codecConfig;
|
||||||
|
|
||||||
|
// TODO: Add support for subtitles too
|
||||||
|
private final List<Metadata> metadatas;
|
||||||
|
|
||||||
|
private PlaybackOutput(SimpleExoPlayer player, ShadowMediaCodecConfig codecConfig) {
|
||||||
|
this.codecConfig = codecConfig;
|
||||||
|
|
||||||
|
metadatas = Collections.synchronizedList(new ArrayList<>());
|
||||||
|
// TODO: Consider passing playback position into MetadataOutput and TextOutput. Calling
|
||||||
|
// player.getCurrentPosition() inside onMetadata/Cues will likely be non-deterministic
|
||||||
|
// because renderer-thread != playback-thread.
|
||||||
|
player.addMetadataOutput(metadatas::add);
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Create an instance that captures the metadata and text output from {@code player} and the audio
|
||||||
|
* and video output via the {@link TeeCodec TeeCodecs} exposed by {@code mediaCodecConfig}.
|
||||||
|
*
|
||||||
|
* <p>Must be called <b>before</b> playback to ensure metadata and text output is captured
|
||||||
|
* correctly.
|
||||||
|
*
|
||||||
|
* @param player The {@link SimpleExoPlayer} to capture metadata and text output from.
|
||||||
|
* @param mediaCodecConfig The {@link ShadowMediaCodecConfig} to capture audio and video output
|
||||||
|
* from.
|
||||||
|
* @return A new instance that can be used to dump the playback output.
|
||||||
|
*/
|
||||||
|
public static PlaybackOutput register(
|
||||||
|
SimpleExoPlayer player, ShadowMediaCodecConfig mediaCodecConfig) {
|
||||||
|
return new PlaybackOutput(player, mediaCodecConfig);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dump(Dumper dumper) {
|
||||||
|
ImmutableMap<String, TeeCodec> codecs = codecConfig.getCodecs();
|
||||||
|
ImmutableList<String> mimeTypes = ImmutableList.sortedCopyOf(codecs.keySet());
|
||||||
|
for (String mimeType : mimeTypes) {
|
||||||
|
dumper.add(Assertions.checkNotNull(codecs.get(mimeType)));
|
||||||
|
}
|
||||||
|
|
||||||
|
dumpMetadata(dumper);
|
||||||
|
}
|
||||||
|
|
||||||
|
private void dumpMetadata(Dumper dumper) {
|
||||||
|
if (metadatas.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dumper.startBlock("MetadataOutput");
|
||||||
|
for (int i = 0; i < metadatas.size(); i++) {
|
||||||
|
dumper.startBlock("Metadata[" + i + "]");
|
||||||
|
Metadata metadata = metadatas.get(i);
|
||||||
|
for (int j = 0; j < metadata.length(); j++) {
|
||||||
|
dumper.add("entry[" + j + "]", metadata.get(j).getClass().getSimpleName());
|
||||||
|
}
|
||||||
|
dumper.endBlock();
|
||||||
|
}
|
||||||
|
dumper.endBlock();
|
||||||
|
}
|
||||||
|
}
|
@ -0,0 +1,133 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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 com.google.android.exoplayer2.testutil;
|
||||||
|
|
||||||
|
import android.media.MediaCodecInfo;
|
||||||
|
import android.media.MediaFormat;
|
||||||
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import com.google.common.collect.ImmutableMap;
|
||||||
|
import com.google.common.primitives.Ints;
|
||||||
|
import java.util.HashMap;
|
||||||
|
import java.util.List;
|
||||||
|
import java.util.Map;
|
||||||
|
import org.junit.rules.ExternalResource;
|
||||||
|
import org.robolectric.shadows.MediaCodecInfoBuilder;
|
||||||
|
import org.robolectric.shadows.ShadowMediaCodec;
|
||||||
|
import org.robolectric.shadows.ShadowMediaCodecList;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A JUnit @Rule to configure Roboelectric's {@link ShadowMediaCodec}.
|
||||||
|
*
|
||||||
|
* <p>Registers a {@link org.robolectric.shadows.ShadowMediaCodec.CodecConfig} for each audio/video
|
||||||
|
* MIME type known by ExoPlayer, and provides access to the bytes passed to these via {@link
|
||||||
|
* TeeCodec}.
|
||||||
|
*/
|
||||||
|
public final class ShadowMediaCodecConfig extends ExternalResource {
|
||||||
|
|
||||||
|
private final Map<String, TeeCodec> codecsByMimeType;
|
||||||
|
|
||||||
|
private ShadowMediaCodecConfig() {
|
||||||
|
this.codecsByMimeType = new HashMap<>();
|
||||||
|
}
|
||||||
|
|
||||||
|
public static ShadowMediaCodecConfig forAllSupportedMimeTypes() {
|
||||||
|
return new ShadowMediaCodecConfig();
|
||||||
|
}
|
||||||
|
|
||||||
|
public ImmutableMap<String, TeeCodec> getCodecs() {
|
||||||
|
return ImmutableMap.copyOf(codecsByMimeType);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void before() throws Throwable {
|
||||||
|
// Video codecs
|
||||||
|
MediaCodecInfo.CodecProfileLevel avcProfileLevel =
|
||||||
|
createProfileLevel(
|
||||||
|
MediaCodecInfo.CodecProfileLevel.AVCProfileHigh,
|
||||||
|
MediaCodecInfo.CodecProfileLevel.AVCLevel62);
|
||||||
|
configureCodec(
|
||||||
|
/* codecName= */ "exotest.video.avc",
|
||||||
|
MimeTypes.VIDEO_H264,
|
||||||
|
ImmutableList.of(avcProfileLevel),
|
||||||
|
ImmutableList.of(MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible));
|
||||||
|
MediaCodecInfo.CodecProfileLevel mpeg2ProfileLevel =
|
||||||
|
createProfileLevel(
|
||||||
|
MediaCodecInfo.CodecProfileLevel.MPEG2ProfileMain,
|
||||||
|
MediaCodecInfo.CodecProfileLevel.MPEG2LevelML);
|
||||||
|
configureCodec(
|
||||||
|
/* codecName= */ "exotest.video.mpeg2",
|
||||||
|
MimeTypes.VIDEO_MPEG2,
|
||||||
|
ImmutableList.of(mpeg2ProfileLevel),
|
||||||
|
ImmutableList.of(MediaCodecInfo.CodecCapabilities.COLOR_FormatYUV420Flexible));
|
||||||
|
|
||||||
|
// Audio codecs
|
||||||
|
configureCodec("exotest.audio.aac", MimeTypes.AUDIO_AAC);
|
||||||
|
configureCodec("exotest.audio.mpegl2", MimeTypes.AUDIO_MPEG_L2);
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
protected void after() {
|
||||||
|
codecsByMimeType.clear();
|
||||||
|
ShadowMediaCodecList.reset();
|
||||||
|
ShadowMediaCodec.clearCodecs();
|
||||||
|
}
|
||||||
|
|
||||||
|
private void configureCodec(String codecName, String mimeType) {
|
||||||
|
configureCodec(
|
||||||
|
codecName,
|
||||||
|
mimeType,
|
||||||
|
/* profileLevels= */ ImmutableList.of(),
|
||||||
|
/* colorFormats= */ ImmutableList.of());
|
||||||
|
}
|
||||||
|
|
||||||
|
private void configureCodec(
|
||||||
|
String codecName,
|
||||||
|
String mimeType,
|
||||||
|
List<MediaCodecInfo.CodecProfileLevel> profileLevels,
|
||||||
|
List<Integer> colorFormats) {
|
||||||
|
MediaFormat mediaFormat = new MediaFormat();
|
||||||
|
mediaFormat.setString(MediaFormat.KEY_MIME, mimeType);
|
||||||
|
MediaCodecInfoBuilder.CodecCapabilitiesBuilder capabilities =
|
||||||
|
MediaCodecInfoBuilder.CodecCapabilitiesBuilder.newBuilder().setMediaFormat(mediaFormat);
|
||||||
|
if (!profileLevels.isEmpty()) {
|
||||||
|
capabilities.setProfileLevels(profileLevels.toArray(new MediaCodecInfo.CodecProfileLevel[0]));
|
||||||
|
}
|
||||||
|
if (!colorFormats.isEmpty()) {
|
||||||
|
capabilities.setColorFormats(Ints.toArray(colorFormats));
|
||||||
|
}
|
||||||
|
ShadowMediaCodecList.addCodec(
|
||||||
|
MediaCodecInfoBuilder.newBuilder()
|
||||||
|
.setName(codecName)
|
||||||
|
.setCapabilities(capabilities.build())
|
||||||
|
.build());
|
||||||
|
// TODO: Update ShadowMediaCodec to consider the MediaFormat.KEY_MAX_INPUT_SIZE value passed
|
||||||
|
// to configure() so we don't have to specify large buffers here.
|
||||||
|
TeeCodec codec = new TeeCodec(mimeType);
|
||||||
|
ShadowMediaCodec.addDecoder(
|
||||||
|
codecName,
|
||||||
|
new ShadowMediaCodec.CodecConfig(
|
||||||
|
/* inputBufferSize= */ 50_000, /* outputBufferSize= */ 50_000, codec));
|
||||||
|
codecsByMimeType.put(mimeType, codec);
|
||||||
|
}
|
||||||
|
|
||||||
|
private static MediaCodecInfo.CodecProfileLevel createProfileLevel(int profile, int level) {
|
||||||
|
MediaCodecInfo.CodecProfileLevel profileLevel = new MediaCodecInfo.CodecProfileLevel();
|
||||||
|
profileLevel.profile = profile;
|
||||||
|
profileLevel.level = level;
|
||||||
|
return profileLevel;
|
||||||
|
}
|
||||||
|
}
|
@ -314,32 +314,16 @@ public abstract class StubExoPlayer extends BasePlayer implements ExoPlayer {
|
|||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Use {@link #setPlaybackSpeed(float)} instead. */
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
@Deprecated
|
|
||||||
@Override
|
@Override
|
||||||
public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) {
|
public void setPlaybackParameters(@Nullable PlaybackParameters playbackParameters) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
/** @deprecated Use {@link #getPlaybackSpeed()} instead. */
|
|
||||||
@SuppressWarnings("deprecation")
|
|
||||||
@Deprecated
|
|
||||||
@Override
|
@Override
|
||||||
public PlaybackParameters getPlaybackParameters() {
|
public PlaybackParameters getPlaybackParameters() {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
}
|
}
|
||||||
|
|
||||||
@Override
|
|
||||||
public void setPlaybackSpeed(float playbackSpeed) {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
|
||||||
public float getPlaybackSpeed() {
|
|
||||||
throw new UnsupportedOperationException();
|
|
||||||
}
|
|
||||||
|
|
||||||
@Override
|
@Override
|
||||||
public void setSeekParameters(@Nullable SeekParameters seekParameters) {
|
public void setSeekParameters(@Nullable SeekParameters seekParameters) {
|
||||||
throw new UnsupportedOperationException();
|
throw new UnsupportedOperationException();
|
||||||
|
@ -0,0 +1,80 @@
|
|||||||
|
/*
|
||||||
|
* Copyright (C) 2020 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 com.google.android.exoplayer2.testutil;
|
||||||
|
|
||||||
|
import com.google.android.exoplayer2.util.MimeTypes;
|
||||||
|
import com.google.common.collect.ImmutableList;
|
||||||
|
import java.nio.ByteBuffer;
|
||||||
|
import java.util.ArrayList;
|
||||||
|
import java.util.Collections;
|
||||||
|
import java.util.List;
|
||||||
|
import org.robolectric.shadows.ShadowMediaCodec;
|
||||||
|
|
||||||
|
/**
|
||||||
|
* A {@link ShadowMediaCodec.CodecConfig.Codec} for Robolectric's {@link ShadowMediaCodec} that
|
||||||
|
* records the contents of buffers passed to it before copying the contents into the output buffer.
|
||||||
|
*
|
||||||
|
* <p>This also implements {@link Dumper.Dumpable} so the recorded buffers can be written out to a
|
||||||
|
* dump file.
|
||||||
|
*/
|
||||||
|
public final class TeeCodec implements ShadowMediaCodec.CodecConfig.Codec, Dumper.Dumpable {
|
||||||
|
|
||||||
|
private final String mimeType;
|
||||||
|
private final List<byte[]> receivedBuffers;
|
||||||
|
|
||||||
|
public TeeCodec(String mimeType) {
|
||||||
|
this.mimeType = mimeType;
|
||||||
|
this.receivedBuffers = Collections.synchronizedList(new ArrayList<>());
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void process(ByteBuffer in, ByteBuffer out) {
|
||||||
|
byte[] bytes = new byte[in.remaining()];
|
||||||
|
in.get(bytes);
|
||||||
|
receivedBuffers.add(bytes);
|
||||||
|
|
||||||
|
if (!MimeTypes.isAudio(mimeType)) {
|
||||||
|
// Don't output audio bytes, because ShadowAudioTrack doesn't advance the playback position so
|
||||||
|
// playback never completes.
|
||||||
|
// TODO: Update ShadowAudioTrack to advance the playback position in a realistic way.
|
||||||
|
out.put(bytes);
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@Override
|
||||||
|
public void dump(Dumper dumper) {
|
||||||
|
if (receivedBuffers.isEmpty()) {
|
||||||
|
return;
|
||||||
|
}
|
||||||
|
dumper.startBlock("MediaCodec (" + mimeType + ")");
|
||||||
|
dumper.add("buffers.length", receivedBuffers.size());
|
||||||
|
for (int i = 0; i < receivedBuffers.size(); i++) {
|
||||||
|
dumper.add("buffers[" + i + "]", receivedBuffers.get(i));
|
||||||
|
}
|
||||||
|
|
||||||
|
dumper.endBlock();
|
||||||
|
}
|
||||||
|
|
||||||
|
/**
|
||||||
|
* Return the buffers received by this codec.
|
||||||
|
*
|
||||||
|
* <p>The list is sorted in the order the buffers were passed to {@link #process(ByteBuffer,
|
||||||
|
* ByteBuffer)}.
|
||||||
|
*/
|
||||||
|
public ImmutableList<byte[]> getReceivedBuffers() {
|
||||||
|
return ImmutableList.copyOf(receivedBuffers);
|
||||||
|
}
|
||||||
|
}
|
Loading…
x
Reference in New Issue
Block a user