AudioKit AKPlayer stop with at AVAudioTime method - ios

In AudioKit there is this method for AKPlayer:
#objc dynamic public func play(at audioTime: AVAudioTime?)
I want the same for stop method because I want to be able to stop the player at any time when the user hits the stop button. I am making a music app and I need to stop the sound in X time which is calculated based on BPM and etc.
Here is how I start my AKPlayer:
drums.play(at: AVAudioTime.now() + timeToClosestBeatGrid)
I want the same API with stop:
drums.stop(at: AVAudioTime.now() + timeToClosestBeatGrid) // this api doesnt exist :(((
I tried using endTime property by setting it but it does not seem to do anything...
How may I accomplish this?
PS: I am not looking for a Timer solution this is because a timer is not 100% accurate. I want my stop method to be 100% accurate just like play method

The most accurate way to schedule events in AudioKit is by using AKSequencer. The sequencer can be connected to a callback instrument, which is a node that passes the events to an user-defined function.
In your case, you would add an event at the time where you want the player to stop. In your callback function, you would stop the player as a response to that event.
This is an outline of what should be done:
Create a track to contain the stop event, using AKSequencer's addTrack method. Connect this track to an AKCallbackInstrument. Please see this answer on how to connect an AKCallbackInstrument to an AKSequencer track.
Add the stop event to the track, at the time position where you want the music to stop. As you will be interpreting the event yourself with a callback function, it doesn't really matter what type of event you use. You could simply use a Note On.
In the callback function, stop the player when that event is received.
This is what your callback function would look like:
func stopCallback(status:UInt8, note:MIDINoteNumber, vel:MIDIVelocity) -> () {
guard let status = AKMIDIStatus(byte: status),
let type = status.type,
type == .noteOn else { return }
drums.stop()
}

According to AudioKit documentation, you can try using the schedule(at:) method:
You can call this to schedule playback in the future or the player will call it when play() is called to load the audio data
After the play() method you should declare this schedule(at:) with an offset AVAudioTime.now() + timeToClosestBeatGrid and specify .dataPlayedBack as completion callback type, because this completion is called when (from docs)...
The buffer or file has finished playing
and now (in completion block) you can call drums.stop()
But... If the .stop() method should be called whenever the button is pressed, why not use some form of delay (Timer or DispatchQueue) with the value timeToClosestBeatGrid as the offset?

Related

Get a callback from AKPlayer at a user specified time

