How to loop background music without lag/delay when song repeats (seamless) - swift

I am trying to make a game with background music looping in it. I made the song file in Adobe Audition (which is similar to audacity) and when I play it in a loop in Adobe Audition it loops how I want it.
When I play it in Xcode however, it has a lag in between the loops. I am using AVFoundations for the sound playing.
I have searched everywhere but I can't find the solution for the problem.
Is there any way you can loop audio files without there being any lags in between ? (I believe its called "seamless looping" )
here is the code:
class GameScene: SKScene {
...// Other Code
var ButtonAudio = URL(fileURLWithPath: Bundle.main.path(forResource: "Gamescene(new)", ofType: "mp3")!)
var ButtonAudioPlayer = AVAudioPlayer()
... //Other Code
}
And When I Call it:
override func didMove(to view: SKView) {
...//Code
ButtonAudioPlayer = try! AVAudioPlayer(contentsOf: ButtonAudio, fileTypeHint: nil)
ButtonAudioPlayer.numberOfLoops = -1
ButtonAudioPlayer.prepareToPlay()
ButtonAudioPlayer.play()
...//More Code
}
Can someone help me with this issue ?
Thank you in advance!

You can use AVPlayerLooper and AVQueuePlayer to do this.
import UIKit
import AVFoundation
class ViewController: UIViewController {
var queuePlayer = AVQueuePlayer()
var playerLooper: AVPlayerLooper?
override func viewDidLoad() {
super.viewDidLoad()
guard let url = Bundle.main.url(forResource: "Gamescene(new)", withExtension: "mp3") else { return }
let playerItem = AVPlayerItem(asset: AVAsset(url: url))
playerLooper = AVPlayerLooper(player: queuePlayer, templateItem: playerItem)
queuePlayer.play()
}
}

The solution proposed by #dave234 only works in iOS > 10. Since I needed to make seamless playback in iOS > 9, I did something different:
Instead of AVPlayer, I created AVQueuePlayer and immediately added two identical melodies to the queue.
Next, I made a listener to the penultimate melody.
When the listener was triggered, I added another similar record to the queue after the last one.
In fact, in order to avoid delay, I always play the penultimate record.
My code:
var player: AVQueuePlayer?
override func viewDidLoad() {
super.viewDidLoad()
if let path = Bundle.main.path(forResource: "music_file", ofType: "mp3") {
player = createPlayer(url: URL(fileURLWithPath: path))
}
}
func createPlayer(url: URL) -> AVQueuePlayer {
let player = AVQueuePlayer(items: [AVPlayerItem(url: url), AVPlayerItem(url: url)])
loopPlayer(playerItem: player.items()[player.items().count - 2])
return player
}
func loopPlayer(playerItem: AVPlayerItem) {
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: playerItem, queue: .main) { _ in
if let player = self.player, let url = (playerItem.asset as? AVURLAsset)?.url {
player.insert(AVPlayerItem(url: url), after: player.items()[player.items().count - 1])
self.loopPlayer(playerItem: player.items()[player.items().count - 2])
}
}
}

Related

Memory Leak with AVPlayer playing online audio streaming

I am making an app for playing podcasts.
the problem is when creating an AVPlayer with URL or AVPlayer item. a memory leak happens but deinit works well with no problem.
weak var presenter: AudioPlayerUpdaterProtocol?
private var audioSession: AVAudioSession?
private var audioPlayer: AVPlayer?
private var audioURL: URL?
private var playbackTimeUpdater: Timer?
deinit {
self.audioSession = nil
self.audioURL = nil
self.audioPlayer = nil
self.playbackTimeUpdater?.invalidate()
removeEndPlayingObserver()
print("deinit audio")
}
private func setupAudioPlayer(){
guard let url = audioURL else {
return
}
let playerItem = AVPlayerItem(url: URL)
// when comment the avplear init no memory leaks found.
audioPlayer = AVPlayer(playerItem: playerItem)
addEndPlayingObserver()
startPlaying()
}
when debugging with instruments I got this
enter image description here
I tried to debug this malloc 16 bytes on Xcode but I failed on this because the object is not at any memory graph.
This is because setting an AVPlayerItem on an AVPlayer creates a hidden strong reference between the two, so persisting the AVPlayerItem will hang onto the AVPlayer, causing a memory leak.
make share the AVPlayerItem and AVPlayer have a 1:1 relationship by creating a new AVPlayerItem when it needs to be assigned to an AVPlayer.
guard let url = audioURL else {
return
}
let originalAsset = AVAsset(url: url)
audioPlayer = AVPlayer(playerItem: AVPlayerItem(asset: originalAsset))
If the above approach doesn't help you try this on your viewDidDissapear:
func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
self.audioPlayer?.replaceCurrentItem(with: nil)
}

