Table of Contents

Class CompanionApi

Namespace
AppleTvControlLibrary.Protocol
Assembly
AppleTvControlLibrary.dll

High level implementation of the Companion API: system info, session lifecycle, HID input, media control (volume), attention state, app listing/launching, and account listing/switching.

public sealed class CompanionApi : ICompanionProtocolListener
Inheritance
CompanionApi
Implements
Inherited Members

Remarks

Text input is intentionally out of scope for this port (Companion-only, per the porting brief).

Constructors

CompanionApi(CompanionProtocol, HapCredentials, string, string, string, string)

Initializes a new instance of the CompanionApi class.

public CompanionApi(CompanionProtocol protocol, HapCredentials credentials, string stableIdentifier, string deviceId, string model, string name)

Parameters

protocol CompanionProtocol

The underlying Companion protocol instance.

credentials HapCredentials

The paired credentials, used to build the _idsID system info field.

stableIdentifier string

A stable, persisted identifier for the _systemInfo _i field (typically six random bytes, hex-encoded, generated once at pair time and never regenerated -- see porting brief WP6 notes on _i).

deviceId string

The device identifier reported as _pubID.

model string

The device model string reported in _systemInfo.

name string

The device name string reported in _systemInfo.

Properties

CurrentSystemStatus

Gets the most recently known system status (power state), updated by the initial FetchAttentionState() snapshot taken during Connect() and by subsequently pushed SystemStatus/TVSystemStatus events.

public SystemStatus CurrentSystemStatus { get; }

Property Value

SystemStatus

DeviceId

Gets the device identifier reported as _pubID.

public string DeviceId { get; }

Property Value

string

IsVolumeControlSupported

Gets a value indicating whether the device currently advertises volume control support via the _iMC event's _mcF bitmask (Volume). When this is false, audio is managed outside Companion (e.g. HDMI-CEC) and GetVolume()/SetVolume(double) must not be used.

public bool IsVolumeControlSupported { get; }

Property Value

bool

Model

Gets the device model string.

public string Model { get; }

Property Value

string

Name

Gets the device name string.

public string Name { get; }

Property Value

string

Sid

Gets the combined session id established by SessionStart().

public long Sid { get; }

Property Value

long

StableIdentifier

Gets the stable identifier used as the _systemInfo _i field.

public string StableIdentifier { get; }

Property Value

string

TextFocusState

Gets the current keyboard (RTI text input) focus state, as last reported by the _tiStarted/_tiStopped events or the _tiStart response.

public KeyboardFocusState TextFocusState { get; }

Property Value

KeyboardFocusState

Methods

AccountList()

Fetch the list of user accounts that can be switched to on the device, as a mapping of account identifier to display name.

[Obsolete("Use AccountListAsync instead.")]
public Dictionary<string, string> AccountList()

Returns

Dictionary<string, string>

A mapping of account identifier to display name. Like AppList(), this is not guaranteed to be populated on every device/tvOS combination, so callers must treat an empty (or missing) result as a normal outcome rather than an error.

AccountListAsync()

Asynchronously fetches the list of accounts.

public Task<Dictionary<string, string>> AccountListAsync()

Returns

Task<Dictionary<string, string>>

AccountListRaw()

Diagnostic-only: fetch the raw, untyped _c content of a FetchUserAccountsEvent response, with no coercion to Dictionary<TKey, TValue> of string/string. AccountList() silently drops any entry whose value is not a plain string, which would hide a richer per-account payload (e.g. a nested dict carrying a "current"/"active" flag) if the device ever sends one. This exists to let that be checked against real hardware rather than assumed from the pyatv source, per the brief's rule 2 (do not invent, do not infer past what the source says).

[Obsolete("Use AccountListRawAsync instead.")]
public Dictionary<object, object?> AccountListRaw()

Returns

Dictionary<object, object>

The raw _c content, or an empty dictionary if missing/malformed.

AccountListRawAsync()

Asynchronously fetches the raw account list response content.

public Task<Dictionary<object, object?>> AccountListRawAsync()

Returns

Task<Dictionary<object, object>>

AppList()

Fetch the list of launchable apps on the device, as a mapping of bundle identifier to display name.

