iOS - AVPlayer播放本地和網(wǎng)絡視頻

AV Foundation的播放都圍繞AVPlayer類展開,AVPlayer是一個用來播放基于時間的視聽媒體的控制器對象。支持播放從本地、分布下載或通過HTTP Live Streaming協(xié)議得到的流媒體,并在多種播放器場景中播放這些視頻資源。

AVplayer

An AVPlayer is a controller object used to manage the playback and timing of a media asset. It provides the interface to control the player’s transport behavior such as its ability to play, pause, change the playback rate, and seek to various points in time within the media’s timeline. You can use an AVPlayer to play local and remote file-based media, such as QuickTime movies and MP3 audio files, as well as audiovisual media served using HTTP Live Streaming.

AVPlayer是一個控制對象用于管理媒體asset的播放,它提供了相關的接口控制播放器的行為,比如:播放、暫停、改變播放的速率、跳轉到媒體時間軸上的某一個點(簡單理解就是實現(xiàn)拖動功能顯示對應的視頻位置內(nèi)容)。我們能夠使用AVPlayer播放本地和遠程的媒體文件(使用 HTTP Live Streaming),比如: QuickTime movies 和 MP3 audio files,所以AVPlayer可以滿足音視頻播放的基本需求。

常用屬性方法

// 播放速率
open var rate: Float

// 播放視頻
open func play()

// 停止播放
open func pause()

// 播放狀態(tài)
open var status: AVPlayer.Status { get }
  • rate

如果rate為0說明是停止狀態(tài),1是則是正常播放狀態(tài)。

注意:

1:AVPlayer繼承NSObject,所以單獨使用AVPlayer時無法顯示視頻的,必須將視頻圖層添加到AVPlayerLayer中方能顯示視頻。

2:AVPlayer一次只能播放單一的媒體資源(asset),但是player實例對象能夠被重復用于播放其它媒體資源,可以調用replaceCurrentItem(with:)方法更新當前播放資源。如果想播放多個資源,我們可以使用AVPlayer的子類AVQueuePlayer,該類能夠創(chuàng)建和管理多個媒體資源列隊,

顯示視頻方式

AVplayerAVPlayerItem都是不可見對象,這意味著它們是不能夠呈現(xiàn)視頻在屏幕上的,我們有兩個基本方法在屏幕上顯示視頻:

  • AVKit

The best way to present your video content is by using the AVKit framework’s AVPlayerViewController class in iOS and tvOS or the AVPlayerView class in macOS. These classes present the video content, along with playback controls and other media features giving you a full-featured playback experience.

使用AVKit,這是最好的方式呈現(xiàn)視頻內(nèi)容,我們只需要使用AVKit框架的AVPlayerViewController類,該類能夠播放視頻內(nèi)容,并且?guī)в邢鄳牟シ趴丶鸵恍┢渌拿襟w特征,能夠進行全屏播放。

  • AVPlayerLayer

If you are building a custom interface for your player, you use a Core Animation CALayer subclass provided by AVFoundation called AVPlayerLayer. The player layer can be set as a view’s backing layer or can be added directly to the layer hierarchy. Unlike AVPlayerView and AVPlayerViewController, a player layer doesn’t present any playback controls, but simply presents the visual content on screen. It is up to you to build the playback transport controls to play, pause, and seek through the media.

使用AVPlayerLayer,如果是為播放器創(chuàng)建自定義界面,我們能夠使用核心動畫CALayer的子類AVPlayerLayer來播放。該播放layer能夠被設置為視圖的backing layer或者直接添加到layer層級中。AVPlayerLayer只是簡單的呈現(xiàn)視頻內(nèi)容,并不像AVPlayerViewAVPlayerViewController,是沒有提供播放控件的。對于播放界面需要什么樣的播放控件,完全取決于我們自己自定義界面實現(xiàn)播放、暫停、調整等一系列功能

AVPlayerLayer

AVPlayerLayer視頻播放圖層對象,它是需要添加到當前視圖的圖層上 。

