New Case Study:See how Anthropic automated 95% of dependency reviews with Socket.Learn More
Socket
Sign inDemoInstall
Socket

@scrypted/chromecast

Package Overview
Dependencies
Maintainers
2
Versions
61
Alerts
File Explorer

Advanced tools

Socket logo

Install Socket

Detect and block malicious and high-risk dependencies

Install

@scrypted/chromecast - npm Package Compare versions

Comparing version 0.1.14 to 0.1.15

dist/main.nodejs.js

16

.vscode/launch.json

@@ -8,3 +8,3 @@ {

{
"name" : "Scrypted Debugger",
"name" : "Scrypted Debugger (qjs)",
"type" : "quickjs",

@@ -25,3 +25,17 @@ "request" : "attach",

},
{
"name": "Scrypted Debugger",
"address": "${config:scrypted.debugHost}",
"port": 9091,
"request": "attach",
"skipFiles": [
"<node_internals>/**"
],
"preLaunchTask": "scrypted: deploy+debug",
"sourceMaps": true,
"localRoot": "${workspaceFolder}/out",
"remoteRoot": "/plugin/",
"type": "pwa-node"
}
]
}

3

.vscode/settings.json

@@ -1,4 +0,3 @@

{
"scrypted.debugHost": "192.168.2.120",
"scrypted.debugHost": "127.0.0.1",
}

@@ -20,2 +20,2 @@ {

]
}
}
{
"name": "@scrypted/chromecast",
"version": "0.1.14",
"version": "0.1.15",
"description": "Send video, audio, and text to speech notifications to Chromecast and Google Home devices",

@@ -30,12 +30,14 @@ "author": "Scrypted",

},
"dependencies": {
"@scrypted/sdk": "file:../../sdk",
"castv2-promise": "^1.0.0",
"mdns": "^2.7.2",
"memoize-one": "^5.1.1",
"mime": "^2.5.2",
"query-string": "^7.0.0"
},
"devDependencies": {
"@scrypted/sdk": "0.0.59",
"raw-loader": "^1.0.0",
"webpack-merge": "^4.2.2"
},
"dependencies": {
"castv2": "^0.1.10",
"castv2-client": "^1.2.0",
"memoize-one": "^5.1.1"
"@types/mime": "^2.0.3",
"@types/node": "^14.14.37"
}
}
'use strict';
const fs = require('fs');
const util = require('util');
import sdk, { Device, DeviceProvider, MediaPlayer, MediaPlayerState, MediaStatus, Notifier, Refresh, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface } from '@scrypted/sdk';
import {EventEmitter} from 'events';
import memoizeOne from 'memoize-one';
import util from 'util';
import sdk, { Device, DeviceProvider, EngineIOHandler, HttpRequest, MediaObject, MediaPlayer, MediaPlayerOptions, MediaPlayerState, MediaStatus, Refresh, ScryptedDeviceBase, ScryptedDeviceType, ScryptedInterface, ScryptedMimeTypes } from '@scrypted/sdk';
import { EventEmitter } from 'events';
import mdns from 'mdns';
import mime from 'mime';
const { mediaManager, deviceManager, log } = sdk;
// fs.registerFile(resolve('../node_modules/castv2/lib/cast_channel.proto'), require('raw-loader!../node_modules/castv2/lib/cast_channel.proto'))
// fs.registerFile(resolve('../node_modules/castv2/lib/cast_channel.proto'), require('raw-loader!../node_modules/castv2/lib/cast_channel.proto'))
const mdns = require('mdns');
const { mediaManager, systemManager, endpointManager, deviceManager, log } = sdk;
const { DefaultMediaReceiver } = require('castv2-client');
const Client = require('castv2-client').Client;
const DefaultMediaReceiver = require('castv2-client').DefaultMediaReceiver;
function ScryptedMediaReceiver() {
DefaultMediaReceiver.apply(this, arguments);
}
ScryptedMediaReceiver.APP_ID = '9E3714BD';
ScryptedMediaReceiver.APP_ID = '00F7C5DD';
util.inherits(ScryptedMediaReceiver, DefaultMediaReceiver);
const audioFetch = (body) => {
var buf = Buffer.from(body);
var mo = mediaManager.createMediaObject(buf, 'text/plain');
return mediaManager.convertMediaObjectToLocalUrl(mo, 'audio/*');
}
// memoize this text conversion, as announcements going to multiple speakers will
// trigger multiple text to speech conversions.
// this is a simple way to prevent thrashing by waiting for the single promise.
var memoizeAudioFetch = memoizeOne(audioFetch);
// castv2 makes the the assumption that protobufjs returns Buffers, which is does not. It returns ArrayBuffers

