AVPlayer, how to save the current recording time - ios

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
}
}
}

Related

Share Extension don't open/share .HEIC type image

i created share extension for my app which can share image/videos from photos(Gallery) and it work properly but here problem is if i select .HEIC type file then click on my share extension it show error as shown in below screenshot .
import UIKit
import Social
import MobileCoreServices
import AVKit
import Toast_Swift
//#objc(ShareExtensionViewController)
class ShareViewController: UIViewController {
#IBOutlet weak var image: UIImageView!
#IBOutlet weak var btn: UIButton!
#IBOutlet weak var lbl: UILabel!
#IBOutlet weak var view2: UIView!
#IBOutlet weak var cancel: UIButton!
#IBOutlet weak var blurview: UIVisualEffectView!
var allMedia = [Data()]
var singleImage = UIImage()
var imagesUrl = [URL]()
let groupPath = FileManager.default.containerURL(forSecurityApplicationGroupIdentifier: "
my group identifire ")
var saveDone = Bool()
override func viewDidLoad() {
super.viewDidLoad()
let attachments = (self.extensionContext?.inputItems.first as? NSExtensionItem)?.attachments ?? []
self.btn.setTitle("Add", for: .normal)
self.btn.layer.cornerRadius = 10
self.cancel.layer.cornerRadius = 10
self.image.clipsToBounds = false
applyshadow(image: image)
blurview.isHidden = true
}
override func viewDidAppear(_ animated: Bool) {
self.handleSharedFile()
}
// MARK:- function
func applyshadow(image: UIImageView) {
image.clipsToBounds = false
image.layer.shadowColor = UIColor.systemGray.cgColor
image.layer.shadowOpacity = 1
image.layer.shadowOffset = CGSize.zero
image.layer.shadowRadius = 7.5
}
func videotext(string : String) ->Bool{
let videoextension = [".MP4",".mp4",".mkv",".MKV",".AVI",".avi",".mov", ".MOV"]
if videoextension.contains(string){
return true
}
return false
}
func videoThumb(filepath: URL) -> UIImage{
do{
// let filepath = data?.appendingPathComponent(lbl[indexPath.row])
let asset = AVURLAsset(url: filepath, options: nil)
let imgGenerator = AVAssetImageGenerator(asset: asset)
imgGenerator.appliesPreferredTrackTransform = true
let cgImage = try imgGenerator.copyCGImage(at: CMTimeMake(value: 0, timescale: 1), actualTime: nil)
let uiImage = UIImage(cgImage: cgImage)
return uiImage
}catch let error {
print("Error: \(error.localizedDescription)")
return UIImage()
}
}
private func handleSharedFile() {
// extracting the path to the URL that is being shared
let attachments = (self.extensionContext?.inputItems.first as? NSExtensionItem)?.attachments ?? []
let contentType = kUTTypeData as String
for provider in attachments {
// Check if the content type is the same as we expected
if provider.hasItemConformingToTypeIdentifier(contentType) {
provider.loadItem(forTypeIdentifier: contentType,
options: nil) { [unowned self] (data, error) in
guard error == nil else { return }
if let url = data as? URL {
imagesUrl.append(url)
print(imagesUrl)
if videotext(string: String(url.lastPathComponent.suffix(4))){
DispatchQueue.main.async {
self.image.image = videoThumb(filepath: url)
if attachments.count > 1 {
lbl.text = "\(attachments.count) Items"
}else{
lbl.text = url.lastPathComponent
}
}
}else {
let imageData = try? Data(contentsOf: url)
DispatchQueue.main.async {
if provider == attachments.last{
self.image.image = UIImage(data: imageData!)
}
self.singleImage = UIImage(data: imageData!)!
if attachments.count > 1 {
lbl.text = "\(attachments.count) Items"
}else{
lbl.text = url.lastPathComponent
}
}
}
}else {
print("Impossible to save image")
}
}
}
}
}
thank You in advance for read, giving attention, answer and upvote my question :)

How to fix AVAudioPlayer issue in my code?

Thanks for visiting this question.
I recently started learning swift. I'm making an audio player ish app.
The nextButton works only for a couple of times, and then it just becomes silent. I'm trying to make an array loop over and over if nextButton is being pressed. So, it means when I press the "next" button, it will go to the next track and if tracks from array finished, it will play all track over again.
All I'm trying to achieve it to get the "next" button work without any stops.
Array and Audio Player:
let sound1 = NSURL(fileURLWithPath: Bundle.main.path(forResource: "1", ofType: "mp3")!)
let sound2 = NSURL(fileURLWithPath: Bundle.main.path(forResource: "2", ofType: "mp3")!)
let sound3 = NSURL(fileURLWithPath: Bundle.main.path(forResource: "3", ofType: "mp3")!)
lazy var soundArray: [NSURL] = [sound1, sound2, sound3]
var audioPlayer = AVAudioPlayer()
var run = true
var currentIndex = 0
func playRandomSound() {
let randNo = Int(arc4random_uniform(UInt32(soundArray.count)))
if run == true{
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback)
try AVAudioSession.sharedInstance().setActive(true)
try audioPlayer = AVAudioPlayer(contentsOf: soundArray[randNo] as URL)
audioPlayer.prepareToPlay()
audioPlayer.play()
} catch {
print(error)
}
}
}
nextButton:
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
currentIndex = indexPath.row
}
#IBAction func nextButton(_ sender: Any) {
audioPlayer.stop()
print("Audio did stop")
if currentIndex + 1 < soundArray.count {
currentIndex += 1
playRandomSound()
}
}
Full Code:
import UIKit
import AVFoundation
extension UILabel {
func pushUp(_ text: String?) {
let animation:CATransition = CATransition()
animation.timingFunction = CAMediaTimingFunction(name: CAMediaTimingFunctionName.easeInEaseOut)
animation.type = CATransitionType.push
animation.subtype = CATransitionSubtype.fromTop
animation.duration = 1
self.layer.add(animation, forKey: CATransitionType.push.rawValue)
self.text = text
}
}
class HideShow: UIViewController, AVAudioPlayerDelegate{
override func viewDidLoad() {
super.viewDidLoad()
}
#IBOutlet weak var moveOn: UILabel!
#IBOutlet weak var Logo: UILabel!
#IBOutlet weak var BGimage: UIImageView!
#IBOutlet weak var nextButton2: UIButton!
#IBOutlet weak var nextButton: UIButton!
#IBOutlet weak var LayDown: UILabel!
#IBOutlet weak var audioChanger: UIButton!
#IBOutlet weak var buttonView: UIView!
let notification = NotificationCenter.default
let toImage = UIImage(named:"BG2.jpeg")
#IBAction func buttonTapped(_ sender: UIButton){
UIView.animate(withDuration: 2) {
self.LayDown.alpha = 1
}
UIView.transition(with: self.BGimage,
duration: 1,
options: .transitionCrossDissolve,
animations: { self.BGimage.image = self.toImage },
completion: nil)
LayDown.pushUp("CLOSE YOUR EYES")
self.nextButton.isHidden = true
self.nextButton2.isHidden = false
}
let sound1 = NSURL(fileURLWithPath: Bundle.main.path(forResource: "1", ofType: "mp3")!)
let sound2 = NSURL(fileURLWithPath: Bundle.main.path(forResource: "2", ofType: "mp3")!)
let sound3 = NSURL(fileURLWithPath: Bundle.main.path(forResource: "3", ofType: "mp3")!)
lazy var soundArray: [NSURL] = [sound1, sound2, sound3]
var audioPlayer = AVAudioPlayer()
var run = true
var currentIndex = 0
func playRandomSound() {
let randNo = Int(arc4random_uniform(UInt32(soundArray.count)))
if run == true{
do {
try AVAudioSession.sharedInstance().setCategory(AVAudioSession.Category.playback)
try AVAudioSession.sharedInstance().setActive(true)
try audioPlayer = AVAudioPlayer(contentsOf: soundArray[randNo] as URL)
audioPlayer.prepareToPlay()
audioPlayer.play()
} catch {
print(error)
}
}
}
#IBAction func buttonTapped2(_ sender: Any) {
UIView.animate(withDuration: 1) {
self.nextButton2.alpha = 0
self.LayDown.alpha = 0
self.nextButton.alpha = 0
self.BGimage.alpha = 0
self.moveOn.alpha = 0
self.Logo.alpha = 0
self.audioChanger.isHidden = false
self.buttonView.isHidden = false
}
playRandomSound()
}
func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) {
currentIndex = indexPath.row
}
#IBAction func nextButton(_ sender: Any) {
audioPlayer.stop()
print("Audio did stop")
if currentIndex + 1 < soundArray.count {
currentIndex += 1
playRandomSound()
}
}
}
Let me know if you have any questions,
Thanks in advance!:)

How to manage the AVAudioPlayer isPlaying in each tableviewCell?

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

Avplayer is created 2 times

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)
}
}

How to automatically go to the next track when the one is currently playing ends?

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
}
}

Resources