npm package: @strasberry/capacitor-cast
Description: This is a plugin for Capacitor that enables cast functionality for web, iOS and Android.
Author: Tarek Bachir
Latest version: 1.0.0

# @strasberry/capacitor-cast

Capacitor plugin to control Google Cast sessions from Web, iOS, and Android with one API.

## Install

```bash
npm install @strasberry/capacitor-cast
npx cap sync
```

## Quickstart

```ts
import { Cast } from '@strasberry/capacitor-cast';

await Cast.initialize();

const capabilities = await Cast.getCapabilities();
if (!capabilities.isSupported) {
  // Platform/runtime does not expose Google Cast sender APIs.
  return;
}

await Cast.showDevicePicker();
await Cast.loadMedia({
  url: 'https://example.com/video.mp4',
  contentType: 'video/mp4',
  title: 'My Video',
  autoplay: true,
});
```

## Documentation

- [Documentation index](./docs/README.md)
- [Getting Started](./docs/getting-started.md)
- [Configuration](./docs/configuration.md)
- [Platform index](./docs/platforms/README.md)
- Platform details:
  - [Web](./docs/platforms/web.md)
  - [iOS](./docs/platforms/ios.md)
  - [Android](./docs/platforms/android.md)
- [Runtime & Permissions API](./docs/runtime-permissions.md)
- [Advanced Usage Example](./docs/advanced-example.md)
- [Angular + Ionic Standalone CastService Example](./docs/angular-ionic-cast-service.md)
- [Troubleshooting](./docs/troubleshooting.md)
- [Official Chromecast resources](./docs/chromecast-official-resources.md)

## Platform Prerequisites

### Web

- Run in a secure context (`https://`) or `http://localhost` during development.
- Use a Cast sender-compatible browser/runtime. Mobile browser support is limited.
- Ensure sender and receiver are on reachable networks and cast discovery is allowed.
- Full Web platform guide: [docs/platforms/web.md](./docs/platforms/web.md)

### iOS

- The plugin ships with Swift Package support and is validated with:

```bash
npm run verify:ios
```

- If your Capacitor iOS app uses Swift Package Manager, keep the generated `CapApp-SPM` setup and run `npx cap sync ios` after updates.
- For Cast discovery on iOS 14+, provide Local Network + Bonjour entries in your app `Info.plist`:
  - `NSLocalNetworkUsageDescription`
  - `NSBonjourServices` including `_googlecast._tcp` and your app-specific Cast service (for example `_<APP_ID>._googlecast._tcp`)
- Full iOS platform guide: [docs/platforms/ios.md](./docs/platforms/ios.md)

### Android

- The plugin bundles Google Cast framework dependency and registers its Cast `OptionsProvider` via manifest merge.
- If the host app already defines `com.google.android.gms.cast.framework.OPTIONS_PROVIDER_CLASS_NAME`, keep only one provider and ensure `receiverApplicationId` is consistently configured.
- Full Android platform guide: [docs/platforms/android.md](./docs/platforms/android.md)

## Compatibility

- Capacitor: `@capacitor/core >= 8.0.0`
- iOS deployment target: `15.0+`
- Android min SDK: `24+`
- Android target SDK in plugin template: `36`

## Configuration

Set plugin values in `capacitor.config.ts` (or `capacitor.config.json`):

```ts
/// <reference types="@strasberry/capacitor-cast" />

import { CapacitorConfig } from '@capacitor/cli';

const config: CapacitorConfig = {
  plugins: {
    Cast: {
      uiMode: 'picker',
      autoJoinPolicy: 'origin_scoped',
    },
  },
};

export default config;
```

- `receiverApplicationId`: optional. Defaults to `CC1AD845` (Default Media Receiver) when omitted.
- `receiverAppId`: backward-compatible alias for `receiverApplicationId`.
- `uiMode`: optional, defaults to `picker`.
- `autoJoinPolicy`: optional, defaults to `origin_scoped`.
- Detailed config guide: [docs/configuration.md](./docs/configuration.md)

`uiMode` behavior:

- `picker`: `showDevicePicker()` and `requestSession()` can open device chooser.
- `headless`: `showDevicePicker()` rejects with `UI_MODE_NOT_AVAILABLE`; `requestSession()` can only resume an already active session.
- `nativeButton`: host-native cast UI drives connection; `showDevicePicker()` and `requestSession()` reject with `UI_MODE_NOT_AVAILABLE`.

