From 07dcb0b8d63e68643c402fdcb7386d6720cfcecb Mon Sep 17 00:00:00 2001 From: Max Cobb Date: Thu, 6 Jul 2023 14:24:52 +0100 Subject: [PATCH] updated engine property to agoraEngine, lowered compatability of rtc engine, updated video streaming article, and inline docs --- Package.swift | 2 +- Sources/SwiftUIRtc/AgoraManager.swift | 140 +++++++++--------- Sources/SwiftUIRtc/AgoraVideoCanvasView.swift | 85 +++++------ .../SwiftUI-Video-Streaming.md | 32 ++-- 4 files changed, 123 insertions(+), 136 deletions(-) diff --git a/Package.swift b/Package.swift index c316aa0..a22f7d1 100644 --- a/Package.swift +++ b/Package.swift @@ -10,7 +10,7 @@ let package = Package( .library(name: "SwiftUIRtc", targets: ["SwiftUIRtc"]), ], dependencies: [ - .package(url: "https://github.com/AgoraIO/AgoraRtcEngine_iOS", from: "4.1.0"), + .package(url: "https://github.com/AgoraIO/AgoraRtcEngine_iOS", from: "4.0.0"), ], targets: [ .target( diff --git a/Sources/SwiftUIRtc/AgoraManager.swift b/Sources/SwiftUIRtc/AgoraManager.swift index 78527b3..c6c4a72 100644 --- a/Sources/SwiftUIRtc/AgoraManager.swift +++ b/Sources/SwiftUIRtc/AgoraManager.swift @@ -8,114 +8,112 @@ import Foundation import AgoraRtcKit -/** - * ``AgoraManager`` is a class that provides an interface to the Agora RTC Engine Kit. - * It conforms to the `ObservableObject` and `AgoraRtcEngineDelegate` protocols. - * - * Use AgoraManager to set up and manage Agora RTC sessions, manage the client's role, - * and control the * client's connection to the Agora RTC server. - * */ +/// ``AgoraManager`` is a class that provides an interface to the Agora RTC Engine Kit. +/// It conforms to the `ObservableObject` and `AgoraRtcEngineDelegate` protocols. +/// +/// Use AgoraManager to set up and manage Agora RTC sessions, manage the client's role, +/// and control the client's connection to the Agora RTC server. open class AgoraManager: NSObject, ObservableObject, AgoraRtcEngineDelegate { /// The Agora App ID for the session. public let appId: String /// The client's role in the session. public var role: AgoraClientRole = .audience { - didSet { engine.setClientRole(role) } + didSet { agoraEngine.setClientRole(role) } } /// Integer ID of the local user. - @Published public private(set) var localUserId: UInt = 0 + @Published public var localUserId: UInt = 0 /// The Agora RTC Engine Kit for the session. - public var engine: AgoraRtcEngineKit { + public var agoraEngine: AgoraRtcEngineKit { let eng = AgoraRtcEngineKit.sharedEngine(withAppId: appId, delegate: self) eng.enableVideo() eng.setClientRole(role) return eng } + @available(*, deprecated, renamed: "agoraEngine") + public var engine: AgoraRtcEngineKit { self.agoraEngine } /// The set of all users in the channel. @Published public var allUsers: Set = [] - /** - * Initializes a new instance of `AgoraManager` with the specified app ID and client role. - * - * - Parameters: - * - appId: The Agora App ID for the session. - * - role: The client's role in the session. The default value is `.audience`. - */ + /// Initializes a new instance of `AgoraManager` with the specified app ID and client role. + /// + /// - Parameters: + /// - appId: The Agora App ID for the session. + /// - role: The client's role in the session. The default value is `.audience`. public init(appId: String, role: AgoraClientRole = .audience) { self.appId = appId self.role = role } - /** - * Joins a channel, starting the connection to an RTC session. - * - Parameters: - * - channel: Name of the channel to join. - * - token: Token to join the channel, this can be nil for an weak security testing session. - * - uid: User ID of the local user. This can be 0 to allow the engine to automatically assign an ID. - * - info: Info is currently unused by RTC, it is reserved for future use. - */ - open func joinChannel(_ channel: String, token: String? = nil, uid: UInt = 0, info: String? = nil) { - self.engine.joinChannel(byToken: token, channelId: channel, info: info, uid: uid) + /// Joins a channel, starting the connection to an RTC session. + /// - Parameters: + /// - channel: Name of the channel to join. + /// - token: Token to join the channel, this can be nil for an weak security testing session. + /// - uid: User ID of the local user. This can be 0 to allow the engine to automatically assign an ID. + /// - info: Info is currently unused by RTC, it is reserved for future use. + /// - Returns: 0 if no error joining channel, < 0 if there was an error. + @discardableResult + open func joinChannel( + _ channel: String, token: String? = nil, uid: UInt = 0, info: String? = nil + ) -> Int32 { + self.agoraEngine.joinChannel(byToken: token, channelId: channel, info: info, uid: uid) } - /** - * Leaves the channel and stops the preview for the session. - * - * - Parameter leaveChannelBlock: An optional closure that will be called when the client leaves the channel. - * The closure takes an * `AgoraChannelStats` object as its parameter. - * - * - * This method also empties all entries in ``allUsers``, - */ - open func leaveChannel(leaveChannelBlock: ((AgoraChannelStats) -> Void)? = nil) { - self.engine.leaveChannel(leaveChannelBlock) - self.engine.stopPreview() - AgoraRtcEngineKit.destroy() + /// Leaves the channel and stops the preview for the session. + /// + /// - Parameter leaveChannelBlock: An optional closure that will be called when the client leaves the channel. + /// The closure takes an `AgoraChannelStats` object as its parameter. + /// + /// This method also empties all entries in ``allUsers``, + @discardableResult + open func leaveChannel( + leaveChannelBlock: ((AgoraChannelStats) -> Void)? = nil + ) -> Int32 { + let leaveErr = self.agoraEngine.leaveChannel(leaveChannelBlock) + self.agoraEngine.stopPreview() + defer { AgoraRtcEngineKit.destroy() } self.allUsers.removeAll() + return leaveErr } - /** - * The delegate is telling us that the local user has successfully joined the channel. - * - Parameters: - * - engine: The Agora RTC engine kit object. - * - channel: The channel name. - * - uid: The ID of the user joining the channel. - * - elapsed: The time elapsed (ms) from the user calling `joinChannel` until this method is called. - * - * If the client's role is `.broadcaster`, this method also adds the broadcaster's userId (``localUserId``) to the ``allUsers`` set. - */ - open func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinChannel channel: String, withUid uid: UInt, elapsed: Int) { + /// The delegate is telling us that the local user has successfully joined the channel. + /// - Parameters: + /// - engine: The Agora RTC engine kit object. + /// - channel: The channel name. + /// - uid: The ID of the user joining the channel. + /// - elapsed: The time elapsed (ms) from the user calling `joinChannel` until this method is called. + /// + /// If the client's role is `.broadcaster`, this method also adds the broadcaster's userId (``localUserId``) to the ``allUsers`` set. + open func rtcEngine( + _ engine: AgoraRtcEngineKit, didJoinChannel channel: String, + withUid uid: UInt, elapsed: Int + ) { self.localUserId = uid if self.role == .broadcaster { self.allUsers.insert(uid) } } - /** - * The delegate is telling us that a remote user has joined the channel. - * - * - Parameters: - * - engine: The Agora RTC engine kit object. - * - uid: The ID of the user joining the channel. - * - elapsed: The time elapsed (ms) from the user calling `joinChannel` until this method is called. - * - * This method adds the remote user to the `allUsers` set. - */ + /// The delegate is telling us that a remote user has joined the channel. + /// + /// - Parameters: + /// - engine: The Agora RTC engine kit object. + /// - uid: The ID of the user joining the channel. + /// - elapsed: The time elapsed (ms) from the user calling `joinChannel` until this method is called. + /// + /// This method adds the remote user to the `allUsers` set. open func rtcEngine(_ engine: AgoraRtcEngineKit, didJoinedOfUid uid: UInt, elapsed: Int) { self.allUsers.insert(uid) } - /** - * The delegate is telling us that a remote user has left the channel. - * - * - Parameters: - * - engine: The Agora RTC engine kit object. - * - uid: The ID of the user who left the channel. - * - reason: The reason why the user left the channel. - * - * This method removes the remote user from the `allUsers` set. - */ + /// The delegate is telling us that a remote user has left the channel. + /// + /// - Parameters: + /// - engine: The Agora RTC engine kit object. + /// - uid: The ID of the user who left the channel. + /// - reason: The reason why the user left the channel. + /// + /// This method removes the remote user from the `allUsers` set. open func rtcEngine(_ engine: AgoraRtcEngineKit, didOfflineOfUid uid: UInt, reason: AgoraUserOfflineReason) { self.allUsers.remove(uid) } diff --git a/Sources/SwiftUIRtc/AgoraVideoCanvasView.swift b/Sources/SwiftUIRtc/AgoraVideoCanvasView.swift index 20e7eab..2514aad 100644 --- a/Sources/SwiftUIRtc/AgoraVideoCanvasView.swift +++ b/Sources/SwiftUIRtc/AgoraVideoCanvasView.swift @@ -23,27 +23,24 @@ extension AgoraRtcVideoCanvas: ObservableObject {} /// while avoiding a strong dependency on ``AgoraManager``. public protocol CanvasViewHelper: AnyObject { /// Instance of the Agora RTC Engine - var engine: AgoraRtcEngineKit { get } + var agoraEngine: AgoraRtcEngineKit { get } /// Id of the local user in the channel. var localUserId: UInt { get } } +/// Add the ``CanvasViewHelper`` protocol to ``AgoraManager``. extension AgoraManager: CanvasViewHelper {} -/** - * AgoraVideoCanvasView is a UIViewRepresentable struct that provides a view for displaying remote or local video in an Agora RTC session. - * - * Use AgoraVideoCanvasView to create a view that displays the video stream from a remote user or the local user's camera in an Agora RTC session. - * You can specify the render mode, crop area, and setup mode for the view. - */ +/// AgoraVideoCanvasView is a UIViewRepresentable struct that provides a view for displaying remote or local video in an Agora RTC session. +/// +/// Use AgoraVideoCanvasView to create a view that displays the video stream from a remote user or the local user's camera in an Agora RTC session. +/// You can specify the render mode, crop area, and setup mode for the view. public struct AgoraVideoCanvasView: VIEW_REP { /// The `AgoraRtcVideoCanvas` object that represents the video canvas for the view. @StateObject var canvas = AgoraRtcVideoCanvas() - /** - * Reference to a protocol ``CanvasViewHelper`` that helps with fetching the engine instance, - * as well as the local user's ID. ``AgoraManager`` conforms to this protocol. - */ + /// Reference to a protocol ``CanvasViewHelper`` that helps with fetching the engine instance, as well as the local user's ID. + /// ``AgoraManager`` conforms to this protocol. public weak var manager: CanvasViewHelper? /// The user ID of the remote user whose video to display, or `0` to display the local user's video. public let uid: UInt @@ -57,18 +54,16 @@ public struct AgoraVideoCanvasView: VIEW_REP { /// The setup mode for the view. public var setupMode: AgoraVideoViewSetupMode = .replace - /** - * Create a new AgoraRtcVideoCanvas, for displaying a remote or local video stream in a SwiftUI view. - * - * - Parameters: - * - manager: An instance of an object that conforms to ``CanvasViewHelper``, such as ``AgoraManager``. - * - uid: The user ID for the video stream. - * - renderMode: The render mode for the video stream, which determines how the video is scaled and displayed. - * - cropArea: The portion of the video stream to display, specified as a CGRect with values between 0 and 1. - * - setupMode: The mode for setting up the video view, which determines whether to replace or merge with existing views. - * - * - Returns: An AgoraVideoCanvasView instance, which can be added to a SwiftUI view hierarchy. - */ + /// Create a new AgoraRtcVideoCanvas, for displaying a remote or local video stream in a SwiftUI view. + /// + /// - Parameters: + /// - manager: An instance of an object that conforms to ``CanvasViewHelper``, such as ``AgoraManager``. + /// - uid: The user ID for the video stream. + /// - renderMode: The render mode for the video stream, which determines how the video is scaled and displayed. + /// - cropArea: The portion of the video stream to display, specified as a CGRect with values between 0 and 1. + /// - setupMode: The mode for setting up the video view, which determines whether to replace or merge with existing views. + /// + /// - Returns: An AgoraVideoCanvasView instance, which can be added to a SwiftUI view hierarchy. public init( manager: CanvasViewHelper, uid: UInt, renderMode: AgoraVideoRenderMode = .hidden, @@ -86,24 +81,20 @@ public struct AgoraVideoCanvasView: VIEW_REP { self.uid = uid } #if os(macOS) - /** - * Creates and configures a `NSView` for the view. This UIView will be the view the video is rendered onto. - * - * - Parameter context: The `NSViewRepresentable` context. - * - * - Returns: A `NSView` for displaying the video stream. - */ + /// Creates and configures a `NSView` for the view. This UIView will be the view the video is rendered onto. + /// + /// - Parameter context: The `NSViewRepresentable` context. + /// + /// - Returns: A `NSView` for displaying the video stream. public func makeNSView(context: Context) -> VIEW_CLASS { setupCanvasView() } #elseif os(iOS) - /** - * Creates and configures a `UIView` for the view. This UIView will be the view the video is rendered onto. - * - * - Parameter context: The `UIViewRepresentable` context. - * - * - Returns: A `UIView` for displaying the video stream. - */ + /// Creates and configures a `UIView` for the view. This UIView will be the view the video is rendered onto. + /// + /// - Parameter context: The `UIViewRepresentable` context. + /// + /// - Returns: A `UIView` for displaying the video stream. public func makeUIView(context: Context) -> VIEW_CLASS { setupCanvasView() } @@ -119,17 +110,15 @@ public struct AgoraVideoCanvasView: VIEW_REP { canvasView.isHidden = false if self.uid == self.manager?.localUserId { // Start the local video preview - manager?.engine.startPreview() - manager?.engine.setupLocalVideo(canvas) + manager?.agoraEngine.startPreview() + manager?.agoraEngine.setupLocalVideo(canvas) } else { - manager?.engine.setupRemoteVideo(canvas) + manager?.agoraEngine.setupRemoteVideo(canvas) } return canvasView } - /** - * Updates the `AgoraRtcVideoCanvas` object for the view with new values, if necessary. - */ + /// Updates the `AgoraRtcVideoCanvas` object for the view with new values, if necessary. func updateCanvasValues() { if canvas.renderMode == renderMode, canvas.cropArea == cropArea, canvas.setupMode == setupMode { return @@ -139,13 +128,13 @@ public struct AgoraVideoCanvasView: VIEW_REP { if canvas.cropArea != cropArea { canvas.cropArea = cropArea } if canvas.setupMode != setupMode { canvas.setupMode = setupMode } - if self.uid == 0 { manager?.engine.setupLocalVideo(canvas) - } else { manager?.engine.setupRemoteVideo(canvas) } + if self.canvas.uid == self.uid { return } + self.canvas.uid = self.uid + if self.uid == self.manager?.localUserId { manager?.agoraEngine.setupLocalVideo(canvas) + } else { self.manager?.agoraEngine.setupRemoteVideo(canvas) } } - /** - * Updates the Canvas view. - */ + /// Updates the Canvas view. #if os(iOS) public func updateUIView(_ uiView: VIEW_CLASS, context: Context) { self.updateCanvasValues() diff --git a/Sources/SwiftUIRtc/SwiftUIRtc.docc/SwiftUI-Video-Streaming.md b/Sources/SwiftUIRtc/SwiftUIRtc.docc/SwiftUI-Video-Streaming.md index 0e3fd3a..75d84b4 100644 --- a/Sources/SwiftUIRtc/SwiftUIRtc.docc/SwiftUI-Video-Streaming.md +++ b/Sources/SwiftUIRtc/SwiftUIRtc.docc/SwiftUI-Video-Streaming.md @@ -2,43 +2,43 @@ ## Overview -SwiftUI has emerged as a game-changer in app development, providing a modern and intuitive way to build user interfaces for various Apple platforms. While SwiftUI offers tremendous capabilities, there has been a noticeable absence of robust solutions for live streaming applications. However, the SwiftUIRtc package fills this gap by offering a seamless integration of live streaming capabilities within SwiftUI. In this article, we will explore how SwiftUIRtc empowers developers to create compelling live streaming applications using the power of SwiftUI. +SwiftUI has emerged as a game-changer in app development, providing a modern and intuitive way to build user interfaces for various Apple platforms. While SwiftUI offers tremendous capabilities, there is a noticeable absence of robust solutions for live streaming applications. However, the SwiftUIRtc package fills this gap by offering a seamless integration of live streaming capabilities in SwiftUI. In this article, we explore how SwiftUIRtc empowers developers to create compelling live streaming applications using the power of SwiftUI. ## The Challenge of Live Streaming in SwiftUI -Live streaming poses unique challenges in terms of real-time video rendering, media synchronization, and interactive features. SwiftUI, although a powerful UI framework, lacks native support for live streaming components. Developers often face the daunting task of integrating third-party libraries and complex workarounds using UIViewRespresentable over and over again to achieve live streaming functionality within their SwiftUI apps. +Live streaming poses unique challenges in terms of real-time video rendering, media synchronization, and interactive features. SwiftUI, although a powerful UI framework, lacks native support for live streaming components. Developers often face the daunting task of integrating third-party libraries and complex workarounds using UIViewRespresentable over and over again to achieve live streaming functionality in their SwiftUI apps. ## A SwiftUI Live Streaming Solution -The package `SwiftUIRtc` is designed to address awkward workarounds needed for live streaming in SwiftUI. It provides a small set of tools and SwiftUI components that enable developers to effortlessly incorporate live streaming capabilities into their SwiftUI applications. By leveraging ``SwiftUIRtc``, developers can overcome the hurdles associated with live streaming integration and focus on delivering an exceptional streaming experience to their users. +The package ``SwiftUIRtc`` is designed to address awkward workarounds needed for live streaming in SwiftUI. It provides a small set of tools and SwiftUI components that enable developers to effortlessly incorporate live streaming capabilities into their SwiftUI applications. By leveraging ``SwiftUIRtc``, developers can overcome the hurdles associated with live streaming integration and focus on delivering an exceptional streaming experience to their users. -The package itself uses Agora's Video RTC engine under the hood, to make sure that the best network is used for this solution. +The package uses the Agora Video RTC engine under the hood to ensure that the best network is used for this solution. ## Key Classes of SwiftUIRtc -Although the package is relatively light by design, its limit classes and methods are everything developers need to start working with live streaming applications right away. +Although the SwiftUIRtc package is relatively light by design, its limited classes and methods are everything developers need to start working with live streaming applications right away. ### AgoraManager -``AgoraManager`` has several very important responsibilities, such as session management and connection handling. AgoraManager will keep track of the users who join and leave the channel, so that you as the developer know what IDs you need to render the video for. These IDs are tracked using delegate methods from Agora's RTC Engine, and stores them in a published variable, ``AgoraManager/allUsers``. +``AgoraManager`` has several very important responsibilities, including session management and connection handling. ``AgoraManager`` will keep track of the users who join and leave the channel so that you as the developer know what IDs you need to render the video for. These IDs are tracked using delegate methods from the Agora RTC engine which stores them in a published variable, ``AgoraManager/allUsers``. -Join and Leave Channel Methods: +Join and leave channel methods: - ``AgoraManager/joinChannel(_:token:uid:info:)`` - ``AgoraManager/leaveChannel(leaveChannelBlock:)`` -Implemented Delegate Methods: +Implemented delegate methods: - ``AgoraManager/rtcEngine(_:didJoinedOfUid:elapsed:)`` - ``AgoraManager/rtcEngine(_:didOfflineOfUid:reason:)`` ### AgoraVideoCanvasView -``AgoraVideoCanvasView`` is a UIViewRepresentable struct that provides a view for displaying remote or local video in an Agora RTC session. With access to the engine through ``AgoraManager``, it displays local and remote video streams when provided just a desired ID. +``AgoraVideoCanvasView`` is a UIViewRepresentable struct that provides a view for displaying remote or local video in an Agora RTC session. With access to the engine through ``AgoraManager``, ``AgoraVideoCanvasView`` displays local and remote video streams when provided with just a desired ID. ## Example -Using just the two classes described above, we can easily add an AgoraManager that keeps track of all the users in the scene, and an AgoraVideoCanvasView for each user listed in the allUsers property as such: +Using just the two classes described above, we can easily add an AgoraManager that keeps track of all the users in the scene and an AgoraVideoCanvasView for each user listed in the allUsers property as follows: ```swift import SwiftUIRtc @@ -64,7 +64,7 @@ struct ScrollingVideoCallView: View { } ``` -When displayed in a ScrollView like above, the different video feeds will just appear in a scrolling list view automatically, scaling to however many users join the channel. +When displayed in a ScrollView as above, the different video feeds will automatically appear in a scrolling list view, scaling to however many users join the channel. @Video(source: scrolling_videos_view.mov, poster: scrolling_videos_view.gif) @@ -72,9 +72,9 @@ As this library matures, more example usage will be linked from here. ## Extending SwiftUIRtc -All of the methods in AgoraManager and AgoraVideoCanvasView are marked as `open`, and the library is of course open source. That means that each of these classes can be extended with subclasses to add functionality tailored to the exact needs of any SwiftUI application. +All of the methods in AgoraManager and AgoraVideoCanvasView are marked as `open`, and the library is open source. That means that each of these classes can be extended with subclasses to add functionality tailored to the exact needs of any SwiftUI application. -For example, if you need to record the quality of incoming streams, you can subclass AgoraManager as such: +For example, if you need to record the quality of incoming streams, you can subclass AgoraManager as follows: ```swift public class CallQualityManager: AgoraManager { @@ -83,7 +83,7 @@ public class CallQualityManager: AgoraManager { } ``` -And add a few additional delegate methods: +And add a couple of additional delegate methods: ```swift extension CallQualityManager { @@ -107,7 +107,7 @@ extension CallQualityManager { } ``` -And if you want to display those call qualities, a simple change to ScrollingVideoCallView (above), adds overlay to each ``AgoraVideoCanvasView``: +To display those call quality data, a simple change to ScrollingVideoCallView (above) adds an overlay to each ``AgoraVideoCanvasView``: ```swift AgoraVideoCanvasView(manager: agoraManager, uid: uid) @@ -121,7 +121,7 @@ AgoraVideoCanvasView(manager: agoraManager, uid: uid) ## Conclusion -SwiftUIRtc brings simplicity and ease to the process of integrating live streaming capabilities to SwiftUI Applications. By leveraging AgoraManager and AgoraVideoCanvasView, you can add video streams through Agora's RTC SDK, without having to deal with non-SwiftUI classes. +SwiftUIRtc brings simplicity and ease of use to the process of integrating live streaming capabilities into SwiftUI applications. By leveraging AgoraManager and AgoraVideoCanvasView, you can add video streams through the Agora RTC SDK, without having to deal with non-SwiftUI classes. Use SwiftUIRtc in your iOS applications today by adding the package with: