.%*.                                                        .-.
               .%@@@+.                                                .--=%@@@-
               =@@@@@@-                                         :--+@@@@@@@@@*
               *@%@@@@@%:                               :--=#@@@@@@@@@@@@#@@+
               @@::%@@@@@@-:                   :::-=%@@@@@@@@@@@@@@%#*-  :@*
              .@@   =@@@@@@@@@+-:::::::-=*@@@@@@@@@@@@@@@@@@@%##-        @@:
              #@#     +%@@@@@@@@@@@@@@@@@@@@@@@@@@@@@%%#*:              -@@
              #@-        *%@@@@@@@@@@@@@@@@@%%%#=                       %@=
              @@:             -=+++==     ..                            @@:
              @@        .               -@@@                           +@@
             #@%     =@@@@-            +@@@@#              *+          %@#
             #@-   -@@@@@@@.          +@@@@@%           .%@@@@=        @@:
             @@:  +@@@@@@@@@         +@+ +@@@         %@@@@@@@@@      -@@
             @@  *@%@*  :@@@        *@+  -@@@       %@@@@@@@@@@@#     @@@
            #@% %@ @%    %@@       *@*   .@@@     =@@@*@@   :@@@@     @@:
            %@:## *@+    :@@      :@@     @@@    %@@: @#     .@@@    .@@
            @@:.  @@:    .@@      @@      @@@   %@:  @@       @@%    @@%
            @@   -@@.    =@@     @@.      @@@  %@.  #@-       @@#    @@.
           %@%   +@@.    @@.    %@-       @@@ #@    @@.      :@@    -@@
           %@:   +@@+   .@%    #@=        @@@.@    *@@       +@+    @@*
           @@:   +@@%. .%%    *@+         @@@      %@@      -@#     @@
           @@    :@@@@@@%    :@@          @@%      %@@      @#     *@@
          %@@     #@@@@*     @@           @@%      %@@.    %@.     @@:
          @@:      ===:     @@.           @@%      %@@:  =@%.     .@@
          @@.              #@=            @@%      %@@%+%@#.      @@%
          @@              +@*             @@#      -@@@@@*       .@@
         %@@             :@@              @@#       %@@#:        =@@
         @@:            .@@               @@#        :           @@=
         @@.            @@.               @@#                   :@@
         @@            #@=                @@%                   %@%
        #@@           +@+                 #@%                  .@@
        @@:          =@#                  +@%                  =@%
        @@          :@%                   -@@                  @@+
        @@         .@@                    -@:                 -@@
       -@@         @%                     .:                  *@#
       @@*         #                                          @@.
       @@                                                    =@@
      .@@                                                    @@*
      .@@                                                   .@@
      *@@                                                   =@#
      @@*                                                   @@*
      @@                                                   -@@
     .@@                                                   +@*
     .@@                                                   @@-
     -@@                                                  =@@
     %@%                                                  #@*
     @@                                                   @@:
     @@                                                  +@@
    .@@                                                  %@+
    :@%                                                  @@.
    :@%                                                 *@%
    -@.                                                 %@=
    %@                                                  @@.
    @@                                                 *@%
    @@                                                 %@=
    @%                                                 @@:
   :@%                                                +@@
   :@*                =+-                             #@+
   :@:              .%@@@.                            @@-
   -@              =@@@@@#                           :@@
   =@             *@#.=@@%                           #@%
   *@            #@-  -@@%                           %@-
   @@           #@:   :@@%                           @@:
   @@          #%     .@@%               .#@@*      -@@
   @%         #%      .@@%              +@@@@@-     %@%
   @#        %@       :@@%            .@@@@@@@@     %@:
   @#       %@        :@@%           +@@% .@@@@     @@:
   @#      %@         :@@%          #@@:   :@@@    -@@
   @#     @@.         .@@@        .@@%     .@@@=   %@%
   @#    @@.          .@@@:      %@@-       @@@%   %@:
   @#   @@             @@@@    %@@@         *@@@   @@:
   @# .@@              @@@@@@@@@@=          :@@@   @@
   * @@@               :@@@@@@@@.           :@@@  *@@
