ios 視訊播放器:AVPlayer(附:seektotime精準定位)

ios 視訊播放器:AVPlayer(附:seektotime精準定位)

Swift 3.1{

func playVideo() {
var playerItem = AVPlayerItem(url: movieURL as URL)
// 建立 AVPlayer 播放器
var player = AVPlayer(playerItem: playerItem)
// 將 AVPlayer 新增到 AVPlayerLayer 上
let playerLayer = AVPlayerLayer(player: player)
// 設定播放頁面大小
playerLayer.frame = CGRect(x: CGFloat(10), y: CGFloat(30), width: CGFloat(view.bounds.size.width - 20), height: CGFloat(200))
// 設定畫面縮放模式
playerLayer.videoGravity = AVLayerVideoGravityResizeAspect
// 在檢視上新增播放器
view.layer.addSublayer(playerLayer)
// 開始播放
player.play()
}

}

網上有很多AVPlayer的播放器,但對於seekToTime,時間跳轉都不是很精確,尤其是在使用定時器的情況下,本人找了半天,都是誤人子弟,所以寫了這篇精準定位的方法:self.player seekToTime(time, toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero)

self.player seekToTime(time, toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero)
本文重點就是這句話,其他的是一些關於實現播放器的程式碼,大家可以不看

一直想做一個用H5寫的播放器,實現跨平臺,解決蘋果不能播放flv的問題,如哪位大神有經驗可以指導一下[email protected]

ObjectIve – c:

   新增庫檔案:AVFoundation.framework
包含標頭檔案:#import <AVFoundation/AVFoundation.h>

簡單實用:通過avplayerItem建立

// 載入網路視訊
NSURL *movieUrl = [NSURL URLWithString:@"http://bos.nj.bpc.baidu.com/tieba-smallvideo/11772_3c435014fb2dd9a5fd56a57cc369f6a0.mp4"];
AVPlayerItem *playerItem = [AVPlayerItem playerItemWithURL:movieUrl];
// 建立 AVPlayer 播放器
AVPlayer *player = [AVPlayer playerWithPlayerItem:playerItem];
// 將 AVPlayer 新增到 AVPlayerLayer 上
AVPlayerLayer *playerLayer = [AVPlayerLayer playerLayerWithPlayer:player];
// 設定播放頁面大小
playerLayer.frame = CGRectMake(10, 30, self.view.bounds.size.width - 20, 200);
// 設定畫面縮放模式
playerLayer.videoGravity = AVLayerVideoGravityResizeAspect;
// 在檢視上新增播放器
[self.view.layer addSublayer:playerLayer];
// 開始播放
[player play];
AVPlayer 本身並不能顯示視訊,而且它也不像 MPMoviePlayerController 有一個view 屬性。如果 AVPlayer 要顯示必須建立一個播放器層 AVPlayerLayer 用於展示,播放器層繼承於CALayer, 
有了 AVPlayerLayer 之新增到控制器檢視的 layer 中即可。要使用 AVPlayer 首先了解一下幾個常用的類:
AVAsset:主要用於獲取多媒體資訊,是一個抽象類,不能直接使用。
AVURLAsset:AVAsset 的子類,可以根據一個URL路徑建立一個包含媒體資訊的AVURLAsset物件。
AVPlayerItem:一個媒體資源管理物件,管理者視訊的一些基本資訊和狀態,一個AVPlayerItem對應著一個視訊資源。

本文是swift 開發的. 使用

https://objectivec2swift.com/#/home/main

可以轉成Objective-C

效果圖

這裡寫圖片描述

MeidaPlayer 框架中的 MPMoviePlayerController 類和 MPMoviePlayerViewController 類是 iOS 中視訊播放的開發相關類和方法。但是介面封裝的已經非常固化,不適合調整。在 iOS9 中,MPMoviePlayerController 與 MPMoviePlayerViewController 類也被完全棄用,開發者使用 AVPlayerViewController 可以十分方便的實現視訊播放的功能並在一些型號的 iPad 上整合畫中畫的功能

介紹:本部落格介紹如何使用AVPlayer進行播放,暫停,視訊切換,迴圈播放,跳轉到指定時間,並精準定位,包含對AVPlayer播放器的監聽等

新增播放器,播放視訊

