在AVPlayer上使用periodicObserver跟踪播放进度

时间:2017-03-22 10:02:20

标签: ios objective-c swift uitableview

编辑:我认为问题在某种程度上与细胞重用能力有关,但仍然无法弄清楚如何修复

我正在构建一个具有tableView的应用程序,其中包含一个带有音乐曲目URL的帖子。当帖子完全可见时,我的播放器开始播放帖子轨道。在播放过程中,我应该在帖子中的波形视图中显示播放进度。

问题是观察只能正常使用3个第一个单元格,因为下一个单元格wavePlot不会更新并且寻求停止工作。

我不知道造成这个问题的原因。任何帮助表示赞赏。

这是一个布局:

Layout

在我的viewController的viewDidLoad中,我使用addPeriodicTimeObserver初始化AVPlayer观察者,并将其返回类型分配给变量playerObserver。

override func viewDidLoad() {
    super.viewDidLoad()

    tableView.delegate = self
    tableView.dataSource = self

    timeLinePostsArray = User.sharedUser.requestTimelineData()

    playerObserver = StreamMusicPlayer.shared.addPeriodicTimeObserver(forInterval: CMTime(value: 1, timescale: 2),
                                                                      queue: DispatchQueue.main,
                                                                      using: { (progress) in
                                                                        if StreamMusicPlayer.shared.isPlaying {
                                                                          self.cellToObserve?.updatePlot(with: progress)

                                                                        }
    })

  }

以下是我用来检测完全可见细胞的代码。当检测到完全可见的单元格时,我将其分配给变量cellToObserve以供观察者使用并开始播放单元格的音乐轨道。

func scrollViewDidScroll(_ scrollView: UIScrollView) {
    
    for item in tableView.indexPathsForVisibleRows!{
      
      if tableView.bounds.contains(tableView.rectForRow(at: item)){
        
        let fullyVisibleCell = tableView.cellForRow(at: item) as! HomeControllerTableViewCell
        
        if StreamMusicPlayer.currentItemURL != fullyVisibleCell.containedMusicTrack?.trackURL.absoluteString {
          
          //Play music track and observe the playback progress
          self.cellToObserve = fullyVisibleCell
          self.numberOfCellToObserve = tableView.indexPath(for: fullyVisibleCell)?.row
          
          fullyVisibleCell.playButton.isEnabled = true
          fullyVisibleCell.plot.isUserInteractionEnabled = true
          
          StreamMusicPlayer.playItem(musicTrack: fullyVisibleCell.containedMusicTrack!)
          
        }
      } else {
        
        let _cell = tableView.cellForRow(at: item) as! HomeControllerTableViewCell
        _cell.plot.isUserInteractionEnabled = false
        _cell.playButton.isEnabled = false
        
      }
    }
  }

在viewController的deinit方法中,我尝试删除玩家的观察者。

func attemptRemoveObservation() {
  if self.playerObserver != nil {
    StreamMusicPlayer.shared.removeTimeObserver(self.playerObserver)
    self.playerObserver = nil
  }
}


deinit {
  self.attemptRemoveObservation()
}

这是我的玩家的实施:

class StreamMusicPlayer: AVPlayer {
  
  private override init(){
    
    super.init()
    
  }

  static var currentItemURL: String?
  
  static var shared = AVPlayer()
  
  static func playItem(musicTrack: MusicTrack) {
    
    let item = AVPlayerItem(url: musicTrack.trackURL)
    
    StreamMusicPlayer.shared.replaceCurrentItem(with: item)
            
    StreamMusicPlayer.currentItemURL = musicTrack.trackURL.absoluteString
  
    StreamMusicPlayer.shared.play()
    
  }
  
}

extension AVPlayer {
  
  var isPlaying: Bool {
    return rate != 0 && error == nil
  }
  
}

如果需要,这里是完整的viewController的源代码:

//
//  HomeViewController.swift
//  CheckMyTrack
//
//  Created by Alexey Savchenko on 09.03.17.
//  Copyright © 2017 Alexey Savchenko. All rights reserved.
//

import UIKit
import CoreMedia
import Foundation

class HomeViewController: UIViewController, UITableViewDelegate, UITableViewDataSource {
  
  //MARK: Vars
  
