i8beef / node-red-contrib-castv2

MIT License
22 stars 14 forks source link

Detect which chromecast (or chromecast group) is currently active (playing) #29

Closed ChillXXL closed 3 years ago

ChillXXL commented 3 years ago

Hello,

I recently got my Ikea Symfonisk remote mapped to a certain chromecast device and can control volume, next and previous track including play/pause which is great! Thanks allot.

The thing is, I have multiple chromecasts in my house and also chromecast groups. The control is now directly mapped to a curtain chromecast device (cc_kitchen). When I change to a chromecast group (cc_kitchen + cc_livingroom) I can no longer control the play/pause etc. and outputs a error "device is not playing" + a audiable "dung" on the device (cc_kitchen) which is I mapped in Node Red.

I want to control all audio disgarding the currently playing chromecast. So if cc_kitchen is only playing then the control is on this device only. Is a group playing (cc_kitchen + cc_livingroom) then this is the controller for the group.

So my question is; how can I detect which chromecast is currently active and use some Node Red voodoo to change the device (or IP?) dynamically dependend on the chromecast device which is playing.

Tried allready:

Thanks in advance, any help is welcome.

i8beef commented 3 years ago

Well each Cast node connects to one Cast device or group. The output of the node will fire anytime there are changes on each Cast device, which you could monitor to see if it is playing something or not, and then set a flow variable to maintain that state. I am NOT sure from memory what the best thing is going to be to monitor there from the payload, you'll have to do some experimenting and see what gets output as you transition from playing to not, etc. Then just shove a gate (could just be a function node that grabs the flow var, checks it, and either returns msg, or null depending on check) in front of each input that checks that flow variable to see if its currently playing, and only pass the message if it is.

You'd only want to do this to the commands you want to gate like this.

ChillXXL commented 3 years ago

Thanks. I will try that. I experimented allready that the best result so far is to use a switch node with on output 1: 'msg.platform.applications[0].isIdleScreen' is true and otherwise = output 2 for chromecast devices with a screen.
And then after the switch node 2 change nodes; on output 1 a SET variabele "CC_NOT_ACTIVE" and on output 2: SET variabele "CC_ACTIVE". In this setup any other message will result in "CC_ACTIVE" and a idle screen in"CC_NOT_ACTIVE".

i8beef commented 3 years ago

Note that a couple different shapes of object can come out of the node. There are PLATFORM status objects and RECEIVER status objects. When you play something you should see the following from a debug node:

  1. A PLATFORM status with a platform.applications for DefaultMediaReceiver that is started up.
  2. A RECEIVER status initial state with payload.playerState = IDLE
  3. A PLATFORM status announcing the receiver entering a player state
  4. Multiple RECEIVER status states as it plays, which will announce changes in timestamp of the playing media
  5. A PLATFORM status announcing the receiver entering default state (note: payload.isIdleScreen never transitions here for me... but "payload.statusText" reset to "Default Media Receiver")
  6. A RECEIVER status announcing the receiver entering payload.playerState = IDLE
  7. (After a minute or two) A PLATFORM status announcing, after idle timeout, there are no platform.applications running (payload doesn't have 'applications' on it at all).

Note you can tell the difference between the two on if the message contains "payload" or "platform".

I think you'd want something more like this:

  1. If its a PLATFORM message, if there's NO "applications" array, then CC is idle. Otherwise, you could check if applications[0].isIdleScreen EXISTS and is FALSE for CC is active. There's a possibility here that the receiver is actually idle (e.g. I think non-screen Google Homes might never transition...)
  2. if its a RECEIVER message, check if "playerState" exists and is "IDLE" for CC is idle, otherwise CC is active.

These two are gonna fight over the resetting the CC variables, but given the order messages are published in you should end up with the right state at the end, and should maintain correct values as media is played, paused, changed out, etc... On first connection I'm pretty sure there is a PLATFORM status that is published out, but there ISN'T one that's published when it JOINs an active RECEIVER app... you might have a case here for me adding a publish on that so that on first connection you'll get both current PLATFORM status, and if an app is currently active, the current state of the RECEIVER as well... That would help close a hole above where you could instead just

  1. If its a PLATFORM message, if there's NO "applications" array, then CC is idle. Otherwise do nothing as you can't know reliably if the active app is idle or not, but:
  2. if its a RECEIVER message, check if "playerState" exists and is "IDLE" for CC is idle, otherwise CC is active.

The slight change to 1 would then mean the RECEIVER status message is SOLELY responsible for giving you active state, but the in the case of initial connection if there's NO app running, you STILL get an initial "idle" from the PLATFORM message check...

i8beef commented 3 years ago

So I played with this a bit and published a new version... on connect it'll ALWAYS publish a PLATFORM status with appropriate "applications" if something (including things not supported here) is running. In addition, if a supported app IS running, it'll attempt to GET_STATUS from it and publish that RECEIVER status... however there's a trick here. If the receiver app (at least DefaultMediaReceiver) isn't actively PLAYING something (i.e., it's IDLE), it won't actually return a status, so you'd just get a message with an explicitly NULL "payload". The only time you'll get a RECEIVER status message with a payload.playerState of "IDLE" is when the receiver itself publishes it at the end of playback.

So I think your rules would have to be

  1. PLATFORM message with platform.applications == null means CC is idle for sure.
  2. RECEIVER message with payload.playerState = "IDLE" means CC is idle for sure.
  3. RECEIVER message with payload === null means CC is idle for sure.
  4. Otherwise, CC is active.

There's en edge case here though: If anything BESIDES the DefaultMediaReceiver is playing, there's no guarantee it has the same RECEIVER status message structure and the same rules apply, and if its an app this node doesn't support, the only messages you'd ever get are the PLATFORM message with an application that isn't the DefaultMediaReceiver... so you might need an additional check that if the platform.applications exists and has an appId that ISN'T "CC1AD845" (DefaultMediaReceiver), you might want to ASSUME that the CC is active, since you won't have RECEIVER status messages to go off in those cases.

Now technically, ANY of the supported apps will still publish RECEIVER status messages... I just tried with Google Play Music and it does for instance... but you might want to still assume that just having those apps RUNNING means you shouldn't interrupt them... otherwise you'd have to check for not just "CC1AD845" but the appId of ANY of the supported apps that would still conform to this RECEIVER status message structure. It'd just be easier to assume the following unless you really need to go down that avenue.

1a. PLATFORM message with platform.applications, but where appId = "CC1AD845" should be ignored, but any OTHER value caused CC = active to be set.