Ben Dodson

Freelance iOS, Apple Watch, and Apple TV Developer

Forcing left-to-right text in iOS localizations

Since iOS 6 it has been the case that any localization that utilises a right-to-left language (such as Arabic) will automatically flip your views so that everything scans from right-to-left. Usually this is desireable but there are certain instances where you may want to disable this functionality (such as with a media player that should scrub from left-to-right). I was recently asked by a client to completely disable the right-to-left functionality for all languages as it was causing too many display issues within the app and customers were specifically saying they’d prefer it to scan that way.

After a bit of searching, the general consensus was that I’d need to manually alter1 all of my horizontal constraints in order to force them to be left to right rather than leading to trailing which will flip based on localization. In a project with 1000s of these constraints this did not seem a suitable course of action and would require any future developers on the project to keep this in mind when creating new constraints.

Instead, I came across a property added to UIView in iOS 9 named semanticContentAttribute. This allows you to choose unspecified (the default which flips based on localization), playback and spatial which are special cases for media controls or directional controls, and forceLeftToRight and forceRightToLeft which work as their names would suggest. Thanks to the UIAppearance protocol, disabling the flipping globally is a simple one-liner:

UIView.appearance().semanticContentAttribute = .forceLeftToRight

This not only flips all content back to left-to-right but also ensures that your UINavigationController will animate from left-to-right as well. Of course, you can also use the appearanceWhenContainedIn: method to limit this global change to specific view controllers of your app should you wish to or to set certain controls to other directions.

The only other thing I needed to change in my project to get this all working was some paragraph styles for attributed strings. I frequently use NSMutableParagraphStyle to set custom line heights and I leave the other properties to their defaults. One of these is alignment which is always left on my devices due to my English language but the default is actually natural which means it renders depending on the language. Searching through my project and finding the few places I’d left out a default and setting it was fairly trivial:

let paragraph = NSMutableParagraphStyle()
paragraph.lineSpacing = 4.0
paragraph.alignment = .left

In total, I only needed to make 8 edits to my project; much easier than trying to edit every horizontal constraint in your storyboard!

  1. If I had gone down that route I likely would have written a build script that would go through every xib and storyboard file and do this for me but I have been burned by manually editing xib files in the past. That’s a young man’s game! ↩︎


Since iOS 9.3 it has been possible to add Apple Music tracks to the media library as such:

