/dev/trouble
Eric Roller's Development Blog

MC-Timer UI Design

- Posted in iOS by

For a new version of MC-Timer, I have been looking at reorganising the playback screen, especially the text pointed out here:

MC-Timer progress text below the rings. Song: Sia - The Greatest

Being near the bottom, I don't think people look at this much and combining step values with timing values is probably confusing.

So maybe it will look better when annotated directly over the progress rings? Thankfully SwiftUI makes it easy to experiment with different layouts.

MC-Timer progress text on the rings

Yes, this helps in the understanding of the values, but I had to drop "Music", "Pause", or "Countdown" which didn't look good when written as a curved text. However, the overall aesthetic suffers tremendously.

So it is probably better to keep the texts in the top corners:

MC-Timer progress text in the top corners

This looks much better and the grouping of the timing values on the left vs. the step values on the right helps also. There is no additional information what the "8 / 121" values mean but it does become apparent whenever the red progress bar increments.

But the wide "15s Pause" text spilling over into the top of the red is not ideal, so I will split that into two lines like this:

MC-Timer split progress text in the top corners

Neat!

My app "MC-Timer" supports playing music with a mixture of Apple Music curated playlists, (Apple Music catalog playlists,) Apple Music songs, library playlists as well as individual songs from your media library.

Playback for songs from your media library and for those streamed from Apple Music works well with the iOS media player.

However, for items in your own "catalog playlist", the Apple Music API will return playParams in the JSON response that may look like this:

"playParams": {
    "id": "i.DVENxPRTdlJoV",
    "kind": "song",
    "isLibrary": true,
    "reporting": false,
    "purchasedId": "253867849"
}

By the way, parsing this into a dictionary of type [String: Any] is a huge pain and I wish the media player API could just accept the JSON as is. Apple, please add: MPMusicPlayerPlayParameters(json: String).

To play this song, one can pass on the dictionary with the parameters to the music player as follows:

if let mppp = MPMusicPlayerPlayParameters(dictionary: playParams) {
    // …
}

However, it only captures part of the dictionary:

(lldb) po mppp
<MPMusicPlayerPlayParameters:0x280d983f0 {
    id = "i.DVENxPRTdlJoV";
    isLibrary = 1;
    kind = song;
}>

And when you try to play that:

player.setQueue(with: MPMusicPlayerPlayParametersQueueDescriptor(playParametersQueue: mppp))
player.play()

you will see:

2021-03-12 14:02:37.105160+0100 app[1626:732477] [tcp] tcp_input [C13.1:3] flags=[R] seq=2895265255, ack=0, win=0 state=LAST_ACK rcv_nxt=2895265255, snd_una=3660699433
2021-03-12 14:02:39.764732+0100 app[1626:732039] [SDKPlayback] Failed to prepareToPlay error: Error Domain=MPMusicPlayerControllerErrorDomain Code=6 "Failed to prepare to play" UserInfo={NSDebugDescription=Failed to prepare to play}

and the music player will play any random song from your library instead.

Neither does it work to play the song via its store identifier:

player.setQueue(with: [ "i.DVENxPRTdlJoV" ])

Apparently, this is a known problem for years, and it has not been fixed.

I hear that the purchaseId should be used instead, but this is undocumented. Also, if that is the case, the MPMusicPlayerPlayParameters should handle that under the hood.

On Apple TV with tvOS 14.4, once Apple Music access is enabled, the following commands will cause an app to crash:

let songStoreID = "900032829" 
let musicPlayer = MPMusicPlayerController.applicationQueuePlayer  // [1]
musicPlayer.setQueue(with: [ songStoreID ])
musicPlayer.prepareToPlay()    // <-- crashes here [2]
musicPlayer.play()   // if skipping [2], it would crash here instead

In the Xcode console log, I note these messages:

// [1]
[APSLogUtils] [AirPlayError] APSCopyPairingIdentity:627: got error 4099/0x1003 NSXPCConnectionInvalid
[MediaRemote] Error fetching local receiver pairing identity (OSStatus = 4099)

// [2]
[SDKPlayback] applicationQueuePlayer _establishConnectionIfNeeded timeout [ping did not pong]

Note: It does NOT crash when using MPMusicPlayerController.systemMusicPlayer.

Apple feedback id: FB8985422

The calculation for 256^8 using Doubles works as expected:

(lldb) po pow(Double(256), Double(8))
1.8446744073709552e+19

However, trying to convert the result into a Decimal crashes:

(lldb) po Decimal(pow(Double(256), Double(8)))
error: Execution was interrupted, reason: EXC_BAD_INSTRUCTION (code=EXC_I386_INVOP, subcode=0x0).
The process has been returned to the state before expression evaluation.

Decimals should handle values with up to 38 digits as mantissa with exponents from –128 through 127.

Apple feedback id: FB7706665

New App: MC-Timer

- Posted in iOS by

I can finally reveal what I have been playing with recently:

MC-Timer App Icon MC-Timer

It is a workout timer which plays music, aimed at training sessions with repeated high-intensity and rest phases. The app will play music when you are working and will be quiet when you are not.

A typical usage example is circuit sessions where you work hard for instance for 45 seconds and rest for 15 seconds, and repeat. In a group, you may want to use the quiet periods to tell your friends what the next exercise is.

MC-Timer Playback screen

You can freely configure the timings and your music playlist, even add songs from Apple Music.

As an universal app it supports both iPhone and iPad. There is even a playback app for Apple TV.

There are neither subscriptions nor in-app purchases, no ads, no pestering review requests, and no usage tracking.

The app is written entirely in Swift, uses MusicKit for playback, and CloudKit with Core Data to synchronise sessions between devices via iCloud. I suppose it could have been finished earlier, but I decided to transition to SwiftUI, which requires iOS 14. Fastlane is used to automate the creation of screenshots.