Play NSSound more times, simultaneously - swift

I need to insert a code, in function, that when called play a sound.
The problem is that the function is called faster than sound duration and so, the sound is played less times than function calls.
function keyDownSound(){
NSSound(named: "tennis")?.play()
}
The problem is that NSSound starts playing only if isn't already played. Any ideas to fix?

Source of problem is that init?(named name: String) return same NSSound instance for same name. You can copy NSSound instance, then several sounds will play simultaneously:
function keyDownSound(){
NSSound(named: "tennis")?.copy().play()
}
Alternative way - start sound again after finish playing. For that you need to implement sound(sound: NSSound, didFinishPlaying aBool: Bool) delegate method. For example:
var sound: NSSound?
var playCount: UInt = 0
func playSoundIfNeeded() {
if playCount > 0 {
if sound == nil {
sound = NSSound(named: "Blow")!
sound?.delegate = self
}
if sound?.playing == false {
playCount -= 1
sound?.play()
}
}
}
func keyDownSound() {
playCount += 1
playSoundIfNeeded()
}
func sound(sound: NSSound, didFinishPlaying aBool: Bool) {
playSoundIfNeeded()
}

Related

How to set NowPlaying properties with a AVQueuePlayer in Swift?

I have an AVQueuePlayer that gets songs from a Firebase Storage via their URL and plays them in sequence.
static func playQueue() {
for song in songs {
guard let url = song.url else { return }
lofiSongs.append(AVPlayerItem(url: url))
}
if queuePlayer == nil {
queuePlayer = AVQueuePlayer(items: lofiSongs)
} else {
queuePlayer?.removeAllItems()
lofiSongs.forEach { queuePlayer?.insert($0, after: nil) }
}
queuePlayer?.seek(to: .zero) // In case we added items back in
queuePlayer?.play()
}
And this works great.
I can also make the lock screen controls appear and use the play pause button like this:
private static func setRemoteControlActions() {
let commandCenter = MPRemoteCommandCenter.shared()
// Add handler for Play Command
commandCenter.playCommand.addTarget { [self] event in
queuePlayer?.play()
return .success
}
// Add handler for Pause Command
commandCenter.pauseCommand.addTarget { [self] event in
if queuePlayer?.rate == 1.0 {
queuePlayer?.pause()
return .success
}
return .commandFailed
}
}
The problem comes with setting the metadata of the player (name, image, etc).
I know it can be done once by setting MPMediaItemPropertyTitle and MPMediaItemArtwork, but how would I change it when the next track loads?
I'm not sure if my approach works for AVQueueplayer, but for playing live streams with AVPlayer you can "listen" to metadata receiving.
extension ViewController: AVPlayerItemMetadataOutputPushDelegate {
func metadataOutput(_ output: AVPlayerItemMetadataOutput, didOutputTimedMetadataGroups groups: [AVTimedMetadataGroup], from track: AVPlayerItemTrack?) {
//look for metadata in groups
}
}
I added the AVPlayerItemMetadataOutputPushDelegate via an extension to my ViewController.
I also found this post.
I hope this gives you a lead to a solution. As said I'm not sure how this works with AVQueuePlayer.

AVAudioPlayerNode skips a lot of sounds when stopped and started again

I'm scheduling a buffer periodically using scheduleBuffer function. The completion handler schedules the next sound when current one is done, like so:
func scheduleSounds() {
if !isPlaying { return }
while beatsScheduled < beatsToScheduleAhead {
// Returns AVAudioTime of the next sound, based on sample time
// first sound is always sample time 0, then increments.
let nextTime = getNextTime()
player.scheduleBuffer(
soundBuffer,
at: nextTime,
options: AVAudioPlayerNodeBufferOptions(rawValue: 0),
completionHandler: {
self.queue!.sync {
print("Finished playing a sound!")
scheduleSounds()
}
}
)
beatsScheduled += 1
if !playerStarted {
player.play(at: nil)
playerStarted = true
}
}
}
When I stop and then start the player/engine there is a delay before the sound starts playing and after logging which sounds play when I noticed that there are couple of sounds that the engine does not play. My suspicion is that the player nodes think that the sample time has already passed relative to the player node time. I think I might not be resetting or stopping something properly, here's how I start and stop the sounds:
func start() {
do {
try engine.start()
isPlaying = true
queue!.sync() {
self.scheduleSounds()
}
} catch {
print("\(error)")
}
}
func stop() {
isPlaying = false
player.stop()
engine.stop()
playerStarted = false
}
So the problem is how do I make sure that there are no sounds that AVAudioPlayerNode skips before playing the sound?

