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)
}
}
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 tableview and have multiple cells in tableView.
Each cell has an item of AVAudioPlayer
And I face a problem.
I don't know how to manage the AVAudioPlayer.
When I play first AVAudioPlayer, and then play second AVAudioPlayer, the sound will overlap.
How to stop first AVAudioPlayer in my customized cell, and play second AVAudioPlayer?
Thanks.
This is my customized cell:
class TableViewCell: UITableViewCell {
#IBOutlet weak var myImageView: UIImageView!
#IBOutlet weak var myChatBubbleView: UIView!
#IBOutlet weak var myDateLabel: UILabel!
#IBOutlet weak var mySecondLabel: UILabel!
#IBOutlet weak var myRecordPlayerBtn: MenuButton!
private var timer:Timer?
private var elapsedTimeInSecond:Int = 0
var audioPlayer:AVAudioPlayer?
var message:ChatroomMessage?
var chatroomId:String = ""
var delegate:PlayRecordDelegate?
override func awakeFromNib() {
super.awakeFromNib()
// Initialization code
self.backgroundColor = defaultBackgroundColor
self.tintColor = defaultChatroomCheckButtonColor
myImageView.layer.masksToBounds = true
myImageView.layer.cornerRadius = defaultIconRadius
myChatBubbleView.backgroundColor = defaultChatGreenBubbleColor
myChatBubbleView.layer.cornerRadius = defaultButtonRadius
myDateLabel.textColor = defaultChatTimeColor
mySecondLabel.textColor = defaultChatTimeColor
mySecondLabel.isHidden = true
myRecordPlayerBtn.imageView?.animationDuration = 1
myRecordPlayerBtn.imageView?.animationImages = [
UIImage(named: "img_myRocordPlaying1")!,
UIImage(named: "img_myRocordPlaying2")!,
UIImage(named: "img_myRocordPlaying3")!
]
}
override func setSelected(_ selected: Bool, animated: Bool) {
super.setSelected(selected, animated: animated)
// Configure the view for the selected state
}
func loadByMessage(_ message:ChatroomMessage, chatroomId:String) {
self.message = message
self.chatroomId = chatroomId
myRecordPlayerBtn.addTarget(self, action: #selector(recordPlay), for: .touchUpInside)
}
func resetRecordAnimation() {
self.myRecordPlayerBtn.imageView!.stopAnimating()
self.myRecordPlayerBtn.isSelected = false
}
func recordPlay(_ sender: UIButton) {
self.myRecordPlayerBtn.imageView?.startAnimating()
let documentsDirectoryURL = try! FileManager().url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent("\(chatroomId)/Record/")
let fileName = message?.content.substring(from: 62)
let fileURL = documentsDirectoryURL.appendingPathComponent(fileName!)
if FileManager.default.fileExists(atPath: fileURL.path) {
let asset = AVURLAsset(url: URL(fileURLWithPath: fileURL.path), options: nil)
let audioDuration = asset.duration
let audioDurationSeconds = CMTimeGetSeconds(audioDuration)
self.elapsedTimeInSecond = Int(audioDurationSeconds)
if audioPlayer?.isPlaying == true {
audioPlayer?.stop()
DispatchQueue.main.async {
self.resetTimer(second: self.elapsedTimeInSecond)
self.startTimer()
}
}
updateTimeLabel()
startTimer()
audioPlayer = try? AVAudioPlayer(contentsOf: fileURL)
audioPlayer?.delegate = self
audioPlayer?.play()
}else{
//don't have file in local
let recordUrl = URL(string: (message?.content)!)
URLSession.shared.downloadTask(with: recordUrl!, completionHandler: { (location, response, error) in
guard
let httpURLResponse = response as? HTTPURLResponse, httpURLResponse.statusCode == 200,
let mimeType = response?.mimeType, mimeType.hasPrefix("audio"),
let location = location, error == nil
else { return }
do {
try FileManager.default.moveItem(at: location, to: fileURL)
let asset = AVURLAsset(url: URL(fileURLWithPath: fileURL.path), options: nil)
let audioDuration = asset.duration
let audioDurationSeconds = CMTimeGetSeconds(audioDuration)
self.elapsedTimeInSecond = Int(audioDurationSeconds)
DispatchQueue.main.async {
self.updateTimeLabel()
self.startTimer()
}
self.audioPlayer = try? AVAudioPlayer(contentsOf: fileURL)
self.audioPlayer?.delegate = self
self.audioPlayer?.play()
} catch {
print(error)
}
}).resume()
}
}
func startTimer() {
timer?.invalidate()
timer = Timer.scheduledTimer(withTimeInterval: 1.0, repeats: true, block: { (timer) in
self.elapsedTimeInSecond -= 1
self.updateTimeLabel()
})
}
func resetTimer(second:Int) {
timer?.invalidate()
elapsedTimeInSecond = second
updateTimeLabel()
}
func updateTimeLabel() {
let seconds = elapsedTimeInSecond % 60
let minutes = (elapsedTimeInSecond/60) % 60
mySecondLabel.isHidden = false
mySecondLabel.text = String(format: "%02d:%02d", minutes,seconds)
}
}
extension TableViewCell:AVAudioPlayerDelegate {
func audioPlayerDidFinishPlaying(_ player: AVAudioPlayer, successfully flag: Bool) {
let documentsDirectoryURL = try! FileManager().url(for: .documentDirectory, in: .userDomainMask, appropriateFor: nil, create: true).appendingPathComponent("\(Id)/Record/")
let fileName = message?.content.substring(from: 62)
let fileURL = documentsDirectoryURL.appendingPathComponent(fileName!)
if FileManager.default.fileExists(atPath: fileURL.path) {
let asset = AVURLAsset(url: URL(fileURLWithPath: fileURL.path), options: nil)
let audioDuration = asset.duration
let audioDurationSeconds = CMTimeGetSeconds(audioDuration)
DispatchQueue.main.async {
self.resetTimer(second: Int(audioDurationSeconds))
self.myRecordPlayerBtn.imageView!.stopAnimating()
self.myRecordPlayerBtn.imageView?.image = #imageLiteral(resourceName: "img_myRocordDefault")
}
}
}
}
Probably first initialize to check if your player is playing
if audioPlayer != nil{
if audioPlayer?.isPlaying == true {
audioPlayer?.stop()
DispatchQueue.main.async {
self.resetTimer(second: self.elapsedTimeInSecond)
self.startTimer()
}
}
}
If you don't want to play two audio track at the same time, you should use a shared instance of AVAudioPlayer
It will be better for performances and you can define the instance as static var in your controller. It will be accessible in each cell.
I have developed a music palyer application, and I used a shared instance in the MusicPlayManager:
class MusicPlayManager{
var player : AVAudioPlayer?
static let sharedInstance = MusicPlayManager.init()
private override init() {
super.init()
}
// something else, such as palyNext, playPrevious methods
}
In your viewController,you can use MusicPlayManager.sharedInstance.player
When I press pause and then play, the music playing again how to solve this problem?
I know that I need to use seekTime(: CMTime), but I don't get
Gif
It's all code
class ViewControllerAudioDetail: UIViewController {
#IBOutlet var list: TableViewControllerAudioList!
#IBOutlet weak var playChange: UIButton!
#IBOutlet weak var timeAudio: UILabel!
#IBOutlet weak var playbackSlider: UISlider!
var player = AVPlayer()
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "БИБЛИОТЕКА"
}
#IBAction func play(_ sender: Any) {
if player.rate == 0 {
let url = URL(string: ViewControllerAudioDetail.urlAudio[0])
let playerItem = AVPlayerItem(url: url!)
player = AVPlayer(playerItem:playerItem)
player.rate = 1.0;
player.play()
playChange.setImage(UIImage(named:"Pause.png"), for: UIControlState.normal)
let duration : CMTime = playerItem.asset.duration
let seconds : Float64 = CMTimeGetSeconds(duration)
playbackSlider.tintColor = UIColor.green
let _ = player.addPeriodicTimeObserver(forInterval: CMTime(seconds: 1, preferredTimescale: CMTimeScale(NSEC_PER_SEC)), queue: DispatchQueue.main) { [weak self] (time) in
self?.playbackSlider.value = Float(CMTimeGetSeconds(time)) / Float(seconds)
}
} else {
playChange.setImage(UIImage(named:"Play.png"), for: UIControlState.normal)
player.pause()
}
}
#IBAction func audioPlaybackSlider(_ sender: Any) {
//перемотка аудиозвука
let duration : CMTime = player.currentItem!.duration
let totalDuration : Float64 = CMTimeGetSeconds(duration)
let value = self.playbackSlider.value
let durationToSeek = Float(totalDuration) * value
player.seek(to: CMTimeMakeWithSeconds(Float64(durationToSeek),player.currentItem!.duration.timescale)) { [](state) in
}
}
}
The problem in your code is, you always create a new player object inside your play method. That is why it always starts from the beginning of the audio.
This should solve your problem
class ViewControllerAudioDetail: UIViewController {
#IBOutlet var list: TableViewControllerAudioList!
#IBOutlet weak var playChange: UIButton!
#IBOutlet weak var timeAudio: UILabel!
#IBOutlet weak var playbackSlider: UISlider!
var player:AVPlayer?
override func viewDidLoad() {
super.viewDidLoad()
navigationItem.title = "БИБЛИОТЕКА"
setupAudioPlayer()
}
func setupAudioPlayer(){
let url = URL(string: ViewControllerAudioDetail.urlAudio[0])
let playerItem = AVPlayerItem(url: url!)
player = AVPlayer(playerItem:playerItem)
playbackSlider.tintColor = UIColor.green
let _ = player!.addPeriodicTimeObserver(forInterval: CMTime(seconds: 1, preferredTimescale: CMTimeScale(NSEC_PER_SEC)), queue: DispatchQueue.main) { [weak self] (time) in
self?.updateSlider(time: time)
}
}
func updateSlider(time:CMTime){
let duration = CMTimeGetSeconds(player!.currentItem!.asset.duration)
self.playbackSlider.value = Float(CMTimeGetSeconds(time)) / Float(duration)
}
#IBAction func play(_ sender: Any) {
if player?.rate == 0 {
player?.rate = 1.0;
player?.play()
playChange.setImage(UIImage(named:"Pause.png"), for: UIControlState.normal)
} else {
playChange.setImage(UIImage(named:"Play.png"), for: UIControlState.normal)
player?.pause()
}
}
#IBAction func audioPlaybackSlider(_ sender: Any) {
//перемотка аудиозвука
let duration = CMTimeGetSeconds(player!.currentItem!.asset.duration)
let value = self.playbackSlider.value
let durationToSeek = Float(duration) * value
self.player?.seek(to: CMTimeMakeWithSeconds(Float64(durationToSeek),player!.currentItem!.duration.timescale)) { [](state) in
}
}
}
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
}
I'm quite new to swift and I want to implement a piece of code that helps me reach the next song when the one that is currently playing ends.
I tried to copy the code inside my "#IBAction func nextAction" (which works fine):
#IBAction func nextAction(sender: AnyObject) {
self.nextTrack()
}
func nextTrack() {
if trackId == 0 || trackId < 4 {
if shuffle.on {
trackId = Int(arc4random_uniform(UInt32(library.count)))
}else {
trackId += 1
}
if let coverImage = library[trackId]["coverImage"]{
coverImageView.image = UIImage(named: "\(coverImage).jpg")
}
songTitleLabel.text = library[trackId]["title"]
artistLabel.text = library[trackId]["artist"]
audioPlayer.currentTime = 0
progressView.progress = 0
let path = NSBundle.mainBundle().pathForResource("\(trackId)", ofType: "mp3")
if let path = path {
let mp3URL = NSURL(fileURLWithPath: path)
do {
audioPlayer = try AVAudioPlayer(contentsOfURL: mp3URL)
audioPlayer.play()
NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(PlayerViewController.updateProgressView), userInfo: nil, repeats: true)
progressView.setProgress(Float(audioPlayer.currentTime/audioPlayer.duration), animated: false)
} catch let error as NSError {
print(error.localizedDescription)
}
}
}
}
And tried to put it inside an if condition like this (inside the viewDidLoad):
if audioPlayer.currentTime >= audioPlayer.duration {
self.nextTrack()
}
I don't have any errors but at runtime this method isn't working and the song ends without playing the next one.
To make the situation more clear here's my controller:
import UIKit
import AVFoundation
class PlayerViewController: UIViewController {
#IBOutlet weak var coverImageView: UIImageView!
#IBOutlet weak var progressView: UIProgressView!
#IBOutlet weak var songTitleLabel: UILabel!
#IBOutlet weak var artistLabel: UILabel!
#IBOutlet weak var shuffle: UISwitch!
var trackId: Int = 0
var library = MusicLibrary().library
var audioPlayer: AVAudioPlayer!
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view.
if let coverImage = library[trackId]["coverImage"]{
coverImageView.image = UIImage(named: "\(coverImage).jpg")
}
songTitleLabel.text = library[trackId]["title"]
artistLabel.text = library[trackId]["artist"]
let path = NSBundle.mainBundle().pathForResource("\(trackId)", ofType: "mp3")
if let path = path {
let mp3URL = NSURL(fileURLWithPath: path)
do {
audioPlayer = try AVAudioPlayer(contentsOfURL: mp3URL)
audioPlayer.play()
NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(PlayerViewController.updateProgressView), userInfo: nil, repeats: true)
progressView.setProgress(Float(audioPlayer.currentTime/audioPlayer.duration), animated: false)
} catch let error as NSError {
print(error.localizedDescription)
}
}
}
override func viewWillDisappear(animated: Bool) {
audioPlayer.stop()
}
func updateProgressView(){
if audioPlayer.playing {
progressView.setProgress(Float(audioPlayer.currentTime/audioPlayer.duration), animated: true)
}
}
#IBAction func playAction(sender: AnyObject) {
if !audioPlayer.playing {
audioPlayer.play()
}
}
#IBAction func stopAction(sender: AnyObject) {
audioPlayer.stop()
audioPlayer.currentTime = 0
progressView.progress = 0
}
#IBAction func pauseAction(sender: AnyObject) {
audioPlayer.pause()
}
#IBAction func fastForwardAction(sender: AnyObject) {
var time: NSTimeInterval = audioPlayer.currentTime
time += 5.0
if time > audioPlayer.duration {
stopAction(self)
}else {
audioPlayer.currentTime = time
}
}
#IBAction func rewindAction(sender: AnyObject) {
var time: NSTimeInterval = audioPlayer.currentTime
time -= 5.0
if time < 0 {
stopAction(self)
}else {
audioPlayer.currentTime = time
}
}
#IBAction func previousAction(sender: AnyObject) {
if trackId != 0 || trackId > 0 {
if shuffle.on {
trackId = Int(arc4random_uniform(UInt32(library.count)))
}else {
trackId -= 1
}
if let coverImage = library[trackId]["coverImage"]{
coverImageView.image = UIImage(named: "\(coverImage).jpg")
}
songTitleLabel.text = library[trackId]["title"]
artistLabel.text = library[trackId]["artist"]
audioPlayer.currentTime = 0
progressView.progress = 0
let path = NSBundle.mainBundle().pathForResource("\(trackId)", ofType: "mp3")
if let path = path {
let mp3URL = NSURL(fileURLWithPath: path)
do {
audioPlayer = try AVAudioPlayer(contentsOfURL: mp3URL)
audioPlayer.play()
NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(PlayerViewController.updateProgressView), userInfo: nil, repeats: true)
progressView.setProgress(Float(audioPlayer.currentTime/audioPlayer.duration), animated: false)
} catch let error as NSError {
print(error.localizedDescription)
}
}
}
}
#IBAction func swipeDownAction(sender: AnyObject) {
self.close()
}
#IBAction func closeAction(sender: AnyObject) {
self.close()
}
#IBAction func nextAction(sender: AnyObject) {
self.nextTrack()
}
func close() {
self.dismissViewControllerAnimated(true, completion: nil)
}
func nextTrack() {
if trackId == 0 || trackId < 4 {
if shuffle.on {
trackId = Int(arc4random_uniform(UInt32(library.count)))
}else {
trackId += 1
}
if let coverImage = library[trackId]["coverImage"]{
coverImageView.image = UIImage(named: "\(coverImage).jpg")
}
songTitleLabel.text = library[trackId]["title"]
artistLabel.text = library[trackId]["artist"]
audioPlayer.currentTime = 0
progressView.progress = 0
let path = NSBundle.mainBundle().pathForResource("\(trackId)", ofType: "mp3")
if let path = path {
let mp3URL = NSURL(fileURLWithPath: path)
do {
audioPlayer = try AVAudioPlayer(contentsOfURL: mp3URL)
audioPlayer.play()
NSTimer.scheduledTimerWithTimeInterval(1.0, target: self, selector: #selector(PlayerViewController.updateProgressView), userInfo: nil, repeats: true)
progressView.setProgress(Float(audioPlayer.currentTime/audioPlayer.duration), animated: false)
} catch let error as NSError {
print(error.localizedDescription)
}
}
}
}
}
All the code is written in Xcode 7.3.1
You should use AVAudioPlayer delegate method audioPlayerDidFinishPlaying(player: AVAudioPlayer, successfully flag: Bool) which is called when the audio player finishes playing a sound.
Make your PlayerViewController confirm to AVAudioPlayerDelegate protocol like this:
class PlayerViewController: UIViewController, AVAudioPlayerDelegate {
Make sure to set self as the delegate of the audioPlayer you create, to do that in your viewDidLoad,previousAction and nextTrack method you need to add
audioPlayer.delegate = self
after this line:
audioPlayer = try AVAudioPlayer(contentsOfURL: mp3URL)
Now you can use the delegate method to know when the audio is finished playing and go to the next track, just add this inside your class:
func audioPlayerDidFinishPlaying(player: AVAudioPlayer, successfully flag: Bool) {
if flag {
self.nextTrack()
} else {
// did not finish successfully
}
}