home-assistant / core

:house_with_garden: Open source home automation that puts local control and privacy first.
https://www.home-assistant.io
Apache License 2.0
74.2k stars 31.16k forks source link

Plex play_media service does not work #28662

Closed kirbyschapman closed 4 years ago

kirbyschapman commented 5 years ago

Home Assistant release with the issue:

version | 0.101.3

Last working Home Assistant release (if known): Unknown - I've never gotten it to work

Operating environment (Hass.io/Docker/Windows/etc.):

hass.io running on a pi 3b+ Integration:

https://www.home-assistant.io/integrations/plex/

Description of problem: With the updated version of the Plex integration, I can now connect to my Plex server. The plex media players show up and provide the correct information about what is playing, etc. The plex sensor also works correctly.

I'm now trying to start a playlist from the service tool. The error I get in the log is: 2019-11-09 08:56:19 DEBUG (SyncWorker_19) [homeassistant.components.plex.media_player] Using album artist because track artist was not found: Plex (Chrome)

This seems like a different response than what I should get for a playlist (not sure why it's telling me about a specific track artist?). The service command I'm sending is in the yaml below

Problem-relevant configuration.yaml entries and (fill out even if it seems unimportant):

entity_id: media_player.plex_chrome
media_content_id: '{ "playlist_name" : "morning music", "shuffle": "1" }'
media_content_type: PLAYLIST

Note: I eliminated the escaped double quotes because those caused JSON decoder errors (the media_content_id JSON is listed first as the example - assuming the error is the first instance of the escaped double quote):

media_content_id: '{ \"playlist_name\" : \"morning music\", \"shuffle\": \"1\" }'

2019-11-09 08:38:35 ERROR (MainThread) [homeassistant.components.websocket_api.http.connection.1916672816] Expecting property name enclosed in double quotes: line 1 column 3 (char 2)
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/components/websocket_api/commands.py", line 133, in handle_call_service
    connection.context(msg),
  File "/usr/src/homeassistant/homeassistant/core.py", line 1236, in async_call
    await asyncio.shield(self._execute_service(handler, service_call))
  File "/usr/src/homeassistant/homeassistant/core.py", line 1261, in _execute_service
    await handler.func(service_call)
  File "/usr/src/homeassistant/homeassistant/helpers/entity_component.py", line 213, in handle_service
    self._platforms.values(), func, call, service_name, required_features
  File "/usr/src/homeassistant/homeassistant/helpers/service.py", line 348, in entity_service_call
    future.result()  # pop exception if have
  File "/usr/src/homeassistant/homeassistant/helpers/service.py", line 372, in _handle_service_platform_call
    await func(entity, data)
  File "/usr/local/lib/python3.7/concurrent/futures/thread.py", line 57, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/usr/src/homeassistant/homeassistant/components/plex/media_player.py", line 594, in play_media
    src = json.loads(media_id)
  File "/usr/local/lib/python3.7/json/__init__.py", line 348, in loads
    return _default_decoder.decode(s)
  File "/usr/local/lib/python3.7/json/decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/usr/local/lib/python3.7/json/decoder.py", line 353, in raw_decode
    obj, end = self.scan_once(s, idx)
json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 3 (char 2)

Traceback (if applicable):

Not applicable

Additional information: I checked in the Plex logs (which are really hard to read), and it does something - I just can't figure out what it is doing...

jjlawren commented 5 years ago

Unfortunately the JSON currently needs to be escaped as media_content_id can only accept strings. The proper formatting would be: media_content_id: "{\"playlist_name\": \"morning music\", \"shuffle\": \"1\"}"

Have other types of media (MUSIC or VIDEO) played for you on this specific client? If nothing plays, you might be running into a more general problem where the Plex player is not discoverable by the Plex server. They need to be on the same subnet (discovery is done over UDP broadcast) and also be able to reach each other directly in order to send any commands from HA.

kirbyschapman commented 5 years ago

Thanks for the response, @jjlawren. I tried the escape quotes, but with single quotes on the outside of the brackets. I'll try it later today with the double quotes.

Based on the plex logs, the communication is happening. The player entity in HA shows all the right information, and the sensor shows correct information. Everything is on the same subnet.

Give me a few hours...

jjlawren commented 5 years ago

A quick test to see if commands work is to try to play/pause through a media control card. If that works then play_media should be possible on that client.

jjlawren commented 5 years ago

@kirbyschapman any luck seeing if the client responds to any commands?

kirbyschapman commented 5 years ago

Sorry for the delay, @jjlawren - got a little side tracked. I did set up the media control card for the each plex instance I have. They do work - I can play/pause plex through the interface.

jjlawren commented 5 years ago

That's a good sign. Let me know if you have any success/trouble with the media_player.play_media service with the escaped JSON formatting. I'd recommend to test playing a specific song/show/movie to ensure it's not a problem specific to the playlist or how that player handles playlists.

BTW, what Plex client type are you testing with?

kirbyschapman commented 5 years ago

Will do. Right now I'm just working with a chrome client. However, I have Plex running on three Rokus as well. That would be the ultimate goal.

kirbyschapman commented 4 years ago

Hi, @jjlawren - I finally got back to this. I'm still having json problems. I have a roku plex client that is called media_player.plex_bedroom. It was created when I first started Plex on the roku. SO that works great. Under states, the media_player.plex_bedroom shows all the correct information about what is playing at the moment. I added a media player in lovelace, and I can pause and restart whatever is playing from there. So that all seems good.

I went to services and selected media_player.play_media and added the following three lines to the service data:

entity_id: media_player.plex_bedroom
media_content_id: '{ \"library_name\" : \"Music\", \"artist_name\" : \"Adele\", \"album_name\" : \"25\", "\track_name\" : \"hello\", \"shuffle\": \"0\" }'
media_content_type: music

When I click to run the service, I get the following error in the log:

json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 3 (char 2)

If it's helpful, the complete traceback is:

2019-12-12 16:09:25 ERROR (MainThread) [homeassistant.components.websocket_api.http.connection.1832838192] Expecting property name enclosed in double quotes: line 1 column 3 (char 2)
Traceback (most recent call last):
  File "/usr/src/homeassistant/homeassistant/components/websocket_api/commands.py", line 133, in handle_call_service
    connection.context(msg),
  File "/usr/src/homeassistant/homeassistant/core.py", line 1235, in async_call
    await asyncio.shield(self._execute_service(handler, service_call))
  File "/usr/src/homeassistant/homeassistant/core.py", line 1260, in _execute_service
    await handler.func(service_call)
  File "/usr/src/homeassistant/homeassistant/helpers/entity_component.py", line 205, in handle_service
    self._platforms.values(), func, call, service_name, required_features
  File "/usr/src/homeassistant/homeassistant/helpers/service.py", line 336, in entity_service_call
    future.result()  # pop exception if have
  File "/usr/src/homeassistant/homeassistant/helpers/service.py", line 360, in _handle_service_platform_call
    await func(entity, data)
  File "/usr/local/lib/python3.7/concurrent/futures/thread.py", line 57, in run
    result = self.fn(*self.args, **self.kwargs)
  File "/usr/src/homeassistant/homeassistant/components/plex/media_player.py", line 593, in play_media
    src = json.loads(media_id)
  File "/usr/local/lib/python3.7/json/__init__.py", line 348, in loads
    return _default_decoder.decode(s)
  File "/usr/local/lib/python3.7/json/decoder.py", line 337, in decode
    obj, end = self.raw_decode(s, idx=_w(s, 0).end())
  File "/usr/local/lib/python3.7/json/decoder.py", line 353, in raw_decode
    obj, end = self.scan_once(s, idx)
json.decoder.JSONDecodeError: Expecting property name enclosed in double quotes: line 1 column 3 (char 2)

Thoughts on what I'm doing wrong? I tried removing the backslashes, but as you mentioned, that didn't work - I didn't get an error - nothing happened.

jjlawren commented 4 years ago

It seems silly, but I think it's because of the single quotes around the payload. Try replacing those with double quotes.

Also, make sure that none of the quotes are "fancy".

kirbyschapman commented 4 years ago

I checked to make sure the quotes are not fancy - they weren't. When I change the single quotes to double quotes, the submit button grays out and I can't run the service. I'm going to put it into an automation and see if that works. Maybe the service tool can't handle a json like this (?)

jjlawren commented 4 years ago

Just noticed that your example has "\track_name\" with the opening quotes escaped on the wrong side.

jjlawren commented 4 years ago

Alternatively you can use a script over the REST API. You'll need to create a long-lived token first:

#!/usr/bin/env python
import json
import requests

url = 'http://<HA_IP_AND_PORT>/api/services/media_player/play_media'
token = '<YOUR_LONG_LIVED_TOKEN>'
headers = {
    'content-type': 'application/json',
    'Authorization': 'Bearer {}'.format(token)
}
payload = {
    "entity_id": "media_player.<YOUR_PLEX_PLAYER>",
    "media_content_id": '{"library_name": "Music", "artist_name": "Adele", "album_name": "25", "track_name" : "hello" }',
    "media_content_type": "MUSIC"
}
print(json.dumps(payload))
result = requests.post(url, headers=headers, data=json.dumps(payload))
print(result, result.text)

EDIT: Updated media_content_id to enclose in single quotes--way easier to read.

jjlawren commented 4 years ago

Tested again with single quotes around the whole payload and that works fine. I think it was just the misaligned quotes around the JSON key.

inputd commented 4 years ago

I'm having the same issue. Using:

entity_id: media_player.plex_chrome media_content_id: "{\"library_name\":\"Movies\",\"video_name\":\"Your Name\",\"shuffle\":\"0\"}" media_content_type: VIDEO

As my Services data. A couples of things to note: Play/Pause/Next/Previous all work fine from the Media Player card After sending the service call, Plex does instantly fill its logs with a ton of data, but nothing changes. *If I misspell the name of the movie on purpose, I do get an error:

Failed to call service media_player/play_media. Unable to find elem: cls=None, attrs={'title__iexact': 'Your Namfe'}

So it appears the service call is formatted correctly, it appears Plex is receiving it, but it does not then do anything with it.

HA version 0.103.0 Hass.io supervisor 192

EDIT1: I found a section in the settings called "debug" within the Plex Web settings, enabled it, and then got the following error/issue:

First a message that says: "message": "[Companion] Successfully processed command from path \"/player/timeline/subscribe\"",

Then a message that says: "message": "[Companion] Received /player/playback/playMedia from Home Assistant",

Then an error that says: "message": "[Companion] encountered Cannot read property 'set' of undefined while processing {\n \"path\": \"/player/playback/playMedia\",\n \"queryAddress\": \"172-30-nn-bunchofthingsredactingjustincase.plex.direct\",\n \"queryContainerKey\": \"/playQueues/71?window=100&own=1\",\n \"queryKey\": \"/library/metadata/453\",\n \"queryMachineIdentifier\": \"1efcf1074b5morehexstuffredacting6477\",\n \"queryOffset\": \"0\",\n \"queryPort\": \"32400\",\n \"queryToken\": \"y6yyXTL4YEzqyusaeryE\",\n \"clientIdentifier\": \"REDACTED\",\n \"deviceName\": \"Home Assistant\",\n \"commandID\": \"38\"\n}",

jjlawren commented 4 years ago

*After sending the service call, Plex does instantly fill its logs with a ton of data, but nothing changes.

@inputd Can you please share anything in the logs from when this occurs? Have you tried with a non-browser Plex client?

Also please bump your log levels and share anything that's related to Plex:

logger:
  logs:
    homeassistant.components.plex: debug
    plexapi: debug
inputd commented 4 years ago

Logger enabled as you wrote out, with "default:critical" to filter out the garbage.

When using the chrome plex player again: Nothing appears in the log file when sending the service data

When casting from my phone to a chromecast: Nothing appears in the log file when sending the service data

When purposely sending an incorrect title name to the chome player Nothing appears in the log files, though I do get the error I mentioned before

When purposes sending an incorrect title name to the chromecast Nothing at all happens, in the logs or via error message

Every few seconds (30? 60?) the following appears:

2019-12-13 15:03:22 DEBUG (SyncWorker_2) [homeassistant.components.plex.server] Updating devices 2019-12-13 15:03:22 DEBUG (SyncWorker_2) [homeassistant.components.plex.server] Refreshing 1efcf107423406adbd3c28455234d56477:rkotqa1ozuvru6y81oxso854 2019-12-13 15:03:22 DEBUG (SyncWorker_2) [homeassistant.components.plex.server] Refreshing 1efc234206adbd3c28455134d56477:undefined 2019-12-13 15:03:22 DEBUG (MainThread) [homeassistant.components.plex.media_player] Refreshing media_player.plex_chrome [<PlexClient:http://127.0.0.1:324:Chrome> / ] 2019-12-13 15:03:22 DEBUG (MainThread) [homeassistant.components.plex.media_player] Refreshing media_player.plex_chromecast [ / ] 2019-12-13 15:03:22 DEBUG (SyncWorker_19) [homeassistant.components.plex.sensor] Refreshing sensor [sensor-1efcf1074b534543533008d56477]

EDIT:

Ok, filtering out the garbage filters out too much. So I waded through the logs with filtering off, and there are only two additional lines.

2019-12-13 15:42:47 DEBUG (MainThread) [homeassistant.components.websocket_api.http.connection.139864363375888] Received {'type': 'call_service', 'domain': 'media_player', 'service': 'play_media', 'service_data': {'entity_id': 'media_player.plex_chrome', 'media_content_id': '{"library_name":"Movies","video_name":"Your Name","shuffle":"0"}', 'media_content_type': 'VIDEO'}, 'id': 89} 2019-12-13 15:42:47 DEBUG (MainThread) [homeassistant.core] Bus:Handling <Event call_service[L]: domain=media_player, service=play_media, service_data=entity_id=media_player.plex_chrome, media_content_id={"library_name":"Movies","video_name":"Your Name","shuffle":"0"}, media_content_type=VIDEO>

jjlawren commented 4 years ago

@kirbyschapman any luck with the suggestions on Dec 13?

@inputd some Plex players simply don't accept playback commands sent by the Plex server. Can you try with another client, such as the Plex desktop client, Plexamp, etc?

kirbyschapman commented 4 years ago

Hello, @jjlawren - I finally got back to this, and it works now as expected! I have tested the integration on 2 different Roku models, and on a Chrome browser running Plex. They all show up and report as they should. I'm running 0.111.4 (latest at this point in time)

Thanks for the effort on this - I now can use this to determine other things (anyone actually watching, or has the player been idle for xx minutes, etc)!

jjlawren commented 4 years ago

@kirbyschapman good to hear. I can't tell from your comment, but do the play_media commands work properly for you now?

kirbyschapman commented 4 years ago

but do the play_media commands work properly for you now

Yep - the work with Rokus and a Chrome browser - those are the only things i've tested so far. Again, thanks for pulling this along

kirbyschapman commented 4 years ago

Hi, @jjlawren - noticed something that I'm not sure is related to my setup, or if it's a general thing. Once the Rokus are in home or idle state, the plex players, after a brief period of time, go to unavailable. Once they are unavailable, they won't accept commands (there's no error, they just don't wake up and respond). It seems that the only way to get them active again is to start playing something (anything - song, playlist, movie). Then I can send it commands. But that kind of defeats the purpose of an automation. Thoughts? Are there settings I need to play around with? I'm not clear on how the players are created and updated.

As an example, in the morning, we use the TV coming on at a specific time as our alarm clock. It would be wonderful to have Plex start on the Roku (which I can do), and then start a specific playlist. But by morning, the player has become unavailable and won't accept commands. Is there any way to wake up the player?

Again, thanks for the effort!

jjlawren commented 4 years ago

That's because the Plex server stops reporting the client availability. Give 0.112 a shot when it comes out later today and see if it improves this behavior. It adds another method to discover and connect to Plex clients which may make your scenario more reliable.

kirbyschapman commented 4 years ago

That makes sense, @jjlawren, and is what I thought was happening. I'll update to 0.112 this weekend and see if the new discovery methods make a difference. Thanks!

kirbyschapman commented 4 years ago

After updating to 0.112.x, I don't really notice a change in behavior. The plex players show up only after I start playing something, and then become unavailable once I change a Roku to something other than Plex. Any thoughts on how to keep the players available?

jjlawren commented 4 years ago

The Plex clients are marked as "Unavailable" because they essentially no longer exist once you turn off the device or switch to a different app on the device.

What's the use-case for keeping them available when they no longer report data and cannot be controlled?

kirbyschapman commented 4 years ago

Hi, @jjlawren - The use case is:

Another case is our workout room:

Interestingly, this morning, the playlist actually started, so the player must have been active even though the Roku was 'home.' Last night, I used the room and played a playlist until 930 pm. At that point, the Roku was switched form Plex to Home (verified in the logs), and then went to idle after 10 minutes. Looking at the player history, it went to idle as well and stayed there until this morning. My wife had used the room and YouTube (not sure this is relevant). Then it was my turn, and the player started up like it was supposed to. I use a Python script to manage all this (which I've verified works):

  media_content  = '{ "playlist_name": "' + plex_playlist +'", "shuffle": "1" }'
  logger.info("media content string: {}".format(media_content))
  service_data   = {'entity_id': plex_player, 'media_content_type': 'PLAYLIST', 'media_content_id': media_content }
  hass.services.call('media_player', 'play_media', service_data, False)

Is there a way to kick-start the client when it's unavailable?

jjlawren commented 4 years ago

It's very dependent on the behavior of the Plex clients/server. We have 3 ways of knowing a Plex client exists:

  1. If it's actively playing something.
  2. If it responds to broadcast packets sent by the Plex server. This requires Plex client & server to be on the same local subnet unless special steps are taken.
  3. If the Plex client is signed into a Plex account and reports its presence to plex.tv, which we query periodically. This is the latest addition.

If it's not available from any of those sources, we don't have a way to discover the client. It's technically possible to attempt to connect to a client by IP:port, but I haven't thought through how that would be implemented and/or configured yet.