AVPlayerLooper not looping my local video - ios

I'm trying to get a video to play in my macOS project and I want it to continuously loop. I was able to get it to play once but I can't figure out how to get it to loop. I'm using the AVPlayerLooper but seem to be messing up somewhere. Here is my code:
import Cocoa
import AVKit
import AVFoundation
class ViewController: NSViewController {
#IBOutlet weak var videoPlayer: AVPlayerView!
override func viewDidLoad() {
//var loop: AVPlayerLooper?
super.viewDidLoad()
guard let path = Bundle.main.path(forResource: "Background", ofType:"mp4") else {
debugPrint("Not found")
return
}
let asset = AVAsset(url: URL(fileURLWithPath: path))
let item = AVPlayerItem(asset: asset)
let queuePlayer = AVQueuePlayer(playerItem: item)
let loop = AVPlayerLooper(player: queuePlayer, templateItem: item)
videoPlayer.player = queuePlayer
videoPlayer.player?.play()
}
}
I also have an AVPlayerView in my storyboard that I have connected to my viewcontroller.
It builds and runs fine but when I run it nothing shows up. I just get a black screen.
Any help is appreciated. Thanks!

AVPlayerLoop is sort of a "top level" object that manages both the player and the player item. So you have to keep it around, e.g. in a custom property of the view controller, or it will be released before it can really do anything
Since AVPlayerLoop manages the items in the AVQueuePlayer, initialize the queue player without passing in the player item

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

Strange behavior of AVPlayer

I noticed a very odd behavior when working with AVPlayer from AVFoundation framework. I'm trying to stream (play) some mp3 radio file from internet.
So I created a new project in Xcode and added the following code to the default ViewController:
import UIKit
import AVFoundation
class ViewController: UIViewController {
let player: AVPlayer = AVPlayer()
override func viewDidLoad() {
super.viewDidLoad()
let asset = AVAsset(url: URL(string: "https://rfcmedia.streamguys1.com/Newport.mp3")!);
let playerItem = AVPlayerItem(asset: asset);
player.replaceCurrentItem(with: playerItem)
player.play();
}
}
There's nothing particularly interesting going on there. I just create an AVPlayerItem instance with the radio station URL and give it to AVPlayer and then ask the player object to play the song and it works just as expected.
However if I remove the player object instantiation from the class block and put it in the viewDidLoad method the code simply doesn't work (doesn't play anything) yet it doesn't crash or spit out any kinds of errors or warning.
import UIKit
import AVFoundation
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
let player: AVPlayer = AVPlayer()
let asset = AVAsset(url: URL(string: "https://rfcmedia.streamguys1.com/Newport.mp3")!);
let playerItem = AVPlayerItem(asset: asset);
player.replaceCurrentItem(with: playerItem)
player.play();
}
}
I can't understand this behavior. Why is that happening? Is it limited to AVPlayer or I should watch out for cases like this?
As Andrea Mugnaini explained in the comments to my post, my second example is going to cause an immediate deallocation by the ARC (Automatic Reference Counting) as we don't keep a strong reference to the AVPlayer object.
This is a common pitfall of developers new to Apple's ecosystem.

Thread1:EXC_BAD_ACCESS(code=1, address = 0X48)

I'm new in Swift. I am trying to Run this code but got an error:
Thread 1: EXC_BAD_ACCESS (code=1, address=0x48) a.
Error appears here:
self.player.play()
Can anyone help in this issue?
import UIKit
import AVFoundation
class ViewController: UIViewController {
var player = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
do {
if let audioPath = Bundle.main.path(forResource: "music", ofType: "mp3") {
try player = AVAudioPlayer(contentsOf: URL(fileURLWithPath: audioPath))
}
} catch {
print("ERROR")
}
self.player.play()
}
What you doing with your code is:
Creating an instance of AVAudioPlayer without any data
Trying to create a new instance with some data from music.mp3 file
Playing it
If the audioPath is not correct, then the player is not correctly created: application will use the one without valid data, leading to a crash during playback.
Your code can be written making the player optional, which helps preventing the crash:
import UIKit
import AVFoundation
class ViewController: UIViewController {
var player: AVAudioPlayer?
override func viewDidLoad() {
super.viewDidLoad()
player = initializePlayer()
player?.play()
}
private func initializePlayer() -> AVAudioPlayer? {
guard let path = Bundle.main.path(forResource: "music", ofType: "mp3") else {
return nil
}
return try? AVAudioPlayer(contentsOf: URL(fileURLWithPath: path))
}
}
Some changes performed to the code:
player is now optional: there is no need to initialize it twice. Plus if the initialization goes well you can use it without problems, otherwise your app won't crash.
The play method is now in an optional chain
I've moved the initialization of the player in a separate private methods, to maintain the code readable. Feel free to change it, it is just a suggestion.
I've got it. The issue is that the file isn't being coping to my app bundle. To fix it:
Click your project
Click your target
Select Build Phases
Expand Copy Bundle Resources
Click '+' and select your file.
Thanks Denis Hennessy

