Swift: AudioPlayerDidFinished will not be called - swift

My AudioPlayerDidFinishPlaying will not be called after audio has finished. I know it has something to do with my delegate, but I can't fix it on my own.
Can somebody give me some tips? I Googled a lot and I found other questions here with the same issue but it didn't work for me.
Thanks
import UIKit
import Parse
import AVFoundation
class ViewControllerMies: UIViewController, AVAudioPlayerDelegate {
var timer = NSTimer()
var player: AVAudioPlayer = AVAudioPlayer()
var currentStateAudio = ""
var oldAudio = String()
func startTimer() {
if player.playing == false {
print("Tijd voor de Spice Girls")
}
let query = PFQuery(className:"CurrentState")
query.getObjectInBackgroundWithId("9r61TRaRqu") {
(objects: PFObject?, error: NSError?) -> Void in
if error == nil && objects != nil {
self.currentStateAudio = objects!.objectForKey("currentState") as! String
print(self.currentStateAudio)
} else {
print(error)
}
}
if (oldAudio != self.currentStateAudio)
{
let audioPath = NSBundle.mainBundle().pathForResource(self.currentStateAudio, ofType: "mp3")!
do {
try player = AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: audioPath))
} catch {
// Process error here
}
player.play()
oldAudio = self.currentStateAudio
}
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
timer = NSTimer.scheduledTimerWithTimeInterval(1, target: self, selector: Selector("startTimer"), userInfo: nil, repeats: true)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func audioPlayerDidFinishPlaying(player: AVAudioPlayer, successfully flag: Bool)
{
print("Finished Playing")
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
// Get the new view controller using segue.destinationViewController.
// Pass the selected object to the new view controller.
}
*/
}

you're not setting yourself as the player's delegate
before calling play() do player.delegate = self

if you are using private delegate then its will not call too use this for resolving this issue
//MARK:- This is correct delegate working fine (use this)
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
audioRecorder = nil
}
//MARK:- Insted of this (this will not call)
private func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
audioRecorder = nil
}

Related

How to made a UIViewController class reusable to pass data back to a viewController that calls it

I was using the code from the following site
https://www.hackingwithswift.com/example-code/media/how-to-scan-a-qr-code
The code works perfectly, the code can be viewed by accessing the link above.
It was a code that capture a QRCode/BarCode from camera and convert it to string.
The part of the the code that shows the string is:
func found(code: String) {
print(code)
}
After that the code string is "printed", the code calls "dismiss" and return to the previous UIViewController.
I want to get the "code" string and get the data to the previous UIViewController.
The only way that I am able to do that now is using the following code:
func found(code: String) {
print("code: \(code)")
ResenhaEquideoIdentificaAnimal1Controller.shared.microchipAnimalTextField.text = code
}
But this code only works if the code is called by the "ResenhaEquideoIdentificaAnimal1Controller" class.
I use the following code to call the new UIViewController inside the "ResenhaEquideoIdentificaAnimal1Controller" class using a UIButton.
let myScannerViewController = MyScannerViewController()
present(myScannerViewController, animated: true, completion: nil)
How can I made this class reusable to be able to call the "MyScannerViewController" class
and send data back to the view that calls it?
You want to use a "delegate patten", that is, when the code is found or something went wrong, you delegate the functionality to some other party to deal with it.
For example, you could modify the existing example to add support for a simple delegate...
import AVFoundation
import UIKit
protocol ScannerDelegate: AnyObject {
func scanner(_ controller: ScannerViewController, didDiscoverCode code: String)
func failedToScanner(_ controller: ScannerViewController)
}
class ScannerViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate {
var captureSession: AVCaptureSession!
var previewLayer: AVCaptureVideoPreviewLayer!
weak var scannerDelegate: ScannerDelegate?
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = UIColor.black
captureSession = AVCaptureSession()
guard let videoCaptureDevice = AVCaptureDevice.default(for: .video) else { return }
let videoInput: AVCaptureDeviceInput
do {
videoInput = try AVCaptureDeviceInput(device: videoCaptureDevice)
} catch {
return
}
if (captureSession.canAddInput(videoInput)) {
captureSession.addInput(videoInput)
} else {
failed()
return
}
let metadataOutput = AVCaptureMetadataOutput()
if (captureSession.canAddOutput(metadataOutput)) {
captureSession.addOutput(metadataOutput)
metadataOutput.setMetadataObjectsDelegate(self, queue: DispatchQueue.main)
metadataOutput.metadataObjectTypes = [.qr]
} else {
failed()
return
}
previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer.frame = view.layer.bounds
previewLayer.videoGravity = .resizeAspectFill
view.layer.addSublayer(previewLayer)
captureSession.startRunning()
}
override func viewWillAppear(_ animated: Bool) {
super.viewWillAppear(animated)
if (captureSession?.isRunning == false) {
captureSession.startRunning()
}
}
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
if (captureSession?.isRunning == true) {
captureSession.stopRunning()
}
}
private func failed() {
captureSession = nil
scannerDelegate?.failedToScanner(self)
}
private func didFind(code: String) {
scannerDelegate?.scanner(self, didDiscoverCode: code)
}
override var prefersStatusBarHidden: Bool {
return true
}
override var supportedInterfaceOrientations: UIInterfaceOrientationMask {
return .portrait
}
// MARK: AVCaptureMetadataOutputObjectsDelegate
func metadataOutput(_ output: AVCaptureMetadataOutput, didOutput metadataObjects: [AVMetadataObject], from connection: AVCaptureConnection) {
captureSession.stopRunning()
if let metadataObject = metadataObjects.first {
guard let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject else { return }
guard let stringValue = readableObject.stringValue else { return }
AudioServicesPlaySystemSound(SystemSoundID(kSystemSoundID_Vibrate))
didFind(code: stringValue)
}
}
}
When you want to scan something, your calling view controller could adopt the protocol...
extension ViewController: ScannerDelegate {
func failedToScanner(_ controller: ScannerViewController) {
controller.dismiss(animated: true) {
let ac = UIAlertController(title: "Scanning not supported", message: "Your device does not support scanning a code from an item. Please use a device with a camera.", preferredStyle: .alert)
ac.addAction(UIAlertAction(title: "OK", style: .default))
self.present(ac, animated: true)
}
}
func scanner(_ controller: ScannerViewController, didDiscoverCode code: String) {
codeLabel.text = code
controller.dismiss(animated: true)
}
}
and when you wanted to present the scanner view controller, you would simply set the view controller as the delegate...
let controller = ScannerViewController()
controller.scannerDelegate = self
present(controller, animated: true)
The great thing about this is, you could easily reject the code if you weren't interested in simply by modifying the delegate workflow

