Commit b593bc69 authored by Alex朱枝文's avatar Alex朱枝文

播放器优化生命周期,优化播放器分配、优化小窗

parent 65cd18a7
...@@ -277,7 +277,10 @@ extension YHLifeViewController: UICollectionViewDelegate, UICollectionViewDataSo ...@@ -277,7 +277,10 @@ extension YHLifeViewController: UICollectionViewDelegate, UICollectionViewDataSo
return return
} }
let item = self.viewModel.liveArr[indexPath.row] let item = self.viewModel.liveArr[indexPath.row]
YHPlayerManager.shared.enterLive(from: nil, id: item.id, url: item.pull_url, title: item.live_title, roomId: item.room_id) // YHPlayerManager.shared.enterLive(from: nil, id: item.id, url: item.pull_url, title: item.live_title, roomId: item.room_id)
//let url = "https://pull-flv-l13.douyincdn.com/stage/stream-116307521507688888_ld5.flv?expire=1733728207&sign=cef0df720ef0dbe3126675d72dcacec2&major_anchor_level=common&abr_pts=-800&_session_id=037-2024120215100750CB84D802B8201D3D81.1733123408338.51633&rsi=1"
let playbackInfo = YHPlayerManager.PlaybackInfo(id: item.id, url: item.pull_url, title: item.live_title, roomId: item.room_id, isLive: true, scene: .fullscreen)
YHPlayerManager.shared.enterLive(from: nil, playbackInfo: playbackInfo)
} }
} }
......
...@@ -141,7 +141,8 @@ extension YHHomeBannerView: FSPagerViewDataSource, FSPagerViewDelegate { ...@@ -141,7 +141,8 @@ extension YHHomeBannerView: FSPagerViewDataSource, FSPagerViewDelegate {
// // TODO: - alex测试 // // TODO: - alex测试
// if index == 1 { // if index == 1 {
// let cell: YHHomeBannerCollectionViewCell? = pagerView.cellForItem(at: index) as? YHHomeBannerCollectionViewCell // let cell: YHHomeBannerCollectionViewCell? = pagerView.cellForItem(at: index) as? YHHomeBannerCollectionViewCell
// YHPlayerManager.shared.enterLive(from: cell?.bannerImagV, id: 23, url: "https://pull-flv-l6.douyincdn.com/stage/stream-116295918585905183.flv?k=e21f1ae1e7591521&t=1733551151&major_anchor_level=common&abr_pts=-800&_session_id=037-202411301359108030CAEAC1F742805E6D.1732946351732.18942&rsi=1", title: nil, roomId: nil, type: .secondary) // let playbackInfo = YHPlayerManager.PlaybackInfo(id: 40, url: "https://pull-flv-l13.douyincdn.com/stage/stream-116307521507688888_ld5.flv?expire=1733728207&sign=cef0df720ef0dbe3126675d72dcacec2&major_anchor_level=common&abr_pts=-800&_session_id=037-2024120215100750CB84D802B8201D3D81.1733123408338.51633&rsi=1", title: nil, roomId: nil, isLive: true, scene: .fullscreen)
// YHPlayerManager.shared.enterLive(from: cell?.bannerImagV, playbackInfo: playbackInfo)
// return // return
// } // }
// // TODO: - alex测试 // // TODO: - alex测试
...@@ -223,16 +224,19 @@ extension YHHomeBannerView: FSPagerViewDataSource, FSPagerViewDelegate { ...@@ -223,16 +224,19 @@ extension YHHomeBannerView: FSPagerViewDataSource, FSPagerViewDelegate {
// video_url 视频链接 // video_url 视频链接
// recorded_cate_id 录播分类id // recorded_cate_id 录播分类id
let cell: YHHomeBannerCollectionViewCell? = pagerView.cellForItem(at: index) as? YHHomeBannerCollectionViewCell let cell: YHHomeBannerCollectionViewCell? = pagerView.cellForItem(at: index) as? YHHomeBannerCollectionViewCell
YHPlayerManager.shared.enterLive(from: cell?.bannerImagV, id: model.live_id, url: model.live_pull_url, title: nil, roomId: nil, type: .secondary) let playbackInfo = YHPlayerManager.PlaybackInfo(id: model.live_id, url: model.live_pull_url, title: nil, roomId: nil, isLive: true, scene: .fullscreen)
YHPlayerManager.shared.enterLive(from: cell?.bannerImagV, playbackInfo: playbackInfo)
printLog("跳转直播") printLog("跳转直播")
case 101://录播 case 101://录播
printLog("跳转录播") printLog("跳转录播")
let cell: YHHomeBannerCollectionViewCell? = pagerView.cellForItem(at: index) as? YHHomeBannerCollectionViewCell let cell: YHHomeBannerCollectionViewCell? = pagerView.cellForItem(at: index) as? YHHomeBannerCollectionViewCell
YHPlayerManager.shared.enterVOD(from: cell?.bannerImagV, id: model.live_id, url: model.video_url, title: nil, type: .secondary) let playbackInfo = YHPlayerManager.PlaybackInfo(id: model.live_id, url: model.video_url, title: nil, roomId: nil, isLive: false, scene: .fullscreen)
YHPlayerManager.shared.enterVOD(from: cell?.bannerImagV, playbackInfo: playbackInfo)
case 102://图片直播 case 102://图片直播
printLog("跳转录播") printLog("跳转录播")
let cell: YHHomeBannerCollectionViewCell? = pagerView.cellForItem(at: index) as? YHHomeBannerCollectionViewCell let cell: YHHomeBannerCollectionViewCell? = pagerView.cellForItem(at: index) as? YHHomeBannerCollectionViewCell
YHPlayerManager.shared.enterLive(from: cell?.bannerImagV, id: model.live_id, url: model.live_pull_url, title: nil, roomId: nil, type: .secondary) let playbackInfo = YHPlayerManager.PlaybackInfo(id: model.live_id, url: model.live_pull_url, title: nil, roomId: nil, isLive: true, scene: .fullscreen)
YHPlayerManager.shared.enterLive(from: cell?.bannerImagV, playbackInfo: playbackInfo)
case 0://0 不需要跳转 case 0://0 不需要跳转
printLog("0 不需要跳转") printLog("0 不需要跳转")
default: default:
...@@ -266,7 +270,10 @@ extension YHHomeBannerView: FSPagerViewDataSource, FSPagerViewDelegate { ...@@ -266,7 +270,10 @@ extension YHHomeBannerView: FSPagerViewDataSource, FSPagerViewDelegate {
// // TODO: - alex测试 // // TODO: - alex测试
// if let cell = cell as? YHHomeBannerCollectionViewCell { // if let cell = cell as? YHHomeBannerCollectionViewCell {
// if index == 1 { // if index == 1 {
// YHPlayerManager.shared.play(url: "https://pull-flv-l11.douyincdn.com/thirdgame/stream-404525958790382412.flv?expire=1733554587&sign=d1e9f927e20f4a3fb4e2dd2a2712e256&major_anchor_level=common&abr_pts=-800&_session_id=037-20241130145626DBDEB00EB11CB388DD95.1732949787574.66743&rsi=1", inView: cell.bannerImagV, title: nil, type: .secondary) //
// let playbackInfo = YHPlayerManager.PlaybackInfo(id: 40, url: "https://pull-flv-l13.douyincdn.com/stage/stream-116307521507688888_ld5.flv?expire=1733728207&sign=cef0df720ef0dbe3126675d72dcacec2&major_anchor_level=common&abr_pts=-800&_session_id=037-2024120215100750CB84D802B8201D3D81.1733123408338.51633&rsi=1", title: nil, roomId: nil, isLive: true, scene: .banner)
// YHPlayerManager.shared.enterBanner(playbackInfo: playbackInfo, inView: cell.bannerImagV)
//
// } else { // } else {
// let player = YHPlayerManager.shared.getPlayer(.secondary) // let player = YHPlayerManager.shared.getPlayer(.secondary)
// player?.setPlayView(nil) // player?.setPlayView(nil)
...@@ -276,8 +283,10 @@ extension YHHomeBannerView: FSPagerViewDataSource, FSPagerViewDelegate { ...@@ -276,8 +283,10 @@ extension YHHomeBannerView: FSPagerViewDataSource, FSPagerViewDelegate {
// // TODO: - alex测试 // // TODO: - alex测试
if model.skip_url.isEmpty == false { if model.skip_url.isEmpty == false {
if let cell = cell as? YHHomeBannerCollectionViewCell { if let cell = cell as? YHHomeBannerCollectionViewCell {
if model.skip_type == 100 { if model.skip_type == 100 || model.skip_type == 102 {
YHPlayerManager.shared.play(url: model.live_pull_url, inView: cell.bannerImagV, title: nil, type: .secondary) YHPlayerManager.shared.play(url: model.live_pull_url, inView: cell.bannerImagV, title: nil, type: .secondary)
let playbackInfo = YHPlayerManager.PlaybackInfo(id: model.live_id, url: model.live_pull_url, title: nil, roomId: nil, isLive: true, scene: .banner)
YHPlayerManager.shared.enterBanner(playbackInfo: playbackInfo, inView: cell.bannerImagV)
} else { } else {
let player = YHPlayerManager.shared.getPlayer(.secondary) let player = YHPlayerManager.shared.getPlayer(.secondary)
player?.setPlayView(nil) player?.setPlayView(nil)
...@@ -302,7 +311,7 @@ extension YHHomeBannerView: FSPagerViewDataSource, FSPagerViewDelegate { ...@@ -302,7 +311,7 @@ extension YHHomeBannerView: FSPagerViewDataSource, FSPagerViewDelegate {
// } // }
// // TODO: - alex测试 // // TODO: - alex测试
if model.skip_url.isEmpty == false { if model.skip_url.isEmpty == false {
if model.skip_type == 100 { if model.skip_type == 100 || model.skip_type == 102 {
YHPlayerManager.shared.stop(type: .secondary) YHPlayerManager.shared.stop(type: .secondary)
} }
} }
......
...@@ -94,7 +94,8 @@ extension YHSelectLookView: UICollectionViewDelegate, UICollectionViewDataSource ...@@ -94,7 +94,8 @@ extension YHSelectLookView: UICollectionViewDelegate, UICollectionViewDataSource
return return
} }
let item = items[indexPath.row] let item = items[indexPath.row]
YHPlayerManager.shared.enterLive(from: nil, id: item.id, url: item.pull_url, title: item.live_title, roomId: item.room_id) let playbackInfo = YHPlayerManager.PlaybackInfo(id: item.id, url: item.pull_url, title: item.live_title, roomId: item.room_id, isLive: true, scene: .fullscreen)
YHPlayerManager.shared.enterLive(from: nil, playbackInfo: playbackInfo)
} }
} }
......
...@@ -31,9 +31,17 @@ class YHPlayer { ...@@ -31,9 +31,17 @@ class YHPlayer {
weak var currentPlayView: UIView? weak var currentPlayView: UIView?
private(set) var currentTitle: String? private(set) var currentTitle: String?
var isMuted: Bool {
get { playerKit.getMute() }
set { playerKit.mute(newValue) }
}
init(type: YHPlayerType, playerKit: AgoraRtcMediaPlayerProtocol) { init(type: YHPlayerType, playerKit: AgoraRtcMediaPlayerProtocol) {
self.type = type self.type = type
self.playerKit = playerKit self.playerKit = playerKit
// 基础设置
playerKit.setLoopCount(-1) // 循环播放
} }
func setPlayView(_ view: UIView?) { func setPlayView(_ view: UIView?) {
...@@ -47,7 +55,10 @@ class YHPlayer { ...@@ -47,7 +55,10 @@ class YHPlayer {
let mediaSource = AgoraMediaSource() let mediaSource = AgoraMediaSource()
mediaSource.url = url mediaSource.url = url
mediaSource.autoPlay = true mediaSource.autoPlay = true
playerKit.open(with: mediaSource) let result = playerKit.open(with: mediaSource)
if result == 0 {
playerKit.play()
}
} }
func stop() { func stop() {
...@@ -65,6 +76,17 @@ class YHPlayer { ...@@ -65,6 +76,17 @@ class YHPlayer {
playerKit.play() playerKit.play()
} }
func reset() {
stop()
setPlayView(nil)
delegate = nil
}
func releasePlayer() {
reset()
//playerKit.destroy()
}
func getPosition() -> Int { func getPosition() -> Int {
return playerKit.getPosition() return playerKit.getPosition()
} }
......
...@@ -12,20 +12,31 @@ import UIKit ...@@ -12,20 +12,31 @@ import UIKit
// MARK: - 播放器管理中心 // MARK: - 播放器管理中心
class YHPlayerManager: NSObject { class YHPlayerManager: NSObject {
// 播放器状态信息 // MARK: - Types
enum PlaybackScene: Int {
case fullscreen // 直播间/点播页
case floating // 小窗
case banner // banner
}
struct PlaybackInfo: Equatable { struct PlaybackInfo: Equatable {
let id: Int let id: Int
let url: String? let url: String?
let title: String? let title: String?
let roomId: String? let roomId: String?
let isLive: Bool let isLive: Bool
var scene: PlaybackScene
var playerType: YHPlayerType
init(id: Int, url: String? = nil, title: String? = nil, roomId: String? = nil, isLive: Bool) { init(id: Int, url: String? = nil, title: String? = nil, roomId: String? = nil, isLive: Bool, scene: PlaybackScene = .fullscreen) {
self.id = id self.id = id
self.url = url self.url = url
self.title = title self.title = title
self.roomId = roomId self.roomId = roomId
self.isLive = isLive self.isLive = isLive
self.scene = scene
playerType = .main
} }
static func == (lhs: Self, rhs: Self) -> Bool { static func == (lhs: Self, rhs: Self) -> Bool {
...@@ -33,174 +44,180 @@ class YHPlayerManager: NSObject { ...@@ -33,174 +44,180 @@ class YHPlayerManager: NSObject {
} }
} }
// MARK: - Properties
static let shared = YHPlayerManager() static let shared = YHPlayerManager()
// 核心组件
private var agoraKit: AgoraRtcEngineKit! private var agoraKit: AgoraRtcEngineKit!
private var players: [YHPlayerType: YHPlayer] = [:] private var activePlayers: [YHPlayerType: YHPlayer] = [:]
private var currentPlaybackInfo: [YHPlayerType: PlaybackInfo] = [:] private var currentPlaybackInfo: [YHPlayerType: PlaybackInfo] = [:]
private var floatingWindow: YHFloatingWindow? private var floatingWindow: YHFloatingWindow?
// 转场动画相关 // 转场动画相关
private var transitionSourceView: UIView? private var transitionSourceView: UIView?
// MARK: - Initialization
override private init() { override private init() {
super.init() super.init()
setupAgoraKit() setupAgoraKit()
setupPlayers()
} }
// MARK: - Setup Methods
private func setupAgoraKit() { private func setupAgoraKit() {
let config = AgoraRtcEngineConfig() let config = AgoraRtcEngineConfig()
config.appId = YhConstant.AgoraRtcKit.appId config.appId = YhConstant.AgoraRtcKit.appId
config.areaCode = .global config.areaCode = .global
config.channelProfile = .liveBroadcasting config.channelProfile = .liveBroadcasting
agoraKit = AgoraRtcEngineKit.sharedEngine(with: config, delegate: self) agoraKit = AgoraRtcEngineKit.sharedEngine(with: config, delegate: self)
// 基础配置
agoraKit.enableVideo() agoraKit.enableVideo()
} }
private func setupPlayers() { // MARK: - Basic Playback Control
// 创建主播放器
if let mainPlayerKit = agoraKit.createMediaPlayer(with: self) {
players[.main] = YHPlayer(type: .main, playerKit: mainPlayerKit)
}
// 创建第二播放器
if let secondaryPlayerKit = agoraKit.createMediaPlayer(with: self) {
players[.secondary] = YHPlayer(type: .secondary, playerKit: secondaryPlayerKit)
}
}
// MARK: - Public Methods
// 获取播放器实例
func getPlayer(_ type: YHPlayerType) -> YHPlayer? {
return players[type]
}
// 播放控制
func play(url: String, inView view: UIView? = nil, title: String? = nil, type: YHPlayerType = .main) { func play(url: String, inView view: UIView? = nil, title: String? = nil, type: YHPlayerType = .main) {
if let player = players[type] { let player = player(for: type)
// 如果提供了视图,则设置播放视图
if let view = view { if let view = view {
player.setPlayView(view) player.setPlayView(view)
} }
player.play(url: url, title: title) player.play(url: url, title: title)
} }
func pause(type: YHPlayerType = .main) {
activePlayers[type]?.pause()
} }
func play(playbackInfo: PlaybackInfo, inView view: UIView? = nil, type: YHPlayerType = .main) { func resume(type: YHPlayerType = .main) {
if let player = players[type] { activePlayers[type]?.resume()
// 如果提供了视图,则设置播放视图
currentPlaybackInfo[type] = playbackInfo
if let view = view {
player.setPlayView(view)
} }
if let url = playbackInfo.url {
player.play(url: url, title: playbackInfo.title) func stop(type: YHPlayerType = .main) {
activePlayers[type]?.stop()
} }
func setPlayView(_ view: UIView?, type: YHPlayerType = .main) {
activePlayers[type]?.setPlayView(view)
} }
func getCurrentPlayer(type: YHPlayerType = .main) -> YHPlayer? {
return activePlayers[type]
} }
func resume(type: YHPlayerType = .main) { func setMute(_ muted: Bool, type: YHPlayerType = .main) {
players[type]?.resume() activePlayers[type]?.isMuted = muted
} }
func pause(type: YHPlayerType = .main) { func getPlayer(_ type: YHPlayerType) -> YHPlayer? {
players[type]?.pause() return activePlayers[type]
} }
func stop(type: YHPlayerType = .main) { // MARK: - Player Management
players[type]?.stop()
private func player(for type: YHPlayerType) -> YHPlayer {
if let existingPlayer = activePlayers[type] {
return existingPlayer
} }
func stopAllPlayers() { let newPlayer = createPlayer(for: type)
players.values.forEach { $0.stop() } activePlayers[type] = newPlayer
return newPlayer
} }
// 视图控制 private func createPlayer(for type: YHPlayerType) -> YHPlayer {
func setPlayView(_ view: UIView?, type: YHPlayerType = .main) { guard let playerKit = agoraKit.createMediaPlayer(with: self) else {
players[type]?.setPlayView(view) fatalError("Failed to create media player")
}
let player = YHPlayer(type: type, playerKit: playerKit)
return player
} }
// MARK: - 资源管理 private func releasePlayer(_ type: YHPlayerType) {
guard let player = activePlayers[type] else { return }
player.stop()
player.releasePlayer()
activePlayers.removeValue(forKey: type)
currentPlaybackInfo.removeValue(forKey: type)
}
func destroy() { private func determinePlayerType(for scene: PlaybackScene) -> YHPlayerType {
stopAllPlayers() switch scene {
players.removeAll() case .banner:
exitFloating() return .secondary
AgoraRtcEngineKit.destroy() case .fullscreen, .floating:
return .main
}
} }
}
extension YHPlayerManager { // MARK: - Scene Management
// MARK: - 场景切换
func enterVOD(from sourceView: UIView?, playbackInfo: PlaybackInfo, type: YHPlayerType = .main) { func enterVOD(from sourceView: UIView?, playbackInfo: PlaybackInfo) {
let playerVC = YHVODPlayerViewController(id: playbackInfo.id, url: playbackInfo.url, title: playbackInfo.title) let playerType = determinePlayerType(for: .fullscreen)
// 保存播放信息 var updatedInfo = playbackInfo
currentPlaybackInfo[type] = playbackInfo updatedInfo.scene = .fullscreen
if let player = players[type] { updatedInfo.playerType = playerType
player.delegate = playerVC
playerVC.player = player // 如果是从banner跳转,需要转移播放进度
} var startPosition: Int = 0
playerVC.playbackInfo = playbackInfo if playbackInfo.scene == .banner,
present(playerVC, from: sourceView) let bannerPlayer = activePlayers[.secondary] {
startPosition = bannerPlayer.getPosition()
releasePlayer(.secondary)
} }
func enterLive(from sourceView: UIView?, playbackInfo: PlaybackInfo, type: YHPlayerType = .main) { let playerVC = YHVODPlayerViewController(id: playbackInfo.id, url: playbackInfo.url, title: playbackInfo.title)
// 保存播放信息 currentPlaybackInfo[playerType] = updatedInfo
currentPlaybackInfo[type] = playbackInfo
let playerVC = YHLivePlayerViewController(id: playbackInfo.id, url: playbackInfo.url, title: playbackInfo.title, roomId: playbackInfo.roomId) let player = player(for: playerType)
playerVC.playbackInfo = playbackInfo
if let player = players[type] {
player.delegate = playerVC player.delegate = playerVC
playerVC.player = player playerVC.player = player
} playerVC.playbackInfo = updatedInfo
playerVC.startPosition = startPosition
// 关闭小窗
exitFloating()
present(playerVC, from: sourceView) present(playerVC, from: sourceView)
} }
func enterLive(from sourceView: UIView?, playbackInfo: PlaybackInfo) {
let playerType = determinePlayerType(for: .fullscreen)
var updatedInfo = playbackInfo
updatedInfo.scene = .fullscreen
updatedInfo.playerType = playerType
func enterVOD(from sourceView: UIView?, id: Int, url: String, title: String?, type: YHPlayerType = .main) { // 如果是从banner跳转,释放副播放器
let playerVC = YHVODPlayerViewController(id: id, url: url, title: title) if playbackInfo.scene == .banner {
// 保存播放信息 releasePlayer(.secondary)
let playbackInfo = PlaybackInfo(id: id, url: url, title: title, isLive: false)
playerVC.playbackInfo = playbackInfo
currentPlaybackInfo[type] = playbackInfo
if let player = players[type] {
player.delegate = playerVC
playerVC.player = player
}
present(playerVC, from: sourceView)
} }
func enterLive(from sourceView: UIView?, id: Int, url: String? = nil, title: String? = nil, roomId: String? = nil, type: YHPlayerType = .main) { let playerVC = YHLivePlayerViewController(id: playbackInfo.id, url: playbackInfo.url, title: playbackInfo.title, roomId: playbackInfo.roomId)
// 保存播放信息 currentPlaybackInfo[playerType] = updatedInfo
let playbackInfo = PlaybackInfo(id: id, url: url, title: title, roomId: roomId, isLive: true)
currentPlaybackInfo[type] = playbackInfo let player = player(for: playerType)
let playerVC = YHLivePlayerViewController(id: id, url: url, title: title, roomId: roomId)
playerVC.playbackInfo = playbackInfo
if let player = players[type] {
player.delegate = playerVC player.delegate = playerVC
playerVC.player = player playerVC.player = player
} playerVC.playbackInfo = updatedInfo
// 关闭小窗
exitFloating()
present(playerVC, from: sourceView) present(playerVC, from: sourceView)
} }
func enterFloating(from viewController: UIViewController? = nil, playbackInfo: PlaybackInfo, type: YHPlayerType = .main) { func enterFloating(from viewController: UIViewController? = nil, playbackInfo: PlaybackInfo) {
guard let window = UIApplication.shared.keyWindow, guard let window = UIApplication.shared.keyWindow else { return }
let player = players[type] else { return }
currentPlaybackInfo[type] = playbackInfo let playerType = determinePlayerType(for: .floating)
let player = player(for: playerType)
var updatedInfo = playbackInfo
updatedInfo.scene = .floating
updatedInfo.playerType = playerType
currentPlaybackInfo[playerType] = updatedInfo
if let url = playbackInfo.url { if let url = playbackInfo.url {
player.play(url: url, title: playbackInfo.title) player.play(url: url, title: playbackInfo.title)
} }
// 获取当前播放视图的截图和位置 // 获取当前播放视图的截图和位置
if let sourceView = player.currentPlayView, if let sourceView = player.currentPlayView,
let sourceSuperview = sourceView.superview { let sourceSuperview = sourceView.superview {
...@@ -210,7 +227,7 @@ extension YHPlayerManager { ...@@ -210,7 +227,7 @@ extension YHPlayerManager {
// 创建浮窗 // 创建浮窗
let floatingWindow = YHFloatingWindow() let floatingWindow = YHFloatingWindow()
floatingWindow.playbackInfo = playbackInfo floatingWindow.playbackInfo = updatedInfo
floatingWindow.delegate = self floatingWindow.delegate = self
floatingWindow.player = player floatingWindow.player = player
self.floatingWindow = floatingWindow self.floatingWindow = floatingWindow
...@@ -224,61 +241,59 @@ extension YHPlayerManager { ...@@ -224,61 +241,59 @@ extension YHPlayerManager {
// 执行动画 // 执行动画
let showFloatingWindow = { [weak self] in let showFloatingWindow = { [weak self] in
guard let self = self else { return } guard let self = self else { return }
// 动画过渡
UIView.animate(withDuration: 0.3, animations: { UIView.animate(withDuration: 0.3, animations: {
snapshotView.frame = targetFrame snapshotView.frame = targetFrame
}, completion: { _ in }, completion: { _ in
// 移除截图视图
snapshotView.removeFromSuperview() snapshotView.removeFromSuperview()
// 显示真正的浮窗
floatingWindow.show(in: window) floatingWindow.show(in: window)
// 切换播放视图
player.setPlayView(floatingWindow.contentView) player.setPlayView(floatingWindow.contentView)
}) })
} }
// 如果有viewController需要关闭
if let viewController = viewController { if let viewController = viewController {
// 先执行动画,动画完成后再关闭页面
showFloatingWindow() showFloatingWindow()
viewController.dismiss(animated: false) viewController.dismiss(animated: false)
} else { } else {
showFloatingWindow() showFloatingWindow()
} }
} else { } else {
// 创建浮窗 // 直接创建浮窗
let floatingWindow = YHFloatingWindow() let floatingWindow = YHFloatingWindow()
floatingWindow.delegate = self floatingWindow.delegate = self
floatingWindow.player = players[type] floatingWindow.player = player
floatingWindow.playbackInfo = updatedInfo
self.floatingWindow = floatingWindow self.floatingWindow = floatingWindow
let showFloatingWindow = { [weak self] in let showFloatingWindow = { [weak self] in
guard let self = self else { return } guard let self = self else { return }
// 显示浮窗
floatingWindow.show(in: window) floatingWindow.show(in: window)
// 切换播放视图
if let player = self.players[type] {
player.setPlayView(floatingWindow.contentView) player.setPlayView(floatingWindow.contentView)
} }
}
// 如果有viewController需要关闭,先关闭再显示浮窗
if let viewController = viewController { if let viewController = viewController {
viewController.dismiss(animated: true) { viewController.dismiss(animated: true) {
showFloatingWindow() showFloatingWindow()
} }
} else { } else {
// 直接显示浮窗
showFloatingWindow() showFloatingWindow()
} }
} }
} }
// 退出浮窗 func enterBanner(playbackInfo: PlaybackInfo, inView view: UIView) {
let playerType = determinePlayerType(for: .banner)
var updatedInfo = playbackInfo
updatedInfo.scene = .banner
updatedInfo.playerType = playerType
currentPlaybackInfo[playerType] = updatedInfo
let player = player(for: playerType)
player.setPlayView(view)
if let url = playbackInfo.url {
player.play(url: url, title: playbackInfo.title)
}
}
func exitFloating() { func exitFloating() {
floatingWindow?.dismiss() floatingWindow?.dismiss()
floatingWindow = nil floatingWindow = nil
...@@ -291,11 +306,36 @@ extension YHPlayerManager { ...@@ -291,11 +306,36 @@ extension YHPlayerManager {
if let topVC = UIApplication.shared.keyWindow?.rootViewController?.topMostViewController() { if let topVC = UIApplication.shared.keyWindow?.rootViewController?.topMostViewController() {
topVC.present(playerVC, animated: true) { [weak self] in topVC.present(playerVC, animated: true) { [weak self] in
self?.exitFloating()
self?.transitionSourceView = nil self?.transitionSourceView = nil
} }
} }
} }
// MARK: - Resource Management
func cleanupOnExit() {
// 停止所有播放
activePlayers.forEach { $0.value.stop() }
// 释放副播放器
releasePlayer(.secondary)
// 清理播放信息
currentPlaybackInfo.removeAll()
floatingWindow?.dismiss()
floatingWindow = nil
// 重置主播放器
if let mainPlayer = activePlayers[.main] {
mainPlayer.reset()
}
}
deinit {
activePlayers.forEach { $0.value.releasePlayer() }
activePlayers.removeAll()
AgoraRtcEngineKit.destroy()
}
} }
// MARK: - AgoraRtcEngineDelegate // MARK: - AgoraRtcEngineDelegate
...@@ -316,28 +356,23 @@ extension YHPlayerManager: AgoraRtcMediaPlayerDelegate { ...@@ -316,28 +356,23 @@ extension YHPlayerManager: AgoraRtcMediaPlayerDelegate {
func AgoraRtcMediaPlayer(_ playerKit: AgoraRtcMediaPlayerProtocol, func AgoraRtcMediaPlayer(_ playerKit: AgoraRtcMediaPlayerProtocol,
didChangedTo state: AgoraMediaPlayerState, didChangedTo state: AgoraMediaPlayerState,
reason: AgoraMediaPlayerReason) { reason: AgoraMediaPlayerReason) {
printLog("Player state changed to: \(state.rawValue), error: \(reason.rawValue)") if let player = activePlayers.values.first(where: { $0.playerKit === playerKit }) {
// 找到对应的播放器并通知代理
if let player = players.values.first(where: { $0.playerKit === playerKit }) {
player.delegate?.player(player, didChangedToState: state, reason: reason) player.delegate?.player(player, didChangedToState: state, reason: reason)
} }
} }
func AgoraRtcMediaPlayer(_ playerKit: AgoraRtcMediaPlayerProtocol, func AgoraRtcMediaPlayer(_ playerKit: AgoraRtcMediaPlayerProtocol,
didChangedTo position: Int) { didChangedTo position: Int) {
// 处理播放进度更新 if let player = activePlayers.values.first(where: { $0.playerKit === playerKit }) {
if let player = players.values.first(where: { $0.playerKit === playerKit }) {
player.delegate?.player(player, didChangedToPosition: position) player.delegate?.player(player, didChangedToPosition: position)
} }
} }
func AgoraRtcMediaPlayer(_ playerKit: AgoraRtcMediaPlayerProtocol, func AgoraRtcMediaPlayer(_ playerKit: AgoraRtcMediaPlayerProtocol,
didReceiveVideoSize size: CGSize) { didReceiveVideoSize size: CGSize) {
if let player = players.values.first(where: { $0.playerKit === playerKit }) { if let player = activePlayers.values.first(where: { $0.playerKit === playerKit }) {
player.delegate?.player(player, didReceiveVideoSize: size) player.delegate?.player(player, didReceiveVideoSize: size)
// 如果是主播放器且在浮窗模式,更新浮窗尺寸
} }
// 更新浮窗视频尺寸
if floatingWindow?.player?.playerKit === playerKit { if floatingWindow?.player?.playerKit === playerKit {
floatingWindow?.setVideoSize(size) floatingWindow?.setVideoSize(size)
...@@ -348,29 +383,28 @@ extension YHPlayerManager: AgoraRtcMediaPlayerDelegate { ...@@ -348,29 +383,28 @@ extension YHPlayerManager: AgoraRtcMediaPlayerDelegate {
// MARK: - YHFloatingWindowDelegate // MARK: - YHFloatingWindowDelegate
extension YHPlayerManager: YHFloatingWindowDelegate { extension YHPlayerManager: YHFloatingWindowDelegate {
// 通过PlaybackInfo查找对应的PlayerType
private func findPlayerType(for playbackInfo: PlaybackInfo) -> YHPlayerType? { private func findPlayerType(for playbackInfo: PlaybackInfo) -> YHPlayerType? {
return currentPlaybackInfo.first(where: { $0.value == playbackInfo })?.key return currentPlaybackInfo.first(where: { $0.value == playbackInfo })?.key
} }
func floatingWindowDidTap(_ window: YHFloatingWindow) { func floatingWindowDidTap(_ window: YHFloatingWindow) {
// 点击浮窗时进入直播间 guard let playbackInfo = window.playbackInfo else { return }
guard let playbackInfo = window.playbackInfo else {
return
}
guard let type = findPlayerType(for: playbackInfo) else {
return
}
if playbackInfo.isLive { if playbackInfo.isLive {
enterLive(from: window.contentView, playbackInfo: playbackInfo, type: type) enterLive(from: window.contentView, playbackInfo: playbackInfo)
} else { } else {
enterVOD(from: window.contentView, playbackInfo: playbackInfo, type: type) enterVOD(from: window.contentView, playbackInfo: playbackInfo)
} }
} }
func floatingWindowDidClose(_ window: YHFloatingWindow) { func floatingWindowDidClose(_ window: YHFloatingWindow) {
// 关闭浮窗时停止播放 guard let playbackInfo = window.playbackInfo,
stopAllPlayers() let playerType = findPlayerType(for: playbackInfo) else {
return
}
let player = player(for: playerType)
player.stop()
floatingWindow = nil floatingWindow = nil
} }
...@@ -405,6 +439,8 @@ extension YHPlayerManager: UIViewControllerTransitioningDelegate { ...@@ -405,6 +439,8 @@ extension YHPlayerManager: UIViewControllerTransitioningDelegate {
} }
} }
// MARK: - UIViewController Extension
extension UIViewController { extension UIViewController {
func topMostViewController() -> UIViewController { func topMostViewController() -> UIViewController {
if let presented = presentedViewController { if let presented = presentedViewController {
......
...@@ -13,6 +13,7 @@ import AgoraRtcKit ...@@ -13,6 +13,7 @@ import AgoraRtcKit
class YHVODPlayerViewController: YHBasePlayerViewController { class YHVODPlayerViewController: YHBasePlayerViewController {
// MARK: - Properties // MARK: - Properties
private let vodId: Int private let vodId: Int
var startPosition: Int = 0
//private let viewModel = YHVideoViewModel() //private let viewModel = YHVideoViewModel()
// MARK: - Initialization // MARK: - Initialization
......
...@@ -75,7 +75,7 @@ class YHFloatingWindow: NSObject { ...@@ -75,7 +75,7 @@ class YHFloatingWindow: NSObject {
let container = UIView(frame: CGRect(x: 0, y: 0, width: 30, height: 30)) let container = UIView(frame: CGRect(x: 0, y: 0, width: 30, height: 30))
container.backgroundColor = .clear container.backgroundColor = .clear
container.addSubview(closeButton) container.addSubview(closeButton)
closeButton.center = CGPoint(x: container.bounds.width/2, y: container.bounds.height/2) closeButton.center = CGPoint(x: container.bounds.width / 2, y: container.bounds.height / 2)
return container return container
}() }()
...@@ -110,7 +110,7 @@ class YHFloatingWindow: NSObject { ...@@ -110,7 +110,7 @@ class YHFloatingWindow: NSObject {
contentView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor), contentView.leadingAnchor.constraint(equalTo: containerView.leadingAnchor),
contentView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor), contentView.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
contentView.topAnchor.constraint(equalTo: containerView.topAnchor), contentView.topAnchor.constraint(equalTo: containerView.topAnchor),
contentView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor) contentView.bottomAnchor.constraint(equalTo: containerView.bottomAnchor),
]) ])
containerView.addSubview(closeButtonContainer) containerView.addSubview(closeButtonContainer)
...@@ -119,7 +119,7 @@ class YHFloatingWindow: NSObject { ...@@ -119,7 +119,7 @@ class YHFloatingWindow: NSObject {
closeButtonContainer.topAnchor.constraint(equalTo: containerView.topAnchor), closeButtonContainer.topAnchor.constraint(equalTo: containerView.topAnchor),
closeButtonContainer.trailingAnchor.constraint(equalTo: containerView.trailingAnchor), closeButtonContainer.trailingAnchor.constraint(equalTo: containerView.trailingAnchor),
closeButtonContainer.widthAnchor.constraint(equalToConstant: 30), closeButtonContainer.widthAnchor.constraint(equalToConstant: 30),
closeButtonContainer.heightAnchor.constraint(equalToConstant: 30) closeButtonContainer.heightAnchor.constraint(equalToConstant: 30),
]) ])
} }
...@@ -198,46 +198,6 @@ class YHFloatingWindow: NSObject { ...@@ -198,46 +198,6 @@ class YHFloatingWindow: NSObject {
} }
} }
private func snapToNearestSize() {
let currentWidth = containerView.bounds.width
let sizeSteps: [CGFloat] = [
Size.minWidth,
Size.maxWidth * 0.33,
Size.maxWidth * 0.5,
Size.maxWidth * 0.75,
Size.maxWidth
]
var targetWidth = sizeSteps[0]
var minDifference = abs(currentWidth - targetWidth)
for size in sizeSteps {
let difference = abs(currentWidth - size)
if difference < minDifference {
minDifference = difference
targetWidth = size
}
}
let targetHeight = targetWidth / videoOrientation.aspectRatio
let centerX = containerView.center.x
let centerY = containerView.center.y
UIView.animate(withDuration: 0.3,
delay: 0,
options: [.curveEaseOut],
animations: {
var frame = self.containerView.frame
frame.size = CGSize(width: targetWidth, height: targetHeight)
frame.origin.x = centerX - targetWidth / 2
frame.origin.y = centerY - targetHeight / 2
self.containerView.frame = frame
self.delegate?.floatingWindow(self, didChangeSize: frame.size)
})
}
// MARK: - Gesture Handlers // MARK: - Gesture Handlers
@objc private func handlePan(_ gesture: UIPanGestureRecognizer) { @objc private func handlePan(_ gesture: UIPanGestureRecognizer) {
...@@ -267,42 +227,109 @@ class YHFloatingWindow: NSObject { ...@@ -267,42 +227,109 @@ class YHFloatingWindow: NSObject {
} }
} }
private func snapToNearestSize() {
guard let superview = containerView.superview else { return }
let currentWidth = containerView.bounds.width
let screenWidth = superview.bounds.width
let screenHeight = superview.bounds.height
// 简化尺寸档位,且确保不超过屏幕宽度的75%
let maxAllowedWidth = min(Size.maxWidth, screenWidth * 0.75)
let sizeSteps: [CGFloat] = [
Size.minWidth,
Size.defaultWidth,
maxAllowedWidth * 0.5,
maxAllowedWidth,
]
// 找到最接近的宽度
let targetWidth = sizeSteps.min(by: { abs($0 - currentWidth) < abs($1 - currentWidth) }) ?? Size.defaultWidth
let targetHeight = targetWidth / videoOrientation.aspectRatio
// 确保高度不超过屏幕高度的75%
let maxAllowedHeight = screenHeight * 0.75
let finalWidth: CGFloat
let finalHeight: CGFloat
if targetHeight > maxAllowedHeight {
finalHeight = maxAllowedHeight
finalWidth = finalHeight * videoOrientation.aspectRatio
} else {
finalWidth = targetWidth
finalHeight = targetHeight
}
UIView.animate(withDuration: 0.3) {
var frame = self.containerView.frame
// 设置新的尺寸
frame.size = CGSize(width: finalWidth, height: finalHeight)
// 计算新的中心点,确保不超出屏幕边界
let centerX = max(finalWidth / 2, min(self.containerView.center.x, screenWidth - finalWidth / 2))
let centerY = max(finalHeight / 2, min(self.containerView.center.y, screenHeight - finalHeight / 2))
// 基于新的中心点设置origin
frame.origin.x = centerX - finalWidth / 2
frame.origin.y = centerY - finalHeight / 2
self.containerView.frame = frame
self.delegate?.floatingWindow(self, didChangeSize: frame.size)
}
}
@objc private func handlePinch(_ gesture: UIPinchGestureRecognizer) { @objc private func handlePinch(_ gesture: UIPinchGestureRecognizer) {
guard let superview = containerView.superview else { return }
switch gesture.state { switch gesture.state {
case .began: case .began:
isResizing = true isResizing = true
initialFrame = containerView.frame initialFrame = containerView.frame
currentScale = 1.0
let touch1 = gesture.location(ofTouch: 0, in: containerView)
let touch2 = gesture.location(ofTouch: 1, in: containerView)
initialDistance = hypot(touch2.x - touch1.x, touch2.y - touch1.y)
case .changed: case .changed:
let touch1 = gesture.location(ofTouch: 0, in: containerView) let scale = gesture.scale
let touch2 = gesture.location(ofTouch: 1, in: containerView) let screenWidth = superview.bounds.width
let currentDistance = hypot(touch2.x - touch1.x, touch2.y - touch1.y) let screenHeight = superview.bounds.height
let scale = (currentDistance / initialDistance) * scaleMultiplier // 计算新的尺寸
let scaleDelta = scale / currentScale var newWidth = initialFrame.width * scale
currentScale = scale var newHeight = newWidth / videoOrientation.aspectRatio
let newWidth = initialFrame.width * scaleDelta // 确保不超过屏幕最大限制(75%的屏幕大小)
let newHeight = newWidth / videoOrientation.aspectRatio let maxWidth = screenWidth * 0.75
let maxHeight = screenHeight * 0.75
var newFrame = initialFrame if newWidth > maxWidth {
newFrame.size = constrainSize(CGSize(width: newWidth, height: newHeight)) newWidth = maxWidth
newHeight = newWidth / videoOrientation.aspectRatio
}
let centerX = containerView.center.x if newHeight > maxHeight {
let centerY = containerView.center.y newHeight = maxHeight
newFrame.origin.x = centerX - newFrame.width / 2 newWidth = newHeight * videoOrientation.aspectRatio
newFrame.origin.y = centerY - newFrame.height / 2 }
CATransaction.begin() // 确保不小于最小尺寸
CATransaction.setDisableActions(true) if newWidth < Size.minWidth {
containerView.frame = newFrame newWidth = Size.minWidth
CATransaction.commit() newHeight = newWidth / videoOrientation.aspectRatio
}
// 保持中心点不变,但确保不超出屏幕边界
var newCenter = containerView.center
// 约束中心点,确保视图不会超出屏幕
newCenter.x = max(newWidth / 2, min(newCenter.x, screenWidth - newWidth / 2))
newCenter.y = max(newHeight / 2, min(newCenter.y, screenHeight - newHeight / 2))
// 更新frame
var newFrame = containerView.frame
newFrame.size = CGSize(width: newWidth, height: newHeight)
newFrame.origin.x = newCenter.x - newWidth / 2
newFrame.origin.y = newCenter.y - newHeight / 2
containerView.frame = newFrame
delegate?.floatingWindow(self, didChangeSize: newFrame.size) delegate?.floatingWindow(self, didChangeSize: newFrame.size)
case .ended, .cancelled: case .ended, .cancelled:
......
...@@ -104,9 +104,9 @@ class YHMyViewController: YHBaseViewController, ConstraintRelatableTarget { ...@@ -104,9 +104,9 @@ class YHMyViewController: YHBaseViewController, ConstraintRelatableTarget {
self.navigationController?.pushViewController(vc) self.navigationController?.pushViewController(vc)
// let url = "https://pull-flv-l13.douyincdn.com/stage/stream-116307521507688888_ld5.flv?expire=1733728207&sign=cef0df720ef0dbe3126675d72dcacec2&major_anchor_level=common&abr_pts=-800&_session_id=037-2024120215100750CB84D802B8201D3D81.1733123408338.51633&rsi=1"
// let url = "https://pull-flv-l11.douyincdn.com/thirdgame/stream-116296425803875148.flv?expire=1733558990&sign=cc69d0ac884efe3613385140611c1702&major_anchor_level=common&abr_pts=-800&_session_id=037-2024113016095034A5715FA5656D873A69.1732954190959.73911&rsi=1" // let playbackInfo = YHPlayerManager.PlaybackInfo(id: 40, url: url, isLive: true, scene: .fullscreen)
// YHPlayerManager.shared.enterLive(from: nil, id: 23, url: url) // YHPlayerManager.shared.enterLive(from: nil, playbackInfo: playbackInfo)
} }
......
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