AzureCommunicationUI/sdk/AzureCommunicationUICalling/Sources/Presentation/VideoViewManager.swift (155 lines of code) (raw):

// // Copyright (c) Microsoft Corporation. All rights reserved. // Licensed under the MIT License. // import AzureCommunicationCalling import Foundation struct RemoteParticipantVideoViewId { let userIdentifier: String let videoStreamIdentifier: String } struct ParticipantRendererViewInfo { let rendererView: UIView let streamSize: CGSize } protocol RendererViewManager: AnyObject { var didRenderFirstFrame: ((CGSize) -> Void)? { get set } func getRemoteParticipantVideoRendererView (_ videoViewId: RemoteParticipantVideoViewId) -> ParticipantRendererViewInfo? func getRemoteParticipantVideoRendererViewSize() -> CGSize? func updateDisplayedRemoteVideoStream(_ videoViewIdArray: [RemoteParticipantVideoViewId]) } class VideoViewManager: NSObject, RendererDelegate, RendererViewManager { struct VideoStreamCache { var renderer: VideoStreamRenderer var rendererView: RendererView var mediaStreamType: CompositeMediaStreamType } private let logger: Logger private var displayedRemoteParticipantsRendererView = MappedSequence<String, VideoStreamCache>() private var localRendererViews = MappedSequence<String, VideoStreamCache>() private let callingSDKWrapper: CallingSDKWrapperProtocol init(callingSDKWrapper: CallingSDKWrapperProtocol, logger: Logger) { self.callingSDKWrapper = callingSDKWrapper self.logger = logger } func updateDisplayedRemoteVideoStream(_ videoViewIdArray: [RemoteParticipantVideoViewId]) { let displayedKeys = videoViewIdArray.map { return generateCacheKey(userIdentifier: $0.userIdentifier, videoStreamId: $0.videoStreamIdentifier) } displayedRemoteParticipantsRendererView.makeKeyIterator().forEach { [weak self] key in if !displayedKeys.contains(key) { self?.disposeRemoteParticipantVideoRendererView(key) } } } func updateDisplayedLocalVideoStream(_ identifier: String?) { localRendererViews.makeKeyIterator().forEach { [weak self] key in if identifier != key { self?.disposeLocalVideoRendererCache(key) } } } func getLocalVideoRendererView(_ videoStreamId: String) -> UIView? { if let localRenderCache = localRendererViews.value(forKey: videoStreamId) { return localRenderCache.rendererView } guard let videoStream: CompositeLocalVideoStream<AzureCommunicationCalling.LocalVideoStream> = callingSDKWrapper.getLocalVideoStream(videoStreamId) else { return nil } let wrappedVideoStream = videoStream.wrappedObject do { let newRenderer: VideoStreamRenderer = try VideoStreamRenderer(localVideoStream: wrappedVideoStream) let newRendererView: RendererView = try newRenderer.createView( withOptions: CreateViewOptions(scalingMode: .crop)) let cache = VideoStreamCache( renderer: newRenderer, rendererView: newRendererView, mediaStreamType: videoStream.mediaStreamType ) localRendererViews.append(forKey: videoStreamId, value: cache) return newRendererView } catch let error { logger.error("Failed to render remote video, reason:\(error.localizedDescription)") return nil } } // MARK: ParticipantRendererViewManager var didRenderFirstFrame: ((CGSize) -> Void)? func getRemoteParticipantVideoRendererView(_ videoViewId: RemoteParticipantVideoViewId) -> ParticipantRendererViewInfo? { let videoStreamId = videoViewId.videoStreamIdentifier let userIdentifier = videoViewId.userIdentifier let cacheKey = generateCacheKey(userIdentifier: videoViewId.userIdentifier, videoStreamId: videoStreamId) if let videoStreamCache = displayedRemoteParticipantsRendererView.value(forKey: cacheKey) { let streamSize = CGSize(width: Int(videoStreamCache.renderer.size.width), height: Int(videoStreamCache.renderer.size.height)) return ParticipantRendererViewInfo(rendererView: videoStreamCache.rendererView, streamSize: streamSize) } guard let participant: CompositeRemoteParticipant< AzureCommunicationCalling.RemoteParticipant, AzureCommunicationCalling.RemoteVideoStream> = callingSDKWrapper.getRemoteParticipant(userIdentifier), let videoStream = participant.videoStreams.first(where: { stream in return String(stream.id) == videoStreamId }) else { return nil } let wrappedVideoStream = videoStream.wrappedObject do { let options = CreateViewOptions(scalingMode: videoStream.mediaStreamType == .screenSharing ? .fit : .crop) let newRenderer: VideoStreamRenderer = try VideoStreamRenderer(remoteVideoStream: wrappedVideoStream) let newRendererView: RendererView = try newRenderer.createView(withOptions: options) let cache = VideoStreamCache(renderer: newRenderer, rendererView: newRendererView, mediaStreamType: videoStream.mediaStreamType) displayedRemoteParticipantsRendererView.append(forKey: cacheKey, value: cache) if videoStream.mediaStreamType == .screenSharing { newRenderer.delegate = self } return ParticipantRendererViewInfo(rendererView: newRendererView, streamSize: .zero) } catch let error { logger.error("Failed to render remote video, reason:\(error.localizedDescription)") return nil } } func getRemoteParticipantVideoRendererViewSize() -> CGSize? { if let screenShare = displayedRemoteParticipantsRendererView.first(where: { cache in cache.mediaStreamType == .screenSharing }) { return CGSize(width: Int(screenShare.renderer.size.width), height: Int(screenShare.renderer.size.height)) } return nil } // MARK: Helper functions func disposeViews() { displayedRemoteParticipantsRendererView.makeKeyIterator().forEach { key in self.disposeRemoteParticipantVideoRendererView(key) } localRendererViews.makeKeyIterator().forEach { key in self.disposeLocalVideoRendererCache(key) } } private func disposeRemoteParticipantVideoRendererView(_ cacheId: String) { if let renderer = displayedRemoteParticipantsRendererView.removeValue(forKey: cacheId) { renderer.renderer.dispose() renderer.renderer.delegate = nil } } private func disposeLocalVideoRendererCache(_ identifier: String) { if let renderer = localRendererViews.removeValue(forKey: identifier) { if renderer.rendererView.isRendering() { renderer.renderer.dispose() } } } private func generateCacheKey(userIdentifier: String, videoStreamId: String) -> String { return ("\(userIdentifier):\(videoStreamId)") } // MARK: RendererDelegate func videoStreamRenderer(didRenderFirstFrame renderer: VideoStreamRenderer) { let size = CGSize(width: Int(renderer.size.width), height: Int(renderer.size.height)) didRenderFirstFrame?(size) } func videoStreamRenderer(didFailToStart renderer: VideoStreamRenderer) { logger.error("Failed to render remote screenshare video. \(renderer)") } }