How To Make Play/Pause Button on Apple TV Remote Play/Pause AVAudioPlayer - swift

I am making a music app for tvOS which has a AVAudioPlayer. I was wondering how to make the Play/Pause Button on Apple TV remote Play/Pause the AVAudioPlayer? Here's my current code:
import UIKit
import AVFoundation
class MusicViewController: UIViewController, AVAudioPlayerDelegate {
#IBOutlet weak var progressView: UIProgressView!
var audioPlayer = AVAudioPlayer()
override func viewDidLoad() {
super.viewDidLoad()
do {
audioPlayer = try AVAudioPlayer(contentsOf: URL.init(fileURLWithPath: Bundle.main.path(forResource: "Roots", ofType: "mp3")!))
audioPlayer.prepareToPlay()
var audioSession = AVAudioSession.sharedInstance()
Timer.scheduledTimer(timeInterval: 0.01, target: self, selector: #selector(updateAudioProgressView), userInfo: nil, repeats: true)
progressView.setProgress(Float(audioPlayer.currentTime/audioPlayer.duration), animated: false)
do {
try audioSession.setCategory(AVAudioSessionCategoryPlayback)
}
}
catch {
print(error)
}
audioPlayer.delegate = self
}
// Set the music to automaticly play and stop
override func viewDidAppear(_ animated: Bool) {
audioPlayer.play()
}
override func viewDidDisappear(_ animated: Bool) {
audioPlayer.stop()
}
func updateAudioProgressView()
{
if audioPlayer.isPlaying
{
// Update progress
progressView.setProgress(Float(audioPlayer.currentTime/audioPlayer.duration), animated: true)
}
}
}
I've been looking arround trying to figure this out. I haven't worked with tvOS before, so this is new for me. Thanks so much for the help!

These functions have been working for us. They add a gesture recognizer that listens for the play/pause button on the remote. You can add this to your app delegate.
func initializePlayButtonRecognition() {
addPlayButtonRecognizer(#selector(AppDelegate.handlePlayButton(_:)))
}
func addPlayButtonRecognizer(_ selector: Selector) {
let playButtonRecognizer = UITapGestureRecognizer(target: self, action:selector)
playButtonRecognizer.allowedPressTypes = [NSNumber(value: UIPressType.playPause.rawValue as Int)]
self.window?.addGestureRecognizer(playButtonRecognizer)
}
func handlePlayButton(_ sender: AnyObject) {
if audioPlayer.isPlaying {
audioPlayer.pause() {
} else {
audioPlayer.play()
}
}

Related

How can I tap to close AVPlayer in Swift 5?

I'm creating an extremely simple game, and I've hidden the video controls.
#IBAction func playVideo1(_ sender: Any) {
// play video connected to button 1
guard let firstVideo = Bundle.main.path(forResource: "Video1", ofType:"mp4") else {
debugPrint("Video not found")
return
}
// create an AVPlayer, passing it mp4
let player = AVPlayer(url: URL(fileURLWithPath: firstVideo))
// Create a new AVPlayerViewController and pass it a reference to the player.
let controller = AVPlayerViewController()
controller.player = player
controller.showsPlaybackControls = false
// Modally present the player and call the player's play() method when complete.
present(controller, animated: true) {
player.play()
}
} // end playVideo1
One of the two options would be OK.
Option 1: Tap to close the video.
Option 2: Have the AVPlayer close automatically at the end of the video.
I appreciate any help.
Thanks!
You can add a tap gesture recognizer to AVPlayerViewController's view (for closing on tap), or you could subscribe to AVPlayerItemDidPlayToEndTime notification (for closing when video ends playing). Something like this:
#IBAction func playVideo1(_ sender: Any) {
// play video connected to button 1
guard let firstVideo = Bundle.main.path(forResource: "Video1", ofType:"mp4") else {
debugPrint("Video not found")
return
}
// create an AVPlayer, passing it mp4
let player = AVPlayer(url: URL(fileURLWithPath: firstVideo))
// Create a new AVPlayerViewController and pass it a reference to the player.
let controller = AVPlayerViewController()
controller.player = player
controller.showsPlaybackControls = false
//for closing when video ends
NotificationCenter.default.addObserver(self, selector: #selector(closePlayer), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: controller.player?.currentItem)
//for closing on tap
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(closePlayer))
controller.view.addGestureRecognizer(tapGestureRecognizer)
// Modally present the player and call the player's play() method when complete.
present(controller, animated: true) {
player.play()
}
} // end playVideo1
#objc func closePlayer() {
dismiss(animated: true)
//if you go notification route, don't forget to remove observer
NotificationCenter.default.removeObserver(self)
}
For closing the videoPlayer when on user tap you can use the following-
let tapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(closePlayerOnTouch))
controller.view.addGestureRecognizer(tapGestureRecognizer)
and then add this after the button -
#objc func closePlayerOnTouch() {
dismiss(animated: true)
NotificationCenter.default.removeObserver(self)
}
#Predrag's answer is really awesome though.
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")
}
}
}

