Perfectionism, Exposure Therapy and the Queue

I want to make Cs the best I can make it. And my wife would probably be the first to tell you that I can get obsessive about that. Perfectionism is as perfectionism does, I guess. So I try to structure my workflow into phases: feature introduction followed by a period focused on bug fixes, performance improvements and code optimization. That's where I am now, and the top of the list is the current queue system. I'm experimenting with a complete teardown and rewrite of the current system and I'd love user input, so what follows is an over-long description of the challenge...


But it's working fine, why change it?

Recently Charles (fellow Torontonian and developer of Picky) and I exchanged some emails about our apps and the current state of music on iOS. We discussed the modest bumps in attention we get each time Apple changes the stock app, the sorry state of the MediaPlayer.framework, and the challenges of establishing a functional playback queue. We shared our queuing strategies, and specifically how Cs achieves gapless queuing. The conversation got me excited about different ways to solve the problem, because while functional, Cesium's current model is clunky and unwieldy.

iOS does not allow much direct interaction with the system playback queue. And what it does allow has pretty damning limitations. My current solution is to abandon Apple's system functions pretty much entirely. Cs does it's own shuffling, it's own repeating and it's own queue management. Only at the very last moment is the ordered list of tracks passed to the OS, exploiting a loophole that allows the whole thing to happen seamlessly. It works pretty well, but it's far from ideal. It's expensive: directing memory, processing resources and time towards recreating a system function. It's also slow. The current model requires resetting the queue every time the track changes, and it takes time to execute that process. As a result the code is riddled with little tweaks and hacks that give the illusion of better performance. Basically, it's perfectionist hell, and supporting it is like exposure therapy. A solution that leverages the system functions more should be faster, more stable and result in an app supported by a saner dev.


Think (about the queue) different.

Lame wordplay totally intended; because I'm going to take a page out of Apple's book here. Putting all these pieces together requires thinking about the queue system in a new way. Is this an attempt to convince the reader that a bug is actually a feature? Absolutely. I told you: Apple's book.

The old playback queue model was centred around collections. You queue a group of tracks and then manipulate the playback of those items. Want to shuffle? Shuffle the whole group. Repeat? Whole group. Apple still sort of uses this model. Even their queue system, follows the same premise. Want to add tracks to Up Next? Well, that's a group that plays before the other group.

Thing is, music isn't really experienced that way. Music is a flow, each song followed by the next. 

Imagine your music consumption as stringing beads on a thread. Each time you play a song you are selecting a bead and adding it to your playback history. The box of beads is your library, and it's up to you how thread the beads. You could put them on in a specific order, or just randomly reach in and grab one. You could choose to put them on in a repeating pattern. You could mix it up at any time. But no matter how you order your beads, the queue only exists in the future. Only the songs still to come. And the beads on the string are no longer part of the same collection as the beads in the box.

Why think about it this way? Because of that loophole I mentioned. Every time the queue is modified (tracks added or removed, reorganizing or processes I still have to manage manually like shuffling by album) it must be reset. And the loophole is that setting the queue without a gap in playback can only be accomplished (right now as far as I know, and Cs is the only non-stock player I'm aware of that succeeds in doing this seamlessly) if the first track in the queue matches the currently playing track. That means that each time the queue is modified I must discard the tracks that have already been played from the collection. They aren't available anymore to shuffle or repeat the group in the old style. Those beads are already on the string.


OK... I don't like change. This better be worth it.

First, with the exception of some edge cases, you shouldn't really notice much difference. With one exception that will be outlined further down everything should pretty much function the way it used to. More importantly it will still function the way you would expect it to, so there should be very little disruption to the user experience.

But the differences under the hood are dramatic. The code for the bulk of the queue and playback management has gone from 15 methods and about 750 lines to only 6 methods and just under 250 lines. To be fair, a good chunk of that is personal improvement and being more efficient with my code, but 60% reduction is huge, and a lot of that comes from passing functionality back to the system. Passing back that functionality should also yield considerable performance improvements, specifically for issues like lag when shuffling large groups of tracks. It also means that rather than storing 3 separate lists of tracks, only 1 is needed. Not having to save and restore those lists should improve Cs launch time.

The new code is so much better that I'm eager to implement it just because it is so much easier to manage and update, but user experience comes first. Which leads us to...


What's the catch?

Unfortunately there is one UI function that I have not been able to figure out how to implement. Because shuffling has been shifted back to the OS, when shuffling the queue I set will be resorted automatically. This means that I can't implement the "Play Next" function while shuffling, because even if I insert the tracks to follow the currently playing track the OS will reorder the queue anyway. So at least for now this function has to be removed when shuffle is enabled. This is unfortunate, because being able to queue up a song or album after a random track is a very handy and pleasant experience. I believe the benefits of the new code outweighs the loss of this function, but I would love to hear feedback from actual users.


Next Steps

I am extremely fortunate that Cesium has begun to garner a little more attention. Recent reviews have all been positive. I'm not wiling to sacrifice that until it's 100% ready. So testing, testing, testing will be the name of the game. If you'd like to help, send me an email and I'd love to add you to the beta program.

I think this will make Cs a better app. We just need to be sure :)

Be well,
Mike