How to keep AVMIDIPlayer playing?

I'm trying to use Apple's AVMIDIPlayer object for playing a MIDI file. It seems easy enough in Swift, using the following code:
let midiFile:NSURL = NSURL(fileURLWithPath:"/path/to/midifile.mid")
var midiPlayer: AVMIDIPlayer?
do {
try midiPlayer = AVMIDIPlayer(contentsOf: midiFile as URL, soundBankURL: nil)
midiPlayer?.prepareToPlay()
} catch {
print("could not create MIDI player")
}
midiPlayer?.play {
print("finished playing")
}
And it plays for about 0.05 seconds. I presume I need to frame it in some kind of loop. I've tried a simple solution:
while stillGoing {
midiPlayer?.play {
let stillGoing = false
}
}
which works, but ramps up the CPU massively. Is there a better way?
Further to the first comment, I've tried making a class, and while it doesn't flag any errors, it doesn't work either.
class midiPlayer {
var player: AVMIDIPlayer?
func play(file: String) {
let myURL = URL(string: file)
do {
try self.player = AVMIDIPlayer.init(contentsOf: myURL!, soundBankURL: nil)
self.player?.prepareToPlay()
} catch {
print("could not create MIDI player")
}
self.player?.play()
}
func stop() {
self.player?.stop()
}
}
// main
let myPlayer = midiPlayer()
let midiFile = "/path/to/midifile.mid"
myPlayer.play(file: midiFile)
You were close with your loop. You just need to give the CPU time to go off and do other things instead of constantly checking to see if midiPlayer is finished yet. Add a call to usleep() in your loop. This one checks every tenth of a second:
let midiFile:NSURL = NSURL(fileURLWithPath:"/Users/steve/Desktop/Untitled.mid")
var midiPlayer: AVMIDIPlayer?
do {
try midiPlayer = AVMIDIPlayer(contentsOfURL: midiFile, soundBankURL: nil)
midiPlayer?.prepareToPlay()
} catch {
print("could not create MIDI player")
}
var stillGoing = true
while stillGoing {
midiPlayer?.play {
print("finished playing")
stillGoing = false
}
usleep(100000)
}
You need to ensure that the midiPlayer object exists until it's done playing. If the above code is just in a single function, midiPlayer will be destroyed when the function returns because there are no remaining references to it. Typically you would declare midiPlayer as a property of an object, like a subclassed controller.
Combining Brendan and Steve's answers, the key is sleep or usleep and sticking the play method outside the loop to avoid revving the CPU.
player?.play({return})
while player!.isPlaying {
sleep(1) // or usleep(10000)
}
The original stillGoing value works, but there is also an isPlaying method.
.play needs something between its brackets to avoid hanging forever after completion.
Many thanks.

Radio Streaming AVPlayer latency (delay) is to high swift 3