let library = MPMediaLibrary()
library.addItem(withProductID: id) { (entity, error) in
	if let error = error {
		NSLog("Error: \(error.localizedDescription)")

This is powerful as you can use a simple identifier to both play a song and add it to the library but it is likely that your UI will want to show an add to library button similar to the Music app on iOS. To remedy this, I’ve created a simple Swift extension1 for MPMediaItem that tells you if a currently playing track is available in your library:

import UIKit
import MediaPlayer

extension MPMediaItem {

    var canAddToLibrary: Bool {
        let id = MPMediaPropertyPredicate(value: persistentID, forProperty: MPMediaItemPropertyPersistentID)
        let query = MPMediaQuery(filterPredicates: [id])
        let count = query.items?.count ?? 0
        return count == 0

Why an extension on MPMediaItem? The only way to know if a track is in your library is to search the user library with an MPMediaQuery. Unfortunately you can’t search on the MPMediaItemPropertyPlaybackStoreID (as some tracks may not be on Apple Music) so instead you need to use the persistent ID property. If you try and play an Apple Music track using an identifier, then you can retrieve an MPMediaItem and use that to get the persistent ID for searching the media library. I use this in my own apps2 by listening to the MPMusicPlayerControllerNowPlayingItemDidChange notification and then checking if there is a nowPlayingItem on my MPMusicPlayerController instance; if there is then check it to find the current status:

var player = MPMusicPlayerController()

override func viewDidLoad() {
    NotificationCenter.default.addObserver(self, selector: #selector(playbackStateDidChange), name: .MPMusicPlayerControllerNowPlayingItemDidChange, object: nil)

@objc func playbackStateDidChange() {
    guard let item = player.nowPlayingItem else {

    // at this point we do not know if the track can be added - any UI for adding should be hidden

    if item.canAddToLibrary {
    	// show your "Add to library" button
    } else {
    	// show some UI to explain "Already in library"

I am fairly sure this is how the Music app works on iOS as you’ll notice when skipping tracks that the UI for the track status is completely hidden until the track is ready to play at which point either an add button or a tick will appear. One thing that caught me out was listening for MPMusicPlayerControllerPlaybackStateDidChange but this seems to fire inconsistently both on iOS 10 and iOS 11 (unless you are on an iPad and run the Music app in split-screen mode in which case it always works) - checking for MPMusicPlayerControllerNowPlayingItemDidChange works consistently and will still yield an MPMediaItem with which to work with.

IMPORTANT: The user will need to have granted permission to access their media library in order for this extension to work. It will crash your app if you do not have NSAppleMusicUsageDescription in your Info.plist (although that is the bare minimum - you should actively check for capabilities before using this as no point showing an “Add to library” button if the user doesn’t have that capability!)

Checkout the MPMediaItem+CanAddToLibrary Swift extension on GitHub

  1. I wrote the extension using Swift 4 with Xcode 9 / iOS 11 SDK but it should work just fine in Swift 3 as it isn’t using any new language stuff. ↩︎

  2. Including an exciting iOS 11 only Apple Music app I’m working on. If you’re an Apple Music subscriber with the iOS 11 beta installed (developer or public), contact me for a test version before it launches in September… ↩︎


One of the great things about Twitter is the way it can connect you to other developers. For many years, I’ve been chatting with Lisa Dziuba and she got in touch with me last year along with her colleague Ahmed Sulaiman to tell me about a new app they were working on, Flawless, which has now officially launched.

Flawless is an absolute godsend for developers working on pixel perfect designs. It is a plugin for the iOS simulator that allows you to compare what is rendering on screen with a static image via various different modes and a slider for opacity. In this way, you can make sure that what you have built matches the image precisely. The app is a plugin for the simulator itself so you don’t need to add any extra code or frameworks to your project. It also works with static images1 so you don’t need to worry how your designs are provided be that by Sketch, Photoshop, or other2.

I’ve been lucky enough to be both a beta tester and to be interviewed by Lisa and Ahmed about my workflows to help them tweak the app. If you are an app developer that has to work to a fixed design, you should definitely check it out. Flawless is available for macOS at the bargain price of $153 and you can find out more (and get a free trial) on their website at

  1. I believe the original idea was to be integrated with Sketch but after they spoke with me and many other developers who don’t work exclusively with Sketch they decided to pivot to being an iOS simulator plugin instead. ↩︎

  2. I have one client who provides me designs via InDesign and he is also the most likely to create a GitHub issue for a dividing line being 0.5px out of alignment - love ya Niki 👊 ↩︎

  3. This has saved me so many subsequent bug follow ups that $15 is almost criminally low. ↩︎

Great British Bee Count 2017

For the past couple of years, I have worked with Two Thirds Water on the Great British Bee Count iOS app for Friends of the Earth. Today, an updated version of the app has gone live to support this years count which runs from 19th May until 30th June 2017:

The main update has been a completely new design which fits the more modern “flat” design which was made popular by iOS 7 whilst also putting the navigation within easy thumb reach even on plus-sized devices. There are also many new functionality changes such as an improved bee picker and fact files on each type of bee that can give you a lot more information.

As I also needed to migrate the app to Swift 3.1, I took the decision to completely rebuild the app from scratch1 so I could make use of some newer iOS features such as stack views and improved auto layout constraints. I also made improvements to the way in which content is stored on the device locally in a Realm database making the whole app feel even faster whilst increasing the reliability of sending count information in the background2.

You can check out the Great British Bee Count on the App Store (it’s free) or learn more about the bee cause.

  1. This was a decision the client was not made aware of and whilst it cost me more time (as I worked to a fixed budget) the end result is an app I can be really proud of. I’d much rather spend a bit more time and money from my own pocket to make something perfect than try and hack something together quickly especially if it is a full redesign of an existing app. Due to changes in the Swift language and a move away from separate xib files to storyboards, I was able to reduce the overall file size by 20% and the amount of code by 45%. ↩︎

  2. For example, if you try and submit a count when you have no network connection, the app can automatically upload this information once connectivity is restored even if it is no longer in the foreground. ↩︎

Reaction Cam

I’m very pleased to announce the release of a new client app I’ve been working on over the past couple of months: Reaction Cam.

I was hired by Elliott Brock to build Reaction Cam, an easy-to-use app that allows you to quickly record and share your reaction to anything from video to content in a browser. Due to the limitations of iOS sandboxing, this was an immense technical challenge but the end result is incredibly slick with the power to record a reaction and the content on screen at the same time. Once recording is complete, you can edit your reaction and the content by trimming and rotating as well as swapping what is used as the “picture in picture” recording (or disabling it altogether)1. A single file is exported at the end of the process for easy sharing with friends on social media.

Supported media for reacting to includes video (both saved on device and online), photos (complete with a swipeable scrolling interface), and a full browser for viewing everything from tweets to blog posts. In addition to the app, I also built an API and an admin system so that Elliott could handpick recommended videos to react to as well as publishing some of the reactions that had been recorded with the app.

I was given a very loose spec and so I designed the app and worked out all of the UX myself; I even designed the app icon! Reaction Cam is built in Swift 3.1 and makes use of the latest features in iOS 10 to allow it to run quickly on all of the various iOS devices with full scaling support for every screen size.

I really enjoyed working with Elliott on Reaction Cam and hope that people will find it to be a best-in-class app for reaction recording. You can download Reaction Cam on the App Store and learn more about it on the Reaction Cam website.

  1. Unlike some other reaction apps, I actually record both the front-facing camera and the content onscreen to separate files rather than simply outputting the front-facing camera into the page and capturing just what is on screen. This is massively important as it allows for editing such as swapping which video is shown in the smaller view (i.e. you might want your reaction to be more prominent), it allows you to move the smaller view around, and it means you can disable one or the other after recording. Of course, capturing both live video and the content on screen separately is a technical challenge but I’m very happy I was able to maintain a 30fps recording from both streams even on the oldest supported device (an iPhone 5). ↩︎


I’m pleased to announce the release of a new client app I’ve been working on for the past few weeks: AlcoPath.

I worked on AlcoPath for Orbis Media as a freelance iOS developer. The app was designed in consultation with the the Nottinghamshire Healthcare NHS Foundation Trust and features a WEKP Cognitive Assessment (incorporating 6CIT, Ataxia test, Opthalmoplegia test, and other associated risk factors) to diagnose Wernicke’s encephalopathy, a Withdrawal Assessment for Alcohol using a revised CIWA-Ar scale, and industry recommended pathways all in-line with NICE Guidance.

The app is available for free and can be used by clinical staff on both iPhone and iPad thanks to a scaling interface suitable for all device sizes. An A4-sized PDF can be generated with the personalised results of each assessment and this can be printed directly from the app. Push notifications were also integrated to inform users quickly of any updates.

In order to render the various assessment questions efficiently and accurately, I built a local PHP-based tool to input the various questions and output a JSON file that the app would then interpret to build each question and the various ways of answering be that with a toggle, multiple selection, or text entry. This prevented the need for the app to connect to an online database but also enabled me to make prompt updates should new questions need to be added or existing questions be edited in the future.

It was a great experience working with Orbis Media on this app and the feedback from clinicians has been great so far. You can download AlcoPath on the App Store and learn more about it at

Building tools for Kylo Ben

I’ve been running my Kylo Ben website about video games since October 2016 and this year I decided to start doing a weekly update about gaming news and what I’ve been playing. Whilst it is fun to do, it is very time consuming as I need to collate interesting links I’ve found, my articles, podcasts, game releases, games I’ve played, and games I’ve purchased which means an average post will take between 1.5 to 2 hours to write. Being a developer means I can build my own digital tools to help me out and so last week I built a few little tools to help cut that time dramatically.

One of the bigger pieces of the weekly roundup is a list of interesting news that has happened in the world of video games. Initially I would copy and paste the URLs of interesting links I found and save them into the Notes app1 on iOS or Mac. This worked fine but it was a little clunky and getting the data back out took time as I’d need to open each one to see what it was and then add Markdown syntax to each URL I wanted to use. To solve this, I wrote an iOS app and a macOS app that would provide extensions for URLs allowing me to quickly save them to my database.

[app name] would like to access Apple Music

The iOS app is purely a blank view controller with a bundled share extension that looks a little like this2:

class ShareViewController: SLComposeServiceViewController {

    override func isContentValid() -> Bool {
        return true

    override func didSelectPost() {
        if let item = extensionContext?.inputItems.first as? NSExtensionItem, let itemProvider = item.attachments?.first as? NSItemProvider {
            if itemProvider.hasItemConformingToTypeIdentifier(kUTTypeURL as String) {
                itemProvider.loadItem(forTypeIdentifier: kUTTypeURL as String, options: nil, completionHandler: { (url, error) in
                    if let shareURL = url as? URL {
              "save", parameters: ["url": shareURL.absoluteString, "title": self.textView.text], onCompletion: { (error, response) in
                            if let error = error {
                                let controller = UIAlertController(title: "ERROR", message: "That didn't work: \(error.localizedDescription)", preferredStyle: .alert)
                                controller.addAction(UIAlertAction(title: "OK", style: .default, handler: { (action) in
                                    self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)
                                self.present(controller, animated: true, completion: nil)
                            } else {
                                self.extensionContext!.completeRequest(returningItems: [], completionHandler: nil)

    override func configurationItems() -> [Any]! {
        return []


This is paired with an NSExtensionActivationSupportsWebURLWithMaxCount entry in the Info.plist file so that it will activate whenever I try and share a URL anywhere within iOS. If I’m in an app reading an article that I want to save, I simply tap the share icon and then choose the Kylo Ben app from the list as shown in the screenshot above. The URL and title will then be sent to my server for retrieval later on.

[app name] would like to access Apple Music

I’ve written a number of Safari extensions in JavaScript before but El Capitan added the option to write native extension in Swift via a Safari Extension bundled with your macOS app. To avoid having to make AJAX calls in JavaScript3, I chose to build a simple macOS app with a Safari Extension that looks like this:

class SafariExtensionHandler: SFSafariExtensionHandler {
    override func toolbarItemClicked(in window: SFSafariWindow) {
        window.getActiveTab { (tab) in
            tab?.getActivePage(completionHandler: { (page) in
                page?.getPropertiesWithCompletionHandler({ (properties) in
                    if let properties = properties, let url = properties.url {
                        let title = properties.title ?? "Unknown Title"
              "save", parameters: ["url": url.absoluteString, "title": title], onCompletion: { (error, response) in
                            if let error = error {
                                NSLog("PROBLEM! \(error)")
                            } else {


When I click on the controller icon, the method above is called and the URL and title are sent to my server; once completed, the page reloads to show me it has been successful. I spent a long time trying to just get a simple alert to display on either success or failure but I couldn’t get it to work correctly. It is possible to interact with JavaScript and I was able to log to the console but any alert would silently fail. If anybody has any tips on that, I’d love to know how to improve it.

The basic template of my weekly update is the same every week and I used to use a number of custom MySQL queries to pull out the various information I needed and then write it up manually. Now that I have my links stored in my database, I decided to write a PHP script to generate as much of my update as possible so all I need to do is fill in some of the blanks that aren’t automatically provided (i.e. upcoming game release dates) and write my own thoughts and opinions around the news articles. I have a basic PHP script which runs a number of MySQL queries and then generates a Markdown document like this:



[Final Fantasy 15's PS4 Pro Update Out Now, Improves Frame Rate And More - GameSpot](

[New PlayStation 4 Pro patch for Final Fantasy XV makes it look worse | Ars Technica](

[This tiny Nintendo Switch feature is already making fans super happy - Polygon](

[Alto's Odyssey awaits, Summer 2017](

[never gonna give you up - What’s In the Box?2?! Take 2](

[Steam Community :: Group Announcements :: Orwell](

[Nintendo tag teams with John Cena for living room-inspired Switch demos - Polygon](

[Look What Mega Bloks Is Doing To Pokémon ](

[Pillars of Eternity 2 campaign clears $3 million - Polygon](

[Take a look at how itty-bitty the Nintendo Switch cartridge is - Polygon](

[Australia Is Coming To Civilization VI](

[Rocket League Original Minis toys expanding with light-up cars - Polygon](

[Hot and heavy Mass Effect pack comes to Cards Against Humanity - Polygon](

And finally, 

###My Posts
- Making the earth move with Stagehand — "I really like the premise of a "reverse platformer" but there simply isn't enough content to keep me coming back when it is stood next to _Tiny Wings_, _Alto's Adventure_, and _Super Mario Run_" [[link](]

- Podcast #xx: Title [[link]()]
- Another Podcast #xx: Title [[link]()]

###Upcoming Game Releases
- _Game Title #1_ (date - platforms) [[link]()]
- _Game Title #2_ (date - platforms) [[link]()]
- _Game Title #3_ (date - platforms) [[link]()]
- _Game Title #4_ (date - platforms) [[link]()]
- _Game Title #5_ (date - platforms) [[link]()]

###Gaming Time
This week I spent 9.6 hours playing six different games:

- **Stagehand** (0.5hrs): Text...
- **Rocket League** (0.6hrs): Text...
- **Pokémon Moon** (0.7hrs): Text...
- **Forza Horizon 3** (1.1hrs): Text...
- **SteamWorld Heist** (2.8hrs): Text...
- **Night in the Woods** (3.9hrs): Text...

This week I added 2 new games to my library: _Crusader Kings II_, _Night in the Woods_.

Details on games I'm planning on playing this week...

Until next time, have a great week!


_Did you enjoy this weekly roundup? Make sure you don't miss one by subscribing to [Kylo Ben Weekly]( - it's this post in email form every Monday!_

The news URLs are simply pulled from the database and wrapped up so that each link uses the title of the page as provided by the macOS and iOS extensions. I will nearly always change the link title (as it’ll be part of a sentence) but it allows me to quickly see what an article is about without needing to open it up and re-read it. The “my posts” section requires no editing at all as it pulls the title, link, and a pull quote directly from the articles I’ve published in the previous week. The podcasts and upcoming game release sections can’t be automatically populated (yet) so I just use placeholder text for these to reduce the amount of effort required. The final section on my gaming time uses a number of queries to get the exact amount of time I’ve spent playing in the past week, adds placeholders for each game so I can write about them, and then lists out any new games I’ve added to my library; all of this is thanks to some scripts I wrote a while back that scrape my Steam and Xbox One libraries to track changes and allow me to render a page showing my gaming time for the past few months.

Once I’ve finished writing, the Markdown file is uploaded to my server and the weekly update will then appear on the website. I then use Byword’s “copy as HTML” feature to generate a HTML version and use that with Mailchimp to write and send out the email version of the update.

With these tools, I can now write my weekly update pretty quickly and only have to focus on what I want to say rather than spending time on copying, pasting, and formatting. If you’re interested in video games, sign up to the weekly email as it is the best way to get a digest of what has been happening over the past week as well as seeing what new games are arriving.

  1. I could have used a service like Pocket to do this but then I’d have to either use two Pocket accounts or fill my personal account with links that I don’t want to read later. ↩︎

  2. This is not what I would call production code quality so don’t just wildly copy and paste this into an app or you’ll likely regret it. Works well enough for my own personal use though! ↩︎

  3. Which is a nightmare when you start hitting cross domain restrictions. ↩︎

Proposal for an Erase Data Passcode

Last month, US-born NASA scientist Sidd Bikkannavar was detained by Customs and Border Patrol agents and told he would not be released until he gave the agents the passcode to his phone. They then took his phone (containing sensitive information from NASA) for 30 minutes before returning it and letting him go. He doesn’t know what information was taken at that point although popular consensus is that the entire device could be cloned within that time period.

Many articles have been written about this but the one that caught my eye was by Quincy Larson of freeCodeCamp entitled “I’ll never bring my phone on an international flight again. Neither should you.

When you travel internationally, you should leave your mobile phone and laptop at home. You can rent phones at most international airports that include data plans.

If you have family overseas, you can buy a second phone and laptop and leave them there at their home.

If you’re an employer, you can create a policy that your employees are not to bring devices with them during international travel. You can then issue them “loaner” laptops and phones once they enter the country.

Since most of our private data is stored in the cloud — and not on individual devices — you could also reset your phone to its factory settings before boarding an international flight. This process will also delete the keys necessary to unencrypt any residual data on your phone (iOS and Android fully encrypt your data).

This way, you could bring your physical phone with you, then reinstall apps and re-authenticate with them once you’ve arrived. If you’re asked to hand over your unlocked phone at the border, there won’t be any personal data on it. All your data will be safe behind the world-class security that Facebook, Google, Apple, Signal, and all these other companies use.

Is all this inconvenient? Absolutely. But it’s the only sane course of action when you consider the gravity of your data falling into the wrong hands.

I’ve seen similar responses on Twitter including one that you should use a burner phone with a different sim. This is all massively inconvenient, even if you follow the “wipe everything and reinstall once you’ve landed” method; bear in mind that the average iPhone takes hours to re-download all of its data1 at a point when you likely need to get maps, book transport, etc.

My suggestion is much simpler; Apple (and other handset manufacturers) should introduce an Erase Data Passcode. This would be a user-defined passcode2 that when entered immediately performs a secure wipe of the device in a similar way in which the existing “Erase Data” option works3. It would be expected that the device would disable power-off options during the secure wipe so that the only way to stop it would be to remove the battery (which in most circumstances would take considerable time at which point the data would be erased).

This is a solution that would also work in other cases such as theft, muggings, or a jealous partner. Whilst Apple have long had the option to remotely wipe your device via this has become far less easy to do quickly if you have 2-Factor Authentication enabled4 as you may not have access to your own devices.

I’ve filed a Radar on this issue (rdar://30553231) and would urge any other Apple customers that deem this to be a good idea to duplicate it. Apple goes to extraordinary lengths to protect user data and fight for the privacy of its customers but all of that is pointless if you are compelled to give up the keys to your device5. It is also pointless to have such powerful devices if we need to reset them every time we travel.

  1. This is especially true if you are data roaming as you usually get the slower speeds not to mention that airports generally have congested networks due to the volume of people. Finally, iOS 10 does a load of additional stuff during the first few days of a new device (like Machine Learning on your entire Photos library) which will cause further battery drain / wear and tear on components. ↩︎

  2. And optional fingerprint for TouchID devices (i.e. my right thumb unlocks the phone, left thumb wipes it) ↩︎

  3. This is an option within Settings > Touch ID & Passcode that will trigger an automatic secure wipe of the device if your passcode has been entered incorrectly ten times. I’ve always wanted an option to reduce this to three times. ↩︎

  4. You have 2-Factor Authentication enabled, right? No!?! Go do that now. ↩︎

  5. As usual, XKCD sums this up nicely↩︎

The Checked Shirt #1 - Lost AirPods, iOS 10.3 beta, App Store changes, and Invoicing

I’m happy to announce a new podcast I’m doing fortnightly with Jason Kneen; The Checked Shirt.

Every fortnight we’ll be producing a 1-2 hour show around the topics of freelance life, technology (specifically Apple), and gaming. Our first episode is available now in which we discuss the AirPods (and how easy it is to lose them), the new changes in iOS 10.3, the ability for developers to leave reviews on the App Store, invoices with Cushion, and lots of other fun stories and anecdotes.

You can get The Checked Shirt from these fine outlets:

Don’t forget to leave a review on iTunes and follow us on Twitter via @thecheckedshirt.

Syncing Apple Music with Spotify

Before Apple Music launched in April 2015 I was a longtime Spotify user and subscriber. I maintained a playlist I affectionately called Ben Dodson’s Definitive Hits Collection which contained nearly 45 hours of songs I thought were particularly good1. On most Tuesday nights, my friend and podcast co-host John Wordsworth and I play a few rounds of Rocket League and we will regularly have the Definitive Hits on whilst we play. There are two issues with this:

  1. As I use Apple Music now, I don’t pay for a Spotify premium account and so I have to put up with adverts (which are utterly terrible).
  2. They aren’t in sync so we might be humming (or badly singing) along to a song that the other person isn’t listening to.

Now I could just recreate the playlist in Apple Music to solve the Spotify ads issue but we still wouldn’t be in sync. As we’re both developers, we decided to remedy this problem with a fairly convoluted solution…

The basic idea is that John acts as the host with the playlist on Spotify (on macOS) playing into his headphones. He has written an app that checks if the track has changed and, if it has, sends the track information to my server. I then use the iTunes Search API to look up the song and find the correct identifier which is then sent to an app on my iPhone via push notification to start the song playing on Apple Music.

I’ll run through each piece and go over the challenges that were encountered.

Retrieving track details from Spotify

I hadn’t heard of it before but Apple has provided a tool called Scripting Bridge since macOS 10.5 which allows you to interface with AppleScript from other programming languages such as Python and Ruby. With this, John was able to write an app that polls Spotify regularly2 to see if the track has changed. If a change is detected, the title, artist name, and album name are all sent to my server so I can begin the process of matching the song on iTunes. In future, we may add more information (track number on the album, duration, etc) in order to try and match better but this is working well enough currently.

Finding a track on Apple Music

The next step is for the server to take the information that has been sent and use the iTunes Search API to try and find a match. This is fairly straightforward and a first draft would send a request like this:

Unfortunately the iTunes API does not allow you to search multiple terms (i.e. artist=rick+astley title=never+gonna+give+you+up) so everything has to be concatenated together which leads to an issue; sometimes the song you expect is not the one you get. For example, consider the song She Looks So Perfect by “5 Seconds of Summer”; If you search for this, the first result on the iTunes API will actually be a the “Ash Demo Vocal” version of the song which is not the one we want. To resolve this, we started sending the album information (in addition to title and artist name) so I could match that manually by iterating through the results; I then only choose the first result if there isn’t a song in the list with the same title and album name.

The next issue I encountered involves the Romanization of Belarusian; the track Solayoh is rightly attributed to Alyona Lanskaya on Spotify but Apple Music uses Romanization so it becomes Alena Lanskaya. If you search term=solayoh+alyona+lanskaya then you get no results. To fix this issue, if no results are returned from the iTunes API then I then do a search for the title alone and return the first result as that works in 99% of cases.

The final issue on the API side revolves around remastered tracks. The song A Horse with No Name is listed as A Horse with No Name - 2006 Remastered Version on Spotify but Apple Music doesn’t include that suffix even though they have the exact same version of the album. To fix this, if there are no results returned (again) then I split the string by non-alphanumeric characters and just try the first part in the lookup. Again, this seems to work in 99% of cases.

Once I have a track, I take the identifier and send it within a push notification along with the server time before I started making API calls (you’ll see why shortly). I use a silent push notification via the content_available flag as I want to wake the app up and run some code but not actually display anything to the end user.

The iOS App

The final piece of the puzzle is an iOS app with a fairly minimal interface3

The key thing for the iOS app to do is to play the track that comes from the push notification. This is fairly easy with an MPMusicPlayerController but we run into problems when the app is in the background as whilst the app will wake up from the silent push it isn’t allowed to play music.

That said, we can enable the background audio capability that allows us to control audio from the background but it only works if audio is already playing. To remedy this, I play a 5 minute track from the album “Silent Tracks of Various Useful Lengths” (id #366737838) on repeat so that the app is continuously playing music… it just happens to be silent music4.

Once a silent push is received, it starts to play the track but it also adds the 5 minute silent track to the queue. This is important as it prevents the background audio from terminating should I have a different length of music to Spotify or if a push is delayed due to network reasons. In essence, a normal track will be played followed by a track of silence whilst it waits for the next notification.

The final issue to solve is one of latency; there is a lot of latency inherent in this setup as we are polling Spotify, sending data to a server, doing one or more lookups against the iTunes API, relying on a push notification, and finally buffering the song in Apple Music! In order to keep us roughly in sync, the app will connect with my server when enabled and fetch the server time so that it can keep time5. When the push notification comes in, it contains a timestamp from when the server was first hit by the macOS app and I can then calculate the offset in order to skip into the track a bit.

For example, lets suppose John starts listening to C’est La Vie by B*Witched6 and his app hits my server at 1485359762 seconds from the unix epoch. This is recorded and sent in the push notification along with ID #298026101. If that process takes 3 seconds, then the iPhone app will know the server time is now 1485359765 and can work out that it needs to skip forward 3 seconds in the song in order to keep me in sync.

Amazingly, this crazy system actually works and we are able to have our playlist synced and ad free on two completely separate streaming services. I built my portion of the project as an iOS app as Windows does not have access to Apple Music yet I play Rocket League on the PC; in order to actually hear the audio, I wear a single AirPod in my right ear underneath my Turtle Beach X10 Headset so I can hear the music but still get the audio from the game and Skype.

It was only after we’d got it working that we realised we could have just set up some form of streaming radio server but that likely wouldn’t have been as much fun…

  1. The actual criteria to add songs is simple; I either have to use the phrase “it’s a classic” to be able to describe the song or it has to be “catchy as f**k”. ↩︎

  2. An improvement would be to hook into some sort of notification so that the app can be told when Spotify changes track rather than polling every second but this works well enough for now. ↩︎

  3. The switch simply activates the app as I’m using various background modes and don’t want my phone to randomly start playing music if John is listening to Spotify whilst we aren’t gaming! ↩︎

  4. I was originally planning on using the track 4′33″ until I found the album of silent audio. ↩︎

  5. Originally I would get the timestamp and then start counting it up with an uptime C method. This had some issues when the device was in standby so I made it simpler and I just work out the offset between the system clock and the server date; then, when I want to know what time it would be on the server, I can just add the offset to the system clock. ↩︎

  6. It’s a classic. ↩︎

« Older Entries