AVPlayerLayer構建于Core Animation之上,是AV Foundation中能找到為數(shù)不多的可見組件。Core Animation是Mac和iOS平臺上負責圖形渲染于動畫的基礎框架,主要用于這些平臺資源的美化和動畫流暢度提升。Core Animation本身具有基于時間的屬性,并且由于它基于OpenGL,所以具有很好的性能,能夠非常好地滿足AV Foundation的各種需要

AVPlayerLayer擴展了Core AnimationCALayer類,并通過框架在屏幕上顯示視頻內(nèi)容。這一圖層并不提供任何可視化控件或其他附件,但是它用作視頻內(nèi)容的渲染面。創(chuàng)建AVPlayerLayer需要一個指向AVPlayer實例的指針,這就將圖層和播放器緊密綁在一起,保證了當播放器基于時間的方法出現(xiàn)時時二者保持同步。AVPlayerLayer與其他CALayer一樣,設置為UIView的備用層,或者手動添加到一個已有的層繼承關系中作為子layer

AVPlayerLayer是一個相對簡單的類,使用起來很簡單。在這一層可以自定義領域只有video gravity??偣部蔀?code>video gravity屬性定義三個不同的gravity值,用來確定在承載層的范圍內(nèi)視頻可以拉伸或縮放的程度。

extension AVLayerVideoGravity {
    // 按原視頻比例顯示,是豎屏的就顯示出豎屏的,兩邊留黑
    public static let resizeAspect: AVLayerVideoGravity
    // 以原比例拉伸視頻,直到兩邊屏幕都占滿,但視頻內(nèi)容有部分就被切割了
    public static let resizeAspectFill: AVLayerVideoGravity
    // 是拉伸視頻內(nèi)容達到邊框占滿,但不按原比例拉伸,這里明顯可以看出寬度被拉伸了
    public static let resize: AVLayerVideoGravity
}

AVPlayerItem

AVPlayerItem是代表一個AVAsset狀態(tài),可以使用它實時的觀察到視頻播放狀態(tài)。管理著視頻的一些基本信息和狀態(tài),一個AVPlayerItem對應著一個視頻資源。

AVPlayerItem存儲了AVAsset對象的引用,它代表被播放的媒體,如果你需要訪問asset的信息,在它進入播放列隊之前,我們能夠使用AVAsynchronousKeyValueLoading協(xié)議中的方法來加載我們所需要的值。

也有可選的方法,那就是AVPlayerItem對象能夠自動根據(jù)你傳遞給構造器方法init(asset:automaticallyLoadedAssetKeys:) 的參數(shù)來加載需要的asset數(shù)據(jù),當AVPlayerItem準備好進行播放,asset相關的屬性都將被加載用于播放。

AVPlayerItem是動態(tài)對象,除了能夠被改變的屬性值之外,其它的可讀屬性值,會在AVPlayer播放期間發(fā)生改變。我們能夠使用 Key-value observing來觀察這些屬性的改變,對于AVPlayerItem最重要的一個屬性就是status。該屬性指示是否playerItem已經(jīng)準備好用于播放。

事實上,當我們第一次創(chuàng)建playerItem的時候,該status屬性是指為unknown,這意味著媒體并沒有加載完成,還沒有準備好播放。當為AVplayer關聯(lián)該playerItem對象,那么playerItem的媒體資源將會立馬準備用于播放。如

func prepareToPlay() {
    let url = <#Asset URL#>
    // Create asset to be played
    asset = AVAsset(url: url)
    
    let assetKeys = [
        "playable",
        "hasProtectedContent"
    ]
    // Create a new AVPlayerItem with the asset and an
    // array of asset keys to be automatically loaded
    playerItem = AVPlayerItem(asset: asset,
                              automaticallyLoadedAssetKeys: assetKeys)
    
    // Register as an observer of the player item's status property
    playerItem.addObserver(self,
                           forKeyPath: #keyPath(AVPlayerItem.status),
                           options: [.old, .new],
                           context: &playerItemContext)
    
    // Associate the player item with the player
    player = AVPlayer(playerItem: playerItem)
}