Microphone access requested without microphone usage - swift

I'm having an issue with an animation (mp4) on my app. Sometimes(not all the time) when the app is launched, it requests microphone access but I am not requesting for one anywhere in the app. I'm only using AVPlayer to play the mp4 content. The code below is the only one related to the player. Any idea why I'm being requested for mic access? Thanks
import UIKit
import Foundation
import MediaPlayer
import AVKit
class AnimationLaunchscreen: UIViewController {
var player: AVPlayer?
override func viewDidLoad() {
super.viewDidLoad()
let timer = Timer.scheduledTimer(timeInterval: 6.0, target: self, selector: #selector(timeToMoveOn), userInfo: nil, repeats: false)
self.loadVideo()
}
#objc func timeToMoveOn() {
self.performSegue(withIdentifier: "goToTableView", sender: self)
}
func loadVideo() {
let path = Bundle.main.path(forResource: "stopwatchAnimation", ofType:"mp4")
let filePathURL = NSURL.fileURL(withPath: path!)
let player = AVPlayer(url: filePathURL)
let playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = self.view.frame
playerLayer.videoGravity = AVLayerVideoGravity.resizeAspectFill
playerLayer.zPosition = -1
self.view.layer.addSublayer(playerLayer)
player.seek(to: CMTime.zero)
player.play()
}
override func viewWillAppear(_ animated: Bool) {
makeStatusBarBlack()
}
}
Adding the answer here so it is easier to find than reading the comments. This really appears to be a bug on simulator only (https://forums.developer.apple.com/thread/110423). Running on device works just fine.

Zero amplitude and frequency from audiokit tracker on device but works on simulator

AudioKit
I have two view controllers that are doing different audio related function. The first view viewController, needs to record audio and also do real-time processing of the amplitude and frequency of the sound being recorded. The second view controller needs to play the recorded audio, and do some stuff with the playhead.
My problem is that when I go back and forth a few times between the two screens on my iphone 6 device, the amplitude and frequency of the tracker only equal 0. This behavior does not happen on my simulator. Similarly, I've tried different combinations of starting + stopping the tracker and/or the audio engine at different points in the workflow but then I just get crashes along the lines of https://github.com/audiokit/AudioKit/issues/643. One thing I have been trying to figure out is how to make a singleton instance of the aukiokit so I can only start the engine once, but I can't get it from examples.
Here are the key sections of code that use the audiokit, microphone and tracker:
First view controller in the workflow
class ViewController: UIViewController {
#IBAction func analyzeSpeechAction(_ sender: Any) {
//Audiokit.stop()
stopTimers()
let vc = AnalysisViewController(nibName: "AnalysisViewController", bundle: nil)
print("ANALYZING")
do {
try player.reloadFile()
} catch { print("Errored reloading.") }
let recordedDuration = player != nil ? player.audioFile.duration : 0
if recordedDuration > 0.0 {
recorder.stop()
}
tracker.stop()
navigationController?.pushViewController(vc, animated: true)
}
func stopTimers(){
if timerTest != nil {
timerTest!.invalidate()
timerTest = nil
}
if timer != nil {
timer!.invalidate()
timer = nil
}
}
var mic: AKMicrophone!
var tracker: AKFrequencyTracker!
var silence: AKBooster!
var mainMixer: AKMixer!
var timerTest : Timer?
var micMixer: AKMixer!
var recorder: AKNodeRecorder!
var player: AKAudioPlayer!
var tape: AKAudioFile!
var micBooster: AKBooster!
override func viewDidLoad() {
super.viewDidLoad()
AKAudioFile.cleanTempDirectory()
AKSettings.bufferLength = .medium
do {
try AKSettings.setSession(category: .playAndRecord, with: .allowBluetoothA2DP)
} catch {
AKLog("Could not set session category.")
}
AKSettings.defaultToSpeaker = true
AKSettings.audioInputEnabled = true
mic = AKMicrophone()
tracker = AKFrequencyTracker(mic)
tracker.start()
silence = AKBooster(tracker, gain: 0)
micMixer = AKMixer(mic)
micBooster = AKBooster(micMixer)
// Will set the level of microphone monitoring
micBooster.gain = 0
recorder = try? AKNodeRecorder(node: micMixer)
if let file = recorder.audioFile {
player = try? AKAudioPlayer(file: file)
}
mainMixer = AKMixer(micBooster)
do {
try recorder.record()
} catch { print("Errored recording.") }
AudioKit.output = mainMixer
}
viewDidAppear(){
AudioKit.stop()
AudioKit.output = silence
AudioKit.start()
if timer == nil{
timer = Timer.scheduledTimer(timeInterval: 1, target:self,
selector: #selector(ViewController.updateCounter), userInfo: nil, repeats: true)
}
}
#objc func updateUI() {
frame = frame + 1
print("AMP")
print(tracker.amplitude)
print(tracker.frequency)
}
}
Second View Controller
class AnalysisViewController: UIViewController {
#IBOutlet weak var barChartView: LineChartView!
#IBAction func recordSpeechNavigation(_ sender: Any) {
let vc = ViewController(nibName: "ViewController", bundle: nil)
//AudioKit.stop()
player.stop()
navigationController?.pushViewController(vc, animated: true)
}
#IBAction func playButtonAction(_ sender: UIButton) {
playAudio()
}
override func viewDidLoad() {
super.viewDidLoad()
//move to completion handler
player.completionHandler = {() -> Void in
self.playerStatusButton.setTitle("Play", for: .normal)
self.timerTest!.invalidate()
self.relativePlayheadPosition = 0.0
self.movePlayhead()
print("complete")
}
}
}
I kind of mashed up a bunch of examples to get this far so I apologize if I am improperly using some of the AudioKit methods. The key behavior occurs between viewDidLoad(), viewDidAppear() and updateUI() of the first view and recordSpeechNavigation() of the second view.
Does anyone have any insight into what might be happening or how I can properly implement this stuff?

