解锁视频开发

This commit is contained in:
zeng 2025-05-06 19:52:12 +08:00
parent e4e92ee9bf
commit b710eb9ac1
19 changed files with 388 additions and 13 deletions

View File

@ -101,4 +101,19 @@ class SPWalletAPI: NSObject {
} }
} }
///
static func requestCoinUnlockVideo(shortPlayId: String, videoId: String, completer: ((_ model: SPVideoUnlockModel?) -> Void)?) {
var param = SPNetworkParameters(path: "/buy_video")
param.isLoding = true
param.parameters = [
"short_play_id" : shortPlayId,
"video_id" : videoId,
]
SPNetwork.request(parameters: param) { (response: SPNetworkResponse<SPVideoUnlockModel>) in
completer?(response.data)
}
}
} }

View File

@ -27,7 +27,7 @@ class SPMineMemberNoView: UIView {
private lazy var activateButton: UIButton = { private lazy var activateButton: UIButton = {
let button = JXButton(type: .custom) let button = JXButton(type: .custom)
button.leftAnyRightmargin = 13 button.leftAndRightMargin = 13
button.setTitle("Activate".localized, for: .normal) button.setTitle("Activate".localized, for: .normal)
button.setTitleColor(.colorFFD791(), for: .normal) button.setTitleColor(.colorFFD791(), for: .normal)
button.jx_font = .fontMedium(ofSize: 14) button.jx_font = .fontMedium(ofSize: 14)

View File

@ -48,7 +48,7 @@ class SPMineMemberYesView: UIView {
button.setTitle("Stream Unlimited".localized, for: .normal) button.setTitle("Stream Unlimited".localized, for: .normal)
button.setTitleColor(.color321704(), for: .normal) button.setTitleColor(.color321704(), for: .normal)
button.jx_font = .fontRegular(ofSize: 12) button.jx_font = .fontRegular(ofSize: 12)
button.leftAnyRightmargin = 12 button.leftAndRightMargin = 12
button.colors = [UIColor.colorF2A3A3().cgColor, UIColor.colorFEE095().cgColor] button.colors = [UIColor.colorF2A3A3().cgColor, UIColor.colorFEE095().cgColor]
button.locations = [0, 1] button.locations = [0, 1]
button.startPoint = .init(x: 0, y: 0.5) button.startPoint = .init(x: 0, y: 0.5)

View File

@ -81,8 +81,7 @@ class SPPlayerDetailViewController: SPPlayerListViewController {
if videoInfo.is_lock == true { if videoInfo.is_lock == true {
self.pause() self.pause()
let view = SPPlayBuyView() self.onPlayBuy()
view.present(in: nil)
return return
} }
@ -130,13 +129,12 @@ extension SPPlayerDetailViewController {
self.viewModel.handleEpisode = { [weak self] in self.viewModel.handleEpisode = { [weak self] in
self?.onEpisode() self?.onEpisode()
} }
} }
} }
extension SPPlayerDetailViewController { extension SPPlayerDetailViewController {
private func onEpisode() { private func onEpisode() {
let view = SPEpisodeView() let view = SPEpisodeView()
view.dataArr = detailModel?.episodeList ?? [] view.dataArr = detailModel?.episodeList ?? []
@ -149,6 +147,48 @@ extension SPPlayerDetailViewController {
self.episodeView = view self.episodeView = view
} }
///
private func onPlayBuy() {
let view = SPPlayBuyView()
view.present(in: nil)
}
///
private func unlockVideo(indexPath: IndexPath) {
guard let videoInfo = detailModel?.episodeList?[indexPath.row] else { return }
guard let shortPlayId = videoInfo.short_play_id, let videoId = videoInfo.short_play_video_id else { return }
SPWalletAPI.requestCoinUnlockVideo(shortPlayId: shortPlayId, videoId: videoId) { [weak self] model in
guard let self = self else { return }
guard let model = model else { return }
switch model.status {
case .jump:
SPToast.show(text: "kAlertMessage_01".localized)
case .noPlay:
SPToast.show(text: "kAlertMessage_02".localized)
case .notEnough:
self.onPlayBuy()
case .success:
videoInfo.is_lock = false
self.reloadData { [weak self] in
guard let self = self else { return }
self.play()
}
default:
break
}
}
}
} }
//MARK: -------------- SPPlayerListViewControllerDataSource -------------- //MARK: -------------- SPPlayerListViewControllerDataSource --------------
@ -158,6 +198,20 @@ extension SPPlayerDetailViewController: SPPlayerListViewControllerDataSource, SP
cell.shortModel = detailModel?.shortPlayInfo cell.shortModel = detailModel?.shortPlayInfo
cell.videoInfo = detailModel?.episodeList?[indexPath.row] cell.videoInfo = detailModel?.episodeList?[indexPath.row]
cell.isLoop = false cell.isLoop = false
let upRow = indexPath.row - 1
if upRow >= 0, let videoInfo = detailModel?.episodeList?[upRow], videoInfo.is_lock == true {
cell.hasLockUpEpisode = true
} else {
cell.hasLockUpEpisode = false
}
cell.clickUnlockButton = { [weak self] (cell) in
guard let self = self else { return }
guard let indexPath = self.collectionView.indexPath(for: cell) else { return }
self.unlockVideo(indexPath: indexPath)
}
} }
return oldCell return oldCell
} }

