@foxxmd/chromecast-client
v1.0.4
Published
A robust Chromecast client written in Typescript
Downloads
182
Maintainers
Readme
Chromecast Client
FORK Changes
This is a fork of https://github.com/dantaylor3/chromecast-client with several quality of life improvements and fixes:
- Make some MediaController status properties optional to better fit real world data
- Format zod errors to be human friendly and return raw data in unwrapped error
PersistentClient
is now a class instead of a function- Fixes unhandled timeout rejection on
connect
- Allows initiating
connect
separately from instantiation usingawait persistentClient.connect()
connect()
is still exported at top-level to prevent breaking change
- Better reconnection logic
- is an
EventEmitter
that re-emitscastv2
events - exposes
castv2
client,connected
andshouldReconnect
fields
- Fixes unhandled timeout rejection on
A Typescript based Chromecast client
This module is a mid-level library which uses castv2 as a basis for communicating with a Chromecast to provide a fully typed, promise-based api for interacting with the Chromecast. It provides
- a persistent client that will keep your client connected to the Chromecast
- controllers for common namespaces used by the Chromecast
- a Platform implementing basic features of the Chromecast
- a DefaultMedia application implementation to control Chromecast media
- a Dashcast application implementation as another application example
- TODO: a spotify application implementation
This module is intended to be composable and reusable for other use cases outside of those implemented and wouldn't have been possible without both castv2 and nodecastor
Installation
Install with Yarn
yarn add @foxxmd/chromecast-client
Install with NPM
npm install @foxxmd/chromecast-client
Usage
Platform
The platform provides basic controls for the Chromecast like changing the volume and launching applications.
import {createPlatform, Persistentclient} from 'chromecast-client'
import {PersistentClient} from './persistentClient'
const client = new PersistentClient({host: '192.168.1.150'})
await client.connect()
const platform = await createPlatform(client)
const status = await platform.getStatus()
console.log('current status', status)
platform.close()
client.close()
{
"applications": [
{
"appId": "E8C28D3C",
"appType": "WEB",
"displayName": "Backdrop",
"iconUrl": "",
"isIdleScreen": true,
"launchedFromCloud": false,
"namespaces": [
{ "name": "urn:x-cast:com.google.cast.debugoverlay" },
{ "name": "urn:x-cast:com.google.cast.cac" },
{ "name": "urn:x-cast:com.google.cast.sse" },
{ "name": "urn:x-cast:com.google.cast.remotecontrol" }
],
"sessionId": "########-####-####-####-############",
"statusText": "",
"transportId": "########-####-####-####-############",
"universalAppId": "E8C28D3C"
}
],
"userEq": {},
"volume": {
"controlType": "attenuation",
"level": 1,
"muted": false,
"stepInterval": 0.05000000074505806
}
}
Using a Controller Directly
We're going to use the Media controller to get the current volume of the Chromecast.
import {PersistentClient, ReceiverController} from 'chromecast-client'
const client = new PersistentClient({host: '192.168.1.150'})
await client.connect()
// launch the media app on the Chromecast and join the session (so we can control the CC)
const controller = ReceiverController.createReceiver({client})
// get the volume from the chromecast and unwrap the result
const volume = (await controller.getVolume()).unwrapAndThrow()
// log the volume level since there weren't any errors (or it would've thrown)
console.log(volume)
// dispose of the controller and close the client
controller.dispose()
client.close()
{
"controlType": "attenuation",
"level": 1,
"muted": false,
"stepInterval": 0.05000000074505806
}
Using an Application
An application is an abstraction on top of one or more controllers that provides a friendly interface to work with. This way, you don't have to work directly with controllers, look up documentation on what commands the protocol supports, etc.
We're going to use the DefaultMediaApp to launch the media app on the chromecast, play some content, control the playback of that content, then stop playing the content
import {DefaultMediaApp, PersistentClient, Result} from 'chromecast-client'
// create a persistent client connected on a given host
const client = new PersistentClient({host: '192.168.1.150'})
await client.connect()
// launch the media app on the Chromecast and join the session (so we can control the CC)
const media = await DefaultMediaApp.launchAndJoin({client}).then(Result.unwrapWithErr)
// if the media app failed to load, log the error
if (!media.isOk) return console.error(media.value)
// queue up a couple of videos
await media.value.queueLoad({
items: [
{
media: {
contentId: 'http://commondatastorage.googleapis.com/gtv-videos-bucket/big_buck_bunny_1080p.mp4',
contentType: 'video/mp4',
streamType: 'BUFFERED',
},
},
{
media: {
contentId: 'http://commondatastorage.googleapis.com/gtv-videos-bucket/sample/ElephantsDream.mp4',
contentType: 'video/mp4',
streamType: 'BUFFERED',
},
},
],
})
// after 8 seconds of playing video, forward the video by 60 seconds
setTimeout(() => media.value.seek({relativeTime: 60}), 8000)
// after 20 seconds of playing video, stop playing and close client
setTimeout(() => {
media.value.stop() // stop playing video
media.value.dispose() // clean up media app event handlers
client.close()
}, 20000)
Error Handling
This library uses an implementation of Result
to encapsulate either a successful value or an error. This is similar to std::result in Rust, LanguageExt.Common.Result in C#, FSharp.Core.Result in F#, and many more.
There is an awesome Typescript library implementing functional programming concepts called fp-ts which I use extensively. I chose not to use fp-ts in this project to make the library more approachable for those without FP experience.
For those who understand FP concepts, the Result class is roughly equivalent to the Either monad and can be easily integrated with fp-ts. For those who don't understand FP concepts or find it cumbersome in Typescript, simply call Result.unwrapWithErr
or Result.unwrapAndThrow
depending on how you'd like to handle errors.
Discovering Chromecasts on the Network
This library doesn't support chromecast discovery directly because it is well supported by other libraries. Any library that supports multicast DNS discovery will work (such as bonjour-service, mdns, or multicast-dns).
Example using bonjour-service
import Bonjour from 'bonjour-service'
const bonjour = new Bonjour()
bonjour.find({ type: 'googlecast' }, (service) => {
console.log(`found chromecast named "${service.name}" at ${service.addresses?.[0]}`)
bonjour.destroy()
})