title: iOS screen sharing (using ReplayKit) using WebRTC in swift
body: <p>I have successfully implemented ReplayKit.</p>
  <p>SampleHandler.swift</p>
  <pre><code>class SampleHandler: RPBroadcastSampleHandler {
  
     override func broadcastStarted(withSetupInfo setupInfo: [String : NSObject]?) {
  
     }
  
    override func processSampleBuffer(_ sampleBuffer: CMSampleBuffer, with sampleBufferType:  RPSampleBufferType) {
          switch sampleBufferType {
  
          case RPSampleBufferType.video:
              break
  
          case RPSampleBufferType.audioApp:
              break
  
          case RPSampleBufferType.audioMic:
              break
          @unknown default:
              return
          }
      }
  }
  </code></pre>
  <p>Question :</p>
  <ol>
  <li>How I can display screen share video too receiver using WebRTC?</li>
  <li>If I want save screen share video in my document directory folder or in gallery than what I need to do?</li>
  </ol>
  <p>I am using WebRTC SDK for mobile in swift.
  My WebRTCClient file.</p>
  <pre><code>    final class WebRTCClient: NSObject {
      
      // The `RTCPeerConnectionFactory` is in charge of creating new RTCPeerConnection instances.
      // A new RTCPeerConnection should be created every new call, but the factory is shared.
       static let factory: RTCPeerConnectionFactory = {
          RTCInitializeSSL()
          let videoEncoderFactory = RTCDefaultVideoEncoderFactory()
          let videoDecoderFactory = RTCDefaultVideoDecoderFactory()
          return RTCPeerConnectionFactory(encoderFactory: videoEncoderFactory, decoderFactory: videoDecoderFactory)
      }()
      
      weak var delegate: WebRTCClientDelegate?
      let peerConnection: RTCPeerConnection
      private let rtcAudioSession =  RTCAudioSession.sharedInstance()
      private let audioQueue = DispatchQueue(label: &quot;audio&quot;)
      private let mediaConstrains = [kRTCMediaConstraintsOfferToReceiveAudio: kRTCMediaConstraintsValueTrue,
                                     kRTCMediaConstraintsOfferToReceiveVideo: kRTCMediaConstraintsValueTrue]    
      private var videoCapturer: RTCVideoCapturer?
      private var localVideoTrack: RTCVideoTrack?
      private var remoteVideoTrack: RTCVideoTrack?
      private var localDataChannel: RTCDataChannel?
      private var remoteDataChannel: RTCDataChannel?
  
      @available(*, unavailable)
      override init() {
          fatalError(&quot;WebRTCClient:init is unavailable&quot;)
      }
      
      required init(iceServers: [String]) {
  
          let config = RTCConfiguration()
         // config.iceServers = [RTCIceServer(urlStrings: iceServers)]
          
          config.iceServers = [RTCIceServer(urlStrings:[&quot;//Turn server URL&quot;],
                                            username:&quot;username&quot;,
                                            credential:&quot;password&quot;)]
          
          
          // Unified plan is more superior than planB
          config.sdpSemantics = .unifiedPlan
          
          // gatherContinually will let WebRTC to listen to any network changes and send any new candidates to the other client
          config.continualGatheringPolicy = .gatherContinually
          
          let constraints = RTCMediaConstraints(mandatoryConstraints: nil,
                                                optionalConstraints: [&quot;DtlsSrtpKeyAgreement&quot;:kRTCMediaConstraintsValueTrue])
          self.peerConnection = WebRTCClient.factory.peerConnection(with: config, constraints: constraints, delegate: nil)
          
          super.init()
          self.createMediaSenders()
          self.configureAudioSession()
          self.peerConnection.delegate = self
      }
      
      // MARK: Signaling
      func offer(completion: @escaping (_ sdp: RTCSessionDescription) -&gt; Void) {
          let constrains = RTCMediaConstraints(mandatoryConstraints: self.mediaConstrains,
                                               optionalConstraints: nil)
          self.peerConnection.offer(for: constrains) { (sdp, error) in
              guard let sdp = sdp else {
                  return
              }
              
              self.peerConnection.setLocalDescription(sdp, completionHandler: { (error) in
                  completion(sdp)
              })
          }
      }
      
      func answer(completion: @escaping (_ sdp: RTCSessionDescription) -&gt; Void)  {
          let constrains = RTCMediaConstraints(mandatoryConstraints: self.mediaConstrains,
                                               optionalConstraints: nil)
          self.peerConnection.answer(for: constrains) { (sdp, error) in
              guard let sdp = sdp else {
                  return
              }
              
              self.peerConnection.setLocalDescription(sdp, completionHandler: { (error) in
                  completion(sdp)
              })
          }
      }
      
      func set(remoteSdp: RTCSessionDescription, completion: @escaping (Error?) -&gt; ()) {
          self.peerConnection.setRemoteDescription(remoteSdp, completionHandler: completion)
      }
      
      func set(remoteCandidate: RTCIceCandidate) {
          self.peerConnection.add(remoteCandidate)
      }
      
      // MARK: Media
      func startCaptureLocalVideo(renderer: RTCVideoRenderer, position : AVCaptureDevice.Position) {
          guard let capturer = self.videoCapturer as? RTCCameraVideoCapturer else {
              return
          }
  
          guard
              let frontCamera = (RTCCameraVideoCapturer.captureDevices().first { $0.position == position }),
              // choose highest res
              let format = (RTCCameraVideoCapturer.supportedFormats(for: frontCamera).sorted { (f1, f2) -&gt; Bool in
                  let width1 = CMVideoFormatDescriptionGetDimensions(f1.formatDescription).width
                  let width2 = CMVideoFormatDescriptionGetDimensions(f2.formatDescription).width
                  return width1 &lt; width2
              }).last,
          
              // choose highest fps
              let fps = (format.videoSupportedFrameRateRanges.sorted { return $0.maxFrameRate &lt; $1.maxFrameRate }.last) else {
              return
          }
  
          capturer.startCapture(with: frontCamera,
                                format: format,
                                fps: Int(fps.maxFrameRate))
          
          self.localVideoTrack?.add(renderer)
      }
      
      func startCaptureLocalTestVideo(renderer: RTCVideoRenderer, position : AVCaptureDevice.Position) {
          guard (self.videoCapturer as? RTCCameraVideoCapturer) != nil else {
              return
          }
  
          guard
              let frontCamera = (RTCCameraVideoCapturer.captureDevices().first { $0.position == position }),
              // choose highest res
              let format = (RTCCameraVideoCapturer.supportedFormats(for: frontCamera).sorted { (f1, f2) -&gt; Bool in
                  let width1 = CMVideoFormatDescriptionGetDimensions(f1.formatDescription).width
                  let width2 = CMVideoFormatDescriptionGetDimensions(f2.formatDescription).width
                  return width1 &lt; width2
              }).last,
          
              // choose highest fps
              let _ = (format.videoSupportedFrameRateRanges.sorted { return $0.maxFrameRate &lt; $1.maxFrameRate }.last) else {
              return
          }
  
          capturer.startCapture(with: frontCamera,
                                format: format,
                                fps: Int(fps.maxFrameRate))
          
          self.localVideoTrack?.add(renderer)
      }
      
      func renderRemoteVideo(to renderer: RTCVideoRenderer) {
          self.remoteVideoTrack?.add(renderer)
      }
      
      private func configureAudioSession() {
          self.rtcAudioSession.lockForConfiguration()
          do {
              try self.rtcAudioSession.setCategory(AVAudioSession.Category.playAndRecord.rawValue)
              try self.rtcAudioSession.setMode(AVAudioSession.Mode.voiceChat.rawValue)
          } catch let error {
              debugPrint(&quot;Error changeing AVAudioSession category: \(error)&quot;)
          }
          self.rtcAudioSession.unlockForConfiguration()
      }
      
      private func createMediaSenders() {
          let streamId = &quot;stream&quot;
          
          // Audio
          let audioTrack = self.createAudioTrack()
          self.peerConnection.add(audioTrack, streamIds: [streamId])
          
          // Video
          let videoTrack = self.createVideoTrack()
          self.localVideoTrack = videoTrack
          self.peerConnection.add(videoTrack, streamIds: [streamId])
          self.remoteVideoTrack = self.peerConnection.transceivers.first { $0.mediaType == .video }?.receiver.track as? RTCVideoTrack
          
          // Data
          if let dataChannel = createDataChannel() {
              dataChannel.delegate = self
              self.localDataChannel = dataChannel
          }
      }
      
      private func createAudioTrack() -&gt; RTCAudioTrack {
          let audioConstrains = RTCMediaConstraints(mandatoryConstraints: nil, optionalConstraints: nil)
          let audioSource = WebRTCClient.factory.audioSource(with: audioConstrains)
          let audioTrack = WebRTCClient.factory.audioTrack(with: audioSource, trackId: &quot;audio0&quot;)
          return audioTrack
      }
      
      private func createVideoTrack() -&gt; RTCVideoTrack {
          let videoSource = WebRTCClient.factory.videoSource()
          
          #if TARGET_OS_SIMULATOR
          self.videoCapturer = RTCFileVideoCapturer(delegate: videoSource)
          #else
          self.videoCapturer = RTCCameraVideoCapturer(delegate: videoSource)
          #endif
          
          let videoTrack = WebRTCClient.factory.videoTrack(with: videoSource, trackId: &quot;video0&quot;)
          return videoTrack
      }
      
      // MARK: Data Channels
      private func createDataChannel() -&gt; RTCDataChannel? {
          let config = RTCDataChannelConfiguration()
          guard let dataChannel = self.peerConnection.dataChannel(forLabel: &quot;WebRTCData&quot;, configuration: config) else {
              debugPrint(&quot;Warning: Couldn't create data channel.&quot;)
              return nil
          }
          return dataChannel
      }
      
      func sendData(_ data: Data) {
          let buffer = RTCDataBuffer(data: data, isBinary: true)
          self.remoteDataChannel?.sendData(buffer)
      }
  }
  
  extension WebRTCClient: RTCPeerConnectionDelegate {
      
      func peerConnection(_ peerConnection: RTCPeerConnection, didChange stateChanged: RTCSignalingState) {
          debugPrint(&quot;peerConnection new signaling state: \(stateChanged)&quot;)
      }
      
      func peerConnection(_ peerConnection: RTCPeerConnection, didAdd stream: RTCMediaStream) {
          debugPrint(&quot;peerConnection did add stream&quot;)
      }
      
      func peerConnection(_ peerConnection: RTCPeerConnection, didRemove stream: RTCMediaStream) {
          debugPrint(&quot;peerConnection did remote stream&quot;)
      }
      
      func peerConnectionShouldNegotiate(_ peerConnection: RTCPeerConnection) {
          debugPrint(&quot;peerConnection should negotiate&quot;)
      }
      
      func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceConnectionState) {
          debugPrint(&quot;peerConnection new connection state: \(newState)&quot;)
          self.delegate?.webRTCClient(self, didChangeConnectionState: newState)
      }
      
      func peerConnection(_ peerConnection: RTCPeerConnection, didChange newState: RTCIceGatheringState) {
          debugPrint(&quot;peerConnection new gathering state: \(newState)&quot;)
      }
      
      func peerConnection(_ peerConnection: RTCPeerConnection, didGenerate candidate: RTCIceCandidate) {
          self.delegate?.webRTCClient(self, didDiscoverLocalCandidate: candidate)
      }
      
      func peerConnection(_ peerConnection: RTCPeerConnection, didRemove candidates: [RTCIceCandidate]) {
          debugPrint(&quot;peerConnection did remove candidate(s)&quot;)
      }
      
      func peerConnection(_ peerConnection: RTCPeerConnection, didOpen dataChannel: RTCDataChannel) {
          debugPrint(&quot;peerConnection did open data channel&quot;)
          self.remoteDataChannel = dataChannel
      }
  }
  
  // MARK:- Audio control
  extension WebRTCClient {
      func muteAudio() {
        //  self.setAudioEnabled(false)
          swapCameraToFront()
      }
      
      func unmuteAudio() {
          self.setAudioEnabled(true)
      }
      
      func muteVideo() {
             self.setVideoEnabled(false)
      }
         
      func unmuteVideo() {
             self.setVideoEnabled(true)
         }
      
      
      // Fallback to the default playing device: headphones/bluetooth/ear speaker
      func speakerOff() {
          self.audioQueue.async { [weak self] in
              guard let self = self else {
                  return
              }
              
              self.rtcAudioSession.lockForConfiguration()
              do {
                  try self.rtcAudioSession.setCategory(AVAudioSession.Category.playAndRecord.rawValue)
                  try self.rtcAudioSession.overrideOutputAudioPort(.none)
              } catch let error {
                  debugPrint(&quot;Error setting AVAudioSession category: \(error)&quot;)
              }
              self.rtcAudioSession.unlockForConfiguration()
          }
      }
      
      // Force speaker
      func speakerOn() {
          self.audioQueue.async { [weak self] in
              guard let self = self else {
                  return
              }
              
              self.rtcAudioSession.lockForConfiguration()
              do {
                  try self.rtcAudioSession.setCategory(AVAudioSession.Category.playAndRecord.rawValue)
                  try self.rtcAudioSession.overrideOutputAudioPort(.speaker)
                  try self.rtcAudioSession.setActive(true)
              } catch let error {
                  debugPrint(&quot;Couldn't force audio to speaker: \(error)&quot;)
              }
              self.rtcAudioSession.unlockForConfiguration()
          }
      }
      
      private func setAudioEnabled(_ isEnabled: Bool) {
          
          let audioTracks = self.peerConnection.transceivers.compactMap { return $0.sender.track as? RTCAudioTrack }
          audioTracks.forEach { $0.isEnabled = isEnabled }
      }
      
      private func setVideoEnabled(_ isEnabled: Bool) {
          
          let videoTracks = self.peerConnection.transceivers.compactMap { return $0.sender.track as? RTCVideoTrack }
            videoTracks.forEach { $0.isEnabled = isEnabled }
          
      }
      
      func swapCameraToFront() {
          
          let localStream: RTCMediaStream? = peerConnection.localStreams.first
          localStream?.removeVideoTrack((localStream?.videoTracks.first)!)
          
          let localVideoTrack: RTCVideoTrack? = self.createVideoTrack()
          if localVideoTrack != nil {
              localStream?.addVideoTrack(localVideoTrack!)
            //  delegate?.appClient(self, didReceiveLocalVideoTrack: localVideoTrack!)
          }
          peerConnection.remove(localStream!)
          peerConnection.add(localStream!)
      }
  
      func swapCameraToBack() {
          let localStream: RTCMediaStream? = peerConnection.localStreams.first
          localStream?.removeVideoTrack((localStream?.videoTracks.first)!)
          let localVideoTrack: RTCVideoTrack? = self.createVideoTrack()
          if localVideoTrack != nil {
               localStream?.addVideoTrack(localVideoTrack!)
          }
          peerConnection.remove(localStream!)
          peerConnection.add(localStream!)
      }
      
  }
  
  extension WebRTCClient: RTCDataChannelDelegate {
      func dataChannelDidChangeState(_ dataChannel: RTCDataChannel) {
          debugPrint(&quot;dataChannel did change state: \(dataChannel.readyState)&quot;)
      }
      
      func dataChannel(_ dataChannel: RTCDataChannel, didReceiveMessageWith buffer: RTCDataBuffer) {
          self.delegate?.webRTCClient(self, didReceiveData: buffer.data)
      }
  }
  </code></pre>
  <p>ViewController.swift file</p>
  <pre><code>override func viewDidLoad() {
      super.viewDidLoad()
     let localRenderer = RTCMTLVideoView(frame: self.localVideoView?.frame ?? CGRect.zero
      let remoteRenderer = RTCMTLVideoView(frame: self.view.frame)
  
      self.view.insertSubview(localRenderer, at: 0)
      self.view.insertSubview(remoteRenderer, at: -1)
  
      self.webRTCClient.startCaptureLocalVideo(renderer: localRenderer, position: .front)
      self.webRTCClient.renderRemoteVideo(to: remoteRenderer)
  </code></pre>
  <p>}</p>
  <p>I am using below WEBRTC project
  <a href="https://github.com/stasel/WebRTC-iOS" rel="nofollow noreferrer">https://github.com/stasel/WebRTC-iOS</a></p>
  <p>More detailed app to demonstrate WebRTC:</p>