Skip to main content
Version: 6.0

Screen share in a 1-to-1 call

This page provides a code example for screen share in a 1-to-1 call.

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 PlanetKitMakeCallSettingBuilder (caller) or PlanetKitVerifyCallSettingBuilder (callee).

// If the sender is a caller
var settingsBuilder = PlanetKitMakeCallSettingBuilder()
.withEnableScreenShareKey(broadcastPort: PORT_NUMBER, broadcastPeerToken: "USER_DEFINED_TOKEN_EXT", broadcastMyToken: "USER_DEFINED_TOKEN_APP")

// If the sender is a callee
var settingsBuilder = PlanetKitVerifyCallSettingBuilder()
.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
}

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(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 PlanetKitCallDelegate. To start sending the screen share stream, call startMyScreenShare() in didStartMyBroadcast().

extension VideoCallViewController: PlanetKitCallDelegate {
...

func didStartMyBroadcast(_ call: PlanetKitCall) {
NSLog("Broadcast: start")
DispatchQueue.main.async {
call.startMyScreenShare() { success in
guard success else {
return
}
}
}
}

...
}

Sender - Stop sending screen share

The code implementation for stopping screen share is as follows.

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

extension VideoCallViewController: PlanetKitCallDelegate {
...

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

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

Receiver - Receive screen share update events

​Check whether a peer's screen share has started or stopped through the peerDidStartScreenShare or peerDidStopScreenShare event of PlanetKitCallDelegate.

extension VideoCallViewController: PlanetKitCallDelegate {
...
func peerDidStartScreenShare(_ call: PlanetKitCall) {
NSLog("peer screen share started")

// UI code here
DispatchQueue.main.async {
self.setPeerScreenShareEnabled(true)
}
}

func peerDidStopScreenShare(_ call: PlanetKitCall, reason: Int32) {
NSLog("peer screen share stopped")

// UI code here
DispatchQueue.main.async {
self.setPeerScreenShareEnabled(false)
}
}
}

Receiver - Render a peer's screen share video

You can display the shared screen by adding a screen share view after the peerDidStartScreenShare event occurs.

func setPeerScreenShareEnabled(_ enable: Bool) {
if enable {
let newView = PlanetKitMTKView(frame: view.bounds, device: nil)
view.addSubview(newView)
self.screenShareView = newView

call.addPeerScreenShareView(delegate: newView)
...
} else {
...
}
}

Receiver - Remove a peer's screen share video

If you do not wish to render the peer's shared screen, you can remove the screen share view from PlanetKitCall.

func setPeerScreenShareEnabled(_ enable: Bool) {
if enable {
...
} else {
if let oldView = screenShareView {
oldView.removeFromSuperview()
screenShareView = nil
call.removePeerScreenShareView(delegate: oldView)
}
}
}