Trying to figure out how to read the Apple headphone's volume buttons to use as a trigger for the camera shutter (as the Apple Camera app does).
From the documentation on Remote Control Events,
Remote Control Received With Event, and this git repo, I've pieced together that I'll probably need an AVAudioPlayer object, .beginReceivingRemoteControlEvents(), and remoteControlReceivedWithEvent, along with making this view canBecomeFirstResponder() return true.
import UIKit
import AVFoundation
class ViewController: UIViewController, AVAudioPlayerDelegate {
var player: AVAudioPlayer!
override func viewDidLoad() {
super.viewDidLoad()
var session: AVAudioSession = AVAudioSession.sharedInstance()
session.setActive(true, error: nil)
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
println("viewDidAppear worked...")
self.becomeFirstResponder()
UIApplication.sharedApplication().beginReceivingRemoteControlEvents()
}
override func canBecomeFirstResponder() -> Bool {
return true
}
override func remoteControlReceivedWithEvent(event: UIEvent) {
let rc = event.subtype
println("does this work? \(rc.rawValue)")
//takePicture()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
}
I expected to get "does this work" when hitting the volume buttons on the headphones, instead I just see it adjust the headphone volume like normal. So I must be missing something, maybe with a delegate or AVSession?
I cross-posted this on r/swift, where I was told it probably requires playing audio (quoted straight from the documentation).
So while this isn't the ideal solution, it works for my own private use.
import UIKit
import AVFoundation
import MediaPlayer
class ViewController: UIViewController, AVAudioPlayerDelegate {
var testPlayer: AVAudioPlayer? = nil
func loadSound(filename: NSString) -> AVAudioPlayer {
let url = NSBundle.mainBundle().URLForResource(filename as String, withExtension: "caf")
var error: NSError? = nil
let player = AVAudioPlayer(contentsOfURL: url, error: &error)
if error != nil {
println("Error loading \(url): \(error?.localizedDescription)")
} else {
player.prepareToPlay()
}
return player
}
override func viewDidLoad() {
super.viewDidLoad()
self.testPlayer = self.loadSound("silence")
self.testPlayer?.numberOfLoops = -1
self.testPlayer?.play()
}
override func canBecomeFirstResponder() -> Bool {
return true
}
override func viewDidAppear(animated: Bool) {
super.viewDidAppear(animated)
self.becomeFirstResponder()
UIApplication.sharedApplication().beginReceivingRemoteControlEvents()
}
override func remoteControlReceivedWithEvent(event: UIEvent) {
let rc = event.subtype
println("rc.rawValue: \(rc.rawValue)")
// take photo
}
}
I noticed that in Apple's camera app, the +/- volume buttons trigger the camera, and the microphone button pauses/plays any audio running in another app, but in this implementation the volume buttons still control the volume (and any audio has been paused when the app is launched).
An rc.rawValue: 103 corresponds to a single click of the microphone button, a double click returns 104, and a triple click returns 105, and then sometimes bumping a couple at a time returns a 108 or 109.
Based on Cody's answer but updated for 2019 (Swift 5)
import UIKit
import AVFoundation
import MediaPlayer
class ViewController: UIViewController, AVAudioPlayerDelegate {
var myPlayer: AVAudioPlayer? = nil
func loadSound(filename: NSString) -> AVAudioPlayer? {
let url = Bundle.main.url(forResource: filename as String, withExtension: "mp3")
do {
let player = try AVAudioPlayer(contentsOf: url ?? URL(fileURLWithPath: ""))
player.prepareToPlay()
return player
}
catch {
print("Error : \(error)")
return nil
}
}
override func viewDidLoad() {
super.viewDidLoad()
guard let testPlayer = loadSound(filename: "silence") else {
print("Not able to load the sound")
return
}
testPlayer.delegate = self
testPlayer.volume = 0.8
testPlayer.numberOfLoops = -1
myPlayer = testPlayer
myPlayer?.play()
}
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
self.becomeFirstResponder()
UIApplication.shared.beginReceivingRemoteControlEvents()
}
override func remoteControlReceived(with event: UIEvent?) {
let rc = event?.subtype
print("rc.rawValue: \(rc?.rawValue)")
// Do your thing
}
}
Related
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.
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.
I am trying to pass button label to the filename of my multimedia.
Unfortunately it's not working.
Idea is when I press button named "cat" it will play filename named "cat", "mp3"
If I press button with label "cow" it will play sound with filename "cow".
So I tried already different variant's but I can't make it work. If you guys have some ideas, please help.
import UIKit
import AVFoundation
class ViewController: UIViewController {
var audioPlayer: AVAudioPlayer!
override func viewDidLoad() {
super.viewDidLoad()
}
func playAudio() {
do {
self.audioPlayer = try AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource("buttonName", ofType: "mp3")!))
self.audioPlayer.play()
} catch {
print("Error")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func playSound(sender: AnyObject) {
let buttonName = sender.currentTitle!
playAudio()
}
}
I'm no expert in Swift, but I don't think that dope LLVM can fix these for you:
You initialized buttonName as local variable, and you didn't pass it to playAudio() (which does not accept parameters anyways), it's 100% not working to me.
Since you didn't pass the local buttonName to playAudio() as a parameter, you can't get the button name within the function scope. Also, you use "buttonName", which is a String object, not even a variable. There's no file called buttonName.mp3 in your bundle, so nothing would happen (will always print("Error")).
Something like this should work: (Not tested, but should be similer)
import UIKit
import AVFoundation
class ViewController: UIViewController {
var audioPlayer: AVAudioPlayer!
override func viewDidLoad() {
super.viewDidLoad()
}
func playAudio(buttonName: String!) {
do {
self.audioPlayer = try AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource(buttonName, ofType: "mp3")!))
self.audioPlayer.play()
} catch {
print("Error")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func playSound(sender: AnyObject) {
let buttonName = sender.currentTitle!
playAudio(buttonName)
}
}
The .pathForResource(:String?,: String?) method of NSBundle does not take a selector, but a String. Hence, in your case, you will always try to open "buttonName.mp3", rather than the contents of some property called buttonName. Moreover, playSound in your function playSound(..) lives only locally in the scope of that function; and since you do not pass its value to playAudio(), the latter does now know of it. Finally, the sender for #IBAction is an UIButton, you're better of actually using type UIButton rather than AnyObject.
import UIKit
import AVFoundation
class ViewController: UIViewController {
var audioPlayer: AVAudioPlayer!
override func viewDidLoad() {
super.viewDidLoad()
}
func playAudio(buttonName: String) {
do {
self.audioPlayer = try AVAudioPlayer(contentsOfURL: NSURL(fileURLWithPath: NSBundle.mainBundle().pathForResource(buttonName, ofType: "mp3") ?? ""))
self.audioPlayer.play()
} catch {
print("Error")
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
}
#IBAction func playSound(sender: UIButton) {
if let buttonName = sender.currentTitle {
playAudio(buttonName)
}
}
}
Notice that you should avoid using forced unwrapping of optionals (!) unless you know specifically that they will not be nil; I've used optional binding instead in the example above (if let ... in playSound(...).
I have background music which starts when the app is launched in GameViewController.swift using the following code:
class GameViewController: UIViewController {
// VARIABLES
var backgroundMusicPlayer : AVAudioPlayer!
// AUDIO PLAYER
func playBackgroundMusic(filename: String) {
let url = NSBundle.mainBundle().URLForResource(filename, withExtension: nil)
var error : NSError? = nil
do {
backgroundMusicPlayer = try AVAudioPlayer(contentsOfURL: url!)
} catch let error1 as NSError {
error = error1
backgroundMusicPlayer = nil
}
if backgroundMusicPlayer == nil {
print("Could not create audio player: \(error!)")
return
}
backgroundMusicPlayer.numberOfLoops = -1
backgroundMusicPlayer.prepareToPlay()
backgroundMusicPlayer.play()
}
func stopBackgroundMusic() {
backgroundMusicPlayer.stop()
}
override func viewDidLoad() {
super.viewDidLoad()
playBackgroundMusic("MainTheme.mp3")
<< Various irrelevant code >>
}
Because this is run in the viewController, it persists through changing scenes on the menu (i.e. opening the "shop" scene) and creates a seamless track. When I click the "Play" button on the menu scene I want the music to then stop, and transition to the game. I have the stopBackgroundMusic() method in the GameViewController but I don't know how to call it on on the menu scene. IN THE MENU SCENE I tried this:
// TOUCH
override func touchesBegan(touches: Set<UITouch>, withEvent event: UIEvent?) {
let touch = touches.first as UITouch?
let touchLocation = touch!.locationInNode(self)
let touchedNode = self.nodeAtPoint(touchLocation)
if touchedNode.name == "startGame" {
GameViewController.stopBackgroundMusic()
let transitionType = SKTransition.fadeWithDuration(2)
let viewSize = self.view?.bounds.size
let scene = GameScene(size: viewSize!)
self.view?.presentScene(scene, transition: transitionType)
}
}
But I get an error saying I'm missing parameter #1 in call for stopBackgroundMusic() which shouldn't require any parameters. Am I calling this method wrong? Thanks!
You are referring to your class by using GameViewController but your function is at the object instance level.
If you declare the variable and function at the class level, your code in the touchesBegan function should work fine.
static var backgroundMusicPlayer : AVAudioPlayer!
class func playBackgroundMusic(filename: String) ...
class func stopBackgroundMusic()
override func viewDidLoad() {
super.viewDidLoad()
GameViewController.playBackgroundMusic("MainTheme.mp3")
<< Various irrelevant code >>
}
Lately I have run into an issue while using the beta version of Xcode (7.0).
I am not able to hear the sound that I play through this code:
(It is a ViewController from the Main.storyboard, there is a button connected to buttonTouchUpInside())
import UIKit
import AVFoundation
class ViewController: UIViewController {
override func viewDidLoad() {
super.viewDidLoad()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func setupAudioPlayerWithFile(file:NSString, type:NSString) -> AVAudioPlayer {
let path = NSBundle.mainBundle().pathForResource(file as String, ofType: type as String)
let url = NSURL.fileURLWithPath(path!)
var audioPlayer:AVAudioPlayer?
do {
try audioPlayer = AVAudioPlayer(contentsOfURL: url)
} catch {
print("NO AUDIO PLAYER")
}
return audioPlayer!
}
#IBAction func buttonTouchUpInside(sender: AnyObject) {
let backMusic = setupAudioPlayerWithFile("sound", type: "wav")
backMusic.play()
}
}
You just have to move the declaration of backMusic out of your IBAction:
Try like this:
class ViewController: UIViewController {
var backMusic: AVAudioPlayer!
// ...
#IBAction func buttonTouchUpInside(sender: AnyObject) {
backMusic = setupAudioPlayerWithFile("sound", type: "wav")
backMusic.play()
}
}