.@@@@@%                 -@@@@@.             .@@@  @@=
@@@@@:                    ..                 @@@  @@.
-@%:                                         @@@  @@.
                                             @@@  @@
                                             -@@ =@@
                                             .@@ @@+
                                             .@@:@@.
                                              @@#@-
                                              @@@.
                                              +@-
                                               -
      
mediocregopher's lil web corner

Home  //  Posts  /  Follow  /  RSS  //  Source  /  License

Story Time: The Hackiest Feature of Them All


This is the story of the hackiest, coolest, most absurd, and most pointless feature I've ever been part of developing. It's probably the one I'm most proud of, despite it being entirely unused.

The year was 2014, or maybe 2015. Grooveshark was a small but popular music streaming service where I had been working for several years. At the time I was working closely with my friend Mike Cugini on Grooveshark's backend systems, and together we were given the task of developing Mobile Broadcasts.

For those not familiar: if you've used Spotify then you've used Grooveshark. The experience is virtually the same at its core. Search for songs/artists/albums, stream songs, build a queue, listen to the queue, make playlists, share playlists, automated recommendation systems for all-of-the-above, friend activity, upcoming concerts, the whole shebang. Except Grooveshark did it first, with more flare.

Broadcasts was a Grooveshark feature which Spotify still lacks. It allowed users to host their own radio station, where other users could hop in and listen to a shared queue of music being managed by the host. Each room had its own real-time chat panel, a title, description, song suggestion mechanism, and host-assigned chat moderators. Hosts could even hand-off ownership of the room to someone else completely, allowing users to develop rotation schedules and keep rooms online 24/7. The experience was very similar to Twitch today, but focused entirely on music.

For a long time Broadcasts was a web-only feature, and compared to the rest of the site didn't see a ton of usage. Since a huge part of music streaming happens away from the computer we had the thought that being able to access Broadcasts on a mobile device would drastically improve usage.

Unfortunately the situation for Grooveshark on mobile was... difficult. We had been kicked out of both Android and iOS app stores for being too hot to handle. For Android this was less of an issue, as users have always been able to side-load apps straight from a website. But for iOS we were basically screwed, as Apple does not allow side-loading, so only users who had jailbroken their devices could use our app.

Grooveshark developed a mobile web app in response. Users could just navigate to our website in their mobile app of choice and use the app as they liked, no app stores in sight. This was in the super early days of mobile browsers, when Android's browser wasn't yet Chrome and was just called "Browser". There was a lot you couldn't do in a mobile browser which you could do in a desktop browser, such as run javascript while the browser wasn't in focus, e.g. when the device was locked.

Broadcasts was entirely dependent on javascript. The browser would fetch the current queue of a room, and then listen to heartbeats being sent by the host to know what timestamp of the current song it should be playing from, as well as receive updates on queue changes, chat messages, and anything else which could happen in a room. This was all backed by an in-house pubsub service I had written, which browsers would communicate with directly via a TCP connection or Websocket (depending on the year). None of this is possible without javascript, and so couldn't be done in a mobile web browser.

It's fair to say that most music listening on mobile devices happens wile the screen is locked. If Mobile Broadcasts couldn't work in that situation then the entire feature was dead on arrival. Nevertheless we pushed forward, though perhaps not without some prodding by execs.

It was pointed out to us that one could listen to NPR's radio broadcast through their website on a mobile device, and it would continue working even when the phone was locked. This was interesting, and proved a good lead. Looking under the hood, we found that NPR wasn't using any javascript or browser trickery to accomplish this. They were simply providing the mobile browser with an MP3 file which never ended, and the browser happily kept consuming it and playing it even when locked.

So the solution was easy: we just needed to make an infinitely long MP3 file. Technologies like shoutcast/icecast had existed for some time and were doing, why couldn't we?

