PlexDevices - Custom Media Player Component

Can you you help me understand how I can modify the code so I can request a PLEX player to shuffle a ‘show or season’ instead of always having to provide an episode number? Or is it possible to get this feature updated in a future release? Not sure if I’m the only one trying to do this…

I’ve been looking into it, tested some things, but no luck. I’ll keep looking but I’ll take any working sample code you find.

I was able to get this working with my previous automation system by first having plex generate a playqueue - with a URL like below:

https://PLEXSERVERIP:32400/playQueues?shuffle=1&type=video&uri=library%3A%2F%2F61d6d471-b0e9-4018-92f1-b84584fbbbea%2Fdirectory%2F%252Flibrary%252Fmetadata%252F1702&X-Plex-Client-Identifier=72c64643c33ce2dfd70eba6fbc74d613&X-Plex-Token=TOKEN

I then parsed out the playQueueID (queueId), and playQueueSelectedMetadataItemID (playId) from the XML response.

Then I would hit the player up (a Roku, in this case) with the below URL and passed in the above parameters.

http://ROKUIP:8324/player/playback/playMedia?key=/library/metadata/“+playId+”&offset=0&machineIdentifier=cf0635a38663bbf7646943a24dfd35e00c09a9d6&protocol=http&address=10.0.0.100&port=32400&containerKey=/playQueues/“+queueId+”&type=video&token=TOKEN&clientidentifier=72c64643c33ce2dfd70eba6fbc74d613

I was able to track this down by using fiddler/chrome dev network tools to see what Plex Web was doing to make this happen when I had the Plex Web player ‘remote control’ my roku, and hit play on a show/season.

If we can’t get this working as part of the media_player, I can proceed with just making it a shell_command to run a couple curl calls, etc.

Thanks,
Brandon

Got something working via playlists but with the side effect of all these playlists being created. I’ll try a different route with just a play queue.

Got it. Will post in a bit.

@lethic - Test this code out. Follow the instructions with the file I posted earlier. Then rename _get_episode (should be about 2 references found to _get_tv_media and then replace the functions _get_tv_media and _client_play_media (both towards the end of the file) with this:

    def _get_tv_media(self, library_name, show_name, season_number,
                      episode_number):
        """Find TV media and return a Plex media object."""
        target_season = None
        target_episode = None

        show = self.device.server.library.section(library_name).get(
            show_name)

        if not season_number:
            playlist_name = (self.entity_id + ' - ' + show_name + ' Episodes')
            return self.device.server.createPlaylist(
                playlist_name, show.episodes())

        for season in show.seasons():
            if int(season.seasonNumber) == int(season_number):
                target_season = season
                break

        if target_season is None:
            _LOGGER.error('Season not found: %s\\%s - S%sE%s', library_name,
                          show_name,
                          str(season_number).zfill(2),
                          str(episode_number).zfill(2))
        else:
            if not episode_number:
                playlist_name = (self.entity_id + ' - ' + show_name +
                                 ' Season ' + str(season_number) + ' Episodes')
                return self.device.server.createPlaylist(
                    playlist_name, target_season.episodes())

            for episode in target_season.episodes():
                if int(episode.index) == int(episode_number):
                    target_episode = episode
                    break

            if target_episode is None:
                _LOGGER.error('Episode not found: %s\\%s - S%sE%s',
                              library_name, show_name,
                              str(season_number).zfill(2),
                              str(episode_number).zfill(2))

        return target_episode

    def _client_play_media(self, media, delete=False, **params):
        """Instruct Plex client to play a piece of media."""
        if not (self.device and
                'playback' in self._device_protocol_capabilities):
            _LOGGER.error('Client cannot play media: %s', self.entity_id)
            return

        import plexapi.playqueue

        playqueue = plexapi.playqueue.PlayQueue.create(self.device.server,
                                                       media, **params)

        self._local_client_control_fix()

        server_url = self.device.server.baseurl.split(':')
        self.device.sendCommand('playback/playMedia', **dict({
            'machineIdentifier':
            self.device.server.machineIdentifier,
            'address':
            server_url[1].strip('/'),
            'port':
            server_url[-1],
            'key':
            media.key,
            'containerKey':
            '/playQueues/%s?window=100&own=1' % playqueue.playQueueID,
        }, **params))

        # delete dynamic playlists used to build playqueue (ex. play tv season)
        if delete:
            media.delete()

Then just call the service like you previously did but leave season_number OR season_number and episode_number blank like this:

{
    "entity_id" : "media_player.plex_7b8c4c63_38ff_417d_ac14_0457796ee1c1",
    "media_content_id": "{ \"library_name\" : \"Adult TV\", \"show_name\" : \"Rick and Morty\", \"season_number\" : \"\", \"episode_number\" : \"\", \"shuffle\": \"1\" }",
    "media_content_type": "EPISODE"
}
1 Like

Submitted for official HA review and incorporation here:

1 Like

Gave it a try, and it works like a charm. You just improved the WAF by 10! :slight_smile:

I’m going to set up some automation rules to include this, so I’ll let you know if I encounter any issues. Can’t wait to see this in the main branch.

Thanks again for the awesome updates to the PLEX component.

I did notice it is creating the playlists… so I assume doing a playqueue wasn’t possible or as easy. Should I update the code to also pass in true for delete, so it cleans them up?

The playlists get cleaned up in my testing as I do have a line of code that deletes the playlist as soon as the play queue starts.

It doesn’t look like the play queue supports ITEMS as the code behind the api shows accepting a playlist or a single media. So I create a playlist, feed it to the queue, and then delete the playlist.

If the playlist is not being deleted, can you try the following? Move this code:

        # delete dynamic playlists used to build playqueue (ex. play tv season)
        if delete:
            media.delete()

Directly under (i.e. the line right after) this code:

        playqueue = plexapi.playqueue.PlayQueue.create(self.device.server,
                                                       media, **params)

I don’t think it is a placement issue, the default value for ‘delete’ is set to false, and the only place I see calling the _client_play_media function is inside play_media, and it doesn’t provide a value… so it will always be false. Did you modify that on your side maybe? If so, I can do the same and see if it works.

        if media:
        self._client_play_media(media, shuffle=src['shuffle'])

I think you’re right - I may have missed copying a line from my dev box. I’ll look in a bit

Are you using the latest commit?
https://github.com/JesseWebDotCom/home-assistant/blob/ae21fa9ce1f29aa6fc958cd7afc2399af68a7055/homeassistant/components/media_player/plex.py

This is the code that deletes the playlist after its put in the play queue:

        if media and media_type == 'EPISODE' and str(media.type) == 'playlist':
            self._client_play_media(media=media, delete=True,
                                    shuffle=src['shuffle'])
        elif media:
            self._client_play_media(media=media, shuffle=src['shuffle'])

Nope, I grabbed the file you posted earlier since you told me to. :stuck_out_tongue: (“Test this code out. Follow the instructions with the file I posted earlier.”)

I’ll pull the latest file and give it a try.

I notice a problem when using this code. When play a music in Plex, the HA front end could not load when I refresh it. It just stuck at “Loading data” page.

I see this error in the log…

17-04-02 11:30:19 ERROR (MainThread) [homeassistant.core] Error doing job: Exception in callback ActiveConnection.handle_subscribe_events.<locals>.forward_events(<Event state_...x_web_firefox>) at /usr/local/lib/python3.4/dist-packages/homeassistant/components/websocket_api.py:346
Traceback (most recent call last):
  File "/usr/local/lib/python3.4/dist-packages/homeassistant/remote.py", line 325, in default
    return json.JSONEncoder.default(self, obj)
  File "/usr/lib/python3.4/json/encoder.py", line 173, in default
    raise TypeError(repr(o) + " is not JSON serializable")
TypeError: __NA__ is not JSON serializable

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/local/lib/python3.4/dist-packages/homeassistant/remote.py", line 331, in default
    for child_obj in obj]