How do I pass a scanned barcode ID from first view controller to second View Controller's UILabel?

This is the barcode scanning tutorial I used in my program, so that you have a lot more context when you read my code: Link
Here is what my program does so far: Essentially, when I scan an item's barcode with my phone, the UIAlert pops up with the barcode ID displayed and a button prompting the user to open the "Results" page. This is all fine and good, but how do I pass that same scanned barcode ID into a label on the Result's page? I have been stuck on this for 2 days now, even though it seems like such an easy task.
Any help is much appreciated <3
Here is my relevant code:
ProductCatalog.plist ->
Link to Image
Scanner_ViewController.swift (first View Controller) ->
import UIKit
import AVFoundation
class Scanner_ViewController: UIViewController, AVCaptureMetadataOutputObjectsDelegate, ScannerDelegate
{
private var scanner: Scanner?
override func viewDidLoad()
{
super.viewDidLoad()
self.scanner = Scanner(withDelegate: self)
guard let scanner = self.scanner else
{
return
}
scanner.requestCaptureSessionStartRunning()
}
override func didReceiveMemoryWarning()
{
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// Mark - AVFoundation delegate methods
public func metadataOutput(_ output: AVCaptureMetadataOutput,
didOutput metadataObjects: [AVMetadataObject],
from connection: AVCaptureConnection)
{
guard let scanner = self.scanner else
{
return
}
scanner.metadataOutput(output,
didOutput: metadataObjects,
from: connection)
}
// Mark - Scanner delegate methods
func cameraView() -> UIView
{
return self.view
}
func delegateViewController() -> UIViewController
{
return self
}
func scanCompleted(withCode code: String)
{
print(code)
showAlert_Success(withTitle: (code))
}
private func showAlert_Success(withTitle title: String)
{
let alertController = UIAlertController(title: title, message: "Product has been successfully scanned", preferredStyle: .alert)
// programatically segue to the next view controller when the UIAlert pops up
alertController.addAction(UIAlertAction(title:"Get Results", style: .default, handler:{ action in self.performSegue(withIdentifier: "toAnalysisPage", sender: self) }))
present(alertController, animated: true)
}
}
Scanner.Swift (accompanies Scanner_ViewController.swift)->
import Foundation
import UIKit
import AVFoundation
protocol ScannerDelegate: class
{
func cameraView() -> UIView
func delegateViewController() -> UIViewController
func scanCompleted(withCode code: String)
}
class Scanner: NSObject
{
public weak var delegate: ScannerDelegate?
private var captureSession : AVCaptureSession?
init(withDelegate delegate: ScannerDelegate)
{
self.delegate = delegate
super.init()
self.scannerSetup()
}
private func scannerSetup()
{
guard let captureSession = self.createCaptureSession()
else
{
return
}
self.captureSession = captureSession
guard let delegate = self.delegate
else
{
return
}
let cameraView = delegate.cameraView()
let previewLayer = self.createPreviewLayer(withCaptureSession: captureSession,
view: cameraView)
cameraView.layer.addSublayer(previewLayer)
}
private func createCaptureSession() -> AVCaptureSession?
{
do
{
let captureSession = AVCaptureSession()
guard let captureDevice = AVCaptureDevice.default(for: .video) else
{
return nil
}
let deviceInput = try AVCaptureDeviceInput(device: captureDevice)
let metaDataOutput = AVCaptureMetadataOutput()
// add device input
if captureSession.canAddInput(deviceInput) && captureSession.canAddOutput(metaDataOutput)
{
captureSession.addInput(deviceInput)
captureSession.addOutput(metaDataOutput)
guard let delegate = self.delegate,
let viewController = delegate.delegateViewController() as? AVCaptureMetadataOutputObjectsDelegate else
{
return nil
}
metaDataOutput.setMetadataObjectsDelegate(viewController,
queue: DispatchQueue.main)
metaDataOutput.metadataObjectTypes = self.metaObjectTypes()
return captureSession
}
}
catch
{
// handle error
}
return nil
}
private func createPreviewLayer(withCaptureSession captureSession: AVCaptureSession,
view: UIView) -> AVCaptureVideoPreviewLayer
{
let previewLayer = AVCaptureVideoPreviewLayer(session: captureSession)
previewLayer.frame = view.layer.bounds
previewLayer.videoGravity = .resizeAspectFill
return previewLayer
}
private func metaObjectTypes() -> [AVMetadataObject.ObjectType]
{
return [.qr,
.code128,
.code39,
.code39Mod43,
.code93,
.ean13,
.ean8,
.interleaved2of5,
.itf14,
.pdf417,
.upce
]
}
public func metadataOutput(_ output: AVCaptureMetadataOutput,
didOutput metadataObjects: [AVMetadataObject],
from connection: AVCaptureConnection)
{
self.requestCaptureSessionStopRunning()
guard let metadataObject = metadataObjects.first,
let readableObject = metadataObject as? AVMetadataMachineReadableCodeObject,
let scannedValue = readableObject.stringValue,
let delegate = self.delegate
else
{
return
}
delegate.scanCompleted(withCode: scannedValue)
}
public func requestCaptureSessionStartRunning()
{
self.toggleCaptureSessionRunningState()
}
public func requestCaptureSessionStopRunning()
{
self.toggleCaptureSessionRunningState()
}
private func toggleCaptureSessionRunningState()
{
guard let captureSession = self.captureSession
else
{
return
}
if !captureSession.isRunning
{
captureSession.startRunning()
}
else
{
captureSession.stopRunning()
}
}
}
Analysis_ViewController.swift (second view controller) ->
Right now, the forKey: has been hard-coded to item ID 8710908501708 because I have no idea how to actually pass camera-scanned ID's into the second View Controller :/
import UIKit
class Analysis_ViewController: UIViewController
{
#IBOutlet weak var productTitle: UILabel!
func getData()
{
let path = Bundle.main.path(forResource:"ProductCatalog", ofType: "plist")
let dict:NSDictionary = NSDictionary(contentsOfFile: path!)!
if (dict.object(forKey: "8710908501708" as Any) != nil)
{
if let levelDict:[String : Any] = dict.object(forKey: "8710908501708" as Any) as? [String : Any]
{
// use a for loop to iterate through all the keys and values in side the "Levels" dictionary
for (key, value) in levelDict
{
// if we find a key named whatever we care about, we can print out the value
if (key == "name")
{
productTitle.text = (value as! String)
}
}
}
}
}
// listing the better options that are safer in comparison to the scanned product image
override func viewDidLoad()
{
super.viewDidLoad()
getData()
}
}
Do you have a variable to hold the scanned ID in your view controllers? If not, you can add var itemID: String? to both Scanner_ViewController and Analysis_ViewController.
Then in your func where you get the scanned code, you can set it to the variable.
func scanCompleted(withCode code: String) {
print(code)
itemID = code // Saves the scanned code to your var
showAlert_Success(withTitle: (code))
}
For passing data to another view controller via segue, you might want to look into this UIViewController method for segues: documentation here. This answer also might help.
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toAnalysisPage" {
if let viewController = segue.destination as? Analysis_ViewController {
viewController.itemID = itemID
}
}
}

