Dynamic type across the board

In preparation of the Catalyst version of Rigelian I have been increasing the font-sizes on most views, and using dynamic type on almost all views. An added bonus is that this will take into account the system wide font size setting that a user has chosen, thereby making the app more usable for those with less than perfect eye-sight.

This update meant refining the auto-layout definitions on many views, and to simplify things, migration to UIStackView in many places.

The following picture shows from left-to-right the sizes small, large (default) and extra-extra-large.

Abstraction through protocols makes UI testing easy

In some of the recent updates of Rigelian there have been bugs that really should have been caught before releasing, but slipped through because of a lack of automated tests. Therefor I revisited the test strategy.

In the past I created some ui-tests, but they were relying on a local mpd player that was started before running the test-cases and shutdown again afterwards. The player was configured with some standard albums. This worked locally but broke when I switched to a new computer, and the approach is also not suited if the application is ever moved into a ci/cd platform like Bitrise.

Luckily the architectural design of the app made a different approach easy: I created a specific test-implementation of the ConnectorProtocol which abstracts an actual network player like mpd or kodi from the app. This test connector mimics a player by implementing the control, browse and status protocols, thereby removing all external dependencies.

The following code snippet from the AppDelegate class will return the player browsers that take care of discovery of players. When run normally, the mpd and kodi browsers are activated, but when testing instead the in-memory test player browser is returned. It will immediately ‘discover’ and activate the test player which is then available for all test activities.

import TestConnector

func getPlayerBrowsers() -> [PlayerBrowserProtocol] {
  #if DEBUG
  if CommandLine.arguments.contains("-test") {
    return [TestPlayerBrowser.init()]
  }
  #endif
         
  return [MPDPlayerBrowser.init(),
          KodiPlayerBrowser.init()]
}

This is a nice example of how good abstraction can make testing easier.

last.fm integration through swift

The upcoming release of Rigelian will focus on the artist view: a more image based view, improved handling of artist images and the addition of artist biographies and similar artists.

Biographies and similar artists will be retrieved from last.fm, which offers an api for this. To abstract the networking part I created a new package that encapsulates the api calls into nice observables.

let lastfmApi = LastFMApi(apiKey:"your key")
lastfmApi.info(artist: "Taylor Swift")
  .subscribe({ onNext: (result) in
     switch result {
       case let .success(info):
         print("Info: \(info)"
       case .failure(error):
         print("Error: \(error)")
     }
  })
  .disposed(by: bag)
 

The package is available on GitHub: https://github.com/katoemba/lastfm-api-swift

CI/CD with Bitrise

To make the development process more reliable, I added 4 of the open-source libraries on my GitHub account to Bitrise. For small developers they offer a free service to have automated build pipelines for different platforms, including the Apple ecosystem.

Every commit will now trigger the build pipeline including running the test set (if present, which is currently not the case for part of my code). At least it will trigger in case existing builds fail. The build status will also be presented on the repository page.

There were some dependencies in 2 of the libraries on UIKit, which were causing build issues. The easiest way to solve that turned out to be to remove that dependency, and instead rely on Foundation only. That also means that the swift packages now (at least in theory) support all the Apple platforms: iOS, macOS, watchOS and tvOS.

Rigelian 2.0 is in the mail

I just completed the last bits-and-pieces and posted the new 2.0 version of Rigelian to the AppStore and into the review and approval process.

When I started the development of Rigelian over 2 years ago, I had a couple of things in mind. The main goal was to create the best remote control for mpd-based players. That meant it had to be easy to setup and use (hide configuration where possible), very responsive and great looking.

But next to that I also had technical goals I wanted to achieve. To make sure that extending and supporting the app would be possible with a reasonable amount of effort, I started from scratch using the latest that Apples development eco-system has to offer. That meant Github, swift, RxSwift, cocoapods and later swift package manager, and many of the great open source packages made available by the swift developer community. And bye-bye to Objective-C after almost 30 years (yes, really 30 years, I did my graduation project in computer science back in 1990 using Objective-C on a DEC Ultrix machine).

And I wanted to use the experience I collected over the last 10 years building music remote controls: first MPoD and MPaD, and later the iOS controller for the Bluesound system. I set out to abstract the generic concepts of a music remote control, and create a front-end that could operate completely independent of the player it is controlling. For that I created ConnectorProtocol which defines a generic way to communicate with a player regarding discovery, control, browsing and status updates. The front-end will only know about the protocol, and have zero knowledge of the specific implementation. The first implementation against the protocol was MPDConnector, which has been the basis for Rigelian up-to 1.7.2. And for the last half year, in parallel to further developing the front-end I created a second implementation, this time for kodi: KodiConnector.

And by now this has reached the point that it’s actually possible to perform the main things you want to do with a music remote, and I’m releasing my newborn into the world. I’m very happy with the result where I can maintain a single code base for the front-end, and seamlessly control 2 completely different kinds of players with an app that looks exactly the same in both cases. It will also mean that future improvements like Siri support will be available for both types of players with almost no additional effort. How nice is that.

Kodi support is nearing completion

It’s 2020 and we’re back on extending Rigelian with support for Kodi. Added recently are the ‘play random album’ and ‘play random songs’ functions. Turns out that adding 100 songs in one command is very slow on Kodi, so I had to limit that to 20 songs to keep the performance acceptable.

Also the sortation of albums in various screens is completed.

Remaining now is the ability to play radio stations, and creating a player setting screen and then we’re good to go.

Using c-style defines with Swift Package Manager

When compiling the c-based library libmpdclient, I ran into the problem that it contains some #define statements which have to be set during the build. It took me while to find out how to set those when building the package with Swift Package Manager.

In the end off course the answer was right there in the documentation, but I only found that after watching one of the SPM videos from WWDC 2019.

let package = Package(
     name: "libmpdclient",
     ...
     targets: [
         .target(
             name: "libmpdclient",
             publicHeadersPath: "mpd",
             cSettings: [
                 .define("DEFAULT_HOST", to: "\"localhost\""),
                 .define("DEFAULT_PORT", to: "6600"),
                 .define("ENABLE_TCP", to: "1")
             ]
         )
     ]
 )

Migration to swift package manager

After some initial try-outs with the swift package manager some months ago, I came back to it today to see if it’s possible to run it side-by-side with cocoapods, and gradually migrate packages over.

To begin with, I took out the libmpdclient sources of the mpdconnector framework, and put it into a framework of its own which can be built and included using swift package manager. While doing this I ran into various workspace and build issues, including a point at which the Xcode build server would crash every time I opened the workspace. But in the end I managed to get it all working again.

The framework is called libmpdclient-swift and is available on GitHub (I will gradually move over all sources from bitbucket). It’s based on the latest 0.21.17 version of the original libmpdclient.

Improved cover art assistant

Most Rigelian support questions are related to cover art not displaying correctly. Unfortunately mpd supports cover art retrieval only in recent versions (since 0.21) and the support is still basic.

To let the users get the best cover art under these constraints, the cover art assistant in Rigelian is updated with new functions, like selecting which sources are used, the order in which those sources are accessed, and a description how each of these sources work. Also added is the possibility to clear the cover art cache, forcing that all images are reloaded.