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,195 +12,212 @@ import UIKit ...@@ -12,195 +12,212 @@ 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
init(id: Int, url: String? = nil, title: String? = nil, roomId: String? = nil, isLive: Bool) { var playerType: YHPlayerType
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 {
lhs.id == rhs.id && lhs.isLive == rhs.isLive lhs.id == rhs.id && lhs.isLive == rhs.isLive
} }
} }
// 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)
}
// 创建第二播放器 func play(url: String, inView view: UIView? = nil, title: String? = nil, type: YHPlayerType = .main) {
if let secondaryPlayerKit = agoraKit.createMediaPlayer(with: self) { let player = player(for: type)
players[.secondary] = YHPlayer(type: .secondary, playerKit: secondaryPlayerKit) if let view = view {
player.setPlayView(view)
} }
player.play(url: url, title: title)
} }
// MARK: - Public Methods func pause(type: YHPlayerType = .main) {
activePlayers[type]?.pause()
// 获取播放器实例
func getPlayer(_ type: YHPlayerType) -> YHPlayer? {
return players[type]
} }
// 播放控制 func resume(type: YHPlayerType = .main) {
func play(url: String, inView view: UIView? = nil, title: String? = nil, type: YHPlayerType = .main) { activePlayers[type]?.resume()
if let player = players[type] {
// 如果提供了视图,则设置播放视图
if let view = view {
player.setPlayView(view)
}
player.play(url: url, title: title)
}
}
func play(playbackInfo: PlaybackInfo, inView view: UIView? = nil, type: YHPlayerType = .main) {
if let player = players[type] {
// 如果提供了视图,则设置播放视图
currentPlaybackInfo[type] = playbackInfo
if let view = view {
player.setPlayView(view)
}
if let url = playbackInfo.url {
player.play(url: url, title: playbackInfo.title)
}
}
} }
func resume(type: YHPlayerType = .main) { func stop(type: YHPlayerType = .main) {
players[type]?.resume() activePlayers[type]?.stop()
} }
func pause(type: YHPlayerType = .main) { func setPlayView(_ view: UIView?, type: YHPlayerType = .main) {
players[type]?.pause() activePlayers[type]?.setPlayView(view)
} }
func stop(type: YHPlayerType = .main) { func getCurrentPlayer(type: YHPlayerType = .main) -> YHPlayer? {
players[type]?.stop() return activePlayers[type]
} }
func stopAllPlayers() { func setMute(_ muted: Bool, type: YHPlayerType = .main) {
players.values.forEach { $0.stop() } activePlayers[type]?.isMuted = muted
} }
// 视图控制 func getPlayer(_ type: YHPlayerType) -> YHPlayer? {
func setPlayView(_ view: UIView?, type: YHPlayerType = .main) { return activePlayers[type]
players[type]?.setPlayView(view)
} }
// MARK: - 资源管理 // MARK: - Player Management
func destroy() { private func player(for type: YHPlayerType) -> YHPlayer {
stopAllPlayers() if let existingPlayer = activePlayers[type] {
players.removeAll() return existingPlayer
exitFloating() }
AgoraRtcEngineKit.destroy()
let newPlayer = createPlayer(for: type)
activePlayers[type] = newPlayer
return newPlayer
} }
}
extension YHPlayerManager { private func createPlayer(for type: YHPlayerType) -> YHPlayer {
// MARK: - 场景切换 guard let playerKit = agoraKit.createMediaPlayer(with: self) else {
fatalError("Failed to create media player")
func enterVOD(from sourceView: UIView?, playbackInfo: PlaybackInfo, type: YHPlayerType = .main) {
let playerVC = YHVODPlayerViewController(id: playbackInfo.id, url: playbackInfo.url, title: playbackInfo.title)
// 保存播放信息
currentPlaybackInfo[type] = playbackInfo
if let player = players[type] {
player.delegate = playerVC
playerVC.player = player
} }
playerVC.playbackInfo = playbackInfo let player = YHPlayer(type: type, playerKit: playerKit)
present(playerVC, from: sourceView) return player
} }
func enterLive(from sourceView: UIView?, playbackInfo: PlaybackInfo, type: YHPlayerType = .main) { private func releasePlayer(_ type: YHPlayerType) {
// 保存播放信息 guard let player = activePlayers[type] else { return }
currentPlaybackInfo[type] = playbackInfo player.stop()
player.releasePlayer()
let playerVC = YHLivePlayerViewController(id: playbackInfo.id, url: playbackInfo.url, title: playbackInfo.title, roomId: playbackInfo.roomId) activePlayers.removeValue(forKey: type)
playerVC.playbackInfo = playbackInfo currentPlaybackInfo.removeValue(forKey: type)
if let player = players[type] { }
player.delegate = playerVC
playerVC.player = player private func determinePlayerType(for scene: PlaybackScene) -> YHPlayerType {
switch scene {
case .banner:
return .secondary
case .fullscreen, .floating:
return .main
} }
present(playerVC, from: sourceView)
} }
// MARK: - Scene Management
func enterVOD(from sourceView: UIView?, id: Int, url: String, title: String?, type: YHPlayerType = .main) {
let playerVC = YHVODPlayerViewController(id: id, url: url, title: title) func enterVOD(from sourceView: UIView?, playbackInfo: PlaybackInfo) {
// 保存播放信息 let playerType = determinePlayerType(for: .fullscreen)
let playbackInfo = PlaybackInfo(id: id, url: url, title: title, isLive: false) var updatedInfo = playbackInfo
playerVC.playbackInfo = playbackInfo updatedInfo.scene = .fullscreen
currentPlaybackInfo[type] = playbackInfo updatedInfo.playerType = playerType
if let player = players[type] {
player.delegate = playerVC // 如果是从banner跳转,需要转移播放进度
playerVC.player = player var startPosition: Int = 0
if playbackInfo.scene == .banner,
let bannerPlayer = activePlayers[.secondary] {
startPosition = bannerPlayer.getPosition()
releasePlayer(.secondary)
} }
let playerVC = YHVODPlayerViewController(id: playbackInfo.id, url: playbackInfo.url, title: playbackInfo.title)
currentPlaybackInfo[playerType] = updatedInfo
let player = player(for: playerType)
player.delegate = playerVC
playerVC.player = player
playerVC.playbackInfo = updatedInfo
playerVC.startPosition = startPosition
// 关闭小窗
exitFloating()
present(playerVC, from: sourceView) present(playerVC, from: sourceView)
} }
func enterLive(from sourceView: UIView?, id: Int, url: String? = nil, title: String? = nil, roomId: String? = nil, type: YHPlayerType = .main) { func enterLive(from sourceView: UIView?, playbackInfo: PlaybackInfo) {
// 保存播放信息 let playerType = determinePlayerType(for: .fullscreen)
let playbackInfo = PlaybackInfo(id: id, url: url, title: title, roomId: roomId, isLive: true) var updatedInfo = playbackInfo
currentPlaybackInfo[type] = playbackInfo updatedInfo.scene = .fullscreen
let playerVC = YHLivePlayerViewController(id: id, url: url, title: title, roomId: roomId) updatedInfo.playerType = playerType
playerVC.playbackInfo = playbackInfo
if let player = players[type] { // 如果是从banner跳转,释放副播放器
player.delegate = playerVC if playbackInfo.scene == .banner {
playerVC.player = player releasePlayer(.secondary)
} }
let playerVC = YHLivePlayerViewController(id: playbackInfo.id, url: playbackInfo.url, title: playbackInfo.title, roomId: playbackInfo.roomId)
currentPlaybackInfo[playerType] = updatedInfo
let player = player(for: playerType)
player.delegate = playerVC
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)
player.setPlayView(floatingWindow.contentView)
// 切换播放视图
if let player = self.players[type] {
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
...@@ -289,13 +304,38 @@ extension YHPlayerManager { ...@@ -289,13 +304,38 @@ extension YHPlayerManager {
playerVC.transitioningDelegate = self playerVC.transitioningDelegate = self
transitionSourceView = sourceView transitionSourceView = sourceView
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
} }
...@@ -387,15 +421,15 @@ extension YHPlayerManager: YHFloatingWindowDelegate { ...@@ -387,15 +421,15 @@ extension YHPlayerManager: YHFloatingWindowDelegate {
extension YHPlayerManager: UIViewControllerTransitioningDelegate { extension YHPlayerManager: UIViewControllerTransitioningDelegate {
func animationController(forPresented presented: UIViewController, func animationController(forPresented presented: UIViewController,
presenting: UIViewController, presenting: UIViewController,
source: UIViewController) -> UIViewControllerAnimatedTransitioning? { source: UIViewController) -> UIViewControllerAnimatedTransitioning? {
if let sourceView = transitionSourceView { if let sourceView = transitionSourceView {
return YHPlayerTransitionAnimator(type: .zoomIn, sourceView: sourceView) return YHPlayerTransitionAnimator(type: .zoomIn, sourceView: sourceView)
} else { } else {
return YHPlayerTransitionAnimator(type: .push) return YHPlayerTransitionAnimator(type: .push)
} }
} }
func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? { func animationController(forDismissed dismissed: UIViewController) -> UIViewControllerAnimatedTransitioning? {
if let sourceView = transitionSourceView { if let sourceView = transitionSourceView {
return YHPlayerTransitionAnimator(type: .zoomOut, sourceView: sourceView) return YHPlayerTransitionAnimator(type: .zoomOut, sourceView: sourceView)
...@@ -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
......
...@@ -19,16 +19,16 @@ protocol YHFloatingWindowDelegate: AnyObject { ...@@ -19,16 +19,16 @@ protocol YHFloatingWindowDelegate: AnyObject {
class YHFloatingWindow: NSObject { class YHFloatingWindow: NSObject {
// MARK: - Properties // MARK: - Properties
weak var player: YHPlayer? weak var player: YHPlayer?
weak var delegate: YHFloatingWindowDelegate? weak var delegate: YHFloatingWindowDelegate?
var playbackInfo: YHPlayerManager.PlaybackInfo? var playbackInfo: YHPlayerManager.PlaybackInfo?
// 视频方向 // 视频方向
enum VideoOrientation { enum VideoOrientation {
case portrait // 竖屏 9:16 case portrait // 竖屏 9:16
case landscape // 横屏 16:9 case landscape // 横屏 16:9
var aspectRatio: CGFloat { var aspectRatio: CGFloat {
switch self { switch self {
case .portrait: return 9.0 / 16.0 case .portrait: return 9.0 / 16.0
...@@ -36,32 +36,32 @@ class YHFloatingWindow: NSObject { ...@@ -36,32 +36,32 @@ class YHFloatingWindow: NSObject {
} }
} }
} }
// 窗口尺寸 // 窗口尺寸
private struct Size { private struct Size {
static let minWidth: CGFloat = 120 static let minWidth: CGFloat = 120
static let maxWidth: CGFloat = UIScreen.main.bounds.width static let maxWidth: CGFloat = UIScreen.main.bounds.width
static let minHeight: CGFloat = 67.5 // 16:9 static let minHeight: CGFloat = 67.5 // 16:9
static let maxHeight: CGFloat = UIScreen.main.bounds.height static let maxHeight: CGFloat = UIScreen.main.bounds.height
static let defaultWidth: CGFloat = 150 static let defaultWidth: CGFloat = 150
static let defaultHeight: CGFloat = 84.375 // 16:9 static let defaultHeight: CGFloat = 84.375 // 16:9
} }
private(set) var contentView: UIView private(set) var contentView: UIView
private var containerView: UIView private var containerView: UIView
private var videoOrientation: VideoOrientation = .landscape private var videoOrientation: VideoOrientation = .landscape
// 手势相关 // 手势相关
private var initialFrame: CGRect = .zero private var initialFrame: CGRect = .zero
private var isResizing: Bool = false private var isResizing: Bool = false
private var initialCenter: CGPoint = .zero private var initialCenter: CGPoint = .zero
// 缩放相关 // 缩放相关
private var currentScale: CGFloat = 1.0 private var currentScale: CGFloat = 1.0
private var initialDistance: CGFloat = 0 private var initialDistance: CGFloat = 0
private let scaleMultiplier: CGFloat = 1.5 private let scaleMultiplier: CGFloat = 1.5
// UI组件 // UI组件
private lazy var closeButton: UIButton = { private lazy var closeButton: UIButton = {
let button = UIButton(type: .custom) let button = UIButton(type: .custom)
...@@ -70,86 +70,86 @@ class YHFloatingWindow: NSObject { ...@@ -70,86 +70,86 @@ class YHFloatingWindow: NSObject {
button.frame = CGRect(x: 0, y: 0, width: 24, height: 24) button.frame = CGRect(x: 0, y: 0, width: 24, height: 24)
return button return button
}() }()
private lazy var closeButtonContainer: UIView = { private lazy var closeButtonContainer: UIView = {
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
}() }()
// MARK: - Initialization // MARK: - Initialization
override init() { override init() {
containerView = UIView(frame: CGRect(x: 0, y: 0, containerView = UIView(frame: CGRect(x: 0, y: 0,
width: Size.defaultWidth, width: Size.defaultWidth,
height: Size.defaultHeight)) height: Size.defaultHeight))
contentView = UIView(frame: containerView.bounds) contentView = UIView(frame: containerView.bounds)
super.init() super.init()
setupUI() setupUI()
setupGestures() setupGestures()
} }
// MARK: - Setup // MARK: - Setup
private func setupUI() { private func setupUI() {
containerView.backgroundColor = .black containerView.backgroundColor = .black
containerView.layer.cornerRadius = 3 containerView.layer.cornerRadius = 3
containerView.clipsToBounds = true containerView.clipsToBounds = true
containerView.layer.masksToBounds = true containerView.layer.masksToBounds = true
containerView.layer.shadowColor = UIColor.black.cgColor containerView.layer.shadowColor = UIColor.black.cgColor
containerView.layer.shadowOffset = CGSize(width: 0, height: 2) containerView.layer.shadowOffset = CGSize(width: 0, height: 2)
containerView.layer.shadowRadius = 4 containerView.layer.shadowRadius = 4
containerView.layer.shadowOpacity = 0.3 containerView.layer.shadowOpacity = 0.3
containerView.addSubview(contentView) containerView.addSubview(contentView)
contentView.translatesAutoresizingMaskIntoConstraints = false contentView.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
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)
closeButtonContainer.translatesAutoresizingMaskIntoConstraints = false closeButtonContainer.translatesAutoresizingMaskIntoConstraints = false
NSLayoutConstraint.activate([ NSLayoutConstraint.activate([
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),
]) ])
} }
private func setupGestures() { private func setupGestures() {
let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:))) let panGesture = UIPanGestureRecognizer(target: self, action: #selector(handlePan(_:)))
let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(handlePinch(_:))) let pinchGesture = UIPinchGestureRecognizer(target: self, action: #selector(handlePinch(_:)))
let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:))) let tapGesture = UITapGestureRecognizer(target: self, action: #selector(handleTap(_:)))
tapGesture.delegate = self tapGesture.delegate = self
containerView.addGestureRecognizer(panGesture) containerView.addGestureRecognizer(panGesture)
containerView.addGestureRecognizer(pinchGesture) containerView.addGestureRecognizer(pinchGesture)
containerView.addGestureRecognizer(tapGesture) containerView.addGestureRecognizer(tapGesture)
} }
// MARK: - Public Methods // MARK: - Public Methods
func calculateInitialFrame() -> CGRect { func calculateInitialFrame() -> CGRect {
let width: CGFloat = Size.defaultWidth let width: CGFloat = Size.defaultWidth
let height: CGFloat = width / videoOrientation.aspectRatio let height: CGFloat = width / videoOrientation.aspectRatio
let x = UIScreen.main.bounds.width - width - 16 let x = UIScreen.main.bounds.width - width - 16
let y = UIScreen.main.bounds.height - height - 100 let y = UIScreen.main.bounds.height - height - 100
return CGRect(x: x, y: y, width: width, height: height) return CGRect(x: x, y: y, width: width, height: height)
} }
func show(in window: UIWindow) { func show(in window: UIWindow) {
containerView.frame = calculateInitialFrame() containerView.frame = calculateInitialFrame()
window.addSubview(containerView) window.addSubview(containerView)
} }
func show(in window: UIWindow, at point: CGPoint? = nil) { func show(in window: UIWindow, at point: CGPoint? = nil) {
if let point = point { if let point = point {
containerView.center = point containerView.center = point
...@@ -159,15 +159,15 @@ class YHFloatingWindow: NSObject { ...@@ -159,15 +159,15 @@ class YHFloatingWindow: NSObject {
y: window.bounds.height - containerView.bounds.height - 100 y: window.bounds.height - containerView.bounds.height - 100
) )
} }
window.addSubview(containerView) window.addSubview(containerView)
containerView.alpha = 0 containerView.alpha = 0
UIView.animate(withDuration: 0.3) { UIView.animate(withDuration: 0.3) {
self.containerView.alpha = 1 self.containerView.alpha = 1
} }
} }
func dismiss() { func dismiss() {
UIView.animate(withDuration: 0.3, animations: { UIView.animate(withDuration: 0.3, animations: {
self.containerView.alpha = 0 self.containerView.alpha = 0
...@@ -175,7 +175,7 @@ class YHFloatingWindow: NSObject { ...@@ -175,7 +175,7 @@ class YHFloatingWindow: NSObject {
self.containerView.removeFromSuperview() self.containerView.removeFromSuperview()
} }
} }
func setVideoSize(_ size: CGSize) { func setVideoSize(_ size: CGSize) {
let orientation: VideoOrientation = size.width > size.height ? .landscape : .portrait let orientation: VideoOrientation = size.width > size.height ? .landscape : .portrait
if orientation != videoOrientation { if orientation != videoOrientation {
...@@ -183,13 +183,13 @@ class YHFloatingWindow: NSObject { ...@@ -183,13 +183,13 @@ class YHFloatingWindow: NSObject {
updateLayoutForOrientation() updateLayoutForOrientation()
} }
} }
// MARK: - Private Methods // MARK: - Private Methods
private func updateLayoutForOrientation() { private func updateLayoutForOrientation() {
let currentWidth = containerView.bounds.width let currentWidth = containerView.bounds.width
let newHeight = currentWidth / videoOrientation.aspectRatio let newHeight = currentWidth / videoOrientation.aspectRatio
UIView.animate(withDuration: 0.3) { UIView.animate(withDuration: 0.3) {
var frame = self.containerView.frame var frame = self.containerView.frame
frame.size.height = newHeight frame.size.height = newHeight
...@@ -197,58 +197,18 @@ class YHFloatingWindow: NSObject { ...@@ -197,58 +197,18 @@ class YHFloatingWindow: NSObject {
self.delegate?.floatingWindow(self, didChangeSize: frame.size) self.delegate?.floatingWindow(self, didChangeSize: frame.size)
} }
} }
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) {
guard !isResizing else { return } guard !isResizing else { return }
let translation = gesture.translation(in: containerView.superview) let translation = gesture.translation(in: containerView.superview)
switch gesture.state { switch gesture.state {
case .began: case .began:
initialCenter = containerView.center initialCenter = containerView.center
case .changed: case .changed:
var newCenter = CGPoint( var newCenter = CGPoint(
x: initialCenter.x + translation.x, x: initialCenter.x + translation.x,
...@@ -257,137 +217,204 @@ class YHFloatingWindow: NSObject { ...@@ -257,137 +217,204 @@ class YHFloatingWindow: NSObject {
newCenter = adjustedPosition(for: newCenter) newCenter = adjustedPosition(for: newCenter)
containerView.center = newCenter containerView.center = newCenter
delegate?.floatingWindow(self, didChangePosition: newCenter) delegate?.floatingWindow(self, didChangePosition: newCenter)
case .ended: case .ended:
let velocity = gesture.velocity(in: containerView.superview) let velocity = gesture.velocity(in: containerView.superview)
handlePanEndedWithVelocity(velocity) handlePanEndedWithVelocity(velocity)
default: default:
break break
} }
} }
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
newFrame.size = constrainSize(CGSize(width: newWidth, height: newHeight)) if newWidth > maxWidth {
newWidth = maxWidth
let centerX = containerView.center.x newHeight = newWidth / videoOrientation.aspectRatio
let centerY = containerView.center.y }
newFrame.origin.x = centerX - newFrame.width / 2
newFrame.origin.y = centerY - newFrame.height / 2 if newHeight > maxHeight {
newHeight = maxHeight
CATransaction.begin() newWidth = newHeight * videoOrientation.aspectRatio
CATransaction.setDisableActions(true) }
// 确保不小于最小尺寸
if newWidth < Size.minWidth {
newWidth = Size.minWidth
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 containerView.frame = newFrame
CATransaction.commit()
delegate?.floatingWindow(self, didChangeSize: newFrame.size) delegate?.floatingWindow(self, didChangeSize: newFrame.size)
case .ended, .cancelled: case .ended, .cancelled:
isResizing = false isResizing = false
snapToNearestSize() snapToNearestSize()
default: default:
break break
} }
} }
@objc private func handleTap(_ gesture: UITapGestureRecognizer) { @objc private func handleTap(_ gesture: UITapGestureRecognizer) {
let location = gesture.location(in: containerView) let location = gesture.location(in: containerView)
if closeButtonContainer.frame.contains(location) { if closeButtonContainer.frame.contains(location) {
return return
} }
delegate?.floatingWindowDidTap(self) delegate?.floatingWindowDidTap(self)
} }
@objc private func closeButtonTapped() { @objc private func closeButtonTapped() {
delegate?.floatingWindowDidClose(self) delegate?.floatingWindowDidClose(self)
dismiss() dismiss()
} }
// MARK: - Helper Methods // MARK: - Helper Methods
private func constrainSize(_ size: CGSize) -> CGSize { private func constrainSize(_ size: CGSize) -> CGSize {
var width = size.width var width = size.width
var height = size.height var height = size.height
if width < Size.minWidth { if width < Size.minWidth {
width = Size.minWidth width = Size.minWidth
} else if width > Size.maxWidth { } else if width > Size.maxWidth {
width = Size.maxWidth width = Size.maxWidth
} }
height = width / videoOrientation.aspectRatio height = width / videoOrientation.aspectRatio
if height > Size.maxHeight { if height > Size.maxHeight {
height = Size.maxHeight height = Size.maxHeight
width = height * videoOrientation.aspectRatio width = height * videoOrientation.aspectRatio
} }
return CGSize(width: width, height: height) return CGSize(width: width, height: height)
} }
private func adjustedPosition(for center: CGPoint) -> CGPoint { private func adjustedPosition(for center: CGPoint) -> CGPoint {
guard let superview = containerView.superview else { return center } guard let superview = containerView.superview else { return center }
var adjustedCenter = center var adjustedCenter = center
let halfWidth = containerView.bounds.width / 2 let halfWidth = containerView.bounds.width / 2
let halfHeight = containerView.bounds.height / 2 let halfHeight = containerView.bounds.height / 2
let safeAreaInsets: UIEdgeInsets let safeAreaInsets: UIEdgeInsets
if #available(iOS 11.0, *) { if #available(iOS 11.0, *) {
safeAreaInsets = superview.safeAreaInsets safeAreaInsets = superview.safeAreaInsets
} else { } else {
safeAreaInsets = .zero safeAreaInsets = .zero
} }
let topLimit = safeAreaInsets.top + halfHeight let topLimit = safeAreaInsets.top + halfHeight
let bottomLimit = superview.bounds.height - safeAreaInsets.bottom - halfHeight let bottomLimit = superview.bounds.height - safeAreaInsets.bottom - halfHeight
adjustedCenter.x = min(max(halfWidth, adjustedCenter.x), adjustedCenter.x = min(max(halfWidth, adjustedCenter.x),
superview.bounds.width - halfWidth) superview.bounds.width - halfWidth)
adjustedCenter.y = min(max(topLimit, adjustedCenter.y), adjustedCenter.y = min(max(topLimit, adjustedCenter.y),
bottomLimit) bottomLimit)
return adjustedCenter return adjustedCenter
} }
private func handlePanEndedWithVelocity(_ velocity: CGPoint) { private func handlePanEndedWithVelocity(_ velocity: CGPoint) {
let magnitude = sqrt((velocity.x * velocity.x) + (velocity.y * velocity.y)) let magnitude = sqrt((velocity.x * velocity.x) + (velocity.y * velocity.y))
let slideMultiplier = magnitude / 200 let slideMultiplier = magnitude / 200
let slideFactor = 0.1 * slideMultiplier let slideFactor = 0.1 * slideMultiplier
var finalCenter = CGPoint( var finalCenter = CGPoint(
x: containerView.center.x + (velocity.x * slideFactor), x: containerView.center.x + (velocity.x * slideFactor),
y: containerView.center.y + (velocity.y * slideFactor) y: containerView.center.y + (velocity.y * slideFactor)
) )
finalCenter = adjustedPosition(for: finalCenter) finalCenter = adjustedPosition(for: finalCenter)
UIView.animate(withDuration: 0.3) { UIView.animate(withDuration: 0.3) {
self.containerView.center = finalCenter self.containerView.center = finalCenter
self.delegate?.floatingWindow(self, didChangePosition: finalCenter) self.delegate?.floatingWindow(self, didChangePosition: finalCenter)
...@@ -399,12 +426,12 @@ class YHFloatingWindow: NSObject { ...@@ -399,12 +426,12 @@ class YHFloatingWindow: NSObject {
extension YHFloatingWindow: UIGestureRecognizerDelegate { extension YHFloatingWindow: UIGestureRecognizerDelegate {
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool { shouldRecognizeSimultaneouslyWith otherGestureRecognizer: UIGestureRecognizer) -> Bool {
return true return true
} }
func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer, func gestureRecognizer(_ gestureRecognizer: UIGestureRecognizer,
shouldReceive touch: UITouch) -> Bool { shouldReceive touch: UITouch) -> Bool {
let location = touch.location(in: containerView) let location = touch.location(in: containerView)
if closeButtonContainer.frame.contains(location) { if closeButtonContainer.frame.contains(location) {
return false return false
......
...@@ -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