tlk / beoplay-macos-remote-gui

Remote control B&O Beoplay loudspeakers from macOS
MIT License
16 stars 1 forks source link

Select source #4

Closed mbolo01 closed 4 years ago

mbolo01 commented 4 years ago

Bonjour. Thanks again for this development that I discovered through the Beoworld forum and which I hope will one day catch B&O interest and support. In the mean time, if I may and for the purpose of sharing only, I would like to submit ideas and report bugs when identified.

Idea: On the same principle than the TuneIn Radio section, a "Select Source" could be a nice addition. Use case: I often change sources for my main product as I have many going through such as internet streaming services, local DLNA media server, a TV, a record deck via an another linked B&O product or simply joining another experience (B&O multiroom). A simple source selector from a source list pulled from the selected product would help, like it has been done for the Home Assistant integration.

At the end of the day your clever development could be the GUI version of the B&O Essence remote product which has basic, but super useful, functions to drive any brand product.

Many thanks again

tlk commented 4 years ago

I think it is a great idea to mimic the B&O Essence remote. Thank you very much, and please feel free to file more ideas and issues here.

tlk commented 4 years ago

Bonjour Jérôme, All those compliments were hard to ignore ;-) so I have added rudimentary support for selecting sources. It pulls a list of sources from the first device it finds, then applies a filter (optional - please see the README) before adding sources to the menu. I have not used this functionality before so I might have missed something, but it seems to work for me. I wonder how well it works in an environment with multiple B&O devices.

Best regards, Thomas

mbolo01 commented 4 years ago

Bonsoir Thomas! Merci, and compliments are well deserved. So, regarding the latest changes, very promising ..... but, there is always a but, they triggered a strange behavior with my devices. Let's start with the beginning: (1) The source list that has been retrieved from the first device found is is the (fortunately) the longest amongst all my devices as this device has more options and is also linked to another device which means that the linked device's sources are part of the first device list (beauty of the B&O ecosystem): IMG_4355 The linked device sources are starting after the "Alarm" source the list and when exposed by the B&O application, a suffix indicates the name of the linked device, e.g. "TuneIn (Moment)": IMG_4359 The difference in the number of items between your app and B&O app is that the latest only exposes a subset of the possible playable sources and I'm not sure I understand their logic in their application. Note also that I've never seen Alarm, Music Stream or ChromeCast exposed by B&O apps. In summary: IMO the logic should be to pull the possible sources of the selected device, as it may vary from device to device based on their capability, and a global filter out to take of the list, when found, items such as QPlay (used in China only) , Alarm (which is not a source) and Airplay or Chromecasts for users who never use it.

(2) when I applied a filter like "defaults write $bundleid sources.types -array tunein music optical", It returned something strange (see image below):

When I applied "defaults write $bundleid sources.types -array optical", no sources were available.

(3) When I applied a source on another product than the primary found product, the source was effectively applied on the other product but sound was coming from the primary product and B&O app was confused until the second product went in standby .... I've never seen that, I'll try to register the sequence of event better .

Lastly, as you figured it I'm french, so I apologizes if sometimes what I write is confusing as it may be a poor fr/en translation.

A bientôt Jérôme

mbolo01 commented 4 years ago

..... quick question: how do I reset a source list to "all"?

tlk commented 4 years ago

Bonsoir! I just added a note to the README.md file on how to reset the source filter (defaults delete). The source filters are compared to the internal type and category values which are pulled from the device, but without some internal knowledge it is difficult to guess the correct values.

You can use the CLI tool to inspect your B&O setup and find the correct type and category values. It needs to be built from the command line (e.g. Terminal.app) - perhaps with some luck it will work :-)

$ xcode-select --install
$ git clone git@github.com:tlk/beoplay-macos-remote-cli.git
$ cd beoplay-macos-remote-cli
$ make install
$ beoplay-cli discover       # <--  note the "host" value from this output
$ BEOPLAY_HOST=Beoplay-M5-28096178.local. beoplay-cli getSources

Adjust the last command line to use the "host" value for each device that has been discovered.

The last command will print out the unfiltered source list from a device. Each line is comma-separated in this format: [id, type, category, name].

The first line of the getSources output says connecting to http://Beoplay-M5-28096178.local.:8080. If you open this link in a browser and add /BeoZone/Zone/Sources to the end of the URL you should be able to see the raw json-encoded data for the device. Perhaps inspecting this will reveal some logic.

