The Day I Forked rabb.it
12.04.2017
5 minute read


Warning. Animal jokes ahead.

Humans are social animals.
They love to do stuff together - even seemingly pointless stuff.
Like watching movies/series with people who are 5000km+ away.

The story begins in January ‘17.
I just joined a new Discord Server and the owner had the crazy idea of organizing “groupwatches”.

This is not an especially new or innovative idea. Many Servers/Guilds do it frequently and watch all kinds of stuff from National Geographic to weird pr0n-like animes.

But how do they achieve that?

The “classic” way

This is the one used by most Guilds/Servers at the time of writing.

Basically you just have to follow these steps:

  • Decide on a movie
  • Find someone with fast internet
  • Make him download/record the movie
  • Find a streaming portal that isn’t too focused on DMCA’s
  • Stream
  • Profit!

Congratulations! You now have your own groupwatch!
But wait, there’s a catch!

Somehow the streaming portals noticed the abuse and developed a technique to block these uses. Without going into too much detail let’s just say that they constantly “scan” your stream for known patterns and mute the audio/video as soon as they find matches.

Back to my story:
The owner decided to ignore my advises and used Youtube Live for our first stream, because she thought that youtube only scans videos after they aired. What a disaster. 15 minutes into the anime, the video was suddenly muted (only a big youtube logo visible). Then it randomly switched between audio- and video-mute. The stream was ruined.

A lot of other services were tried and all of them failed horribly.

In the meantime I left the Guild because of her incompetent leader and founded my own one.
Groupwatches were requested really early, so we had to take our last chance: rabb.it

Going down the rabbit hole

Rabbit is a service that creates a Linux VM and streams the screen to all users in your “room”. You can then simply navigate to any website and watch Youtube, Twitch or whatever with your friends.

This is what an empty room looks like:

Looks like a perfect solution right?
Ha, you fool.

Rabbit is one of the worst platforms imaginable when it comes to watching stuff in groups.

Here’s why:

  • HORRIBLE image quality
  • Delay
  • A max of 25 users
  • Random people are able to join your streams (and thus use your limited spare “seats”)
  • The chat feature is kinda edgy and broken
  • When you “drop” the remote-control ANYONE can “pick it up” and take over the stream

So to sum things up: In a world of DMCA-respecting streaming sites rabb.it is just the smallest evil.
Nothing more - nothing less.
I couldn’t accept this.

The “DIY” way

Project Phoenix is a collection of software and infrastructure that works together to make watching movies for me and the members of my guild snappier, more exciting and more social than ever before.

Here’s how it works:

Phoenix uses the HLS protocol to distribute the stream, because RTMP requires flashplayer to be shown in browsers. HLS works by splitting it’s input into smaller MPEG-TS chunks and distributing those via plain HTTP(s) connections.

When a user opens the stream-website, the following things will happen:

Phoenix | The Video-Client

1. Requesting stream information

VideoJS boots on pageload and fires a request to data.stream.lukas.moe, asking for information. The server responds with an M3U8 playlist containing all available other playlists (PLAYLISTCEPTION o/)

This is used to distribute the stream in different resolutions at the same time.

2. Choosing the right stream

VideoJS now has all the information it needs to stream ANY kind of resolution - but it doesn’t know which one to pick. It’s too early to judge the user’s connection speed, so we have to cheat.

  1. List all streams and their resolutions
  2. Drop all streams that are bigger than the player viewport
  3. Re-add one bigger resolution to prevent huge quality drops if the player is like 1px too small.
  4. Choose the highest stream that’s left.

After the first few HLS segments have been downloaded and viewed we know enough about the connection speed to take it into consideration. However: The viewport-comparison is not replaced but extended by the bandwith speed detection.

3. Getting those segments

After a quality has been picked VideoJS requests it’s content.
Here’s how the full-hd playlist looks like:

Every playlist has a X-MEDIA-SEQUENCE to keep track of the stream progress. That’s because the file is autogenerated each time new segments pop up at the server. X-TARGETDURATION tells us how many seconds of the stream each segment contains. Then the fragments that have to be downloaded are listed.

4. Repeat

Yup that’s basically it from the client’s point of view.

Phoenix | The Chat-Client

The chat client is a tiny piece of javascript that communicates with an even smaller server written in Golang.

1. Connecting

The client connects to the server using a plain HTTP request.
The server responds with 101 - SWITCH PROTOCOLS and tells the client to use a websocket instead. After the websocket has been opened the client is greeted with a session-id.

{
    "op":0,
    "d": {
        "msg":"Ohayo!",
        "uuid":"8A8A8348-A068-821D-9381-0B3CAAD2BF40"
    }
}

2. View-Count

The view count is sent every time a user appears or times out.

{
    "op": 3,
    "d": {
        "viewers": 42
    }
}

3. Message-Input

Whenever a message is sent on discord, the bot forwards it through the websocket.

{
   "op":1,
   "d":{
      "message":{
         "id":"[censored]",
         "channel_id":"[censored]",
         "content":"beep boop",
         "timestamp":"2017-04-12T20:42:07.180000+00:00",
         "edited_timestamp":"",
         "mention_roles":[],
         "mention_everyone":false,
         "author":{ ... },
         "attachments":[],
         "embeds":[],
         "mentions":[],
         "reactions":null
      }
   }
}

4. Message-Output

When sending messages from the web-app back to discord we just send a JSON structure exactly like the one above but also pass some authorization-headers.

That’s it.

Phonenix | The Backend

  • My Streaming client (OBS or FFMPEG) creates a FLV stream and transfers it to my RTMP Ingress.

  • The stream is converted to multiple resolutions at real time using FFMPEG. The whole command operates on streams and doesn’t interact with the harddrive in any way.

  • FFMPEG transfers the converted streams back to NGINX.

  • NGINX splits the stream into smaller chunks for the viewers.

Closing Words

Turns out that building your own social streaming site isn’t hard at all. It has been a nice challenge and will be even more awesome when I’ll start with v2 in 2018.

Stay tuned for that. It will be awesome o/


If you like when I write words, you can fund future blogging (and other cool things) by throwing some spare money into my dev-fund.
It's very much appreciated.



comments powered by Disqus