Skip to main content
Version: 7.1

Screen share in a group call

This page provides a code example for screen share in a group call (conference).

Prerequisites​

Before implementing screen share in your iOS application, you must do the following:

  • The application must implement Broadcast Upload Extension or equivalents to capture screen share.
    • To implement Broadcast Upload Extension, in Xcode, add a new Target to your project, select "Broadcast Upload Extension" template, and activate the extension.
  • Define a port number, a reception token, and a transmission token that will be used for screen share stream transmission.

Sender - Start sending screen share​

The code implementation for starting screen share is as follows.

Set the screen share key in PlanetKit​

The app client of the sender needs to specify predefined port number, reception token, and transmission token, and sets it in withEnableScreenShareKey(broadcastPort:broadcastPeerToken:broadcastMyToken:) of PlanetKitJoinConferenceSettingBuilder.

var settingsBuilder = PlanetKitJoinConferenceSettingBuilder()
.withEnableScreenShareKey(broadcastPort: PORT_NUMBER, broadcastPeerToken: "USER_DEFINED_TOKEN_EXT", broadcastMyToken: "USER_DEFINED_TOKEN_APP")

Implement a screen capturing and transmission module​

Implement a screen capturing module that establishes connection between your app and the SDK through NWConnection.

class BroadcastSender {
private enum State {
case started
case handshaking
case connected
case failed
}

weak var delegate: BroadcastSenderDelegate?

private let broadcastPort: UInt16
private let rxToken: String
private let txToken: String

private let connection: NWConnection
private let queue = DispatchQueue(label: "BroadcastSender.Queue")

static func connection(broadcastPort : UInt16) throws -> NWConnection {
guard let port = NWEndpoint.Port(rawValue: broadcastPort) else {
throw Error.invalidPort
}

let options = NWProtocolTCP.Options()
options.noDelay = true

return NWConnection(host: .ipv4(.loopback), port: port, using: .init(tls: nil, tcp: options))
}

init(delegate: BroadcastSenderDelegate?, broadcastPort: UInt16, rxToken : String, txToken : String) throws {
self.delegate = delegate
self.rxToken = rxToken
self.txToken = txToken

connection = try BroadcastSender.connection(broadcastPort: broadcastPort)
connection.stateUpdateHandler = { [weak self] in
self?.handleConnection(newState: $0)
}
connection.start(queue: DispatchQueue(label: "BroadcastSender.NetworkQueue"))
}

func handShake() {
guard state == .started, let data = txToken.data(using: .utf8) else {
return
}

state = .handshaking

connection.send(content: data, completion: .contentProcessed({ (error) in
self.handShakeProcessed(error: error)
}))
}

func sendVideo(sampleBuffer: CMSampleBuffer) throws {
guard state == .connected,
!sending else {
return
}

try queue.sync {
let data: Data?
// create data with CMSampleBuffer
connection.send(data, completion: .contentProcessed({(error) in NSLog("data processed \(error)")}))
}
}

func handShakeProcessed(error: NWError?) {
queue.sync {
if let error = error {
didFail(error: error)
} else {
handShakeAck()
}
}
}

func handShakeAck() {
guard state == .handshaking, let token = rxToken.data(using: .utf8) else {
return
}

connection.receive(minimumIncompleteLength: token.count, maximumLength: token.count) { (data, context, final, error) in
self.handShakeAckProcessed(data: data, error: error)
}
}

func handShakeAckProcessed(data: Data?, error: NWError?) {
queue.sync {
guard state == .handshaking, let token = rxToken.data(using: .utf8) else {
return
}
if data == token {
state = .connected

} else {

didFail(error: .rejected)
}
}
}

func handleConnection(newState: NWConnection.State) {
queue.sync {
switch newState {
case .ready:
handShake()
case .waiting(let error), .failed(let error):
didFail(error: error)
default:
break
}
}
}
}

If connection is successfully established, you must send screen share streams to the created NWConnection. Implement the SampleHandler class to send the captured screen share streams.

class SampleHandler: RPBroadcastSampleHandler {
private var sender: BroadcastSender?
...
override func broadcastFinished() {
// User has requested to finish the broadcast.
sender?.cancel()
sender = nil
}

let rxToken : String = "USER_DEFINED_TOKEN_APP"
let txToken : String = "USER_DEFINED_TOKEN_EXT"
let broadcastPort : UInt16 = PORT_NUMBER

override func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType: RPSampleBufferType) {
switch sampleBufferType {
case RPSampleBufferType.video:
do {
if let sender = sender {
try autoreleasepool {
try sender.sendVideo(sampleBuffer: sampleBuffer)
}
} else {
sender = try BroadcastSender(delegate: self, broadcastPort: broadcastPort, rxToken: rxToken, txToken: txToken)
}
} catch {
finish(error: error)
}
break
...
}
}

private func finish(error: Error) {
sender?.cancel()
sender = nil

if let description = (error as? LocalizedError)?.errorDescription {
self.finishBroadcastWithError(NSError(domain: "BroadcastSender.ErrorDomain", code: 0, userInfo: [NSLocalizedFailureReasonErrorKey: description]))
} else {
self.finishBroadcastWithError(error)
}
}
}

Start screen share on PlanetKit​

