diff --git a/WebRTC-Sample-App/ConferenceViewController.swift b/WebRTC-Sample-App/ConferenceViewController.swift index b487d20..a585ebf 100644 --- a/WebRTC-Sample-App/ConferenceViewController.swift +++ b/WebRTC-Sample-App/ConferenceViewController.swift @@ -51,7 +51,7 @@ open class ConferenceViewController: UIViewController , AVCaptureVideoDataOutpu //this publishes stream to the room self.publisherStreamId = generateRandomAlphanumericString(length: 10); - self.conferenceClient?.publish(streamId: self.publisherStreamId, token: "", mainTrackId: roomId); + self.conferenceClient?.publish(streamId: self.publisherStreamId, mainTrackId: roomId); //this plays the streams in the room @@ -96,13 +96,16 @@ extension ConferenceViewController: AntMediaClientDelegate removePlayers(); } public func playStarted(streamId: String) { - print("play started"); + debugPrint("play started"); conferenceClient?.speakerOn() + } public func trackAdded(track: RTCMediaStreamTrack, stream: [RTCMediaStream]) { AntMediaClient.printf("Track is added with id:\(track.trackId)") + + if let videoTrack = track as? RTCVideoTrack { remoteViewTrackMap.append(videoTrack); @@ -110,6 +113,7 @@ extension ConferenceViewController: AntMediaClientDelegate self.collectionView.reloadData() } } + } public func trackRemoved(track: RTCMediaStreamTrack) { @@ -139,7 +143,7 @@ extension ConferenceViewController: AntMediaClientDelegate AntMediaClient.printf("Publish started for stream:\(streamId)") //this plays the streams in the room - self.conferenceClient?.play(streamId: self.roomId); + self.conferenceClient?.play(streamId: self.roomId, subscriberId: "subscriberId", subscriberName: "subscriberName", disableTracksByDefault:false); conferenceClient?.getBroadcastObject(forStreamId: self.roomId) diff --git a/WebRTCiOSSDK/api/AntMediaClient.swift b/WebRTCiOSSDK/api/AntMediaClient.swift index eeba25a..b014bd0 100644 --- a/WebRTCiOSSDK/api/AntMediaClient.swift +++ b/WebRTCiOSSDK/api/AntMediaClient.swift @@ -20,6 +20,8 @@ public enum AntMediaClientMode: Int { case conference = 4 case unspecified = 5 + + func getLeaveMessage() -> String { switch self { case .join: @@ -51,6 +53,7 @@ public enum AntMediaClientMode: Int { } open class AntMediaClient: NSObject, AntMediaClientProtocol { + internal static var isDebug: Bool = false internal static var isVerbose: Bool = false @@ -128,6 +131,14 @@ open class AntMediaClient: NSObject, AntMediaClientProtocol { var disableTrackId: String? var reconnectIfRequiresScheduled: Bool = false + + var subscriberId: String = "" + var subscriberCode: String = "" + var subscriberName: String = "" + + var playOnlyDataChannel : Bool = false + var disableTracksByDefault: Bool = false + var publishOnlyDataChannel : Bool = false struct HandshakeMessage: Codable { var command: String? @@ -138,8 +149,15 @@ open class AntMediaClient: NSObject, AntMediaClientProtocol { var mode: String? var mainTrack: String? var trackList: [String] + var subscriberId: String? + var subscriberCode: String? + var subscriberName: String? + var onlyDataChannel: Bool? + var disableTracksByDefault: Bool? } + + public override init() { } @@ -211,7 +229,9 @@ open class AntMediaClient: NSObject, AntMediaClientProtocol { AntMediaClient.printf("Disable track id is not set \(String(describing: self.disableTrackId))") } - let handShakeMesage = HandshakeMessage(command: mode.getName(), streamId: streamId, token: token, video: self.videoEnable, audio: self.audioEnable, mainTrack: self.mainTrackId, trackList: trackList) + let handShakeMesage = HandshakeMessage(command: mode.getName(), streamId: streamId, token: token, video: self.videoEnable, audio: self.audioEnable, mainTrack: self.mainTrackId, trackList: trackList, + subscriberId: self.subscriberId, subscriberCode: self.subscriberCode, subscriberName: self.subscriberName, onlyDataChannel: mode == .publish ? self.publishOnlyDataChannel : self.playOnlyDataChannel, + disableTracksByDefault: self.disableTracksByDefault) let json = try! JSONEncoder().encode(handShakeMesage) return String(data: json, encoding: .utf8)! @@ -353,9 +373,24 @@ open class AntMediaClient: NSObject, AntMediaClientProtocol { RTCAudioSessionConfiguration.setWebRTC(RTCAudioSessionConfiguration()) } - public func publish(streamId: String, token: String = "", mainTrackId: String = "") { + public func publish(streamId: String, token: String = "", mainTrackId: String = "", subsriberId: String = "", subscriberCode: String = "", subscriberName: String = "", onlyDataChannel: Bool=false, videoEnabled: Bool = true) { self.publisherStreamId = streamId + self.publishOnlyDataChannel = onlyDataChannel + self.videoEnable = videoEnabled + + if !subsriberId.isEmpty { + self.subscriberId = subsriberId + } + + if !subscriberCode.isEmpty { + self.subscriberCode = subscriberCode + } + + if !subscriberName.isEmpty { + self.subscriberName = subscriberName + } + // reset default webrtc audio configuation to capture audio and mic resetDefaultWebRTCAudioConfiguation() initPeerConnection(streamId: streamId, mode: AntMediaClientMode.publish, token: token) @@ -375,9 +410,26 @@ open class AntMediaClient: NSObject, AntMediaClientProtocol { } } - public func play(streamId: String, token: String = "") { + public func play(streamId: String, token: String = "", + subscriberId: String = "", subscriberCode: String = "", subscriberName: String = "", + onlyDataChannel: Bool=false, disableTracksByDefault: Bool=false) + { self.playerStreamId = streamId + self.playOnlyDataChannel = onlyDataChannel + self.disableTracksByDefault = disableTracksByDefault + + if !subscriberId.isEmpty { + self.subscriberId = subscriberId + } + + if !subscriberCode.isEmpty { + self.subscriberCode = subscriberCode + } + + if !subscriberName.isEmpty { + self.subscriberName = subscriberName + } if !token.isEmpty { self.playToken = token @@ -483,6 +535,7 @@ open class AntMediaClient: NSObject, AntMediaClientProtocol { open func initPeerConnection(streamId: String = "", mode: AntMediaClientMode = .unspecified, token: String = "") { let id = getStreamId(streamId) + self.mode = mode; if self.webRTCClientMap[id] == nil { AntMediaClient.printf("Has wsClient? (start) : \(String(describing: self.webRTCClientMap[id]))") @@ -867,7 +920,7 @@ open class AntMediaClient: NSObject, AntMediaClientProtocol { // clean the connection self.webRTCClientMap.removeValue(forKey: streamId)?.disconnect() AntMediaClient.printf("Reconnecting to publish the stream:\(streamId)") - self.publish(streamId: streamId) + self.publish(streamId: streamId, onlyDataChannel: self.publishOnlyDataChannel, videoEnabled: self.videoEnable) } else { AntMediaClient.printf("Not trying to reconnect to publish the stream:\(streamId) because ice connection state is not disconnected") } @@ -888,7 +941,7 @@ open class AntMediaClient: NSObject, AntMediaClientProtocol { // clean the connection self.webRTCClientMap.removeValue(forKey: streamId)?.disconnect() AntMediaClient.printf("Reconnecting to play the stream:\(streamId)") - self.play(streamId: streamId) + self.play(streamId: streamId, onlyDataChannel: self.playOnlyDataChannel, disableTracksByDefault:self.disableTracksByDefault) } else { AntMediaClient.printf("Not trying to reconnect to play the stream:\(streamId) because ice connection state is not disconnected") } @@ -1037,6 +1090,29 @@ open class AntMediaClient: NSObject, AntMediaClientProtocol { let streamId = message[STREAM_ID] as? String ?? "" self.delegate?.eventHappened(streamId: streamId, eventType: definition, payload: message) } + else if definition == SUBSCRIBER_COUNT { + let streamId = message[STREAM_ID] as? String ?? "" + let count = message["count"] as! Int + self.delegate?.subscriberCount(streamId: streamId, subscriberCount: count); + } + else if definition == SUBSCRIBER_LIST_NOTIFICATION { + let streamId = message[STREAM_ID] as? String ?? "" + if let subscriberListJSONArray = message["subscriberList"] as? [String] { + var subscribers: [Subscriber] = [] + + for jsonString in subscriberListJSONArray { + if let data = jsonString.data(using: .utf8) { + do { + let subscriber = try JSONDecoder().decode(Subscriber.self, from: data) + subscribers.append(subscriber) + } catch { + print("Failed to decode subscriber: \(error)") + } + } + } + self.delegate?.subscriberList(streamId: streamId, subscriberList: subscribers) + } + } case ROOM_INFORMATION_COMMAND: if let updatedStreamsInTheRoom = message[STREAMS] as? [String] { // check that there is a new stream exists @@ -1274,6 +1350,24 @@ open class AntMediaClient: NSObject, AntMediaClientProtocol { ) } + public func getSubscriberCount(streamId id: String) { + sendCommand( + command: GET_SUBSCRIBER_COUNT_COMMAND, + streamId: id + ) + } + + public func getSubscriberList(streamId id: String, offset: Int, limit: Int) { + + let jsonString = [ + COMMAND: GET_SUBSCRIBER_LIST_COMMAND, + STREAM_ID: id, + OFFSET: offset, + SIZE: limit].json + + webSocket?.write(string: jsonString) + } + func invalidateTimers() { audioLevelGetterTimer?.invalidate() audioLevelGetterTimer = nil @@ -1345,9 +1439,7 @@ extension AntMediaClient: WebRTCClientDelegate { if let eventType = json?[EVENT_TYPE] { // event happened if let incomingStreamId = json?[STREAM_ID] { - - self.delegate?.eventHappened(streamId: incomingStreamId as! String, eventType: eventType as! String) - + self.delegate?.eventHappened( streamId: incomingStreamId as! String, eventType: eventType as! String diff --git a/WebRTCiOSSDK/api/AntMediaClientDelegate.swift b/WebRTCiOSSDK/api/AntMediaClientDelegate.swift index 7c8ac44..6803f3d 100644 --- a/WebRTCiOSSDK/api/AntMediaClientDelegate.swift +++ b/WebRTCiOSSDK/api/AntMediaClientDelegate.swift @@ -24,6 +24,11 @@ public class StreamInformation { } } +public struct Subscriber: Codable { + let subscriberId: String + let subscriberName: String +} + public protocol AntMediaClientDelegate: AnyObject { /** @@ -178,6 +183,16 @@ public protocol AntMediaClientDelegate: AnyObject { If it's 0, it means it's silent. If it's 1, it means audio is max. */ func audioLevelChanged(_ client: AntMediaClient, audioLevel: Double, hasAudio: Bool) + + /** + The delegate method is called as response to getSubscriberCount for the stream + */ + func subscriberCount(streamId:String, subscriberCount:Int) + + /** + The delegate method is called as a response to getSubscriberList for the stream + */ + func subscriberList(streamId:String, subscriberList:[Subscriber]) } public extension AntMediaClientDelegate { @@ -279,4 +294,12 @@ public extension AntMediaClientDelegate { AntMediaClient.printf("streamId: \(streamId) stats received") } + func subscriberCount(streamId:String, subscriberCount:Int) { + AntMediaClient.printf("subscriberCount for: \(streamId) is received as \(subscriberCount)") + } + + func subscriberList(streamId:String, subscriberList:[Subscriber]) { + AntMediaClient.printf("subscriberList for: \(streamId) is received: \(subscriberList)") + } + } diff --git a/WebRTCiOSSDK/api/AntMediaClientProtocol.swift b/WebRTCiOSSDK/api/AntMediaClientProtocol.swift index e14657a..ffdb80f 100644 --- a/WebRTCiOSSDK/api/AntMediaClientProtocol.swift +++ b/WebRTCiOSSDK/api/AntMediaClientProtocol.swift @@ -12,6 +12,8 @@ import WebRTC let COMMAND = "command" let STREAM_ID = "streamId" +let OFFSET = "offset" +let SIZE = "size" let TRACK_ID = "trackId" let ENABLED = "enabled" let TOKEN_ID = "token" @@ -35,8 +37,14 @@ let EVENT_TYPE_MIC_UNMUTED = "MIC_UNMUTED" let EVENT_TYPE_CAM_TURNED_OFF = "CAM_TURNED_OFF" let EVENT_TYPE_CAM_TURNED_ON = "CAM_TURNED_ON" let GET_BROADCAST_OBJECT_COMMAND = "getBroadcastObject" +let GET_SUBSCRIBER_COUNT_COMMAND = "getSubscriberCount" +let GET_SUBSCRIBER_LIST_COMMAND = "getSubscribers" + let BROADCAST_OBJECT_NOTIFICATION = "broadcastObject" public let RESOLUTION_CHANGE_INFO_COMMAND = "resolutionChangeInfo" +public let SUBSCRIBER_COUNT = "subscriberCount" +public let SUBSCRIBER_LIST_NOTIFICATION = "subscriberList"; + public let EVENT_TYPE_TRACK_LIST_UPDATED = "TRACK_LIST_UPDATED" public let EVENT_TYPE_VIDEO_TRACK_ASSIGNMENT_LIST = "VIDEO_TRACK_ASSIGNMENT_LIST" @@ -120,17 +128,31 @@ public protocol AntMediaClientProtocol { /** Publish stream to the server with streamId and roomId. - Parameters - - streamId: the id of the stream that is going to be published. + - streamId: the id of the stream that is going to be published. Required - mainTrackId: the id of the main stream or conference room that this stream will be published. It's optional value + - token: The access token to publish the stream. If JWT or any other token security is enabled in the Ant Media Server, this field is required + - subsriberId: The subscriberId. If TOTP is enabled in server it is mandatory otherwise optional to have some field s + - subscriberCode: The subscriber code is the TOTP code which is mandatory if the TOTP security is enabled on the server side + - subscriberName: Subscriber name which is optional + - onlyDataChannel: Option to publish for data channel not audio/video + - videoEnabled: Send stream with video or not */ - func publish(streamId: String, token: String, mainTrackId: String) + func publish(streamId: String, token: String, mainTrackId: String, subsriberId: String, subscriberCode: String, subscriberName: String, + onlyDataChannel: Bool, videoEnabled: Bool) /** Starts to play a stream on the server side - Parameters - streamId: the id of the stream or id of the conference room. It supports playing both of them + - token: The access token to publish the stream. If JWT or any other token security is enabled in the Ant Media Server, this field is required + - subscriberId: The subscriberId. If TOTP is enabled in server it is mandatory otherwise optional to have some field s + - subscriberCode: The subscriber code is the TOTP code which is mandatory if the TOTP security is enabled on the server side + - subscriberName: Subscriber name which is optional + - onlyDataChannel: Option to publish for data channel not audio/video */ - func play(streamId: String, token: String) + func play(streamId: String, token: String, + subscriberId: String, subscriberCode: String, subscriberName: String, onlyDataChannel: Bool, + disableTracksByDefault: Bool) /** Sets the camera position front or back. This method is effective if it's called before `initPeerConnection()` and `start()` method. @@ -412,6 +434,16 @@ public protocol AntMediaClientProtocol { */ func getBroadcastObject(forStreamId id: String) + /** + Get the number of subscribers for the stream id + */ + func getSubscriberCount(streamId id: String) + + /** + Get the subscriber list for the stream id + */ + func getSubscriberList(streamId id: String, offset: Int, limit: Int) + /** Register for Audio Level Extraction to get the audioLevel from the microphone. It's good to use to detect if user is speaking when he muted himself. When this method is called, `audioLevelChanged` delegate method will be called automatically