After I’d been using PeaPodder (my podcast app) for a few months, I came across the occasional podcast whose episodes would not play. The problem was happening here:
let itemPlayer = try AVAudioPlayer(contentsOf: audioURL)
The issue was that audioURL had the extension .mp3, but the file was using m4a encoding. (Interestingly, the desktop version of the app was able to play these mis-extensioned audio files.) My first idea was to see if the episode DTO schema offered any clues. Sadly no clues in the DTO. Even the url for fetching the audio was abstracted:
https://www.listennotes.com/e/a66bb6a91aa54a36867e9d68caaa8f8d/
My next approach was to check the HTTP headers in the episode fetch response. For the file that was m4a, the response headers included a Content Type header with the value audio/mpeg. When I used this header value to set the file extension to .mpeg, the file played correctly. Bravo!
I also checked a few of the podcasts whose episodes were playing correctly. Their HTTP response header dictionaries did not include a Content Type value. Based on this, I assumed the following process would work for me:
- Start with a default file extension of .mp3
- If there is a Content Type entry in the HTTP response header dictionary and it has the format
audio/blahoverride the default file extension to .blah
And this all appeared to be working swimmingly. But I eventually uncovered a second order problem.
Before encountering this m4a problem I had discovered that the audio_file_length values I was receiving in the episode dto was usually incorrect. This had led to odd behaviour when seeking/scrubbing within an episode. Also, I noticed problems in the episode detail view when the episode was nearly finished. In these situations, the scrubber was pinned at the right and the displayed remaining time was negative.
To fix this length problem I discovered a way to determine the actual length of an audio file, using AVURLAsset. Wonderful, I was now able to use this calculated value for the audio length and fall back to the dto value if/when there was a problem in the AVURLAsset code.
After my mpeg file extension fix, I noticed my scrubbers were again showing negative time remaining near the ends of episodes. The extension change was breaking the code used to determine the true episode length. Regression.
I discovered the AVURLAsset code was unable to load some of the files that were being designated as Content Type ‘audio/mpeg.’ Out of curiosity, I tried using .mp3 as the extension for these files, and the AVURLAsset was able to determine the audio length. Despite my earlier observations, some of the podcast episodes that were mp3 encoded were reporting the Content Type of audio/mpeg.
AVAudioPlayer was ok with both mp3 and m4a files using the mpeg extension, but AVURLAsset was not happy with mp3 files using the .mpeg extension. So if I wanted to determine the audio length of the episodes, I’d need to fine another way to determine the file extension that would allow AVURLAsset to function. (To be honest, I considered the possibility of starting with the .mpeg extension and then if the audio length check failed, assume the file was mp3 encoded, change the file extension and re-try the AVURLAsset code. I knew there had to be a better way…)
My first thought was to open the file, and I’m pretty sure there would be some sort of flag declaring whether it was an mp3 or m4a file. But I didn’t want to have to do this. So I started by noodling around in URLSessionDownloadTask. It turned out the initial request was getting redirected. The url in original request was the unhelpful one shown above, but the redirected request included the name of the file to be downloaded. Fortunately, the HTTPResponse includes an originalRequest property and a currentRequest property. The currentRequest value includes the filename and the extension of the file being fetched. The app now uses this extension when saving the fetches episode.
So far, this approach has worked for all the files I have tried. Also, to make it easier to spot problems in the future, I’ve made a couple of enhancements to the episode detail view. First the episode length gets decorated with a * when the dto value is displayed. If there is no asterisk it means the audio length calculation succeeded. Second the view now displays the extension that gets extracted from the url in URLRequest object.
So far these changes seem to be working as expected. As an added bonus, thanks to the small changes in the UI, I can now easily check every episode to check if my new code worked correctly or not. As an aside, I find it odd that AVURLAsset gets thrown off when an mp3 file has the extension mpeg.