為了處理屬性的改變,我們需要重寫通知方法observeValue(forKeyPath:of:change:context:)方法

override func observeValue(forKeyPath keyPath: String?,
                           of object: Any?,
                           change: [NSKeyValueChangeKey : Any]?,
                           context: UnsafeMutableRawPointer?) {
    // Only handle observations for the playerItemContext
    guard context == &playerItemContext else {
        super.observeValue(forKeyPath: keyPath,
                           of: object,
                           change: change,
                           context: context)
        return
    }
    
    if keyPath == #keyPath(AVPlayerItem.status) {
        let status: AVPlayerItemStatus
        
        // Get the status change from the change dictionary
        if let statusNumber = change?[.newKey] as? NSNumber {
            status = AVPlayerItemStatus(rawValue: statusNumber.intValue)!
        } else {
            status = .unknown
        }
        
        // Switch over the status
        switch status {
        case .readyToPlay:
        // Player item is ready to play.
        case .failed:
        // Player item failed. See error.
        case .unknown:
            // Player item is not yet ready.
        }
    }
}

AVPlayerItem常用屬性方法

監(jiān)控PlayerItem

open var status: AVPlayerItem.Status { get }
open var loadedTimeRanges: [NSValue] { get }
open var duration: CMTime { get } // 視頻總時間
  • status

該值是一個可觀察屬性,用于確定是否接受者能夠播放,當值為failed時,接受者將不能夠用于進行播放,需要創(chuàng)建一個新的實例取代。

public enum  AVPlayerItemStatus :Int {
    case unknown    // 未知狀態(tài)
    case readyToPlay // 準備播放狀態(tài),表示player item準備被播放
    case failed     // 失敗狀態(tài)
 }
  • loadedTimeRanges

該屬性是一個數(shù)組,主要是包含已經(jīng)下載的媒體數(shù)據(jù),所提供的范圍可能不連續(xù)。

移動播放頭(Moving the Playhead)

open func seek(to time: CMTime, completionHandler: ((Bool) -> Void)? = nil) 跳到指定位置

獲取播放信息(Getting Information About Playback)

open var isPlaybackLikelyToKeepUp: Bool { get }
open var isPlaybackBufferEmpty: Bool { get }
  • isPlaybackBufferEmpty

是否播放已經(jīng)消耗了所有的緩存媒體數(shù)據(jù),并且播放將結束

  • isPlaybackLikelyToKeepUp

確定是否播放將繼續(xù)不會停止

獲取時間信息(Getting Timing Information)

open var forwardPlaybackEndTime: CMTime  // 跳到結束位置
open var reversePlaybackEndTime: CMTime  // 跳到開始位置

AVAsset

AVAsset代表一個抽象的媒體,包括標題,文件大小等等,不關聯(lián)任何格式,每個AVAsset由多個track組成,每個track可以是一個音頻通道或者視頻通道,經(jīng)常使用AVAsset的子類AVURLAsset初始化asset,傳入URL,該URL引用了視聽媒體的資源,比如:stream(包括:HTTP live streams), QuickTime電影文件,MP3文件,和其它格式的文件,我們也可以使用其它具體的子類來初始化asset,具體子類擴大了視聽媒體有用方式的基本模型,比如:AVComposition用于處理臨時的編輯

由于音視頻資源的實時性,當asset初始化成功之后,keys中對應的有些value可能并不會立馬可能獲得。key所對應的value可以在任何時候獲取,并且是同步返回的,所以可能阻塞調用線程。為了避免阻塞情況,我們可以注冊對應key的kvo通知,更多信息AVAsynchronousKeyValueLoading.

為了播放AVAsset實例,需要初始化AVPlayerItem,使用player item來建立AVAsset的呈現(xiàn)狀態(tài)(比如:是否在被播放的時候僅僅只限制asset的緩存范圍),而且為AVplayer對象提供該player item對象用于播放,或者組合多個player item。

