├── 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 | [](https://travis-ci.org/SnapKit/SnapKit)
6 | [](https://github.com/SnapKit/SnapKit)
7 | [](https://cocoapods.org/pods/SnapKit)
8 | [](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 | 
27 |
28 | 
29 |
30 | 
31 |
32 | 
33 |
34 | 
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 | 
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 |
--------------------------------------------------------------------------------