ThimraTV/MoviaBox/Class/Player/Controller/SPPlayerListViewController.swift
2025-06-05 18:04:53 +08:00

421 lines
14 KiB
Swift
Raw Blame History

This file contains ambiguous Unicode characters

This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.

//
// SPPlayerListViewController.swift
// MoviaBox
//
// Created by on 2025/4/8.
//
import UIKit
import AVKit
@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 sp_playerListViewController(_ viewController: SPPlayerListViewController, didChangeIndexPathForVisible indexPath: IndexPath)
// @objc optional func sp_playerListViewController(_ viewController: SPPlayerListViewController, didScrollFromIndex fromIndex: Int, toIndex: Int)
///
// @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 isFirstPlay = true
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()
self.jx_popGestureType = .disabled
//
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()
// self.play()
}
}
override func viewDidDisappear(_ animated: Bool) {
super.viewDidDisappear(animated)
self.viewModel.currentPlayer?.pause()
///scrollViewDidEndDecelerating
DispatchQueue.main.asyncAfter(deadline: .now() + 0.2) { [weak self] in
guard let self = self else { return }
self.scrollDidEnd(self.collectionView)
}
}
func setDataArr(dataArr: [Any], completer: (() -> Void)?) {
clearDataArr()
self.dataArr = dataArr
reloadData(completion: completer)
}
func addDataArr(dataArr: [Any]) {
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()
}
func clearDataArr() {
self.dataArr.removeAll()
self.viewModel.currentPlayer = nil
self.currentIndexPath = .init(row: 0, section: 0)
self.collectionView.contentOffset = .init(x: 0, y: 0)
self.collectionView.reloadData()
}
func play() {
if self.isDidAppear {
self.viewModel.currentPlayer?.start()
}
self.viewModel.isPlaying = true
if getDataCount() - currentIndexPath.row <= 2 {
self.loadMoreData()
}
if isFirstPlay {
isFirstPlay = false
let offset = self.collectionView.contentOffset.y + 0.2
self.collectionView.setContentOffset(CGPoint(x: 0, y: offset), animated: false)
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) {
let offset = self.collectionView.contentOffset.y
self.collectionView.setContentOffset(CGPoint(x: 0, y: floor(offset)), animated: false)
}
}
}
func pause() {
self.viewModel.isPlaying = false
self.viewModel.currentPlayer?.pause()
}
func reloadData(completion: (() -> Void)? = nil) {
CATransaction.setCompletionBlock { [weak self] in
guard let self = self else { return }
let cell = self.collectionView.cellForItem(at: self.currentIndexPath) as? SPPlayerListCell
self.viewModel.currentPlayer = cell
completion?()
}
CATransaction.begin()
self.collectionView.reloadData()
CATransaction.commit()
}
func getDataCount() -> Int {
return self.collectionView(self.collectionView, numberOfItemsInSection: 0)
}
func scrollToItem(indexPath: IndexPath, animated: Bool = true, completer: (() -> Void)? = nil) {
CATransaction.setCompletionBlock { [weak self] in
guard let self = self else { return }
if !animated {
if self.currentIndexPath != indexPath {
self.skip(indexPath: indexPath)
} else {
self.play()
}
}
completer?()
}
CATransaction.begin()
self.collectionView.scrollToItem(at: indexPath, at: .top, animated: animated);
CATransaction.commit()
}
///
func currentPlayFinish() {
if self.autoNextEpisode {
scrollToNextEpisode()
}
}
///
func currentPlayTimeDidChange(time: Int) {
}
}
extension SPPlayerListViewController {
private func sp_setupUI() {
view.addSubview(collectionView)
collectionView.snp.makeConstraints { make in
// make.edges.equalToSuperview()
// make.center.equalToSuperview()
make.top.equalToSuperview()
make.left.equalToSuperview()
make.width.equalTo(self.contentSize.width)
make.height.equalTo(self.contentSize.height)
}
}
private func sp_addActio() {
self.viewModel.handlePauseOrPlay = { [weak self] in
self?.clickPauseOrPlay()
}
self.viewModel.handlePlayFinish = { [weak self] in
self?.currentPlayFinish()
}
self.viewModel.handlePlayTimeDidChange = { [weak self] time in
self?.currentPlayTimeDidChange(time: time)
}
}
}
extension SPPlayerListViewController {
///
private func clickPauseOrPlay() {
if self.viewModel.isPlaying {
self.pause()
} else {
self.play()
}
}
///
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
}
}
//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
didChangeIndexPathForVisible()
}
spLog(message: "++++++++\(indexPath)")
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 floor(offsetY) == floor(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
didChangeIndexPathForVisible()
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)
}
private func didChangeIndexPathForVisible() {
self.delegate?.sp_playerListViewController?(self, didChangeIndexPathForVisible: self.currentIndexPath)
}
}