Skip to content

Instantly share code, notes, and snippets.

@casperboone
Last active October 11, 2022 16:00
Show Gist options
  • Star 0 You must be signed in to star a gist
  • Fork 0 You must be signed in to fork a gist
  • Save casperboone/acfc212b9e3357660677a462bc33b21e to your computer and use it in GitHub Desktop.
Save casperboone/acfc212b9e3357660677a462bc33b21e to your computer and use it in GitHub Desktop.

Websockets

General

Our websocket implementation is based on the sockjs-protocol, of which we are running the Node server, and provide an accompanying Javascript client.

Our sockjs endpoint is https://socket.qmusic.nl/api (or https://socket.qmusic.be/api or https://socket.joe.be/api)

Communication

On the socket you can join multiple data channels. Over those channels data is sent. A channel consist of a station, a entity and an action.

So for example, to listen to the most recent plays of Qmusic your subscription would look like this:

{"station": "qmusic_nl", "entity": "plays", "action": "play"}

Communication over the socket happens in sending and receiving JSON messages. When subscribing the JSON payload should have an action, a sub (which is your subscription above), an id (an integer which you choose yourself, and by which all resulting ) and optionally a backlog parameter.

So an example subscription to most recent plays of Qmusic looks like this:

 {
   "action": "join",
   "sub": {"station": "qmusic_nl", "entity": "plays", "action": "play"}, 
   "id": 3, 
   "backlog": 1
 }

This subscribes you to that channel and you'll see data coming in. The backlog parameter tells the server you already want X number of posts that were sent before. Allowing you to get an initial value, without having to wait on another occurence.

An incoming data payload has a similar structure. Also having an action field (which is always data), an ids field corresponding to the passed id (Multiple subscriptions can overlap), a data field which is a stringified JSON, and a key field, which is a unique identifier of that message, allowing you to ensure you don't process things twice.

An example of a response:

{
  "action": "data",
  "ids": [3],
  "data": "{\"station\":\"qmusic_be\",\"entity\":\"plays\",\"action\":\"play\",\"data\":{\"played_at\":\"2018-05-15T16:14:01+02:00\",\"slug\":\"in-my-blood-4\",\"title\":\"In My Blood\",\"selector_code\":\"TR_3282235\",...},\"timestamp\":1526393642233,\"backlog\":10000}",
  "key": "2769465"
}

It is also possible to subscribe to all actions on an entity. In that case you can omit the action key in your sub. After that you receive all actions on the specific action. This is relevant on some entities where there are more actions (for example broadcasts which might get updated, deleted etc.)

Leaving a sub

Sometimes it makes sense to leave a specific subscription again. You can do this by sending a "leave" action, passing the ids of the channels you joined and want to leave again. So for example:

{
"action": "leave",
"ids": [3]
}

Authentication

While most socket calls are anonymous and global, some data is private, and requires you to authenticate over the socket. An example of such a call is the messages call, which requires you to be signed in with your Gigya ID. Authentication is done with the "authentication" action:

{
  "action": "authenticate",
  "station": "qmusic_nl",
  "method": "gigya",
  "token": ""
}

When the call succeeds you will not get anything new over the socket. When it fails you get an action authfail.

After authentication you can listen to authentication-scoped calls. The way to do this is by passing the authentication scope (gigya, in this case) with a scope parameter. So for example:

{
  "action": "join",
  "id":0,
  "sub": {
    "station":"qmusic_nl",
    "entity":"messages",
    "action":"message",
    "scope":"gigya"
  }
}

Javascript client

To subscribe to messages send over the socket, you must implement our Q-socket client.

First include the javascript file asynchronously:

  (function(q,m,u,s,i,c){ i=m.createElement(s);c=m.getElementsByTagName(s)[0];i.async=1;i.src=u;c.parentNode.insertBefore(i,c) })(window, document, 'https://socket.qmusic.be/js/q+sock.js', 'script')


Connecting:

var sock = Q.connect('qmusic_be', 'https://socket.qmusic.be/api');

You can enable debug mode to output all incoming messages to console:

var sock = Q.connect('qmusic_be', 'https://socket.qmusic.be/api', {debug: true});

Next, subscribe to the necessary channels:

var entity_plays = sock.subscribe('plays')

And finally, listen to all "play" messages:

entity_plays.on("play", function(data, msg) {
  //Do something with data
});

So, all together we can shorten this to:


var q = Q.connect('qmusic_be');
q.subscribe('plays')
  .on('play', function(track, msg) {
  //Do something with our track
});

To do authenticated calls you need to call the authenticate method first, and then subscribe with gigya scope. Example:

sock.authenticate("gigya", {token: ""})
sock.subscribe('messages').on('message', callback, {scope: "gigya"})

Images

Depending on the source and the use case, images from the API are sometimes returned as a full URL, and sometimes as a relative path. For example /9/0d/2b/c0/1383049/1400x1400bb.jpg.

The idea behind this is that you can get the original file (of undefined size), or a predefined preset size from our API. The original file is available at our CDN url (http://cdn-radio.dpgmedia.net + the path), or you can use one of the presets of the API.

For the website we currently use these presets:

Depending on the device you're on, orientation, connection etc. you can optimize which file/size to fetch. New presets can easily be made by the API developers.

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment