Skip to content

Commit

Permalink
Merge pull request #88 from AgoraIO-Community/precall-view
Browse files Browse the repository at this point in the history
Release Version: 4.1.0

## Release Notes

- You can now enable the camera + mic before joining a call
- Fix end-call leaving the video enabled
  • Loading branch information
maxxfrazer authored Mar 2, 2023
2 parents cfb3e90 + 8788a4c commit 4f089eb
Show file tree
Hide file tree
Showing 12 changed files with 112 additions and 39 deletions.
2 changes: 1 addition & 1 deletion Sources/Agora-Video-UIKit/AgoraCameraSourcePush.swift
Original file line number Diff line number Diff line change
Expand Up @@ -106,7 +106,7 @@ public protocol AgoraCameraSourcePushDelegate: AnyObject {

open class AgoraCameraSourcePush: NSObject {
fileprivate var delegate: AgoraCameraSourcePushDelegate?
private var localVideoPreview: CustomVideoSourcePreview?
internal var localVideoPreview: CustomVideoSourcePreview?

/// Active capture session
public let captureSession: AVCaptureSession
Expand Down
4 changes: 2 additions & 2 deletions Sources/Agora-Video-UIKit/AgoraCollectionViewer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -227,7 +227,7 @@ extension AgoraVideoViewer: MPCollectionViewDelegate, MPCollectionViewDataSource
if self.agoraSettings.showSelf {
self.collectionViewVideos = Array(self.userVideoLookup.values)
} else {
self.collectionViewVideos = Array(self.userVideoLookup.filter { $0.key != self.userID}.values)
self.collectionViewVideos = Array(self.userVideoLookup.filter { $0.key != 0 }.values)
}
default:
self.collectionViewVideos.removeAll()
Expand All @@ -251,7 +251,7 @@ extension AgoraVideoViewer: MPCollectionViewDelegate, MPCollectionViewDataSource
var myActiveSpeaker: UInt?
switch self.style {
case .pinned:
myActiveSpeaker = self.overrideActiveSpeaker ?? self.activeSpeaker ?? self.userID
myActiveSpeaker = self.overrideActiveSpeaker ?? self.activeSpeaker ?? 0
default:
break
}
Expand Down
2 changes: 1 addition & 1 deletion Sources/Agora-Video-UIKit/AgoraConnectionData.swift
Original file line number Diff line number Diff line change
Expand Up @@ -22,7 +22,7 @@ public struct AgoraConnectionData {
/// Token to be used to connect to a RTM channel, can be nil.
public var rtmToken: String?
/// Channel the object is connected to. This cannot be set with the initialiser.
public var channel: String?
public internal(set) var channel: String?
/// Agora Real-time Communication Identifier (Agora Video/Audio SDK).
public var rtcId: UInt
/// Agora Real-time Messaging Identifier (Agora RTM SDK).
Expand Down
2 changes: 2 additions & 0 deletions Sources/Agora-Video-UIKit/AgoraSettings.swift
Original file line number Diff line number Diff line change
Expand Up @@ -182,6 +182,8 @@ public struct AgoraSettings {
/// and camera to not be activated at all.
public var cameraEnabled: Bool = true

public internal(set) var previewEnabled: Bool = false

/// Show the icon for remote user video feeds to request mute/unmute of devices
public var showRemoteRequestOptions: Bool = true

Expand Down
2 changes: 1 addition & 1 deletion Sources/Agora-Video-UIKit/AgoraUIKit.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,7 +25,7 @@ public struct AgoraUIKit: Codable {
/// Framework type of UIKit. "native", "flutter", "reactnative"
public fileprivate(set) var framework: String
/// Version of UIKit being used
public static let version = "4.0.7"
public static let version = "4.1.0"
/// Framework type of UIKit. "native", "flutter", "reactnative"
public static let framework = "native"
#if os(iOS)
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,8 +22,8 @@ extension AgoraVideoViewer: AgoraRtcEngineDelegate {
) {
let isHost = newRole == .broadcaster
if !isHost {
self.userVideoLookup.removeValue(forKey: self.userID)
} else if self.userVideoLookup[self.userID] == nil {
self.userVideoLookup.removeValue(forKey: 0)
} else if self.userVideoLookup[0] == nil {
self.addLocalVideo()
}

Expand Down Expand Up @@ -73,7 +73,7 @@ extension AgoraVideoViewer: AgoraRtcEngineDelegate {
videoView.audioMuted = state == .stopped
} else if state != .stopped {
self.addUserVideo(with: uid).audioMuted = false
if self.activeSpeaker == nil && uid != self.userID {
if self.activeSpeaker == nil && uid != 0 {
self.activeSpeaker = uid
}
}
Expand Down Expand Up @@ -156,7 +156,7 @@ extension AgoraVideoViewer: AgoraRtcEngineDelegate {
switch state {
case .decoding:
self.addUserVideo(with: uid).videoMuted = false
if self.activeSpeaker == nil && uid != self.userID {
if self.activeSpeaker == nil && uid != 0 {
self.activeSpeaker = uid
}
case .stopped:
Expand Down Expand Up @@ -207,8 +207,14 @@ extension AgoraVideoViewer: AgoraRtcEngineDelegate {
error: AgoraLocalVideoStreamError, sourceType: AgoraVideoSourceType
) {
switch state {
case .capturing, .stopped:
self.userVideoLookup[self.userID]?.videoMuted = state == .stopped
case .capturing:
if !self.agoraSettings.previewEnabled {
self.addLocalVideo()?.videoMuted = false
}
case .stopped:
if !self.agoraSettings.previewEnabled {
self.videoLookup[0]?.videoMuted = true
}
default:
break
}
Expand All @@ -234,8 +240,14 @@ extension AgoraVideoViewer: AgoraRtcEngineDelegate {
error: AgoraAudioLocalError
) {
switch state {
case .recording, .stopped:
self.userVideoLookup[self.userID]?.audioMuted = state == .stopped
case .recording:
if !self.agoraSettings.previewEnabled {
self.addLocalVideo()?.audioMuted = false
}
case .stopped:
if !self.agoraSettings.previewEnabled {
self.videoLookup[0]?.audioMuted = true
}
default:
break
}
Expand Down
4 changes: 2 additions & 2 deletions Sources/Agora-Video-UIKit/AgoraVideoViewer+Buttons.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,10 +30,10 @@ extension AgoraVideoViewer {
containerSize = CGSize(width: containerSize.height, height: containerSize.width)
frameOriginY = (self.bounds.height - CGFloat(contWidth)) / 2
if self.agoraSettings.buttonPosition == .left {
frameOriginX = 30
frameOriginX = 20
resizeMask = [.flexibleTopMargin, .flexibleRightMargin, .flexibleBottomMargin]
} else {
frameOriginX = self.bounds.width - self.agoraSettings.buttonSize - 20 - 10
frameOriginX = self.bounds.width - containerSize.width - 20
resizeMask = [.flexibleTopMargin, .flexibleLeftMargin, .flexibleBottomMargin]
}
case .bottom: break
Expand Down
30 changes: 18 additions & 12 deletions Sources/Agora-Video-UIKit/AgoraVideoViewer+JoinChannel.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,12 +82,13 @@ extension AgoraVideoViewer {
/// - Returns: An integer representing Agora's joinChannelByToken response. If response is `nil`,
/// that means it has continued on another thread due to requesting camera/mic permissions,
/// or you area already in the channel. If the response is 0, everything is fine.
@discardableResult
public func join(
@discardableResult public func join(
channel: String, with token: String?,
as role: AgoraClientRole = .broadcaster, uid: UInt? = nil,
mediaOptions: AgoraRtcChannelMediaOptions? = nil
) -> Int32? {
// Once we join the channel, preview is not relevant.
self.agoraSettings.previewEnabled = false
if self.connectionData == nil { fatalError("No app ID is provided") }
if role == .broadcaster {
if !self.checkForPermissions(self.activePermissions, callback: { error in
Expand Down Expand Up @@ -160,27 +161,32 @@ extension AgoraVideoViewer {

/// Leave channel stops all preview elements
/// - Parameters:
/// - stopPreview: Stops the local preview and the video
/// - leaveChannelBlock: This callback indicates that a user leaves a channel, and provides the statistics of the call.
/// - stopPreview: Stops the local preview and the video
/// - leaveChannelBlock: This callback indicates that a user leaves a channel, and provides the statistics of the call.
/// - Returns: Same return as AgoraRtcEngineKit.leaveChannel, 0 means no problem, less than 0 means there was an issue leaving
@discardableResult
@objc open func leaveChannel(
@discardableResult @objc open func leaveChannel(
stopPreview: Bool = true, _ leaveChannelBlock: ((AgoraChannelStats) -> Void)? = nil
) -> Int32 {
self.agoraSettings.previewEnabled = !stopPreview
guard let chName = self.connectionData.channel else {
AgoraVideoViewer.agoraPrint(.error, message: "Not in a channel, could not leave")
// Returning 0 to just say we are not in a channel
return 0
}
self.connectionData.channel = nil
self.agkit.setupLocalVideo(nil)
self.customCamera?.stopCapture()
if stopPreview, self.userRole == .broadcaster { agkit.stopPreview() }
if stopPreview, self.userRole == .broadcaster {
agkit.stopPreview()
self.agkit.setupLocalVideo(nil)
self.customCamera?.stopCapture()
}
self.userVideoLookup = self.userVideoLookup.filter {
if !stopPreview, $0.key == 0 { return true }
$0.value.removeFromSuperview()
return false
}
self.activeSpeaker = nil
self.remoteUserIDs = []
self.userVideoLookup = [:]
self.backgroundVideoHolder.subviews.forEach { $0.removeFromSuperview() }
self.controlContainer?.isHidden = true
self.controlContainer?.isHidden = stopPreview
let leaveChannelRtn = self.agkit.leaveChannel(leaveChannelBlock)
defer { if leaveChannelRtn == 0 { delegate?.leftChannel(chName) } }
return leaveChannelRtn
Expand Down
52 changes: 46 additions & 6 deletions Sources/Agora-Video-UIKit/AgoraVideoViewer+LocalVideo.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,11 +17,11 @@ extension AgoraVideoViewer: AgoraCameraSourcePushDelegate {
/// Adds the local video feed to the user video collections.
/// - Returns: The newly created (or already created) local video feed container.
internal func addLocalVideo() -> AgoraSingleVideoView? {
if self.userID == 0 || self.userVideoLookup[self.userID] != nil {
return self.userVideoLookup[self.userID]
if self.userVideoLookup[0] != nil {
return self.userVideoLookup[0]
}
let vidView = AgoraSingleVideoView(
uid: self.userID, micColor: self.agoraSettings.colors.micFlag
uid: 0, micColor: self.agoraSettings.colors.micFlag
)
vidView.canvas.renderMode = self.agoraSettings.videoRenderMode
self.agkit.setupLocalVideo(vidView.canvas)
Expand All @@ -31,13 +31,53 @@ extension AgoraVideoViewer: AgoraCameraSourcePushDelegate {
vidView.customCameraView = CustomVideoSourcePreview(frame: .zero)
vidView.customCameraView?.isHidden = true
self.customCamera = AgoraCameraSourcePush(delegate: self, localVideoPreview: vidView.customCameraView)

customCamera?.startCapture(ofDevice: device)
}
self.userVideoLookup[self.userID] = vidView
self.userVideoLookup[0] = vidView
return vidView
}

internal func removeLocalVideo() {
guard let localVideo = self.userVideoLookup[0] else {
return
}
self.agkit.setupLocalVideo(nil)
if !self.agoraSettings.externalVideoSettings.enabled {
self.agkit.stopPreview()
} else if self.agoraSettings.externalVideoSettings.captureDevice != nil {
localVideo.customCameraView?.removeFromSuperview()
self.customCamera?.stopCapture()
self.customCamera?.localVideoPreview = nil
}
localVideo.removeFromSuperview()
self.userVideoLookup.removeValue(forKey: 0)
}

/// Initialises the pre-call view. This shows the local Video and lets the user adjust their scene before joining a call.
/// Do not call this method if you're already in a channel.
public func startPrecallVideo() {
guard !self.agoraSettings.previewEnabled, self.connectionData.channel == nil else {
return
}
self.agoraSettings.previewEnabled = true
if self.userRole == .audience {
self.setRole(to: .broadcaster)
}
self.addLocalVideo()?.videoMuted = !agoraSettings.cameraEnabled
self.addLocalVideo()?.audioMuted = !agoraSettings.micEnabled
self.rtcEngine(rtcEngine, didClientRoleChanged: .audience, newRole: .broadcaster, newRoleOptions: .none)
}

/// Stops the precall view if we are not in a channel and preview is enabled
public func stopPrecallVideo() {
guard self.agoraSettings.previewEnabled, self.connectionData.channel == nil else {
return
}
self.removeLocalVideo()
self.controlContainer?.isHidden = true
self.agoraSettings.previewEnabled = false
}

/// Set or change the current capture device.
/// - Parameter captureDevice: Desired AVCaptureDevice to be set up.
/// - Returns: Returns true if successful, else false.
Expand Down Expand Up @@ -72,7 +112,7 @@ extension AgoraVideoViewer: AgoraCameraSourcePushDelegate {
// once we have the video frame, we can push to agora sdk
self.agkit.pushExternalVideoFrame(videoFrame)

if let localUser = userVideoLookup[self.userID], localUser.videoMuted {
if let localUser = userVideoLookup[0], localUser.videoMuted {
self.rtcEngine(
self.agkit, localVideoStateChangedOf: AgoraVideoLocalState.capturing,
error: .OK, sourceType: AgoraVideoSourceType.camera
Expand Down
11 changes: 8 additions & 3 deletions Sources/Agora-Video-UIKit/AgoraVideoViewer+Ordering.swift
Original file line number Diff line number Diff line change
Expand Up @@ -35,7 +35,7 @@ extension AgoraVideoViewer {

/// Randomly select an activeSpeaker that is not the local user
@objc open func setRandomSpeaker() {
if let randomNotMe = self.userVideoLookup.keys.shuffled().filter({ $0 != self.userID }).randomElement() {
if let randomNotMe = self.userVideoLookup.keys.shuffled().filter({ $0 != 0 }).randomElement() {
// active speaker has left, reassign activeSpeaker to a random member
self.activeSpeaker = randomNotMe
} else {
Expand Down Expand Up @@ -81,6 +81,11 @@ extension AgoraVideoViewer {
}
}

open override func layoutSubviews() {
super.layoutSubviews()
self.reorganiseVideos()
}

/// Display grid when there are only two video members
fileprivate func gridForTwo() {
// when there are 2 videos we display them ontop of eachother
Expand All @@ -102,7 +107,7 @@ extension AgoraVideoViewer {
.width, .height, .maxYMargin, .minYMargin, .maxXMargin, .minXMargin
]
#endif
if self.agoraSettings.usingDualStream && self.userID != keyVals.key {
if self.agoraSettings.usingDualStream && keyVals.key != 0 {
self.agkit.setRemoteVideoStream(
keyVals.key,
type: self.agoraSettings.gridThresholdHighBitrate > 2 ? .high : .low
Expand Down Expand Up @@ -143,7 +148,7 @@ extension AgoraVideoViewer {
#elseif os(macOS)
videoSessionView.autoresizingMask = [.width, .height, .maxYMargin, .minYMargin, .maxXMargin, .minXMargin]
#endif
if self.agoraSettings.usingDualStream && videoID != self.userID {
if self.agoraSettings.usingDualStream && videoID != 0 {
self.agkit.setRemoteVideoStream(
videoID,
type: vidCounts <= self.agoraSettings.gridThresholdHighBitrate ? .high : .low
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -54,6 +54,9 @@ extension AgoraVideoViewer {
).cgColor
#endif
}
if self.agoraSettings.previewEnabled {
self.addLocalVideo()?.videoMuted = !self.agoraSettings.cameraEnabled
}
}

/// Manually set the camera to be enabled or disabled.
Expand Down Expand Up @@ -94,6 +97,8 @@ extension AgoraVideoViewer {
error: .OK, sourceType: AgoraVideoSourceType.camera
)
}
} else {
_ = enabled ? self.agkit.startPreview() : self.agkit.stopPreview()
}

updateCamButton()
Expand Down Expand Up @@ -127,7 +132,7 @@ extension AgoraVideoViewer {
return
}
self.agoraSettings.micEnabled = enabled
self.userVideoLookup[self.userID]?.audioMuted = !self.agoraSettings.micEnabled
self.userVideoLookup[0]?.audioMuted = !self.agoraSettings.micEnabled
self.agkit.muteLocalAudioStream(!self.agoraSettings.micEnabled)
if self.agoraSettings.micEnabled {
// This is only enabled. If you want to disable it then do so manually.
Expand Down
7 changes: 5 additions & 2 deletions Sources/Agora-Video-UIKit/AgoraVideoViewer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -112,6 +112,9 @@ open class AgoraVideoViewer: MPView, SingleVideoViewDelegate {
/// as well as agora video configuration.
public internal(set) var agoraSettings: AgoraSettings

internal var previewEnabled: Bool {
self.agoraSettings.previewEnabled
}
#if canImport(AgoraRtmControl)
/// Controller class for managing RTM messages
public var rtmController: AgoraRtmController?
Expand Down Expand Up @@ -366,10 +369,10 @@ open class AgoraVideoViewer: MPView, SingleVideoViewDelegate {
return [:]
}
return self.userVideoLookup.filter {
$0.key == (self.overrideActiveSpeaker ?? self.activeSpeaker ?? self.userID)
$0.key == (self.overrideActiveSpeaker ?? self.activeSpeaker ?? 0)
}
} else if self.style == .grid {
return self.userVideoLookup.filter { ($0.key != self.userID || self.agoraSettings.showSelf) }
return self.userVideoLookup.filter { ($0.key != 0 || self.agoraSettings.showSelf) }
} else { return [:] }
}

Expand Down

0 comments on commit 4f089eb

Please sign in to comment.