為了收集一個或者多個資源asset的視聽數(shù)據(jù)結構,我們可以插入AVAsset對象到AVMutableComposition。

處理時間

AVPlayerAVPlayerItem都時基于時間的對象,但是在我們使用它們的功能前,需要了解在AVFoundation框架中出現(xiàn)時間的方式。AVFoundation使用一種可靠的方法來表示時間信息,那就是CMTime數(shù)據(jù)結構

CMTime

CMTime為時間的正確表示給出來一種結構,即分數(shù)值的方式,具體定義如下:

public struct CMTime {

    public var value: CMTimeValue 
    public var timescale: CMTimeScale 
    public var flags: CMTimeFlags
    public var epoch: CMTimeEpoch 

    public init()
    public init(value: CMTimeValue, timescale: CMTimeScale, flags: CMTimeFlags, epoch: CMTimeEpoch)
}

這個結構最關鍵的兩個值是value和timescale。value是64位整數(shù),timescale是一個32位整數(shù),在時間呈現(xiàn)樣式中分別作為分子和分母。 value/timescale = seconds.

如何使用CMTimeMake函數(shù)創(chuàng)建時間

// 0.5 seconds
CMTime halfSeconds = CMTimeMake(1, 2);
// 5 seconds
CMTime fiveSeconds = CMTimeMake(5, 1);
// one sample from a 44.1kHz audio file
CMTime oneSample = CMTimeMake(1, 44100);

// zero time value
CMTime zeroTime = kCMTimeZero;

時間監(jiān)聽

KVO對于常見的狀態(tài)監(jiān)控表現(xiàn)得非常出色,并且可以監(jiān)聽AVPlayerItemAVPlayer的許多屬性。不過KVO也有不能勝任的場景,比如:AVPlayer的時間變化。這些監(jiān)聽類型都是自身具有明顯的動態(tài)特性并需要非常高的精確度,這一點要比標準的鍵值監(jiān)聽要求高。為滿足這一需求AVPlayer提供了兩種基于時間的監(jiān)聽方法,讓應用程序可以對時間變化進行精確的監(jiān)聽

定期監(jiān)聽

通常情況下,我們希望以一定的時間間隔獲得通知。如果需要隨著時間的變化移動播放頭位置或更新時間顯示,這非常重要。利用AVPlayeraddPeriodicTimeObserver(forInterval interval: CMTime, queue: DispatchQueue?, using block: @escaping (CMTime) -> Void) -> Any方法可以很容易地監(jiān)聽到此類變化。這個方法需要傳遞如下參數(shù):

  • interval:一個用于指定通知周期間隔的CMTime值
  • queue:通知發(fā)送的順序調度列隊。大多數(shù)時候,我們希望這些通知發(fā)生在主列隊,在如果沒有明確指定的情況下默認為主列隊。需要注意的是不可以使用并行調度列隊,因為API沒有處理并行列隊的方法,否則會導致一些不可知的問題
  • block:一個在指定的時間間隔中將會在列隊上調用的回調塊。這個塊傳遞的一個CMTime值用于指示播放器的當前時間
func addPeriodicTimeObserver() {
    // Invoke callback every half second
    let interval = CMTime(seconds: 0.5,
                          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] time in
            // update player transport UI
    }
}

邊界時間監(jiān)聽

AVPlayer還提供了一種更有針對性的方法來監(jiān)聽時間,應用程序可以得到播放器時間軸中多個邊界點的遍歷結果。這一方法主要用于同步用戶界面變更或隨著視頻播放記錄一些可視化數(shù)據(jù)。比如,可以定義25%、50%和75%邊界的標記,以此判斷用戶播放進度。要使用這個功能,需要用到addBoundaryTimeObserver(forTimes times: [NSValue], queue: DispatchQueue?, using block: @escaping () -> Void) -> Any方法,并提供以下參數(shù):

  • times:CMTime值組成的數(shù)組,定義了需要通知的邊界點
  • queue:與定期類似,為方法提供一個用來發(fā)送通知的順序調度列隊,指定NULL等同于明確設置主列隊
  • block:每當正常播放中跨越一個邊界點時就會在列隊中調用這個回調塊。有趣的是,該塊不提供遍歷的CMTime值,所以開發(fā)者需要為此執(zhí)行一些額外計算進行確定
