Commit 613ab8a6 authored by Alex朱枝文's avatar Alex朱枝文

直播、录播、banner等播放器管理类

parent 2a71c649
This diff is collapsed.
//
// YHFloatingWindow.swift
// galaxy
//
// Created by alexzzw on 2024/11/29.
// Copyright © 2024 https://www.galaxy-immi.com. All rights reserved.
//
import Foundation
//
// YHPlayer.swift
// galaxy
//
// Created by alexzzw on 2024/11/29.
// Copyright © 2024 https://www.galaxy-immi.com. All rights reserved.
//
import AgoraRtcKit
import Foundation
// MARK: - 播放器类型
enum YHPlayerType {
case main
case secondary
}
protocol YHPlayerDelegate: AnyObject {
func player(_ player: YHPlayer, didChangedToState state: AgoraMediaPlayerState, reason: AgoraMediaPlayerReason)
func player(_ player: YHPlayer, didChangedToPosition position: Int)
func player(_ player: YHPlayer, didReceiveVideoSize size: CGSize)
}
// MARK: - 播放器实例封装
class YHPlayer {
weak var delegate: YHPlayerDelegate?
let type: YHPlayerType
let playerKit: AgoraRtcMediaPlayerProtocol
private(set) var currentURL: String?
weak var currentPlayView: UIView?
private(set) var currentTitle: String?
init(type: YHPlayerType, playerKit: AgoraRtcMediaPlayerProtocol) {
self.type = type
self.playerKit = playerKit
}
func setPlayView(_ view: UIView?) {
currentPlayView = view
playerKit.setView(view)
}
func play(url: String, title: String? = nil) {
currentURL = url
currentTitle = title
let mediaSource = AgoraMediaSource()
mediaSource.url = url
mediaSource.autoPlay = true
playerKit.open(with: mediaSource)
}
func stop() {
playerKit.stop()
currentPlayView = nil
currentURL = nil
currentTitle = nil
}
func pause() {
playerKit.pause()
}
func resume() {
playerKit.play()
}
func getPosition() -> Int {
return playerKit.getPosition()
}
func getDuration() -> Int {
return playerKit.getDuration()
}
func getPlayState() -> AgoraMediaPlayerState {
return playerKit.getPlayerState()
}
}
......@@ -6,168 +6,106 @@
// Copyright © 2024 https://www.galaxy-immi.com. All rights reserved.
//
import AgoraRtcKit
// YHVODPlayerViewController.swift
import UIKit
import AgoraRtcKit
class YHVODPlayerViewController: YHBasePlayerViewController {
private let videoInfo: YHVideoInfo
private var playConfig: YHVideoPlayConfig
// MARK: - Properties
private let vodId: Int
//private let viewModel = YHVideoViewModel()
init(videoInfo: YHVideoInfo, playConfig: YHVideoPlayConfig = YHVideoPlayConfig()) {
self.videoInfo = videoInfo
self.playConfig = playConfig
// MARK: - Initialization
init(id: Int, url: String? = nil, title: String? = nil) {
self.vodId = id
super.init(nibName: nil, bundle: nil)
player?.delegate = self
if let url = url {
//play(url: url, title: title)
}
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupVideoInfo()
startPlayback()
loadVideoDetail()
}
private func setupVideoInfo() {
// 设置视频标题
title = videoInfo.title
// MARK: - Data Loading
private func loadVideoDetail() {
// viewModel.getVideoDetail(videoId: vodId) { [weak self] videoDetail, error in
// guard let self = self else { return }
//
// if let videoDetail = videoDetail {
// self.handleVideoDetailSuccess(videoDetail)
// } else {
// printLog("YHVODPlayerViewController: 请求失败")
// if let errorMsg = error?.errorMsg, !errorMsg.isEmpty {
// YHHUD.flash(message: errorMsg)
// }
// }
// }
}
// private func handleVideoDetailSuccess(_ detail: YHVideoDetailModel) {
// // 如果没有预设URL,使用接口返回的URL播放
// if currentPlayingURL == nil {
// play(url: detail.playUrl, title: detail.title)
// }
// }
}
// 设置作者信息
setupAuthorInfo()
// MARK: - YHPlayerDelegate
extension YHVODPlayerViewController: YHPlayerDelegate {
func player(_ player: YHPlayer, didChangedToState state: AgoraMediaPlayerState, reason: AgoraMediaPlayerReason) {
switch state {
case .playing:
controlView.updatePlayButton(isPlaying: true)
updatePlayCount()
// 设置播放器配置
setupPlayerConfig()
}
case .paused, .stopped:
controlView.updatePlayButton(isPlaying: false)
private func setupAuthorInfo() {
// 添加作者信息视图
let authorInfoView = YHVideoAuthorInfoView(author: videoInfo.author)
view.addSubview(authorInfoView)
case .failed:
showAlert(message: "播放失败,错误原因:\(reason.rawValue)")
authorInfoView.snp.makeConstraints { make in
make.top.equalTo(playerView.snp.bottom)
make.left.right.equalToSuperview()
make.height.equalTo(60)
default:
break
}
}
private func setupPlayerConfig() {
// 设置播放器配置
if playConfig.muteOnStart {
playerKit.adjustPlayoutVolume(0)
}
}
func player(_ player: YHPlayer, didChangedToPosition position: Int) {
let duration = player.playerKit.getDuration()
guard duration > 0 else { return }
private func startPlayback() {
// 获取合适的视频URL
let networkType: YHNetworkType = getNetworkType()
if let videoUrl = videoInfo.getBestQualityUrl(for: networkType) {
play(with: videoUrl)
} else {
//showAlert(message: "无法获取视频地址")
}
}
private func getNetworkType() -> YHNetworkType {
// 实现网络类型检测逻辑
return .wifi
}
let progress = Float(position) / Float(duration)
let currentTime = formatTime(position)
let totalTime = formatTime(duration)
// MARK: - Player Control
private func play(with url: String) {
let mediaSource = AgoraMediaSource()
mediaSource.url = url
mediaSource.autoPlay = true
let result = playerKit.open(with: mediaSource)
if result != 0 {
showAlert(message: "播放失败,错误码:\(result)")
}
controlView.updateProgress(progress,
currentTime: currentTime,
totalTime: totalTime)
}
private func updatePlayCount() {
// 更新播放次数的逻辑
func player(_ player: YHPlayer, didReceiveVideoSize size: CGSize) {
// 处理视频尺寸变化,如有需要
}
}
// 作者信息视图
class YHVideoAuthorInfoView: UIView {
private let author: YHVideoAuthor
private lazy var avatarImageView: UIImageView = {
let imageView = UIImageView()
imageView.layer.cornerRadius = 20
imageView.layer.masksToBounds = true
imageView.contentMode = .scaleAspectFill
return imageView
}()
private lazy var nicknameLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 16, weight: .medium)
return label
}()
private lazy var followButton: UIButton = {
let button = UIButton(type: .system)
button.layer.cornerRadius = 15
button.clipsToBounds = true
return button
}()
init(author: YHVideoAuthor) {
self.author = author
super.init(frame: .zero)
setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupUI() {
addSubview(avatarImageView)
addSubview(nicknameLabel)
addSubview(followButton)
// 设置约束
avatarImageView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(16)
make.centerY.equalToSuperview()
make.size.equalTo(CGSize(width: 40, height: 40))
}
nicknameLabel.snp.makeConstraints { make in
make.left.equalTo(avatarImageView.snp.right).offset(12)
make.centerY.equalToSuperview()
}
followButton.snp.makeConstraints { make in
make.right.equalToSuperview().offset(-16)
make.centerY.equalToSuperview()
make.width.equalTo(80)
make.height.equalTo(30)
}
// 设置数据
nicknameLabel.text = author.nickname
updateFollowButton()
// 加载头像
if let avatarUrl = author.avatarUrl {
// 使用SDWebImage或其他图片加载库加载头像
// avatarImageView.sd_setImage(with: URL(string: avatarUrl))
}
}
private func updateFollowButton() {
let title = author.isFollowed ? "已关注" : "关注"
let backgroundColor = author.isFollowed ? UIColor.systemGray3 : UIColor.systemBlue
followButton.setTitle(title, for: .normal)
followButton.backgroundColor = backgroundColor
followButton.setTitleColor(.white, for: .normal)
// MARK: - Helper Methods
private extension YHVODPlayerViewController {
func updatePlayCount() {
// YHAPIService.shared.updateVideoPlayCount(id: vodId) { [weak self] result in
// switch result {
// case .success:
// break
// case .failure(let error):
// printLog("更新播放次数失败: \(error)")
// }
// }
}
}
......@@ -180,7 +180,7 @@ extension YHIMHelper {
func fetchHistoryMessage(roomID: String, completion: @escaping ([EMChatMessage]?, EMError?) -> Void) {
let option = EMFetchServerMessagesOption()
option.direction = .up // 时间戳逆序的消息因为要倒置表格
EMClient.shared().chatManager?.fetchMessagesFromServer(by: roomID, conversationType: .chatRoom, cursor: nil, pageSize: 50, option: option, completion: { result, err in
EMClient.shared().chatManager?.fetchMessagesFromServer(by: roomID, conversationType: .chatRoom, cursor: nil, pageSize: 10, option: option, completion: { result, err in
DispatchQueue.main.async {
if let err = err {
// 获取失败
......
......@@ -8,20 +8,34 @@
import UIKit
protocol YHPlayerControlViewDelegate: AnyObject {
func playerControlView(_ view: YHPlayerControlView, didTapBack button: UIButton)
func playerControlView(_ view: YHPlayerControlView, didTapPlay button: UIButton)
func playerControlView(_ view: YHPlayerControlView, didSeekTo position: Float)
func playerControlView(_ view: YHPlayerControlView, didChangeQuality quality: YHVideoQuality)
func playerControlView(_ view: YHPlayerControlView, didTapFullscreen button: UIButton)
}
class YHPlayerControlView: UIView {
// MARK: - Properties
weak var delegate: YHMediaPlayerViewDelegate?
weak var delegate: YHPlayerControlViewDelegate?
// MARK: - UI Components
private lazy var dimView: UIView = {
let view = UIView()
view.backgroundColor = UIColor.black.withAlphaComponent(0.4)
return view
}()
private lazy var topBar: UIView = {
let view = UIView()
view.backgroundColor = UIColor.black.withAlphaComponent(0.5)
view.backgroundColor = .clear
return view
}()
private lazy var backButton: UIButton = {
let button = UIButton(type: .system)
button.setImage(UIImage(systemName: "chevron.left"), for: .normal)
button.setImage(UIImage(named: "icon_back_white"), for: .normal)
button.tintColor = .white
button.addTarget(self, action: #selector(backButtonTapped), for: .touchUpInside)
return button
......@@ -36,24 +50,26 @@ class YHPlayerControlView: UIView {
private lazy var bottomBar: UIView = {
let view = UIView()
view.backgroundColor = UIColor.black.withAlphaComponent(0.5)
view.backgroundColor = .clear
return view
}()
private lazy var playButton: UIButton = {
let button = UIButton(type: .system)
button.setImage(UIImage(systemName: "play.fill"), for: .normal)
button.tintColor = .white
let button = UIButton(type: .custom)
button.setImage(UIImage(named: "icon_play"), for: .normal)
button.setImage(UIImage(named: "icon_pause"), for: .selected)
button.addTarget(self, action: #selector(playButtonTapped), for: .touchUpInside)
return button
}()
private lazy var progressSlider: UISlider = {
let slider = UISlider()
slider.minimumTrackTintColor = .systemBlue
slider.maximumTrackTintColor = .gray
slider.setThumbImage(UIImage(systemName: "circle.fill")?.withTintColor(.white, renderingMode: .alwaysOriginal), for: .normal)
slider.minimumTrackTintColor = UIColor.brandMainColor
slider.maximumTrackTintColor = .white.withAlphaComponent(0.3)
slider.setThumbImage(UIImage(named: "icon_slider_thumb"), for: .normal)
slider.addTarget(self, action: #selector(sliderValueChanged(_:)), for: .valueChanged)
slider.addTarget(self, action: #selector(sliderTouchBegan(_:)), for: .touchDown)
slider.addTarget(self, action: #selector(sliderTouchEnded(_:)), for: [.touchUpInside, .touchUpOutside, .touchCancel])
return slider
}()
......@@ -68,15 +84,16 @@ class YHPlayerControlView: UIView {
private lazy var qualityButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("清晰度", for: .normal)
button.titleLabel?.font = .systemFont(ofSize: 14)
button.setTitleColor(.white, for: .normal)
button.addTarget(self, action: #selector(qualityButtonTapped), for: .touchUpInside)
return button
}()
private lazy var fullscreenButton: UIButton = {
let button = UIButton(type: .system)
button.setImage(UIImage(systemName: "arrow.up.left.and.arrow.down.right"), for: .normal)
button.tintColor = .white
let button = UIButton(type: .custom)
button.setImage(UIImage(named: "icon_fullscreen"), for: .normal)
button.setImage(UIImage(named: "icon_fullscreen_exit"), for: .selected)
button.addTarget(self, action: #selector(fullscreenButtonTapped), for: .touchUpInside)
return button
}()
......@@ -94,6 +111,7 @@ class YHPlayerControlView: UIView {
// MARK: - Setup
private func setupUI() {
addSubview(dimView)
addSubview(topBar)
addSubview(bottomBar)
......@@ -110,6 +128,10 @@ class YHPlayerControlView: UIView {
}
private func setupConstraints() {
dimView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
topBar.snp.makeConstraints { make in
make.top.left.right.equalToSuperview()
make.height.equalTo(44)
......@@ -122,7 +144,9 @@ class YHPlayerControlView: UIView {
}
titleLabel.snp.makeConstraints { make in
make.center.equalTo(topBar)
make.left.equalTo(backButton.snp.right).offset(8)
make.right.equalTo(topBar).offset(-16)
make.centerY.equalTo(topBar)
}
bottomBar.snp.makeConstraints { make in
......@@ -162,15 +186,25 @@ class YHPlayerControlView: UIView {
// MARK: - Actions
@objc private func backButtonTapped() {
// Handle back action
delegate?.playerControlView(self, didTapBack: backButton)
}
@objc private func playButtonTapped() {
delegate?.didTapPlayButton()
playButton.isSelected.toggle()
delegate?.playerControlView(self, didTapPlay: playButton)
}
@objc private func sliderValueChanged(_ slider: UISlider) {
delegate?.didSeekToPosition(slider.value)
delegate?.playerControlView(self, didSeekTo: slider.value)
timeLabel.text = "\(formatTime(slider.value)) / \(timeLabel.text?.components(separatedBy: " / ").last ?? "00:00")"
}
@objc private func sliderTouchBegan(_ slider: UISlider) {
// 可以在这里暂停播放
}
@objc private func sliderTouchEnded(_ slider: UISlider) {
// 可以在这里恢复播放
}
@objc private func qualityButtonTapped() {
......@@ -178,7 +212,8 @@ class YHPlayerControlView: UIView {
}
@objc private func fullscreenButtonTapped() {
delegate?.didToggleFullscreen()
fullscreenButton.isSelected.toggle()
delegate?.playerControlView(self, didTapFullscreen: fullscreenButton)
}
private func showQualitySelector() {
......@@ -186,7 +221,8 @@ class YHPlayerControlView: UIView {
YHVideoQuality.allCases.forEach { quality in
let action = UIAlertAction(title: quality.rawValue, style: .default) { [weak self] _ in
self?.delegate?.didChangeQuality(quality)
guard let self = self else { return }
self.delegate?.playerControlView(self, didChangeQuality: quality)
}
alert.addAction(action)
}
......@@ -197,13 +233,10 @@ class YHPlayerControlView: UIView {
viewController.present(alert, animated: true)
}
}
}
// MARK: - YHPlayerControlView Extension
extension YHPlayerControlView {
// MARK: - Public Methods
func updatePlayButton(isPlaying: Bool) {
let imageName = isPlaying ? "pause.fill" : "play.fill"
playButton.setImage(UIImage(systemName: imageName), for: .normal)
playButton.isSelected = isPlaying
}
func updateProgress(_ progress: Float, currentTime: String, totalTime: String) {
......@@ -220,4 +253,24 @@ extension YHPlayerControlView {
self.alpha = show ? 1.0 : 0.0
}
}
func updateFullscreenButton(isFullscreen: Bool) {
fullscreenButton.isSelected = isFullscreen
}
// MARK: - Helper Methods
private func formatTime(_ progress: Float) -> String {
let duration = timeLabel.text?.components(separatedBy: " / ").last ?? "00:00"
let components = duration.components(separatedBy: ":")
guard let minuteString = components.first,
let secondString = components.last,
let minutes = Int(minuteString),
let seconds = Int(secondString) else {
return "00:00"
}
let totalSeconds = minutes * 60 + seconds
let currentSeconds = Int(Float(totalSeconds) * progress)
return String(format: "%02d:%02d", currentSeconds / 60, currentSeconds % 60)
}
}
Markdown is supported
0% or
You are about to add 0 people to the discussion. Proceed with caution.
Finish editing this message first!
Please register or to comment