TypeError: '_NA' object is not iterable

During handling of the above exception, another exception occurred:

Traceback (most recent call last):
  File "/usr/lib/python3.4/asyncio/events.py", line 120, in _run
    self._callback(*self._args)
  File "/usr/local/lib/python3.4/dist-packages/homeassistant/components/websocket_api.py", line 353, in forward_events
    self.send_message(event_message(msg['id'], event))
  File "/usr/local/lib/python3.4/dist-packages/homeassistant/components/websocket_api.py", line 221, in send_message
    self.wsock.send_json(message, dumps=JSON_DUMP)
  File "/home/pi/.homeassistant/deps/aiohttp/web_ws.py", line 197, in send_json
    return self.send_str(dumps(data))
  File "/usr/lib/python3.4/json/__init__.py", line 237, in dumps
    **kw).encode(obj)
  File "/usr/lib/python3.4/json/encoder.py", line 192, in encode
    chunks = self.iterencode(o, _one_shot=True)
  File "/usr/lib/python3.4/json/encoder.py", line 250, in iterencode
    return _iterencode(o, 0)
  File "/usr/local/lib/python3.4/dist-packages/homeassistant/remote.py", line 334, in default
    return json.JSONEncoder.default(self, obj)
  File "/usr/lib/python3.4/json/encoder.py", line 173, in default
    raise TypeError(repr(o) + " is not JSON serializable")
TypeError: __NA__ is not JSON serializable
17-04-02 11:33:12 ERROR (MainThread) [homeassistant.components.websocket_api] WS 1843984368: Unexpected TypeError {'id': 3, 'type': 'get_states'}

It doesn’t happen when I delete the custom component and use the official one.

Latest file works like a charm and deletes the playlist. Thanks!

1 Like

Yep (thanks), should be fixed here:
https://github.com/JesseWebDotCom/home-assistant/blob/b99dd19ad64e072d1e311633ac00fac9ada19c02/homeassistant/components/media_player/plex.py

thanks! it works now.

Hi everyone,
Might be a silly question but I’d love to know how you are all doing certain things. Relatively new to HA and things like that and reading some of the latest comments has my interest piqued.

How are you doing things like exposing the username playing the Plex content to HA? What I mean is, how can I have that displayed? I’d like to be able to see who is playing the content from the media_player component. Also with the rating? Can that be displayed along with the username in the same component?

@lethic what do your automations look like that allow you to start playing content on Plex from HA? I’d love to know that!

One last thing, does anyone have issues with the media players in the HA UI still showing something is playing when it’s been hours since the media was stopped? It’s​ like they aren’t​ going into idle as I assume they should be?

Regards,
William

Can’t comment on the username, etc - I simply just use the existing Plex Sensor for that which does show it when clicking on the card.

For the automations, I currently only have one set up right now - wife likes to fall asleep to a show, so I do this command when we turn off our house/goodnight, after 9pm (and before 5am).

- service: media_player.play_media
  data:
    entity_id: media_player.plex_master_bedroom
    media_content_id: "{\"library_name\": \"TV\", \"show_name\": \"ShowNameGoesHere\", \"season_number\": \"\", \"episode_number\": \"\", \"shuffle\": \"1\"}"
    media_content_type: "EPISODE"

Hi. How does one implement this? I am new. What do I do with the plex.py file? What do I need to change/add in configuration.yaml? Are there instructions somewhere on how to set this up?

Thanks guys.
HA Newb