func addBoundaryTimeObserver() {
    var times = [NSValue]()
    // Set initial time to zero
    var currentTime = kCMTimeZero
    // Divide the asset's duration into quarters.
    let interval = CMTimeMultiplyByFloat64(asset.duration, 0.25)
    
    // Build boundary times at 25%, 50%, 75%, 100%
    while currentTime < asset.duration {
        currentTime = currentTime + interval
        times.append(NSValue(time:currentTime))
    }
    // Queue on which to invoke the callback
    let mainQueue = DispatchQueue.main
    // Add time observer
    timeObserverToken =
        player.addBoundaryTimeObserver(forTimes: times, queue: mainQueue) {
            [weak self] time in
            // Update UI
    }
}

AVPlayer視頻播放基本步驟

1:創(chuàng)建視頻資源地址URL,可以是網(wǎng)絡URL
2:通過URL創(chuàng)建視頻內(nèi)容對象AVPlayerItem,一個視頻對應一個AVPlayerItem
3:創(chuàng)建AVPlayer視頻播放器對象,需要一個AVPlayerItem進行初始化
4:創(chuàng)建AVPlayerLayer播放圖層對象,添加到顯示視圖上
5:播放器播放play,播放器暫停pause
6:添加通知中心監(jiān)聽視頻播放完成,使用KVO監(jiān)聽播放內(nèi)容的屬性變化
7:進度條監(jiān)聽是調用AVPlayer的對象方法:open func addPeriodicTimeObserver(forInterval interval:CMTime, queue:DispatchQueue?, using block:@escaping(CMTime) -> Swift.Void) ->Any

實戰(zhàn)

使用AVPlayer自定義一個簡單播放器播放視頻

實現(xiàn)效果

2018-12-07 10-48-30.2018-12-07 10_49_28.gif

基本部分

import UIKit
import AVFoundation

class AVPlayerController: UIViewController {

    var containerView: UIView!   //播放器容器
    var playOrPauseButton: UIButton! //播放/暫停按鈕
    var progress: UIProgressView!   //播放進度
    var timeLabel: UILabel!     //顯示播放時間

    var player: AVPlayer? //播放器對象
    var playerItem: AVPlayerItem? //播放資源對象
    var timeObserver: Any! //時間觀察者

    override func viewDidLoad() {
        super.viewDidLoad()
        view.backgroundColor = UIColor.white
        setupUI()
        addPlayerToAVPlayerLayer()
        addPlayerItemObserver()
        addProgressObserver()
    }
}
  • 布局UI功能

containerView創(chuàng)建容器視圖用于顯示視頻,playOrPauseButton播放、暫停按鈕用于控制視頻的播放和暫停;progress進度條顯示視頻當前的播放進度;時間timelabel顯示當前的播放時間.

 func setupUI(){
     // 容器視圖
     containerView = UIView(frame: CGRect(x: 0,
                                          y: 100,
                                          width: self.view.frame.width,
                                          height: 200))
     containerView.backgroundColor = UIColor.gray
     view.addSubview(containerView)

     // 播放暫停按鈕
     playOrPauseButton = UIButton(type: .custom)
     playOrPauseButton.frame = CGRect(x: containerView.frame.minX + 10,
                                      y: containerView.frame.maxY + 5,
                                      width: 30,
                                      height: 30)
     playOrPauseButton.setImage(UIImage(named:"pause"), for: .normal)
     playOrPauseButton.addTarget(self,
                                 action: #selector(playOrPauseButtonClicked(button:)),
                                 for: .touchUpInside)
     view.addSubview(playOrPauseButton)

     // 進度條
     progress = UIProgressView(frame: CGRect(x: playOrPauseButton.frame.maxX + 5,
                                                y: playOrPauseButton.frame.minY,
                                                width: 220,
                                                height: 20))
     progress.center.y = playOrPauseButton.center.y;
     progress.progressTintColor = UIColor.blue
     progress.trackTintColor = UIColor.gray
     view.addSubview(progress)

     // 時間
     timeLabel = UILabel(frame: CGRect(x: progress.frame.maxX + 5,
                                       y: playOrPauseButton.frame.minY,
                                       width: 60,
                                       height: 20))
     timeLabel.font = UIFont.systemFont(ofSize: 12.0)
     timeLabel.textColor = UIColor.red
     timeLabel.center.y = playOrPauseButton.center.y
     view.addSubview(timeLabel)
 }
  • 獲取本地資源

