189 Commits

Author SHA1 Message Date
tianyifeng
d58b4fd6a6 Handle the bitmap loading result with applicationHandler
Before this change, the bitmap loading result with mainHandler, in which we set the metadata to `MediaSessionCompat`. However, the `MediaSessionCompat` is not thread safe, all calls should be made from the same thread. In the other calls to `MediaSessionCompat`, we ensure that they are on the application thread (which may be or may not be main thread), so we should do the same for `setMetadata` when bitmap arrives.

Also removes a comment in `DefaultMediaNotificationProvider` as bitmap request caching is already moved to CacheBitmapLoader.

PiperOrigin-RevId: 490524209
(cherry picked from commit 80927260fd46413b7d1efafed72360b10049af2a)
2023-01-25 17:41:00 +00:00
bachinger
8b0c0761f3 Exclude tracks from PlayerInfo if not changed
This change includes a change in the `IMediaController.aidl` file and needs
to provide backwards compatibility for when a client connects that is of an older or
newer version of the current service implementation.

This CL proposes to create a new AIDL method `onPlayerInfoChangedWithExtensions`
that is easier to extend in the future because it does use an `Bundle` rather than
primitives. A `Bundle` can be changed in a backward/forwards compatible way
in case we need further changes.

The compatibility handling is provided in `MediaSessionStub` and `MediaControllerStub`. The approach is not based on specific AIDL/Binder features but implemented fully in application code.

Issue: androidx/media#102
#minor-release
PiperOrigin-RevId: 490483068
(cherry picked from commit 3d8c52f28d5d3ef04c14868e15036563a9fc662d)
2023-01-25 17:41:00 +00:00
tonihei
9829ff3d4c Add helper method to convert platform session token to Media3 token
This avoids that apps have to depend on the legacy compat support
library when they want to make this conversion.

Also add a version to both helper methods that takes a Looper to
give apps the option to use an existing Looper, which should be
much faster than spinning up a new thread for every method call.

Issue: androidx/media#171
PiperOrigin-RevId: 490441913
(cherry picked from commit 03f0b53cf823bb4878f884c055b550b52b9b57ab)
2023-01-25 17:40:56 +00:00
bachinger
782a69e38c Migrate BROWSER_ROOT_HINTS_KEY_ROOT_CHILDREN_SUPPORTED_FLAGS to Media3
PiperOrigin-RevId: 490376734
(cherry picked from commit 1803d1cdb8cf429c3d0a3fdbbecbad25145db8c4)
2023-01-25 17:38:34 +00:00
tianyifeng
d3d99f0194 Load bitmaps for MediaSessionCompat.QueueItem.
When receiving the `onTimelineChanged` callback, we convert the timeline to the list of `QueueItem`s, where decoding a bitmap is needed for building each of the `QueueItem`s. The strategy is similar to what we did in <unknown commit> for list of `MediaBrowserCompat.MediaItem` - set the queue item list until the bitmaps decoding for all the `MediaItem`s are completed.

PiperOrigin-RevId: 490283587
(cherry picked from commit 8ce1213ddddb98e0483610cfeaeba3daa5ad9a78)
2023-01-25 17:38:34 +00:00
tonihei
fa6b8fe06d Do not require package visibility when obtaining SessionTokens
The only reason this is required at the moment is to set the
process UID field in the token, that is supposed to make it easier
for controller apps to identify the session. However, if this
visibility is not provided, it shouldn't stop us from creating
the controller for this session.

Also docuement more clearly what UID means in this context.

PiperOrigin-RevId: 490184508
(cherry picked from commit c41a5c842080a7e75b9d92acc06d583bd20c7abb)
2023-01-25 17:37:32 +00:00
tonihei
f4f801a809 Do not require package visibility when connecting to a Media3 session
When we currently call SessionToken.createSessionToken with a legacy
token, we call the package manager to get the process UID. This
requires visiblity to the target package, which may not be available
unless the target runs a service known to the controller app.

However, when connecting to a Media3, this UID doesn't have to be
known, so we can move the call closer to where it's needed to
avoid the unncessary visibility check.

In addition, a legacy session may reply with unknown result code
to the session token request, which we should handle as well.

One of the constructor can be removed since it was only used from
a test.

