diff --git a/server/package-lock.json b/server/package-lock.json index a5b70801f..f2f4689b0 100644 --- a/server/package-lock.json +++ b/server/package-lock.json @@ -1,12 +1,12 @@ { "name": "@scrypted/server", - "version": "0.6.24", + "version": "0.6.26", "lockfileVersion": 2, "requires": true, "packages": { "": { "name": "@scrypted/server", - "version": "0.6.24", + "version": "0.6.26", "license": "ISC", "dependencies": { "@ffmpeg-installer/ffmpeg": "^1.1.0", diff --git a/server/python/plugin_remote.py b/server/python/plugin_remote.py index f1811a529..2b10b7e9a 100644 --- a/server/python/plugin_remote.py +++ b/server/python/plugin_remote.py @@ -47,7 +47,7 @@ class SystemManager(scrypted_python.scrypted_sdk.types.SystemManager): return await self.api.getComponent(id) -class MediaObjectRemote(scrypted_python.scrypted_sdk.types.MediaObject): +class MediaObject(scrypted_python.scrypted_sdk.types.MediaObject): def __init__(self, data, mimeType, sourceId): self.mimeType = mimeType self.data = data @@ -93,7 +93,7 @@ class MediaManager: async def createMediaObject(self, data: Any, mimeType: str, options: scrypted_python.scrypted_sdk.types.MediaObjectOptions = None) -> scrypted_python.scrypted_sdk.types.MediaObject: # return await self.createMediaObject(data, mimetypes, options) - return MediaObjectRemote(data, mimeType, options.get('sourceId', None) if options else None) + return MediaObject(data, mimeType, options.get('sourceId', None) if options else None) async def createMediaObjectFromUrl(self, data: str, options: scrypted_python.scrypted_sdk.types. MediaObjectOptions = None) -> scrypted_python.scrypted_sdk.types.MediaObject: return await self.mediaManager.createMediaObjectFromUrl(data, options) diff --git a/server/src/plugin/media.ts b/server/src/plugin/media.ts index c11c005fd..8e7289e7f 100644 --- a/server/src/plugin/media.ts +++ b/server/src/plugin/media.ts @@ -1,4 +1,4 @@ -import { BufferConverter, DeviceManager, FFmpegInput, MediaManager, MediaObject, MediaObjectOptions, MediaStreamUrl, ScryptedDevice, ScryptedInterface, ScryptedInterfaceProperty, ScryptedMimeTypes, ScryptedNativeId, SystemDeviceState, SystemManager } from "@scrypted/types"; +import { BufferConverter, DeviceManager, FFmpegInput, MediaManager, MediaObject as MediaObjectInterface, MediaObjectOptions, MediaStreamUrl, ScryptedDevice, ScryptedInterface, ScryptedInterfaceProperty, ScryptedMimeTypes, ScryptedNativeId, SystemDeviceState, SystemManager } from "@scrypted/types"; import axios from 'axios'; import pathToFfmpeg from 'ffmpeg-static'; import fs from 'fs'; @@ -11,6 +11,21 @@ import path from 'path'; import MimeType from 'whatwg-mimetype'; import { MediaObjectRemote } from "./plugin-api"; +class MediaObject implements MediaObjectRemote { + __proxy_props: any; + + constructor(public mimeType: string, public sourceId: string, public data: any) { + this.__proxy_props = { + mimeType, + sourceId, + } + } + + async getData(): Promise { + return Promise.resolve(this.data); + } +} + function typeMatches(target: string, candidate: string): boolean { // candidate will accept anything if (candidate === '*' || target === '*') @@ -164,7 +179,7 @@ export abstract class MediaManagerBase implements MediaManager { this.extraConverters = []; } - async convertMediaObjectToJSON(mediaObject: MediaObject, toMimeType: string): Promise { + async convertMediaObjectToJSON(mediaObject: MediaObjectInterface, toMimeType: string): Promise { const buffer = await this.convertMediaObjectToBuffer(mediaObject, toMimeType); return JSON.parse(buffer.toString()); } @@ -231,7 +246,7 @@ export abstract class MediaManagerBase implements MediaManager { return converters; } - ensureMediaObjectRemote(mediaObject: string | MediaObject): MediaObjectRemote { + ensureMediaObjectRemote(mediaObject: string | MediaObjectInterface): MediaObjectRemote { if (typeof mediaObject === 'string') { const mime = mimeType.getType(mediaObject); return this.createMediaObjectRemote(mediaObject, mime); @@ -239,29 +254,29 @@ export abstract class MediaManagerBase implements MediaManager { return mediaObject as MediaObjectRemote; } - async convertMediaObject(mediaObject: MediaObject, toMimeType: string): Promise { + async convertMediaObject(mediaObject: MediaObjectInterface, toMimeType: string): Promise { const converted = await this.convert(this.getConverters(), this.ensureMediaObjectRemote(mediaObject), toMimeType); return converted.data; } - async convertMediaObjectToInsecureLocalUrl(mediaObject: string | MediaObject, toMimeType: string): Promise { + async convertMediaObjectToInsecureLocalUrl(mediaObject: string | MediaObjectInterface, toMimeType: string): Promise { const intermediate = await this.convert(this.getConverters(), this.ensureMediaObjectRemote(mediaObject), toMimeType); const converted = this.createMediaObjectRemote(intermediate.data, intermediate.mimeType); const url = await this.convert(this.getConverters(), converted, ScryptedMimeTypes.InsecureLocalUrl); return url.data.toString(); } - async convertMediaObjectToBuffer(mediaObject: MediaObject, toMimeType: string): Promise { + async convertMediaObjectToBuffer(mediaObject: MediaObjectInterface, toMimeType: string): Promise { const intermediate = await this.convert(this.getConverters(), this.ensureMediaObjectRemote(mediaObject), toMimeType); return intermediate.data as Buffer; } - async convertMediaObjectToLocalUrl(mediaObject: string | MediaObject, toMimeType: string): Promise { + async convertMediaObjectToLocalUrl(mediaObject: string | MediaObjectInterface, toMimeType: string): Promise { const intermediate = await this.convert(this.getConverters(), this.ensureMediaObjectRemote(mediaObject), toMimeType); const converted = this.createMediaObjectRemote(intermediate.data, intermediate.mimeType); const url = await this.convert(this.getConverters(), converted, ScryptedMimeTypes.LocalUrl); return url.data.toString(); } - async convertMediaObjectToUrl(mediaObject: string | MediaObject, toMimeType: string): Promise { + async convertMediaObjectToUrl(mediaObject: string | MediaObjectInterface, toMimeType: string): Promise { const intermediate = await this.convert(this.getConverters(), this.ensureMediaObjectRemote(mediaObject), toMimeType); const converted = this.createMediaObjectRemote(intermediate.data, intermediate.mimeType); const url = await this.convert(this.getConverters(), converted, ScryptedMimeTypes.Url); @@ -280,26 +295,15 @@ export abstract class MediaManagerBase implements MediaManager { 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 { - return Promise.resolve(data); - } - } - return new MediaObjectImpl(); + return new MediaObject(mimeType, sourceId, data); } - async createFFmpegMediaObject(ffMpegInput: FFmpegInput, options?: MediaObjectOptions): Promise { + async createFFmpegMediaObject(ffMpegInput: FFmpegInput, options?: MediaObjectOptions): Promise { return this.createMediaObjectRemote(Buffer.from(JSON.stringify(ffMpegInput)), ScryptedMimeTypes.FFmpegInput, options); } - async createMediaObjectFromUrl(data: string, options?: MediaObjectOptions): Promise { + async createMediaObjectFromUrl(data: string, options?: MediaObjectOptions): Promise { const url = new URL(data); const scheme = url.protocol.slice(0, -1); const mimeType = ScryptedMimeTypes.SchemePrefix + scheme; @@ -320,7 +324,7 @@ export abstract class MediaManagerBase implements MediaManager { return new MediaObjectImpl(); } - async createMediaObject(data: any, mimeType: string, options?: MediaObjectOptions): Promise { + async createMediaObject(data: any, mimeType: string, options?: MediaObjectOptions): Promise { return this.createMediaObjectRemote(data, mimeType, options); } @@ -423,7 +427,7 @@ export abstract class MediaManagerBase implements MediaManager { } if (converter.toMimeType === ScryptedMimeTypes.MediaObject) { - const mo = await converter.convert(value, valueMime.essence, toMimeType, { sourceId }) as MediaObject; + const mo = await converter.convert(value, valueMime.essence, toMimeType, { sourceId }) as MediaObjectInterface; const found = await this.convertMediaObject(mo, toMimeType); return { data: found, diff --git a/server/src/rpc.ts b/server/src/rpc.ts index 03678139f..8f89f526c 100644 --- a/server/src/rpc.ts +++ b/server/src/rpc.ts @@ -237,9 +237,10 @@ export class RpcPeer { // @ts-ignore finalizers = new FinalizationRegistry(entry => this.finalize(entry as LocalProxiedEntry)); nameDeserializerMap = new Map(); + onSerialization = new Map void>(); constructorSerializerMap = new Map(); transportSafeArgumentTypes = RpcPeer.getDefaultTransportSafeArgumentTypes(); - killed: Promise; + killed: Promise; killedDeferred: Deferred; tags: any = {}; @@ -298,10 +299,9 @@ export class RpcPeer { ]); constructor(public selfName: string, public peerName: string, public send: (message: RpcMessage, reject?: (e: Error) => void, serializationContext?: any) => void) { - this.killed = new Promise((resolve, reject) => { + this.killed = new Promise((resolve, reject) => { this.killedDeferred = { resolve, reject }; - }); - this.killed.catch(() => { }); + }).catch(e => e.message || 'Unknown Error'); } createPendingResult(cb: (id: string, reject: (e: Error) => void) => void): Promise { @@ -322,6 +322,8 @@ export class RpcPeer { } kill(message?: string) { + if (Object.isFrozen(this.pendingResults)) + return; const error = new RPCResultError(this, message || 'peer was killed'); this.killedDeferred.reject(error); for (const result of Object.values(this.pendingResults)) { @@ -444,6 +446,7 @@ export class RpcPeer { } let __remote_constructor_name = value.__proxy_constructor || value.constructor?.name?.toString(); + this.onSerialization.get(__remote_constructor_name)?.(value); let proxiedEntry = this.localProxied.get(value); if (proxiedEntry) {