Become nil Referring to YTPlayer from a different view - ios

I want to control the video player of the video view from the screen transition destination.
I want to play a video by tapping the cell
Try to
// Select(Tap) to cell
override func tableView(_ table: UITableView,didSelectRowAt indexPath: IndexPath) {
let video = ViewController()
video.playerView.playVideo()
}
But fatal error: unexpectedly found nil while unwrapping an Optional value
Append:
I tried to use YTPlayer from https://github.com/youtube/youtube-ios-player-helper
But I did not understand how to write in your code "YTPlayer".
YTPlayer doesn't have YTPlayer(exist YTPlayerView) and YTPlayerLayer.
My viewcontoller's code here
import UIKit
import youtube_ios_player_helper
import AVFoundation
class ViewController: UIViewController, YTPlayerViewDelegate{
#IBOutlet var playerView: YTPlayerView!
var appDelegate:AppDelegate = UIApplication.shared.delegate as! AppDelegate
override func viewDidLoad() {
super.viewDidLoad()
self.playerView.delegate = self
self.playerView.sizeToFit()
let vars = ["playsinline": 1, "controls": 2, "showinfo": 0, "origin":"https://www.google.com"] as [String : Any]
if self.appDelegate.selectMV == ""
{
print("Not Load",self.appDelegate.selectMV)
}
else
{
print("Load",self.appDelegate.selectMV)
playerView.load(withVideoId: self.appDelegate.selectMV, playerVars: vars)
}
let audioSession = AVAudioSession.sharedInstance()
do {
try audioSession.setCategory(AVAudioSessionCategoryPlayback)
}
catch {
print("")
}
}
func playerViewDidBecomeReady(_ playerView: YTPlayerView) {
playerView.playVideo()
}
func play() {
print("Push! play")
//Error playerView nil
playerView.load(withVideoId: self.appDelegate.selectMV)
playerView.playVideo()
}
}

It's quit simple.. You need to code in didselect as you've done. But This kinds of code will never works here. You have to code like this in ViewDidLoad() or ViewWillAppear() First.
self.player = AVPlayer(url: NSURL(fileURLWithPath: filePath) as URL)
self.playerLayer = AVPlayerLayer(player: self.player)
self.playerLayer.frame = self.vview.bounds
Then In didSelect Only code for simply
Player.play()
If you want to change playerItem There simply code in didSelect Like this..
player.replaceCurrentItemWithPlayerItem(AVPlayerItem(URL: NSURL(fileURLWithPath: filePath)))
It's for the single View. Try like this and if you need any help feel free to ask me.

Related

How to turn off Background Music using a Toggle Switch in Xcode using Swift?

I am playing background music in my app from the App delegate once the app launches. Now, In my 2nd V.C. I have set up a toggle switch to turn om/off the background music. But, whenever I am running the follwing code, my app is crashing giving me this error:-
Fatal error: Unexpectedly found nil while implicitly unwrapping an Optional value
Could anyone please let me know how can I implement the following task in my V.C. Would appreciate your help! Thanks:)
**App Delegate**
let vc = SecondViewController()
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
// Override point for customization after application launch.
vc.playMusic()
return true
}
**Second View Controller**
import UIKit
import AVFoundation
class SecondViewController: UIViewController {
#IBOutlet weak var musicToggleSwitch: UISwitch!
var music: AVAudioPlayer!
let vc_1 = ViewController()
override func viewDidLoad() {
super.viewDidLoad()
self.musicToggleSwitch.setOn(UserDefaults.standard.bool(forKey: "musicToggleState"), animated: true)
}
#IBAction func musicToggleSwitch(_ sender: UISwitch) {
if (musicToggleSwitch.isOn == true) {
if (music.isPlaying == false) {
music.play()
}
}
else {
if (music.isPlaying == true) {
music.stop()
}
}
UserDefaults.standard.set(sender.isOn, forKey: "musicToggleState")
}
func playMusic() {
if let musicURL = Bundle.main.url(forResource: "Music", withExtension: "mp3") {
if let audioPlayer = try? AVAudioPlayer(contentsOf: musicURL) {
music = audioPlayer
music.numberOfLoops = -1
music.play()
}
}
}
}
This should solve your issues.
If it still doesn't play, add some breakpoints in the creation block of the music variable to see what goes wrong.
class SecondViewController: UIViewController {
#IBOutlet weak var musicToggleSwitch: UISwitch!
var music: AVAudioPlayer? = {
guard let musicURL = Bundle.main.url(forResource: "Music", withExtension: "mp3") else {
return nil
}
let audioPlayer = try? AVAudioPlayer(contentsOf: musicURL)
audioPlayer?.numberOfLoops = -1
return audioPlayer
}()
override func viewDidLoad() {
super.viewDidLoad()
self.musicToggleSwitch.setOn(UserDefaults.standard.bool(forKey: "musicToggleState"), animated: true)
}
#IBAction func musicToggleSwitch(_ sender: UISwitch) {
guard let music = music, sender.isOn != music.isPlaying else {
return
}
if sender.isOn {
music.play()
} else {
music.stop()
}
UserDefaults.standard.set(sender.isOn, forKey: "musicToggleState")
}
func playMusic() {
music?.play()
}
}
The problem is this line:
let vc = SecondViewController()
That is not the same SecondViewController that you see in your interface. So playMusic is called on the wrong view controller, and on the view controller you can see music remains nil and you crash.

