Skip to content

Commit

Permalink
SPARK-120378 Group call with optional composited view
Browse files Browse the repository at this point in the history
  • Loading branch information
kliu committed Apr 8, 2020
1 parent 4dcb59c commit 10d08cf
Show file tree
Hide file tree
Showing 5 changed files with 69 additions and 20 deletions.
2 changes: 2 additions & 0 deletions CHANGELOG.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,6 +73,8 @@ All notable changes to this project will be documented in this file.
Released on 2020-3-30.
#### Added
- Support for threaded messaging.
- Support compose and render the active speaker video with other attendee video and all the names in one single view.
- Support single, filmstrip and grid layouts for the composed video view.

#### Updated
- Update Wme.framework.
Expand Down
11 changes: 11 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -480,6 +480,17 @@ Here are some examples of how to use the iOS SDK in your app.
})
```
19. Change the layout for the active speaker and other attendee composed video
```swift
let option: MediaOption = MediaOption.audioVideo(local: ..., remote: ...)
option.layout = .grid
webex.phone.dial(spaceId, option: option) { ret in
// ...
}
```
## Migration from Cisco SparkSDK
Expand Down
39 changes: 28 additions & 11 deletions Source/Phone/Call/CallClient.swift
Original file line number Diff line number Diff line change
Expand Up @@ -121,9 +121,9 @@ class CallClient {
return RequestParameter(result)
}

private func body(device: Device, json: [String:Any?] = [:]) -> RequestParameter {
private func body(composedVideo: Bool, device: Device, json: [String:Any?] = [:]) -> RequestParameter {
var result = json
result["device"] = ["url":device.deviceUrl.absoluteString, "deviceType":device.deviceType, "regionCode":device.countryCode, "countryCode":device.regionCode, "capabilities":["groupCallSupported":true, "sdpSupported":true]]
result["device"] = ["url":device.deviceUrl.absoluteString, "deviceType": (composedVideo ? "WEB" : device.deviceType), "regionCode":device.countryCode, "countryCode":device.regionCode, "capabilities":["groupCallSupported":true, "sdpSupported":true]]
result["respOnlySdp"] = true //coreFeatures.isResponseOnlySdpEnabled()
return RequestParameter(result)
}
Expand All @@ -136,23 +136,28 @@ class CallClient {
return ["localMedias": [["type": "SDP", "localSdp": mediaInfoJSON]]]
}

private func handleLocusOnlySDPResponse(completionHandler: @escaping (ServiceResponse<CallModel>) -> Void) ->((ServiceResponse<CallResponseModel>) -> Void) {
private func handleLocusOnlySDPResponse(layout: MediaOption.VideoLayout? = nil, queue: DispatchQueue? = nil, completionHandler: @escaping (ServiceResponse<CallModel>) -> Void) ->((ServiceResponse<CallResponseModel>) -> Void) {
return {
result in
switch result.result {
case .success(let callResponse):
if var callModel = callResponse.callModel {
callModel.setMediaConnections(newMediaConnections: callResponse.mediaConnections)
if let layout = layout, let url = callModel.myself?.url, let device = callModel.myself?.deviceUrl {
self.layout(url, by: device, layout: layout, queue: queue ?? DispatchQueue.main) { _ in
completionHandler(ServiceResponse.init(result.response, Result.success(callModel)))
}
return;
}
completionHandler(ServiceResponse.init(result.response, Result.success(callModel)))
}
case .failure(let error):
completionHandler(ServiceResponse.init(result.response, Result.failure(error)))
}
}

}

func create(_ toAddress: String, moderator:Bool? = false, PIN:String? = nil, by device: Device, localMedia: MediaModel, queue: DispatchQueue, completionHandler: @escaping (ServiceResponse<CallModel>) -> Void) {
func create(_ toAddress: String, moderator:Bool? = false, PIN:String? = nil, by device: Device, localMedia: MediaModel, layout: MediaOption.VideoLayout?, queue: DispatchQueue, completionHandler: @escaping (ServiceResponse<CallModel>) -> Void) {
var json = convertToJson(mediaInfo: localMedia)
json["invitee"] = ["address" : toAddress]
json["supportsNativeLobby"] = true
Expand All @@ -161,23 +166,22 @@ class CallClient {
let request = ServiceRequest.Builder(authenticator, service: .locus, device: device)
.method(.post)
.path("loci").path("call")
.body(body(device: device, json: json))
.body(body(composedVideo: layout != .single, device: device, json: json))
.queue(queue)
.build()

request.responseObject(handleLocusOnlySDPResponse(completionHandler: completionHandler))
request.responseObject(handleLocusOnlySDPResponse(layout: layout, queue: queue, completionHandler: completionHandler))
}

func join(_ callUrl: String, by device: Device, localMedia: MediaModel, queue: DispatchQueue, completionHandler: @escaping (ServiceResponse<CallModel>) -> Void) {
func join(_ callUrl: String, by device: Device, localMedia: MediaModel, layout: MediaOption.VideoLayout?, queue: DispatchQueue, completionHandler: @escaping (ServiceResponse<CallModel>) -> Void) {
let json = convertToJson(mediaInfo: localMedia)
let request = ServiceRequest.Builder(authenticator, endpoint: callUrl)
.method(.post)
.path("participant")
.body(body(device: device, json: json))
.body(body(composedVideo: layout != .single, device: device, json: json))
.queue(queue)
.build()

request.responseObject(handleLocusOnlySDPResponse(completionHandler: completionHandler))
request.responseObject(handleLocusOnlySDPResponse(layout: layout, queue: queue, completionHandler: completionHandler))
}

func leave(_ participantUrl: String, by device: Device, queue: DispatchQueue, completionHandler: @escaping (ServiceResponse<CallModel>) -> Void) {
Expand Down Expand Up @@ -300,6 +304,19 @@ class CallClient {
request.responseObject(completionHandler)
}

func layout(_ participantUrl: String, by deviceUrl: String, layout: MediaOption.VideoLayout, queue: DispatchQueue, completionHandler: @escaping (ServiceResponse<CallModel>) -> Void) {
let parameters: [String: Any?] = ["layout":["deviceUrl":deviceUrl, "type":layout.type]]
let request = ServiceRequest.Builder(authenticator, endpoint: participantUrl)
.method(.patch)
.path("controls")
.body(RequestParameter(parameters))
.keyPath("locus")
.queue(queue)
.build()

request.responseObject(completionHandler)
}

func keepAlive(_ url: String, queue: DispatchQueue, completionHandler: @escaping (ServiceResponse<Any>) -> Void) {
let request = ServiceRequest.Builder(authenticator, endpoint: url)
.method(.get)
Expand Down
31 changes: 25 additions & 6 deletions Source/Phone/Call/MediaOption.swift
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,26 @@ import Foundation
/// - since: 1.2.0
public struct MediaOption {

/// The video layout for the active speaker and other attendees in the group video meeting.
///
/// - since: 2.5.0
public enum VideoLayout {
case single
case filmstrip
case grid

var type: String {
switch self {
case .single:
return "Single"
case .grid:
return "Equal"
default:
return "ActivePresence"
}
}
}

/// Constructs an audio only media option.
///
/// - since: 1.2.0
Expand Down Expand Up @@ -65,19 +85,18 @@ public struct MediaOption {
}

var localVideoView: MediaRenderView?

var remoteVideoView: MediaRenderView?

var screenShareView: MediaRenderView?

fileprivate var _uuid: UUID?

let hasVideo: Bool

let hasScreenShare: Bool

let applicationGroupIdentifier:String?

/// The video layout for the active speaker and other attendees in the group video meeting.
///
/// - since: 2.5.0
public var layout: VideoLayout?

init() {
self.hasVideo = false
self.hasScreenShare = false
Expand Down
6 changes: 3 additions & 3 deletions Source/Phone/Phone.swift
Original file line number Diff line number Diff line change
Expand Up @@ -306,15 +306,15 @@ public class Phone {
if let device = self.devices.device {
let media = MediaModel(sdp: localSDP, audioMuted: false, videoMuted: false, reachabilities: reachabilities)
if target.isEndpoint {
self.client.create(target.address, by: device, localMedia: media, queue: self.queue.underlying) { res in
self.client.create(target.address, by: device, localMedia: media, layout: option.layout, queue: self.queue.underlying) { res in
self.doLocusResponse(LocusResult.call(target.isGroup, device, option.uuid, tempMediaContext, res, completionHandler))
self.queue.yield()
}
}
else {
self.conversations.getLocusUrl(conversation: target.address, by: device, queue: self.queue.underlying) { res in
if let url = res.result.data?.locusUrl {
self.client.join(url, by: device, localMedia: media, queue: self.queue.underlying) { resNew in
self.client.join(url, by: device, localMedia: media, layout: option.layout, queue: self.queue.underlying) { resNew in
self.doLocusResponse(LocusResult.call(target.isGroup, device, option.uuid, tempMediaContext, resNew, completionHandler))
self.queue.yield()
}
Expand Down Expand Up @@ -471,7 +471,7 @@ public class Phone {
tempMediaContext.prepare(option: option, phone: self)
let media = MediaModel(sdp: tempMediaContext.getLocalSdp(), audioMuted: false, videoMuted: false, reachabilities: self.reachability.feedback?.reachabilities)
self.queue.sync {
self.client.join(call.url, by: call.device, localMedia: media, queue: self.queue.underlying) { res in
self.client.join(call.url, by: call.device, localMedia: media, layout: option.layout, queue: self.queue.underlying) { res in
self.doLocusResponse(LocusResult.join(call, res, completionHandler))
self.queue.yield()
}
Expand Down

0 comments on commit 10d08cf

Please sign in to comment.