Swift: multiple functions working at the same time

In my app I have 2 UIButtons, each button plays 2 sounds (plays one sound, pauses, plays the other sound), and the UIButtons image changes every time it is pressed, my app also has a few tabs.the problem is that when I press a UIButton (which takes about 4 seconds for the sounds to be played and be done) I can not press another button, or even switch between tabs, the app freezes until the action of UIbutton gets done with, then I can press other buttons or switch tabs.
here's my code:
import UIKit
import AVFoundation
import CoreData
class FirstViewController: UIViewController {
var player:AVAudioPlayer = AVAudioPlayer()
var player2:AVAudioPlayer = AVAudioPlayer()
#IBAction func play(_ sender: Any) {
player.play()
sleep(1)
player2.play()
}
#IBAction func play2(_ sender: Any) {
player.play()
sleep(1)
player2.play()
}
#IBOutlet weak var play: UIButton!
#IBOutlet weak var play2: UIButton!
var buttonActive = false
#IBAction func pPressed(_ sender: Any) {
if buttonActive {
play.setBackgroundImage(UIImage(named: "SwitchD-Icon-40"), for: .normal)
} else {
play.setBackgroundImage(UIImage(named: "Switch-Icon-40"), for: .normal)
}
buttonActive = !buttonActive
}
#IBAction func p2Pressed(_ sender: Any) {
if buttonActive {
play2.setBackgroundImage(UIImage(named: "SwitchD-Icon-40"), for: .normal)
} else {
play2.setBackgroundImage(UIImage(named: "Switch-Icon-40"), for: .normal)
}
buttonActive = !buttonActive
}
override func viewDidLoad() {
super.viewDidLoad()
play.setBackgroundImage(UIImage(named: "SwitchD-Icon-40"), for: .normal)
play2.setBackgroundImage(UIImage(named: "SwitchD-Icon-40"), for: .normal)
do
{
let audioPath = Bundle.main.path(forResource: "DTMF-1", ofType: "mp3")
try player = AVAudioPlayer(contentsOf: NSURL(fileURLWithPath: audioPath!) as URL)
let audioPath2 = Bundle.main.path(forResource: "DTMF-2", ofType: "mp3")
try player2 = AVAudioPlayer(contentsOf: NSURL(fileURLWithPath: audioPath2!) as URL)
}
Is there any way to make my app be faster?
One famous way to implement do something, pause and do other thing is to use Timer.
The following code also includes:
How to properly instantiate AVAudioPlayer
How to invalidate working Timer
How to stop AVAudioPlayer when it may be reused soon
How to stop playing on transition
Please try:
class FirstViewController: UIViewController {
var player: AVAudioPlayer? //### You should not instantiate unused objects
var player2: AVAudioPlayer?
var secondSoundTimer: Timer?
private func startPlaying() {
stopPlaying()
player?.play()
//### Using `Timer` is one way to achieve pause-like thing.
//### If you need to target older iOSs than 10.0, you need to modify this part.
secondSoundTimer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: false) {timer in
self.player2?.play()
self.secondSoundTimer?.invalidate()
self.secondSoundTimer = nil
}
}
private func stopPlaying() {
secondSoundTimer?.invalidate()
secondSoundTimer = nil
if let player = player, player.isPlaying {
//### Stop playing (keeping internal `prepared` state) and rewind to the first place.
player.pause()
player.currentTime = 0
}
if let player2 = player2, player2.isPlaying {
player2.pause()
player2.currentTime = 0
}
}
#IBOutlet weak var play: UIButton!
#IBOutlet weak var play2: UIButton!
var buttonActive = false
#IBAction func pPressed(_ sender: Any) {
startPlaying()
if buttonActive {
play.setBackgroundImage(UIImage(named: "SwitchD-Icon-40"), for: .normal)
} else {
play.setBackgroundImage(UIImage(named: "Switch-Icon-40"), for: .normal)
}
buttonActive = !buttonActive
}
//### Don't you need `button2Active`?
#IBAction func p2Pressed(_ sender: Any) {
startPlaying()
if buttonActive {
play2.setBackgroundImage(UIImage(named: "SwitchD-Icon-40"), for: .normal)
} else {
play2.setBackgroundImage(UIImage(named: "Switch-Icon-40"), for: .normal)
}
buttonActive = !buttonActive
}
override func viewDidLoad() {
super.viewDidLoad()
play.setBackgroundImage(UIImage(named: "SwitchD-Icon-40"), for: .normal)
play2.setBackgroundImage(UIImage(named: "SwitchD-Icon-40"), for: .normal)
do {
//### You can get URL directly from Bundle
//### It's better to apply forced-unwrapping as soon as possible, if you need it eventually.
let audioURL = Bundle.main.url(forResource: "DTMF-1", withExtension: "mp3")!
player = try AVAudioPlayer(contentsOf: audioURL)
//### `prepareToPlay()` improves response for the first play.
player?.prepareToPlay()
let audioURL2 = Bundle.main.url(forResource: "DTMF-2", withExtension: "mp3")!
player2 = try AVAudioPlayer(contentsOf: audioURL2)
player2?.prepareToPlay()
} catch {
print(error)
//... or any other things you need...
}
//...
}
//### Stop playing on transition to other ViewConrollers.
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
stopPlaying()
}
}