audioPlayerDidFinishPlaying func issues

I am trying to make my app so that when a user touches a UIImageView A certain sound will play. However, while that sound is playing, I want the UIImageView.isUserInteractionEnabled to be assigned to false. Once the sound is done playing I want the UIImageView.isUserInteractionEnabled to be assigned to true. When ever I run the following code I get an error in the card class, even if I force unwrap. Below is the class that contains the image view I want to disable.
class SecondViewController: UIViewController , UIGestureRecognizerDelegate {
#IBOutlet weak var imgPhoto: UIImageView!
func imageTapped(tapGestureRecognizer: UITapGestureRecognizer)
{
imgPhoto.isUserInteractionEnabled = false
itemList[imageIndex].playSound()
}
}
This is the class where the playSound func is located.
import Foundation; import UIKit; import AVFoundation
class Card: NSObject
{
var player: AVAudioPlayer?
var svc = SecondViewController()
var image: UIImage
var soundUrl: String
init(image: UIImage, soundUrl: String, isActive:Bool = true) {
self.image = image
self.soundUrl = soundUrl
}
func playSound()
{
guard let url = Bundle.main.url(forResource: self.soundUrl, withExtension: "m4a") else { return }
do
{
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
player = try AVAudioPlayer(contentsOf: url)
player?.delegate = self
guard let player = player else { return }
player.prepareToPlay()
player.play()
audioPlayerDidFinishPlaying(player)
print("play")
} catch let error {
print(error.localizedDescription)
}
}
}
extension Card: AVAudioPlayerDelegate {
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer){
svc.imgPhoto.isUserInteractionEnabled = true
}
}
this is the error I get
The problem is this line:
var svc = SecondViewController()
That svc is not your SecondViewController, the existing one in the interface with an imgPhoto. Instead, you are creating a new, separate, and above all blank view controller with an empty view, so its imgPhoto is nil and you crash when you refer to it.
What you want to do is use the "delegate" pattern to hold a reference to the real SecondViewController so you can talk to it.

How to track when song is finished AVAudioPlayer

