diff --git a/CLAutolayoutController/CLAutolayoutController.swift b/CLAutolayoutController/CLAutolayoutController.swift index 80e2042..ee63016 100644 --- a/CLAutolayoutController/CLAutolayoutController.swift +++ b/CLAutolayoutController/CLAutolayoutController.swift @@ -19,7 +19,14 @@ class CLAutolayoutController: CLController { } private lazy var player: CLPlayer = { - let view = CLPlayer() + let view = CLPlayer { config in + config.isAutoRotate = true + config.isGestureInteractionEnabled = false + config.autoFadeOut = 8 + config.topBarHiddenStyle = .onlySmall + config.maskImage = UIImage(named: "placeholder") + } + view.delegate = self return view }() @@ -104,7 +111,13 @@ private extension CLAutolayoutController { private extension CLAutolayoutController { func initData() { + player.title = NSMutableAttributedString("Apple", attributes: { $0 + .font(.systemFont(ofSize: 16)) + .foregroundColor(.white) + .alignment(.center) + }) player.url = URL(string: "https://www.apple.com/105/media/us/iphone-x/2017/01df5b43-28e4-4848-bf20-490c34a926a7/films/feature/iphone-x-feature-tpl-cc-us-20170912_1280x720h.mp4") + player.play() } } @@ -125,6 +138,22 @@ extension CLAutolayoutController { @objc private extension CLAutolayoutController { func changeAction() { + player.title = NSMutableAttributedString("这是一个标题", attributes: { $0 + .font(.systemFont(ofSize: 16)) + .foregroundColor(.orange) + .alignment(.left) + }) player.url = URL(string: "https://www.apple.com/105/media/us/mac/family/2018/46c4b917_abfd_45a3_9b51_4e3054191797/films/peter/mac-peter-tpl-cc-us-2018_1280x720h.mp4") + player.play() + } +} + +extension CLAutolayoutController: CLPlayerDelegate { + func didClickBackButton(in _: CLPlayer) { + print("didClickBackButton") + } + + func didPlayToEndTime(in _: CLPlayer) { + print("didPlayToEndTime") } } diff --git a/CLCollectionViewController/CLCollectionViewController.swift b/CLCollectionViewController/CLCollectionViewController.swift new file mode 100644 index 0000000..4be1ea2 --- /dev/null +++ b/CLCollectionViewController/CLCollectionViewController.swift @@ -0,0 +1,198 @@ +// +// CLCollectionViewController.swift +// CLPlayer +// +// Created by Chen JmoVxia on 2021/12/16. +// + +import SnapKit +import UIKit + +// MARK: - JmoVxia---枚举 + +extension CLCollectionViewController {} + +// MARK: - JmoVxia---类-属性 + +class CLCollectionViewController: CLController { + override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { + super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit {} + + private lazy var collectionView: UICollectionView = { + let layout = UICollectionViewFlowLayout() + layout.minimumLineSpacing = 10 + layout.minimumInteritemSpacing = 10 + layout.itemSize = CGSize(width: view.bounds.width - 20, height: (view.bounds.width - 20) * 9.0 / 16.0) + let view = UICollectionView(frame: .zero, collectionViewLayout: layout) + view.contentInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) + view.backgroundColor = .clear + view.delegate = self + view.dataSource = self + view.register(CLCollectionViewCell.self, forCellWithReuseIdentifier: "CLCollectionViewCell") + return view + }() + + private var player: CLPlayer? + + let array = [ + "http://vfx.mtime.cn/Video/2019/03/19/mp4/190319212559089721.mp4", + "http://vfx.mtime.cn/Video/2019/03/18/mp4/190318231014076505.mp4", + "http://vfx.mtime.cn/Video/2019/02/04/mp4/190204084208765161.mp4", + "http://vfx.mtime.cn/Video/2019/03/21/mp4/190321153853126488.mp4", + "http://vfx.mtime.cn/Video/2019/03/19/mp4/190319222227698228.mp4", + "http://vfx.mtime.cn/Video/2019/03/19/mp4/190319212559089721.mp4", + "http://vfx.mtime.cn/Video/2019/03/18/mp4/190318231014076505.mp4", + "http://vfx.mtime.cn/Video/2019/03/18/mp4/190318214226685784.mp4", + "http://vfx.mtime.cn/Video/2019/03/19/mp4/190319104618910544.mp4", + "http://vfx.mtime.cn/Video/2019/03/19/mp4/190319125415785691.mp4", + "http://vfx.mtime.cn/Video/2019/03/17/mp4/190317150237409904.mp4", + "http://vfx.mtime.cn/Video/2019/03/14/mp4/190314223540373995.mp4", + "http://vfx.mtime.cn/Video/2019/03/14/mp4/190314102306987969.mp4", + "http://vfx.mtime.cn/Video/2019/03/13/mp4/190313094901111138.mp4", + "http://vfx.mtime.cn/Video/2019/03/12/mp4/190312143927981075.mp4", + "http://vfx.mtime.cn/Video/2019/03/12/mp4/190312083533415853.mp4", + "http://vfx.mtime.cn/Video/2019/03/09/mp4/190309153658147087.mp4", + "https://www.apple.com/105/media/us/mac/family/2018/46c4b917_abfd_45a3_9b51_4e3054191797/films/grimes/mac-grimes-tpl-cc-us-2018_1280x720h.mp4", + "https://www.apple.com/105/media/us/iphone-x/2017/01df5b43-28e4-4848-bf20-490c34a926a7/films/feature/iphone-x-feature-tpl-cc-us-20170912_1280x720h.mp4", + "https://www.apple.com/105/media/us/mac/family/2018/46c4b917_abfd_45a3_9b51_4e3054191797/films/peter/mac-peter-tpl-cc-us-2018_1280x720h.mp4", + "http://mirror.aarnet.edu.au/pub/TED-talks/911Mothers_2010W-480p.mp4", + ] +} + +// MARK: - JmoVxia---生命周期 + +extension CLCollectionViewController { + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + } + + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + } + + override func viewDidLoad() { + super.viewDidLoad() + initUI() + makeConstraints() + initData() + } + + override func viewWillDisappear(_ animated: Bool) { + super.viewWillDisappear(animated) + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + } + + override func viewDidLayoutSubviews() { + super.viewDidLayoutSubviews() + } +} + +// MARK: - JmoVxia---布局 + +private extension CLCollectionViewController { + func initUI() { + updateTitleLabel { $0.text = "CollectionView" } + view.addSubview(collectionView) + } + + func makeConstraints() { + collectionView.snp.makeConstraints { make in + make.left.right.bottom.equalToSuperview() + if #available(iOS 11.0, *) { + make.top.equalTo(view.safeAreaLayoutGuide.snp.top) + } else { + make.top.equalTo(topLayoutGuide.snp.bottom) + } + } + } +} + +// MARK: - JmoVxia---数据 + +private extension CLCollectionViewController { + func initData() {} +} + +// MARK: - JmoVxia---override + +extension CLCollectionViewController {} + +// MARK: - JmoVxia---objc + +@objc private extension CLCollectionViewController {} + +// MARK: - JmoVxia---私有方法 + +private extension CLCollectionViewController { + func playWithIndexPath(_ indexPath: IndexPath) { + guard let cell = collectionView.cellForItem(at: indexPath) else { return } + + if player == nil { + player = CLPlayer() + } + player?.title = NSMutableAttributedString("这是一个标题", attributes: { $0 + .font(.systemFont(ofSize: 16)) + .foregroundColor(.orange) + .alignment(.center) + }) + player?.url = URL(string: array[indexPath.row]) + cell.contentView.addSubview(player!) + player?.snp.remakeConstraints { make in + make.top.left.equalToSuperview() + make.size.equalTo(CGSize(width: cell.bounds.width, height: cell.bounds.height - 10)) + } + player?.play() + } +} + +// MARK: - JmoVxia---公共方法 + +extension CLCollectionViewController {} + +extension CLCollectionViewController: UICollectionViewDataSource { + func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { + let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CLCollectionViewCell", for: indexPath) + return cell + } + + func collectionView(_: UICollectionView, numberOfItemsInSection _: Int) -> Int { + return array.count + } +} + +extension CLCollectionViewController: UICollectionViewDelegate { + func collectionView(_: UICollectionView, didSelectItemAt indexPath: IndexPath) { + playWithIndexPath(indexPath) + } + + func collectionView(_: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { + guard let player = player else { return } + guard array[indexPath.row] == player.url?.absoluteString else { return } + + cell.contentView.addSubview(player) + player.snp.remakeConstraints { make in + make.top.left.equalToSuperview() + make.size.equalTo(CGSize(width: cell.bounds.width, height: cell.bounds.height - 10)) + } + player.play() + } + + func collectionView(_: UICollectionView, didEndDisplaying _: UICollectionViewCell, forItemAt indexPath: IndexPath) { + guard let player = player else { return } + guard array[indexPath.row] == player.url?.absoluteString else { return } + + player.removeFromSuperview() + player.pause() + } +} diff --git a/CLCollectionViewController/View/CLCollectionViewCell.swift b/CLCollectionViewController/View/CLCollectionViewCell.swift new file mode 100644 index 0000000..2d8bcc7 --- /dev/null +++ b/CLCollectionViewController/View/CLCollectionViewCell.swift @@ -0,0 +1,51 @@ +// +// CLCollectionViewCell.swift +// CLPlayer +// +// Created by Chen JmoVxia on 2021/12/16. +// + +import UIKit + +class CLCollectionViewCell: UICollectionViewCell { + override init(frame: CGRect) { + super.init(frame: frame) + initUI() + makeConstraints() + } + + @available(*, unavailable) + required init?(coder _: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + private lazy var iconImageView: UIImageView = { + let view = UIImageView() + view.image = UIImage(named: "placeholder") + return view + }() + + private lazy var playImageView: UIImageView = { + let view = UIImageView() + view.image = UIImage(named: "play") + return view + }() +} + +// MARK: - JmoVxia---布局 + +private extension CLCollectionViewCell { + func initUI() { + contentView.addSubview(iconImageView) + iconImageView.addSubview(playImageView) + } + + func makeConstraints() { + iconImageView.snp.makeConstraints { make in + make.edges.equalToSuperview() + } + playImageView.snp.makeConstraints { make in + make.center.equalToSuperview() + } + } +} diff --git a/CLFrameController/CLFrameController.swift b/CLFrameController/CLFrameController.swift index 8454c06..d11327f 100644 --- a/CLFrameController/CLFrameController.swift +++ b/CLFrameController/CLFrameController.swift @@ -93,7 +93,13 @@ private extension CLFrameController { private extension CLFrameController { func initData() { + player.title = NSMutableAttributedString("Apple", attributes: { $0 + .font(.systemFont(ofSize: 16)) + .foregroundColor(.white) + .alignment(.center) + }) player.url = URL(string: "https://www.apple.com/105/media/cn/mac/family/2018/46c4b917_abfd_45a3_9b51_4e3054191797/films/bruce/mac-bruce-tpl-cn-2018_1280x720h.mp4") + player.play() } } @@ -112,6 +118,12 @@ extension CLFrameController { @objc private extension CLFrameController { func changeAction() { + player.title = NSMutableAttributedString("这是一个标题", attributes: { $0 + .font(.systemFont(ofSize: 16)) + .foregroundColor(.white) + .alignment(.left) + }) player.url = URL(string: "http://vfx.mtime.cn/Video/2019/03/09/mp4/190309153658147087.mp4") + player.play() } } diff --git a/CLHomeController/Controller/CLHomeController.swift b/CLHomeController/Controller/CLHomeController.swift index daa281e..9120238 100644 --- a/CLHomeController/Controller/CLHomeController.swift +++ b/CLHomeController/Controller/CLHomeController.swift @@ -123,6 +123,15 @@ private extension CLHomeController { } tableViewHepler.dataSource.append(item) } + do { + let item = CLListItem() + item.title = "UICollectionView" + item.didSelectCellCallback = { [weak self] _ in + guard let self = self else { return } + self.pushToCollectionView() + } + tableViewHepler.dataSource.append(item) + } tableView.reloadData() } } @@ -149,6 +158,10 @@ private extension CLHomeController { func pushToTableView() { navigationController?.pushViewController(CLTableViewController(), animated: true) } + + func pushToCollectionView() { + navigationController?.pushViewController(CLCollectionViewController(), animated: true) + } } // MARK: - JmoVxia---公共方法 diff --git a/CLPlayer.podspec b/CLPlayer.podspec index a08b5cb..f0a6255 100755 --- a/CLPlayer.podspec +++ b/CLPlayer.podspec @@ -1,7 +1,7 @@ Pod::Spec.new do |s| s.name = 'CLPlayer' - s.version = '2.0.1' + s.version = '2.0.2' s.summary = 'Swift版自定义AVPlayer' s.description = <<-DESC CLPlayer是基于系统AVPlayer封装的视频播放器. diff --git a/CLPlayer.xcodeproj/project.pbxproj b/CLPlayer.xcodeproj/project.pbxproj index 289cb9e..f5ffaf0 100644 --- a/CLPlayer.xcodeproj/project.pbxproj +++ b/CLPlayer.xcodeproj/project.pbxproj @@ -11,6 +11,8 @@ 5B2D28E32729446800E85A53 /* CLRotateAnimationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2D28E22729446800E85A53 /* CLRotateAnimationView.swift */; }; 5B2D28E52729463800E85A53 /* CLPlayer.bundle in Resources */ = {isa = PBXBuildFile; fileRef = 5B2D28E42729463800E85A53 /* CLPlayer.bundle */; }; 5B2D28E72729468000E85A53 /* CLImageHelper.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B2D28E62729468000E85A53 /* CLImageHelper.swift */; }; + 5B33B4DE2769EAA20086B402 /* CLPlayerConfigure.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B33B4DD2769EAA20086B402 /* CLPlayerConfigure.swift */; }; + 5B33B4E02769F2990086B402 /* CLPlayerDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B33B4DF2769F2990086B402 /* CLPlayerDelegate.swift */; }; 5B37DEE02727D9ED002A377A /* UIColor+CLExtension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B37DEDF2727D9ED002A377A /* UIColor+CLExtension.swift */; }; 5B37DEE32727DA5E002A377A /* CLNavigationController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B37DEE22727DA5E002A377A /* CLNavigationController.swift */; }; 5B37DEE52727DB04002A377A /* CLController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B37DEE42727DB04002A377A /* CLController.swift */; }; @@ -23,11 +25,14 @@ 5B83040E2728FE0800A92423 /* CLFullScreenLeftController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B83040D2728FE0800A92423 /* CLFullScreenLeftController.swift */; }; 5B8304102728FE1100A92423 /* CLFullScreenRightController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B83040F2728FE1100A92423 /* CLFullScreenRightController.swift */; }; 5B830414272917FD00A92423 /* CLAnimationTransitioning.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5B830413272917FD00A92423 /* CLAnimationTransitioning.swift */; }; + 5BA11A70276AD7290093B418 /* NSMutableAttributedString+Extension.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA11A6F276AD7290093B418 /* NSMutableAttributedString+Extension.swift */; }; 5BA39B182727D147005059F5 /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA39B172727D147005059F5 /* AppDelegate.swift */; }; 5BA39B1F2727D147005059F5 /* Main.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5BA39B1D2727D147005059F5 /* Main.storyboard */; }; 5BA39B212727D148005059F5 /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = 5BA39B202727D148005059F5 /* Assets.xcassets */; }; 5BA39B242727D148005059F5 /* LaunchScreen.storyboard in Resources */ = {isa = PBXBuildFile; fileRef = 5BA39B222727D148005059F5 /* LaunchScreen.storyboard */; }; 5BA39B2F2727D1EE005059F5 /* CLTabBarController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BA39B2C2727D1EE005059F5 /* CLTabBarController.swift */; }; + 5BC41038276B18AB00DFE028 /* CLCollectionViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC41037276B18AB00DFE028 /* CLCollectionViewController.swift */; }; + 5BC4103B276B19C300DFE028 /* CLCollectionViewCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BC4103A276B19C300DFE028 /* CLCollectionViewCell.swift */; }; 5BE1F31B2727DCDB0090F6C8 /* CLMoreController.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BE1F31A2727DCDB0090F6C8 /* CLMoreController.swift */; }; 5BE1F3202727DDC00090F6C8 /* CLListCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BE1F31F2727DDC00090F6C8 /* CLListCell.swift */; }; 5BE1F3252727DE4C0090F6C8 /* CLListItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5BE1F3242727DE4C0090F6C8 /* CLListItem.swift */; }; @@ -52,6 +57,8 @@ 5B2D28E22729446800E85A53 /* CLRotateAnimationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLRotateAnimationView.swift; sourceTree = ""; }; 5B2D28E42729463800E85A53 /* CLPlayer.bundle */ = {isa = PBXFileReference; lastKnownFileType = "wrapper.plug-in"; path = CLPlayer.bundle; sourceTree = ""; }; 5B2D28E62729468000E85A53 /* CLImageHelper.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLImageHelper.swift; sourceTree = ""; }; + 5B33B4DD2769EAA20086B402 /* CLPlayerConfigure.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLPlayerConfigure.swift; sourceTree = ""; }; + 5B33B4DF2769F2990086B402 /* CLPlayerDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLPlayerDelegate.swift; sourceTree = ""; }; 5B37DEDF2727D9ED002A377A /* UIColor+CLExtension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "UIColor+CLExtension.swift"; sourceTree = ""; }; 5B37DEE22727DA5E002A377A /* CLNavigationController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLNavigationController.swift; sourceTree = ""; }; 5B37DEE42727DB04002A377A /* CLController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLController.swift; sourceTree = ""; }; @@ -64,6 +71,7 @@ 5B83040D2728FE0800A92423 /* CLFullScreenLeftController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLFullScreenLeftController.swift; sourceTree = ""; }; 5B83040F2728FE1100A92423 /* CLFullScreenRightController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLFullScreenRightController.swift; sourceTree = ""; }; 5B830413272917FD00A92423 /* CLAnimationTransitioning.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLAnimationTransitioning.swift; sourceTree = ""; }; + 5BA11A6F276AD7290093B418 /* NSMutableAttributedString+Extension.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSMutableAttributedString+Extension.swift"; sourceTree = ""; }; 5BA39B142727D147005059F5 /* CLPlayer.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = CLPlayer.app; sourceTree = BUILT_PRODUCTS_DIR; }; 5BA39B172727D147005059F5 /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; 5BA39B1E2727D147005059F5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/Main.storyboard; sourceTree = ""; }; @@ -71,6 +79,8 @@ 5BA39B232727D148005059F5 /* Base */ = {isa = PBXFileReference; lastKnownFileType = file.storyboard; name = Base; path = Base.lproj/LaunchScreen.storyboard; sourceTree = ""; }; 5BA39B252727D148005059F5 /* Info.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; 5BA39B2C2727D1EE005059F5 /* CLTabBarController.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CLTabBarController.swift; sourceTree = ""; }; + 5BC41037276B18AB00DFE028 /* CLCollectionViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLCollectionViewController.swift; sourceTree = ""; }; + 5BC4103A276B19C300DFE028 /* CLCollectionViewCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLCollectionViewCell.swift; sourceTree = ""; }; 5BE1F31A2727DCDB0090F6C8 /* CLMoreController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLMoreController.swift; sourceTree = ""; }; 5BE1F31F2727DDC00090F6C8 /* CLListCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLListCell.swift; sourceTree = ""; }; 5BE1F3242727DE4C0090F6C8 /* CLListItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CLListItem.swift; sourceTree = ""; }; @@ -105,6 +115,7 @@ 5B37DEE12727D9F0002A377A /* Extension */ = { isa = PBXGroup; children = ( + 5BA11A6F276AD7290093B418 /* NSMutableAttributedString+Extension.swift */, 5BE1F3332727E3800090F6C8 /* UIImage+Extension.swift */, 5B37DEDF2727D9ED002A377A /* UIColor+CLExtension.swift */, ); @@ -158,6 +169,7 @@ 5BE1F3282727E0C60090F6C8 /* CLAutolayoutController */, 5BFADCB42768888500A596A6 /* CLFrameController */, 5BE1F32B2727E11F0090F6C8 /* CLTableViewController */, + 5BC41036276B188900DFE028 /* CLCollectionViewController */, 5BE1F3372727E55E0090F6C8 /* Resources */, 5BA39B152727D147005059F5 /* Products */, D532A78DB1B29559F40D05B2 /* Pods */, @@ -177,6 +189,8 @@ isa = PBXGroup; children = ( 5BE1F3382727E5780090F6C8 /* CLPlayer.swift */, + 5B33B4DD2769EAA20086B402 /* CLPlayerConfigure.swift */, + 5B33B4DF2769F2990086B402 /* CLPlayerDelegate.swift */, 5B2D28E62729468000E85A53 /* CLImageHelper.swift */, 5BFADCB9276893B600A596A6 /* CLGCDTimer.swift */, 5BFADCB72768936C00A596A6 /* CLPlayerContentView */, @@ -196,6 +210,23 @@ path = Base; sourceTree = ""; }; + 5BC41036276B188900DFE028 /* CLCollectionViewController */ = { + isa = PBXGroup; + children = ( + 5BC41037276B18AB00DFE028 /* CLCollectionViewController.swift */, + 5BC41039276B19B500DFE028 /* View */, + ); + path = CLCollectionViewController; + sourceTree = ""; + }; + 5BC41039276B19B500DFE028 /* View */ = { + isa = PBXGroup; + children = ( + 5BC4103A276B19C300DFE028 /* CLCollectionViewCell.swift */, + ); + path = View; + sourceTree = ""; + }; 5BE1F31C2727DD960090F6C8 /* CLHomeController */ = { isa = PBXGroup; children = ( @@ -537,21 +568,26 @@ 5B37DEF02727DBD9002A377A /* CLCellProtocol.swift in Sources */, 5B37DEEF2727DBD9002A377A /* CLCellItemProtocol.swift in Sources */, 5BE1F32A2727E0F80090F6C8 /* CLAutolayoutController.swift in Sources */, + 5B33B4DE2769EAA20086B402 /* CLPlayerConfigure.swift in Sources */, 5BE1F3322727E21E0090F6C8 /* CLBackView.swift in Sources */, 5B72C197272A868E004B9AEA /* CLPlayerContentViewDelegate.swift in Sources */, 5BFADCBA276893B600A596A6 /* CLGCDTimer.swift in Sources */, 5B37DEF12727DBD9002A377A /* CLTableViewHepler.swift in Sources */, 5B8304102728FE1100A92423 /* CLFullScreenRightController.swift in Sources */, 5B2D28E32729446800E85A53 /* CLRotateAnimationView.swift in Sources */, + 5BC41038276B18AB00DFE028 /* CLCollectionViewController.swift in Sources */, 5B830414272917FD00A92423 /* CLAnimationTransitioning.swift in Sources */, 5B72C195272A39B1004B9AEA /* CLSlider.swift in Sources */, + 5BC4103B276B19C300DFE028 /* CLCollectionViewCell.swift in Sources */, 5BFADCAC2767415200A596A6 /* CLPlayerContentPanelHeadView.swift in Sources */, 5BE1F3342727E3800090F6C8 /* UIImage+Extension.swift in Sources */, 5B2D28E72729468000E85A53 /* CLImageHelper.swift in Sources */, 5B37DEE72727DBA5002A377A /* CLHomeController.swift in Sources */, + 5B33B4E02769F2990086B402 /* CLPlayerDelegate.swift in Sources */, 5BF306642728DFB40046075A /* CLFullScreenController.swift in Sources */, 5BE1F3252727DE4C0090F6C8 /* CLListItem.swift in Sources */, 5B37DEE52727DB04002A377A /* CLController.swift in Sources */, + 5BA11A70276AD7290093B418 /* NSMutableAttributedString+Extension.swift in Sources */, 5BE1F3202727DDC00090F6C8 /* CLListCell.swift in Sources */, 5BFADCB6276888B400A596A6 /* CLFrameController.swift in Sources */, ); diff --git a/CLPlayer/CLFullScreenController/CLAnimationTransitioning.swift b/CLPlayer/CLFullScreenController/CLAnimationTransitioning.swift index 52cf117..dc84c73 100644 --- a/CLPlayer/CLFullScreenController/CLAnimationTransitioning.swift +++ b/CLPlayer/CLFullScreenController/CLAnimationTransitioning.swift @@ -79,8 +79,9 @@ extension CLAnimationTransitioning: UIViewControllerAnimatedTransitioning { make.size.equalTo(transitionContext.containerView.bounds.size) } if #available(iOS 11.0, *) { - let safeAreaInsets = keyWindow?.safeAreaInsets ?? .zero - playView.contentView.animationLayout(safeAreaInsets: safeAreaInsets) + playView.contentView.animationLayout(safeAreaInsets: keyWindow?.safeAreaInsets ?? .zero, to: .fullScreen) + } else { + playView.contentView.animationLayout(safeAreaInsets: .zero, to: .fullScreen) } UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0, options: .layoutSubviews, animations: { toView.transform = .identity @@ -120,9 +121,9 @@ extension CLAnimationTransitioning: UIViewControllerAnimatedTransitioning { make.center.equalTo(centerInWindow) make.size.equalTo(originSize) } - if #available(iOS 11.0, *) { - playView.contentView.animationLayout(safeAreaInsets: .zero) - } + + playView.contentView.animationLayout(safeAreaInsets: .zero, to: .small) + UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0.0, options: .layoutSubviews, animations: { fromView.transform = .identity transitionContext.containerView.setNeedsLayout() diff --git a/CLPlayer/CLPlayer.bundle/CLBack@2x.png b/CLPlayer/CLPlayer.bundle/CLBack@2x.png index b6ae8af..f428efd 100644 Binary files a/CLPlayer/CLPlayer.bundle/CLBack@2x.png and b/CLPlayer/CLPlayer.bundle/CLBack@2x.png differ diff --git a/CLPlayer/CLPlayer.bundle/CLBack@3x.png b/CLPlayer/CLPlayer.bundle/CLBack@3x.png index e0a0d1a..8b49b83 100644 Binary files a/CLPlayer/CLPlayer.bundle/CLBack@3x.png and b/CLPlayer/CLPlayer.bundle/CLBack@3x.png differ diff --git a/CLPlayer/CLPlayer.bundle/CLMore@2x.png b/CLPlayer/CLPlayer.bundle/CLMore@2x.png index 3411c1d..babf1be 100644 Binary files a/CLPlayer/CLPlayer.bundle/CLMore@2x.png and b/CLPlayer/CLPlayer.bundle/CLMore@2x.png differ diff --git a/CLPlayer/CLPlayer.bundle/CLMore@3x.png b/CLPlayer/CLPlayer.bundle/CLMore@3x.png index bae3c66..01a4810 100644 Binary files a/CLPlayer/CLPlayer.bundle/CLMore@3x.png and b/CLPlayer/CLPlayer.bundle/CLMore@3x.png differ diff --git a/CLPlayer/CLPlayer.swift b/CLPlayer/CLPlayer.swift index d185ea5..73df3cd 100644 --- a/CLPlayer/CLPlayer.swift +++ b/CLPlayer/CLPlayer.swift @@ -10,8 +10,10 @@ import SnapKit import UIKit public class CLPlayer: UIView { - public override init(frame: CGRect) { + public init(frame: CGRect = .zero, configure: ((inout CLPlayerConfigure) -> Void)? = nil) { super.init(frame: frame) + configure?(&config) + updateConfig() initUI() makeConstraints() } @@ -22,14 +24,16 @@ public class CLPlayer: UIView { } deinit { - NotificationCenter.default.removeObserver(self, name: UIDevice.orientationDidChangeNotification, object: nil) + if config.isAutoRotate { + NotificationCenter.default.removeObserver(self, name: UIDevice.orientationDidChangeNotification, object: nil) + } NotificationCenter.default.removeObserver(self, name: UIApplication.willResignActiveNotification, object: nil) NotificationCenter.default.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil) print("CLPlayer deinit") } private(set) lazy var contentView: CLPlayerContentView = { - let view = CLPlayerContentView() + let view = CLPlayerContentView(config: config) view.delegate = self return view }() @@ -49,6 +53,8 @@ public class CLPlayer: UIView { return timer }() + private var config = CLPlayerConfigure() + private var animationTransitioning: CLAnimationTransitioning? private var fullScreenController: CLFullScreenController? @@ -61,6 +67,8 @@ public class CLPlayer: UIView { private var isUserPause: Bool = false + private var isEnterBackground: Bool = false + private var player: AVPlayer? private var playerLayer: AVPlayerLayer? @@ -72,7 +80,7 @@ public class CLPlayer: UIView { NotificationCenter.default.removeObserver(self, name: .AVPlayerItemDidPlayToEndTime, object: oldPlayerItem) } guard let playerItem = playerItem else { return } - NotificationCenter.default.addObserver(self, selector: #selector(playerDidToEnd), name: .AVPlayerItemDidPlayToEndTime, object: playerItem) + NotificationCenter.default.addObserver(self, selector: #selector(didPlaybackEnds), name: .AVPlayerItemDidPlayToEndTime, object: playerItem) statusObserve = playerItem.observe(\.status, options: [.new]) { [weak self] _, _ in self?.observeStatusAction() @@ -114,10 +122,17 @@ public class CLPlayer: UIView { } } + public var title: NSMutableAttributedString? { + didSet { + guard let title = title else { return } + contentView.title = title + } + } + public var url: URL? { didSet { guard let url = url else { return } - resetPlayer() + stop() let session = AVAudioSession.sharedInstance() do { try session.setCategory(.playback) @@ -130,10 +145,14 @@ public class CLPlayer: UIView { playerLayer = layer as? AVPlayerLayer playerLayer?.videoGravity = videoGravity playerLayer?.player = player - - play() } } + + public var isFullScreen: Bool { + return contentView.screenState == .fullScreen + } + + weak var delegate: CLPlayerDelegate? } // MARK: - JmoVxia---override @@ -150,10 +169,12 @@ private extension CLPlayer { func initUI() { backgroundColor = .black addSubview(contentView) - if !UIDevice.current.isGeneratingDeviceOrientationNotifications { - UIDevice.current.beginGeneratingDeviceOrientationNotifications() + if config.isAutoRotate { + if !UIDevice.current.isGeneratingDeviceOrientationNotifications { + UIDevice.current.beginGeneratingDeviceOrientationNotifications() + } + NotificationCenter.default.addObserver(self, selector: #selector(deviceOrientationDidChange), name: UIDevice.orientationDidChangeNotification, object: nil) } - NotificationCenter.default.addObserver(self, selector: #selector(deviceOrientationDidChange), name: UIDevice.orientationDidChangeNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(appDidEnterBackground), name: UIApplication.willResignActiveNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(appDidEnterPlayground), name: UIApplication.didBecomeActiveNotification, object: nil) } @@ -163,14 +184,19 @@ private extension CLPlayer { make.edges.equalToSuperview() } } + + func updateConfig() { + videoGravity = config.videoGravity + } } // MARK: - JmoVxia---objc @objc private extension CLPlayer { - func playerDidToEnd() { + func didPlaybackEnds() { contentView.playState = .end sliderTimer.suspend() + delegate?.didPlayToEndTime(in: self) } func deviceOrientationDidChange() { @@ -178,8 +204,10 @@ private extension CLPlayer { case .portrait: dismiss() case .landscapeLeft: + guard superview != nil else { return } presentController(CLFullScreenRightController()) case .landscapeRight: + guard superview != nil else { return } presentController(CLFullScreenLeftController()) default: break @@ -187,10 +215,12 @@ private extension CLPlayer { } func appDidEnterBackground() { + isEnterBackground = true pause() } func appDidEnterPlayground() { + isEnterBackground = false play() } } @@ -246,27 +276,6 @@ private extension CLPlayer { let value = currentDuration / totalDuration contentView.setSliderProgress(Float(value), animated: false) } - - func resetPlayer() { - statusObserve?.invalidate() - loadedTimeRangesObserve?.invalidate() - playbackBufferEmptyObserve?.invalidate() - - statusObserve = nil - loadedTimeRangesObserve = nil - playbackBufferEmptyObserve = nil - - playerItem = nil - player = nil - playerLayer = nil - - contentView.playState = .unknow - contentView.setProgress(0, animated: false) - contentView.setSliderProgress(0, animated: false) - contentView.setTotalDuration(0) - contentView.setCurrentDuration(0) - sliderTimer.resume() - } } // MARK: - JmoVxia---Screen @@ -298,13 +307,8 @@ private extension CLPlayer { // MARK: - JmoVxia---公共方法 public extension CLPlayer { - func pause() { - contentView.playState = .pause - player?.pause() - sliderTimer.suspend() - } - func play() { + guard !isEnterBackground else { return } guard let playerItem = playerItem else { return } guard !isUserPause else { return } if contentView.playState == .end { @@ -319,6 +323,33 @@ public extension CLPlayer { player?.rate = rate sliderTimer.resume() } + + func pause() { + contentView.playState = .pause + player?.pause() + sliderTimer.suspend() + } + + func stop() { + statusObserve?.invalidate() + loadedTimeRangesObserve?.invalidate() + playbackBufferEmptyObserve?.invalidate() + + statusObserve = nil + loadedTimeRangesObserve = nil + playbackBufferEmptyObserve = nil + + playerItem = nil + player = nil + playerLayer = nil + + contentView.playState = .unknow + contentView.setProgress(0, animated: false) + contentView.setSliderProgress(0, animated: false) + contentView.setTotalDuration(0) + contentView.setCurrentDuration(0) + sliderTimer.resume() + } } // MARK: - JmoVxia---UIViewControllerTransitioningDelegate @@ -352,7 +383,7 @@ extension CLPlayer: CLPlayerContentViewDelegate { func sliderTouchEnded(_ slider: CLSlider, in _: CLPlayerContentView) { guard let playerItem = playerItem else { return } if slider.value == 1 { - playerDidToEnd() + didPlaybackEnds() } else if playerItem.isPlaybackLikelyToKeepUp { play() } else { @@ -368,6 +399,7 @@ extension CLPlayer: CLPlayerContentViewDelegate { func didClickBackButton(in contentView: CLPlayerContentView) { guard contentView.screenState == .fullScreen else { return } dismiss() + delegate?.didClickBackButton(in: self) } func didClickPlayButton(isPlay: Bool, in _: CLPlayerContentView) { diff --git a/CLPlayer/CLPlayerConfigure.swift b/CLPlayer/CLPlayerConfigure.swift new file mode 100644 index 0000000..3b13d06 --- /dev/null +++ b/CLPlayer/CLPlayerConfigure.swift @@ -0,0 +1,62 @@ +// +// CLPlayerConfigure.swift +// CLPlayer +// +// Created by Chen JmoVxia on 2021/12/15. +// + +import AVFoundation +import UIKit + +public struct CLPlayerConfigure { + /// 顶部工具条隐藏风格 + public enum CLPlayerTopBarHiddenStyle { + /// 小屏和全屏都不隐藏 + case never + /// 小屏和全屏都隐藏 + case always + /// 小屏隐藏,全屏不隐藏 + case onlySmall + } + + /// 自动旋转 + public var isAutoRotate = true + /// 手势控制 + public var isGestureInteractionEnabled = true + /// 是否显示更多面板 + public var isShowMorePanel = true + /// 顶部工具条隐藏风格 + public var topBarHiddenStyle: CLPlayerTopBarHiddenStyle = .onlySmall + /// 工具条自动消失时间 + public var autoFadeOut: TimeInterval = 5 + /// 默认拉伸方式 + public var videoGravity: AVLayerVideoGravity = .resizeAspectFill + /// 顶部工具条背景颜色 + public var topToobarBackgroundColor: UIColor = .black.withAlphaComponent(0.6) + /// 底部工具条背景颜色 + public var bottomToolbarBackgroundColor: UIColor = .black.withAlphaComponent(0.6) + /// 进度条背景颜色 + public var progressBackgroundColor: UIColor = .white.withAlphaComponent(0.35) + /// 缓冲条缓冲进度颜色 + public var progressBufferColor: UIColor = .white.withAlphaComponent(0.5) + /// 进度条播放完成颜色 + public var progressFinishedColor: UIColor = .white + /// 转子背景颜色 + public var loadingBackgroundColor: UIColor = .white + /// 返回按钮图片 + public var backImage: UIImage? + /// 更多按钮图片 + public var moreImage: UIImage? + /// 播放按钮图片 + public var playImage: UIImage? + /// 暂停按钮图片 + public var pauseImage: UIImage? + /// 进度滑块图片 + public var sliderImage: UIImage? + /// 最大化按钮图片 + public var maxImage: UIImage? + /// 最小化按钮图片 + public var minImage: UIImage? + /// 封面图片 + public var maskImage: UIImage? +} diff --git a/CLPlayer/CLPlayerContentView/CLPlayerContentView.swift b/CLPlayer/CLPlayerContentView/CLPlayerContentView.swift index 5e766f6..71eaa78 100644 --- a/CLPlayer/CLPlayerContentView/CLPlayerContentView.swift +++ b/CLPlayer/CLPlayerContentView/CLPlayerContentView.swift @@ -36,9 +36,10 @@ extension CLPlayerContentView { } } -class CLPlayerContentView: UIView { - override init(frame: CGRect) { - super.init(frame: frame) +class CLPlayerContentView: UIImageView { + init(config: CLPlayerConfigure) { + self.config = config + super.init(frame: .zero) initUI() makeConstraints() } @@ -49,8 +50,8 @@ class CLPlayerContentView: UIView { } private var volumeSlider: UISlider? = { - let volumeView = MPVolumeView() - return volumeView.subviews.first(where: { $0 is UISlider }) as? UISlider + let view = MPVolumeView() + return view.subviews.first(where: { $0 is UISlider }) as? UISlider }() private lazy var topToolView: UIView = { @@ -84,18 +85,18 @@ class CLPlayerContentView: UIView { private lazy var backButton: UIButton = { let view = UIButton() view.setImage(CLImageHelper.imageWithName("CLBack"), for: .normal) - view.setImage(CLImageHelper.imageWithName("CLBack"), for: .highlighted) - view.setImage(CLImageHelper.imageWithName("CLBack"), for: .selected) view.addTarget(self, action: #selector(backButtonAction), for: .touchUpInside) return view }() + private lazy var titleLabel: UILabel = { + let view = UILabel() + return view + }() + private lazy var moreButton: UIButton = { let view = UIButton() - view.isHidden = true view.setImage(CLImageHelper.imageWithName("CLMore"), for: .normal) - view.setImage(CLImageHelper.imageWithName("CLMore"), for: .highlighted) - view.setImage(CLImageHelper.imageWithName("CLMore"), for: .selected) view.addTarget(self, action: #selector(moreButtonAction), for: .touchUpInside) return view }() @@ -204,6 +205,13 @@ class CLPlayerContentView: UIView { return gesture }() + var title: NSMutableAttributedString? { + didSet { + guard let title = title else { return } + titleLabel.attributedText = title + } + } + var currentRate: Float = 1.0 { didSet { guard currentRate != oldValue else { return } @@ -225,14 +233,12 @@ class CLPlayerContentView: UIView { guard screenState != oldValue else { return } switch screenState { case .small: - moreButton.isHidden = true - fullButton.isSelected = false + topToolView.isHidden = config.topBarHiddenStyle != .never hiddenMorePanel() case .animating: break case .fullScreen: - moreButton.isHidden = false - fullButton.isSelected = true + topToolView.isHidden = config.topBarHiddenStyle == .always } } } @@ -254,6 +260,7 @@ class CLPlayerContentView: UIView { failButton.isHidden = true playButton.isSelected = true loadingView.stopAnimation() + image = nil case .buffering: sliderView.isUserInteractionEnabled = true failButton.isHidden = true @@ -270,10 +277,13 @@ class CLPlayerContentView: UIView { failButton.isHidden = true playButton.isSelected = false loadingView.stopAnimation() + image = config.maskImage } } } + private var config: CLPlayerConfigure! + private var isShowMorePanel: Bool = false { didSet { guard isShowMorePanel != oldValue else { return } @@ -340,13 +350,17 @@ class CLPlayerContentView: UIView { private extension CLPlayerContentView { func initUI() { + updateConfig() + clipsToBounds = true autoresizesSubviews = true + isUserInteractionEnabled = true addSubview(topToolView) addSubview(bottomToolView) addSubview(loadingView) topToolView.addSubview(backButton) + topToolView.addSubview(titleLabel) topToolView.addSubview(moreButton) bottomToolView.addSubview(bottomContentView) bottomToolView.addSubview(bottomSafeView) @@ -388,12 +402,17 @@ private extension CLPlayerContentView { make.size.equalTo(40) } backButton.snp.makeConstraints { make in - make.left.equalTo(10) + make.left.equalTo(-40) make.size.equalTo(40) make.centerY.equalToSuperview() } + titleLabel.snp.makeConstraints { make in + make.left.equalTo(backButton.snp.right).offset(15) + make.right.equalTo(moreButton.snp.left).offset(-15) + make.centerY.height.equalToSuperview() + } moreButton.snp.makeConstraints { make in - make.right.equalTo(-10) + make.right.equalTo(40) make.size.equalTo(40) make.centerY.equalToSuperview() } @@ -433,6 +452,62 @@ private extension CLPlayerContentView { make.width.equalTo(morePanelWidth) } } + + func updateConfig() { + currentVideoGravity = config.videoGravity + topToolView.isHidden = screenState == .small ? config.topBarHiddenStyle != .never : config.topBarHiddenStyle == .always + moreButton.isHidden = !config.isShowMorePanel + topToolView.backgroundColor = config.topToobarBackgroundColor + bottomToolView.backgroundColor = config.bottomToolbarBackgroundColor + progressView.trackTintColor = config.progressBackgroundColor + progressView.progressTintColor = config.progressBufferColor + sliderView.minimumTrackTintColor = config.progressFinishedColor + loadingView.updateWithConfigure { $0.backgroundColor = self.config.loadingBackgroundColor } + + let backImage: UIImage? = { + guard let image = config.backImage else { return CLImageHelper.imageWithName("CLBack") } + return image + }() + backButton.setImage(backImage, for: .normal) + + let moreImage: UIImage? = { + guard let image = config.moreImage else { return CLImageHelper.imageWithName("CLMore") } + return image + }() + moreButton.setImage(moreImage, for: .normal) + + let playImage: UIImage? = { + guard let image = config.playImage else { return CLImageHelper.imageWithName("CLPlay") } + return image + }() + playButton.setImage(playImage, for: .normal) + + let pauseImage: UIImage? = { + guard let image = config.pauseImage else { return CLImageHelper.imageWithName("CLPause") } + return image + }() + playButton.setImage(pauseImage, for: .selected) + + let maxImage: UIImage? = { + guard let image = config.maxImage else { return CLImageHelper.imageWithName("CLFullscreen") } + return image + }() + fullButton.setImage(maxImage, for: .normal) + + let minImage: UIImage? = { + guard let image = config.minImage else { return CLImageHelper.imageWithName("CLSmallscreen") } + return image + }() + fullButton.setImage(minImage, for: .selected) + + let sliderImage: UIImage? = { + guard let image = config.sliderImage else { return CLImageHelper.imageWithName("CLSlider") } + return image + }() + sliderView.setThumbImage(sliderImage, for: .normal) + + image = config.maskImage + } } // MARK: - JmoVxia---objc @@ -529,7 +604,7 @@ private extension CLPlayerContentView { } func autoFadeOutTooView() { - autoFadeOutTimer = CLGCDTimer(interval: 0, delaySecs: 5.25, repeats: false, action: { [weak self] _ in + autoFadeOutTimer = CLGCDTimer(interval: 0, delaySecs: 0.25 + config.autoFadeOut, repeats: false, action: { [weak self] _ in self?.isShowToolView = false }) autoFadeOutTimer?.start() @@ -543,16 +618,19 @@ private extension CLPlayerContentView { // MARK: - JmoVxia---公共方法 extension CLPlayerContentView { - @available(iOS 11.0, *) - func animationLayout(safeAreaInsets: UIEdgeInsets) { + func animationLayout(safeAreaInsets: UIEdgeInsets, to screenState: CLPlayerScreenState) { bottomSafeView.snp.updateConstraints { make in make.height.equalTo(safeAreaInsets.bottom) } backButton.snp.updateConstraints { make in - make.left.equalTo(safeAreaInsets.left + 10) + make.left.equalTo(screenState == .small ? -40 : safeAreaInsets.left + 10) + } + titleLabel.snp.updateConstraints { make in + make.left.equalTo(backButton.snp.right).offset(screenState == .small ? 15 : 10) + make.right.equalTo(moreButton.snp.left).offset(screenState == .small ? -15 : -10) } moreButton.snp.updateConstraints { make in - make.right.equalTo(-safeAreaInsets.left - 10) + make.right.equalTo(screenState == .small ? 40 : -safeAreaInsets.left - 10) } playButton.snp.updateConstraints { make in make.left.equalTo(safeAreaInsets.left + 10) @@ -560,6 +638,10 @@ extension CLPlayerContentView { fullButton.snp.updateConstraints { make in make.right.equalTo(-safeAreaInsets.right - 10) } + + fullButton.isSelected = screenState == .fullScreen + + topToolView.isHidden = screenState == .small ? config.topBarHiddenStyle != .never : config.topBarHiddenStyle == .always } func setProgress(_ progress: Float, animated: Bool) { @@ -636,7 +718,7 @@ extension CLPlayerContentView: UIGestureRecognizerDelegate { if morePanelCollectionView.bounds.contains(touch.location(in: morePanelCollectionView)) { return false } else if gestureRecognizer == panGesture { - return screenState == .fullScreen + return screenState == .fullScreen && config.isGestureInteractionEnabled } return true } diff --git a/CLPlayer/CLPlayerContentView/CLSlider.swift b/CLPlayer/CLPlayerContentView/CLSlider.swift index cac783f..2226a72 100644 --- a/CLPlayer/CLPlayerContentView/CLSlider.swift +++ b/CLPlayer/CLPlayerContentView/CLSlider.swift @@ -32,7 +32,6 @@ private extension CLSlider { func initUI() { let thumbImage = CLImageHelper.imageWithName("CLSlider") setThumbImage(thumbImage, for: .normal) - setThumbImage(thumbImage, for: .highlighted) } } diff --git a/CLPlayer/CLPlayerDelegate.swift b/CLPlayer/CLPlayerDelegate.swift new file mode 100644 index 0000000..6141633 --- /dev/null +++ b/CLPlayer/CLPlayerDelegate.swift @@ -0,0 +1,15 @@ +// +// CLPlayerDelegate.swift +// CLPlayer +// +// Created by Chen JmoVxia on 2021/12/15. +// + +import UIKit + +public protocol CLPlayerDelegate: AnyObject { + /// 点击顶部工具条返回按钮 + func didClickBackButton(in player: CLPlayer) + /// 视频播放结束 + func didPlayToEndTime(in player: CLPlayer) +} diff --git a/CLTableViewController/Controller/CLTableViewController.swift b/CLTableViewController/Controller/CLTableViewController.swift index fc63c50..b3cfae8 100644 --- a/CLTableViewController/Controller/CLTableViewController.swift +++ b/CLTableViewController/Controller/CLTableViewController.swift @@ -27,7 +27,7 @@ class CLTableViewController: CLController { deinit {} private lazy var tableViewHepler: CLTableViewHepler = { - let hepler = CLTableViewHepler() + let hepler = CLTableViewHepler(delegate: self) return hepler }() @@ -45,10 +45,7 @@ class CLTableViewController: CLController { return view }() - private lazy var player: CLPlayer = { - let view = CLPlayer() - return view - }() + private var player: CLPlayer? } // MARK: - JmoVxia---生命周期 @@ -106,12 +103,40 @@ private extension CLTableViewController { private extension CLTableViewController { func initData() { - for _ in 0 ..< 30 { + let array = [ + "http://vfx.mtime.cn/Video/2019/03/19/mp4/190319212559089721.mp4", + "http://vfx.mtime.cn/Video/2019/03/18/mp4/190318231014076505.mp4", + "http://vfx.mtime.cn/Video/2019/02/04/mp4/190204084208765161.mp4", + "http://vfx.mtime.cn/Video/2019/03/21/mp4/190321153853126488.mp4", + "http://vfx.mtime.cn/Video/2019/03/19/mp4/190319222227698228.mp4", + "http://vfx.mtime.cn/Video/2019/03/19/mp4/190319212559089721.mp4", + "http://vfx.mtime.cn/Video/2019/03/18/mp4/190318231014076505.mp4", + "http://vfx.mtime.cn/Video/2019/03/18/mp4/190318214226685784.mp4", + "http://vfx.mtime.cn/Video/2019/03/19/mp4/190319104618910544.mp4", + "http://vfx.mtime.cn/Video/2019/03/19/mp4/190319125415785691.mp4", + "http://vfx.mtime.cn/Video/2019/03/17/mp4/190317150237409904.mp4", + "http://vfx.mtime.cn/Video/2019/03/14/mp4/190314223540373995.mp4", + "http://vfx.mtime.cn/Video/2019/03/14/mp4/190314102306987969.mp4", + "http://vfx.mtime.cn/Video/2019/03/13/mp4/190313094901111138.mp4", + "http://vfx.mtime.cn/Video/2019/03/12/mp4/190312143927981075.mp4", + "http://vfx.mtime.cn/Video/2019/03/12/mp4/190312083533415853.mp4", + "http://vfx.mtime.cn/Video/2019/03/09/mp4/190309153658147087.mp4", + "https://www.apple.com/105/media/us/mac/family/2018/46c4b917_abfd_45a3_9b51_4e3054191797/films/grimes/mac-grimes-tpl-cc-us-2018_1280x720h.mp4", + "https://www.apple.com/105/media/us/iphone-x/2017/01df5b43-28e4-4848-bf20-490c34a926a7/films/feature/iphone-x-feature-tpl-cc-us-20170912_1280x720h.mp4", + "https://www.apple.com/105/media/us/mac/family/2018/46c4b917_abfd_45a3_9b51_4e3054191797/films/peter/mac-peter-tpl-cc-us-2018_1280x720h.mp4", + "http://mirror.aarnet.edu.au/pub/TED-talks/911Mothers_2010W-480p.mp4", + ] + for string in array { let item = CLTableViewItem() - item.didSelectCellCallback = { [weak self] value in + item.title = NSMutableAttributedString("这是一个标题", attributes: { $0 + .font(.systemFont(ofSize: 16)) + .foregroundColor(.orange) + .alignment(.center) + }) + item.url = URL(string: string) + item.didSelectCellCallback = { [weak self] indexPath in guard let self = self else { return } - guard let cell = self.tableView.cellForRow(at: value) else { return } - self.playWithCell(cell) + self.playWithIndexPath(indexPath) } tableViewHepler.dataSource.append(item) } @@ -130,16 +155,44 @@ extension CLTableViewController {} // MARK: - JmoVxia---私有方法 private extension CLTableViewController { - func playWithCell(_ cell: UITableViewCell) { - player.url = URL(string: "https://www.apple.com/105/media/us/mac/family/2018/46c4b917_abfd_45a3_9b51_4e3054191797/films/grimes/mac-grimes-tpl-cc-us-2018_1280x720h.mp4") + func playWithIndexPath(_ indexPath: IndexPath) { + guard let item = tableViewHepler.dataSource[indexPath.row] as? CLTableViewItem else { return } + guard let cell = tableView.cellForRow(at: indexPath) else { return } + + if player == nil { + player = CLPlayer() + } + player?.title = item.title + player?.url = item.url + cell.contentView.addSubview(player!) + player?.snp.remakeConstraints { make in + make.top.left.equalToSuperview() + make.size.equalTo(CGSize(width: cell.bounds.width, height: cell.bounds.height - 10)) + } + player?.play() + } +} + +extension CLTableViewController: UITableViewDelegate { + func tableView(_: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { + guard let player = player else { return } + let url = (tableViewHepler.dataSource[indexPath.row] as? CLTableViewItem)?.url?.absoluteString + guard url == player.url?.absoluteString else { return } + cell.contentView.addSubview(player) player.snp.remakeConstraints { make in make.top.left.equalToSuperview() make.size.equalTo(CGSize(width: cell.bounds.width, height: cell.bounds.height - 10)) } + player.play() } -} -// MARK: - JmoVxia---公共方法 + func tableView(_: UITableView, didEndDisplaying _: UITableViewCell, forRowAt indexPath: IndexPath) { + guard let player = player else { return } + let url = (tableViewHepler.dataSource[indexPath.row] as? CLTableViewItem)?.url?.absoluteString + guard url == player.url?.absoluteString else { return } -extension CLTableViewController {} + player.removeFromSuperview() + player.pause() + } +} diff --git a/CLTableViewController/View/Item/CLTableViewItem.swift b/CLTableViewController/View/Item/CLTableViewItem.swift index 5e4e347..dd75b7a 100644 --- a/CLTableViewController/View/Item/CLTableViewItem.swift +++ b/CLTableViewController/View/Item/CLTableViewItem.swift @@ -8,6 +8,8 @@ import UIKit class CLTableViewItem: NSObject { + var title = NSMutableAttributedString() + var url: URL? var didSelectCellCallback: ((IndexPath) -> Void)? } diff --git a/Extension/NSMutableAttributedString+Extension.swift b/Extension/NSMutableAttributedString+Extension.swift new file mode 100644 index 0000000..2328293 --- /dev/null +++ b/Extension/NSMutableAttributedString+Extension.swift @@ -0,0 +1,177 @@ +// +// NSMutableAttributedString+Extension.swift +// CKD +// +// Created by Chen JmoVxia on 2021/3/26. +// Copyright © 2021 JmoVxia. All rights reserved. +// + +import Foundation +import UIKit + +extension NSMutableAttributedString { + /// 快捷初始化 + convenience init(_ text: String, attributes: ((AttributesItem) -> Void)? = nil) { + let item = AttributesItem() + attributes?(item) + self.init(string: text, attributes: item.attributes) + } + + /// 添加字符串并为此段添加对应的Attribute + @discardableResult + func addText(_ text: String, attributes: ((AttributesItem) -> Void)? = nil) -> NSMutableAttributedString { + let item = AttributesItem() + attributes?(item) + append(NSMutableAttributedString(string: text, attributes: item.attributes)) + return self + } + + /// 添加Attribute作用于当前整体字符串,如果不包含传入的attribute,则增加当前特征 + @discardableResult + func addAttributes(_ attributes: (AttributesItem) -> Void) -> NSMutableAttributedString { + let item = AttributesItem() + attributes(item) + enumerateAttributes(in: NSRange(string.startIndex ..< string.endIndex, in: string), options: .reverse) { oldAttribute, range, _ in + var newAtt = oldAttribute + for item in item.attributes where !oldAttribute.keys.contains(item.key) { + newAtt[item.key] = item.value + } + addAttributes(newAtt, range: range) + } + return self + } + + /// 添加图片 + @discardableResult + func addImage(_ image: UIImage?, _ bounds: CGRect) -> NSMutableAttributedString { + let attch = NSTextAttachment() + attch.image = image + attch.bounds = bounds + append(NSAttributedString(attachment: attch)) + return self + } +} + +extension NSAttributedString { + class AttributesItem { + private(set) var attributes = [NSAttributedString.Key: Any]() + private(set) lazy var paragraphStyle = NSMutableParagraphStyle() + /// 字体 + @discardableResult + func font(_ value: UIFont) -> AttributesItem { + attributes[.font] = value + return self + } + + /// 字体颜色 + @discardableResult + func foregroundColor(_ value: UIColor) -> AttributesItem { + attributes[.foregroundColor] = value + return self + } + + /// 斜体 + @discardableResult + func oblique(_ value: CGFloat) -> AttributesItem { + attributes[.obliqueness] = value + return self + } + + /// 文本横向拉伸属性,正值横向拉伸文本,负值横向压缩文本 + @discardableResult + func expansion(_ value: CGFloat) -> AttributesItem { + attributes[.expansion] = value + return self + } + + /// 字间距 + @discardableResult + func kern(_ value: CGFloat) -> AttributesItem { + attributes[.kern] = value + return self + } + + /// 删除线 + @discardableResult + func strikeStyle(_ value: NSUnderlineStyle) -> AttributesItem { + attributes[.strikethroughStyle] = value.rawValue + return self + } + + /// 删除线颜色 + @discardableResult + func strikeColor(_ value: UIColor) -> AttributesItem { + attributes[.strikethroughColor] = value + return self + } + + /// 下划线 + @discardableResult + func underlineStyle(_ value: NSUnderlineStyle) -> AttributesItem { + attributes[.underlineStyle] = value.rawValue + return self + } + + /// 下划线颜色 + @discardableResult + func underlineColor(_ value: UIColor) -> AttributesItem { + attributes[.underlineColor] = value + return self + } + + /// 设置基线偏移值,正值上偏,负值下偏 + @discardableResult + func baselineOffset(_ value: CGFloat) -> AttributesItem { + attributes[.baselineOffset] = value + return self + } + + /// 居中方式 + @discardableResult + func alignment(_ value: NSTextAlignment) -> AttributesItem { + paragraphStyle.alignment = value + attributes[.paragraphStyle] = paragraphStyle + return self + } + + /// 字符截断类型 + @discardableResult + func lineBreakMode(_ value: NSLineBreakMode) -> AttributesItem { + paragraphStyle.lineBreakMode = value + attributes[.paragraphStyle] = paragraphStyle + return self + } + + /// 行间距 + @discardableResult + func lineSpacing(_ value: CGFloat) -> AttributesItem { + paragraphStyle.lineSpacing = value + attributes[.paragraphStyle] = paragraphStyle + return self + } + + /// 最小行高 + @discardableResult + func minimumLineHeight(_ value: CGFloat) -> AttributesItem { + paragraphStyle.minimumLineHeight = value + attributes[.paragraphStyle] = paragraphStyle + return self + } + + /// 最大行高 + @discardableResult + func maximumLineHeight(_ value: CGFloat) -> AttributesItem { + paragraphStyle.maximumLineHeight = value + attributes[.paragraphStyle] = paragraphStyle + return self + } + + /// 段落间距 + @discardableResult + func paragraphSpacing(_ value: CGFloat) -> AttributesItem { + paragraphStyle.paragraphSpacing = value + attributes[.paragraphStyle] = paragraphStyle + return self + } + } +} diff --git a/README.md b/README.md index f3dfc28..630c727 100755 --- a/README.md +++ b/README.md @@ -1,7 +1,29 @@ # 使用AVPlayer自定义支持全屏的播放器 -# 用法 +## 功能 -```swift -cocoapods导入,pod 'CLPlayer' -``` +- [x] 支持cocoapods +- [x] 支持旋转屏:全屏模式和小屏模式切换,自动感应旋转 +- [x] 支持网络和本地视频资源播放 +- [x] 支持cell中播放,列表播放 +- [x] 支持手势改变屏幕的亮度(屏幕左半边) +- [x] 支持手势改变音量大小(屏幕右半边) +- [x] 支持拖动Slider快进快退 +- [x] 全面适配iPhone X +- [x] 支持循环播放 +- [x] 支持倍速播放(0.5X、1.0X、1.25X、1.5X、1.75X、2X) +- [x] 支持动态改变播放器的填充模式 + +## 用法 + +`pod 'CLPlayer'` + +## 说明 + +**本次更新为`Swift`重制尝鲜版,后续会更新更多功能,如有问题请反馈** + +## 效果 +![DnJU9Z](https://gitee.com/JmoVxia/Photos/raw/master/DnJU9Z.png) +![T5IUcZ](https://gitee.com/JmoVxia/Photos/raw/master/T5IUcZ.png) +![ZanPno](https://gitee.com/JmoVxia/Photos/raw/master/ZanPno.png) +![tMTyKk](https://gitee.com/JmoVxia/Photos/raw/master/tMTyKk.png)