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()
}
}
Related
I am making an app that will display a random quote from a stoic philosopher. Right now, I am stuck on trying to make the correct picture pop up. (User clicks on a Button with the philosopher's name on it, and then a new view pops up with an image of the philosopher and a random quote by him).
class ViewController: UIViewController {
var allQuotes = [String]()
var pictures = [String]()
#IBOutlet var Epictetus: UIButton!
#IBOutlet var Seneca: UIButton!
#IBOutlet var MarcusAurelius: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
// Create a constant fm and assign it the value returned by FileManager.default (built in system type)
let fm = FileManager.default
// Declares a new constant called path that sets the resource path of ours apps buddle.
// A bundle is a directory containing our compiled program and all our assets
let path = Bundle.main.resourcePath!
// items array will be a constant collection of the names of all the files found in the directory of our app
let items = try! fm.contentsOfDirectory(atPath: path)
// create a loop to go through all of our items...
for item in items {
if item.hasSuffix("jpg"){
pictures.append(item)
}
}
print(pictures)
title = "Stoicism"
if let stoicQuotesURL = Bundle.main.url(forResource: "quotes", withExtension: "txt"){
if let stoicQuotes = try? String(contentsOf: stoicQuotesURL) {
allQuotes = stoicQuotes.components(separatedBy: "\n\n")
}
}
}
#IBAction func buttonTapped(_ sender: UIButton) {
if sender.tag == 0 {
if let vc = storyboard?.instantiateViewController(identifier: "Picture") as? PictureViewController {
vc.selectedImage = pictures[0]
navigationController?.pushViewController(vc, animated: true)
}
}
else if sender.tag == 1 {
if let vc = storyboard?.instantiateViewController(identifier: "Picture") as? PictureViewController {
vc.selectedImage = pictures[1]
navigationController?.pushViewController(vc, animated: true)
}
}
else if sender.tag == 2 {
if let vc = storyboard?.instantiateViewController(identifier: "Picture") as? PictureViewController {
vc.selectedImage = pictures[2]
navigationController?.pushViewController(vc, animated: true)
}
}
}
}
That's the code for my main viewController.
import UIKit
class PictureViewController: UIViewController {
#IBOutlet var picture: UIImageView!
#IBOutlet var imageView: UIImageView!
var selectedImage: String?
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
if let imageToLoad = selectedImage {
imageView.image = UIImage(named: imageToLoad)
}
}
override func viewWillAppear(_ animated: Bool) {
// doing it for the parent class
super.viewWillAppear(animated)
// if its a nav Cont then it will hide bars on tap...
}
// now make sure it turns off when you go back to the main screen
override func viewWillDisappear(_ animated: Bool) {
super.viewWillDisappear(animated)
}
/*
// MARK: - Navigation
// In a storyboard-based application, you will often want to do a little preparation before navigation
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
// Get the new view controller using segue.destination.
// Pass the selected object to the new view controller.
}
*/
}
That's the code for the viewController that has the imageView. Right now, the image that's popping up is always the preset (Marcus Aurelius), even though my code looks correct to me. Obviously it isn't (also, I've already debugged and ensured through print statements that the jpg files add to the pictures array correctly).
Any help would be appreciated.
First of all, this code is really silly:
#IBAction func buttonTapped(_ sender: UIButton) {
if sender.tag == 0 {
if let vc = storyboard?.instantiateViewController(identifier: "Picture") as? PictureViewController {
vc.selectedImage = pictures[0]
navigationController?.pushViewController(vc, animated: true)
}
}
else if sender.tag == 1 {
if let vc = storyboard?.instantiateViewController(identifier: "Picture") as? PictureViewController {
vc.selectedImage = pictures[1]
navigationController?.pushViewController(vc, animated: true)
}
}
else if sender.tag == 2 {
if let vc = storyboard?.instantiateViewController(identifier: "Picture") as? PictureViewController {
vc.selectedImage = pictures[2]
navigationController?.pushViewController(vc, animated: true)
}
}
}
Do you see that everything in those lines is identical except for the numbers? So make the number a variable:
#IBAction func buttonTapped(_ sender: UIButton) {
if let vc = storyboard?.instantiateViewController(identifier: "Picture") as? PictureViewController {
print(sender.tag)
vc.selectedImage = pictures[sender.tag]
navigationController?.pushViewController(vc, animated: true)
}
}
See how much shorter and clearer that is? Okay, I've also added a print statement. This will print the tag to the console. You need to make sure that your buttons do have the right tags. If they do, your code should work.
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")
}
}
}
I'm making a musicPlayer app in Swift, which is based on Tab Bar Controller and now is creating the NowPlayingViewController which present the playing scene as below screenshot showed. This NowPlayingViewController will be opened when I click the table row from another tableViewController, and the song was played.
My question is that when I dismiss this View Controller to go back the previous view or move to other view of Tab Bar Controller, the music playing is stoped. After some googles, someone suggests to use singleton to make a shared session for music playing.
/// here is my project's storyboard
/// I change my code to add one Audio Manager.swift as the singleton class. However I'm not sure if I'm doing the right thing, need help. Also don't find method about getting playingSong variable from NowPlayingVewController...
import AVKit
class AudioManager {
// use Singleton pattern keep the music continuing when user move through the App.
static let sharedInstance = AudioManager()
var audioPlayer: AVAudioPlayer!
var playingSong: SongData?
private init() {
// config for audio background playing
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [.mixWithOthers])
print("Playback OK")
try AVAudioSession.sharedInstance().setActive(true)
print("Session is Active")
} catch {
print(error)
}
}
func startMusic() {
do {
// how to retrieve the playingSong variable from NowPlayingViewController?
// playingSong =
try audioPlayer = AVAudioPlayer(contentsOf: playingSong!.url)
audioPlayer.prepareToPlay()
audioPlayer.play()
} catch {
print("could not load file")
}
}
func pauseMusic() {
audioPlayer.pause()
}
}
/// NowPlayViewController.swift, in case it is not fully aligned with above picture because it is just an example image to show the playing scene.
import UIKit
import AVKit
class NowPlayingViewController: UIViewController {
var playingSong: SongData?
var audioPlayer: AVAudioPlayer!
var isPlaying = true
var timer:Timer!
#IBOutlet weak var songTitle: UILabel!
#IBOutlet weak var songAlbum: UILabel!
#IBOutlet weak var songArtwork: UIImageView!
#IBOutlet weak var playOrPauseButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
songTitle.text = playingSong?.songName
songAlbum.text = playingSong?.albumName
songArtwork.image = playingSong?.albumArtwork
// start play music in AudioManager sharedInstance
AudioManager.sharedInstance.startMusic()
}
#IBAction func NaviBack(_ sender: UIButton) {
print("press button")
// not sure about this, use present modally of segue,
// so need to use dismiss to return to the previous scene/view.
self.dismiss(animated: true, completion: nil)
}
#IBAction func PlayOrPauseMusic(_ sender: UIButton) {
if isPlaying {
print("isPlaying")
AudioManager.sharedInstance.pauseMusic()
isPlaying = false
playOrPauseButton.setTitle("Pause", for: .normal)
} else {
print("isPaused")
AudioManager.sharedInstance.startMusic()
isPlaying = true
playOrPauseButton.setTitle("Play", for: .normal)
}
}
}
/// Sending data to NowPlayingViewController from tableViewController using Segue present modally.
// define segue to the transition to NowPlaying View
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "playingSong" {
if let indexPath = self.tableView.indexPathForSelectedRow {
let controller = segue.destination as! NowPlayingViewController
if resultSearchController.isActive {
controller.playingSong = filteredTableData[indexPath.row]
} else {
controller.playingSong = tableData[indexPath.row]
}
}
}
}
Oops, I found my AudioManger is actually worked by getting the playingSong: SongData from tableViewController when user click the cell row in this view. I declare this playingSong as static var in order to access/use it from AudioManager.
Now music is playing when user navigation through the Tab Bar Controller, update my code as below. Next step could be add a button on the left/right top of navi view to re-present the playing scene:)
Err, I found that it still has issue, the play and resume for the current song is working, but if I select another song in the tableViewController, then it still plays the previous song in NowPlayingView. The reason could be the init() only once, so I need to find a way to re-assign value to sharedInstance when select another cell row.
import AVKit
class AudioManager {
// use Singleton pattern keep the music continuing when user move through the App.
static let sharedInstance = AudioManager()
var audioPlayer: AVAudioPlayer!
var playingSong: SongData?
private init() {
// config for audio background playing
do {
try AVAudioSession.sharedInstance().setCategory(.playback, mode: .default, options: [.mixWithOthers])
print("Playback OK")
try AVAudioSession.sharedInstance().setActive(true)
print("Session is Active")
} catch {
print(error)
}
do {
playingSong = SongsTableViewController.playingSong
try audioPlayer = AVAudioPlayer(contentsOf: playingSong!.url)
audioPlayer.prepareToPlay()
} catch {
print("could not load file")
}
}
func playMusic() {
audioPlayer.play()
}
func pauseMusic() {
audioPlayer.pause()
}
}
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?
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()
}
}