Skip to main content
Publishing allows the local participant to share their camera, microphone, or screen with other participants in the room.

Quick Start

The simplest way to publish tracks is using the convenience methods:
let room = Room()
try await room.connect(url: url, token: token)

// Publish camera
try await room.localParticipant.setCamera(enabled: true)

// Publish microphone
try await room.localParticipant.setMicrophone(enabled: true)

// Publish screen share
try await room.localParticipant.setScreenShare(enabled: true)

Publishing Camera

Basic Camera Publishing

// Enable camera with defaults
let publication = try await localParticipant.setCamera(enabled: true)

// Disable camera (mutes the track)
try await localParticipant.setCamera(enabled: false)

Camera with Custom Options

Specify capture and publish options:
let captureOptions = CameraCaptureOptions(
    position: .front,                    // .front or .back
    preferredFormat: .preset1920x1080,   // Video resolution preset
    preferredFPS: 30                      // Frame rate
)

let publishOptions = VideoPublishOptions(
    preferredCodec: .vp8,                 // Preferred video codec (.vp8, .h264, .av1)
    simulcast: true,                       // Enable simulcast
    degradationPreference: .maintainResolution,
    name: "camera",                        // Custom track name
    screenShareSimulcastLayers: []         // For screen share only
)

let publication = try await localParticipant.setCamera(
    enabled: true,
    captureOptions: captureOptions,
    publishOptions: publishOptions
)
Simulcast allows the server to send different quality levels to different subscribers based on their bandwidth and preferences.

Advanced: Manual Camera Track

For more control, create and publish tracks manually:
// Create camera track
let cameraTrack = LocalVideoTrack.createCameraTrack(
    options: CameraCaptureOptions(position: .front)
)

// Publish the track
let publication = try await localParticipant.publish(
    videoTrack: cameraTrack,
    options: VideoPublishOptions(simulcast: true)
)

Switching Camera

Switch between front and back camera:
if let cameraPublication = localParticipant.getTrackPublication(source: .camera) as? LocalTrackPublication,
   let cameraTrack = cameraPublication.track as? LocalVideoTrack,
   let cameraCapturer = cameraTrack.capturer as? CameraCapturer {
    
    try await cameraCapturer.switchCameraPosition()
}

Publishing Microphone

Basic Microphone Publishing

// Enable microphone
let publication = try await localParticipant.setMicrophone(enabled: true)

// Disable microphone (mutes the track)
try await localParticipant.setMicrophone(enabled: false)

Microphone with Custom Options

let captureOptions = AudioCaptureOptions(
    echoCancellation: true,
    noiseSuppression: true,
    autoGainControl: true,
    typingNoiseDetection: false,
    experimentalNoiseSuppression: false,
    experimentalAutoGainControl: false
)

let publishOptions = AudioPublishOptions(
    name: "microphone",
    encoding: AudioEncoding.presetMusic,  // Or .presetSpeech
    dtx: true,                             // Discontinuous transmission
    red: true                              // Redundant encoding
)

let publication = try await localParticipant.setMicrophone(
    enabled: true,
    captureOptions: captureOptions,
    publishOptions: publishOptions
)

Audio Encoding Presets

// For speech (lower bitrate)
let speechEncoding = AudioEncoding.presetSpeech  // 20 kbps

// For music (higher quality)
let musicEncoding = AudioEncoding.presetMusic    // 128 kbps

// Custom encoding
let customEncoding = AudioEncoding(
    maxBitrate: 64_000,  // 64 kbps
    priority: .high,
    networkPriority: .high
)
Enable DTX (Discontinuous Transmission) to save bandwidth when there’s no audio. Enable RED (Redundant Encoding) to improve reliability on poor networks.

Advanced: Manual Audio Track

// Create audio track
let audioTrack = LocalAudioTrack.createTrack(
    options: AudioCaptureOptions(
        echoCancellation: true,
        noiseSuppression: true
    )
)

// Publish the track
let publication = try await localParticipant.publish(
    audioTrack: audioTrack,
    options: AudioPublishOptions(dtx: true)
)

Publishing Screen Share

Screen sharing varies by platform due to OS limitations.

iOS Screen Share

iOS supports two modes:

In-App Screen Share

Captures only your app’s content:
let publication = try await localParticipant.setScreenShare(enabled: true)

System-Wide Screen Share (Broadcast Extension)

For capturing the entire screen including other apps:
  1. Create a Broadcast Upload Extension in Xcode
  2. Configure the extension with your app group
  3. Enable broadcasting:
// Configure room options to use broadcast extension
let roomOptions = RoomOptions(
    defaultScreenShareCaptureOptions: ScreenShareCaptureOptions(
        useBroadcastExtension: true
    )
)

let room = Room(roomOptions: roomOptions)

// This will prompt user to start broadcast
try await localParticipant.setScreenShare(enabled: true)
iOS system-wide screen sharing requires a Broadcast Upload Extension. In-app screen share only captures your app’s content.

macOS Screen Share

