Skip to content

MediaLibraryService.MediaLibrarySession::notifyChildrenChanged doesn't update Android Auto Browse #561

Closed

Description

@fourofspades

Version

Media3 1.1.0

More version details

I have an issue when a MediaItem is added locally on the device, I call MediaLibraryService.MediaLibrarySession::notifyChildrenChanged with the updated library, however the browser in Android Auto isn't updated (the Queue is however updated).

Wondering if there are issues in this area, or if this is not the correct API to call?

Devices that reproduce the issue

Pixel 7 with Sony Android Auto head unit
Emulated Pixel 6 and Android Auto head unit emulator
I believe all other devices.

Devices that do not reproduce the issue

None

Reproducible in the demo app?

Not tested

Reproduction steps

Connect phone to Android Auto head unit
Add an item to the phone's media database
Call the MediaLibraryService.MediaLibrarySession::notifyChildrenChanged API
open the Browser on Android Auto
Notice new stream is not displayed.

Expected result

New stream should be displayed.

Actual result

New stream is not displayed.

Media

N/A

Bug Report

Activity

  1. ChernyshovYuriy commented on Aug 4, 2023

    @ChernyshovYuriy

    Yes, I was about to ask the same. Within MediaLibraryService extension class, what is a way to update media browser, similar way notifyChildrenChanged worked for MediaBrowserService.

  2. rohitjoins commented on Aug 4, 2023

    @rohitjoins
    Contributor

    Hi @fourofspades,

    From the documentation of MediaLibraryService : If the browser isn't subscribing to the parent, nothing will happen. Did you make sure the browser is subscribed?

    Also it would easier for us to debug if you can try and reproduce the problem on our demo-session app. Thanks!

  3. self-assigned this
    on Aug 4, 2023
  4. fourofspades commented on Aug 5, 2023

    @fourofspades
    Author

    Done some more work on this, and I'm not exactly sure what fixed this, however I am now getting the browser updating when the phone's library updates.

    I had already implemented onSubscribe, my fix was to manually call it again when the local collection changes, taking care not to create a recursive calling loop (I bundled a flag in the params to not refresh the library, as my library call into the library would also do that).

  5. fourofspades commented on Aug 5, 2023

    @fourofspades
    Author

    See above. It's now working as expected.

  6. ChernyshovYuriy commented on Aug 8, 2023

    @ChernyshovYuriy

    I am sorry for the late response. I think my issue a bit different. What I meant is if there is a class extends MediaLibraryService and internal collection of media items changed what is a way to ask framework to call MediaLibrarySession.Callback: onGetChildren, etc ...
    In case of MediaBrowserService it was notifyChildrenChanged.
    I am doing Automotive implementation of the service.

  7. tonihei commented on Aug 8, 2023

    @tonihei
    Collaborator
  8. ChernyshovYuriy commented on Aug 8, 2023

    @ChernyshovYuriy

    Yes, and it doesn't work to me. The use case is simple, I need to call onGetChildren or onGetLibraryRoot to update current browse tree on Automotive. Invoking mSession.notifyChildrenChanged doesn't trigger any callbacks on MediaLibrarySession.Callback.
    Unfortunately, your sample app doesnt have automotive variant to test on.

  9. 16 remaining items

  10. marcbaechinger commented on Sep 15, 2023

    @marcbaechinger
    Contributor

    or maybe make the onSubscribe return a RESULT_SUCCESS by default ?

    I agree that would be a solution that removes some friction and confusion.

    I have a commit that does exactly this. However, when I do return RESULT_SUCCESS from onSubscribe unconditionally then a client app can subscribe a browser for a parentId that does not exist. Actually, a client app that is accessing your app can subscribe for as many parentIds (like millions) until your app has an OutOfMemory exception and crashes.

    I can of course limit this, so that we for instance only allow a browser to subscribe to lets say 20 parent ids. This would prevent such an OOM attack but looks a bit arbitrary and can be confusing as well. I'm inclined to implement such an upper limit of lets say 20 parentId a browser can subscribe to. Once this limit is reached, no more subscription would be allowed. The issue/downside is that this upper limit probably also would be in place if an app actually overrides onSubscribe or carefully implements access controls for apps in onConnect.

    What are the sentiments of developers having such a productive app? How would you implement onSubscribe yourself? Would you check whether a given parentId exist in your database/catalogue, or would you just unconditionally return RESULT_SUCCESS (I obviously do not recommend this :)) ? Would you do so even after having learned about a malicious app could do what I explained above? Do you check the signature of Android Automotive in onConnect to make sure no other app than Automotive can connect?

    Thoughts?

  11. yuroyami commented on Sep 16, 2023

    @yuroyami

    I now grasp the reasoning behind the decision to not have onSubscribe automatically return RESULT_SUCCESS. Lemme offer my perspective on this matter, based on my observations of media apps (note that this is subjective) :

    I believe that most media apps don't require verification of callers. This is primarily because media content, like music, is generally not sensitive (with an emphasis on "most" here). Additionally, it's rare, if not non-existent, to encounter malicious apps that would attempt to exploit a media app's browser service solely to access its data. Many music apps either play local files or enable remote music playback, such as the likes of Spotify.

    In my experience, there's often no need to validate the identity of callers. The need for caller identity verification is quite niche and, therefore, negligible. I don't see any compelling reasons, other than those you've mentioned, for onSubscribe not to return RESULT_SUCCESS by default. Implementing a default limit of 20 subscribers seems practical, and in reality, this limit would likely never be reached. To mitigate any potential confusion, it would be beneficial to include this information in the Javadocs; this way any confusion can be addressed, and users will have clear guidance on the limit.

    Let alone the fact that this may potentially fix the (probably) broken Android Auto support, where Android Auto is not refreshing the media library (#644) since it's requiring all browsers to explicitly subscribe in order to receive callbacks. This however will require some way to add the android auto browser passively to the subscriber list.

  12. yuroyami commented on Sep 19, 2023

    @yuroyami

    I consider the idea of adopting the logic contained in UAMP's PackageValidator.kt officially under media3 in the sole case where the caller is one of those listed in the allowed_media_callers.xml to be completely valid in order to register the android auto browser as a passive subscriber.

    It may even be enough to check the package name (one of the two: "com.google.android.projection.gearhead" or "com.android.auto"). However, it's very easy to mimic a package name, so as I mentioned, the package validator class does the job of detecting the authenticity of the caller.

    I am not sure how things work under the hood as i've taken very quick and non-thorough looks on the source code, but I believe it's one of the two:

    • Either to check the identity of the caller (whether it is android auto or not) and register it to the subscriber list when the latter requests the library root. I believe this would work best.

    • Or when the user calls notifyChildrenChanged, where media3 would need to iterate through the controllers and see if any of them meets the android auto criteria. I know that it already has a list of subscribers that it iterates through, but this can be done at most once, just so that the Android Auto controller does not get left out.

    However, since I am not all aware of how media3 works internally, I cannot think of a valid solution. I may be looking at the wrong direction, or maybe the Android Auto list of items not being refreshed is not related at all to this.

  13. marcbaechinger commented on Sep 27, 2023

    @marcbaechinger
    Contributor

    The commit above provides a default implementation of Callback.onSubscribe(parentId). It calls Callback.onGetItem(parentId) and expects the app to return a browsable item with RESULT_SUCCESS. If this is the case the browser is automatically subscribed and notifyChildrenChanged(browser, ...) is called once immediately.

    An app can later call MediaLibrarySession.getSubscribedControllers(parentId) to get all controllers subscribed to the parentId and call notifyChildrenChanged(controller, parentId, ...) to inform about further changes.

    This behavior can be changed by overriding MediaLibrarySession.Callback.onSubscribe().

    This will be included in 1.2.0 for which we will ship the next alpha soon. I'm closing this issue. Please open a new issue if required.

  14. fdfmobileapps commented on Oct 3, 2023

    @fdfmobileapps

    Hi @marcbaechinger

    Thanks for all the work with Media3, it is a great improvement on the disparate methods required in the previous versions of ExoPlayer, I am certainly noticing the benefits in tidying up and flexibility already.

    Right now I am attempting to resolve an issue, which is identical in nature to the one raised earlier regarding onGetChildren not being called for Android Automotive after notifying of a change to children via:

    mediaLibrarySession.notifyChildrenChanged(controller,"favourites",allFavouriteItems.count(),LibraryParams.Builder().build())

    The onGetChildren for parent Id "favourites" is not being called again. Is called once when the service starts but is not called upon subsequent requests via the notifyChildrenChanged. Hence, if the list of items updates the Automotive UI is not updating children. Which leaves an invalid list presented to the user.

    I have attempted to update to the alpha version mentioned hoping to use the onGetItem method to confirm subscription, but before proceeding further I have immediately noticed that my list of customcommands is no longer displaying correctly. These worked perfectly in 1.1.1, but with they look like this:

    In 1.2.0 Alpha:
    image

    In 1.1.1:

    image

    The icons work in both cases - they trigger their callbacks but can't proceed testing out the alpha until I understand why these are now incorrect. So essentially:

    1. The icons are not appearing correctly in the alpha version,

    and also noticed

    1. if(controller.packageName.contains("gearhead", true) || controller.packageName.contains("auto", true)) is no longer a reliable means of testing for the calling package to determine which custom commands to avail. I have tested by removing the if statement and simply pass through the customCommands, but the packageName details appear to have changed in some way. I have not invested much time looking into this point at this stage.

    I am eager to resolve the issue around the subscriptions for my parentIds, but not wanting to delve further into the alpha if I am already immediately noticing an issue with customCommands.

    Many thanks for your advice in advance!

  15. marcbaechinger commented on Oct 3, 2023

    @marcbaechinger
    Contributor

    Thanks for your report. Lets leave the onChildrenChanged topic to #644.

    Regarding custom commands with AA/AAOS. In the main branch, we've updated the demo app, enabled Android Auto support for the session demo and added an Automotive OS module. Both apps use the same DemoMediaLibrarySessionCallback. With this custom commands work for me on the emulator for Android Auto and Automotive OS (and the mobile notification).

    In short, set the available commands and custom layout of the media notification controller to set the actions and custom actions of the platform/media1 session where Android Auto and Automotive OS read from.

    Note: MediaSession.isAutomotiveController/isAutoCompanionController is not in alpha-02 but will land in beta-01. If you use the same package name comparison as in the source code this works as well. :)

    EDIT: Sorry, I forgot to ask can you give me more details on how you configure the custom commands/layout in onConnect/onCustomCommand?

  16. okmyan commented on Oct 30, 2023

    @okmyan

    Just another comment regarding best practice for other readers who want to support controllers subscribing to a category:

    1. Override MediaLibraryService.Callback.onSubscribe() to maintain a Map<String, List<ControllerInfo>> keyed by the parentId. This gives you all the subscribed controllers/browsers for a given parentId. The callback needs to return aRESULT_SUCCESS for instance with Futures.immediateFuture(LibraryResult.ofVoid()) else Media3 will not put the caller into the list of subscribed controllers.
    2. When the children of a given parentId changes, consult the map from above and call session.notifyChildrenChanged(browser, parentId, childrenCount, params) for each of the browsers in the map to inform selected subscribed clients of your choice about the change in the library.
    3. Override MediaLibraryService.Callback.onSubscribe() to remove the ControllerInfo from the map you are maintaining.

    Hello @marcbaechinger,

    Could you explain, why should we use Map<String, List<ControllerInfo>> instead of Map<String, ControllerInfo>? I mean, I have only one implementation of MediaLibraryService and expect 1 call of onSubscribe and notifyChildrenChanged functions. In which cases I may expect 2 different ControllerInfo for one parentId?

    The second question: How can I even have several versions of ControllerInfo for different parentId? It is possible to save only one ControllerInfo and use it always when calling notifyChildrenChanged?

  17. marcbaechinger commented on Oct 31, 2023

    @marcbaechinger
    Contributor

    The library can't know how many controller/browser connect or subscribe and other apps may expect more than one call to subscribe.

    It can be that 2 apps are connected to your session each with their browser and then both subscribe for the same parentId. So for each parentId there can be multiple controller infos subscribed to the same parentId. So I figure it must be a List<ControllerInfo>. Please let me know if I have misunderstood your question.

    The second question: How can I even have several versions of ControllerInfo for different parentId? It is possible to save only one ControllerInfo and use it always when calling notifyChildrenChanged?

    I'm not sure I understand the question. If you have multiple controllers connected, then you need to notify multiple controllers. You may want to notify only certain controllers for whatever reasons, so you can select from the list of ControllerInfo. If you want to notify all controllers that are subscribed to a certain parentId you can use notifyChildrenChanged without the controller info argument.

  18. okmyan commented on Oct 31, 2023

    @okmyan

    Hi @marcbaechinger,

    Thanks a lot for a quick answer! Now I use a list of ControllerInfo and notify all controllers. It solved my issue, thanks. I tried to use notifyChildrenChanged without the controller info argument, but it didn't work for a some reason. But now I use list of controllers and generally it's okay for me

    I have also the following question:
    Why we need to set itemCount parameter in notifyChildren function and how this field is used? What happens if I will always set 0 as a parameter or just the wrong number for a some reason, for example by mistake.

    Thanks in advance!

  19. locked and limited conversation to collaborators on Nov 27, 2023
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Metadata

Metadata

Labels

Type

No type

Projects

No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions