I want a user to press a button, it changes background color (to yellow), a WAV is played and on completion of the WAV the button reverts to its original color (to red). So have a completion handler around the sound. Have tried various combinations of the code below but the WAV plays and the button doesn't appear to change color.
Is this the wrong approach or am I doing something wrong? Don't want to have to put completion handlers around the color changes as that, I presume, is overkill.
Many thanks.
typealias CompletionHandler = (success:Bool) -> Void
#IBAction func fireButton(sender: AnyObject) {
playLaser( { (success)-> Void in
if success {
self.shots -= 1
self.labelShotsLeft.text = String(self.shots)
} else {
}
})
}
func playLaser(completionHandler: CompletionHandler) {
fireButton.layer.backgroundColor = UIColor.yellowColor().CGColor
let url = NSBundle.mainBundle().URLForResource("laser", withExtension: "wav")!
do {
player = try AVAudioPlayer(contentsOfURL: url)
guard let player = player else { return }
player.prepareToPlay()
player.play()
} catch let error as NSError {
print(error.description)
}
self.fireButton.layer.backgroundColor = UIColor.redColor().CGColor
completionHandler(success: true)
}
To detect AVAudioPlayer finish playing, you need to use AVAudioPlayerDelegate.
You may need to write something like this:
func playLaser(completionHandler: CompletionHandler) {
fireButton.layer.backgroundColor = UIColor.yellowColor().CGColor
let url = NSBundle.mainBundle().URLForResource("laser", withExtension: "wav")!
do {
player = try AVAudioPlayer(contentsOfURL: url)
guard let player = player else { return }
player.delegate = self //<- Sorry, this was missing in my first post
player.play()
} catch let error as NSError {
print(error.description)
}
audioPlayerCompletionHandler = completionHandler
}
var audioPlayerCompletionHandler: CompletionHandler?
func audioPlayerDidFinishPlaying(player: AVAudioPlayer, successfully flag: Bool) {
self.fireButton.layer.backgroundColor = UIColor.redColor().CGColor
audioPlayerCompletionHandler?(success: true)
}
(You need to add conformance to AVAudioPlayerDelegate to your ViewController's declaration header.)
Code does not magically pause and wait just because you say play.play() — that would be horrible! Thus, your so-called completion handler is not a completion handler at all. It runs immediately — that is, as soon you start playing. Your code does nothing about obtaining information as to when the audio player has finished playing.
For that, you need to configure a delegate and receive the delegate message that audio player emits when it finishes playing.
This is one of those questions which is a little more subtle than meets the eye. I tried putting three completion handlers around each task: change colour to yellow, play sound, change colour back to red. The code was being executed in the correct sequence as I NSLogged it but the button never changed colour due to screen updating controls. Here is the code that works that I hope other readers might find useful:
Swift 2.0
#IBAction func fireButton(sender: AnyObject) {
dispatch_async(dispatch_get_global_queue(DISPATCH_QUEUE_PRIORITY_DEFAULT, 0)) {
dispatch_sync(dispatch_get_main_queue()) {
self.fireButton.layer.backgroundColor = UIColor.yellowColor().CGColor
}
self.playLaser( { (success)-> Void in
if success {
self.shots -= 1
} else {
}
})
dispatch_sync(dispatch_get_main_queue()) {
self.labelShotsLeft.text = String(self.shots)
self.fireButton.layer.backgroundColor = UIColor.redColor().CGColor
}
}
}
func playLaser(completion: (success: Bool) -> ()) {
let url = NSBundle.mainBundle().URLForResource("laser", withExtension: "wav")!
do {
player = try AVAudioPlayer(contentsOfURL: url)
guard let player = player else { return }
player.play()
completion(success: true)
} catch let error as NSError {
completion(success: false)
}
}
Swift 3.0
#IBAction func fireButton(_ sender: AnyObject) {
let fireQueue = DispatchQueue(label: "queueFirebutton")
fireQueue.async {
DispatchQueue.main.sync {
self.fireButtonDisabled()
}
DispatchQueue.main.sync {
self.playLaser()
self.shots -= 1
if self.shots <= 0 {
self.shots = 0
}
}
DispatchQueue.main.sync {
if self.shots < 0 { self.shots = 0}
self.labelShotsLeft.text = String(self.shots)
sleep(1)
self.fireButtonEnabled()
}
}
}
func playLaser() {
let url = Bundle.main.url(forResource: "laser", withExtension: "wav")!
do {
player = try AVAudioPlayer(contentsOf: url)
guard let player = player else { return }
player.play()
} catch {
}
}
Related
Problem = PlaySound below crashes with this error:
AddInstanceForFactory: No factory registered for id
1st problem of 3 to go before submission to App Store
Setting a breakpoint before the guard let url block = NO error.
Setting a breakpoint after the guard let url block = error!!, coupled with a horrible static sound happening. Note that if I set NO breakpoints, the correct sound happens but with the above error.
What am I missing?
Within my SKScene Class:
func controllerInputDetected(gamepad: GCExtendedGamepad,
element: GCControllerElement,
index: Int) {
// rightShoulder
if (gamepad.rightShoulder == element)
{
if (gamepad.rightShoulder.value != 0)
{
startGame()
}
}
}
As shown above, I call the following function by pressing a shoulder button on my Gamepad:
func startGame() {
PlaySound(theSoundName: "roar")
}
func PlaySound(theSoundName: String) {
guard let url = Bundle.main.url(forResource: "audio/" + theSoundName,
withExtension: "mp3") else {
print("sound not found")
return
}
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default)
try AVAudioSession.sharedInstance().setActive(true)
itsSoundPlayer = try AVAudioPlayer(contentsOf: url)
if theSoundName == "roar" {
itsSoundPlayer?.numberOfLoops = -1 // forever
}
itsSoundPlayer!.play()
}
catch let error as NSError {
print("error: \(error.localizedDescription)")
}
}
FWIW the above iOS error changes for tvOS =
<NSError: 0x600002cdf540; domain: FBSSceneSnapshotErrorDomain; code: 4; reason: "an unrelated condition or state was not satisfied"
Again, what am I missing?
I am having some issues with playing sound in Xcode 9.2.
I tried watching few different tutorials and guides, but with no luck.
Either code is outdated, my app with crashes, the sound doesn't work, or it starts and cuts out after few milliseconds...
func updateDiceImages() {
// This is where the actual sound is attempted to play,
it should be triggered every time the button is pressed. Rest of the app works fine.
var soundEffect: AVAudioPlayer?
let path = Bundle.main.path(forResource: "rollingDice.wav", ofType:nil)!
let url = URL(fileURLWithPath: path)
do {
soundEffect = try AVAudioPlayer(contentsOf: url)
soundEffect?.play()
} catch {
print(error)
}
...
I am just not sure what I am doing wrong, tried tons of variations.
I am completely open to giving any information that I can provide. I didn't paste the whole code so it doesn't bother or swamp the people that try to read it.
Thank you so much for the help! =)
This is a taken piece of code from one of my applications, it works completely.
import AVFoundation
final class AudioManager {
private init() { }
static let shared = AudioManager()
private var player: AVAudioPlayer?
enum SoundType {
case incomingCall
}
open func playSound(_ type: SoundType) {
switch type {
case .incomingCall:
playIncomingCall()
}
}
// MARK: - Sounds
private func playIncomingCall() {
guard let url = Bundle.main.url(forResource: "incoming", withExtension: "wav") else { return }
playSound(url)
}
private func playSound(_ url: URL) {
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryAmbient)
try AVAudioSession.sharedInstance().setActive(true)
player = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileType.wav.rawValue)
guard let player = player else { return }
player.numberOfLoops = -1 // infinity loop
player.prepareToPlay()
player.play()
debugPrint("playSound play sound")
} catch {
debugPrint(error.localizedDescription)
}
}
open func stop() {
player?.stop()
}
}
I am trying to (1) download a piece of audio from a link, (2) add that newly-downloaded audio to an AVPlayer and (3) play it. Something is going wrong at step (3) and I'm looking for any guidance. Here's the code, including my alamofire and download functions, as I fear something may be going wrong at that stage.
import AVFoundation
class SettingAlarmViewController: UIViewController {
var player:AVPlayer!
override func viewDidLoad() {
super.viewDidLoad()
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
}
catch {
// report for an error
}
}
func getLatestPodcastURL(completion: #escaping (URL) -> ()) {
let RSSUrl: String = "https://www.npr.org/rss/podcast.php?id=510318"
Alamofire.request(RSSUrl).responseRSS() {response in
if let podcastURL: String = response.result.value?.items[0].enclosure!
{
let audioURL = URL(string: podcastURL)
completion(audioURL!)
}
else {
//error handling
}
}
}
func downloadSongAsynch(audioUrl: URL, completion: #escaping (URL) -> ()) {
let fileManager = FileManager.default
let documentsDirectoryURL = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first!.appendingPathComponent("podcasts/")
do {
try fileManager.createDirectory(atPath: documentsDirectoryURL.path,
withIntermediateDirectories: true, attributes: nil)
} catch {
//error handling
}
let destinationUrl = documentsDirectoryURL.appendingPathComponent(audioUrl.lastPathComponent)
URLSession.shared.downloadTask(with: audioUrl, completionHandler: { (location, response, error) -> Void in
guard let location = location, error == nil else { return }
do {
try FileManager.default.moveItem(at: location, to: destinationUrl)
} catch {
//error handling
}
}).resume()
completion(destinationUrl)
}
#IBAction func SetUpBotton(_ sender: Any) {
getLatestPodcastURL() {response in
//Uses an asynchronous call to Alamofire to get the podcast URL
self.downloadPodcastAsynch(audioUrl: response){response2 in
self.player = AVPlayer(url: response2)
print(self.player.currentItem)
}
#IBAction func PlayButton(_ sender: Any) {
player.play()
print(player.currentItem)
}
The log consistently shows my current item: >
But nothing plays. I have checked that the audio is working by trying to use the URL to stream this content. That works fine. I am getting the following:
BoringSSL errors "[BoringSSL] Function boringssl_session_errorlog: line 2871 [boringssl_session_read] SSL_ERROR_ZERO_RETURN(6): operation failed because the connection was cleanly shut down with a close_notify alert
but from what I've read, this is just a bug in the latest update and shouldn't be impacting the download. Any thoughts on this?
I have been trying to make a game to teach myself swift, and cant seem to get this code to work. I am extremely new to this, and can't seem to find out why it won't work... XCode doesn't flag any problems, build sucseed, and debugger even prints "Got to Stage 1 & Got to Stage 2... anything help?
I Imported AVFoundation..
class GAME {
class func SuperStartGame(playerwhowon1: SKSpriteNode) {
var player = AVAudioPlayer()
func PlaySound() {
guard let URL = Bundle.main.url(forResource: "PowerUp", withExtension: "mp3")
else {
print("Didn't Find URL")
return
}
do {
print("Got to Stage 1")
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
player = try AVAudioPlayer(contentsOf: URL, fileTypeHint: "mp3")
player.prepareToPlay()
player.play()
print("Got to Stage 2")
} catch let error as NSError {
print("error: \(error.localizedDescription)")
}
RoundNumber += 1
Round.text = "Round \(RoundNumber)"
if playerwhowon1 == Mine {
MyScore.run(addscoreM) {
PlaySound()
Round.run(NewRoundForRound) {
...
Code keeps going.. thats the only part that is relevant to the sound. I added the sound file to Xcode, and made sure it was added tot he project target... it is in my main bundle.
Make sure that your device isn't muted
mp3 file is copied to bundle
Example VC playing sound:
class ViewController: UIViewController {
var game = Game()
#IBAction func playAction(_ sender: UIButton) {
game.playSound()
}
}
class Game {
var player: AVAudioPlayer?
func playSound() {
guard let URL = Bundle.main.url(forResource: "SampleAudio", withExtension: "mp3") else {
print("Didn't Find URL")
return
}
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
player = try AVAudioPlayer(contentsOf: URL, fileTypeHint: "mp3")
player?.prepareToPlay()
player?.play()
} catch let error as NSError {
print("\(error.localizedDescription)")
}
}
}
I'm creating a game where the user controls a character with a jetpack. When the jetpack intersects a diamond, I add the diamond to their total and then play a sound. However, the sound makes the game pause for a tenth of a second or so and disrupts the flow. This is the code I'm using:
var diamondSound = NSBundle.mainBundle().URLForResource("diamondCollect", withExtension: "wav")!
var diamondPlayer = AVAudioPlayer?()
class GameScene: SKScene{
override func didMoveToView(view: SKView) {
do {
diamondPlayer = try AVAudioPlayer(contentsOfURL: diamondSound)
guard let player = diamondPlayer else { return }
player.prepareToPlay()
} catch let error as NSError {
print(error.description)
}
}
And then later:
override func update(currentTime: CFTimeInterval) {
if character.intersectsNode(diamond){
diamondPlayer?.play()
addDiamond()
diamond.removeFromParent()
}
}
Also I am using Sprite Kit if that matters. Any help is greatly appreciated!
Usually, I prefeer to use SKAction.playSoundWithFile in my games but this one have a limitation, there is no volume setting.
So, whit this extension you can solve this lack:
public extension SKAction {
public class func playSoundFileNamed(fileName: String, atVolume: Float, waitForCompletion: Bool) -> SKAction {
let nameOnly = (fileName as NSString).stringByDeletingPathExtension
let fileExt = (fileName as NSString).pathExtension
let soundPath = NSBundle.mainBundle().URLForResource(nameOnly, withExtension: fileExt)
var player: AVAudioPlayer! = AVAudioPlayer()
do { player = try AVAudioPlayer(contentsOfURL: soundPath!, fileTypeHint: nil) }
catch let error as NSError { print(error.description) }
player.volume = atVolume
let playAction: SKAction = SKAction.runBlock { () -> Void in
player.prepareToPlay()
player.play()
}
if(waitForCompletion){
let waitAction = SKAction.waitForDuration(player.duration)
let groupAction: SKAction = SKAction.group([playAction, waitAction])
return groupAction
}
return playAction
}
}
Usage:
self.runAction(SKAction.playSoundFileNamed("diamondCollect.wav", atVolume:0.5, waitForCompletion: true))