I have a local JSON file with some data. Need to know, how to load audio from local JSON and load it to the button in table view. My JSON:
{
"beatPack":
{
"loops": [
{
"name": "Away we go",
"producer": "Tubular Kingz",
"count": "28",
"genre": "Lo-fi Hip Hop",
"imagename": "beatpackone"
"songName": "alien.mp3"
}
]
}
}
So it should display each of the song to the button in the table view. Here is my song methods, they can display song in each of the row but not from JSON and not in the correct numeration that I need:
func playLoop(songName: String) {
let url = Bundle.main.url(forResource: songName, withExtension: ".mp3") // you should check it for errors
audioPlayer = try! AVAudioPlayer(contentsOf: url!) // of course you should catch the error and deal with it...
audioPlayer.play()
print(songName)
}
func gettingSongName() {
let folderURL = URL(fileURLWithPath: Bundle.main.resourcePath!)
do {
let songPath = try FileManager.default.contentsOfDirectory(at: folderURL, includingPropertiesForKeys: nil, options: .skipsHiddenFiles)
for song in songPath {
var mySong = song.absoluteString
if mySong.contains(".mp3") {
let findString = mySong.components(separatedBy: "/")
mySong = findString[findString.count - 1]
mySong = mySong.replacingOccurrences(of: " ", with: " ")
mySong = mySong.replacingOccurrences(of: ".mp3", with: "")
songs.append(mySong)
}
}
} catch {
}
}
Here is my table view:
extension BPLibraryViewController: UITableViewDataSource, UITableViewDelegate, UIScrollViewDelegate {
func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat {
return 180
}
func numberOfSections(in tableView: UITableView) -> Int {
return 1
}
public func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return loopsName.count
}
public func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
let cell = tableView.dequeueReusableCell(withIdentifier: S.newOneCell, for: indexPath) as! BeatPackLibraryCell
let loopsCount = loopsName[indexPath.row] //Instance of our arrays from JSON
cell.loopNameLabel.text = loopsCount.name
cell.producerNameLabel.text = loopsCount.producer
cell.loopsCountLabel.text = String(loopsName.count)
cell.genreLabel.text = loopsCount.genre
cell.firstBeatButtonLabel.setImage(UIImage(named: loopsCount.imagename), for: .normal)
cell.delegate = self
cell.selectionStyle = .none
cell.tag = indexPath.row
if let playingCell = currentPlayingIndex, playingCell == indexPath.row {
cell.firstBtnOutlet.setImage(UIImage(named: "pauseButtonPack.png"), for:
.normal)
} else {
cell.firstBtnOutlet.setImage(UIImage(named: "playButtonPack.png"), for:
.normal)
}
return cell
}
Button which playing song in each of the row:
extension BPLibraryViewController: BeatPackLibraryDelegate {
func didTapFirstSong(cell: BeatPackLibraryCell) {
let indexPath = self.tableView.indexPath(for: cell)
if currentPlayingIndex == cell.tag {
audioPlayer.pause()
currentPlayingIndex = nil
} else { //IF PAUSE BUTTON
playLoop(songName: songs[cell.tag])
currentPlayingIndex = cell.tag
}
tableView.reloadData()
print("Done")
}
So how to load songs from local JSON file and display it on each of the row.
CodePudding user response:
First of all the method playLoop
cannot work because the file extension in the withExtension
parameter must be specified without the dot.
My suggestion is to add a computed property in the Loop
struct to get the URL in the application bundle for example
struct Loop : Decodable {
let name, producer, count, genre, imagename, songName : String
private enum CodingKeys : String, CodingKey { case name, producer, count, genre, imagename, songName }
var songURL : URL {
let components = songName.components(separatedBy: ".")
guard components.count == 2,
let url = Bundle.main.url(forResource: components[0], withExtension: components[1]) else { fatalError("Audio file is missing") }
return url
}
}
The CodingKeys are required to exclude songURL
from being decoded.
If all audio files are mp3 files you can even omit the extension in the JSON and the computed property becomes much shorter
struct Loop : Decodable {
let name, producer, count, genre, imagename, songName : String
private enum CodingKeys : String, CodingKey { case name, producer, count, genre, imagename, songName }
var songURL : URL {
guard let url = Bundle.main.url(forResource: songName, withExtension: "mp3") else { fatalError("Audio file is missing") }
return url
}
}
Now you can change the playLoop
method to
func playLoop(songURL: URL) {
audioPlayer = try! AVAudioPlayer(contentsOf: songURL)
audioPlayer.play()
print(songURL.lastPathComponent)
}
and play the song from in the tap method
else { //IF PAUSE BUTTON
playLoop(songURL: loopsName[indexPath.row].songURL)
currentPlayingIndex = cell.tag
}
The method gettingSongName()
is not needed. By the way it can be written in 2 lines
func gettingSongName() {
guard let mp3URLs = Bundle.main.urls(forResourcesWithExtension: "mo3", subdirectory: nil) else { return }
songs = mp3URLs.map{$0.deletingPathExtension().lastPathComponent}
}
Notes:
- Never call
absoluteString
on a file system URL, you can get the path simply withpath
. - Name your data source array
loops
and the item incellforRow
loop
. Your naming is pretty confusing.