How to play next track from Document Directory in swift - ios

I make a simple media player. I have a UITableViewController with mp3 files from Document Directory and UIViewController which plays mp3 files. I pass NSURL of a mp3 file from UITableViewController to UIViewController and I can play it. I want to make buttons are which will be turn track to next or previous. How can I make it?
The code is passing NSURL on a specific file.
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var playerVC = (segue.destinationViewController as! UINavigationController).topViewController as! PlayMusicViewController
var indexPath = tableView.indexPathForSelectedRow()
var nameOfObjectForPass = listOfMP3Files![indexPath!.row] // default it's name and
var fileManager = NSFileManager.defaultManager()
var wayToFile = fileManager.URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask)
var passMusicFileURL: NSURL? // for pass mp3
if let documentPath: NSURL = wayToFile.first as? NSURL {
let musicFile = documentPath.URLByAppendingPathComponent(nameOfObjectForPass)
println(musicFile)
passMusicFileURL = musicFile
}
if segue.identifier == "listenMusic" {
playerVC.nameMusicFile = nameOfObjectForPass // name
playerVC.mp3URL = passMusicFileURL
// test
playerVC.allUrlsVC = allURLS
}
The code is playing mp3 file
func playMusic() {
AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, error: nil) // == true
AVAudioSession.sharedInstance().setActive(true, error: nil)
var error: NSError?
audioPlayer = AVAudioPlayer(contentsOfURL: mp3URL, error: &error)
// audioPlayer.prepareToPlay()
if currentPause == nil {
} else {
audioPlayer.currentTime = currentPause
}
//
audioPlayer.volume = 0.5
audioPlayer.play()
}
UPDATE
I made following
var arrayMP3url: Array<AnyObject>!
func playNextSound() {
var queue = AVQueuePlayer(URL: mp3URL)
var current = queue.currentItem
var arrayForSearch = arrayMP3url as! [NSURL]
var arrNS = arrayForSearch as NSArray
var index = arrNS.indexOfObject(mp3URL!)
println("index \(index)")
println("array for search \(arrayForSearch)")
println("current song \(current)")
}
But I always get the same index from NSArray

Use your array of mp3 files, and determine current playing index using indexPath, increase or, decrease the current playing index on button click as Next, or, Prev, and change the mp3 file by the index.
Replay the music.
Simple.
Ping if you need more help.
Update
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var playerVC = (segue.destinationViewController as! UINavigationController).topViewController as! PlayMusicViewController
var indexPath = tableView.indexPathForSelectedRow();
//We only need to pass current index, and array of mp3 urls
playerVC.curIndex=indexPath.row;
playerVC.mp3s= listOfMP3Files;
}
So as described above, just declare two properties in your playerVC, ie
curIndex as int and mp3s as NSArray.
In the function
func playMusic() {
var nameOfObjectForPass = mp3s![curIndex]; // default it's name and
var fileManager = NSFileManager.defaultManager()
var wayToFile = fileManager.URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask)
var passMusicFileURL: NSURL? // for pass mp3
if let documentPath: NSURL = wayToFile.first as? NSURL {
let musicFile = documentPath.URLByAppendingPathComponent(nameOfObjectForPass)
println(musicFile)
passMusicFileURL = musicFile
}
self.nameMusicFile = nameOfObjectForPass // name
self.mp3URL = passMusicFileURL
// test
self.allUrlsVC = allURLS
AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, error: nil) // == true
AVAudioSession.sharedInstance().setActive(true, error: nil)
var error: NSError?
audioPlayer = AVAudioPlayer(contentsOfURL: mp3URL, error: &error)
// audioPlayer.prepareToPlay()
if currentPause == nil {
} else {
audioPlayer.currentTime = currentPause
}
//
audioPlayer.volume = 0.5
audioPlayer.play()
}
Then simply define your next and previous index calculations like
func playNextSound(){
self.curIndex++;
var maxCount=self.mp3s.count-1;
if(self.curIndex>maxCount){
self.curIndex=maxCount;
}
playMusic();
}
func playPrevSound(){
self.curIndex--;
if(self.curIndex<0){
self.curIndex=0;
}
playMusic();
}
Hope it helps. Cheers.

Related

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

ios swift play video uploaded from cloudkit

