Skip to main content
Dynacast (Dynamic Broadcasting) is a powerful feature that automatically pauses video layers that aren’t being consumed by any subscribers. This significantly reduces publishing CPU and bandwidth usage without manual intervention.

Overview

When dynacast is enabled, the LiveKit server monitors which video layers each subscriber is actually receiving and sends updates to the publisher. The publisher then automatically pauses unused layers, reducing:
  • Encoding CPU usage
  • Upstream bandwidth consumption
  • Battery drain on mobile devices
Layers are automatically resumed when subscribers request them again.

Enabling Dynacast

Enable dynacast in RoomOptions:
import LiveKit

let roomOptions = RoomOptions(
    dynacast: true
)

let room = Room(roomOptions: roomOptions)

How It Works

  1. Publisher sends multiple simulcast layers
  2. Server tracks which layers each subscriber is receiving
  3. Server sends updates to publisher about layer demand
  4. Publisher pauses encoding of unused layers
  5. When demand increases, publisher resumes paused layers
// Example: Publisher sends 3 layers (high, medium, low)
// - Subscriber A receives high quality
// - Subscriber B receives medium quality
// - No subscriber receives low quality
// → Publisher pauses encoding low quality layer

Benefits

CPU Savings

Dynacast reduces encoding workload when not all layers are needed:
// Without dynacast: Always encode all 3 layers
// CPU usage: 100%

// With dynacast: Only encode consumed layers
// If only high quality is subscribed:
// CPU usage: ~40% (encoding 1 of 3 layers)

Bandwidth Savings

Upstream bandwidth is reduced proportionally:
// Example bitrates:
// High:   2.0 Mbps
// Medium: 1.0 Mbps
// Low:    0.5 Mbps

// Without dynacast: 3.5 Mbps total
// With dynacast (only high subscribed): 2.0 Mbps
// Savings: ~43%

Requirements

Dynacast requires simulcast to be enabled. Ensure VideoPublishOptions.simulcast is set to true (default).
let videoOptions = VideoPublishOptions(
    simulcast: true  // Required for dynacast
)

let roomOptions = RoomOptions(
    dynacast: true,
    defaultVideoPublishOptions: videoOptions
)

Monitoring Dynacast

Track which layers are active using statistics:
class MyTrackDelegate: TrackDelegate {
    func track(
        _ track: Track,
        didUpdateStatistics statistics: TrackStatistics,
        simulcastStatistics: [VideoCodec: TrackStatistics]
    ) {
        for stream in statistics.outboundRtpStream {
            let isActive = stream.active ?? false
            let rid = stream.rid ?? "unknown"
            let bytesSent = stream.bytesSent ?? 0
            
            print("Layer \(rid): active=\(isActive), bytes=\(bytesSent)")
        }
    }
}

Adaptive Stream

Adaptive Stream is a complementary feature that optimizes the subscriber side by automatically adjusting quality based on video view size and visibility.

Enabling Adaptive Stream

let roomOptions = RoomOptions(
    adaptiveStream: true,
    dynacast: true  // Use both for maximum optimization
)

let room = Room(roomOptions: roomOptions)

How Adaptive Stream Works

  1. SDK monitors size and visibility of video views
  2. Automatically requests appropriate quality layer
  3. Pauses video when view is not visible
  4. Resumes video when view becomes visible
// View is 1920×1080 → Request high quality
// View is 640×360   → Request medium quality
// View is hidden    → Pause video (dimensions: 0×0)

Automatic Quality Management

// With adaptiveStream enabled:
let roomOptions = RoomOptions(adaptiveStream: true)
let room = Room(roomOptions: roomOptions)

// VideoView automatically manages quality
let videoView = VideoView()
videoView.track = remoteVideoTrack

// When view size changes or becomes hidden,
// SDK automatically adjusts subscribed quality
When adaptiveStream is enabled, you cannot manually set video quality using set(videoQuality:) or set(preferredDimensions:). The SDK manages quality automatically based on view size.

Manual Quality Control