`autoJoinPolicy` accepted values:

- `origin_scoped` (default)
- `tab_and_origin_scoped`
- `page_scoped`

Call `initialize()` once before any cast method. Configuration is read from Capacitor plugin config and is not overridden at runtime.
On web-only runtime, ensure `window.Capacitor.config.plugins.Cast` (or `plugins.cast`) is available before `initialize()`.

## Lifecycle & Events

- Register listeners once per app lifecycle (avoid rebinding the same callbacks on each render/re-init).
- Supported listener events:
  - `castError`
  - `messageReceived`
- Read runtime state explicitly via:
  - `getCastState()`
  - `getSession()`
  - `getMediaStatus()`
- Snapshot payloads are intentionally platform-specific and thin:
  - Web `getCastState()` aligns with `cast.framework.CastStateEventData` shape.
  - Web `getMediaStatus()` aligns with `cast.framework.RemotePlayer` shape.
- On web, trigger `showDevicePicker()` / `requestSession()` from a direct user gesture.

## Migration Notes (vNext)

- `getCastState().castState` is now a raw snapshot object (or `null`), not a normalized string.
- Web `getMediaStatus().mediaStatus` now exposes `RemotePlayer` snapshot data.
- Session snapshots no longer include plugin-derived connection booleans (`isConnected`, `isConnecting`) on native platforms.

## Custom Messaging (Generic)

Use custom namespaces to exchange arbitrary application messages with your receiver.

```ts
await Cast.subscribeNamespace({ namespace: 'urn:x-cast:com.example.channel' });

await Cast.sendMessage({
  namespace: 'urn:x-cast:com.example.channel',
  message: {
    action: 'PING',
    timestamp: new Date().toISOString(),
  },
});

await Cast.addListener('messageReceived', (event) => {
  console.log(event.namespace, event.message, event.raw);
});
```

- The plugin only transports namespace + payload.
- Message schema and protocol versioning are fully owned by your app sender/receiver.
- Incoming JSON strings are parsed to objects when possible; otherwise payload is exposed as raw string.

## Error Handling

The plugin emits and rejects with typed `CastErrorCode` values:

- `UNSUPPORTED_PLATFORM`: Cast sender runtime is unavailable.
- `NOT_INITIALIZED`: `initialize()` has not been called yet.
- `NO_ACTIVE_SESSION`: operation requires an active cast session.
- `INVALID_ARGUMENT`: missing or invalid method input.
- `UI_MODE_NOT_AVAILABLE`: method not available for current `uiMode`.
- `OPERATION_FAILED`: native/web Cast SDK operation failed.

## Troubleshooting

- No devices discovered:
  - Verify Web secure context, local network reachability, and iOS local network permissions.
- `UNSUPPORTED_PLATFORM` on Web:
  - Confirm a Cast sender-compatible browser/runtime and successful sender SDK load.
  - Ensure `window.Capacitor.config.plugins.Cast` exists before `initialize()`.
- Web console logs `GET chrome-extension://invalid/ net::ERR_FAILED`:
  - This can be emitted by Google Cast sender bootstrap and is often non-blocking.
  - Verify actual impact through `initialize().isSupported` and `getCapabilities().canShowDevicePicker`.
- `NO_ACTIVE_SESSION` on media controls:
  - Start or resume a cast session before calling `loadMedia`, `play`, `pause`, `seek`, or volume methods.
- Session resume not working in `headless` mode:
  - Ensure a restorable cast session exists; `headless` will not open a picker.
- Android app already has Cast integration:
  - Resolve `OPTIONS_PROVIDER_CLASS_NAME` ownership to a single provider.
- Android logs many `Invalid stack map table` warnings in `com.google.android.gms.cast`:
  - Disable Jetifier (`android.enableJetifier=false`) or exclude Cast artifacts with `android.jetifier.ignorelist`.
- Xcode warns about `example-app/node_modules/.../.swiftpm/xcode` when plugin is linked locally:
  - Remove example artifacts from the plugin workspace:
    ```bash
    rm -rf example-app/node_modules example-app/.swiftpm
    ```
