-
Notifications
You must be signed in to change notification settings - Fork 170
Remote control API
The areas marked "MUST" below have a degree of leeway when implemented on clients which have restricted or very different UI conventions (e.g. Roku).
- Controller MUST display a player selection button during navigation and during media playback.
- Button displays a selection dialog with all players:
- If the controller is a player, it MUST list itself first.
- The dialog MUST have a refresh button which refreshes the list of players by all means available.
- Selecting a player results in:
- The player selection button highlights to show a remote player is selected.
- All play operations default to the selected remote player.
- The selected remote player does NOT persist across app launches.
- Failure to connect to the player (i.e. subscribe) or at any time, if sending a command fails, the controller MUST notify the user via a platform appropriate toast and consider the player disconnected. This implies button status and if the controller is showing the Now Playing screen, it should just back to showing local state.
- When controlling a player, the controller MUST display a "Now Playing" button that changes its icon as appropriate for the type of media the player is playing. If the player is not playing any media, then this button is grayed out. When clicked, the controller MUST navigate to an appropriate control screen for the media type. This screen MAY display a "Now Playing" button if appropriate. When not controlling a player, the "Now Playing" button MUST lead to a local media screen as appropriate (e.g. local music).
- The Now Playing button leads to a media-appropriate playback screen (e.g.
music artwork + seek bar + buttons).
- The remote screen SHOULD resemble the local playback screen as much as possible, or be identical (e.g. for music).
- For remote video, the screen should show background art in place of actual playing video.
- For photo galleries, the screen should show the same images as the player, and offer standard back/forward buttons.
- As noted above, the screen MUST have a player selection button.
- If multiple type of media are active (slideshow + music) the controller should show the foreground media (slideshow).
- When in navigation mode, changing the selected player changes which player is being controlled.
- When on the Now Playing screen, changing the selected player moves the media playback between players.
- For example, if controller is showing a video being played remotely, and user selects local player, playback MUST be stopped on the remote player, and resumed on the local one.
- If music is playing on the local player, and a remote player is selected, music playback MUST stop on the local player and be picked up on the remote player.
- When playback is stopped on the player (e.g. a local user at the player stops a video and goes back to navigation), and the controller is on the Now Playing screen, it remains there, but shows the stopped state.
The following UI flow should be employed when running into these error conditions:
- Error on starting remote playback: Present user with a dialog indicating a failure, and offering the choice to play the media back locally. "Your video was not able to start, would you like to play on this device?"
- Error occurs during playback: If the application is active, and the controller isn't playing other media, the controller should issue a similar error to above and offer continuing the playback locally.
- Players MUST advertise as supporting the ‘plex’ protocol and the plex/media-player content-type.
- Players MUST advertise their client identifier in order to allow controllers to uniquely identify them.
The response is line-terminated with CRLF:
HELLO * HTTP/1.0
Content-Type: plex/media-player
Name: Smoke
Port: 32467
Product: Plex for Windows
Protocol: plex
Protocol-Version: 1
Protocol-Capabilities: timeline,playback,navigation,mirror,playqueues
Resource-Identifier: 02a06dff-8014-472a-9aec-261a847a7b47
Version: 1.5.0.0
In order to test availability, all Plex resources which are running MUST respond
to the /resources
endpoint with a Media Container containing one Device child element
for each functionality the device provides (e.g. player, server). This endpoint is
implemented also by myPlex, which will return registered players and servers. In
addition, the media server can return multiple players in the case of proxying,
for example, AirPlay and DLNA players.
For example:
<MediaContainer>
<Server title="Office" machineIdentifier="x" platform="MacOSX" platformVersion="10.9.0" /> <!-- All existing root attributes -->
<Player title="Smoke" machineIdentifier="abc123" product="Plex for Windows" version="1.6.0.4" platform="Windows" platformVersion="6.3.9600" protocolVersion="1" protocolCapabilities="timeline,playback" deviceClass="pc" />
<Player title="Living Room Apple TV" ... deviceProtocol="airplay" />
</MediaContainer>
The deviceClass
parmeter is one of: phone, tablet, stb, tv, pc, cloud. The
deviceProtocol
attribute defaults to speaking "plex" protocol.
If the resources endpoint is exposing resources that are not local (e.g. myPlex, or the media server talking about non-proxied players), then each resouce element has a list of connections.
<MediaContainer>
<Server ...>
<Connection protocol="http" address="x.x.x.x" port="p" class="wifi">
<Connection protocol="http" address="x.x.x.y" port="p" class="ethernet">
</Server>
</MediaContainer>
Add new headers to GDM player advertising:
- Protocol-Version (default is 0, 1 means you support this new API).
-
Protocol-Capabilities comma-separated values for parts of the protocol
supported. Example header value:
timeline,playback,navigation,mirror,playqueues
(all five supported).- Players MUST implement the timeline and playback sections.
- They MAY choose to not implement navigation.
- Supporting playqueue means the player will manage moving the window of the playqueue as tracks progress as well as responding to requests refreshPlayQueue from a controller. If this capability is missing the controller might want to put the queue in read-only mode when it's being casted since there is no way to notify the player about updates.
- Server returns new attributes in
/clients
(and new/resources
) endpoint:protocolVersion="x"
protocolCapabilities="x,y,z"
There are three sources of player information for a controller:
- GDM discovery.
- The PMS
/clients
endpoint. - The upcoming myPlex
/resources
endpoint, which will return players and servers. - The upcoming PMS
/resources
endpoint, which will return server and proxied player.
Controllers should manage player discovery in an asynchronous fashion, so that presenting the player selection dialog is instantaneous from a UX perspective. In all likelihood code can be generally shared with existing server management code.
Controllers should ask all known servers for resources, as different servers could be proxying different players for various reasons. Players connectivity testing should be performed identically to server testing (where multiple connections might exist).
All commands sent to a player MUST include a X-Plex-Client-Identifier
header
identifying the controller.
All messages sent to a controller (i.e. timeline requests) MUST include a
X-Plex-Client-Identifier
header identifying the player.
Controllers MUST send commands directly to any player that implements the ‘plex’
protocol. Servers proxying commands to a non-native player device (e.g.
DLNA/AirPlay devices) may masquerade as ‘plex’ players by advertising as players
and proxying requests from controllers. Consequently, all requests sent to a
player MUST include an X-Plex-Target-Client-Identifier
header, identifying the
player the controller believes it is talking to. This allows a proxy player to
multiplex requests to N players.
If the controller sees that the target identifier is the same as the identifier (all cases but proxied player), it's free to omit the header.
If present, players should verify that X-Plex-Target-Client-Identifier
matches
its own identifier and return HTTP error 404 if it doesn't.
Commands return an HTTP response code of 200 OK and an empty body if they
succeed, and an HTTP error code if they fail. Besides the general 400
(badly formed request), general application-level errors are returned as
HTTP 500 errors, along with code/status in an XML Response
. Specific
error codes will be defined as we go.
<Response code=”xxx” status=”This is a message”>
-
/player/timeline/subscribe?protocol=PPP&port=XXX&commandID=Y
- Required Headers:
X-Plex-Client-Identifier
,X-Plex-Device-Name
- Controller sends subscribe every 30 seconds.
- Protocol is http or https.
- Player Subscription times out after 90 seconds.
- Player MUST respond to subscribe by immediately sending timeline data to the new observer. If the player isn't playing, this is used to pass initial state (e.g. volume levels) for each type.
- If player is idle, it MUST at least pass back the minimal state information as shown above.
- Players MUST accept and process commands received from unsubscribed controllers, even if they lack a commandID.
- If the controller is currently subscribed to a remote player, the controller MUST disconnect from them when it receives a subscribe.
- If the controller currently has subscriptions from controllers, when they
subscribe to a remote player, the controller MUST disconnnect all
subscriptions and send a final timeline with
disconnected="1"
in the container.
- Required Headers:
-
/player/timeline/unsubscribe
- Required Headers:
X-Plex-Client-Identifier
,X-Plex-Device-Name
- Takes effect immediately.
- Removed by client identifier.
- Required Headers:
-
/player/timeline/poll?wait=0/1
- Required Headers:
X-Plex-Client-Identifier
,X-Plex-Device-Name
- This is an alternative to the subscribe command for controllers that cannot use persistent connections to receive updates from the player.
- Player MUST reply to poll with the same response as is POSTed via timeline as defined below.
- If
wait=0
(or not specified), the player MUST respond immediately. - If
wait=1
, the controller MUST hold the request and respond to the player when the next timeline request would be sent to an actual subscriber. - Client MUST pass the
X-Plex-Client-Identifier
header with this request to allow the player to track these more ephemeral subscribers. - Note that
commandID
must be sent to this endpoint too, this allows debouncing with commands sent.
- Required Headers:
/player/playback/setParameters?volume=[0, 100]&shuffle=0/1&repeat=0/1/2
/player/playback/setStreams?audioStreamID=X&subtitleStreamID=Y&videoStreamID=Z
-
/player/playback/seekTo?offset=XXX
- Offset is measured in milliseconds. -
/player/playback/skipTo?key=X
- Playback skips to item with matching key. -
/player/playback/refreshPlayQueue?playQueueID=XXX
- Tells the player that a playQueue needs to be refreshed. This is usually done by the controller when it has modified a playQueue that is being casted. The player should make sure to refresh the playQueue if it's currently displayed or playing. The playQueueID is also supplied to differenciate between multiple PlayQueues if available. When a PlayQueue is refreshed the player MUST update all controllers with a timeline request described below. /player/navigation/home
-
/player/navigation/music
- Navigate to the player’s music playback view, if music is currently playing. -
/player/mirror/details
- Can be called from the remote to show the pre-play screen for a specific item. The function takes the following args:-
key
is the relative path of the item to show -
machineIdentifier
is the machineIdentifier of the server that the item is located. -
address
is the ip address of the target server -
port
is the port used by the target server -
protocol
is a scheme: http or https. No protocol argument means http. -
token
is the access token the player should use to access the server. (NB - the controller must obtain a transient token from the server).
-
All of the playback commands take a mandatory type
argument, to specify
which media type to apply the command to, (except for playMedia
).
-
/player/playback/playMedia
now accepts key, offset, machineIdentifier, address, port, protocol, token, and containerKey parameters instead of the previous path parameter. This is consistent with the /:/timeline parameters, and provides players with all information required to establish a connection.-
key
is a relative path that provides metadata for the item to play. -
offset
is an integer representing the number of milliseconds at which to start playing, with zero representing the beginning -
machineIdentifier
is the machine identifier of the target server. -
address
is the ip address of the target server -
port
is the port used by the target server -
protocol
is a scheme: http or https. No protocol argument means http. -
token
is the access token the player should use to access the server. (NB - the controller must obtain a transient token from the server). - If the video is coming from a myPlex queue, the
machineIdentifier
must benode
, andaddress
must benode.plexapp.com
. -
containerKey
is a relative path that provides the media container containing a set of items for context around thekey
. All players MUST play continuously through the container fromkey
. -
mediaIndex
is an optional parameter which must be used when the user has explicitly selected a particular media item to be played. It corresponds to the ordinal offset into the set of media items offered by the server. If this parameter is omitted, or the value falls outside that of the known set of media items, the player should simply try to select whatever media item is thought to be "best" by running it's own Media Decision Engine.
-
-
/player/playback/stepBack
seeks back 15 seconds, or the expected platform value. -
/player/playback/stepForward
seeks forward 30 seconds, or the expected platform value.
-
/player/application/setText?field=<field>&text=<text>
- can be sent when textFieldFocused has been received. The field specifies the field which is getting text set.
These playback commands take an optional “type” argument (music, photo, video), in case there are multiple things happening (e.g. music in the background, photo slideshow in the foreground).
/player/playback/pause
/player/playback/play
/player/playback/skipNext
/player/playback/skipPrevious
/player/playback/stop
The difference between pause and stop is nuanced enough it's worth spelling it out,
from the perspective of the player. A pause
command:
- Keeps the play queue intact.
- If the player is showing full-screen playback, it still shows it.
- Keeps the view offset (e.g. middle of a track).
On the other hand, a stop
command:
- Keeps the play queue intact.
- If the player is showing full-screen playback, it exits back to nav.
- View offset and play queue head is reset.
No change in the following commands:
/player/navigation/moveRight
/player/navigation/moveLeft
/player/navigation/moveDown
/player/navigation/moveUp
/player/navigation/select
/player/navigation/back
- All commands to the player MUST have a
commandID=X
parameter. - Controllers MUST increment this ID every command sent.
- Players MUST retain local transient state recording the last completed commandID for each subscription.
- Players MUST send the last known
commandID=X
back with each timeline request for each subscription. - The commandID parameter in the subscribe message establishes a new command id baseline for the specific controller. The player MUST reset its local last known command id to that value when a subscribe message is received.
- This allows players to easily de-bounce.
The controller uses the command IDs to do the following:
- After sending PlayMedia, the controller ignores timelines older than the last PlayMedia commandID.
- After sending a command that affects UI, the controller ignores the relevant subsets of the timelines that are older than that command. This makes things that naively resonate like scrubbing or volume control very smooth.
- Sent to all subscribed controllers at the following path:
/:/timeline
as a POST with XML, representing state for all media activity on the player.
<MediaContainer location="y" commandID="z" textFieldFocused="t">
<Timeline type="video" state="stopped" controllable="..." />
<Timeline type="music" state="paused" time="42442" duration="235225" machineIdentifier="yyy" address="x.x.x.x" port="32400" protocol="http" token="k" key="x" containerKey="y" volume="75" controllable="..." />
<Timeline type="photo" state="playing" machineIdentifier="zzz" address="x.x.x.x" port="32400" protocol="http" token="k" key="x" controllable="..." />
</MediaContainer>
The X-Plex-Client-Identifier
header should be checked to ensure the timeline
is coming from the expected player. To support both polling and regular subscribers,
this header MUST be on the the POSTs to timeline subscribers and a response header
on the poll responses. In order to allow polling subscribers access to this header,
you need to include the Access-Control-Expose-Headers: X-Plex-Client-Identifier
header
in the response to the poll GET request. More info on supporting CORS.
The following data is sent:
-
state
is the state of the media playback. Valid values arestopped
,paused
,playing
,buffering
anderror
. -
duration
is the duration of the media, in milliseconds. -
time
is the current playback position, in milliseconds. -
ratingKey
is the corresponding rating key attribute in the media. -
key
is the corresponding key attributre in the media. -
containerKey
is the key for the container, if appropriate. -
audioStreamID
videoStreamID
subtitleStreamID
to pass selected streams. -
machineIdentifier
,address
,port
,protocol
,token
to identify server. -
type=photo|video|music
to identify for which media types the notification applies (because player might be playing music in the background and displaying photos full screen). -
mediaIndex
to identify the index of the<Media>
currently playing, starting at 0. If not present, implies a single media. -
partIndex
to identify the index of the part curently playing, starting at 0. If not present, an index of 0 is assumed, with a single part. -
partCount
to indicate how many total parts are present. Omitting implies a single part. - A state of stopped can be optionally sent between items (e.g. tracks within an
album). If this state is being sent, send
continuing="1"
to specify that there is something else to display. -
controllable=[volume,shuffle,repeat,audioStream,videoStream,subtitleStream,skipPrevious,skipNext,seekTo,stepBack,stepForward,stop,playPause]
to identify controllable parameters/valid actions. This is sent for each media type. -
seekRange=X-Y
is optionally sent to specify the specific subrange that is seekable, in milliseconds -
volume=[0,100], shuffle=0/1, repeat=0/1/2
(where 1 = repeat one, 2 = repeat all). If parameter is not sent, it doesn't apply (perhaps to current media, e.g. volume to photos) and shouldn't be displayed. -
location=[navigation,fullScreenVideo,fullScreenPhoto,fullScreenMusic]
to identify where the app is currently. -
textFieldFocused=[field]
if the player has focused a text field and can receive input. The field specifies the specific text field which which is focused. If there is no field name to be obtained, use generic "field". -
textFieldContent=[content]
If there is a text field focused, contains the contents of the field. -
textFieldSecure=1
Sent if the focused text field is secure (e.g. password). -
playQueueID=XXX
Sent if the player is currently playing a playQueue. -
playQueueItemID=XXX
Sent if the player is currently playing a playQueue. -
playQueueVersion=XXX
This should be set to the current playQueueVersion if the the player is modifying a playQueue, should be used by controllers to know when to update the playQueue. Players MUST send the playQueueItemID in the same timeline. - Player MUST send timeline request to controllers every time the state changes, or at a minimum, every time the play offset increments by a second. This allows the client to display a smooth play time without keeping its own timer.
- Player SHOULD use HTTP/1.1 with persistent connections when talking to controllers to avoid overhead.
Sometimes errors occur during playback. In this case, a timeline will be sent to
subscribers with state="error"
, and then appropriately set code
and
status
attributes which parallel error responses.
In the case of a player wanting to disconnect a controller, they must send
back a last timeline with disconnected="1"
in the media container. A
controller receiving this MUST not issue another timed subscribe.
Play queues are a media container on the server that contains a window of items around the currently played item. They are created from a library item and can be added to, rearranged and navigated.
A play queue aware client will use a play queue as the container of items it's playing through. Not all content can be added to server play queues so clients are expected to have a local fallback equivalent that offers a useful subset of the functionality.
The play queue must be refetched as playback continues - since the returned container is a limited sized window.
A client that uses play queues has new responsibilities when acting as a remote controller or player. It cannot assume that the corresponding player or controller is aware of play queues.
If the controller is using a remote play queue, they should pass the play queue URL as the containerKey
for playMedia
.
Since the corresponding player may not recognise the container as a windowed collection, the URL should be postfixed with window=200
to provide a large window of the play queue when the oblivious player asks. 'own=1' should also be provided so that the server attributes the play queue to the correct client.
If the play queue is a local fallback then the last queued content should be sent as the containerKey
. In trivial cases this will match the play queue, but will obviously not match a hand crafted play queue and will only play the last item. Local play queues are only needed for channels, legacy and synced servers.
The client will typically show UI for the active play queue and allow reordering, deletion and addition to it, as well as skipping directly to an item in the list. The controller is responsible for checking the connected player has the playqueues
capability before allowing such actions.
When such an action is performed. The controller must inform the player with the refreshPlayQueue
command.
If the attached player is capable of using play queues then it may also provide UI for manipulating the queue. The controller should notice any changes in the returned timeline's playQueueVersion
and refetch the controller's play queue when this changes.
The player needs to advertise its capability for play queues. This is done via the playqueues
capability. This indicates it will refresh its play queue when asked and is capable of continuous playback of a collection of videos.
When passed a containerKey
of the form playQueues\{id}?window=200&own=1
then a play queue aware player should fetch that play queue. The player also needs to deal with non play queue keys - both for legacy controllers and for when the controller has had a local play queue and resorted to sending the last added item.
A player that uses play queues is responsible for refetching when informed that in needs to. The refreshPlayQueue
command will be sent by a controller in such a situation.
If the player displays UI for manipulating the play queue and the user modifies it then the player must return the new version in the timeline back to the controller.