Commit 603302ca authored by Alex朱枝文's avatar Alex朱枝文

接入声网SDK以及直播间搭建

parent 23987683
......@@ -107,7 +107,8 @@ target 'galaxy' do
pod 'QY_iOS_SDK', '9.9.2'
#阿里云日志
pod 'AliyunLogProducer', '4.3.3'
#声网SDK播放器等(先弄完整的,后续剪裁)
pod 'AgoraRtcEngine_iOS', '4.4.0' #, :subspecs => ['RtcBasic']
end
......
This diff is collapsed.
//
// YHBasePlayerViewController.swift
// galaxy
//
// Created by alexzzw on 2024/11/25.
// Copyright © 2024 https://www.galaxy-immi.com. All rights reserved.
//
import AgoraRtcKit
import UIKit
class YHBasePlayerViewController: UIViewController {
// MARK: - Properties
var playerKit: AgoraRtcMediaPlayerProtocol!
var agoraKit: AgoraRtcEngineKit!
// MARK: - UI Components
private lazy var containerView: UIView = {
let view = UIView()
view.backgroundColor = .black
return view
}()
lazy var playerView: UIView = {
let view = UIView()
view.backgroundColor = .black
return view
}()
private lazy var controlView: YHPlayerControlView = {
let view = YHPlayerControlView()
view.delegate = self
return view
}()
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupUI()
setupAgoraKit()
}
// MARK: - Setup
private func setupUI() {
view.backgroundColor = .black
view.addSubview(containerView)
containerView.addSubview(playerView)
containerView.addSubview(controlView)
setupConstraints()
}
private func setupConstraints() {
containerView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
playerView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
controlView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
}
private func setupAgoraKit() {
let config = AgoraRtcEngineConfig()
config.appId = YhConstant.AgoraRtcKit.appId
agoraKit = AgoraRtcEngineKit.sharedEngine(with: config, delegate: self)
playerKit = agoraKit.createMediaPlayer(with: self)
playerKit.setView(playerView)
playerKit.setRenderMode(.fit)
}
}
extension YHBasePlayerViewController: YHMediaPlayerViewDelegate {
// MARK: - MediaPlayerViewDelegate
func didTapPlayButton() {
if playerKit.getPlayerState() == .playing {
playerKit.pause()
} else {
playerKit.play()
}
}
func didTapStopButton() {
playerKit.stop()
}
func didSeekToPosition(_ position: Float) {
let duration = playerKit.getDuration()
let seekPosition = Int64(Float(duration) * position)
playerKit.seek(toPosition: Int(seekPosition))
}
func didChangeQuality(_ quality: YHVideoQuality) {
// 根据不同清晰度切换播放源
switch quality {
case .auto:
// 自动码率逻辑
break
case .sd:
// 切换到标清源
break
case .hd:
// 切换到高清源
break
case .fhd:
// 切换到超清源
break
}
}
func didToggleFullscreen() {
// 切换全屏
if UIDevice.current.orientation.isPortrait {
let value = UIInterfaceOrientation.landscapeRight.rawValue
UIDevice.current.setValue(value, forKey: "orientation")
} else {
let value = UIInterfaceOrientation.portrait.rawValue
UIDevice.current.setValue(value, forKey: "orientation")
}
}
}
extension YHBasePlayerViewController: AgoraRtcMediaPlayerDelegate {
// MARK: - AgoraRtcMediaPlayerDelegate
func AgoraRtcMediaPlayer(_ playerKit: AgoraRtcMediaPlayerProtocol,
didChangedTo state: AgoraMediaPlayerState,
reason: AgoraMediaPlayerReason) {
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
switch state {
case .opening:
print("正在打开媒体源")
case .playing:
self.controlView.updatePlayButton(isPlaying: true)
print("正在播放")
case .paused:
self.controlView.updatePlayButton(isPlaying: false)
print("已暂停")
case .stopped:
self.controlView.updatePlayButton(isPlaying: false)
print("已停止")
case .failed:
print("播放失败,错误原因:\(reason.rawValue)")
self.showAlert(message: "播放失败,错误原因:\(reason.rawValue)")
default:
break
}
}
}
func AgoraRtcMediaPlayer(_ playerKit: AgoraRtcMediaPlayerProtocol,
didChangedTo position: Int) {
DispatchQueue.main.async { [weak self] in
guard let self = self else { return }
let duration = self.playerKit.getDuration()
guard duration > 0 else { return }
let progress = Float(position) / Float(duration)
let currentTime = self.formatTime(position)
let totalTime = self.formatTime(duration)
self.controlView.updateProgress(progress,
currentTime: currentTime,
totalTime: totalTime)
}
}
}
extension YHBasePlayerViewController: AgoraRtcEngineDelegate {
// MARK: - AgoraRtcEngineDelegate
func rtcEngine(_ engine: AgoraRtcEngineKit, didOccurWarning warningCode: AgoraWarningCode) {
print("Warning: \(warningCode.rawValue)")
}
func rtcEngine(_ engine: AgoraRtcEngineKit, didOccurError errorCode: AgoraErrorCode) {
DispatchQueue.main.async { [weak self] in
self?.showAlert(message: "RTC错误:\(errorCode.rawValue)")
}
}
}
extension YHBasePlayerViewController {
// MARK: - Helper Methods
private func formatTime(_ timeInMilliseconds: Int) -> String {
let totalSeconds = timeInMilliseconds / 1000
let minutes = totalSeconds / 60
let seconds = totalSeconds % 60
return String(format: "%02d:%02d", minutes, seconds)
}
private func showAlert(message: String) {
let alert = UIAlertController(title: "提示",
message: message,
preferredStyle: .alert)
alert.addAction(UIAlertAction(title: "确定", style: .default))
present(alert, animated: true)
}
}
//
// YHLivePlayerViewController.swift
// galaxy
//
// Created by alexzzw on 2024/11/25.
// Copyright © 2024 https://www.galaxy-immi.com. All rights reserved.
//
import AgoraRtcKit
import UIKit
class YHLivePlayerViewController: YHBasePlayerViewController {
// MARK: - Properties
private var roomInfo: YHLiveRoomInfo
private var messageListView: YHLiveMessageListView!
// MARK: - Initialization
init(roomInfo: YHLiveRoomInfo) {
self.roomInfo = roomInfo
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
// MARK: - Lifecycle
override func viewDidLoad() {
super.viewDidLoad()
setupMessageList()
startLive()
}
// MARK: - Setup
private func setupMessageList() {
messageListView = YHLiveMessageListView()
view.addSubview(messageListView)
messageListView.snp.makeConstraints { make in
make.left.right.equalToSuperview()
make.bottom.equalTo(view.safeAreaLayoutGuide)
make.height.equalTo(view.snp.height).multipliedBy(0.3)
}
}
private func startLive() {
play(with: roomInfo.streamUrl)
}
// 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)")
}
}
}
extension YHLivePlayerViewController {
override func AgoraRtcMediaPlayer(_ playerKit: AgoraRtcMediaPlayerProtocol,
didChangedTo state: AgoraMediaPlayerState,
reason: AgoraMediaPlayerReason) {
super.AgoraRtcMediaPlayer(playerKit, didChangedTo: state, reason: reason)
// 添加直播特定的状态处理
switch state {
case .playing:
// 直播开始时的特殊处理
break
case .stopped:
// 直播结束时的特殊处理
break
default:
break
}
}
}
//
// YHVODPlayerViewController.swift
// galaxy
//
// Created by alexzzw on 2024/11/25.
// Copyright © 2024 https://www.galaxy-immi.com. All rights reserved.
//
import AgoraRtcKit
import UIKit
class YHVODPlayerViewController: YHBasePlayerViewController {
private let videoInfo: YHVideoInfo
private var playConfig: YHVideoPlayConfig
init(videoInfo: YHVideoInfo, playConfig: YHVideoPlayConfig = YHVideoPlayConfig()) {
self.videoInfo = videoInfo
self.playConfig = playConfig
super.init(nibName: nil, bundle: nil)
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
override func viewDidLoad() {
super.viewDidLoad()
setupVideoInfo()
startPlayback()
}
private func setupVideoInfo() {
// 设置视频标题
title = videoInfo.title
// 设置作者信息
setupAuthorInfo()
// 设置播放器配置
setupPlayerConfig()
}
private func setupAuthorInfo() {
// 添加作者信息视图
let authorInfoView = YHVideoAuthorInfoView(author: videoInfo.author)
view.addSubview(authorInfoView)
authorInfoView.snp.makeConstraints { make in
make.top.equalTo(playerView.snp.bottom)
make.left.right.equalToSuperview()
make.height.equalTo(60)
}
}
private func setupPlayerConfig() {
// 设置播放器配置
if playConfig.muteOnStart {
playerKit.adjustPlayoutVolume(0)
}
}
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
}
// 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)")
}
}
private func updatePlayCount() {
// 更新播放次数的逻辑
}
}
// 作者信息视图
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)
}
}
//
// YHMediaProtocolUtils.swift
// galaxy
//
// Created by alexzzw on 2024/11/24.
// Copyright © 2024 https://www.galaxy-immi.com. All rights reserved.
//
import Foundation
// MARK: - Protocols
protocol YHMediaPlayerViewDelegate: AnyObject {
func didTapPlayButton()
func didTapStopButton()
func didSeekToPosition(_ position: Float)
func didChangeQuality(_ quality: YHVideoQuality)
func didToggleFullscreen()
}
// MARK: - Enums
enum YHVideoQuality: String, CaseIterable {
case auto = "自动"
case sd = "标清"
case hd = "高清"
case fhd = "超清"
}
enum YHPlayerStatus {
case idle
case playing
case paused
case stopped
case error(String)
}
// MARK: - Data Models
struct YHLiveRoomInfo {
let roomId: String
let anchorId: String
let anchorName: String
let streamUrl: String
let title: String
let coverUrl: String?
var currentQuality: YHVideoQuality = .auto
var viewerCount: Int = 0
var likeCount: Int = 0
var status: YHLiveRoomStatus = .idle
}
enum YHLiveRoomStatus {
case idle
case living
case ended
case error(String)
}
struct YHLiveMessage {
let id: String = UUID().uuidString
let type: YHLiveMessageType
let sender: YHLiveUser
let content: String
let timestamp: Date
static func createNormalMessage(sender: YHLiveUser, content: String) -> YHLiveMessage {
return YHLiveMessage(type: .normal, sender: sender, content: content, timestamp: Date())
}
static func createSystemMessage(_ content: String) -> YHLiveMessage {
let system = YHLiveUser(userId: "system", nickname: "系统", level: 0, isAdmin: true)
return YHLiveMessage(type: .system, sender: system, content: content, timestamp: Date())
}
}
enum YHLiveMessageType {
case normal // 普通消息
case system // 系统消息
case gift // 礼物消息
case enter // 进入消息
case like // 点赞消息
}
struct YHLiveUser {
let userId: String
let nickname: String
let level: Int
let isAdmin: Bool
var avatar: String?
}
// MARK: - Video Models
struct YHVideoInfo {
let videoId: String
let title: String
let description: String?
let coverUrl: String?
let duration: Int // 视频时长(秒)
let createTime: Date // 创建时间
var playCount: Int // 播放次数
let category: YHVideoCategory
let author: YHVideoAuthor
let videoUrls: [YHVideoQualityURL] // 不同清晰度的视频地址
// 获取指定清晰度的视频URL
func getVideoUrl(for quality: YHVideoQuality) -> String? {
return videoUrls.first { $0.quality == quality }?.url
}
// 获取最佳清晰度(根据网络情况)
func getBestQualityUrl(for networkType: YHNetworkType) -> String? {
switch networkType {
case .wifi:
return videoUrls.sorted { $0.quality.rawValue > $1.quality.rawValue }.first?.url
case .cellular:
return videoUrls.first { $0.quality == .sd }?.url
case .none:
return videoUrls.first { $0.quality == .sd }?.url
}
}
}
// 视频作者信息
struct YHVideoAuthor {
let userId: String
let nickname: String
let avatarUrl: String?
let description: String?
var followerCount: Int
var isFollowed: Bool
}
// 视频分类
enum YHVideoCategory: String {
case entertainment = "娱乐"
case sports = "体育"
case news = "新闻"
case education = "教育"
case gaming = "游戏"
case music = "音乐"
case other = "其他"
}
// 不同清晰度的视频URL
struct YHVideoQualityURL {
let quality: YHVideoQuality
let url: String
let bitrate: Int // 比特率
let resolution: YHVideoResolution
}
// 视频分辨率
struct YHVideoResolution {
let width: Int
let height: Int
var description: String {
return "\(width)x\(height)"
}
}
// 网络类型
enum YHNetworkType {
case wifi
case cellular
case none
}
// 视频播放相关的配置
struct YHVideoPlayConfig {
var autoPlay: Bool = true // 自动播放
var loopPlay: Bool = false // 循环播放
var muteOnStart: Bool = false // 开始时静音
var preferredQuality: YHVideoQuality = .auto // 首选清晰度
var enableBackgroundPlay: Bool = false // 允许后台播放
}
// 视频播放状态
enum YHVideoPlayState {
case idle // 空闲
case loading // 加载中
case playing // 播放中
case paused // 暂停
case ended // 结束
case error(String) // 错误
}
// 视频播放进度信息
struct YHVideoProgress {
let currentTime: TimeInterval
let duration: TimeInterval
var progress: Float {
return duration > 0 ? Float(currentTime / duration) : 0
}
}
//
// YHLiveMessageCell.swift
// galaxy
//
// Created by alexzzw on 2024/11/25.
// Copyright © 2024 https://www.galaxy-immi.com. All rights reserved.
//
import UIKit
class YHLiveMessageCell: UITableViewCell {
static let reuseIdentifier = "YHLiveMessageCell"
// MARK: - UI Components
private let containerView: UIView = {
let view = UIView()
view.backgroundColor = UIColor.black.withAlphaComponent(0.4)
view.layer.cornerRadius = 8
return view
}()
private let levelLabel: YHPaddedLabel = {
let label = YHPaddedLabel(padding: UIEdgeInsets(top: 2, left: 4, bottom: 2, right: 4))
label.font = .systemFont(ofSize: 10)
label.textColor = .white
label.backgroundColor = .systemBlue
label.layer.cornerRadius = 4
label.layer.masksToBounds = true
return label
}()
private let nicknameLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 14, weight: .medium)
label.textColor = .systemYellow
return label
}()
private let contentLabel: UILabel = {
let label = UILabel()
label.font = .systemFont(ofSize: 14)
label.textColor = .white
label.numberOfLines = 0
return label
}()
// MARK: - Initialization
override init(style: UITableViewCell.CellStyle, reuseIdentifier: String?) {
super.init(style: style, reuseIdentifier: reuseIdentifier)
setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupUI() {
backgroundColor = .clear
selectionStyle = .none
contentView.addSubview(containerView)
containerView.addSubview(levelLabel)
containerView.addSubview(nicknameLabel)
containerView.addSubview(contentLabel)
setupConstraints()
}
private func setupConstraints() {
containerView.snp.makeConstraints { make in
make.left.equalToSuperview().offset(8)
make.right.lessThanOrEqualToSuperview().offset(-8)
make.top.equalToSuperview().offset(4)
make.bottom.equalToSuperview().offset(-4)
}
levelLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(8)
make.centerY.equalTo(nicknameLabel)
}
nicknameLabel.snp.makeConstraints { make in
make.left.equalTo(levelLabel.snp.right).offset(8)
make.top.equalToSuperview().offset(8)
}
contentLabel.snp.makeConstraints { make in
make.left.equalToSuperview().offset(8)
make.right.equalToSuperview().offset(-8)
make.top.equalTo(nicknameLabel.snp.bottom).offset(4)
make.bottom.equalToSuperview().offset(-8)
}
}
// MARK: - Configuration
func configure(with message: YHLiveMessage) {
levelLabel.text = "Lv.\(message.sender.level)"
nicknameLabel.text = message.sender.nickname
switch message.type {
case .normal:
configureNormalMessage(message)
case .system:
configureSystemMessage(message)
case .gift:
configureGiftMessage(message)
case .enter:
configureEnterMessage(message)
case .like:
configureLikeMessage(message)
}
}
private func configureNormalMessage(_ message: YHLiveMessage) {
contentLabel.text = message.content
contentLabel.textColor = .white
if message.sender.isAdmin {
nicknameLabel.textColor = .systemRed
} else {
nicknameLabel.textColor = .systemYellow
}
}
private func configureSystemMessage(_ message: YHLiveMessage) {
levelLabel.isHidden = true
nicknameLabel.isHidden = true
contentLabel.text = message.content
contentLabel.textColor = .systemGray
contentLabel.textAlignment = .center
}
private func configureGiftMessage(_ message: YHLiveMessage) {
contentLabel.text = message.content
contentLabel.textColor = .systemPink
}
private func configureEnterMessage(_ message: YHLiveMessage) {
contentLabel.text = "进入了直播间"
contentLabel.textColor = .systemGreen
}
private func configureLikeMessage(_ message: YHLiveMessage) {
contentLabel.text = "点了赞"
contentLabel.textColor = .systemPink
}
}
//
// YHLiveMessageListView.swift
// galaxy
//
// Created by alexzzw on 2024/11/25.
// Copyright © 2024 https://www.galaxy-immi.com. All rights reserved.
//
import UIKit
class YHLiveMessageListView: UIView {
// MARK: - Properties
private var messages: [YHLiveMessage] = []
private let maxMessageCount = 200
// MARK: - UI Components
private lazy var tableView: UITableView = {
let tableView = UITableView()
tableView.backgroundColor = .clear
tableView.separatorStyle = .none
tableView.showsVerticalScrollIndicator = false
tableView.allowsSelection = false
tableView.delegate = self
tableView.dataSource = self
tableView.register(YHLiveMessageCell.self, forCellReuseIdentifier: YHLiveMessageCell.reuseIdentifier)
return tableView
}()
private lazy var messageInputView: YHMessageInputView = {
let view = YHMessageInputView(frame: CGRect.zero)
view.delegate = self
return view
}()
// MARK: - Initialization
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupUI() {
backgroundColor = .clear
addSubview(tableView)
addSubview(messageInputView)
setupConstraints()
}
private func setupConstraints() {
tableView.snp.makeConstraints { make in
make.top.left.right.equalToSuperview()
make.bottom.equalTo(messageInputView.snp.top)
}
messageInputView.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.height.equalTo(44)
}
}
// MARK: - Public Methods
func addMessage(_ message: YHLiveMessage) {
messages.append(message)
if messages.count > maxMessageCount {
messages.removeFirst(messages.count - maxMessageCount)
}
let indexPath = IndexPath(row: messages.count - 1, section: 0)
tableView.insertRows(at: [indexPath], with: .fade)
scrollToBottom(animated: true)
}
func clearMessages() {
messages.removeAll()
tableView.reloadData()
}
private func scrollToBottom(animated: Bool) {
guard !messages.isEmpty else { return }
let lastIndex = IndexPath(row: messages.count - 1, section: 0)
tableView.scrollToRow(at: lastIndex, at: .bottom, animated: animated)
}
}
// MARK: - UITableViewDelegate & UITableViewDataSource
extension YHLiveMessageListView: UITableViewDelegate, UITableViewDataSource {
func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
return messages.count
}
func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
guard let cell = tableView.dequeueReusableCell(
withIdentifier: YHLiveMessageCell.reuseIdentifier,
for: indexPath) as? YHLiveMessageCell else {
return UITableViewCell()
}
let message = messages[indexPath.row]
cell.configure(with: message)
return cell
}
}
// MARK: - MessageInputViewDelegate
extension YHLiveMessageListView: YHMessageInputViewDelegate {
func messageInputView(_ inputView: YHMessageInputView, didSendMessage text: String) {
// 创建当前用户
let currentUser = YHLiveUser(userId: "current_user",
nickname: "我",
level: 1,
isAdmin: false)
// 创建消息
let message = YHLiveMessage.createNormalMessage(sender: currentUser,
content: text)
// 添加消息到列表
addMessage(message)
}
}
//
// YHMessageInputView.swift
// galaxy
//
// Created by alexzzw on 2024/11/25.
// Copyright © 2024 https://www.galaxy-immi.com. All rights reserved.
//
import UIKit
protocol YHMessageInputViewDelegate: AnyObject {
func messageInputView(_ inputView: YHMessageInputView, didSendMessage text: String)
}
class YHMessageInputView: UIView {
// MARK: - Properties
weak var delegate: YHMessageInputViewDelegate?
// MARK: - UI Components
private lazy var textField: UITextField = {
let textField = UITextField()
textField.placeholder = "说点什么..."
textField.textColor = .white
textField.backgroundColor = UIColor.white.withAlphaComponent(0.2)
textField.layer.cornerRadius = 17
textField.leftView = UIView(frame: CGRect(x: 0, y: 0, width: 10, height: 34))
textField.leftViewMode = .always
textField.returnKeyType = .send
textField.delegate = self
return textField
}()
private lazy var sendButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("发送", for: .normal)
button.setTitleColor(.white, for: .normal)
button.addTarget(self, action: #selector(sendButtonTapped), for: .touchUpInside)
return button
}()
// MARK: - Initialization
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
private func setupUI() {
backgroundColor = UIColor.black.withAlphaComponent(0.4)
addSubview(textField)
addSubview(sendButton)
setupConstraints()
}
private func setupConstraints() {
textField.snp.makeConstraints { make in
make.left.equalToSuperview().offset(8)
make.centerY.equalToSuperview()
make.right.equalTo(sendButton.snp.left).offset(-8)
make.height.equalTo(34)
}
sendButton.snp.makeConstraints { make in
make.right.equalToSuperview().offset(-8)
make.centerY.equalToSuperview()
make.width.equalTo(50)
}
}
@objc private func sendButtonTapped() {
sendMessage()
}
private func sendMessage() {
guard let text = textField.text?.trimmingCharacters(in: .whitespacesAndNewlines),
!text.isEmpty else {
return
}
delegate?.messageInputView(self, didSendMessage: text)
textField.text = ""
}
}
// MARK: - UITextFieldDelegate
extension YHMessageInputView: UITextFieldDelegate {
func textFieldShouldReturn(_ textField: UITextField) -> Bool {
sendMessage()
textField.resignFirstResponder()
return true
}
}
//
// YHPlayerControlView.swift
// galaxy
//
// Created by alexzzw on 2024/11/25.
// Copyright © 2024 https://www.galaxy-immi.com. All rights reserved.
//
import UIKit
class YHPlayerControlView: UIView {
// MARK: - Properties
weak var delegate: YHMediaPlayerViewDelegate?
// MARK: - UI Components
private lazy var topBar: UIView = {
let view = UIView()
view.backgroundColor = UIColor.black.withAlphaComponent(0.5)
return view
}()
private lazy var backButton: UIButton = {
let button = UIButton(type: .system)
button.setImage(UIImage(systemName: "chevron.left"), for: .normal)
button.tintColor = .white
button.addTarget(self, action: #selector(backButtonTapped), for: .touchUpInside)
return button
}()
private lazy var titleLabel: UILabel = {
let label = UILabel()
label.textColor = .white
label.font = .systemFont(ofSize: 16, weight: .medium)
return label
}()
private lazy var bottomBar: UIView = {
let view = UIView()
view.backgroundColor = UIColor.black.withAlphaComponent(0.5)
return view
}()
private lazy var playButton: UIButton = {
let button = UIButton(type: .system)
button.setImage(UIImage(systemName: "play.fill"), for: .normal)
button.tintColor = .white
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.addTarget(self, action: #selector(sliderValueChanged(_:)), for: .valueChanged)
return slider
}()
private lazy var timeLabel: UILabel = {
let label = UILabel()
label.textColor = .white
label.font = .systemFont(ofSize: 12)
label.text = "00:00 / 00:00"
return label
}()
private lazy var qualityButton: UIButton = {
let button = UIButton(type: .system)
button.setTitle("清晰度", for: .normal)
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
button.addTarget(self, action: #selector(fullscreenButtonTapped), for: .touchUpInside)
return button
}()
// MARK: - Initialization
override init(frame: CGRect) {
super.init(frame: frame)
setupUI()
}
required init?(coder: NSCoder) {
super.init(coder: coder)
setupUI()
}
// MARK: - Setup
private func setupUI() {
addSubview(topBar)
addSubview(bottomBar)
topBar.addSubview(backButton)
topBar.addSubview(titleLabel)
bottomBar.addSubview(playButton)
bottomBar.addSubview(progressSlider)
bottomBar.addSubview(timeLabel)
bottomBar.addSubview(qualityButton)
bottomBar.addSubview(fullscreenButton)
setupConstraints()
}
private func setupConstraints() {
topBar.snp.makeConstraints { make in
make.top.left.right.equalToSuperview()
make.height.equalTo(44)
}
backButton.snp.makeConstraints { make in
make.left.equalTo(topBar).offset(16)
make.centerY.equalTo(topBar)
make.size.equalTo(CGSize(width: 44, height: 44))
}
titleLabel.snp.makeConstraints { make in
make.center.equalTo(topBar)
}
bottomBar.snp.makeConstraints { make in
make.left.right.bottom.equalToSuperview()
make.height.equalTo(44)
}
playButton.snp.makeConstraints { make in
make.left.equalTo(bottomBar).offset(16)
make.centerY.equalTo(bottomBar)
make.size.equalTo(CGSize(width: 44, height: 44))
}
progressSlider.snp.makeConstraints { make in
make.left.equalTo(playButton.snp.right).offset(8)
make.right.equalTo(timeLabel.snp.left).offset(-8)
make.centerY.equalTo(bottomBar)
}
timeLabel.snp.makeConstraints { make in
make.right.equalTo(qualityButton.snp.left).offset(-8)
make.centerY.equalTo(bottomBar)
make.width.equalTo(100)
}
qualityButton.snp.makeConstraints { make in
make.right.equalTo(fullscreenButton.snp.left).offset(-8)
make.centerY.equalTo(bottomBar)
}
fullscreenButton.snp.makeConstraints { make in
make.right.equalTo(bottomBar).offset(-16)
make.centerY.equalTo(bottomBar)
make.size.equalTo(CGSize(width: 44, height: 44))
}
}
// MARK: - Actions
@objc private func backButtonTapped() {
// Handle back action
}
@objc private func playButtonTapped() {
delegate?.didTapPlayButton()
}
@objc private func sliderValueChanged(_ slider: UISlider) {
delegate?.didSeekToPosition(slider.value)
}
@objc private func qualityButtonTapped() {
showQualitySelector()
}
@objc private func fullscreenButtonTapped() {
delegate?.didToggleFullscreen()
}
private func showQualitySelector() {
let alert = UIAlertController(title: "选择清晰度", message: nil, preferredStyle: .actionSheet)
YHVideoQuality.allCases.forEach { quality in
let action = UIAlertAction(title: quality.rawValue, style: .default) { [weak self] _ in
self?.delegate?.didChangeQuality(quality)
}
alert.addAction(action)
}
alert.addAction(UIAlertAction(title: "取消", style: .cancel))
if let viewController = self.window?.rootViewController {
viewController.present(alert, animated: true)
}
}
}
// MARK: - YHPlayerControlView Extension
extension YHPlayerControlView {
func updatePlayButton(isPlaying: Bool) {
let imageName = isPlaying ? "pause.fill" : "play.fill"
playButton.setImage(UIImage(systemName: imageName), for: .normal)
}
func updateProgress(_ progress: Float, currentTime: String, totalTime: String) {
progressSlider.value = progress
timeLabel.text = "\(currentTime) / \(totalTime)"
}
func setTitle(_ title: String) {
titleLabel.text = title
}
func showControls(_ show: Bool) {
UIView.animate(withDuration: 0.3) {
self.alpha = show ? 1.0 : 0.0
}
}
}
......@@ -228,6 +228,11 @@ extension YhConstant {
}
struct AgoraRtcKit {
static let appId: String = "f1da9c5b9fb946148761278273f43a14"
static let certificate: String? = "1776134c2dcb4d60bc3d1983fef02212"
}
// MARK: - 通知相关 名称
class YhNotification {
//登录成功
......
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