/*
根據playerItem建立播放器player 
*/
if player == nil {
playerItem = AVPlayerItem(URL: movieUrl)
player = AVPlayer(playerItem: playerItem)
/*
建立一個播放器層 AVPlayerLayer 用於展示
*/
let playerLayer:AVPlayerLayer = AVPlayerLayer(player: player)
playerLayer.frame = self.view.frame
playerLayer.videoGravity = AVLayerVideoGravityResizeAspect
self.view.layer.addSublayer(playerLayer)
/*
設定playerLayer.videoGravity
*/
playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
/*
設定播放完成通知 Swift的方法有些特殊和oc不同#selector(SituationalEnglishVc.handleTapGesture)
*/           NSNotificationCenter.defaultCenter().addObserver(self,
selector: #selector(playerEnd),
name: AVPlayerItemDidPlayToEndTimeNotification,
object: playerItem)
addSomeObserver()
playState = true
player.status
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(SituationalEnglishVc.handleTapGesture))
tapGesture.numberOfTapsRequired = 1
let tapView = UIView.init(frame: playerLayer.frame)
avplayerView.addSubview(tapView)
tapView.addGestureRecognizer(tapGesture)
player.play()
/*
建立定時器,用於在特定時間跳轉
*/
getCurrentTimer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: #selector(SituationalEnglishVc.getCurrentMovieTime), userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(getCurrentTimer, forMode: NSDefaultRunLoopMode)
}

暫停播放

 player.pause()

切換電影

movieUrl = NSURL(string: "http://bos.nj.bpc.baidu.com/tieba-smallvideo/11772_3c435014fb2dd9a5fd56a57cc369f6a0.mp4")!
playerItem = AVPlayerItem(URL: movieUrl)
//用這個方法就可以切換
player.replaceCurrentItemWithPlayerItem(playerItem)

備註:這是電影神燈的介面,神燈產品使用者體驗群:573431381 大家可以下載一下,看看有什麼技術可以探討一下

seekToTime精準定位

關於這個seektotime,是個坑,我跳了好多次,比如我要調到10秒,結果跳到了6秒,我要調15秒,跳到了18秒,很奇怪,我以為是AVPlayer本身定位不準,後來發現這樣解決是可以的
我們不能用這個方法self.player.seekToTime(Time: CMTime)
使用這個方法,通過設定偏差tolerance,來精確設定的時間是多少,很管用
self.player.seekToTime(time, toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero)

還有一些冗餘程式碼是對tableview的visibleCells進行操作的大家可以不看:如下

func scrollToCurrentRow(idx:Int){
currentRow = idx
if self.chineseArray.count > 0 {
let indexPath = NSIndexPath(forRow: idx, inSection: 0)
for item in sentenceTb.visibleCells{
let cell = item as! SentenceTableViewCell
cell.EnglishLb.textColor = colorFromHex("666666")
cell.ChineseLb.textColor = colorFromHex("666666")
}
let cell = sentenceTb.cellForRowAtIndexPath(indexPath) as? SentenceTableViewCell
if cell != nil {
cell!.EnglishLb.textColor = colorFromHex("5eecff")
cell!.ChineseLb.textColor = colorFromHex("5eecff")
}
switch currentRow {
case chineseArray.count - 1:
sentenceTb.scrollToRowAtIndexPath(indexPath, atScrollPosition: UITableViewScrollPosition.Bottom, animated: true)
break
case chineseArray.count - 2:
sentenceTb.scrollToRowAtIndexPath(indexPath, atScrollPosition: UITableViewScrollPosition.Middle, animated: true)
break
default:
sentenceTb.scrollToRowAtIndexPath(indexPath, atScrollPosition: UITableViewScrollPosition.Top, animated: true)
break
}
}
}

最後附上所有程式碼

