I coded an app with some buttons. One of them shows an interstitial Ad by pressing it. When I close the ad all the buttons on the view disappear. How can I fix that?
//Collection Sound
var boomSound = NSURL(fileURLWithPath: Bundle.main.path(forResource: "135936__bradwesson__collectcoin", ofType: "wav")!)
var audioPlayer = AVAudioPlayer()
#IBOutlet var ScoreLabel: UILabel!
#IBOutlet weak var BT6: UIButton!
#IBOutlet weak var BT5: UIButton!
#IBOutlet weak var BT4: UIButton!
#IBOutlet weak var BT3: UIButton!
#IBOutlet weak var BT2: UIButton!
#IBOutlet weak var BT1: UIButton!
var interstitial: GADInterstitial!
//When reaching x Taps
var taps = Int(){
didSet {
if taps == 330 {
print("You have reached 5 taps !!")
}
}
}
override func viewDidLoad() {
super.viewDidLoad()
//load interstitial Ad
interstitial = GADInterstitial(adUnitID: "ca-app-pub-1469592343938512/2951120388")
let request2 = GADRequest()
interstitial.load(request2)
authPlayer()
let defaults = UserDefaults.standard
if let storedTaps = defaults.object(forKey: "key") as? Int {
self.taps = storedTaps
setLabel(storedTaps)
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
//HIDE STATUS BAR
override var prefersStatusBarHidden : Bool {
return true
}
#IBAction func BTN(_ sender: AnyObject) {
sender.setTitleColor(UIColor.red, for: UIControlState())
taps += 1
setLabel(taps)
let defaults = UserDefaults.standard
defaults.set(taps, forKey: "key")
}
func setLabel(_ taps:Int) {
ScoreLabel.text = "TAPS \(taps)"
}
//GameCenter
func authPlayer(){
let localPlayer = GKLocalPlayer.localPlayer()
localPlayer.authenticateHandler = {
(view, error) in
if view != nil {
self.present(view!, animated: true, completion: nil)
}
else {
print(GKLocalPlayer.localPlayer().isAuthenticated)
}
}
}
//GameCenter
func saveHighscore( _ number : Int){
if GKLocalPlayer.localPlayer().isAuthenticated {
let scoreReporter = GKScore(leaderboardIdentifier: "TAPME")
scoreReporter.value = Int64(number)
let scoreArray : [GKScore] = [scoreReporter]
GKScore.report(scoreArray, withCompletionHandler: nil)
}
}
//GameCenter
func showLeaderboard(){
let viewController = self.view.window?.rootViewController
let gcvc = GKGameCenterViewController()
gcvc.gameCenterDelegate = self
viewController?.present(gcvc, animated: true, completion: nil)
}
//GameCenter
func gameCenterViewControllerDidFinish(_ gameCenterViewController: GKGameCenterViewController) {
gameCenterViewController.dismiss(animated: true, completion: nil)
}
//Reset TapsLabel
#IBAction func reset(_ sender: AnyObject) {
taps = 0
setLabel(taps)
let defaults = UserDefaults.standard
defaults.set(taps, forKey: "key")
//load interstitial Ad
if (interstitial.isReady){
interstitial.present(fromRootViewController: self)
interstitial = createAD()
}
}
//Load GameCenter
#IBAction func CallGC(_ sender: UIButton) {
showLeaderboard()
saveHighscore(taps)
}
//When view is loaded
override func viewDidAppear(_ animated: Bool) {
BT6.isHidden = true
BT5.isHidden = true
BT4.isHidden = true
BT3.isHidden = true
BT2.isHidden = true
//Hiding all but one button when the view controller loads
}
//Random button view
#IBAction func BT6(_ sender: AnyObject) {
//this checks when BT6 is pressed and then hides it
BT6.isHidden = true
let random = Int(arc4random_uniform(UInt32(4)))
if random == 0 {
BT5.isHidden = false
} else if random == 1 {
BT4.isHidden = false
} else if random == 2 {
BT3.isHidden = false
} else if random == 3 {
BT2.isHidden = false
} else if random == 4 {
BT1.isHidden = false
}
//this part creates a randomiser between 0-4 and depending on which number turns out, it will hide a certain button
do {
audioPlayer = try AVAudioPlayer(contentsOf: boomSound as URL)
audioPlayer.prepareToPlay()
audioPlayer.play()
} catch {
// Catch exception
}
taps += 1
setLabel(taps)
let defaults = UserDefaults.standard
defaults.set(taps, forKey: "key")
}
#IBAction func BT5(_ sender: AnyObject) {
BT5.isHidden = true
let random = Int(arc4random_uniform(UInt32(4)))
if random == 0 {
BT6.isHidden = false
} else if random == 1 {
BT4.isHidden = false
} else if random == 2 {
BT3.isHidden = false
} else if random == 3 {
BT2.isHidden = false
} else if random == 4 {
BT1.isHidden = false
}
taps += 1
setLabel(taps)
let defaults = UserDefaults.standard
defaults.set(taps, forKey: "key")
do {
audioPlayer = try AVAudioPlayer(contentsOf: boomSound as URL)
audioPlayer.prepareToPlay()
audioPlayer.play()
} catch {
// Catch exception
}
}
#IBAction func BT4(_ sender: AnyObject) {
BT4.isHidden = true
let random = Int(arc4random_uniform(UInt32(4)))
if random == 0 {
BT5.isHidden = false
} else if random == 1 {
BT6.isHidden = false
} else if random == 2 {
BT3.isHidden = false
} else if random == 3 {
BT2.isHidden = false
} else if random == 4 {
BT1.isHidden = false
}
taps += 1
setLabel(taps)
let defaults = UserDefaults.standard
defaults.set(taps, forKey: "key")
do {
audioPlayer = try AVAudioPlayer(contentsOf: boomSound as URL)
audioPlayer.prepareToPlay()
audioPlayer.play()
} catch {
// Catch exception
}
}
#IBAction func BT3(_ sender: AnyObject) {
BT3.isHidden = true
let random = Int(arc4random_uniform(UInt32(4)))
if random == 0 {
BT5.isHidden = false
} else if random == 1 {
BT4.isHidden = false
} else if random == 2 {
BT6.isHidden = false
} else if random == 3 {
BT2.isHidden = false
} else if random == 4 {
BT1.isHidden = false
}
taps += 1
setLabel(taps)
let defaults = UserDefaults.standard
defaults.set(taps, forKey: "key")
do {
audioPlayer = try AVAudioPlayer(contentsOf: boomSound as URL)
audioPlayer.prepareToPlay()
audioPlayer.play()
} catch {
// Catch exception
}
}
#IBAction func BT2(_ sender: AnyObject) {
BT2.isHidden = true
let random = Int(arc4random_uniform(UInt32(4)))
if random == 0 {
BT5.isHidden = false
} else if random == 1 {
BT4.isHidden = false
} else if random == 2 {
BT3.isHidden = false
} else if random == 3 {
BT6.isHidden = false
} else if random == 4 {
BT1.isHidden = false
}
taps += 1
setLabel(taps)
let defaults = UserDefaults.standard
defaults.set(taps, forKey: "key")
do {
audioPlayer = try AVAudioPlayer(contentsOf: boomSound as URL)
audioPlayer.prepareToPlay()
audioPlayer.play()
} catch {
// Catch exception
}
}
#IBAction func BT1(_ sender: AnyObject) {
BT1.isHidden = true
let random = Int(arc4random_uniform(UInt32(4)))
if random == 0 {
BT5.isHidden = false
} else if random == 1 {
BT4.isHidden = false
} else if random == 2 {
BT3.isHidden = false
} else if random == 3 {
BT2.isHidden = false
} else if random == 4 {
BT6.isHidden = false
}
taps += 1
setLabel(taps)
let defaults = UserDefaults.standard
defaults.set(taps, forKey: "key")
do {
audioPlayer = try AVAudioPlayer(contentsOf: boomSound as URL)
audioPlayer.prepareToPlay()
audioPlayer.play()
} catch {
// Catch exception
}
}
}
//interstitial Ad
func createAD() -> GADInterstitial{
let interstitial = GADInterstitial(adUnitID: "ca-app-pub-1469592343938512/2951120388")
interstitial.load(GADRequest())
return interstitial
}
This is called each time view appear on screen, not when it loads. Move that code inside ViewDidLoad method
//When view is loaded
override func viewDidAppear(_ animated: Bool) {
super.viewDidAppear(animated)
BT6.isHidden = true
BT5.isHidden = true
BT4.isHidden = true
BT3.isHidden = true
BT2.isHidden = true
//Hiding all but one button when the view controller loads
}
Related
To put it in context, I'm developing an app that is recording a lot of time while in background or in the app. I'm facing two problems using AVAudioRecorder to record in my app:
My recording resumes fine when, for example, I play an audio in WhatsApp or a video in Instagram. But when I play a song in Apple Music or a play a voice note, it doesn't resume the recording again.
If I'm in a Phone Call and I start the recording, my app crashes and outputs the following:
(Error de OSStatus 561017449.)
I think also need to handle when the input/output device changes so my app doesn't crash.
The code that I implemented for the class where I'm recording (and also playing the sound I use recorder so I can try it and see if it works) is this:
class RecordViewController: UIViewController, AVAudioPlayerDelegate , AVAudioRecorderDelegate {
#IBOutlet weak var principalLabel: UILabel!
#IBOutlet weak var loginLabel: UILabel!
#IBOutlet weak var recordBTN: UIButton!
#IBOutlet weak var playBTN: UIButton!
var audioRecorder : AVAudioRecorder!
var audioPlayer : AVAudioPlayer!
var isAudioRecordingGranted: Bool!
var isRecording = false
var isPlaying = false
override func viewDidLoad() {
super.viewDidLoad()
view.backgroundColor = .white
check_record_permission()
checkActivateMicrophone()
NotificationCenter.default.addObserver(self, selector: #selector(handleInterruption(notification:)), name: AVAudioSession.interruptionNotification, object: audioRecorder)
let tapRegister: UITapGestureRecognizer = UITapGestureRecognizer(target: self, action: #selector(self.goToLogin))
loginLabel.addGestureRecognizer(tapRegister)
loginLabel.isUserInteractionEnabled = true
}
#objc func goToLogin() {
self.performSegue(withIdentifier: "goToLogin", sender: self)
}
#objc func handleInterruption(notification: Notification) {
guard let userInfo = notification.userInfo,
let typeInt = userInfo[AVAudioSessionInterruptionTypeKey] as? UInt,
let type = AVAudioSession.InterruptionType(rawValue: typeInt) else {
return
}
switch type {
case .began:
if isRecording {
print("OOOOOOO")
audioRecorder.pause()
}
break
case .ended:
guard let optionsValue = userInfo[AVAudioSessionInterruptionOptionKey] as? UInt else { return }
let options = AVAudioSession.InterruptionOptions(rawValue: optionsValue)
if options.contains(.shouldResume) {
audioRecorder.record()
print("AAAAAAA")
} else {
print("III")
DispatchQueue.main.asyncAfter(deadline: .now() + 2.0) {
self.audioRecorder.record()
}
}
default: ()
}
}
func check_record_permission()
{
switch AVAudioSession.sharedInstance().recordPermission {
case AVAudioSessionRecordPermission.granted:
isAudioRecordingGranted = true
break
case AVAudioSessionRecordPermission.denied:
isAudioRecordingGranted = false
break
case AVAudioSessionRecordPermission.undetermined:
AVAudioSession.sharedInstance().requestRecordPermission({ (allowed) in
if allowed {
self.isAudioRecordingGranted = true
} else {
self.isAudioRecordingGranted = false
}
})
break
default:
break
}
}
func checkActivateMicrophone() {
if !isAudioRecordingGranted {
playBTN.isEnabled = false
playBTN.alpha = 0.5
recordBTN.isEnabled = false
recordBTN.alpha = 0.5
principalLabel.text = "Activa el micrófono desde ajustes"
} else {
playBTN.isEnabled = true
playBTN.alpha = 1
recordBTN.isEnabled = true
recordBTN.alpha = 1
principalLabel.text = "¡Prueba las grabaciones!"
}
}
func getDocumentsDirectory() -> URL
{
let paths = FileManager.default.urls(for: .documentDirectory, in: .userDomainMask)
let documentsDirectory = paths[0]
return documentsDirectory
}
func getFileUrl() -> URL
{
let filename = "myRecording.m4a"
let filePath = getDocumentsDirectory().appendingPathComponent(filename)
return filePath
}
func setup_recorder()
{
if isAudioRecordingGranted
{
let session = AVAudioSession.sharedInstance()
do
{
try session.setCategory(AVAudioSession.Category.playAndRecord, options: .defaultToSpeaker)
try session.setActive(true)
let settings = [
AVFormatIDKey: Int(kAudioFormatMPEG4AAC),
AVSampleRateKey: 44100,
AVNumberOfChannelsKey: 2,
AVEncoderAudioQualityKey:AVAudioQuality.high.rawValue
]
audioRecorder = try AVAudioRecorder(url: getFileUrl(), settings: settings)
audioRecorder.delegate = self
audioRecorder.isMeteringEnabled = true
audioRecorder.prepareToRecord()
}
catch let error {
print(error.localizedDescription)
}
}
else
{
print("AAAAA")
}
}
#IBAction func recordAct(_ sender: Any) {
if(isRecording) {
finishAudioRecording(success: true)
recordBTN.setTitle("Record", for: .normal)
playBTN.isEnabled = true
isRecording = false
}
else
{
setup_recorder()
audioRecorder.record()//aaaaaa
recordBTN.setTitle("Stop", for: .normal)
playBTN.isEnabled = false
isRecording = true
}
}
func finishAudioRecording(success: Bool)
{
if success
{
audioRecorder.stop()
audioRecorder = nil
print("recorded successfully.")
}
else
{
print("Recording failed.")
}
}
func prepare_play()
{
do
{
audioPlayer = try AVAudioPlayer(contentsOf: getFileUrl())
audioPlayer.delegate = self
audioPlayer.prepareToPlay()
}
catch{
print("Error")
}
}
#IBAction func playAct(_ sender: Any) {
if(isPlaying)
{
audioPlayer.stop()
recordBTN.isEnabled = true
playBTN.setTitle("Play", for: .normal)
isPlaying = false
}
else
{
if FileManager.default.fileExists(atPath: getFileUrl().path)
{
recordBTN.isEnabled = false
playBTN.setTitle("pause", for: .normal)
prepare_play()
audioPlayer.play()
isPlaying = true
}
else
{
print("Audio file is missing.")
}
}
}
func audioRecorderDidFinishRecording(_ recorder: AVAudioRecorder, successfully flag: Bool)
{
if !flag
{
print("UUU")
finishAudioRecording(success: false)
}
playBTN.isEnabled = true
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool)
{
recordBTN.isEnabled = true
}
}
I implemented the "Background Mode" of Audio, AirPlay and Picture in Picture
I have a detailed controller where AVPlayer is created, when I start the player, then I exit the controller and I enter that data about the song is not saved and when I click on play AVPlayer is created again. The question is how to make AVPlayer save all the data and do not have to delete the old player. Gif with the problem below
it is my code:
ViewControllerAudioInfo is controller in which I get data about songs
func request(){
let urlData = "https:---.com/local/apps/apple/library_detail.php/?idLibrary=\(detail!.id!)"
var urlRequest = URLRequest(url: URL(string: urlData)!)
urlRequest.timeoutInterval = 300
let task = URLSession.shared.dataTask(with: urlRequest) { (data,response,error) in
if error != nil{
print(error ?? 0)
return
}
DispatchQueue.main.async {
let json = try! JSONSerialization.jsonObject(with: data!, options: .allowFragments) as! [String: Any]
if let posts = json["FILE"] as? [AnyObject] {
for post in posts {
var info = Modal()
info.AudioName = post["NAME"] as? String
info.UrlName = post["SRC"] as? String
info.ImageViewAudio = self.detail?.ImageView
info.AudioName = info.AudioName?.replacingOccurrences(of:".mp3", with: "")
self.mod.append(info)
}
}
}
}
self.preloadEnd()
task.resume()
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "audioDetail" {
let destinationVC = segue.destination as! ViewControllerAudioDetail
destinationVC.mod = mod
}
if segue.identifier == "list" {
let destinationVC = segue.destination as! TableViewControllerAudioList
destinationVC.mod = mod
}
}
And detailController
import UIKit
import AVFoundation
class ViewControllerAudioDetail: UIViewController {
static var avPlayer:AVPlayer?
var status = false
var timeSlider = false
fileprivate let seekDuration: Float64 = 10
fileprivate let seekDurationThirty: Float64 = 30
var sliderEndTime:Any!
var sliderDurationTime:Any!
var mod = [Modal]()
#IBOutlet weak var menuButton: UIBarButtonItem!
#IBOutlet weak var ImageView: UIImageView!
#IBOutlet weak var startTime: UILabel!
#IBOutlet weak var endTime: UILabel!
#IBOutlet weak var sliderSong: UISlider!
#IBOutlet weak var name: UILabel!
#IBOutlet weak var Volume: UISlider!
#IBOutlet weak var iconChange: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
//кнопка назад
let backItem = UIBarButtonItem()
backItem.title = ""
navigationItem.backBarButtonItem = backItem
menu()
sliderSong.minimumValue = 0
sliderSong.maximumValue = 1
sliderSong.setThumbImage(UIImage(named: "thumb.png"), for: .normal)
name.sizeToFit()
name.text = mod[thisSong].AudioName
ImageView.image = mod[0].ImageViewAudio
player(urlSong:mod[thisSong].UrlName!)
self.timeSlider = true
self.status = true
Status()
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
let _ = try AVAudioSession.sharedInstance().setActive(true)
} catch let error as NSError {
print("an error occurred when audio session category.\n \(error)")
}
}
func menu(){
if revealViewController() != nil {
menuButton.target = revealViewController()
menuButton.action = #selector(SWRevealViewController.rightRevealToggle(_:))
view.addGestureRecognizer(self.revealViewController().panGestureRecognizer())
}
}
#IBAction func sliderSong(_ sender: UISlider) {
//перемотка аудиозвука
let duration = CMTimeGetSeconds(ViewControllerAudioDetail.avPlayer!.currentItem!.asset.duration)
let value = sliderSong.value
let durationToSeek = Float(duration) * value
ViewControllerAudioDetail.avPlayer?.seek(to: CMTimeMakeWithSeconds(Float64(durationToSeek),ViewControllerAudioDetail.avPlayer!.currentItem!.duration.timescale)) { [](state) in
if (self.iconChange.currentImage?.isEqual(UIImage(named: "Play.png")))! {
ViewControllerAudioDetail.avPlayer?.pause()
} else if (self.iconChange.currentImage?.isEqual(UIImage(named: "Pause.png")))!{
ViewControllerAudioDetail.avPlayer?.play()
}
}
}
#IBAction func volume(_ sender: UISlider) {
ViewControllerAudioDetail.avPlayer?.volume = sender.value
}
#IBAction func minusThirtySec(_ sender: Any) {
let playerCurrentTime = CMTimeGetSeconds((ViewControllerAudioDetail.avPlayer?.currentTime())!)
var newTime = playerCurrentTime - seekDurationThirty
if newTime < 0 {
newTime = 0
}
let time2: CMTime = CMTimeMake(Int64(newTime * 1000 as Float64), 1000)
ViewControllerAudioDetail.avPlayer?.seek(to: time2, toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero)
}
#IBAction func minusTenSec(_ sender: Any) {
let playerCurrentTime = CMTimeGetSeconds((ViewControllerAudioDetail.avPlayer?.currentTime())!)
var newTime = playerCurrentTime - seekDuration
if newTime < 0 {
newTime = 0
}
let time2: CMTime = CMTimeMake(Int64(newTime * 1000 as Float64), 1000)
ViewControllerAudioDetail.avPlayer?.seek(to: time2, toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero)
}
#IBAction func plusTenSec(_ sender: Any) {
guard let duration = ViewControllerAudioDetail.avPlayer?.currentItem?.duration else{
return
}
let playerCurrentTime = CMTimeGetSeconds((ViewControllerAudioDetail.avPlayer?.currentTime())!)
let newTime = playerCurrentTime + seekDuration
if newTime < (CMTimeGetSeconds(duration) - seekDuration) {
let time2: CMTime = CMTimeMake(Int64(newTime * 1000 as Float64), 1000)
ViewControllerAudioDetail.avPlayer?.seek(to: time2, toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero)
}
}
#IBAction func plusThirtySec(_ sender: Any) {
guard let duration = ViewControllerAudioDetail.avPlayer?.currentItem?.duration else{
return
}
let playerCurrentTime = CMTimeGetSeconds((ViewControllerAudioDetail.avPlayer?.currentTime())!)
let newTime = playerCurrentTime + seekDurationThirty
if newTime < (CMTimeGetSeconds(duration) - seekDuration) {
let time2: CMTime = CMTimeMake(Int64(newTime * 1000 as Float64), 1000)
ViewControllerAudioDetail.avPlayer?.seek(to: time2, toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero)
}
}
#IBAction func Next(_ sender: Any) {
ViewControllerAudioDetail.avPlayer?.removeTimeObserver(sliderEndTime)
ViewControllerAudioDetail.avPlayer?.removeTimeObserver(sliderDurationTime)
if thisSong == mod.count - 1 {
thisSong = 0
} else {
thisSong += 1
}
if thisSong != mod.count{
name.text = mod[thisSong].AudioName
player(urlSong:mod[thisSong].UrlName!)
Status()
}
}
#IBAction func Back(_ sender: Any) {
ViewControllerAudioDetail.avPlayer?.removeTimeObserver(sliderEndTime)
ViewControllerAudioDetail.avPlayer?.removeTimeObserver(sliderDurationTime)
if thisSong != 0 {
thisSong -= 1
} else {
thisSong = mod.endIndex - 1
}
name.text = mod[thisSong].AudioName
player(urlSong:mod[thisSong].UrlName!)
Status()
}
func Status(){
timeSlider = false
if status == true {
iconChange.setImage(UIImage(named:"Pause.png"), for: .normal)
ViewControllerAudioDetail.avPlayer?.play()
} else {
iconChange.setImage(UIImage(named:"Play.png"), for: .normal)
ViewControllerAudioDetail.avPlayer?.pause()
}
}
#IBAction func Play(_ sender: Any) {
if ViewControllerAudioDetail.avPlayer?.rate == 0 && status == false{
status = true
ViewControllerAudioDetail.avPlayer?.play()
ViewControllerAudioDetail.avPlayer?.rate = 1.0
iconChange.setImage(UIImage(named:"Pause.png"), for: .normal)
if timeSlider == false {
sliderDurationTime = ViewControllerAudioDetail.avPlayer?.addPeriodicTimeObserver(forInterval: CMTimeMakeWithSeconds(1, 1), queue: nil, using: {
(CMTime) -> Void in
self.updateProgressBar()
})
}
} else {
status = false
ViewControllerAudioDetail.avPlayer?.rate = 0.0
ViewControllerAudioDetail.avPlayer?.pause()
iconChange.setImage(UIImage(named:"Play.png"), for: .normal)
}
}
func player(urlSong:String) {
let url = URL(string: urlSong)
let playerItem = AVPlayerItem(url: url!)
ViewControllerAudioDetail.avPlayer = AVPlayer(playerItem:playerItem)
NotificationCenter.default.addObserver(self, selector:#selector(playerDidFinishPlaying), name: NSNotification.Name.AVPlayerItemDidPlayToEndTime, object: playerItem)
sliderDurationTime = ViewControllerAudioDetail.avPlayer?.addPeriodicTimeObserver(forInterval: CMTimeMakeWithSeconds(1, 1), queue: nil, using: {
(CMTime) -> Void in
self.updateProgressBar()
})
sliderEndTime = ViewControllerAudioDetail.avPlayer!.addPeriodicTimeObserver(forInterval: CMTime(seconds: 1, preferredTimescale: CMTimeScale(NSEC_PER_SEC)), queue: DispatchQueue.main) { [weak self] (time) in
let duration = CMTimeGetSeconds((ViewControllerAudioDetail.avPlayer!.currentItem!.asset.duration))
self?.sliderSong.value = Float(CMTimeGetSeconds(time)) / Float(duration)
}
let duration = CMTimeGetSeconds(ViewControllerAudioDetail.avPlayer!.currentItem!.asset.duration)
let minutesTextOut = Int(duration) / 60 % 60
let secondsTextOut = Int(duration) % 60
let strDuration = String(format:"%02d:%02d", minutesTextOut, secondsTextOut)
endTime.text = strDuration
}
func playerDidFinishPlaying(note: NSNotification) {
ViewControllerAudioDetail.avPlayer?.removeTimeObserver(sliderDurationTime)
ViewControllerAudioDetail.avPlayer?.removeTimeObserver(sliderEndTime)
NotificationCenter.default.removeObserver(self)
if thisSong == mod.count - 1 {
thisSong = 0
} else {
thisSong += 1
}
if thisSong != mod.count{
name.text = mod[thisSong].AudioName
player(urlSong:mod[thisSong].UrlName!)
Status()
}
}
func updateProgressBar(){
let timeNow = Int(ViewControllerAudioDetail.avPlayer!.currentTime().value) / Int(ViewControllerAudioDetail.avPlayer!.currentTime().timescale)
let minutesText = timeNow / 60
let secondsText = timeNow % 60
let duration = String(format:"%02d:%02d", minutesText, secondsText)
startTime.text = duration
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
override func prepare(for segue: UIStoryboardSegue, sender: Any?) {
if segue.identifier == "toList" {
let vc = segue.destination as! TableViewControllerAudioList
vc.mod = mod
}
}
Instead of globally declaring an AVPlayer, declare it as a static property on your ViewControllerAudioDetail to prevent it from being deinitalised (as this is better practice):
static var avPlayer:AVPlayer?
You should then restore the state of your player view components based on the state of this ViewControllerAudioDetail.avPlayer. You can determine if it is playing or not based on the information given here and can find out the current time using ViewControllerAudioDetail.avPlayer.currentTime(), for example.
Below is the code to check whether song is already playing or not....
if avPlayer != nil {
if ((avPlayer.rate != 0) && (avPlayer.error == nil)) {
// No need to initialise player again....
} else {
// initialise the code here...
avPlayer = AVPlayer(playerItem:playerItem)
avPlayer.play()
avPlayer.actionAtItemEnd = .advance
avPlayer.addObserver(self, forKeyPath: "currentItem", options: [.new, .initial] , context: nil)
}
}
I created the 4 UIButtons on the Main StoryBoard.
I would like "Button4" to implement the other buttons function in a row. That means when I press button 4, player 1 should be played first, after that player 2 and after that player 3.
However, when I press "Button4", "Button2" and "Button3" are played at same time.
fileprivate var player1:AVAudioPlayer?
fileprivate var player2:AVAudioPlayer?
fileprivate var player3:AVAudioPlayer?
let url1 = Bundle.main.bundleURL.appendingPathComponent("music1.mp3")
let url2 = Bundle.main.bundleURL.appendingPathComponent("music2.mp3")
let url3 = Bundle.main.bundleURL.appendingPathComponent("music3.mp3")
#IBAction func pushButton1(sender: UIButton) {
Player(url: url1)
}
#IBAction func pushButton2(sender: UIButton) {
Player1(url: url2)
}
#IBAction func pushButton3(_ sender: UIButton) {
Player2(url: url1, url2: url2, url3: url3)
}
//"yourButton2" and "yourButton3" is played at same time in this code at player2
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
if (player === player1) {
yourButton.isSelected = false
} else if (player === player2) {
yourButton2.isSelected = false
} else if (player === player3) {
yourButton.isSelected = false
player2!.play()
yourButton2.isSelected = true
player2!.play()
yourButton3.isSelected = true
player1!.play()
}
}
func Player(url: URL) {
do {
try player1 = AVAudioPlayer(contentsOf:url)
player1!.play()
yourButton.isSelected = true
player1!.delegate = self
} catch {
print(error)
}
}
func Player1(url: URL) {
do {
try player2 = AVAudioPlayer(contentsOf:url)
player2!.play()
yourButton2.isSelected = true
player2!.delegate = self
} catch {
print(error)
}
}
func Player2(url: URL, url2: URL, url3: URL) {
do {
try player3 = AVAudioPlayer(contentsOf:url)
try player2 = AVAudioPlayer(contentsOf: url2)
try player1 = AVAudioPlayer(contentsOf: url3)
player3!.play()
yourButton.isSelected = true
player3!.delegate = self
player2!.delegate = self
player1!.delegate = self
} catch {
print(error)
}
}
Rather than using an AVAudioPlayer, how about an AVQueuePlayer?
Here's a quick example:
var files = ["file1", "file2", "file3"]
var player: AVQueuePlayer = {
var pathArray = [String]()
files.forEach { resource in
if let path = Bundle.main.path(forResource: resource, ofType: "mp3") {
pathArray.append(path)
}
}
var urlArray = [URL]()
pathArray.forEach { path in
urlArray.append(URL(fileURLWithPath: path))
}
var playerItems = [AVPlayerItem]()
urlArray.forEach { url in
playerItems.append(AVPlayerItem(url: url))
}
let player = AVQueuePlayer(items: playerItems)
player.actionAtItemEnd = AVPlayerActionAtItemEnd.advance
return player
}()
and in your button's action:
#IBAction func buttonTapped(_ sender: UIButton) {
files = ["file2", "file3", "file1"]
player.play()
}
Admittedly, this is pretty quick and dirty, but something kind of like this because we're passing the files array into the player. It shouldn't be too difficult to find more optimization for this code.
EDIT: realized I wasn't passing in an array of AVPlayerItems, so updated.
Edit....
Each of the buttons, 1, 2, 3 etc...will work but "push button 2" is a bit of a mix...
Anyway, reexplain?
I set the 4 UIButtons on the Main StoryBoard.
I would like "Button4" to implement the method of the other three Buttons in a row. (At first, plays an audio file and set selected "Button1" then "Button2" then "Button3")
However "Button2" and "Button3" are played at same time.
fileprivate var player1:AVAudioPlayer?
fileprivate var player2:AVAudioPlayer?
fileprivate var player3:AVAudioPlayer?
let url1 = Bundle.main.bundleURL.appendingPathComponent("music1.mp3")
let url2 = Bundle.main.bundleURL.appendingPathComponent("music2.mp3")
let url3 = Bundle.main.bundleURL.appendingPathComponent("music3.mp3")
#IBAction func pushButton1(sender: UIButton) {
Player(url: url1)
}
#IBAction func pushButton2(sender: UIButton) {
Player1(url: url2)
}
#IBAction func pushButton3(_ sender: UIButton) {
Player2(url: url1, url2: url2, url3: url3)
}
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
if (player === player1) {
yourButton.isSelected = false
} else if (player === player2) {
yourButton2.isSelected = false
} else if (player === player3) {
yourButton.isSelected = false
player2!.play()
yourButton2.isSelected = true
player2!.play()
yourButton3.isSelected = true
player1!.play()
}
}
func Player(url: URL) {
do {
try player1 = AVAudioPlayer(contentsOf:url)
player1!.play()
yourButton.isSelected = true
player1!.delegate = self
} catch {
print(error)
}
}
func Player1(url: URL) {
do {
try player2 = AVAudioPlayer(contentsOf:url)
player2!.play()
yourButton2.isSelected = true
player2!.delegate = self
} catch {
print(error)
}
}
func Player2(url: URL, url2: URL, url3: URL) {
do {
try player3 = AVAudioPlayer(contentsOf:url)
try player2 = AVAudioPlayer(contentsOf: url2)
try player1 = AVAudioPlayer(contentsOf: url3)
player3!.play()
yourButton.isSelected = true
player3!.delegate = self
player2!.delegate = self
player1!.delegate = self
} catch {
print(error)
}
}
I believe that when you press button 4, player 1 should be played first, after that player 2 and after that player 3. For this task, the below code should work.
let url1 = Bundle.main.bundleURL.appendingPathComponent("music1.mp3")
let url2 = Bundle.main.bundleURL.appendingPathComponent("music2.mp3")
let url3 = Bundle.main.bundleURL.appendingPathComponent("music3.mp3")
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
if (player === player1) {
yourButton.isSelected = false
yourButton2.isSelected = true
do {
//initialise player 2
player2 = try AVAudioPlayer(contentsOf: url2)
player2!.delegate = self
}catch {
}
player2!.play()
} else if (player === player2) {
yourButton3.isSelected = true
yourButton2.isSelected = false
do {
//initialise player 3
player3 = try AVAudioPlayer(contentsOf: url3)
player3!.delegate = self
}catch {
}
player3!.play()
} else if (player === player3) {
yourButton3.isSelected = false
}
}
I want to play stream audio from the Internet. I wrote code that plays stream but it don't have any buffer so if signal is weak application stop playing audio. This is my code:
import UIKit
import AVFoundation
import MediaPlayer
import AudioToolbox
class ViewController: UIViewController {
var playerItem:AVPlayerItem?
var player:AVPlayer?
#IBOutlet weak var PlayButton: UIButton!
override func viewDidLoad() {
super.viewDidLoad()
var buffer = AVAudioBuffer ()
let url = NSURL (string: "http://radio.afera.com.pl/afera64.aac")
playerItem = AVPlayerItem(URL: url!)
player = AVPlayer(playerItem: playerItem!)
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
#IBAction func PlayButtonTapped(sender: AnyObject)
{
if ((player!.rate != 0) && (player!.error == nil))
{
player!.pause()
PlayButton.setImage(UIImage(named:"Play"), forState: UIControlState.Normal)
}
else
{
player!.play()
PlayButton.setImage(UIImage(named:"Stop"), forState:UIControlState.Normal)
}
}
}
I have no idea how to buffer this stream. I searched apple documentation but can't find anything in Swift.
I find something like AVAudioBuffer but I don't know how to use it, and if its correct to resolve this problem.
P.S. C# have documentation on MSDN, is something similar on Apple in case of Swift?
The answer is in creating an error delegate that launched a selector every time the player stopped (The error changes when the network connection is interrupted or the stream didn't load properly):
Here are delegates, just outside and above my RadioPlayer class:
protocol errorMessageDelegate {
func errorMessageChanged(newVal: String)
}
class:
import Foundation
import AVFoundation
import UIKit
class RadioPlayer : NSObject {
static let sharedInstance = RadioPlayer()
var instanceDelegate:sharedInstanceDelegate? = nil
var sharedInstanceBool = false {
didSet {
if let delegate = self.instanceDelegate {
delegate.sharedInstanceChanged(self.sharedInstanceBool)
}
}
}
private var player = AVPlayer(URL: NSURL(string: Globals.radioURL)!)
private var playerItem = AVPlayerItem?()
private var isPlaying = false
var errorDelegate:errorMessageDelegate? = nil
var errorMessage = "" {
didSet {
if let delegate = self.errorDelegate {
delegate.errorMessageChanged(self.errorMessage)
}
}
}
override init() {
super.init()
errorMessage = ""
let asset: AVURLAsset = AVURLAsset(URL: NSURL(string: Globals.radioURL)!, options: nil)
let statusKey = "tracks"
asset.loadValuesAsynchronouslyForKeys([statusKey], completionHandler: {
var error: NSError? = nil
dispatch_async(dispatch_get_main_queue(), {
let status: AVKeyValueStatus = asset.statusOfValueForKey(statusKey, error: &error)
if status == AVKeyValueStatus.Loaded{
let playerItem = AVPlayerItem(asset: asset)
self.player = AVPlayer(playerItem: playerItem)
self.sharedInstanceBool = true
} else {
self.errorMessage = error!.localizedDescription
print(error!)
}
})
})
NSNotificationCenter.defaultCenter().addObserverForName(
AVPlayerItemFailedToPlayToEndTimeNotification,
object: nil,
queue: nil,
usingBlock: { notification in
print("Status: Failed to continue")
self.errorMessage = "Stream was interrupted"
})
print("Initializing new player")
}
func resetPlayer() {
errorMessage = ""
let asset: AVURLAsset = AVURLAsset(URL: NSURL(string: Globals.radioURL)!, options: nil)
let statusKey = "tracks"
asset.loadValuesAsynchronouslyForKeys([statusKey], completionHandler: {
var error: NSError? = nil
dispatch_async(dispatch_get_main_queue(), {
let status: AVKeyValueStatus = asset.statusOfValueForKey(statusKey, error: &error)
if status == AVKeyValueStatus.Loaded{
let playerItem = AVPlayerItem(asset: asset)
//playerItem.addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions.New, context: &ItemStatusContext)
self.player = AVPlayer(playerItem: playerItem)
self.sharedInstanceBool = true
} else {
self.errorMessage = error!.localizedDescription
print(error!)
}
})
})
}
func bufferFull() -> Bool {
return bufferAvailableSeconds() > 45.0
}
func bufferAvailableSeconds() -> NSTimeInterval {
// Check if there is a player instance
if ((player.currentItem) != nil) {
// Get current AVPlayerItem
let item: AVPlayerItem = player.currentItem!
if (item.status == AVPlayerItemStatus.ReadyToPlay) {
let timeRangeArray: NSArray = item.loadedTimeRanges
if timeRangeArray.count < 1 { return(CMTimeGetSeconds(kCMTimeInvalid)) }
let aTimeRange: CMTimeRange = timeRangeArray.objectAtIndex(0).CMTimeRangeValue
//let startTime = CMTimeGetSeconds(aTimeRange.end)
let loadedDuration = CMTimeGetSeconds(aTimeRange.duration)
return (NSTimeInterval)(loadedDuration);
}
else {
return(CMTimeGetSeconds(kCMTimeInvalid))
}
}
else {
return(CMTimeGetSeconds(kCMTimeInvalid))
}
}
func play() {
player.play()
isPlaying = true
print("Radio is \(isPlaying ? "" : "not ")playing")
}
func pause() {
player.pause()
isPlaying = false
print("Radio is \(isPlaying ? "" : "not ")playing")
}
func currentlyPlaying() -> Bool {
return isPlaying
}
}
protocol sharedInstanceDelegate {
func sharedInstanceChanged(newVal: Bool)
}
RadioViewController:
import UIKit
import AVFoundation
class RadioViewController: UIViewController, errorMessageDelegate, sharedInstanceDelegate {
// MARK: Properties
var firstErrorSkip = true
var firstInstanceSkip = true
#IBOutlet weak var listenLabel: UILabel!
#IBOutlet weak var radioSwitch: UIImageView!
#IBAction func back(sender: AnyObject) {
print("Dismissing radio view")
if let navigationController = self.navigationController
{
navigationController.popViewControllerAnimated(true)
}
}
#IBAction func switched(sender: AnyObject) {
toggle()
}
override func viewDidLoad() {
super.viewDidLoad()
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback)
print("AVAudioSession Category Playback OK")
do {
try AVAudioSession.sharedInstance().setActive(true)
print("AVAudioSession is Active")
} catch let error as NSError {
print(error.localizedDescription)
}
} catch let error as NSError {
print(error.localizedDescription)
}
RadioPlayer.sharedInstance.errorDelegate = self
RadioPlayer.sharedInstance.instanceDelegate = self
if RadioPlayer.sharedInstance.currentlyPlaying() {
radioSwitch.image = UIImage(named: "Radio_Switch_Active")
listenLabel.text = "Click to Pause Radio Stream:"
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
func toggle() {
if RadioPlayer.sharedInstance.currentlyPlaying() {
pauseRadio()
} else {
playRadio()
}
}
func playRadio() {
firstErrorSkip = false
firstInstanceSkip = false
if RadioPlayer.sharedInstance.errorMessage != "" || RadioPlayer.sharedInstance.bufferFull() {
resetStream()
} else {
radioSwitch.image = UIImage(named: "Radio_Switch_Active")
listenLabel.text = "Click to Pause Radio Stream:"
RadioPlayer.sharedInstance.play()
}
}
func pauseRadio() {
RadioPlayer.sharedInstance.pause()
radioSwitch.image = UIImage(named: "Radio_Switch_Inactive")
listenLabel.text = "Click to Play Radio Stream:"
}
func resetStream() {
print("Reloading interrupted stream");
RadioPlayer.sharedInstance.resetPlayer()
//RadioPlayer.sharedInstance = RadioPlayer();
RadioPlayer.sharedInstance.errorDelegate = self
RadioPlayer.sharedInstance.instanceDelegate = self
if RadioPlayer.sharedInstance.bufferFull() {
radioSwitch.image = UIImage(named: "Radio_Switch_Active")
listenLabel.text = "Click to Pause Radio Stream:"
RadioPlayer.sharedInstance.play()
} else {
playRadio()
}
}
func errorMessageChanged(newVal: String) {
if !firstErrorSkip {
print("Error changed to '\(newVal)'")
if RadioPlayer.sharedInstance.errorMessage != "" {
print("Showing Error Message")
let alertController = UIAlertController(title: "Stream Failure", message: RadioPlayer.sharedInstance.errorMessage, preferredStyle: UIAlertControllerStyle.Alert)
alertController.addAction(UIAlertAction(title: "Dismiss", style: UIAlertActionStyle.Default, handler: nil))
self.presentViewController(alertController, animated: true, completion: nil)
pauseRadio()
}
} else {
print("Skipping first init")
firstErrorSkip = false
}
}
func sharedInstanceChanged(newVal: Bool) {
if !firstInstanceSkip {
print("Detected New Instance")
if newVal {
RadioPlayer.sharedInstance.play()
}
} else {
firstInstanceSkip = false
}
}
}
Hope this will help :)
change
playerItem = AVPlayerItem?()
to
playerItem:AVPlayerItem?