I want to track when playing song is finished. I tried different solutions from the web but they could not solve my problem.
I implemented audioPlayerDidFinishPlaying method but it is not working.
How can I understand if playing song is finished?
I am playing songs with playSound function
playSound func:
func playSound(name: String ) {
guard let url = Bundle.main.url(forResource: name, withExtension: "mp3") else {
print("url not found")
return
}
do {
/// this codes for making this app ready to takeover the device audio
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
/// change fileTypeHint according to the type of your audio file (you can omit this)
player = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileTypeMPEGLayer3)
// no need for prepareToPlay because prepareToPlay is happen automatically when calling play()
player!.play()
} catch let error as NSError {
print("error: \(error.localizedDescription)")
}
}
audioPlayerDidFinishPlaying func:
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
print("finished")//It is not working, not printing "finished"
}
How can I solve my problem? How to track when playing song is finished
EDIT: I am adding whole code.
//
// ViewController.swift
import UIKit
import SwiftVideoBackground
import AudioToolbox
import AVFoundation
class ViewController: UIViewController,AVAudioPlayerDelegate {
var player: AVAudioPlayer?
#IBOutlet weak var backgroundVideo: BackgroundVideo!
#IBOutlet weak var initialLabel: UILabel!
#IBOutlet weak var statementLabel: UILabel!
var mp3: [String] = ["turk_milleti_demokrattir","xyz"]
var fav: [String] = ["0","0"]
var name: [String] = ["Türk milleti demokrattır","xy"]
var toggleState = 1
#IBOutlet weak var playB: UIButton!
var counter = 0
var duration = 0.1
override func viewDidLoad() {
super.viewDidLoad()
player?.delegate = self
playB.setImage(UIImage(named: "playbtn.png"), for: .normal)
statementLabel.text = name[counter]
backgroundVideo.createBackgroundVideo(name: "abc", type: "mp4")
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func likeButton(_ sender: Any) {
fav[counter] = "1"
print(fav[0...1])
}
#IBAction func playButton(_ sender: Any) {
let name = mp3[counter]
playSound(name: name)
let playBtn = sender as! UIButton
if toggleState == 1 {
player?.play()
toggleState = 2
playBtn.setImage(UIImage(named: "pausebtn.png"), for: .normal)
} else {
player?.pause()
toggleState = 1
playBtn.setImage(UIImage(named:"playbtn.png"),for: .normal)
}
}
#IBAction func nextButton(_ sender: Any) {
counter = counter + 1
if counter == mp3.count {
counter = 0
}
toggleState = 2
playB.setImage(UIImage(named: "pausebtn.png"), for: .normal)
playSound(name: mp3[counter])
statementLabel.text = name[counter]
}
func playSound(name: String ) {
guard let url = Bundle.main.url(forResource: name, withExtension: "mp3") else {
print("url not found")
return
}
do {
/// this codes for making this app ready to takeover the device audio
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
/// change fileTypeHint according to the type of your audio file (you can omit this)
player = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileTypeMPEGLayer3)
// no need for prepareToPlay because prepareToPlay is happen automatically when calling play()
player!.play()
} catch let error as NSError {
print("error: \(error.localizedDescription)")
}
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
print("finished")//It is not working, not printing "finished"
}
}
I solved my problem with help of Leo Dabus.
I changed my edited code. I moved player?.delegate = self
to playSound func. Finally, it is working.
playSound & audioPlayerDidFinishPlaying function:
func playSound(name: String ) {
guard let url = Bundle.main.url(forResource: name, withExtension: "mp3") else {
print("url not found")
return
}
do {
/// this codes for making this app ready to takeover the device audio
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
try AVAudioSession.sharedInstance().setActive(true)
/// change fileTypeHint according to the type of your audio file (you can omit this)
player = try AVAudioPlayer(contentsOf: url, fileTypeHint: AVFileTypeMPEGLayer3)
player?.delegate = self
// no need for prepareToPlay because prepareToPlay is happen automatically when calling play()
player!.play()
} catch let error as NSError {
print("error: \(error.localizedDescription)")
}
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
print("finished")//It is working now! printed "finished"!
}
Do not forget to add AVAudioPlayerDelegate to ViewController!
class ViewController: UIViewController,AVAudioPlayerDelegate {
You are not setting the player's delegate correctly.
In viewDidLoad, your player is going to be nil, so this line:
player?.delegate = self
Will do nothing (The question mark is optional chaining, so if player == nil, it does nothing.)
You need to set the delegate after loading the player.

Swift : Player audio file from another class file

First of all, I just want to let you know I'm a newbie in iOS programming ;)
I would like to know if the following scenario is possible or which way I need to update my code to make it work.
I would like to play an audio file located in the player class file within the ViewController file.
I added the file "test.wav" to the root of project.
My problem
When I play the sound by tapping a button, the program can't find the sound file. I have the following error message:
fatal error: unexpectedly found nil while unwrapping an Optional value
(lldb)
Main swift file: ViewController.swift
import UIKit
import AVFoundation
class ViewController: UIViewController {
var player1: Player!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func playAudio(sender: AnyObject) {
player1.playAudioFile()
}
}
Class file: Player.swift
import Foundation
import AVFoundation
class Player {
// Variables
var vc: ViewController!
var audioFile: AVAudioPlayer!
// Initializer
init (){
}
// Methods
func playAudioFile() {
if audioFile.playing {
audioFile.stop()
} else {
audioFile.play()
}
}
// Intialize ViewController
init (vc:ViewController!) {
// Set path for the attack sound
let audioSnd = NSBundle.mainBundle().pathForResource("test", ofType: "wav")
let audioFileURL = NSURL(fileURLWithPath: audioSnd!)
do {
try audioFile = AVAudioPlayer(contentsOfURL: audioFileURL, fileTypeHint: nil)
audioFile.prepareToPlay()
} catch let err as NSError {
print(err.debugDescription)
}
}
}
More information
My program has a class "Player", with subclasses such as "Human" and "Monster".
By default, my class player has some audio files for attacks, dying, etc.
Under some conditions, the "player" can become a human or a monster and get custom attack and dying sounds.
Thanks a lot for your help! ;)
Why are you initializing you player with a viewController ? You dont need that.
Change the Player class to:
import Foundation
import AVFoundation
class Player {
// Variables
var audioFile: AVAudioPlayer!
// Initializer
init() {
// Set path for the attack sound
let audioSnd = NSBundle.mainBundle().pathForResource("test", ofType: "wav")
let audioFileURL = NSURL(fileURLWithPath: audioSnd!)
do {
try audioFile = AVAudioPlayer(contentsOfURL: audioFileURL, fileTypeHint: nil)
audioFile.prepareToPlay()
} catch let err as NSError {
print(err.debugDescription)
}
}
// Methods
func playAudioFile() {
if audioFile.playing {
audioFile.stop()
} else {
audioFile.play()
}
}
}
And your ViewController.swift to:
import UIKit
import AVFoundation
class ViewController: UIViewController {
var player1: Player!
override func viewDidLoad() {
super.viewDidLoad()
// Note, that you need to initialize the player1 variable
player1 = Player()
}
#IBAction func playAudio(sender: AnyObject) {
player1.playAudioFile()
}
}
Swift is functional programming, so it is way better to separate the audio operation and viewController into different file/class. Then they could be reused in any scenarios.
Audio part
import AVFoundation
class AudioPlayer {
// Singleton to keep audio playing anywhere
static let shared = AudioPlayer()
var player: AVAudioPlayer?
private init() {}
func play(url: URL) {
do {
try AVAudioSession.sharedInstance().setMode(.default)
try AVAudioSession.sharedInstance().setActive(true, options: .notifyOthersOnDeactivation)
player = try AVAudioPlayer(contentsOf: url)
guard let player = player else { return }
player.prepareToPlay()
player.play()
} catch {
print("error occurred")
}
}
}
ViewController
import UIKit
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func playAudio(sender: AnyObject) {
let audioSnd = NSBundle.mainBundle().pathForResource("test", ofType: "wav")
// don't use force unwrap
guard let audioFileURL = NSURL(fileURLWithPath: audioSnd) else { return }
// call the shared instance here
AudioPlayer.shared.play(url: url)
}
}

