// // SPPlayerListViewController.swift // ShortPlay // // Created by 曾觉新 on 2025/4/8. // import UIKit @objc protocol SPPlayerListViewControllerDelegate { ///加载新数据 @objc optional func sp_playerViewControllerLoadNewDataV2(playerViewController: SPPlayerListViewController) ///将要加载更多数据 @objc optional func sp_playerViewControllerShouldLoadMoreData(playerViewController: SPPlayerListViewController) -> Bool ///加载更多数据 @objc optional func sp_playerViewControllerLoadMoreData(playerViewController: SPPlayerListViewController) ///向上加载更多数据 @objc optional func sp_playerViewControllerLoadUpMoreData(playerViewController: SPPlayerListViewController) ///新页面展示完成 // @objc optional func yd_playerViewController(playerListViewController: BCListPlayerViewController, didShowPlayerPage playerViewController: YDBasePlayerViewController) } @objc protocol SPPlayerListViewControllerDataSource { func sp_playerListViewController(_ viewController: SPPlayerListViewController, _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath, oldCell: UICollectionViewCell) -> UICollectionViewCell func sp_playerListViewController(_ viewController: SPPlayerListViewController, _ collectionView: UICollectionView, numberOfItemsInSection section: Int, oldNumber: Int) -> Int } class SPPlayerListViewController: SPViewController { var contentSize: CGSize { return CGSize(width: kSPScreenWidth, height: kSPScreenHeight - kSPTabBarHeight) } var PlayerCellClass: SPPlayerListCell.Type { return SPPlayerListCell.self } weak var delegate: SPPlayerListViewControllerDelegate? weak var dataSource: SPPlayerListViewControllerDataSource? private(set) var dataArr: [Any] = [] var pagination: SPListPaginationModel? ///自动下一级 var autoNextEpisode = false private(set) var viewModel = SPPlayerListViewModel() private(set) var currentIndexPath = IndexPath(row: 0, section: 0) private lazy var collectionViewLayout: UICollectionViewLayout = { let layout = UICollectionViewFlowLayout() layout.itemSize = contentSize layout.minimumInteritemSpacing = 0 layout.minimumLineSpacing = 0 return layout }() private(set) lazy var collectionView: SPCollectionView = { let collectionView = SPCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout) collectionView.delegate = self collectionView.dataSource = self collectionView.isPagingEnabled = true collectionView.showsVerticalScrollIndicator = false collectionView.showsHorizontalScrollIndicator = false collectionView.bounces = false collectionView.scrollsToTop = false PlayerCellClass.registerCell(collectionView: collectionView) return collectionView }() deinit { NotificationCenter.default.removeObserver(self) } override func viewDidLoad() { super.viewDidLoad() //视频缓存 do { try? KTVHTTPCache.proxyStart() } NotificationCenter.default.addObserver(self, selector: #selector(didBecomeActiveNotification), name: UIApplication.didBecomeActiveNotification, object: nil) NotificationCenter.default.addObserver(self, selector: #selector(willResignActiveNotification), name: UIApplication.willResignActiveNotification, object: nil) sp_setupUI() sp_addActio() } override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) } override func viewDidAppear(_ animated: Bool) { super.viewDidAppear(animated) if getDataCount() > 0 && self.viewModel.isPlaying { self.viewModel.currentPlayer?.start() } } override func viewDidDisappear(_ animated: Bool) { super.viewDidDisappear(animated) self.viewModel.currentPlayer?.pause() } func setDataArr(dataArr: [Any]) { self.dataArr = dataArr reloadData() } func clearDataArr() { self.dataArr.removeAll() self.viewModel.currentPlayer = nil self.collectionView.reloadData() } func play() { if self.isDidAppear { self.viewModel.currentPlayer?.start() } self.viewModel.isPlaying = true if getDataCount() - currentIndexPath.row <= 2 { self.loadMoreData() } if currentIndexPath.row <= 2 { // self.loadUpMoreData() } } func reloadData() { CATransaction.begin() self.collectionView.reloadData() CATransaction.commit() } func getDataCount() -> Int { return self.collectionView(self.collectionView, numberOfItemsInSection: 0) } } extension SPPlayerListViewController { private func sp_setupUI() { view.addSubview(collectionView) collectionView.snp.makeConstraints { make in make.edges.equalToSuperview() } } private func sp_addActio() { self.viewModel.handlePauseOrPlay = { [weak self] in self?.clickPauseOrPlay() } self.viewModel.handlePlayFinish = { [weak self] in self?.currentPlayFinish() } } } extension SPPlayerListViewController { ///点击播放或暂停 private func clickPauseOrPlay() { // let model = self.dataArr[currentIndexPath.section] ///打开支付页面 // if self.onVideoPay() { // return // } if self.viewModel.isPlaying { self.viewModel.isPlaying = false self.viewModel.currentPlayer?.pause() } else { self.viewModel.isPlaying = true self.viewModel.currentPlayer?.start() } } ///当前播放完成 private func currentPlayFinish() { guard self.autoNextEpisode else { return } scrollToNextEpisode() } ///滑动至下一级 private func scrollToNextEpisode() { var contentOffset = self.collectionView.contentOffset if hasNextEpisode() { contentOffset.y = 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 } } //MARK: -------------- UICollectionViewDelegate & UICollectionViewDataSource -------------- extension SPPlayerListViewController: UICollectionViewDelegate, UICollectionViewDataSource { func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell { var cell: UICollectionViewCell = PlayerCellClass.dequeueReusableCell(collectionView: collectionView, indexPath: indexPath) if let newCell = self.dataSource?.sp_playerListViewController(self, collectionView, cellForItemAt: indexPath, oldCell: cell) { cell = newCell } if let cell = cell as? SPPlayerListCell { if cell.viewModel == nil { cell.viewModel = viewModel } // let model = dataArr[indexPath.row] // cell.model = model } if self.viewModel.currentPlayer == nil, indexPath == currentIndexPath, let playerProtocol = cell as? SPPlayerProtocol { self.currentIndexPath = indexPath self.viewModel.currentPlayer = playerProtocol } return cell } func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int { let count = dataArr.count if let newCount = self.dataSource?.sp_playerListViewController(self, collectionView, numberOfItemsInSection: section, oldNumber: count) { return newCount } else { return count } } //滑动停止 func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) { scrollDidEnd(scrollView) } func scrollViewDidEndScrollingAnimation(_ scrollView: UIScrollView) { scrollDidEnd(scrollView) } 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 offsetY == cell.frame.origin.y { if self.currentIndexPath != indexPath { self.skip(indexPath: indexPath) } } } } private func skip(indexPath: IndexPath) { currentIndexPath = indexPath guard let currentPlayer = self.collectionView.cellForItem(at: indexPath) as? SPPlayerProtocol else { return } self.viewModel.currentPlayer = currentPlayer // currentCell = self.collectionView.cellForItem(at: indexPath) as? BCListPlayerCell self.play() } } //MARK: -------------- APP生命周期 -------------- extension SPPlayerListViewController { @objc func didBecomeActiveNotification() { if getDataCount() > 0 && self.viewModel.isPlaying && isDidAppear { self.viewModel.currentPlayer?.start() } } @objc func willResignActiveNotification() { self.viewModel.currentPlayer?.pause() } } extension SPPlayerListViewController { private func loadMoreData() { let isLoad = self.delegate?.sp_playerViewControllerShouldLoadMoreData?(playerViewController: self) if isLoad != false { self.delegate?.sp_playerViewControllerLoadMoreData?(playerViewController: self) } } private func loadUpMoreData() { // DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in // guard let self = self else { return } // } self.delegate?.sp_playerViewControllerLoadUpMoreData?(playerViewController: self) } }