├── AppDelegate.swift ├── Base ├── CLController │ └── CLController.swift ├── CLNavigationController │ ├── Controller │ │ └── CLNavigationController.swift │ └── View │ │ └── CLBackView.swift └── CLTabBarController │ └── CLTabBarController.swift ├── CLAutolayoutController └── CLAutolayoutController.swift ├── CLCollectionViewController ├── CLCollectionViewController.swift └── View │ └── CLCollectionViewCell.swift ├── CLFrameController ├── CLFrameController.swift └── CLPlaceholderView.swift ├── CLHomeController ├── Controller │ └── CLHomeController.swift └── View │ ├── Cell │ └── CLListCell.swift │ └── Item │ └── CLListItem.swift ├── CLMoreController └── Controller │ └── CLMoreController.swift ├── CLPlayer.podspec ├── CLPlayer.xcodeproj ├── project.pbxproj ├── project.xcworkspace │ ├── contents.xcworkspacedata │ └── xcshareddata │ │ └── IDEWorkspaceChecks.plist └── xcuserdata │ └── jmovxia.xcuserdatad │ └── xcschemes │ └── xcschememanagement.plist ├── CLPlayer.xcworkspace ├── contents.xcworkspacedata └── xcshareddata │ └── IDEWorkspaceChecks.plist ├── CLPlayer ├── CLFullScreenController │ ├── CLAnimationTransitioning.swift │ ├── CLFullScreenController.swift │ ├── CLFullScreenLeftController.swift │ └── CLFullScreenRightController.swift ├── CLGCDTimer.swift ├── CLImageHelper.swift ├── CLPlayer.bundle │ ├── CLBack@2x.png │ ├── CLBack@3x.png │ ├── CLFullscreen@2x.png │ ├── CLFullscreen@3x.png │ ├── CLMore@2x.png │ ├── CLMore@3x.png │ ├── CLPause@2x.png │ ├── CLPause@3x.png │ ├── CLPlay@2x.png │ ├── CLPlay@3x.png │ ├── CLSlider@2x.png │ ├── CLSlider@3x.png │ ├── CLSmallscreen@2x.png │ └── CLSmallscreen@3x.png ├── CLPlayer.swift ├── CLPlayerConfigure.swift ├── CLPlayerContentView │ ├── CLPlayerContentPanelCell.swift │ ├── CLPlayerContentPanelHeadView.swift │ ├── CLPlayerContentView.swift │ ├── CLPlayerContentViewDelegate.swift │ ├── CLRotateAnimationView.swift │ └── CLSlider.swift ├── CLPlayerDelegate.swift └── CLPlayerView.swift ├── CLStackViewController └── CLStackViewController.swift ├── CLTableView ├── CLCellItemProtocol │ └── CLCellItemProtocol.swift ├── CLCellProtocol │ └── CLCellProtocol.swift └── CLTableViewHepler │ └── CLTableViewHepler.swift ├── CLTableViewController ├── Controller │ └── CLTableViewController.swift └── View │ ├── Cell │ └── CLTableViewCell.swift │ └── Item │ └── CLTableViewItem.swift ├── Extension ├── NSMutableAttributedString+Extension.swift ├── UIColor+CLExtension.swift └── UIImage+Extension.swift ├── LICENSE ├── Podfile ├── Podfile.lock ├── Pods ├── Headers │ └── Public │ │ └── SnapKit │ │ ├── SnapKit-umbrella.h │ │ └── SnapKit.modulemap ├── Manifest.lock ├── Pods.xcodeproj │ ├── project.pbxproj │ └── xcuserdata │ │ └── jmovxia.xcuserdatad │ │ └── xcschemes │ │ ├── Pods-CLPlayer.xcscheme │ │ ├── SnapKit.xcscheme │ │ ├── SwiftFormat.xcscheme │ │ └── xcschememanagement.plist ├── SnapKit │ ├── LICENSE │ └── README.md ├── SwiftFormat │ ├── CommandLineTool │ │ └── swiftformat │ ├── LICENSE.md │ └── README.md └── Target Support Files │ ├── Pods-CLPlayer │ ├── Pods-CLPlayer-acknowledgements.markdown │ ├── Pods-CLPlayer-acknowledgements.plist │ ├── Pods-CLPlayer-dummy.m │ ├── Pods-CLPlayer-umbrella.h │ ├── Pods-CLPlayer.debug.xcconfig │ ├── Pods-CLPlayer.modulemap │ └── Pods-CLPlayer.release.xcconfig │ ├── SnapKit │ ├── SnapKit-dummy.m │ ├── SnapKit-prefix.pch │ ├── SnapKit-umbrella.h │ ├── SnapKit.debug.xcconfig │ ├── SnapKit.modulemap │ └── SnapKit.release.xcconfig │ └── SwiftFormat │ ├── SwiftFormat.debug.xcconfig │ └── SwiftFormat.release.xcconfig ├── README.md └── Resources ├── Assets.xcassets ├── AccentColor.colorset │ └── Contents.json ├── AppIcon.appiconset │ ├── Contents.json │ ├── icon-1024.png │ ├── icon-20@2x-ipad.png │ ├── icon-20@3x.png │ ├── icon-29-ipad.png │ ├── icon-29@2x-ipad.png │ ├── icon-29@3x.png │ ├── icon-40@2x.png │ ├── icon-40@3x.png │ ├── icon-60@2x.png │ └── icon-60@3x.png ├── Contents.json ├── back.imageset │ ├── Contents.json │ ├── back@2x.png │ └── back@3x.png ├── homeNormal.imageset │ ├── Contents.json │ ├── homeNormal@2x.png │ └── homeNormal@3x.png ├── homeSelected.imageset │ ├── Contents.json │ ├── homeSelected@2x.png │ └── homeSelected@3x.png ├── logo.imageset │ ├── Contents.json │ └── logo.png ├── meArrowRight.imageset │ ├── Contents.json │ ├── meArrowRight@2x.png │ └── meArrowRight@3x.png ├── meNormal.imageset │ ├── Contents.json │ ├── meNormal@2x.png │ └── meNormal@3x.png ├── meSelected.imageset │ ├── Contents.json │ ├── meSelected@2x.png │ └── meSelected@3x.png ├── placeholder.imageset │ ├── Contents.json │ └── placeholder.png ├── placeholder1.imageset │ ├── Contents.json │ └── placeholder1.png ├── play.imageset │ ├── Contents.json │ ├── play@2x.png │ └── play@3x.png ├── slider.imageset │ ├── Contents.json │ ├── slider@2x.png │ └── slider@3x.png └── slider_dog.imageset │ ├── Contents.json │ ├── slider_dog@2x.png │ └── slider_dog@3x.png ├── Base.lproj ├── LaunchScreen.storyboard └── Main.storyboard └── Info.plist /AppDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // AppDelegate.swift 3 | // CLPlayer 4 | // 5 | // Created by Chen JmoVxia on 2021/10/26. 6 | // 7 | 8 | import UIKit 9 | 10 | @main 11 | class AppDelegate: UIResponder, UIApplicationDelegate { 12 | var window: UIWindow? 13 | func application(_: UIApplication, didFinishLaunchingWithOptions _: [UIApplication.LaunchOptionsKey: Any]?) -> Bool { 14 | window?.rootViewController = CLTabBarController() 15 | window?.makeKeyAndVisible() 16 | return true 17 | } 18 | } 19 | 20 | extension AppDelegate { 21 | func application(_: UIApplication, supportedInterfaceOrientationsFor _: UIWindow?) -> UIInterfaceOrientationMask { 22 | return .allButUpsideDown 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Base/CLController/CLController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLController.swift 3 | // CLPlayer 4 | // 5 | // Created by Chen JmoVxia on 2021/10/26. 6 | // 7 | 8 | import SnapKit 9 | import UIKit 10 | 11 | // MARK: - JmoVxia---类-属性 12 | 13 | class CLController: UIViewController { 14 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { 15 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 16 | } 17 | 18 | @available(*, unavailable) 19 | required init?(coder _: NSCoder) { 20 | fatalError("init(coder:) has not been implemented") 21 | } 22 | 23 | private lazy var titleLabel: UILabel = { 24 | let view = UILabel() 25 | view.font = .boldSystemFont(ofSize: 18) 26 | view.textColor = .black 27 | return view 28 | }() 29 | 30 | /// 导航条高度 31 | var navigationBarHeight: CGFloat { 32 | return navigationController?.navigationBar.bounds.height ?? 0 33 | } 34 | 35 | deinit {} 36 | } 37 | 38 | // MARK: - JmoVxia---生命周期 39 | 40 | extension CLController { 41 | override func viewWillAppear(_ animated: Bool) { 42 | super.viewWillAppear(animated) 43 | } 44 | 45 | override func viewDidAppear(_ animated: Bool) { 46 | super.viewDidAppear(animated) 47 | } 48 | 49 | override func viewDidLoad() { 50 | super.viewDidLoad() 51 | initUI() 52 | makeConstraints() 53 | initData() 54 | } 55 | 56 | override func viewWillDisappear(_ animated: Bool) { 57 | super.viewWillDisappear(animated) 58 | } 59 | 60 | override func viewDidDisappear(_ animated: Bool) { 61 | super.viewDidDisappear(animated) 62 | } 63 | 64 | override func viewDidLayoutSubviews() { 65 | super.viewDidLayoutSubviews() 66 | } 67 | } 68 | 69 | // MARK: - JmoVxia---布局 70 | 71 | private extension CLController { 72 | func initUI() { 73 | titleLabel.text = title 74 | navigationItem.titleView = titleLabel 75 | view.backgroundColor = .white 76 | } 77 | 78 | func makeConstraints() {} 79 | } 80 | 81 | // MARK: - JmoVxia---数据 82 | 83 | private extension CLController { 84 | func initData() {} 85 | } 86 | 87 | // MARK: - JmoVxia---override 88 | 89 | extension CLController { 90 | // 是否支持自动转屏 91 | override var shouldAutorotate: Bool { 92 | return false 93 | } 94 | 95 | // 支持哪些屏幕方向 96 | override var supportedInterfaceOrientations: UIInterfaceOrientationMask { 97 | return .portrait 98 | } 99 | 100 | // 默认的屏幕方向(当前ViewController必须是通过模态出来的UIViewController(模态带导航的无效)方式展现出来的,才会调用这个方法) 101 | override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { 102 | return .portrait 103 | } 104 | 105 | /// 状态栏样式 106 | override var preferredStatusBarStyle: UIStatusBarStyle { 107 | return .default 108 | } 109 | 110 | /// 是否隐藏状态栏 111 | override var prefersStatusBarHidden: Bool { 112 | return false 113 | } 114 | } 115 | 116 | // MARK: - JmoVxia---objc 117 | 118 | @objc private extension CLController {} 119 | 120 | // MARK: - JmoVxia---私有方法 121 | 122 | private extension CLController {} 123 | 124 | // MARK: - JmoVxia---公共方法 125 | 126 | extension CLController { 127 | /// 更新顶部label 128 | func updateTitleLabel(_ viewCallback: @escaping ((UILabel) -> Void)) { 129 | DispatchQueue.main.async { 130 | viewCallback(self.titleLabel) 131 | self.titleLabel.sizeToFit() 132 | } 133 | } 134 | } 135 | -------------------------------------------------------------------------------- /Base/CLNavigationController/Controller/CLNavigationController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLNavigationController.swift 3 | // CLPlayer 4 | // 5 | // Created by Chen JmoVxia on 2021/10/26. 6 | // 7 | 8 | import SnapKit 9 | import UIKit 10 | 11 | // MARK: - JmoVxia---类-属性 12 | 13 | class CLNavigationController: UINavigationController { 14 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { 15 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 16 | } 17 | 18 | override init(rootViewController: UIViewController) { 19 | super.init(rootViewController: rootViewController) 20 | } 21 | 22 | @available(*, unavailable) 23 | required init?(coder _: NSCoder) { 24 | fatalError("init(coder:) has not been implemented") 25 | } 26 | 27 | /// 自定义返回响应 28 | var customBackActionCallback: (() -> (Bool))? 29 | 30 | private lazy var backItem: CLBackView = { 31 | let view = CLBackView() 32 | view.title = " " 33 | view.addTarget(self, action: #selector(backItemAction), for: .touchUpInside) 34 | return view 35 | }() 36 | 37 | deinit {} 38 | } 39 | 40 | // MARK: - JmoVxia---生命周期 41 | 42 | extension CLNavigationController { 43 | override func viewWillAppear(_ animated: Bool) { 44 | super.viewWillAppear(animated) 45 | } 46 | 47 | override func viewDidAppear(_ animated: Bool) { 48 | super.viewDidAppear(animated) 49 | } 50 | 51 | override func viewDidLoad() { 52 | super.viewDidLoad() 53 | initUI() 54 | makeConstraints() 55 | initData() 56 | } 57 | 58 | override func viewWillDisappear(_ animated: Bool) { 59 | super.viewWillDisappear(animated) 60 | } 61 | 62 | override func viewDidDisappear(_ animated: Bool) { 63 | super.viewDidDisappear(animated) 64 | } 65 | 66 | override func viewDidLayoutSubviews() { 67 | super.viewDidLayoutSubviews() 68 | } 69 | } 70 | 71 | // MARK: - JmoVxia---布局 72 | 73 | private extension CLNavigationController { 74 | func initUI() { 75 | view.backgroundColor = .white 76 | } 77 | 78 | func makeConstraints() {} 79 | } 80 | 81 | // MARK: - JmoVxia---数据 82 | 83 | private extension CLNavigationController { 84 | func initData() {} 85 | } 86 | 87 | // MARK: - JmoVxia---override 88 | 89 | extension CLNavigationController { 90 | // 是否支持自动转屏 91 | override var shouldAutorotate: Bool { 92 | return topViewController?.shouldAutorotate ?? false 93 | } 94 | 95 | // 支持哪些屏幕方向 96 | override var supportedInterfaceOrientations: UIInterfaceOrientationMask { 97 | return topViewController?.supportedInterfaceOrientations ?? .portrait 98 | } 99 | 100 | // 默认的屏幕方向(当前ViewController必须是通过模态出来的UIViewController(模态带导航的无效)方式展现出来的,才会调用这个方法) 101 | override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { 102 | return topViewController?.preferredInterfaceOrientationForPresentation ?? .portrait 103 | } 104 | 105 | /// 状态栏样式 106 | override var preferredStatusBarStyle: UIStatusBarStyle { 107 | return topViewController?.preferredStatusBarStyle ?? .default 108 | } 109 | 110 | /// 是否隐藏状态栏 111 | override var prefersStatusBarHidden: Bool { 112 | return topViewController?.prefersStatusBarHidden ?? false 113 | } 114 | } 115 | 116 | extension CLNavigationController { 117 | override func pushViewController(_ viewController: UIViewController, animated: Bool) { 118 | if !viewControllers.isEmpty { 119 | viewController.hidesBottomBarWhenPushed = true 120 | viewController.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: backItem) 121 | } 122 | super.pushViewController(viewController, animated: animated) 123 | } 124 | 125 | override func setViewControllers(_ viewControllers: [UIViewController], animated: Bool) { 126 | if !viewControllers.isEmpty { 127 | viewControllers.last?.hidesBottomBarWhenPushed = true 128 | viewControllers.last?.navigationItem.leftBarButtonItem = UIBarButtonItem(customView: backItem) 129 | } 130 | super.setViewControllers(viewControllers, animated: animated) 131 | } 132 | } 133 | 134 | // MARK: - JmoVxia---objc 135 | 136 | @objc private extension CLNavigationController { 137 | func backItemAction() { 138 | let isCustomBack = customBackActionCallback?() ?? false 139 | if !isCustomBack { 140 | if presentingViewController != nil, viewControllers.count == 1 { 141 | dismiss(animated: true) 142 | } else { 143 | popViewController(animated: true) 144 | } 145 | } 146 | } 147 | } 148 | 149 | // MARK: - JmoVxia---私有方法 150 | 151 | private extension CLNavigationController {} 152 | 153 | // MARK: - JmoVxia---公共方法 154 | 155 | extension CLNavigationController {} 156 | -------------------------------------------------------------------------------- /Base/CLNavigationController/View/CLBackView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLBackView.swift 3 | // CLPlayer 4 | // 5 | // Created by Chen JmoVxia on 2021/10/26. 6 | // 7 | 8 | import SnapKit 9 | import UIKit 10 | 11 | // MARK: - JmoVxia---类-属性 12 | 13 | class CLBackView: UIControl { 14 | override init(frame: CGRect) { 15 | super.init(frame: frame) 16 | initUI() 17 | makeConstraints() 18 | initData() 19 | } 20 | 21 | @available(*, unavailable) 22 | required init?(coder _: NSCoder) { 23 | fatalError("init(coder:) has not been implemented") 24 | } 25 | 26 | var title: String = " " { 27 | didSet { 28 | textLabel.text = title 29 | textLabel.sizeToFit() 30 | super.setNeedsLayout() 31 | super.layoutIfNeeded() 32 | } 33 | } 34 | 35 | private lazy var textLabel: UILabel = { 36 | let view = UILabel() 37 | view.textColor = .black 38 | view.font = .systemFont(ofSize: 16) 39 | return view 40 | }() 41 | 42 | private lazy var backimageView: UIImageView = { 43 | let view = UIImageView() 44 | view.image = UIImage(named: "back")?.tintImage(.black) 45 | return view 46 | }() 47 | } 48 | 49 | // MARK: - JmoVxia---布局 50 | 51 | private extension CLBackView { 52 | func initUI() { 53 | addSubview(backimageView) 54 | addSubview(textLabel) 55 | } 56 | 57 | func makeConstraints() { 58 | backimageView.snp.makeConstraints { make in 59 | make.left.centerY.equalToSuperview() 60 | make.width.equalTo(20) 61 | make.height.equalTo(20) 62 | make.bottom.equalTo(-5).priority(.low) 63 | make.top.equalTo(5).priority(.low) 64 | } 65 | textLabel.snp.makeConstraints { make in 66 | make.left.equalTo(backimageView.snp.right).offset(7) 67 | make.centerY.equalTo(0) 68 | make.right.equalTo(0).priority(.high) 69 | } 70 | } 71 | } 72 | 73 | // MARK: - JmoVxia---数据 74 | 75 | private extension CLBackView { 76 | func initData() {} 77 | } 78 | 79 | // MARK: - JmoVxia---override 80 | 81 | extension CLBackView {} 82 | 83 | // MARK: - JmoVxia---objc 84 | 85 | @objc private extension CLBackView {} 86 | 87 | // MARK: - JmoVxia---私有方法 88 | 89 | private extension CLBackView {} 90 | 91 | // MARK: - JmoVxia---公共方法 92 | 93 | extension CLBackView {} 94 | -------------------------------------------------------------------------------- /Base/CLTabBarController/CLTabBarController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLTabBarController.swift 3 | // CLPlayer 4 | // 5 | // Created by Chen JmoVxia on 2021/10/25. 6 | // 7 | 8 | import UIKit 9 | 10 | // MARK: - JmoVxia---类-属性 11 | 12 | class CLTabBarController: UITabBarController { 13 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { 14 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 15 | } 16 | 17 | @available(*, unavailable) 18 | required init?(coder _: NSCoder) { 19 | fatalError("init(coder:) has not been implemented") 20 | } 21 | 22 | deinit {} 23 | } 24 | 25 | // MARK: - JmoVxia---生命周期 26 | 27 | extension CLTabBarController { 28 | override func viewWillAppear(_ animated: Bool) { 29 | super.viewWillAppear(animated) 30 | } 31 | 32 | override func viewDidAppear(_ animated: Bool) { 33 | super.viewDidAppear(animated) 34 | } 35 | 36 | override func viewDidLoad() { 37 | super.viewDidLoad() 38 | initUI() 39 | makeConstraints() 40 | initData() 41 | } 42 | 43 | override func viewWillDisappear(_ animated: Bool) { 44 | super.viewWillDisappear(animated) 45 | } 46 | 47 | override func viewDidDisappear(_ animated: Bool) { 48 | super.viewDidDisappear(animated) 49 | } 50 | 51 | override func viewDidLayoutSubviews() { 52 | super.viewDidLayoutSubviews() 53 | } 54 | } 55 | 56 | // MARK: - JmoVxia---布局 57 | 58 | private extension CLTabBarController { 59 | func initUI() { 60 | view.backgroundColor = .white 61 | UITabBar.appearance().unselectedItemTintColor = .hex("#999999") 62 | UITabBar.appearance().tintColor = .hex("#24C065") 63 | addChild(child: CLHomeController(), title: "Home", image: UIImage(named: "homeNormal"), selectedImage: UIImage(named: "homeSelected")) 64 | addChild(child: CLMoreController(), title: "More", image: UIImage(named: "meNormal"), selectedImage: UIImage(named: "meSelected")) 65 | } 66 | 67 | func makeConstraints() {} 68 | } 69 | 70 | // MARK: - JmoVxia---数据 71 | 72 | private extension CLTabBarController { 73 | func initData() {} 74 | } 75 | 76 | // MARK: - JmoVxia---override 77 | 78 | extension CLTabBarController { 79 | // 是否支持自动转屏 80 | override var shouldAutorotate: Bool { 81 | guard let navigationController = selectedViewController as? UINavigationController else { return selectedViewController?.shouldAutorotate ?? false } 82 | return navigationController.topViewController?.shouldAutorotate ?? false 83 | } 84 | 85 | // 支持哪些屏幕方向 86 | override var supportedInterfaceOrientations: UIInterfaceOrientationMask { 87 | guard let navigationController = selectedViewController as? UINavigationController else { return selectedViewController?.supportedInterfaceOrientations ?? .portrait } 88 | return navigationController.topViewController?.supportedInterfaceOrientations ?? .portrait 89 | } 90 | 91 | // 默认的屏幕方向(当前ViewController必须是通过模态出来的UIViewController(模态带导航的无效)方式展现出来的,才会调用这个方法) 92 | override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { 93 | guard let navigationController = selectedViewController as? UINavigationController else { return selectedViewController?.preferredInterfaceOrientationForPresentation ?? .portrait } 94 | return navigationController.topViewController?.preferredInterfaceOrientationForPresentation ?? .portrait 95 | } 96 | 97 | /// 状态栏样式 98 | override var preferredStatusBarStyle: UIStatusBarStyle { 99 | guard let navigationController = selectedViewController as? UINavigationController else { return selectedViewController?.preferredStatusBarStyle ?? .default } 100 | return navigationController.topViewController?.preferredStatusBarStyle ?? .default 101 | } 102 | 103 | /// 是否隐藏状态栏 104 | override var prefersStatusBarHidden: Bool { 105 | guard let navigationController = selectedViewController as? UINavigationController else { return selectedViewController?.prefersStatusBarHidden ?? false } 106 | return navigationController.topViewController?.prefersStatusBarHidden ?? false 107 | } 108 | } 109 | 110 | // MARK: - JmoVxia---objc 111 | 112 | @objc private extension CLTabBarController {} 113 | 114 | // MARK: - JmoVxia---私有方法 115 | 116 | private extension CLTabBarController { 117 | func addChild(child: UIViewController, title: String, image: UIImage?, selectedImage: UIImage?) { 118 | child.title = title 119 | child.tabBarItem.setTitleTextAttributes([.font: UIFont.systemFont(ofSize: 11)], for: .normal) 120 | child.tabBarItem.setTitleTextAttributes([.font: UIFont.boldSystemFont(ofSize: 11)], for: .selected) 121 | child.tabBarItem.image = image?.withRenderingMode(.alwaysOriginal) 122 | child.tabBarItem.selectedImage = selectedImage?.withRenderingMode(.alwaysOriginal) 123 | let navController = CLNavigationController(rootViewController: child) 124 | addChild(navController) 125 | } 126 | } 127 | 128 | // MARK: - JmoVxia---公共方法 129 | 130 | extension CLTabBarController {} 131 | -------------------------------------------------------------------------------- /CLAutolayoutController/CLAutolayoutController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLAutolayoutController.swift 3 | // CLPlayer 4 | // 5 | // Created by Chen JmoVxia on 2021/12/14. 6 | // 7 | 8 | import SnapKit 9 | import UIKit 10 | 11 | class CLAutolayoutController: CLController { 12 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { 13 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 14 | } 15 | 16 | @available(*, unavailable) 17 | required init?(coder _: NSCoder) { 18 | fatalError("init(coder:) has not been implemented") 19 | } 20 | 21 | private lazy var placeholderView: CLPlaceholderView = { 22 | let view = CLPlaceholderView() 23 | view.addTarget(self, action: #selector(playAction), for: .touchUpInside) 24 | return view 25 | }() 26 | 27 | private lazy var player: CLPlayer = { 28 | let view = CLPlayer() 29 | view.placeholder = placeholderView 30 | return view 31 | }() 32 | 33 | private lazy var changeButton: UIButton = { 34 | let view = UIButton() 35 | view.titleLabel?.font = .systemFont(ofSize: 18) 36 | view.setTitle("切换视频", for: .normal) 37 | view.setTitle("切换视频", for: .selected) 38 | view.setTitle("切换视频", for: .highlighted) 39 | view.setTitleColor(.orange, for: .normal) 40 | view.setTitleColor(.orange, for: .selected) 41 | view.setTitleColor(.orange, for: .highlighted) 42 | view.addTarget(self, action: #selector(changeAction), for: .touchUpInside) 43 | return view 44 | }() 45 | 46 | deinit { 47 | print("CLAutolayoutController deinit") 48 | } 49 | } 50 | 51 | // MARK: - JmoVxia---生命周期 52 | 53 | extension CLAutolayoutController { 54 | override func viewWillAppear(_ animated: Bool) { 55 | super.viewWillAppear(animated) 56 | } 57 | 58 | override func viewDidAppear(_ animated: Bool) { 59 | super.viewDidAppear(animated) 60 | } 61 | 62 | override func viewDidLoad() { 63 | super.viewDidLoad() 64 | initUI() 65 | makeConstraints() 66 | initData() 67 | } 68 | 69 | override func viewWillDisappear(_ animated: Bool) { 70 | super.viewWillDisappear(animated) 71 | } 72 | 73 | override func viewDidDisappear(_ animated: Bool) { 74 | super.viewDidDisappear(animated) 75 | } 76 | 77 | override func viewDidLayoutSubviews() { 78 | super.viewDidLayoutSubviews() 79 | } 80 | } 81 | 82 | // MARK: - JmoVxia---布局 83 | 84 | private extension CLAutolayoutController { 85 | func initUI() { 86 | updateTitleLabel { $0.text = "UIView" } 87 | view.addSubview(player) 88 | view.addSubview(changeButton) 89 | } 90 | 91 | func makeConstraints() { 92 | player.snp.makeConstraints { make in 93 | make.left.right.equalToSuperview() 94 | make.top.equalTo(view.safeAreaLayoutGuide.snp.top) 95 | make.height.equalTo(view.bounds.width / (16.0 / 9.0)) 96 | } 97 | changeButton.snp.makeConstraints { make in 98 | make.centerX.equalToSuperview() 99 | make.bottom.equalTo(-150) 100 | } 101 | } 102 | } 103 | 104 | // MARK: - JmoVxia---数据 105 | 106 | private extension CLAutolayoutController { 107 | func initData() { 108 | player.title = NSMutableAttributedString("Apple", attributes: { $0 109 | .font(.systemFont(ofSize: 16)) 110 | .foregroundColor(.white) 111 | .alignment(.center) 112 | }) 113 | } 114 | } 115 | 116 | extension CLAutolayoutController { 117 | override var shouldAutorotate: Bool { 118 | return false 119 | } 120 | 121 | // 支持哪些屏幕方向 122 | override var supportedInterfaceOrientations: UIInterfaceOrientationMask { 123 | return .portrait 124 | } 125 | } 126 | 127 | // MARK: - JmoVxia---objc 128 | 129 | @objc private extension CLAutolayoutController { 130 | func playAction() { 131 | placeholderView.imageView.image = UIImage(named: "placeholder") 132 | 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") 133 | player.play() 134 | } 135 | 136 | func changeAction() { 137 | placeholderView.imageView.image = UIImage(named: "placeholder1") 138 | player.title = NSMutableAttributedString("这是一个标题", attributes: { $0 139 | .font(.systemFont(ofSize: 16)) 140 | .foregroundColor(.white) 141 | .alignment(.left) 142 | }) 143 | player.url = URL(string: "http://vjs.zencdn.net/v/oceans.mp4") 144 | player.play() 145 | } 146 | } 147 | -------------------------------------------------------------------------------- /CLCollectionViewController/CLCollectionViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLCollectionViewController.swift 3 | // CLPlayer 4 | // 5 | // Created by Chen JmoVxia on 2021/12/16. 6 | // 7 | 8 | import SnapKit 9 | import UIKit 10 | 11 | // MARK: - JmoVxia---枚举 12 | 13 | extension CLCollectionViewController {} 14 | 15 | // MARK: - JmoVxia---类-属性 16 | 17 | class CLCollectionViewController: CLController { 18 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { 19 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 20 | } 21 | 22 | @available(*, unavailable) 23 | required init?(coder _: NSCoder) { 24 | fatalError("init(coder:) has not been implemented") 25 | } 26 | 27 | deinit {} 28 | 29 | private lazy var collectionView: UICollectionView = { 30 | let width = (view.bounds.width - 20) 31 | let layout = UICollectionViewFlowLayout() 32 | layout.minimumLineSpacing = 10 33 | layout.minimumInteritemSpacing = 10 34 | layout.itemSize = CGSize(width: width, height: width * 9.0 / 16.0) 35 | let view = UICollectionView(frame: .zero, collectionViewLayout: layout) 36 | view.contentInset = UIEdgeInsets(top: 10, left: 10, bottom: 10, right: 10) 37 | view.backgroundColor = .clear 38 | view.delegate = self 39 | view.dataSource = self 40 | view.register(CLCollectionViewCell.self, forCellWithReuseIdentifier: "CLCollectionViewCell") 41 | return view 42 | }() 43 | 44 | private var player: CLPlayer? 45 | 46 | let array = [ 47 | "https://www.apple.com/105/media/cn/mac/family/2018/46c4b917_abfd_45a3_9b51_4e3054191797/films/bruce/mac-bruce-tpl-cn-2018_1280x720h.mp4", 48 | "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", 49 | "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", 50 | "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", 51 | "http://mirror.aarnet.edu.au/pub/TED-talks/911Mothers_2010W-480p.mp4", 52 | "https://media.w3.org/2010/05/sintel/trailer.mp4", 53 | "https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/7194236f31b2e1e3da0fe06cfed4ba2b.mp4", 54 | "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4", 55 | "http://vjs.zencdn.net/v/oceans.mp4", 56 | "https://media.w3.org/2010/05/sintel/trailer.mp4", 57 | "http://mirror.aarnet.edu.au/pub/TED-talks/911Mothers_2010W-480p.mp4", 58 | "https://sample-videos.com/video123/mp4/480/big_buck_bunny_480p_2mb.mp4", 59 | "https://www.apple.com/105/media/cn/mac/family/2018/46c4b917_abfd_45a3_9b51_4e3054191797/films/bruce/mac-bruce-tpl-cn-2018_1280x720h.mp4", 60 | "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", 61 | "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", 62 | "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", 63 | "http://mirror.aarnet.edu.au/pub/TED-talks/911Mothers_2010W-480p.mp4", 64 | "https://media.w3.org/2010/05/sintel/trailer.mp4", 65 | "https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/7194236f31b2e1e3da0fe06cfed4ba2b.mp4", 66 | "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4", 67 | "http://vjs.zencdn.net/v/oceans.mp4", 68 | "https://media.w3.org/2010/05/sintel/trailer.mp4", 69 | ] 70 | } 71 | 72 | // MARK: - JmoVxia---生命周期 73 | 74 | extension CLCollectionViewController { 75 | override func viewWillAppear(_ animated: Bool) { 76 | super.viewWillAppear(animated) 77 | } 78 | 79 | override func viewDidAppear(_ animated: Bool) { 80 | super.viewDidAppear(animated) 81 | } 82 | 83 | override func viewDidLoad() { 84 | super.viewDidLoad() 85 | initUI() 86 | makeConstraints() 87 | initData() 88 | } 89 | 90 | override func viewWillDisappear(_ animated: Bool) { 91 | super.viewWillDisappear(animated) 92 | } 93 | 94 | override func viewDidDisappear(_ animated: Bool) { 95 | super.viewDidDisappear(animated) 96 | } 97 | 98 | override func viewDidLayoutSubviews() { 99 | super.viewDidLayoutSubviews() 100 | } 101 | } 102 | 103 | // MARK: - JmoVxia---布局 104 | 105 | private extension CLCollectionViewController { 106 | func initUI() { 107 | updateTitleLabel { $0.text = "CollectionView" } 108 | view.addSubview(collectionView) 109 | } 110 | 111 | func makeConstraints() { 112 | collectionView.snp.makeConstraints { make in 113 | make.left.right.bottom.equalToSuperview() 114 | if #available(iOS 11.0, *) { 115 | make.top.equalTo(view.safeAreaLayoutGuide.snp.top) 116 | } else { 117 | make.top.equalTo(topLayoutGuide.snp.bottom) 118 | } 119 | } 120 | } 121 | } 122 | 123 | // MARK: - JmoVxia---数据 124 | 125 | private extension CLCollectionViewController { 126 | func initData() {} 127 | } 128 | 129 | // MARK: - JmoVxia---override 130 | 131 | extension CLCollectionViewController { 132 | override var shouldAutorotate: Bool { 133 | return false 134 | } 135 | 136 | // 支持哪些屏幕方向 137 | override var supportedInterfaceOrientations: UIInterfaceOrientationMask { 138 | return .portrait 139 | } 140 | } 141 | 142 | // MARK: - JmoVxia---objc 143 | 144 | @objc private extension CLCollectionViewController {} 145 | 146 | // MARK: - JmoVxia---私有方法 147 | 148 | private extension CLCollectionViewController { 149 | func playWithIndexPath(_ indexPath: IndexPath) { 150 | guard let cell = collectionView.cellForItem(at: indexPath) else { return } 151 | 152 | if player == nil { 153 | player = CLPlayer() 154 | } 155 | player?.title = NSMutableAttributedString("这是一个标题", attributes: { $0 156 | .font(.systemFont(ofSize: 16)) 157 | .foregroundColor(.orange) 158 | .alignment(.center) 159 | }) 160 | player?.url = URL(string: array[indexPath.row]) 161 | cell.contentView.addSubview(player!) 162 | player?.frame = cell.contentView.bounds 163 | player?.play() 164 | } 165 | } 166 | 167 | // MARK: - JmoVxia---公共方法 168 | 169 | extension CLCollectionViewController {} 170 | 171 | extension CLCollectionViewController: UICollectionViewDataSource { 172 | func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { 173 | let cell = collectionView.dequeueReusableCell(withReuseIdentifier: "CLCollectionViewCell", for: indexPath) 174 | return cell 175 | } 176 | 177 | func collectionView(_: UICollectionView, numberOfItemsInSection _: Int) -> Int { 178 | return array.count 179 | } 180 | } 181 | 182 | extension CLCollectionViewController: UICollectionViewDelegate { 183 | func collectionView(_: UICollectionView, didSelectItemAt indexPath: IndexPath) { 184 | playWithIndexPath(indexPath) 185 | } 186 | 187 | func collectionView(_: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { 188 | guard let player = player else { return } 189 | guard array[indexPath.row] == player.url?.absoluteString else { return } 190 | 191 | cell.contentView.addSubview(player) 192 | player.frame = cell.contentView.bounds 193 | player.play() 194 | } 195 | 196 | func collectionView(_: UICollectionView, didEndDisplaying _: UICollectionViewCell, forItemAt indexPath: IndexPath) { 197 | guard let player = player else { return } 198 | guard array[indexPath.row] == player.url?.absoluteString else { return } 199 | 200 | player.removeFromSuperview() 201 | player.pause() 202 | } 203 | } 204 | -------------------------------------------------------------------------------- /CLCollectionViewController/View/CLCollectionViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLCollectionViewCell.swift 3 | // CLPlayer 4 | // 5 | // Created by Chen JmoVxia on 2021/12/16. 6 | // 7 | 8 | import UIKit 9 | 10 | class CLCollectionViewCell: UICollectionViewCell { 11 | override init(frame: CGRect) { 12 | super.init(frame: frame) 13 | initUI() 14 | makeConstraints() 15 | } 16 | 17 | @available(*, unavailable) 18 | required init?(coder _: NSCoder) { 19 | fatalError("init(coder:) has not been implemented") 20 | } 21 | 22 | private lazy var iconImageView: UIImageView = { 23 | let view = UIImageView() 24 | view.image = UIImage(named: "placeholder") 25 | return view 26 | }() 27 | 28 | private lazy var playImageView: UIImageView = { 29 | let view = UIImageView() 30 | view.image = UIImage(named: "play") 31 | return view 32 | }() 33 | } 34 | 35 | // MARK: - JmoVxia---布局 36 | 37 | private extension CLCollectionViewCell { 38 | func initUI() { 39 | contentView.addSubview(iconImageView) 40 | iconImageView.addSubview(playImageView) 41 | } 42 | 43 | func makeConstraints() { 44 | iconImageView.snp.makeConstraints { make in 45 | make.edges.equalToSuperview() 46 | } 47 | playImageView.snp.makeConstraints { make in 48 | make.center.equalToSuperview() 49 | } 50 | } 51 | } 52 | -------------------------------------------------------------------------------- /CLFrameController/CLFrameController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLFrameController.swift 3 | // CLPlayer 4 | // 5 | // Created by Chen JmoVxia on 2021/12/14. 6 | // 7 | 8 | import AVFoundation 9 | import SnapKit 10 | import UIKit 11 | 12 | class CLFrameController: CLController { 13 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { 14 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 15 | } 16 | 17 | @available(*, unavailable) 18 | required init?(coder _: NSCoder) { 19 | fatalError("init(coder:) has not been implemented") 20 | } 21 | 22 | private lazy var placeholderView: CLPlaceholderView = { 23 | let view = CLPlaceholderView() 24 | view.addTarget(self, action: #selector(playAction), for: .touchUpInside) 25 | return view 26 | }() 27 | 28 | private lazy var player: CLPlayer = { 29 | let view = CLPlayer(frame: CGRect(x: 0, y: UIApplication.shared.statusBarFrame.size.height + navigationBarHeight + 50, width: view.bounds.width, height: view.bounds.width / (16.0 / 9.0))) { config in 30 | config.videoGravity = .resizeAspectFill 31 | config.topBarHiddenStyle = .never 32 | config.isHiddenToolbarWhenStart = false 33 | config.image.thumb = UIImage(named: "slider_dog") 34 | config.thumbImageOffset = 10 35 | config.color.progress = .orange 36 | config.color.progressFinished = .red 37 | config.color.progressBuffer = .yellow 38 | } 39 | view.placeholder = placeholderView 40 | return view 41 | }() 42 | 43 | private lazy var changeButton: UIButton = { 44 | let view = UIButton() 45 | view.titleLabel?.font = .systemFont(ofSize: 18) 46 | view.setTitle("切换视频", for: .normal) 47 | view.setTitle("切换视频", for: .selected) 48 | view.setTitle("切换视频", for: .highlighted) 49 | view.setTitleColor(.orange, for: .normal) 50 | view.setTitleColor(.orange, for: .selected) 51 | view.setTitleColor(.orange, for: .highlighted) 52 | view.addTarget(self, action: #selector(changeAction), for: .touchUpInside) 53 | return view 54 | }() 55 | 56 | private lazy var seekButton: UIButton = { 57 | let view = UIButton() 58 | view.titleLabel?.font = .systemFont(ofSize: 18) 59 | view.setTitle("跳转播放进度", for: .normal) 60 | view.setTitle("跳转播放进度", for: .selected) 61 | view.setTitle("跳转播放进度", for: .highlighted) 62 | view.setTitleColor(.orange, for: .normal) 63 | view.setTitleColor(.orange, for: .selected) 64 | view.setTitleColor(.orange, for: .highlighted) 65 | view.addTarget(self, action: #selector(seekAction), for: .touchUpInside) 66 | return view 67 | }() 68 | 69 | deinit { 70 | print("CLFrameController deinit") 71 | } 72 | } 73 | 74 | // MARK: - JmoVxia---生命周期 75 | 76 | extension CLFrameController { 77 | override func viewWillAppear(_ animated: Bool) { 78 | super.viewWillAppear(animated) 79 | } 80 | 81 | override func viewDidAppear(_ animated: Bool) { 82 | super.viewDidAppear(animated) 83 | } 84 | 85 | override func viewDidLoad() { 86 | super.viewDidLoad() 87 | initUI() 88 | makeConstraints() 89 | initData() 90 | } 91 | 92 | override func viewWillDisappear(_ animated: Bool) { 93 | super.viewWillDisappear(animated) 94 | } 95 | 96 | override func viewDidDisappear(_ animated: Bool) { 97 | super.viewDidDisappear(animated) 98 | } 99 | 100 | override func viewDidLayoutSubviews() { 101 | super.viewDidLayoutSubviews() 102 | } 103 | } 104 | 105 | // MARK: - JmoVxia---布局 106 | 107 | private extension CLFrameController { 108 | func initUI() { 109 | updateTitleLabel { $0.text = "UIView" } 110 | view.addSubview(player) 111 | view.addSubview(changeButton) 112 | view.addSubview(seekButton) 113 | } 114 | 115 | func makeConstraints() { 116 | changeButton.snp.makeConstraints { make in 117 | make.centerX.equalToSuperview() 118 | make.bottom.equalTo(-150) 119 | } 120 | seekButton.snp.makeConstraints { make in 121 | make.centerX.equalToSuperview() 122 | make.top.equalTo(changeButton.snp.bottom).offset(10) 123 | } 124 | } 125 | } 126 | 127 | // MARK: - JmoVxia---数据 128 | 129 | private extension CLFrameController { 130 | func initData() { 131 | player.title = NSMutableAttributedString("Apple", attributes: { $0 132 | .font(.systemFont(ofSize: 16)) 133 | .foregroundColor(.white) 134 | .alignment(.center) 135 | }) 136 | } 137 | } 138 | 139 | extension CLFrameController { 140 | override var shouldAutorotate: Bool { 141 | return false 142 | } 143 | 144 | // 支持哪些屏幕方向 145 | override var supportedInterfaceOrientations: UIInterfaceOrientationMask { 146 | return .portrait 147 | } 148 | } 149 | 150 | // MARK: - JmoVxia---objc 151 | 152 | @objc private extension CLFrameController { 153 | func playAction() { 154 | placeholderView.imageView.image = UIImage(named: "placeholder") 155 | 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") 156 | player.seek(to: .init(time: .init(value: 10, timescale: 1))) 157 | player.play() 158 | } 159 | 160 | func changeAction() { 161 | placeholderView.imageView.image = UIImage(named: "placeholder1") 162 | player.title = NSMutableAttributedString("这是一个标题", attributes: { $0 163 | .font(.systemFont(ofSize: 16)) 164 | .foregroundColor(.white) 165 | .alignment(.left) 166 | }) 167 | player.url = URL(string: "http://vjs.zencdn.net/v/oceans.mp4") 168 | player.play() 169 | } 170 | 171 | func seekAction() { 172 | player.seek(to: .init(time: .init(value: 25, timescale: 1))) 173 | } 174 | } 175 | -------------------------------------------------------------------------------- /CLFrameController/CLPlaceholderView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLPlaceholderView.swift 3 | // CLPlayer 4 | // 5 | // Created by Chen JmoVxia on 2022/12/9. 6 | // 7 | 8 | import UIKit 9 | 10 | // MARK: - JmoVxia---枚举 11 | 12 | extension CLPlaceholderView {} 13 | 14 | // MARK: - JmoVxia---类-属性 15 | 16 | class CLPlaceholderView: UIControl { 17 | override init(frame: CGRect) { 18 | super.init(frame: frame) 19 | initSubViews() 20 | makeConstraints() 21 | } 22 | 23 | @available(*, unavailable) 24 | required init?(coder: NSCoder) { 25 | fatalError("init(coder:) has not been implemented") 26 | } 27 | 28 | private(set) lazy var imageView: UIImageView = { 29 | let view = UIImageView(image: UIImage(named: "placeholder")) 30 | view.isUserInteractionEnabled = false 31 | return view 32 | }() 33 | 34 | private lazy var playImageView: UIImageView = { 35 | let view = UIImageView(image: UIImage(named: "play")) 36 | view.isUserInteractionEnabled = false 37 | return view 38 | }() 39 | } 40 | 41 | // MARK: - JmoVxia---布局 42 | 43 | private extension CLPlaceholderView { 44 | func initSubViews() { 45 | addSubview(imageView) 46 | addSubview(playImageView) 47 | } 48 | 49 | func makeConstraints() { 50 | imageView.snp.makeConstraints { make in 51 | make.edges.equalToSuperview() 52 | } 53 | playImageView.snp.makeConstraints { make in 54 | make.center.equalToSuperview() 55 | } 56 | } 57 | } 58 | 59 | // MARK: - JmoVxia---override 60 | 61 | extension CLPlaceholderView {} 62 | 63 | // MARK: - JmoVxia---objc 64 | 65 | @objc private extension CLPlaceholderView {} 66 | 67 | // MARK: - JmoVxia---私有方法 68 | 69 | private extension CLPlaceholderView {} 70 | 71 | // MARK: - JmoVxia---公共方法 72 | 73 | extension CLPlaceholderView {} 74 | -------------------------------------------------------------------------------- /CLHomeController/Controller/CLHomeController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLHomeController.swift 3 | // CLPlayer 4 | // 5 | // Created by Chen JmoVxia on 2021/10/26. 6 | // 7 | 8 | import AVFoundation 9 | import MobileCoreServices 10 | import SnapKit 11 | import UIKit 12 | 13 | // MARK: - JmoVxia---类-属性 14 | 15 | class CLHomeController: CLController { 16 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { 17 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 18 | } 19 | 20 | @available(*, unavailable) 21 | required init?(coder _: NSCoder) { 22 | fatalError("init(coder:) has not been implemented") 23 | } 24 | 25 | deinit {} 26 | 27 | private lazy var tableViewHepler: CLTableViewHepler = { 28 | let hepler = CLTableViewHepler() 29 | return hepler 30 | }() 31 | 32 | private lazy var tableView: UITableView = { 33 | let view = UITableView(frame: .zero, style: .plain) 34 | view.showsVerticalScrollIndicator = false 35 | view.showsHorizontalScrollIndicator = false 36 | view.backgroundColor = .white 37 | view.separatorStyle = .none 38 | view.dataSource = tableViewHepler 39 | view.delegate = tableViewHepler 40 | if #available(iOS 11.0, *) { 41 | view.contentInsetAdjustmentBehavior = .never 42 | } 43 | return view 44 | }() 45 | } 46 | 47 | // MARK: - JmoVxia---生命周期 48 | 49 | extension CLHomeController { 50 | override func viewWillAppear(_ animated: Bool) { 51 | super.viewWillAppear(animated) 52 | } 53 | 54 | override func viewDidAppear(_ animated: Bool) { 55 | super.viewDidAppear(animated) 56 | } 57 | 58 | override func viewDidLoad() { 59 | super.viewDidLoad() 60 | initUI() 61 | makeConstraints() 62 | initData() 63 | print("本框架基于AVPlayer封装,支持格式:\n\(getAllowedAVPlayerFileExtensions())") 64 | } 65 | 66 | override func viewWillDisappear(_ animated: Bool) { 67 | super.viewWillDisappear(animated) 68 | } 69 | 70 | override func viewDidDisappear(_ animated: Bool) { 71 | super.viewDidDisappear(animated) 72 | } 73 | 74 | override func viewDidLayoutSubviews() { 75 | super.viewDidLayoutSubviews() 76 | } 77 | } 78 | 79 | // MARK: - JmoVxia---布局 80 | 81 | private extension CLHomeController { 82 | func initUI() { 83 | view.addSubview(tableView) 84 | } 85 | 86 | func makeConstraints() { 87 | tableView.snp.makeConstraints { make in 88 | make.left.right.bottom.equalToSuperview() 89 | if #available(iOS 11.0, *) { 90 | make.top.equalTo(view.safeAreaLayoutGuide.snp.top) 91 | } else { 92 | make.top.equalTo(topLayoutGuide.snp.bottom) 93 | } 94 | } 95 | } 96 | } 97 | 98 | // MARK: - JmoVxia---数据 99 | 100 | private extension CLHomeController { 101 | func initData() { 102 | do { 103 | let item = CLListItem() 104 | item.title = "Frame布局" 105 | item.didSelectCellCallback = { [weak self] _ in 106 | guard let self = self else { return } 107 | self.pushToFrame() 108 | } 109 | tableViewHepler.dataSource.append(item) 110 | } 111 | do { 112 | let item = CLListItem() 113 | item.title = "Autolayout布局" 114 | item.didSelectCellCallback = { [weak self] _ in 115 | guard let self = self else { return } 116 | self.pushToAutolayout() 117 | } 118 | tableViewHepler.dataSource.append(item) 119 | } 120 | do { 121 | let item = CLListItem() 122 | item.title = "UIStackView布局" 123 | item.didSelectCellCallback = { [weak self] _ in 124 | guard let self = self else { return } 125 | self.pushToStackView() 126 | } 127 | tableViewHepler.dataSource.append(item) 128 | } 129 | do { 130 | let item = CLListItem() 131 | item.title = "UITableView" 132 | item.didSelectCellCallback = { [weak self] _ in 133 | guard let self = self else { return } 134 | self.pushToTableView() 135 | } 136 | tableViewHepler.dataSource.append(item) 137 | } 138 | do { 139 | let item = CLListItem() 140 | item.title = "UICollectionView" 141 | item.didSelectCellCallback = { [weak self] _ in 142 | guard let self = self else { return } 143 | self.pushToCollectionView() 144 | } 145 | tableViewHepler.dataSource.append(item) 146 | } 147 | tableView.reloadData() 148 | } 149 | } 150 | 151 | // MARK: - JmoVxia---override 152 | 153 | extension CLHomeController {} 154 | 155 | // MARK: - JmoVxia---objc 156 | 157 | @objc private extension CLHomeController {} 158 | 159 | // MARK: - JmoVxia---私有方法 160 | 161 | private extension CLHomeController { 162 | func getAllowedAVPlayerFileExtensions() -> [String] { 163 | let avTypes = AVURLAsset.audiovisualTypes() 164 | var avExtensions = avTypes.map { UTTypeCopyPreferredTagWithClass($0 as CFString, kUTTagClassFilenameExtension)?.takeRetainedValue() as String? ?? "" } 165 | avExtensions = avExtensions.filter { !$0.isEmpty } 166 | return avExtensions 167 | } 168 | 169 | func pushToFrame() { 170 | navigationController?.pushViewController(CLFrameController(), animated: true) 171 | } 172 | 173 | func pushToAutolayout() { 174 | navigationController?.pushViewController(CLAutolayoutController(), animated: true) 175 | } 176 | 177 | func pushToStackView() { 178 | navigationController?.pushViewController(CLStackViewController(), animated: true) 179 | } 180 | 181 | func pushToTableView() { 182 | navigationController?.pushViewController(CLTableViewController(), animated: true) 183 | } 184 | 185 | func pushToCollectionView() { 186 | navigationController?.pushViewController(CLCollectionViewController(), animated: true) 187 | } 188 | } 189 | 190 | // MARK: - JmoVxia---公共方法 191 | 192 | extension CLHomeController {} 193 | -------------------------------------------------------------------------------- /CLHomeController/View/Cell/CLListCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLListCell.swift 3 | // CLPlayer 4 | // 5 | // Created by Chen JmoVxia on 2021/10/26. 6 | // 7 | 8 | import UIKit 9 | 10 | // MARK: - JmoVxia---类-属性 11 | 12 | class CLListCell: UITableViewCell { 13 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 14 | super.init(style: style, reuseIdentifier: reuseIdentifier) 15 | initUI() 16 | makeConstraints() 17 | } 18 | 19 | @available(*, unavailable) 20 | required init?(coder _: NSCoder) { 21 | fatalError("init(coder:) has not been implemented") 22 | } 23 | 24 | private lazy var titleLabel: UILabel = { 25 | let view = UILabel() 26 | view.textColor = .hex("#343434") 27 | view.font = .systemFont(ofSize: 15) 28 | return view 29 | }() 30 | 31 | private lazy var arrowImageView: UIImageView = { 32 | let view = UIImageView() 33 | view.image = UIImage(named: "meArrowRight") 34 | return view 35 | }() 36 | 37 | private lazy var lineView: UIView = { 38 | let view = UIView() 39 | view.backgroundColor = .hex("#F0F0F0") 40 | return view 41 | }() 42 | } 43 | 44 | // MARK: - JmoVxia---布局 45 | 46 | private extension CLListCell { 47 | private func initUI() { 48 | isExclusiveTouch = true 49 | selectionStyle = .none 50 | contentView.addSubview(titleLabel) 51 | contentView.addSubview(arrowImageView) 52 | contentView.addSubview(lineView) 53 | arrowImageView.setContentCompressionResistancePriority(.defaultHigh, for: .horizontal) 54 | arrowImageView.setContentHuggingPriority(.defaultHigh, for: .horizontal) 55 | } 56 | 57 | private func makeConstraints() { 58 | arrowImageView.snp.makeConstraints { make in 59 | make.centerY.equalToSuperview() 60 | make.right.equalTo(-15) 61 | } 62 | titleLabel.snp.makeConstraints { make in 63 | make.left.equalTo(15) 64 | make.right.equalTo(arrowImageView.snp.left).offset(-15) 65 | make.centerY.equalToSuperview() 66 | } 67 | lineView.snp.makeConstraints { make in 68 | make.left.equalTo(15) 69 | make.right.bottom.equalToSuperview() 70 | make.height.equalTo(0.5) 71 | } 72 | } 73 | } 74 | 75 | // MARK: - JmoVxia---CLCellProtocol 76 | 77 | extension CLListCell: CLCellProtocol { 78 | func setItem(_ item: CLCellItemProtocol) { 79 | guard let item = item as? CLListItem else { return } 80 | titleLabel.text = item.title 81 | } 82 | } 83 | 84 | // MARK: - JmoVxia---数据 85 | 86 | private extension CLListCell { 87 | func initData() {} 88 | } 89 | 90 | // MARK: - JmoVxia---override 91 | 92 | extension CLListCell {} 93 | 94 | // MARK: - JmoVxia---objc 95 | 96 | @objc private extension CLListCell {} 97 | 98 | // MARK: - JmoVxia---私有方法 99 | 100 | private extension CLListCell {} 101 | 102 | // MARK: - JmoVxia---公共方法 103 | 104 | extension CLListCell {} 105 | -------------------------------------------------------------------------------- /CLHomeController/View/Item/CLListItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLListItem.swift 3 | // CLPlayer 4 | // 5 | // Created by Chen JmoVxia on 2021/10/26. 6 | // 7 | 8 | import UIKit 9 | 10 | class CLListItem: NSObject { 11 | var title = "" 12 | var didSelectCellCallback: ((IndexPath) -> Void)? 13 | } 14 | 15 | extension CLListItem: CLCellItemProtocol { 16 | func bindCell() -> UITableViewCell.Type { 17 | return CLListCell.self 18 | } 19 | 20 | func cellHeight() -> CGFloat { 21 | return 50 22 | } 23 | } 24 | -------------------------------------------------------------------------------- /CLMoreController/Controller/CLMoreController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLMoreController.swift 3 | // CLPlayer 4 | // 5 | // Created by Chen JmoVxia on 2021/10/26. 6 | // 7 | 8 | import SnapKit 9 | import UIKit 10 | 11 | // MARK: - JmoVxia---类-属性 12 | 13 | class CLMoreController: CLController { 14 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { 15 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 16 | } 17 | 18 | @available(*, unavailable) 19 | required init?(coder _: NSCoder) { 20 | fatalError("init(coder:) has not been implemented") 21 | } 22 | 23 | deinit {} 24 | 25 | private lazy var tableViewHepler: CLTableViewHepler = { 26 | let hepler = CLTableViewHepler() 27 | return hepler 28 | }() 29 | 30 | private lazy var tableView: UITableView = { 31 | let view = UITableView(frame: .zero, style: .plain) 32 | view.showsVerticalScrollIndicator = false 33 | view.showsHorizontalScrollIndicator = false 34 | view.backgroundColor = .white 35 | view.separatorStyle = .none 36 | view.dataSource = tableViewHepler 37 | view.delegate = tableViewHepler 38 | if #available(iOS 11.0, *) { 39 | view.contentInsetAdjustmentBehavior = .never 40 | } 41 | return view 42 | }() 43 | } 44 | 45 | // MARK: - JmoVxia---生命周期 46 | 47 | extension CLMoreController { 48 | override func viewWillAppear(_ animated: Bool) { 49 | super.viewWillAppear(animated) 50 | } 51 | 52 | override func viewDidAppear(_ animated: Bool) { 53 | super.viewDidAppear(animated) 54 | } 55 | 56 | override func viewDidLoad() { 57 | super.viewDidLoad() 58 | initUI() 59 | makeConstraints() 60 | initData() 61 | } 62 | 63 | override func viewWillDisappear(_ animated: Bool) { 64 | super.viewWillDisappear(animated) 65 | } 66 | 67 | override func viewDidDisappear(_ animated: Bool) { 68 | super.viewDidDisappear(animated) 69 | } 70 | 71 | override func viewDidLayoutSubviews() { 72 | super.viewDidLayoutSubviews() 73 | } 74 | } 75 | 76 | // MARK: - JmoVxia---布局 77 | 78 | private extension CLMoreController { 79 | func initUI() { 80 | view.addSubview(tableView) 81 | } 82 | 83 | func makeConstraints() { 84 | tableView.snp.makeConstraints { make in 85 | make.left.right.bottom.equalToSuperview() 86 | if #available(iOS 11.0, *) { 87 | make.top.equalTo(view.safeAreaLayoutGuide.snp.top) 88 | } else { 89 | make.top.equalTo(topLayoutGuide.snp.bottom) 90 | } 91 | } 92 | } 93 | } 94 | 95 | // MARK: - JmoVxia---数据 96 | 97 | private extension CLMoreController { 98 | func initData() {} 99 | } 100 | 101 | // MARK: - JmoVxia---override 102 | 103 | extension CLMoreController {} 104 | 105 | // MARK: - JmoVxia---objc 106 | 107 | @objc private extension CLMoreController {} 108 | 109 | // MARK: - JmoVxia---私有方法 110 | 111 | private extension CLMoreController {} 112 | 113 | // MARK: - JmoVxia---公共方法 114 | 115 | extension CLMoreController {} 116 | -------------------------------------------------------------------------------- /CLPlayer.podspec: -------------------------------------------------------------------------------- 1 | Pod::Spec.new do |s| 2 | 3 | s.name = 'CLPlayer' 4 | s.version = '2.0.7' 5 | s.summary = 'Swift版自定义AVPlayer' 6 | s.description = <<-DESC 7 | CLPlayer是基于系统AVPlayer封装的视频播放器. 8 | * 支持Autolayout、UIStackView、Frame. 9 | * 支持UITableView、UICollectionView. 10 | * 支持亮度、音量调节. 11 | * 支持进度调节. 12 | * 支持倍数播放. 13 | DESC 14 | s.homepage = 'https://github.com/JmoVxia/CLPlayer' 15 | s.license = { :type => 'MIT', :file => 'LICENSE' } 16 | s.authors = {'JmoVxia' => '269968846@qq.com'} 17 | s.social_media_url = 'https://github.com/JmoVxia' 18 | s.swift_versions = ['5.0'] 19 | s.ios.deployment_target = '12.0' 20 | s.source = {:git => 'https://github.com/JmoVxia/CLPlayer.git', :tag => s.version} 21 | s.source_files = ['CLPlayer/**/*.swift'] 22 | s.resource = 'CLPlayer/CLPlayer.bundle' 23 | s.requires_arc = true 24 | s.frameworks = 'UIKit','MediaPlayer' 25 | s.dependency 'SnapKit' 26 | 27 | end -------------------------------------------------------------------------------- /CLPlayer.xcodeproj/project.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 8 | -------------------------------------------------------------------------------- /CLPlayer.xcodeproj/project.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CLPlayer.xcodeproj/xcuserdata/jmovxia.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | CLPlayer.xcscheme_^#shared#^_ 8 | 9 | orderHint 10 | 5 11 | 12 | 13 | 14 | 15 | -------------------------------------------------------------------------------- /CLPlayer.xcworkspace/contents.xcworkspacedata: -------------------------------------------------------------------------------- 1 | 2 | 4 | 6 | 7 | 9 | 10 | 11 | -------------------------------------------------------------------------------- /CLPlayer.xcworkspace/xcshareddata/IDEWorkspaceChecks.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | IDEDidComputeMac32BitWarning 6 | 7 | 8 | 9 | -------------------------------------------------------------------------------- /CLPlayer/CLFullScreenController/CLAnimationTransitioning.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLAnimationTransitioning.swift 3 | // CLPlayer 4 | // 5 | // Created by Chen JmoVxia on 2021/10/27. 6 | // 7 | 8 | import SnapKit 9 | import UIKit 10 | 11 | extension CLAnimationTransitioning { 12 | enum AnimationType { 13 | case present 14 | case dismiss 15 | } 16 | 17 | enum AnimationOrientation { 18 | case left 19 | case right 20 | case fullRight 21 | } 22 | } 23 | 24 | class CLAnimationTransitioning: NSObject { 25 | private let keyWindow: UIWindow? = { 26 | if #available(iOS 13.0, *) { 27 | return UIApplication.shared.windows.filter { $0.isKeyWindow }.last 28 | } else { 29 | return UIApplication.shared.keyWindow 30 | } 31 | }() 32 | 33 | private weak var playerView: CLPlayerView? 34 | 35 | private weak var parentStackView: UIStackView? 36 | 37 | private var initialCenter: CGPoint = .zero 38 | 39 | private var finalCenter: CGPoint = .zero 40 | 41 | private var initialBounds: CGRect = .zero 42 | 43 | private var animationOrientation: AnimationOrientation = .left 44 | 45 | var animationType: AnimationType = .present 46 | 47 | init(playerView: CLPlayerView, animationOrientation: AnimationOrientation) { 48 | self.playerView = playerView 49 | self.animationOrientation = animationOrientation 50 | parentStackView = playerView.superview as? UIStackView 51 | initialBounds = playerView.bounds 52 | initialCenter = playerView.center 53 | finalCenter = playerView.convert(initialCenter, to: nil) 54 | } 55 | } 56 | 57 | extension CLAnimationTransitioning: UIViewControllerAnimatedTransitioning { 58 | func transitionDuration(using _: UIViewControllerContextTransitioning?) -> TimeInterval { 59 | return 0.35 60 | } 61 | 62 | func animateTransition(using transitionContext: UIViewControllerContextTransitioning) { 63 | guard let playerView = playerView else { return } 64 | 65 | if animationType == .present { 66 | guard let toView = transitionContext.view(forKey: .to) else { return } 67 | guard let toController = transitionContext.viewController(forKey: .to) as? CLFullScreenController else { return } 68 | 69 | let startCenter = transitionContext.containerView.convert(initialCenter, from: playerView) 70 | transitionContext.containerView.addSubview(toView) 71 | toController.mainStackView.addArrangedSubview(playerView) 72 | toView.bounds = initialBounds 73 | toView.center = startCenter 74 | toView.transform = .init(rotationAngle: toController.isKind(of: CLFullScreenLeftController.self) ? Double.pi * 0.5 : Double.pi * -0.5) 75 | 76 | if #available(iOS 11.0, *) { 77 | playerView.contentView.animationLayout(safeAreaInsets: keyWindow?.safeAreaInsets ?? .zero, to: .fullScreen) 78 | } else { 79 | playerView.contentView.animationLayout(safeAreaInsets: .zero, to: .fullScreen) 80 | } 81 | UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0, options: .layoutSubviews, animations: { 82 | toView.transform = .identity 83 | toView.bounds = transitionContext.containerView.bounds 84 | toView.center = transitionContext.containerView.center 85 | playerView.contentView.setNeedsLayout() 86 | playerView.contentView.layoutIfNeeded() 87 | }) { _ in 88 | toView.transform = .identity 89 | toView.bounds = transitionContext.containerView.bounds 90 | toView.center = transitionContext.containerView.center 91 | transitionContext.completeTransition(true) 92 | UIViewController.attemptRotationToDeviceOrientation() 93 | } 94 | } else { 95 | guard let parentStackView = parentStackView else { return } 96 | guard let fromView = transitionContext.view(forKey: .from) else { return } 97 | guard let toView = transitionContext.view(forKey: .to) else { return } 98 | 99 | transitionContext.containerView.addSubview(toView) 100 | transitionContext.containerView.addSubview(fromView) 101 | toView.frame = transitionContext.containerView.bounds 102 | 103 | playerView.contentView.animationLayout(safeAreaInsets: .zero, to: .small) 104 | UIView.animate(withDuration: transitionDuration(using: transitionContext), delay: 0.0, options: .layoutSubviews, animations: { 105 | fromView.transform = .identity 106 | fromView.center = self.finalCenter 107 | fromView.bounds = self.initialBounds 108 | playerView.contentView.setNeedsLayout() 109 | playerView.contentView.layoutIfNeeded() 110 | }) { _ in 111 | fromView.transform = .identity 112 | fromView.center = self.finalCenter 113 | fromView.bounds = self.initialBounds 114 | parentStackView.addArrangedSubview(playerView) 115 | fromView.removeFromSuperview() 116 | transitionContext.completeTransition(true) 117 | UIViewController.attemptRotationToDeviceOrientation() 118 | } 119 | } 120 | } 121 | } 122 | -------------------------------------------------------------------------------- /CLPlayer/CLFullScreenController/CLFullScreenController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLFullScreenController.swift 3 | // CLPlayer 4 | // 5 | // Created by Chen JmoVxia on 2021/10/27. 6 | // 7 | 8 | import UIKit 9 | 10 | // MARK: - JmoVxia---类-属性 11 | 12 | class CLFullScreenController: UIViewController { 13 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { 14 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 15 | } 16 | 17 | @available(*, unavailable) 18 | required init?(coder _: NSCoder) { 19 | fatalError("init(coder:) has not been implemented") 20 | } 21 | 22 | deinit {} 23 | 24 | private(set) lazy var mainStackView: UIStackView = { 25 | let view = UIStackView() 26 | view.isUserInteractionEnabled = true 27 | view.axis = .horizontal 28 | view.distribution = .fill 29 | view.alignment = .fill 30 | view.insetsLayoutMarginsFromSafeArea = false 31 | view.isLayoutMarginsRelativeArrangement = true 32 | view.layoutMargins = .zero 33 | view.spacing = 0 34 | return view 35 | }() 36 | } 37 | 38 | // MARK: - JmoVxia---生命周期 39 | 40 | extension CLFullScreenController { 41 | override func viewWillAppear(_ animated: Bool) { 42 | super.viewWillAppear(animated) 43 | } 44 | 45 | override func viewDidAppear(_ animated: Bool) { 46 | super.viewDidAppear(animated) 47 | } 48 | 49 | override func viewDidLoad() { 50 | super.viewDidLoad() 51 | initUI() 52 | } 53 | 54 | override func viewWillDisappear(_ animated: Bool) { 55 | super.viewWillDisappear(animated) 56 | } 57 | 58 | override func viewDidDisappear(_ animated: Bool) { 59 | super.viewDidDisappear(animated) 60 | } 61 | 62 | override func viewDidLayoutSubviews() { 63 | super.viewDidLayoutSubviews() 64 | } 65 | } 66 | 67 | // MARK: - JmoVxia---布局 68 | 69 | private extension CLFullScreenController { 70 | func initUI() { 71 | view.backgroundColor = .black 72 | view.addSubview(mainStackView) 73 | mainStackView.snp.makeConstraints { make in 74 | make.edges.equalToSuperview() 75 | } 76 | } 77 | } 78 | 79 | // MARK: - JmoVxia---override 80 | 81 | extension CLFullScreenController { 82 | override var shouldAutorotate: Bool { 83 | return true 84 | } 85 | 86 | override var supportedInterfaceOrientations: UIInterfaceOrientationMask { 87 | return .landscape 88 | } 89 | 90 | override var preferredStatusBarStyle: UIStatusBarStyle { 91 | return .default 92 | } 93 | 94 | override var prefersStatusBarHidden: Bool { 95 | return true 96 | } 97 | 98 | override var prefersHomeIndicatorAutoHidden: Bool { 99 | return true 100 | } 101 | } 102 | -------------------------------------------------------------------------------- /CLPlayer/CLFullScreenController/CLFullScreenLeftController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLFullScreenLeftController.swift 3 | // CLPlayer 4 | // 5 | // Created by Chen JmoVxia on 2021/10/27. 6 | // 7 | 8 | import UIKit 9 | 10 | // MARK: - JmoVxia---类-属性 11 | 12 | class CLFullScreenLeftController: CLFullScreenController { 13 | override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { 14 | return .landscapeLeft 15 | } 16 | 17 | deinit { 18 | print("CLFullScreenLeftController deinit") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /CLPlayer/CLFullScreenController/CLFullScreenRightController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLFullScreenRightController.swift 3 | // CLPlayer 4 | // 5 | // Created by Chen JmoVxia on 2021/10/27. 6 | // 7 | 8 | import UIKit 9 | 10 | // MARK: - JmoVxia---类-属性 11 | 12 | class CLFullScreenRightController: CLFullScreenController { 13 | override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { 14 | return .landscapeRight 15 | } 16 | 17 | deinit { 18 | print("CLFullScreenRightController deinit") 19 | } 20 | } 21 | -------------------------------------------------------------------------------- /CLPlayer/CLGCDTimer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLGCDTimer.swift 3 | // CLPlayer 4 | // 5 | // Created by Chen JmoVxia on 2021/10/27. 6 | // 7 | 8 | import UIKit 9 | 10 | class CLGCDTimer: NSObject { 11 | public enum TimerState { 12 | case suspended 13 | case resumed 14 | } 15 | 16 | /// 执行时间 17 | public private(set) var interval: TimeInterval! 18 | /// 第一次执行延迟时间延迟时间 19 | public private(set) var initialDelay: TimeInterval! 20 | /// 队列 21 | public private(set) var queue: DispatchQueue! 22 | /// 定时器 23 | public private(set) var timer: DispatchSourceTimer! 24 | /// 运行状态 25 | public private(set) var state: TimerState = .suspended 26 | /// 执行次数 27 | public private(set) var numberOfActions = Int.zero 28 | /// 响应回调 29 | public private(set) var eventHandler: ((Int) -> Void)? 30 | 31 | /// 创建定时器 32 | /// 33 | /// - Parameters: 34 | /// - interval: 间隔时间 35 | /// - delaySecs: 第一次执行延迟时间,默认为0 36 | /// - queue: 定时器调用的队列,默认主队列 37 | /// - repeats: 是否重复执行,默认true 38 | /// - action: 响应 39 | public init(interval: TimeInterval, 40 | initialDelay: TimeInterval = 0, 41 | queue: DispatchQueue = .main) 42 | { 43 | super.init() 44 | self.interval = interval 45 | self.initialDelay = initialDelay 46 | self.queue = queue 47 | timer = DispatchSource.makeTimerSource(queue: queue) 48 | timer.schedule(deadline: .now() + initialDelay, repeating: interval) 49 | timer.setEventHandler { [weak self] in 50 | guard let self = self else { return } 51 | self.numberOfActions += 1 52 | self.eventHandler?(self.numberOfActions) 53 | } 54 | } 55 | 56 | deinit { 57 | timer?.setEventHandler(handler: nil) 58 | timer?.cancel() 59 | eventHandler = nil 60 | resume() 61 | } 62 | } 63 | 64 | extension CLGCDTimer { 65 | /// 开始 66 | public func run(_ handler: @escaping ((_ numberOfActions: Int) -> Void)) { 67 | eventHandler = handler 68 | resume() 69 | } 70 | 71 | /// 暂停 72 | public func pause() { 73 | guard let timer = timer else { return } 74 | guard state != .suspended else { return } 75 | state = .suspended 76 | timer.suspend() 77 | } 78 | 79 | /// 恢复定时器 80 | public func resume() { 81 | guard state != .resumed else { return } 82 | guard let timer = timer else { return } 83 | state = .resumed 84 | timer.resume() 85 | } 86 | } 87 | -------------------------------------------------------------------------------- /CLPlayer/CLImageHelper.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLImageHelper.swift 3 | // CLPlayer 4 | // 5 | // Created by Chen JmoVxia on 2021/10/27. 6 | // 7 | 8 | import UIKit 9 | 10 | public class CLImageHelper: NSObject { 11 | public static func imageWithName(_ name: String) -> UIImage? { 12 | let filePath = Bundle(for: classForCoder()).resourcePath! + "/CLPlayer.bundle" 13 | let bundle = Bundle(path: filePath) 14 | let scale = max(min(Int(UIScreen.main.scale), 2), 3) 15 | return .init(named: "\(name)@\(scale)x", in: bundle, compatibleWith: nil) 16 | } 17 | } 18 | -------------------------------------------------------------------------------- /CLPlayer/CLPlayer.bundle/CLBack@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/CLPlayer/CLPlayer.bundle/CLBack@2x.png -------------------------------------------------------------------------------- /CLPlayer/CLPlayer.bundle/CLBack@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/CLPlayer/CLPlayer.bundle/CLBack@3x.png -------------------------------------------------------------------------------- /CLPlayer/CLPlayer.bundle/CLFullscreen@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/CLPlayer/CLPlayer.bundle/CLFullscreen@2x.png -------------------------------------------------------------------------------- /CLPlayer/CLPlayer.bundle/CLFullscreen@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/CLPlayer/CLPlayer.bundle/CLFullscreen@3x.png -------------------------------------------------------------------------------- /CLPlayer/CLPlayer.bundle/CLMore@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/CLPlayer/CLPlayer.bundle/CLMore@2x.png -------------------------------------------------------------------------------- /CLPlayer/CLPlayer.bundle/CLMore@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/CLPlayer/CLPlayer.bundle/CLMore@3x.png -------------------------------------------------------------------------------- /CLPlayer/CLPlayer.bundle/CLPause@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/CLPlayer/CLPlayer.bundle/CLPause@2x.png -------------------------------------------------------------------------------- /CLPlayer/CLPlayer.bundle/CLPause@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/CLPlayer/CLPlayer.bundle/CLPause@3x.png -------------------------------------------------------------------------------- /CLPlayer/CLPlayer.bundle/CLPlay@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/CLPlayer/CLPlayer.bundle/CLPlay@2x.png -------------------------------------------------------------------------------- /CLPlayer/CLPlayer.bundle/CLPlay@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/CLPlayer/CLPlayer.bundle/CLPlay@3x.png -------------------------------------------------------------------------------- /CLPlayer/CLPlayer.bundle/CLSlider@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/CLPlayer/CLPlayer.bundle/CLSlider@2x.png -------------------------------------------------------------------------------- /CLPlayer/CLPlayer.bundle/CLSlider@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/CLPlayer/CLPlayer.bundle/CLSlider@3x.png -------------------------------------------------------------------------------- /CLPlayer/CLPlayer.bundle/CLSmallscreen@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/CLPlayer/CLPlayer.bundle/CLSmallscreen@2x.png -------------------------------------------------------------------------------- /CLPlayer/CLPlayer.bundle/CLSmallscreen@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/CLPlayer/CLPlayer.bundle/CLSmallscreen@3x.png -------------------------------------------------------------------------------- /CLPlayer/CLPlayer.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLPlayer.swift 3 | // CLPlayer 4 | // 5 | // Created by Chen JmoVxia on 2023/11/10. 6 | // 7 | 8 | import AVFoundation 9 | import UIKit 10 | 11 | // MARK: - JmoVxia---扩展 12 | 13 | public extension CLPlayer { 14 | struct CLPlayerSeek { 15 | let time: CMTime 16 | var toleranceBefore: CMTime = .zero 17 | var toleranceAfter: CMTime = .zero 18 | } 19 | } 20 | 21 | // MARK: - JmoVxia---类-属性 22 | 23 | public class CLPlayer: UIStackView { 24 | public init(frame: CGRect = .zero, config: ((inout CLPlayerConfigure) -> Void)? = nil) { 25 | super.init(frame: frame) 26 | config?(&self.config) 27 | initSubViews() 28 | makeConstraints() 29 | } 30 | 31 | @available(*, unavailable) 32 | required init(coder: NSCoder) { 33 | fatalError("init(coder:) has not been implemented") 34 | } 35 | 36 | private lazy var playerView: CLPlayerView = { 37 | let view = CLPlayerView(config: config) 38 | view.backButtonTappedHandler = { [weak self] in 39 | guard let self else { return } 40 | self.delegate?.playerDidClickBackButton(self) 41 | } 42 | view.playToEndHandler = { [weak self] in 43 | guard let self else { return } 44 | self.delegate?.playerDidFinishPlaying(self) 45 | } 46 | view.playProgressChanged = { [weak self] value in 47 | guard let self else { return } 48 | self.delegate?.player(self, didUpdateProgress: value) 49 | } 50 | view.playFailed = { [weak self] error in 51 | guard let self else { return } 52 | self.delegate?.player(self, didFailWithError: error) 53 | } 54 | return view 55 | }() 56 | 57 | private var config = CLPlayerConfigure() 58 | 59 | public var totalDuration: TimeInterval { 60 | playerView.totalDuration 61 | } 62 | 63 | public var currentDuration: TimeInterval { 64 | playerView.currentDuration 65 | } 66 | 67 | public var playbackProgress: CGFloat { 68 | playerView.playbackProgress 69 | } 70 | 71 | public var rate: Float { 72 | playerView.rate 73 | } 74 | 75 | public var isFullScreen: Bool { 76 | playerView.contentView.screenState == .fullScreen 77 | } 78 | 79 | public var isPlaying: Bool { 80 | playerView.contentView.playState == .playing 81 | } 82 | 83 | public var isBuffering: Bool { 84 | playerView.contentView.playState == .buffering 85 | } 86 | 87 | public var isFailed: Bool { 88 | playerView.contentView.playState == .failed 89 | } 90 | 91 | public var isPaused: Bool { 92 | playerView.contentView.playState == .pause 93 | } 94 | 95 | public var isEnded: Bool { 96 | playerView.contentView.playState == .ended 97 | } 98 | 99 | public var title: NSMutableAttributedString? { 100 | didSet { 101 | guard let title = title else { return } 102 | playerView.contentView.title = title 103 | } 104 | } 105 | 106 | public var url: URL? { 107 | didSet { 108 | guard let url = url else { return } 109 | playerView.url = url 110 | } 111 | } 112 | 113 | public weak var placeholder: UIView? { 114 | didSet { 115 | playerView.contentView.placeholderView = placeholder 116 | } 117 | } 118 | 119 | public weak var delegate: CLPlayerDelegate? 120 | } 121 | 122 | // MARK: - JmoVxia---布局 123 | 124 | private extension CLPlayer { 125 | func initSubViews() { 126 | insetsLayoutMarginsFromSafeArea = false 127 | distribution = .fill 128 | alignment = .fill 129 | addArrangedSubview(playerView) 130 | } 131 | 132 | func makeConstraints() {} 133 | } 134 | 135 | // MARK: - JmoVxia---override 136 | 137 | extension CLPlayer {} 138 | 139 | // MARK: - JmoVxia---objc 140 | 141 | @objc private extension CLPlayer {} 142 | 143 | // MARK: - JmoVxia---私有方法 144 | 145 | private extension CLPlayer {} 146 | 147 | // MARK: - JmoVxia---公共方法 148 | 149 | public extension CLPlayer { 150 | func play() { 151 | playerView.play() 152 | } 153 | 154 | func pause() { 155 | playerView.pause() 156 | } 157 | 158 | func stop() { 159 | playerView.stop() 160 | } 161 | 162 | func seek(to time: CLPlayerSeek) { 163 | playerView.seek(to: time) 164 | } 165 | } 166 | -------------------------------------------------------------------------------- /CLPlayer/CLPlayerConfigure.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLPlayerConfigure.swift 3 | // CLPlayer 4 | // 5 | // Created by Chen JmoVxia on 2021/12/15. 6 | // 7 | 8 | import AVFoundation 9 | import UIKit 10 | 11 | public struct CLPlayerConfigure { 12 | public struct CLPlayerColor { 13 | /// 顶部工具条背景颜色 14 | public var topToobar: UIColor 15 | /// 底部工具条背景颜色 16 | public var bottomToolbar: UIColor 17 | /// 进度条背景颜色 18 | public var progress: UIColor 19 | /// 缓冲条缓冲进度颜色 20 | public var progressBuffer: UIColor 21 | /// 进度条播放完成颜色 22 | public var progressFinished: UIColor 23 | /// 转子背景颜色 24 | public var loading: UIColor 25 | 26 | public init(topToobar: UIColor = UIColor.black.withAlphaComponent(0.6), 27 | bottomToolbar: UIColor = UIColor.black.withAlphaComponent(0.6), 28 | progress: UIColor = UIColor.white.withAlphaComponent(0.35), 29 | progressBuffer: UIColor = UIColor.white.withAlphaComponent(0.5), 30 | progressFinished: UIColor = UIColor.white, 31 | loading: UIColor = UIColor.white) 32 | { 33 | self.topToobar = topToobar 34 | self.bottomToolbar = bottomToolbar 35 | self.progress = progress 36 | self.progressBuffer = progressBuffer 37 | self.progressFinished = progressFinished 38 | self.loading = loading 39 | } 40 | } 41 | 42 | public struct CLPlayerImage { 43 | /// 返回按钮图片 44 | public var back: UIImage? 45 | /// 更多按钮图片 46 | public var more: UIImage? 47 | /// 播放按钮图片 48 | public var play: UIImage? 49 | /// 暂停按钮图片 50 | public var pause: UIImage? 51 | /// 进度滑块图片 52 | public var thumb: UIImage? 53 | /// 最大化按钮图片 54 | public var max: UIImage? 55 | /// 最小化按钮图片 56 | public var min: UIImage? 57 | 58 | public init(back: UIImage? = CLImageHelper.imageWithName("CLBack"), 59 | more: UIImage? = CLImageHelper.imageWithName("CLMore"), 60 | play: UIImage? = CLImageHelper.imageWithName("CLPlay"), 61 | pause: UIImage? = CLImageHelper.imageWithName("CLPause"), 62 | thumb: UIImage? = CLImageHelper.imageWithName("CLSlider"), 63 | max: UIImage? = CLImageHelper.imageWithName("CLFullscreen"), 64 | min: UIImage? = CLImageHelper.imageWithName("CLSmallscreen")) 65 | { 66 | self.back = back 67 | self.more = more 68 | self.play = play 69 | self.pause = pause 70 | self.thumb = thumb 71 | self.max = max 72 | self.min = min 73 | } 74 | } 75 | 76 | /// 顶部工具条隐藏风格 77 | public enum CLPlayerTopBarHiddenStyle { 78 | /// 小屏和全屏都不隐藏 79 | case never 80 | /// 小屏和全屏都隐藏 81 | case always 82 | /// 小屏隐藏,全屏不隐藏 83 | case onlySmall 84 | } 85 | 86 | /// 自动旋转类型 87 | public enum CLPlayerAutoRotateStyle { 88 | /// 禁止 89 | case none 90 | /// 只支持小屏 91 | case small 92 | /// 只支持全屏 93 | case fullScreen 94 | /// 全部 95 | case all 96 | } 97 | 98 | /// 手势控制类型 99 | public enum CLPlayerGestureInteraction { 100 | /// 禁止 101 | case none 102 | /// 只支持小屏 103 | case small 104 | /// 只支持全屏 105 | case fullScreen 106 | /// 全部 107 | case all 108 | } 109 | 110 | /// 是否隐藏更多面板 111 | public var isHiddenMorePanel = false 112 | /// 初始界面是否显示工具条 113 | public var isHiddenToolbarWhenStart = true 114 | /// 手势控制 115 | public var gestureInteraction = CLPlayerGestureInteraction.fullScreen 116 | /// 自动旋转类型 117 | public var rotateStyle = CLPlayerAutoRotateStyle.all 118 | /// 顶部工具条隐藏风格 119 | public var topBarHiddenStyle = CLPlayerTopBarHiddenStyle.onlySmall 120 | /// 工具条自动消失时间 121 | public var autoFadeOut = 8.0 122 | /// 默认拉伸方式 123 | public var videoGravity = AVLayerVideoGravity.resizeAspect 124 | /// 颜色 125 | public var color = CLPlayerColor() 126 | /// 图片 127 | public var image = CLPlayerImage() 128 | /// 滑块水平偏移量 129 | public var thumbImageOffset = 0.0 130 | /// 滑块点击范围偏移 131 | public var thumbClickableOffset = CGPoint(x: 30, y: 40) 132 | } 133 | -------------------------------------------------------------------------------- /CLPlayer/CLPlayerContentView/CLPlayerContentPanelCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLPlayerContentPanelCell.swift 3 | // CLPlayer 4 | // 5 | // Created by Chen JmoVxia on 2021/12/13. 6 | // 7 | 8 | import SnapKit 9 | import UIKit 10 | 11 | class CLPlayerContentPanelCell: UICollectionViewCell { 12 | override init(frame: CGRect) { 13 | super.init(frame: frame) 14 | initSubViews() 15 | makeConstraints() 16 | } 17 | 18 | @available(*, unavailable) 19 | required init?(coder _: NSCoder) { 20 | fatalError("init(coder:) has not been implemented") 21 | } 22 | 23 | private lazy var titleLabel: UILabel = { 24 | let view = UILabel() 25 | view.textAlignment = .left 26 | view.font = .systemFont(ofSize: 14) 27 | view.textColor = .white 28 | view.adjustsFontSizeToFitWidth = true 29 | return view 30 | }() 31 | 32 | var title: String? { 33 | didSet { 34 | guard title != oldValue else { return } 35 | titleLabel.text = title 36 | } 37 | } 38 | 39 | var isCurrent: Bool = false { 40 | didSet { 41 | guard isCurrent != oldValue else { return } 42 | titleLabel.textColor = isCurrent ? .orange : .white 43 | } 44 | } 45 | } 46 | 47 | private extension CLPlayerContentPanelCell { 48 | func initSubViews() { 49 | contentView.addSubview(titleLabel) 50 | } 51 | 52 | func makeConstraints() { 53 | titleLabel.snp.makeConstraints { make in 54 | make.top.equalTo(10) 55 | make.left.equalTo(15) 56 | make.right.equalTo(-15) 57 | make.bottom.equalTo(-10) 58 | } 59 | } 60 | } 61 | -------------------------------------------------------------------------------- /CLPlayer/CLPlayerContentView/CLPlayerContentPanelHeadView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLPlayerContentPanelHeadView.swift 3 | // CLPlayer 4 | // 5 | // Created by Chen JmoVxia on 2021/12/13. 6 | // 7 | 8 | import SnapKit 9 | import UIKit 10 | 11 | class CLPlayerContentPanelHeadView: UICollectionReusableView { 12 | override init(frame: CGRect) { 13 | super.init(frame: frame) 14 | initSubViews() 15 | makeConstraints() 16 | } 17 | 18 | @available(*, unavailable) 19 | required init?(coder _: NSCoder) { 20 | fatalError("init(coder:) has not been implemented") 21 | } 22 | 23 | private lazy var titleLabel: UILabel = { 24 | let view = UILabel() 25 | view.textAlignment = .left 26 | view.font = .systemFont(ofSize: 14) 27 | view.textColor = .white.withAlphaComponent(0.6) 28 | view.adjustsFontSizeToFitWidth = true 29 | return view 30 | }() 31 | 32 | var title: String? { 33 | didSet { 34 | guard title != oldValue else { return } 35 | titleLabel.text = title 36 | } 37 | } 38 | } 39 | 40 | private extension CLPlayerContentPanelHeadView { 41 | func initSubViews() { 42 | addSubview(titleLabel) 43 | } 44 | 45 | func makeConstraints() { 46 | titleLabel.snp.makeConstraints { make in 47 | make.top.equalTo(10) 48 | make.left.equalTo(15) 49 | make.right.equalTo(-15) 50 | make.bottom.equalTo(-10) 51 | } 52 | } 53 | } 54 | -------------------------------------------------------------------------------- /CLPlayer/CLPlayerContentView/CLPlayerContentViewDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLPlayerContentViewDelegate.swift 3 | // CLPlayer 4 | // 5 | // Created by Chen JmoVxia on 2021/10/28. 6 | // 7 | 8 | import AVFoundation 9 | import Foundation 10 | import UIKit 11 | 12 | protocol CLPlayerContentViewDelegate: AnyObject { 13 | func didClickFailButton(in contentView: CLPlayerContentView) 14 | 15 | func didClickBackButton(in contentView: CLPlayerContentView) 16 | 17 | func contentView(_ contentView: CLPlayerContentView, didClickPlayButton isPlay: Bool) 18 | 19 | func contentView(_ contentView: CLPlayerContentView, didClickFullButton isFull: Bool) 20 | 21 | func contentView(_ contentView: CLPlayerContentView, didChangeRate rate: Float) 22 | 23 | func contentView(_ contentView: CLPlayerContentView, didChangeVideoGravity videoGravity: AVLayerVideoGravity) 24 | 25 | func contentView(_ contentView: CLPlayerContentView, sliderTouchBegan slider: CLSlider) 26 | 27 | func contentView(_ contentView: CLPlayerContentView, sliderValueChanged slider: CLSlider) 28 | 29 | func contentView(_ contentView: CLPlayerContentView, sliderTouchEnded slider: CLSlider) 30 | } 31 | -------------------------------------------------------------------------------- /CLPlayer/CLPlayerContentView/CLRotateAnimationView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLRotateAnimationView.swift 3 | // CLPlayer 4 | // 5 | // Created by Chen JmoVxia on 2021/10/27. 6 | // 7 | 8 | import UIKit 9 | 10 | class CLRotateAnimationViewConfigure: NSObject { 11 | /// 开始起点 12 | var startAngle: CGFloat = -(.pi * 0.5) 13 | /// 开始结束点 14 | var endAngle: CGFloat = .pi * 1.5 15 | /// 动画总时间 16 | var duration: TimeInterval = 2 17 | /// 动画间隔时间 18 | var intervalDuration: TimeInterval = 0.12 19 | /// 小球个数 20 | var number: NSInteger = 5 21 | /// 小球直径 22 | var diameter: CGFloat = 8 23 | /// 小球背景颜色 24 | var backgroundColor: UIColor = .white 25 | 26 | fileprivate class func defaultConfigure() -> CLRotateAnimationViewConfigure { 27 | let configure = CLRotateAnimationViewConfigure() 28 | return configure 29 | } 30 | } 31 | 32 | class CLRotateAnimationView: UIView { 33 | /// 默认配置 34 | private let configure = CLRotateAnimationViewConfigure.defaultConfigure() 35 | /// layer数组 36 | private var layerArray: [CALayer] = Array() 37 | /// 是否开始动画 38 | var isStart: Bool = false 39 | /// 是否暂停 40 | private var isPause: Bool = false 41 | 42 | override init(frame: CGRect) { 43 | super.init(frame: frame) 44 | } 45 | 46 | @available(*, unavailable) 47 | required init?(coder _: NSCoder) { 48 | fatalError("init(coder:) has not been implemented") 49 | } 50 | 51 | private func animation() { 52 | let origin_x: CGFloat = frame.size.width * 0.5 53 | let origin_y: CGFloat = frame.size.height * 0.5 54 | for item in 0 ..< configure.number { 55 | // 创建layer 56 | let scale = CGFloat(configure.number + 1 - item) / CGFloat(configure.number + 1) 57 | let layer = CALayer() 58 | layer.backgroundColor = configure.backgroundColor.cgColor 59 | layer.frame = CGRect(x: -5000, y: -5000, width: scale * configure.diameter, height: scale * configure.diameter) 60 | layer.cornerRadius = scale * configure.diameter * 0.5 61 | // 运动路径 62 | let pathAnimation = CAKeyframeAnimation(keyPath: "position") 63 | pathAnimation.calculationMode = .paced 64 | pathAnimation.fillMode = .forwards 65 | pathAnimation.isRemovedOnCompletion = false 66 | pathAnimation.duration = (configure.duration) - Double((configure.intervalDuration) * Double(configure.number)) 67 | pathAnimation.beginTime = Double(item) * configure.intervalDuration 68 | pathAnimation.timingFunction = CAMediaTimingFunction(name: .easeInEaseOut) 69 | pathAnimation.path = UIBezierPath(arcCenter: CGPoint(x: origin_x, y: origin_y), radius: (frame.size.width - configure.diameter) * 0.5, startAngle: configure.startAngle, endAngle: configure.endAngle, clockwise: true).cgPath 70 | // 动画组,动画组时间长于单个动画时间,会有停留效果 71 | let group = CAAnimationGroup() 72 | group.animations = [pathAnimation] 73 | group.duration = configure.duration 74 | group.isRemovedOnCompletion = false 75 | group.fillMode = .forwards 76 | group.repeatCount = MAXFLOAT 77 | 78 | layer.add(group, forKey: "RotateAnimation") 79 | layerArray.append(layer) 80 | } 81 | } 82 | 83 | /// 更新配置 84 | func updateWithConfigure(_ configureBlock: ((CLRotateAnimationViewConfigure) -> Void)?) { 85 | configureBlock?(configure) 86 | let intervalDuration = CGFloat(CGFloat(configure.duration) / 2.0 / CGFloat(configure.number)) 87 | configure.intervalDuration = min(configure.intervalDuration, TimeInterval(intervalDuration)) 88 | if isStart { 89 | stopAnimation() 90 | startAnimation() 91 | } 92 | } 93 | } 94 | 95 | extension CLRotateAnimationView { 96 | /// 开始动画 97 | func startAnimation() { 98 | if layerArray.isEmpty { 99 | animation() 100 | for item in layerArray { 101 | layer.addSublayer(item) 102 | } 103 | isStart = true 104 | } 105 | } 106 | 107 | /// 停止动画 108 | func stopAnimation() { 109 | for item in layerArray { 110 | item.removeFromSuperlayer() 111 | } 112 | layerArray.removeAll() 113 | isStart = false 114 | } 115 | 116 | /// 暂停动画 117 | func pauseAnimation() { 118 | if isPause { 119 | return 120 | } 121 | isPause = true 122 | // 取出当前时间,转成动画暂停的时间 123 | let pausedTime = layer.convertTime(CACurrentMediaTime(), from: nil) 124 | // 设置动画运行速度为0 125 | layer.speed = 0.0 126 | // 设置动画的时间偏移量,指定时间偏移量的目的是让动画定格在该时间点的位置 127 | layer.timeOffset = pausedTime 128 | } 129 | 130 | /// 恢复动画 131 | func resumeAnimation() { 132 | if !isPause { 133 | return 134 | } 135 | isPause = false 136 | // 获取暂停的时间差 137 | let pausedTime = layer.timeOffset 138 | layer.speed = 1.0 139 | layer.timeOffset = 0.0 140 | layer.beginTime = 0.0 141 | // 用现在的时间减去时间差,就是之前暂停的时间,从之前暂停的时间开始动画 142 | let timeSincePause = layer.convertTime(CACurrentMediaTime(), from: nil) - pausedTime 143 | layer.beginTime = timeSincePause 144 | } 145 | } 146 | -------------------------------------------------------------------------------- /CLPlayer/CLPlayerContentView/CLSlider.swift: -------------------------------------------------------------------------------- 1 | import UIKit 2 | 3 | class CLSlider: UISlider { 4 | private var lastThumbBounds = CGRect.zero 5 | 6 | var thumbClickableOffset = CGPoint(x: 30.0, y: 40.0) 7 | 8 | var verticalSliderOffset: CGFloat = 0.0 9 | 10 | override func trackRect(forBounds bounds: CGRect) -> CGRect { 11 | let newTrackRect = super.trackRect(forBounds: bounds) 12 | return CGRect(origin: newTrackRect.origin, size: CGSize(width: newTrackRect.width, height: 2)) 13 | } 14 | 15 | override func thumbRect(forBounds bounds: CGRect, trackRect rect: CGRect, value: Float) -> CGRect { 16 | var thumbRect = rect 17 | thumbRect.origin.x = thumbRect.minX - verticalSliderOffset 18 | thumbRect.size.width = thumbRect.width + verticalSliderOffset * 2.0 19 | lastThumbBounds = super.thumbRect(forBounds: bounds, trackRect: thumbRect, value: value) 20 | return lastThumbBounds 21 | } 22 | 23 | override func hitTest(_ point: CGPoint, with event: UIEvent?) -> UIView? { 24 | let view = super.hitTest(point, with: event) 25 | guard view != self else { return view } 26 | guard point.x >= 0, point.x < bounds.width else { return view } 27 | guard point.y >= -thumbClickableOffset.x * 0.5, point.y < lastThumbBounds.height + thumbClickableOffset.y else { return view } 28 | return self 29 | } 30 | 31 | override func point(inside point: CGPoint, with event: UIEvent?) -> Bool { 32 | let isInside = super.point(inside: point, with: event) 33 | guard !isInside else { return isInside } 34 | guard point.x >= lastThumbBounds.minX - thumbClickableOffset.x, point.x <= lastThumbBounds.maxX + thumbClickableOffset.x else { return isInside } 35 | guard point.y >= -thumbClickableOffset.y, point.y < lastThumbBounds.height + thumbClickableOffset.y else { return isInside } 36 | return true 37 | } 38 | } 39 | -------------------------------------------------------------------------------- /CLPlayer/CLPlayerDelegate.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLPlayerDelegate.swift 3 | // CLPlayer 4 | // 5 | // Created by Chen JmoVxia on 2021/12/15. 6 | // 7 | 8 | import UIKit 9 | 10 | public protocol CLPlayerDelegate: AnyObject { 11 | /// 点击顶部工具条返回按钮 12 | func playerDidClickBackButton(_ player: CLPlayer) 13 | /// 视频播放结束 14 | func playerDidFinishPlaying(_ player: CLPlayer) 15 | /// 播放器播放进度变化 16 | func player(_ player: CLPlayer, didUpdateProgress progress: CGFloat) 17 | /// 播放器播放失败 18 | func player(_ player: CLPlayer, didFailWithError error: Error?) 19 | } 20 | 21 | public extension CLPlayerDelegate { 22 | func playerDidClickBackButton(_ player: CLPlayer) {} 23 | func playerDidFinishPlaying(_ player: CLPlayer) {} 24 | func player(_ player: CLPlayer, didUpdateProgress progress: CGFloat) {} 25 | func player(_ player: CLPlayer, didFailWithError error: Error?) {} 26 | } 27 | -------------------------------------------------------------------------------- /CLPlayer/CLPlayerView.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLPlayerView.swift 3 | // CLPlayer 4 | // 5 | // Created by Chen JmoVxia on 2021/10/26. 6 | // 7 | 8 | import AVFoundation 9 | import SnapKit 10 | import UIKit 11 | 12 | extension CLPlayerView { 13 | enum CLWaitReadyToPlayState { 14 | case nomal 15 | case pause 16 | case play 17 | } 18 | } 19 | 20 | class CLPlayerView: UIView { 21 | init(config: CLPlayerConfigure) { 22 | super.init(frame: .zero) 23 | self.config = config 24 | initSubViews() 25 | makeConstraints() 26 | (layer as? AVPlayerLayer)?.videoGravity = self.config.videoGravity 27 | } 28 | 29 | @available(*, unavailable) 30 | required init?(coder _: NSCoder) { 31 | fatalError("init(coder:) has not been implemented") 32 | } 33 | 34 | deinit { 35 | NotificationCenter.default.removeObserver(self, name: UIDevice.orientationDidChangeNotification, object: nil) 36 | NotificationCenter.default.removeObserver(self, name: UIApplication.willResignActiveNotification, object: nil) 37 | NotificationCenter.default.removeObserver(self, name: UIApplication.didBecomeActiveNotification, object: nil) 38 | } 39 | 40 | private(set) lazy var contentView: CLPlayerContentView = { 41 | let view = CLPlayerContentView(config: config) 42 | view.delegate = self 43 | return view 44 | }() 45 | 46 | private let keyWindow: UIWindow? = { 47 | if #available(iOS 13.0, *) { 48 | return UIApplication.shared.windows.filter { $0.isKeyWindow }.last 49 | } else { 50 | return UIApplication.shared.keyWindow 51 | } 52 | }() 53 | 54 | private var seekTime: CLPlayer.CLPlayerSeek? = nil 55 | 56 | private var waitReadyToPlayState: CLWaitReadyToPlayState = .nomal 57 | 58 | private var sliderTimer: CLGCDTimer? 59 | 60 | private var bufferTimer: CLGCDTimer? 61 | 62 | private var config = CLPlayerConfigure() 63 | 64 | private var animationTransitioning: CLAnimationTransitioning? 65 | 66 | private var fullScreenController: CLFullScreenController? 67 | 68 | private var statusObserve: NSKeyValueObservation? 69 | 70 | private var loadedTimeRangesObserve: NSKeyValueObservation? 71 | 72 | private var playbackBufferEmptyObserve: NSKeyValueObservation? 73 | 74 | private var isUserPause: Bool = false 75 | 76 | private var isEnterBackground: Bool = false 77 | 78 | private var player: AVPlayer? 79 | 80 | private var playerItem: AVPlayerItem? { 81 | didSet { 82 | guard playerItem != oldValue else { return } 83 | if let oldPlayerItem = oldValue { 84 | NotificationCenter.default.removeObserver(self, name: .AVPlayerItemDidPlayToEndTime, object: oldPlayerItem) 85 | } 86 | guard let playerItem = playerItem else { return } 87 | NotificationCenter.default.addObserver(self, selector: #selector(didPlaybackEnds), name: .AVPlayerItemDidPlayToEndTime, object: playerItem) 88 | 89 | statusObserve = playerItem.observe(\.status, options: [.new]) { [weak self] _, _ in 90 | self?.observeStatusAction() 91 | } 92 | } 93 | } 94 | 95 | private(set) var totalDuration: TimeInterval = .zero { 96 | didSet { 97 | guard totalDuration != oldValue else { return } 98 | contentView.setTotalDuration(totalDuration) 99 | } 100 | } 101 | 102 | private(set) var currentDuration: TimeInterval = .zero { 103 | didSet { 104 | guard currentDuration != oldValue else { return } 105 | contentView.setCurrentDuration(min(currentDuration, totalDuration)) 106 | } 107 | } 108 | 109 | private(set) var playbackProgress: CGFloat = .zero { 110 | didSet { 111 | guard playbackProgress != oldValue else { return } 112 | contentView.setSliderProgress(Float(playbackProgress), animated: false) 113 | let oldIntValue = Int(oldValue * 100) 114 | let intValue = Int(playbackProgress * 100) 115 | if intValue != oldIntValue { 116 | DispatchQueue.main.async { 117 | self.playProgressChanged?(CGFloat(intValue) / 100) 118 | } 119 | } 120 | } 121 | } 122 | 123 | private(set) var rate: Float = 1.0 { 124 | didSet { 125 | guard rate != oldValue else { return } 126 | play() 127 | } 128 | } 129 | 130 | var isFullScreen: Bool { 131 | return contentView.screenState == .fullScreen 132 | } 133 | 134 | var isPlaying: Bool { 135 | return contentView.playState == .playing 136 | } 137 | 138 | var isBuffering: Bool { 139 | return contentView.playState == .buffering 140 | } 141 | 142 | var isFailed: Bool { 143 | return contentView.playState == .failed 144 | } 145 | 146 | var isPaused: Bool { 147 | return contentView.playState == .pause 148 | } 149 | 150 | var isEnded: Bool { 151 | return contentView.playState == .ended 152 | } 153 | 154 | var title: NSMutableAttributedString? { 155 | didSet { 156 | guard let title = title else { return } 157 | contentView.title = title 158 | } 159 | } 160 | 161 | var url: URL? { 162 | didSet { 163 | guard let url = url else { return } 164 | stop() 165 | let session = AVAudioSession.sharedInstance() 166 | do { 167 | try session.setCategory(.playback) 168 | try session.setActive(true) 169 | } catch { 170 | print("set session error:\(error)") 171 | } 172 | playerItem = AVPlayerItem(asset: .init(url: url)) 173 | player = AVPlayer(playerItem: playerItem) 174 | (layer as? AVPlayerLayer)?.player = player 175 | } 176 | } 177 | 178 | weak var placeholder: UIView? { 179 | didSet { 180 | contentView.placeholderView = placeholder 181 | } 182 | } 183 | 184 | var backButtonTappedHandler: (() -> Void)? 185 | 186 | var playToEndHandler: (() -> Void)? 187 | 188 | var playProgressChanged: ((CGFloat) -> Void)? 189 | 190 | var playFailed: ((Error?) -> Void)? 191 | } 192 | 193 | // MARK: - JmoVxia---override 194 | 195 | extension CLPlayerView { 196 | override class var layerClass: AnyClass { 197 | return AVPlayerLayer.classForCoder() 198 | } 199 | } 200 | 201 | // MARK: - JmoVxia---布局 202 | 203 | private extension CLPlayerView { 204 | func initSubViews() { 205 | backgroundColor = .black 206 | addSubview(contentView) 207 | NotificationCenter.default.addObserver(self, selector: #selector(appDidEnterBackground), name: UIApplication.willResignActiveNotification, object: nil) 208 | NotificationCenter.default.addObserver(self, selector: #selector(appDidEnterPlayground), name: UIApplication.didBecomeActiveNotification, object: nil) 209 | if !UIDevice.current.isGeneratingDeviceOrientationNotifications { 210 | UIDevice.current.beginGeneratingDeviceOrientationNotifications() 211 | } 212 | NotificationCenter.default.addObserver(self, selector: #selector(deviceOrientationDidChange), name: UIDevice.orientationDidChangeNotification, object: nil) 213 | } 214 | 215 | func makeConstraints() { 216 | contentView.snp.makeConstraints { make in 217 | make.edges.equalToSuperview() 218 | } 219 | } 220 | } 221 | 222 | // MARK: - JmoVxia---objc 223 | 224 | @objc private extension CLPlayerView { 225 | func didPlaybackEnds() { 226 | currentDuration = totalDuration 227 | playbackProgress = 1.0 228 | contentView.playState = .ended 229 | sliderTimer?.pause() 230 | DispatchQueue.main.async { 231 | self.playToEndHandler?() 232 | } 233 | } 234 | 235 | func deviceOrientationDidChange() { 236 | guard config.rotateStyle != .none else { return } 237 | if config.rotateStyle == .small, isFullScreen { return } 238 | if config.rotateStyle == .fullScreen, !isFullScreen { return } 239 | 240 | switch UIDevice.current.orientation { 241 | case .portrait: 242 | dismiss() 243 | case .landscapeLeft: 244 | presentWithOrientation(.left) 245 | case .landscapeRight: 246 | presentWithOrientation(.right) 247 | default: 248 | break 249 | } 250 | } 251 | 252 | func appDidEnterBackground() { 253 | isEnterBackground = true 254 | pause() 255 | } 256 | 257 | func appDidEnterPlayground() { 258 | isEnterBackground = false 259 | guard contentView.playState != .ended else { return } 260 | play() 261 | } 262 | } 263 | 264 | // MARK: - JmoVxia---observe 265 | 266 | private extension CLPlayerView { 267 | func observeStatusAction() { 268 | guard let playerItem = playerItem else { return } 269 | if playerItem.status == .readyToPlay { 270 | contentView.playState = .readyToPlay 271 | totalDuration = TimeInterval(playerItem.duration.value) / TimeInterval(playerItem.duration.timescale) 272 | 273 | sliderTimer = CLGCDTimer(interval: 0.1) 274 | sliderTimer?.run { [weak self] _ in 275 | self?.sliderTimerAction() 276 | } 277 | 278 | loadedTimeRangesObserve = playerItem.observe(\.loadedTimeRanges, options: [.new]) { [weak self] _, _ in 279 | self?.observeLoadedTimeRangesAction() 280 | } 281 | 282 | playbackBufferEmptyObserve = playerItem.observe(\.isPlaybackBufferEmpty, options: [.new]) { [weak self] _, _ in 283 | self?.observePlaybackBufferEmptyAction() 284 | } 285 | if let seekTime { 286 | player?.seek(to: seekTime.time, toleranceBefore: seekTime.toleranceBefore, toleranceAfter: seekTime.toleranceAfter) 287 | self.seekTime = nil 288 | } 289 | 290 | switch waitReadyToPlayState { 291 | case .nomal: 292 | break 293 | case .pause: 294 | pause() 295 | case .play: 296 | play() 297 | } 298 | } else if playerItem.status == .failed { 299 | contentView.playState = .failed 300 | DispatchQueue.main.async { 301 | self.playFailed?(playerItem.error) 302 | } 303 | } 304 | } 305 | 306 | func observeLoadedTimeRangesAction() { 307 | guard let timeInterval = availableDuration() else { return } 308 | guard let duration = playerItem?.duration else { return } 309 | let totalDuration = TimeInterval(CMTimeGetSeconds(duration)) 310 | contentView.setProgress(Float(timeInterval / totalDuration), animated: false) 311 | } 312 | 313 | func observePlaybackBufferEmptyAction() { 314 | guard playerItem?.isPlaybackBufferEmpty ?? false else { return } 315 | bufferingSomeSecond() 316 | } 317 | } 318 | 319 | private extension CLPlayerView { 320 | func availableDuration() -> TimeInterval? { 321 | guard let timeRange = playerItem?.loadedTimeRanges.first?.timeRangeValue else { return nil } 322 | let startSeconds = CMTimeGetSeconds(timeRange.start) 323 | let durationSeconds = CMTimeGetSeconds(timeRange.duration) 324 | return .init(startSeconds + durationSeconds) 325 | } 326 | 327 | func bufferingSomeSecond() { 328 | guard playerItem?.status == .readyToPlay else { return } 329 | guard contentView.playState != .failed else { return } 330 | 331 | player?.pause() 332 | sliderTimer?.pause() 333 | 334 | contentView.playState = .buffering 335 | bufferTimer = CLGCDTimer(interval: 3.0, initialDelay: 3.0) 336 | bufferTimer?.run { [weak self] _ in 337 | guard let playerItem = self?.playerItem else { return } 338 | self?.bufferTimer = nil 339 | if playerItem.isPlaybackLikelyToKeepUp { 340 | self?.play() 341 | } else { 342 | self?.bufferingSomeSecond() 343 | } 344 | } 345 | } 346 | 347 | func sliderTimerAction() { 348 | guard let playerItem = playerItem else { return } 349 | guard playerItem.duration.timescale != .zero else { return } 350 | 351 | currentDuration = CMTimeGetSeconds(playerItem.currentTime()) 352 | playbackProgress = currentDuration / totalDuration 353 | } 354 | } 355 | 356 | // MARK: - JmoVxia---Screen 357 | 358 | private extension CLPlayerView { 359 | func dismiss() { 360 | guard Thread.isMainThread else { return DispatchQueue.main.async { self.dismiss() } } 361 | guard contentView.screenState == .fullScreen else { return } 362 | guard let controller = fullScreenController else { return } 363 | contentView.screenState = .animating 364 | controller.dismiss(animated: true, completion: { 365 | self.contentView.screenState = .small 366 | self.fullScreenController = nil 367 | UIViewController.attemptRotationToDeviceOrientation() 368 | }) 369 | } 370 | 371 | func presentWithOrientation(_ orientation: CLAnimationTransitioning.AnimationOrientation) { 372 | guard Thread.isMainThread else { return DispatchQueue.main.async { self.presentWithOrientation(orientation) } } 373 | guard superview != nil else { return } 374 | guard fullScreenController == nil else { return } 375 | guard contentView.screenState == .small else { return } 376 | guard let rootViewController = keyWindow?.rootViewController else { return } 377 | contentView.screenState = .animating 378 | 379 | animationTransitioning = CLAnimationTransitioning(playerView: self, animationOrientation: orientation) 380 | 381 | fullScreenController = orientation == .right ? CLFullScreenLeftController() : CLFullScreenRightController() 382 | fullScreenController?.transitioningDelegate = self 383 | fullScreenController?.modalPresentationStyle = .fullScreen 384 | rootViewController.present(fullScreenController!, animated: true, completion: { 385 | self.contentView.screenState = .fullScreen 386 | UIViewController.attemptRotationToDeviceOrientation() 387 | }) 388 | } 389 | } 390 | 391 | // MARK: - JmoVxia---公共方法 392 | 393 | extension CLPlayerView { 394 | func play() { 395 | guard !isEnterBackground else { return } 396 | guard !isUserPause else { return } 397 | guard let playerItem = playerItem else { return } 398 | guard playerItem.status == .readyToPlay else { 399 | contentView.playState = .waiting 400 | waitReadyToPlayState = .play 401 | return 402 | } 403 | guard playerItem.isPlaybackLikelyToKeepUp else { 404 | bufferingSomeSecond() 405 | return 406 | } 407 | if contentView.playState == .ended { 408 | player?.seek(to: CMTimeMake(value: 0, timescale: 1), toleranceBefore: .zero, toleranceAfter: .zero) 409 | } 410 | contentView.playState = .playing 411 | player?.play() 412 | player?.rate = rate 413 | sliderTimer?.resume() 414 | waitReadyToPlayState = .nomal 415 | bufferTimer = nil 416 | } 417 | 418 | func pause() { 419 | guard playerItem?.status == .readyToPlay else { 420 | waitReadyToPlayState = .pause 421 | return 422 | } 423 | contentView.playState = .pause 424 | player?.pause() 425 | sliderTimer?.pause() 426 | bufferTimer = nil 427 | waitReadyToPlayState = .nomal 428 | } 429 | 430 | func stop() { 431 | statusObserve?.invalidate() 432 | loadedTimeRangesObserve?.invalidate() 433 | playbackBufferEmptyObserve?.invalidate() 434 | 435 | statusObserve = nil 436 | loadedTimeRangesObserve = nil 437 | playbackBufferEmptyObserve = nil 438 | 439 | playerItem = nil 440 | player = nil 441 | 442 | isUserPause = false 443 | 444 | waitReadyToPlayState = .nomal 445 | 446 | contentView.playState = .unknow 447 | contentView.setProgress(0, animated: false) 448 | playbackProgress = 0 449 | totalDuration = 0 450 | currentDuration = 0 451 | sliderTimer = nil 452 | seekTime = nil 453 | } 454 | 455 | func seek(to time: CLPlayer.CLPlayerSeek) { 456 | if contentView.playState.canFastForward { 457 | player?.seek(to: time.time, toleranceBefore: time.toleranceBefore, toleranceAfter: time.toleranceAfter) 458 | } else { 459 | seekTime = time 460 | } 461 | } 462 | } 463 | 464 | // MARK: - JmoVxia---UIViewControllerTransitioningDelegate 465 | 466 | extension CLPlayerView: UIViewControllerTransitioningDelegate { 467 | func animationController(forPresented _: UIViewController, presenting _: UIViewController, source _: UIViewController) -> UIViewControllerAnimatedTransitioning? { 468 | animationTransitioning?.animationType = .present 469 | return animationTransitioning 470 | } 471 | 472 | func animationController(forDismissed _: UIViewController) -> UIViewControllerAnimatedTransitioning? { 473 | animationTransitioning?.animationType = .dismiss 474 | return animationTransitioning 475 | } 476 | } 477 | 478 | // MARK: - JmoVxia---CLPlayerContentViewDelegate 479 | 480 | extension CLPlayerView: CLPlayerContentViewDelegate { 481 | func contentView(_ contentView: CLPlayerContentView, didClickPlayButton isPlay: Bool) { 482 | isUserPause = isPlay 483 | isPlay ? pause() : play() 484 | } 485 | 486 | func contentView(_ contentView: CLPlayerContentView, didClickFullButton isFull: Bool) { 487 | isFull ? dismiss() : presentWithOrientation(.fullRight) 488 | } 489 | 490 | func contentView(_ contentView: CLPlayerContentView, didChangeRate rate: Float) { 491 | self.rate = rate 492 | } 493 | 494 | func contentView(_ contentView: CLPlayerContentView, didChangeVideoGravity videoGravity: AVLayerVideoGravity) { 495 | (layer as? AVPlayerLayer)?.videoGravity = videoGravity 496 | } 497 | 498 | func contentView(_ contentView: CLPlayerContentView, sliderTouchBegan slider: CLSlider) { 499 | pause() 500 | } 501 | 502 | func contentView(_ contentView: CLPlayerContentView, sliderValueChanged slider: CLSlider) { 503 | currentDuration = totalDuration * TimeInterval(slider.value) 504 | let dragedCMTime = CMTimeMake(value: Int64(ceil(currentDuration)), timescale: 1) 505 | player?.seek(to: dragedCMTime, toleranceBefore: .zero, toleranceAfter: .zero) 506 | } 507 | 508 | func contentView(_ contentView: CLPlayerContentView, sliderTouchEnded slider: CLSlider) { 509 | guard let playerItem = playerItem else { return } 510 | if slider.value == 1 { 511 | didPlaybackEnds() 512 | } else if playerItem.isPlaybackLikelyToKeepUp { 513 | play() 514 | } else { 515 | bufferingSomeSecond() 516 | } 517 | } 518 | 519 | func didClickFailButton(in _: CLPlayerContentView) { 520 | guard let url = url else { return } 521 | self.url = url 522 | } 523 | 524 | func didClickBackButton(in contentView: CLPlayerContentView) { 525 | guard contentView.screenState == .fullScreen else { return } 526 | DispatchQueue.main.async { 527 | self.dismiss() 528 | self.backButtonTappedHandler?() 529 | } 530 | } 531 | } 532 | -------------------------------------------------------------------------------- /CLStackViewController/CLStackViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLStackViewController.swift 3 | // CLPlayer 4 | // 5 | // Created by Chen JmoVxia on 2021/12/14. 6 | // 7 | 8 | import SnapKit 9 | import UIKit 10 | 11 | class CLStackViewController: CLController { 12 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { 13 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 14 | } 15 | 16 | @available(*, unavailable) 17 | required init?(coder _: NSCoder) { 18 | fatalError("init(coder:) has not been implemented") 19 | } 20 | 21 | private lazy var placeholderView: CLPlaceholderView = { 22 | let view = CLPlaceholderView() 23 | view.addTarget(self, action: #selector(playAction), for: .touchUpInside) 24 | return view 25 | }() 26 | 27 | private lazy var player: CLPlayer = { 28 | let view = CLPlayer() 29 | view.placeholder = placeholderView 30 | return view 31 | }() 32 | 33 | private lazy var changeButton: UIButton = { 34 | let view = UIButton() 35 | view.backgroundColor = UIColor.orange.withAlphaComponent(0.3) 36 | view.titleLabel?.font = .systemFont(ofSize: 18) 37 | view.setTitle("切换视频", for: .normal) 38 | view.setTitle("切换视频", for: .selected) 39 | view.setTitle("切换视频", for: .highlighted) 40 | view.setTitleColor(.orange, for: .normal) 41 | view.setTitleColor(.orange, for: .selected) 42 | view.setTitleColor(.orange, for: .highlighted) 43 | view.addTarget(self, action: #selector(changeAction), for: .touchUpInside) 44 | return view 45 | }() 46 | 47 | private lazy var mainStackView: UIStackView = { 48 | let view = UIStackView() 49 | view.axis = .vertical 50 | view.distribution = .fill 51 | view.alignment = .fill 52 | view.insetsLayoutMarginsFromSafeArea = false 53 | view.isLayoutMarginsRelativeArrangement = true 54 | view.layoutMargins = .zero 55 | view.spacing = 40 56 | return view 57 | }() 58 | 59 | deinit { 60 | print("CLStackViewController deinit") 61 | } 62 | } 63 | 64 | // MARK: - JmoVxia---生命周期 65 | 66 | extension CLStackViewController { 67 | override func viewWillAppear(_ animated: Bool) { 68 | super.viewWillAppear(animated) 69 | } 70 | 71 | override func viewDidAppear(_ animated: Bool) { 72 | super.viewDidAppear(animated) 73 | } 74 | 75 | override func viewDidLoad() { 76 | super.viewDidLoad() 77 | initUI() 78 | makeConstraints() 79 | initData() 80 | } 81 | 82 | override func viewWillDisappear(_ animated: Bool) { 83 | super.viewWillDisappear(animated) 84 | } 85 | 86 | override func viewDidDisappear(_ animated: Bool) { 87 | super.viewDidDisappear(animated) 88 | } 89 | 90 | override func viewDidLayoutSubviews() { 91 | super.viewDidLayoutSubviews() 92 | } 93 | } 94 | 95 | // MARK: - JmoVxia---布局 96 | 97 | private extension CLStackViewController { 98 | func initUI() { 99 | updateTitleLabel { $0.text = "UIView" } 100 | view.addSubview(mainStackView) 101 | mainStackView.addArrangedSubview(player) 102 | mainStackView.addArrangedSubview(changeButton) 103 | } 104 | 105 | func makeConstraints() { 106 | mainStackView.snp.makeConstraints { make in 107 | make.left.right.equalToSuperview() 108 | make.center.equalToSuperview() 109 | make.height.equalTo(view.bounds.width / (16.0 / 9.0) + 50) 110 | } 111 | changeButton.snp.makeConstraints { make in 112 | make.height.equalTo(50) 113 | } 114 | } 115 | } 116 | 117 | // MARK: - JmoVxia---数据 118 | 119 | private extension CLStackViewController { 120 | func initData() { 121 | player.title = NSMutableAttributedString("Apple", attributes: { $0 122 | .font(.systemFont(ofSize: 16)) 123 | .foregroundColor(.white) 124 | .alignment(.center) 125 | }) 126 | } 127 | } 128 | 129 | extension CLStackViewController { 130 | override var shouldAutorotate: Bool { 131 | return false 132 | } 133 | 134 | // 支持哪些屏幕方向 135 | override var supportedInterfaceOrientations: UIInterfaceOrientationMask { 136 | return .portrait 137 | } 138 | } 139 | 140 | // MARK: - JmoVxia---objc 141 | 142 | @objc private extension CLStackViewController { 143 | func playAction() { 144 | placeholderView.imageView.image = UIImage(named: "placeholder") 145 | 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") 146 | player.play() 147 | } 148 | 149 | func changeAction() { 150 | placeholderView.imageView.image = UIImage(named: "placeholder1") 151 | player.title = NSMutableAttributedString("这是一个标题", attributes: { $0 152 | .font(.systemFont(ofSize: 16)) 153 | .foregroundColor(.white) 154 | .alignment(.left) 155 | }) 156 | player.url = URL(string: "http://vjs.zencdn.net/v/oceans.mp4") 157 | player.play() 158 | } 159 | } 160 | -------------------------------------------------------------------------------- /CLTableView/CLCellItemProtocol/CLCellItemProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLCellItemProtocol.swift 3 | // CKD 4 | // 5 | // Created by JmoVxia on 2020/3/26. 6 | // Copyright © 2020 JmoVxia. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol CLCellItemProtocol { 12 | /// 加载cell 13 | var cellForRowCallback: ((IndexPath) -> Void)? { get set } 14 | /// 将要显示cell 15 | var willDisplayCallback: ((IndexPath) -> Void)? { get set } 16 | /// 点击cell回调 17 | var didSelectCellCallback: ((IndexPath) -> Void)? { get set } 18 | /// 绑定cell 19 | func bindCell() -> UITableViewCell.Type 20 | /// 创建cell 21 | func dequeueReusableCell(tableView: UITableView, indexPath: IndexPath) -> UITableViewCell 22 | /// 高度 23 | func cellHeight() -> CGFloat 24 | } 25 | 26 | extension CLCellItemProtocol { 27 | var cellForRowCallback: ((IndexPath) -> Void)? { 28 | get { 29 | return nil 30 | } 31 | set {} 32 | } 33 | 34 | var willDisplayCallback: ((IndexPath) -> Void)? { 35 | get { 36 | return nil 37 | } 38 | set {} 39 | } 40 | 41 | var didSelectCellCallback: ((IndexPath) -> Void)? { 42 | get { 43 | return nil 44 | } 45 | set {} 46 | } 47 | 48 | func dequeueReusableCell(tableView: UITableView, indexPath _: IndexPath) -> UITableViewCell { 49 | let cellClass = bindCell() 50 | let identifier = String(describing: cellClass) 51 | var tableViewCell: UITableViewCell! 52 | if let cell = tableView.dequeueReusableCell(withIdentifier: identifier) { 53 | tableViewCell = cell 54 | } else { 55 | tableViewCell = cellClass.init(style: .default, reuseIdentifier: identifier) 56 | } 57 | (tableViewCell as? CLCellProtocol)?.setItem(self) 58 | return tableViewCell 59 | } 60 | 61 | /// 高度 62 | func cellHeight() -> CGFloat { 63 | return UITableView.automaticDimension 64 | } 65 | } 66 | -------------------------------------------------------------------------------- /CLTableView/CLCellProtocol/CLCellProtocol.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLCellProtocol.swift 3 | // CKD 4 | // 5 | // Created by JmoVxia on 2020/3/26. 6 | // Copyright © 2020 JmoVxia. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | protocol CLCellProtocol { 12 | /// 设置item 13 | func setItem(_ item: CLCellItemProtocol) 14 | } 15 | -------------------------------------------------------------------------------- /CLTableView/CLTableViewHepler/CLTableViewHepler.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLTableViewHepler.swift 3 | // CKD 4 | // 5 | // Created by Chen JmoVxia on 2020/9/17. 6 | // Copyright © 2020 JmoVxia. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | class CLTableViewHepler: NSObject { 12 | var dataSource = [CLCellItemProtocol]() 13 | private weak var delegate: UITableViewDelegate? 14 | init(delegate: UITableViewDelegate? = nil) { 15 | self.delegate = delegate 16 | super.init() 17 | } 18 | 19 | override func forwardingTarget(for aSelector: Selector!) -> Any? { 20 | if super.responds(to: aSelector) { 21 | return self 22 | } else if let delegate = delegate, delegate.responds(to: aSelector) { 23 | return delegate 24 | } 25 | return self 26 | } 27 | 28 | override func responds(to aSelector: Selector!) -> Bool { 29 | if let delegate = delegate { 30 | return super.responds(to: aSelector) || delegate.responds(to: aSelector) 31 | } 32 | return super.responds(to: aSelector) 33 | } 34 | } 35 | 36 | // MARK: - JmoVxia---UITableViewDelegate 37 | 38 | extension CLTableViewHepler: UITableViewDelegate { 39 | func tableView(_ tableView: UITableView, didSelectRowAt indexPath: IndexPath) { 40 | if let delegate = delegate, delegate.responds(to: #selector(tableView(_:didSelectRowAt:))) { 41 | delegate.tableView!(tableView, didSelectRowAt: indexPath) 42 | } else { 43 | dataSource[indexPath.row].didSelectCellCallback?(indexPath) 44 | } 45 | } 46 | 47 | func tableView(_ tableView: UITableView, heightForRowAt indexPath: IndexPath) -> CGFloat { 48 | if let delegate = delegate, delegate.responds(to: #selector(tableView(_:heightForRowAt:))) { 49 | return delegate.tableView!(tableView, heightForRowAt: indexPath) 50 | } else { 51 | return dataSource[indexPath.row].cellHeight() 52 | } 53 | } 54 | 55 | func tableView(_ tableView: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { 56 | if let delegate = delegate, delegate.responds(to: #selector(tableView(_:willDisplay:forRowAt:))) { 57 | delegate.tableView!(tableView, willDisplay: cell, forRowAt: indexPath) 58 | } else { 59 | dataSource[indexPath.row].willDisplayCallback?(indexPath) 60 | } 61 | } 62 | } 63 | 64 | // MARK: - JmoVxia---UITableViewDataSource 65 | 66 | extension CLTableViewHepler: UITableViewDataSource { 67 | func tableView(_: UITableView, numberOfRowsInSection _: Int) -> Int { 68 | return dataSource.count 69 | } 70 | 71 | func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell { 72 | let item = dataSource[indexPath.row] 73 | item.cellForRowCallback?(indexPath) 74 | return item.dequeueReusableCell(tableView: tableView, indexPath: indexPath) 75 | } 76 | } 77 | -------------------------------------------------------------------------------- /CLTableViewController/Controller/CLTableViewController.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLTableViewController.swift 3 | // CLPlayer 4 | // 5 | // Created by Chen JmoVxia on 2021/10/26. 6 | // 7 | 8 | import SnapKit 9 | import UIKit 10 | 11 | // MARK: - JmoVxia---枚举 12 | 13 | extension CLTableViewController {} 14 | 15 | // MARK: - JmoVxia---类-属性 16 | 17 | class CLTableViewController: CLController { 18 | override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) { 19 | super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil) 20 | } 21 | 22 | @available(*, unavailable) 23 | required init?(coder _: NSCoder) { 24 | fatalError("init(coder:) has not been implemented") 25 | } 26 | 27 | deinit {} 28 | 29 | private lazy var tableViewHepler: CLTableViewHepler = { 30 | let hepler = CLTableViewHepler(delegate: self) 31 | return hepler 32 | }() 33 | 34 | private lazy var tableView: UITableView = { 35 | let view = UITableView(frame: .zero, style: .plain) 36 | view.showsVerticalScrollIndicator = false 37 | view.showsHorizontalScrollIndicator = false 38 | view.backgroundColor = .white 39 | view.separatorStyle = .none 40 | view.dataSource = tableViewHepler 41 | view.delegate = tableViewHepler 42 | if #available(iOS 11.0, *) { 43 | view.contentInsetAdjustmentBehavior = .never 44 | } 45 | return view 46 | }() 47 | 48 | private var player: CLPlayer? 49 | } 50 | 51 | // MARK: - JmoVxia---生命周期 52 | 53 | extension CLTableViewController { 54 | override func viewWillAppear(_ animated: Bool) { 55 | super.viewWillAppear(animated) 56 | } 57 | 58 | override func viewDidAppear(_ animated: Bool) { 59 | super.viewDidAppear(animated) 60 | } 61 | 62 | override func viewDidLoad() { 63 | super.viewDidLoad() 64 | initUI() 65 | makeConstraints() 66 | initData() 67 | } 68 | 69 | override func viewWillDisappear(_ animated: Bool) { 70 | super.viewWillDisappear(animated) 71 | } 72 | 73 | override func viewDidDisappear(_ animated: Bool) { 74 | super.viewDidDisappear(animated) 75 | } 76 | 77 | override func viewDidLayoutSubviews() { 78 | super.viewDidLayoutSubviews() 79 | } 80 | } 81 | 82 | // MARK: - JmoVxia---布局 83 | 84 | private extension CLTableViewController { 85 | func initUI() { 86 | updateTitleLabel { $0.text = "TableView" } 87 | view.addSubview(tableView) 88 | } 89 | 90 | func makeConstraints() { 91 | tableView.snp.makeConstraints { make in 92 | make.left.right.bottom.equalToSuperview() 93 | if #available(iOS 11.0, *) { 94 | make.top.equalTo(view.safeAreaLayoutGuide.snp.top) 95 | } else { 96 | make.top.equalTo(topLayoutGuide.snp.bottom) 97 | } 98 | } 99 | } 100 | } 101 | 102 | // MARK: - JmoVxia---数据 103 | 104 | private extension CLTableViewController { 105 | func initData() { 106 | let array = [ 107 | "https://www.apple.com/105/media/cn/mac/family/2018/46c4b917_abfd_45a3_9b51_4e3054191797/films/bruce/mac-bruce-tpl-cn-2018_1280x720h.mp4", 108 | "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", 109 | "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", 110 | "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", 111 | "http://mirror.aarnet.edu.au/pub/TED-talks/911Mothers_2010W-480p.mp4", 112 | "https://media.w3.org/2010/05/sintel/trailer.mp4", 113 | "https://cdn.cnbj1.fds.api.mi-img.com/mi-mall/7194236f31b2e1e3da0fe06cfed4ba2b.mp4", 114 | "http://clips.vorwaerts-gmbh.de/big_buck_bunny.mp4", 115 | "http://vjs.zencdn.net/v/oceans.mp4", 116 | "https://media.w3.org/2010/05/sintel/trailer.mp4", 117 | "http://mirror.aarnet.edu.au/pub/TED-talks/911Mothers_2010W-480p.mp4", 118 | "https://sample-videos.com/video123/mp4/480/big_buck_bunny_480p_2mb.mp4", 119 | ] 120 | for string in array { 121 | let item = CLTableViewItem() 122 | item.title = NSMutableAttributedString("这是一个标题", attributes: { $0 123 | .font(.systemFont(ofSize: 16)) 124 | .foregroundColor(.orange) 125 | .alignment(.center) 126 | }) 127 | item.url = URL(string: string) 128 | item.didSelectCellCallback = { [weak self] indexPath in 129 | guard let self = self else { return } 130 | self.playWithIndexPath(indexPath) 131 | } 132 | tableViewHepler.dataSource.append(item) 133 | } 134 | tableView.reloadData() 135 | } 136 | } 137 | 138 | // MARK: - JmoVxia---override 139 | 140 | extension CLTableViewController { 141 | override var shouldAutorotate: Bool { 142 | return false 143 | } 144 | 145 | // 支持哪些屏幕方向 146 | override var supportedInterfaceOrientations: UIInterfaceOrientationMask { 147 | return .portrait 148 | } 149 | } 150 | 151 | // MARK: - JmoVxia---objc 152 | 153 | @objc private extension CLTableViewController {} 154 | 155 | // MARK: - JmoVxia---私有方法 156 | 157 | private extension CLTableViewController { 158 | func playWithIndexPath(_ indexPath: IndexPath) { 159 | guard let item = tableViewHepler.dataSource[indexPath.row] as? CLTableViewItem else { return } 160 | guard let cell = tableView.cellForRow(at: indexPath) else { return } 161 | 162 | let player = player ?? CLPlayer() 163 | cell.contentView.addSubview(player) 164 | player.snp.makeConstraints { make in 165 | make.left.top.width.equalToSuperview() 166 | make.height.equalToSuperview().offset(-10) 167 | } 168 | player.title = item.title 169 | player.url = item.url 170 | player.play() 171 | } 172 | } 173 | 174 | extension CLTableViewController: UITableViewDelegate { 175 | func tableView(_: UITableView, willDisplay cell: UITableViewCell, forRowAt indexPath: IndexPath) { 176 | guard let player = player else { return } 177 | let url = (tableViewHepler.dataSource[indexPath.row] as? CLTableViewItem)?.url?.absoluteString 178 | guard url == player.url?.absoluteString else { return } 179 | 180 | cell.contentView.addSubview(player) 181 | player.snp.makeConstraints { make in 182 | make.left.top.width.equalToSuperview() 183 | make.height.equalToSuperview().offset(-10) 184 | } 185 | player.play() 186 | } 187 | 188 | func tableView(_: UITableView, didEndDisplaying _: UITableViewCell, forRowAt indexPath: IndexPath) { 189 | guard let player = player else { return } 190 | let url = (tableViewHepler.dataSource[indexPath.row] as? CLTableViewItem)?.url?.absoluteString 191 | guard url == player.url?.absoluteString else { return } 192 | 193 | player.removeFromSuperview() 194 | player.pause() 195 | } 196 | } 197 | -------------------------------------------------------------------------------- /CLTableViewController/View/Cell/CLTableViewCell.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLTableViewCell.swift 3 | // CLPlayer 4 | // 5 | // Created by Chen JmoVxia on 2021/12/14. 6 | // 7 | 8 | import UIKit 9 | 10 | // MARK: - JmoVxia---类-属性 11 | 12 | class CLTableViewCell: UITableViewCell { 13 | override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) { 14 | super.init(style: style, reuseIdentifier: reuseIdentifier) 15 | initUI() 16 | makeConstraints() 17 | } 18 | 19 | @available(*, unavailable) 20 | required init?(coder _: NSCoder) { 21 | fatalError("init(coder:) has not been implemented") 22 | } 23 | 24 | private lazy var iconImageView: UIImageView = { 25 | let view = UIImageView() 26 | view.image = UIImage(named: "placeholder") 27 | return view 28 | }() 29 | 30 | private lazy var playImageView: UIImageView = { 31 | let view = UIImageView() 32 | view.image = UIImage(named: "play") 33 | return view 34 | }() 35 | } 36 | 37 | // MARK: - JmoVxia---布局 38 | 39 | private extension CLTableViewCell { 40 | func initUI() { 41 | selectionStyle = .none 42 | backgroundColor = UIColor.orange.withAlphaComponent(0.5) 43 | contentView.addSubview(iconImageView) 44 | iconImageView.addSubview(playImageView) 45 | } 46 | 47 | func makeConstraints() { 48 | iconImageView.snp.makeConstraints { make in 49 | make.top.left.right.equalToSuperview() 50 | make.bottom.equalTo(-10) 51 | } 52 | playImageView.snp.makeConstraints { make in 53 | make.center.equalToSuperview() 54 | } 55 | } 56 | } 57 | 58 | // MARK: - JmoVxia---CKDCellProtocol 59 | 60 | extension CLTableViewCell: CLCellProtocol { 61 | func setItem(_ item: CLCellItemProtocol) { 62 | guard let _ = item as? CLTableViewItem else { return } 63 | } 64 | } 65 | 66 | // MARK: - JmoVxia---数据 67 | 68 | private extension CLTableViewCell { 69 | func initData() {} 70 | } 71 | 72 | // MARK: - JmoVxia---override 73 | 74 | extension CLTableViewCell {} 75 | 76 | // MARK: - JmoVxia---objc 77 | 78 | @objc private extension CLTableViewCell {} 79 | 80 | // MARK: - JmoVxia---私有方法 81 | 82 | private extension CLTableViewCell {} 83 | 84 | // MARK: - JmoVxia---公共方法 85 | 86 | extension CLTableViewCell {} 87 | -------------------------------------------------------------------------------- /CLTableViewController/View/Item/CLTableViewItem.swift: -------------------------------------------------------------------------------- 1 | // 2 | // CLTableViewItem.swift 3 | // CLPlayer 4 | // 5 | // Created by Chen JmoVxia on 2021/12/14. 6 | // 7 | 8 | import UIKit 9 | 10 | class CLTableViewItem: NSObject { 11 | var title = NSMutableAttributedString() 12 | var url: URL? 13 | var didSelectCellCallback: ((IndexPath) -> Void)? 14 | } 15 | 16 | extension CLTableViewItem: CLCellItemProtocol { 17 | func bindCell() -> UITableViewCell.Type { 18 | return CLTableViewCell.self 19 | } 20 | 21 | func cellHeight() -> CGFloat { 22 | return 300 23 | } 24 | } 25 | -------------------------------------------------------------------------------- /Extension/NSMutableAttributedString+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // NSMutableAttributedString+Extension.swift 3 | // CKD 4 | // 5 | // Created by Chen JmoVxia on 2021/3/26. 6 | // Copyright © 2021 JmoVxia. All rights reserved. 7 | // 8 | 9 | import Foundation 10 | import UIKit 11 | 12 | extension NSMutableAttributedString { 13 | /// 快捷初始化 14 | convenience init(_ text: String, attributes: ((AttributesItem) -> Void)? = nil) { 15 | let item = AttributesItem() 16 | attributes?(item) 17 | self.init(string: text, attributes: item.attributes) 18 | } 19 | 20 | /// 添加字符串并为此段添加对应的Attribute 21 | @discardableResult 22 | func addText(_ text: String, attributes: ((AttributesItem) -> Void)? = nil) -> NSMutableAttributedString { 23 | let item = AttributesItem() 24 | attributes?(item) 25 | append(NSMutableAttributedString(string: text, attributes: item.attributes)) 26 | return self 27 | } 28 | 29 | /// 添加Attribute作用于当前整体字符串,如果不包含传入的attribute,则增加当前特征 30 | @discardableResult 31 | func addAttributes(_ attributes: (AttributesItem) -> Void) -> NSMutableAttributedString { 32 | let item = AttributesItem() 33 | attributes(item) 34 | enumerateAttributes(in: NSRange(string.startIndex ..< string.endIndex, in: string), options: .reverse) { oldAttribute, range, _ in 35 | var newAtt = oldAttribute 36 | for item in item.attributes where !oldAttribute.keys.contains(item.key) { 37 | newAtt[item.key] = item.value 38 | } 39 | addAttributes(newAtt, range: range) 40 | } 41 | return self 42 | } 43 | 44 | /// 添加图片 45 | @discardableResult 46 | func addImage(_ image: UIImage?, _ bounds: CGRect) -> NSMutableAttributedString { 47 | let attch = NSTextAttachment() 48 | attch.image = image 49 | attch.bounds = bounds 50 | append(NSAttributedString(attachment: attch)) 51 | return self 52 | } 53 | } 54 | 55 | extension NSAttributedString { 56 | class AttributesItem { 57 | private(set) var attributes = [NSAttributedString.Key: Any]() 58 | private(set) lazy var paragraphStyle = NSMutableParagraphStyle() 59 | /// 字体 60 | @discardableResult 61 | func font(_ value: UIFont) -> AttributesItem { 62 | attributes[.font] = value 63 | return self 64 | } 65 | 66 | /// 字体颜色 67 | @discardableResult 68 | func foregroundColor(_ value: UIColor) -> AttributesItem { 69 | attributes[.foregroundColor] = value 70 | return self 71 | } 72 | 73 | /// 斜体 74 | @discardableResult 75 | func oblique(_ value: CGFloat) -> AttributesItem { 76 | attributes[.obliqueness] = value 77 | return self 78 | } 79 | 80 | /// 文本横向拉伸属性,正值横向拉伸文本,负值横向压缩文本 81 | @discardableResult 82 | func expansion(_ value: CGFloat) -> AttributesItem { 83 | attributes[.expansion] = value 84 | return self 85 | } 86 | 87 | /// 字间距 88 | @discardableResult 89 | func kern(_ value: CGFloat) -> AttributesItem { 90 | attributes[.kern] = value 91 | return self 92 | } 93 | 94 | /// 删除线 95 | @discardableResult 96 | func strikeStyle(_ value: NSUnderlineStyle) -> AttributesItem { 97 | attributes[.strikethroughStyle] = value.rawValue 98 | return self 99 | } 100 | 101 | /// 删除线颜色 102 | @discardableResult 103 | func strikeColor(_ value: UIColor) -> AttributesItem { 104 | attributes[.strikethroughColor] = value 105 | return self 106 | } 107 | 108 | /// 下划线 109 | @discardableResult 110 | func underlineStyle(_ value: NSUnderlineStyle) -> AttributesItem { 111 | attributes[.underlineStyle] = value.rawValue 112 | return self 113 | } 114 | 115 | /// 下划线颜色 116 | @discardableResult 117 | func underlineColor(_ value: UIColor) -> AttributesItem { 118 | attributes[.underlineColor] = value 119 | return self 120 | } 121 | 122 | /// 设置基线偏移值,正值上偏,负值下偏 123 | @discardableResult 124 | func baselineOffset(_ value: CGFloat) -> AttributesItem { 125 | attributes[.baselineOffset] = value 126 | return self 127 | } 128 | 129 | /// 居中方式 130 | @discardableResult 131 | func alignment(_ value: NSTextAlignment) -> AttributesItem { 132 | paragraphStyle.alignment = value 133 | attributes[.paragraphStyle] = paragraphStyle 134 | return self 135 | } 136 | 137 | /// 字符截断类型 138 | @discardableResult 139 | func lineBreakMode(_ value: NSLineBreakMode) -> AttributesItem { 140 | paragraphStyle.lineBreakMode = value 141 | attributes[.paragraphStyle] = paragraphStyle 142 | return self 143 | } 144 | 145 | /// 行间距 146 | @discardableResult 147 | func lineSpacing(_ value: CGFloat) -> AttributesItem { 148 | paragraphStyle.lineSpacing = value 149 | attributes[.paragraphStyle] = paragraphStyle 150 | return self 151 | } 152 | 153 | /// 最小行高 154 | @discardableResult 155 | func minimumLineHeight(_ value: CGFloat) -> AttributesItem { 156 | paragraphStyle.minimumLineHeight = value 157 | attributes[.paragraphStyle] = paragraphStyle 158 | return self 159 | } 160 | 161 | /// 最大行高 162 | @discardableResult 163 | func maximumLineHeight(_ value: CGFloat) -> AttributesItem { 164 | paragraphStyle.maximumLineHeight = value 165 | attributes[.paragraphStyle] = paragraphStyle 166 | return self 167 | } 168 | 169 | /// 段落间距 170 | @discardableResult 171 | func paragraphSpacing(_ value: CGFloat) -> AttributesItem { 172 | paragraphStyle.paragraphSpacing = value 173 | attributes[.paragraphStyle] = paragraphStyle 174 | return self 175 | } 176 | } 177 | } 178 | -------------------------------------------------------------------------------- /Extension/UIColor+CLExtension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIColor+CLExtension.swift 3 | // CKD 4 | // 5 | // Created by JmoVxia on 2020/2/25. 6 | // Copyright © 2020 JmoVxia. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIColor { 12 | // 16进制颜色 13 | class func hex(_ string: String, alpha: CGFloat = 1.0) -> UIColor { 14 | let hexString = string.trimmingCharacters(in: .whitespacesAndNewlines) 15 | let scanner = Scanner(string: hexString) 16 | 17 | if hexString.hasPrefix("#") { 18 | scanner.scanLocation = 1 19 | } else if hexString.hasPrefix("0x") { 20 | scanner.scanLocation = 2 21 | } 22 | var color: UInt32 = 0 23 | scanner.scanHexInt32(&color) 24 | 25 | let mask = 0x0000_00FF 26 | let r = Int(color >> 16) & mask 27 | let g = Int(color >> 8) & mask 28 | let b = Int(color) & mask 29 | 30 | let red = CGFloat(r) / 255.0 31 | let green = CGFloat(g) / 255.0 32 | let blue = CGFloat(b) / 255.0 33 | 34 | return self.init(red: red, green: green, blue: blue, alpha: alpha) 35 | } 36 | 37 | /// 颜色16进制字符串 38 | var hexString: String { 39 | var r: CGFloat = 0 40 | var g: CGFloat = 0 41 | var b: CGFloat = 0 42 | var a: CGFloat = 0 43 | getRed(&r, green: &g, blue: &b, alpha: &a) 44 | if a == 1.0 { 45 | return String(format: "%0.2X%0.2X%0.2X", UInt(r * 255), UInt(g * 255), UInt(b * 255)) 46 | } else { 47 | return String(format: "%0.2X%0.2X%0.2X%0.2X", UInt(r * 255), UInt(g * 255), UInt(b * 255), UInt(a * 255)) 48 | } 49 | } 50 | 51 | /// 主题色 52 | @objc class var themeColor: UIColor { 53 | return hex("2DD178") 54 | } 55 | 56 | /// 随机色 57 | class var randomColor: UIColor { 58 | let red = CGFloat(arc4random() % 256) / 255.0 59 | let green = CGFloat(arc4random() % 256) / 255.0 60 | let blue = CGFloat(arc4random() % 256) / 255.0 61 | return UIColor(red: red, green: green, blue: blue, alpha: 0.35) 62 | } 63 | 64 | // 获取反色(补色) 65 | var invertColor: UIColor { 66 | var r: CGFloat = 0, g: CGFloat = 0, b: CGFloat = 0 67 | getRed(&r, green: &g, blue: &b, alpha: nil) 68 | return UIColor(red: 1.0 - r, green: 1.0 - g, blue: 1.0 - b, alpha: 1) 69 | } 70 | } 71 | -------------------------------------------------------------------------------- /Extension/UIImage+Extension.swift: -------------------------------------------------------------------------------- 1 | // 2 | // UIImage+Extension.swift 3 | // CL 4 | // 5 | // Created by JmoVxia on 2020/2/26. 6 | // Copyright © 2020 JmoVxia. All rights reserved. 7 | // 8 | 9 | import UIKit 10 | 11 | extension UIImage { 12 | /// 生成纯色图片 13 | class func imageWithColor(_ color: UIColor) -> UIImage { 14 | let rect = CGRect(x: 0, y: 0, width: 1, height: 1) 15 | UIGraphicsBeginImageContext(rect.size) 16 | let context = UIGraphicsGetCurrentContext() 17 | context?.setFillColor(color.cgColor) 18 | context!.fill(rect) 19 | let image = UIGraphicsGetImageFromCurrentImageContext() 20 | UIGraphicsEndImageContext() 21 | return image! 22 | } 23 | 24 | /// 修改图片颜色 25 | func tintImage(_ color: UIColor) -> UIImage? { 26 | UIGraphicsBeginImageContextWithOptions(size, _: false, _: 0.0) 27 | let context = UIGraphicsGetCurrentContext() 28 | draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height)) 29 | if let context = context { 30 | context.setBlendMode(.sourceAtop) 31 | } 32 | context?.setFillColor(color.cgColor) 33 | context?.fill(CGRect(x: 0, y: 0, width: size.width, height: size.height)) 34 | let tintedImage = UIGraphicsGetImageFromCurrentImageContext() 35 | UIGraphicsEndImageContext() 36 | return tintedImage 37 | } 38 | 39 | /// 修正方向图片 40 | var fixOrientationImage: UIImage { 41 | if imageOrientation == .up { 42 | return self 43 | } 44 | UIGraphicsBeginImageContextWithOptions(size, false, scale) 45 | draw(in: CGRect(x: 0, y: 0, width: size.width, height: size.height)) 46 | if let normalizedImage: UIImage = UIGraphicsGetImageFromCurrentImageContext() { 47 | UIGraphicsEndImageContext() 48 | return normalizedImage 49 | } else { 50 | return self 51 | } 52 | } 53 | } 54 | 55 | extension UIImage { 56 | // MARK: - 压缩图片大小 57 | 58 | func compressSize(with maxSize: Int) -> Data? { 59 | // 先判断当前质量是否满足要求,不满足再进行压缩 60 | guard var finallImageData = jpegData(compressionQuality: 1.0) else { return nil } 61 | if finallImageData.count / 1024 <= maxSize { 62 | return finallImageData 63 | } 64 | // 先调整分辨率 65 | var defaultSize = CGSize(width: 1024, height: 1024) 66 | guard let compressImage = scaleSize(defaultSize), let compressImageData = compressImage.jpegData(compressionQuality: 1.0) else { 67 | return nil 68 | } 69 | finallImageData = compressImageData 70 | 71 | // 保存压缩系数 72 | var compressionQualityArray = [CGFloat]() 73 | let avg: CGFloat = 1.0 / 250 74 | var value = avg 75 | var i: CGFloat = 250.0 76 | repeat { 77 | i -= 1 78 | value = i * avg 79 | compressionQualityArray.append(value) 80 | } while i >= 1 81 | 82 | // 调整大小,压缩系数数组compressionQualityArr是从大到小存储,思路:使用二分法搜索 83 | guard let halfData = halfFuntion(array: compressionQualityArray, image: compressImage, sourceData: finallImageData, maxSize: maxSize) else { 84 | return nil 85 | } 86 | finallImageData = halfData 87 | // 如果还是未能压缩到指定大小,则进行降分辨率 88 | while finallImageData.count == 0 { 89 | // 每次降100分辨率 90 | if defaultSize.width - 100 <= 0 || defaultSize.height - 100 <= 0 { 91 | break 92 | } 93 | defaultSize = CGSize(width: defaultSize.width - 100, height: defaultSize.height - 100) 94 | guard let lastValue = compressionQualityArray.last, 95 | let newImageData = compressImage.jpegData(compressionQuality: lastValue), 96 | let tempImage = UIImage(data: newImageData), 97 | let tempCompressImage = tempImage.scaleSize(defaultSize), 98 | let sourceData = tempCompressImage.jpegData(compressionQuality: 1.0), 99 | let halfData = halfFuntion(array: compressionQualityArray, image: tempCompressImage, sourceData: sourceData, maxSize: maxSize) 100 | else { 101 | return nil 102 | } 103 | finallImageData = halfData 104 | } 105 | return finallImageData 106 | } 107 | 108 | // MARK: - 调整图片分辨率/尺寸(等比例缩放) 109 | 110 | func scaleSize(_ newSize: CGSize) -> UIImage? { 111 | let heightScale = size.height / newSize.height 112 | let widthScale = size.width / newSize.width 113 | 114 | var finallSize = CGSize(width: size.width, height: size.height) 115 | if widthScale > 1.0, widthScale > heightScale { 116 | finallSize = CGSize(width: size.width / widthScale, height: size.height / widthScale) 117 | } else if heightScale > 1.0, widthScale < heightScale { 118 | finallSize = CGSize(width: size.width / heightScale, height: size.height / heightScale) 119 | } 120 | UIGraphicsBeginImageContext(CGSize(width: Int(finallSize.width), height: Int(finallSize.height))) 121 | draw(in: CGRect(x: 0, y: 0, width: finallSize.width, height: finallSize.height)) 122 | let newImage = UIGraphicsGetImageFromCurrentImageContext() 123 | UIGraphicsEndImageContext() 124 | return newImage 125 | } 126 | 127 | // MARK: - 二分法 128 | 129 | private func halfFuntion(array: [CGFloat], image: UIImage, sourceData: Data, maxSize: Int) -> Data? { 130 | var tempFinallImageData = sourceData 131 | var finallImageData = Data() 132 | var start = 0 133 | var end = array.count - 1 134 | var index = 0 135 | 136 | var difference = Int.max 137 | while start <= end { 138 | index = start + (end - start) / 2 139 | guard let data = image.jpegData(compressionQuality: array[index]) else { 140 | return nil 141 | } 142 | tempFinallImageData = data 143 | let sizeOrigin = tempFinallImageData.count 144 | let sizeOriginKB = sizeOrigin / 1024 145 | if sizeOriginKB > maxSize { 146 | start = index + 1 147 | } else if sizeOriginKB < maxSize { 148 | if maxSize - sizeOriginKB < difference { 149 | difference = maxSize - sizeOriginKB 150 | finallImageData = tempFinallImageData 151 | } 152 | if index <= 0 { 153 | break 154 | } 155 | end = index - 1 156 | } else { 157 | break 158 | } 159 | } 160 | return finallImageData 161 | } 162 | } 163 | 164 | extension UIImage { 165 | /// 智能压缩图片大小 166 | func smartCompressImage() -> Data? { 167 | guard let finallImageData = jpegData(compressionQuality: 1.0) else { return nil } 168 | if finallImageData.count / 1024 <= 300 { 169 | return finallImageData 170 | } 171 | var width = size.width 172 | var height = size.height 173 | let longSide = max(width, height) 174 | let shortSide = min(width, height) 175 | let scale = shortSide / longSide 176 | if shortSide < 1080 || longSide < 1080 { 177 | return jpegData(compressionQuality: 0.5) 178 | } else { 179 | if width < height { 180 | width = 1080 181 | height = 1080 / scale 182 | } else { 183 | width = 1080 / scale 184 | height = 1080 185 | } 186 | UIGraphicsBeginImageContext(CGSize(width: width, height: height)) 187 | draw(in: CGRect(x: 0, y: 0, width: width, height: height)) 188 | let compressImage = UIGraphicsGetImageFromCurrentImageContext() 189 | UIGraphicsEndImageContext() 190 | return compressImage?.jpegData(compressionQuality: 0.5) 191 | } 192 | } 193 | } 194 | -------------------------------------------------------------------------------- /LICENSE: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 JmoVxia 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Podfile: -------------------------------------------------------------------------------- 1 | source 'https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git' 2 | platform :ios, '12.0' 3 | #use_frameworks! 4 | use_modular_headers! 5 | inhibit_all_warnings! 6 | install! 'cocoapods', :deterministic_uuids => false 7 | 8 | target 'CLPlayer' do 9 | inhibit_all_warnings! 10 | pod 'SnapKit' 11 | pod 'SwiftFormat/CLI' 12 | pod 'LookinServer', :subspecs => ['Swift'], :configurations => ['Debug'] 13 | end 14 | 15 | post_install do |installer| 16 | installer.pods_project.targets.each do |target| 17 | target.build_configurations.each do |config| 18 | if config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'].to_f < 12.0 19 | config.build_settings['IPHONEOS_DEPLOYMENT_TARGET'] = '12.0' 20 | end 21 | end 22 | end 23 | end 24 | 25 | -------------------------------------------------------------------------------- /Podfile.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - LookinServer/Core (1.2.8) 3 | - LookinServer/Swift (1.2.8): 4 | - LookinServer/Core 5 | - SnapKit (5.7.1) 6 | - SwiftFormat/CLI (0.55.5) 7 | 8 | DEPENDENCIES: 9 | - LookinServer/Swift 10 | - SnapKit 11 | - SwiftFormat/CLI 12 | 13 | SPEC REPOS: 14 | https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git: 15 | - LookinServer 16 | - SnapKit 17 | - SwiftFormat 18 | 19 | SPEC CHECKSUMS: 20 | LookinServer: 1b2b61c6402ae29fa22182d48f5cd067b4e99e80 21 | SnapKit: d612e99e678a2d3b95bf60b0705ed0a35c03484a 22 | SwiftFormat: 548c7b221429101546d3caeeeb672f11578df0ad 23 | 24 | PODFILE CHECKSUM: ef8371675a67d45b1e997f6d8c113acea73d85d7 25 | 26 | COCOAPODS: 1.16.2 27 | -------------------------------------------------------------------------------- /Pods/Headers/Public/SnapKit/SnapKit-umbrella.h: -------------------------------------------------------------------------------- 1 | ../../../Target Support Files/SnapKit/SnapKit-umbrella.h -------------------------------------------------------------------------------- /Pods/Headers/Public/SnapKit/SnapKit.modulemap: -------------------------------------------------------------------------------- 1 | ../../../Target Support Files/SnapKit/SnapKit.modulemap -------------------------------------------------------------------------------- /Pods/Manifest.lock: -------------------------------------------------------------------------------- 1 | PODS: 2 | - LookinServer/Core (1.2.8) 3 | - LookinServer/Swift (1.2.8): 4 | - LookinServer/Core 5 | - SnapKit (5.7.1) 6 | - SwiftFormat/CLI (0.55.5) 7 | 8 | DEPENDENCIES: 9 | - LookinServer/Swift 10 | - SnapKit 11 | - SwiftFormat/CLI 12 | 13 | SPEC REPOS: 14 | https://mirrors.tuna.tsinghua.edu.cn/git/CocoaPods/Specs.git: 15 | - LookinServer 16 | - SnapKit 17 | - SwiftFormat 18 | 19 | SPEC CHECKSUMS: 20 | LookinServer: 1b2b61c6402ae29fa22182d48f5cd067b4e99e80 21 | SnapKit: d612e99e678a2d3b95bf60b0705ed0a35c03484a 22 | SwiftFormat: 548c7b221429101546d3caeeeb672f11578df0ad 23 | 24 | PODFILE CHECKSUM: ef8371675a67d45b1e997f6d8c113acea73d85d7 25 | 26 | COCOAPODS: 1.16.2 27 | -------------------------------------------------------------------------------- /Pods/Pods.xcodeproj/xcuserdata/jmovxia.xcuserdatad/xcschemes/Pods-CLPlayer.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 53 | 54 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Pods/Pods.xcodeproj/xcuserdata/jmovxia.xcuserdatad/xcschemes/SnapKit.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 53 | 54 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Pods/Pods.xcodeproj/xcuserdata/jmovxia.xcuserdatad/xcschemes/SwiftFormat.xcscheme: -------------------------------------------------------------------------------- 1 | 2 | 5 | 8 | 9 | 15 | 21 | 22 | 23 | 24 | 25 | 30 | 31 | 32 | 33 | 43 | 44 | 50 | 51 | 53 | 54 | 57 | 58 | 59 | -------------------------------------------------------------------------------- /Pods/Pods.xcodeproj/xcuserdata/jmovxia.xcuserdatad/xcschemes/xcschememanagement.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | SchemeUserState 6 | 7 | LookinServer.xcscheme 8 | 9 | isShown 10 | 11 | orderHint 12 | 0 13 | 14 | Pods-CLPlayer.xcscheme 15 | 16 | isShown 17 | 18 | orderHint 19 | 1 20 | 21 | SnapKit-SnapKit_Privacy.xcscheme 22 | 23 | isShown 24 | 25 | orderHint 26 | 3 27 | 28 | SnapKit.xcscheme 29 | 30 | isShown 31 | 32 | orderHint 33 | 2 34 | 35 | SwiftFormat.xcscheme 36 | 37 | isShown 38 | 39 | orderHint 40 | 4 41 | 42 | 43 | SuppressBuildableAutocreation 44 | 45 | 46 | 47 | -------------------------------------------------------------------------------- /Pods/SnapKit/LICENSE: -------------------------------------------------------------------------------- 1 | Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Pods/SnapKit/README.md: -------------------------------------------------------------------------------- 1 | 2 | 3 | SnapKit is a DSL to make Auto Layout easy on both iOS and OS X. 4 | 5 | [![Build Status](https://travis-ci.org/SnapKit/SnapKit.svg)](https://travis-ci.org/SnapKit/SnapKit) 6 | [![Platform](https://img.shields.io/cocoapods/p/SnapKit.svg?style=flat)](https://github.com/SnapKit/SnapKit) 7 | [![Cocoapods Compatible](https://img.shields.io/cocoapods/v/SnapKit.svg)](https://cocoapods.org/pods/SnapKit) 8 | [![Carthage compatible](https://img.shields.io/badge/Carthage-compatible-4BC51D.svg?style=flat)](https://github.com/Carthage/Carthage) 9 | 10 | #### ⚠️ **To use with Swift 4.x please ensure you are using >= 4.0.0** ⚠️ 11 | #### ⚠️ **To use with Swift 5.x please ensure you are using >= 5.0.0** ⚠️ 12 | 13 | ## Contents 14 | 15 | - [Requirements](#requirements) 16 | - [Migration Guides](#migration-guides) 17 | - [Communication](#communication) 18 | - [Installation](#installation) 19 | - [Usage](#usage) 20 | - [Credits](#credits) 21 | - [License](#license) 22 | 23 | ## Requirements 24 | 25 | - iOS 12.0+ / Mac OS X 10.13+ / tvOS 10.0+ 26 | - Xcode 10.0+ 27 | - Swift 4.0+ 28 | 29 | ## Migration Guides 30 | 31 | - [SnapKit 3.0 Migration Guide](Documentation/SnapKit%203.0%20Migration%20Guide.md) 32 | 33 | ## Communication 34 | 35 | - If you **need help**, use [Stack Overflow](http://stackoverflow.com/questions/tagged/snapkit). (Tag 'snapkit') 36 | - If you'd like to **ask a general question**, use [Stack Overflow](http://stackoverflow.com/questions/tagged/snapkit). 37 | - If you **found a bug**, open an issue. 38 | - If you **have a feature request**, open an issue. 39 | - If you **want to contribute**, submit a pull request. 40 | 41 | 42 | ## Installation 43 | 44 | ### CocoaPods 45 | 46 | [CocoaPods](http://cocoapods.org) is a dependency manager for Cocoa projects. You can install it with the following command: 47 | 48 | ```bash 49 | $ gem install cocoapods 50 | ``` 51 | 52 | > CocoaPods 1.1.0+ is required to build SnapKit 4.0.0+. 53 | 54 | To integrate SnapKit into your Xcode project using CocoaPods, specify it in your `Podfile`: 55 | 56 | ```ruby 57 | source 'https://github.com/CocoaPods/Specs.git' 58 | platform :ios, '10.0' 59 | use_frameworks! 60 | 61 | target '' do 62 | pod 'SnapKit', '~> 5.7.0' 63 | end 64 | ``` 65 | 66 | Then, run the following command: 67 | 68 | ```bash 69 | $ pod install 70 | ``` 71 | 72 | ### Carthage 73 | 74 | [Carthage](https://github.com/Carthage/Carthage) is a decentralized dependency manager that builds your dependencies and provides you with binary frameworks. 75 | 76 | You can install Carthage with [Homebrew](http://brew.sh/) using the following command: 77 | 78 | ```bash 79 | $ brew update 80 | $ brew install carthage 81 | ``` 82 | 83 | To integrate SnapKit into your Xcode project using Carthage, specify it in your `Cartfile`: 84 | 85 | ```ogdl 86 | github "SnapKit/SnapKit" ~> 5.0.0 87 | ``` 88 | 89 | Run `carthage update` to build the framework and drag the built `SnapKit.framework` into your Xcode project. 90 | 91 | ### Swift Package Manager 92 | 93 | [Swift Package Manager](https://swift.org/package-manager/) is a tool for managing the distribution of Swift code. It’s integrated with the Swift build system to automate the process of downloading, compiling, and linking dependencies. 94 | 95 | > Xcode 11+ is required to build SnapKit using Swift Package Manager. 96 | 97 | To integrate SnapKit into your Xcode project using Swift Package Manager, add it to the dependencies value of your `Package.swift`: 98 | 99 | ```swift 100 | dependencies: [ 101 | .package(url: "https://github.com/SnapKit/SnapKit.git", .upToNextMajor(from: "5.0.1")) 102 | ] 103 | ``` 104 | 105 | ### Manually 106 | 107 | If you prefer not to use either of the aforementioned dependency managers, you can integrate SnapKit into your project manually. 108 | 109 | --- 110 | 111 | ## Usage 112 | 113 | ### Quick Start 114 | 115 | ```swift 116 | import SnapKit 117 | 118 | class MyViewController: UIViewController { 119 | 120 | lazy var box = UIView() 121 | 122 | override func viewDidLoad() { 123 | super.viewDidLoad() 124 | 125 | self.view.addSubview(box) 126 | box.backgroundColor = .green 127 | box.snp.makeConstraints { (make) -> Void in 128 | make.width.height.equalTo(50) 129 | make.center.equalTo(self.view) 130 | } 131 | } 132 | 133 | } 134 | ``` 135 | 136 | ### Playground 137 | You can try SnapKit in Playground. 138 | 139 | **Note:** 140 | 141 | > To try SnapKit in playground, open `SnapKit.xcworkspace` and build SnapKit.framework for any simulator first. 142 | 143 | ### Resources 144 | 145 | - [Documentation](https://snapkit.github.io/SnapKit/docs/) 146 | - [F.A.Q.](https://snapkit.github.io/SnapKit/faq/) 147 | 148 | ## Credits 149 | 150 | - Robert Payne ([@robertjpayne](https://twitter.com/robertjpayne)) 151 | - Many other contributors 152 | 153 | ## License 154 | 155 | SnapKit is released under the MIT license. See LICENSE for details. 156 | -------------------------------------------------------------------------------- /Pods/SwiftFormat/CommandLineTool/swiftformat: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/Pods/SwiftFormat/CommandLineTool/swiftformat -------------------------------------------------------------------------------- /Pods/SwiftFormat/LICENSE.md: -------------------------------------------------------------------------------- 1 | MIT License 2 | 3 | Copyright (c) 2016 Nick Lockwood 4 | 5 | Permission is hereby granted, free of charge, to any person obtaining a copy 6 | of this software and associated documentation files (the "Software"), to deal 7 | in the Software without restriction, including without limitation the rights 8 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 9 | copies of the Software, and to permit persons to whom the Software is 10 | furnished to do so, subject to the following conditions: 11 | 12 | The above copyright notice and this permission notice shall be included in all 13 | copies or substantial portions of the Software. 14 | 15 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 16 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 17 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 18 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 19 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 20 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 21 | SOFTWARE. 22 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-CLPlayer/Pods-CLPlayer-acknowledgements.markdown: -------------------------------------------------------------------------------- 1 | # Acknowledgements 2 | This application makes use of the following third party libraries: 3 | 4 | ## LookinServer 5 | 6 | MIT License 7 | 8 | Copyright (c) [2023] [LI KAI] 9 | 10 | Permission is hereby granted, free of charge, to any person obtaining a copy 11 | of this software and associated documentation files (the "Software"), to deal 12 | in the Software without restriction, including without limitation the rights 13 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 14 | copies of the Software, and to permit persons to whom the Software is 15 | furnished to do so, subject to the following conditions: 16 | 17 | The above copyright notice and this permission notice shall be included in all 18 | copies or substantial portions of the Software. 19 | 20 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 21 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 22 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 23 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 24 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 25 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 26 | SOFTWARE. 27 | 28 | ## SnapKit 29 | 30 | Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit 31 | 32 | Permission is hereby granted, free of charge, to any person obtaining a copy 33 | of this software and associated documentation files (the "Software"), to deal 34 | in the Software without restriction, including without limitation the rights 35 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 36 | copies of the Software, and to permit persons to whom the Software is 37 | furnished to do so, subject to the following conditions: 38 | 39 | The above copyright notice and this permission notice shall be included in 40 | all copies or substantial portions of the Software. 41 | 42 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 43 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 44 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 45 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 46 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 47 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 48 | THE SOFTWARE. 49 | 50 | 51 | ## SwiftFormat 52 | 53 | MIT License 54 | 55 | Copyright (c) 2016 Nick Lockwood 56 | 57 | Permission is hereby granted, free of charge, to any person obtaining a copy 58 | of this software and associated documentation files (the "Software"), to deal 59 | in the Software without restriction, including without limitation the rights 60 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 61 | copies of the Software, and to permit persons to whom the Software is 62 | furnished to do so, subject to the following conditions: 63 | 64 | The above copyright notice and this permission notice shall be included in all 65 | copies or substantial portions of the Software. 66 | 67 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 68 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 69 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 70 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 71 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 72 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 73 | SOFTWARE. 74 | 75 | Generated by CocoaPods - https://cocoapods.org 76 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-CLPlayer/Pods-CLPlayer-acknowledgements.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | PreferenceSpecifiers 6 | 7 | 8 | FooterText 9 | This application makes use of the following third party libraries: 10 | Title 11 | Acknowledgements 12 | Type 13 | PSGroupSpecifier 14 | 15 | 16 | FooterText 17 | MIT License 18 | 19 | Copyright (c) [2023] [LI KAI] 20 | 21 | Permission is hereby granted, free of charge, to any person obtaining a copy 22 | of this software and associated documentation files (the "Software"), to deal 23 | in the Software without restriction, including without limitation the rights 24 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 25 | copies of the Software, and to permit persons to whom the Software is 26 | furnished to do so, subject to the following conditions: 27 | 28 | The above copyright notice and this permission notice shall be included in all 29 | copies or substantial portions of the Software. 30 | 31 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 32 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 33 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 34 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 35 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 36 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 37 | SOFTWARE. 38 | License 39 | GPL-3.0 40 | Title 41 | LookinServer 42 | Type 43 | PSGroupSpecifier 44 | 45 | 46 | FooterText 47 | Copyright (c) 2011-Present SnapKit Team - https://github.com/SnapKit 48 | 49 | Permission is hereby granted, free of charge, to any person obtaining a copy 50 | of this software and associated documentation files (the "Software"), to deal 51 | in the Software without restriction, including without limitation the rights 52 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 53 | copies of the Software, and to permit persons to whom the Software is 54 | furnished to do so, subject to the following conditions: 55 | 56 | The above copyright notice and this permission notice shall be included in 57 | all copies or substantial portions of the Software. 58 | 59 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 60 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 61 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 62 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 63 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 64 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 65 | THE SOFTWARE. 66 | 67 | License 68 | MIT 69 | Title 70 | SnapKit 71 | Type 72 | PSGroupSpecifier 73 | 74 | 75 | FooterText 76 | MIT License 77 | 78 | Copyright (c) 2016 Nick Lockwood 79 | 80 | Permission is hereby granted, free of charge, to any person obtaining a copy 81 | of this software and associated documentation files (the "Software"), to deal 82 | in the Software without restriction, including without limitation the rights 83 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 84 | copies of the Software, and to permit persons to whom the Software is 85 | furnished to do so, subject to the following conditions: 86 | 87 | The above copyright notice and this permission notice shall be included in all 88 | copies or substantial portions of the Software. 89 | 90 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 91 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 92 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 93 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 94 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 95 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE 96 | SOFTWARE. 97 | 98 | License 99 | MIT 100 | Title 101 | SwiftFormat 102 | Type 103 | PSGroupSpecifier 104 | 105 | 106 | FooterText 107 | Generated by CocoaPods - https://cocoapods.org 108 | Title 109 | 110 | Type 111 | PSGroupSpecifier 112 | 113 | 114 | StringsTable 115 | Acknowledgements 116 | Title 117 | Acknowledgements 118 | 119 | 120 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-CLPlayer/Pods-CLPlayer-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_Pods_CLPlayer : NSObject 3 | @end 4 | @implementation PodsDummy_Pods_CLPlayer 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-CLPlayer/Pods-CLPlayer-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double Pods_CLPlayerVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char Pods_CLPlayerVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-CLPlayer/Pods-CLPlayer.debug.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/LookinServer" 5 | LIBRARY_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/LookinServer" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift $(SDKROOT)/usr/lib/swift 6 | OTHER_CFLAGS = $(inherited) -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/LookinServer/LookinServer.modulemap" -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.modulemap" -isystem "${PODS_ROOT}/Headers/Public" 7 | OTHER_LDFLAGS = $(inherited) -ObjC -l"LookinServer" -l"SnapKit" -l"swiftCoreGraphics" -framework "UIKit" 8 | OTHER_MODULE_VERIFIER_FLAGS = $(inherited) "-F${PODS_CONFIGURATION_BUILD_DIR}/LookinServer" "-F${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "-F${PODS_CONFIGURATION_BUILD_DIR}/SwiftFormat" 9 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/LookinServer/LookinServer.modulemap" -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.modulemap" 10 | PODS_BUILD_DIR = ${BUILD_DIR} 11 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 12 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 13 | PODS_ROOT = ${SRCROOT}/Pods 14 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 15 | SWIFT_INCLUDE_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/LookinServer" "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftFormat" 16 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-CLPlayer/Pods-CLPlayer.modulemap: -------------------------------------------------------------------------------- 1 | module Pods_CLPlayer { 2 | umbrella header "Pods-CLPlayer-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/Pods-CLPlayer/Pods-CLPlayer.release.xcconfig: -------------------------------------------------------------------------------- 1 | ALWAYS_EMBED_SWIFT_STANDARD_LIBRARIES = YES 2 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | HEADER_SEARCH_PATHS = $(inherited) "${PODS_ROOT}/Headers/Public" "${PODS_ROOT}/Headers/Public/LookinServer" 5 | LIBRARY_SEARCH_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "${TOOLCHAIN_DIR}/usr/lib/swift/${PLATFORM_NAME}" /usr/lib/swift $(SDKROOT)/usr/lib/swift 6 | OTHER_CFLAGS = $(inherited) -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.modulemap" 7 | OTHER_LDFLAGS = $(inherited) -ObjC -l"SnapKit" -l"swiftCoreGraphics" 8 | OTHER_MODULE_VERIFIER_FLAGS = $(inherited) "-F${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "-F${PODS_CONFIGURATION_BUILD_DIR}/SwiftFormat" 9 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -Xcc -fmodule-map-file="${PODS_CONFIGURATION_BUILD_DIR}/SnapKit/SnapKit.modulemap" 10 | PODS_BUILD_DIR = ${BUILD_DIR} 11 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 12 | PODS_PODFILE_DIR_PATH = ${SRCROOT}/. 13 | PODS_ROOT = ${SRCROOT}/Pods 14 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 15 | SWIFT_INCLUDE_PATHS = $(inherited) "${PODS_CONFIGURATION_BUILD_DIR}/SnapKit" "${PODS_CONFIGURATION_BUILD_DIR}/SwiftFormat" 16 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/SnapKit/SnapKit-dummy.m: -------------------------------------------------------------------------------- 1 | #import 2 | @interface PodsDummy_SnapKit : NSObject 3 | @end 4 | @implementation PodsDummy_SnapKit 5 | @end 6 | -------------------------------------------------------------------------------- /Pods/Target Support Files/SnapKit/SnapKit-prefix.pch: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | -------------------------------------------------------------------------------- /Pods/Target Support Files/SnapKit/SnapKit-umbrella.h: -------------------------------------------------------------------------------- 1 | #ifdef __OBJC__ 2 | #import 3 | #else 4 | #ifndef FOUNDATION_EXPORT 5 | #if defined(__cplusplus) 6 | #define FOUNDATION_EXPORT extern "C" 7 | #else 8 | #define FOUNDATION_EXPORT extern 9 | #endif 10 | #endif 11 | #endif 12 | 13 | 14 | FOUNDATION_EXPORT double SnapKitVersionNumber; 15 | FOUNDATION_EXPORT const unsigned char SnapKitVersionString[]; 16 | 17 | -------------------------------------------------------------------------------- /Pods/Target Support Files/SnapKit/SnapKit.debug.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SnapKit 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LIBRARY_SEARCH_PATHS = $(inherited) $(SDKROOT)/usr/lib/swift 5 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -suppress-warnings -import-underlying-module -Xcc -fmodule-map-file="${SRCROOT}/${MODULEMAP_FILE}" 6 | PODS_BUILD_DIR = ${BUILD_DIR} 7 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 8 | PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} 9 | PODS_ROOT = ${SRCROOT} 10 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/SnapKit 11 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 12 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 13 | SKIP_INSTALL = YES 14 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 15 | -------------------------------------------------------------------------------- /Pods/Target Support Files/SnapKit/SnapKit.modulemap: -------------------------------------------------------------------------------- 1 | module SnapKit { 2 | umbrella header "SnapKit-umbrella.h" 3 | 4 | export * 5 | module * { export * } 6 | } 7 | -------------------------------------------------------------------------------- /Pods/Target Support Files/SnapKit/SnapKit.release.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SnapKit 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | LIBRARY_SEARCH_PATHS = $(inherited) $(SDKROOT)/usr/lib/swift 5 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -suppress-warnings -import-underlying-module -Xcc -fmodule-map-file="${SRCROOT}/${MODULEMAP_FILE}" 6 | PODS_BUILD_DIR = ${BUILD_DIR} 7 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 8 | PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} 9 | PODS_ROOT = ${SRCROOT} 10 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/SnapKit 11 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 12 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 13 | SKIP_INSTALL = YES 14 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 15 | -------------------------------------------------------------------------------- /Pods/Target Support Files/SwiftFormat/SwiftFormat.debug.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftFormat 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -suppress-warnings -import-underlying-module -Xcc -fmodule-map-file="${SRCROOT}/${MODULEMAP_FILE}" 5 | PODS_BUILD_DIR = ${BUILD_DIR} 6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 7 | PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} 8 | PODS_ROOT = ${SRCROOT} 9 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftFormat 10 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 11 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 12 | SKIP_INSTALL = YES 13 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 14 | -------------------------------------------------------------------------------- /Pods/Target Support Files/SwiftFormat/SwiftFormat.release.xcconfig: -------------------------------------------------------------------------------- 1 | CLANG_WARN_QUOTED_INCLUDE_IN_FRAMEWORK_HEADER = NO 2 | CONFIGURATION_BUILD_DIR = ${PODS_CONFIGURATION_BUILD_DIR}/SwiftFormat 3 | GCC_PREPROCESSOR_DEFINITIONS = $(inherited) COCOAPODS=1 4 | OTHER_SWIFT_FLAGS = $(inherited) -D COCOAPODS -suppress-warnings -import-underlying-module -Xcc -fmodule-map-file="${SRCROOT}/${MODULEMAP_FILE}" 5 | PODS_BUILD_DIR = ${BUILD_DIR} 6 | PODS_CONFIGURATION_BUILD_DIR = ${PODS_BUILD_DIR}/$(CONFIGURATION)$(EFFECTIVE_PLATFORM_NAME) 7 | PODS_DEVELOPMENT_LANGUAGE = ${DEVELOPMENT_LANGUAGE} 8 | PODS_ROOT = ${SRCROOT} 9 | PODS_TARGET_SRCROOT = ${PODS_ROOT}/SwiftFormat 10 | PODS_XCFRAMEWORKS_BUILD_DIR = $(PODS_CONFIGURATION_BUILD_DIR)/XCFrameworkIntermediates 11 | PRODUCT_BUNDLE_IDENTIFIER = org.cocoapods.${PRODUCT_NAME:rfc1034identifier} 12 | SKIP_INSTALL = YES 13 | USE_RECURSIVE_SCRIPT_INPUTS_IN_SCRIPT_PHASES = YES 14 | -------------------------------------------------------------------------------- /README.md: -------------------------------------------------------------------------------- 1 | # 前言 2 | 3 |     很早之前开源了一个简单的视频播放器,由于年久失修,效果惨目忍睹,最近特意花时间对其进行了深度重构。旧版本后期不再维护,新版本使用`Swift`实现,后续会增加更多功能。不想看文字的请自行下载代码------>>>[CLPlayer](https://github.com/JmoVxia/CLPlayer) 4 | # 旧版本 VS 重构版本 5 | 6 | **1.新版本使用`Swift`,旧版本使用`Objective-C`** 7 | 8 | **2.新版本采用自定义转场实现全屏,旧版本使用旋转屏幕** 9 | 10 | **3.新版本不需要手动销毁播放器** 11 | 12 | **4.新版本修复了老版本遗留bug** 13 | 14 | **5.新版本降低了代码耦合性** 15 | 16 | **6.新版本增加了倍数播放,切换填充模式** 17 | 18 | **7.新版本提供更丰富的API** 19 | 20 | **8.新版本适配了iPhone X** 21 | 22 | **9.新版本移除了状态栏相关配置** 23 | 24 | # 效果 25 | 26 | ![效果图](https://upload-images.jianshu.io/upload_images/1979970-3f35995fbe988a91.gif?imageMogr2/auto-orient/strip) 27 | 28 | ![全屏](https://upload-images.jianshu.io/upload_images/1979970-46f701b4f1d654ee.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 29 | 30 | ![控制面板](https://upload-images.jianshu.io/upload_images/1979970-dd643aea0e8db12f.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 31 | 32 | ![UITableView](https://upload-images.jianshu.io/upload_images/1979970-6eaee76837eb46aa.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 33 | 34 | ![UICollectionView](https://upload-images.jianshu.io/upload_images/1979970-7d239b30f5b91d72.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 35 | 36 | # 功能 37 | 38 | - [x] 支持Autolayout、UIStackView、Frame 39 | - [x] 支持全屏模式、小屏模式 40 | - [x] 支持跟随手机自动旋转 41 | - [x] 支持本地视频、网络`URL` 42 | - [x] 支持`UITableView` 43 | - [x] 支持`UICollectionView` 44 | - [x] 支持手势改变屏幕的亮度(屏幕左半边) 45 | - [x] 支持手势改变音量大小(屏幕右半边) 46 | - [x] 支持拖动`UISlider`快进快退 47 | - [x] 支持`iPhone X`留海屏 48 | - [x] 支持倍速播放(`0.5X、1.0X、1.25X、1.5X、1.75X、2X`) 49 | - [x] 支持动态改变播放器的填充模式(`适应、拉伸、填充`) 50 | - [x] 支持`cocoapods` 51 | 52 | # 接入指南 53 | 54 | **项目必须支持全屏,建议将屏幕支持方向交由当前显示的控制器自行管理。** 55 | #### 项目支持全屏方案 56 | 57 | #### **1.先勾选支持方向,只保留`portrait`,保证APP启动不会横屏** 58 | 59 | ![image.png](https://upload-images.jianshu.io/upload_images/1979970-e805d44b28ba55c0.png?imageMogr2/auto-orient/strip%7CimageView2/2/w/1240) 60 | 61 | #### **2.`AppDelegate`中重写`func application(_ application: UIApplication, supportedInterfaceOrientationsFor window: UIWindow?) -> UIInterfaceOrientationMask {}`方法** 62 | 63 | ```swift 64 | func application(_: UIApplication, supportedInterfaceOrientationsFor _: UIWindow?) -> UIInterfaceOrientationMask { 65 | return .allButUpsideDown 66 | } 67 | ``` 68 | 69 | #### **3.在父类中重写屏幕控制相关方法** 70 | 71 | ```swift 72 | // 是否支持自动转屏 73 | override var shouldAutorotate: Bool { 74 | guard let navigationController = selectedViewController as? UINavigationController else { return selectedViewController?.shouldAutorotate ?? false } 75 | return navigationController.topViewController?.shouldAutorotate ?? false 76 | } 77 | 78 | // 支持哪些屏幕方向 79 | override var supportedInterfaceOrientations: UIInterfaceOrientationMask { 80 | guard let navigationController = selectedViewController as? UINavigationController else { return selectedViewController?.supportedInterfaceOrientations ?? .portrait } 81 | return navigationController.topViewController?.supportedInterfaceOrientations ?? .portrait 82 | } 83 | 84 | // 默认的屏幕方向(当前ViewController必须是通过模态出来的UIViewController(模态带导航的无效)方式展现出来的,才会调用这个方法) 85 | override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { 86 | guard let navigationController = selectedViewController as? UINavigationController else { return selectedViewController?.preferredInterfaceOrientationForPresentation ?? .portrait } 87 | return navigationController.topViewController?.preferredInterfaceOrientationForPresentation ?? .portrait 88 | } 89 | ``` 90 | `UINavigationController` 91 | 92 | ```swift 93 | // 是否支持自动转屏 94 | override var shouldAutorotate: Bool { 95 | return topViewController?.shouldAutorotate ?? false 96 | } 97 | 98 | // 支持哪些屏幕方向 99 | override var supportedInterfaceOrientations: UIInterfaceOrientationMask { 100 | return topViewController?.supportedInterfaceOrientations ?? .portrait 101 | } 102 | 103 | // 默认的屏幕方向(当前ViewController必须是通过模态出来的UIViewController(模态带导航的无效)方式展现出来的,才会调用这个方法) 104 | override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { 105 | return topViewController?.preferredInterfaceOrientationForPresentation ?? .portrait 106 | } 107 | ``` 108 | `UIViewController` 109 | ```swift 110 | // 是否支持自动转屏 111 | override var shouldAutorotate: Bool { 112 | return false 113 | } 114 | 115 | // 支持哪些屏幕方向 116 | override var supportedInterfaceOrientations: UIInterfaceOrientationMask { 117 | return .portrait 118 | } 119 | 120 | // 默认的屏幕方向(当前ViewController必须是通过模态出来的UIViewController(模态带导航的无效)方式展现出来的,才会调用这个方法) 121 | override var preferredInterfaceOrientationForPresentation: UIInterfaceOrientation { 122 | return .portrait 123 | } 124 | ``` 125 | #### **4.部分页面需要支持多方向** 126 | 127 | 在对应控制器中重写以下方法 128 | 129 | ```swift 130 | override var shouldAutorotate: Bool { 131 | return true 132 | } 133 | 134 | // 支持哪些屏幕方向 135 | override var supportedInterfaceOrientations: UIInterfaceOrientationMask { 136 | return .allButUpsideDown 137 | } 138 | ``` 139 | 140 | #### 基础配置 141 | 142 | ```swift 143 | public struct CLPlayerConfigure { 144 | /// 顶部工具条隐藏风格 145 | public enum CLPlayerTopBarHiddenStyle { 146 | /// 小屏和全屏都不隐藏 147 | case never 148 | /// 小屏和全屏都隐藏 149 | case always 150 | /// 小屏隐藏,全屏不隐藏 151 | case onlySmall 152 | } 153 | 154 | /// 自动旋转 155 | public var isAutoRotate = true 156 | /// 手势控制 157 | public var isGestureInteractionEnabled = true 158 | /// 是否显示更多面板 159 | public var isShowMorePanel = true 160 | /// 顶部工具条隐藏风格 161 | public var topBarHiddenStyle: CLPlayerTopBarHiddenStyle = .onlySmall 162 | /// 工具条自动消失时间 163 | public var autoFadeOut: TimeInterval = 5 164 | /// 默认拉伸方式 165 | public var videoGravity: AVLayerVideoGravity = .resizeAspectFill 166 | /// 顶部工具条背景颜色 167 | public var topToobarBackgroundColor: UIColor = .black.withAlphaComponent(0.6) 168 | /// 底部工具条背景颜色 169 | public var bottomToolbarBackgroundColor: UIColor = .black.withAlphaComponent(0.6) 170 | /// 进度条背景颜色 171 | public var progressBackgroundColor: UIColor = .white.withAlphaComponent(0.35) 172 | /// 缓冲条缓冲进度颜色 173 | public var progressBufferColor: UIColor = .white.withAlphaComponent(0.5) 174 | /// 进度条播放完成颜色 175 | public var progressFinishedColor: UIColor = .white 176 | /// 转子背景颜色 177 | public var loadingBackgroundColor: UIColor = .white 178 | /// 返回按钮图片 179 | public var backImage: UIImage? 180 | /// 更多按钮图片 181 | public var moreImage: UIImage? 182 | /// 播放按钮图片 183 | public var playImage: UIImage? 184 | /// 暂停按钮图片 185 | public var pauseImage: UIImage? 186 | /// 进度滑块图片 187 | public var sliderImage: UIImage? 188 | /// 最大化按钮图片 189 | public var maxImage: UIImage? 190 | /// 最小化按钮图片 191 | public var minImage: UIImage? 192 | /// 封面图片 193 | public var maskImage: UIImage? 194 | } 195 | ``` 196 | # 总结 197 | 198 |     本次重构为`Swift`第一版,后续会持续更新,定制化开发请自行参考[CLPlayer](https://github.com/JmoVxia/CLPlayer)修改 , 如果喜欢,欢迎star。 199 | 200 | # 参考资料 201 | 202 | 1. [iOS播放器全屏方案](https://www.jianshu.com/p/182f6d1e7b04) 203 | 204 | 2. [iOS状态栏](https://www.justisit.com/15626010144789.html) 205 | 206 | 3. [iOS播放器全屏旋转实现](https://www.jianshu.com/p/84a148e58fc8) 207 | 208 | 4. [iOS横竖屏旋转解决方案 - Swift](https://www.jianshu.com/p/539b265bcb5d) 209 | 210 | 5. [iOS视频旋转探究](https://drinking.github.io/iOS-video-rotation) 211 | 212 | 6. [iOS屏幕旋转的解决方案](https://www.jianshu.com/p/c973817d40c8) 213 | -------------------------------------------------------------------------------- /Resources/Assets.xcassets/AccentColor.colorset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "colors" : [ 3 | { 4 | "idiom" : "universal" 5 | } 6 | ], 7 | "info" : { 8 | "author" : "xcode", 9 | "version" : 1 10 | } 11 | } 12 | -------------------------------------------------------------------------------- /Resources/Assets.xcassets/AppIcon.appiconset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "icon-20@2x-ipad.png", 5 | "idiom" : "iphone", 6 | "scale" : "2x", 7 | "size" : "20x20" 8 | }, 9 | { 10 | "filename" : "icon-20@3x.png", 11 | "idiom" : "iphone", 12 | "scale" : "3x", 13 | "size" : "20x20" 14 | }, 15 | { 16 | "filename" : "icon-29-ipad.png", 17 | "idiom" : "iphone", 18 | "scale" : "1x", 19 | "size" : "29x29" 20 | }, 21 | { 22 | "filename" : "icon-29@2x-ipad.png", 23 | "idiom" : "iphone", 24 | "scale" : "2x", 25 | "size" : "29x29" 26 | }, 27 | { 28 | "filename" : "icon-29@3x.png", 29 | "idiom" : "iphone", 30 | "scale" : "3x", 31 | "size" : "29x29" 32 | }, 33 | { 34 | "filename" : "icon-40@2x.png", 35 | "idiom" : "iphone", 36 | "scale" : "2x", 37 | "size" : "40x40" 38 | }, 39 | { 40 | "filename" : "icon-40@3x.png", 41 | "idiom" : "iphone", 42 | "scale" : "3x", 43 | "size" : "40x40" 44 | }, 45 | { 46 | "filename" : "icon-60@2x.png", 47 | "idiom" : "iphone", 48 | "scale" : "2x", 49 | "size" : "60x60" 50 | }, 51 | { 52 | "filename" : "icon-60@3x.png", 53 | "idiom" : "iphone", 54 | "scale" : "3x", 55 | "size" : "60x60" 56 | }, 57 | { 58 | "filename" : "icon-1024.png", 59 | "idiom" : "ios-marketing", 60 | "scale" : "1x", 61 | "size" : "1024x1024" 62 | } 63 | ], 64 | "info" : { 65 | "author" : "xcode", 66 | "version" : 1 67 | } 68 | } 69 | -------------------------------------------------------------------------------- /Resources/Assets.xcassets/AppIcon.appiconset/icon-1024.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/Resources/Assets.xcassets/AppIcon.appiconset/icon-1024.png -------------------------------------------------------------------------------- /Resources/Assets.xcassets/AppIcon.appiconset/icon-20@2x-ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/Resources/Assets.xcassets/AppIcon.appiconset/icon-20@2x-ipad.png -------------------------------------------------------------------------------- /Resources/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/Resources/Assets.xcassets/AppIcon.appiconset/icon-20@3x.png -------------------------------------------------------------------------------- /Resources/Assets.xcassets/AppIcon.appiconset/icon-29-ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/Resources/Assets.xcassets/AppIcon.appiconset/icon-29-ipad.png -------------------------------------------------------------------------------- /Resources/Assets.xcassets/AppIcon.appiconset/icon-29@2x-ipad.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/Resources/Assets.xcassets/AppIcon.appiconset/icon-29@2x-ipad.png -------------------------------------------------------------------------------- /Resources/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/Resources/Assets.xcassets/AppIcon.appiconset/icon-29@3x.png -------------------------------------------------------------------------------- /Resources/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/Resources/Assets.xcassets/AppIcon.appiconset/icon-40@2x.png -------------------------------------------------------------------------------- /Resources/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/Resources/Assets.xcassets/AppIcon.appiconset/icon-40@3x.png -------------------------------------------------------------------------------- /Resources/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/Resources/Assets.xcassets/AppIcon.appiconset/icon-60@2x.png -------------------------------------------------------------------------------- /Resources/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/Resources/Assets.xcassets/AppIcon.appiconset/icon-60@3x.png -------------------------------------------------------------------------------- /Resources/Assets.xcassets/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "info" : { 3 | "author" : "xcode", 4 | "version" : 1 5 | } 6 | } 7 | -------------------------------------------------------------------------------- /Resources/Assets.xcassets/back.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "back@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "back@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Resources/Assets.xcassets/back.imageset/back@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/Resources/Assets.xcassets/back.imageset/back@2x.png -------------------------------------------------------------------------------- /Resources/Assets.xcassets/back.imageset/back@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/Resources/Assets.xcassets/back.imageset/back@3x.png -------------------------------------------------------------------------------- /Resources/Assets.xcassets/homeNormal.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "homeNormal@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "homeNormal@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Resources/Assets.xcassets/homeNormal.imageset/homeNormal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/Resources/Assets.xcassets/homeNormal.imageset/homeNormal@2x.png -------------------------------------------------------------------------------- /Resources/Assets.xcassets/homeNormal.imageset/homeNormal@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/Resources/Assets.xcassets/homeNormal.imageset/homeNormal@3x.png -------------------------------------------------------------------------------- /Resources/Assets.xcassets/homeSelected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "homeSelected@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "homeSelected@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Resources/Assets.xcassets/homeSelected.imageset/homeSelected@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/Resources/Assets.xcassets/homeSelected.imageset/homeSelected@2x.png -------------------------------------------------------------------------------- /Resources/Assets.xcassets/homeSelected.imageset/homeSelected@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/Resources/Assets.xcassets/homeSelected.imageset/homeSelected@3x.png -------------------------------------------------------------------------------- /Resources/Assets.xcassets/logo.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "logo.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Resources/Assets.xcassets/logo.imageset/logo.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/Resources/Assets.xcassets/logo.imageset/logo.png -------------------------------------------------------------------------------- /Resources/Assets.xcassets/meArrowRight.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "meArrowRight@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "meArrowRight@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Resources/Assets.xcassets/meArrowRight.imageset/meArrowRight@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/Resources/Assets.xcassets/meArrowRight.imageset/meArrowRight@2x.png -------------------------------------------------------------------------------- /Resources/Assets.xcassets/meArrowRight.imageset/meArrowRight@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/Resources/Assets.xcassets/meArrowRight.imageset/meArrowRight@3x.png -------------------------------------------------------------------------------- /Resources/Assets.xcassets/meNormal.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "meNormal@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "meNormal@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Resources/Assets.xcassets/meNormal.imageset/meNormal@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/Resources/Assets.xcassets/meNormal.imageset/meNormal@2x.png -------------------------------------------------------------------------------- /Resources/Assets.xcassets/meNormal.imageset/meNormal@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/Resources/Assets.xcassets/meNormal.imageset/meNormal@3x.png -------------------------------------------------------------------------------- /Resources/Assets.xcassets/meSelected.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "meSelected@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "meSelected@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Resources/Assets.xcassets/meSelected.imageset/meSelected@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/Resources/Assets.xcassets/meSelected.imageset/meSelected@2x.png -------------------------------------------------------------------------------- /Resources/Assets.xcassets/meSelected.imageset/meSelected@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/Resources/Assets.xcassets/meSelected.imageset/meSelected@3x.png -------------------------------------------------------------------------------- /Resources/Assets.xcassets/placeholder.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "placeholder.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Resources/Assets.xcassets/placeholder.imageset/placeholder.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/Resources/Assets.xcassets/placeholder.imageset/placeholder.png -------------------------------------------------------------------------------- /Resources/Assets.xcassets/placeholder1.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "filename" : "placeholder1.png", 5 | "idiom" : "universal", 6 | "scale" : "1x" 7 | }, 8 | { 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "idiom" : "universal", 14 | "scale" : "3x" 15 | } 16 | ], 17 | "info" : { 18 | "author" : "xcode", 19 | "version" : 1 20 | } 21 | } 22 | -------------------------------------------------------------------------------- /Resources/Assets.xcassets/placeholder1.imageset/placeholder1.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/Resources/Assets.xcassets/placeholder1.imageset/placeholder1.png -------------------------------------------------------------------------------- /Resources/Assets.xcassets/play.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "play@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "play@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Resources/Assets.xcassets/play.imageset/play@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/Resources/Assets.xcassets/play.imageset/play@2x.png -------------------------------------------------------------------------------- /Resources/Assets.xcassets/play.imageset/play@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/Resources/Assets.xcassets/play.imageset/play@3x.png -------------------------------------------------------------------------------- /Resources/Assets.xcassets/slider.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "slider@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "slider@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Resources/Assets.xcassets/slider.imageset/slider@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/Resources/Assets.xcassets/slider.imageset/slider@2x.png -------------------------------------------------------------------------------- /Resources/Assets.xcassets/slider.imageset/slider@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/Resources/Assets.xcassets/slider.imageset/slider@3x.png -------------------------------------------------------------------------------- /Resources/Assets.xcassets/slider_dog.imageset/Contents.json: -------------------------------------------------------------------------------- 1 | { 2 | "images" : [ 3 | { 4 | "idiom" : "universal", 5 | "scale" : "1x" 6 | }, 7 | { 8 | "filename" : "slider_dog@2x.png", 9 | "idiom" : "universal", 10 | "scale" : "2x" 11 | }, 12 | { 13 | "filename" : "slider_dog@3x.png", 14 | "idiom" : "universal", 15 | "scale" : "3x" 16 | } 17 | ], 18 | "info" : { 19 | "author" : "xcode", 20 | "version" : 1 21 | } 22 | } 23 | -------------------------------------------------------------------------------- /Resources/Assets.xcassets/slider_dog.imageset/slider_dog@2x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/Resources/Assets.xcassets/slider_dog.imageset/slider_dog@2x.png -------------------------------------------------------------------------------- /Resources/Assets.xcassets/slider_dog.imageset/slider_dog@3x.png: -------------------------------------------------------------------------------- https://raw.githubusercontent.com/JmoVxia/CLPlayer/dac33e7f205394b33d8b98474c89e67e3d4367d7/Resources/Assets.xcassets/slider_dog.imageset/slider_dog@3x.png -------------------------------------------------------------------------------- /Resources/Base.lproj/LaunchScreen.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | 35 | 36 | 37 | 38 | 39 | 40 | 41 | 42 | 43 | 44 | 45 | 46 | -------------------------------------------------------------------------------- /Resources/Base.lproj/Main.storyboard: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | 6 | 7 | 8 | 9 | 10 | 11 | 12 | 13 | 14 | 15 | 16 | 17 | 18 | 19 | 20 | 21 | 22 | 23 | 24 | 25 | 26 | 27 | 28 | 29 | 30 | 31 | 32 | 33 | 34 | -------------------------------------------------------------------------------- /Resources/Info.plist: -------------------------------------------------------------------------------- 1 | 2 | 3 | 4 | 5 | NSAppTransportSecurity 6 | 7 | NSAllowsArbitraryLoads 8 | 9 | 10 | 11 | 12 | --------------------------------------------------------------------------------