Implement didStartMyBroadcast() of PlanetKitConferenceDelegate. To start sending the screen share stream, call startMyScreenShare() in didStartMyBroadcast().

  • The subgroupName parameter determines the destination of screen share.
  • If the subgroupName parameter is nil, screen share is sent to the main room.
extension VideoConferenceViewController: PlanetKitConferenceDelegate {
...

func didStartMyBroadcast(_ conference: PlanetKitConference) {
NSLog("Broadcast: start")
DispatchQueue.main.async {
conference.startMyScreenShare(subgroupName: nil) { success in
guard success else {
return
}
}
}
}

...
}

Sender - Change the destination of screen share​

Depending on the type of destination, you can change the destination of screen share as follows:

  • To change the destination to a subgroup other than the main room, call changeMyScreenShareDestination() with the subgroup name.
  • To change the destination to the main room, call changeMyScreenShareDestinationToMainRoom().
// Change the destination to a subgroup other than the main room
conference.changeMyScreenShareDestination(subgroupName: subgroupName) { success in
guard success else { return }
// UI code here if success
}

// Change the destination to the main room
conference.changeMyScreenShareDestinationToMainRoom() { success in
guard success else { return }
// UI code here if success
}

Sender - Stop sending screen share​

The code implementation for stopping screen share is as follows.

Implement didFinishMyBroadcast() and didErrorMyBroadcast() of PlanetKitConferenceDelegate. To stop screen share, call stopMyScreenShare() in didFinishMyBroadcast() and didErrorMyBroadcast().

extension VideoConferenceViewController: PlanetKitConferenceDelegate {
...

func didFinishMyBroadcast(_ conference: PlanetKitConference) {
NSLog("Broadcast: finish")
DispatchQueue.main.async {
self.conference?.stopMyScreenShare() { success in
}
}
}

func didErrorMyBroadcast(_ conference: PlanetKitConference, error: PlanetKitScreenShare.BroadcastError) {
NSLog("Broadcast: error \(error)")
DispatchQueue.main.async {
self.conference?.stopMyScreenShare() { success in
}
}
}
}

Receiver - Receive screen share update events​

​Check whether a peer's screen share has started or stopped through the screenShareDidUpdate event of PlanetKitConferenceDelegate.

Note

If you have already created peer control, skip the next step and use it for the Receiver - Start or stop a peer's screen share video step. Otherwise, create peer control as in the next step.

extension VideoConferenceViewController: PlanetKitConferenceDelegate {
...
func screenShareDidUpdate(_ conference: PlanetKitConference, updated: PlanetKitConferenceScreenShareUpdateParam) {
if updated.state == .enabled {
NSLog("start screen share - \(updated.peerId) \(updated.subgroupName ?? "main")")
}
else {
NSLog("stop screen share - \(updated.peerId) \(updated.subgroupName ?? "main")")
}

// UI code here
DispatchQueue.main.async {
self.loadPage(self.currentPage)
}
}
}

Receiver - Create and register peer control​

When a peer is connected to the call, create and register peer control for the peer.

extension VideoConferenceViewController {
...

var peers: [PlanetKitConferencePeer]
var peerView: ScreenPeerVideoView

func loadPage(_ page: Int) {
...

peerView.setupPeerControl(conference: conference, peer: peers[index])
}
}

class ScreenPeerVideoView: UIView {
...

var peerControl: PlanetKitPeerControl!

func setupPeerControl(conference: PlanetKitConference, peer: PlanetKitConferencePeer) {
guard let peerControl = conference.createPeerControl(peer: peer) else {
// Error code here
return
}
peerControl.register(self) { success in
// UI code here
self.setScreenShareEnabled(peer.screenShareState == .enabled)
}
self.peerControl = peerControl
}

func finalPeerControl() {
peerControl.unregister() { success in
// UI code here
}
}
}

Receiver - Start or stop a peer's screen share video​

Check whether a peer's screen share has started or stopped through the didUpdateScreenShare event of PlanetKitPeerControlDelegate.

  • When the peer's screen share started, add a peer screen share view and call startScreenShare() of PlanetKitPeerControl.
  • When the peer's screen share stopped, remove the peer screen share view and call stopScreenShare() of PlanetKitPeerControl.
class ScreenPeerVideoView: UIView {
...

var peerControl: PlanetKitPeerControl!
var peerScreenShareView: PlanetKitMTKView!

func setScreenShareEnabled(_ enabled: Bool) {
if enabled {
peerScreenShareView = PlanetKitMTKView(frame: view.bounds, device: nil)
view.addSubview(peerScreenShareView)

let subgroupName = peerControl.peer.currentScreenShareSubgroupName
peerControl.startScreenShare(delegate: peerScreenShareView, subgroupName: subgroupName) { success in
// UI code here
}
}
else {
peerScreenShareView.removeFromSuperview()

peerControl.stopScreenShare() { success in
// UI code here
}
}
}
}

extension ScreenPeerVideoView: PlanetKitPeerControlDelegate {
...

func didUpdateScreenShare(_ peerControl: PlanetKitPeerControl, subgroup: PlanetKitSubgroup, status: PlanetKitScreenShareStatus, disabledReason: NSNumber?) {
DispatchQueue.main.async {
self.setScreenShareEnabled((status.state == .enabled))
}
}
}