iOS Swift AVAudioPlayer playAtTime method doesn't work - ios

My code is very simple like this:
import UIKit
import AVFoundation
class VC: UIViewController {
var player:AVAudioPlayer!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
let url = NSURL.fileURLWithPath(NSBundle.mainBundle().pathForResource("audioName", ofType: "mp3")!)
do{
try player = AVAudioPlayer(contentsOfURL: url)
print("duration", player.duration)// duration 200
player.prepareToPlay()
player.playAtTime(50.0)
}catch{
}
}
}
when player.play() is called, the audio can play normally.
I don't know why the playAtTime function doesn't work.
Please help!

playAtTime plays the sound at a time in the future, specified relative to the device's current time. So the time parameter needs to be the current device time plus your required delay (in seconds):
player.playAtTime(player.currentDeviceTime + 50)
player.currentTime will be 0 when you initialise the player so this isn't the property to use.
Apple doc is here

func playAtTime(time: NSTimeInterval) -> Bool
This function is meant to play the sound some time in the future, based on and greater than deviceCurrentTime, i would suggest to try:
player.playAtTime(player.currentTime + 50.0)

Related

Sound is not playing when using AVFoundation in Swift

I am trying to play some background music for my game. I am not getting an error, but the music itself is not playing at all. I have checked the sound on the iOS Simulator and on my MacBook but I cannot hear anything, so I was wondering if anyone knew what is wrong with my code. I have even tried declaring var musicPlayer: AVAudioPlayer! and var musicPlayer: AVAudioPlayer? but it still does not work. I am currently using Xcode version 13.1.
import AVFoundation
class GameViewController: UIViewController {
var musicPlayer = AVAudioPlayer()
override func viewWillLayoutSubviews() {
super.viewWillLayoutSubviews()
// Build the menu scene
let menuScene = MenuScene()
let skView = self.view as? SKView
// Ignore drawing order of child nodes in order to increase performance
skView?.ignoresSiblingOrder = true
// Size the scene to fit the view exactly
menuScene.size = view.bounds.size
// Show the menu
skView?.presentScene(menuScene)
// Start background music
if let musicPath = Bundle.main.path(forResource: "backgroundMusic.m4a", ofType: nil) {
let url = URL(fileURLWithPath: musicPath)
do {
musicPlayer = try AVAudioPlayer(contentsOf: url)
musicPlayer.numberOfLoops = -1
musicPlayer.prepareToPlay()
musicPlayer.play()
}
catch {/* Couldn't load music file */}
}
}
move your audio player code into SKScene.didMove(to view: SKView) - i.e. in MenuScene not in your view controller

Resume sound in AVAudioPlayer with swift

I am trying to play a clip of sound that can be paused, resumed or stopped. My pause button works, and the action function for this button is shown below. However I cannot get my resume function to work. When I press the resume button no audio is played. I have read around online and the only tips I can find are to use the prepareToPlay() function and to set shortStartTimeDelay to a value greater than 0.0. I have tried both of these to no avail.
timeAtPause is a global variable of type NSTimeInterval
The action function for the pause button is as follows:
#IBAction func pauseAllAudio(sender: UIButton) {
timeAtPause = audioPlayer.currentTime
audioPlayer.pause()
}
The action function for the resume button is as follows:
#IBAction func resumeAllAudio(sender: UIButton) {
let shortStartDelay = 0.01
audioPlayer.prepareToPlay()
audioPlayer.playAtTime(timeAtPause + shortStartDelay)
}
Any tips on how to resume the audio would be really appreciated.
Thank you for your time.
You should not use playAtTime() to resume the AVAudioPlayer, as documentation states:
Plays a sound asynchronously, starting at a specified point in the
audio output device’s timeline.
and
Use this method to precisely synchronize the playback of two or more
AVAudioPlayer objects.
And, even if you use it, it should be used in conjunction with deviceCurrentTime plus the time in seconds to have the delay. In one word, it's not meant to be used to resume the paused player. Instead, just use play() to resume the playback.
You probably did what I did. Upon calling my play() function, I created a new player every time:
// The Wrong Way
#IBAction func playAction(sender: AnyObject) {
// do and catch omitted for brevity
player = try AVAudioPlayer(contentsOf: goodURL)
player.play()
}
However this should be done at some other initialization time, such as when you have the user choose the file to play.
Or, if you're hard-coding the file to play, you could initialize at an earlier time, such as viewDidLoad().
Then, once your AVAudioPlayer is initialized, you can call .play() and pause() on it and they will work correctly. And .play() will resume after a pause.
To simply pause and restart at the same point in the audio file, the following works:
#IBAction func playAction(sender: AnyObject) {
player.play()
}
#IBAction func pauseAction(sender: AnyObject) {
player.pause()
}
I found these to be helpful. If you are doing this in Swift, I did this a little differently. I created an AudioPlayer class after importing AVFoundation and used these two functions to pause and resume the music. In the SwiftUI call, I used an #State boolean that toggled between the button states. Here is the code that I used:
import AVFoundation
class MusicPlayer {
static let shared = MusicPlayer()
var audioPlayer: AVAudioPlayer?
// Pause music
func pauseBackgroundMusic() {
guard let audioPlayer = audioPlayer else { return }
audioPlayer.pause()
}
// Resume music
func resumeBackgroundMusic() {
guard let audioPlayer = audioPlayer else { return }
audioPlayer.play()
}
}
And in the View:
Define an #State variable:
#State var pauseMode = false
And call the music player as follows in the Button action code:
Button(action: {
if pauseMode {
MusicPlayer.shared.resumeBackgroundMusic()
self.pauseMode.toggle()
} else {
MusicPlayer.shared.pauseBackgroundMusic()
self.pauseMode.toggle()
}
})
{
Image(systemName: "playpause")
}
FYI - I posted this in SwiftUI since newer programmers (and Apple) seem to be moving in this direction. I figured this might help someone (like me!) who came to this question seeking a Swift / SwiftUI answer.

