播放器功能开发

This commit is contained in:
zjx 2025-07-03 17:48:21 +08:00
parent 1914557261
commit 3d8dfd2fe8
31 changed files with 1139 additions and 42 deletions

View File

@ -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 = "<group>"; };
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 = "<group>"; };
BF3338E72E15218F00B10F76 /* UINavigationBar+BRAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UINavigationBar+BRAdd.swift"; sourceTree = "<group>"; };
BF3338E92E152B8100B10F76 /* BRPlayerCache.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRPlayerCache.swift; sourceTree = "<group>"; };
BF3338EB2E154BFE00B10F76 /* BRPlayerControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRPlayerControlView.swift; sourceTree = "<group>"; };
BF3338EF2E15569600B10F76 /* BRExploreViewController.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRExploreViewController.swift; sourceTree = "<group>"; };
BF3338F22E16169A00B10F76 /* BRExplorePlayerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRExplorePlayerCell.swift; sourceTree = "<group>"; };
BF3338F42E1616B200B10F76 /* BRExploreControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRExploreControlView.swift; sourceTree = "<group>"; };
BF3338F62E16176900B10F76 /* BRDetailPlayerCell.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRDetailPlayerCell.swift; sourceTree = "<group>"; };
BF3338F82E16178700B10F76 /* BRDetailControlView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRDetailControlView.swift; sourceTree = "<group>"; };
BF3338FA2E161CF000B10F76 /* NSNumber+BRAdd.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSNumber+BRAdd.swift"; sourceTree = "<group>"; };
BF3338FC2E1626A500B10F76 /* BRPlayerControlProtocol.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = BRPlayerControlProtocol.swift; sourceTree = "<group>"; };
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 = "<group>"; };
BF692AE22E0A475D00A5C2DA /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
@ -233,6 +253,32 @@
path = Pods;
sourceTree = "<group>";
};
BF3338ED2E15566C00B10F76 /* Explore */ = {
isa = PBXGroup;
children = (
BF3338EE2E15567200B10F76 /* Controller */,
BF3338F12E16167B00B10F76 /* View */,
);
path = Explore;
sourceTree = "<group>";
};
BF3338EE2E15567200B10F76 /* Controller */ = {
isa = PBXGroup;
children = (
BF3338EF2E15569600B10F76 /* BRExploreViewController.swift */,
);
path = Controller;
sourceTree = "<group>";
};
BF3338F12E16167B00B10F76 /* View */ = {
isa = PBXGroup;
children = (
BF3338F22E16169A00B10F76 /* BRExplorePlayerCell.swift */,
BF3338F42E1616B200B10F76 /* BRExploreControlView.swift */,
);
path = View;
sourceTree = "<group>";
};
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 = "<group>";
@ -472,6 +522,9 @@
isa = PBXGroup;
children = (
BF692B592E0AAADD00A5C2DA /* BRPlayerListCell.swift */,
BF3338EB2E154BFE00B10F76 /* BRPlayerControlView.swift */,
BF3338F62E16176900B10F76 /* BRDetailPlayerCell.swift */,
BF3338F82E16178700B10F76 /* BRDetailControlView.swift */,
);
path = View;
sourceTree = "<group>";
@ -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 */,

View File

@ -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"))

View File

@ -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)
}
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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
}
}
}

View File

@ -66,6 +66,28 @@ class BRVideoAPI {
}
}
}
///
static func requestExploreVideo(page: Int, revolution: BRShortModel.VideoRevolution? = nil, completer: ((_ listModel: BRListModel<BRShortModel>?) -> 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<BRListModel<BRShortModel>>) in
completer?(response.data)
}
}
}

View File

@ -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?()
}
}
}

View File

@ -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)
}
}
}

View File

@ -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
}
}

View File

@ -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)
}
}

View File

@ -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)
// }
}
*/
}
}

View File

@ -0,0 +1,16 @@
//
// BRPlayerControlProtocol.swift
// BeeReel
//
// Created by on 2025/7/3.
//
import UIKit
@objc protocol BRPlayerControlProtocol {
///
func singleTapEvent()
}

View File

@ -33,6 +33,9 @@ import UIKit
///
func pause()
///
func stop()
///
func replay()

View File

@ -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
}
*/
}

View File

@ -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
}
}

View File

@ -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() {
}
}

View File

@ -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)
}
}

View File

@ -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)
}
}

View File

@ -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
}
}

View File

@ -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))
}
}

View File

@ -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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 385 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 485 B

View File

@ -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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 467 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 646 B

View File

@ -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
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 4.7 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 9.2 KiB

View File

@ -23,6 +23,7 @@ target 'BeeReel' do
pod 'Toast' #吐司提示
# pod 'YYKit' #工具类
pod 'SJVideoPlayer' #播放器
pod 'SJMediaCacheServer' #播放器缓存
pod 'WMZPageController' #分页控制器
pod 'YYCategories'
pod 'YYText'