Files
midi_ios/TUIKit/TUIRoomKit/Source/View/Page/Widget/VideoSeat/VideoSeatCell.swift
2025-08-14 10:07:49 +08:00

243 lines
8.1 KiB
Swift

//
// VideoSeatCell.swift
// TUIVideoSeat
//
// Created by WesleyLei on 2021/12/16.
// Copyright © 2021 Tencent. All rights reserved.
//
import SnapKit
import UIKit
class VideoSeatCell: UICollectionViewCell {
var seatItem: VideoSeatItem?
var isSupportedAmplification: Bool {
return seatItem?.videoStreamType == .screenStream
}
private lazy var scrollRenderView: UIScrollView = {
let scrollView = UIScrollView()
scrollView.backgroundColor = UIColor(0x17181F)
scrollView.layer.cornerRadius = 16
scrollView.layer.masksToBounds = true
scrollView.layer.borderWidth = 2
scrollView.layer.borderColor = UIColor.clear.cgColor
scrollView.showsVerticalScrollIndicator = false
scrollView.showsHorizontalScrollIndicator = false
scrollView.maximumZoomScale = 5
scrollView.minimumZoomScale = 1
scrollView.isScrollEnabled = false
scrollView.delegate = self
return scrollView
}()
let renderView: UIView = {
let view = UIView(frame: .zero)
view.backgroundColor = .clear
return view
}()
let backgroundMaskView: UIView = {
let view = UIView(frame: .zero)
view.backgroundColor = UIColor(0x17181F)
view.layer.cornerRadius = 16
view.layer.masksToBounds = true
return view
}()
let userInfoView: VideoSeatUserStatusView = {
let view = VideoSeatUserStatusView()
return view
}()
let avatarImageView: UIImageView = {
let imageView = UIImageView(frame: .zero)
imageView.layer.masksToBounds = true
return imageView
}()
private var isViewReady = false
override func didMoveToWindow() {
super.didMoveToWindow()
guard !isViewReady else {
return
}
isViewReady = true
constructViewHierarchy()
activateConstraints()
contentView.backgroundColor = .clear
}
private func constructViewHierarchy() {
scrollRenderView.addSubview(renderView)
scrollRenderView.addSubview(backgroundMaskView)
contentView.addSubview(scrollRenderView)
contentView.addSubview(avatarImageView)
contentView.addSubview(userInfoView)
}
private func activateConstraints() {
scrollRenderView.snp.makeConstraints { make in
make.edges.equalToSuperview().inset(2)
}
renderView.snp.makeConstraints { make in
make.center.equalToSuperview()
make.width.equalToSuperview()
make.height.equalToSuperview()
}
backgroundMaskView.snp.makeConstraints { make in
make.edges.equalToSuperview()
}
userInfoView.snp.makeConstraints { make in
make.height.equalTo(24)
make.bottom.equalToSuperview().offset(-5)
make.leading.equalToSuperview().offset(5)
make.width.lessThanOrEqualTo(self).multipliedBy(0.9)
}
}
@objc private func resetVolumeView() {
guard let seatItem = seatItem else { return }
userInfoView.updateUserVolume(hasAudio: seatItem.hasAudioStream, volume: 0)
scrollRenderView.layer.borderColor = UIColor.clear.cgColor
}
override func prepareForReuse() {
scrollRenderView.zoomScale = 1.0
}
deinit {
NSObject.cancelPreviousPerformRequests(withTarget: self)
debugPrint("deinit \(self)")
}
}
extension VideoSeatCell: UIScrollViewDelegate {
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
return isSupportedAmplification ? renderView : nil
}
}
// MARK: - Public
extension VideoSeatCell {
func updateUI(item: VideoSeatItem) {
seatItem = item
let placeholder = UIImage(named: "room_default_user", in: tuiRoomKitBundle(), compatibleWith: nil)
avatarImageView.sd_setImage(with: URL(string: item.avatarUrl), placeholderImage: placeholder)
avatarImageView.isHidden = item.videoStreamType == .screenStream ? true : item.hasVideoStream
backgroundMaskView.isHidden = item.videoStreamType == .screenStream ? true : item.hasVideoStream
userInfoView.updateUserStatus(item)
resetVolumeView()
DispatchQueue.main.asyncAfter(deadline: .now() + 0.1) { [weak self] in
guard let self = self else { return }
let width = min(self.mm_w / 2, 72)
self.avatarImageView.layer.cornerRadius = width * 0.5
guard let _ = self.avatarImageView.superview else { return }
self.avatarImageView.snp.remakeConstraints { make in
make.height.width.equalTo(width)
make.center.equalToSuperview()
}
}
}
func updateUIVolume(item: VideoSeatItem) {
userInfoView.updateUserVolume(hasAudio: item.hasAudioStream, volume: item.userVoiceVolume)
if item.userVoiceVolume > 0 && item.hasAudioStream {
if item.videoStreamType != .screenStream {
scrollRenderView.layer.borderColor = UIColor(0xA5FE33).cgColor
}
} else {
scrollRenderView.layer.borderColor = UIColor.clear.cgColor
}
resetVolume()
}
func resetVolume() {
NSObject.cancelPreviousPerformRequests(withTarget: self, selector: #selector(resetVolumeView), object: nil)
perform(#selector(resetVolumeView), with: nil, afterDelay: 1)
}
}
class TUIVideoSeatDragCell: VideoSeatCell {
typealias DragCellClickBlock = () -> Void
var clickBlock: DragCellClickBlock?
private var isViewReady = false
override func didMoveToWindow() {
super.didMoveToWindow()
guard !isViewReady else {
return
}
isViewReady = true
bindInteraction()
}
private func bindInteraction() {
addGesture()
}
func updateSize(size: CGSize) {
var frame = self.frame
frame.size = size
self.frame = frame
center = adsorption(centerPoint: center)
}
}
// MARK: - gesture
extension TUIVideoSeatDragCell {
private func addGesture() {
let tap = UITapGestureRecognizer(target: self, action: #selector(click))
addGestureRecognizer(tap)
let dragGesture = UIPanGestureRecognizer(target: self, action: #selector(dragViewDidDrag(gesture:)))
addGestureRecognizer(dragGesture)
}
@objc private func click() {
clickBlock?()
}
@objc private func dragViewDidDrag(gesture: UIPanGestureRecognizer) {
guard let viewSuperview = superview else { return }
let moveState = gesture.state
let viewCenter = center
switch moveState {
case .changed:
let point = gesture.translation(in: viewSuperview)
center = CGPoint(x: viewCenter.x + point.x, y: viewCenter.y + point.y)
break
case .ended:
let point = gesture.translation(in: viewSuperview)
let newPoint = CGPoint(x: viewCenter.x + point.x, y: viewCenter.y + point.y)
UIView.animate(withDuration: 0.2) {
self.center = self.adsorption(centerPoint: newPoint)
}
break
default: break
}
gesture.setTranslation(.zero, in: viewSuperview)
}
private func adsorption(centerPoint: CGPoint) -> CGPoint {
guard let viewSuperview = superview else { return centerPoint }
let limitMargin = 5.0
let frame = self.frame
let point = CGPoint(x: centerPoint.x - frame.width / 2, y: centerPoint.y - frame.height / 2)
var newPoint = point
if centerPoint.x < (viewSuperview.frame.width / 2) {
newPoint.x = limitMargin
} else {
newPoint.x = viewSuperview.frame.width - frame.width - limitMargin
}
if point.y <= limitMargin {
newPoint.y = limitMargin
} else if (point.y + frame.height) > (viewSuperview.frame.height - limitMargin) {
newPoint.y = viewSuperview.frame.height - frame.height - limitMargin
}
return CGPoint(x: newPoint.x + frame.width / 2, y: newPoint.y + frame.height / 2)
}
}