实现主要功能:
- 1、返回前一页
- 2、上下渐变层
- 3、遮罩imageView层
- 4、播放暂停按钮
- 5、屏幕中间播放按钮
- 6、进度条
- 7、播放时间显示
- 8、大屏/小屏切换
- 9、屏幕亮度调节
- 10、系统声音调节
- 11、快进/快退
- 12、点击屏幕隐藏/展示上下渐变层
github链接
https://github.com/kongzichixiangjiao/YYPlayer
使用实例:
注:
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>NSAllowsArbitraryLoads</key>
<true/>
</dict>
</plist>
// 示例:
@IBOutlet weak var playerBackView: UIView!
override func viewDidLoad() {
super.viewDidLoad()
}
func initPlayerView() {
self.pplayerBackView.addSubview(pView)
playerBackView.addConstraint(NSLayoutConstraint(item: pView, attribute: .left, relatedBy: .equal, toItem: playerBackView, attribute: .left, multiplier: 1, constant: 0))
playerBackView.addConstraint(NSLayoutConstraint(item: pView, attribute: .top, relatedBy: .equal, toItem: playerBackView, attribute: .top, multiplier: 1, constant: 0))
playerBackView.addConstraint(NSLayoutConstraint(item: pView, attribute: .bottom, relatedBy: .equal, toItem: playerBackView, attribute: .bottom, multiplier: 1, constant: 0))
playerBackView.addConstraint(NSLayoutConstraint(item: pView, attribute: .right, relatedBy: .equal, toItem: playerBackView, attribute: .right, multiplier: 1, constant: 0))
}
lazy var pView: YYPlayerView = {
let v = YYPlayerView.loadPlayerView()
v.urlString = "http://qiniu.puxinasset.com/099f507aed7c5799d5ff40386a1a9615.mp4"
v.maskImageView.image = UIImage()
return v
}()
或者
pView.snp.makeConstraints({ (make) in
make.edges.equalTo(playerBackView)
})
let pView = YYPlayerView.loadPlayerView().then {
$0.urlString = "http://qiniu.puxinasset.com/099f507aed7c5799d5ff40386a1a9615.mp4"
}
添加系统类
import MediaPlayer
枚举
// MARK: 播放器状态枚举
enum YYPlayerState: Int {
case play = 1, pause = 2, stop = 3, finished = 4, unknow = 99
}
// MARK: 播放器屏幕枚举
enum YYPlayerScreenState: Int {
case full = 1, small = 2
}
YYPlayerView类中
主要设置如下参数即可
// 是否展示预览图
public var isShowMaskImageView: Bool = true
// 快进/快退时间
public var intervalTime: Double = 4
// 播放的url
public var urlString: String? {
didSet {
guard let url = URL(string: urlString!) else {
print("错误 urlString ❎❌❌❌❌❎")
return
}
config(url: url)
}
}
初始化方法
// 初始化YYPlayerView方法
static func loadPlayerView() -> YYPlayerView {
return Bundle.main.loadNibNamed("YYPlayerView", owner: self, options: nil)?.last as! YYPlayerView
}
更新视图:
1.初始化
2.横竖屏切换
override func layoutSubviews() {
super.layoutSubviews()
// 横竖屏切换从新设置playerLayer大小, 第一次添加playerLayer也会走起
screenView.layer.insertSublayer(YYPlayer.share.playerLayer, below: self.maskImageView.layer)
YYPlayer.share.playerLayer.frame = bounds
}
添加tap和pan手势
1.点击只操作隐藏/显示渐变层
2.pan主要操作亮度、快进/快退、声音
// 添加手势
private func initGestureRecognizer() {
let tap = UITapGestureRecognizer(target: self, action: #selector(tapScreenView(_:)))
self.screenView.addGestureRecognizer(tap)
let pan = UIPanGestureRecognizer(target: self, action: #selector(panScreenView(_:)))
self.screenView.addGestureRecognizer(pan)
}
获取设置声音的slider来更改声音
// 获取设置声音的slider
lazy var volumeSlider: UISlider? = {
let v = MPVolumeView()
v.showsRouteButton = true
v.showsVolumeSlider = true
v.sizeToFit()
v.frame = CGRect(x: 0, y: 0, width: 100, height: 100)
for v in v.subviews {
if (v.classForCoder.description() == "MPVolumeSlider") {
return v as? UISlider
}
}
return nil
}()
实时更新进度条和时间的闭包
// 实时更新进度条和时间的闭包
lazy var displayLinkHandler: YYPlayer.DisplayLinkHandler = {
[weak self] currentTime, catchTime, state in
if let weakSelf = self {
if state == .play {
if !weakSelf.isSliderDragging {
weakSelf.currentTimeLabel.text = String(format: "%02d:%02d", Int(currentTime.seconds) / 60, Int(currentTime.seconds) % 60)
weakSelf.progressSlider.value = Float(currentTime.seconds / weakSelf.totalTime)
weakSelf.currentTime = currentTime.seconds
}
}
}
}
获取当前player的viewcontroller
// MARK: 获取当前的controller
extension YYPlayerView {
fileprivate struct AssociatedKeys {
static var playerViewController: UIViewController = UIViewController()
}
open var playerViewController: UIViewController? {
get {
guard let playerViewController = objc_getAssociatedObject(self, &AssociatedKeys.playerViewController) as? UIViewController else {
var next = self.next
while next != nil {
if next!.isKind(of: UIViewController.self) {
return next as? UIViewController
}
next = next?.next
}
return nil
}
return playerViewController
}
}
}
YYPlayer类
监听四中keyPath
// 监听四种keyPath
private let kPath_status = "status"
private let kPath_loadedTimeRanges = "loadedTimeRanges"
private let kPath_playbackBufferEmpty = "playbackBufferEmpty"
private let kPath_playbackLikelyToKeepUp = "playbackLikelyToKeepUp"
单例
static let share: YYPlayer = YYPlayer()
添加通知:播放结束、异常中断、进入后台、进入前天
// MARK: 监听通知:播放结束、异常中断、进入后台、进入前天
fileprivate func addNotificationCenter() {
// 添加视频播放结束通知
NotificationCenter.default.addObserver(self, selector: #selector(didPlayToEndTimeNotification), name: Notification.Name.AVPlayerItemDidPlayToEndTime, object: playerItem)
// 添加视频异常中断通知
NotificationCenter.default.addObserver(self, selector: #selector(playbackStalledNotification), name: Notification.Name.AVPlayerItemPlaybackStalled, object: playerItem)
// 添加程序将要进入后台通知
NotificationCenter.default.addObserver(self, selector: #selector(willEnterBcakgroundNotification), name: Notification.Name.UIApplicationWillResignActive, object: nil)
// 添加程序已经返回前台通知
NotificationCenter.default.addObserver(self, selector: #selector(didEnterPlayGroundNotification), name: Notification.Name.UIApplicationDidBecomeActive, object: nil)
}
播放方法
private func _play() {
if playerInfoState != .readyToPlay {
return
}
player.play()
displayLink = CADisplayLink(target: self, selector: #selector(refreshProgress))
displayLink.add(to: RunLoop.main, forMode: .defaultRunLoopMode)
}
// 实时刷新progress
@objc private func refreshProgress() {
displayLinkHandler(playerItem.currentTime(), CMTime(), .play)
}
YYPlayerButton类
class YYPlayerButton: UIButton {
var playState: YYPlayerState! {
didSet {
switch playState {
case .play:
self.isSelected = true
case .pause:
self.isSelected = false
case .unknow:
print("更改状态时候发生未知错误")
case .none:
break
case .some(_):
break
}
}
}
var fullState: YYPlayerScreenState! {
didSet {
switch fullState {
case .full:
self.isSelected = true
case .small:
self.isSelected = false
case .none:
break
case .some(_):
break
}
}
}
}
YYPlayerGradualChangeView类
class YYPlayerGradualChangeView: UIView {
@IBInspectable var isUpDeep: Bool = false
var gradientLayer: CAGradientLayer?
override func awakeFromNib() {
super.awakeFromNib()
self.backgroundColor = UIColor.clear
}
override func layoutSubviews() {
super.layoutSubviews()
let gradientLayer = initGradientLayer()
self.layer.addSublayer(gradientLayer)
gradientLayer.frame = self.layer.bounds
}
func initGradientLayer() -> CAGradientLayer {
if let g = gradientLayer {
return g
}
let g = CAGradientLayer()
let colors = [
UIColor(red: 0, green: 0, blue: 0, alpha: 0.8).cgColor,
UIColor(red: 0, green: 0, blue: 0, alpha: 0.1).cgColor,
]
let locations = [
0.0, 1
]
g.colors = colors
g.locations = locations as [NSNumber]
let startPoint = self.isUpDeep ? CGPoint(x: 0.5, y: 0) : CGPoint(x: 0.5, y: 1)
let endPoint = self.isUpDeep ? CGPoint(x: 0.5, y: 1) : CGPoint(x: 0.5, y: 0)
g.startPoint = startPoint
g.endPoint = endPoint
gradientLayer = g
return g
}
}
设置屏幕旋转
var extension_key: UInt = 1205
extension UIResponder {
var allowRotation: Bool? {
get {
return objc_getAssociatedObject(self, &extension_key) as? Bool
}
set(newValue) {
objc_setAssociatedObject(self, &extension_key, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
}
}
// UIDevice.current.setValue(value, forKey: kOrientation) 之后调用此方法
@objc(application:supportedInterfaceOrientationsForWindow:) func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {
if allowRotation == true {
return .landscapeRight
} else {
return .portrait
}
}
}
详细代码点击链接
https://github.com/kongzichixiangjiao/YYPlayer
感谢:ly_coder
https://github.com/LY-Coder/LYPlayer