选集功能开发
@ -14,7 +14,6 @@ class AppDelegate: UIResponder, UIApplicationDelegate {
|
|||||||
|
|
||||||
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
func application(_ application: UIApplication, didFinishLaunchingWithOptions launchOptions: [UIApplication.LaunchOptionsKey: Any]?) -> Bool {
|
||||||
|
|
||||||
|
|
||||||
self.appConfig()
|
self.appConfig()
|
||||||
|
|
||||||
SPLoginManager.manager.requestVisitorLogin(completer: nil)
|
SPLoginManager.manager.requestVisitorLogin(completer: nil)
|
||||||
|
@ -26,3 +26,11 @@ extension String: SmartCodable {
|
|||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension String {
|
||||||
|
///获取文字Size
|
||||||
|
func size(font: UIFont, size: CGSize = CGSize(width: CGFloat(MAXFLOAT), height: CGFloat(MAXFLOAT))) -> CGSize{
|
||||||
|
let string: NSString = self as NSString
|
||||||
|
return string.size(for: font, size: size, mode: .byWordWrapping)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
@ -63,5 +63,7 @@ extension UIColor {
|
|||||||
static func colorF56490(alpha: CGFloat = 1) -> UIColor {
|
static func colorF56490(alpha: CGFloat = 1) -> UIColor {
|
||||||
return color(hex: 0xF56490, alpha: alpha)
|
return color(hex: 0xF56490, alpha: alpha)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -41,4 +41,37 @@ class SPVideoAPI: NSObject {
|
|||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
///收藏短剧
|
||||||
|
static func requestCollectShort(isCollect: Bool, shortPlayId: String, success: (() -> Void)?) {
|
||||||
|
let path: String
|
||||||
|
if isCollect {
|
||||||
|
path = "/collect"
|
||||||
|
} else {
|
||||||
|
path = "/cancelCollect"
|
||||||
|
}
|
||||||
|
|
||||||
|
var param = SPNetworkParameters(path: path)
|
||||||
|
param.isLoding = true
|
||||||
|
param.parameters = [
|
||||||
|
"short_play_id" : shortPlayId
|
||||||
|
]
|
||||||
|
|
||||||
|
SPNetwork.request(parameters: param) { (response: SPNetworkResponse<String>) in
|
||||||
|
if response.code == SPNetworkCodeSucceed {
|
||||||
|
success?()
|
||||||
|
NotificationCenter.default.post(name: SPVideoAPI.updateShortCollectStateNotification, object: nil, userInfo: [
|
||||||
|
"state" : isCollect,
|
||||||
|
"id" : shortPlayId,
|
||||||
|
])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SPVideoAPI {
|
||||||
|
///更新短剧关注状态 [ "state" : isCollect, "id" : shortPlayId,]
|
||||||
|
@objc static let updateShortCollectStateNotification = NSNotification.Name(rawValue: "SPVideoAPI.updateShortCollectStateNotification")
|
||||||
|
|
||||||
}
|
}
|
||||||
|
101
ShortPlay/Base/Networking/Base/CryptorService.swift
Normal file
@ -0,0 +1,101 @@
|
|||||||
|
//// CryptorService.swift
|
||||||
|
// QJDrama
|
||||||
|
//
|
||||||
|
// Created by Yao on 2025/4/16
|
||||||
|
//
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
class CryptorService {
|
||||||
|
// 定义常量
|
||||||
|
static let EN_STR_TAG: String = "$" // 替换为实际的加密标记
|
||||||
|
|
||||||
|
// 解密字符串
|
||||||
|
static func decrypt(data: String) -> String {
|
||||||
|
guard data.hasPrefix(EN_STR_TAG) else {
|
||||||
|
// fatalError("Invalid encoded string")
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
let decryptedData = deStrBytes(data: data)
|
||||||
|
return String(data: decryptedData, encoding: .utf8) ?? ""
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从十六进制字符串解密字节
|
||||||
|
static func deStrBytes(data: String) -> Data {
|
||||||
|
|
||||||
|
let hexData = String(data.dropFirst())
|
||||||
|
var bytes = Data()
|
||||||
|
|
||||||
|
var index = hexData.startIndex
|
||||||
|
while index < hexData.endIndex {
|
||||||
|
let nextIndex = hexData.index(index, offsetBy: 2, limitedBy: hexData.endIndex) ?? hexData.endIndex
|
||||||
|
let byteString = String(hexData[index..<nextIndex])
|
||||||
|
|
||||||
|
if let byte = UInt8(byteString, radix: 16) {
|
||||||
|
bytes.append(byte)
|
||||||
|
}
|
||||||
|
|
||||||
|
index = nextIndex
|
||||||
|
}
|
||||||
|
|
||||||
|
return de(data: bytes)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 解密数据
|
||||||
|
static func de(data: Data) -> Data {
|
||||||
|
guard !data.isEmpty else {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
let saltLen = Int(data[data.startIndex])
|
||||||
|
guard data.count >= 1 + saltLen else {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
let salt = data.subdata(in: 1..<1+saltLen)
|
||||||
|
let encryptedData = data.subdata(in: 1+saltLen..<data.count)
|
||||||
|
|
||||||
|
return deWithSalt(data: encryptedData, salt: salt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 使用盐值解密数据
|
||||||
|
static func deWithSalt(data: Data, salt: Data) -> Data {
|
||||||
|
let decryptedData = cxEd(data: data)
|
||||||
|
return removeSalt(data: decryptedData, salt: salt)
|
||||||
|
}
|
||||||
|
|
||||||
|
// 加密/解密数据(按位取反)
|
||||||
|
static func cxEd(data: Data) -> Data {
|
||||||
|
return Data(data.map { $0 ^ 0xFF })
|
||||||
|
}
|
||||||
|
|
||||||
|
// 从数据中移除盐值
|
||||||
|
static func removeSalt(data: Data, salt: Data) -> Data {
|
||||||
|
guard !salt.isEmpty else {
|
||||||
|
return data
|
||||||
|
}
|
||||||
|
|
||||||
|
var result = Data()
|
||||||
|
let saltBytes = [UInt8](salt)
|
||||||
|
let saltCount = saltBytes.count
|
||||||
|
|
||||||
|
for (index, byte) in data.enumerated() {
|
||||||
|
let saltByte = saltBytes[index % saltCount]
|
||||||
|
let decryptedByte = calRemoveSalt(v: byte, s: saltByte)
|
||||||
|
result.append(decryptedByte)
|
||||||
|
}
|
||||||
|
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
|
||||||
|
// 计算移除盐值后的字节
|
||||||
|
static func calRemoveSalt(v: UInt8, s: UInt8) -> UInt8 {
|
||||||
|
if v >= s {
|
||||||
|
return v - s
|
||||||
|
} else {
|
||||||
|
return UInt8(0xFF) - (s - v) + 1
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -34,11 +34,14 @@ class SPNetwork: NSObject {
|
|||||||
while loding {
|
while loding {
|
||||||
if !SPLoginManager.manager.isRefreshingToken {
|
if !SPLoginManager.manager.isRefreshingToken {
|
||||||
loding = false
|
loding = false
|
||||||
|
spLog(message: "======等待结束")
|
||||||
|
} else {
|
||||||
|
spLog(message: "======等待中")
|
||||||
}
|
}
|
||||||
RunLoop.current.run(mode: .default, before: Date.distantFuture)
|
RunLoop.current.run(mode: .default, before: Date.distantFuture)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
spLog(message: "======开始请求")
|
||||||
_request(parameters: parameters, completion: completion)
|
_request(parameters: parameters, completion: completion)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -140,11 +143,11 @@ class SPNetwork: NSObject {
|
|||||||
var response: SPNetworkResponse<T>?
|
var response: SPNetworkResponse<T>?
|
||||||
|
|
||||||
let time = Date().timeIntervalSince1970
|
let time = Date().timeIntervalSince1970
|
||||||
if let decrypted = SPCryptService.decrypt(data) {
|
let decrypted = CryptorService.decrypt(data: data)
|
||||||
spLog(message: decrypted)
|
spLog(message: decrypted)
|
||||||
response = SPNetworkResponse<T>.deserialize(from: decrypted)
|
response = SPNetworkResponse<T>.deserialize(from: decrypted)
|
||||||
response?.rawData = decrypted
|
response?.rawData = decrypted
|
||||||
}
|
|
||||||
spLog(message: Date().timeIntervalSince1970 - time)
|
spLog(message: Date().timeIntervalSince1970 - time)
|
||||||
|
|
||||||
if let response = response {
|
if let response = response {
|
||||||
|
@ -7,6 +7,18 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
|
/*
|
||||||
|
https://api-moviatv.moviatv.com/93f03506/
|
||||||
|
|
||||||
|
https://api-mireotv.mireotv.com/4da6fd4c/
|
||||||
|
|
||||||
|
https://api-vibeoshort.vibeoshort.com/bf86d973/
|
||||||
|
|
||||||
|
https://api-viontv.viontv.com/b7afef99/
|
||||||
|
|
||||||
|
https://api-zyreotv.zyreotv.com/7834f11d/
|
||||||
|
*/
|
||||||
|
|
||||||
#if DEBUG
|
#if DEBUG
|
||||||
let SPBaseURL = "https://test1-api.guyantv.com"
|
let SPBaseURL = "https://test1-api.guyantv.com"
|
||||||
let SPWebBaseURL = "https://www.guyantv.com"
|
let SPWebBaseURL = "https://www.guyantv.com"
|
||||||
|
@ -46,7 +46,7 @@ extension SPForYouViewController: SPPlayerListViewControllerDataSource {
|
|||||||
|
|
||||||
if let cell = oldCell as? SPPlayerListCell {
|
if let cell = oldCell as? SPPlayerListCell {
|
||||||
if let model = dataArr[indexPath.row] as? SPShortModel {
|
if let model = dataArr[indexPath.row] as? SPShortModel {
|
||||||
cell.model = model
|
cell.shortModel = model
|
||||||
cell.videoInfo = model.video_info
|
cell.videoInfo = model.video_info
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -106,7 +106,7 @@ extension SPHomeHeaderView: ZKCycleScrollViewDelegate, ZKCycleScrollViewDataSour
|
|||||||
func cycleScrollView(_ cycleScrollView: ZKCycleScrollView, didSelectItemAt index: Int) {
|
func cycleScrollView(_ cycleScrollView: ZKCycleScrollView, didSelectItemAt index: Int) {
|
||||||
let model = moduleModel?.bannerData?[index]
|
let model = moduleModel?.bannerData?[index]
|
||||||
|
|
||||||
let vc = SPTVPlayerListViewController()
|
let vc = SPPlayerDetailViewController()
|
||||||
vc.shortPlayId = model?.short_play_id
|
vc.shortPlayId = model?.short_play_id
|
||||||
self.viewController?.navigationController?.pushViewController(vc, animated: true)
|
self.viewController?.navigationController?.pushViewController(vc, animated: true)
|
||||||
}
|
}
|
||||||
|
@ -83,7 +83,7 @@ extension SPHomeHotView: UICollectionViewDelegate, UICollectionViewDataSource {
|
|||||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||||
let model = self.dataArr?[indexPath.row]
|
let model = self.dataArr?[indexPath.row]
|
||||||
|
|
||||||
let vc = SPTVPlayerListViewController()
|
let vc = SPPlayerDetailViewController()
|
||||||
vc.shortPlayId = model?.short_play_id
|
vc.shortPlayId = model?.short_play_id
|
||||||
self.viewController?.navigationController?.pushViewController(vc, animated: true)
|
self.viewController?.navigationController?.pushViewController(vc, animated: true)
|
||||||
}
|
}
|
||||||
|
@ -101,7 +101,7 @@ extension SPHomeTrendingView: UICollectionViewDataSource, UICollectionViewDelega
|
|||||||
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||||
let model = self.dataArr?[indexPath.row]
|
let model = self.dataArr?[indexPath.row]
|
||||||
|
|
||||||
let vc = SPTVPlayerListViewController()
|
let vc = SPPlayerDetailViewController()
|
||||||
vc.shortPlayId = model?.short_play_id
|
vc.shortPlayId = model?.short_play_id
|
||||||
self.viewController?.navigationController?.pushViewController(vc, animated: true)
|
self.viewController?.navigationController?.pushViewController(vc, animated: true)
|
||||||
}
|
}
|
||||||
|
@ -1,5 +1,5 @@
|
|||||||
//
|
//
|
||||||
// SPTVPlayerListViewController.swift
|
// SPPlayerDetailViewController.swift
|
||||||
// ShortPlay
|
// ShortPlay
|
||||||
//
|
//
|
||||||
// Created by 曾觉新 on 2025/4/10.
|
// Created by 曾觉新 on 2025/4/10.
|
||||||
@ -7,10 +7,10 @@
|
|||||||
|
|
||||||
import UIKit
|
import UIKit
|
||||||
|
|
||||||
class SPTVPlayerListViewController: SPPlayerListViewController {
|
class SPPlayerDetailViewController: SPPlayerListViewController {
|
||||||
|
|
||||||
override var PlayerCellClass: SPPlayerListCell.Type {
|
override var PlayerCellClass: SPPlayerListCell.Type {
|
||||||
return SPTVPlayerListCell.self
|
return SPPlayerDetailCell.self
|
||||||
}
|
}
|
||||||
|
|
||||||
override var contentSize: CGSize {
|
override var contentSize: CGSize {
|
||||||
@ -23,12 +23,17 @@ class SPTVPlayerListViewController: SPPlayerListViewController {
|
|||||||
|
|
||||||
private var detailModel: SPVideoDetailModel?
|
private var detailModel: SPVideoDetailModel?
|
||||||
|
|
||||||
|
private weak var episodeView: SPEpisodeView?
|
||||||
|
|
||||||
override func viewDidLoad() {
|
override func viewDidLoad() {
|
||||||
super.viewDidLoad()
|
super.viewDidLoad()
|
||||||
self.autoNextEpisode = true
|
self.autoNextEpisode = true
|
||||||
self.dataSource = self
|
self.dataSource = self
|
||||||
|
self.delegate = self
|
||||||
|
|
||||||
requestDetailData()
|
requestDetailData()
|
||||||
|
|
||||||
|
_addAction()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
@ -49,11 +54,33 @@ class SPTVPlayerListViewController: SPPlayerListViewController {
|
|||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension SPPlayerDetailViewController {
|
||||||
|
|
||||||
|
private func _addAction() {
|
||||||
|
self.viewModel.handleEpisode = { [weak self] in
|
||||||
|
self?.onEpisode()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private func onEpisode() {
|
||||||
|
let view = SPEpisodeView()
|
||||||
|
view.dataArr = detailModel?.episodeList ?? []
|
||||||
|
view.shortModel = detailModel?.shortPlayInfo
|
||||||
|
view.currentIndex = self.currentIndexPath.row
|
||||||
|
view.didSelectedIndex = { [weak self] (index) in
|
||||||
|
self?.scrollToItem(indexPath: IndexPath(row: index, section: 0))
|
||||||
|
}
|
||||||
|
view.present(in: nil)
|
||||||
|
self.episodeView = view
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
//MARK: -------------- SPPlayerListViewControllerDataSource --------------
|
//MARK: -------------- SPPlayerListViewControllerDataSource --------------
|
||||||
extension SPTVPlayerListViewController: SPPlayerListViewControllerDataSource {
|
extension SPPlayerDetailViewController: SPPlayerListViewControllerDataSource, SPPlayerListViewControllerDelegate {
|
||||||
func sp_playerListViewController(_ viewController: SPPlayerListViewController, _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath, oldCell: UICollectionViewCell) -> UICollectionViewCell {
|
func sp_playerListViewController(_ viewController: SPPlayerListViewController, _ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath, oldCell: UICollectionViewCell) -> UICollectionViewCell {
|
||||||
if let cell = oldCell as? SPPlayerListCell {
|
if let cell = oldCell as? SPPlayerDetailCell {
|
||||||
cell.model = detailModel
|
cell.shortModel = detailModel?.shortPlayInfo
|
||||||
cell.videoInfo = detailModel?.episodeList?[indexPath.row]
|
cell.videoInfo = detailModel?.episodeList?[indexPath.row]
|
||||||
cell.isLoop = false
|
cell.isLoop = false
|
||||||
}
|
}
|
||||||
@ -63,10 +90,14 @@ extension SPTVPlayerListViewController: SPPlayerListViewControllerDataSource {
|
|||||||
func sp_playerListViewController(_ viewController: SPPlayerListViewController, _ collectionView: UICollectionView, numberOfItemsInSection section: Int, oldNumber: Int) -> Int {
|
func sp_playerListViewController(_ viewController: SPPlayerListViewController, _ collectionView: UICollectionView, numberOfItemsInSection section: Int, oldNumber: Int) -> Int {
|
||||||
return detailModel?.episodeList?.count ?? 0
|
return detailModel?.episodeList?.count ?? 0
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func sp_playerListViewController(_ viewController: SPPlayerListViewController, didChangeIndexPathForVisible indexPath: IndexPath) {
|
||||||
|
self.episodeView?.currentIndex = indexPath.row
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
extension SPTVPlayerListViewController {
|
extension SPPlayerDetailViewController {
|
||||||
|
|
||||||
private func requestDetailData() {
|
private func requestDetailData() {
|
||||||
guard let shortPlayId = self.shortPlayId else { return }
|
guard let shortPlayId = self.shortPlayId else { return }
|
@ -19,6 +19,8 @@ import UIKit
|
|||||||
///向上加载更多数据
|
///向上加载更多数据
|
||||||
@objc optional func sp_playerViewControllerLoadUpMoreData(playerViewController: SPPlayerListViewController)
|
@objc optional func sp_playerViewControllerLoadUpMoreData(playerViewController: SPPlayerListViewController)
|
||||||
|
|
||||||
|
///当前展示的发生变化
|
||||||
|
@objc optional func sp_playerListViewController(_ viewController: SPPlayerListViewController, didChangeIndexPathForVisible indexPath: IndexPath)
|
||||||
///新页面展示完成
|
///新页面展示完成
|
||||||
// @objc optional func yd_playerViewController(playerListViewController: BCListPlayerViewController, didShowPlayerPage playerViewController: YDBasePlayerViewController)
|
// @objc optional func yd_playerViewController(playerListViewController: BCListPlayerViewController, didShowPlayerPage playerViewController: YDBasePlayerViewController)
|
||||||
}
|
}
|
||||||
@ -31,6 +33,7 @@ import UIKit
|
|||||||
func sp_playerListViewController(_ viewController: SPPlayerListViewController, _ collectionView: UICollectionView, numberOfItemsInSection section: Int, oldNumber: Int) -> Int
|
func sp_playerListViewController(_ viewController: SPPlayerListViewController, _ collectionView: UICollectionView, numberOfItemsInSection section: Int, oldNumber: Int) -> Int
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
class SPPlayerListViewController: SPViewController {
|
class SPPlayerListViewController: SPViewController {
|
||||||
@ -153,6 +156,10 @@ class SPPlayerListViewController: SPViewController {
|
|||||||
func getDataCount() -> Int {
|
func getDataCount() -> Int {
|
||||||
return self.collectionView(self.collectionView, numberOfItemsInSection: 0)
|
return self.collectionView(self.collectionView, numberOfItemsInSection: 0)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func scrollToItem(indexPath: IndexPath) {
|
||||||
|
self.collectionView.scrollToItem(at: indexPath, at: .top, animated: true);
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SPPlayerListViewController {
|
extension SPPlayerListViewController {
|
||||||
@ -175,6 +182,7 @@ extension SPPlayerListViewController {
|
|||||||
self.viewModel.handlePlayFinish = { [weak self] in
|
self.viewModel.handlePlayFinish = { [weak self] in
|
||||||
self?.currentPlayFinish()
|
self?.currentPlayFinish()
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
@ -227,6 +235,8 @@ extension SPPlayerListViewController {
|
|||||||
}
|
}
|
||||||
return true
|
return true
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
//MARK: -------------- UICollectionViewDelegate & UICollectionViewDataSource --------------
|
//MARK: -------------- UICollectionViewDelegate & UICollectionViewDataSource --------------
|
||||||
@ -251,6 +261,7 @@ extension SPPlayerListViewController: UICollectionViewDelegate, UICollectionView
|
|||||||
if self.viewModel.currentPlayer == nil, indexPath == currentIndexPath, let playerProtocol = cell as? SPPlayerProtocol {
|
if self.viewModel.currentPlayer == nil, indexPath == currentIndexPath, let playerProtocol = cell as? SPPlayerProtocol {
|
||||||
self.currentIndexPath = indexPath
|
self.currentIndexPath = indexPath
|
||||||
self.viewModel.currentPlayer = playerProtocol
|
self.viewModel.currentPlayer = playerProtocol
|
||||||
|
didChangeIndexPathForVisible()
|
||||||
}
|
}
|
||||||
|
|
||||||
return cell
|
return cell
|
||||||
@ -293,6 +304,7 @@ extension SPPlayerListViewController: UICollectionViewDelegate, UICollectionView
|
|||||||
guard let currentPlayer = self.collectionView.cellForItem(at: indexPath) as? SPPlayerProtocol else { return }
|
guard let currentPlayer = self.collectionView.cellForItem(at: indexPath) as? SPPlayerProtocol else { return }
|
||||||
self.viewModel.currentPlayer = currentPlayer
|
self.viewModel.currentPlayer = currentPlayer
|
||||||
// currentCell = self.collectionView.cellForItem(at: indexPath) as? BCListPlayerCell
|
// currentCell = self.collectionView.cellForItem(at: indexPath) as? BCListPlayerCell
|
||||||
|
didChangeIndexPathForVisible()
|
||||||
self.play()
|
self.play()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -329,4 +341,8 @@ extension SPPlayerListViewController {
|
|||||||
// }
|
// }
|
||||||
self.delegate?.sp_playerViewControllerLoadUpMoreData?(playerViewController: self)
|
self.delegate?.sp_playerViewControllerLoadUpMoreData?(playerViewController: self)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func didChangeIndexPathForVisible() {
|
||||||
|
self.delegate?.sp_playerListViewController?(self, didChangeIndexPathForVisible: self.currentIndexPath)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
70
ShortPlay/Class/Player/View/SPEpisodeCell.swift
Normal file
@ -0,0 +1,70 @@
|
|||||||
|
//
|
||||||
|
// SPEpisodeCell.swift
|
||||||
|
// ShortPlay
|
||||||
|
//
|
||||||
|
// Created by 曾觉新 on 2025/4/16.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class SPEpisodeCell: SPCollectionViewCell {
|
||||||
|
|
||||||
|
|
||||||
|
var videoInfoModel: SPVideoInfoModel? {
|
||||||
|
didSet {
|
||||||
|
textLabel.text = "\(videoInfoModel?.episode ?? 0)"
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var sp_isSelected: Bool = false {
|
||||||
|
didSet {
|
||||||
|
textLabel.isHidden = sp_isSelected
|
||||||
|
playImageView.isHidden = !sp_isSelected
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private lazy var textLabel: UILabel = {
|
||||||
|
let label = UILabel()
|
||||||
|
label.font = .fontLight(ofSize: 14)
|
||||||
|
label.textColor = .colorD2D2D2()
|
||||||
|
return label
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var playImageView: UIImageView = {
|
||||||
|
let imageView = UIImageView(image: UIImage(named: "play_icon_01"))
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
contentView.backgroundColor = .colorFFFFFF(alpha: 0.14)
|
||||||
|
contentView.layer.cornerRadius = 7
|
||||||
|
contentView.layer.masksToBounds = true
|
||||||
|
|
||||||
|
|
||||||
|
_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SPEpisodeCell {
|
||||||
|
|
||||||
|
private func _setupUI() {
|
||||||
|
contentView.addSubview(textLabel)
|
||||||
|
contentView.addSubview(playImageView)
|
||||||
|
|
||||||
|
textLabel.snp.makeConstraints { make in
|
||||||
|
make.center.equalToSuperview()
|
||||||
|
}
|
||||||
|
|
||||||
|
playImageView.snp.makeConstraints { make in
|
||||||
|
make.center.equalToSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
153
ShortPlay/Class/Player/View/SPEpisodeView.swift
Normal file
@ -0,0 +1,153 @@
|
|||||||
|
//
|
||||||
|
// SPEpisodeView.swift
|
||||||
|
// ShortPlay
|
||||||
|
//
|
||||||
|
// Created by 曾觉新 on 2025/4/16.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class SPEpisodeView: HWPanModalContentView {
|
||||||
|
|
||||||
|
var currentIndex: Int = 0 {
|
||||||
|
didSet {
|
||||||
|
self.collectionView.reloadData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var shortModel: SPShortModel? {
|
||||||
|
didSet {
|
||||||
|
coverImageView.sp_setImage(url: shortModel?.image_url)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var dataArr: [SPVideoInfoModel] = [] {
|
||||||
|
didSet {
|
||||||
|
self.collectionView.reloadData()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var didSelectedIndex: ((_ index: Int) -> Void)?
|
||||||
|
|
||||||
|
//MARK: UI属性
|
||||||
|
private lazy var collectionViewLayout: UICollectionViewFlowLayout = {
|
||||||
|
let itemWidth = floor((kSPScreenWidth - 10 * 4 - 30) / 5)
|
||||||
|
|
||||||
|
let layout = UICollectionViewFlowLayout()
|
||||||
|
layout.itemSize = .init(width: itemWidth, height: 50)
|
||||||
|
layout.minimumLineSpacing = 10
|
||||||
|
layout.minimumInteritemSpacing = 10
|
||||||
|
layout.sectionInset = .init(top: 0, left: 15, bottom: 0, right: 15)
|
||||||
|
return layout
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var collectionView: SPCollectionView = {
|
||||||
|
let collectionView = SPCollectionView(frame: .zero, collectionViewLayout: collectionViewLayout)
|
||||||
|
collectionView.delegate = self
|
||||||
|
collectionView.dataSource = self
|
||||||
|
SPEpisodeCell.registerCell(collectionView: collectionView)
|
||||||
|
return collectionView
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var indicatorView: UIView = {
|
||||||
|
let view = UIView()
|
||||||
|
view.backgroundColor = .colorFFFFFF()
|
||||||
|
view.layer.cornerRadius = 2
|
||||||
|
view.layer.masksToBounds = true
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
private lazy var coverImageView: SPImageView = {
|
||||||
|
let imageView = SPImageView()
|
||||||
|
imageView.layer.cornerRadius = 6
|
||||||
|
imageView.layer.masksToBounds = true
|
||||||
|
return imageView
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
//MARK: HWPanModalPresentable
|
||||||
|
override func panScrollable() -> UIScrollView? {
|
||||||
|
return collectionView
|
||||||
|
}
|
||||||
|
|
||||||
|
override func longFormHeight() -> PanModalHeight {
|
||||||
|
return PanModalHeightMake(.content, kSPScreenHeight * (2 / 3))
|
||||||
|
}
|
||||||
|
|
||||||
|
override func showDragIndicator() -> Bool {
|
||||||
|
return false
|
||||||
|
}
|
||||||
|
|
||||||
|
override func backgroundConfig() -> HWBackgroundConfig {
|
||||||
|
let config = HWBackgroundConfig()
|
||||||
|
config.backgroundAlpha = 0.4
|
||||||
|
return config
|
||||||
|
}
|
||||||
|
|
||||||
|
override func present(in view: UIView?) {
|
||||||
|
super.present(in: view)
|
||||||
|
self.hw_contentView.addEffectView(style: .dark)
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SPEpisodeView {
|
||||||
|
|
||||||
|
private func _setupUI() {
|
||||||
|
addSubview(indicatorView)
|
||||||
|
addSubview(coverImageView)
|
||||||
|
addSubview(self.collectionView)
|
||||||
|
|
||||||
|
self.indicatorView.snp.makeConstraints { make in
|
||||||
|
make.centerX.equalToSuperview()
|
||||||
|
make.top.equalToSuperview().offset(15)
|
||||||
|
make.width.equalTo(30)
|
||||||
|
make.height.equalTo(4)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.coverImageView.snp.makeConstraints { make in
|
||||||
|
make.left.equalToSuperview().offset(15)
|
||||||
|
make.top.equalToSuperview().offset(39)
|
||||||
|
make.width.equalTo(55)
|
||||||
|
make.height.equalTo(74)
|
||||||
|
}
|
||||||
|
|
||||||
|
self.collectionView.snp.makeConstraints { make in
|
||||||
|
make.edges.equalToSuperview()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
//MARK: -------------- UICollectionViewDelegate & UICollectionViewDataSource --------------
|
||||||
|
extension SPEpisodeView: UICollectionViewDelegate, UICollectionViewDataSource {
|
||||||
|
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
||||||
|
let cell = SPEpisodeCell.dequeueReusableCell(collectionView: collectionView, indexPath: indexPath)
|
||||||
|
cell.videoInfoModel = self.dataArr[indexPath.row]
|
||||||
|
cell.sp_isSelected = indexPath.row == currentIndex
|
||||||
|
return cell
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
||||||
|
return self.dataArr.count
|
||||||
|
}
|
||||||
|
|
||||||
|
func collectionView(_ collectionView: UICollectionView, didSelectItemAt indexPath: IndexPath) {
|
||||||
|
guard indexPath.row != currentIndex else { return }
|
||||||
|
self.didSelectedIndex?(indexPath.row)
|
||||||
|
self.dismiss(animated: true) {
|
||||||
|
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
@ -15,15 +15,20 @@ class SPPlayerControlView: UIView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var model: Any? {
|
|
||||||
|
var shortModel: SPShortModel? {
|
||||||
didSet {
|
didSet {
|
||||||
guard let model = model as? SPShortModel else { return }
|
updateCollectButtonState()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var videoInfo: SPVideoInfoModel? {
|
||||||
|
didSet {
|
||||||
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
///滑动进度条
|
///滑动进度条
|
||||||
var panProgressFinishBlock: ((_ progress: CGFloat) -> Void)?
|
var panProgressFinishBlock: ((_ progress: CGFloat) -> Void)?
|
||||||
|
|
||||||
@ -40,6 +45,7 @@ class SPPlayerControlView: UIView {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
//MARK: UI属性
|
||||||
private(set) lazy var progressView: SPPlayerProgressView = {
|
private(set) lazy var progressView: SPPlayerProgressView = {
|
||||||
let view = SPPlayerProgressView()
|
let view = SPPlayerProgressView()
|
||||||
view.panStart = { [weak self] in
|
view.panStart = { [weak self] in
|
||||||
@ -65,17 +71,36 @@ class SPPlayerControlView: UIView {
|
|||||||
return imageView
|
return imageView
|
||||||
}()
|
}()
|
||||||
|
|
||||||
|
///右边功能区
|
||||||
|
private(set) lazy var rightFeatureView: UIStackView = {
|
||||||
|
let view = UIStackView(arrangedSubviews: [collectButton])
|
||||||
|
view.axis = .vertical
|
||||||
|
view.spacing = 25
|
||||||
|
return view
|
||||||
|
}()
|
||||||
|
|
||||||
|
///收藏按钮
|
||||||
|
private lazy var collectButton: UIButton = {
|
||||||
|
let button = createFeatureButton(title: "Save".localized, selectedTitle: "Added".localized, image: UIImage(named: "collect_icon_01"), selectedImage: UIImage(named: "collect_icon_01_selected"))
|
||||||
|
button.addTarget(self, action: #selector(handleCollectButton), for: .touchUpInside)
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
|
||||||
deinit {
|
deinit {
|
||||||
viewModel?.removeObserver(self, forKeyPath: "isPlaying")
|
viewModel?.removeObserver(self, forKeyPath: "isPlaying")
|
||||||
|
NotificationCenter.default.removeObserver(self)
|
||||||
}
|
}
|
||||||
|
|
||||||
override init(frame: CGRect) {
|
override init(frame: CGRect) {
|
||||||
super.init(frame: frame)
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
NotificationCenter.default.addObserver(self, selector: #selector(updateShortCollectStateNotification), name: SPVideoAPI.updateShortCollectStateNotification, object: nil)
|
||||||
|
|
||||||
let tap = UITapGestureRecognizer(target: self, action: #selector(hadlePlayAndOrPaused))
|
let tap = UITapGestureRecognizer(target: self, action: #selector(hadlePlayAndOrPaused))
|
||||||
self.addGestureRecognizer(tap)
|
self.addGestureRecognizer(tap)
|
||||||
|
|
||||||
|
|
||||||
|
|
||||||
sp_setupUI()
|
sp_setupUI()
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -89,6 +114,23 @@ class SPPlayerControlView: UIView {
|
|||||||
fatalError("init(coder:) has not been implemented")
|
fatalError("init(coder:) has not been implemented")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
||||||
|
func createFeatureButton(title: String?, selectedTitle: String? = nil, image: UIImage?, selectedImage: UIImage? = nil) -> UIButton {
|
||||||
|
let button = JXButton(type: .custom)
|
||||||
|
button.titleDirection = .down
|
||||||
|
button.setImage(image, for: .normal)
|
||||||
|
button.setImage(selectedImage, for: .selected)
|
||||||
|
button.setImage(selectedImage, for: [.selected, .highlighted])
|
||||||
|
button.setTitle(title, for: .normal);
|
||||||
|
button.setTitle(selectedTitle, for: .selected);
|
||||||
|
button.setTitle(selectedTitle, for: [.selected, .highlighted])
|
||||||
|
button.setTitleColor(.colorFFFFFF(alpha: 0.9), for: .normal)
|
||||||
|
button.setTitleColor(.colorF564B6(), for: .selected)
|
||||||
|
button.jx_font = .fontLight(ofSize: 11);
|
||||||
|
return button
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
extension SPPlayerControlView {
|
extension SPPlayerControlView {
|
||||||
@ -96,6 +138,7 @@ extension SPPlayerControlView {
|
|||||||
private func sp_setupUI() {
|
private func sp_setupUI() {
|
||||||
addSubview(progressView)
|
addSubview(progressView)
|
||||||
addSubview(playImageView)
|
addSubview(playImageView)
|
||||||
|
addSubview(rightFeatureView)
|
||||||
|
|
||||||
progressView.snp.makeConstraints { make in
|
progressView.snp.makeConstraints { make in
|
||||||
make.left.equalToSuperview().offset(10)
|
make.left.equalToSuperview().offset(10)
|
||||||
@ -108,6 +151,11 @@ extension SPPlayerControlView {
|
|||||||
make.center.equalToSuperview()
|
make.center.equalToSuperview()
|
||||||
make.width.height.equalTo(100)
|
make.width.height.equalTo(100)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
rightFeatureView.snp.makeConstraints { make in
|
||||||
|
make.right.equalToSuperview().offset(-15)
|
||||||
|
make.bottom.equalToSuperview().offset(-200)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -132,7 +180,29 @@ extension SPPlayerControlView {
|
|||||||
// SPAPPTool.topViewController()?.navigationController?.pushViewController(vc, animated: true)
|
// SPAPPTool.topViewController()?.navigationController?.pushViewController(vc, animated: true)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@objc private func handleCollectButton() {
|
||||||
|
guard let shortPlayId = self.videoInfo?.short_play_id else { return }
|
||||||
|
|
||||||
|
let isCollect = !(self.shortModel?.is_collect ?? false)
|
||||||
|
|
||||||
|
SPVideoAPI.requestCollectShort(isCollect: isCollect, shortPlayId: shortPlayId) {
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
@objc private func updateShortCollectStateNotification(sender: Notification) {
|
||||||
|
guard let userInfo = sender.userInfo else { return }
|
||||||
|
guard let shortPlayId = userInfo["id"] as? String else { return }
|
||||||
|
guard let isCollect = userInfo["state"] as? Bool else { return }
|
||||||
|
guard shortPlayId == self.videoInfo?.short_play_id else { return }
|
||||||
|
|
||||||
|
self.shortModel?.is_collect = isCollect;
|
||||||
|
updateCollectButtonState()
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
private func updateCollectButtonState() {
|
||||||
|
self.collectButton.isSelected = self.shortModel?.is_collect ?? false
|
||||||
|
}
|
||||||
|
|
||||||
}
|
}
|
||||||
|
|
||||||
|
26
ShortPlay/Class/Player/View/SPPlayerDetailCell.swift
Normal file
@ -0,0 +1,26 @@
|
|||||||
|
//
|
||||||
|
// SPPlayerDetailCell.swift
|
||||||
|
// ShortPlay
|
||||||
|
//
|
||||||
|
// Created by 曾觉新 on 2025/4/10.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class SPPlayerDetailCell: SPPlayerListCell {
|
||||||
|
|
||||||
|
override var PlayerControlViewClass: SPPlayerControlView.Type {
|
||||||
|
return SPPlayerDetailControlView.self
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
}
|
44
ShortPlay/Class/Player/View/SPPlayerDetailControlView.swift
Normal file
@ -0,0 +1,44 @@
|
|||||||
|
//
|
||||||
|
// SPPlayerDetailControlView.swift
|
||||||
|
// ShortPlay
|
||||||
|
//
|
||||||
|
// Created by 曾觉新 on 2025/4/16.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class SPPlayerDetailControlView: SPPlayerControlView {
|
||||||
|
|
||||||
|
|
||||||
|
private lazy var episodeButton: UIButton = {
|
||||||
|
let button = createFeatureButton(title: "Episodes".localized, image: UIImage(named: "episodes_icon_01"))
|
||||||
|
button.addTarget(self, action: #selector(handleEpisodeButton), for: .touchUpInside)
|
||||||
|
return button
|
||||||
|
}()
|
||||||
|
|
||||||
|
override init(frame: CGRect) {
|
||||||
|
super.init(frame: frame)
|
||||||
|
|
||||||
|
_setupUI()
|
||||||
|
}
|
||||||
|
|
||||||
|
@MainActor required init?(coder: NSCoder) {
|
||||||
|
fatalError("init(coder:) has not been implemented")
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SPPlayerDetailControlView {
|
||||||
|
|
||||||
|
private func _setupUI() {
|
||||||
|
self.rightFeatureView.addArrangedSubview(episodeButton)
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|
||||||
|
|
||||||
|
extension SPPlayerDetailControlView {
|
||||||
|
@objc private func handleEpisodeButton() {
|
||||||
|
self.viewModel?.handleEpisode?()
|
||||||
|
}
|
||||||
|
}
|
@ -9,6 +9,10 @@ import UIKit
|
|||||||
|
|
||||||
class SPPlayerListCell: SPCollectionViewCell, SPPlayerProtocol {
|
class SPPlayerListCell: SPCollectionViewCell, SPPlayerProtocol {
|
||||||
|
|
||||||
|
var PlayerControlViewClass: SPPlayerControlView.Type {
|
||||||
|
return SPPlayerControlView.self
|
||||||
|
}
|
||||||
|
|
||||||
weak var viewModel: SPPlayerListViewModel? {
|
weak var viewModel: SPPlayerListViewModel? {
|
||||||
didSet {
|
didSet {
|
||||||
controlView.viewModel = viewModel
|
controlView.viewModel = viewModel
|
||||||
@ -39,7 +43,7 @@ class SPPlayerListCell: SPCollectionViewCell, SPPlayerProtocol {
|
|||||||
}()
|
}()
|
||||||
|
|
||||||
private lazy var controlView: SPPlayerControlView = {
|
private lazy var controlView: SPPlayerControlView = {
|
||||||
let view = SPPlayerControlView()
|
let view = PlayerControlViewClass.init()
|
||||||
view.panProgressFinishBlock = { [weak self] progress in
|
view.panProgressFinishBlock = { [weak self] progress in
|
||||||
guard let self = self else { return }
|
guard let self = self else { return }
|
||||||
let duration = CGFloat(self.player.duration)
|
let duration = CGFloat(self.player.duration)
|
||||||
@ -65,19 +69,30 @@ class SPPlayerListCell: SPCollectionViewCell, SPPlayerProtocol {
|
|||||||
self.controlView.progress = 0
|
self.controlView.progress = 0
|
||||||
self.coverImageView.isHidden = false
|
self.coverImageView.isHidden = false
|
||||||
|
|
||||||
if let model = model as? SPShortModel {
|
// if let model = model as? SPShortModel {
|
||||||
self.controlView.model = model
|
// self.controlView.model = model
|
||||||
coverImageView.sp_setImage(url: model.image_url)
|
// coverImageView.sp_setImage(url: model.image_url)
|
||||||
} else if let model = model as? SPVideoDetailModel {
|
// } else if let model = model as? SPVideoDetailModel {
|
||||||
self.controlView.model = model.shortPlayInfo
|
// self.controlView.model = model.shortPlayInfo
|
||||||
coverImageView.sp_setImage(url: model.shortPlayInfo?.image_url)
|
// coverImageView.sp_setImage(url: model.shortPlayInfo?.image_url)
|
||||||
|
// }
|
||||||
|
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
var shortModel: SPShortModel? {
|
||||||
|
didSet {
|
||||||
|
self.controlView.shortModel = shortModel
|
||||||
|
coverImageView.sp_setImage(url: shortModel?.image_url)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var videoInfo: SPVideoInfoModel? {
|
var videoInfo: SPVideoInfoModel? {
|
||||||
didSet {
|
didSet {
|
||||||
|
self.controlView.progress = 0
|
||||||
|
self.coverImageView.isHidden = false
|
||||||
|
self.controlView.videoInfo = videoInfo
|
||||||
|
|
||||||
player.setPlayUrl(url: videoInfo?.video_url ?? "")
|
player.setPlayUrl(url: videoInfo?.video_url ?? "")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
@ -1,12 +0,0 @@
|
|||||||
//
|
|
||||||
// SPTVPlayerListCell.swift
|
|
||||||
// ShortPlay
|
|
||||||
//
|
|
||||||
// Created by 曾觉新 on 2025/4/10.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
|
|
||||||
class SPTVPlayerListCell: SPPlayerListCell {
|
|
||||||
|
|
||||||
}
|
|
@ -41,6 +41,7 @@ class SPPlayerListViewModel: NSObject {
|
|||||||
var handlePauseOrPlay: (() -> Void)?
|
var handlePauseOrPlay: (() -> Void)?
|
||||||
///播放完成
|
///播放完成
|
||||||
var handlePlayFinish: (() -> Void)?
|
var handlePlayFinish: (() -> Void)?
|
||||||
|
///选集
|
||||||
|
var handleEpisode: (() -> Void)?
|
||||||
|
|
||||||
}
|
}
|
||||||
|
22
ShortPlay/Source/Assets.xcassets/icon/collect_icon_01.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "收藏@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "收藏@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
ShortPlay/Source/Assets.xcassets/icon/collect_icon_01.imageset/收藏@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
ShortPlay/Source/Assets.xcassets/icon/collect_icon_01.imageset/收藏@3x.png
vendored
Normal file
After Width: | Height: | Size: 2.3 KiB |
22
ShortPlay/Source/Assets.xcassets/icon/collect_icon_01_selected.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "收藏@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "收藏@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
ShortPlay/Source/Assets.xcassets/icon/collect_icon_01_selected.imageset/收藏@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
ShortPlay/Source/Assets.xcassets/icon/collect_icon_01_selected.imageset/收藏@3x.png
vendored
Normal file
After Width: | Height: | Size: 3.3 KiB |
22
ShortPlay/Source/Assets.xcassets/icon/episodes_icon_01.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Episodes@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "Episodes@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
ShortPlay/Source/Assets.xcassets/icon/episodes_icon_01.imageset/Episodes@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.8 KiB |
BIN
ShortPlay/Source/Assets.xcassets/icon/episodes_icon_01.imageset/Episodes@3x.png
vendored
Normal file
After Width: | Height: | Size: 3.3 KiB |
22
ShortPlay/Source/Assets.xcassets/icon/play_icon_01.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
|||||||
|
{
|
||||||
|
"images" : [
|
||||||
|
{
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "1x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "play1@2x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "2x"
|
||||||
|
},
|
||||||
|
{
|
||||||
|
"filename" : "play1@3x.png",
|
||||||
|
"idiom" : "universal",
|
||||||
|
"scale" : "3x"
|
||||||
|
}
|
||||||
|
],
|
||||||
|
"info" : {
|
||||||
|
"author" : "xcode",
|
||||||
|
"version" : 1
|
||||||
|
}
|
||||||
|
}
|
BIN
ShortPlay/Source/Assets.xcassets/icon/play_icon_01.imageset/play1@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
ShortPlay/Source/Assets.xcassets/icon/play_icon_01.imageset/play1@3x.png
vendored
Normal file
After Width: | Height: | Size: 2.5 KiB |
@ -11,3 +11,4 @@
|
|||||||
#import <Toast.h>
|
#import <Toast.h>
|
||||||
#import "NSUserDefaults+JXAdd.h"
|
#import "NSUserDefaults+JXAdd.h"
|
||||||
#import <KTVHTTPCache.h>
|
#import <KTVHTTPCache.h>
|
||||||
|
#import <HWPanModal/HWPanModal.h>
|
||||||
|
@ -17,3 +17,6 @@
|
|||||||
"Trending Now" = "Trending Now";
|
"Trending Now" = "Trending Now";
|
||||||
"Editor's Hotlist" = "Editor's Hotlist";
|
"Editor's Hotlist" = "Editor's Hotlist";
|
||||||
"Shorts for You" = "Shorts for You";
|
"Shorts for You" = "Shorts for You";
|
||||||
|
"Episodes" = "Episodes";
|
||||||
|
"Save" = "Save";
|
||||||
|
"Added" = "Added";
|
||||||
|
218
ShortPlay/Thirdparty/JXButton/JXButton.swift
vendored
Normal file
@ -0,0 +1,218 @@
|
|||||||
|
//
|
||||||
|
// JXButton.swift
|
||||||
|
// BoJia
|
||||||
|
//
|
||||||
|
// Created by 火山传媒 on 2024/5/31.
|
||||||
|
//
|
||||||
|
|
||||||
|
import UIKit
|
||||||
|
|
||||||
|
class JXButton: UIButton {
|
||||||
|
lazy var jx_font: UIFont? = self.titleLabel?.font {
|
||||||
|
didSet {
|
||||||
|
self.titleLabel?.font = jx_font
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
var maxTitleWidth: CGFloat = 0
|
||||||
|
var titleDirection: UITextLayoutDirection?
|
||||||
|
///左右边距
|
||||||
|
var leftAnyRightmargin: CGFloat = 0
|
||||||
|
|
||||||
|
///文字与图片的间距
|
||||||
|
var space: CGFloat = 0
|
||||||
|
|
||||||
|
private var imageRect: CGRect = .zero
|
||||||
|
private var titleRect: CGRect = .zero
|
||||||
|
|
||||||
|
|
||||||
|
override func layoutSubviews() {
|
||||||
|
super.layoutSubviews()
|
||||||
|
}
|
||||||
|
|
||||||
|
override var intrinsicContentSize: CGSize {
|
||||||
|
|
||||||
|
var width: CGFloat = 0
|
||||||
|
var height: CGFloat = 0
|
||||||
|
switch titleDirection {
|
||||||
|
case .left:
|
||||||
|
width = imageRect.width + titleRect.width + space
|
||||||
|
if imageRect.height > titleRect.height {
|
||||||
|
height = imageRect.height
|
||||||
|
} else {
|
||||||
|
height = titleRect.height
|
||||||
|
}
|
||||||
|
|
||||||
|
case .up:
|
||||||
|
if imageRect.width > titleRect.width {
|
||||||
|
width = imageRect.width
|
||||||
|
} else {
|
||||||
|
width = titleRect.width
|
||||||
|
}
|
||||||
|
height = titleRect.height + imageRect.height + space
|
||||||
|
|
||||||
|
case .down:
|
||||||
|
if imageRect.width > titleRect.width {
|
||||||
|
width = imageRect.width
|
||||||
|
} else {
|
||||||
|
width = titleRect.width
|
||||||
|
}
|
||||||
|
height = titleRect.height + imageRect.height + space
|
||||||
|
|
||||||
|
default:
|
||||||
|
width = imageRect.width + titleRect.width + space
|
||||||
|
if imageRect.height > titleRect.height {
|
||||||
|
height = imageRect.height
|
||||||
|
} else {
|
||||||
|
height = titleRect.height
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
let size = CGSize(width: width + leftAnyRightmargin * 2, height: height)
|
||||||
|
return size
|
||||||
|
}
|
||||||
|
|
||||||
|
override func imageRect(forContentRect contentRect: CGRect) -> CGRect {
|
||||||
|
let imageSize = currentImage?.size ?? .zero
|
||||||
|
let textSize = self._textSize()
|
||||||
|
let contentWidth = self.frame.size.width
|
||||||
|
let contentHeight = self.frame.size.height
|
||||||
|
|
||||||
|
var x: CGFloat = 0
|
||||||
|
var y: CGFloat = 0
|
||||||
|
var width: CGFloat = 0
|
||||||
|
var height: CGFloat = 0
|
||||||
|
|
||||||
|
switch titleDirection {
|
||||||
|
case .left:
|
||||||
|
x = (contentWidth - space) / 2 - imageSize.width / 2 + textSize.width / 2 + space
|
||||||
|
y = contentHeight / 2 - imageSize.height / 2
|
||||||
|
width = imageSize.width
|
||||||
|
height = imageSize.height
|
||||||
|
|
||||||
|
case .up:
|
||||||
|
x = contentWidth / 2 - imageSize.width / 2
|
||||||
|
y = (contentHeight - space) / 2 - imageSize.height / 2 + textSize.height / 2 + space
|
||||||
|
width = imageSize.width
|
||||||
|
height = imageSize.height
|
||||||
|
|
||||||
|
case .down:
|
||||||
|
x = contentWidth / 2 - imageSize.width / 2
|
||||||
|
y = (contentHeight - space) / 2 - imageSize.height / 2 - textSize.height / 2
|
||||||
|
width = imageSize.width
|
||||||
|
height = imageSize.height
|
||||||
|
|
||||||
|
default:
|
||||||
|
x = (contentWidth - space) / 2 - imageSize.width / 2 - textSize.width / 2
|
||||||
|
y = contentHeight / 2 - imageSize.height / 2
|
||||||
|
width = imageSize.width
|
||||||
|
height = imageSize.height
|
||||||
|
}
|
||||||
|
self.imageRect = CGRect(x: x, y: y, width: width, height: height)
|
||||||
|
|
||||||
|
return self.imageRect
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
override func titleRect(forContentRect contentRect: CGRect) -> CGRect {
|
||||||
|
let imageSize = currentImage?.size ?? .zero
|
||||||
|
let textSize = self._textSize()
|
||||||
|
let contentWidth = self.frame.size.width
|
||||||
|
let contentHeight = self.frame.size.height
|
||||||
|
|
||||||
|
var x: CGFloat = 0
|
||||||
|
var y: CGFloat = 0
|
||||||
|
var width: CGFloat = 0
|
||||||
|
var height: CGFloat = 0
|
||||||
|
|
||||||
|
switch titleDirection {
|
||||||
|
case .left:
|
||||||
|
x = (contentWidth - space) / 2 - imageSize.width / 2 - textSize.width / 2
|
||||||
|
y = contentHeight / 2 - textSize.height / 2
|
||||||
|
width = textSize.width
|
||||||
|
height = textSize.height
|
||||||
|
|
||||||
|
case .up:
|
||||||
|
x = contentWidth / 2 - textSize.width / 2
|
||||||
|
y = (contentHeight - space) / 2 - textSize.height / 2 - imageSize.height / 2
|
||||||
|
width = textSize.width
|
||||||
|
height = textSize.height
|
||||||
|
|
||||||
|
case .down:
|
||||||
|
x = contentWidth / 2 - textSize.width / 2
|
||||||
|
y = (contentHeight - space) / 2 - textSize.height / 2 + imageSize.height / 2 + space
|
||||||
|
width = textSize.width
|
||||||
|
height = textSize.height
|
||||||
|
|
||||||
|
default:
|
||||||
|
x = (contentWidth - space) / 2 + imageSize.width / 2 - textSize.width / 2 + space
|
||||||
|
y = contentHeight / 2 - textSize.height / 2
|
||||||
|
width = textSize.width
|
||||||
|
height = textSize.height
|
||||||
|
}
|
||||||
|
|
||||||
|
self.titleRect = CGRect(x: x, y: y, width: width, height: height)
|
||||||
|
return self.titleRect
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
private var borderColors: [UInt : UIColor] = [:]
|
||||||
|
|
||||||
|
func jx_setBorderColor(_ color: UIColor?, for state: UIControl.State) {
|
||||||
|
var arr = self.borderColors
|
||||||
|
let index = state.rawValue
|
||||||
|
arr[index] = color
|
||||||
|
if state == .selected {
|
||||||
|
let i = index + UIControl.State.highlighted.rawValue
|
||||||
|
arr[i] = color
|
||||||
|
}
|
||||||
|
self.borderColors = arr
|
||||||
|
updateStatus()
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateStatus() {
|
||||||
|
var color = self.borderColors[self.state.rawValue]?.cgColor
|
||||||
|
if (color == nil) {
|
||||||
|
color = self.borderColors[UIControl.State.normal.rawValue]?.cgColor
|
||||||
|
}
|
||||||
|
self.layer.borderColor = color
|
||||||
|
}
|
||||||
|
|
||||||
|
override var isSelected: Bool {
|
||||||
|
didSet {
|
||||||
|
updateStatus()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
|
||||||
|
extension JXButton {
|
||||||
|
|
||||||
|
private func _textSize() -> CGSize {
|
||||||
|
// let size = CGSize(width: self.bounds.size.width, height: self.bounds.size.height)
|
||||||
|
|
||||||
|
var attr: [NSAttributedString.Key : Any] = [:]
|
||||||
|
attr[NSAttributedString.Key.font] = jx_font
|
||||||
|
|
||||||
|
let paragraphStyle = NSMutableParagraphStyle()
|
||||||
|
paragraphStyle.lineBreakMode = .byTruncatingMiddle
|
||||||
|
attr[NSAttributedString.Key.paragraphStyle] = paragraphStyle
|
||||||
|
|
||||||
|
// var rect = self.currentTitle?.ocString.boundingRect(with: size, options: [.usesLineFragmentOrigin, .usesFontLeading], attributes: attr, context: nil) ?? .zero
|
||||||
|
|
||||||
|
if let font = jx_font {
|
||||||
|
var size = self.currentTitle?.size(font: font) ?? .zero
|
||||||
|
|
||||||
|
if maxTitleWidth != 0 && maxTitleWidth < size.width {
|
||||||
|
size = CGSize(width: maxTitleWidth, height: size.height)
|
||||||
|
}
|
||||||
|
// if maxTitleWidth != 0 && maxTitleWidth < rect.size.width {
|
||||||
|
// rect.size = CGSize(width: maxTitleWidth, height: rect.size.height)
|
||||||
|
// }
|
||||||
|
return size
|
||||||
|
} else {
|
||||||
|
return .zero
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
}
|