Repository navigation
Developer Documentation
This page explains how Clementine's network remote works: what an app needs to do to control Clementine. Clementine Remote's own code for it is in app/src/main/java/de/qspool/clementineremote/backend/.
When the network remote is on, Clementine listens on TCP port 5500 (it can be changed in Clementine's Network Remote settings). If Only allow connections from the local network is on, Clementine closes connections from addresses outside the local network straight away, without any response.
Clementine shares the port with an HTTP server, which streams music to remote devices. It tells them apart by the first byte a client sends: 0x00–0x08 is the remote protocol (the first byte of its length prefix), an upper-case letter is HTTP. A client must send something within 10 seconds of connecting, or Clementine closes the connection.
Clementine announces itself over mDNS (Zeroconf/Bonjour) as _clementine._tcp in the local domain, with its port. Discovery only works on the local network: it doesn't cross VPNs or subnets, so apps should let users enter an address too.
The communication between Clementine and its clients uses protocol buffers. The messages are defined in Clementine's ext/libclementine-remote/remotecontrolmessages.proto. Clementine Remote keeps a copy in app/src/main/proto/remotecontrolmessages.proto, which CI compares with Clementine's (scripts/check-proto-drift.py).
Packets have the following structure:
+----------------+------------------------------------------------+
| Packet length | Payload (message) |
+----------------+------------------------------------------------+
The packet length is a 32-bit big-endian unsigned integer giving the payload's size in bytes. Clementine rejects lengths above 128 MiB.
Every message sent and received is a Message, with a type from the MsgType enum at the top of the file, and the protocol version (see Versions below). Most types carry their own message inside Message, which you fill in when sending and read when receiving.
Once connected, the client sends a CONNECT message, with the authentication code if Clementine asks for one.
If Clementine requires an authentication code and the code is wrong, it replies with a DISCONNECT message whose reason is Wrong_Auth_Code, and closes the connection. Any other message before a correct CONNECT gets a DISCONNECT with the reason Not_Authenticated.
After the CONNECT message, Clementine sends its version (INFO), then its "first data": the current track and its position, the volume, the open playlists, the shuffle and repeat modes and, unless the CONNECT message asks it not to (send_playlist_songs), the songs of the active playlist. A FIRST_DATA_SENT_COMPLETE message ends it.
A connection that only downloads says so in its CONNECT message (downloader), and gets no first data. A CONNECT message with a renderer offers the client as a speaker that Clementine can stream music to, when Clementine allows playing on remote devices.
You control Clementine by sending messages with the control types: PLAY, PAUSE, STOP, NEXT, PREVIOUS, SET_VOLUME, LOVE, CHANGE_SONG, and so on. Clementine carries out the command straight away.
Clementine sends the current song's metadata when a track starts. When something changes (the volume, a playlist's songs, the playlists, the shuffle or repeat mode, and so on), it sends the change to every client. You don't have to ask for the state: Clementine tells all clients.
Clementine sends a KEEP_ALIVE message every 10 seconds while clients are connected. Use it to notice that the connection has been lost.
When Clementine closes, it sends a DISCONNECT message to all clients.
A client that wants to disconnect should send a DISCONNECT message to Clementine too.
Each message must include the protocol version. The version field has a default value, which is the version of the .proto file you built with: fill it in from that default, for example in C++:
msg->set_version(msg->default_instance().version());
The file is proto2, which doesn't send a field that isn't set, even one with a default, so set it explicitly. Clementine Remote does it in Java like this:
builder.setVersion(builder.getDefaultInstanceForType().getVersion());
Protocol buffers are backwards compatible, but newer features might not work with an older protocol version. Such messages are normally ignored, and shouldn't make a client misbehave.