I am trying to play a video downloaded from cloudkit. I use the same query method that I use for downloading image:
publicData.performQuery(query, inZoneWithID: nil) { results, error in
if error == nil { // There is no error
for cafe in results! {
let newCafe = Cafe()
newCafe.address = cafe["address"] as? String
newCafe.name = cafe["name"] as? String
newCafe.email = cafe["email"] as? String
newCafe.description = cafe["description"] as? String
newCafe.location = cafe["location"] as? CLLocation
newCafe.cafeImage = cafe["cafeImage"] as? CKAsset
newCafe.offer_wifi = cafe["offer_wifi"] as? Bool
newCafe.smoking_area = cafe["smoking_area"] as? Bool
newCafe.cafeVideo = cafe["video"] as? CKAsset // <== I want to use this
self.cafes.append(newCafe)
let defaults: NSUserDefaults = NSUserDefaults.standardUserDefaults()
defaults.setInteger(self.cafes.count, forKey: "PreviousCafeCount")
dispatch_async(dispatch_get_main_queue(), { () -> Void in
self.tableView.reloadData()
Spinner.sharedLoader.hideLoading()
})
inside the cafeDetailViewController, I create a button that trigger playing a video using AVPlayer. AVKit and AVFoundation are already imported.
#IBAction func playVideo(sender: AnyObject) {
if let file = cafeDetail.cafeVideo {
let player = AVPlayer(URL: file.fileURL)
let playerController = AVPlayerViewController()
playerController.player = player
self.addChildViewController(playerController)
self.view.addSubview(playerController.view)
playerController.view.frame = self.view.frame
player.play()
}
}
However the result is this:
follow up question: how can I implement model association in swift? Similar to has_many and belongs_to association in rails. I don't think downloading the whole video beforehand is a good solution.
From what I can see, you need to save the video to a local file and then play that file. This is modified from something I wrote to mess around with CloudKit.
import UIKit
import CloudKit
import AVKit
import AVFoundation
class CloudKitTestViewController: UIViewController {
let publicDatabase = CKContainer.defaultContainer().publicCloudDatabase
var videoURL: NSURL!
#IBAction func load(sender: AnyObject) {
let predicate = NSPredicate(format: "videoName = %#", "nameOfVideoGoesHere")
activityIndicator.startAnimating()
let query = CKQuery(recordType: "Videos", predicate: predicate)
publicDatabase.performQuery(query, inZoneWithID: nil) { (results, error) in
if error != nil {
dispatch_async(dispatch_get_main_queue()) {
self.notifyUser("Cloud Access Error",
message: error!.localizedDescription)
}
} else {
if results!.count > 0 {
let record = results![0]
dispatch_async(dispatch_get_main_queue()) {
!)
let video = record.objectForKey("videoVideo") as! CKAsset
self.videoURL = video.fileURL
let videoData = NSData(contentsOfURL: self.videoURL!)
let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0]
let destinationPath = NSURL(fileURLWithPath: documentsPath).URLByAppendingPathComponent("filename.mov", isDirectory: false)
NSFileManager.defaultManager().createFileAtPath(destinationPath.path!, contents:videoData, attributes:nil)
self.videoURL = destinationPath
print(self.videoURL)
}
} else {
dispatch_async(dispatch_get_main_queue()) {
self.notifyUser("No Match Found",
message: "No record matching the address was found")
}
}
}
dispatch_async(dispatch_get_main_queue(), {
self.activityIndicator.stopAnimating()
})
}
}
override func prepareForSegue(segue: UIStoryboardSegue,
sender: AnyObject?) {
let destination = segue.destinationViewController as!
AVPlayerViewController
let url = videoURL
print(videoURL)
destination.player = AVPlayer(URL: url!)
}
}

How to change NSIndexPath in another UIViewController in swift