[Obsolete("Use AppListAsync instead.")]
public Dictionary<string, string> AppList()

Returns

Dictionary<string, string>

A mapping of bundle identifier to display name. This is the only Companion feature with a confirmed history of returning empty on some tvOS point releases, so callers must treat an empty (or missing) result as a normal outcome rather than an error and must not hard-depend on the list being non-empty.

AppListAsync()

Asynchronously fetches the list of launchable apps.

public Task<Dictionary<string, string>> AppListAsync()

Returns

Task<Dictionary<string, string>>

Connect()

Runs the connection bring-up sequence: system info, touch subscription, session start, TV Remote Client session start, and text input session start.

[Obsolete("Use ConnectAsync instead.")]
public void Connect()

ConnectAsync()

Asynchronously runs the connection bring-up sequence.

public Task ConnectAsync()

Returns

Task

FetchAttentionState()

Fetch the current attention state (system status) from the device.

[Obsolete("Use FetchAttentionStateAsync instead.")]
public SystemStatus FetchAttentionState()

Returns

SystemStatus

The current SystemStatus.

FetchAttentionStateAsync()

Asynchronously fetches the current attention state.

public Task<SystemStatus> FetchAttentionStateAsync()

Returns

Task<SystemStatus>

GetVolume()

Gets the current volume level, in percent ([0.0-100.0]).

[Obsolete("Use GetVolumeAsync instead.")]
public double GetVolume()

Returns

double

GetVolumeAsync()

Asynchronously gets the current volume level.

public Task<double> GetVolumeAsync()

Returns

Task<double>

LaunchApp(string)

Launch an app on the device, by bundle identifier or by URL/URL scheme (for deep-linking into content rather than opening an app cold).

[Obsolete("Use LaunchAppAsync instead.")]
public void LaunchApp(string bundleIdOrUrl)

Parameters

bundleIdOrUrl string

A bundle identifier (e.g. com.apple.TVWatchList), or a URL/URL scheme to open.

LaunchAppAsync(string)

Asynchronously launches an app on the device.

public Task LaunchAppAsync(string bundleIdOrUrl)

Parameters

bundleIdOrUrl string

Returns

Task

MediaControlCommand(MediaControlCommand, Dictionary<string, object?>?)

Send a media control command to the device.

[Obsolete("Use MediaControlCommandAsync instead.")]
public Dictionary<object, object?> MediaControlCommand(MediaControlCommand command, Dictionary<string, object?>? args = null)

Parameters

command MediaControlCommand

The media control command to send.

args Dictionary<string, object>

Additional command-specific arguments, if any.

Returns

Dictionary<object, object>