View File

@ -186,7 +186,11 @@ class SPPlayerListViewController: SPViewController {
} }
func reloadData(completion: (() -> Void)? = nil) { func reloadData(completion: (() -> Void)? = nil) {
CATransaction.setCompletionBlock { 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?() completion?()
} }
CATransaction.begin() CATransaction.begin()

View File

@ -0,0 +1,175 @@
//
// SPPlayLockView.swift
// MoviaBox
//
// Created by on 2025/5/6.
//
import UIKit
class SPPlayLockView: UIView {
///
var isUnlockUpEpisode: Bool = false {
didSet {
updateUnlockButton()
}
}
var videoInfo: SPVideoInfoModel? {
didSet {
coinView.setTitle("\(videoInfo?.coins ?? 0)", for: .normal)
}
}
///
var clickUnlockButton: (() -> Void)?
//MARK: UI
private lazy var containerView: UIView = {
let view = UIView()
return view
}()
private lazy var lockImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "lock_icon_02"))
return imageView
}()
private lazy var lockTextLabel: UILabel = {
let label = UILabel()
label.font = .fontRegular(ofSize: 18)
label.textColor = .colorFFFFFF()
label.text = "This episode is locked".localized
return label
}()
private lazy var unlockButton: UIButton = {
let button = UIButton()
button.backgroundColor = .colorFF3232()
button.layer.cornerRadius = 27
button.layer.masksToBounds = true
button.addTarget(self, action: #selector(handleUnlockButton), for: .touchUpInside)
return button
}()
private lazy var unlockStackView: UIStackView = {
let view = UIStackView()
view.isUserInteractionEnabled = false
view.axis = .horizontal
view.spacing = 6
view.alignment = .center
return view
}()
private lazy var unlockImageView: UIImageView = {
let imageView = UIImageView(image: UIImage(named: "unlock_icon_01"))
return imageView
}()
private lazy var unlockTitleLabel: UILabel = {
let label = UILabel()
label.font = .fontRegular(ofSize: 16)
label.textColor = .colorFFFFFF()
return label
}()
private lazy var coinView: UIButton = {
let view = JXButton(type: .custom)
view.isUserInteractionEnabled = false
view.backgroundColor = .color000000(alpha: 0.2)
view.layer.cornerRadius = 19
view.layer.masksToBounds = true
view.jx_font = .fontRegular(ofSize: 16)
view.setTitleColor(.colorFFFFFF(), for: .normal)
view.setImage(UIImage(named: "coin_icon_04"), for: .normal)
view.space = 8
view.leftAndRightMargin = 8
view.snp.makeConstraints { make in
make.height.equalTo(38)
}
return view
}()
override init(frame: CGRect) {
super.init(frame: frame)
backgroundColor = .color000000(alpha: 0.75)
updateUnlockButton()
_setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
@objc private func handleUnlockButton() {
if isUnlockUpEpisode {
SPToast.show(text: "kAlertMessage_01".localized)
return
}
self.clickUnlockButton?()
}
private func updateUnlockButton() {
unlockStackView.removeAllArrangedSubview()
unlockStackView.addArrangedSubview(unlockImageView)
unlockStackView.addArrangedSubview(unlockTitleLabel)
if isUnlockUpEpisode {
unlockTitleLabel.text = "Unlock the previous episode".localized
} else {
unlockTitleLabel.text = "Unlock now for".localized
unlockStackView.addArrangedSubview(coinView)
}
}
}
extension SPPlayLockView {
private func _setupUI() {
addSubview(containerView)
containerView.addSubview(lockImageView)
containerView.addSubview(lockTextLabel)
containerView.addSubview(unlockButton)
unlockButton.addSubview(unlockStackView)
containerView.snp.makeConstraints { make in
make.centerY.equalToSuperview()
make.left.right.equalToSuperview()
}
lockImageView.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalToSuperview()
}
lockTextLabel.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.top.equalTo(lockImageView.snp.bottom).offset(13)
}
unlockButton.snp.makeConstraints { make in
make.centerX.equalToSuperview()
make.left.equalToSuperview().offset(22)
make.bottom.equalToSuperview()
make.top.equalTo(lockTextLabel.snp.bottom).offset(35)
make.height.equalTo(54)
}
unlockStackView.snp.makeConstraints { make in
make.top.bottom.equalToSuperview()
make.centerX.equalToSuperview()
}
}
}