Live stream using AVPlayer not playing in iOS 11

I am trying to stream a music from remote url. I am trying to run this in iOS 11 but it not play the music.
ViewController
var session = AVAudioSession.sharedInstance()
var LQPlayer: AVPlayer?
let LOW_URL = URL(string: "http://someLInk.pls")! // not an original url provided at this time.
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
self.avPlayerSetup()
}
func avPlayerSetup() {
do {
try session.setCategory(AVAudioSessionCategoryPlayback)
try session.overrideOutputAudioPort(.none)
try session.setActive(true)
} catch {
print("AVPlayer setup error \(error.localizedDescription)")
}
}
func initPlayer() {
LQPlayer = AVPlayer(url: LOW_URL)
print("player allocated")
}
func deAllocPlayer() {
LQPlayer = nil
print("player deallocated")
}
#IBAction func playBtn(_ sender: Any) {
initPlayer()
LQPlayer?.play()
}
#IBAction func pauseBtn(_ sender: Any) {
LQPlayer?.pause()
deAllocPlayer()
}
}
I set Allow Arbitrary Loads YES in info.plist.
Above code the URL I given is dummy. Actual url is working fine.
Working Code with Live Video Stream
#IBOutlet weak var player_View: UIView!
var LQPlayer: AVPlayer?
let LOW_URL = URL(string:"http://www.streambox.fr/playlists/test_001/stream.m3u8")!
override func viewDidLoad() {
super.viewDidLoad()
self.avPlayerSetup()
LQPlayer = AVPlayer.init(url: LOW_URL)
let avPlayerView = AVPlayerViewController()
avPlayerView.view.frame = self.player_View.bounds
avPlayerView.player = LQPlayer
self.player_View.addSubview(avPlayerView.view)
}
func avPlayerSetup() {
do {
try audioSession.setCategory(AVAudioSessionCategoryPlayback)
try audioSession.overrideOutputAudioPort(AVAudioSessionPortOverride.speaker)
try audioSession.setActive(true)
} catch {
print("AVPlayer setup error \(error.localizedDescription)")
}
}
func initPlayer() {
LQPlayer = AVPlayer(url:LOW_URL)
print("player allocated")
}
func deAllocPlayer() {
LQPlayer = nil
print("player deallocated")
}
#IBAction func playBtn(_ sender: Any) {
// initPlayer()
LQPlayer?.play()
}
#IBAction func pauseBtn(_ sender: Any) {
LQPlayer?.pause()
deAllocPlayer()
}

