AVAudioPlayer Error in Swift? - ios

So I'm trying to play a random audio track from an array when a certain view loads. Everything's fine, but when I go to load that view, I get an error. Here is the class code of the View Controller:
class SoundGameViewController: UIViewController {
var levelOneSounds : [NSURL]!
var levelOneAudioPlayer: AVAudioPlayer! = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
var error : NSError?
levelOneSounds = NSBundle.mainBundle().URLsForResourcesWithExtension("mp3", subdirectory: "Level 1") as! [NSURL]
var randomSound = levelOneSounds[Int(arc4random_uniform(UInt32(levelOneSounds.count)))]
levelOneAudioPlayer = AVAudioPlayer(contentsOfURL: randomSound, error: &error)
if error != nil { println(error!) }
levelOneAudioPlayer.play()
}
The error shows up on the last line, levelOneAudioPlayer.play(). This is the error:
Thread 1: EXC_BAD_INSTRUCTION . . . (code = exc_1386_INVOP, subcode = 0 x 0
However, with one of the sounds, it actually ends up working and plays the sound when the view loads. But with all the other sounds, the view doesn't load at all and I get the error. Sometimes, the view does load but I get no sound and no error. Here is an image of the folders I hold all of the sounds in: http://i.stack.imgur.com/JBdGU.png
I have tried numerous times asking questions on Stack Overflow and research about it, but I just can't seem to get it. I have also looked for many tutorials on debugging, but I guess I'm not experienced enough to really understand what I need to do in this situation. I'm 13, and I don't have Apple Developer for much longer, and this is becoming really bothersome. I'll go as far as sending someone the project if they can fix this error.
I'm new to programming, so I'm sorry if I made an obvious mistake. Thanks!

Related

How to stop bookmark from crashing PDF app?

I have a PDF Reader app using PDFKit with iOS 13 Swift, which has a bookmark button that can bookmark a particular page. It works fine for me while I am still looking at my PDF, but if I bookmark at least 1 page in my PDF and go back to my file browser, and pick my PDF again, the app crashes.
Here is my code:
private func updateBookmarkStatus() {
if let documentURL = pdfDocument?.documentURL?.absoluteString,
let bookmarks = UserDefaults.standard.array(forKey: documentURL) as? [Int],
let currentPage = pdfView.currentPage,
let index = pdfDocument?.index(for: currentPage) {
bookmarkButton.image = bookmarks.contains(index) ? #imageLiteral(resourceName: "Bookmark-P") : #imageLiteral(resourceName: "Bookmark-N")
}
}
and it crashes with
Thread 1: Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
This update function is called upon loading each separate page of the PDF. However, when I exit and enter my PDF again, it crashes here because I think Swift can't find the array of bookmarks since at least one has been marked. My app only crashes if I mark at least 1 bookmark, then exit to my file browser, then open my PDF again.
Any help is greatly appreciated.

An AVPlayerItem can occupy only one position in a player's queue at a time?

I have seen many questions on this and none have had a solution. I am getting:
An AVPlayerItem can occupy only one position in a player's queue at a time
In the console after quickly taping a view button which should have one of two states:
Successfully preloaded video
only a videoURL which will then be loaded in another VC.
Why is this happening and how can I fix it?
This is my code which handels this in the transitioned to VC:
if numberMedia == 0 {
if selectedPost!.interimMedia[numberMedia].playerLayer != nil {
playCurrentMediaVid(currentMedia: selectedPost!.interimMedia[numberMedia])
} else {
let videoURL = selectedPost!.interimMedia[numberMedia].videoURL
if videoURL != nil {
//if we did not preload the video but have the cached vidURL
preloadVideo(media: selectedPost!.interimMedia[numberMedia])
playCurrentMediaVid(currentMedia: selectedPost!.interimMedia[numberMedia])
} else {
//if we dont have the cached vid url
selectedPost!.interimMedia[numberMedia].videoURL = getVideoURL(stringUrl: selectedPost!.interimMedia[numberMedia].videoURLString)
preloadVideo(media: selectedPost!.interimMedia[numberMedia])
playCurrentMediaVid(currentMedia: selectedPost!.interimMedia[numberMedia])
}
}
}
I looked into this problem and found the following problem:
If you print the status.rawValue of the playerQueue and currentItem (the AVPlayerItem) of the AVPlayerQueue (sub-class of AVPlayer) you'll find that the status will be either 0 or 1. For reference: 0 means status.unknown and 1 means status.readyToPlay (the one you want). So if both prints are 0 it fails, which is why you get the error. The position is at unknown, yet you are trying to .play() the playerQueue, which of course results in the error.
My recommendation to you (the fix) is to either find out a way to make the loading of the media inside the AVPlayerItem faster (which may be done putting the URL in a AVAsset instead of trying to immediately init a URL into a AVPlayer), or can use KVO to detect when it has finished loading (sort of like a completionBlock-esque solution).

Adding Sprite to scenes causing EXC_BAD_ACCESS

So I couldn't find a good answer that works for my problem. I am creating a SpriteKit game on iOS. Im just going to give you guys a quick overview of my setup and then go over the problem. I think that's the best way to explain this.
The Setup:
Basically in my didMove(to view:) method I call two helper functions: setupNodes() and setupLevel(). What these functions do is load the different scenes and SKSpriteNodes into memory and then add them to the scene. Here is the code I use to do this:
func setupNodes(){
doubleVent = (loadForegroundOverlayTemplate("DoubleVent").copy() as! SKSpriteNode)
}
func loadForegroundOverlayTemplate(_ fileName: String) -> SKSpriteNode{
let overlayScene = SKScene(fileNamed: fileName)!
let overlayTemplate = overlayScene.childNode(withName: "ForegroundOverlay")!.copy() as! SKSpriteNode
return overlayTemplate
}
This now stores the scene which is a child of an SKNode called "ForegroundOverlay". I then pass this to a function called "createForegroundOverlay()" like so:
let overlaySprite = doubleVent
createForegroundOverlay(doubleVent)
and then "createForegroundOverlay" adds the SKSpriteNode stored in doubleVent to the scene like so:
func createForegroundOverlay(_ overlayTemplate: SKSpriteNode) {
let foregroundOverlay = overlayTemplate.copy() as? SKSpriteNode
lastOverlayPosition.x = lastOverlayPosition.x + (lastOverlayWidth + ((foregroundOverlay?.size.width)!)/2)
lastOverlayWidth = (foregroundOverlay?.size.width)!/2.0
foregroundOverlay?.position = lastOverlayPosition
//TESTING PURPOSES
if TESTING_NODE_POS{
print("The last overlay position x:\(lastOverlayPosition.x), y:\(lastOverlayPosition.y)")
print("The last overlay width is \(lastOverlayWidth)")
print("Adding a foreground overlay \(count)")
count += 1
}
//TESTING PURPOSES
if TESTING_BOX_OUTLINE {
let foregroundPos = foregroundOverlay?.position
let boxLocation = foregroundOverlay?.childNode(withName: "Box1")?.position
if boxLocation != nil {
print("The location of the box \(String(describing: boxLocation))")
print("The foregroundLocation is \(String(describing: foregroundPos))")
print("last overlay position is \(lastOverlayPosition)")
}
}
//add the foreground overlay as a child of the foreground node
fgNode.addChild(foregroundOverlay!)
}
the variables for positioning - lastOverlayPosition, lastOverlayWidth etc.. - are just properties of my GameScene class used to know where and when to add the overlay that was passed.
The fgNode is a node that I stored from my GameScene.sks file like so:
let worldNode = self.childNode(withName: "World")!
bgNode = worldNode.childNode(withName: "Background")!
bgCityNode = worldNode.childNode(withName: "BackgroundCity")
cityBGOverlayTemplate = (bgCityNode.childNode(withName: "Overlay")!.copy() as! SKNode)
cityOverlayWidth = bgCityNode.calculateAccumulatedFrame().width
backgroundOverlayTemplate = (bgNode.childNode(withName: "Overlay")!.copy() as! SKNode)
backgroundOverlayWidth = backgroundOverlayTemplate.calculateAccumulatedFrame().width
fgNode = worldNode.childNode(withName: "Foreground")!
This was also done in my setupNodes() method.
The problem:
So maybe some of you guys have already seen the problem, but the problem is that when I launch my game it crashes and I get the message:
"Thread 1: EXC_BAD_ACCESS: (code = 1, address = 0x78)"
It is the exact same message every single time a crash occurs. I think I understand what the error is saying. Basically there is a pointer pointing to some location in memory (0x78) that has nothing there. So dereferencing that pointer obviously causes a fault. Here is where I get confused... This only happens about 50% of the time. when I run and build the project it builds successfully every time and then crashes 50% of the time with that error message. Secondly, this occurs at the very beginning. This is odd to me because how can some memory already be freed resulting in a bad pointer at the very beginning of the game. Also if the crash doesn't occur at launch then a crash never occurs except for when I reload the scene after the game over scene is displayed, which is basically the same as the game being relaunched.
With some time I narrowed the problem to one line of code:
fgNode.addChild(foregroundOverlay!)
if I comment this line out, no crash ever occurs (I tried building and running 50 times). The problem must be with the foregroundOverlay variable, which was setup using the code in the setup section of this discussion. So I have no idea how to fix this... Any Ideas??
P.S. it might also be worth noting that adding a child to the scene in this way only noticeably became a problem when I upgraded to xCode 10.0 and that I have considered using zombies, but I don't think that would help since the crash only happens at the launch of the game.
If I was unclear anywhere please let me know.

addPeriodicTimeObserver keeps AVPlayer instances

After being all over Stack Overflow and the deepest corners of the internet, I'm yet to find the solution. I hope someone out there will be able help me out with a problem I've had for days now.
The app in question has a collection view with lots of items. When you click on it you get to a preview collection view. And finally (where I need help) when you click "GO" you come to a collection view with cells that fills out the entire screen. It consists of an AVPlayerLayer and AVPlayer. Each time you scroll to right or left you see another video. The following code works:
UICollectionViewCell
class PageCell: UICollectionViewCell {
var player: AVPlayer?
var playerLayer: AVPlayerLayer?
var playerItem: AVPlayerItem?
var videoAsset: AVAsset?
var indexPath: IndexPath?
var page: Page? {
didSet {
guard let unwrappedPage = page, let unwrappedIndexPath = indexPath else { return }
addingVideo(videoID: unwrappedPage.steps[unwrappedIndexPath.item].video)
}
}
func setupPlayerView() {
player = AVPlayer()
playerLayer = AVPlayerLayer(player: player)
playerLayer?.videoGravity = .resizeAspectFill
videoView.layer.addSublayer(playerLayer!)
layoutIfNeeded()
playerLayer?.frame = videoView.bounds
}
func addingVideo(videoID: String) {
setupPlayerView()
guard let url = URL(string: videoID) else { return }
videoAsset = AVAsset(url: url)
activityIndicatorView.startAnimating()
videoAsset?.loadValuesAsynchronously(forKeys: ["duration"]) {
guard self.videoAsset?.statusOfValue(forKey: "duration", error: nil) == .loaded else { return }
self.player?.play()
self.playerItem = AVPlayerItem(asset: self.videoAsset!)
self.player?.replaceCurrentItem(with: self.playerItem)
}
}
In the UICollectionViewController I'm reusing cells. Since there is no way to know the number of AVPlayer instances, I simply made a helper variable inside the PageCell and it seems like three cells are getting reused (the normal amount when dequeuing and reusing cells). Now when I close this UICollectionViewController the AVPlayer instances seems to disappear/close.
Now, the problem arises when I want to loop the videos. Using AVPlayerLooper is not an option because it simply is too laggy (I've implemented it in a dusin different ways without luck). So my solution was to use a period time observer inside the videoAsset?.loadValuesAsynchronously block:
self.timeObserver = self.player?.addPeriodicTimeObserver(forInterval: CMTime(value: 1, timescale: 2),
queue: DispatchQueue.global(), using: { (progressTime) in
if let totalDuration = self.player?.currentItem?.duration{
if progressTime == totalDuration {
self.player?.seek(to: kCMTimeZero)
self.player?.play()
}
}
})
Problem
Video showcasing problem: Problem video
The problem arises when having +17 AVPlayer instances running simultaneously then suddenly the videos won't load anymore. iOS devices have a hardware limitation of everything from 4 to 18 AVPlayer instances running at the same time (most likely depending on RAM), see the following Stack Overflow posts just to mention a few:
AVPlayerItemStatusFailed error when too many AVPlayer instances created
How many AVPlayers are allowed to be created at the same time?
AVPlayer not able to play after playing some items
Loading issues with AVPlayerItem from local URL
See also these articles for more insight:
Building native video Pins
Too Many AVPlayers?
Because the problem only occurs when adding the time observer I suspect that it keeps the AVPlayer instances "alive" even though I've closed the collection view.
Notes to Problem video: Every time I press "GO" one AVPlayer instance is created. When I swipe to the right two more cells are created hence two more AVPlayer instances. All in all 3 AVPlayer instances are created each time accumulating in the end to about 17 instances and the videos will not load anymore. I've tried scrolling through all the videos each time I've pressed "GO" but this does not change the outcome with a maximum of three cells being reused all the time.
What I've tried to solve the problem
Try 1:
I made playerLayer a global variable and in viewDidDisappear inside the UICollectionViewController I added:
playerLayer?.player = nil
This resulted in one AVPlayer instance to disappear/close when I closed the cells onto the "GO" page (i.e. the view disappeared). This meant that I hit 17 instances a bit later than when I didn't add this code. Together with the code above I also tried adding playerLayer = nil, playerLayer?.player?.replaceCurrentItem(with: nil) and playerLayer?.removeFromSuperlayer() but the only thing that changed anything was playerLayer?.player = nil. It should be noted that if I do not scroll and simply open the first video and close, open the first video and close and so on, I could do it "forever" without any problems. So, in this case one instance was created and then closed/disappeared afterwards.
Video showcasing try 1: Try 1 video
Try 2:
I changed the addPeriodicTimeObserver block to:
self.timeObserver = playerLayer?.player?.addPeriodicTimeObserver(forInterval: CMTime(value: 1, timescale:
2), queue: DispatchQueue.global(), using: { (progressTime) in
if let totalDuration = playerLayer?.player?.currentItem?.duration{
if progressTime == totalDuration {
playerLayer?.player?.seek(to: kCMTimeZero)
playerLayer?.player?.play()
}
}
})
In this block I essentially changed all self.player? to playerLayer?.player?.
This made me able to open and close the view with the videos as much as I wanted - so the AVPlayer instances were somehow closing/disappearing. Though now the looping didn't work. The first video would loop initially. But then I swiped to the second cell and this video would not loop. Then I swiped back to the first cell and now this wouldn't loop either. If I added playerLayer?.player = nil like in "Try 1" no effect occurred with neither the open and close nor the loops.
Video showcasing try 2: Try 2 video
Try 3:
I made the timeObserver variable global and tried many many things. But when I tried to remove the observer(s) it always resulted in the following error:
'NSInvalidArgumentException', reason: 'An instance of AVPlayer cannot remove a time observer that was added by a different instance of AVPlayer.'
The error indicates that the time observers are clearly all over the place. This was clear when I added helper variables (counters) inside the addPeriodicTimeObserver block and found out that the time observers were quickly adding up. At one point with all the different combinations of the code presented in this post, I was able to remove the time observer at any time during the scrolling. But this resulted in only removing a single time observer out of many - resulting in the same situation as in "Try 1" where I was able to remove a single AVPlayer instance each time I closed the view.
General note:
To test whether or not it really was only three AVPlayer instances that were created each time I press "GO" and swiped I made a test where I scrolled through over 20 videos, but there was no problem loading them. Therefore I'm quite sure, as mentioned earlier, that a maximum of three AVPlayer instances are created in the view each time.
Conclusion
The problem is, as I see it, that when I add the time observers in order to loop the videos they accumulate and keep the AVPlayer instances "alive". Remember that I had no problem at all without the time observers. When applying playerLayer?.player = nil it seems as though, I was able to "save" an instance but then again it's so hard to tell when I don't know the amount of AVPlayer instances currently "active". Simply put, all I wanna do is to delete all AVPlayer instances the moment the view disappears and it will be happy days. If anyone got this far, I will be overly grateful if you are able to help me out.
There is a retain cycle. Use weak self in escaping closures. The retain cycle you created was:
cell -> Player -> observer closure -> cell
Try this:
self.timeObserver = self.player?.addPeriodicTimeObserver(forInterval: CMTime(value: 1, timescale: 2),
queue: DispatchQueue.global(), using: { [weak self] (progressTime) in
if let totalDuration = self?.player?.currentItem?.duration{
if progressTime == totalDuration {
self?.player?.seek(to: kCMTimeZero)
self?.player?.play()
}
}
})

Playing Sounds Automatically when going to a new view controller

I am trying to make a simple reading flash card ios app for esl students using swift.
How can I take what I know about using buttons to play sounds, and have the sounds play automatically when a new page/view is loaded up on the phone? It seems really simple, but I can't figure it out.
This is what I have for the button
var error: NSERROR?
audioPlayer = AVAudioPlayer(contentsOfURL: alertSound, error: &error)
audioPlayer.preparetoPlay()
audioPlayer.play()
Again this works for the button, but I figure it has something to do with the last 2 lines.
For example: After hitting start there is a picture of a Computer. How I have it now is when you click the computer, a voice says the word, and spells it.
I want it to be really intrinsic in case "Click here" doesn't resonate with the class (these are real esl students!) so I would like to have it automatically play.
Thanks!
Place your code inside the viewDidLoad() method of each view controller as such:
override func viewDidLoad() {
var error: NSERROR?
audioPlayer = AVAudioPlayer(contentsOfURL: alertSound, error: &error)
audioPlayer.preparetoPlay()
audioPlayer.play()
}

Resources