LWPlayer.swift 20 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577
  1. //
  2. // LWPlayer.swift
  3. // SwiftBilibili
  4. //
  5. // Created by 罗文 on 2021/3/28.
  6. // Copyright © 2021年 罗文. All rights reserved.
  7. //
  8. import UIKit
  9. import AVFoundation
  10. import MediaPlayer
  11. open class LWPlayer: NSObject {
  12. public static var showLog = true
  13. open weak var delegate: LWPlayerDelegate?
  14. open var videoGravity = LWPlayerVideoGravity.aspectFill{
  15. didSet {
  16. if let layer = self.playerView?.layer as? AVPlayerLayer{
  17. layer.videoGravity = AVLayerVideoGravity(rawValue: videoGravity.rawValue)
  18. }
  19. }
  20. }
  21. /// 视频类型
  22. open var videoType = LWPlayerVideoType.av
  23. /// 设置url会自动播放
  24. open var autoPlay = true
  25. /// 设备横屏时自动旋转(phone)
  26. open var autoLandscapeFullScreenLandscape = UIDevice.current.userInterfaceIdiom == .phone
  27. /// 全屏的模式
  28. open var fullScreenMode = LWPlayerFullScreenMode.landscape
  29. /// 全屏时status bar的样式
  30. open var fullScreenPreferredStatusBarStyle = UIStatusBarStyle.lightContent
  31. /// 全屏时status bar的背景色
  32. open var fullScreenStatusbarBackgroundColor = UIColor.black.withAlphaComponent(0.3)
  33. /// 上下滑动屏幕的控制类型
  34. open var slideTrigger = (left:LWPlayerSlideTrigger.volume,right:LWPlayerSlideTrigger.brightness)
  35. /// 左右滑动屏幕改变视频进度
  36. open var canSlideProgress = true
  37. /// 嵌入模式的控制皮肤
  38. open var controlViewForEmbedded : UIView?
  39. /// 浮动模式的控制皮肤
  40. open var controlViewForFloat : UIView?
  41. /// 全屏模式的控制皮肤
  42. open var controlViewForFullscreen : UIView?
  43. /// 嵌入模式的容器
  44. open weak var embeddedContentView: UIView?
  45. /// 嵌入模式的显示隐藏
  46. open private(set) var controlsHidden = false
  47. /// 过多久自动消失控件,设置为<=0不消失
  48. open var autohiddenTimeInterval: TimeInterval = 5
  49. /// 返回按钮block
  50. open var backButtonBlock:(( _ fromDisplayMode: LWPlayerDisplayMode) -> Void)?
  51. open var controlViewForIntercept : UIView? {
  52. didSet{
  53. self.updateCustomView()
  54. }
  55. }
  56. private var playerView: LWPlayerView?
  57. open var view: UIView{
  58. if self.playerView == nil {
  59. self.playerView = LWPlayerView(controlView: self.controlView)
  60. }
  61. return self.playerView!
  62. }
  63. private var timeObserver: Any?
  64. private var timer : Timer?
  65. open private(set) var isM3U8 = false
  66. /// 视频截图
  67. open private(set) var imageGenerator: AVAssetImageGenerator?
  68. /// 视频截图m3u8
  69. open private(set) var videoOutput: AVPlayerItemVideoOutput?
  70. open private(set) var contentURL :URL?{
  71. didSet{
  72. guard let url = contentURL else {
  73. return
  74. }
  75. self.isM3U8 = url.absoluteString.hasSuffix(".m3u8")
  76. }
  77. }
  78. open var isLive: Bool? {
  79. if let duration = self.duration {
  80. return duration.isNaN
  81. }
  82. return nil
  83. }
  84. //放置于playerView之上
  85. open var controlView : UIView?{
  86. if let view = self.controlViewForIntercept{
  87. return view
  88. }
  89. switch self.displayMode {
  90. case .embedded:
  91. return self.controlViewForEmbedded
  92. case .fullscreen:
  93. return self.controlViewForFullscreen
  94. case .float:
  95. return self.controlViewForFloat
  96. case .none:
  97. return self.controlViewForEmbedded
  98. }
  99. }
  100. open private(set) var player: AVPlayer? {
  101. willSet{
  102. removePlayerObserver()
  103. }
  104. didSet{
  105. addPlayerObserver()
  106. }
  107. }
  108. open private(set) var playerAsset: AVAsset?{
  109. didSet{
  110. if oldValue != playerAsset{
  111. if let playerAsset = playerAsset {
  112. self.imageGenerator = AVAssetImageGenerator(asset: playerAsset)
  113. }else{
  114. self.imageGenerator = nil
  115. }
  116. }
  117. }
  118. }
  119. open private(set) var playerItem: AVPlayerItem?{
  120. willSet{
  121. if playerItem != newValue{
  122. removePlayerItemObserver()
  123. removePlayerNotifications()
  124. }
  125. }
  126. didSet {
  127. if playerItem != oldValue{
  128. addPlayerItemObserver()
  129. addPlayerNotifications()
  130. }
  131. }
  132. }
  133. open fileprivate(set) var state = LWPlayerState.unknown {
  134. didSet{
  135. if oldValue != state {
  136. playStateDidChange()
  137. }
  138. }
  139. }
  140. open private(set) var displayMode = LWPlayerDisplayMode.none{
  141. didSet{
  142. if oldValue != displayMode{
  143. (self.controlView as? LWPlayerDelegate)?.player(self, playerDisplayModeDidChange: displayMode)
  144. self.delegate?.player(self, playerDisplayModeDidChange: displayMode)
  145. }
  146. }
  147. }
  148. open private(set) var lastDisplayMode = LWPlayerDisplayMode.none
  149. /// 视频是否正在播放
  150. open var isPlaying:Bool{
  151. guard let player = self.player else {
  152. return false
  153. }
  154. return player.rate > Float(0) && player.error == nil
  155. }
  156. /// 视频长度
  157. open var duration: TimeInterval? {
  158. if let duration = self.player?.duration {
  159. return duration
  160. }
  161. return nil
  162. }
  163. /// 视频进度
  164. open var currentTime: TimeInterval? {
  165. if let currentTime = self.player?.currentTime {
  166. return currentTime
  167. }
  168. return nil
  169. }
  170. /// 视频播放速率
  171. open var rate: Float{
  172. get {
  173. if let player = self.player {
  174. return player.rate
  175. }
  176. return .nan
  177. }
  178. set {
  179. if let player = self.player {
  180. player.rate = newValue
  181. }
  182. }
  183. }
  184. /// 系统音量
  185. open var systemVolume: Float{
  186. get {
  187. return LWPlayerUtils.systemVolumeSlider.value
  188. }
  189. set {
  190. LWPlayerUtils.systemVolumeSlider.value = newValue
  191. }
  192. }
  193. //life cycle
  194. deinit {
  195. NotificationCenter.default.removeObserver(self)
  196. self.timer?.invalidate()
  197. self.timer = nil
  198. self.releasePlayerResource()
  199. }
  200. public override init() {
  201. super.init()
  202. self.commonInit()
  203. }
  204. public init(controlView: UIView?) {
  205. super.init()
  206. if controlView == nil{
  207. self.controlViewForEmbedded = UIView()
  208. }else{
  209. self.controlViewForEmbedded = controlView
  210. }
  211. self.commonInit()
  212. }
  213. //MARK: - Public
  214. open func playWithURL(_ url: URL?,embeddedContentView contentView: UIView? = nil) {
  215. self.contentURL = url
  216. self.prepareToPlay()
  217. if let contentView = contentView {
  218. self.embeddedContentView = contentView
  219. self.embeddedContentView!.addSubview(self.view)
  220. self.view.frame = self.embeddedContentView!.bounds
  221. self.displayMode = .embedded
  222. }
  223. }
  224. open func play(){
  225. self.state = .playing
  226. self.player?.play()
  227. }
  228. open func pause(){
  229. self.state = .pause
  230. self.player?.pause()
  231. }
  232. open func stop() {
  233. //let lastState = self.state
  234. self.state = .stopped
  235. self.player?.pause()
  236. self.releasePlayerResource()
  237. }
  238. open func seek(to time: TimeInterval, completionHandler: ((Bool) -> Swift.Void )? = nil) {
  239. guard let player = self.player else { return }
  240. let lastState = self.state
  241. if let currentTime = self.currentTime {
  242. if currentTime > time {
  243. self.state = .seekingBackward
  244. }else if currentTime < time {
  245. self.state = .seekingForward
  246. }
  247. }
  248. player.seek(to: CMTimeMakeWithSeconds(time, preferredTimescale:CMTimeScale(NSEC_PER_SEC)), toleranceBefore:.zero, toleranceAfter:.zero, completionHandler: { [weak self] (finished) in
  249. guard let `self` = self else { return }
  250. switch self.state {
  251. case .seekingBackward,.seekingForward:
  252. self.state = lastState
  253. default: break
  254. }
  255. completionHandler?(finished)
  256. })
  257. }
  258. open func updateCustomView(toDisplayMode: LWPlayerDisplayMode? = nil) {
  259. var nextDisplayMode = self.displayMode
  260. defer { self.displayMode = nextDisplayMode }
  261. if toDisplayMode != nil{
  262. nextDisplayMode = toDisplayMode!
  263. }
  264. if let view = self.controlViewForIntercept{
  265. self.playerView?.controlView = view
  266. self.displayMode = nextDisplayMode
  267. }else{
  268. switch nextDisplayMode {
  269. case .embedded:
  270. //playerView加问号,其实不关心playerView存不存在,存在就更新
  271. if self.playerView?.controlView == nil || self.playerView?.controlView != self.controlViewForEmbedded{
  272. if self.controlViewForEmbedded == nil {
  273. // self.controlViewForEmbedded = self.controlViewForFullscreen ?? Bundle(for: EZPlayerControlView.self).loadNibNamed(String(describing: EZPlayerControlView.self), owner: self, options: nil)?.last as? EZPlayerControlView
  274. }
  275. }
  276. self.playerView?.controlView = self.controlViewForEmbedded
  277. case .fullscreen:
  278. if self.playerView?.controlView == nil || self.playerView?.controlView != self.controlViewForFullscreen{
  279. if self.controlViewForFullscreen == nil {
  280. // self.controlViewForFullscreen = self.controlViewForEmbedded ?? Bundle(for: EZPlayerControlView.self).loadNibNamed(String(describing: EZPlayerControlView.self), owner: self, options: nil)?.last as? EZPlayerControlView
  281. }
  282. }
  283. self.playerView?.controlView = self.controlViewForFullscreen
  284. case .float:
  285. if self.playerView?.controlView == nil || self.playerView?.controlView != self.controlViewForFloat{
  286. if self.controlViewForFloat == nil {
  287. // self.controlViewForFloat = Bundle(for: EZPlayerFloatView.self).loadNibNamed(String(describing: EZPlayerFloatView.self), owner: self, options: nil)?.last as? UIView
  288. }
  289. }
  290. self.playerView?.controlView = self.controlViewForFloat
  291. break
  292. case .none:
  293. //初始化的时候
  294. if self.controlView == nil {
  295. self.controlViewForEmbedded = LWPlayerControlView()
  296. }
  297. }
  298. }
  299. }
  300. //MARK: - Private
  301. private func commonInit() {
  302. updateCustomView()
  303. self.timer?.invalidate()
  304. self.timer = nil
  305. self.timer = Timer.timerWithTimeInterval(0.5, block: {[weak self] in
  306. guard let `self` = self,
  307. let _ = self.player,
  308. let playerItem = self.playerItem
  309. else { return }
  310. if playerItem.isPlaybackLikelyToKeepUp && self.state == .playing {
  311. self.state = .buffering
  312. }
  313. if playerItem.isPlaybackLikelyToKeepUp && (self.state == .buffering || self.state == .readyToPlay) {
  314. self.state = .playing
  315. }
  316. }, repeats: true)
  317. RunLoop.current.add(self.timer!, forMode: .common)
  318. }
  319. private func prepareToPlay() {
  320. guard let url = self.contentURL else {
  321. self.state = .error(.invalidContentURL)
  322. return
  323. }
  324. self.releasePlayerResource()
  325. self.playerAsset = AVAsset(url: url)
  326. let keys = ["tracks","duration","commonMetadata","availableMediaCharacteristicsWithMediaSelectionOptions"]
  327. self.playerItem = AVPlayerItem(asset: self.playerAsset!, automaticallyLoadedAssetKeys: keys)
  328. self.player = AVPlayer(playerItem: playerItem!)
  329. if self.playerView == nil {
  330. self.playerView = LWPlayerView(controlView:self.controlView )
  331. }
  332. (self.playerView?.layer as! AVPlayerLayer).videoGravity = AVLayerVideoGravity(rawValue: self.videoGravity.rawValue)
  333. self.playerView?.config(player: self)
  334. (self.controlView as? LWPlayerDelegate)?.player(self, showLoading: true)
  335. self.delegate?.player(self, showLoading: true)
  336. }
  337. private func resetPlayerResource() {
  338. self.contentURL = nil
  339. if let videoOutput = self.videoOutput {
  340. self.playerItem?.remove(videoOutput)
  341. self.videoOutput = nil
  342. }
  343. self.playerAsset = nil
  344. self.playerItem = nil
  345. self.player?.replaceCurrentItem(with: nil)
  346. self.playerView?.layer.removeAllAnimations()
  347. (self.controlView as? LWPlayerDelegate)?.player(self, loadedTimeDidChange: 0, totalDuration: 0)
  348. self.delegate?.player(self, loadedTimeDidChange: 0, totalDuration: 0)
  349. (self.controlView as? LWPlayerDelegate)?.player(self, playedTimeDidChange:0, totalDuration: 0)
  350. self.delegate?.player(self, playedTimeDidChange: 0, totalDuration: 0)
  351. }
  352. private func releasePlayerResource() {
  353. if let videoOutput = self.videoOutput {
  354. self.playerItem?.remove(videoOutput)
  355. self.videoOutput = nil
  356. }
  357. self.playerAsset = nil
  358. self.playerItem = nil
  359. self.player?.replaceCurrentItem(with: nil)
  360. self.playerView?.layer.removeAllAnimations()
  361. self.playerView?.removeFromSuperview()
  362. self.playerView = nil
  363. if let timeObserver = self.timeObserver{
  364. self.player?.removeTimeObserver(timeObserver)
  365. self.timeObserver = nil
  366. }
  367. }
  368. }
  369. // MARK: - Notifation Selector & KVO
  370. extension LWPlayer {
  371. private func removePlayerObserver() {
  372. if let timeObserver = timeObserver {
  373. player?.removeTimeObserver(timeObserver)
  374. }
  375. }
  376. private func removePlayerItemObserver() {
  377. playerItem?.removeObserver(self, forKeyPath: #keyPath(AVPlayerItem.status))
  378. playerItem?.removeObserver(self, forKeyPath: #keyPath(AVPlayerItem.loadedTimeRanges))
  379. playerItem?.removeObserver(self, forKeyPath: #keyPath(AVPlayerItem.isPlaybackBufferEmpty))
  380. playerItem?.removeObserver(self, forKeyPath: #keyPath(AVPlayerItem.isPlaybackLikelyToKeepUp))
  381. }
  382. private func removePlayerNotifications() {
  383. NotificationCenter.default.removeObserver(self, name: .AVPlayerItemDidPlayToEndTime, object: nil)
  384. NotificationCenter.default.removeObserver(self, name: UIApplication.willEnterForegroundNotification, object: nil)
  385. NotificationCenter.default.removeObserver(self, name: UIApplication.didEnterBackgroundNotification, object: nil)
  386. }
  387. private func addPlayerObserver() {
  388. timeObserver = player?.addPeriodicTimeObserver(forInterval: CMTimeMakeWithSeconds(1, preferredTimescale: CMTimeScale(NSEC_PER_SEC)), queue: DispatchQueue.main, using: {[weak self] (time) in
  389. guard let `self` = self,
  390. let currentTime = self.currentTime,
  391. let duration = self.duration
  392. else { return }
  393. (self.controlView as? LWPlayerDelegate)?.player(self, playedTimeDidChange: currentTime, totalDuration: duration)
  394. self.delegate?.player(self, playedTimeDidChange: currentTime, totalDuration: duration)
  395. })
  396. }
  397. private func addPlayerItemObserver() {
  398. playerItem?.addObserver(self, forKeyPath: #keyPath(AVPlayerItem.status), options: NSKeyValueObservingOptions.new, context: nil)
  399. playerItem?.addObserver(self, forKeyPath: #keyPath(AVPlayerItem.loadedTimeRanges), options: NSKeyValueObservingOptions.new, context: nil)
  400. // 缓冲区空了,需要等待数据
  401. playerItem?.addObserver(self, forKeyPath: #keyPath(AVPlayerItem.isPlaybackBufferEmpty), options: NSKeyValueObservingOptions.new, context: nil)
  402. // 缓冲区有足够数据可以播放了
  403. playerItem?.addObserver(self, forKeyPath: #keyPath(AVPlayerItem.isPlaybackLikelyToKeepUp), options: NSKeyValueObservingOptions.new, context: nil)
  404. }
  405. private func addPlayerNotifications() {
  406. NotificationCenter.default.addObserver(self, selector: #selector(playerItemDidPlayToEnd), name: .AVPlayerItemDidPlayToEndTime, object: nil)
  407. NotificationCenter.default.addObserver(self, selector: #selector(applicationWillEnterForeground), name: UIApplication.willEnterForegroundNotification, object: nil)
  408. NotificationCenter.default.addObserver(self, selector: #selector(applicationDidEnterBackground), name: UIApplication.didEnterBackgroundNotification, object: nil)
  409. }
  410. @objc private func playerItemDidPlayToEnd(_ notification: Notification) {
  411. self.state = .stopped
  412. }
  413. @objc private func applicationWillEnterForeground(_ notification: Notification) {
  414. }
  415. @objc private func applicationDidEnterBackground(_ notification: Notification) {
  416. }
  417. open override func observeValue(forKeyPath keyPath: String?, of object: Any?, change: [NSKeyValueChangeKey : Any]?, context: UnsafeMutableRawPointer?) {
  418. if let item = object as? AVPlayerItem, let keyPath = keyPath {
  419. if item == self.playerItem {
  420. switch keyPath {
  421. case #keyPath(AVPlayerItem.status):
  422. print("AVPlayerItem's status is changed: \(item.status.rawValue)")
  423. if item.status == .readyToPlay {
  424. let lastState = self.state
  425. if self.state != .playing{
  426. self.state = .readyToPlay
  427. }
  428. //自动播放
  429. if self.autoPlay && lastState == .unknown{
  430. self.play()
  431. }
  432. } else if item.status == .failed {
  433. self.state = .error(.playerFail)
  434. }
  435. case #keyPath(AVPlayerItem.loadedTimeRanges):
  436. print("AVPlayerItem's loadedTimeRanges is changed")
  437. let loadedTimeRanges = item.loadedTimeRanges
  438. if let bufferTimeRange = loadedTimeRanges.first?.timeRangeValue {
  439. let star = bufferTimeRange.start.seconds // The start time of the time range.
  440. let duration = bufferTimeRange.duration.seconds // The duration of the time range.
  441. let bufferTime = star + duration
  442. (self.controlView as? LWPlayerDelegate)?.player(self, loadedTimeDidChange: bufferTime, totalDuration: self.duration ?? 0)
  443. self.delegate?.player(self, loadedTimeDidChange: bufferTime, totalDuration: self.duration ?? 0)
  444. }
  445. case #keyPath(AVPlayerItem.isPlaybackBufferEmpty):
  446. print("AVPlayerItem's playbackBufferEmpty is changed")
  447. case #keyPath(AVPlayerItem.isPlaybackLikelyToKeepUp):
  448. print("AVPlayerItem's playbackLikelyToKeepUp is changed")
  449. default:
  450. break
  451. }
  452. }
  453. }
  454. }
  455. }
  456. // MARK: - State change
  457. extension LWPlayer {
  458. private func playStateDidChange() {
  459. (self.controlView as? LWPlayerDelegate)?.player(self, playerStateDidChange: state)
  460. self.delegate?.player(self, playerStateDidChange: state)
  461. switch state {
  462. case .buffering:
  463. (self.controlView as? LWPlayerDelegate)?.player(self, showLoading: true)
  464. self.delegate?.player(self, showLoading: true)
  465. case .error(_):
  466. (self.controlView as? LWPlayerDelegate)?.player(self, showLoading: false)
  467. self.delegate?.player(self, showLoading: false)
  468. default:
  469. (self.controlView as? LWPlayerDelegate)?.player(self, showLoading: false)
  470. self.delegate?.player(self, showLoading: false)
  471. }
  472. }
  473. }