Triggering event only on a change of variable output

I have created the following coin toss program which also triggers an audio file with each coin toss every second. I'd like to know how to change this so that the audio only triggers when the result changes from a heads to a tails or vice versa. Not every second as it is now. Any help is greatly appreciated.
import UIKit
import AVFoundation
class ViewController: UIViewController {
var times: Timer!
var timer: Timer!
var coinFlip : Int = 0
var coinResult : String = ""
var audioPlayer : AVAudioPlayer!
let soundArray = ["note1", "note7"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
times = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(fiftyFifty), userInfo: nil, repeats: true)
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(result), userInfo: nil, repeats: true)
result()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func fiftyFifty() -> Int {
coinFlip = Int(arc4random_uniform(2))
return coinFlip
}
func result() -> String {
if coinFlip == 1 {
coinResult = "Heads"
print("Heads")
playSound(soundFileName: soundArray[0])
}
else {
coinResult = "Tails"
print("Tails")
playSound(soundFileName: soundArray[1])
}
}
func playSound(soundFileName : String) {
let soundURL = Bundle.main.url(forResource: soundFileName, withExtension: "wav")
do {
audioPlayer = try AVAudioPlayer(contentsOf: soundURL!)
}
catch {
print(error)
}
audioPlayer.play()
}
}
I've now changed it to add a new variable output from result(), coinResult. Not sure if this helps but hopefully might.
If I understood well, when the coin flips, you want to play a song, in this case you can simply change your coinFlip declaration to that:
var coinFlip : Int = 0 {
didSet {
result()
}
}
didSet is a property observer that observes and respond to changes in property's value.
didSet is called immediately after the new value is stored.
You can read more about property observers in Apple's documentation.
EDIT: Your code will be like this:
import UIKit
import AVFoundation
class ViewController: UIViewController {
var times: Timer!
var timer: Timer!
// Change this
var coinFlip : Int = 0 {
didSet {
result()
}
}
var audioPlayer : AVAudioPlayer!
let soundArray = ["note1", "note7"]
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
times = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(fiftyFifty), userInfo: nil, repeats: true)
timer = Timer.scheduledTimer(timeInterval: 1, target: self, selector: #selector(result), userInfo: nil, repeats: true)
result()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func fiftyFifty() -> Int {
coinFlip = Int(arc4random_uniform(2))
return coinFlip
}
func result() {
if coinFlip == 1 {
print("Heads")
playSound(soundFileName: soundArray[0])
}
else {
print("Tails")
playSound(soundFileName: soundArray[1])
}
}
func playSound(soundFileName : String) {
let soundURL = Bundle.main.url(forResource: soundFileName, withExtension: "wav")
do {
audioPlayer = try AVAudioPlayer(contentsOf: soundURL!)
}
catch {
print(error)
}
audioPlayer.play()
}
}

