sdk/server: media object updates to track source id

This commit is contained in:
Koushik Dutta
2022-04-03 20:16:17 -07:00
parent a041f4f653
commit 64f5ab9483
19 changed files with 207 additions and 125 deletions

View File

@@ -194,7 +194,7 @@ export interface Notifier {
*/
export interface MediaObject {
mimeType: string;
console?: Console;
sourceId?: string;
}
/**
* StartStop represents a device that can be started, stopped, and possibly paused and resumed. Typically vacuum cleaners or washers.
@@ -704,12 +704,17 @@ export interface SoftwareUpdate {
updateAvailable?: boolean;
}
export interface BufferConvertorOptions {
sourceId?: string;
}
/**
* Add a converter to be used by Scrypted to convert buffers from one mime type to another mime type.
* May optionally accept string urls if accept-url is a fromMimeType parameter.
*/
export interface BufferConverter {
convert(data: string | Buffer | any, fromMimeType: string, toMimeType: string): Promise<MediaObject | Buffer | any>;
convert(data: string | Buffer | any, fromMimeType: string, toMimeType: string, options?: BufferConvertorOptions): Promise<MediaObject | Buffer | any>;
fromMimeType?: string;
toMimeType?: string;
@@ -911,9 +916,9 @@ export interface OauthClient {
export interface MediaObjectOptions {
/**
* The default console to be used when logging usage of the MediaObject.
* The device id of the source of the MediaObject.
*/
console?: Console;
sourceId?: string;
}
export interface MediaManager {
@@ -960,7 +965,7 @@ export interface MediaManager {
/**
* Create a MediaObject from an URL. The mime type will be determined dynamically while resolving the url.
*/
createMediaObjectFromUrl(data: string, options?: MediaObjectOptions): Promise<MediaObject>;
createMediaObjectFromUrl(data: string, options?: MediaObjectOptions): Promise<MediaObject>;
/**
* Create a MediaObject.
@@ -1456,13 +1461,7 @@ export enum ScryptedMimeTypes {
PushEndpoint = 'text/x-push-endpoint',
MediaStreamUrl = 'text/x-media-url',
FFmpegInput = 'x-scrypted/x-ffmpeg-input',
/**
* An RTCSignalingChannel/VideoCamera will return x-scrypted-rtc-signaling-<unique-prefix>/x-<unique-suffix>.
* RTC clients can inspect the mime and convert the contents to a buffer containing the string device id.
* If the client does not support WebRTC, it may try to convert it to an FFmpeg media object,
* which should also be trapped and handled by the endpoint using its internal signaling.
*/
RTCAVSignalingPrefix = 'x-scrypted-rtc-signaling-',
RTCSignalingChannel = 'x-scrypted/x-scrypted-rtc-signaling-channel',
SchemePrefix = 'x-scrypted/x-scrypted-scheme-',
MediaObject = 'x-scrypted/x-scrypted-media-object',
}

6
sdk/index.d.ts vendored
View File

@@ -1,5 +1,5 @@
export * from './types/index';
import { DeviceBase } from './types/index';
import { DeviceBase, MediaObject } from './types/index';
import type { ScryptedNativeId, EventListenerRegister } from './types/index';
import type { ScryptedInterface, ScryptedStatic, Logger, DeviceState } from './types/index';
export declare class ScryptedDeviceBase extends DeviceBase {
@@ -12,6 +12,8 @@ export declare class ScryptedDeviceBase extends DeviceBase {
get storage(): Storage;
get log(): Logger;
get console(): Console;
createMediaObject(data: any, mimeType: string): Promise<MediaObject>;
getMediaObjectConsole(mediaObject: MediaObject): Console;
_lazyLoadDeviceState(): void;
/**
* Fire an event for this device.
@@ -38,6 +40,8 @@ export declare class MixinDeviceBase<T> extends DeviceBase implements DeviceStat
constructor(options: MixinDeviceOptions<T>);
get storage(): Storage;
get console(): Console;
createMediaObject(data: any, mimeType: string): Promise<MediaObject>;
getMediaObjectConsole(mediaObject: MediaObject): Console;
/**
* Fire an event for this device.
*/

View File

@@ -36,6 +36,16 @@ class ScryptedDeviceBase extends index_1.DeviceBase {
}
return this._console;
}
async createMediaObject(data, mimeType) {
return mediaManager.createMediaObject(data, mimeType, {
sourceId: this.id,
});
}
getMediaObjectConsole(mediaObject) {
if (typeof mediaObject.sourceId !== 'string')
return this.console;
return deviceManager.getMixinConsole(mediaObject.sourceId, this.nativeId);
}
_lazyLoadDeviceState() {
if (!this._deviceState) {
if (this.nativeId) {
@@ -81,6 +91,16 @@ class MixinDeviceBase extends index_1.DeviceBase {
}
return this._console;
}
async createMediaObject(data, mimeType) {
return mediaManager.createMediaObject(data, mimeType, {
sourceId: this.id,
});
}
getMediaObjectConsole(mediaObject) {
if (typeof mediaObject.sourceId !== 'string')
return this.console;
return deviceManager.getMixinConsole(mediaObject.sourceId, this.mixinProviderNativeId);
}
/**
* Fire an event for this device.
*/

View File

@@ -1,5 +1,5 @@
export * from './types/index'
import { ScryptedInterfaceProperty, DeviceBase } from './types/index';
import { ScryptedInterfaceProperty, DeviceBase, MediaObject } from './types/index';
import type { ScryptedNativeId, DeviceManager, SystemManager, MediaManager, EndpointManager, EventListenerRegister } from './types/index';
import type { ScryptedInterface, ScryptedStatic, Logger, DeviceState } from './types/index';
@@ -35,6 +35,18 @@ export class ScryptedDeviceBase extends DeviceBase {
return this._console;
}
async createMediaObject(data: any, mimeType: string) {
return mediaManager.createMediaObject(data, mimeType, {
sourceId: this.id,
});
}
getMediaObjectConsole(mediaObject: MediaObject): Console {
if (typeof mediaObject.sourceId !== 'string')
return this.console;
return deviceManager.getMixinConsole(mediaObject.sourceId, this.nativeId);
}
_lazyLoadDeviceState() {
if (!this._deviceState) {
if (this.nativeId) {
@@ -102,6 +114,18 @@ export class MixinDeviceBase<T> extends DeviceBase implements DeviceState {
return this._console;
}
async createMediaObject(data: any, mimeType: string) {
return mediaManager.createMediaObject(data, mimeType, {
sourceId: this.id,
});
}
getMediaObjectConsole(mediaObject: MediaObject): Console {
if (typeof mediaObject.sourceId !== 'string')
return this.console;
return deviceManager.getMixinConsole(mediaObject.sourceId, this.mixinProviderNativeId);
}
/**
* Fire an event for this device.
*/

4
sdk/package-lock.json generated
View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/sdk",
"version": "0.0.187",
"version": "0.0.190",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/sdk",
"version": "0.0.187",
"version": "0.0.190",
"license": "ISC",
"dependencies": {
"@babel/preset-typescript": "^7.16.7",

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/sdk",
"version": "0.0.187",
"version": "0.0.190",
"description": "",
"main": "index.js",
"scripts": {

View File

@@ -132,7 +132,7 @@ class ScryptedMimeTypes(Enum):
MediaObject = "x-scrypted/x-scrypted-media-object"
MediaStreamUrl = "text/x-media-url"
PushEndpoint = "text/x-push-endpoint"
RTCAVSignalingPrefix = "x-scrypted-rtc-signaling-"
RTCSignalingChannel = "x-scrypted/x-scrypted-rtc-signaling-channel"
SchemePrefix = "x-scrypted/x-scrypted-scheme-"
Url = "text/x-uri"
@@ -197,6 +197,10 @@ class BufferConverter(TypedDict):
toMimeType: str
pass
class BufferConvertorOptions(TypedDict):
sourceId: str
pass
class ColorHsv(TypedDict):
h: float
s: float
@@ -305,7 +309,7 @@ class Logger(TypedDict):
pass
class MediaObjectOptions(TypedDict):
console: Console
sourceId: str
pass
class MediaPlayerOptions(TypedDict):
@@ -512,7 +516,7 @@ class Brightness:
class BufferConverter:
fromMimeType: str
toMimeType: str
async def convert(self, data: Any, fromMimeType: str, toMimeType: str) -> Any:
async def convert(self, data: Any, fromMimeType: str, toMimeType: str, options: BufferConvertorOptions = None) -> Any:
pass
pass

19
sdk/types/index.d.ts vendored
View File

@@ -338,7 +338,7 @@ export interface Notifier {
*/
export interface MediaObject {
mimeType: string;
console?: Console;
sourceId?: string;
}
/**
* StartStop represents a device that can be started, stopped, and possibly paused and resumed. Typically vacuum cleaners or washers.
@@ -798,12 +798,15 @@ export interface SoftwareUpdate {
installUpdate(): Promise<void>;
updateAvailable?: boolean;
}
export interface BufferConvertorOptions {
sourceId?: string;
}
/**
* Add a converter to be used by Scrypted to convert buffers from one mime type to another mime type.
* May optionally accept string urls if accept-url is a fromMimeType parameter.
*/
export interface BufferConverter {
convert(data: string | Buffer | any, fromMimeType: string, toMimeType: string): Promise<MediaObject | Buffer | any>;
convert(data: string | Buffer | any, fromMimeType: string, toMimeType: string, options?: BufferConvertorOptions): Promise<MediaObject | Buffer | any>;
fromMimeType?: string;
toMimeType?: string;
}
@@ -994,9 +997,9 @@ export interface OauthClient {
}
export interface MediaObjectOptions {
/**
* The default console to be used when logging usage of the MediaObject.
* The device id of the source of the MediaObject.
*/
console?: Console;
sourceId?: string;
}
export interface MediaManager {
/**
@@ -1482,13 +1485,7 @@ export declare enum ScryptedMimeTypes {
PushEndpoint = "text/x-push-endpoint",
MediaStreamUrl = "text/x-media-url",
FFmpegInput = "x-scrypted/x-ffmpeg-input",
/**
* An RTCSignalingChannel/VideoCamera will return x-scrypted-rtc-signaling-<unique-prefix>/x-<unique-suffix>.
* RTC clients can inspect the mime and convert the contents to a buffer containing the string device id.
* If the client does not support WebRTC, it may try to convert it to an FFmpeg media object,
* which should also be trapped and handled by the endpoint using its internal signaling.
*/
RTCAVSignalingPrefix = "x-scrypted-rtc-signaling-",
RTCSignalingChannel = "x-scrypted/x-scrypted-rtc-signaling-channel",
SchemePrefix = "x-scrypted/x-scrypted-scheme-",
MediaObject = "x-scrypted/x-scrypted-media-object"
}

View File

@@ -752,13 +752,7 @@ var ScryptedMimeTypes;
ScryptedMimeTypes["PushEndpoint"] = "text/x-push-endpoint";
ScryptedMimeTypes["MediaStreamUrl"] = "text/x-media-url";
ScryptedMimeTypes["FFmpegInput"] = "x-scrypted/x-ffmpeg-input";
/**
* An RTCSignalingChannel/VideoCamera will return x-scrypted-rtc-signaling-<unique-prefix>/x-<unique-suffix>.
* RTC clients can inspect the mime and convert the contents to a buffer containing the string device id.
* If the client does not support WebRTC, it may try to convert it to an FFmpeg media object,
* which should also be trapped and handled by the endpoint using its internal signaling.
*/
ScryptedMimeTypes["RTCAVSignalingPrefix"] = "x-scrypted-rtc-signaling-";
ScryptedMimeTypes["RTCSignalingChannel"] = "x-scrypted/x-scrypted-rtc-signaling-channel";
ScryptedMimeTypes["SchemePrefix"] = "x-scrypted/x-scrypted-scheme-";
ScryptedMimeTypes["MediaObject"] = "x-scrypted/x-scrypted-media-object";
})(ScryptedMimeTypes = exports.ScryptedMimeTypes || (exports.ScryptedMimeTypes = {}));

View File

@@ -895,7 +895,7 @@ export interface Notifier {
*/
export interface MediaObject {
mimeType: string;
console?: Console;
sourceId?: string;
}
/**
* StartStop represents a device that can be started, stopped, and possibly paused and resumed. Typically vacuum cleaners or washers.
@@ -1405,12 +1405,17 @@ export interface SoftwareUpdate {
updateAvailable?: boolean;
}
export interface BufferConvertorOptions {
sourceId?: string;
}
/**
* Add a converter to be used by Scrypted to convert buffers from one mime type to another mime type.
* May optionally accept string urls if accept-url is a fromMimeType parameter.
*/
export interface BufferConverter {
convert(data: string | Buffer | any, fromMimeType: string, toMimeType: string): Promise<MediaObject | Buffer | any>;
convert(data: string | Buffer | any, fromMimeType: string, toMimeType: string, options?: BufferConvertorOptions): Promise<MediaObject | Buffer | any>;
fromMimeType?: string;
toMimeType?: string;
@@ -1612,9 +1617,9 @@ export interface OauthClient {
export interface MediaObjectOptions {
/**
* The default console to be used when logging usage of the MediaObject.
* The device id of the source of the MediaObject.
*/
console?: Console;
sourceId?: string;
}
export interface MediaManager {
@@ -1661,7 +1666,7 @@ export interface MediaManager {
/**
* Create a MediaObject from an URL. The mime type will be determined dynamically while resolving the url.
*/
createMediaObjectFromUrl(data: string, options?: MediaObjectOptions): Promise<MediaObject>;
createMediaObjectFromUrl(data: string, options?: MediaObjectOptions): Promise<MediaObject>;
/**
* Create a MediaObject.
@@ -2157,13 +2162,7 @@ export enum ScryptedMimeTypes {
PushEndpoint = 'text/x-push-endpoint',
MediaStreamUrl = 'text/x-media-url',
FFmpegInput = 'x-scrypted/x-ffmpeg-input',
/**
* An RTCSignalingChannel/VideoCamera will return x-scrypted-rtc-signaling-<unique-prefix>/x-<unique-suffix>.
* RTC clients can inspect the mime and convert the contents to a buffer containing the string device id.
* If the client does not support WebRTC, it may try to convert it to an FFmpeg media object,
* which should also be trapped and handled by the endpoint using its internal signaling.
*/
RTCAVSignalingPrefix = 'x-scrypted-rtc-signaling-',
RTCSignalingChannel = 'x-scrypted/x-scrypted-rtc-signaling-channel',
SchemePrefix = 'x-scrypted/x-scrypted-scheme-',
MediaObject = 'x-scrypted/x-scrypted-media-object',
}

View File

@@ -1,12 +1,12 @@
{
"name": "@scrypted/types",
"version": "0.0.29",
"version": "0.0.32",
"lockfileVersion": 2,
"requires": true,
"packages": {
"": {
"name": "@scrypted/types",
"version": "0.0.29",
"version": "0.0.32",
"license": "ISC",
"devDependencies": {}
}

View File

@@ -1,6 +1,6 @@
{
"name": "@scrypted/types",
"version": "0.0.29",
"version": "0.0.32",
"description": "",
"main": "index.js",
"author": "",

View File

@@ -132,7 +132,7 @@ class ScryptedMimeTypes(Enum):
MediaObject = "x-scrypted/x-scrypted-media-object"
MediaStreamUrl = "text/x-media-url"
PushEndpoint = "text/x-push-endpoint"
RTCAVSignalingPrefix = "x-scrypted-rtc-signaling-"
RTCSignalingChannel = "x-scrypted/x-scrypted-rtc-signaling-channel"
SchemePrefix = "x-scrypted/x-scrypted-scheme-"
Url = "text/x-uri"
@@ -197,6 +197,10 @@ class BufferConverter(TypedDict):
toMimeType: str
pass
class BufferConvertorOptions(TypedDict):
sourceId: str
pass
class ColorHsv(TypedDict):
h: float
s: float
@@ -305,7 +309,7 @@ class Logger(TypedDict):
pass
class MediaObjectOptions(TypedDict):
console: Console
sourceId: str
pass
class MediaPlayerOptions(TypedDict):
@@ -512,7 +516,7 @@ class Brightness:
class BufferConverter:
fromMimeType: str
toMimeType: str
async def convert(self, data: Any, fromMimeType: str, toMimeType: str) -> Any:
async def convert(self, data: Any, fromMimeType: str, toMimeType: str, options: BufferConvertorOptions = None) -> Any:
pass
pass

View File

@@ -11,7 +11,7 @@
"dependencies": {
"@mapbox/node-pre-gyp": "^1.0.8",
"@scrypted/ffmpeg": "^1.0.10",
"@scrypted/types": "^0.0.28",
"@scrypted/types": "^0.0.32",
"adm-zip": "^0.5.3",
"axios": "^0.21.1",
"body-parser": "^1.19.0",
@@ -157,9 +157,9 @@
}
},
"node_modules/@scrypted/types": {
"version": "0.0.28",
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.0.28.tgz",
"integrity": "sha512-6gxswU3SAyu79y+iS93yho1/0D8BtPSfgdOMOF29cx4L0ZRMkfHZ6czBteQj21BBpHQSXPd0AGCDwYZd5pf27Q=="
"version": "0.0.32",
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.0.32.tgz",
"integrity": "sha512-hQq3AkoEKgbfxQS3FerIN0P82rHjDqV0hEGyHjtmrHWozx9y4W9ihHPETkAOGxL7cb63HffUjcKtFCGWowBfkw=="
},
"node_modules/@tootallnate/once": {
"version": "1.1.2",
@@ -3340,9 +3340,9 @@
}
},
"@scrypted/types": {
"version": "0.0.28",
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.0.28.tgz",
"integrity": "sha512-6gxswU3SAyu79y+iS93yho1/0D8BtPSfgdOMOF29cx4L0ZRMkfHZ6czBteQj21BBpHQSXPd0AGCDwYZd5pf27Q=="
"version": "0.0.32",
"resolved": "https://registry.npmjs.org/@scrypted/types/-/types-0.0.32.tgz",
"integrity": "sha512-hQq3AkoEKgbfxQS3FerIN0P82rHjDqV0hEGyHjtmrHWozx9y4W9ihHPETkAOGxL7cb63HffUjcKtFCGWowBfkw=="
},
"@tootallnate/once": {
"version": "1.1.2",

View File

@@ -5,7 +5,7 @@
"dependencies": {
"@mapbox/node-pre-gyp": "^1.0.8",
"@scrypted/ffmpeg": "^1.0.10",
"@scrypted/types": "^0.0.28",
"@scrypted/types": "^0.0.32",
"adm-zip": "^0.5.3",
"axios": "^0.21.1",
"body-parser": "^1.19.0",

View File

@@ -1,19 +1,19 @@
import { ScryptedInterfaceProperty, SystemDeviceState, MediaStreamUrl, BufferConverter, FFMpegInput, MediaManager, MediaObject, ScryptedInterface, ScryptedMimeTypes, SystemManager } from "@scrypted/types";
import { MediaObjectRemote } from "./plugin-api";
import mimeType from 'mime'
import { getInstalledFfmpeg } from '@scrypted/ffmpeg';
import { BufferConverter, BufferConvertorOptions, DeviceManager, FFMpegInput, MediaManager, MediaObject, MediaObjectOptions, MediaStreamUrl, ScryptedInterface, ScryptedInterfaceProperty, ScryptedMimeTypes, ScryptedNativeId, SystemDeviceState, SystemManager } from "@scrypted/types";
import axios from 'axios';
import child_process from 'child_process';
import { once } from 'events';
import fs from 'fs';
import tmp from 'tmp';
import os from 'os';
import { getInstalledFfmpeg } from '@scrypted/ffmpeg'
import Graph from 'node-dijkstra';
import MimeType from 'whatwg-mimetype';
import axios from 'axios';
import https from 'https';
import rimraf from "rimraf";
import mimeType from 'mime';
import mkdirp from "mkdirp";
import Graph from 'node-dijkstra';
import os from 'os';
import path from 'path';
import rimraf from "rimraf";
import tmp from 'tmp';
import MimeType from 'whatwg-mimetype';
import { MediaObjectRemote } from "./plugin-api";
function typeMatches(target: string, candidate: string): boolean {
// candidate will accept anything
@@ -33,7 +33,7 @@ const httpsAgent = new https.Agent({
export abstract class MediaManagerBase implements MediaManager {
builtinConverters: BufferConverter[] = [];
constructor(public console: Console) {
constructor() {
for (const h of ['http', 'https']) {
this.builtinConverters.push({
fromMimeType: ScryptedMimeTypes.SchemePrefix + h,
@@ -56,7 +56,7 @@ export abstract class MediaManagerBase implements MediaManager {
convert: async (data, fromMimeType, toMimeType) => {
const filename = data.toString();
const ab = await fs.promises.readFile(filename);
const mt = mimeType.lookup(data.toString());
const mt = mimeType.lookup(data.toString());
const mo = this.createMediaObject(ab, mt);
return mo;
}
@@ -121,7 +121,9 @@ export abstract class MediaManagerBase implements MediaManager {
this.builtinConverters.push({
fromMimeType: ScryptedMimeTypes.FFmpegInput,
toMimeType: 'image/jpeg',
convert: async (data, fromMimeType: string): Promise<Buffer> => {
convert: async (data, fromMimeType: string, toMimeType: string, options?: BufferConvertorOptions): Promise<Buffer> => {
const console = this.getMixinConsole(options.sourceId, undefined);
const ffInput: FFMpegInput = JSON.parse(data.toString());
const args = [
@@ -161,6 +163,8 @@ export abstract class MediaManagerBase implements MediaManager {
abstract getSystemState(): { [id: string]: { [property: string]: SystemDeviceState } };
abstract getDeviceById<T>(id: string): T;
abstract getPluginDeviceId(): string;
abstract getMixinConsole(mixinId: string, nativeId: ScryptedNativeId): Console;
async getFFmpegPath(): Promise<string> {
// try to get the ffmpeg path as a value of another variable
@@ -238,11 +242,7 @@ export abstract class MediaManagerBase implements MediaManager {
return url.data.toString();
}
async createFFmpegMediaObject(ffMpegInput: FFMpegInput): Promise<MediaObject> {
return this.createMediaObjectRemote(Buffer.from(JSON.stringify(ffMpegInput)), ScryptedMimeTypes.FFmpegInput);
}
createMediaObjectRemote(data: any | Buffer | Promise<string | Buffer>, mimeType: string): MediaObjectRemote {
createMediaObjectRemote(data: any | Buffer | Promise<string | Buffer>, mimeType: string, options?: MediaObjectOptions): MediaObjectRemote {
if (typeof data === 'string')
throw new Error('string is not a valid type. if you intended to send a url, use createMediaObjectFromUrl.');
if (!mimeType)
@@ -253,12 +253,15 @@ export abstract class MediaManagerBase implements MediaManager {
if (data.constructor.name !== Buffer.name)
data = Buffer.from(JSON.stringify(data));
const sourceId = typeof options?.sourceId === 'string' ? options?.sourceId : this.getPluginDeviceId();
class MediaObjectImpl implements MediaObjectRemote {
__proxy_props = {
mimeType,
sourceId,
}
mimeType = mimeType;
sourceId = sourceId;
async getData(): Promise<Buffer | string> {
return Promise.resolve(data);
}
@@ -266,21 +269,24 @@ export abstract class MediaManagerBase implements MediaManager {
return new MediaObjectImpl();
}
async createMediaObject(data: any, mimeType: string): Promise<MediaObject> {
return this.createMediaObjectRemote(data, mimeType);
async createFFmpegMediaObject(ffMpegInput: FFMpegInput, options?: MediaObjectOptions): Promise<MediaObject> {
return this.createMediaObjectRemote(Buffer.from(JSON.stringify(ffMpegInput)), ScryptedMimeTypes.FFmpegInput, options);
}
async createMediaObjectFromUrl(data: string): Promise<MediaObject> {
async createMediaObjectFromUrl(data: string, options?: MediaObjectOptions): Promise<MediaObject> {
const url = new URL(data);
const scheme = url.protocol.slice(0, -1);
const mimeType = ScryptedMimeTypes.SchemePrefix + scheme;
const sourceId = typeof options?.sourceId === 'string' ? options?.sourceId : this.getPluginDeviceId();
class MediaObjectImpl implements MediaObjectRemote {
__proxy_props = {
mimeType,
sourceId,
}
mimeType = mimeType;
sourceId = sourceId;
async getData(): Promise<Buffer | string> {
return Promise.resolve(data);
}
@@ -288,6 +294,10 @@ export abstract class MediaManagerBase implements MediaManager {
return new MediaObjectImpl();
}
async createMediaObject(data: any, mimeType: string, options?: MediaObjectOptions): Promise<MediaObject> {
return this.createMediaObjectRemote(data, mimeType, options);
}
async convert(converters: BufferConverter[], mediaObject: MediaObjectRemote, toMimeType: string): Promise<{ data: Buffer | string | any, mimeType: string }> {
// console.log('converting', mediaObject.mimeType, toMimeType);
const mediaMime = new MimeType(mediaObject.mimeType);
@@ -300,6 +310,11 @@ export abstract class MediaManagerBase implements MediaManager {
}
}
let sourceId = mediaObject?.sourceId;
if (typeof sourceId !== 'string')
sourceId = this.getPluginDeviceId();
const console = this.getMixinConsole(sourceId, undefined);
const converterIds = new Map<BufferConverter, string>();
const converterReverseids = new Map<string, BufferConverter>();
let id = 0;
@@ -363,6 +378,7 @@ export abstract class MediaManagerBase implements MediaManager {
route.splice(route.length - 1);
let value = await mediaObject.getData();
let valueMime = new MimeType(mediaObject.mimeType);
for (const node of route) {
const converter = converterReverseids.get(node);
const converterToMimeType = new MimeType(converter.toMimeType);
@@ -372,7 +388,7 @@ export abstract class MediaManagerBase implements MediaManager {
const targetMimeType = `${type}/${subtype}`;
if (converter.toMimeType === ScryptedMimeTypes.MediaObject) {
const mo = await converter.convert(value, valueMime.essence, toMimeType) as MediaObject;
const mo = await converter.convert(value, valueMime.essence, toMimeType, { sourceId }) as MediaObject;
const found = await this.convertMediaObjectToBuffer(mo, toMimeType);
return {
data: found,
@@ -380,7 +396,7 @@ export abstract class MediaManagerBase implements MediaManager {
};
}
value = await converter.convert(value, valueMime.essence, targetMimeType) as string | Buffer;
value = await converter.convert(value, valueMime.essence, targetMimeType, { sourceId }) as string | Buffer;
valueMime = new MimeType(targetMimeType);
}
@@ -392,8 +408,8 @@ export abstract class MediaManagerBase implements MediaManager {
}
export class MediaManagerImpl extends MediaManagerBase {
constructor(public systemManager: SystemManager, console: Console) {
super(console);
constructor(public systemManager: SystemManager, public deviceManager: DeviceManager) {
super();
}
getSystemState(): { [id: string]: { [property: string]: SystemDeviceState; }; } {
@@ -403,16 +419,35 @@ export class MediaManagerImpl extends MediaManagerBase {
getDeviceById<T>(id: string): T {
return this.systemManager.getDeviceById<T>(id);
}
getPluginDeviceId(): string {
return this.deviceManager.getDeviceState().id;
}
getMixinConsole(mixinId: string, nativeId: string): Console {
if (typeof mixinId !== 'string')
return this.deviceManager.getDeviceConsole(nativeId);
return this.deviceManager.getMixinConsole(mixinId, nativeId);
}
}
export class MediaManagerHostImpl extends MediaManagerBase {
constructor(public systemState: { [id: string]: { [property: string]: SystemDeviceState } },
public getDeviceById: (id: string) => any,
console: Console) {
super(console);
constructor(public pluginDeviceId: string,
public systemState: { [id: string]: { [property: string]: SystemDeviceState } },
public console: Console,
public getDeviceById: (id: string) => any) {
super();
}
getSystemState(): { [id: string]: { [property: string]: SystemDeviceState; }; } {
return this.systemState;
}
getPluginDeviceId(): string {
return this.pluginDeviceId;
}
getMixinConsole(mixinId: string, nativeId: string): Console {
return this.console;
}
}

View File

@@ -1,29 +1,29 @@
import { RpcPeer } from '../rpc';
import { Device, EngineIOHandler } from '@scrypted/types';
import AdmZip from 'adm-zip';
import { Device, EngineIOHandler } from '@scrypted/types'
import { ScryptedRuntime } from '../runtime';
import { Plugin } from '../db-types';
import io, { Socket } from 'engine.io';
import { setupPluginRemote } from './plugin-remote';
import { PluginAPIProxy, PluginRemote, PluginRemoteLoadZipOptions } from './plugin-api';
import { Logger } from '../logger';
import { MediaManagerHostImpl } from './media';
import WebSocket from 'ws';
import { sleep } from '../sleep';
import { PluginHostAPI } from './plugin-host-api';
import path from 'path';
import { PluginDebug } from './plugin-debug';
import { ensurePluginVolume, getScryptedVolume } from './plugin-volume';
import { ConsoleServer, createConsoleServer } from './plugin-console';
import { LazyRemote } from './plugin-lazy-remote';
import crypto from 'crypto';
import io, { Socket } from 'engine.io';
import fs from 'fs';
import mkdirp from 'mkdirp';
import path from 'path';
import rimraf from 'rimraf';
import { RuntimeWorker } from './runtime/runtime-worker';
import { PythonRuntimeWorker } from './runtime/python-worker';
import WebSocket from 'ws';
import { Plugin } from '../db-types';
import { Logger } from '../logger';
import { RpcPeer } from '../rpc';
import { ScryptedRuntime } from '../runtime';
import { sleep } from '../sleep';
import { MediaManagerHostImpl } from './media';
import { PluginAPIProxy, PluginRemote, PluginRemoteLoadZipOptions } from './plugin-api';
import { ConsoleServer, createConsoleServer } from './plugin-console';
import { PluginDebug } from './plugin-debug';
import { PluginHostAPI } from './plugin-host-api';
import { LazyRemote } from './plugin-lazy-remote';
import { setupPluginRemote } from './plugin-remote';
import { ensurePluginVolume, getScryptedVolume } from './plugin-volume';
import { NodeForkWorker } from './runtime/node-fork-worker';
import { NodeThreadWorker } from './runtime/node-thread-worker';
import { PythonRuntimeWorker } from './runtime/python-worker';
import { RuntimeWorker } from './runtime/runtime-worker';
const serverVersion = require('../../package.json').version;
@@ -109,6 +109,7 @@ export class PluginHost {
// allow garbage collection of the base 64 contents
plugin = undefined;
const pluginDeviceId = scrypted.findPluginDevice(this.pluginId)._id;
const logger = scrypted.getDeviceLogger(scrypted.findPluginDevice(this.pluginId));
const volume = getScryptedVolume();
@@ -157,7 +158,7 @@ export class PluginHost {
const { runtime } = this.packageJson.scrypted;
const mediaManager = runtime === 'python'
? new MediaManagerHostImpl(scrypted.stateManager.getSystemState(), id => scrypted.getDevice(id), console)
? new MediaManagerHostImpl(pluginDeviceId, scrypted.stateManager.getSystemState(), console, id => scrypted.getDevice(id))
: undefined;
this.api = new PluginHostAPI(scrypted, this.pluginId, this, mediaManager);

View File

@@ -1,13 +1,13 @@
import { RpcMessage, RpcPeer } from '../rpc';
import { SystemManager, DeviceManager, ScryptedNativeId, ScryptedStatic } from '@scrypted/types'
import { attachPluginRemote, PluginReader } from './plugin-remote';
import { PluginAPI } from './plugin-api';
import { MediaManagerImpl } from './media';
import { PassThrough } from 'stream';
import { Console } from 'console'
import { DeviceManager, ScryptedNativeId, ScryptedStatic, SystemManager } from '@scrypted/types';
import { Console } from 'console';
import net from 'net';
import { install as installSourceMapSupport } from 'source-map-support';
import net from 'net'
import { PassThrough } from 'stream';
import { RpcMessage, RpcPeer } from '../rpc';
import { MediaManagerImpl } from './media';
import { PluginAPI } from './plugin-api';
import { installOptionalDependencies } from './plugin-npm-dependencies';
import { attachPluginRemote, PluginReader } from './plugin-remote';
import { createREPLServer } from './plugin-repl';
export function startPluginRemote(pluginId: string, peerSend: (message: RpcMessage, reject?: (e: Error) => void) => void) {
@@ -172,9 +172,10 @@ export function startPluginRemote(pluginId: string, peerSend: (message: RpcMessa
let postInstallSourceMapSupport: (scrypted: ScryptedStatic) => void;
attachPluginRemote(peer, {
createMediaManager: async (sm) => {
createMediaManager: async (sm, dm) => {
systemManager = sm;
return new MediaManagerImpl(systemManager, getPluginConsole());
deviceManager = dm
return new MediaManagerImpl(systemManager, dm);
},
onGetRemote: async (_api, _pluginId) => {
api = _api;

View File

@@ -327,7 +327,7 @@ export interface WebSocketCustomHandler {
export type PluginReader = (name: string) => Buffer;
export interface PluginRemoteAttachOptions {
createMediaManager?: (systemManager: SystemManager) => Promise<MediaManager>;
createMediaManager?: (systemManager: SystemManager, deviceManager: DeviceManager) => Promise<MediaManager>;
getServicePort?: (name: string, ...args: any[]) => Promise<number>;
getDeviceConsole?: (nativeId?: ScryptedNativeId) => Console;
getPluginConsole?: () => Console;
@@ -351,7 +351,7 @@ export function attachPluginRemote(peer: RpcPeer, options?: PluginRemoteAttachOp
const systemManager = new SystemManagerImpl();
const deviceManager = new DeviceManagerImpl(systemManager, getDeviceConsole, getMixinConsole);
const endpointManager = new EndpointManagerImpl();
const mediaManager = await api.getMediaManager() || await createMediaManager(systemManager);
const mediaManager = await api.getMediaManager() || await createMediaManager(systemManager, deviceManager);
peer.params['mediaManager'] = mediaManager;
const ioSockets: { [id: string]: WebSocketConnectCallbacks } = {};