Skip to content

@corewire/airplay ​

ts
import { AirPlaySession, connect, pair } from '@corewire/airplay';

See Sessions and channels and URLs and audio streams.

Sessions ​

ExportDescription
connect(target: AirPlayTarget, options?: ConnectOptions): Promise<AirPlaySession>TCP, GET /info, pair-verify or a transient pairing, then encryption. Throws PairingError, ConnectionClosedError
pair(target: AirPlayTarget, options?: PairOptions): Promise<PinPairing>Starts a PIN pairing; the receiver shows its PIN once this resolves

AirPlaySession ​

class AirPlaySession extends EventEmitter<{closed: [reason: SessionCloseReason, error?: Error]}>

MemberDescription
closeReason: SessionCloseReason | undefinedWhy the session ended; undefined while open
context: Context
controlThe encrypted RTSP control channel; see ControlChannel
features: bigintThe feature mask from /info
info: Record<string, any>The /info plist
isOpen: boolean
pairingId: BufferOur pairing identifier
remoteControl: RemoteControlChannels | undefinedThe data and event channels once remote control is open
sessionUUID: stringIdentifies the session in SETUP bodies
signal: AbortSignalAborts with a ConnectionClosedError when the session ends
supportsUrlPlayback: booleanWhether the receiver plays a URL itself
target: AirPlayTarget
hasFeature(feature: bigint): boolean
close(): Promise<void>Ends the session and every connection on it, without TEARDOWN
openRemoteControl(options?: RemoteControlOptions): Promise<RemoteControlChannels>SETUP, event channel, RECORD, data channel and keepalive. Throws SetupError, ConnectionClosedError
playUrl(url: string, options?: PlayUrlOptions): Promise<UrlPlayback>URL playback on a connection of its own. Throws PlaybackError, SetupError
streamAudio(source: AudioSource, options?: StreamAudioOptions): Promise<AudioPlayback>An audio stream on a connection of its own. Throws SetupError
event closedFires once, with the reason and an optional error

The constructor takes the parts of a finished handshake. Use connect().

PinPairing ​

MemberDescription
signal: AbortSignalAborts when the pairing connection closes
pin(code: string): Promise<AccessoryCredentials>Runs pair-setup M1 to M6. A wrong PIN rejects with PairingError and leaves the connection open
close(): voidCloses the pairing connection

Channels ​

ControlChannel ​

class ControlChannel extends RtspClient, the type of AirPlaySession.control. The entry point does not export the class. Reach it through a session.

MemberDescription
activeRemoteId: string, dacpId: string, sessionId: stringThe session headers
isEncrypted: boolean
pendingTeardown: Promise<void> | undefinedSettles once the TEARDOWN in flight is answered, times out or loses its connection
enableEncryption(readKey: Buffer, writeKey: Buffer): voidSwitches to encrypted frames
command(streamId: number, payload: Record<string, unknown> | ArrayBuffer, sessionUUID: string): Promise<Response>A play queue command as POST /command
feedback(stats?: Record<string, unknown>): Promise<Response>The keepalive
flush(uri: string, headers: Record<string, string>): Promise<Response>
flushBuffered(uri: string, headers?: Record<string, string>): Promise<Response>
get(path, headers?, timeout?), post(path, body?, headers?, timeout?), put(path, body?, headers?, timeout?), record(path, headers?, timeout?), setup(path, body?, headers?, timeout?): Promise<Response>Plain requests
getProperty(property: string): Promise<Response>Reads a receiver property such as Volume or ClusterUUID
setProperty(property: string, body?): Promise<Response>Writes a receiver property
scrub(position: number): Promise<Response>Seeks URL playback
setAudioMode(mode: string): Promise<Response>Such as default, moviePlayback or spoken
setParameter(parameter: string, value: string): Promise<Response>SET_PARAMETER with a text/parameters body
setVolume(volume: number): Promise<Response>
stop(): Promise<Response>Stops URL playback
teardown(path: string, headers?, timeout?): Promise<Response | undefined>Ends the session on the receiver; undefined when the receiver closed the connection first

Requests resolve with any status. The caller checks it.

DataChannel ​

class DataChannel extends EncryptedChannel<DataChannelEventMap>

MemberDescription
constructor(context: Context, address: string, port: number, sharedSecret: Buffer, seed: bigint)
exchange(message: ProtocolMessage | [ProtocolMessage, DescExtension], timeout?: number): Promise<ProtocolMessage>Resolves with the reply with the same identifier. Throws TimeoutError, ConnectionClosedError
send(message: ProtocolMessage | [ProtocolMessage, DescExtension]): voidSends without waiting. Throws ConnectionClosedError
eventsrawMessage for every message, then one event per MRP message name with the decoded extension

EventChannel ​

class EventChannel extends EncryptedChannel<EventChannelEventMap>

MemberDescription
constructor(context: Context, address: string, port: number, sharedSecret: Buffer)
respond(status: number, statusText: string, headers?, body?: Buffer): voidAnswers a request of the receiver
event command: [data: Record<string, unknown>]Any POST /command
event playQueue: [event: PlayQueueCommand]A decoded play queue command
event duckAudio, unduckAudio: [data: Record<string, unknown>]
event sessionDied: []The receiver ended the session

Playback ​

UrlPlayback and AudioPlayback share one shape for waiting until playback is done: ended: Promise<void>. RAOP and @corewire/devices use the same shape.

UrlPlayback ​