Play RTSP on Mac OS X from IP Camera - Swift

Since I spend the whole day going through Apple's Documentation and search on google how to connect and play a feed/stream through my IP camera which I can access it through the IP(can access it through my browser), I was wondering if anyone can point me to the right direction?
All the relative searches are leading me to the iOS SDK.
I have already imported AVFoundation and UIKit on my project. And I already know that the Mac OS X SDK has an attribute called AVPlayerView.
What I am asking here is how to do the following, on an application written in Swift, for the Mac:
let moviePath = NSBundle.mainBundle().pathForResource("sample_iPod", ofType: "m4v")
if let path = moviePath {
let url = NSURL.fileURLWithPath(path)
let player = AVPlayer(URL: url)
let playerViewController = AVPlayerViewController()
playerViewController.player = player
self.presentViewController(playerViewController, animated: true) {
if let validPlayer = playerViewController.player {
validPlayer.play()
}
}
}
Obviously my path will be of type let fileUrl = NSURL(string: "hhtp://10.1.xx.xx")
I found out that AVPlayerViewController is not on the MacOS.
Also, I saw that lately the QTPlayer(or whatever is called) has moved into AVFoundation for the Mac
Thanks in advance..!
This is what I have so far
import Cocoa
import AVKit
import AVFoundation
class ViewController: NSViewController {
#IBOutlet var avPlayerView: AVPlayerView!
override func viewDidLoad() {
super.viewDidLoad()
let asset = AVAsset(URL: NSURL.fileURLWithPath("http://10.1.xx.xx"))
let item = AVPlayerItem(asset: asset)
let player = AVPlayer(playerItem: item)
avPlayerView.player = player
player.play()
}

'An AVPlayerItem cannot be associated with more than one instance of AVPlayer'

I know this question is all over Stack Overflow but still, most of them are old and not related with what I'm going to ask here.
So I've got an array with AVPlayerItems and an AVQueuePlayer. At first I set the AVQueuePlayer to play the array items:
func mediaPicker(mediaPicker: MPMediaPickerController!, didPickMediaItems mediaItemCollection: MPMediaItemCollection!) {
mediaItems = mediaItemCollection.items
for thisItem in mediaItems as [MPMediaItem] {
var itemUrl = thisItem.valueForProperty(MPMediaItemPropertyAssetURL) as? NSURL
var playerItem = AVPlayerItem(URL: itemUrl)
mediaArray.append(playerItem)
}
updateMetadata()
audioPlayer = AVQueuePlayer(items: mediaArray)
self.dismissViewControllerAnimated(true, completion: nil)
}
But, when the user, for example, adds a new Item or delete one, I try updating the player (the same way I set it in the first place), but it gives me the error. I want to know if there is any way around of it, or any solution. This is how the user deletes a song:
func tableView(tableView: UITableView, commitEditingStyle editingStyle: UITableViewCellEditingStyle, forRowAtIndexPath indexPath: NSIndexPath) {
if editingStyle == UITableViewCellEditingStyle.Delete{
mediaArray.removeAtIndex(indexPath.row)
tableView.deleteRowsAtIndexPaths([indexPath], withRowAnimation: UITableViewRowAnimation.Automatic)
audioPlayer = AVQueuePlayer(items: mediaArray) //here gives the error
}
}
I'm killing myself over this, so, if someone can help me, I would appreciate it.
The problem is that you're creating a new player object when you update the array of items with this line (in tableView:commitEditingStyle:forRowAtIndexPath:),
audioPlayer = AVQueuePlayer(items: mediaArray)
It seems that the player items are still associated with the original player when you do this. You should be able to delete that line, to make it work properly. Inserting or deleting items from mediaArray should be enough to do what you want; you don't need to create a new player.
The way I solved this issue is that I kept a reference to AVAsset objects in an array and then mapped it to [AVPlayerItem] (the type that AVQUeuePlayer init accepts).
To elaborate, we needed to make an audio recorder (code on github) that can be paused, so I used an AVQueuePlayer, but ran into the same issues as the OP, and the only clean solution is to recreate this player object with a new array.
The relevant code in Swift 4.1:
class RecordViewController: UIViewController {
var audioRecorder: AVAudioRecorder?
var audioChunks = [AVURLAsset]()
var queuePlayer: AVQueuePlayer?
// irrelevant code omitted
func stopRecorder() {
self.audioRecorder?.stop()
let assetURL = self.audioRecorder!.url
self.audioRecorder = nil
let asset = AVURLAsset(url: assetURL)
self.audioChunks.append(asset)
let assetKeys = ["playable"]
let playerItems = self.audioChunks.map {
AVPlayerItem(asset: $0, automaticallyLoadedAssetKeys: assetKeys)
}
self.queuePlayer = AVQueuePlayer(items: playerItems)
self.queuePlayer?.actionAtItemEnd = .advance
}
func startPlayer() {
self.queuePlayer?.play()
}
func stopPlayer() {
self.queuePlayer?.pause()
self.queuePlayer = nil
}
}
(In the end, AVAssetExportSession is used to stitch the chunks of audio back together, so storing AVAssets insead of AVPlayerItems was even more beneficial in the end.)
Using this code solved the problem for me:
player?.pause()
let playerItem = AVPlayerItem(asset: myAsset)
player = nil
player = AVPlayer()
player?.replaceCurrentItem(with: playerItem)
Instead of:
player?.pause()
player = nil
player = AVPlayer()
player?.replaceCurrentItem(with: availablePlayerItem)
//availablePlayerItem is a playerItem which has been created before and played once with this AVPlayer
Note:
In both codes, the "player" is a global variable in my class which is:
var player : AVPlayer? = nil
From Apple documentation:
Before add an item:
canInsertItem(_:afterItem:)
Returns a Boolean value that indicates
whether a given player item can be inserted into the player’s queue.
func canInsertItem(_ item: AVPlayerItem!, afterItem afterItem: AVPlayerItem!) -> Bool
Adding an Item:
insertItem(_:afterItem:)
Places given player item after a specified item in the queue.
func insertItem(_ item: AVPlayerItem!, afterItem afterItem: AVPlayerItem!)
Removing an Item:
removeItem(_:)
Removes a given player item from the queue.
func removeItem(_ item: AVPlayerItem!)
It is required to check that the player item can be inserted into the player’s queue. Then add, remove or replace the player item based on the conditions as below:
if player.canInsert(playerItem, after: nil) == true {
player = AVQueuePlayer(playerItem: playerItem)
} else {
player.remove(self.playerItem)
player.replaceCurrentItem(with: playerItem)
}
I initially initialized an AVPlayerItem inside a cell and added it to an AVPlayer (also inside the cell). I later presented a new vc and passed over that same cell's AVPlayerItem to a different AVPlayer inside the new vc and got a crash. To fix it I just made a copy of the playerItem using .copy() as? AVPlayerItem. A copy gives the new copy a different memory address from the original.
cell:
var cellPlayer: AVPlayer?
var cellsPlayerItem: AVPlayerItem?
cellsPlayerItem = AVPlayerItem(asset: AVAsset(url: url)) // cellsPlayerItem's memory address 0x1111111
cellPlayer = AVPlayer(playerItem: cellsPlayerItem!)
presented vc:
var vcPlayer: AVPlayer?
let playerItemCopy = cellsPlayerItem.copy() as? AVPlayerItem // playerItemCopy's memory address 0x2222222
let vcPlayer = AVPlayer(playerItem: playerItemCopy)

Resources