In SpriteKit (for those unfamiliar with it) there's a way to load and unload scenes, and a transition (visual) between them.
I'm trying to make a sound play between scenes, as they transition... that doesn't stutter.
So far, all the ways I've tried either create no sound, or the sound stutters, even using a sound manager, like this:
import AVFoundation
import SpriteKit
open class SoundManager {
static let soundStart = SKAction.playSoundFileNamed("ActionBeep_AP1.7", waitForCompletion: true)
static var coinSound = NSURL(fileURLWithPath:Bundle.main.path(forResource: "ActionBeep_AP1.7", ofType: "wav")!)
static var audioPlayer = AVAudioPlayer()
open static func playCoinSound(){
guard let soundToPlay = try? AVAudioPlayer(contentsOf: coinSound as URL) else {
fatalError("Failed to initialize the audio player with asset: \(coinSound)")
}
soundToPlay.prepareToPlay()
self.audioPlayer = soundToPlay
self.audioPlayer.play()
}
}
Anyone had any success making scene transition sounds run smoothly? I realise there's a lot going on during a scene transition, and that's probably not helping the sound engine. But think there must be a way to make a clear sound play during a scene transition.
And yes, I've tried with .caf and .mp3 and .wav files, all with different "compression" and raw states. I think it's how I play the sounds that's the problem, not the file type.
As crashoverride777 said, you need to change when you load the sound manager. You could set up a thread in your didMoveToView() function to minimize loading time:
DispatchQueue.global(qos: .userInitiated).async {
// Load your sound stuff
}
Now, you have your sound file loaded for whenever you need it, lag-free.
Related
This code using AVPlayer works only on Playground
import AVFoundation
var player = AVPlayer()
let playerItem = AVPlayerItem(url: URL(string: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3")!)
player = AVPlayer(playerItem: playerItem)
player.play()
When I tried to run it on my SwiftUI App on my physical device, using this code:
Button(action:{
var player = AVPlayer()
let playerItem = AVPlayerItem(url: URL(string: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3")!)
player = AVPlayer(playerItem: playerItem)
player.play()
print("Works")
},label:{
Image("play")
})
It prints Works to the console. However, it does not play any sound on the device.
Would appreciate any help, can't find anything yet here.
Thank you so much!
In SwiftUI, Views are value types. They are only data that describe the things on screen. They can be created or destroyed or copied at any time. AVPlayer is a reference to a specific player object. You're assuming here that it will continue to exist, and there will only be one of them. That's not something that a SwiftUI View provides.
You need to move your AVPlayer outside of the View (into Model objects), and just bind UI actions to it.
I'm using the following code to play a sound file twice. The second sound plays immediately following the first
Is it possible to leave a time gap (1 second) between them? I've tried to find a solution reading the Apple Docs
var letterSound: AVAudioPlayer!
...
#IBAction func speakLetter(sender: AnyObject) {
let soundFile: String = "SoundFile" + String(letterNumber)
let path = Bundle.main.path(forResource: soundFile, ofType:"mp3")!
let url = URL(fileURLWithPath: path)
do {
let sound = try AVAudioPlayer(contentsOf: url)
letterSound = sound
letterSound.numberOfLoops = 1
letterSound.play()
} catch {
// couldn't load file :(
}
}
One option would be to add some 'white space' or silence at the end of your audio file.
If you don't have an audio editor i'd recommend Audacity, it's free and there are lots of tutorials on how to add silence.
If you want a more code based option you could look in to using the AVAudioPlayerDelegate didFinishPlaying method to detect the end of the first play and then use play(atTime:) to trigger the next loop 1 second from now.
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
player.play(atTime: player.deviceCurrentTime + 1.0)
}
This code is untested and I think it might create an infinite loop so you may need some additional logic here to make sure it only loops once
I have this code as part of a tvOS app
import Foundation
import UIKit
import AVFoundation
import AVKit
class videoPlayer: UIViewController {
var thePlayer: AVPlayer?
var movieToPlay: AVPlayerItem?
let movieURL="http://trailers.apple.com/movies/independent/walkingwiththeenemy/walkingwiththeenemy-tlr1_720p.mov"
override func viewDidLoad(){
let theVideoPlayer = AVPlayerViewController()
theVideoPlayer.player = thePlayer
self.addChildViewController(theVideoPlayer)
self.view.addSubview(theVideoPlayer.view)
theVideoPlayer.view.frame = self.view.frame
let movieToPlay = AVPlayerItem(URL: NSURL(string: movieURL)!)
thePlayer = AVPlayer(playerItem: movieToPlay)
self.thePlayer!.play()
}
}
When the view is loaded the code plays audio but no video.
When one swipes the Apple TV remote a timeline appears with 0:00 duration at the right and 0:00 at the left as the audio plays. There is also a red no-smoking-like sign that appears at the left end of the timeline. My understanding is that the sign means that the video is in the wrong format, but the audio plays (and the clip is from Apple). The video plays plays in other AppleTV apps.
I think the code is correct (it works in another app) and I can't figure out next steps to debug. Can anyone see an error?
The code above worked just as expected when I placed it in a separate viewController and then presented it from the viewController where is had resided. I don't understand why, exactly, but it solved the problem video with audio issue.
I'm trying to set a background jingle for my game which is supposed to start at the Home scene and then loop continuously even while on the other scenes.
This is what my class looks like:
class AudioPlayer: AVAudioPlayer {
var audioPlayer: AVAudioPlayer? = AVAudioPlayer()
let ost = NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("bkg", ofType: "mp3")!)
func playOst() {
do {
audioPlayer = try AVAudioPlayer(contentsOfURL: ost)
}
catch {
audioPlayer = nil
}
audioPlayer?.prepareToPlay()
audioPlayer!.play()
audioPlayer!.numberOfLoops = -1
}
func stopOst() {
audioPlayer!.stop()
}
since I put the play method in the home scene, it will start playing a new track every time I load it, overlaying one track on another making it result in a big mess. How can I prevent it?
Make your audio player class a singleton, or let AppDelegate look after it. This will also allow you to control the music from any scene/screen in your app at any time.
Whenever your home screen loads check to see if music is already playing, if not, call playOst, otherwise do nothing.
I am trying to add sound effects to a game app I am making in Xcode 6. Here is the code that I am using to play the sounds:
var jumpSoundPlayer:AVAudioPlayer = AVAudioPlayer()
var jumpAudioPath:String = ""
var jumpSoundError: NSError? = nil
func setUpSoundPlayers(){
jumpAudioPath = NSBundle.mainBundle().pathForResource("Jump", ofType: "mp3")!
jumpSoundError = nil
jumpSoundPlayer = AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: jumpAudioPath), fileTypeHint: "mp3", error: &jumpSoundError)
}
func playJumpSound(volume:Float = 1){
jumpSoundPlayer.volume = volume
jumpSoundPlayer.play()
}
I can get sounds to play, but it seems as though it will not play a sound until the one currently playing is finished. How can I get it so that multiple sounds can play at once and overlap each other?
Also, every time a sound plays it drops the frame rate down a little bit. Is there a way to fix that?
I believe you have to use
jumpSoundPlayer.prepareToPlay()
before using
jumpSoundPlayer.play()
Also the only solution to drop the frame rate a little less, is to set up all the AVAudioPlayers before using them or at the beginning of the game.