<!--
{
  "documentType" : "article",
  "framework" : "AVSystemRouting",
  "identifier" : "/documentation/AVSystemRouting/routing-media-to-third-party-devices",
  "metadataVersion" : "0.1.0",
  "role" : "article",
  "title" : "Routing media to third-party devices"
}
-->

# Routing media to third-party devices

Respond to routing events and control playback on a TV, speaker, or other media device.

## Overview

When people select a media device, your app receives a
routing event. You respond by creating a session that delivers media to that device. This article
describes the full workflow, from registering an observer to controlling remote playback.

## Register an observer

People can switch media routes at any time by selecting a device. To respond, register an observer
with the shared [`AVSystemRouteController`](/documentation/AVSystemRouting/AVSystemRouteController-18ns8).

Your observer conforms to [`AVSystemRouteControllerObserver`](/documentation/AVSystemRouting/AVSystemRouteControllerObserver-5syvg). When the controller activates
or deactivates a route, it calls your observer with an event, and your observer returns a Boolean
that indicates whether it handled the event:

```swift
class RouteObserver: AVSystemRouteControllerObserver {
    func systemRouteController(
        _ controller: AVSystemRouteController,
        handle event: AVSystemRouteEvent
    ) async -> Bool {
        switch event.reason {
        case .activate:
            return await startPlayback(on: event.route)
        case .deactivate:
            stopPlayback()
            return true
        @unknown default:
            return false
        }
    }
}

let observer = RouteObserver()
_ = AVSystemRouteController.shared.addObserver(observer)
```

## Start a URL-based playback session

When you receive an [`AVSystemRouteEvent`](/documentation/AVSystemRouting/AVSystemRouteEvent-2elr5) with an
[`AVSystemRouteEvent.Reason.activate`](/documentation/AVSystemRouting/AVSystemRouteEvent-2elr5/Reason-swift.enum/activate) reason, create an [`AVSystemRouteSession`](/documentation/AVSystemRouting/AVSystemRouteSession-gp78)
and start it on the route. Use [`AVSystemRoute.LaunchMode.player`](/documentation/AVSystemRouting/AVSystemRoute-5s2um/LaunchMode/player) to launch the system media
player on the remote device:

```swift
func startPlayback(on route: AVSystemRoute) async -> Bool {
    let url = URL(string: "https://example.com/media.m3u8")!
    let session = AVSystemRouteSession(url: url, mode: .player)

    guard route.addSession(session) else {
        return false
    }

    do {
        let mediaSession = try await session.start()
        // Use `mediaSession.playbackControl` to build a playback UI.
        return true
    } catch {
        route.removeSession(session)
        return false
    }
}
```

The [`start()`](/documentation/AVSystemRouting/AVSystemRouteSession-gp78/start()) method returns an [`AVSystemRouteMediaSession`](/documentation/AVSystemRouting/AVSystemRouteMediaSession-98ioq)
that your app uses to control playback and communicate with the remote device.

## Control and observe remote playback

Use the [`playbackControl`](/documentation/AVSystemRouting/AVSystemRouteMediaSession-98ioq/playbackControl) property to drive playback on the
remote device and observe its state. The control conforms to `AVInterfaceControllable` and is
available for both [`AVSystemRoute.LaunchMode.player`](/documentation/AVSystemRouting/AVSystemRoute-5s2um/LaunchMode/player) and [`AVSystemRoute.LaunchMode.application`](/documentation/AVSystemRouting/AVSystemRoute-5s2um/LaunchMode/application)
sessions.

Read properties to reflect remote playback state in your UI, and assign to mutable properties
to send commands to the remote device:

```swift
func configurePlayback(for mediaSession: AVSystemRouteMediaSession) {
    guard let control = mediaSession.playbackControl else { return }

    // Read state to drive your UI.
    let title = control.metadata.title
    let duration = control.timeRange.duration
    let isPlaying = control.isPlaying

    // Send commands to the remote device.
    control.currentPlaybackPosition = CMTime(seconds: 30, preferredTimescale: 600)
    control.playbackSpeed = 1.0
    control.volume = 0.8
}
```

The `playbackControl` property supports <doc://com.apple.documentation/documentation/Observation> in Swift, allowing SwiftUI views to update automatically
as the remote playback state changes.

Report your playback metadata to `MPNowPlayingInfoCenter` so the system displays accurate now-playing
information across the person’s devices.

## Start an application session

Some streaming devices run dedicated apps on the remote device. Rather than playing
a URL through the system media player, launch your companion app on the device and
communicate with it directly. To enable bidirectional communication with the companion
app through a data channel, use [`AVSystemRoute.LaunchMode.application`](/documentation/AVSystemRouting/AVSystemRoute-5s2um/LaunchMode/application) when creating
the session:

```swift
let session = AVSystemRouteSession(url: url, mode: .application)
```

After starting an application session, use the [`AVSystemRouteMediaSession`](/documentation/AVSystemRouting/AVSystemRouteMediaSession-98ioq) class’s [`dataChannel`](/documentation/AVSystemRouting/AVSystemRouteMediaSession-98ioq/dataChannel)
to exchange data with the remote device. Assign an [`AVSystemRouteDataDelegate`](/documentation/AVSystemRouting/AVSystemRouteDataDelegate-7vt4b)
to receive incoming data:

```swift
class DataHandler: AVSystemRouteDataDelegate {
    func receive(_ data: Data) async throws {
        // Process data from the remote device.
    }
}

let handler = DataHandler()

func configureDataChannel(_ mediaSession: AVSystemRouteMediaSession) {
    guard let dataChannel = mediaSession.dataChannel else { return }
    dataChannel.dataDelegate = handler
}
```

Send data to the remote device using the channel’s [`send(_:)`](/documentation/AVSystemRouting/AVSystemRoute-5s2um/DataChannel/send(_:))
method:

```swift
func sendCommand(_ command: Data, to mediaSession: AVSystemRouteMediaSession) async throws {
    guard let dataChannel = mediaSession.dataChannel else { return }
    try await dataChannel.send(command)
}
```

---

Copyright &copy; 2026 Apple Inc. All rights reserved. | [Terms of Use](https://www.apple.com/legal/internet-services/terms/site.html) | [Privacy Policy](https://www.apple.com/privacy/privacy-policy)