Given login/signUp code by Parse.com, has binary operator error

I am making SignUp/SignIn pages with watching youtube video.
He makes these pages with parseUI,
In his video, there is no error, but I get an error which is
"binary operator "|" cannot be applied to two 'PFLoginFields' operands" (It is checked where in my code down there. )
I checked in parse.com, parse example code is exactly same code with his.
So my Question is
How can I fix this code to work properly?
or Is there any other Binary operator I can use instead of ||?
import UIKit
import Parse
import ParseUI
class NewResisterVC: UIViewController, PFLogInViewControllerDelegate, PFSignUpViewControllerDelegate {
var logInViewController : PFLogInViewController = PFLogInViewController()
var signUpViewController : PFSignUpViewController = PFSignUpViewController()
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
if (PFUser.currentUser() == nil) {
/////////// HERE IS A PROBELM /////////////////
self.logInViewController.fields = (PFLogInFields.UsernameAndPassword | PFLogInFields.LogInButton | PFLogInFields.SignUpButton | PFLogInFields.PasswordForgotten | PFLogInFields.DismissButton)
//////////////////////////////////////////
var loginLogoTitle = UILabel()
loginLogoTitle.text = "bany"
self.logInViewController.logInView!.logo = loginLogoTitle
self.logInViewController.delegate = self
var signUpLogoTitle = UILabel()
signUpLogoTitle.text = "bany"
self.signUpViewController.signUpView!.logo = signUpLogoTitle
self.signUpViewController.delegate = self
self.logInViewController.signUpController = self.signUpViewController
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//MARK: Parse Login
func logInViewController(logInController: PFLogInViewController, shouldBeginLogInWithUsername username: String, password: String) -> Bool {
if(!username.isEmpty || !password.isEmpty) {
return true
}else{
return false
}
}
func logInViewController(logInController: PFLogInViewController, didLogInUser user: PFUser) {
self.dismissViewControllerAnimated(true, completion: nil)
}
func logInViewController(logInController: PFLogInViewController, didFailToLogInWithError error: NSError?) {
print("Fail to login")
}
//MARK: Parse Sign Up
func signUpViewController(signUpController: PFSignUpViewController, didSignUpUser user: PFUser) {
self.dismissViewControllerAnimated(true, completion: nil)
}
func signUpViewController(signUpController: PFSignUpViewController, didFailToSignUpWithError error: NSError?) {
print("fail to sign up...")
}
func signUpViewControllerDidCancelSignUp(signUpController: PFSignUpViewController) {
print("User dismissed sign up")
}
// mark: Actions
#IBAction func simpleAction(send: AnyObject) {
self.presentViewController(self.logInViewController, animated: true, completion: nil)
}
}
The guy in video is using xcode 6 with swift 1.2, you are using xcode 7 with swift 2.0. Syntax has changed, you have to rewrite this line like this:
self.logInViewController.fields = [PFLogInFields.UsernameAndPassword,
PFLogInFields.LogInButton, PFLogInFields.SignUpButton,
PFLogInFields.PasswordForgotten, PFLogInFields.DismissButton]
Also note that you don't need to write typename prefix when assigning to variable:
self.logInViewController.fields = [.UsernameAndPassword, .LogInButton,
.SignUpButton, .PasswordForgotten, .DismissButton]