Capture displays or windows:
if #available(macOS 12.3, *) {
    // Capture main display
    let mainDisplay = try await MacOSScreenCapturer.mainDisplaySource()
    let screenTrack = LocalVideoTrack.createMacOSScreenShareTrack(
        source: mainDisplay,
        options: ScreenShareCaptureOptions()
    )
    
    let publication = try await localParticipant.publish(
        videoTrack: screenTrack
    )
    
    // Or use the convenience method
    try await localParticipant.setScreenShare(enabled: true)
}
Capture specific window:
let sources = try await MacOSScreenCapturer.sources()
for source in sources {
    if source.type == .window && source.name == "Safari" {
        let screenTrack = LocalVideoTrack.createMacOSScreenShareTrack(
            source: source,
            options: ScreenShareCaptureOptions()
        )
        try await localParticipant.publish(videoTrack: screenTrack)
        break
    }
}

Screen Share Options

let options = ScreenShareCaptureOptions(
    dimensions: Dimensions(width: 1920, height: 1080),
    fps: 15,
    useBroadcastExtension: false  // iOS only
)

Publish State

Check if a track is published:
if let publication = localParticipant.getTrackPublication(source: .camera) {
    print("Camera is published")
    print("Track name: \(publication.name)")
    print("Track SID: \(publication.sid)")
    print("Is muted: \(publication.isMuted)")
}

Unpublishing Tracks

Using Convenience Methods

Mutes the track but keeps it published:
// Mute camera
try await localParticipant.setCamera(enabled: false)

// Mute microphone
try await localParticipant.setMicrophone(enabled: false)
For screen share, it unpublishes:
// Stop and unpublish screen share
try await localParticipant.setScreenShare(enabled: false)

Manual Unpublishing

Completely unpublish a track:
if let publication = localParticipant.getTrackPublication(source: .camera) as? LocalTrackPublication {
    try await localParticipant.unpublish(publication: publication)
}
Unpublishing stops the track and removes it from the room. Other participants will receive an unpublish event.

Muting and Unmuting

Mute/unmute published tracks:
if let publication = localParticipant.getTrackPublication(source: .camera) as? LocalTrackPublication {
    // Mute the track
    try await publication.mute()
    
    // Unmute the track
    try await publication.unmute()
}
Or directly on the track:
if let track = publication.track as? LocalVideoTrack {
    try await track.mute()
    try await track.unmute()
}

Publishing Events

Monitor publish events:
class MyDelegate: RoomDelegate {
    func room(_ room: Room, participant: Participant, didPublishTrack publication: TrackPublication) {
        print("Published \(publication.source) track")
    }
    
    func room(_ room: Room, participant: Participant, didUnpublishTrack publication: TrackPublication) {
        print("Unpublished \(publication.source) track")
    }
}

Publishing Custom Tracks

Publish tracks from custom sources:
// Create a custom video source
let videoSource = RTC.createVideoSource(forScreenShare: false)
let capturer = MyCustomCapturer(videoSource: videoSource)

let customTrack = LocalVideoTrack(
    name: "custom",
    source: .camera,
    capturer: capturer,
    videoSource: videoSource,
    reportStatistics: false
)

try await localParticipant.publish(
    videoTrack: customTrack,
    options: VideoPublishOptions()
)

Publish Options Reference

VideoPublishOptions

VideoPublishOptions(
    preferredCodec: .vp8,               // Video codec
    preferredBackupCodec: .h264,         // Backup codec for simulcast
    simulcast: true,                     // Enable simulcast
    degradationPreference: .maintainResolution,
    name: "camera",                      // Track name
    streamName: nil,                     // Custom stream name
    screenShareSimulcastLayers: []       // Simulcast layers for screen share
)

AudioPublishOptions

AudioPublishOptions(
    name: "microphone",
    encoding: AudioEncoding.presetMusic,
    dtx: true,                           // Discontinuous transmission
    red: true,                           // Redundant encoding
    streamName: nil
)

Permissions

Before publishing, ensure the participant has the required permissions:
let permissions = localParticipant.permissions

if permissions.canPublish {
    if permissions.canPublishSources.contains(Track.Source.camera.rawValue) {
        try await localParticipant.setCamera(enabled: true)
    } else {
        print("Cannot publish camera - insufficient permissions")
    }
}
Attempting to publish without proper permissions will result in an error. Always check permissions before publishing.

Concurrent Publishing

When connecting with microphone enabled:
let connectOptions = ConnectOptions(
    enableMicrophone: true  // Publishes microphone concurrently during connect
)

try await room.connect(
    url: url,
    token: token,
    connectOptions: connectOptions
)

// Microphone will be published as soon as connection completes
This optimizes connection time by publishing the microphone track in parallel with the connection process.

Best Practices

  1. Use convenience methods: setCamera(), setMicrophone() handle common cases
  2. Enable simulcast: Improves quality adaptation for video tracks
  3. Set appropriate encoding: Use speech preset for voice, music for higher quality
  4. Check permissions: Verify permissions before attempting to publish
  5. Handle failures: Wrap publish calls in try-catch blocks
  6. Unpublish when done: Stop tracks to release resources (camera, microphone)
  7. Mute instead of unpublish: For temporary silence, mute is faster than unpublish/publish
  8. Monitor publish state: Check publication state before attempting operations

Build docs developers (and LLMs) love