I’m trying to get a callback at a given point in an AKPlayer’s file playback (currently, just before the end). I see the Apple docs on addBoundaryTimeObserver(), which would work, but it doesn’t seem to be accessible from AKPlayer (I guess an AVAudioPlayerNode vs AVPlayer thing). Any suggestions? I see a few callbacks in AVAudioPlayerNode… maybe I could determine the buffer based on the desired time and use dataConsumed?
The goal is to trigger another event just before the file finishes playing (there is a callback on completion, but obviously that's too late).
If anybody has done something similar, or knows of something similar (a gist, etc), that would be great.
There's an AudioKit playground called AKPlaygroundLoop that shows you how to call an arbitrary handler periodically, based on CADisplayLink. In your handler you could check the AKPlayer's currentTime and if it's close to the end (say 1 second before) you could trigger whatever event you want.
This is a rough outline:
var player: AKPlayer!
var loop: AKPlaygroundLoop!
func play() {
// ...
playgroundLoop = AKPlaygroundLoop(frequency: 10.0, handler: myHandler)
}
func myHandler() {
if player.currentTime >= player.duration - 1.0 {
// trigger some event
}
}
See also this answer for advice on how to synchronize events with AudioKit.

Playing random audio files in sequence with AKPlayer

I am working on a sort of multiple audio playback project. First, I have 10 mp3 files in a folder. I wanted AKPlayer to play one of these audio files randomly, but in sequence - one after the other. But playing a random file after another random file seems to be tricky. Here's what I've written:
let file = try? AKAudioFile(readFileName: String(arc4random_uniform(9)+1) + ".mp3")
let player = AKPlayer(audioFile: file!)
player1.isLoopiong = true
player.buffering = .always
AudioKit.output = AKPlayer
try? AudioKit.start()
player.start(at: startTime)
This code loops the first chosen random file forever - but I simply wanted to play each random files once. Is there any way I can reload the 'file' so the player starts again when it's done playing? I've tried calling multiple AKPlayers (but calling 10 players must be wrong), if player.isPlaying = false, sequencer, etc, but couldn't exactly figure out how. Apologize for such a newbie question. Thank you so much.
AKPlayer has a completion handler
to be called when Audio is done playing. The handler won’t be called
if stop() is called while playing or when looping from a buffer.
The completion handler type is AKCallback, which is a typealias for () -> Void. If you have some good reason not to use 10 AKPlayers, you could probably use the completion handler to change the file and restart the player. But you could also create an array with 10 AKPlayers, each loaded with a different file, and have a function that selects a player at random for playback (or that cycles through a a pre-shuffled array). The completion handler for each player in the array could call this function, when appropriate. As per the doc quoted above, make sure that the AKPlayer is not looping or else the completion handler won't be called.
yes, you can use the completionHandler of the player to load a new file into the same player when playback finishes. In your completion block:
player.load(url: nextFile)
player.play()
Another approach is to use the AKClipPlayer with 10 clips of a predetermined random order and schedule them in sequence. This method will be the most seamless (if that matters).

Sequence operations

I have a little card playing app. When the computer is playing, some functions are being fired after some time to mimic a real player, like so:
self.operation.addOperation {
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now()+2) {
self.passTurn()
}
}
Everything works fine, but when I want to cancel the game and start a new one, the app crashes if I do it within two seconds before the self.passTurn() function fires.
Ideally I would like to have some sort of pause between the different operations. The above mentioned operation gets released from the queue immediately after it fires the delayed function, so cancelling or suspends the operation does not work.
Is it possible to somehow retain the operation in the queue for two seconds and release it afterwards when the actual function is done?
Thanks!

Play a sound for a specific number of times in AVAudioPlayerNode in iOS

There is an option numberofloops in AVAudioPlayer which repeats a sound file for a specified number of times.
I have to implement this type of function in AVAudioPlayerNode and I found an option just to loop a sound file in an infinite number of times in a loop using the following code
audioPlayerNode.scheduleBuffer(audioFileBuffer, atTime: nil,
options:.Loops, completionHandler: nil)
Is it possible to repeatedly play a file in a fixed number of times like as AVAudioPlayer? Is there any code sample to achieve this?
Am not sure if there is an option to do it however you can achieve this by creating a simple function like below.
func playBuffer(buffer,times) {
for (var i=1;i<=times;i++) {
audioPlayerNode.scheduleBuffer(audioFileBuffer)
}
}
and then you call the function like this
playBuffer(buffer,3) // to execute three times.
// or
playBuffer(buffer,1) // to execute one time.
p.s: This is almost like a pseudo code just to show the logic. you have to write it in swift.

Youtube API on clicking a different times does BUFFERING (state=3) always occur?

I am using the Youtube API and was wondering if anyone knew if when you click the youtube player's time (at the bottom of the video) to progress/or go back to an earlier point in the video if the BUFFERING, or state=3 value always occur?
For example:
function onPlayerStateChange(event) {
//video is buffering, one cause is the user
//clicked to progress/go back in the video.
//Does buffering state always happen in this case?
if (event.data == 3) {
//BUFFERING
}
}
It is intended to fire 3 (buffering) due to onStateChange when pressing play button or player's time. As stated here, event fires whenever the player's state changes. The data property of the event object that the API passes your evnt listener function will specifiy an integer that corresponds to the new player state. Below are the possible values:
-1 (unstarted)
0 (ended)
1 (playing)
2 (paused)
3 (buffering)
5 (video cued).

Resources