加載本地或網(wǎng)絡資源,然后創(chuàng)建playerItem,通過playerItem創(chuàng)建player,最后由player創(chuàng)建playerLayer并添加到自定義的播放視圖上,當視頻資源加載完畢會播放視頻

 func addPlayerToAVPlayerLayer(){
     // 獲取本地視頻資源
     guard let path = Bundle.main.path(forResource: "trailer", ofType: ".mp4") else { return }
     // 播放本地視頻
     let url = URL(fileURLWithPath: path)
     // 播放網(wǎng)絡視頻
     // let url = URL(string: path)!
     playerItem = AVPlayerItem(url: url)
     player = AVPlayer(playerItem: self.playerItem)

     // 創(chuàng)建視頻播放器圖層對象
     let playerLayer = AVPlayerLayer(player: player)
     playerLayer.frame = CGRect(x: containerView.frame.minX,
                                y: 0,
                                width: containerView.frame.width,
                                height: containerView.frame.height)
     playerLayer.videoGravity = .resizeAspectFill //視頻填充模式
     containerView.layer.addSublayer(playerLayer)
 }
  • 監(jiān)聽playerItem狀態(tài)
 func addPlayerItemObserver(){
     // 為AVPlayerItem添加status屬性觀察,得到資源準備好,開始播放視頻
     playerItem?.addObserver(self, forKeyPath: "status", options: .new, context: nil)
     // 監(jiān)聽AVPlayerItem的loadedTimeRanges屬性來監(jiān)聽緩沖進度更新
     playerItem?.addObserver(self, forKeyPath: "loadedTimeRanges", options: .new, context: nil)
     NotificationCenter.default.addObserver(self,
                                            selector: #selector(playerItemDidReachEnd(notification:)),
                                            name: .AVPlayerItemDidPlayToEndTime, object: playerItem)
 }

重寫監(jiān)聽方法,控制播放

 ///  通過KVO監(jiān)控播放器狀態(tài)
 ///
 /// - parameter keyPath: 監(jiān)控屬性
 /// - parameter object:  監(jiān)視器
 /// - parameter change:  狀態(tài)改變
 /// - parameter context: 上下文
 override func observeValue(forKeyPath keyPath: String?,
                             of object: Any?,
                             change: [NSKeyValueChangeKey : Any]?,
                             context: UnsafeMutableRawPointer?) {
      guard let object = object as? AVPlayerItem  else { return }
      guard let keyPath = keyPath else { return }
      if keyPath == "status" {
          if object.status == .readyToPlay { //當資源準備好播放,那么開始播放視頻
              player?.play()
              print("正在播放...,視頻總長度:\(formatPlayTime(seconds: CMTimeGetSeconds(object.duration)))")
          } else if object.status == .failed || object.status == .unknown {
              print("播放出錯")
          }
      } else if keyPath == "loadedTimeRanges" {
          let loadedTime = availableDurationWithplayerItem()
          print("當前加載進度\(loadedTime)")
      }
 }

 // 將秒轉成時間字符串的方法,因為我們將得到秒。
 func formatPlayTime(seconds: Float64) -> String {
     let min = Int(seconds / 60)
     let sec = Int(seconds.truncatingRemainder(dividingBy: 60))
     return String(format: "%02d:%02d", min, sec)
 }

