@@ -14,10 +14,27 @@ class UserModel {
1414 var uid : UInt = 0
1515 var canvasView : SampleBufferDisplayView ?
1616 var trackId : UInt32 = 0
17- var isJoin : Bool = false
18- var customSource : AgoraYUVImageSourcePush ?
17+ var isJoin : Bool = false {
18+ didSet {
19+ self . canvasView? . videoView. isHidden = !isJoin
20+ self . canvasView? . videoView. reset ( )
21+ }
22+ }
1923 var isEncode : Bool = false
24+ var customSource : AgoraYUVImageSourcePush ?
2025 var customEncodeSource : KFMP4Demuxer ?
26+
27+ func reset( ) {
28+ self . customSource? . stopSource ( )
29+ self . customSource = nil
30+ self . customEncodeSource? . cancelReading ( )
31+ self . customEncodeSource = nil
32+
33+ self . uid = UInt ( Int . random ( in: 10000 ... 99999 ) )
34+ self . trackId = 0
35+ self . isJoin = false
36+ self . isEncode = false
37+ }
2138}
2239
2340class CustomVideoSourcePushMultiEntry : UIViewController {
@@ -46,16 +63,14 @@ class CustomVideoSourcePushMultiEntry: UIViewController {
4663}
4764
4865class CustomVideoSourcePushMultiMain : BaseViewController {
49- var localVideo = Bundle . loadView ( fromNib: " VideoViewSampleBufferDisplayView " ,
50- withType: SampleBufferDisplayView . self)
51- lazy var remoteVideos : [ UserModel ] = ( 0 ..< 3 ) . map ( { _ in
66+ private let localUid : UInt = UInt ( Int . random ( in: 10000 ... 99999 ) )
67+ lazy var remoteVideos : [ UserModel ] = ( 0 ..< 4 ) . map ( { _ in
5268 let model = UserModel ( )
53- model. uid = UInt ( Int . random ( in: 10000 ... 99999 ) )
5469 model. canvasView = Bundle . loadView ( fromNib: " VideoViewSampleBufferDisplayView " ,
5570 withType: SampleBufferDisplayView . self)
71+ model. reset ( )
5672 return model
5773 } )
58- var customCamera : AgoraYUVImageSourcePush ?
5974
6075 @IBOutlet weak var container : AGEVideoContainer !
6176 var agoraKit : AgoraRtcEngineKit !
@@ -77,26 +92,14 @@ class CustomVideoSourcePushMultiMain: BaseViewController {
7792 Util . configPrivatization ( agoraKit: agoraKit)
7893 agoraKit. setLogFile ( LogUtils . sdkLogPath ( ) )
7994
80- container. layoutStream2x2 ( views: [ localVideo ] + remoteVideos. compactMap ( { $0. canvasView } ) )
95+ container. layoutStream2x2 ( views: remoteVideos. compactMap ( { $0. canvasView } ) )
8196 // make myself a broadcaster
8297 // agoraKit.setClientRole(.broadcaster)
8398
8499 // enable video module and set up video encoding configs
85100 agoraKit. enableVideo ( )
86101 agoraKit. enableAudio ( )
87102
88- // setup my own camera as custom video source
89- // note setupLocalVideo is not working when using pushExternalVideoFrame
90- // so you will have to prepare the preview yourself
91- customCamera = AgoraYUVImageSourcePush ( size: CGSize ( width: 320 , height: 180 ) ,
92- fileName: " sample " ,
93- frameRate: 15 ,
94- isHDR: false )
95- customCamera? . trackId = agoraKit. createCustomVideoTrack ( )
96- customCamera? . delegate = self
97- customCamera? . startSource ( )
98- agoraKit. setExternalVideoSource ( true , useTexture: true , sourceType: . videoFrame)
99-
100103 let resolution = ( GlobalSettings . shared. getSetting ( key: " resolution " ) ? . selectedOption ( ) . value as? CGSize ) ?? . zero
101104 let fps = ( GlobalSettings . shared. getSetting ( key: " fps " ) ? . selectedOption ( ) . value as? AgoraVideoFrameRate ) ?? . fps15
102105 let orientation = ( GlobalSettings . shared. getSetting ( key: " orientation " ) ?
@@ -116,29 +119,39 @@ class CustomVideoSourcePushMultiMain: BaseViewController {
116119 // 2. If app certificate is turned on at dashboard, token is needed
117120 // when joining channel. The channel name and uid used to calculate
118121 // the token has to match the ones used for channel join
119- joinChannel ( uid: 999 ,
120- trackId: customCamera ? . trackId ?? 0 ,
122+ joinChannel ( uid: localUid ,
123+ trackId: nil ,
121124 publishEncodedVideoTrack: false )
122125 }
123- private func joinChannel( uid: UInt , trackId: UInt32 , publishEncodedVideoTrack: Bool ) {
126+ private func joinChannel( uid: UInt , trackId: UInt32 ? , publishEncodedVideoTrack: Bool ) {
124127 guard let channelName = configs [ " channelName " ] as? String else { return }
125128 let option = AgoraRtcChannelMediaOptions ( )
126- option. publishCustomVideoTrack = !publishEncodedVideoTrack
127129 option. publishMicrophoneTrack = false
128- option. autoSubscribeAudio = true
129- option. autoSubscribeVideo = true
130- option. customVideoTrackId = Int ( trackId)
131- option. publishEncodedVideoTrack = publishEncodedVideoTrack
132- option. clientRoleType = GlobalSettings . shared. getUserRole ( )
133- let connection = AgoraRtcConnection ( )
134- connection. localUid = uid
135- connection. channelId = channelName
130+ option. publishCameraTrack = false
131+
132+ let delegate : AgoraRtcEngineDelegate ? = self
133+ if let trackId = trackId {
134+ option. publishCustomVideoTrack = !publishEncodedVideoTrack
135+ option. customVideoTrackId = Int ( trackId)
136+ option. publishEncodedVideoTrack = publishEncodedVideoTrack
137+ option. clientRoleType = . broadcaster
138+ option. autoSubscribeAudio = false
139+ option. autoSubscribeVideo = false
140+ // delegate = nil
141+ } else {
142+ option. clientRoleType = . audience
143+ option. autoSubscribeAudio = true
144+ option. autoSubscribeVideo = true
145+ }
136146 NetworkManager . shared. generateToken ( channelName: channelName, uid: uid) { token in
147+ let connection = AgoraRtcConnection ( )
148+ connection. localUid = uid
149+ connection. channelId = channelName
137150 let result = self . agoraKit. joinChannelEx ( byToken: token,
138- connection: connection,
139- delegate: self ,
140- mediaOptions: option,
141- joinSuccess: nil )
151+ connection: connection,
152+ delegate: delegate ,
153+ mediaOptions: option,
154+ joinSuccess: nil )
142155 if result != 0 {
143156 // Usually happens with invalid parameters
144157 // Error code description can be found at:
@@ -211,51 +224,61 @@ class CustomVideoSourcePushMultiMain: BaseViewController {
211224
212225 @IBAction func onDestoryVideoTrack( _ sender: Any ) {
213226 guard let channelName = configs [ " channelName " ] as? String else { return }
214- let userModel = remoteVideos. filter ( { $0. isJoin == true } ) . last
215- userModel? . isJoin = false
216- userModel? . customSource? . stopSource ( )
217- userModel? . customEncodeSource? . cancelReading ( )
218- userModel? . customEncodeSource = nil
219- userModel? . canvasView? . videoView. reset ( )
220- userModel? . customSource = nil
227+ guard let userModel = remoteVideos. filter ( { $0. trackId != 0 } ) . last else { return }
228+ let trackId = UInt ( userModel. trackId)
221229 let connection = AgoraRtcConnection ( )
222- connection. localUid = userModel? . uid ?? 0
230+ connection. localUid = userModel. uid
223231 connection. channelId = channelName
224- agoraKit. destroyCustomVideoTrack ( UInt ( userModel? . trackId ?? 0 ) )
225- agoraKit. destroyCustomEncodedVideoTrack ( UInt ( userModel? . trackId ?? 0 ) )
226- userModel? . trackId = 0
227- userModel? . isEncode = false
228- userModel? . uid = UInt ( Int . random ( in: 10000 ... 99999 ) )
232+ agoraKit. destroyCustomVideoTrack ( trackId)
233+ agoraKit. destroyCustomEncodedVideoTrack ( trackId)
229234 agoraKit. leaveChannelEx ( connection) { state in
230- LogUtils . log ( message: " warning : \( state. description) " , level: . info)
235+ LogUtils . log ( message: " leaveChannelEx : \( state. description) " , level: . info)
231236 }
237+
238+ cleanCanvas ( uid: userModel. uid)
232239 }
233240
234241 override func willMove( toParent parent: UIViewController ? ) {
235242 if parent == nil {
236- // stop capture
237- customCamera? . stopSource ( )
238243 // leave channel when exiting the view
239244 if isJoined, let channelName = configs [ " channelName " ] as? String {
240245 remoteVideos. forEach ( {
241246 let connection = AgoraRtcConnection ( )
242247 connection. localUid = $0. uid
243248 connection. channelId = channelName
244- $0. customSource? . stopSource ( )
245249 agoraKit. leaveChannelEx ( connection) { state in
246- LogUtils . log ( message: " warning : \( state. description) " , level: . info)
250+ LogUtils . log ( message: " leaveChannelEx : \( state. description) " , level: . info)
247251 }
252+ $0. reset ( )
248253 } )
249254 let connection = AgoraRtcConnection ( )
250- connection. localUid = 999
255+ connection. localUid = localUid
251256 connection. channelId = channelName
252257 agoraKit. leaveChannelEx ( connection) { state in
253- LogUtils . log ( message: " warning : \( state. description) " , level: . info)
258+ LogUtils . log ( message: " leaveChannelEx : \( state. description) " , level: . info)
254259 }
255- AgoraRtcEngineKit . destroy ( )
256260 }
261+ AgoraRtcEngineKit . destroy ( )
257262 }
258263 }
264+
265+ private func cleanCanvas( uid: UInt ) {
266+ guard let channelName = configs [ " channelName " ] as? String ,
267+ let userModel = remoteVideos. first ( where: { $0. uid == uid } ) else { return }
268+ LogUtils . log ( message: " cleanCanvas: \( uid) " , level: . info)
269+
270+ // to unlink your view from sdk, so that your view reference will be released
271+ // note the video will stay at its last frame, to completely remove it
272+ // you will need to remove the EAGL sublayer from your binded view
273+ userModel. reset ( )
274+
275+ let videoCanvas = AgoraRtcVideoCanvas ( )
276+ videoCanvas. uid = uid
277+ let connect = AgoraRtcConnection ( )
278+ connect. localUid = localUid
279+ connect. channelId = channelName
280+ agoraKit. setupRemoteVideoEx ( videoCanvas, connection: connect)
281+ }
259282}
260283
261284/// agora rtc engine delegate events
@@ -267,7 +290,7 @@ extension CustomVideoSourcePushMultiMain: AgoraRtcEngineDelegate {
267290 /// cn: https://docs.agora.io/cn/Voice/API%20Reference/oc/Constants/AgoraWarningCode.html
268291 /// @param warningCode warning code of the problem
269292 func rtcEngine( _ engine: AgoraRtcEngineKit , didOccurWarning warningCode: AgoraWarningCode ) {
270- LogUtils . log ( message: " warning : \( warningCode. description) " , level: . warning)
293+ LogUtils . log ( message: " didOccurWarning : \( warningCode. description) " , level: . warning)
271294 }
272295
273296 /// callback when error occured for agora sdk, you are recommended to display the error descriptions on demand
@@ -277,8 +300,8 @@ extension CustomVideoSourcePushMultiMain: AgoraRtcEngineDelegate {
277300 /// cn: https://doc.shengwang.cn/api-ref/rtc/ios/error-code
278301 /// @param errorCode error code of the problem
279302 func rtcEngine( _ engine: AgoraRtcEngineKit , didOccurError errorCode: AgoraErrorCode ) {
280- LogUtils . log ( message: " error : \( errorCode) " , level: . error)
281- self . showAlert ( title: " Error " , message: " Error \( errorCode. description) occur " )
303+ LogUtils . log ( message: " didOccurError : \( errorCode) " , level: . error)
304+ self . showAlert ( title: " Error " , message: " didOccurError \( errorCode. description) occur " )
282305 }
283306
284307 func rtcEngine( _ engine: AgoraRtcEngineKit , didJoinChannel channel: String , withUid uid: UInt , elapsed: Int ) {
@@ -290,58 +313,42 @@ extension CustomVideoSourcePushMultiMain: AgoraRtcEngineDelegate {
290313 /// @param uid uid of remote joined user
291314 /// @param elapsed time elapse since current sdk instance join the channel in ms
292315 func rtcEngine( _ engine: AgoraRtcEngineKit , didJoinedOfUid uid: UInt , elapsed: Int ) {
316+ guard let channelName = configs [ " channelName " ] as? String else { return }
317+ if let _ = remoteVideos. first ( where: { $0. uid == uid } ) { return }
318+ guard let userModel = remoteVideos. first ( where: { $0. isJoin == false && $0. trackId == 0 } ) else { return }
293319 LogUtils . log ( message: " remote user join: \( uid) \( elapsed) ms " , level: . info)
320+ userModel. uid = uid
321+ userModel. isJoin = true
294322
295- // Only one remote video view is available for this
296- // tutorial. Here we check if there exists a surface
297- // view tagged as this uid.
298- if uid == 999 { return }
299- for model in remoteVideos where model. uid == uid {
300- return
301- }
323+ // the view to be binded
302324 let videoCanvas = AgoraRtcVideoCanvas ( )
303325 videoCanvas. uid = uid
304- // the view to be binded
305- guard let userModel = remoteVideos. first ( where: { $0. isJoin == false } ) else { return }
306326 videoCanvas. view = userModel. canvasView? . videoView
307- videoCanvas. renderMode = . hidden
308- userModel. uid = uid
309- userModel. isJoin = true
310- agoraKit. setupRemoteVideo ( videoCanvas)
327+ videoCanvas. renderMode = . fit
328+
329+ let connect = AgoraRtcConnection ( )
330+ connect. localUid = localUid
331+ connect. channelId = channelName
332+ agoraKit. setupRemoteVideoEx ( videoCanvas, connection: connect)
311333 }
312334
313335 /// callback when a remote user is leaving the channel, note audience in live broadcast mode will NOT trigger this event
314336 /// @param uid uid of remote joined user
315337 /// @param reason reason why this user left, note this event may be triggered when the remote user
316338 /// become an audience in live broadcasting profile
317339 func rtcEngine( _ engine: AgoraRtcEngineKit , didOfflineOfUid uid: UInt , reason: AgoraUserOfflineReason ) {
340+ guard let _ = remoteVideos. first ( where: { $0. uid == uid } ) else { return }
318341 LogUtils . log ( message: " remote user left: \( uid) reason \( reason) " , level: . info)
319342
320- // to unlink your view from sdk, so that your view reference will be released
321- // note the video will stay at its last frame, to completely remove it
322- // you will need to remove the EAGL sublayer from your binded view
323- let userModel = remoteVideos. first ( where: { $0. uid == uid } )
324- userModel? . isJoin = false
325- userModel? . uid = UInt ( Int . random ( in: 10000 ... 99999 ) )
326- userModel? . canvasView? . videoView. reset ( )
343+ cleanCanvas ( uid: uid)
327344 }
328345}
329346
330347/// agora camera video source, the delegate will get frame data from camera
331348extension CustomVideoSourcePushMultiMain : AgoraYUVImageSourcePushDelegate {
332349 func onVideoFrame( _ buffer: CVPixelBuffer , size: CGSize , trackId: UInt , rotation: Int32 ) {
333350 let videoFrame = AgoraVideoFrame ( )
334- /** Video format:
335- * - 1: I420
336- * - 2: BGRA
337- * - 3: NV21
338- * - 4: RGBA
339- * - 5: IMC2
340- * - 7: ARGB
341- * - 8: NV12
342- * - 12: iOS texture (CVPixelBufferRef)
343- */
344- videoFrame. format = 12
351+ videoFrame. format = AgoraVideoFormat . cvPixelNV12. rawValue
345352 videoFrame. textureBuf = buffer
346353 videoFrame. rotation = Int32 ( rotation)
347354
@@ -350,12 +357,8 @@ extension CustomVideoSourcePushMultiMain: AgoraYUVImageSourcePushDelegate {
350357 outputVideoFrame. height = Int32 ( size. height)
351358 outputVideoFrame. pixelBuffer = buffer
352359 outputVideoFrame. rotation = rotation
353- if customCamera? . trackId ?? 0 == trackId {
354- localVideo. videoView. renderVideoPixelBuffer ( outputVideoFrame)
355- } else {
356- let userModel = remoteVideos. first ( where: { $0. trackId == trackId } )
357- userModel? . canvasView? . videoView. renderVideoPixelBuffer ( outputVideoFrame)
358- }
360+ let userModel = remoteVideos. first ( where: { $0. trackId == trackId } )
361+ userModel? . canvasView? . videoView. renderVideoPixelBuffer ( outputVideoFrame)
359362 // once we have the video frame, we can push to agora sdk
360363 agoraKit? . pushExternalVideoFrame ( videoFrame, videoTrackId: trackId)
361364 }
0 commit comments