Buffering the next song using a second AVPlayer - swift

I want to play a list of songs but due to a custom queue system I don't want to use AVQueuePlayer, to have a seamless transition between songs (not having to wait for the next song to load/buffer) I want to create a second player which loads the next song in the queue and assign it to the main player, is it a legit solution?
Something like this:
var player = AVPlayer()
var nextSongPlayer = AVPlayer()
//...
func playSong() {
let playerItem = AVPlayerItem(url: "URL1")
player.replaceCurrentItem(with: playerItem)
//...
let player = AVPlayerItem(url: "URL2")
nextSongPlayer.replaceCurrentItem(with: player)
//...
player.play()
}
func nextSong() {
player = nextSongPlayer
player = play
}
Or I should create an AVPlayerItem with the second URL and replace the current plying item with that? or there is a better solution?

The code is almost wright, I had tp create a new AVPlayer and assign it to the nextSongPlayer:
//...
let playerItem = AVPlayerItem(url: "URL2")
let tempPlayer = AVPlayer()
tempPlayer.replaceCurrentItem(with: player)
tempPlayer.play() // Makes the player buffer the song
tempPlayer.pause()
self. nextSongPlayer = tempPlayer()
//...

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)
}

Prevent memory leak, high memory usage using video(AVPlayer)?

I made an app in which there is a View Controller which which has a view and some buttons in it. On the view I show a 2sec long video, which repeats continuously.
I used instruments to check my memory usage. It showed me that my app's memory usage continuously increases at the View Controller where the video is. After 30 sec my app gets up to 1gb memory usage.
In the code I made a weak variable which breaks the retain cycle, so when I go from the View Controller which shows the video to an another View Controller, then my memory usage drops.
BUT: My purpose is to get dropped the memory usage each time the video starts repeating, or is there something else what I should consider to do?
Thank you in advance!
backView is the view I am using for showing the video.
// Set up the video player.
var startVideo = true
private func playVideo(exercise : String, type : String) {
guard let path = Bundle.main.path(forResource: exercise, ofType: type) else {
debugPrint("video.mp4 not found")
return
}
weak var player = AVPlayer(url: URL(fileURLWithPath: path))
let playerController = AVPlayerViewController()
playerController.player = player
let playerLayerAV = AVPlayerLayer(player: player)
//now we set the size of frame to be like the view ("backview")
playerLayerAV.frame = backView.bounds
// the backview is the view i'm using it to insert a video
backView.layer.addSublayer(playerLayerAV)
player!.play()
if startVideo == true {
player!.play()
}else if startVideo == false {
player?.pause()
}
NotificationCenter.default.addObserver(forName: .AVPlayerItemDidPlayToEndTime, object: player!.currentItem, queue: .main) { _ in
player!.seek(to: kCMTimeZero)
player!.play()
}
}
Try the following to continually loop a video
let playerItem = AVPlayerItem(url: selectedItem)
let player = AVQueuePlayer(playerItem: playerItem)
let playerLooper = AVPlayerLooper(player: player, templateItem: playerItem)
let playerLayer = AVPlayerLayer(player: player)
player.play()

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

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])
}
}
}

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)

AVPlayer selectively playing MP3 audio file in documents directory

I'm writing a podcast app and I want the user to play the local file they've downloaded if it exists (rather than stream it). The streaming works fine, but if the local file is found, it's not played by AVPlayer (this is actually not true for one out of about 5 files for reasons beyond me -- tapping on one podcast will play locally (I'm in airplane mode to verify).
I believe this is the Swift equivalent of this question, but I haven't been able to implement that solution in Swift.
Here's the code:
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
var theURL = posts.objectAtIndex(indexPath.row).valueForKey("enclosure") as? String
var theMp3Filename = theURL!.lastPathComponent
for i in downloadedList {
if i as! String == theMp3Filename {
// If we've d/l'ed the mp3, insert the local path
theURL = documentsDirectory().stringByAppendingPathComponent(theMp3Filename)
var path: NSURL = NSURL.fileURLWithPath(theURL!)!
player = AVPlayer(URL: path)
player.play()
} else {
let podcastEpisode = AVPlayerItem(URL: NSURL(string: theURL!))
player = AVPlayer(playerItem: podcastEpisode)
player.play()
}
}
}
downloadedList is just an array I generate with the downloaded filenames. I am getting into the local condition, and the path looks right, but AVPlayer usually isn't working.
Any guidance would be appreciated.
Add this right after you set your path variable for playing downloaded audio:
let asset = AVURLAsset(URL: path)
let playerItem = AVPlayerItem(asset: asset)
player = AVPlayer(playerItem: playerItem)
player.play()