I get NSIndexPath array from UITableViewController and NSIndexPath of a specific file. I get mp3 files from the document folder in NSIndexPath array and I get a specific file from the selected row from UITableViewCell in a NSIndexPath file.
I want to switch mp3 files for it need change NSIndexPath in UIViewController is which plays music. Or is there different methods are which switching mp3 files from document folder?
The UITableViewController code
override func prepareForSegue(segue: UIStoryboardSegue, sender: AnyObject?) {
var playerVC = (segue.destinationViewController as! UINavigationController).topViewController as! PlayMusicViewController
var indexPath = tableView.indexPathForSelectedRow()
var nameOfObjectForPass = listOfMP3Files![indexPath!.row] // default it's name and
var fileManager = NSFileManager.defaultManager()
var wayToFile = fileManager.URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask)
var passMusicFileURL: NSURL? // for pass mp3
if let documentPath: NSURL = wayToFile.first as? NSURL {
let musicFile = documentPath.URLByAppendingPathComponent(nameOfObjectForPass)
println(musicFile)
passMusicFileURL = musicFile
}
var currentTrackIndex = tableView.indexPathForSelectedRow()!
var allIndexTable = tableView.indexPathsForVisibleRows()! as! [NSIndexPath]
if segue.identifier == "listenMusic" {
playerVC.nameMusicFile = nameOfObjectForPass // name
playerVC.mp3URL = passMusicFileURL
// test
playerVC.currentIndex = currentTrackIndex
playerVC.allIndex = allIndexTable
}
}
The UIViewController code
var currentIndex: NSIndexPath!
var allIndex = [AnyObject]() as! [NSIndexPath]
func playNextSound() {
println("index \(currentIndex)")
println("all object \(allIndex)")
var indextFound = find(allIndex, currentIndex)
println("index found \(indextFound)")
}
The code is which plays music
func playMusic() {
AVAudioSession.sharedInstance().setCategory(AVAudioSessionCategoryPlayback, error: nil) // == true
AVAudioSession.sharedInstance().setActive(true, error: nil)
var error: NSError?
audioPlayer = AVAudioPlayer(contentsOfURL: mp3URL, error: &error)
// audioPlayer.prepareToPlay()
if currentPause == nil {
} else {
audioPlayer.currentTime = currentPause
}
//
audioPlayer.volume = 0.5
audioPlayer.play()
}
I get following results in the console
index {length = 2, path = 0 - 0} it is current object
it is all object from document folder
all object [ {length = 2, path = 0 - 0}, {length = 2, path = 0 - 1}]
var newURL: NSURL!
#IBAction func nextTrack(sender: UIButton) {
println("touched next")
var nowRow: Int = self.currentRow
var maxCount = self.arrayOfMP3.count-1 // -1
if nowRow < maxCount {
nowRow = self.currentRow++
println("plus")
} else if nowRow == arrayOfMP3.endIndex {
nowRow = maxCount
println("equals")
}
// for play/pause button setting
currentPause = nil
//println("current pause\(currentPause)")
next = false
//
println("current row \(currentRow)")
println("end index \(arrayOfMP3.endIndex)")
var nextTrack = arrayOfMP3![nowRow]
var nextDirectory = fileManager.URLsForDirectory(NSSearchPathDirectory.DocumentDirectory, inDomains: NSSearchPathDomainMask.UserDomainMask)
if var nextURL: NSURL = nextDirectory.first as? NSURL {
newURL = nextURL.URLByAppendingPathComponent(nextTrack)
}
var nameArtist: String!
var nameSong: String!
var imageString: NSData!
var itemPlayer = AVPlayerItem(URL: newURL)
var listMetaData = itemPlayer.asset.commonMetadata as! [AVMetadataItem]
for item in listMetaData {
if item.commonKey == "title" {
nameSong = item.stringValue
}
if item.commonKey == "artist" {
nameArtist = item.stringValue
}
if item.commonKey == "album" {
item.stringValue
}
if item.commonKey == "artwork" {
imageString = item.dataValue
}
}
nameLabel?.text = "\(nameArtist) \(nameSong)"
if imageString != nil {
imageView?.image = UIImage(data: imageString)
} else {
imageView?.image = nil
}
tapButtonPlay = false
playMusic()
}

How can I make my iOS app continuously loop through a playlists of sounds?

