mirror of
https://github.com/pkkid/python-plexapi
synced 2024-11-10 14:14:19 +00:00
Add lookup & control of linked Sonos speakers
This commit is contained in:
parent
6eea7cce0f
commit
55b335ee7c
3 changed files with 112 additions and 1 deletions
|
@ -157,7 +157,7 @@ class PlexClient(PlexObject):
|
||||||
log.debug('%s %s', method.__name__.upper(), url)
|
log.debug('%s %s', method.__name__.upper(), url)
|
||||||
headers = self._headers(**headers or {})
|
headers = self._headers(**headers or {})
|
||||||
response = method(url, headers=headers, timeout=timeout, **kwargs)
|
response = method(url, headers=headers, timeout=timeout, **kwargs)
|
||||||
if response.status_code not in (200, 201):
|
if response.status_code not in (200, 201, 204):
|
||||||
codename = codes.get(response.status_code)[0]
|
codename = codes.get(response.status_code)[0]
|
||||||
errtext = response.text.replace('\n', ' ')
|
errtext = response.text.replace('\n', ' ')
|
||||||
message = '(%s) %s; %s %s' % (response.status_code, codename, response.url, errtext)
|
message = '(%s) %s; %s %s' % (response.status_code, codename, response.url, errtext)
|
||||||
|
|
|
@ -12,6 +12,7 @@ from plexapi.client import PlexClient
|
||||||
from plexapi.compat import ElementTree
|
from plexapi.compat import ElementTree
|
||||||
from plexapi.library import LibrarySection
|
from plexapi.library import LibrarySection
|
||||||
from plexapi.server import PlexServer
|
from plexapi.server import PlexServer
|
||||||
|
from plexapi.sonos import PlexSonosClient
|
||||||
from plexapi.sync import SyncItem, SyncList
|
from plexapi.sync import SyncItem, SyncList
|
||||||
from plexapi.utils import joinArgs
|
from plexapi.utils import joinArgs
|
||||||
from requests.status_codes import _codes as codes
|
from requests.status_codes import _codes as codes
|
||||||
|
@ -209,6 +210,12 @@ class MyPlexAccount(PlexObject):
|
||||||
data = self.query(MyPlexResource.key)
|
data = self.query(MyPlexResource.key)
|
||||||
return [MyPlexResource(self, elem) for elem in data]
|
return [MyPlexResource(self, elem) for elem in data]
|
||||||
|
|
||||||
|
def sonos_speakers(self):
|
||||||
|
if 'companions_sonos' not in self.subscriptionFeatures:
|
||||||
|
return []
|
||||||
|
data = self.query('https://sonos.plex.tv/resources')
|
||||||
|
return [PlexSonosClient(self, elem) for elem in data]
|
||||||
|
|
||||||
def inviteFriend(self, user, server, sections=None, allowSync=False, allowCameraUpload=False,
|
def inviteFriend(self, user, server, sections=None, allowSync=False, allowCameraUpload=False,
|
||||||
allowChannels=False, filterMovies=None, filterTelevision=None, filterMusic=None):
|
allowChannels=False, filterMovies=None, filterTelevision=None, filterMusic=None):
|
||||||
""" Share library content with the specified user.
|
""" Share library content with the specified user.
|
||||||
|
|
104
plexapi/sonos.py
Normal file
104
plexapi/sonos.py
Normal file
|
@ -0,0 +1,104 @@
|
||||||
|
# -*- coding: utf-8 -*-
|
||||||
|
import requests
|
||||||
|
from plexapi import CONFIG
|
||||||
|
from plexapi.client import PlexClient
|
||||||
|
from plexapi.exceptions import BadRequest
|
||||||
|
from plexapi.playqueue import PlayQueue
|
||||||
|
|
||||||
|
|
||||||
|
class PlexSonosClient(PlexClient):
|
||||||
|
""" Class for interacting with a Sonos speaker via the Plex API. This class
|
||||||
|
makes requests to an external Plex API which then forwards the
|
||||||
|
Sonos-specific commands back to your Plex server & Sonos speakers. Use
|
||||||
|
of this feature requires an active Plex Pass subscription and Sonos
|
||||||
|
speakers linked to your Plex account. It also requires remote access to
|
||||||
|
be working properly.
|
||||||
|
|
||||||
|
More details on the Sonos integration are avaialble here:
|
||||||
|
https://support.plex.tv/articles/218237558-requirements-for-using-plex-for-sonos/
|
||||||
|
|
||||||
|
The Sonos API emulates the Plex player control API closely:
|
||||||
|
https://github.com/plexinc/plex-media-player/wiki/Remote-control-API
|
||||||
|
|
||||||
|
Parameters:
|
||||||
|
server (:class:`~plexapi.myplex.PlexAccount`): PlexAccount instance this
|
||||||
|
Sonos speaker is associated with.
|
||||||
|
data (ElementTree): Response from Plex Sonos API used to build this client.
|
||||||
|
|
||||||
|
Attributes:
|
||||||
|
deviceClass (str): "speaker"
|
||||||
|
lanIP (str): Local IP address of speaker.
|
||||||
|
machineIdentifier (str): Unique ID for this device.
|
||||||
|
platform (str): "Sonos"
|
||||||
|
platformVersion (str): Build version of Sonos speaker firmware.
|
||||||
|
product (str): "Sonos"
|
||||||
|
protocol (str): "plex"
|
||||||
|
protocolCapabilities (list<str>): List of client capabilities (timeline, playback,
|
||||||
|
playqueues, provider-playback)
|
||||||
|
server (:class:`~plexapi.server.PlexServer`): Server this client is connected to.
|
||||||
|
session (:class:`~requests.Session`): Session object used for connection.
|
||||||
|
title (str): Name of this Sonos speaker.
|
||||||
|
token (str): X-Plex-Token used for authenication
|
||||||
|
_baseurl (str): Address of public Plex Sonos API endpoint.
|
||||||
|
_commandId (int): Counter for commands sent to Plex API.
|
||||||
|
_token (str): Token associated with linked Plex account.
|
||||||
|
_session (obj): Requests session object used to access this client.
|
||||||
|
"""
|
||||||
|
|
||||||
|
def __init__(self, account, data):
|
||||||
|
self._data = data
|
||||||
|
self.deviceClass = data.attrib.get("deviceClass")
|
||||||
|
self.machineIdentifier = data.attrib.get("machineIdentifier")
|
||||||
|
self.product = data.attrib.get("product")
|
||||||
|
self.platform = data.attrib.get("platform")
|
||||||
|
self.platformVersion = data.attrib.get("platformVersion")
|
||||||
|
self.protocol = data.attrib.get("protocol")
|
||||||
|
self.protocolCapabilities = data.attrib.get("protocolCapabilities")
|
||||||
|
self.lanIP = data.attrib.get("lanIP")
|
||||||
|
self.title = data.attrib.get("title")
|
||||||
|
self._baseurl = "https://sonos.plex.tv"
|
||||||
|
self._commandId = 0
|
||||||
|
self._token = account._token
|
||||||
|
self._session = account._session or requests.Session()
|
||||||
|
|
||||||
|
# Dummy values for PlexClient inheritance
|
||||||
|
self._last_call = 0
|
||||||
|
self._proxyThroughServer = False
|
||||||
|
self._showSecrets = CONFIG.get("log.show_secrets", "").lower() == "true"
|
||||||
|
|
||||||
|
def playMedia(self, media, offset=0, **params):
|
||||||
|
|
||||||
|
if hasattr(media, "playlistType"):
|
||||||
|
mediatype = media.playlistType
|
||||||
|
else:
|
||||||
|
if isinstance(media, PlayQueue):
|
||||||
|
mediatype = media.items[0].listType
|
||||||
|
else:
|
||||||
|
mediatype = media.listType
|
||||||
|
|
||||||
|
if mediatype == "audio":
|
||||||
|
mediatype = "music"
|
||||||
|
else:
|
||||||
|
raise BadRequest("Sonos currently only supports music for playback")
|
||||||
|
|
||||||
|
server_protocol, server_address, server_port = media._server._baseurl.split(":")
|
||||||
|
server_address = server_address.strip("/")
|
||||||
|
server_port = server_port.strip("/")
|
||||||
|
|
||||||
|
playqueue = media if isinstance(media, PlayQueue) else media._server.createPlayQueue(media)
|
||||||
|
self.sendCommand("playback/playMedia", **dict({
|
||||||
|
"type": "music",
|
||||||
|
"providerIdentifier": "com.plexapp.plugins.library",
|
||||||
|
"containerKey": f"/playQueues/{playqueue.playQueueID}?own=1",
|
||||||
|
"key": media.key,
|
||||||
|
"offset": offset,
|
||||||
|
"machineIdentifier": media._server.machineIdentifier,
|
||||||
|
"protocol": server_protocol,
|
||||||
|
"address": server_address,
|
||||||
|
"port": server_port,
|
||||||
|
"token": media._server.createToken(),
|
||||||
|
"commandID": self._nextCommandId(),
|
||||||
|
#"X-Plex-Client-Identifier": client_id,
|
||||||
|
"X-Plex-Token": media._server._token,
|
||||||
|
"X-Plex-Target-Client-Identifier": self.machineIdentifier,
|
||||||
|
}, **params))
|
Loading…
Reference in a new issue