It's easy enough for NPR to generate an infinite MP3, their broadcast was already in the form of an audio stream. Grooveshark Broadcasts, on the other hand, were composed of discrete songs, each having its own MP3 file which the clients were expected to fetch and play individually. Mobile Broadcasts would need a service to do this fetching/playing server-side, and stitch together the songs into a continuous, real-time MP3 file. Nothing off-the-shelf would have worked.

In the end, here's a quick overview of all the tasks that the Mobile Broadcast service had to deal with:

So not a trivial set of requirements by any means, and made even harder by the fact that we were dealing with MP3 files.

There are three different formats that might be used when embedding metadata into an MP3, and depending on which you're talking about they might put the metadata at the front or back of the file. For this reason you can't just concatenate two MP3 files together and call it a new file; you have to first strip the metadata. We were writing the new mobile broadcast service in Go, a relatively new language at the time, which meant we had to do all this from scratch, ourselves. For every MP3 file which was fetched from the file servers, our service had to look for any or all of the three metadata format tags and remove them before the files could be used.

File seeking turned into another problem. Let's say the host seeks to the halfway point of a song. You can't just pick some random byte location in the middle of an MP3 file and start sending data from there. MP3 files are composed of frames, and each frame can have a different bit rate. This allows for optimizations where if there's a part of a song which is relatively silent or low-complexity the file can encode that part with a lower bitrate, taking up less space for that section. Lower bitrate means fewer bytes used for the same duration of time.

For our purposes this meant we had to write a decoder for the MP3 file spec itself in order to seek within a song. When a seek occurred we would start decoding frames from the beginning of the file, calculating the duration of each frame based on its bitrate, until we hit (roughly) the correct timestamp. Once found we could start streaming bytes starting from that frame.

MP3 frames introduced another wrinkle, which was that when a mobile user joined a broadcast we couldn't just start streaming them bytes at the same location as all the other listeners of the broadcast were getting. If the user's stream of bytes didn't start with an MP3 frame the browser would reject it completely and not play anything. So for every broadcast we had to keep a buffer which held the last MP3 frame streamed, and all bytes streamed since then. When a user joined the broadcast we would dump the buffer first, and then they could be considered "caught up" with the rest of the listeners. When the song changed or was seeked we would have to reset that buffer.

Luckily, Grooveshark only supported MP3 files. Otherwise we would have had to do this song and dance in other formats too.

All of this is to say nothing of having to develop a whole separate Broadcasts client in Go which could listen to and understand the pubsub messages, since the only other client available was the in-browser one, which wasn't really built with this use-case in mind (and we didn't want to do all this bit-twiddling in javascript anyway). And then layered in there was authentication, redundancy, and all the other concerns which come with a "web scale" application.

So yeah, it was a big project to support an incredible hack of a feature, but it did work. I remember listening to a Broadcast while driving around town and having it work alright. And considering that my music was being chosen in real-time by some person across the world, interacting with their small community of EDM lovers using memes and lingo that only existed in that small space, bringing in the cutting edge of that genre every day, on a website which was built in a cave and was never supposed to exist... it was pretty fucking cool.

My pride in this feature doesn't come from how well used it was. I don't think Mobile Broadcasts ever had more than a few hundred users at a time. My pride is more from the sheer absurdity of it. As Teller said: "Sometimes magic is just someone spending more time on something than anyone else might reasonably expect." Someone might reasonably expect us to just not bother, we should focus on polishing desktop web, it's not possible to get a real-time interactive feature like Broadcasts working in a mobile browser. Hell, it's probably still not possible, even today. But we kept digging and found a way to make it possible, and the result did indeed seem a bit like magic.


Shout out again to Cugini for being just crazy enough to see this stupid feature through to the end, and also to whoever helped us on the frontend side, I'm sorry I don't remember who it was (but hmu if it was you!)

Mike Cugini, otherwise known as betamike.

Published 2023-09-13


This site can also be accessed via the gemini protocol: gemini://mediocregopher.com/

What is gemini?