The decoded response content (the message's _c field).

MediaControlCommandAsync(MediaControlCommand, Dictionary<string, object?>?)

Asynchronously sends a media control command.

public Task<Dictionary<object, object?>> MediaControlCommandAsync(MediaControlCommand command, Dictionary<string, object?>? args = null)

Parameters

command MediaControlCommand
args Dictionary<string, object>

Returns

Task<Dictionary<object, object>>

SendClick(InputAction)

Send a touch click (tap on the touch surface, distinct from a directional-pad Select press). This is what pyatv's remote-widget "select" gesture actually sends when driven from a touchpad rather than a D-pad: a Select button press/release (button code 6) followed by a touch Click event in the bottom-right corner of the touch surface.

[Obsolete("Use SendClickAsync instead.")]
public void SendClick(InputAction action)

Parameters

action InputAction

The click gesture: single tap, double tap, or press-and-hold.

SendClickAsync(InputAction)

Asynchronously sends a touch click.

public Task SendClickAsync(InputAction action)

Parameters

action InputAction

Returns

Task

SendHidCommand(bool, HidCommand)

Send a HID command.

[Obsolete("Use SendHidCommandAsync instead.")]
public void SendHidCommand(bool down, HidCommand command)

Parameters

down bool

true for a button-down event, false for button-up.

command HidCommand

The button being pressed or released.

SendHidCommandAsync(bool, HidCommand)

Asynchronously sends a HID command.

public Task SendHidCommandAsync(bool down, HidCommand command)

Parameters

down bool
command HidCommand

Returns

Task

SendHidEvent(int, int, TouchAction)

Send a touch event.

[Obsolete("Use SendHidEventAsync instead.")]
public void SendHidEvent(int x, int y, TouchAction mode)

Parameters

x int

The x coordinate, in the range [0, 1000].

y int

The y coordinate, in the range [0, 1000].

mode TouchAction

The touch phase.

SendHidEventAsync(int, int, TouchAction)

Asynchronously sends a touch event.

public Task SendHidEventAsync(int x, int y, TouchAction mode)

Parameters

x int
y int
mode TouchAction

Returns

Task

SessionStart()

Start a Companion session.

[Obsolete("Use SessionStartAsync instead.")]
public void SessionStart()

SessionStartAsync()

Asynchronously starts a Companion session.

public Task SessionStartAsync()

Returns

Task

SessionStop()

Stop the current Companion session.

[Obsolete("Use SessionStopAsync instead.")]
public void SessionStop()

SessionStopAsync()

Asynchronously stops the current Companion session.

public Task SessionStopAsync()

Returns

Task

SetVolume(double)

Sets the current volume level.

[Obsolete("Use SetVolumeAsync instead.")]
public void SetVolume(double level)

Parameters

level double

The new volume level, in percent ([0.0-100.0]).

SetVolumeAsync(double)

Asynchronously sets the current volume level.

public Task SetVolumeAsync(double level)

Parameters

level double

Returns

Task

SubscribeEvent(string)

Subscribe to updates for an event.

[Obsolete("Use SubscribeEventAsync instead.")]
public void SubscribeEvent(string eventName)

Parameters

eventName string

The event identifier to subscribe to.

SubscribeEventAsync(string)

Asynchronously subscribes to updates for an event.

public Task SubscribeEventAsync(string eventName)

Parameters

eventName string

Returns

Task

SwitchAccount(string)

Switch the active user account on the device.

[Obsolete("Use SwitchAccountAsync instead.")]
public void SwitchAccount(string accountId)

Parameters

accountId string

The account identifier to switch to, as returned by AccountList().

SwitchAccountAsync(string)

Asynchronously switches the active user account.

public Task SwitchAccountAsync(string accountId)

Parameters

accountId string

Returns

Task

SystemInfo()

Send system information to the device.

[Obsolete("Use SystemInfoAsync instead.")]
public void SystemInfo()

SystemInfoAsync()

Asynchronously sends system information to the device.

public Task SystemInfoAsync()

Returns

Task

TextAppend(string)

Append text to the virtual keyboard.

[Obsolete("Use TextAppendAsync instead.")]
public void TextAppend(string text)

Parameters

text string

The text to insert.

TextAppendAsync(string)

Asynchronously appends text to the virtual keyboard.

public Task TextAppendAsync(string text)

Parameters

text string

Returns

Task

TextClear()

Clear the virtual keyboard text.

[Obsolete("Use TextClearAsync instead.")]
public void TextClear()

TextClearAsync()

Asynchronously clears the virtual keyboard text.

public Task TextClearAsync()

Returns

Task

TextGet()

Get the current virtual keyboard text.

[Obsolete("Use TextGetAsync instead.")]
public string? TextGet()

Returns

string

TextGetAsync()

Asynchronously gets the current virtual keyboard text.

public Task<string?> TextGetAsync()

Returns

Task<string>

TextInputCommand(string, bool)

Send a text input command: refreshes the RTI session, optionally clears the current text, then optionally inserts new text.

[Obsolete("Use TextInputCommandAsync instead.")]
public string? TextInputCommand(string text, bool clearPreviousInput = false)

Parameters

text string

The text to insert, or an empty string to insert nothing.

clearPreviousInput bool

Whether to clear the existing text before inserting.

Returns

string

The resulting text field contents, or null if there is no focused text field.

TextInputCommandAsync(string, bool)

Asynchronously sends a text input command.

public Task<string?> TextInputCommandAsync(string text, bool clearPreviousInput = false)

Parameters

text string
clearPreviousInput bool

Returns

Task<string>

TextInputStart()

Start a text input session.

[Obsolete("Use TextInputStartAsync instead.")]
public Dictionary<object, object?> TextInputStart()

Returns

Dictionary<object, object>

TextInputStartAsync()

Asynchronously starts a text input session.

public Task<Dictionary<object, object?>> TextInputStartAsync()

Returns

Task<Dictionary<object, object>>

TextInputStop()

Stop the current text input session.

[Obsolete("Use TextInputStopAsync instead.")]
public void TextInputStop()

TextInputStopAsync()

Asynchronously stops the current text input session.

public Task TextInputStopAsync()

Returns

Task

TextSet(string)

Replace the virtual keyboard text.

[Obsolete("Use TextSetAsync instead.")]
public void TextSet(string text)

Parameters

text string

The new text.

TextSetAsync(string)

Asynchronously replaces the virtual keyboard text.

public Task TextSetAsync(string text)

Parameters

text string

Returns

Task

ToggleMute()

Toggles mute by saving the current volume and setting it to zero, or restoring the previously saved volume. Requires IsVolumeControlSupported.

[Obsolete("Use ToggleMuteAsync instead.")]
public bool ToggleMute()

Returns

bool

true if the device is now muted, otherwise false.

ToggleMuteAsync()

Asynchronously toggles mute.

public Task<bool> ToggleMuteAsync()

Returns

Task<bool>

TouchStart()

Subscribe to touch gestures.

[Obsolete("Use TouchStartAsync instead.")]
public void TouchStart()

TouchStartAsync()

Asynchronously subscribes to touch gestures.

public Task TouchStartAsync()

Returns

Task

TouchStop()

Unsubscribe from touch gestures.

[Obsolete("Use TouchStopAsync instead.")]
public void TouchStop()

TouchStopAsync()

Asynchronously unsubscribes from touch gestures.

public Task TouchStopAsync()

Returns

Task

TvRcSessionStart()

Open a TV Remote Client session. tvOS does not answer FetchAttentionState until a TV Remote Client session is registered with tvremoted; older devices may simply error on this command, so failures here are intentionally swallowed.

[Obsolete("Use TvRcSessionStartAsync instead.")]
public void TvRcSessionStart()

TvRcSessionStartAsync()

Asynchronously opens a TV Remote Client session.

public Task TvRcSessionStartAsync()

Returns

Task

UnsubscribeEvent(string)

Unsubscribe from updates for an event.

[Obsolete("Use UnsubscribeEventAsync instead.")]
public void UnsubscribeEvent(string eventName)

Parameters

eventName string

The event identifier to unsubscribe from.

UnsubscribeEventAsync(string)

Asynchronously unsubscribes from updates for an event.

public Task UnsubscribeEventAsync(string eventName)

Parameters

eventName string

Returns

Task

Events

ConnectionClosed

Raised when the connection to the device is closed or lost, whether cleanly (e.g. the remote end closing the socket) or unexpectedly (e.g. a transport, decrypt, or dispatch failure). Inspect Exception to distinguish the two.

public event EventHandler<ConnectionClosedEventArgs>? ConnectionClosed

Event Type

EventHandler<ConnectionClosedEventArgs>

Remarks

Mirrors pyatv's DeviceListener.connection_lost/connection_closed callbacks (pyatv/interface.py). Unlike pyatv, this port does not implement automatic reconnection; consumers that want to reconnect must do so themselves in response to this event.

MediaControlCapabilitiesChanged

Raised whenever an updated _iMC event is received and the device's advertised media-control capability flags (including IsVolumeControlSupported) may have changed.

public event EventHandler? MediaControlCapabilitiesChanged

Event Type

EventHandler

SystemStatusChanged

Raised whenever a pushed SystemStatus/TVSystemStatus event changes CurrentSystemStatus, including transitions between non-Asleep states (e.g. Awake to Screensaver). Unlike pyatv, which only notifies on the collapsed on/off boundary, this event fires on every raw state change; inspect CurrentSystemStatus from the handler for the granular value, or compare it against Asleep if only on/off matters.

public event EventHandler? SystemStatusChanged

Event Type

EventHandler

TextFocusStateChanged

Raised whenever TextFocusState is updated by a _tiStarted, _tiStopped event, or a _tiStart response.

public event EventHandler? TextFocusStateChanged

Event Type

EventHandler