  var playerObserver: Any!
  var timeLinePostsArray: [TimeLinePost] = []
  var cellToObserve: HomeControllerTableViewCell?
  var numberOfCellToObserve: Int?
  //MARK: Outlets
  
  
  @IBOutlet weak var tableView: UITableView!
  
  
  override func viewDidLoad() {
    super.viewDidLoad()
    
    tableView.delegate = self
    tableView.dataSource = self
    
    timeLinePostsArray = User.sharedUser.requestTimelineData()
    
    playerObserver = StreamMusicPlayer.shared.addPeriodicTimeObserver(forInterval: CMTime(value: 1, timescale: 2),
                                                                      queue: DispatchQueue.main,
                                                                      using: { (progress) in
                                                                        if StreamMusicPlayer.shared.isPlaying {
                                                                          self.cellToObserve?.updatePlot(with: progress)
                                                                          
                                                                        }
    })
    
  }
  
  func attemptRemoveObservation(){
    if self.playerObserver != nil{
      StreamMusicPlayer.shared.removeTimeObserver(self.playerObserver)
      self.playerObserver = nil
    }
  }
  
  //MARK: TableView delegate methods
  
  
  
  func numberOfSections(in tableView: UITableView) -> Int {
    return 1
  }
  
  func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
    
    return timeLinePostsArray.count
    
  }
  
  //TODO: Implement correct change of observing cell plot
  
  func scrollViewDidScroll(_ scrollView: UIScrollView) {
    
    for item in tableView.indexPathsForVisibleRows!{
      
      if tableView.bounds.contains(tableView.rectForRow(at: item)){
        
        let fullyVisibleCell = tableView.cellForRow(at: item) as! HomeControllerTableViewCell
        
        if StreamMusicPlayer.currentItemURL != fullyVisibleCell.containedMusicTrack?.trackURL.absoluteString {
          
          //Play music track and observe the playback progress
          self.cellToObserve = fullyVisibleCell
          self.numberOfCellToObserve = tableView.indexPath(for: fullyVisibleCell)?.row
          
          fullyVisibleCell.playButton.isEnabled = true
          fullyVisibleCell.plot.isUserInteractionEnabled = true
          
          StreamMusicPlayer.playItem(musicTrack: fullyVisibleCell.containedMusicTrack!)
          
        }
      } else {
        
        let _cell = tableView.cellForRow(at: item) as! HomeControllerTableViewCell
        _cell.plot.isUserInteractionEnabled = false
        _cell.playButton.isEnabled = false
        
      }
    }
  }
  
  func tableView(_ tableView: UITableView, didEndDisplaying cell: UITableViewCell, forRowAt indexPath: IndexPath) {
    //    if let _cell = cell as? HomeControllerTableViewCell{
    //      if self.playerToken != nil{
    //        StreamMusicPlayer.shared.removeTimeObserver(self.playerToken)
    //        self.playerToken = nil
    //      }
    //    }
  }
  
  
  func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
    
    let cell = tableView.dequeueReusableCell(withIdentifier: "HomeViewControllerCell", for: indexPath) as? HomeControllerTableViewCell
    
    let track = timeLinePostsArray[indexPath.row].musicTrack
    
    cell?.containedMusicTrack = track
    
    cell?.authorNameLabel.text = timeLinePostsArray[indexPath.row].authorName
    cell?.authorPicture.image = timeLinePostsArray[indexPath.row].authorPicture
    cell?.trackName.text = track.trackName
    
    cell?.plot.populateWithData(from: track.trackWaveformData)
    cell?.plot.normalColor = UIColor.gray
    cell?.plot.progressColor = UIColor.orange
    
    
    cell?.playAction = { (cell) in
      
      if StreamMusicPlayer.shared.isPlaying {
        
        if StreamMusicPlayer.currentItemURL == cell.containedMusicTrack?.trackURL.absoluteString {
          
          cell.playButton.setImage(UIImage(named: "play"), for: .normal)
          
          StreamMusicPlayer.shared.pause()
          
        }
        
      } else {
        
        StreamMusicPlayer.shared.play()
        
        cell.playButton.setImage(UIImage(named: "pause"), for: .normal)
        
      }
      
    }
    
    cell?.likeAction = { (cell) in
      
      
      
    }
    
    return cell!
    
  }
  
  
  deinit {
    self.attemptRemoveObservation()
  }
}

1 个答案:

答案 0 :(得分:0)

我找到了问题的根源。 有必要为我使用的自定义单元类实现prepareForReuse方法,并执行单元的波形图的清理。 WaveformPlot的波形属性需要明确清空。

我的实施如下:

override func prepareForReuse() {
    plot.clearPlot()
    plot.waveforms = []

  }
  
func clearPlot(){
      
      for item in self.subviews{
        
        item.removeFromSuperview()
        
      }
      
      guard let layers = self.layer.sublayers else { return }
      
      for _item in layers{
        _item.removeFromSuperlayer()
      }
      
}