//
//  SituationalEnglishVc.swift
//  eTimeMachine
//
//  Created by fsk-0-1-n on 16/9/18.
//  Copyright © 2016年 smartit. All rights reserved.
//
import UIKit
class SituationalEnglishVc: UIViewController,UITableViewDelegate,UITableViewDataSource {
var movieUrl : NSURL!
var player : AVPlayer!
var playState : Bool!
var playerItem : AVPlayerItem! = nil
var getCurrentTimer : NSTimer! = nil
var replayTimer : NSTimer! = nil
var wordStart : CGFloat!
var replayNum : Int!
var isCyclePlay : Bool!
var isRePlayNow : Bool!
var currentRow:Int = 0
var xzlrc = XZLrc()
var timeArray = [CGFloat]()
var wordArray = [String]()
var chineseArray = [String]()
@IBOutlet weak var sentenceTb: UITableView!
@IBOutlet weak var wordView: UIView!
@IBOutlet weak var avplayerView: UIView!
@IBOutlet weak var controlView: UIView!
override func viewWillAppear(animated: Bool) {
self.navigationController?.navigationBarHidden = false
setStatusBarDefault(false)
setNavBarStyle()
var barBtn = UIBarButtonItem.init(title: "<", style: UIBarButtonItemStyle.Done , target: nil, action: nil)
self.navigationItem.leftBarButtonItem = barBtn
}
override func viewDidLoad() {
super.viewDidLoad()
movieUrl = NSURL(string: "http://bos.nj.bpc.baidu.com/tieba-smallvideo/11772_3c435014fb2dd9a5fd56a57cc369f6a0.mp4")!
self.navigationItem.title = "場景轟炸"
self.hideTabBar()
replayNum = 0
wordStart = 10.100000
isCyclePlay = false
isRePlayNow = false
getTitleNet(2)
AVPlayerSetting()
LoadingGif()
AVPlayerControlView()
setSentenceTableView()
sentenceTb.separatorStyle = UITableViewCellSeparatorStyle.None
}
override func viewWillDisappear(animated: Bool) {
self.player.removeObserver(self, forKeyPath: "status")
if getCurrentTimer != nil {
getCurrentTimer.invalidate()
getCurrentTimer = nil
}
if replayTimer != nil {
replayTimer.invalidate()
replayTimer = nil
}
self.player.pause()
if self.player != nil {
avplayerView.removeFromSuperview()
avplayerView = nil
}
}
func getTitleNet(moiveId:Int) {
//獲取字幕資訊
xzlrc.getLrcWithUrl(moiveId) {
self.timeArray = self.xzlrc.timeArray
self.wordArray = self.xzlrc.wordArray
self.chineseArray = self.xzlrc.chineseArray
self.sentenceTb.reloadData()
}
}
func AVPlayerControlView()  {
controlView.backgroundColor = colorFromHex( "1b1a18", alpha: 0.6)
controlView.hidden = false
}
func AVPlayerSetting() {
if player == nil {
playerItem = AVPlayerItem(URL: movieUrl)
player = AVPlayer(playerItem: playerItem)
let playerLayer:AVPlayerLayer = AVPlayerLayer(player: player)
playerLayer.frame = avplayerView.frame
playerLayer.videoGravity = AVLayerVideoGravityResizeAspect
avplayerView.layer.addSublayer(playerLayer)
playerLayer.videoGravity = AVLayerVideoGravityResizeAspectFill
NSNotificationCenter.defaultCenter().addObserver(self,
selector: #selector(playerEnd),
name: AVPlayerItemDidPlayToEndTimeNotification,
object: playerItem)
addSomeObserver()
playState = true
player.status
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(SituationalEnglishVc.handleTapGesture))
tapGesture.numberOfTapsRequired = 1
let tapView = UIView.init(frame: playerLayer.frame)
avplayerView.addSubview(tapView)
tapView.addGestureRecognizer(tapGesture)
player.play()
getCurrentTimer = NSTimer.scheduledTimerWithTimeInterval(0.1, target: self, selector: #selector(SituationalEnglishVc.getCurrentMovieTime), userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(getCurrentTimer, forMode: NSDefaultRunLoopMode)
}
}
var webView:UIWebView!
func LoadingGif() {
let path = NSBundle.mainBundle().pathForResource("catLittle", ofType: "gif")!
let gifData = NSData(contentsOfFile: path)
webView = UIWebView(frame:avplayerView.frame)
self.view.addSubview(webView)
webView.layer.masksToBounds = true
webView.layer.cornerRadius = 5
webView.scalesPageToFit = true
webView.scrollView.scrollEnabled = false
webView.backgroundColor = UIColor.clearColor()
webView.opaque = false
webView.loadData(gifData!, MIMEType: "image/gif", textEncodingName: "", baseURL: NSURL.init())
self.view.addSubview(webView)
}
func addSomeObserver() {
//監聽PlayerItem這個類
self.player.addObserver(self, forKeyPath: "status", options: NSKeyValueObservingOptions.New, context: nil)
}
override func observeValueForKeyPath(keyPath: String?, ofObject object: AnyObject?, change: [String : AnyObject]?, context: UnsafeMutablePointer<Void>) {
if (keyPath == "status") {
if player.status == AVPlayerStatus.ReadyToPlay
{
print("準備播放")
if self.webView != nil{
self.webView.removeFromSuperview()
webView = nil
}
}
else if player.status == AVPlayerStatus.Failed || player.status == AVPlayerStatus.Unknown {
player.pause()
}
}
}
func setSentenceTableView() {
sentenceTb.backgroundColor = colorFromHex("1b1a18")
sentenceTb.delegate = self
sentenceTb.dataSource = self
}
func tableView(tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return timeArray.count
}
func tableView(tableView: UITableView, heightForRowAtIndexPath indexPath: NSIndexPath) -> CGFloat {
var ss = CGSize()
ss = strSize(chineseArray[indexPath.row], withMaxSize: CGSize(width: 200,height: 0), withFont: UIFont.systemFontOfSize(15), withLineBreakMode: NSLineBreakMode.ByCharWrapping)
var sss = CGSize()
sss = strSize(wordArray[indexPath.row], withMaxSize: CGSize(width: 200,height: 0), withFont: UIFont.systemFontOfSize(15), withLineBreakMode: NSLineBreakMode.ByCharWrapping)
return ss.height sss.height 8
}
func tableView(tableView: UITableView, cellForRowAtIndexPath indexPath: NSIndexPath) -> UITableViewCell {
var sentenceCell = tableView.dequeueReusableCellWithIdentifier("thirdID")as?SentenceTableViewCell
if sentenceCell == nil
{
sentenceCell = SentenceTableViewCell(style: .Default, reuseIdentifier: "thirdID")
}
sentenceCell?.ChineseLb.text = self.chineseArray[indexPath.row]
sentenceCell?.EnglishLb.text = self.wordArray[indexPath.row]
var ss = CGSize()
ss = strSize(self.wordArray[indexPath.row], withMaxSize: CGSize(width: 200,height: 0), withFont: UIFont.systemFontOfSize(15), withLineBreakMode: NSLineBreakMode.ByCharWrapping)
sentenceCell?.EnglishLb.frame = CGRectMake(self.view.frame.size.width/2-100, 4, 200, ss.height)
var sss = CGSize()
sss = strSize(self.chineseArray[indexPath.row], withMaxSize: CGSize(width: 200,height: 0), withFont: UIFont.systemFontOfSize(15), withLineBreakMode: NSLineBreakMode.ByCharWrapping)
sentenceCell?.ChineseLb.frame = CGRectMake(self.view.frame.size.width/2-100, ss.height 4, 200, sss.height)
return sentenceCell!
}
func strSize(str: String, withMaxSize size: CGSize, withFont font: UIFont, withLineBreakMode mode: NSLineBreakMode) -> CGSize {
var s: CGSize
s = str.boundingRectWithSize(size, options: [.UsesLineFragmentOrigin, .UsesFontLeading], attributes: [NSFontAttributeName: font], context: nil).size
return s
}
func tableView(tableView: UITableView, didSelectRowAtIndexPath indexPath: NSIndexPath) {
tableView.deselectRowAtIndexPath(indexPath, animated: true)
}
func handleTapGesture()
{
UIView.animateWithDuration(0.5) {
self.controlView.hidden = !self.controlView.hidden
}
}
func playerEnd() {
if isCyclePlay == true {
player.seekToTime(CMTimeMake(0, 1))
player.play()
}
}
@IBAction func playOrPauseClick(sender: AnyObject) {
if playState == true {
player.pause()
playState = false
}
else
{
player.play()
playState = true
}
}
@IBAction func leftMovieClick(sender: AnyObject) {
getTitleNet(1)
movieUrl = NSURL(string: "http://bos.nj.bpc.baidu.com/tieba-smallvideo/11772_3c435014fb2dd9a5fd56a57cc369f6a0.mp4")!
playerItem = AVPlayerItem(URL: movieUrl)
player.replaceCurrentItemWithPlayerItem(playerItem)
}
@IBAction func cyclePlayClick(sender: AnyObject) {
isCyclePlay = !isCyclePlay
}
@IBAction func rightMovieClick(sender: AnyObject) {
getTitleNet(4)
movieUrl = NSURL(string: "http://w2.dwstatic.com/1/5/1525/127352-100-1434554639.mp4")!
playerItem = AVPlayerItem(URL: movieUrl)
player.replaceCurrentItemWithPlayerItem(playerItem)
}
@IBAction func wordListBook(sender: AnyObject) {
}
func getCurrentMovieTime() {
scrollAccrodingTimeArray()
}
func scrollAccrodingTimeArray() {
let currentSecond:Double = CMTimeGetSeconds(self.playerItem.currentTime())
let s = String(format: "%.1f", currentSecond)
if isRePlayNow == false {
//判斷當前時間是否要重複強調
if s == String(format: "%.1f", wordStart)
{
isRePlayNow = true
replaySetting()
}
}
for index in 0 ..< timeArray.count {
let ss = String(format: "%.1f", timeArray[index]/1000)
//判斷當前時間是否要滾動句子
if ss == s {
scrollToCurrentRow(index)
}
}
}
func replaySetting() {
replayTimer = NSTimer.scheduledTimerWithTimeInterval(1.5, target: self, selector: #selector(SituationalEnglishVc.replayThreeTimes), userInfo: nil, repeats: true)
NSRunLoop.mainRunLoop().addTimer(replayTimer, forMode: NSDefaultRunLoopMode)
}
func replayThreeTimes() {
if replayNum  >= 3
{
replayTimer.invalidate()
replayTimer = nil
isRePlayNow = false
replayNum = 0
return
}
replayNum = replayNum 1
let timeScale = Int32(self.player.currentItem!.asset.duration.timescale)
let time = CMTimeMakeWithSeconds(Float64(wordStart), timeScale);
self.player.seekToTime(time, toleranceBefore: kCMTimeZero, toleranceAfter: kCMTimeZero)
self.player.seekToTime(<#T##time: CMTime##CMTime#>)
let currentTime1:CMTime = self.playerItem.currentTime()
let currentSecond1:Double = CMTimeGetSeconds(currentTime1)
print("###### \(currentSecond1)")
}
func scrollToCurrentRow(idx:Int){
currentRow = idx
if self.chineseArray.count > 0 {
let indexPath = NSIndexPath(forRow: idx, inSection: 0)
for item in sentenceTb.visibleCells{
let cell = item as! SentenceTableViewCell
cell.EnglishLb.textColor = colorFromHex("666666")
cell.ChineseLb.textColor = colorFromHex("666666")
}
let cell = sentenceTb.cellForRowAtIndexPath(indexPath) as? SentenceTableViewCell
if cell != nil {
cell!.EnglishLb.textColor = colorFromHex("5eecff")
cell!.ChineseLb.textColor = colorFromHex("5eecff")
}
switch currentRow {
case chineseArray.count - 1:
sentenceTb.scrollToRowAtIndexPath(indexPath, atScrollPosition: UITableViewScrollPosition.Bottom, animated: true)
break
case chineseArray.count - 2:
sentenceTb.scrollToRowAtIndexPath(indexPath, atScrollPosition: UITableViewScrollPosition.Middle, animated: true)
break
default:
sentenceTb.scrollToRowAtIndexPath(indexPath, atScrollPosition: UITableViewScrollPosition.Top, animated: true)
break
}
}
}
override func didReceiveMemoryWarning() {
super.didReceiveMemoryWarning()
// Dispose of any resources that can be recreated.
}
}
class SentenceTableViewCell: UITableViewCell {
let EnglishLb = UILabel()
let ChineseLb = UILabel()
override init(style: UITableViewCellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
self.contentView.addSubview(ChineseLb)
self.contentView.addSubview(EnglishLb)
self.contentView.backgroundColor = colorFromHex("1b1a18")
setUpviews()
self.backgroundColor = colorFromHex("1b1a18")
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
func setUpviews() {
EnglishLb.frame = CGRectMake(self.contentView.frame.size.width/2-100 , 0, 200, 20)
EnglishLb.text = "['prlk(e)l]"
EnglishLb.font = UIFont.systemFontOfSize(15)
EnglishLb.textColor = UIColor.whiteColor()
EnglishLb.textAlignment = NSTextAlignment.Center
EnglishLb.numberOfLines = 0;
ChineseLb.frame = CGRectMake(self.contentView.frame.size.width/2-100, 20, 200, 20)
ChineseLb.text = "n:規劃;計劃"
ChineseLb.font = UIFont.systemFontOfSize(15)
ChineseLb.textColor = UIColor.whiteColor()
ChineseLb.textAlignment = NSTextAlignment.Center
ChineseLb.numberOfLines = 0
}
}