190 lines
6.5 KiB
Swift
190 lines
6.5 KiB
Swift
//
|
|
// Copyright (c) 2021 Open Whisper Systems. All rights reserved.
|
|
//
|
|
|
|
import Foundation
|
|
import SignalRingRTC
|
|
|
|
class GroupCallRemoteVideoManager: Dependencies {
|
|
private var currentGroupCall: GroupCall? {
|
|
guard let call = Self.callService.currentCall, call.isGroupCall else { return nil }
|
|
return call.groupCall
|
|
}
|
|
|
|
// MARK: - Remote Video Views
|
|
private var videoViews = [UInt32: [GroupCallRemoteMemberView.Mode: GroupCallRemoteVideoView]]()
|
|
|
|
func remoteVideoView(for device: RemoteDeviceState, mode: GroupCallRemoteMemberView.Mode) -> GroupCallRemoteVideoView {
|
|
AssertIsOnMainThread()
|
|
|
|
var currentVideoViewsDevice = videoViews[device.demuxId] ?? [:]
|
|
|
|
if let current = currentVideoViewsDevice[mode] { return current }
|
|
|
|
let videoView = GroupCallRemoteVideoView(demuxId: device.demuxId)
|
|
videoView.sizeDelegate = self
|
|
videoView.isGroupCall = true
|
|
|
|
if mode == .speaker { videoView.isFullScreen = true }
|
|
|
|
currentVideoViewsDevice[mode] = videoView
|
|
videoViews[device.demuxId] = currentVideoViewsDevice
|
|
|
|
return videoView
|
|
}
|
|
|
|
private func destroyRemoteVideoView(for demuxId: UInt32) {
|
|
AssertIsOnMainThread()
|
|
|
|
videoViews[demuxId]?.forEach { $0.value.removeFromSuperview() }
|
|
videoViews[demuxId] = nil
|
|
}
|
|
|
|
private var updateVideoRequestsDebounceTimer: Timer?
|
|
private func updateVideoRequests() {
|
|
updateVideoRequestsDebounceTimer?.invalidate()
|
|
updateVideoRequestsDebounceTimer = Timer.scheduledTimer(withTimeInterval: 0.2, repeats: false, block: { [weak self] _ in
|
|
AssertIsOnMainThread()
|
|
guard let self = self else { return }
|
|
guard let groupCall = self.currentGroupCall else { return }
|
|
|
|
let videoRequests: [VideoRequest] = groupCall.remoteDeviceStates.map { demuxId, _ in
|
|
guard let renderingVideoViews = self.videoViews[demuxId]?.values.filter({ $0.isRenderingVideo }),
|
|
!renderingVideoViews.isEmpty else {
|
|
return VideoRequest(demuxId: demuxId, width: 0, height: 0, framerate: nil)
|
|
}
|
|
|
|
let width = renderingVideoViews.reduce(into: 0, { $0 = max($0, $1.currentSize.width) })
|
|
let height = renderingVideoViews.reduce(into: 0, { $0 = max($0, $1.currentSize.height) })
|
|
|
|
return VideoRequest(
|
|
demuxId: demuxId,
|
|
width: UInt16(width),
|
|
height: UInt16(height),
|
|
framerate: height <= GroupCallVideoOverflow.itemHeight ? 15 : 30
|
|
)
|
|
}
|
|
|
|
groupCall.updateVideoRequests(resolutions: videoRequests)
|
|
})
|
|
}
|
|
}
|
|
|
|
extension GroupCallRemoteVideoManager: GroupCallRemoteVideoViewSizeDelegate {
|
|
func groupCallRemoteVideoViewDidChangeSize(remoteVideoView: GroupCallRemoteVideoView) {
|
|
AssertIsOnMainThread()
|
|
updateVideoRequests()
|
|
}
|
|
|
|
func groupCallRemoteVideoViewDidChangeSuperview(remoteVideoView: GroupCallRemoteVideoView) {
|
|
AssertIsOnMainThread()
|
|
guard let device = currentGroupCall?.remoteDeviceStates[remoteVideoView.demuxId] else { return }
|
|
remoteVideoView.configure(for: device)
|
|
updateVideoRequests()
|
|
}
|
|
}
|
|
|
|
extension GroupCallRemoteVideoManager: CallServiceObserver {
|
|
func didUpdateCall(from oldValue: SignalCall?, to newValue: SignalCall?) {
|
|
guard oldValue != newValue else { return }
|
|
videoViews.forEach { self.destroyRemoteVideoView(for: $0.key) }
|
|
oldValue?.removeObserver(self)
|
|
newValue?.addObserverAndSyncState(observer: self)
|
|
}
|
|
}
|
|
|
|
extension GroupCallRemoteVideoManager: CallObserver {
|
|
func groupCallRemoteDeviceStatesChanged(_ call: SignalCall) {
|
|
for (demuxId, videoViews) in videoViews {
|
|
guard let device = call.groupCall.remoteDeviceStates[demuxId] else {
|
|
destroyRemoteVideoView(for: demuxId)
|
|
continue
|
|
}
|
|
videoViews.values.forEach { $0.configure(for: device) }
|
|
}
|
|
}
|
|
|
|
func groupCallEnded(_ call: SignalCall, reason: GroupCallEndReason) {
|
|
videoViews.keys.forEach { destroyRemoteVideoView(for: $0) }
|
|
}
|
|
}
|
|
|
|
private protocol GroupCallRemoteVideoViewSizeDelegate: AnyObject {
|
|
func groupCallRemoteVideoViewDidChangeSize(remoteVideoView: GroupCallRemoteVideoView)
|
|
func groupCallRemoteVideoViewDidChangeSuperview(remoteVideoView: GroupCallRemoteVideoView)
|
|
}
|
|
|
|
class GroupCallRemoteVideoView: UIView {
|
|
fileprivate weak var sizeDelegate: GroupCallRemoteVideoViewSizeDelegate?
|
|
|
|
fileprivate private(set) var currentSize: CGSize = .zero {
|
|
didSet {
|
|
guard oldValue != currentSize else { return }
|
|
remoteVideoView.frame = CGRect(origin: .zero, size: currentSize)
|
|
sizeDelegate?.groupCallRemoteVideoViewDidChangeSize(remoteVideoView: self)
|
|
}
|
|
}
|
|
|
|
// We cannot subclass this, for some unknown reason WebRTC
|
|
// will not render frames properly if we try to.
|
|
private let remoteVideoView = RemoteVideoView()
|
|
|
|
private weak var videoTrack: RTCVideoTrack? {
|
|
didSet {
|
|
guard oldValue != videoTrack else { return }
|
|
oldValue?.remove(remoteVideoView)
|
|
videoTrack?.add(remoteVideoView)
|
|
}
|
|
}
|
|
|
|
override var frame: CGRect {
|
|
didSet { currentSize = frame.size }
|
|
}
|
|
|
|
override var bounds: CGRect {
|
|
didSet { currentSize = bounds.size }
|
|
}
|
|
|
|
override func didMoveToSuperview() {
|
|
sizeDelegate?.groupCallRemoteVideoViewDidChangeSuperview(remoteVideoView: self)
|
|
}
|
|
|
|
var isGroupCall: Bool {
|
|
get { remoteVideoView.isGroupCall }
|
|
set { remoteVideoView.isGroupCall = newValue }
|
|
}
|
|
|
|
var isFullScreen: Bool {
|
|
get { remoteVideoView.isFullScreen }
|
|
set { remoteVideoView.isFullScreen = newValue }
|
|
}
|
|
|
|
var isScreenShare: Bool {
|
|
get { remoteVideoView.isScreenShare }
|
|
set { remoteVideoView.isScreenShare = newValue }
|
|
}
|
|
|
|
var isRenderingVideo: Bool { videoTrack != nil }
|
|
|
|
fileprivate let demuxId: UInt32
|
|
fileprivate init(demuxId: UInt32) {
|
|
self.demuxId = demuxId
|
|
super.init(frame: .zero)
|
|
addSubview(remoteVideoView)
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("init(coder:) has not been implemented")
|
|
}
|
|
|
|
deinit { videoTrack = nil }
|
|
|
|
func configure(for device: RemoteDeviceState) {
|
|
guard device.demuxId == demuxId else {
|
|
return owsFailDebug("Tried to configure with incorrect device")
|
|
}
|
|
|
|
videoTrack = superview == nil ? nil : device.videoTrack
|
|
}
|
|
}
|