PiperOrigin-RevId: 489917706
(cherry picked from commit 2fd4aac310787d1a57207b5142a0ab08d5e1a2a5)
2023-01-25 17:37:31 +00:00
tonihei
91c51fe94d Mark broadcast receivers as not exported
They are called from the system only and don't need to be exported
to be visible to other apps.

PiperOrigin-RevId: 489210264
(cherry picked from commit 22ccc1a1286803868970fb2b1eafe63e9c669a5c)
2023-01-25 17:37:31 +00:00
tianyifeng
f3268ac8ae Load bitmaps for MediaBrowserCompat.
* Transforms the `ListenableFuture<LibraryResult<MediaItem>>` and `ListenableFuture<LibraryResult<List<MediaItem>>>` to `ListenableFuture<MediaBrowserCompat.MediaItem>` and `ListenableFuture<List<MediaBrowserCompat.MediaItem>>`, and the result will be sent out when `ListenableFuture` the `MediaBrowserCompat.MediaItem` (or the list of it) is fulfilled.
* Add `artworkData` to the tests in `MediaBrowserCompatWithMediaLibraryServiceTest`.

PiperOrigin-RevId: 489205547
(cherry picked from commit 4ce171a3cfef7ce1f533fdc0b7366d7b18ef44d1)
2023-01-25 17:37:31 +00:00
bachinger
73d40e1cfc Add bundling exclusions with unit tests
The exclusion will be used in a follow-up CL when sending PlayerInfo updates.

#minor-release

PiperOrigin-RevId: 488939258
(cherry picked from commit bae509009bd62554876ecb7485708e50af4eaa2a)
2023-01-25 17:37:31 +00:00
bachinger
4adfb5f50f Add pending sequence before sending the remote session task
#minor-release

PiperOrigin-RevId: 488885069
(cherry picked from commit e1eb8b6dd89fac48d62e80e95c0e959815720b35)
2022-11-17 10:06:29 +00:00
tonihei
8cc37db8e5 Rename getVideoSurfaceSize to getSurfaceSize
This better matches the callback name (onSurfaceSizeChanged) and
probably cause less confusion with getVideoSize.

PiperOrigin-RevId: 488669786
(cherry picked from commit 1143edc59ac96ec8524e8203d9445dbb0f006630)
2022-11-17 10:06:29 +00:00
tonihei
7f17ff6d00 Ensure listener invocations use final state variable.
The MediaControllerImplBase listener invocations currently use the
class member state that can change if one of the listener method
implementations changes the state recursively.

Updating the listener invocations to use a final local variable
ensures all listeners get consistent updates.

PiperOrigin-RevId: 487503373
(cherry picked from commit 33bea391d42cb5669e6d2c97c0553f3c0c63467b)
2022-11-10 12:56:11 +00:00
tonihei
58d670d941 Align PlaybackStateCompat states with logic in MediaSessionConnector
Creating the PlaybackStateCompat from a media3 Player state is done
already by the MediaSessionConnector (and used widely). The media3
session module should set the same states under the same circumstances
to ensure compatiblity and consistency.

PlaybackStateCompat changes made in media3 session:
 - Use STATE_STOPPED when player is ended instead of STATE_PAUSED
 - Use STATE_PLAYING when playback is suppressed temporarily.
 - Set the playback speed to 0 if the player is not playing.
 - Add extras for mediaId and user-set playback speed.

Part of the problem was that Player.isPlaying() was used to check
the state. Unfortunately, MockPlayer.isPlaying() is implemented in
a way that makes it hard to test these changes, because the value
is set independently of playbackState, playWhenReady and suppression
reason. To be able to write consistent, logical tests, this change
also removes the independent setting of isPlaying in MockPlayer to
align it better with a real player. This requires to update some
other tests to use alternative methods.

PiperOrigin-RevId: 487500859
(cherry picked from commit c5e071e556931331cc780fda5177d556d51ff3da)
2022-11-10 12:40:37 +00:00
tonihei
824cbf405c Avoid notifying connection twice from MediaControllerImplLegacy.
The connection to a legacy MediaSession may receive additional
onSessionReady callbacks that are treated as additional state updates.
We currently also set the "notifyConnected" flag for these updates
even though we are connected already, causing an IllegalStateException.

Fix the exception by not setting this flag.

We can also remove the wording about "locked" updates since this class
operates everything on a single application thread.

