最近在學習swift,恰巧現在負責的項目中有關于視頻播放的一些東西,就想著用swift去實現,視頻播放在原OC項目中已實現基本功能,所以就模仿OC去寫swift視頻播放,在寫的過程中發(fā)現與OC的實現方法出入還是比較大的,網上關于swift視頻播放資料不是很全面,所以想給大家分享一下知識點。
先來看一下實現的效果,沒實現效果我知道同學肯定看不下去
簡單說一下工程結構,所有關于播放的東西以及布局都是在AVPlayer
文件夾下,視頻播放的布局是基于SnapKit
三方庫來布局了,因為在OC里用慣了Masonry
所以工程里依然沿用這個庫。因為項目里面有線路切換和音視頻切換功能,,如果你有未加密的視頻鏈接或者音頻鏈接直接把sdk刪掉也是可以的,也是可以正常播放的。
關鍵代碼是放在MPlayerView
這個文件中,輔助視圖布局的三個文件分別是:RateView
音視頻倍速切換ResolutionView
高清度切換SwitchCircuitView
線路切換。
剛開始做的時候,把所有的功能代碼都全部放入MPlayerView
這個文件中,發(fā)現耦合度太多,代碼可讀性差,所以我將代碼拆出來各自負責的模塊放入各自的功能,比如高清度切換功能,實現的功能就是放在ResolutionView
文件下。
在這里就不貼太多的代碼,我將在本文末放demo的下載地址。現在就簡單聊一下實現過程吧。視頻播放界面我用的是一個單例實現的,剛開始不是用單例實現,但是為了把代碼拆出來放到各自的功能區(qū)所以用單例實現是最好的方法。由于swift放棄了OC里的dispatch_once
實現單例方法,swift3.0以后的單例寫法:
/// 創(chuàng)建播放器單例
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
“這是由初始化方法的完備性需求所決定的,以保證類型的安全。
由于swift里面有嚴格的類型檢查,就比如在做手勢滑動的時候,手勢剛開始滑動的時候肯定需要記錄一下當前播放器的位置我在項目中是定義的sumTime
屬性是一個CMTime
類型,如果在OC里大可不必這樣,來看一下swift與OC代碼的區(qū)別
swift寫法
/// 給sumTime初值
let time = self.player?.currentTime()
self.sumTime = CMTimeMake((time?.value)!, (time?.timescale)!)
OC寫法
// 給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)
想查看整個過程可以看播放器手勢添加與創(chuàng)建
這一塊,我已經用MARK:
標記起來了。
一個視頻播放實現起來并不困難,只要處理好player
與platitem
就行了。最難得就是,如果手機屏幕旋轉,怎么能讓視頻跟著屏幕自適應呢,我在工程里面通過UIDevice
變化添加的是屏幕旋轉監(jiān)聽:
/**
* 監(jiān)聽設備旋轉通知
*/
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中自己去查.