In my app I play audio live streaming and the delay is very important. I'm using AVPlayer but it takes 5-6 sec to start and I need it 3 sec max of delay. How can I do it to start playing faster and reduce that delay?
Set a small buffer will do the job? how to set it up with AVPlayer?
This is my RadioPlayer class:
import Foundation
import AVFoundation
class RadioPlayer {
static let sharedInstance = RadioPlayer()
private var player = AVPlayer()
private var isPlaying = false
private var language: LanguageDOM?
func play() {
player.play()
isPlaying = true
}
func pause() {
player.pause()
isPlaying = false
}
func toggle() {
if isPlaying == true {
pause()
} else {
play()
}
}
func currentTimePlaying() -> CMTime {
return player.currentTime()
}
func changeLanguage(nlanguage: LanguageDOM){
self.pause()
self.language = nlanguage
player = AVPlayer(url: NSURL(string: nlanguage.url)! as URL)
self.play()
}
func currentlyPlaying() -> Bool {
return isPlaying
}
func currentLanguage() -> LanguageDOM {
return self.language!
}
func currentLanguageId() -> Int {
if self.language == nil {
return -1
}
else {
return language!.id
}
}
}
I'm assuming your network is fast enough to load the necessary buffer for 3 second delays.
What you want to look at is the -prerollAtRate: of AVPlayer. When used properly, it will allow for minimal latency between when you press play, and when you hear the sound. It requires however that part of the song be downloaded already for processing.
As for AVAudioSession, it is not what you're looking for, AVPlayer is the correct class for you.
If AVPlayer isn't fast enough I suggest looking into BASS which is a low level C like audio library built upon AudioUnits framework which allow for precise and fast control over your stream.

Swift: Issue with my NSTimer?

So in general terms, this is what i want my program to do: music will play, randomly generate a number between 3 and 12, then set a timer according to that number, when the timer is finished stop the music for 1 second, then after that one second, play the music again(and repeat the process, generate random number...)
This is the code I have written to do so, it works great the first run, but the second run it starts calling the functions at a faster rate(not following the timer) and sometimes in a different order(you can test this by looking at the print out)
var randomTimeInteger = Int()
var stopMusicTimer = NSTimer()
var startMusicTimer = NSTimer()
override func viewDidLoad(){
playMusic()
}
//Call to get random interger
func generateRandomInteger(){
print("Inside generate random integer")
let lower = 3
let upper = 12
randomTimeInteger = lower + Int(arc4random_uniform(UInt32(upper - lower + 1)))
print("Random Integer is : \(randomTimeInteger)")
initiateTimer()
}
//Start the timer
func initiateTimer() {
print("Inside initate Timer")
//Set Timer
stopMusicTimer = NSTimer.scheduledTimerWithTimeInterval(Double(randomTimeInteger), target: self, selector: "stopMusic", userInfo: nil, repeats: true)
}
//Stop the music
func stopMusic(){
print("Inside stop music")
GameViewController.backgroundAudio?.stop()
startMusicTimer = NSTimer.scheduledTimerWithTimeInterval(3.0, target: self, selector: "playMusic", userInfo: nil, repeats: true)
}
func playMusic(){
print("Inside play music")
GameViewController.backgroundAudio?.play()
generateRandomInteger()
}
Any idea what the problem is? Or if there is a better way to do so? PLEASE HELP!
You have one timer that tries to stop the music every 3 to 12 seconds. And sometimes you create a timer that tries to start the music every 3 seconds. And sometimes you create more of these timers. So eventually you have lots and lots of timers that try starting and stopping the music at random time.
To stop a repeating timer, call invalidate.
And don't initialise the timers as you do, NSTimer() returns nothing useful. Just declare the variables as NSTimer?
There are really numerous viable approaches.
Starting from the top, I would sketch the design as follows:
func playMusic(completion: (ErrorType?)->()) {
play(randomDuration()) { error in
if error != nil {
completion(error)
return
}
delay(1.0, f: play)
}
}
func randomDuration() -> Double {...}
func play(duration: Double, completion: (ErrorType?)->()) {
let player = Player(...)
player.resume()
delay(duration) {
player.suspend()
completion(nil)
}
}
Function delay(_:f:) is implemented in terms of dispatch_after.
You will probably notice, that playMusic runs indefinitely. This is by your requirements, but in practice you need a way to stop it.