View File

@ -107,9 +107,6 @@ class SPPlayerControlView: UIView {
return button return button
}() }()
///
// private lazy var lock
deinit { deinit {
viewModel?.removeObserver(self, forKeyPath: "isPlaying") viewModel?.removeObserver(self, forKeyPath: "isPlaying")
NotificationCenter.default.removeObserver(self) NotificationCenter.default.removeObserver(self)

View File

@ -13,9 +13,26 @@ class SPPlayerDetailCell: SPPlayerListCell {
return SPPlayerDetailControlView.self return SPPlayerDetailControlView.self
} }
///
var clickUnlockButton: ((_ cell: SPPlayerDetailCell) -> Void)?
///
var hasLockUpEpisode = false {
didSet {
guard let controlView = self.controlView as? SPPlayerDetailControlView else { return }
controlView.hasLockUpEpisode = hasLockUpEpisode
}
}
override init(frame: CGRect) { override init(frame: CGRect) {
super.init(frame: frame) super.init(frame: frame)
if let controlView = self.controlView as? SPPlayerDetailControlView {
controlView.clickUnlockButton = { [weak self] in
guard let self = self else { return }
self.clickUnlockButton?(self)
}
}
} }
@MainActor required init?(coder: NSCoder) { @MainActor required init?(coder: NSCoder) {

View File

@ -72,6 +72,27 @@ class SPPlayerDetailControlView: SPPlayerControlView {
} }
} }
override var videoInfo: SPVideoInfoModel? {
didSet {
if videoInfo?.is_lock == true {
lockView.isHidden = false
lockView.videoInfo = videoInfo
} else {
lockView.isHidden = true
}
}
}
///
var hasLockUpEpisode = false {
didSet {
lockView.isUnlockUpEpisode = hasLockUpEpisode
}
}
///
var clickUnlockButton: (() -> Void)?
/// ///
private var timer: Timer? private var timer: Timer?
@ -129,6 +150,15 @@ class SPPlayerDetailControlView: SPPlayerControlView {
return button return button
}() }()
///
private lazy var lockView: SPPlayLockView = {
let view = SPPlayLockView()
view.clickUnlockButton = { [weak self] in
self?.clickUnlockButton?()
}
return view
}()
deinit { deinit {
self.viewModel?.removeObserver(self, forKeyPath: "speedModel") self.viewModel?.removeObserver(self, forKeyPath: "speedModel")
} }
@ -219,6 +249,8 @@ extension SPPlayerDetailControlView {
toolView.addSubview(progressTimeLabel) toolView.addSubview(progressTimeLabel)
addSubview(retreatButton) addSubview(retreatButton)
addSubview(advanceButton) addSubview(advanceButton)
addSubview(lockView)
// self.bringSubviewToFront(rightFeatureView)
self.progressTimeLabel.snp.makeConstraints { make in self.progressTimeLabel.snp.makeConstraints { make in
make.centerY.equalToSuperview() make.centerY.equalToSuperview()
@ -235,6 +267,10 @@ extension SPPlayerDetailControlView {
make.left.equalTo(playImageView.snp.right).offset(kSPMainW(50)) make.left.equalTo(playImageView.snp.right).offset(kSPMainW(50))
} }
lockView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
} }
} }