To manually control quality, disable adaptive stream:
let roomOptions = RoomOptions(
    adaptiveStream: false,
    dynacast: true  // Dynacast still works
)

// Now you can manually set quality
try await remotePublication.set(videoQuality: .high)
try await remotePublication.set(preferredDimensions: Dimensions(width: 1920, height: 1080))

Best Practices

1. Enable Both Features

For optimal performance, enable both dynacast and adaptive stream:
let roomOptions = RoomOptions(
    dynacast: true,        // Optimize publisher
    adaptiveStream: true   // Optimize subscriber
)

2. Use with Simulcast

Always enable simulcast for dynacast to work:
let videoOptions = VideoPublishOptions(simulcast: true)
let roomOptions = RoomOptions(
    dynacast: true,
    defaultVideoPublishOptions: videoOptions
)

3. Monitor Quality Limitations

Track why quality is being limited:
func track(_ track: Track, didUpdateStatistics statistics: TrackStatistics, simulcastStatistics: [VideoCodec: TrackStatistics]) {
    for stream in statistics.outboundRtpStream {
        if let reason = stream.qualityLimitationReason {
            switch reason {
            case .bandwidth:
                print("Layer \(stream.rid ?? ""): Limited by bandwidth")
            case .cpu:
                print("Layer \(stream.rid ?? ""): Limited by CPU")
            default:
                break
            }
        }
    }
}

4. Handle State Changes

Layers may activate/deactivate based on subscriber demand:
func track(_ track: Track, didUpdateStatistics statistics: TrackStatistics, simulcastStatistics: [VideoCodec: TrackStatistics]) {
    let activeLayers = statistics.outboundRtpStream
        .filter { $0.active == true }
        .count
    
    print("Active layers: \(activeLayers) / \(statistics.outboundRtpStream.count)")
}

Use Cases

Large Conferences

// 20 participants in a grid
// - Active speaker shown in large view: high quality
// - Others in small tiles: low quality
// - Dynacast: Publisher only encodes high + low (not medium)
// - Adaptive stream: Each subscriber requests appropriate layer

Screen Sharing

// Screen share with 10 viewers
// - 5 viewers in fullscreen: high quality
// - 5 viewers in thumbnail: low quality
// - Dynacast: Only encode high + low layers
// - Saves ~33% CPU and bandwidth

Mobile Battery Optimization

// Publisher on mobile device
// - Room has 10 participants
// - Only 3 are actively viewing this track
// - Dynacast reduces encoding to only needed layers
// - Significant battery savings

Comparison: Dynacast vs Adaptive Stream

FeatureDynacastAdaptive Stream
OptimizesPublisher sideSubscriber side
ReducesEncoding CPU & upstream bandwidthDownstream bandwidth
Based onSubscriber demandView size & visibility
RequiresSimulcast enabledVideo views attached
Works withAll video tracksRemote video tracks

Troubleshooting

Dynacast Not Working

  1. Verify simulcast is enabled:
let publication = room.localParticipant.videoTracks.first?.value
print("Simulcast enabled: \(publication?.isSimulcasted ?? false)")
  1. Check room options:
print("Dynacast enabled: \(room._state.roomOptions.dynacast)")

Adaptive Stream Not Working

  1. Verify adaptive stream is enabled:
print("Adaptive stream enabled: \(room._state.roomOptions.adaptiveStream)")
  1. Ensure views are properly configured:
let videoView = VideoView()
videoView.track = track
print("Adaptive stream size: \(videoView.adaptiveStreamSize)")

Performance Metrics

Typical savings with dynacast enabled:
  • CPU usage: 30-60% reduction when not all layers are subscribed
  • Upstream bandwidth: Proportional to unused layers (up to 60% for single layer)
  • Battery life: 20-40% improvement on mobile devices
Results vary based on:
  • Number of simulcast layers
  • Subscriber distribution across layers
  • Video content complexity
  • Device capabilities

See Also

  • Simulcast - Multiple quality layer publishing
  • Statistics - Monitor track performance
  • Source code: Sources/LiveKit/Core/Room+SignalClientDelegate.swift:74

Build docs developers (and LLMs) love