學習swift有段時間了,原來寫過一個基於 swift 3.0 的視頻播放,後來有同窗聯繫我說,在音頻鎖屏的狀況下,沒法用控制面板拖動進度條調節播放進度,因此又將原來的代碼拿過來從新整理了下也順便更新到了4.0版本。在把原來的代碼拿來的時候發現原來有好多地方都是錯誤的,原來在 OC 項目裏面已經寫過一遍關於視頻播放的東西因此就按照原來的邏輯寫了 swift 版本,其實裏面不少代碼我也是經過查找資料和看文檔拼湊出來的,對於 swift 的語句也是隻知其一;不知其二,但願各位看官多多包涵。git
先來看一下實現的效果,一圖勝千言(第一張是 iOS 10系統,第二張是 iOS 11系統)。github
demo下載地址簡單說一下工程結構,全部關於佈局都是在Player
文件夾下的MPlayerViewModel
文件中,考慮到耦合度的緣由,因此將視頻播放的全部 UI 佈局所有抽離出來,在播放器 view 裏將會頻繁看到一個叫viewModel
的對象,它既 UI 佈局也是佈局控件的全部者。視頻播放的佈局是基於SnapKit
三方庫來佈局了,由於在OC裏用慣了Masonry
因此工程裏依然沿用這個庫。主要代碼是放到MPlayerView
這個文件中的,其中還有一個由 OC 寫的DeviceTool
文件主要用來作頁面強制旋轉用的,強制旋轉這一部分我如今尚未更好的解決辦法只能橋接 OC 裏的方法。swift
視頻播放界面我用的是一個單例實現的,剛開始不是用單例實現,可是爲了把代碼拆出來放到各自的功能區因此用單例實現是最好的方法。因爲swift放棄了OC裏的dispatch_once
實現單例方法,swift3.0之後的單例寫法:api
/// 建立播放器單例
static let shared = MPlayerView()
private override init(frame: CGRect) {
super.init(frame: frame)
}
required init?(coder aDecoder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
複製代碼
在swift3.0以後重寫init方法必須實現required init
方法,這麼作也是爲了安全,由於在OC裏init
方法並不能保證子類完成初始化,增長required
「這是由初始化方法的完備性需求所決定的,以保證類型的安全。在建立視頻播放視圖有兩種建立方式:1.用單利建立。2.init 初始化 ,這兩種方法均可以達到視頻播放的效果。緩存
1.單利初始化
self.playerView = MPlayerView.shared.initWithFrame(frame: self.view.frame, videoUrl: videoUrl, type: "VIDEO")
2.init 初始化
self.playerView = MPlayerView().initWithFrame(frame: CGRect.init(x: 0, y: 0, width: Screen_width, height: Screen_width * 9/16), videoUrl: videoUrl, type: "VIDEO")
複製代碼
因爲swift裏面有嚴格的類型檢查,就好比在作手勢滑動的時候,手勢剛開始滑動的時候確定須要記錄一下當前播放器的位置我在項目中是定義的sumTime
屬性是一個CMTime
類型,若是在OC裏大可沒必要這樣,來看一下swift與OC代碼的區別安全
swift寫法網絡
/// 給sumTime初值
let time = self.player?.currentTime()
self.sumTime = CMTimeMake((time?.value)!, (time?.timescale)!)
複製代碼
OC寫法ide
// 給sumTime初值
CMTime time = self.player.currentTime;
self.sumTime = time.value/time.timescale;
複製代碼
滑動的距離是一個Double
類型,而self.sumTime
是CMTime類型,倆者確定不能想加算出結束滑動的距離,因此將double類型轉換成CMTime類型用如下方法:佈局
CMTime.init(seconds: Double.init(value/200), preferredTimescale: CMTimeScale(NSEC_PER_SEC))
複製代碼
若是是OC的話直接括號強轉類型便可實現。學習
知道滑動的距離和記錄滑動前的距離倆者想加便是當前位置,轉化成CMTime類型:
self.sumTime = CMTimeAdd(self.sumTime!, addend)
複製代碼
手勢是滑動了,可是進度條也是要跟着一塊兒滑動的,有人說我把進度條刷新放到player的代理裏面,手勢滑動完只須要把時間傳給播放器,播放器根據當前時間和總時間去更新進度條,這樣作也對,可是有一點就是,若是網速很差,手勢已經滑動到5分鐘了,而進度條還停留在1分鐘的地方,播放器緩存完畢了,進度條會瞬間跳到5分鐘,從而形成卡頓的假象體驗也不是很好,因此解決這個方法是手勢滑動的時候也更新進度條,可是手勢滑動的時候都是CMTime類型,怎麼轉成Float
類型,由於slider?.value
是float類型。能夠這樣:經過CMTimeGetSeconds
方法獲得一個Float64
再經過Float.init
方法獲得一個float類型,看一下實現:
let sliderTime = CMTimeGetSeconds(self.sumTime!)/CMTimeGetSeconds(totalMovieDuration)
self.slider?.value = Float.init(sliderTime)
複製代碼
想查看整個過程能夠看播放器手勢添加與建立
這一塊,我已經用MARK:
標記起來了。
在視頻播放過程當中,對視頻的監聽是必不可少的,監聽播放器狀態,播放器緩存...等,因爲播放器比較簡單,功能較少,剛開始我只監聽了status
屬性,後來我加上來loadedTimeRanges
緩存狀態,緩存這部分的緩存進度計算我已經實現了,可是沒有用到只是簡單的打印了一下。
在對播放器status
屬性監聽中加入了控制面板信息,是由MPNowPlayingInfoCenter
來實現的,經過改變nowPlayingInfo
裏面對應的信息來更新面板信息,裏面有好多屬性,好比MPMediaItemPropertyTitle
設置音頻標題,MPMediaItemPropertyArtist
做者、MPNowPlayingInfoPropertyElapsedPlaybackTime
當前播放過的時間、MPMediaItemPropertyPlaybackDuration
播放總時間等等。剛開始作的時候由於鎖屏要更新時間,而nowPlayingInfo
又是一個字典類型的再加上須要更新界面佈局的時間和進度條,直接將播放器時間強制轉換成 string 類型,因此將這一部分放到了時間觀察裏面,由於時間觀察會一直進行因此鎖屏界面信息也會一直更新,這樣帶來一個問題就是鎖屏界面的圖片若是是網絡圖片,每1秒就要請求一下圖片並且要不斷的更新這樣帶來的結果可想而知。後來才知道,將MPNowPlayingInfoPropertyElapsedPlaybackTime
屬性設置成self.player!.currentTime()
播放器當前時間就會自動更新控制面板信息,調用的地方也很關鍵,必須放在播放器已經播放的監聽裏面。
響應遠程控制是由MPRemoteCommandCenter
來實現的,裏面有不少屬性,好比:playCommand
播放響應事件、pauseCommand
暫停響應事件、nextTrackCommand
下一曲響應事件、likeCommand
喜歡按鈕,相似網易雲音樂的那個鎖屏,若是設置了likeCommand
則dislikeCommand
是上一首響應事件、previousTrackCommand
上一首,外部拖動進度條是changePlaybackPositionCommand
,系統有一個專門的方法來出來遠程拖動進度條響應事件:
open func addTarget(handler: @escaping (MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus) -> Any
複製代碼
大概控制面板能用到的這些信息差很少也就這麼多,若是想了解更多的能夠看一下文檔或者查閱資料。
一個視頻播放實現起來並不困難,只要處理好player
與platitem
就好了。最難的就是,若是手機屏幕旋轉,怎麼能讓視頻跟着屏幕自適應呢,我在工程裏面經過UIDevice
變化添加的是屏幕旋轉監聽:
/**
* 監聽設備旋轉通知
*/
private func listeningRotating() {
UIDevice.current.beginGeneratingDeviceOrientationNotifications()
NotificationCenter.default.addObserver(self, selector: #selector(onDeviceOrientationChange), name:NSNotification.Name.UIDeviceOrientationDidChange, object: nil)
}
複製代碼
若是用戶把屏幕旋轉關掉,就是控制中心那個開關,用戶旋轉屏幕,怎麼能讓畫面跟着跑呢,我百度的不少資料,試了也不少方法,可是都不理想,用的仍是OC的代碼,由於swift裏面移除了NSInvocation
屬性,用的依然是OC的屏幕強制旋轉,只能使用橋接文件:
//這個方法是在網上找的
+ (void)interfaceOrientation:(UIInterfaceOrientation)orientation{
if ([[UIDevice currentDevice] respondsToSelector:@selector(setOrientation:)]) {
SEL selector = NSSelectorFromString(@"setOrientation:");
NSInvocation *invocation = [NSInvocation invocationWithMethodSignature:[UIDevice instanceMethodSignatureForSelector:selector]];
[invocation setSelector:selector];
[invocation setTarget:[UIDevice currentDevice]];
int val = orientation;
// 從2開始是由於0 1 兩個參數已經被selector和target佔用
[invocation setArgument:&val atIndex:2];
[invocation invoke];
}
}
複製代碼
由於作的是視頻播放,因此進入後臺後視頻會暫停,這個屬於正常現象,若是在視頻模式下,進入後臺利用控制面板是沒法將視頻播放的,若是在音頻模式下,進入後臺利用控制面板是可讓視頻播放的。大概就介紹這麼多,一言半句也說得不是很明白,若是還有不明白的知識點能夠去demo中本身去查,我也是一個初學者裏面不少東西都是查資料得來的並不能保證其內容的正確性。