Swift 5 Loop MP4 In UIView With Full Screen Controls

I've spent a few days trying different combinations of code I've come across on StackOverflow to no avail.
Here's the code I'm currently using to display MP4 videos (looped, no sound) within a UIView component.
import Foundation
import AVFoundation
import AVKit
class VideoPlayerLooped {
public var videoPlayer:AVQueuePlayer?
public var videoPlayerLayer:AVPlayerLayer?
var playerLooper: NSObject?
var queuePlayer: AVQueuePlayer?
func playVideo(fileName:String, inView:UIView)
{
if let path = Bundle.main.path(forResource: fileName, ofType: "mp4")
{
let url = URL(fileURLWithPath: path)
let playerItem = AVPlayerItem(url: url as URL)
_ = try? AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback, mode: .default, options: .mixWithOthers)
videoPlayer = AVQueuePlayer(items: [playerItem])
videoPlayer?.isMuted = true
videoPlayer?.preventsDisplaySleepDuringVideoPlayback = false
playerLooper = AVPlayerLooper(player: videoPlayer!, templateItem: playerItem)
videoPlayerLayer = AVPlayerLayer(player: videoPlayer)
videoPlayerLayer!.frame = inView.bounds
inView.layer.addSublayer(videoPlayerLayer!)
videoPlayer?.play()
}
}
func remove()
{
videoPlayerLayer?.removeFromSuperlayer()
}
}
Although this solution works to play and loop MP4s, it doesn't provide the user with an option to click the video for a full screen version (landscape rotation) with controls.
Any recommendations or light that can be shed will be most helpful!
You could use an AVPlayerViewController. Apple has a tutorial on the subject in the Xcode help system. It's also available online. Check this link.

Audio not playing in SwiftUI

I'm having an issue getting an audio file to play when the function is called.
I am using an AVAudioPlayer to try and play the file following the instructions form here:
https://www.hackingwithswift.com/example-code/media/how-to-play-sounds-using-avaudioplayer
After the button is pressed in the view, it calls a func to play the sound, but from what I can tell, nothing is played. There are no errors thrown, and the file is found. The app also uses a speech synthesizer when a button is pushed, and that plays fine.
I looked around stack overflow and followed the instructions from here:
Where to place code for audio playback in a SwiftUI app
But still the audio is not played when the button is pushed.
Here is the func:
func playSound() {
var sound = AVAudioPlayer()
if let path = Bundle.main.path(forResource: "TryAgain", ofType: "wav") {
do {
sound = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path))
print("Playing sound")
sound.play()
} catch {
print( "Could not find file")
}
}
Here is the class:
class Player: BindableObject {
let willChange = PassthroughSubject<Player, Never>()
var isPlaying: Bool = false {
willSet {
willChange.send(self)
}
}
func playSound() {
var sound = AVAudioPlayer()
if let path = Bundle.main.path(forResource: "TryAgainWav", ofType: "wav") {
do {
sound = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path))
print("Playing sound")
sound.play()
} catch {
print( "Could not find file")
}
}
}
}
Update: Not sure if this helps, but I built this in with IB instead of SwiftUI and noticed the same message is printed in the console when I click the button to play the audio file:
2019-07-22 11:29:41.075568-0700 PlaySoundPrac[13952:46549155] [plugin] AddInstanceForFactory: No factory registered for id F8BB1C28-BAE8-11D6-9C31-00039315CD46
Any help would be greatly appreciated
I'm pretty new myself, but I'll do my best. What happens if you move the declaration of AVAudioPlayer outside the function? For example:
import Combine
class Player: ObservableObject {
var sound: AVAudioPlayer!
let willChange = PassthroughSubject<Player, Never>()
var isPlaying: Bool = false {
willSet {
willChange.send(self)
}
}
func playSound() {
if let path = Bundle.main.path(forResource: "TryAgainWav", ofType: "wav") {
do {
sound = try AVAudioPlayer(contentsOf: URL(fileURLWithPath: path))
print("Playing sound")
sound.play()
} catch {
print( "Could not find file")
}
}
}
}
Try this out and let me know if it works or not! Thanks.
I had a problem that audio was not played on iOS 13 Simulator in Xcode 11.5 while it was okay in Xcode SwiftUI preview and physical device.