I'm trying to make an app that plays a series of chimes related to the content of an array, whenever all the chimes are played the app should start playing them again from the beginning of the array.
This is my code:
var counter = 0
var song = ["1","2","3","4"]
func music() {
while counter < song.count {
var audioPath = NSBundle.mainBundle().pathForResource("\(song[counter])", ofType: "wav")!
var error : NSError? = nil
player = AVAudioPlayer(contentsOfURL: NSURL(string: audioPath), error: &error)
if error == nil {
player.delegate = self
player.prepareToPlay()
player.play()
}
func audioPlayerDidFinishPlaying(player: AVAudioPlayer!, successfully flag: Bool) {
if flag {
counter++
}
}
if ((counter + 1) == song.count) {
counter = 0
}
}
}
How can I achieve this?
You don't need to have a loop here. The logic is the following: you play a music file, when this file is over you increment the counter and play the next file
var counter = 0
var song = ["1","2","3","4"]
func music()
{
var audioPath = NSBundle.mainBundle().pathForResource("\(song[counter])", ofType: "wav")!
var error : NSError? = nil
player = AVAudioPlayer(contentsOfURL: NSURL(string: audioPath), error: &error)
if error == nil {
player.delegate = self
player.prepareToPlay()
player.play()
}
func audioPlayerDidFinishPlaying(player: AVAudioPlayer!, successfully flag: Bool)
{
player.delegate = nil
if flag {
counter++
}
if ((counter + 1) == song.count) {
counter = 0
}
music()
}
You can do this easily using an AVQueuePlayer…
let song = ["1","2","3","4"]
func music() {
let items = song.compactMap {
Bundle.main.url(forResource: "\($0)", withExtension: "wav")
}.map {
AVPlayerItem(url: $0)
}
let player = AVQueuePlayer(items: items)
player.delegate = self
player.play()
}

Playing Audio File from the directory in swift

I am trying to save a audio file from a server to the users phone so they dont have to download it again its always there. I have almost everything but i am trying to test and see if the audio file actually plays after it is saved. How do i do this?
Code i have:
var urlWebView = NSURL(string: "http://domain.com//////audios////Nightmares.wav")
var requestWebView = NSURLRequest(URL: urlWebView)
NSURLConnection.sendAsynchronousRequest(requestWebView, queue: NSOperationQueue.mainQueue(), completionHandler: {
response, data, error in
if error != nil {
println("There was an error")
} else {
let musicFile = (data: data)
var documentsDirectory:String?
var paths:[AnyObject] = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
if paths.count > 0 {
documentsDirectory = paths[0] as? String
var savePath = documentsDirectory! + "/audio.wav"
NSFileManager.defaultManager().createFileAtPath(savePath, contents: data, attributes: nil)
self.audioPlayer = AVAudioPlayer(contentsOfURL: savePath, error: nil)
//tried to play it here but i cant since savePath is a string and not actually audio file
}
}
})
import UIKit
import Foundation
import AVFoundation
class ViewController: UIViewController {
#IBOutlet weak var strFiles: UITextView!
var myPlayer = AVAudioPlayer()
var yourSound:NSURL?
func prepareYourSound(myData:NSData) {
myPlayer = AVAudioPlayer(data: myData, error: nil)
myPlayer.prepareToPlay()
}
override func viewDidLoad() {
super.viewDidLoad()
// Do any additional setup after loading the view, typically from a nib.
var urlWebView = NSURL(string: "http://freewavesamples.com/files/Korg-DS-8-Rotary-Organ-C6.wav")!
var requestWebView = NSURLRequest(URL: urlWebView)
NSURLConnection.sendAsynchronousRequest(requestWebView, queue: NSOperationQueue.mainQueue(), completionHandler: {
response, data, error in
if error != nil {
println("There was an error")
} else {
let musicFile = (data: data)
var documentsDirectory:String?
var paths:[AnyObject] = NSSearchPathForDirectoriesInDomains(NSSearchPathDirectory.DocumentDirectory, NSSearchPathDomainMask.UserDomainMask, true)
if paths.count > 0 {
documentsDirectory = paths[0] as? String
var savePath = documentsDirectory! + "/audio.wav"
NSFileManager.defaultManager().createFileAtPath(savePath, contents: data, attributes: nil)
self.prepareYourSound(musicFile)
self.myPlayer.play()
//tried to play it here but i cant since savePath is a string and not actually audio file
// list your files from disk (documents)
let documentsPath = NSSearchPathForDirectoriesInDomains(.DocumentDirectory, .UserDomainMask, true)[0] as String
let files = NSFileManager().enumeratorAtPath(documentsPath)
var myFiles:[String] = []
while let file: AnyObject = files?.nextObject() {
myFiles.append(file as String)
self.strFiles.text = "\(self.strFiles.text)\n\(file as String)"
}
}
}
})
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}

Resources