374 lines
14 KiB
Swift
374 lines
14 KiB
Swift
//
|
|
// TUIVideoSeat.swift
|
|
// TUIVideoSeat
|
|
//
|
|
// Created by WesleyLei on 2022/9/13.
|
|
// Copyright © 2022 Tencent. All rights reserved.
|
|
//
|
|
|
|
import UIKit
|
|
|
|
protocol TUIVideoSeatViewResponder: AnyObject {
|
|
func switchPosition()
|
|
func clickVideoSeat()
|
|
func startPlayVideoStream(item: VideoSeatItem, renderView: UIView?)
|
|
func stopPlayVideoStream(item: VideoSeatItem)
|
|
func updateSpeakerPlayVideoState(currentPageIndex: Int)
|
|
func stopScreenCapture()
|
|
}
|
|
|
|
class TUIVideoSeatView: UIView {
|
|
private let CellID_Normal = "VideoSeatCell_Normal"
|
|
private let CellID_Mini = "VideoSeatCell_Mini"
|
|
private let viewModel: TUIVideoSeatViewModel
|
|
private var isViewReady: Bool = false
|
|
weak var responder: TUIVideoSeatViewResponder?
|
|
|
|
private var pageControl: UIPageControl = {
|
|
let control = UIPageControl()
|
|
control.currentPage = 0
|
|
control.numberOfPages = 1
|
|
control.hidesForSinglePage = true
|
|
control.isUserInteractionEnabled = false
|
|
return control
|
|
}()
|
|
|
|
init(viewModel: TUIVideoSeatViewModel) {
|
|
self.viewModel = viewModel
|
|
super.init(frame: .zero)
|
|
viewModel.viewResponder = self
|
|
responder = viewModel
|
|
isUserInteractionEnabled = true
|
|
}
|
|
|
|
override func didMoveToWindow() {
|
|
super.didMoveToWindow()
|
|
guard !isViewReady else { return }
|
|
constructViewHierarchy()
|
|
activateConstraints()
|
|
bindInteraction()
|
|
isViewReady = true
|
|
}
|
|
|
|
override func layoutSubviews() {
|
|
super.layoutSubviews()
|
|
if let item = moveMiniscreen.seatItem,!moveMiniscreen.isHidden {
|
|
moveMiniscreen.updateSize(size: videoSeatLayout.getMiniscreenFrame(item: item).size)
|
|
}
|
|
let offsetYu = Int(attendeeCollectionView.contentOffset.x) % Int(attendeeCollectionView.mm_w)
|
|
let offsetMuti = CGFloat(offsetYu) / attendeeCollectionView.mm_w
|
|
let currentPage = (offsetMuti > 0.5 ? 1 : 0) + (Int(attendeeCollectionView.contentOffset.x) / Int(attendeeCollectionView.mm_w))
|
|
attendeeCollectionView.setContentOffset(
|
|
CGPoint(x: CGFloat(pageControl.currentPage) * attendeeCollectionView.frame.size.width,
|
|
y: attendeeCollectionView.contentOffset.y), animated: false)
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
lazy var videoSeatLayout: VideoSeatLayout = {
|
|
let layout = VideoSeatLayout(viewModel: viewModel)
|
|
layout.delegate = self
|
|
return layout
|
|
}()
|
|
|
|
lazy var attendeeCollectionView: UICollectionView = {
|
|
let collection = UICollectionView(frame: CGRect(x: 0, y: 0, width: frame.size.width, height: frame.size.height), collectionViewLayout:
|
|
videoSeatLayout)
|
|
collection.register(VideoSeatCell.self, forCellWithReuseIdentifier: CellID_Normal)
|
|
collection.register(TUIVideoSeatDragCell.self, forCellWithReuseIdentifier: CellID_Mini)
|
|
collection.isPagingEnabled = true
|
|
collection.showsVerticalScrollIndicator = false
|
|
collection.showsHorizontalScrollIndicator = false
|
|
collection.isUserInteractionEnabled = true
|
|
collection.contentMode = .scaleToFill
|
|
collection.backgroundColor = UIColor(0x0F1014)
|
|
if #available(iOS 11.0, *) {
|
|
collection.contentInsetAdjustmentBehavior = .never
|
|
} else {
|
|
// Fallback on earlier versions
|
|
}
|
|
if #available(iOS 10.0, *) {
|
|
collection.isPrefetchingEnabled = true
|
|
} else {
|
|
// Fallback on earlier versions
|
|
}
|
|
collection.dataSource = self
|
|
collection.delegate = self
|
|
return collection
|
|
}()
|
|
|
|
lazy var moveMiniscreen: TUIVideoSeatDragCell = {
|
|
let cell = TUIVideoSeatDragCell()
|
|
cell.frame = videoSeatLayout.getMiniscreenFrame(item: nil)
|
|
cell.isHidden = true
|
|
addSubview(cell)
|
|
return cell
|
|
}()
|
|
|
|
lazy var screenCaptureMaskView: ScreenCaptureMaskView = {
|
|
let view = ScreenCaptureMaskView(frameType: .fullScreen)
|
|
view.responder = self.responder
|
|
view.isHidden = true
|
|
return view
|
|
}()
|
|
|
|
let placeholderView: UIView = {
|
|
let view = UIView(frame: .zero)
|
|
view.isHidden = true
|
|
return view
|
|
}()
|
|
|
|
func constructViewHierarchy() {
|
|
backgroundColor = .clear
|
|
addSubview(placeholderView)
|
|
addSubview(attendeeCollectionView)
|
|
addSubview(pageControl)
|
|
addSubview(screenCaptureMaskView)
|
|
}
|
|
|
|
func activateConstraints() {
|
|
placeholderView.snp.makeConstraints { make in
|
|
make.edges.equalToSuperview()
|
|
}
|
|
attendeeCollectionView.snp.makeConstraints { make in
|
|
make.edges.equalToSuperview()
|
|
}
|
|
pageControl.snp.makeConstraints { make in
|
|
make.height.equalTo(24)
|
|
make.centerX.equalToSuperview()
|
|
make.bottom.equalToSuperview().offset(-5)
|
|
}
|
|
screenCaptureMaskView.snp.makeConstraints { make in
|
|
make.edges.equalToSuperview()
|
|
}
|
|
}
|
|
|
|
func bindInteraction() {
|
|
screenCaptureMaskView.isHidden = !EngineManager.shared.store.currentUser.hasScreenStream
|
|
addGesture()
|
|
}
|
|
|
|
private func addGesture() {
|
|
let tap = UITapGestureRecognizer(target: self, action: #selector(clickVideoSeat))
|
|
addGestureRecognizer(tap)
|
|
}
|
|
|
|
@objc private func clickVideoSeat() {
|
|
responder?.clickVideoSeat()
|
|
}
|
|
|
|
func updatePageControl() {
|
|
let offsetYu = Int(attendeeCollectionView.contentOffset.x) % Int(attendeeCollectionView.mm_w)
|
|
let offsetMuti = CGFloat(offsetYu) / attendeeCollectionView.mm_w
|
|
pageControl.currentPage = (offsetMuti > 0.5 ? 1 : 0) + (Int(attendeeCollectionView.contentOffset.x) / Int(attendeeCollectionView.mm_w))
|
|
|
|
if let seatItem = moveMiniscreen.seatItem, seatItem.hasVideoStream {
|
|
if pageControl.currentPage == 0 && !moveMiniscreen.isHidden {
|
|
responder?.startPlayVideoStream(item: seatItem, renderView: moveMiniscreen.renderView)
|
|
} else {
|
|
responder?.startPlayVideoStream(item: seatItem, renderView: getVideoVisibleCell(seatItem)?.renderView)
|
|
}
|
|
}
|
|
}
|
|
|
|
deinit {
|
|
debugPrint("deinit \(self)")
|
|
}
|
|
}
|
|
|
|
// MARK: - TUIVideoSeatViewModelResponder
|
|
|
|
extension TUIVideoSeatView: TUIVideoSeatViewModelResponder {
|
|
private func freshCollectionView(block: () -> Void) {
|
|
CATransaction.begin()
|
|
CATransaction.setDisableActions(true)
|
|
block()
|
|
CATransaction.commit()
|
|
}
|
|
|
|
func reloadData() {
|
|
freshCollectionView { [weak self] in
|
|
guard let self = self else { return }
|
|
self.attendeeCollectionView.reloadData()
|
|
}
|
|
}
|
|
|
|
func insertItems(at indexPaths: [IndexPath]) {
|
|
freshCollectionView { [weak self] in
|
|
guard let self = self else { return }
|
|
let cellNumber = self.attendeeCollectionView.numberOfItems(inSection: 0)
|
|
let listSeatItemNumber = self.viewModel.listSeatItem.count
|
|
guard cellNumber + indexPaths.count == listSeatItemNumber else { return }
|
|
self.attendeeCollectionView.performBatchUpdates { [weak self] in
|
|
guard let self = self else { return }
|
|
self.attendeeCollectionView.insertItems(at: indexPaths)
|
|
}
|
|
}
|
|
}
|
|
|
|
func deleteItems(at indexPaths: [IndexPath]) {
|
|
freshCollectionView { [weak self] in
|
|
guard let self = self else { return }
|
|
var resultArray: [IndexPath] = []
|
|
let numberOfSections = self.attendeeCollectionView.numberOfSections
|
|
for indexPath in indexPaths {
|
|
let section = indexPath.section
|
|
let item = indexPath.item
|
|
guard section < numberOfSections && item < self.attendeeCollectionView.numberOfItems(inSection: section)
|
|
else { continue }
|
|
resultArray.append(indexPath)
|
|
}
|
|
let cellNumber = self.attendeeCollectionView.numberOfItems(inSection: 0)
|
|
let listSeatItemNumber = self.viewModel.listSeatItem.count
|
|
guard cellNumber - indexPaths.count == listSeatItemNumber else { return }
|
|
self.attendeeCollectionView.performBatchUpdates { [weak self] in
|
|
guard let self = self else { return }
|
|
self.attendeeCollectionView.deleteItems(at: resultArray)
|
|
}
|
|
}
|
|
}
|
|
|
|
func updateVideoSeatCellUI(_ item: VideoSeatItem) {
|
|
if let seatItem = moveMiniscreen.seatItem, seatItem.userId == item.userId {
|
|
moveMiniscreen.updateUI(item: seatItem)
|
|
}
|
|
guard let cell = getVideoVisibleCell(item) else { return }
|
|
cell.updateUI(item: item)
|
|
}
|
|
|
|
func updateSeatVolume(_ item: VideoSeatItem) {
|
|
guard let cell = getVideoVisibleCell(item) else { return }
|
|
cell.updateUIVolume(item: item)
|
|
}
|
|
|
|
func getVideoVisibleCell(_ item: VideoSeatItem) -> VideoSeatCell? {
|
|
let cellArray = attendeeCollectionView.visibleCells
|
|
guard let cell = cellArray.first(where: { cell in
|
|
if let seatCell = cell as? VideoSeatCell, seatCell.seatItem == item {
|
|
return true
|
|
} else {
|
|
return false
|
|
}
|
|
}) as? VideoSeatCell else { return nil }
|
|
return cell
|
|
}
|
|
|
|
func updateMiniscreen(_ item: VideoSeatItem?) {
|
|
guard let item = item else {
|
|
moveMiniscreen.isHidden = true
|
|
return
|
|
}
|
|
if attendeeCollectionView.contentOffset.x > 0 {
|
|
return
|
|
}
|
|
if let seatItem = moveMiniscreen.seatItem, seatItem.userId != item.userId, (getVideoVisibleCell(seatItem) == nil) {
|
|
responder?.stopPlayVideoStream(item: seatItem)
|
|
}
|
|
moveMiniscreen.updateSize(size: videoSeatLayout.getMiniscreenFrame(item: item).size)
|
|
moveMiniscreen.isHidden = false
|
|
bringSubviewToFront(moveMiniscreen)
|
|
moveMiniscreen.updateUI(item: item)
|
|
if item.isHasVideoStream {
|
|
responder?.startPlayVideoStream(item: item, renderView: moveMiniscreen.renderView)
|
|
}
|
|
}
|
|
|
|
func updateMiniscreenVolume(_ item: VideoSeatItem) {
|
|
moveMiniscreen.updateUIVolume(item: item)
|
|
}
|
|
|
|
func getMoveMiniscreen() -> TUIVideoSeatDragCell {
|
|
return moveMiniscreen
|
|
}
|
|
|
|
func showScreenCaptureMaskView(isShow: Bool) {
|
|
screenCaptureMaskView.isHidden = !isShow
|
|
if isShow {
|
|
screenCaptureMaskView.superview?.bringSubviewToFront(screenCaptureMaskView)
|
|
}
|
|
}
|
|
|
|
func destroyVideoSeatResponder() {
|
|
responder = nil
|
|
attendeeCollectionView.delegate = nil
|
|
attendeeCollectionView.dataSource = nil
|
|
}
|
|
}
|
|
|
|
// MARK: - UICollectionViewDelegateFlowLayout
|
|
|
|
extension TUIVideoSeatView: UICollectionViewDelegateFlowLayout {
|
|
func collectionView(_ collectionView: UICollectionView, willDisplay cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
|
|
guard let seatItem = viewModel.listSeatItem[safe: indexPath.item] else { return }
|
|
guard let seatCell = cell as? VideoSeatCell else { return }
|
|
if seatItem.isHasVideoStream {
|
|
responder?.startPlayVideoStream(item: seatItem, renderView: seatCell.renderView)
|
|
} else {
|
|
responder?.stopPlayVideoStream(item: seatItem)
|
|
}
|
|
seatCell.updateUI(item: seatItem)
|
|
}
|
|
|
|
func collectionView(_ collectionView: UICollectionView, didEndDisplaying cell: UICollectionViewCell, forItemAt indexPath: IndexPath) {
|
|
guard let seatCell = cell as? VideoSeatCell else { return }
|
|
if let seatItem = seatCell.seatItem {
|
|
responder?.stopPlayVideoStream(item: seatItem)
|
|
}
|
|
}
|
|
}
|
|
|
|
extension TUIVideoSeatView: UIScrollViewDelegate {
|
|
func scrollViewDidEndDecelerating(_ scrollView: UIScrollView) {
|
|
let currentPageIndex = Int(scrollView.contentOffset.x / scrollView.mm_w)
|
|
responder?.updateSpeakerPlayVideoState(currentPageIndex: currentPageIndex)
|
|
if currentPageIndex == 0 {
|
|
addSubview(moveMiniscreen)
|
|
} else {
|
|
attendeeCollectionView.addSubview(moveMiniscreen)
|
|
}
|
|
updatePageControl()
|
|
}
|
|
|
|
func scrollViewWillBeginDragging(_ scrollView: UIScrollView) {
|
|
attendeeCollectionView.addSubview(moveMiniscreen)
|
|
}
|
|
}
|
|
|
|
// MARK: - UICollectionViewDataSource
|
|
|
|
extension TUIVideoSeatView: UICollectionViewDataSource {
|
|
func numberOfSections(in collectionView: UICollectionView) -> Int {
|
|
return 1
|
|
}
|
|
|
|
func collectionView(_ collectionView: UICollectionView, numberOfItemsInSection section: Int) -> Int {
|
|
return viewModel.listSeatItem.count
|
|
}
|
|
|
|
func collectionView(_ collectionView: UICollectionView, cellForItemAt indexPath: IndexPath) -> UICollectionViewCell {
|
|
if viewModel.videoSeatViewType == .largeSmallWindowType, indexPath.row == 1 {
|
|
let cell = collectionView.dequeueReusableCell(withReuseIdentifier: CellID_Mini, for: indexPath) as! TUIVideoSeatDragCell
|
|
cell.clickBlock = {[weak self] in
|
|
guard let self = self else { return }
|
|
self.viewModel.switchPosition()
|
|
}
|
|
return cell
|
|
} else {
|
|
let cell = collectionView.dequeueReusableCell(
|
|
withReuseIdentifier: CellID_Normal,
|
|
for: indexPath) as! VideoSeatCell
|
|
return cell
|
|
}
|
|
}
|
|
}
|
|
|
|
// MARK: - UICollectionViewDataSource
|
|
|
|
extension TUIVideoSeatView: VideoSeatLayoutDelegate {
|
|
func updateNumberOfPages(numberOfPages: NSInteger) {
|
|
pageControl.numberOfPages = numberOfPages
|
|
}
|
|
}
|