Issue: androidx/media#49
PiperOrigin-RevId: 487487286
(cherry picked from commit b24161a6f6af184840d9eae7a63276b73a4a4d80)
2022-11-10 11:10:11 +00:00
tonihei
dddc6026e9 Wait with PlayerInfo updates until all pending operations are done
Accepting a PlayerInfo while the MediaController is masking its state
means we are reverting all masking changes we've made earlier. This
only makes sense if the update already contains the masked operation.
If multiple operations are in flight (or are sent from the session
while they are in flight), we need to wait until all of them are
handled before accepting new updates.

In cases where a new update from the session excludes the Timeline
and the masked state is incompatible with the new update, we also
risk an exception if we accept the update too early.

PiperOrigin-RevId: 487266899
(cherry picked from commit 0b4ba3e3a6130253b801ddc231501168efce8901)
2022-11-09 17:29:31 +00:00
christosts
707c2d894d MediaController: Add missing event flags (2/2)
This is the follow-up commit where the onEvents callback
raised by MediaController contains the missing events, for the
case where MediaController is connected to a legacy MediaSession.

#minor-release

PiperOrigin-RevId: 487231996
(cherry picked from commit c403b4ce7ce9a2964ff754bb394782cc36fb882d)
2022-11-09 14:58:49 +00:00
michaelkatz
8270dd4536 Changed MediaController to return last estimated position while paused
The method getCurrentPosition() may return a lesser position during pause than the previous retrieved value due to ipc call delay in playerInfo update. Users see track position jump backwards at pause. Fixed to return last estimated position while paused if have not received updated playerInfo. Code is deduped to point getContentPosition() to getCurrentPosition() when !isPlayingAd.

PiperOrigin-RevId: 486617341
(cherry picked from commit 9336b95bf4355c4b1e43f95879d9af45cf90cd19)
2022-11-07 11:36:36 +00:00
Googler
b780635c0b Add 'Player.getVideoSurfaceSize' that returns the size of the surface
on which the video is rendered.

Design Doc: go/aaos-mu-media-dd

PiperOrigin-RevId: 485884772
2022-11-03 15:50:19 +00:00
rohks
5ac4700c9a Update notification when timeline changes
Notification buttons for next/previous should change based on the new index of the currently played media item after another media item is added or removed from a playlist.

Issue: androidx/media#130
PiperOrigin-RevId: 485869144
(cherry picked from commit 2633f37a2f95ff38bede573ede83cfa123cb5eb9)
2022-11-03 14:37:30 +00:00
christosts
96a25a76a1 Clean imports in MediaControllerImplLegacy
Remove static imports to constants and imports to intdefs, to make the
code more readable.

PiperOrigin-RevId: 485592288
(cherry picked from commit 8db6b71805c817f8fc868139199329529975df6e)
2022-11-02 14:45:12 +00:00
tianyifeng
f5afec955a Load bitmaps for MediaMetadataCompat and handle the metadata updates.
* Add `Listener` in `MediaSession` with method `onNotificationRefreshRequired(MediaSession)`.
* Add `MediaSessionService` as the listener of the `MediaSession` when `MediaSession` is added to `MediaSessionService`
* Load bitmap when update metadata in `MediaSessionLegacyStub` and call `onNotificationRefreshRequired` when bitmap asynchronously arrives.

PiperOrigin-RevId: 485376145
(cherry picked from commit 77fedd8d7d511ff97b0441aa9136d0f1aaecef4b)
2022-11-01 18:43:26 +00:00
andrewlewis
690ebb3dcd Upgrade dackka and fix some generation errors
#minor-release

PiperOrigin-RevId: 484483080
(cherry picked from commit 3069d8130be1bef156bef2d0b3541648fd5dbf9a)
2022-10-28 10:03:56 +00:00
bachinger
dddb4841af Set BROWSER_SERVICE_EXTRAS_KEY_SEARCH_SUPPORTED automatically
This root extra needs to be set by apps manually in media1 and we
can do that automatically in Media3 based on the available session
commands.

#minor-release

PiperOrigin-RevId: 484286833
(cherry picked from commit 4c9ca8fad1c4d4c475a0baee243c099204bc23af)
2022-10-27 17:20:06 +00:00
bachinger
1c81587398 Use MediaBrowserCompat.rootHints as connections hints
In Media3 there is the useful concept of connection hints that a
client can set when building the session and that are sent to the
service and passed to the `Callback.onConnect()` method when the
browser connects.