Impossible to stop AVPlayer

I am currently testing the use of AVPlayer with audio streaming url, using Swift. There are play() and pause() methods, but the problem is that, pausing only, the stream remains cached in the device.
Here is my test code :
import UIKit
import AVFoundation
class ViewController: UIViewController {
let player = AVPlayer(URL: NSURL(string: "http://streaming.radio.rtl.fr/rtl-1-48-192")!)
#IBOutlet weak var btnPlay: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func btnPress(sender: AnyObject) {
if (btnPlay.titleLabel?.text == "Play") {
initPlayer()
btnPlay.setTitle("Stop", forState: UIControlState.Normal)
} else {
stopPlayer()
btnPlay.setTitle("Play", forState: UIControlState.Normal)
}
}
func initPlayer() {
player.play()
}
func stopPlayer() {
// player.currentItem = nil // Last thing I tried, but generate an error
player.pause()
}
}
Here are the issues when trying somethings :
player = nil : "Cannot assign a value of type 'NilLiteralCOnvertible' to a value of type 'AVPlayer'"
player.currentItem = nil : "Cannot assign to property: 'currentItem' is a get-only property"
I tried everything, even through AVQueuePlayer without any effective result. (obviously, since I only have one item in my case).
How to stop AVPlayer or destroy his instance ?
From this post I found the best solution to completely stop AVPlayer before you leave or start a new player:
videoPlayer.replaceCurrentItemWithPlayerItem(nil)
[Update] For SWIFT 3:
player.replaceCurrentItem(with: nil)
If you declare player as an optional variable, you can then set the player to nil to deallocate it.
Silly example but it shows what happens:
import UIKit
import AVFoundation
class ViewController: UIViewController {
#IBOutlet weak var btnPlay: UIButton!
var player:AVPlayer?
override func viewDidLoad() {
super.viewDidLoad()
}
#IBAction func btnPress(sender: AnyObject) {
if (btnPlay.titleLabel?.text == "Play") {
initPlayer()
btnPlay.setTitle("Stop", forState: UIControlState.Normal)
} else {
stopPlayer()
btnPlay.setTitle("Play", forState: UIControlState.Normal)
}
}
func initPlayer() {
if let play = player {
print("playing")
play.play()
} else {
print("player allocated")
player = AVPlayer(URL: NSURL(string: "http://streaming.radio.rtl.fr/rtl-1-48-192")!)
print("playing")
player!.play()
}
}
func stopPlayer() {
if let play = player {
print("stopped")
play.pause()
player = nil
print("player deallocated")
} else {
print("player was already deallocated")
}
}
}
SWIFT 3 Version:
player.replaceCurrentItem(with: nil)

Resources