5
votes

I've got an AVQueuePlayer, and under specific circumstances, it will start playing the next track before the previous one has finished. It doesn't play them both simultaneously, it just cuts out the first track early and starts the next one. This definitely happens when both tracks are http streams, I haven't tried when playing the files locally. Also, this doesn't happen with every track. Only a specific two tracks, when put together, will cause the issue. Most tracks don't have this problem, but a significant number do. Other media players do not show these symptoms when fed these tracks. The tracks that show the symptoms have to be AAC encoded, I have no such issues when streaming their MP3 versions. The AAC files are encoded using libfaac, VBR 90%. The exact command line to encode the tracks:

ffmpeg -loglevel error -probesize 10000000 -i "$input" -strict -2 -acodec libfaac -q:a 90 -vn "$output.m4a"

I believe the issue is related to AVQueuePlayer aggressively trying to play tracks gaplessly, as I know there's some metadata contained in AAC that allows two tracks to be played in sequence with no gap (particularly useful in electronic mixes) - these files should not have this metadata though, and surely it wouldn't cause AVQueuePlayer to freak out when playing two unrelated tracks? Edit: Determined not to be the cause, see below.

If you want to reproduce the issue yourself, it takes an extremely minimal amount of code, just create a basic project, include AVFoundation, and do something like the below:

self.queuePlayer = [AVQueuePlayer queuePlayerWithItems: @[[AVPlayerItem playerItemWithURL: [NSURL URLWithString: @"https://eqbeats.org/track/4875/aac"]], [AVPlayerItem playerItemWithURL: [NSURL URLWithString: @"https://eqbeats.org/track/4499/aac"]]]];
[self.queuePlayer play];

Let the first track play to almost-completion (it'll run for about 100 seconds or so), and you'll hear it abruptly end and the second track will come on.

There are workarounds I think, such as intercepting a message when the next track starts and checking the previous track's play state, or adding the next item to the queue only after the previous starts... but I worry about the reliability of such methods, particularly when the app is backgrounded. If there's no fix for this client side, I can investigate if there's a way to strip out this metadata server side (assuming that is the issue) or just resign myself to using the much larger, much worse-sounding MP3 versions.

AVFoundation's just a riot - I'm sure anyone who's had the horror of working with it will agree. I'm debating whether to raise this as a bug with Apple, but since it's survived two major iOS updates, I get this feeling this stuff is just business-as-usual for AVQueuePlayer. Judging from my research on this issue, I'm clearly not the only one struggling with this framework:

Full disclosure: My app is actually written in RubyMotion, but I've been able to reproduce the issue in Objective-C with a lot less code (so a lot less possible points of failure), so I'm fairly certain RubyMotion or my use of AVFoundation is not the culprit here.

Edit: Just tested it with local files using file:// links, the problem is still there so it's definitely not a problem with the streaming engine or the webserver.

Edit 2: I've looked into the MP4 spec and how iTunes and AVFoundation determine gaplessness in MP4. Turns out in the moov.udta.meta.ilst.--- atom, there should be three atoms, mean, name, and data, which tells the decoder various bits needed to recreate gapless. The files that cause the issue however do not have these atoms so it's definitely not a result of gapless playback. I decided to run the original files (one's a FLAC, the other's an MP3) through CoreAudio's AAC encoder, and the resulting files play perfectly fine through AVQueuePlayer (even after I strip out all the gapless tags with AtomicParsley), so it looks like a bug in libfaac's output or AVFoundation's decoder when dealing with non-Apple AAC bitstreams.

1

1 Answers

5
votes

Don't use libfaac with AVFoundation. It doesn't like it. We're now using libfdk and it doesn't suck.

ffmpeg -loglevel error -probesize 10000000 -i "$input" -acodec libfdk_aac -vbr 3 -vn "$output.m4a"