@@ -39,4 +23,4 @@ // in the quickjs environment.

if (buffer && (buffer.constructor.name === ArrayBuffer.name || buffer.constructor.name === Uint8Array.name)) {
var ret = Buffer.from(buffer);
return ret;
var ret = Buffer.from(buffer);
return ret;
}

@@ -46,3 +30,3 @@ return buffer;

const BufferConcat = Buffer.concat;
Buffer.concat = function(bufs) {
Buffer.concat = function (bufs) {
var copy = [];

@@ -55,22 +39,7 @@ for (var buf of bufs) {

// const Buffer = require('buffer');
// const BufferConcat = Buffer.concat;
// Buffer.concat = function(buffers) {
// var fixed = [];
// for (let buffer of buffers) {
// if (buffer.constructor.name !== 'Buffer') {
// fixed.push(Buffer.from(buffer));
// }
// else {
// fixed.push(buffer);
// }
// }
// return BufferConcat(fixed);
// }
class CastDevice extends ScryptedDeviceBase implements Notifier, MediaPlayer, Refresh {
class CastDevice extends ScryptedDeviceBase implements MediaPlayer, Refresh, EngineIOHandler {
provider: CastDeviceProvider;
host: any;
device: Device;
port: number;

@@ -82,7 +51,7 @@ constructor(provider: CastDeviceProvider, nativeId: string) {

currentApp: string;
currentApp: any;
playerPromise: Promise<any>;
connectPlayer(app: string): Promise<any> {
connectPlayer(app: any): Promise<any> {
if (this.playerPromise) {
if (this.currentApp === app) {
if (this.currentApp === app && this.clientPromise) {
return this.playerPromise;

@@ -93,3 +62,7 @@ }

player.removeAllListeners();
player.close();
try {
player.close();
}
catch (e) {
}
});

@@ -134,42 +107,37 @@ this.playerPromise = undefined;

var promise;
var resolved = false;
return this.clientPromise = promise = new Promise((resolve, reject) => {
var client = new Client();
client.on('error', err => {
this.log.i(`Client error: ${err.message}`);
const cleanup = () => {
client.removeAllListeners();
client.close();
if (this.clientPromise === promise) {
this.clientPromise = undefined;
}
if (!resolved) {
resolved = true;
reject(err);
}
}
client.on('close', cleanup);
client.on('error', err => {
this.log.i(`Client error: ${err.message}`);
cleanup();
reject(err);
});
client.on('status', status => {
client.on('status', async status => {
this.log.i(JSON.stringify(status));
this.joinPlayer()
.catch(() => { });
try {
await this.joinPlayer();
}
catch (e) {
}
})
client.connect(this.host, () => {
this.log.i(`client connected.`);
resolved = true;
resolve(client);
});
})
.catch(err => {
this.log.i(`client connect error: ${err.message}`);
this.clientPromise = undefined;
throw err;
})
}
sendMediaToClient(title, mediaUrl, mimeType, opts?) {
var media = {
tokens = new Map<string, MediaObject>();
async sendMediaToClient(title: string, mediaUrl: string, mimeType: string, opts?: any) {
var media: any = {
// Here you can plug an URL to any mp4, webm, mp3 or jpg file with the proper contentType.

@@ -189,46 +157,108 @@ contentId: mediaUrl,

customData: {
senderId: pushManager.getSenderId(),
registrationId: pushManager.getRegistrationId(),
}
};
var app;
if (!mediaUrl.startsWith('http')) {
app = ScryptedMediaReceiver;
opts = opts || {
autoplay: true,
}
else {
app = DefaultMediaReceiver;
const player = await this.connectPlayer(DefaultMediaReceiver)
player.load(media, opts, (err, status) => {
if (err) {
this.log.e(`load error: ${err}`);
return;
}
this.log.i(`media loaded playerState=${status.playerState}`);
});
}
async load(media: string|MediaObject, options: MediaPlayerOptions) {
// check to see if this is url friendly media.
if (typeof media === 'string')
media = mediaManager.createMediaObject(media, ScryptedMimeTypes.Url);
if (media.mimeType === ScryptedMimeTypes.LocalUrl ||
media.mimeType === ScryptedMimeTypes.InsecureLocalUrl ||
media.mimeType === ScryptedMimeTypes.Url ||
media.mimeType.startsWith('image/') ||
media.mimeType.startsWith('video/')) {
// chromecast can handle insecure local urls, but not self signed secure urls.
const url = media.mimeType === ScryptedMimeTypes.InsecureLocalUrl || media.mimeType === ScryptedMimeTypes.LocalUrl
? await mediaManager.convertMediaObjectToInsecureLocalUrl(media, media.mimeType)
: await mediaManager.convertMediaObjectToUrl(media, media.mimeType);
this.sendMediaToClient(options && (options as any).title,
url,
// prefer the provided mime type hint, otherwise infer from url.
options.mimeType || mime.getType(url));
return;
}
opts = opts || {
// this media object is something weird that can't be handled by a straightforward url.
// try to make a webrtc a/v session to handle it.
const engineio = await endpointManager.getPublicLocalEndpoint(this.nativeId) + 'engine.io/';
const mo = mediaManager.createMediaObject(engineio, ScryptedMimeTypes.LocalUrl);
const cameraStreamAuthToken = await mediaManager.convertMediaObjectToUrl(mo, ScryptedMimeTypes.LocalUrl);
const token = Math.random().toString();
this.tokens.set(token, media);
var castMedia: any = {
contentId: cameraStreamAuthToken,
contentType: ScryptedMimeTypes.LocalUrl,
streamType: 'LIVE',
// Title and cover displayed while buffering
metadata: {
type: 0,
metadataType: 0,
title: options?.title || 'Scrypted',
},
customData: {
token,
}
};
const opts = {
autoplay: true,
}
this.connectPlayer(app)
.then(player => {
player.load(media, opts, (err, status) => {
if (err) {
this.log.e(`load error: ${err}`);
return;
}
this.log.i(`media loaded playerState=${status.playerState}`);
});
})
.catch(err => {
this.log.e(`connect error: ${err}`);
});
const player = await this.connectPlayer(ScryptedMediaReceiver as any)
player.load(castMedia, opts, (err, status) => {
if (err) {
this.log.e(`load error: ${err}`);
return;
}
this.log.i(`media loaded playerState=${status.playerState}`);
});
}
load(media, options) {
// the mediaManager is provided by Scrypted and can be used to convert
// MediaObjects into other objects.
// For example, a MediaObject from a RTSP camera can be converted to an externally
// accessible Uri png image using mediaManager.convert.
mediaManager.convertMediaObjectToUrl(media, null)
.then(result => {
this.sendMediaToClient(options && options.title, result, media.mimeType);
});
async onConnection(request: HttpRequest, webSocketUrl: string) {
const ws = new WebSocket(webSocketUrl);
ws.onmessage = async (message) => {
const token = message.data as string;
const media = this.tokens.get(token);
if (!media) {
ws.close();
return;
}
const offer = await mediaManager.convertMediaObjectToBuffer(
media,
ScryptedMimeTypes.RTCAVOffer
);
ws.send(offer.toString());
const answer = await new Promise(resolve => ws.onmessage = (message) => resolve(message.data));
const mo = mediaManager.createMediaObject(Buffer.from(answer as string), ScryptedMimeTypes.RTCAVAnswer);
mediaManager.convertMediaObjectToBuffer(mo, ScryptedMimeTypes.RTCAVOffer);
}
}
static CastInactive = new Error('Media player is inactive.');
mediaPlayerPromise: Promise<any>;

@@ -255,3 +285,3 @@ mediaPlayerStatus: any;

this.updateState();
reject(CastDevice.CastInactive);
reject(new Error('Media player is inactive.'));
return;

@@ -303,9 +333,9 @@ }

start() {
this.joinPlayer()
.then(player => player.play());
async start() {
const player = await this.joinPlayer();
player.start();
}
pause() {
this.joinPlayer()
.then(player => player.pause());
async pause() {
const player = await this.joinPlayer();
player.pause();
}

@@ -331,3 +361,3 @@ parseState(): MediaPlayerState {

this.stateTimestamp = Date.now();
const mediaPlayerStatus = this.getMediaStatus();
const mediaPlayerStatus = this.getMediaStatusInternal();
switch (mediaPlayerStatus.mediaPlayerState) {

@@ -347,3 +377,6 @@ case MediaPlayerState.Idle:

}
getMediaStatus(): MediaStatus {
async getMediaStatus() {
return this.getMediaStatusInternal();
}
getMediaStatusInternal(): MediaStatus {
var mediaPlayerState: MediaPlayerState = this.parseState();

@@ -363,61 +396,33 @@ const media = this.mediaPlayerStatus && this.mediaPlayerStatus.media;

}
seek(milliseconds: number): void {
this.joinPlayer()
.then(player => player.seek(milliseconds));
async seek(milliseconds: number) {
const player = await this.joinPlayer();
player.seek(milliseconds);
}
resume(): void {
this.joinPlayer()
.then(player => player.play());
async resume() {
const player = await this.joinPlayer();
player.play();
}
stop() {
this.joinPlayer()
.then(player => player.stop());
async stop() {
const player = await this.joinPlayer();
// this would disconnect and leave it in a launched but idle state
// player.stop();
// this returns to the homescreen
const client = await this.clientPromise;
client.stop(player, () => console.log('stpoped'));
this.clientPromise = null;
}
skipNext(): void {
this.joinPlayer()
.then(player => player.media.sessionRequest({ type: 'QUEUE_NEXT' }));
async skipNext() {
const player = await this.joinPlayer();
player.media.sessionRequest({ type: 'QUEUE_NEXT' });
}
skipPrevious(): void {
this.joinPlayer()
.then(player => player.media.sessionRequest({ type: 'QUEUE_PREV' }));
async skipPrevious() {
const player = await this.joinPlayer();
player.media.sessionRequest({ type: 'QUEUE_PREV' });
}
sendNotificationToHost(title, body, media, mimeType) {
if (!media || this.type == 'Speaker') {
log.i('fetching audio: ' + body);
memoizeAudioFetch(body)
.then(result => {
const insecure = result.toString().replace('https://', 'http://').replace(':9443', ':10080');
this.log.i(`sending audio ${insecure}`);
this.sendMediaToClient(title, insecure, 'audio/*');
})
.catch(e => {
this.log.e(`error memoizing audio ${e}`);
// do not cache errors.
memoizeAudioFetch = memoizeOne(audioFetch);
});
return;
}
mediaManager.convertMediaObjectToUrl(media, null)
.then(result => {
this.log.i(`url: ${result}`);
this.sendMediaToClient(title, result, mimeType);
});
}
sendNotification(title, body, media, mimeType) {
if (!this.device) {
this.provider.search.removeAllListeners(this.id);
this.provider.search.once(this.id, () => this.sendNotificationToHost(title, body, media, mimeType));
this.provider.discoverDevices(30000);
return;
}
setImmediate(() => this.sendNotificationToHost(title, body, media, mimeType));
}
getRefreshFrequency(): number {
async getRefreshFrequency(): Promise<number> {
return 60;
}
refresh(refreshInterface: string, userInitiated: boolean): void {
async refresh(refreshInterface: string, userInitiated: boolean) {
this.joinPlayer()

@@ -448,3 +453,10 @@ .catch(() => { });

var interfaces = ['Notifier', 'MediaPlayer', 'Refresh'];
var interfaces = [
ScryptedInterface.MediaPlayer,
ScryptedInterface.Refresh,
ScryptedInterface.StartStop,
ScryptedInterface.Pause,
ScryptedInterface.EngineIOHandler,
ScryptedInterface.HttpRequestHandler,
];

@@ -463,4 +475,6 @@ var device: Device = {

var host = service.addresses[0];
const host = service.addresses[0];
const port = service.port;
this.log.i(`found cast device: ${name}`);

@@ -471,2 +485,3 @@

castDevice.host = host;
castDevice.port = port;

@@ -484,3 +499,3 @@ this.search.emit(id);

discoverDevices(duration: number) {
async discoverDevices(duration: number) {
if (this.searching) {

@@ -487,0 +502,0 @@ return;

Sorry, the diff of this file is not supported yet

SocketSocket SOC 2 Logo

Product

  • Package Alerts
  • Integrations
  • Docs
  • Pricing
  • FAQ
  • Roadmap
  • Changelog

Packages

npm

Stay in touch

Get open source security insights delivered straight into your inbox.


  • Terms
  • Privacy
  • Security

Made with ⚡️ by Socket Inc