These connection hints are then included in the `ControllerInfo`
object that later will be passed to every callback method and the
implementor can then take decisions specific to these connection
hints.

These connection hints are not available in media1. However, when
an app creates a `MediaBrowserCompat` object, the constructor takes
a rootHint object that is sent to
`MediaBrowserServiceCompat.onGetRoot()`.

This change uses the browser rootHints as the connection hints when
creating the `ControllerInfo` for legacy browsers and makes them
available to the `MediaLibrarySession.Callback` domain methods in
the same way as connection hints of a Media3 browser.

PiperOrigin-RevId: 484220748
(cherry picked from commit 21022c77be44fefeaa25ef9fb72be7d48e51a0bc)
2022-10-27 11:50:03 +00:00
tianyifeng
88a413b2cb Add injection of BitmapLoader from MediaSession.
* Add `BitmapLoader` in `MediaSession.Builder` and `MediaLibrarySession.Builder`.
* Pass `BitmapLoader` into the constructor of `MediaSession`, `MediaSessionImpl`, `MediaLibrarySession` and `MediaLibrarySessionImpl`.
* Add an interface method `loadBitmapFromMetadata(MediaMetadata)` in `BitmapLoader`.
* Remove the reference of `BitmapLoader` in `DefaultMediaNotificationProvider`.

PiperOrigin-RevId: 483654596
(cherry picked from commit 3f69df72db7a4aa481dd315f008375148f548805)
2022-10-25 13:21:08 +00:00
ibaker
373c23c11b Create NotificationChannel in DefaultMediaNotificationProvider.Api26
The inner class avoids a verification failure, which can lead to slower
execution at runtime.

PiperOrigin-RevId: 483639417
(cherry picked from commit 026aea7d3d85406ef9acf4e65cfffbc5e683a762)
2022-10-25 11:54:05 +00:00
microkatz
9fbc464ce3 Merge pull request #141 from tzugen:patch-4
PiperOrigin-RevId: 483395026
(cherry picked from commit e2a77f7be9989aaf70749f7caf62bc8b3f1feb0b)
2022-10-31 11:28:21 +00:00
bachinger
adf264c7c0 Minor simplification when setting metadata to platform session
PiperOrigin-RevId: 482805730
(cherry picked from commit a47f530b926394f6e1f3c11bbcbb2d2e38434e5a)
2022-10-21 15:56:34 +00:00
tianyifeng
1ce13aa721 Add CacheBitmapLoader in the session module
* Add `CacheBitmapLoader`.
* Add `CacheBitmapLoaderTest`.
* Remove the `BitmapLoadRequest` and some bitmap caching logic in `DefaultMediaNotificationProvider` since we moved all of them in `CacheBitmapLoader`.
* Modify `DefaultMediaNotificationProviderTest`.

PiperOrigin-RevId: 482787445
(cherry picked from commit ca4edff1fd61c58ce5c56f9bbd9ff80ce8a6670c)
2022-10-21 14:28:35 +00:00
christosts
0727b1f6f2 Use Service.stopForeground(int) on API 24+
The MediaNotficationManager stops the service from the foreground
calling Service.stopForeground(boolean) which is deprecated in API 33.
This change calls Service.stopForeground(int), which was added in API
24.

#minor-release

PiperOrigin-RevId: 482190332
(cherry picked from commit 601eaba7a6dcce963d5a3547d939ea41404ad3fe)
2022-10-19 13:41:26 +00:00
christosts
ba84c2a09c MediaController: Add missing event flags (1/2)
This is the first commit out of two. This change adds the missing event
flags for the onEvents() callback when MediaController is connected to a
media3 session (see MediaControllerImplBase). I updated the
MediaControllerListenerTest and MediaControllerStateMaskingTest with
assertions that on onEvents() is called alongside individual
Player.Listener callbacks.

There will be a follow-up change for the case where a MediaController is
connected to a legacy MediaSession (MediaControllerImplLegacy). I've
split this in two separate changes to make the size of the commit
manageable for reviewing.

#minor-release