The json-encoded data can be humanized by copy-pasting it into a json beautifier such as https://www.jsonformatter.io

I noticed there is a property for each source called "borrowed", perhaps that indicates when a source is from a linked device? (such as those after the "ALARM" source from your first screenshot).

Finally, I guess it would be better if the source list is refreshed whenever a new device is selected. This way the app should only send known source id's to a device - possibly avoiding the odd behaviour you experienced in (3).

mbolo01 commented 4 years ago

Bonjour, You are right, the "borrowed" property is the one that indicates a source borrowed from another product. Below two sections from the Zones output, the 1st one is relevant to the main product (Core) and the second is relevant to the linked product (Moment) where you can find the associated jid for the source: [ "music:2797.1293039.28607421@products.bang-olufsen.com", { "id": "music:2797.1293039.28607421@products.bang-olufsen.com", "friendlyName": "Music", "sourceType": { "type": "MUSIC" }, "category": "MUSIC", "inUse": true, "profile": "", "borrowed": false, "linkable": true, <.................... text removed ..............> "product": { "jid": "2797.1293039.28607421@products.bang-olufsen.com", "friendlyName": "Core" }, <.................... text removed ..............> ], [ "linein:2997.1290084.24751784@products.bang-olufsen.com", { "id": "linein:2997.1290084.24751784@products.bang-olufsen.com", "friendlyName": "Line-In", "sourceType": { "type": "LINE IN" }, "category": "MUSIC", "inUse": true, "profile": "", "borrowed": true, "linkable": true, <.................... text removed ..............>
"product": { "jid": "2997.1290084.24751784@products.bang-olufsen.com", "friendlyName": "Moment" },

It would be good to mark them in the list of Sources to differentiate them form the product sources itself.

I agree about refreshing the list for each product when "clicked", this is what I meant in my previous comment due to the possible difference between products, and we know now that using a single jid to control other products produces unexpected behavior.

You may have seen that you can configure a Local Sources list in your product. This list of local sources is used for example by the Essence Remote which triggers the sources cyclically as you press the play button. I was not able to identify a property in the Zone/Sources output that indicates a selected local list. You'll find in the js associated to the product local list configuration more information about these local list think, see image below:

screenshot_102

Note that Alarm is a hidden source, I suggest you hide it too if possible.

Finally, a "join multiroom experience" option would also be a good enhancement in the Sources. The Home Assistant has a reference to it, but I have not seen it working. Fyi, when you have multiple products configured for multiroom, a product can join another product that is streaming something by either physically acting on the product itself or a remote, or through the B&O App.

More work :-(

tlk commented 4 years ago

Hello again Jérôme, This is great feedback, thanks! I would like to have a look at the iOS app especially on how it pulls the filtered list of "Local sources"/"Touch to join" from a device. Probably some time next week. Will keep you updated. Best regards, Thomas

tlk commented 4 years ago

Bonsoir Jérôme, I have had a look at how the iOS app talks to the device but it does not pull a filtered list of "Local sources" from the device. Maybe it is not supported or maybe it is because the iOS app operates in a different mode when it does not find multiple devices on the network. The filtered list is accessible from the admin interface on port 80 (:80/api/getData?path=settings%3A%2Fbeo%2Fsources%2FcontrolledSources&roles=value) as you found out, but I think it's surprising if this is not exposed via the API on port 8080. Oh well.

The BeoplayRemoteGUI.app is now updated such that it refreshes the sources list when a device is clicked, and instead of using the sources.types and sources.categories allow-lists I have added a sources.hideTypes blocklist so it is possible to hide ALARM and other unwanted source types.

Perhaps this is enough for it to be usable.

By the way, was thinking that instead of using submenus for the sources and tunein stations, those items could be visible from the main menu, ala:

mockup

Thoughts?

best regards, Thomas

mbolo01 commented 4 years ago

Bonsoir Thomas!

The filtered list access is indeed and unfortunately not accessible through the App. It is a request for enhancement raised by several BeoWorld members, and I adhere to the idea. I guess being only available through the admin interface prevents you to leverage it in your code? Otherwise you would make a lot of people happy and, as I wrote before, the Essence Remote leverages this short list in a way that when you press the "on" button while already connected, it loops in this source list as you press the "on" button.

I tested the per-device source list fetching and it works very well so far, well done! If you could display the source origin when it is a linked source, it would be great and less confusing. I reckon that it is hard for you to test if you only have one device, but if you read "borrowed": true while parsing the list of sources, then you could capture the value of the following "friendlyName" and add it to the source name in the form of (). If you look at the sample I posted earlier, it would then display something like LINEIN (Moment).

I will test the exclusion list and revert to you.

Maybe you could leverage the admin url output to build an allowed list based on the source that are "enabled: true"? Linked source would require more work to reconcile the product ID exposed in the admin output with the one in the Json (?) output to capture the friendlyName, e.g:

     "controlledSources": [
        {
           "deviceId": "",
           **"enabled": true,
           "sourceId": "toslink",**
           "enabledExternal": true
        },
        {
           "deviceId": "",
           **"enabled": true,
           "sourceId": "radio",**
           "enabledExternal": true
        },
        {
           "deviceId": "",
           **"enabled": true,
           "sourceId": "music",**
           "enabledExternal": true
        },
        {
           "deviceId": "2997.1290084.24751784@products.bang-olufsen.com",
           **"enabled": true,
           "sourceId": "linein:2997.1290084.24751784@products.bang-olufsen.com",**
           "enabledExternal": false
        },
        {
           "deviceId": "",
           "enabled": false,
           "sourceId": "qplay",
           "enabledExternal": true
        },
        {
           "deviceId": "",
           "enabled": false,
           "sourceId": "spotify",
           "enabledExternal": true
        },
      ........ text removed ......
  "type": "controlledSources"

} ]

Exposing the radios and the sources items directly in the menu is a very good idea to reduce the number of actions to reach the ultimate point, but I would only display the 3 or 5 first items of each list and enable a" more items" option per group (sources, radio) for those who may have more, like me, to avoid a having a too long drop down menu. Up to the user to set their top items by placing them at the beginning of the list.

Merci!

mbolo01 commented 4 years ago

..... forgot to mention the following: I usually have to launch the application twice to get the full list of my devices. Maybe waiting more time for product advertisement is required at startup or auto-refreshes while the menu is developed?

tlk commented 4 years ago

Bonsoir Jérôme!

The app has been adjusted and the linked source origin logic should now render as Line-In (Moment)

When a device is selected the app will fetch data from the device on port 80 and 8080. I will be surprised if this works on all devices, but why not try :-) Worst case the app will either crash or fail to populate the source dropdown until the next device is selected.

I have adjusted a Bonjour/mdns browser timeout from 1s to 10s to address the device discovery issue - curious to know if that helps!

Best regards, Thomas

mbolo01 commented 4 years ago

Bonsoir Thomas! Waw, well done! It works perfectly with two exceptions: 1 - Beosound Moment, as expected, does not react to the local/reduced source list fetch, because it does not share the same software than the other products. Maybe the Moment must be treated as an exception to this process when detected, or more global, port 80 fails to return data, then 8080 is the only output processed, an idea. 2 - For my Core which has linked source, the linked sources are now well identified which is awesome, but I have more sources than expected. The following shows you the screenshot and the 80 output (full and formatted): IMG_4386

[ { "controlledSources": { "controlledSources": [ { "deviceId": "", "enabled": true, "sourceId": "toslink", "enabledExternal": true }, { "deviceId": "", "enabled": true, "sourceId": "radio", "enabledExternal": true }, { "deviceId": "", "enabled": true, "sourceId": "music", "enabledExternal": true }, { "deviceId": "2997.1290084.24751784@products.bang-olufsen.com", "enabled": true, "sourceId": "linein:2997.1290084.24751784@products.bang-olufsen.com", "enabledExternal": false }, { "deviceId": "", "enabled": false, "sourceId": "qplay", "enabledExternal": true }, { "deviceId": "", "enabled": false, "sourceId": "spotify", "enabledExternal": true }, { "deviceId": "", "enabled": false, "sourceId": "bluetooth", "enabledExternal": true }, { "deviceId": "", "enabled": false, "sourceId": "linein", "enabledExternal": true }, { "deviceId": "", "enabled": false, "sourceId": "airplay", "enabledExternal": false }, { "deviceId": "", "enabled": false, "sourceId": "deezer", "enabledExternal": false }, { "deviceId": "", "enabled": false, "sourceId": "googlecast", "enabledExternal": false }, { "deviceId": "", "enabled": false, "sourceId": "dlna", "enabledExternal": false }, { "deviceId": "", "enabled": false, "sourceId": "alarm", "enabledExternal": false }, { "deviceId": "2997.1290084.24751784@products.bang-olufsen.com", "enabled": false, "sourceId": "radio:2997.1290084.24751784@products.bang-olufsen.com", "enabledExternal": false }, { "deviceId": "2997.1290084.24751784@products.bang-olufsen.com", "enabled": false, "sourceId": "patternplay:2997.1290084.24751784@products.bang-olufsen.com", "enabledExternal": false }, { "deviceId": "2997.1290084.24751784@products.bang-olufsen.com", "enabled": false, "sourceId": "qplay:2997.1290084.24751784@products.bang-olufsen.com", "enabledExternal": false }, { "deviceId": "2997.1290084.24751784@products.bang-olufsen.com", "enabled": false, "sourceId": "bluetooth:2997.1290084.24751784@products.bang-olufsen.com", "enabledExternal": false }, { "deviceId": "2997.1290084.24751784@products.bang-olufsen.com", "enabled": false, "sourceId": "music:2997.1290084.24751784@products.bang-olufsen.com", "enabledExternal": false } ] }, "type": "controlledSources" } ]

Many thanks again, very good work! Best regards, Jérôme

tlk commented 4 years ago

Great to hear! Just made some minor changes, is it better?

mbolo01 commented 4 years ago

Bravo et merci! All is in order from a sources perspective, including for the Moment. It is now representative of the per device user local sources selection as well as the sources an Essence remote can control. Basically the tool behaves like a virtual "Essence remote" linked to many devices v.s a physical Essence remote that can be linked to one device only.

Joining an experience is the last step to include in the possible sources.

Regarding the devices not being all visible in the tool at startup, the 10 delay didn't change anything. I have to run several load/unload/load to get to the point I'm lucky having all the devices listed.

I also have a weird issue, that I only noticed today, that may be around since a while: my Core goes off every 20 minutes like when you press the power icon button located at the top left of the B&O app main screen. Looking at the device through the B&O app "now playing" screen, it does not show a usual off status but a "tunein" paused status instead. Very confusing and unusual. Note that the all lowercase "tunein" wording is coming from your tool when using the TuneIn Radio option. This "tunein" indicator is never shown when the product is controlled by the B&O App. I thought it could be a phantom code running on my Mac that could do that, but the device went off even after a Mac reboot. I restarted the product and after 20' it didn't go off, but I only played with sources and volume with the tool. I'll get back to you with more details when I'm able to reproduce the issue, but my feeling is that the Radio option may introduce some disorder somewhere.

You are getting there, trust me!

mbolo01 commented 4 years ago

Bonjour Thomas, Some TuneIn observations: I think I have a better idea about the the logic behind the scene. When TuneIn is controlled through B&O code, the first step may be to fetch the TuneIn user's favorites (which requires a login to TuneIn), including some static metadata such the radio name. This list of radios may then be used to build a kind of play queue that can be controlled like other play queues, with the exception that the play queue content is not exposed in the app now playing screen like a music play queues are. Controlling the radio play queue is like controlling a music play queue: pause/start/forward/backward.

When controlled by your application, only the selected TuneIn radio is pushed in the play queue, which means that forward/backward actions have no effect and this is perfectly normal.

In order to introduce this kind of navigation within a radio list, and without a need to login to TuneIn, do you think it is possible to push the list of the application configured radios so it can be controlled by the app backward/forward options, or from a physical remote if closer? I understand it is useless form your app point of view only as the radio list is directly seletable without the need to navigate forward/backward, but could be a nice addition so it can be used jointly by a physical remote. I see that as a nice to have.

Regarding the "tuneIn" label exposed instead of the radio name when controlled by your application, I think it is coming from the fact that you don't push the radio name when stating it (according to my packet sniffing):

{ "playQueueItem" : { "id" : "s262533", "station" : { "id" : "s262533", "image" : [ { "size" : "medium", "mediatype" : "image\/jpg", "url" : "" } ], "name" : "", "tuneIn" : { "stationId" : "s262533", "location" : "" } }, "behaviour" : "planned" } }

Kind regards, Jérôme

tlk commented 4 years ago

Bonsoir Jérôme, I have just created a bunch of github issues to track the different subjects - hope it makes sense! Will follow up on your great input under the new github issues, and consider this "select source" topic to be "done" for now :-) best regards, Thomas

mbolo01 commented 4 years ago

Bonsoir Thomas. It makes perfect sense, I'll make sure to use this methodology in the future. Agreed, select source is done for now. Kind regards, Jérôme