3

I have a simple audio file in .wav format (the audio file is cut perfectly to loop). I've tried different methods to loop it. My first attempt was simply using AVPlayer and NSNotification to detect when audioItem ended to seek time at zero and play again. However, there was clearly a gap.

I've been looking at different solutions online, and found people using AVQueuePlayer to do a switching: Looping AVPlayer seamlessly

However, when implemented, this still produces a gap.

Here's my current notification code:

weak var weakSelf = self
NSNotificationCenter.defaultCenter().addObserverForName(AVPlayerItemDidPlayToEndTimeNotification, object: nil, queue: nil, usingBlock: {(note: NSNotification) -> Void in

        if weakSelf?.currentQueuePlayer.currentItem == weakSelf?.currentAudioItemOne {

            weakSelf?.currentQueuePlayer.insertItem((weakSelf?.currentAudioItemTwo)!, afterItem: nil)
            weakSelf?.currentAudioItemTwo.seekToTime(kCMTimeZero)
        } else {
            weakSelf?.currentQueuePlayer.insertItem((weakSelf?.currentAudioItemOne)!, afterItem: nil)
            weakSelf?.currentAudioItemOne.seekToTime(kCMTimeZero)
        }

    })

Here's my code to set up the current QueuePlayer.

let audioPlayerItem = AVPlayerItem(URL: url)
currentAudioItemOne = audioPlayerItem


currentAudioItemTwo = audioPlayerItem

currentQueuePlayer = AVQueuePlayer()
currentQueuePlayer.insertItem(currentAudioItemOne, afterItem: nil)

currentQueuePlayer.play()

I've been working at this problem for several days now. Any leads or new things to try would be appreciated. The only thing I haven't tried so far is lower quality audio files. These .wav files are all over 1mb, and had be suspecting that the file size could be affecting the seamless looping.

EDIT:

Using AVPlayerLooper to create the 'Treadmill' effect:

        let url = URL(fileURLWithPath: path)
        let audioPlayerItem = AVPlayerItem(url: url)
        currentAudioItemOne = audioPlayerItem

        currentQueuePlayer = AVQueuePlayer()
        currentAudioPlayerLayer = AVPlayerLayer(player: currentQueuePlayer)
        currentAudioLooper = AVPlayerLooper(player: currentQueuePlayer, templateItem: currentAudioItemOne)

        currentQueuePlayer.play() 

EDIT 2:

afinfo on one of my wav files:

Num Tracks:     1
----
Data format:     2 ch,  44100 Hz, 'lpcm' (0x0000000C) 16-bit little-endian signed integer
                no channel layout.
estimated duration: 11.302336 sec
audio bytes: 1993732
audio packets: 498433
bit rate: 1411200 bits per second
packet size upper bound: 4
maximum packet size: 4
audio data file offset: 44
not optimized
source bit depth: I16
----
Community
  • 1
  • 1
Peter Li
  • 309
  • 3
  • 11

1 Answers1

4

You are inserting the item too late in your current solution. You need to queue up more than one initial item, so there's always a primed AVPlayerItem ready to go.

This is called the AVPlayerQueue "treadmill pattern" as better described in this WWDC 2016 session. If you're targeting iOS 10, you can use new AVPlayerLooper class which does it for you (also described in the same link). Apple has also provided a sample project which provides an example of both strategies.

Lower level solutions include queuing up the audio buffers to an AVAudioEngine instance or using an AudioQueue or mashing the buffers together yourself with an AudioUnit.

NSTJ
  • 3,858
  • 2
  • 27
  • 34
Rhythmic Fistman
  • 34,352
  • 5
  • 87
  • 159
  • Thanks, I was able to update to Xcode 8 and implement AVPlayerLooper, however I still hear an obvious gap. This is so strange. The music file is cut perfectly. I don't know what's wrong. I'm definitely implementing AVPlayerLooper correctly. I can paste my code on the edit. – Peter Li Sep 13 '16 at 14:47
  • It's .wav uncompressed. The sound guy I get the stuff from is convinced that it's a perfect loop. And from listening to it, I believe him. I feel like I'm missing something up. The AVPlayerLooper should work... I'm so shocked. Should I try the classic treadmill implementation using AVPlayerQueue? Or maybe I'm using AVPlayerLooper wrong. I don't know. Any direction would be awesome. Thanks for the response man. This has been emotionally brutal... – Peter Li Sep 14 '16 at 19:25
  • Just wanted to say. I used AVAudioEngine buffer and it seems to work. Any idea why treadmill couldn't pull off what AVAudioEngine could? – Peter Li Sep 15 '16 at 05:55
  • I tried your code with `AVAudioPlayer` and my own wav and it worked perfectly... But if `AVAudioEngine` works for you, go for it I guess. – Rhythmic Fistman Sep 15 '16 at 05:56
  • One thought: can you run afinfo on your wav file? – Rhythmic Fistman Sep 15 '16 at 06:14
  • That's so strange... I wonder why AVAudioEngine worked though and not AVPlayerLooper. I attached the afinfo on the edit of my post. – Peter Li Sep 15 '16 at 07:01
  • that's strange. it looks like my wav. one last thing - have you looked at the waveform in a sound editor, to make sure the beginning and end continuously match up? – Rhythmic Fistman Sep 15 '16 at 13:41
  • Let us [continue this discussion in chat](http://chat.stackoverflow.com/rooms/123468/discussion-between-peter-li-and-rhythmic-fistman). – Peter Li Sep 15 '16 at 17:58