View File

@ -43,7 +43,7 @@ class SPPlayerListCell: SPCollectionViewCell, SPPlayerProtocol {
return imageView return imageView
}() }()
private lazy var controlView: SPPlayerControlView = { private(set) lazy var controlView: SPPlayerControlView = {
let view = PlayerControlViewClass.init() 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 }

View File

@ -0,0 +1,26 @@
//
// SPVideoUnlockModel.swift
// MoviaBox
//
// Created by on 2025/5/6.
//
import UIKit
import SmartCodable
class SPVideoUnlockModel: SPModel, SmartCodable {
enum ResponseStatus: String, SmartCaseDefaultable {
///
case jump = "jump"
///
case noPlay = "no_play"
///
case notEnough = "not_enough"
///
case success = "success"
}
var status: ResponseStatus?
}

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Frame 125@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Frame 125@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 2.6 KiB

Binary file not shown.

After

Width:  |  Height:  |  Size: 3.8 KiB

View File

@ -0,0 +1,22 @@
{
"images" : [
{
"idiom" : "universal",
"scale" : "1x"
},
{
"filename" : "Frame@2x.png",
"idiom" : "universal",
"scale" : "2x"
},
{
"filename" : "Frame@3x.png",
"idiom" : "universal",
"scale" : "3x"
}
],
"info" : {
"author" : "xcode",
"version" : 1
}
}

Binary file not shown.

After

Width:  |  Height:  |  Size: 400 B

Binary file not shown.

After

Width:  |  Height:  |  Size: 534 B

View File

@ -85,7 +85,14 @@
"Check in" = "Check in"; "Check in" = "Check in";
"Recharge Coins" = "Recharge Coins"; "Recharge Coins" = "Recharge Coins";
"Purchase VIP" = "Purchase VIP"; "Purchase VIP" = "Purchase VIP";
"This episode is locked" = "This episode is locked";
"Unlock now for" = "Unlock now for";
"Unlock the previous episode" = "Unlock the previous episode";
///请购买上一集提示
"kAlertMessage_01" = "The previous episode of this series has not been unlocked yet. Please unlock the previous episode first.";
///没有找到视频提示
"kAlertMessage_02" = "Purchase failed, please try again later!";
"kLoginAgreementText" = "By continuing, you agree to the User Agreement and Privacy Policy"; "kLoginAgreementText" = "By continuing, you agree to the User Agreement and Privacy Policy";
"kBuyMemberTipText" = "Auto renew · Cancel anytime"; "kBuyMemberTipText" = "Auto renew · Cancel anytime";

View File

@ -17,7 +17,7 @@ class JXButton: UIButton {
var maxTitleWidth: CGFloat = 0 var maxTitleWidth: CGFloat = 0
var titleDirection: UITextLayoutDirection? var titleDirection: UITextLayoutDirection?
/// ///
var leftAnyRightmargin: CGFloat = 0 var leftAndRightMargin: CGFloat = 0
/// ///
var space: CGFloat = 0 var space: CGFloat = 0
@ -96,7 +96,7 @@ class JXButton: UIButton {
} }
} }
let size = CGSize(width: width + leftAnyRightmargin * 2, height: height) let size = CGSize(width: width + leftAndRightMargin * 2, height: height)
return size return size
} }