PiperOrigin-RevId: 481933437
(cherry picked from commit 46d5a0e33bd562cd9e6420b342a68bf112847f83)
2022-10-18 15:54:54 +00:00
bachinger
fc1089f68a Migrate media constants from androidx.media.util.MediaConstants
Adds root extras and metadata extras to MockMediaLibraryService and MockMediaBrowserCompatService and completed test cases for asserting
interoperability with a media1 or Media3 browser.

PiperOrigin-RevId: 480854842
(cherry picked from commit 006a519a0e83d5b9e4262a5bbc228c02b3c9cf5f)
2022-10-13 11:18:46 +00:00
christosts
0ceec3ebb9 MediaControllerImplBase: clean imports
Remove static imports to constants and imports to intdefs, to make the
code more readable.

PiperOrigin-RevId: 479594144
(cherry picked from commit 36e12fbadb7b274f770a5904675493300c8fa89c)
2022-10-07 16:07:50 +00:00
aquilescanta
506653bbcc Fix typo in MediaController javadoc
PiperOrigin-RevId: 479268879
(cherry picked from commit 0beccb6e141f40d6cb8cca4839213df2ab600c01)
2022-10-06 10:21:12 +00:00
christosts
1cfeddc369 Misc fix on DefaultMediaNotificationProviderTest
PiperOrigin-RevId: 479079184
(cherry picked from commit bf948db669c32ab5eea2432b7c2c61667af1388a)
2022-10-05 17:22:40 +00:00
Googler
9842c273e9 Allow using different notification IDs for different media sessions
When a media service currently produces multiple media sessions, the notification of the second session overwrites the notification of the first one, because all sessions use the same notification ID. When we use different notification IDs for different sessions, multiple media notifications can be up at the same time, which means that they can both be controlled at the same time.

PiperOrigin-RevId: 478709069
(cherry picked from commit 7783c6e4f71943b543ddb569b120bdbbab362b96)
2022-10-04 07:38:38 +00:00
bachinger
05ebdb9211 Don't start the service in the foreground with a pause intent
`PlaybackStateCompat.toKeyCode(command)` was replaced by our
own implementation of `toKeyCode()`. The legacy implementation used PLAY and PAUSE, while the new implementation uses PLAY_PAUSE. This made `pause` a pending intent that attempt to start the service in the foreground, but `service.startForeground()` won't be called in `MediaNotificationManager.updateNotificationInternal` when paused.

PiperOrigin-RevId: 476895752
(cherry picked from commit acd9e581c494b18f6347f5abb2cf9fc82344c723)
2022-09-26 14:56:00 +00:00
bachinger
cc6c6aa62b Call callback future listeners on app handler
Calling maybeUpdateLegacyErrorState potentially creates a new legacy playback
state which involves calling player methods. This change makes sure that the call
sites of `maybeUpdateLegacyErrorState` are called on the app thread as enforced by
the library.

PiperOrigin-RevId: 476406282
(cherry picked from commit 74124f48ea838fcdb436726ba8337e82abc7552e)
2022-09-23 17:34:25 +00:00
rohks
4d43f840f1 Fix instrumentation tests not working via Gradle
PiperOrigin-RevId: 475560401
(cherry picked from commit c96e010d351bdd8b45f88b9731c5a80bcec0d2d4)
2022-09-20 15:25:38 +00:00
Googler
8773b59fb5 Clarify exception message.
The exception fires when intent resolution fails to find a service which declares an appropriate intent-filter. The existing message is confusing; it's trying to say that the service couldn't be found but the double negative renders it incorrect.

#cleanup
#minor-release

