Home > OS >  Play songs by tapping the button in table view from local JSON file using swift
Play songs by tapping the button in table view from local JSON file using swift

Time:09-17

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 with path.
  • Name your data source array loops and the item in cellforRow loop. Your naming is pretty confusing.
  • Related