MemberDescription
signal: AbortSignalAborts when the playback's connection closes
static start(channels, url: string, position: number): Promise<UrlPlayback>Used by AirPlaySession.playUrl(); the constructor is private
ended: Promise<void>Resolves when playback ends, then closes the connection; rejects with PlaybackError when the receiver reports an error. Lazy: the first read starts watching the play queue, or polling /playback-info on a receiver without one
getPlaybackInfo(): Promise<PlaybackInfo | null>Empty when nothing plays, null when the receiver did not answer
stop(): voidStops playback and closes the connection, without TEARDOWN

AudioPlayback ​

AirPlaySession.streamAudio() creates it. The constructor takes internal parts.

MemberDescription
ended: Promise<void>Resolves when the source ran out and TEARDOWN went out; rejects when the stream failed or was stopped halfway
signal: AbortSignal
stats: AudioStreamStats
stop(): voidStops the source and closes the connection, without TEARDOWN

Timing ​

TimingServer ​

MemberDescription
constructor(options?: {logger?: Logger})Errors of the socket go to logger
port: numberThe UDP port; 0 until bound
listen(): Promise<void>Binds a random port
close(): void
onMessage(data: Buffer, info: RemoteInfo): void, onError(err: Error): voidSocket handlers

PtpMaster ​

Experimental IEEE 1588 grandmaster for receivers with PTP support.

MemberDescription
constructor(peerAddress: string)
start(): Promise<{eventPort: number; generalPort: number}>Binds 319 and 320, or ephemeral ports without permission
stop(): void
state: 'idle' | 'master' | 'stopped'
eventPort, generalPort, clockIdentity, syncsSent, announcesSent, delayReqsReceived, delayRespsSent, announcesReceivedPorts and counters
onEventPacket(), onGeneralPacket(), onEventError(), onGeneralError()Socket handlers

Discovery and features ​

ExportDescription
AIRPLAY_SERVICE'_airplay._tcp.local'
discover(options?: DiscoverOptions): Promise<AirPlayDiscoveryResult[]>The AirPlay receivers on the network; DiscoverOptions comes from @corewire/core/discovery
toTarget(result: DiscoveryResult): AirPlayTargetThe connect target for a result
withFeatures(result: DiscoveryResult): AirPlayDiscoveryResultAdds the parsed feature mask
parseFeatures(features: string): bigintReads a features TXT value: one hex value or low,high
AirPlayFeatureFlags: Record<string, bigint>Feature flags by name
hasFeatureFlag(features: bigint, flag: bigint): boolean
describeFlags(features: bigint): AirPlayFeatureFlagName[]The names of the flags that are set
getProtocolVersion(txt: Record<string, string>): 1 | 2
getPairingRequirement(txt: Record<string, string>): PairingRequirement'none' | 'pin' | 'transient' | 'homekit'
isPasswordRequired(txt: Record<string, string>): boolean
isRemoteControlSupported(txt: Record<string, string>): boolean
generateActiveRemoteId(): string, generateDacpId(): string, generateSessionId(): stringRandom identifiers for RTSP headers
AUDIO_SAMPLE_RATE (44100), AUDIO_CHANNELS (2), AUDIO_BYTES_PER_CHANNEL (2)The audio format

Types ​

TypeDefinition
AirPlayTarget{address: string; port?: number; id?: string; txt?: Readonly<Record<string, string>>; features?: bigint}
ConnectOptions{credentials?: AccessoryCredentials; context?: Context; identity?: DeviceIdentity; timing?: TimingServer; signal?: AbortSignal}
PairOptionsPick<ConnectOptions, 'context' | 'identity' | 'signal'>
SessionCloseReason'local' | 'remote' | 'timeout' | 'error'
RemoteControlChannels{data: DataChannel; events: EventChannel}
RemoteControlOptions{subscribe?(data: DataChannel): void}
PlayUrlOptions{position?: number; signal?: AbortSignal}
StreamAudioOptions{volumeDb?: number; ptpMaster?: PtpMaster; signal?: AbortSignal}
PlaybackInfo{duration?; position?; rate?; readyToPlay?; error?: {code; domain}; state?: PlayQueueState}
PlayQueueCommandRecord<string, unknown>
PlayQueueState'loading' | 'playing' | 'paused' | 'stopped'
AudioSource{duration: number; readFrames(count: number): Promise<Buffer | null>; reset(); start(); stop()}; 44100 Hz stereo signed 16-bit big-endian PCM
AudioStreamStats{packetsSent; retransmitRequests; retransmitsFulfilled; retransmitsFailed; packetLossRate; totalBytesSent}
DataChannelEventMaprawMessage plus one entry per MrpMessageName
EventChannelEventMapcommand, playQueue, duckAudio, unduckAudio, sessionDied
AirPlayDiscoveryResultDiscoveryResult & {features?: bigint}
AirPlayFeatureFlagNamestring
PairingRequirement'none' | 'pin' | 'transient' | 'homekit'

Client identity ​

identity in ConnectOptions and PairOptions overrides what the client presents. A field you leave out keeps its default. The defaults are name CoreWire (DEFAULT_CLIENT_NAME of @corewire/core/transport), model iPhone17,3, OS iPhone OS 26.3 (25D63), source version 935.7.1, bundle com.apple.TVRemote version 700. The name goes into the SETUP body, the URL playback body, the MRP device info and the pair-setup M5 message.

CoreWire is released under the MIT license.