- Full troubleshooting guide: [docs/troubleshooting.md](./docs/troubleshooting.md)

## API

<docgen-index>

* [`isInitialized()`](#isinitialized)
* [`checkPermissions()`](#checkpermissions)
* [`requestPermissions()`](#requestpermissions)
* [`initialize()`](#initialize)
* [`getCapabilities()`](#getcapabilities)
* [`getCastState()`](#getcaststate)
* [`getSession()`](#getsession)
* [`requestSession()`](#requestsession)
* [`showDevicePicker()`](#showdevicepicker)
* [`endSession(...)`](#endsession)
* [`loadMedia(...)`](#loadmedia)
* [`play()`](#play)
* [`pause()`](#pause)
* [`stop()`](#stop)
* [`seek(...)`](#seek)
* [`setVolume(...)`](#setvolume)
* [`setMuted(...)`](#setmuted)
* [`getMediaStatus()`](#getmediastatus)
* [`sendMessage(...)`](#sendmessage)
* [`subscribeNamespace(...)`](#subscribenamespace)
* [`unsubscribeNamespace(...)`](#unsubscribenamespace)
* [`addListener('castError', ...)`](#addlistenercasterror-)
* [`addListener('messageReceived', ...)`](#addlistenermessagereceived-)
* [`removeAllListeners()`](#removealllisteners)
* [Interfaces](#interfaces)
* [Type Aliases](#type-aliases)

</docgen-index>

<docgen-api>
<!--Update the source file JSDoc comments and rerun docgen to update the docs below-->

### isInitialized()

```typescript
isInitialized() => Promise<IsInitializedResult>
```

Returns whether the plugin has been initialized.

**Returns:** <code>Promise&lt;<a href="#isinitializedresult">IsInitializedResult</a>&gt;</code>

--------------------


### checkPermissions()

```typescript
checkPermissions() => Promise<CastPermissionStatus>
```

Returns the current local network permission status.

**Returns:** <code>Promise&lt;<a href="#castpermissionstatus">CastPermissionStatus</a>&gt;</code>

--------------------


### requestPermissions()

```typescript
requestPermissions() => Promise<CastPermissionStatus>
```

Requests local network permission when applicable.

**Returns:** <code>Promise&lt;<a href="#castpermissionstatus">CastPermissionStatus</a>&gt;</code>

--------------------


### initialize()

```typescript
initialize() => Promise<InitializeResult>
```

Initializes the plugin from `capacitor.config.*` values.

**Returns:** <code>Promise&lt;<a href="#initializeresult">InitializeResult</a>&gt;</code>

--------------------


### getCapabilities()

```typescript
getCapabilities() => Promise<CastCapabilities>
```

Returns the capabilities for the current platform/runtime.

**Returns:** <code>Promise&lt;<a href="#castcapabilities">CastCapabilities</a>&gt;</code>

--------------------


### getCastState()

```typescript
getCastState() => Promise<CastStateResult>
```

Returns the current cast state from the underlying SDK.

**Returns:** <code>Promise&lt;<a href="#caststateresult">CastStateResult</a>&gt;</code>

--------------------


### getSession()

```typescript
getSession() => Promise<SessionResult>
```

Returns the currently active cast session snapshot if any.

**Returns:** <code>Promise&lt;<a href="#sessionresult">SessionResult</a>&gt;</code>

--------------------


### requestSession()

```typescript
requestSession() => Promise<void>
```

Requests a cast session.

--------------------


### showDevicePicker()

```typescript
showDevicePicker() => Promise<void>
```

Opens the cast device picker when available for the configured UI mode.

--------------------


### endSession(...)

```typescript
endSession(options?: EndSessionOptions | undefined) => Promise<void>
```

Ends the active cast session.

| Param         | Type                                                            |
| ------------- | --------------------------------------------------------------- |
| **`options`** | <code><a href="#endsessionoptions">EndSessionOptions</a></code> |

--------------------


### loadMedia(...)

```typescript
loadMedia(request: LoadMediaRequest) => Promise<LoadMediaResult>
```

Loads media in the active cast session.

| Param         | Type                                                          |
| ------------- | ------------------------------------------------------------- |
| **`request`** | <code><a href="#loadmediarequest">LoadMediaRequest</a></code> |

**Returns:** <code>Promise&lt;<a href="#loadmediaresult">LoadMediaResult</a>&gt;</code>

--------------------


### play()

```typescript
play() => Promise<void>
```

Resumes playback for the active media item.

--------------------


### pause()

```typescript
pause() => Promise<void>
```

Pauses playback for the active media item.

--------------------


### stop()

```typescript
stop() => Promise<void>
```

Stops playback for the active media item.

--------------------


### seek(...)

```typescript
seek(options: SeekOptions) => Promise<void>
```

Seeks the active media item to a position in seconds.

| Param         | Type                                                |
| ------------- | --------------------------------------------------- |
| **`options`** | <code><a href="#seekoptions">SeekOptions</a></code> |

--------------------


### setVolume(...)

```typescript
setVolume(options: SetVolumeOptions) => Promise<void>
```

Sets remote device volume in range [0, 1].

| Param         | Type                                                          |
| ------------- | ------------------------------------------------------------- |
| **`options`** | <code><a href="#setvolumeoptions">SetVolumeOptions</a></code> |

--------------------


### setMuted(...)

```typescript
setMuted(options: SetMutedOptions) => Promise<void>
```

Sets remote device muted state.

| Param         | Type                                                        |
| ------------- | ----------------------------------------------------------- |
| **`options`** | <code><a href="#setmutedoptions">SetMutedOptions</a></code> |

--------------------


### getMediaStatus()

```typescript
getMediaStatus() => Promise<MediaStatusResult>
```

Returns the current media status snapshot if available.

**Returns:** <code>Promise&lt;<a href="#mediastatusresult">MediaStatusResult</a>&gt;</code>

--------------------


### sendMessage(...)

```typescript
sendMessage(options: SendMessageOptions) => Promise<void>
```

Sends a custom message to the active cast session on the provided namespace.

| Param         | Type                                                              |
| ------------- | ----------------------------------------------------------------- |
| **`options`** | <code><a href="#sendmessageoptions">SendMessageOptions</a></code> |

--------------------


### subscribeNamespace(...)

```typescript
subscribeNamespace(options: NamespaceOptions) => Promise<void>
```

Subscribes native/web SDK message callbacks for an explicit namespace.

| Param         | Type                                                          |
| ------------- | ------------------------------------------------------------- |
| **`options`** | <code><a href="#namespaceoptions">NamespaceOptions</a></code> |

--------------------


### unsubscribeNamespace(...)

```typescript
unsubscribeNamespace(options: NamespaceOptions) => Promise<void>
```

Unsubscribes native/web SDK message callbacks for an explicit namespace.

| Param         | Type                                                          |
| ------------- | ------------------------------------------------------------- |
| **`options`** | <code><a href="#namespaceoptions">NamespaceOptions</a></code> |

--------------------


### addListener('castError', ...)

```typescript
addListener(eventName: 'castError', listenerFunc: (event: CastErrorEvent) => void) => Promise<PluginListenerHandle>
```

Listens for typed cast errors.

| Param              | Type                                                                          |
| ------------------ | ----------------------------------------------------------------------------- |
| **`eventName`**    | <code>'castError'</code>                                                      |
| **`listenerFunc`** | <code>(event: <a href="#casterrorevent">CastErrorEvent</a>) =&gt; void</code> |

**Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</code>

--------------------


### addListener('messageReceived', ...)

```typescript
addListener(eventName: 'messageReceived', listenerFunc: (event: MessageReceivedEvent) => void) => Promise<PluginListenerHandle>
```

Listens for incoming custom namespace messages.

| Param              | Type                                                                                      |
| ------------------ | ----------------------------------------------------------------------------------------- |
| **`eventName`**    | <code>'messageReceived'</code>                                                            |
| **`listenerFunc`** | <code>(event: <a href="#messagereceivedevent">MessageReceivedEvent</a>) =&gt; void</code> |

**Returns:** <code>Promise&lt;<a href="#pluginlistenerhandle">PluginListenerHandle</a>&gt;</code>

--------------------


### removeAllListeners()

```typescript
removeAllListeners() => Promise<void>
```

Removes all plugin listeners.

--------------------


### Interfaces


#### IsInitializedResult

| Prop                | Type                 |
| ------------------- | -------------------- |
| **`isInitialized`** | <code>boolean</code> |


#### CastPermissionStatus

| Prop               | Type                                                        |
| ------------------ | ----------------------------------------------------------- |
| **`localNetwork`** | <code><a href="#permissionstate">PermissionState</a></code> |


#### InitializeResult

| Prop                        | Type                                              |
| --------------------------- | ------------------------------------------------- |
| **`isSupported`**           | <code>boolean</code>                              |
| **`uiMode`**                | <code><a href="#castuimode">CastUiMode</a></code> |
| **`receiverApplicationId`** | <code>string</code>                               |


#### CastCapabilities

| Prop                         | Type                 |
| ---------------------------- | -------------------- |
| **`isSupported`**            | <code>boolean</code> |
| **`canRequestSession`**      | <code>boolean</code> |
| **`canShowDevicePicker`**    | <code>boolean</code> |
| **`supportsMediaControl`**   | <code>boolean</code> |
| **`supportsVolumeControl`**  | <code>boolean</code> |
| **`supportsCustomChannels`** | <code>boolean</code> |


#### CastStateResult

| Prop            | Type                                                            |
| --------------- | --------------------------------------------------------------- |
| **`castState`** | <code><a href="#caststatesnapshot">CastStateSnapshot</a></code> |


#### SessionResult

| Prop          | Type                                                                        |
| ------------- | --------------------------------------------------------------------------- |
| **`session`** | <code><a href="#castsessionsnapshot">CastSessionSnapshot</a> \| null</code> |


#### EndSessionOptions

| Prop              | Type                 |
| ----------------- | -------------------- |
| **`stopCasting`** | <code>boolean</code> |


#### LoadMediaResult

| Prop            | Type                |
| --------------- | ------------------- |
| **`requestId`** | <code>string</code> |


#### LoadMediaRequest

| Prop                 | Type                                                             | Description                                               |
| -------------------- | ---------------------------------------------------------------- | --------------------------------------------------------- |
| **`url`**            | <code>string</code>                                              | Media URL reachable by the receiver device.               |
| **`contentType`**    | <code>string</code>                                              | MIME type of the media (for example `video/mp4`).         |
| **`title`**          | <code>string</code>                                              | Convenience title field mapped to receiver metadata.      |
| **`subtitle`**       | <code>string</code>                                              | Convenience subtitle field mapped to receiver metadata.   |
| **`posterUrl`**      | <code>string</code>                                              | Poster image URL added to metadata images when provided.  |
| **`streamType`**     | <code><a href="#caststreamtype">CastStreamType</a></code>        | Stream type used by Cast receivers.                       |
| **`autoplay`**       | <code>boolean</code>                                             | Whether playback should start automatically after load.   |
| **`currentTime`**    | <code>number</code>                                              | Initial playback position in seconds.                     |
| **`customData`**     | <code><a href="#record">Record</a>&lt;string, unknown&gt;</code> | Custom payload forwarded to the receiver load request.    |
| **`tracks`**         | <code>CastTrack[]</code>                                         | Optional text/audio/video tracks for the media item.      |
| **`activeTrackIds`** | <code>number[]</code>                                            | Active track identifiers to enable right after loading.   |
| **`metadata`**       | <code><a href="#castmediametadata">CastMediaMetadata</a></code>  | Optional metadata payload merged with convenience fields. |


#### CastTrack

| Prop              | Type                                                             |
| ----------------- | ---------------------------------------------------------------- |
| **`trackId`**     | <code>number</code>                                              |
| **`type`**        | <code>'TEXT' \| 'AUDIO' \| 'VIDEO'</code>                        |
| **`name`**        | <code>string</code>                                              |
| **`language`**    | <code>string</code>                                              |
| **`subtype`**     | <code>string</code>                                              |
| **`contentId`**   | <code>string</code>                                              |
| **`contentType`** | <code>string</code>                                              |
| **`customData`**  | <code><a href="#record">Record</a>&lt;string, unknown&gt;</code> |


#### CastMediaMetadata

| Prop              | Type                                                             |
| ----------------- | ---------------------------------------------------------------- |
| **`title`**       | <code>string</code>                                              |
| **`subtitle`**    | <code>string</code>                                              |
| **`studio`**      | <code>string</code>                                              |
| **`releaseDate`** | <code>string</code>                                              |
| **`images`**      | <code>string[]</code>                                            |
| **`customData`**  | <code><a href="#record">Record</a>&lt;string, unknown&gt;</code> |


#### SeekOptions

| Prop           | Type                |
| -------------- | ------------------- |
| **`position`** | <code>number</code> |


#### SetVolumeOptions

| Prop        | Type                |
| ----------- | ------------------- |
| **`level`** | <code>number</code> |


#### SetMutedOptions

| Prop        | Type                 |
| ----------- | -------------------- |
| **`muted`** | <code>boolean</code> |


#### MediaStatusResult

| Prop              | Type                                                                                |
| ----------------- | ----------------------------------------------------------------------------------- |
| **`mediaStatus`** | <code><a href="#castmediastatussnapshot">CastMediaStatusSnapshot</a> \| null</code> |


#### SendMessageOptions

| Prop            | Type                                                                       | Description                                                            |
| --------------- | -------------------------------------------------------------------------- | ---------------------------------------------------------------------- |
| **`namespace`** | <code>string</code>                                                        | Cast message namespace (for example `urn:x-cast:com.example.channel`). |
| **`message`**   | <code>string \| <a href="#record">Record</a>&lt;string, unknown&gt;</code> | Message payload sent to the receiver.                                  |


#### NamespaceOptions

| Prop            | Type                | Description                                                            |
| --------------- | ------------------- | ---------------------------------------------------------------------- |
| **`namespace`** | <code>string</code> | Cast message namespace (for example `urn:x-cast:com.example.channel`). |


#### PluginListenerHandle

| Prop         | Type                                      |
| ------------ | ----------------------------------------- |
| **`remove`** | <code>() =&gt; Promise&lt;void&gt;</code> |


#### CastErrorEvent

| Prop          | Type                                                             | Description                                         |
| ------------- | ---------------------------------------------------------------- | --------------------------------------------------- |
| **`code`**    | <code><a href="#casterrorcode">CastErrorCode</a></code>          | Stable plugin error code.                           |
| **`message`** | <code>string</code>                                              | Human-readable error message.                       |
| **`method`**  | <code>string</code>                                              | Plugin method that emitted or propagated the error. |
| **`data`**    | <code><a href="#record">Record</a>&lt;string, unknown&gt;</code> | Optional platform-specific diagnostic payload.      |


#### MessageReceivedEvent

| Prop            | Type                                                                       | Description                                                                     |
| --------------- | -------------------------------------------------------------------------- | ------------------------------------------------------------------------------- |
| **`namespace`** | <code>string</code>                                                        | Namespace on which the message was received.                                    |
| **`message`**   | <code>string \| <a href="#record">Record</a>&lt;string, unknown&gt;</code> | Parsed message payload when JSON object parsing succeeds, otherwise raw string. |
| **`raw`**       | <code>string</code>                                                        | Raw message string when available from the Cast SDK.                            |


### Type Aliases


#### PermissionState

<code>'prompt' | 'prompt-with-rationale' | 'granted' | 'denied'</code>


#### CastUiMode

<code>'picker' | 'nativeButton' | 'headless'</code>


#### CastStateSnapshot

Raw cast state snapshot (platform-specific shape).

<code><a href="#record">Record</a>&lt;string, unknown&gt; | null</code>


#### Record

Construct a type with a set of properties K of type T

<code>{ [P in K]: T; }</code>


#### CastSessionSnapshot

Raw session snapshot (platform-specific shape).

<code><a href="#record">Record</a>&lt;string, unknown&gt;</code>


#### CastStreamType

<code>'BUFFERED' | 'LIVE' | 'OTHER'</code>


#### CastMediaStatusSnapshot

Raw media status snapshot (platform-specific shape).

<code><a href="#record">Record</a>&lt;string, unknown&gt;</code>


#### CastErrorCode

<code>'UNSUPPORTED_PLATFORM' | 'NOT_INITIALIZED' | 'NO_ACTIVE_SESSION' | 'INVALID_ARGUMENT' | 'UI_MODE_NOT_AVAILABLE' | 'OPERATION_FAILED'</code>

</docgen-api>