I'm trying to use AVQueuePlayer to create a seamless audio loop, however, I don't know why there is a small silent pause between loops?

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
----
ios
swift
avfoundation
avplayer
asked on Stack Overflow Sep 11, 2016 by Peter Li • edited May 23, 2017 by Community

1 Answer

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.

answered on Stack Overflow Sep 12, 2016 by Rhythmic Fistman • edited Dec 16, 2016 by NSTJ

User contributions licensed under CC BY-SA 3.0