Media uploads processing

When you add some of your media to the system it first needs to be processed. For music this is so the periods of silence at the beginning and end of tracks can be removed and to determine whether those tracks need to seamlessly blend into the following track. For booklets it is to map the areas of the PDF to specific tracks so that the associated content displays for choreography and technique.

If this content has been processed before then yours will automatically match and it will be assigned to your account and be immediately available. If it has not yet been processed before then it goes into a queue for me to do. That can take time. Most music can be done reasonably quickly although if a release has not yet been defined then that may take more time. Some are simple and can easily clone the previous release and change the relevant track details. Some are a lot more complex and change format which means they take quite a bit longer. BODYJAM is the main offender here although the early TONE releases gave them a run for their money.

The choreography always takes the greatest amount of time. Sometimes it can be done in five minutes while on other occasions it can take up to an hour. It’s quicker with more recent releases which use a consistent template. The very early ones are more problematic, especially when they are photos taken at random angles.

I try to process something that is pending for each user every day. Usually the oldest and the most recent items they have added. With that in mind this post will be updated with the overall number of media items that are still awaiting processing. I will add the totals I see at the start of my workday and at the ends that people can see how much work there is still to go.

Any differences between the end of one day (EOD) and the beginning of the next day (BOD) will be due to people adding new content while I’m asleep.

DateAudio BODChorey BODAudio EODChorey EOD
24 Dec874478
23 Dec590452745476
22 Dec460426385420
21 Dec503433417426
20 Dec627448496433
19 Dec806454626448
18 Dec736454704454
17 Dec572433501425

Building the app – What I wish I’d done differently

Are there some bits I wish I’d done differently? It’s looking back with 20/20 vision on a software project – of course there are!

The most obvious is that it doesn’t yet have all the latest bells and whistles Apple have added into their frameworks in the last couple of years. Things such as async / await and a greater use of Combine. This is due to a slightly conservative view towards new implementations. While I still retain a fondness for anything which simplifies and improves things I’ve had my fair share of working with ‘bleeding edge’ items in the past. The difference in this case is that as an indie I lose time and don’t get paid anything if it doesn’t provide the claimed benefits. That does introduce a touch of reticence and a willingness to let others charge in first. Sometimes it means you avoid going down a path which peters out; other times you realise it would have moved you along quicker.

By the time I’ve seen the stability and effectiveness of async / await I have an amount of working code where it’s not cost-effective to replace at this time. It’s something that will be done later as part of code refactoring once the version with the last of the milestone features has been released. It’s the same with Combine which I currently use mainly for processing web service responses. It will be used in more areas of the code as it becomes increasingly more closely tied in with the other frameworks.

Automated testing is one thing which is lacking. It was nowhere near as simple to implement when I first started the project as it is now. I did play around with the XCTest classes then but struggled with how best to implement the instantiate / teardown items. It also didn’t allow easy handling of cases where you have to wait for certain tasks of an indeterminate length to complete before validating your results. I think a fair bit of it also came from me not having a fully solidified design for a while and so didn’t know exactly what I was meant to be testing.

In the team-based projects I’ve worked in the past there is usually a defined set of requirements and you derive your design to fit within those constraints. The test cases can then be generated from those items before any code is even written. In my case it became an issue of available time resources once I’d finally inked in the design and so automated tests didn’t happen. They will start to be added while I’m adding the async / await and Combine modifications. Apart from validation and regression testing they also make it much easier to make the app display a specific screen with known data and screenshot it. That makes updating the App Store promotional images in different localisations trivially easy.

I also wish I could remember more of my French and German from classes in high school as that would make it simpler to generated the localisations for those languages.

Future features

This is a list of the features that we’ll be looking to add in future. There’s no delivery time assigned to them because that can only be done after analysis and design to find out the true extent of development required. If there’s something that you’d like which isn’t shown here then drop us a line.

  • Video support
  • More reports about your attendance and trends
  • Improved Apple Watch capabilities
  • Improved iPad layout
  • Mac version
  • Android version
  • Windows version

Music Licences