播放結束,回到最開始位置,播放按鈕顯示帶播放圖標

@objc func playerItemDidReachEnd(notification: Notification) {
        player?.seek(to: CMTime.zero,
                     toleranceBefore: CMTime.zero,
                     toleranceAfter: CMTime.zero)
        progress.progress = 0.0
        playOrPauseButton.setImage(UIImage(named:"play"), for: .normal)
 }

獲取當前加載進度

 func availableDurationWithplayerItem() -> TimeInterval {
      guard let loadedTimeRanges = player?.currentItem?.loadedTimeRanges,
            let first = loadedTimeRanges.first else {
                fatalError()
      }
      // 本次緩沖時間范圍
      let timeRange = first.timeRangeValue
      let startSeconds = CMTimeGetSeconds(timeRange.start) // 本次緩沖起始時間
      let durationSecound = CMTimeGetSeconds(timeRange.duration)// 緩沖時間
      let result = startSeconds + durationSecound// 緩沖總長度
      return result
 }
  • 給播放器添加進度監(jiān)聽
 func addProgressObserver(){
     // 這里設置每秒執(zhí)行一次.
     timeObserver =  player?.addPeriodicTimeObserver(forInterval: CMTimeMake(value: Int64(1.0),
                                                     timescale: Int32(1.0)),
                                                     queue: DispatchQueue.main) { [weak self] (time: CMTime) in
                                                         self?.updateProgress(time)
        }
    }

 func updateProgress(_ time: CMTime) {
     // CMTimeGetSeconds函數(shù)是將CMTime轉換為秒,如果CMTime無效,將返回NaN
     guard let playerItem = playerItem else {
         return
     }
     let currentTime = CMTimeGetSeconds(time)
     let totalTime = CMTimeGetSeconds(playerItem.duration)
     // 更新顯示的時間和進度條
     self.timeLabel.text = self.formatPlayTime(seconds: CMTimeGetSeconds(time))
     self.progress.setProgress(Float(currentTime/totalTime), animated: true)
     print("當前已經(jīng)播放\(self.formatPlayTime(seconds: CMTimeGetSeconds(time)))")
 }
  • 播放、暫停
 @objc func playOrPauseButtonClicked(button: UIButton) {
       if let player = player {
           if player.rate == 0 { // 點擊時已暫停
               button.setImage(UIImage(named:"pause"), for: .normal)
               player.play()
           } else if player.rate == 1 {// 點擊時正在播放
               player.pause()
               button.setImage(UIImage(named:"play"), for: .normal)
           }
       }
 }

最后,記得去除監(jiān)聽觀察者

 func removeObserver() {
     playerItem?.removeObserver(self, forKeyPath: "status")
     playerItem?.removeObserver(self, forKeyPath: "loadedTimeRanges")
     player?.removeTimeObserver(timeObserver)
     NotificationCenter.default.removeObserver(self,
                                               name:  .AVPlayerItemDidPlayToEndTime,
                                               object: playerItem)
 }

 deinit {
     removeObserver()
 }

參考

AVFoundation Programming Guide
AVPlayer
AVPlayerItem
AVAsset

最后編輯于
?著作權歸作者所有,轉載或內(nèi)容合作請聯(lián)系作者
【社區(qū)內(nèi)容提示】社區(qū)部分內(nèi)容疑似由AI輔助生成,瀏覽時請結合常識與多方信息審慎甄別。
平臺聲明:文章內(nèi)容(如有圖片或視頻亦包括在內(nèi))由作者上傳并發(fā)布,文章內(nèi)容僅代表作者本人觀點,簡書系信息發(fā)布平臺,僅提供信息存儲服務。

友情鏈接更多精彩內(nèi)容