Communication
MediaRemoteTV is a TCP based protocol which uses length prefixed protobuf encoded messages to communicate between the client and server. The prefixed length is encoded as a Google Protobuf base 128 varint.
The Apple TV acts as the server and clients can connect to it in order to issue various commands (playback, keyboard, voice, game controller, etc).
All messages are encrypted after negotiating the pairing between the client and the Apple TV.
Protocol Message
Each message exchanged between the Apple TV and the remote client is wrapped in a common envelope which contains the type of the message among other fields.
syntax = "proto2";
message ProtocolMessage {
extensions 6 to max;
enum Type {
SEND_COMMAND_MESSAGE = 1;
SEND_COMMAND_RESULT_MESSAGE = 2;
GET_STATE_MESSAGE = 3;
SET_STATE_MESSAGE = 4;
SET_ARTWORK_MESSAGE = 5;
REGISTER_HID_DEVICE_MESSAGE = 6;
REGISTER_HID_DEVICE_RESULT_MESSAGE = 7;
SEND_HID_EVENT_MESSAGE = 8;
SEND_HID_REPORT_MESSAGE = 9;
SEND_VIRTUAL_TOUCH_EVENT_MESSAGE = 10;
NOTIFICATION_MESSAGE = 11;
CONTENT_ITEMS_CHANGED_NOTIFICATION_MESSAGE = 12;
// 13-14 not used
DEVICE_INFO_MESSAGE = 15;
CLIENT_UPDATES_CONFIG_MESSAGE = 16;
VOLUME_CONTROL_AVAILABILITY_MESSAGE = 17;
GAME_CONTROLLER_MESSAGE = 18;
REGISTER_GAME_CONTROLLER_MESSAGE = 19;
REGISTER_GAME_CONTROLLER_RESPONSE_MESSAGE = 20;
UNREGISTER_GAME_CONTROLLER_MESSAGE = 21;
REGISTER_FOR_GAME_CONTROLLER_EVENTS_MESSAGE = 22;
KEYBOARD_MESSAGE = 23;
GET_KEYBOARD_SESSION_MESSAGE = 24;
TEXT_INPUT_MESSAGE = 25;
GET_VOICE_INPUT_DEVICES_MESSAGE = 26;
GET_VOICE_INPUT_DEVICES_RESPONSE_MESSAGE = 27;
REGISTER_VOICE_INPUT_DEVICE_MESSAGE = 28;
REGISTER_VOICE_INPUT_DEVICE_RESPONSE_MESSAGE = 29;
SET_RECORDING_STATE_MESSAGE = 30;
SEND_VOICE_INPUT_MESSAGE = 31;
GET_PLAYBACK_QUEUE_MESSAGE = 32;
TRANSACTION_MESSAGE = 33;
CRYPTO_PAIRING_MESSAGE = 34;
GAME_CONTROLLER_PROPERTIES_MESSAGE = 35;
SET_READY_STATE_MESSAGE = 36;
DEVICE_INFO_UPDATE_MESSAGE = 37;
SET_CONNECTION_STATE_MESSAGE = 38;
SEND_BUTTON_EVENT_MESSAGE = 39;
SET_HILITE_MODE_MESSAGE = 40;
WAKE_DEVICE_MESSAGE = 41;
GENERIC_MESSAGE = 42;
SEND_PACKED_VIRTUAL_TOUCH_EVENT_MESSAGE = 43;
SEND_LYRICS_EVENT_MESSAGE = 44;
SET_NOW_PLAYING_CLIENT_MESSAGE = 46;
SET_NOW_PLAYING_PLAYER_MESSAGE = 47;
}
required Type type = 1; // Identifies which underlying message is filled in.
optional string identifier = 2;
optional string authenticationToken = 3;
optional int32 errorCode = 4;
optional uint64 timestamp = 5;
}
Identifiers
As MediaRemoteTV is an asynchronous protocol, identifiers are used to map a responses to requests. By setting identifier
in the envelope message (ProtocolMessage
) to a random UUID4 before sending it, the response will contain the same identifier upon reception.
Not all messages use identifiers, nor does all of them support it. A message will only contain an identifer if it was triggered by a request. No "events" (e.g. status updates about playing media) messages will contain identifiers. Also, some other messages implicitly does not support it, like CryptoPairingMessage
. In the latter case, no messages of other types can occur simultaneously so identifiers are not needed.
An example of a message using an identifier
can be seen below: DeviceInfoMessage
Initiating a Connection
After discovering the MediaRemoteTV service, you open a TCP connection to the discovered port, 49152
on tvOS version 9.2.1.
Once the connection is open, the Apple TV expects a DeviceInfoMessage
to be sent.
syntax = "proto2";
import "ProtocolMessage.proto";
extend ProtocolMessage {
optional DeviceInfoMessage deviceInfoMessage = 20;
}
message DeviceInfoMessage {
required string uniqueIdentifier = 1; // Example: B8D8678C-9DA9-4D29-9338-5D6B827B8063
required string name = 2; // Example: Jean's iPhone
optional string localizedModelName = 3; // Example: iPhone
required string systemBuildVersion = 4; // Example: 13F69
required string applicationBundleIdentifier = 5; // Example: com.example.myremote
optional string applicationBundleVersion = 6; // Example: 107
required int32 protocolVersion = 7; // Example: 1
}
CLIENT -> SERVER
type: DEVICE_INFO_MESSAGE identifier: "4D673CA3-03A8-4343-82CF-4FA2B6C0CFF1" priority: 0 [deviceInfoMessage] { uniqueIdentifier: "B8D8678C-9DA9-4D29-9338-5D6B827B8063" name: "Jean\'s iPhone" localizedModelName: "iPhone" systemBuildVersion: "13F69" applicationBundleIdentifier: "com.example.myremote" applicationBundleVersion: "5" protocolVersion: 1 }
SERVER -> CLIENT
type: DEVICE_INFO_MESSAGE identifier: "4D673CA3-03A8-4343-82CF-4FA2B6C0CFF1" priority: 0 [deviceInfoMessage] { uniqueIdentifier: "B52D1E7B-66FD-4632-8FBB-644614325855" name: "Apple TV" systemBuildVersion: "13Y825" applicationBundleIdentifier: "com.apple.mediaremoted" protocolVersion: 1 }
This exchange allows the client and server to known whether they've already paired each other.
Then the pairing process begins.