Or why you will see tags such as #ppca, #ppl, #ppca-free, #ppl-free, #stream in the app.

Most countries have licensing systems in place that require a fee to be paid when music is played in a public space.  This includes cafes, bars, nightclubs, and gyms.  There are usually two payments – one for the actual playing of the music (the performing rights) and one for the authors of the songs (the publishing rights).  They are generally standard rates with the former being higher than the latter.  

The entity in Australia is called the Phonographic Performance Company of Australia which is usually abbreviated to PPCA. In the UK it is the Phonographic Performance Limited (PPL). The USA appears to have American Society of Composers, Authors and Publishers (ASCAP), BMI (Broadcast Music Inc.), and Society of European Stage Authors and Composers (SESAC).

In Australia in 2010 the group licensing the performing rights (PPCA) decided to massively increase their chargeable rate.  After court cases they were permitted to do so.  It meant that for each class using licenced music (such as a Les Mills one) a gym would see its music licence fee rise from about $2.50 for the performing rights to at least $15.00 – a minimum sixfold increase.  Multiply that by 30-100 classes a week depending on the size of the gym and understandably there was some consternation over this.  The general view was that it would become necessary to cut the majority of group fitness classes using original music in order to stay financially viable.

Recognising that Australia was one of its biggest markets Les Mills quickly started to release cover versions of the music in their releases there.  That meant gyms still had to pay the publishing rights fee (which stayed sensibly acceptable) but they now didn’t have to pay the performing rights fee.