How to stream audio for only a known duration using swift

I'm using AVPlayer (I don't need to, but I wanna stream it and start playing as soon as possible) to play an m4a file (it's an iTunes audio preview). Only I only want it to play a part of that file.
I'm able to set a start time but not an end time.
Using a timer is not working because I'm using URL as a http address. I'm playing as it loads, without downloading the file.
I also saw solutions in Objective-C to use KVO to know when music starts playing but I'm thinking this is not the best approach since I'm using swift and also because of glitches that may occur so the song will not stop at the right moment.
You can add a addBoundaryTimeObserverForTimes to your AVPlayer as follow:
update: Xcode 8.3.2 • Swift 3.1
import UIKit
import AVFoundation
class ViewController: UIViewController {
var player: AVPlayer!
var observer: Any!
override func viewDidLoad() {
super.viewDidLoad()
guard let url = URL(string: "https://www.example.com/audio.mp3") else { return }
player = AVPlayer(url: url)
let boundary: TimeInterval = 30
let times = [NSValue(time: CMTimeMake(Int64(boundary), 1))]
observer = player.addBoundaryTimeObserver(forTimes: times, queue: nil) {
[weak self] time in
print("30s reached")
if let observer = self?.observer {
self?.player.removeTimeObserver(observer)
}
self?.player.pause()
}
player.play()
print("started loading")
}
}

Play sound effect without latency

I am trying to make a little app to teach myself some swift and I'm having some problems figuring out how to get my app to function a certain way.
My app should be able to play an airhorn sound just like the way it sounds in this video...
https://www.youtube.com/watch?v=Ks5bzvT-D6I
But each time I tap the screen repeatedly there is a slight delay before the sound is played so it's not sounding like that at all.
import UIKit
import AVFoundation
class ViewController: UIViewController {
var audioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
var hornSound = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("horn", ofType: "mp3")!)
AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, error: nil)
AVAudioSession.sharedInstance().setActive(true, error: nil)
var error:NSError?
audioPlayer = AVAudioPlayer(contentsOfURL: hornSound, error: &error)
audioPlayer.prepareToPlay()
}
#IBAction func playSound(sender: UIButton) {
audioPlayer.pause()
audioPlayer.currentTime = 0
audioPlayer.play()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
I have also come across this thread about using spritekit
Creating and playing a sound in swift
And in trying that I got it to play the sound without the delay, but with sprite kit I can't stop the existing sound, so they just overlap which is not the effect I want.
Is there a work around to get this working the way it sounds in the video.
Apple recommends AVAudioPlayer for playback of audio data unless you require very low I/O latency.
So you might want to try another approach.
In one of my apps I play countdown sounds by creating a system sound ID from my wav file. Try this in your class:
import UIKit
import AudioToolbox
class ViewController: UIViewController {
var sound: SystemSoundID = 0
override func viewDidLoad() {
super.viewDidLoad()
var hornSound = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("horn", ofType: "mp3")!)
AudioServicesCreateSystemSoundID(hornSound!, &self.sound)
}
#IBAction func playSound(sender: UIButton) {
AudioServicesPlaySystemSound(self.sound)
}
...
}

ReEdited : Audio not playing in real ios Device but playing in simulated devices in Swift2

This is my code in swift2 , absolutely no error ,Im running on a real device but no sound is coming ? no exceptions nothing am I missing something?
import UIKit
import AVFoundation
class ViewController: UIViewController {
#IBAction func play() {
audioPlayer.play()
}
var audioPlayer : AVAudioPlayer!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let alertSound = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("drum", ofType: "mp3")!)
do {
audioPlayer = try AVAudioPlayer(contentsOfURL: alertSound)
audioPlayer.prepareToPlay()
audioPlayer.volume = 1.0
audioPlayer.play()
print("yes")
}catch _ {
audioPlayer = nil
}
}
}
I found out what was the problem from one of the videos of Apple's WWDC Conference
http://adcdownload.apple.com/videos/wwdc_2010__hd/session_412__audio_development_for_iphone_os_part_1.mov (only viewable on safari)
The problem was audio routing , when i plugged in the earphone I could listen to the sound from the real device .
When the route is changed we need to set the proper audio routing using AVAudioSession . More info in that video above.
Import AVFoundation and add this to your viewDidLoad:
var err = NSErrorPointer()
AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, error: err)

Resources