From 3d8dfd2fe8924b04af4d05bd3ac3174a3162f6d7 Mon Sep 17 00:00:00 2001 From: zjx Date: Thu, 3 Jul 2025 17:48:21 +0800 Subject: [PATCH] =?UTF-8?q?=E6=92=AD=E6=94=BE=E5=99=A8=E5=8A=9F=E8=83=BD?= =?UTF-8?q?=E5=BC=80=E5=8F=91?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- BeeReel.xcodeproj/project.pbxproj | 64 ++++++ .../Base/Controller/BRTabBarController.swift | 2 +- .../Base/Controller/BRViewController.swift | 74 +++++++ BeeReel/Base/Extension/NSNumber+BRAdd.swift | 39 ++++ BeeReel/Base/Extension/UIColor+BRAdd.swift | 4 + .../Extension/UINavigationBar+BRAdd.swift | 88 +++++++++ BeeReel/Base/Network/API/BRVideoAPI.swift | 22 +++ .../Controller/BRExploreViewController.swift | 123 ++++++++++++ .../Explore/View/BRExploreControlView.swift | 96 +++++++++ .../Explore/View/BRExplorePlayerCell.swift | 17 ++ .../BRPlayerListViewController.swift | 186 +++++++++++++++++- .../BRVideoDetailViewController.swift | 112 ++++++++--- .../Model/BRPlayerControlProtocol.swift | 16 ++ .../Class/Player/Model/BRPlayerProtocol.swift | 3 + .../Player/View/BRDetailControlView.swift | 20 ++ .../Player/View/BRDetailPlayerCell.swift | 16 ++ .../Player/View/BRPlayerControlView.swift | 49 +++++ .../Class/Player/View/BRPlayerListCell.swift | 44 ++++- .../Player/ViewModel/BRPlayerViewModel.swift | 21 +- BeeReel/Lib/Player/BRPlayer.swift | 90 ++++++++- BeeReel/Lib/Player/BRPlayerCache.swift | 28 +++ .../icon/full_icon_01.imageset/Contents.json | 22 +++ .../icon/full_icon_01.imageset/Frame@2x.png | Bin 0 -> 385 bytes .../icon/full_icon_01.imageset/Frame@3x.png | Bin 0 -> 485 bytes .../icon/hot_icon_03.imageset/Contents.json | 22 +++ .../icon/hot_icon_03.imageset/Frame@2x.png | Bin 0 -> 467 bytes .../icon/hot_icon_03.imageset/Frame@3x.png | Bin 0 -> 646 bytes .../nav_back_icon_01.imageset/Contents.json | 22 +++ .../Frame 1498@2x.png | Bin 0 -> 4862 bytes .../Frame 1498@3x.png | Bin 0 -> 9403 bytes Podfile | 1 + 31 files changed, 1139 insertions(+), 42 deletions(-) create mode 100644 BeeReel/Base/Extension/NSNumber+BRAdd.swift create mode 100644 BeeReel/Base/Extension/UINavigationBar+BRAdd.swift create mode 100644 BeeReel/Class/Explore/Controller/BRExploreViewController.swift create mode 100644 BeeReel/Class/Explore/View/BRExploreControlView.swift create mode 100644 BeeReel/Class/Explore/View/BRExplorePlayerCell.swift create mode 100644 BeeReel/Class/Player/Model/BRPlayerControlProtocol.swift create mode 100644 BeeReel/Class/Player/View/BRDetailControlView.swift create mode 100644 BeeReel/Class/Player/View/BRDetailPlayerCell.swift create mode 100644 BeeReel/Class/Player/View/BRPlayerControlView.swift create mode 100644 BeeReel/Lib/Player/BRPlayerCache.swift create mode 100644 BeeReel/Sources/Assets.xcassets/icon/full_icon_01.imageset/Contents.json create mode 100644 BeeReel/Sources/Assets.xcassets/icon/full_icon_01.imageset/Frame@2x.png create mode 100644 BeeReel/Sources/Assets.xcassets/icon/full_icon_01.imageset/Frame@3x.png create mode 100644 BeeReel/Sources/Assets.xcassets/icon/hot_icon_03.imageset/Contents.json create mode 100644 BeeReel/Sources/Assets.xcassets/icon/hot_icon_03.imageset/Frame@2x.png create mode 100644 BeeReel/Sources/Assets.xcassets/icon/hot_icon_03.imageset/Frame@3x.png create mode 100644 BeeReel/Sources/Assets.xcassets/icon/nav_back_icon_01.imageset/Contents.json create mode 100644 BeeReel/Sources/Assets.xcassets/icon/nav_back_icon_01.imageset/Frame 1498@2x.png create mode 100644 BeeReel/Sources/Assets.xcassets/icon/nav_back_icon_01.imageset/Frame 1498@3x.png diff --git a/BeeReel.xcodeproj/project.pbxproj b/BeeReel.xcodeproj/project.pbxproj index 7a15c39..78a5cbb 100644 --- a/BeeReel.xcodeproj/project.pbxproj +++ b/BeeReel.xcodeproj/project.pbxproj @@ -9,6 +9,16 @@ /* Begin PBXBuildFile section */ 440A41A6E6A22A02807AE759 /* Pods_BeeReel.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 899B3015B03D5E1A5A6507EB /* Pods_BeeReel.framework */; }; BF0DBDD12E0D4E150035F6B4 /* BRTabBar.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF0DBDD02E0D4E150035F6B4 /* BRTabBar.swift */; }; + BF3338E82E15219500B10F76 /* UINavigationBar+BRAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3338E72E15218F00B10F76 /* UINavigationBar+BRAdd.swift */; }; + BF3338EA2E152B8100B10F76 /* BRPlayerCache.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3338E92E152B8100B10F76 /* BRPlayerCache.swift */; }; + BF3338EC2E154BFE00B10F76 /* BRPlayerControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3338EB2E154BFE00B10F76 /* BRPlayerControlView.swift */; }; + BF3338F02E15569600B10F76 /* BRExploreViewController.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3338EF2E15569600B10F76 /* BRExploreViewController.swift */; }; + BF3338F32E16169A00B10F76 /* BRExplorePlayerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3338F22E16169A00B10F76 /* BRExplorePlayerCell.swift */; }; + BF3338F52E1616B200B10F76 /* BRExploreControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3338F42E1616B200B10F76 /* BRExploreControlView.swift */; }; + BF3338F72E16176900B10F76 /* BRDetailPlayerCell.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3338F62E16176900B10F76 /* BRDetailPlayerCell.swift */; }; + BF3338F92E16178700B10F76 /* BRDetailControlView.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3338F82E16178700B10F76 /* BRDetailControlView.swift */; }; + BF3338FB2E161CF900B10F76 /* NSNumber+BRAdd.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3338FA2E161CF000B10F76 /* NSNumber+BRAdd.swift */; }; + BF3338FD2E1626B000B10F76 /* BRPlayerControlProtocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF3338FC2E1626A500B10F76 /* BRPlayerControlProtocol.swift */; }; BF692AEB2E0A475D00A5C2DA /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF692AE12E0A475D00A5C2DA /* AppDelegate.swift */; }; BF692AEC2E0A475D00A5C2DA /* SceneDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = BF692AE82E0A475D00A5C2DA /* SceneDelegate.swift */; }; BF692AEE2E0A475D00A5C2DA /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = BF692AE22E0A475D00A5C2DA /* Assets.xcassets */; }; @@ -104,6 +114,16 @@ 86290EBFA8B93C91B3BAD835 /* Pods-ShortBox.debug.xcconfig */ = {isa = PBXFileReference; includeInIndex = 1; lastKnownFileType = text.xcconfig; name = "Pods-ShortBox.debug.xcconfig"; path = "Target Support Files/Pods-ShortBox/Pods-ShortBox.debug.xcconfig"; sourceTree = ""; }; 899B3015B03D5E1A5A6507EB /* Pods_BeeReel.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Pods_BeeReel.framework; sourceTree = BUILT_PRODUCTS_DIR; }; BF0DBDD02E0D4E150035F6B4 /* BRTabBar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRTabBar.swift; sourceTree = ""; }; + BF3338E72E15218F00B10F76 /* UINavigationBar+BRAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationBar+BRAdd.swift"; sourceTree = ""; }; + BF3338E92E152B8100B10F76 /* BRPlayerCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRPlayerCache.swift; sourceTree = ""; }; + BF3338EB2E154BFE00B10F76 /* BRPlayerControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRPlayerControlView.swift; sourceTree = ""; }; + BF3338EF2E15569600B10F76 /* BRExploreViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRExploreViewController.swift; sourceTree = ""; }; + BF3338F22E16169A00B10F76 /* BRExplorePlayerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRExplorePlayerCell.swift; sourceTree = ""; }; + BF3338F42E1616B200B10F76 /* BRExploreControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRExploreControlView.swift; sourceTree = ""; }; + BF3338F62E16176900B10F76 /* BRDetailPlayerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRDetailPlayerCell.swift; sourceTree = ""; }; + BF3338F82E16178700B10F76 /* BRDetailControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRDetailControlView.swift; sourceTree = ""; }; + BF3338FA2E161CF000B10F76 /* NSNumber+BRAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSNumber+BRAdd.swift"; sourceTree = ""; }; + BF3338FC2E1626A500B10F76 /* BRPlayerControlProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRPlayerControlProtocol.swift; sourceTree = ""; }; BF692AC92E0A475500A5C2DA /* BeeReel.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = BeeReel.app; sourceTree = BUILT_PRODUCTS_DIR; }; BF692AE12E0A475D00A5C2DA /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; BF692AE22E0A475D00A5C2DA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -233,6 +253,32 @@ path = Pods; sourceTree = ""; }; + BF3338ED2E15566C00B10F76 /* Explore */ = { + isa = PBXGroup; + children = ( + BF3338EE2E15567200B10F76 /* Controller */, + BF3338F12E16167B00B10F76 /* View */, + ); + path = Explore; + sourceTree = ""; + }; + BF3338EE2E15567200B10F76 /* Controller */ = { + isa = PBXGroup; + children = ( + BF3338EF2E15569600B10F76 /* BRExploreViewController.swift */, + ); + path = Controller; + sourceTree = ""; + }; + BF3338F12E16167B00B10F76 /* View */ = { + isa = PBXGroup; + children = ( + BF3338F22E16169A00B10F76 /* BRExplorePlayerCell.swift */, + BF3338F42E1616B200B10F76 /* BRExploreControlView.swift */, + ); + path = View; + sourceTree = ""; + }; BF692AC02E0A475500A5C2DA = { isa = PBXGroup; children = ( @@ -302,6 +348,7 @@ BF692AF52E0A47D400A5C2DA /* Class */ = { isa = PBXGroup; children = ( + BF3338ED2E15566C00B10F76 /* Explore */, BF692B682E0BC78C00A5C2DA /* Home */, BF692B4A2E0AA84C00A5C2DA /* Player */, ); @@ -370,6 +417,8 @@ BF692B0C2E0A7A9A00A5C2DA /* Extension */ = { isa = PBXGroup; children = ( + BF3338FA2E161CF000B10F76 /* NSNumber+BRAdd.swift */, + BF3338E72E15218F00B10F76 /* UINavigationBar+BRAdd.swift */, BFC676BD2E13A8DD006659E5 /* UIScrollView+BRRefresh.swift */, BFC6768C2E123D67006659E5 /* AttributedString+BRAdd.swift */, BFC6767A2E0E9736006659E5 /* UIStackView+BRAdd.swift */, @@ -444,6 +493,7 @@ isa = PBXGroup; children = ( BF692B462E0A9B7400A5C2DA /* BRPlayer.swift */, + BF3338E92E152B8100B10F76 /* BRPlayerCache.swift */, ); path = Player; sourceTree = ""; @@ -472,6 +522,9 @@ isa = PBXGroup; children = ( BF692B592E0AAADD00A5C2DA /* BRPlayerListCell.swift */, + BF3338EB2E154BFE00B10F76 /* BRPlayerControlView.swift */, + BF3338F62E16176900B10F76 /* BRDetailPlayerCell.swift */, + BF3338F82E16178700B10F76 /* BRDetailControlView.swift */, ); path = View; sourceTree = ""; @@ -479,6 +532,7 @@ BF692B4E2E0AA88600A5C2DA /* Model */ = { isa = PBXGroup; children = ( + BF3338FC2E1626A500B10F76 /* BRPlayerControlProtocol.swift */, BFC676802E122733006659E5 /* BRPlayerProtocol.swift */, BFC676862E122E36006659E5 /* BRVideoDetailModel.swift */, ); @@ -800,6 +854,7 @@ BFC676732E0E938B006659E5 /* BRTableView.swift in Sources */, BFC676932E126A62006659E5 /* BRSpotlightNewMainCell.swift in Sources */, BFC6768D2E123D6E006659E5 /* AttributedString+BRAdd.swift in Sources */, + BF3338F52E1616B200B10F76 /* BRExploreControlView.swift in Sources */, BF692B132E0A7B9000A5C2DA /* BRUserInfo.swift in Sources */, BF692B042E0A76D200A5C2DA /* BRLoginManager.swift in Sources */, BFC6769D2E129794006659E5 /* BRHomeTop10ViewController.swift in Sources */, @@ -808,6 +863,7 @@ BF692B342E0A87C800A5C2DA /* UIDevice+BRAdd.swift in Sources */, BF692B3E2E0A8D2300A5C2DA /* BRTabBarController.swift in Sources */, BF692B542E0AA8FA00A5C2DA /* BRCollectionView.swift in Sources */, + BF3338E82E15219500B10F76 /* UINavigationBar+BRAdd.swift in Sources */, BF692B472E0A9B7900A5C2DA /* BRPlayer.swift in Sources */, BF692B6E2E0BD4CB00A5C2DA /* BRHomeHeaderView.swift in Sources */, BF692AFA2E0A6F0900A5C2DA /* BRNetwork.swift in Sources */, @@ -818,6 +874,8 @@ BF692B782E0D3A1200A5C2DA /* BRHomeModuleItem.swift in Sources */, BF692B5A2E0AAADD00A5C2DA /* BRPlayerListCell.swift in Sources */, BF692B162E0A7CD600A5C2DA /* BRHUD.swift in Sources */, + BF3338F72E16176900B10F76 /* BRDetailPlayerCell.swift in Sources */, + BF3338EA2E152B8100B10F76 /* BRPlayerCache.swift in Sources */, BFC676952E126BBF006659E5 /* BRSpotlightNewCell.swift in Sources */, BF692B402E0A8FA100A5C2DA /* UIColor+BRAdd.swift in Sources */, BF692B102E0A7B4300A5C2DA /* BRUserDefaultsKey.swift in Sources */, @@ -830,6 +888,7 @@ BFC676812E122733006659E5 /* BRPlayerProtocol.swift in Sources */, BFC676BC2E138ABB006659E5 /* BRNewReleasesViewController.swift in Sources */, BF692B5F2E0B812800A5C2DA /* BRTabBarItemContainer.swift in Sources */, + BF3338EC2E154BFE00B10F76 /* BRPlayerControlView.swift in Sources */, BF692B652E0BC53900A5C2DA /* CGMutablePath+BRRoundedCorner.swift in Sources */, BFC676752E0E93B3006659E5 /* BRTableViewCell.swift in Sources */, BF78108B2E0D4EB3007DEEBC /* BRURLPath.swift in Sources */, @@ -839,16 +898,20 @@ BFC676BE2E13A8EB006659E5 /* UIScrollView+BRRefresh.swift in Sources */, BF692B182E0A7D8900A5C2DA /* BRToast.swift in Sources */, BF692B0E2E0A7AF300A5C2DA /* UserDefaults+BRAdd.swift in Sources */, + BF3338FD2E1626B000B10F76 /* BRPlayerControlProtocol.swift in Sources */, BF692B582E0AAA6F00A5C2DA /* UIScreen+BRAdd.swift in Sources */, BF692B1F2E0A804600A5C2DA /* BRLocalizedManager.swift in Sources */, BF692B612E0B814F00A5C2DA /* BRTabBarItemContentView.swift in Sources */, BF692B012E0A74A200A5C2DA /* BRDefine.swift in Sources */, BFC6767B2E0E973B006659E5 /* UIStackView+BRAdd.swift in Sources */, + BF3338F32E16169A00B10F76 /* BRExplorePlayerCell.swift in Sources */, + BF3338F92E16178700B10F76 /* BRDetailControlView.swift in Sources */, BFC676AB2E1372BD006659E5 /* BRHomeTop3Cell.swift in Sources */, BFC676892E122FDD006659E5 /* BRVideoAPI.swift in Sources */, BFC6769B2E1285C5006659E5 /* BRPagerViewTransformer.swift in Sources */, BFC676832E122CC5006659E5 /* BRPlayerViewModel.swift in Sources */, BF692B672E0BC6C700A5C2DA /* AppDelegate+BRConfig.swift in Sources */, + BF3338FB2E161CF900B10F76 /* NSNumber+BRAdd.swift in Sources */, BF692B222E0A820D00A5C2DA /* String+BRAdd.swift in Sources */, BF692B632E0B9D4800A5C2DA /* BRTabBarItem.swift in Sources */, BFC6768B2E123690006659E5 /* BRVideoRevolutionManager.swift in Sources */, @@ -867,6 +930,7 @@ BF692B732E0D397700A5C2DA /* BRHomeAPI.swift in Sources */, BF692B7A2E0D3BD300A5C2DA /* BRShortModel.swift in Sources */, BFC676712E0E9234006659E5 /* BRSpotlightViewViewController.swift in Sources */, + BF3338F02E15569600B10F76 /* BRExploreViewController.swift in Sources */, BF0DBDD12E0D4E150035F6B4 /* BRTabBar.swift in Sources */, BF692B562E0AA92100A5C2DA /* BRCollectionViewCell.swift in Sources */, BF692B072E0A771C00A5C2DA /* BRModel.swift in Sources */, diff --git a/BeeReel/Base/Controller/BRTabBarController.swift b/BeeReel/Base/Controller/BRTabBarController.swift index 0273498..8922c36 100644 --- a/BeeReel/Base/Controller/BRTabBarController.swift +++ b/BeeReel/Base/Controller/BRTabBarController.swift @@ -71,7 +71,7 @@ extension BRTabBarController { private func br_setup() { let nav1 = createNavigationController(viewController: BRHomeViewController(), title: "首页".localized, image: UIImage(named: "tabbar_icon_01"), selectedImage: UIImage(named: "tabbar_icon_01_selected")) - let nav2 = createNavigationController(viewController: BRViewController(), title: "推荐".localized, image: UIImage(named: "tabbar_icon_02"), selectedImage: UIImage(named: "tabbar_icon_02_selected")) + let nav2 = createNavigationController(viewController: BRExploreViewController(), title: "推荐".localized, image: UIImage(named: "tabbar_icon_02"), selectedImage: UIImage(named: "tabbar_icon_02_selected")) let nav3 = createNavigationController(viewController: BRViewController(), title: "首页".localized, image: UIImage(named: "tabbar_icon_03"), selectedImage: UIImage(named: "tabbar_icon_03_selected")) let nav4 = createNavigationController(viewController: BRViewController(), title: "首页".localized, image: UIImage(named: "tabbar_icon_04"), selectedImage: UIImage(named: "tabbar_icon_04_selected")) diff --git a/BeeReel/Base/Controller/BRViewController.swift b/BeeReel/Base/Controller/BRViewController.swift index 92025e9..5fa4396 100644 --- a/BeeReel/Base/Controller/BRViewController.swift +++ b/BeeReel/Base/Controller/BRViewController.swift @@ -25,6 +25,14 @@ class BRViewController: UIViewController { self.isViewDidLoad = true self.view.backgroundColor = .backgroundColor() + if let navi = navigationController { + if navi.visibleViewController == self { + if navi.viewControllers.count > 1 { + configNavigationBack() + } + } + } + } @@ -56,3 +64,69 @@ class BRViewController: UIViewController { completer?() } } + + +extension UIViewController { + func configNavigationBack(_ imageName: String = "nav_back_icon_01") { + let image = UIImage(named: imageName) + + let leftBarButtonItem = UIBarButtonItem(image: image, style: .plain ,target: self,action: #selector(handleNavBack)) + navigationItem.leftBarButtonItem = leftBarButtonItem + } + + @objc func handleNavBack() { + self.br_toLastViewController(animated: true) + } + + func br_toLastViewController(animated: Bool) { + if self.navigationController != nil + { + if self.navigationController?.viewControllers.count == 1 + { + self.dismiss(animated: animated, completion: nil) + } else { + self.navigationController?.popViewController(animated: animated) + } + } + else if self.presentingViewController != nil { + self.dismiss(animated: animated, completion: nil) + } + } +} + +extension UIViewController { + ///设置导航默认样式 + func br_setNavigationNormalStyle(backgroundColor: UIColor = .clear, + isTranslucent: Bool = true, + prefersLargeTitles: Bool = false) + { + self.br_setNavigationBackgroundColor(color: backgroundColor, isTranslucent: isTranslucent) + self.br_setNavigationTitleStyle() + self.navigationController?.navigationBar.prefersLargeTitles = prefersLargeTitles + } + + ///设置导航背景色 + func br_setNavigationBackgroundColor(color: UIColor?, isTranslucent: Bool = false) { + guard let nav = navigationController else { return } + if nav.visibleViewController == self { + nav.navigationBar.br_setBackgroundColor(backgroundColor: color) + nav.navigationBar.br_setTranslucent(isTranslucent: isTranslucent) + } + } + + ///设置标题样式 + func br_setNavigationTitleStyle(color: UIColor? = nil, font: UIFont? = nil) { + guard let nav = navigationController else { return } + if nav.visibleViewController == self { + //标题样式 + var titleTextAttributes = UINavigationBar.br_normalTitleTextAttributes + if let color = color { + titleTextAttributes[NSAttributedString.Key.foregroundColor] = color + } + if let font = font { + titleTextAttributes[NSAttributedString.Key.font] = font + } + nav.navigationBar.br_setTitleTextAttributes(titleTextAttributes: titleTextAttributes) + } + } +} diff --git a/BeeReel/Base/Extension/NSNumber+BRAdd.swift b/BeeReel/Base/Extension/NSNumber+BRAdd.swift new file mode 100644 index 0000000..7928ec7 --- /dev/null +++ b/BeeReel/Base/Extension/NSNumber+BRAdd.swift @@ -0,0 +1,39 @@ +// +// NSNumber+BRAdd.swift +// BeeReel +// +// Created by 湖南秦九 on 2025/7/3. +// + +extension NSNumber { + + func br_toString(maximumFractionDigits: Int = 10, minimumFractionDigits: Int? = nil, roundingMode: NumberFormatter.RoundingMode? = nil) -> String { + let formatter = NumberFormatter() + formatter.minimumIntegerDigits = 1 + formatter.maximumFractionDigits = maximumFractionDigits + if let minimumFractionDigits = minimumFractionDigits { + formatter.minimumFractionDigits = minimumFractionDigits + } + if let roundingMode = roundingMode { + formatter.roundingMode = roundingMode + } + formatter.numberStyle = .none + return formatter.string(from: self) ?? "0" + } +} + + +extension Int { + func br_formatTimeGroup() -> (String, String, String) { + let seconds = self + + var s: String = "00" + var m: String = "00" + var h: String = "00" + s = String(format: "%02d", Int(Int(seconds) % 60)) + m = String(format: "%02d", Int(seconds / 60) % 60) + h = String(format: "%02d", Int(seconds / 3600)) + + return (h, m, s) + } +} diff --git a/BeeReel/Base/Extension/UIColor+BRAdd.swift b/BeeReel/Base/Extension/UIColor+BRAdd.swift index 63b8d65..ffb6194 100644 --- a/BeeReel/Base/Extension/UIColor+BRAdd.swift +++ b/BeeReel/Base/Extension/UIColor+BRAdd.swift @@ -58,4 +58,8 @@ extension UIColor { static func colorE3FC37(alpha: CGFloat = 1) -> UIColor { return UIColor(rgb: 0xE3FC37, alpha: alpha) } + + static func color5A5D45(alpha: CGFloat = 1) -> UIColor { + return UIColor(rgb: 0x5A5D45, alpha: alpha) + } } diff --git a/BeeReel/Base/Extension/UINavigationBar+BRAdd.swift b/BeeReel/Base/Extension/UINavigationBar+BRAdd.swift new file mode 100644 index 0000000..e48742d --- /dev/null +++ b/BeeReel/Base/Extension/UINavigationBar+BRAdd.swift @@ -0,0 +1,88 @@ +// +// UINavigationBar+BRAdd.swift +// BeeReel +// +// Created by 湖南秦九 on 2025/7/2. +// + +import UIKit + +extension UINavigationBar { + + static let br_normalTitleFont = UIFont.fontBold(ofSize: 16) + static var br_normalTitleColor: UIColor { + get { + return .colorFFFFFF() + } + } + + /** + 默认标题样式 + */ + static var br_normalTitleTextAttributes: [NSAttributedString.Key : Any] { + get { + return [ + NSAttributedString.Key.font : br_normalTitleFont, + NSAttributedString.Key.foregroundColor : br_normalTitleColor + ] + } + } + + /** + 默认背景色 + */ + static var br_normalBackgroundColor: UIColor { + get { + return .black + } + } + + @available(iOS 13.0, *) + static let navBarAppearance: UINavigationBarAppearance = { + let navBarAppearance = UINavigationBarAppearance() + navBarAppearance.configureWithOpaqueBackground() + // 背景色 + navBarAppearance.backgroundColor = br_normalBackgroundColor + // 去掉半透明效果 + navBarAppearance.backgroundEffect = nil + // 去除导航栏阴影(如果不设置clear,导航栏底下会有一条阴影线) + navBarAppearance.shadowColor = UIColor.clear + // 字体颜色 + navBarAppearance.titleTextAttributes = br_normalTitleTextAttributes + + return navBarAppearance + }() + + + func br_setTranslucent(isTranslucent: Bool) { + self.isTranslucent = isTranslucent + } + + func br_setBackgroundColor(backgroundColor: UIColor?) { + if #available(iOS 15.0, *) { + UINavigationBar.navBarAppearance.backgroundColor = backgroundColor + self.standardAppearance = UINavigationBar.navBarAppearance + self.scrollEdgeAppearance = UINavigationBar.navBarAppearance + } + + if let backgroundColor = backgroundColor { + self.setBackgroundImage(UIImage(color: backgroundColor), for: .default) + } else { + self.setBackgroundImage(UIImage(), for: .default) + } + } + + func br_setTitleTextAttributes(titleTextAttributes: [NSAttributedString.Key : Any]?) { + + if #available(iOS 15.0, *) { + if let titleTextAttributes = titleTextAttributes { + UINavigationBar.navBarAppearance.titleTextAttributes = titleTextAttributes + } + self.scrollEdgeAppearance = UINavigationBar.navBarAppearance + self.standardAppearance = UINavigationBar.navBarAppearance + } else { + + self.titleTextAttributes = titleTextAttributes + } + } +} diff --git a/BeeReel/Base/Network/API/BRVideoAPI.swift b/BeeReel/Base/Network/API/BRVideoAPI.swift index 782ee7e..0eb1911 100644 --- a/BeeReel/Base/Network/API/BRVideoAPI.swift +++ b/BeeReel/Base/Network/API/BRVideoAPI.swift @@ -66,6 +66,28 @@ class BRVideoAPI { } } } + + ///推荐短剧 + static func requestExploreVideo(page: Int, revolution: BRShortModel.VideoRevolution? = nil, completer: ((_ listModel: BRListModel?) -> Void)?) { + + var parameters: [String : Any] = [ + "page_size" : 20, + "current_page" : page + ] + + if let revolution = revolution?.rawValue { + parameters["revolution"] = revolution + } + + var param = BRNetworkParameters(path: "/getRecommands") + param.method = .get + param.parameters = parameters + + + BRNetwork.request(parameters: param) { (response: BRNetworkResponse>) in + completer?(response.data) + } + } } diff --git a/BeeReel/Class/Explore/Controller/BRExploreViewController.swift b/BeeReel/Class/Explore/Controller/BRExploreViewController.swift new file mode 100644 index 0000000..45b366c --- /dev/null +++ b/BeeReel/Class/Explore/Controller/BRExploreViewController.swift @@ -0,0 +1,123 @@ +// +// BRExploreViewController.swift +// BeeReel +// +// Created by 湖南秦九 on 2025/7/2. +// + +import UIKit + +class BRExploreViewController: BRPlayerListViewController { + + + private lazy var dataArr: [BRShortModel] = [] + private lazy var page: Int = 1 + private var pagination: BRListPaginationModel? + + override var CellClass: BRPlayerListCell.Type { + return BRExplorePlayerCell.self + } + + override var contentSize: CGSize { + let width = UIScreen.width - 30 + let height = UIScreen.height - UIScreen.customTabBarHeight - 40 - 50 - UIScreen.statusBarHeight + return CGSize(width: width, height: height) + } + + override func viewDidLoad() { + super.viewDidLoad() + self.edgesForExtendedLayout = .all + self.delegate = self + self.dataSource = self + + self.requestDataArr(page: 1) + + br_setupUI() + } + + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.setNavigationBarHidden(true, animated: true) + } + + + func addDataArr(dataArr: [BRShortModel]) { + guard dataArr.count > 0 else { return } + + var indexPaths: [IndexPath] = [] + var startRow = self.dataArr.count + + dataArr.forEach { _ in + indexPaths.append(IndexPath(row: startRow, section: 0)) + startRow += 1 + } + self.dataArr += dataArr + + CATransaction.setCompletionBlock(nil) + CATransaction.begin() + self.collectionView.insertItems(at: indexPaths) + CATransaction.commit() + } +} + +extension BRExploreViewController { + + private func br_setupUI() { + self.view.backgroundColor = .color1C1C1C() + self.collectionView.layer.cornerRadius = 20 + self.collectionView.layer.masksToBounds = true + + self.collectionView.snp.remakeConstraints { make in + make.left.equalToSuperview().offset(15) + make.top.equalToSuperview().offset(UIScreen.statusBarHeight + 50) + make.width.equalTo(contentSize.width) + make.height.equalTo(contentSize.height) + } + } + +} + + +//MARK: -------------- BRPlayerListViewControllerDelegate BRPlayerListViewControllerDataSource -------------- +extension BRExploreViewController: BRPlayerListViewControllerDelegate, BRPlayerListViewControllerDataSource { + + func br_playerListViewController(_ viewController: BRPlayerListViewController, _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath, oldCell: UICollectionViewCell) -> UICollectionViewCell { + let model = dataArr[indexPath.row] + if let cell = oldCell as? BRPlayerListCell { + cell.shortModel = model + cell.videoInfo = model.video_info + } + + return oldCell + } + + func br_playerListViewController(_ viewController: BRPlayerListViewController, _ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return dataArr.count + } +} + + +extension BRExploreViewController { + + private func requestDataArr(page: Int, completer: (() -> Void)? = nil) { + + BRVideoAPI.requestExploreVideo(page: page, revolution: BRVideoRevolutionManager.manager.revolution) { [weak self] listModel in + guard let self = self else { return } + self.collectionView.isHidden = false + if let listModel = listModel, let list = listModel.list { + if page == 1 { + self.dataArr = list + self.clearData() + self.reloadData() + } else { + self.addDataArr(dataArr: list) + } + self.pagination = listModel.pagination + } + completer?() + } + + } + +} + diff --git a/BeeReel/Class/Explore/View/BRExploreControlView.swift b/BeeReel/Class/Explore/View/BRExploreControlView.swift new file mode 100644 index 0000000..380ee5d --- /dev/null +++ b/BeeReel/Class/Explore/View/BRExploreControlView.swift @@ -0,0 +1,96 @@ +// +// BRExploreControlView.swift +// BeeReel +// +// Created by 湖南秦九 on 2025/7/3. +// + +import UIKit + +class BRExploreControlView: BRPlayerControlView { + + + override var videoInfo: BRVideoInfoModel? { + didSet { + } + } + + override var shortModel: BRShortModel? { + didSet { + hotView.setNeedsUpdateConfiguration() + } + } + + private lazy var hotView: UIButton = { + var config = UIButton.Configuration.plain() + config.image = UIImage(named: "hot_icon_03") + config.imagePadding = 3 + config.imagePlacement = .leading + config.contentInsets = .init(top: 0, leading: 10, bottom: 0, trailing: 10) + + let button = UIButton(configuration: config) + button.isUserInteractionEnabled = false + button.backgroundColor = .color5A5D45(alpha: 0.2) + button.layer.cornerRadius = 17 + button.layer.masksToBounds = true + button.configurationUpdateHandler = { [weak self] button in + guard let self = self else { return } + let count = shortModel?.watch_total ?? 0 + var string = "\(count)" + if count >= 1000 { + let num = NSNumber(value: Float(count) / 1000) + string = num.br_toString(maximumFractionDigits: 1, minimumFractionDigits: 0) + "k" + } + button.configuration?.attributedTitle = AttributedString.br_createAttributedString(string: string, color: .colorFFFFFF(), font: .fontMedium(ofSize: 12)) + } + return button + }() + + private lazy var fullButton: UIButton = { + let button = UIButton(type: .custom) + button.setImage(UIImage(named: "full_icon_01"), for: .normal) + button.setBackgroundImage(UIImage(color: .color5A5D45(alpha: 0.2)), for: .normal) + button.layer.cornerRadius = 17 + button.layer.masksToBounds = true + button.addTarget(self, action: #selector(handleFullButton), for: .touchUpInside) + return button + }() + + override init(frame: CGRect) { + super.init(frame: frame) + br_setupUI() + } + + @MainActor required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + @objc private func handleFullButton() { + let vc = BRVideoDetailViewController() + vc.shortPlayId = self.shortModel?.short_play_id + self.viewController?.navigationController?.pushViewController(vc, animated: true) + } + +} + +extension BRExploreControlView { + + private func br_setupUI() { + addSubview(hotView) + addSubview(fullButton) + + hotView.snp.makeConstraints { make in + make.right.equalToSuperview().offset(-10) + make.top.equalToSuperview().offset(10) + make.height.equalTo(34) + } + + fullButton.snp.makeConstraints { make in + make.left.equalToSuperview().offset(10) + make.centerY.equalTo(hotView) + make.width.equalTo(40) + make.height.equalTo(34) + } + } + +} diff --git a/BeeReel/Class/Explore/View/BRExplorePlayerCell.swift b/BeeReel/Class/Explore/View/BRExplorePlayerCell.swift new file mode 100644 index 0000000..e908931 --- /dev/null +++ b/BeeReel/Class/Explore/View/BRExplorePlayerCell.swift @@ -0,0 +1,17 @@ +// +// BRExplorePlayerCell.swift +// BeeReel +// +// Created by 湖南秦九 on 2025/7/3. +// + +import UIKit + +class BRExplorePlayerCell: BRPlayerListCell { + + override var ControlViewClass: BRPlayerControlView.Type { + return BRExploreControlView.self + } + + +} diff --git a/BeeReel/Class/Player/Controller/BRPlayerListViewController.swift b/BeeReel/Class/Player/Controller/BRPlayerListViewController.swift index 6d5d456..df80a45 100644 --- a/BeeReel/Class/Player/Controller/BRPlayerListViewController.swift +++ b/BeeReel/Class/Player/Controller/BRPlayerListViewController.swift @@ -6,6 +6,7 @@ // import UIKit +import SJMediaCacheServer @objc protocol BRPlayerListViewControllerDelegate { @@ -44,11 +45,18 @@ class BRPlayerListViewController: BRViewController { return BRPlayerListCell.self } - private(set) lazy var viewModel = BRPlayerViewModel() + private(set) lazy var viewModel: BRPlayerViewModel = { + let vm = BRPlayerViewModel() + vm.delegate = self + return vm + }() weak var delegate: BRPlayerListViewControllerDelegate? weak var dataSource: BRPlayerListViewControllerDataSource? + ///预加载 + private var prePrefetchTask: MCSPrefetchTask? + private var nextPrefetchTask: MCSPrefetchTask? private lazy var collectionViewLayout: UICollectionViewLayout = { let layout = UICollectionViewFlowLayout() @@ -67,13 +75,19 @@ class BRPlayerListViewController: BRViewController { collectionView.showsHorizontalScrollIndicator = false collectionView.bounces = false collectionView.scrollsToTop = false - collectionView.register(CellClass.self, forCellWithReuseIdentifier: "cell") + collectionView.register(CellClass.self, forCellWithReuseIdentifier: "playerCell") return collectionView }() + deinit { + self.prePrefetchTask?.cancel() + self.nextPrefetchTask?.cancel() + self.collectionView.removeFromSuperview() + } + override func viewDidLoad() { super.viewDidLoad() - + self.statusBarStyle = .lightContent br_setupUI() } @@ -81,6 +95,30 @@ class BRPlayerListViewController: BRViewController { super.viewWillAppear(animated) } + override func viewDidAppear(_ animated: Bool) { + super.viewDidAppear(animated) + + if self.viewModel.isPlaying { + self.viewModel.currentPlayer?.start() + } + } + + override func viewDidDisappear(_ animated: Bool) { + super.viewDidDisappear(animated) + self.viewModel.currentPlayer?.pause() + + + DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in + guard let self = self else { return } + self.scrollDidEnd(self.collectionView) + } + } + + ///返回上个视频路径 需要子类重写 + var previousVideoUrl: String? { return nil } + ///返回下个视频路径 + var nextVideoUrl: String? { return nil } + func play() { if self.isDidAppear { self.viewModel.currentPlayer?.start() @@ -92,6 +130,47 @@ class BRPlayerListViewController: BRViewController { // self.loadMoreData() // } } + + func reloadData(completion: (() -> Void)? = nil) { + + UIView.performWithoutAnimation { + self.collectionView.reloadData() + } + + self.collectionView.performBatchUpdates(nil) { [weak self] finish in + guard let self = self else { return } + let cell = self.collectionView.cellForItem(at: viewModel.currentIndexPath) as? BRPlayerProtocol + self.viewModel.currentPlayer = cell + + completion?() + } + } + + func scrollToItem(indexPath: IndexPath, animated: Bool = true, completer: (() -> Void)? = nil) { + + UIView.performWithoutAnimation { + self.collectionView.scrollToItem(at: indexPath, at: .top, animated: animated); + } + + self.collectionView.performBatchUpdates(nil) { [weak self] _ in + guard let self = self else { return } + if !animated { + if viewModel.currentIndexPath != indexPath { + self.skip(indexPath: indexPath) + } else { + self.play() + } + } + completer?() + } + } + + func clearData() { + self.viewModel.currentPlayer = nil + self.viewModel.currentIndexPath = .init(row: 0, section: 0) + self.collectionView.contentOffset = .init(x: 0, y: 0) + self.collectionView.reloadData() + } } @@ -113,7 +192,7 @@ extension BRPlayerListViewController { extension BRPlayerListViewController: UICollectionViewDelegate, UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { - var cell = collectionView.dequeueReusableCell(withReuseIdentifier: "cell", for: indexPath) + var cell = collectionView.dequeueReusableCell(withReuseIdentifier: "playerCell", for: indexPath) if let newCell = self.dataSource?.br_playerListViewController(self, collectionView, cellForItemAt: indexPath, oldCell: cell) { cell = newCell } @@ -123,10 +202,25 @@ extension BRPlayerListViewController: UICollectionViewDelegate, UICollectionView } } + if self.viewModel.currentPlayer == nil, indexPath == viewModel.currentIndexPath, let playerProtocol = cell as? BRPlayerProtocol { + viewModel.currentIndexPath = indexPath + self.viewModel.currentPlayer = playerProtocol + didChangeIndexPathForVisible() + } return cell } + func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) { + + self.prePrefetchTask?.cancel() + self.nextPrefetchTask?.cancel() + + self.prePrefetchTask = self.prefetchTask(url: self.previousVideoUrl) + self.nextPrefetchTask = self.prefetchTask(url: self.nextVideoUrl) + + } + func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { if let count = self.dataSource?.br_playerListViewController(self, collectionView, numberOfItemsInSection: section) { return count @@ -134,6 +228,82 @@ extension BRPlayerListViewController: UICollectionViewDelegate, UICollectionView return 0 } } + + //滑动停止 + func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { + scrollDidEnd(scrollView) + brLog(message: "scrollViewDidEndDecelerating+++++++++++++++") + } + + func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { + scrollDidEnd(scrollView) + brLog(message: "scrollViewDidEndScrollingAnimation+++++++++++++++") + } + + func scrollViewDidEndDragging(_ scrollView: UIScrollView, willDecelerate decelerate: Bool) { +// if !decelerate { +// scrollDidEnd(scrollView) +// } + brLog(message: "scrollViewDidEndDragging+++++++++++++++\(decelerate)") + } + + private func scrollDidEnd(_ scrollView: UIScrollView) { + let offsetY = scrollView.contentOffset.y + let indexPaths = self.collectionView.indexPathsForVisibleItems + for indexPath in indexPaths { + guard let cell = self.collectionView.cellForItem(at: indexPath) else { continue } + if floor(offsetY) == floor(cell.frame.origin.y) { + if viewModel.currentIndexPath != indexPath { + self.skip(indexPath: indexPath) + } + } + } + } + + private func skip(indexPath: IndexPath) { + guard let currentPlayer = self.collectionView.cellForItem(at: indexPath) as? BRPlayerProtocol else { return } + viewModel.currentIndexPath = indexPath + self.viewModel.currentPlayer = currentPlayer + didChangeIndexPathForVisible() + self.play() + } +} + +//MARK: -------------- BRPlayerViewModelDelegate -------------- +extension BRPlayerListViewController: BRPlayerViewModelDelegate { + + func br_currentVideoPlayFinish(viewModel: BRPlayerViewModel) { + scrollToNextEpisode() + } + + +} + +extension BRPlayerListViewController { + + ///滑动至下一级 + private func scrollToNextEpisode() { + + var contentOffset = self.collectionView.contentOffset + + if hasNextEpisode() { + contentOffset.y = floor(contentOffset.y + self.contentSize.height) + self.collectionView.setContentOffset(contentOffset, animated: true) + } else { + self.viewModel.currentPlayer?.replay() + } + } + + + ///是否还有下一级 + private func hasNextEpisode() -> Bool { + let contentOffset = self.collectionView.contentOffset + let contentSize = self.collectionView.contentSize + if contentOffset.y >= contentSize.height - self.contentSize.height { + return false + } + return true + } } @@ -154,3 +324,11 @@ extension BRPlayerListViewController { self.delegate?.br_playerListViewController?(self, didChangeIndexPathForVisible: viewModel.currentIndexPath) } } + +//MARK: -------------- 预加载 -------------- +extension BRPlayerListViewController { + + private func prefetchTask(url: String?) -> MCSPrefetchTask? { + return BRPlayerCache.prefetch(urlString: url) + } +} diff --git a/BeeReel/Class/Player/Controller/BRVideoDetailViewController.swift b/BeeReel/Class/Player/Controller/BRVideoDetailViewController.swift index dc1c04e..50cdaa6 100644 --- a/BeeReel/Class/Player/Controller/BRVideoDetailViewController.swift +++ b/BeeReel/Class/Player/Controller/BRVideoDetailViewController.swift @@ -10,23 +10,91 @@ import UIKit class BRVideoDetailViewController: BRPlayerListViewController { + override var contentSize: CGSize { + return .init(width: UIScreen.width, height: UIScreen.height) + } + + override var CellClass: BRPlayerListCell.Type { + return BRDetailPlayerCell.self + } + var shortPlayId: String? var activityId: String? private var detailModel: BRVideoDetailModel? + + + //MARK: UI属性 + private lazy var backButton: UIButton = { + let button = UIButton(type: .custom) + button.setImage(UIImage(named: "nav_back_icon_01"), for: .normal) + button.addTarget(self, action: #selector(handleNavBack), for: .touchUpInside) + return button + }() override func viewDidLoad() { super.viewDidLoad() - + self.delegate = self + self.dataSource = self self.requestDetailData() + + br_setupUI() } + override func viewWillAppear(_ animated: Bool) { + super.viewWillAppear(animated) + self.navigationController?.setNavigationBarHidden(true, animated: true) + } + override var previousVideoUrl: String? { + let index = self.viewModel.currentIndexPath.row - 1 + guard index > 0 else { return nil } + return detailModel?.episodeList?[index].video_url + } + + override var nextVideoUrl: String? { + let index = self.viewModel.currentIndexPath.row + 1 + guard index < (detailModel?.episodeList?.count ?? 0) else { return nil } + return detailModel?.episodeList?[index].video_url + } } +extension BRVideoDetailViewController { + + private func br_setupUI() { + view.addSubview(backButton) + + backButton.snp.makeConstraints { make in + make.left.equalToSuperview().offset(15) + make.top.equalToSuperview().offset(UIScreen.statusBarHeight + 10) + } + } + +} + + + +//MARK: -------------- BRPlayerListViewControllerDataSource BRPlayerListViewControllerDelegate -------------- +extension BRVideoDetailViewController: BRPlayerListViewControllerDataSource, BRPlayerListViewControllerDelegate { + + func br_playerListViewController(_ viewController: BRPlayerListViewController, _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath, oldCell: UICollectionViewCell) -> UICollectionViewCell { + + if let cell = oldCell as? BRPlayerListCell { + cell.videoInfo = self.detailModel?.episodeList?[indexPath.row] + cell.shortModel = self.detailModel?.shortPlayInfo + } + + return oldCell + } + + func br_playerListViewController(_ viewController: BRPlayerListViewController, _ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { + return self.detailModel?.episodeList?.count ?? 0 + } + +} extension BRVideoDetailViewController { @@ -43,32 +111,30 @@ extension BRVideoDetailViewController { guard let self = self else { return } guard let model = model else { return } self.detailModel = model -// self.videoNameLabel.text = model.shortPlayInfo?.name - /* self.reloadData { [weak self] in guard let self = self else { return } - - if let indexPath = indexPath { - self.scrollToItem(indexPath: indexPath, animated: false) - - } else if let videoInfo = self.detailModel?.video_info { - var row: Int? - self.detailModel?.episodeList?.enumerated().forEach({ - if $1.id == videoInfo.id { - row = $0 - } - }) - if let row = row { - self.scrollToItem(indexPath: .init(row: row, section: 0), animated: false) - } else { - self.scrollToItem(indexPath: .init(row: 0, section: 0), animated: false) - } - } else { - self.scrollToItem(indexPath: .init(row: 0, section: 0), animated: false) - } + self.play() +// if let indexPath = indexPath { +// self.scrollToItem(indexPath: indexPath, animated: false) +// +// } else if let videoInfo = self.detailModel?.video_info { +// var row: Int? +// self.detailModel?.episodeList?.enumerated().forEach({ +// if $1.id == videoInfo.id { +// row = $0 +// } +// }) +// if let row = row { +// self.scrollToItem(indexPath: .init(row: row, section: 0), animated: false) +// } else { +// self.scrollToItem(indexPath: .init(row: 0, section: 0), animated: false) +// } +// } else { +// self.scrollToItem(indexPath: .init(row: 0, section: 0), animated: false) +// } } - */ + } } diff --git a/BeeReel/Class/Player/Model/BRPlayerControlProtocol.swift b/BeeReel/Class/Player/Model/BRPlayerControlProtocol.swift new file mode 100644 index 0000000..80f4bd9 --- /dev/null +++ b/BeeReel/Class/Player/Model/BRPlayerControlProtocol.swift @@ -0,0 +1,16 @@ +// +// BRPlayerControlProtocol.swift +// BeeReel +// +// Created by 湖南秦九 on 2025/7/3. +// + + +import UIKit + +@objc protocol BRPlayerControlProtocol { + + ///点击事件 + func singleTapEvent() + +} diff --git a/BeeReel/Class/Player/Model/BRPlayerProtocol.swift b/BeeReel/Class/Player/Model/BRPlayerProtocol.swift index 54ba3b7..528198d 100644 --- a/BeeReel/Class/Player/Model/BRPlayerProtocol.swift +++ b/BeeReel/Class/Player/Model/BRPlayerProtocol.swift @@ -33,6 +33,9 @@ import UIKit ///暂停播放 func pause() + ///停止播放 + func stop() + ///从头播放 func replay() diff --git a/BeeReel/Class/Player/View/BRDetailControlView.swift b/BeeReel/Class/Player/View/BRDetailControlView.swift new file mode 100644 index 0000000..d833cea --- /dev/null +++ b/BeeReel/Class/Player/View/BRDetailControlView.swift @@ -0,0 +1,20 @@ +// +// BRDetailControlView.swift +// BeeReel +// +// Created by 湖南秦九 on 2025/7/3. +// + +import UIKit + +class BRDetailControlView: BRPlayerControlView { + + /* + // Only override draw() if you perform custom drawing. + // An empty implementation adversely affects performance during animation. + override func draw(_ rect: CGRect) { + // Drawing code + } + */ + +} diff --git a/BeeReel/Class/Player/View/BRDetailPlayerCell.swift b/BeeReel/Class/Player/View/BRDetailPlayerCell.swift new file mode 100644 index 0000000..8975b8d --- /dev/null +++ b/BeeReel/Class/Player/View/BRDetailPlayerCell.swift @@ -0,0 +1,16 @@ +// +// BRDetailPlayerCell.swift +// BeeReel +// +// Created by 湖南秦九 on 2025/7/3. +// + +import UIKit + +class BRDetailPlayerCell: BRPlayerListCell { + + override var ControlViewClass: BRPlayerControlView.Type { + return BRDetailControlView.self + } + +} diff --git a/BeeReel/Class/Player/View/BRPlayerControlView.swift b/BeeReel/Class/Player/View/BRPlayerControlView.swift new file mode 100644 index 0000000..1f454e9 --- /dev/null +++ b/BeeReel/Class/Player/View/BRPlayerControlView.swift @@ -0,0 +1,49 @@ +// +// BRPlayerControlView.swift +// BeeReel +// +// Created by 湖南秦九 on 2025/7/2. +// + +import UIKit + + +class BRPlayerControlView: UIView, BRPlayerControlProtocol { + + + + + + weak var viewModel: BRPlayerViewModel? { + didSet { + + } + } + + var shortModel: BRShortModel? { + didSet { + + } + } + + var videoInfo: BRVideoInfoModel? { + didSet { + + } + } + + + + override init(frame: CGRect) { + super.init(frame: frame) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + +//MARK: BRPlayerControlProtocol + func singleTapEvent() { + + } +} diff --git a/BeeReel/Class/Player/View/BRPlayerListCell.swift b/BeeReel/Class/Player/View/BRPlayerListCell.swift index 667d694..1db766a 100644 --- a/BeeReel/Class/Player/View/BRPlayerListCell.swift +++ b/BeeReel/Class/Player/View/BRPlayerListCell.swift @@ -10,14 +10,22 @@ import UIKit class BRPlayerListCell: BRCollectionViewCell, BRPlayerProtocol { - var viewModel: BRPlayerViewModel? { + weak var viewModel: BRPlayerViewModel? { didSet { - + self.controlView.viewModel = viewModel + } + } + + var shortModel: BRShortModel? { + didSet { + self.controlView.shortModel = shortModel + self.player.coverImageView?.br_setImage(url: shortModel?.image_url) } } var videoInfo: BRVideoInfoModel? { didSet { + self.controlView.videoInfo = videoInfo player.setPlayUrl(url: videoInfo?.video_url ?? "") } } @@ -35,11 +43,15 @@ class BRPlayerListCell: BRCollectionViewCell, BRPlayerProtocol { } func start() { - + player.start() } func pause() { - + player.pause() + } + + func stop() { + player.stop() } func replay() { @@ -51,9 +63,15 @@ class BRPlayerListCell: BRCollectionViewCell, BRPlayerProtocol { } + var ControlViewClass: BRPlayerControlView.Type { + return BRPlayerControlView.self + } + private lazy var player: BRPlayer = { - let player = BRPlayer() + let player = BRPlayer(controlView: nil) player.playerView = self.playerView + player.delegate = self + return player }() @@ -62,6 +80,11 @@ class BRPlayerListCell: BRCollectionViewCell, BRPlayerProtocol { return view }() + private lazy var controlView: BRPlayerControlView = { + let view = ControlViewClass.init() + return view + }() + override init(frame: CGRect) { super.init(frame: frame) @@ -78,10 +101,21 @@ extension BRPlayerListCell { private func br_setupUI() { contentView.addSubview(playerView) + playerView.snp.makeConstraints { make in make.edges.equalToSuperview() } + + } + +} + +//MARK: -------------- BRPlayerDelegate -------------- +extension BRPlayerListCell: BRPlayerDelegate { + + func br_playerDidPlayFinish(_ player: BRPlayer) { + self.viewModel?.playFinish(player: self) } } diff --git a/BeeReel/Class/Player/ViewModel/BRPlayerViewModel.swift b/BeeReel/Class/Player/ViewModel/BRPlayerViewModel.swift index 04d063b..6b1643b 100644 --- a/BeeReel/Class/Player/ViewModel/BRPlayerViewModel.swift +++ b/BeeReel/Class/Player/ViewModel/BRPlayerViewModel.swift @@ -7,14 +7,22 @@ import UIKit + +@objc protocol BRPlayerViewModelDelegate { + + @objc optional func br_currentVideoPlayFinish(viewModel: BRPlayerViewModel) +} + + class BRPlayerViewModel: NSObject { + weak var delegate: BRPlayerViewModelDelegate? @objc dynamic var isPlaying: Bool = true var currentIndexPath = IndexPath(row: 0, section: 0) - var currentPlayer: BRPlayerProtocol? { + var currentPlayer: BRPlayerProtocol? { didSet { oldValue?.isCurrent = false oldValue?.pause() @@ -24,3 +32,14 @@ class BRPlayerViewModel: NSObject { } } } + + + +extension BRPlayerViewModel { + + func playFinish(player: BRPlayerProtocol) { + guard (player as? UICollectionViewCell) == (currentPlayer as? UICollectionViewCell) else { return } + self.delegate?.br_currentVideoPlayFinish?(viewModel: self) + } + +} diff --git a/BeeReel/Lib/Player/BRPlayer.swift b/BeeReel/Lib/Player/BRPlayer.swift index 5349a4b..8e9308f 100644 --- a/BeeReel/Lib/Player/BRPlayer.swift +++ b/BeeReel/Lib/Player/BRPlayer.swift @@ -9,15 +9,51 @@ import UIKit import SJBaseVideoPlayer -class BRPlayer { +@objc protocol BRPlayerDelegate: NSObjectProtocol { + ///更新当前总进度 + @objc optional func br_playerDurationDidChange(_ player: BRPlayer, duration: TimeInterval) + ///更新当前进度 + @objc optional func br_playerCurrentTimeDidChange(_ player: BRPlayer, time: TimeInterval) + + ///播放状态变化 +// @objc optional func vp_player(_ player: VPPlayer, playStateDidChanged state: VPPlayer.PlayState) + + ///加载状态发生变化 +// @objc optional func vp_player(_ player: VPPlayer, loadStateDidChange state: VPPlayer.LoadState) + + ///播放时间发生变化 +// @objc optional func vp_playTimeChanged(_ player: BRPlayer, currentTime: Int, duration: Int) + + ///显示首帧 +// @objc optional func br_firstRenderedStart(_ player: BRPlayer) + + ///准备完成 +// @objc optional func br_playerReadyToPlay(_ player: BRPlayer) + + ///播放完成 + @objc optional func br_playerDidPlayFinish(_ player: BRPlayer) + + ///缓冲完成 +// @objc optional func br_playLoadingEnd(_ player: BRPlayer) +} + +class BRPlayer: NSObject { private lazy var player: SJBaseVideoPlayer = { let player = SJBaseVideoPlayer() player.autoplayWhenSetNewAsset = false + player.resumePlaybackWhenAppDidEnterForeground = false + player.accurateSeeking = true + player.videoGravity = .resizeAspectFill +// player.disableVolumeSetting = true + player.rotationManager?.isDisabledAutorotation = true + player.controlLayerDataSource = self return player }() - var playerView: UIView? { + weak var delegate: BRPlayerDelegate? + + weak var playerView: UIView? { didSet { playerView?.addSubview(player.view) player.view.snp.makeConstraints { make in @@ -25,7 +61,14 @@ class BRPlayer { } } } + + private(set) weak var br_controlView: BRPlayerControlProtocol?; + var coverImageView: UIImageView? { + return self.player.presentView.placeholderImageView + } + + ///精确到秒 var duration: TimeInterval { return self.player.duration } @@ -34,14 +77,23 @@ class BRPlayer { return self.player.currentTime } - init() { + deinit { + brLog(message: "播放器销毁") + } + + init(controlView: BRPlayerControlProtocol?) { + super.init() + self.br_controlView = controlView + setupPlayer() } func setPlayUrl(url: String) { self.stop() guard let url = URL(string: url) else { return } - let asset = SJVideoPlayerURLAsset(url: url) + guard let proxyUrl = BRPlayerCache.proxyURL(url: url) else { return } + + let asset = SJVideoPlayerURLAsset(url: proxyUrl) self.player.urlAsset = asset } @@ -53,22 +105,34 @@ class BRPlayer { self.player.pause() } + ///用户暂停 + func pauseForUser() { + self.player.pauseForUser() + } + func stop() { self.player.stop() } func seek(toTime: Int) { - + self.player.seek(toTime: TimeInterval(toTime)) } } extension BRPlayer { private func setupPlayer() { + //设置支持的手势 + self.player.gestureController.supportedGestureTypes = .singleTap + self.player.gestureController.singleTapHandler = { [weak self] _, _ in + guard let self = self else { return } + self.br_controlView?.singleTapEvent() + } + //播放完成回调 self.player.playbackObserver.playbackDidFinishExeBlock = { [weak self] player in guard let self = self else { return } - + self.delegate?.br_playerDidPlayFinish?(self) } //播放状态改变 self.player.playbackObserver.playbackStatusDidChangeExeBlock = { [weak self] player in @@ -78,13 +142,23 @@ extension BRPlayer { //播放时长改变 self.player.playbackObserver.durationDidChangeExeBlock = { [weak self] player in guard let self = self else { return } - + self.delegate?.br_playerDurationDidChange?(self, duration: player.duration) } //播放进度改变 self.player.playbackObserver.currentTimeDidChangeExeBlock = { [weak self] player in guard let self = self else { return } - + self.delegate?.br_playerCurrentTimeDidChange?(self, time: player.currentTime) } + + + } } + +//MARK: -------------- SJVideoPlayerControlLayerDataSource -------------- +extension BRPlayer: SJVideoPlayerControlLayerDataSource { + func controlView() -> UIView! { + return self.br_controlView as? UIView + } +} diff --git a/BeeReel/Lib/Player/BRPlayerCache.swift b/BeeReel/Lib/Player/BRPlayerCache.swift new file mode 100644 index 0000000..92982be --- /dev/null +++ b/BeeReel/Lib/Player/BRPlayerCache.swift @@ -0,0 +1,28 @@ +// +// BRPlayerCache.swift +// BeeReel +// +// Created by 湖南秦九 on 2025/7/2. +// + +import UIKit +import SJMediaCacheServer + +class BRPlayerCache { + + static func proxyURL(url: URL) -> URL? { + return SJMediaCacheServer.shared().proxyURL(from: url) + } + + ///预加载数据 + static func prefetch(url: URL?) -> MCSPrefetchTask? { + guard let url = url else { return nil } + return SJMediaCacheServer.shared().prefetch(with: url, prefetchSize: 1 * 1024 * 1024) + } + + static func prefetch(urlString: String?) -> MCSPrefetchTask? { + guard let str = urlString else { return nil } + return prefetch(url: URL(string: str)) + } + +} diff --git a/BeeReel/Sources/Assets.xcassets/icon/full_icon_01.imageset/Contents.json b/BeeReel/Sources/Assets.xcassets/icon/full_icon_01.imageset/Contents.json new file mode 100644 index 0000000..5c4d3b1 --- /dev/null +++ b/BeeReel/Sources/Assets.xcassets/icon/full_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Frame@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Frame@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/BeeReel/Sources/Assets.xcassets/icon/full_icon_01.imageset/Frame@2x.png b/BeeReel/Sources/Assets.xcassets/icon/full_icon_01.imageset/Frame@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..01560e690f9623c54bf38481db385b5001011445 GIT binary patch literal 385 zcmeAS@N?(olHy`uVBq!ia0vp^8X(NU1|)m_?Z^dEoCO|{#S9E$svykh8Km+7D9BhG z^pg`zE{Xq(>i)>FAqq@TI&I+I4%q6)_&Qk5klsW0rcCnTSi zN!K`a;kfCE!X-N@@8xS`c?d?GnzQJu&Sy;%_oB}GPhMOM5P5fXs){nJwTzv}oEW0}%#1th>c(8PP>lVKo=Ie@+wK_OeYkE%ZENolUaO1|3 zb>hz#{4rX$-7MK%ZRHd8>319=*4_W1-Es1Q=V9gRUZO{y^jdvWv+)!Ve<6_L7_*{w zBa7kDd$+V3iq|y#2oab(d9#Xcx})9PB|ict7H}WfA&~guQC@y*wZwD3t7mpOX?1<- b-@`EXU*FmP2W)==LyW=G)z4*}Q$iB}<#wN7 literal 0 HcmV?d00001 diff --git a/BeeReel/Sources/Assets.xcassets/icon/full_icon_01.imageset/Frame@3x.png b/BeeReel/Sources/Assets.xcassets/icon/full_icon_01.imageset/Frame@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..f053f0ba533d35ed0d3b76381dc7f007049ec1a4 GIT binary patch literal 485 zcmVE3nU2S zkAb`Z;GA>LIrm6H2uB)y$ag_aXoWvRxmU?^Jwb;N>Y7yNhZ_gH6OTNwAq%wiIkCmZgHt#j;z$CS%!Lu-RBP4K^LiCQ*awSQeIb zu*6cV=F28HXs|Q(w%6G;}4lO{gvlV7Z8$P zB!L6K9ScOP$qf902gsu$JCW!c{0Aho#|vw?H6(JvIk&5nXNT8}xiig~VeRpiWUbs# zN%jhV1ufQNm`Y-}<q}L#yC@z-I6wQ;`<~jvl|nFeErEf?{Wout4%HH=$v!i z6U;(W0)|`JW13Mb*h#`ZvREUQqS98EqLKKNXsfL*#Y&ko#>7lQgj+m4ttO)@vscXC zX;u(tCw44~VwjwCd}LoFFDPgtNdql>px?8Ix@;o3XRnt^-$vR}ONHpckK`p`~*KQpySKq+XbToZBg%uo{@Op!Ji7HE$;9K(DUfzZB$t} z-#uDTf|t*a9vA#QH90MnaV!(F7BR9wsfew~J~Xi!VU3^xCEw*5Z=KA^kqrO<002ov JPDHLkV1lb~!YBX$ literal 0 HcmV?d00001 diff --git a/BeeReel/Sources/Assets.xcassets/icon/hot_icon_03.imageset/Frame@3x.png b/BeeReel/Sources/Assets.xcassets/icon/hot_icon_03.imageset/Frame@3x.png new file mode 100644 index 0000000000000000000000000000000000000000..9b6a7856f775047c2f23c2a81a92276492cff67a GIT binary patch literal 646 zcmV;10(t$3P)!(^-iFjNMEGY%8^=oxM z!|li^25G3b816OQM9z~H!ikG2+Fe`+R4#{0hB&cdGXUDr3?)}SV#GREtS3i#$|u@M z{)|SWqd2`#7&-e*@W-MRi@$k*c^x8jjWAZ)b!qYUjIbfV8>fLzOC{t&;qV#qU664^ zhIV_Q><+$4z+cU*KFnltR%GEQ5@w^Wf5fp-3FP9F5-^31iO*KMmYKfryuT*~p2nU5 zKSL|Sj+Fq6?mxI}&4`|e$A^9+qQJHL_B{V04&bdwYl=kJZals-m#rC5*bv?AL^>T0HiYjjmg|qMl;7X`6aCPsI`bYNCoMM4}swyY} zVZSq{BlNKgwTN9&0%(@n{zPid<<{adQAcmW(ClcF8|oTPAa+u gMS$~nYexT_U&MfU{><9_O8@`>07*qoM6N<$f;?v(`~Uy| literal 0 HcmV?d00001 diff --git a/BeeReel/Sources/Assets.xcassets/icon/nav_back_icon_01.imageset/Contents.json b/BeeReel/Sources/Assets.xcassets/icon/nav_back_icon_01.imageset/Contents.json new file mode 100644 index 0000000..725eae5 --- /dev/null +++ b/BeeReel/Sources/Assets.xcassets/icon/nav_back_icon_01.imageset/Contents.json @@ -0,0 +1,22 @@ +{ + "images" : [ + { + "idiom" : "universal", + "scale" : "1x" + }, + { + "filename" : "Frame 1498@2x.png", + "idiom" : "universal", + "scale" : "2x" + }, + { + "filename" : "Frame 1498@3x.png", + "idiom" : "universal", + "scale" : "3x" + } + ], + "info" : { + "author" : "xcode", + "version" : 1 + } +} diff --git a/BeeReel/Sources/Assets.xcassets/icon/nav_back_icon_01.imageset/Frame 1498@2x.png b/BeeReel/Sources/Assets.xcassets/icon/nav_back_icon_01.imageset/Frame 1498@2x.png new file mode 100644 index 0000000000000000000000000000000000000000..375883e0ea915bd7cfcbc154be06c41f2150e148 GIT binary patch literal 4862 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 BZtDO5 literal 0 HcmV?d00001 diff --git a/Podfile b/Podfile index 40b4c58..4178169 100644 --- a/Podfile +++ b/Podfile @@ -23,6 +23,7 @@ target 'BeeReel' do pod 'Toast' #吐司提示 # pod 'YYKit' #工具类 pod 'SJVideoPlayer' #播放器 + pod 'SJMediaCacheServer' #播放器缓存 pod 'WMZPageController' #分页控制器 pod 'YYCategories' pod 'YYText'