So if you’re teaching in a gym that pays the performing rights fee then you can play the original music (these have tags such as #ppca and #ppl).  Otherwise you should play the cover versions (these have tags such as #ppca-free and #ppl-free). The latter ones are the equivalent to what used to be known as royalty-free music where you would purchase a CD of cover versions, usually all set to the same bpm and blending in to each other.

So who benefitted from the original spat all those years ago?  Well the PPCA lost some income as a lot of gyms switched over to using cover music.  It also showed other industries that was an option so damaged the PPCA’s reputation and influence.  Les Mills have to pay more because they need to make cover versions although they seem to have fallen on their feet with that in recent years as they can now use them as part of their LMOD product.  As someone who suffered through their introduction they’re competent tracks but rarely exceed the original so the participants lose out.  Overall, someone originally got greedy and everyone ended up losing.  Except the lawyers, of course.

As a consequence of this Les Mills stopped making the cover versions of BODYJAM after eleven releases because it was not financially viable.  That would have killed the programme in Australia.  However, a concerted social media campaign convinced Fitness First – the largest chain in the country with that programme – into negotiating a special licence with the PPCA only for that programme. That continues to this day with the original music while all other programme classes use the cover versions.  Fitness First comes in for a degree of (sometimes well-justified) criticism but in this particular case they deserve credit.

Building the app – The New Version

A discussion of the first version of the app can be found here.

This involved a lot of consideration of users and business aspects before I even started thinking about the design.  The first thought on deciding to create a new version was whether to upgrade the existing one or create a new product.  I decided on the latter because the amount of change I was intending to make made it an entirely new entity with a different user interface, different internal data structures, new data synchronisation, and a new means of charging.  Given that I couldn’t see an easy way of migrating existing data across it made sense to treat it as an entirely different product.

I gave a lot of thought to the method of charging users while I was building it and came up with a number of possibilities over the years.  They all kept circling back to that it had to be a subscription as I needed to be able to cover the hosting and bandwidth costs.  They happened on a regular basis and there was no way I’d attempt to pick a one-off cost and then amortise that over the lifetime of the user.  If I had the skills to do that I’d be working as an actuary.  Since I prefer to treat people in the manner how I’d like to be treated I decided to make the subscription relate only to media transfer.  The meant if they stopped subscribing all of their music would still play on their devices.  I believe people should not be locked in to paying for what they themselves created.  If my product is good and useful people will pay me.  They should not be restricted if they elect to do otherwise.  I can imagine a few companies reading that and going ‘well we don’t want to employ his sort here with those type of ethics’.

Sorting out how to implement that also meant that it simplified development as I didn’t have to worry about adding in large amounts of code to handle conditions about which features are available to the user.  It also allowed a free trial not limited by time to be made available to all users.  

I decided that the existing version of the app will be removed from the App Store when the new version is released.  This is because I do not want to support two versions, especially when one of them is so old, loaded with technical debt, and it is becoming increasingly difficult to generate the ongoing data for it.  The data for it will be updated probably for fifteen months afterwards.  At that point the app will continue to run but no new release data will be made available for it.  No doubt there will be complaints but I consider it a necessary action given my available time resources against the fact that once it has been purchased I receive no further income from that user.  For those who have recently purchased the existing app I will ask them to contact me and I’ll provide a free extension to their purchased subscription on the new version.  I’ll be working on a trust basis with this as adding extra steps for people to prove they have purchased something would only sour relationships built over the years with users.  Plus I believe that most people are fundamentally fair and honest.

I’ll no doubt end up rewriting the above few paragraphs as the events unfold after launch.

So having gone through that the non-people part was far less stressful.  Rather than having a data file pulled down from a server I decided to centralise things on a database.  Eventually I want the app running on Android so that ruled out using iCloud.  In the end I decided on what I’m familiar with from writing share-trading systems at Citibank and went for SQL Server.  After looking through cloud providers Azure seemed to provide the best cost-benefit implementation.

The initial version of the server was running on a local PC with the free version of SQL Server.  I have written SOAP in the past but as anyone who has done the same will tell you: for the sake of our sanity we do not speak about that time in our lives.  For this I decided to use web services which meant implementing a JSON interface.  The main reason for this is the incredibly simple way Visual Studio allows you to build them at the server and the graceful manner in which Swift allows you to interact with them at the client.  The automatic mapping to a structure makes programmers gurgle happily.  Once they had been defined it was simple to build a class which ran in the background and transferred the fixed data from the server down to each client.  Processing the user data was a little more complex since it can be created on multiple devices and there’s the need to synchronise it across those devices while also ensuring the chance of duplicated data is reduced.

The database design required different ways of looking at data storage.  In relational databases such as SQL Server you try to ensure your data is as normalised as possible.  This means having minimal duplication and designing index values in such a way that you can access your data in all the ways you require without needing to add additional processing.  It’s a craft – like woodwork – that takes time to learn as there are some required basics but each implementation is different.  The final result reflects that particular set of requirements plus your own experience.

There’s an old maxim in cricket that if you win the toss then 99% of the time you choose to bat.  For the other 1% of the time you carefully consider all factors and then you choose to bat.  The same goes for using Core Data on iOS devices and Macs.  The original version of the app used an SQLite library because it was close to the SQL & ODBC products I’d used in prior times.  This time I decided to use Core Data as it appeared to have become a more mature and stable product since when I’d last looked at it.  It required some work to understand the concepts as it’s really an object graph rather than a relational database.  Working out how to map the data between Core Data and SQL Server and also how to generate the object graphs for use by the app took time.  Data storage structures tend to remain fixed as changing them can cause knock-on effects through the rest of the system.  That’s the reason we think very long and hard about them and spend a great deal of effort to ensure they serve the best purpose before starting to build the rest of the app.  Measure twice and cut once applies here as well as woodworking.

Once I was relatively happy with the data structures I started coding the screens.  I’d originally started with the extremely high-tech approach of sketching some boxes on a notepad and thinking like a user.  What would the screen do, how would it look, and how would I easily interact with it?  That led to a version I wrote using UIKit.  Which was followed by thinking I could improve things a little bit in certain areas and so going back and changing them.  This was repeated quite a number of times as seeing something working always gave better feedback than simply looking at a static image.  It was progressing reasonably well.

Then March 2020 arrived.

You’d think working from home would be perfect for an indie developer but the whole uncertainty of those first months of lockdown killed all motivation, especially as we worried over family and friends who were far more at risk.  But eventually the mind craves exercise and so I decided to learn SwiftUI.  It was the hot new method from Apple of writing user interfaces.  As is fairly common the first version of it had been flaky and sluggish but it had improved significantly by the second year.  After being in the Apple development environment for a while you begin to recognise which tools they are pushing as to being the long-term preferred ones.  Swift quickly became one of those and it was obvious SwiftUI would rapidly follow.  So I sat down, read a lot of tutorials, watched a number of WWDC videos, grabbed a lot of sample code, and wrote my own bits of it until I felt I understood how it worked.  The slightly annoying part was in my first UIKit-based attempt at this version I’d written a load of code that generated notifications regarding what pieces of data had changed on screen.  This allowed the screen to update only those sections instead of refreshing the entire panel which gave a far better experience for the user.  The following year Apple released a set of APIs which did exactly the same thing.  SwiftUI then implemented the same thing in an even simpler and more concise manner.  You could consider what I’d written as wasted effort but it showed I was on the right track and I was now able to produce a more compact app as a major part of the complexity was now inside the operating system.  With most code less is generally better and perhaps only 15-20% of all code ever written for an app ends up in the final version.

After completing the app to a point where I felt comfortable showing other people I received feedback from some instructors.  Most of it was positive although the more critical responses proved of greater benefit as it improved the app.  I then asked users of the existing version if any of them would like to try a TestFlight version of the new app.  A few did.  A small percentage of them stuck around to provide ongoing feedback.  The worth of that cannot be underestimated.  I told them first up that I wanted their actual views no matter how critical.  And they took to that approach with alacrity.  It was a little bruising to the ego when they almost unanimously pointed out that the way I’d carefully and logically implemented one feature was completely and utterly confusing and they’d like it changed to a way I’d rejected early in the design process.  I explained the reasons for the implementation and how I thought it was better than what they were suggesting. 

A few days later I changed it to what they suggested because they were right.  They have real-world experience whereas I’ve only been observing and talking to instructors and building my view of their world off that.  It’s not fully matching reality and if that doesn’t occur then the product will never be as good as it should be.

Developers can be sensitive over criticism because software is created directly from the thoughts of the programmer.  Therefore some of them treat criticism of their product as direct criticism of themselves.  Learning to separate those two aspects was one of the best things I learned decades ago.

One issue with building it over time is that the underlying technology will change. Recently Apple implemented await / async in the Swift language which is a far better approach for coding when having multiple parts of the app running simultaneously. My decision was whether to switch over to using it or keep with the existing approach. I decided to go for the latter as (a) I judged it was not a good use of my time to rewrite existing working code and (b) because it was a new feature and I’m now at the stage of my career where I prefer using what I know works reliably. I’ve worked with bleeding-edge operating systems, frameworks, and products in the past. They were always interesting and sometimes fun but the time for them is not when trying to finish a project. That said await / async seems to have been a very good implementation (it’s worked well in C# for years) and it’s something I’ll use for new developments and when refactoring in future.

Currently the app is being used in classes.  I do still get requests from my TestFlight people but these are now more minor issues and most of them seem happy.  I’m also not seeing any crash logs appearing which gives both users and me confidence that we’re on the right track.  I just implemented RevenueCat to handle the payment charging (I’ve looked through the StoreKit documentation, blinked, shaken my head, shuddered, and then decided I’ll use a highly-rated third-party product instead). The bits that still need doing are fleshing out the contents of this site, refining the operational processing that will comes with (hopefully) an increase in users, and adding in the last few bits of functionality for the app.

Building the app – The First Version

The app you see on this site is a redesign of the original one. That initial version was written back in 2010 when I first decided I wanted to finally learn how to develop for iOS. That meant learning Objective-C at the same time. Which you soon discovered should be more accurately called [[[[[[Objective-C] the] one] with] all] the] brackets]. Since the best way to learn is to actually create something I decided to combine it with my other love of Les Mills group fitness programmes.

Being honest the first version was a little flaky. The Cocoa frameworks weren’t at the level of stability and polish that they are today. Especially the audio playlist one which was a bit of a problem since the app used it heavily. A helpful instructor used it in a few classes that I attended and we discovered issues such as switching on airplane mode before class prevents her mother calling and interrupting a series of jump kicks in the middle of a track. Phones then had nowhere near the level of ubiquity they do today so it was also learning about how best to use them in the environment of the time.

Eventually I put it out for public use and was surprised to get even a few downloads. I made it free because I wasn’t completely happy with how it turned out. Despite that it picked up a bit of traction through word of mouth. I could tell that because I was getting a few support emails. That was mostly split between “it’s not finding my music” and “it stopped playing during class”. The latter was the most serious and came from trying to use the built-in AudioPlayer class. While it worked for most things it had trouble working reliably with the continual checking I needed to do to track things during a class. So I ended up rewriting that component using AVMutableComposition. That turned out to be far more reliable and the problems playing in class tailed off.

The matching was an issue in the early days and it remains one with the current version all these years later. The problem was how to detect which song on a user’s device matches the one in a Les Mills release. Back then phones had nowhere near the processing power they do now. That meant you couldn’t do the obvious way of today in generating a SHA256 hashcode on each file as that would have taken an unacceptably long time and chewed through the phone battery very quickly. So I decided on ‘fingerprinting’ a CD (there were no music downloads at that time) which involved getting the running time and order of the tracks then comparing that data layout against the albums in a user’s library to see which ones matched. In most cases it picked up quite a lot. The advantage was that as long as the same album title was on all tracks and they were numbered consecutively it didn’t mind what the song titles or artists were. That naming was a major concern back then as no-one used the same format for those fields when ripping from a CD.

There were a few issues because most people simply let the ripping software pull the data down from the internet and it wasn’t always correct. Even today I still get BODYBALANCE 50 albums that state they are BODYATTACK 50. Getting clean data from lots of people is not an easy task so there’s a fair bit of pre-processing that goes on before I can add it into the system.

The matching had two sources of problems. The first was when two releases had the same fingerprint. This was something that became increasingly common as the number of releases increased with time. It was affected by the need to have a 5-second leeway on each song as CD-ripping software back then varied with some adding a few seconds before and/or after a track. So the matching had to be a little fuzzy. Around that time there was a fight between the gym and music industries in Australia which led to a lot of gyms switching to use cover music (see here for the full details). That now meant two versions of a release could easily have the same fingerprint. It meant adding in a workaround where the user had to select which album matched against a release if there was a conflict. It did work but I still remain unhappy at the implementation.

The second and main issue with matching was that it checked against the music in the iOS Music Library. Most people already had their music in there and given that devices then had storage space that was still very small you couldn’t really add more data such was music files into your own app storage area. Assuming people put the whole album into the music library things worked fine. But some people only put a few songs because they’d never be teaching the others again. So my support emails were mostly explaining this to them.

It was at this point in 2013 that I had a bad day and having got another support email decided to put a price on the app just to stop too many more people using it. Bizarrely this increased the number of downloads. I can only assume it was on the basis that if it costs something then it is viewed as having a greater inherent value over something that is free. I wasn’t going to knock back the income even though this does show that as a businessman I’m a reasonably good developer.

Things ran fine after that. Some Les Mills staff even gave me a mention which helped with sales. Then Apple stepped in with their cloud music. That product was a complete disaster with how they handled people’s music libraries. Many remember their live albums were suddenly half-filled with studio versions. It was even worse for group fitness instructors as their purchased versions which had been specially remixed to fit the choreography were replaced by something which had the same name but didn’t have the required extra bridges and chorus. Which made it useless to be played in class. So I had to find out what was happening, add features into the app to minimise the issues it generated, and then communicate it to all the affected users. While the severity of it has reduced over time I’m still having to occasionally do this today – eight years after the issue first started.

Despite the problems I see it continues to do the most important role of all which is providing users with a product which helps them in their life. These are some of the reviews that have been added to the App Store over the years:

It was always my intention to produce the next generation of the app because like most people who have built something and see it every day we can spot the flaws and where it chafes most for the user. So I finally started doing it.

A discussion of the new version of the app can be found here.