基於swift4.0實現視頻播放、屏幕旋轉、倍速播放、手勢調節,鎖屏面板等功能

學習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喜歡按鈕,相似網易雲音樂的那個鎖屏,若是設置了likeCommanddislikeCommand是上一首響應事件、previousTrackCommand上一首,外部拖動進度條是changePlaybackPositionCommand,系統有一個專門的方法來出來遠程拖動進度條響應事件:

open func addTarget(handler: @escaping (MPRemoteCommandEvent) -> MPRemoteCommandHandlerStatus) -> Any
複製代碼

大概控制面板能用到的這些信息差很少也就這麼多,若是想了解更多的能夠看一下文檔或者查閱資料。

屏幕旋轉問題

一個視頻播放實現起來並不困難,只要處理好playerplatitem就好了。最難的就是,若是手機屏幕旋轉,怎麼能讓視頻跟着屏幕自適應呢,我在工程裏面經過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中本身去查,我也是一個初學者裏面不少東西都是查資料得來的並不能保證其內容的正確性。

相關文章
相關標籤/搜索