播放器开发完成
@ -160,5 +160,17 @@ extension UIColor {
|
||||
static func color5A5C67(alpha: CGFloat = 1) -> UIColor {
|
||||
return color(hex: 0x5A5C67, alpha: alpha)
|
||||
}
|
||||
|
||||
static func color3D4556(alpha: CGFloat = 1) -> UIColor {
|
||||
return color(hex: 0x3D4556, alpha: alpha)
|
||||
}
|
||||
|
||||
static func color1C1C1E(alpha: CGFloat = 1) -> UIColor {
|
||||
return color(hex: 0x1C1C1E, alpha: alpha)
|
||||
}
|
||||
|
||||
static func colorEC3324(alpha: CGFloat = 1) -> UIColor {
|
||||
return color(hex: 0xEC3324, alpha: alpha)
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -104,6 +104,17 @@ class SPVideoAPI: NSObject {
|
||||
}
|
||||
}
|
||||
|
||||
///获取视频分类
|
||||
static func requestShortCategoryList() {
|
||||
var param = SPNetworkParameters(path: "/getCategories")
|
||||
param.method = .get
|
||||
|
||||
SPNetwork.request(parameters: param) { (response: SPNetworkResponse<SPListModel<SPShortModel>>) in
|
||||
// completer?(response.data)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension SPVideoAPI {
|
||||
|
@ -0,0 +1,27 @@
|
||||
//
|
||||
// SPAllShortViewController.swift
|
||||
// Thimra
|
||||
//
|
||||
// Created by Overseas on 2025/4/23.
|
||||
//
|
||||
|
||||
import UIKit
|
||||
|
||||
class SPAllShortViewController: SPViewController {
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
self.view.backgroundColor = .clear
|
||||
SPVideoAPI.requestShortCategoryList()
|
||||
|
||||
}
|
||||
|
||||
override func setBgImageView() {
|
||||
|
||||
}
|
||||
}
|
||||
|
||||
extension SPAllShortViewController {
|
||||
|
||||
|
||||
}
|
@ -11,10 +11,16 @@ import UIKit
|
||||
class SPExplorePageController: SPViewController {
|
||||
|
||||
private lazy var titles: [String] = {
|
||||
let arr = ["Shorts".localized, "Featured".localized, "All".localized]
|
||||
let arr = ["Shorts".localized, "All".localized]
|
||||
return arr
|
||||
}()
|
||||
|
||||
private lazy var viewControllers: [SPViewController] = {
|
||||
let vc1 = SPExploreViewController()
|
||||
let vc2 = SPAllShortViewController()
|
||||
return [vc1, vc2]
|
||||
}()
|
||||
|
||||
private lazy var itemWidthArr: [NSNumber] = {
|
||||
var arr: [NSNumber] = []
|
||||
self.titles.forEach {
|
||||
@ -129,7 +135,7 @@ extension SPExplorePageController: WMPageControllerDelegate, WMPageControllerDat
|
||||
}
|
||||
|
||||
func pageController(_ pageController: WMPageController, viewControllerAt index: Int) -> UIViewController {
|
||||
return SPExploreViewController()
|
||||
return viewControllers[index]
|
||||
}
|
||||
|
||||
|
||||
|
@ -15,7 +15,7 @@ class SPExploreViewController: SPPlayerListViewController {
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
|
||||
self.autoNextEpisode = true
|
||||
requestDataArr(page: 1)
|
||||
|
||||
self.delegate = self
|
||||
@ -53,6 +53,7 @@ extension SPExploreViewController: SPPlayerListViewControllerDataSource {
|
||||
cell.shortModel = model
|
||||
cell.videoInfo = model.video_info
|
||||
}
|
||||
cell.isLoop = false
|
||||
}
|
||||
return oldCell
|
||||
}
|
||||
@ -70,8 +71,9 @@ extension SPExploreViewController {
|
||||
guard let self = self else { return }
|
||||
if let listModel = listModel, let list = listModel.list {
|
||||
if page == 1 {
|
||||
self.setDataArr(dataArr: list)
|
||||
self.play()
|
||||
self.setDataArr(dataArr: list) { [weak self] in
|
||||
self?.play()
|
||||
}
|
||||
} else {
|
||||
self.addDataArr(dataArr: list)
|
||||
}
|
||||
|
@ -12,27 +12,59 @@ class SPExplorePlayerControlView: SPPlayerControlView {
|
||||
|
||||
override var shortModel: SPShortModel? {
|
||||
didSet {
|
||||
desLabel.text = shortModel?.sp_description
|
||||
// desLabel.text = shortModel?.sp_description
|
||||
videoInfoView.shortModel = shortModel
|
||||
|
||||
// shortModel?.episode_total
|
||||
updateEpisodeLabel()
|
||||
}
|
||||
}
|
||||
|
||||
private lazy var desLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .fontRegular(ofSize: 12)
|
||||
label.textColor = .colorD2D2D2()
|
||||
label.numberOfLines = 2
|
||||
return label
|
||||
}()
|
||||
override var videoInfo: SPVideoInfoModel? {
|
||||
didSet {
|
||||
updateEpisodeLabel()
|
||||
}
|
||||
}
|
||||
|
||||
// private lazy var desLabel: UILabel = {
|
||||
// let label = UILabel()
|
||||
// label.font = .fontRegular(ofSize: 12)
|
||||
// label.textColor = .colorD2D2D2()
|
||||
// label.numberOfLines = 2
|
||||
// return label
|
||||
// }()
|
||||
|
||||
private lazy var videoInfoView: SPVideoPlayerInfoView = {
|
||||
let view = SPVideoPlayerInfoView()
|
||||
return view
|
||||
}()
|
||||
|
||||
private lazy var episodeIconImageView: UIImageView = {
|
||||
let imageView = UIImageView(image: UIImage(named: "episode_icon_01"))
|
||||
return imageView
|
||||
}()
|
||||
|
||||
private lazy var episodeLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .fontRegular(ofSize: 12)
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var allEpisodeButton: UIButton = {
|
||||
let button = JXButton(type: .custom)
|
||||
button.setTitle("Full Episodes".localized, for: .normal)
|
||||
button.setTitleColor(.colorEC3324(), for: .normal)
|
||||
button.setImage(UIImage(named: "arrow_right_icon_01"), for: .normal)
|
||||
button.jx_font = .fontMedium(ofSize: 12)
|
||||
button.titleDirection = .left
|
||||
button.space = 4
|
||||
button.addTarget(self, action: #selector(handleAllEpisodeButton), for: .touchUpInside)
|
||||
return button
|
||||
}()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
self.progressView.isHidden = true
|
||||
// self.progressView.isHidden = true
|
||||
|
||||
_setupUI()
|
||||
}
|
||||
@ -41,23 +73,67 @@ class SPExplorePlayerControlView: SPPlayerControlView {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
private func updateEpisodeLabel() {
|
||||
let totalEpisode = String(format: "EP%@".localized, "\(self.shortModel?.episode_total ?? 0)")
|
||||
let currentEpisode = String(format: "EP%@".localized, self.videoInfo?.episode ?? "0")
|
||||
let episodeStr = "\(currentEpisode)/\(totalEpisode)"
|
||||
let range = NSRange(location: episodeStr.length() - totalEpisode.length(), length: totalEpisode.length())
|
||||
|
||||
let string = NSMutableAttributedString(string: episodeStr)
|
||||
string.color = .colorEC3324()
|
||||
string.setColor(.colorFFFFFF(), range: range)
|
||||
|
||||
episodeLabel.attributedText = string
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension SPExplorePlayerControlView {
|
||||
|
||||
@objc private func handleAllEpisodeButton() {
|
||||
let vc = SPPlayerDetailViewController()
|
||||
vc.shortPlayId = self.shortModel?.short_play_id
|
||||
self.viewController?.navigationController?.pushViewController(vc, animated: true)
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension SPExplorePlayerControlView {
|
||||
|
||||
private func _setupUI() {
|
||||
addSubview(desLabel)
|
||||
// addSubview(desLabel)
|
||||
addSubview(videoInfoView)
|
||||
toolView.addSubview(episodeIconImageView)
|
||||
toolView.addSubview(episodeLabel)
|
||||
toolView.addSubview(allEpisodeButton)
|
||||
|
||||
desLabel.snp.makeConstraints { make in
|
||||
make.left.equalToSuperview().offset(15)
|
||||
make.right.lessThanOrEqualToSuperview().offset(-30)
|
||||
make.bottom.equalToSuperview().offset(-15)
|
||||
}
|
||||
// desLabel.snp.makeConstraints { make in
|
||||
// make.left.equalToSuperview().offset(15)
|
||||
// make.right.lessThanOrEqualToSuperview().offset(-30)
|
||||
// make.bottom.equalToSuperview().offset(-15)
|
||||
// }
|
||||
|
||||
videoInfoView.snp.makeConstraints { make in
|
||||
make.left.equalToSuperview().offset(15)
|
||||
make.bottom.equalTo(desLabel.snp.top).offset(-10)
|
||||
// make.bottom.equalTo(desLabel.snp.top).offset(-10)
|
||||
make.bottom.equalToSuperview().offset(-54)
|
||||
}
|
||||
|
||||
episodeIconImageView.snp.makeConstraints { make in
|
||||
make.centerY.equalToSuperview()
|
||||
make.left.equalToSuperview().offset(14)
|
||||
}
|
||||
|
||||
episodeLabel.snp.makeConstraints { make in
|
||||
make.centerY.equalToSuperview()
|
||||
make.left.equalTo(episodeIconImageView.snp.right).offset(4)
|
||||
}
|
||||
|
||||
allEpisodeButton.snp.makeConstraints { make in
|
||||
make.top.bottom.equalToSuperview()
|
||||
make.right.equalToSuperview().offset(-14)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -13,47 +13,36 @@ class SPVideoPlayerInfoView: UIView {
|
||||
didSet {
|
||||
coverImageView.sp_setImage(url: shortModel?.image_url)
|
||||
titleLabel.text = shortModel?.name
|
||||
desLabel.text = shortModel?.sp_description
|
||||
}
|
||||
}
|
||||
|
||||
//MARK: UI属性
|
||||
private lazy var bgView: UIView = {
|
||||
let view = UIView()
|
||||
view.layer.cornerRadius = 5
|
||||
view.layer.masksToBounds = true
|
||||
view.backgroundColor = .color000000(alpha: 0.27)
|
||||
return view
|
||||
}()
|
||||
override var intrinsicContentSize: CGSize {
|
||||
return CGSize(width: 200, height: 68)
|
||||
}
|
||||
|
||||
//MARK: UI属性
|
||||
private lazy var coverImageView: SPImageView = {
|
||||
let imageView = SPImageView()
|
||||
imageView.layer.cornerRadius = 5
|
||||
imageView.layer.cornerRadius = 4
|
||||
imageView.layer.masksToBounds = true
|
||||
imageView.layer.borderWidth = 1
|
||||
imageView.layer.borderColor = UIColor.colorFFFFFF(alpha: 0.26).cgColor
|
||||
return imageView
|
||||
}()
|
||||
|
||||
private lazy var titleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .fontRegular(ofSize: 13)
|
||||
label.font = .fontMedium(ofSize: 14)
|
||||
label.textColor = .colorFFFFFF()
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var moreButton: JXButton = {
|
||||
let button = JXButton(type: .custom)
|
||||
button.colors = [UIColor.colorF56490().cgColor, UIColor.colorD568D2().cgColor]
|
||||
button.startPoint = .init(x: 0, y: 0.5)
|
||||
button.endPoint = .init(x: 1, y: 0.5)
|
||||
button.locations = [0, 1]
|
||||
button.setImage(UIImage(named: "play_icon_02"), for: .normal)
|
||||
button.setTitle("Series".localized, for: .normal)
|
||||
button.setTitleColor(.colorFFFFFF(), for: .normal)
|
||||
button.jx_font = .fontRegular(ofSize: 11)
|
||||
button.layer.cornerRadius = 10.5
|
||||
button.layer.masksToBounds = true
|
||||
button.space = 2
|
||||
button.addTarget(self, action: #selector(handleMoreButton), for: .touchUpInside)
|
||||
return button
|
||||
private lazy var desLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .fontRegular(ofSize: 12)
|
||||
label.textColor = .colorA8A5AA()
|
||||
return label
|
||||
}()
|
||||
|
||||
override init(frame: CGRect) {
|
||||
@ -76,34 +65,30 @@ class SPVideoPlayerInfoView: UIView {
|
||||
extension SPVideoPlayerInfoView {
|
||||
|
||||
private func _setupUI() {
|
||||
addSubview(bgView)
|
||||
|
||||
addSubview(coverImageView)
|
||||
addSubview(titleLabel)
|
||||
addSubview(moreButton)
|
||||
addSubview(desLabel)
|
||||
|
||||
bgView.snp.makeConstraints { make in
|
||||
make.left.right.bottom.equalToSuperview()
|
||||
make.width.equalTo(240)
|
||||
make.height.equalTo(54)
|
||||
}
|
||||
|
||||
coverImageView.snp.makeConstraints { make in
|
||||
make.left.bottom.top.equalToSuperview()
|
||||
make.width.equalTo(49)
|
||||
make.height.equalTo(66)
|
||||
make.left.top.bottom.equalToSuperview()
|
||||
make.width.equalTo(46)
|
||||
make.height.equalTo(68)
|
||||
}
|
||||
|
||||
titleLabel.snp.makeConstraints { make in
|
||||
make.left.equalTo(coverImageView.snp.right).offset(10)
|
||||
make.top.equalTo(bgView).offset(5)
|
||||
make.right.lessThanOrEqualToSuperview().offset(-18)
|
||||
make.top.equalToSuperview().offset(16)
|
||||
make.right.lessThanOrEqualToSuperview()
|
||||
make.width.lessThanOrEqualTo(145)
|
||||
}
|
||||
|
||||
moreButton.snp.makeConstraints { make in
|
||||
make.left.equalTo(coverImageView.snp.right).offset(10)
|
||||
make.bottom.equalToSuperview().offset(-5)
|
||||
make.width.equalTo(59)
|
||||
make.height.equalTo(21)
|
||||
desLabel.snp.makeConstraints { make in
|
||||
make.left.equalTo(titleLabel)
|
||||
make.top.equalTo(titleLabel.snp.bottom).offset(8)
|
||||
make.right.lessThanOrEqualToSuperview()
|
||||
make.width.lessThanOrEqualTo(145)
|
||||
}
|
||||
|
||||
|
||||
|
@ -14,7 +14,7 @@ class SPPlayerDetailViewController: SPPlayerListViewController {
|
||||
}
|
||||
|
||||
override var contentSize: CGSize {
|
||||
return CGSize(width: kSPScreenWidth, height: kSPScreenHeight)
|
||||
return CGSize(width: kSPScreenWidth, height: kSPScreenHeight - kSPTabbarSafeBottomMargin - 35)
|
||||
}
|
||||
|
||||
|
||||
@ -37,11 +37,24 @@ class SPPlayerDetailViewController: SPPlayerListViewController {
|
||||
|
||||
private lazy var titleLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .fontLight(ofSize: 15)
|
||||
label.textColor = .colorFFFFFF(alpha: 0.9)
|
||||
label.font = .fontBold(ofSize: 18)
|
||||
label.textColor = .colorFFFFFF()
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var episodeLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .fontMedium(ofSize: 18)
|
||||
label.textColor = .colorFFFFFF(alpha: 0.4)
|
||||
return label
|
||||
}()
|
||||
|
||||
private lazy var bottomView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .color1C1C1E()
|
||||
return view
|
||||
}()
|
||||
|
||||
override func viewDidLoad() {
|
||||
super.viewDidLoad()
|
||||
self.autoNextEpisode = true
|
||||
@ -78,6 +91,8 @@ extension SPPlayerDetailViewController {
|
||||
private func _setupUI() {
|
||||
view.addSubview(backButton)
|
||||
view.addSubview(titleLabel)
|
||||
view.addSubview(episodeLabel)
|
||||
view.addSubview(bottomView)
|
||||
|
||||
backButton.snp.makeConstraints { make in
|
||||
make.left.equalToSuperview().offset(5)
|
||||
@ -88,7 +103,18 @@ extension SPPlayerDetailViewController {
|
||||
titleLabel.snp.makeConstraints { make in
|
||||
make.left.equalTo(backButton.snp.right)
|
||||
make.centerY.equalTo(backButton)
|
||||
make.right.equalToSuperview().offset(-15)
|
||||
// make.right.equalToSuperview().offset(-15)
|
||||
make.width.lessThanOrEqualTo(kSPScreenWidth - 130)
|
||||
}
|
||||
|
||||
episodeLabel.snp.makeConstraints { make in
|
||||
make.centerY.equalTo(titleLabel)
|
||||
make.left.equalTo(titleLabel.snp.right).offset(16)
|
||||
}
|
||||
|
||||
bottomView.snp.makeConstraints { make in
|
||||
make.left.right.bottom.equalToSuperview()
|
||||
make.top.equalTo(self.collectionView.snp.bottom)
|
||||
}
|
||||
}
|
||||
|
||||
@ -136,7 +162,9 @@ extension SPPlayerDetailViewController: SPPlayerListViewControllerDataSource, SP
|
||||
self.episodeView?.currentIndex = indexPath.row
|
||||
let videoInfo = detailModel?.episodeList?[indexPath.row]
|
||||
|
||||
titleLabel.text = String(format: "kPlayerDetailTitleString".localized, "\(videoInfo?.episode ?? "0")", self.detailModel?.shortPlayInfo?.name ?? "")
|
||||
// titleLabel.text = String(format: "kPlayerDetailTitleString".localized, "\(videoInfo?.episode ?? "0")", self.detailModel?.shortPlayInfo?.name ?? "")
|
||||
titleLabel.text = detailModel?.shortPlayInfo?.name
|
||||
episodeLabel.text = "\(videoInfo?.episode ?? "0")/\(detailModel?.shortPlayInfo?.episode_total ?? 0)"
|
||||
}
|
||||
}
|
||||
|
||||
|
@ -121,9 +121,9 @@ class SPPlayerListViewController: SPViewController {
|
||||
}
|
||||
|
||||
|
||||
func setDataArr(dataArr: [Any]) {
|
||||
func setDataArr(dataArr: [Any], completer: (() -> Void)?) {
|
||||
self.dataArr = dataArr
|
||||
reloadData()
|
||||
reloadData(completion: completer)
|
||||
}
|
||||
|
||||
func addDataArr(dataArr: [Any]) {
|
||||
@ -260,7 +260,7 @@ extension SPPlayerListViewController {
|
||||
var contentOffset = self.collectionView.contentOffset
|
||||
|
||||
if hasNextEpisode() {
|
||||
contentOffset.y = contentOffset.y + self.contentSize.height
|
||||
contentOffset.y = floor(contentOffset.y + self.contentSize.height)
|
||||
self.collectionView.setContentOffset(contentOffset, animated: true)
|
||||
} else {
|
||||
self.viewModel.currentPlayer?.replay()
|
||||
|
@ -17,6 +17,12 @@ protocol SPPlayerProtocol: NSObjectProtocol {
|
||||
|
||||
var isCurrent: Bool { get set }
|
||||
|
||||
|
||||
///总进度
|
||||
var duration: Int { get }
|
||||
///当前进度
|
||||
var currentPosition: Int { get }
|
||||
|
||||
var rate: Float { get set }
|
||||
|
||||
///播放准备
|
||||
@ -31,4 +37,6 @@ protocol SPPlayerProtocol: NSObjectProtocol {
|
||||
///从头播放
|
||||
func replay()
|
||||
|
||||
///设置进度
|
||||
func seekToTime(toTime: Int)
|
||||
}
|
||||
|
@ -169,7 +169,7 @@ class SPEpisodeView: HWPanModalContentView {
|
||||
|
||||
override func backgroundConfig() -> HWBackgroundConfig {
|
||||
let config = HWBackgroundConfig()
|
||||
config.backgroundAlpha = 0.4
|
||||
config.backgroundAlpha = 0.6
|
||||
return config
|
||||
}
|
||||
|
||||
|
@ -78,11 +78,18 @@ class SPPlayerControlView: UIView {
|
||||
return view
|
||||
}()
|
||||
|
||||
private lazy var playImageView: UIImageView = {
|
||||
let imageView = UIImageView()
|
||||
imageView.backgroundColor = .red
|
||||
imageView.isHidden = true
|
||||
return imageView
|
||||
private(set) lazy var toolView: UIView = {
|
||||
let view = UIView()
|
||||
view.backgroundColor = .color1C1C1E()
|
||||
return view
|
||||
}()
|
||||
|
||||
private(set) lazy var playImageView: UIButton = {
|
||||
let button = UIButton(type: .custom)
|
||||
button.isHidden = true
|
||||
button.setImage(UIImage(named: "play_icon_01"), for: .normal)
|
||||
button.isUserInteractionEnabled = false
|
||||
return button
|
||||
}()
|
||||
|
||||
///右边功能区
|
||||
@ -110,7 +117,7 @@ class SPPlayerControlView: UIView {
|
||||
|
||||
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(handleScreen))
|
||||
tap.delegate = self
|
||||
self.addGestureRecognizer(tap)
|
||||
|
||||
@ -139,12 +146,24 @@ class SPPlayerControlView: UIView {
|
||||
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);
|
||||
button.setTitleColor(.colorFFFFFF(), for: .normal)
|
||||
// button.setTitleColor(.colorF564B6(), for: .selected)
|
||||
button.jx_font = .fontMedium(ofSize: 12)
|
||||
return button
|
||||
}
|
||||
|
||||
@objc func handleScreen() {
|
||||
self.hadlePlayAndOrPaused()
|
||||
}
|
||||
|
||||
func updatePlayIconState() {
|
||||
let isPlaying = self.viewModel?.isPlaying ?? false
|
||||
if isCurrent {
|
||||
playImageView.isHidden = isPlaying
|
||||
} else {
|
||||
playImageView.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@ -153,6 +172,7 @@ extension SPPlayerControlView {
|
||||
private func sp_setupUI() {
|
||||
addSubview(bottomGradientView)
|
||||
addSubview(progressView)
|
||||
progressView.addSubview(toolView)
|
||||
addSubview(playImageView)
|
||||
addSubview(rightFeatureView)
|
||||
|
||||
@ -162,15 +182,17 @@ extension SPPlayerControlView {
|
||||
}
|
||||
|
||||
progressView.snp.makeConstraints { make in
|
||||
make.left.equalToSuperview().offset(10)
|
||||
make.centerX.equalToSuperview()
|
||||
make.bottom.equalToSuperview().offset(-20)
|
||||
make.height.equalTo(30)
|
||||
make.left.right.bottom.equalToSuperview()
|
||||
make.height.equalTo(40)
|
||||
}
|
||||
|
||||
toolView.snp.makeConstraints { make in
|
||||
make.left.right.top.equalToSuperview()
|
||||
make.bottom.equalToSuperview().offset(-2)
|
||||
}
|
||||
|
||||
playImageView.snp.makeConstraints { make in
|
||||
make.center.equalToSuperview()
|
||||
make.width.height.equalTo(100)
|
||||
}
|
||||
|
||||
rightFeatureView.snp.makeConstraints { make in
|
||||
@ -182,15 +204,6 @@ extension SPPlayerControlView {
|
||||
|
||||
extension SPPlayerControlView {
|
||||
|
||||
private func updatePlayIconState() {
|
||||
let isPlaying = self.viewModel?.isPlaying ?? false
|
||||
if isCurrent {
|
||||
playImageView.isHidden = isPlaying
|
||||
} else {
|
||||
playImageView.isHidden = true
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func hadlePlayAndOrPaused() {
|
||||
self.viewModel?.handlePauseOrPlay?()
|
||||
|
||||
|
@ -8,6 +8,15 @@
|
||||
import UIKit
|
||||
|
||||
class SPPlayerDetailControlView: SPPlayerControlView {
|
||||
///暂停按钮状态
|
||||
enum PauseState {
|
||||
///隐藏状态
|
||||
case hidden
|
||||
///显示暂停按钮
|
||||
case showPause
|
||||
///显示播放按钮
|
||||
case showPlay
|
||||
}
|
||||
|
||||
|
||||
override var durationTime: Int {
|
||||
@ -33,22 +42,50 @@ class SPPlayerDetailControlView: SPPlayerControlView {
|
||||
didSet {
|
||||
if isCurrent {
|
||||
showSpeedSelectedView(isShow: false)
|
||||
} else {
|
||||
self.timer?.invalidate()
|
||||
self.timer = nil
|
||||
self.pauseState = .hidden
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///暂停按钮状态
|
||||
private lazy var pauseState = PauseState.hidden {
|
||||
didSet {
|
||||
switch pauseState {
|
||||
case .hidden:
|
||||
self.playImageView.isHidden = true
|
||||
self.retreatButton.isHidden = true
|
||||
self.advanceButton.isHidden = true
|
||||
case .showPause:
|
||||
self.playImageView.isHidden = false
|
||||
self.retreatButton.isHidden = false
|
||||
self.advanceButton.isHidden = false
|
||||
self.playImageView.setImage(UIImage(named: "pause_icon_01"), for: .normal)
|
||||
case .showPlay:
|
||||
self.playImageView.isHidden = false
|
||||
self.retreatButton.isHidden = false
|
||||
self.advanceButton.isHidden = false
|
||||
self.playImageView.setImage(UIImage(named: "play_icon_01"), for: .normal)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
///暂停按钮倒计时
|
||||
private var timer: Timer?
|
||||
|
||||
//MARK: UI属性
|
||||
private lazy var episodeButton: UIButton = {
|
||||
let button = createFeatureButton(title: "Episodes".localized, image: UIImage(named: "episodes_icon_01"))
|
||||
let button = createFeatureButton(title: "Episodes".localized, image: UIImage(named: "episode_icon_02"))
|
||||
button.addTarget(self, action: #selector(handleEpisodeButton), for: .touchUpInside)
|
||||
return button
|
||||
}()
|
||||
|
||||
private lazy var progressTimeLabel: UILabel = {
|
||||
let label = UILabel()
|
||||
label.font = .fontLight(ofSize: 12)
|
||||
label.textColor = .colorFFFFFF(alpha: 0.9)
|
||||
label.font = .fontRegular(ofSize: 12)
|
||||
label.textColor = .colorFFFFFF()
|
||||
return label
|
||||
}()
|
||||
|
||||
@ -74,12 +111,32 @@ class SPPlayerDetailControlView: SPPlayerControlView {
|
||||
return view
|
||||
}()
|
||||
|
||||
///快退
|
||||
private lazy var retreatButton: UIButton = {
|
||||
let button = UIButton(type: .custom)
|
||||
button.isHidden = true
|
||||
button.setImage(UIImage(named: "speed_icon_01"), for: .normal)
|
||||
button.addTarget(self, action: #selector(handleRetreatButton), for: .touchUpInside)
|
||||
return button
|
||||
}()
|
||||
|
||||
///快进
|
||||
private lazy var advanceButton: UIButton = {
|
||||
let button = UIButton(type: .custom)
|
||||
button.isHidden = true
|
||||
button.setImage(UIImage(named: "speed_icon_02"), for: .normal)
|
||||
button.addTarget(self, action: #selector(handleAdvanceButton), for: .touchUpInside)
|
||||
return button
|
||||
}()
|
||||
|
||||
deinit {
|
||||
self.viewModel?.removeObserver(self, forKeyPath: "speedModel")
|
||||
}
|
||||
|
||||
override init(frame: CGRect) {
|
||||
super.init(frame: frame)
|
||||
self.playImageView.isUserInteractionEnabled = true
|
||||
self.playImageView.addTarget(self, action: #selector(handlePlayImageView), for: .touchUpInside)
|
||||
|
||||
_setupUI()
|
||||
}
|
||||
@ -107,6 +164,51 @@ class SPPlayerDetailControlView: SPPlayerControlView {
|
||||
private func updateSpeedButton() {
|
||||
self.speedButton.setTitle(self.viewModel?.speedModel.formatString(), for: .normal)
|
||||
}
|
||||
|
||||
///点击屏幕
|
||||
override func handleScreen() {
|
||||
|
||||
if self.pauseState == .hidden {
|
||||
self.pauseState = .showPause
|
||||
resetTimer()
|
||||
} else if self.pauseState == .showPause {
|
||||
self.pauseState = .hidden
|
||||
self.cleanTimer()
|
||||
}
|
||||
}
|
||||
|
||||
override func updatePlayIconState() {
|
||||
guard isCurrent else { return }
|
||||
guard self.pauseState != .hidden else { return }
|
||||
|
||||
if self.viewModel?.isPlaying == true {
|
||||
self.pauseState = .showPause
|
||||
self.resetTimer()
|
||||
} else {
|
||||
self.pauseState = .showPlay
|
||||
self.cleanTimer()
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
@objc private func handleHiddenTimer() {
|
||||
self.pauseState = .hidden
|
||||
}
|
||||
|
||||
///清空计时器
|
||||
private func cleanTimer() {
|
||||
self.timer?.invalidate()
|
||||
self.timer = nil
|
||||
}
|
||||
|
||||
///重置计时器
|
||||
private func resetTimer() {
|
||||
cleanTimer()
|
||||
self.timer = Timer.scheduledTimer(timeInterval: 5, target: YYWeakProxy(target: self), selector: #selector(handleHiddenTimer), userInfo: nil, repeats: false)
|
||||
}
|
||||
|
||||
|
||||
|
||||
}
|
||||
|
||||
extension SPPlayerDetailControlView {
|
||||
@ -114,34 +216,48 @@ extension SPPlayerDetailControlView {
|
||||
private func _setupUI() {
|
||||
self.rightFeatureView.addArrangedSubview(episodeButton)
|
||||
|
||||
addSubview(progressTimeLabel)
|
||||
addSubview(speedButton)
|
||||
addSubview(speedSelectedView)
|
||||
toolView.addSubview(progressTimeLabel)
|
||||
addSubview(retreatButton)
|
||||
addSubview(advanceButton)
|
||||
// addSubview(speedButton)
|
||||
// addSubview(speedSelectedView)
|
||||
|
||||
self.progressView.snp.remakeConstraints { make in
|
||||
make.left.equalToSuperview().offset(15)
|
||||
make.centerX.equalToSuperview()
|
||||
make.height.equalTo(30)
|
||||
make.bottom.equalToSuperview().offset(-(kSPTabbarSafeBottomMargin + 10))
|
||||
}
|
||||
// self.progressView.snp.remakeConstraints { make in
|
||||
// make.left.equalToSuperview().offset(15)
|
||||
// make.centerX.equalToSuperview()
|
||||
// make.height.equalTo(30)
|
||||
// make.bottom.equalToSuperview().offset(-(kSPTabbarSafeBottomMargin + 10))
|
||||
// }
|
||||
|
||||
self.progressTimeLabel.snp.makeConstraints { make in
|
||||
make.left.equalTo(self.progressView)
|
||||
make.bottom.equalTo(self.progressView).offset(-12)
|
||||
// make.left.equalTo(self.progressView)
|
||||
// make.bottom.equalTo(self.progressView).offset(-12)
|
||||
make.centerY.equalToSuperview()
|
||||
make.left.equalToSuperview().offset(16)
|
||||
}
|
||||
|
||||
speedButton.snp.makeConstraints { make in
|
||||
make.centerY.equalTo(self.progressTimeLabel)
|
||||
make.right.equalToSuperview().offset(-15)
|
||||
make.width.equalTo(40)
|
||||
make.height.equalTo(20)
|
||||
retreatButton.snp.makeConstraints { make in
|
||||
make.centerY.equalTo(playImageView)
|
||||
make.right.equalTo(playImageView.snp.left).offset(kSPMainW(-50))
|
||||
}
|
||||
|
||||
speedSelectedView.snp.makeConstraints { make in
|
||||
make.left.right.equalToSuperview()
|
||||
make.bottom.equalTo(self.speedButton.snp.top).offset(-30)
|
||||
advanceButton.snp.makeConstraints { make in
|
||||
make.centerY.equalTo(playImageView)
|
||||
make.left.equalTo(playImageView.snp.right).offset(kSPMainW(50))
|
||||
}
|
||||
|
||||
// speedButton.snp.makeConstraints { make in
|
||||
// make.centerY.equalTo(self.progressTimeLabel)
|
||||
// make.right.equalToSuperview().offset(-15)
|
||||
// make.width.equalTo(40)
|
||||
// make.height.equalTo(20)
|
||||
// }
|
||||
|
||||
// speedSelectedView.snp.makeConstraints { make in
|
||||
// make.left.right.equalToSuperview()
|
||||
// make.bottom.equalTo(self.speedButton.snp.top).offset(-30)
|
||||
// }
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
@ -164,4 +280,24 @@ extension SPPlayerDetailControlView {
|
||||
self.speedSelectedView.currentSpeed = self.viewModel?.speedModel.speed ?? .x1
|
||||
}
|
||||
}
|
||||
|
||||
@objc private func handlePlayImageView() {
|
||||
|
||||
|
||||
self.viewModel?.handlePauseOrPlay?()
|
||||
}
|
||||
|
||||
///快退
|
||||
@objc private func handleRetreatButton() {
|
||||
self.viewModel?.seekToTime(toTime: self.currentTime - 5)
|
||||
|
||||
resetTimer()
|
||||
}
|
||||
|
||||
///快进
|
||||
@objc private func handleAdvanceButton() {
|
||||
self.viewModel?.seekToTime(toTime: self.currentTime + 5)
|
||||
|
||||
resetTimer()
|
||||
}
|
||||
}
|
||||
|
@ -9,6 +9,7 @@ import UIKit
|
||||
|
||||
class SPPlayerListCell: SPCollectionViewCell, SPPlayerProtocol {
|
||||
|
||||
|
||||
var PlayerControlViewClass: SPPlayerControlView.Type {
|
||||
return SPPlayerControlView.self
|
||||
}
|
||||
@ -111,9 +112,22 @@ class SPPlayerListCell: SPCollectionViewCell, SPPlayerProtocol {
|
||||
}
|
||||
}
|
||||
|
||||
///总进度
|
||||
var duration: Int {
|
||||
get {
|
||||
return player.duration
|
||||
}
|
||||
}
|
||||
///当前进度
|
||||
var currentPosition: Int {
|
||||
get {
|
||||
return player.currentPosition
|
||||
}
|
||||
}
|
||||
|
||||
var rate: Float {
|
||||
set {
|
||||
return player.rate = newValue
|
||||
player.rate = newValue
|
||||
}
|
||||
get {
|
||||
return player.rate
|
||||
@ -139,6 +153,10 @@ class SPPlayerListCell: SPCollectionViewCell, SPPlayerProtocol {
|
||||
player.replay()
|
||||
}
|
||||
|
||||
func seekToTime(toTime: Int) {
|
||||
player.seekToTime(toTime: toTime)
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
extension SPPlayerListCell {
|
||||
|
@ -32,10 +32,10 @@ class SPPlayerProgressView: UIView {
|
||||
///滑动进度
|
||||
private var panProgress: CGFloat = 0
|
||||
|
||||
var progressColor: UIColor = .colorFFFFFF(alpha: 0.12)
|
||||
var currentProgress: UIColor = .colorFFFFFF(alpha: 0.48)
|
||||
var progressColor: UIColor = .color3D4556()
|
||||
var currentProgress: UIColor = .colorFFFFFF()
|
||||
|
||||
var lineWidth: CGFloat = 5
|
||||
var lineWidth: CGFloat = 2
|
||||
|
||||
///是否在滑动中
|
||||
private var isPaning: Bool = false
|
||||
|
@ -37,6 +37,10 @@ class SPPlayerListViewModel: NSObject {
|
||||
currentPlayer?.rate = speedModel.getRate()
|
||||
}
|
||||
|
||||
///设置进度
|
||||
func seekToTime(toTime: Int) {
|
||||
self.currentPlayer?.seekToTime(toTime: toTime)
|
||||
}
|
||||
|
||||
///点暂停或播放
|
||||
var handlePauseOrPlay: (() -> Void)?
|
||||
|
@ -171,8 +171,14 @@ class SPPlayer: NSObject {
|
||||
}
|
||||
|
||||
func seekToTime(toTime: Int) {
|
||||
// self.player.seek(toTime: Int64(toTime), seekMode: AVP_SEEKMODE_ACCURATE)
|
||||
self.player.seek(toTime: TimeInterval(toTime), completionHandler: nil)
|
||||
var time = toTime
|
||||
if time < 0 {
|
||||
time = 0
|
||||
}
|
||||
if time > self.duration {
|
||||
time = self.duration
|
||||
}
|
||||
self.player.seek(toTime: TimeInterval(time), completionHandler: nil)
|
||||
}
|
||||
|
||||
|
||||
|
@ -5,12 +5,12 @@
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "play@2x.png",
|
||||
"filename" : "Frame@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "play@3x.png",
|
||||
"filename" : "Frame@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
BIN
Thimra/Source/Assets.xcassets/icon/arrow_right_icon_01.imageset/Frame@2x.png
vendored
Normal file
After Width: | Height: | Size: 455 B |
BIN
Thimra/Source/Assets.xcassets/icon/arrow_right_icon_01.imageset/Frame@3x.png
vendored
Normal file
After Width: | Height: | Size: 574 B |
22
Thimra/Source/Assets.xcassets/icon/episode_icon_01.imageset/Contents.json
vendored
Normal 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
|
||||
}
|
||||
}
|
BIN
Thimra/Source/Assets.xcassets/icon/episode_icon_01.imageset/Frame@2x.png
vendored
Normal file
After Width: | Height: | Size: 393 B |
BIN
Thimra/Source/Assets.xcassets/icon/episode_icon_01.imageset/Frame@3x.png
vendored
Normal file
After Width: | Height: | Size: 551 B |
Before Width: | Height: | Size: 1.8 KiB After Width: | Height: | Size: 1.8 KiB |
Before Width: | Height: | Size: 3.3 KiB After Width: | Height: | Size: 3.3 KiB |
22
Thimra/Source/Assets.xcassets/icon/pause_icon_01.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "Frame 1912056659@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "Frame 1912056659@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
Thimra/Source/Assets.xcassets/icon/pause_icon_01.imageset/Frame 1912056659@2x.png
vendored
Normal file
After Width: | Height: | Size: 379 B |
BIN
Thimra/Source/Assets.xcassets/icon/pause_icon_01.imageset/Frame 1912056659@3x.png
vendored
Normal file
After Width: | Height: | Size: 605 B |
22
Thimra/Source/Assets.xcassets/icon/play_icon_01.imageset/Contents.json
vendored
Normal file
@ -0,0 +1,22 @@
|
||||
{
|
||||
"images" : [
|
||||
{
|
||||
"idiom" : "universal",
|
||||
"scale" : "1x"
|
||||
},
|
||||
{
|
||||
"filename" : "Frame 43@2x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "2x"
|
||||
},
|
||||
{
|
||||
"filename" : "Frame 43@3x.png",
|
||||
"idiom" : "universal",
|
||||
"scale" : "3x"
|
||||
}
|
||||
],
|
||||
"info" : {
|
||||
"author" : "xcode",
|
||||
"version" : 1
|
||||
}
|
||||
}
|
BIN
Thimra/Source/Assets.xcassets/icon/play_icon_01.imageset/Frame 43@2x.png
vendored
Normal file
After Width: | Height: | Size: 929 B |
BIN
Thimra/Source/Assets.xcassets/icon/play_icon_01.imageset/Frame 43@3x.png
vendored
Normal file
After Width: | Height: | Size: 1.3 KiB |
Before Width: | Height: | Size: 280 B |
Before Width: | Height: | Size: 431 B |
22
Thimra/Source/Assets.xcassets/icon/speed_icon_01.imageset/Contents.json
vendored
Normal 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
|
||||
}
|
||||
}
|
BIN
Thimra/Source/Assets.xcassets/icon/speed_icon_01.imageset/Frame@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
Thimra/Source/Assets.xcassets/icon/speed_icon_01.imageset/Frame@3x.png
vendored
Normal file
After Width: | Height: | Size: 1.9 KiB |
22
Thimra/Source/Assets.xcassets/icon/speed_icon_02.imageset/Contents.json
vendored
Normal 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
|
||||
}
|
||||
}
|
BIN
Thimra/Source/Assets.xcassets/icon/speed_icon_02.imageset/Frame@2x.png
vendored
Normal file
After Width: | Height: | Size: 1.3 KiB |
BIN
Thimra/Source/Assets.xcassets/icon/speed_icon_02.imageset/Frame@3x.png
vendored
Normal file
After Width: | Height: | Size: 1.8 KiB |
@ -20,7 +20,6 @@
|
||||
"Episodes" = "Episodes";
|
||||
"Save" = "Save";
|
||||
"Added" = "Added";
|
||||
"Series" = "Series";
|
||||
"Order Record" = "Order Record";
|
||||
"Language" = "Language";
|
||||
"Privacy Policy" = "Privacy Policy";
|
||||
@ -40,6 +39,9 @@
|
||||
"More" = "More";
|
||||
"Historical search" = "Historical search";
|
||||
"Top Search" = "Top Search";
|
||||
"EP%@" = "EP%@";
|
||||
"Full Episodes" = "Full Episodes";
|
||||
"All" = "All";
|
||||
"Shorts" = "Shorts";
|
||||
|
||||
|
||||
///视频详情标题
|
||||
"kPlayerDetailTitleString" = "Episode %@ / %@";
|
||||
|
@ -428,7 +428,7 @@ static NSInteger const kWMControllerCountUndefined = -1;
|
||||
WMScrollView *scrollView = [[WMScrollView alloc] init];
|
||||
scrollView.scrollsToTop = NO;
|
||||
scrollView.pagingEnabled = YES;
|
||||
scrollView.backgroundColor = [UIColor whiteColor];
|
||||
scrollView.backgroundColor = [UIColor clearColor];
|
||||
scrollView.delegate = self;
|
||||
scrollView.showsVerticalScrollIndicator = NO;
|
||||
scrollView.showsHorizontalScrollIndicator = NO;
|
||||
@ -681,7 +681,7 @@ static NSInteger const kWMControllerCountUndefined = -1;
|
||||
#pragma mark - Life Cycle
|
||||
- (void)viewDidLoad {
|
||||
[super viewDidLoad];
|
||||
self.view.backgroundColor = [UIColor whiteColor];
|
||||
self.view.backgroundColor = [UIColor clearColor];
|
||||
if (!self.childControllersCount) return;
|
||||
[self wm_calculateSize];
|
||||
[self wm_addScrollView];
|
||||
|