PiperOrigin-RevId: 472736740
(cherry picked from commit 15d3d74e16bf9830e5ae32d0a8edf691474d0b4e)
2022-09-07 15:56:07 +00:00
Googler
1bf431566f Fix 4 ErrorProneStyle findings:
* Non-standard parameter comment; prefer `/* paramName= */ arg`
  (see http://go/bugpattern/ParameterComment) (2 times)
* This catch block catches an exception and re-throws another, but swallows the caught exception rather than setting it as a cause. This can make debugging harder.
  (see http://go/bugpattern/UnusedException)
* This comment contains Javadoc or HTML tags, but isn't started with a double asterisk (/**); is it meant to be Javadoc?
  (see http://go/bugpattern/AlmostJavadoc)

This CL looks good? Just LGTM and Approve it!
This CL doesn’t look good? This is what you can do:
* Revert this CL, by replying "REVERT: <provide reason>"
* File a bug under go/error-prone-bug for category ErrorProneStyle if there's an issue with the CL content.
* File a bug under go/rosie-bug if there's an issue with how the CL was managed.
* Revert this CL and not get a CL that cleans up these paths in the future by
replying "BLOCKLIST: <provide reason>". This is not reversible! We recommend to
opt out the respective paths in your CL Robot configuration instead:
go/clrobot-opt-out.

This CL was generated by CL Robot - a tool that cleans up code findings
(go/clrobot). The affected code paths have been enabled for CL Robot in //depot/google3/java/com/google/android/libraries/media/METADATA which is reachable following include_presubmits from //depot/google3/third_party/java_src/android_libs/media/METADATA.
Anything wrong with the signup? File a bug at go/clrobot-bug.

#codehealth

Tested:
    Local presubmit tests passed.
PiperOrigin-RevId: 472255768
(cherry picked from commit 3a2e0d37177034b3012204ac16cf7b3d4e3436b9)
2022-09-05 12:19:14 +00:00
bachinger
1b7060776b Remove assertion that prevents masking of ad periods
The assertion asserts against a `Period` and an `AdPlaybackState` which actually
asserts against a resolved ad which is what `ExoPlayerImplInternal` does later and
what gives us a `SEEK_ADJUSTMENT`. However, this assertion is not required at the
moment of masking, because we are sure that the resolved seek results in a content
period and never an ad period.

#minor-release
Issue: androidx/media#122
PiperOrigin-RevId: 471827072
(cherry picked from commit 73f86682e94024c780564dbc287f06d47dfeca26)
2022-09-02 17:05:21 +00:00
rohks
422e317156 Provide methods to override notification fields
`contentTitle` and `contentText` can now be overridden.

Issue: androidx/media#150
PiperOrigin-RevId: 471287701
(cherry picked from commit a45df2fdcb5c0d1e8c6eb8ab23e421e8d9233e68)
2022-08-31 17:23:20 +00:00
rohks
4c29eee330 Switch incorrectly configured native multidex to legacy
Native multidex can only be used for binaries with minSdkVersion of 21 or higher, but minSdkVersion was specified to 16.

PiperOrigin-RevId: 470003836
(cherry picked from commit 5b3f32023063c90a63a5e30eeefab161b0a7f824)
2022-08-25 16:07:42 +00:00
Marc Baechinger
f73018a269 Merge pull request #157 from vanniktech:onAddMediaItems-example
PiperOrigin-RevId: 469410221
(cherry picked from commit ca41026c5b4d03815e83c85987efceee5fe2e144)
2022-09-30 16:56:19 +00:00
rohks
e7aee55fca Fix missing commas in documentation of DefaultMediaNotificationProvider
PiperOrigin-RevId: 468257769
(cherry picked from commit 6950f3aaa8bf229c0441b3d10cae75bef3ee30f5)
2022-08-17 18:54:01 +00:00
tonihei
f3d48c21f4 Define CueGroup.EMPTY_TIME_ZERO for convenience
We create an empty CueGroup in many places as default or
where none is needed. Instead, we can define a constant
for this purpose and reuse it.

PiperOrigin-RevId: 467944841
(cherry picked from commit 59895646e03ee15aed5e80fb8e60b7f98a7afe5f)
2022-08-16 16:27:56 +00:00
christosts
19cc87c04f MediaNotificationManager: handle playback ended
When the player finishes playback and reaches the STATE_ENDED,
the notification remains visible with a pause button and the
service is kept in the foreground. This is a bug.

With this change, when the player reaches the STATE_ENDED, the
service is stopped from the foreground and a notification is shown
with a play button. If the play icon is tapped, the player will restart
playback of the last played item. Playing the last played item again
is the existing behavior when play/pause commands are received from
the legacy MediaSession (e.g. BT headset buttons).

#minor-release

Issue: google/ExoPlayer#112
PiperOrigin-RevId: 467231509
(cherry picked from commit 7a7e1eb23b4bbf7d0c6c15b2f484434e65cd2c68)
2022-08-12 16:53:36 +00:00
bachinger
1bee4f86fb Add interface version of MediaSessionStub
PiperOrigin-RevId: 464052708
(cherry picked from commit a28b3ef77843bb75938b2ca738ebedf0c29c1129)
2022-07-29 11:52:06 +00:00