Как использовать addBoundaryTimeObserver? - PullRequest
0 голосов
/ 30 декабря 2018

Я пытаюсь создать ручной счетчик ударов для аудиофайлов.

Как это работает?
Пока AVPlayer воспроизводит аудиофайл, пользователь нажимает кнопку в пользовательском интерфейсе, которая записывает количество ударов песни.Число ударов записывается в последовательности от 1 до 8 и создает массив ударов [[String:String]] //timeInSeconds:beatCount

После записи количества ударов вместе с timeInSeconds каждого удара я сохраняю массив ударов в виде строкового представления вбазу данных, я загружаю ее позже, и когда я снова играю песню, я хочу отобразить в метке счетчик ударов, который я записывал ранее каждый раз, когда timeInSeconds преобразуется в func addBoundaryTimeObserver(url: URL).

Чтов чем проблема? В func addBoundaryTimeObserver(url: URL), когда я сравниваю ключи arrayOfBeats с strongSelf.timeString, совпадения нет.
Если вы проверите приведенные ниже операторы печати, вы увидите, что player?.addBoundaryTimeObserver(forTimes: timesToTransverse, queue: mainQueue) вызывается простонемного позже, поэтому нет соответствия для timesToTransverse, хотя мы воспроизводим один и тот же аудиофайл с той же скоростью.

 // print statement in func createStringOfBeatsToSaveInDB()
finalString is 4.43:1,5.63:2,6.28:3  //seconds:beatCount

 //print statement in func addBoundaryTimeObserver(url: URL)
CMTime times recorded are 
 [CMTime: {4430000000/1000000000 = 4.430}, 
 CMTime: {5630000000/1000000000 = 5.630}, 
 CMTime: {6280000000/1000000000 = 6.280}]


//print statement in func addBoundaryTimeObserver(url: URL)
//This is NOT the expected result
 timeinSeconds double is 6.67019307613373
 timeString format: %.2f is 6.67

 timeinSeconds double is 7.86572802066803
 timeString format: %.2f is 7.87

 timeinSeconds double is 8.51492607593536
 timeString format: %.2f is 8.51




var beatCount = 0
var arrayOfBeats = [String: Int]()  //time: beatCount
var timeInSeconds: Double = 0
var startTime: Double = 0
var timeString: String = ""

var player: AVPlayer?
var timeObserverToken: Any?