Detecting AVPlayer video start stop events

Here is nice simple avplayer piece of code playing a small collection of videos in a queue. My question. I actually want to pause between videos on my queue. Is it possible?
I did note that rate fires twice; status fires just once as does notification.
import UIKit
import AVKit
import AVFoundation
class ViewController: UIViewController {
#IBOutlet weak var VideoView: UIView!
var player:AVQueuePlayer = AVQueuePlayer()
#IBAction func NextSlide(sender: AnyObject) {
player.play()
}
override func viewDidLoad() {
func NextSlide(sender: AnyObject) {
}
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
let arrayOfPaths: [String] = ["http://192.100.1.1:8080/dino1.mov","http://192.100.1.1:8080/dino1.mov","http://192.100.1.1:8080/dino1.mov"]
var shots = [AVPlayerItem]()
for item in arrayOfPaths {
let url2adopt = NSURL(string: item)
let avAsset = AVURLAsset(URL: url2adopt!)
let shot = AVPlayerItem(asset: avAsset)
shots.append(shot)
}
player = AVQueuePlayer(items: shots)
let playerLayer = AVPlayerLayer(player: player)
playerLayer.frame = VideoView.layer.bounds
VideoView.layer.addSublayer(playerLayer)
player.addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions.New, context: nil)
player.addObserver(self, forKeyPath: "rate", options: NSKeyValueObservingOptions.New, context: nil)
NSNotificationCenter.defaultCenter().addObserver(self, selector: "itemDidFinishPlaying:", name: AVPlayerItemDidPlayToEndTimeNotification, object: player.currentItem)
player.play()
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
// catch changes to status
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if (keyPath == "rate") {
print(player.rate)
}
if (keyPath == "status") {
print(player.status)
}
}
func itemDidFinishPlaying(notification:NSNotification) {
print("finished")
}
}
Add this line:
player.addObserver(
self, forKeyPath:"currentItem", options:.Initial, context:nil)
Now you'll be notified every time there is a change in the queue item currently being played.