Sound causing game to lag in swift sprite kit game?

New code
class SceneTwo: SKScene, SKPhysicsContactDelegate {
let flap = SKAction.playSoundFileNamed("flap.caf", waitForCompletion: false)
let whack = SKAction.playSoundFileNamed("whack.caf", waitForCompletion: false)
let tap = SKAction.playSoundFileNamed("tap.caf", waitForCompletion: false)
Then I simply have put
run(tap)
run(flap) etc
where necessary..
Hi just wondering if I am using the correct coding to play sounds in my game. For some context my game is similar to Flappy bird. One sound is played each time the screen is touched (when the bird has impulse upwards) the second sound is when the bird collects a coin in between each wall.
I have noticed that both of these sounds are causing my game to lag.
Below is my relative sound code for the game.
import AVFoundation
var flap: AVAudioPlayer?
var tap: AVAudioPlayer?
override func didMove(to view: SKView) {
tap?.prepareToPlay()
flap?.prepareToPlay()
func playFlap() {
let url = Bundle.main.url(forResource: "flap", withExtension: "caf")!
do {
flap = try AVAudioPlayer(contentsOf: url)
guard let flap = flap else { return }
flap.play()
} catch let error {
print(error.localizedDescription)
}
}
func playTap() {
let url = Bundle.main.url(forResource: "tap", withExtension: "caf")!
do {
tap = try AVAudioPlayer(contentsOf: url)
guard let tap = tap else { return }
tap.play()
} catch let error {
print(error.localizedDescription)
}
}
After this I have simply
playTap()
playFlap()
to where they are needed.
The sound is clear it just seems to make my spawning walls jump a little bit when the sound is made.
Is there something I am doing that is wrong?
You are getting lag because you are not preloading the sound files. You can preload them at App Launch, and then when you need just play them. For reference look into this stackoverflow's post
And if you still face the same issue then you can add sound in background queue, as demostrated here
let qualityOfServiceClass = QOS_CLASS_BACKGROUND
let backgroundQueue = dispatch_get_global_queue(qualityOfServiceClass, 0)
dispatch_async(backgroundQueue, {
audioPlayer.play()
})

AVQueuePlayer scrubber thumbnail always first video

I have implemented player AVPlayerViewController on tvOS using AVQueuePlayer. The playback of videos works as expected but on all videos following the first the scrubber / time slider shows thumbnails for the first video.
The player is being setup using the following code:
class TFLPlayerController: AVPlayerViewController, AVPlayerViewControllerDelegate {
var queuePlayer = AVQueuePlayer()
...
func addVideoToQueue(video: Video, after: AVPlayerItem?){
let mainVideo = AVPlayerItem(URL: NSURL(string: videoURL)!)
self.queuePlayer.insertItem(mainVideo, afterItem: nil)
}
func readyToPlay() {
self.addVideoToQueue(videoRecord, after: nil)
self.player = self.queuePlayer
if let currentItem = queuePlayer.currentItem {
// Set time to get make a call to the API for the next video
let callUpNextAPITime = CMTimeMakeWithSeconds(0.5, currentItem.asset.duration.timescale)
let callUpNextAPITimeValue = NSValue(CMTime: callUpNextAPITime)
let getUpNextObserver = queuePlayer.addBoundaryTimeObserverForTimes([callUpNextAPITimeValue], queue: dispatch_get_main_queue(), usingBlock: { () -> Void in
self.getUpNext()
})
}
self.player?.play()
}
func getUpNext(){
// Get the next video to play
let playlistId = presentingPlaylist?.identifier ?? nil
if let currentItem = self.queuePlayer.currentItem {
// Get the current items id
let currentVideoId = itemVideo[currentItem]?.identifier
if let currentVideoId = currentVideoId {
// Call API to get the next video to be played from the server
dataLayer.getNextVideo(currentVideoId, playlistIdentifier: playlistId) {
video, error in
...
if let nextVideo = video {
let currentVideo: AVPlayerItem = self.queuePlayer.currentItem!
self.addVideoToQueue(nextVideo, after: currentVideo)
}
}
}
}
}
I have also instantiated the AVQueuePlayer using the code below as I thought there might be an issue with the initial queue setup but the issue remain:
AVQueuePlayer(playerItem: mainVideo)