var temporaryArrOfbeats = [[String:String]]()
var finalStringOfBeats = ""


  @IBAction func playStopSong(_ sender: Any) {

    //if player was stopped
    if isFinished == true {
            player?.replaceCurrentItem(with: nil)
            removePeriodicTimeObserver()
            createStringOfBeatsToSaveInDB()

    }else {
        let url = URL(string: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3")!
                self.addPeriodicTimeObserver(url: url)
    }

    //toggle the isFinished boolean value to decide if player stopped
    isFinished = !isFinished

}//end playStopSong




 func addPeriodicTimeObserver(url: URL) {

    let playerItem = AVPlayerItem(url: url)
    player = AVPlayer(playerItem: playerItem)
    player?.play()

    startTime = Date().timeIntervalSinceReferenceDate
    let durationInSeconds = CMTimeGetSeconds(playerItem.asset.duration)
    let durationInMinutes = durationInSeconds / 60
    songTotalTime.text = String(format: "%.2f", durationInMinutes)

    // Invoke callback every 0.05 seconds
    let interval = CMTime(seconds: 0.05,
                          preferredTimescale: CMTimeScale(NSEC_PER_SEC))

    // Queue on which to invoke the callback
    let mainQueue = DispatchQueue.main

    // Add time observer
    timeObserverToken = player?.addPeriodicTimeObserver(forInterval: interval, queue: mainQueue) { [weak self] timeNow in

        guard let strongSelf = self else {return}
        let duration = CMTimeGetSeconds(playerItem.duration)
        strongSelf.progressView.progress = Float((CMTimeGetSeconds(timeNow) / duration))

        //Total time since timer started, in seconds
        strongSelf.timeInSeconds = Date().timeIntervalSinceReferenceDate - strongSelf.startTime

        //Convert the time to a string with 2 decimal places
        let timeString = String(format: "%.2f", strongSelf.timeInSeconds)
        strongSelf.timeString = timeString

        //Display the time string to a label in our view controller
        strongSelf.songTimeLeft.text = timeString

        // expression evaluates to true if player stopped. Now we can compare timeString with keys in arrayOfBeats (var arrayOfBeats:[String: Int]()  //timeInSeconds: beatCount)
        if strongSelf.isPlaySongWithRecordedBeatEnabled == true {
            strongSelf.comparePeriodicTimeObserver(timeString: timeString)
        }
    }
}//end addPeriodicTimeObserver


  func createStringOfBeatsToSaveInDB() {
    let newArrMapped: [(key: String, value: String)] = temporaryArrOfbeats.flatMap {$0}

    let totalCount = newArrMapped.count
    var count = 0

    newArrMapped.forEach { (timeString, beatCount) in
        count += 1

        let newString = "\(timeString):\(beatCount)"
        if count == totalCount {
            finalStringOfBeats.append(newString)
        }else {
            finalStringOfBeats.append(newString)
            finalStringOfBeats.append(",")
        }
    }
        print("finalString is \(finalStringOfBeats)")
}//end createStringOfBeatsToSaveInDB



  @IBAction func tapBeat(_ sender: Any) {
     addBeatToArray()
 }

 func addBeatToArray() {

    if beatCount == 8 {beatCount = 0}
    if beatCount == 0 || beatCount <= 7 {beatCount += 1}

        showBeatCount.text = "\(beatCount)"
        arrayOfBeats[timeString] = beatCount

    let newPair = [timeString:String(beatCount)]
    temporaryArrOfbeats.append(newPair)
   //            print("time:beatCount \(timeString): \(beatCount) ")

}


     //after user stops the song we can play the same song again, but this time we will compare the keys of `arrayOfBeats` against the current playing time 
      @IBAction func playSongWithRecordedBeat(_ sender: Any) {
    if arrayOfBeats.count != 0 {

       beatCount = 0
       timeInSeconds  = 0
       timeString = ""

        isPlaySongWithRecordedBeatEnabled = true
          let testURL = URL(string: "https://www.soundhelix.com/examples/mp3/SoundHelix-Song-1.mp3")!
           addBoundaryTimeObserver(url: testURL)
    }
}



 func addBoundaryTimeObserver(url: URL) {

    let playerItem = AVPlayerItem(url: url)
    player = AVPlayer(playerItem: playerItem)

    var timesToTransverse = [NSValue]()

    // Build boundary times from arrayOfBeats keys
    let keys = arrayOfBeats.keys.compactMap {$0}

   //get the times for every single time Tap Beat button was touched and assign them to timesToTransverse
    keys.forEach { (key) in
        let second: Double = Double("\(key)")!
        let cmtime = CMTime(seconds: second, preferredTimescale: CMTimeScale(NSEC_PER_SEC))

        let cmtimeValue = NSValue(time: cmtime)
            timesToTransverse.append(cmtimeValue)
    }

   print("CMTime times recorded are \(times)")

    startTime = Date().timeIntervalSinceReferenceDate
    player?.play()
    // Queue on which to invoke the callback
    let mainQueue = DispatchQueue.main
    // Add time observer
    timeObserverToken = 
        player?.addBoundaryTimeObserver(forTimes: timesToTransverse, queue: mainQueue) {
            [weak self] in

            guard let strongSelf = self else {return}

            //time that passed since song started to play
            strongSelf.timeInSeconds = Date().timeIntervalSinceReferenceDate - strongSelf.startTime


            let timeString = String(format: "%.2f", strongSelf.timeInSeconds)
            strongSelf.timeString = timeString

            print("timeinSeconds double is \(strongSelf.timeInSeconds)")
            print("timeString format: %.2f is \(timeString)")

            strongSelf.comparePeriodicTimeObserver(timeString: timeString)
        }
}//end addBoundaryTimeObserver



  //after admin has finished tapping the beat count in UI, he can tap on playSongWithRecordedBeat button and this method will display the beatCount recorded
  func comparePeriodicTimeObserver(timeString: String) {

   //var arrayOfBeats:[String: Int]()  //timeInSeconds: beatCount
    let keys = arrayOfBeats.keys.compactMap {$0}



    if keys.contains(timeString) {
        print("timeString is \(timeString)")
        let _ =  arrayOfBeats.filter({ (key, beatCount) -> Bool in
            if key == timeString {
                showBeatCount.text = "\(beatCount)"
                print("key, val found \(key): \(beatCount)")
                return true
            }
            return false
        })
    }
}

enter image description here

...